Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"at": "2026-10-12T18:39:09.087Z",
|
||||||
|
"task": 222,
|
||||||
|
"problems": [],
|
||||||
|
"title": "Q116 stack: Console shows CLIProxyAPI usage per account, read-only (decision 114)",
|
||||||
|
"status": 201,
|
||||||
|
"id": 45,
|
||||||
|
"readBack": 200,
|
||||||
|
"found": true,
|
||||||
|
"author": "bot-mosaic-stack-sage"
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Sage, 2026-10-12: Mos sage-0093. Comments on #222 (Q116 stack) with the design input from ops-01's #221 plan.
|
||||||
|
// Sage's own key (bot user 13). The sync key reads the task title first and the comment back. No state
|
||||||
|
// change to the task. Prints no secret.
|
||||||
|
// Usage: node comment-222-input.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON
|
||||||
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||||
|
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
|
||||||
|
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
|
||||||
|
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
|
||||||
|
const TASK = 222;
|
||||||
|
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
||||||
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
const p = (...xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(SIG)}</p>`;
|
||||||
|
const TEXT = "Design input for the #222 brief, from Mos sage-0093 and ops-01's #221 plan (Vikunja comment c44 on #221). The CLIProxyAPI GUI on dragon-lin already moves the 60 s usage queue into usage-records/usage.db, a live SQLite file of about 33k rows. #222 reads that file read-only and needs no management key. Don't add a second management-API reader: it would need the key and would race the GUI for the queue. #221 is a separate chisel tunnel at /cliproxy that forwards only the management paths, with a CI test that /v1 never passes; Authentik is unchanged. Sage checks the file path and schema when writing the brief. Order unchanged: after #221 and row 77 (lead decision 115).";
|
||||||
|
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
|
||||||
|
async function api(method, path, body, auth) {
|
||||||
|
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
|
||||||
|
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
||||||
|
return { status: r.status, json };
|
||||||
|
}
|
||||||
|
const list = (j) => j?.items ?? (Array.isArray(j) ? j : []);
|
||||||
|
const rec = { at: new Date().toISOString(), task: TASK, problems: [] };
|
||||||
|
const save = () => {
|
||||||
|
const out = JSON.stringify(rec, null, 1) + "\n";
|
||||||
|
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
|
||||||
|
writeFileSync(OUT, out);
|
||||||
|
};
|
||||||
|
const task = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
|
||||||
|
rec.title = task.json?.title ?? null;
|
||||||
|
if (task.status !== 200 || task.json?.project_id !== 19) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}`);
|
||||||
|
writeFileSync(OUT, "", { flag: "wx" });
|
||||||
|
const r = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: p(TEXT) }, key);
|
||||||
|
Object.assign(rec, { status: r.status, id: r.json?.id ?? null });
|
||||||
|
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) rec.problems.push(`comment: ${r.status} ${r.json?.message ?? ""}`);
|
||||||
|
else {
|
||||||
|
const back = await api("GET", `/api/v2/tasks/${TASK}/comments?per_page=50`, undefined, sync);
|
||||||
|
const found = list(back.json).find((c) => c.id === r.json.id);
|
||||||
|
Object.assign(rec, { readBack: back.status, found: Boolean(found), author: found?.author?.username ?? null });
|
||||||
|
if (back.status !== 200 || !found) rec.problems.push(`read-back: ${back.status}`);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||||
|
if (rec.problems.length) process.exit(2);
|
||||||
Reference in New Issue
Block a user