@task0/cli 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +1023 -428
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -58,14 +58,14 @@ function generateObjectId(type) {
58
58
  function isTaskObjectId(id) {
59
59
  return TASK_ID_RE.test(id);
60
60
  }
61
- var RESOURCE_PREFIXES, PREFIX_TO_TYPE, BASE62, ID_LEN, BASE, MOD, TASK_ID_RE, PREFIXES_PATTERN, OBJECT_ID_RE;
61
+ var RESOURCE_PREFIXES, LEGACY_PREFIX_TO_TYPE, PREFIX_TO_TYPE, BASE62, ID_LEN, BASE, MOD, TASK_ID_RE, PREFIXES_PATTERN, OBJECT_ID_RE;
62
62
  var init_object_id = __esm({
63
63
  "../../packages/shared/dist/object-id.js"() {
64
64
  "use strict";
65
65
  RESOURCE_PREFIXES = {
66
66
  task: "tsk",
67
67
  project: "prj",
68
- runtime: "rt",
68
+ agent_run: "run",
69
69
  issue: "iss",
70
70
  inbox: "ibx",
71
71
  inbox_note: "note",
@@ -81,7 +81,15 @@ var init_object_id = __esm({
81
81
  task_kr_link: "krl",
82
82
  task_comment: "cmt",
83
83
  agent: "agt",
84
- daemon: "dmn"
84
+ daemon: "dmn",
85
+ user: "usr",
86
+ runtime_profile: "rtp",
87
+ api_token: "apit",
88
+ automation: "aut",
89
+ automation_run: "autr"
90
+ };
91
+ LEGACY_PREFIX_TO_TYPE = {
92
+ rt: "agent_run"
85
93
  };
86
94
  PREFIX_TO_TYPE = Object.fromEntries(Object.entries(RESOURCE_PREFIXES).map(([k, v]) => [v, k]));
87
95
  BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -89,7 +97,10 @@ var init_object_id = __esm({
89
97
  BASE = 62n;
90
98
  MOD = BASE ** BigInt(ID_LEN);
91
99
  TASK_ID_RE = /^tsk_[A-Za-z0-9]{5,12}$/;
92
- PREFIXES_PATTERN = Object.values(RESOURCE_PREFIXES).join("|");
100
+ PREFIXES_PATTERN = [
101
+ ...Object.values(RESOURCE_PREFIXES),
102
+ ...Object.keys(LEGACY_PREFIX_TO_TYPE)
103
+ ].join("|");
93
104
  OBJECT_ID_RE = new RegExp(`\\b(?:${PREFIXES_PATTERN})_[A-Za-z0-9]{5,12}\\b`, "g");
94
105
  }
95
106
  });
@@ -444,8 +455,14 @@ async function updateTaskWorkflow(taskYml, patch) {
444
455
  const raw = readTaskYaml(taskYml);
445
456
  const current = raw.workflow || {};
446
457
  const merged = { ...current, ...patch, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
447
- if (patch.runtimes) {
448
- merged.runtimes = { ...current.runtimes || {}, ...patch.runtimes };
458
+ if (patch.runtimes || patch.agent_runs || current.runtimes || current.agent_runs) {
459
+ merged.agent_runs = {
460
+ ...current.runtimes || {},
461
+ ...current.agent_runs || {},
462
+ ...patch.runtimes || {},
463
+ ...patch.agent_runs || {}
464
+ };
465
+ delete merged.runtimes;
449
466
  }
450
467
  if (patch.decisions) {
451
468
  const next = { ...current.decisions || {} };
@@ -903,8 +920,8 @@ var init_task_state2 = __esm({
903
920
  // src/main.ts
904
921
  import { readFileSync } from "fs";
905
922
  import { fileURLToPath as fileURLToPath2 } from "url";
906
- import path24 from "path";
907
- import { Command as Command22 } from "commander";
923
+ import path25 from "path";
924
+ import { Command as Command23 } from "commander";
908
925
 
909
926
  // src/commands/source.ts
910
927
  import { Command } from "commander";
@@ -1122,7 +1139,7 @@ async function request(method, pathname, body) {
1122
1139
  });
1123
1140
  } catch (error2) {
1124
1141
  const err = new Error(
1125
- `Cannot reach task0 API at ${apiBaseUrl()}. Start it with \`task0 serve\` (or \`task0 ui\` for dev).`
1142
+ `Cannot reach task0 API at ${apiBaseUrl()}. Start the task0-server binary (download from GitHub Releases) or repoint TASK0_API_URL.`
1126
1143
  );
1127
1144
  err.cause = error2;
1128
1145
  throw err;
@@ -1144,11 +1161,11 @@ async function request(method, pathname, body) {
1144
1161
  return parsed;
1145
1162
  }
1146
1163
  var api = {
1147
- get: (path25) => request("GET", path25),
1148
- post: (path25, body) => request("POST", path25, body ?? {}),
1149
- put: (path25, body) => request("PUT", path25, body ?? {}),
1150
- patch: (path25, body) => request("PATCH", path25, body ?? {}),
1151
- del: (path25) => request("DELETE", path25)
1164
+ get: (path26) => request("GET", path26),
1165
+ post: (path26, body) => request("POST", path26, body ?? {}),
1166
+ put: (path26, body) => request("PUT", path26, body ?? {}),
1167
+ patch: (path26, body) => request("PATCH", path26, body ?? {}),
1168
+ del: (path26) => request("DELETE", path26)
1152
1169
  };
1153
1170
 
1154
1171
  // src/commands/task/triage.ts
@@ -1157,23 +1174,23 @@ import chalk3 from "chalk";
1157
1174
  import fs10 from "fs";
1158
1175
  import path10 from "path";
1159
1176
 
1160
- // src/core/runtime-wait.ts
1161
- async function getRuntime(id) {
1162
- return api.get(`/api/runtimes/${encodeURIComponent(id)}`);
1177
+ // src/core/agent-run-wait.ts
1178
+ async function getAgentRun(id) {
1179
+ return api.get(`/api/agent-runs/${encodeURIComponent(id)}`);
1163
1180
  }
1164
- async function waitForRuntime(id, opts = {}) {
1181
+ async function waitForAgentRun(id, opts = {}) {
1165
1182
  const interval = opts.intervalMs ?? 2e3;
1166
1183
  const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : null;
1167
1184
  let lastStatus = "";
1168
1185
  while (true) {
1169
- const snap = await getRuntime(id);
1186
+ const snap = await getAgentRun(id);
1170
1187
  if (snap.status !== lastStatus) {
1171
1188
  lastStatus = snap.status;
1172
1189
  opts.onTick?.(snap);
1173
1190
  }
1174
1191
  if (snap.status === "done" || snap.status === "error") return snap;
1175
1192
  if (deadline && Date.now() > deadline) {
1176
- throw new Error(`Timeout waiting for runtime ${id} (last status: ${snap.status})`);
1193
+ throw new Error(`Timeout waiting for agent-run ${id} (last status: ${snap.status})`);
1177
1194
  }
1178
1195
  await new Promise((r) => setTimeout(r, interval));
1179
1196
  }
@@ -1251,11 +1268,11 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1251
1268
  `/api/agents/${encodeURIComponent(opts.agent)}/run`,
1252
1269
  body
1253
1270
  );
1254
- const runtimeId = resp.runtime.id;
1255
- if (!opts.json) console.log(chalk3.green(`triage runtime ${runtimeId}`));
1256
- await updateWorkflow(loc.taskYml, { runtimes: { triage: runtimeId } });
1271
+ const agentRunId = resp.runtime.id;
1272
+ if (!opts.json) console.log(chalk3.green(`triage runtime ${agentRunId}`));
1273
+ await updateWorkflow(loc.taskYml, { agent_runs: { triage: agentRunId } });
1257
1274
  if (opts.wait) {
1258
- const final = await waitForRuntime(runtimeId, {
1275
+ const final = await waitForAgentRun(agentRunId, {
1259
1276
  onTick: (s) => {
1260
1277
  if (!opts.json) console.log(chalk3.dim(`[triage] ${s.status}${s.phase ? " " + s.phase : ""}`));
1261
1278
  }
@@ -1288,7 +1305,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1288
1305
  console.log(chalk3.dim(`blockingQuestionCount: ${blockingQuestionCount}`));
1289
1306
  } else {
1290
1307
  console.log(JSON.stringify({
1291
- runtimeId,
1308
+ agentRunId,
1292
1309
  issueOverview: "ISSUE.md",
1293
1310
  issueFiles,
1294
1311
  blockingQuestionCount
@@ -1296,7 +1313,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1296
1313
  }
1297
1314
  process.exit(blockingQuestionCount > 0 ? 2 : 0);
1298
1315
  }
1299
- if (opts.json) console.log(JSON.stringify({ runtimeId }, null, 2));
1316
+ if (opts.json) console.log(JSON.stringify({ agentRunId }, null, 2));
1300
1317
  } catch (err) {
1301
1318
  console.error(chalk3.red(err.message));
1302
1319
  process.exit(1);
@@ -1361,24 +1378,24 @@ var exec = new Command4("exec").description("Execute a plan against the task (cw
1361
1378
  `/api/agents/${encodeURIComponent(opts.agent)}/run`,
1362
1379
  body
1363
1380
  );
1364
- const runtimeId = resp.runtime.id;
1365
- if (!opts.json) console.log(chalk4.green(`exec runtime ${runtimeId} (plan: ${planFile})`));
1366
- await updateWorkflow(loc.taskYml, { phase: "executing", runtimes: { exec: runtimeId } });
1381
+ const agentRunId = resp.runtime.id;
1382
+ if (!opts.json) console.log(chalk4.green(`exec runtime ${agentRunId} (plan: ${planFile})`));
1383
+ await updateWorkflow(loc.taskYml, { phase: "executing", agent_runs: { exec: agentRunId } });
1367
1384
  if (opts.wait) {
1368
- const final = await waitForRuntime(runtimeId, {
1385
+ const final = await waitForAgentRun(agentRunId, {
1369
1386
  onTick: (s) => {
1370
1387
  if (!opts.json) console.log(chalk4.dim(`[exec] ${s.status}${s.phase ? " " + s.phase : ""}`));
1371
1388
  }
1372
1389
  });
1373
1390
  if (final.status === "done") {
1374
1391
  if (!opts.json) console.log(chalk4.green("exec completed"));
1375
- if (opts.json) console.log(JSON.stringify({ runtimeId, planFile, result: final.result }, null, 2));
1392
+ if (opts.json) console.log(JSON.stringify({ agentRunId, planFile, result: final.result }, null, 2));
1376
1393
  return;
1377
1394
  }
1378
1395
  console.error(chalk4.red(`exec failed: ${final.error || "unknown"}`));
1379
1396
  process.exit(1);
1380
1397
  }
1381
- if (opts.json) console.log(JSON.stringify({ runtimeId, planFile }, null, 2));
1398
+ if (opts.json) console.log(JSON.stringify({ agentRunId, planFile }, null, 2));
1382
1399
  } catch (err) {
1383
1400
  console.error(chalk4.red(err.message));
1384
1401
  process.exit(1);
@@ -1435,13 +1452,16 @@ function preview(body, width = 60) {
1435
1452
  const single = body.replace(/\s+/g, " ").trim();
1436
1453
  return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
1437
1454
  }
1438
- function printRow(c) {
1455
+ function printRow(c, indent = "") {
1439
1456
  const id = chalk6.cyan(c.id.padEnd(14));
1440
1457
  const author = chalk6.magenta(c.author.padEnd(12));
1441
- console.log(`${id} ${author} ${preview(c.body)} ${chalk6.dim(c.updated_at)}`);
1458
+ console.log(`${indent}${id} ${author} ${preview(c.body)} ${chalk6.dim(c.updated_at)}`);
1442
1459
  }
1443
1460
  function printDetail(c) {
1444
1461
  console.log(`${chalk6.bold("id:")} ${c.id}`);
1462
+ if ("parent_comment_id" in c) {
1463
+ console.log(`${chalk6.bold("parent:")} ${c.parent_comment_id}`);
1464
+ }
1445
1465
  console.log(`${chalk6.bold("author:")} ${c.author}`);
1446
1466
  console.log(`${chalk6.bold("created:")} ${c.created_at}`);
1447
1467
  console.log(`${chalk6.bold("updated:")} ${c.updated_at}`);
@@ -1469,17 +1489,22 @@ comment.command("list <taskId>").description("List comments on a task (taskId is
1469
1489
  console.log(chalk6.dim("No comments."));
1470
1490
  return;
1471
1491
  }
1472
- for (const c of comments) printRow(c);
1492
+ for (const c of comments) {
1493
+ printRow(c);
1494
+ for (const reply of c.replies ?? []) {
1495
+ printRow(reply, chalk6.dim(" \u21B3 "));
1496
+ }
1497
+ }
1473
1498
  } catch (err) {
1474
1499
  fail(err);
1475
1500
  }
1476
1501
  });
1477
- comment.command("add <taskId>").description("Add a comment to a task").option("--body <text>", "Comment body (markdown)").option("--file <path>", "Read body from file (- for stdin)").option("--author <name>", "Author label (default: user)", "user").option("--json", "Output JSON").action(async (taskId, opts) => {
1502
+ comment.command("add <taskId>").description("Add a comment to a task").option("--body <text>", "Comment body (markdown)").option("--file <path>", "Read body from file (- for stdin)").option("--author <name>", "Author label (default: user)", "user").option("--reply-to <cmtId>", "Create a second-level reply under a top-level comment").option("--json", "Output JSON").action(async (taskId, opts) => {
1478
1503
  try {
1479
1504
  const body = readBodyFromOpts(opts);
1480
1505
  const { comment: created } = await api.post(
1481
1506
  `/api/tasks/${encodeURIComponent(taskId)}/comments`,
1482
- { body, author: opts.author }
1507
+ { body, author: opts.author, parent_comment_id: opts.replyTo }
1483
1508
  );
1484
1509
  if (opts.json) {
1485
1510
  console.log(JSON.stringify({ comment: created }, null, 2));
@@ -1814,61 +1839,9 @@ var ui = new Command9("ui").description("Launch the dashboard").option("-p, --po
1814
1839
  process.on("SIGTERM", () => child.kill("SIGTERM"));
1815
1840
  });
1816
1841
 
1817
- // src/commands/serve.ts
1842
+ // src/commands/models.ts
1818
1843
  import { Command as Command10 } from "commander";
1819
- import { spawn as spawn2 } from "child_process";
1820
- import fs15 from "fs";
1821
- import path14 from "path";
1822
1844
  import chalk10 from "chalk";
1823
- var SERVER_DIR = path14.resolve(
1824
- import.meta.dirname,
1825
- "..",
1826
- "..",
1827
- "..",
1828
- "server"
1829
- );
1830
- var HTTP_ENTRY = path14.join(SERVER_DIR, "src", "main.ts");
1831
- var CLI_TSX = path14.resolve(
1832
- import.meta.dirname,
1833
- "..",
1834
- "..",
1835
- "node_modules",
1836
- ".bin",
1837
- "tsx"
1838
- );
1839
- var serve = new Command10("serve").description("Run the headless task0 API server").option("-p, --port <port>", "Port number", "4318").option("-H, --host <address>", "Bind address", "127.0.0.1").action((opts) => {
1840
- if (!fs15.existsSync(HTTP_ENTRY)) {
1841
- console.error(chalk10.red(`Server entry not found: ${HTTP_ENTRY}`));
1842
- process.exit(1);
1843
- }
1844
- if (!fs15.existsSync(CLI_TSX)) {
1845
- console.error(chalk10.red(`tsx not found at ${CLI_TSX}. Run \`pnpm install\` in the CLI package.`));
1846
- process.exit(1);
1847
- }
1848
- console.log(chalk10.green(`Starting task0 API on http://${opts.host}:${opts.port}`));
1849
- const child = spawn2(CLI_TSX, [HTTP_ENTRY], {
1850
- cwd: SERVER_DIR,
1851
- stdio: "inherit",
1852
- env: {
1853
- ...process.env,
1854
- TASK0_API_PORT: opts.port,
1855
- TASK0_API_HOST: opts.host
1856
- }
1857
- });
1858
- child.on("error", (err) => {
1859
- console.error(chalk10.red(`Failed to start server: ${err.message}`));
1860
- process.exit(1);
1861
- });
1862
- child.on("exit", (code) => {
1863
- process.exit(code ?? 0);
1864
- });
1865
- process.on("SIGINT", () => child.kill("SIGINT"));
1866
- process.on("SIGTERM", () => child.kill("SIGTERM"));
1867
- });
1868
-
1869
- // src/commands/models.ts
1870
- import { Command as Command11 } from "commander";
1871
- import chalk11 from "chalk";
1872
1845
 
1873
1846
  // ../../packages/shared/dist/types/agent.js
1874
1847
  var AGENT_KINDS = ["coding", "llm_api", "workflow"];
@@ -1880,8 +1853,8 @@ function isCodingRuntimeAgent(value) {
1880
1853
  }
1881
1854
 
1882
1855
  // ../../packages/shared/dist/types/runtime.js
1883
- var RUNTIME_STATUS_VALUES = ["starting", "running", "done", "error"];
1884
- var RUNTIME_STATUS_SET = new Set(RUNTIME_STATUS_VALUES);
1856
+ var AGENT_RUN_STATUS_VALUES = ["starting", "running", "done", "error"];
1857
+ var AGENT_RUN_STATUS_SET = new Set(AGENT_RUN_STATUS_VALUES);
1885
1858
  var RUNTIME_AGENTS = CODING_RUNTIME_AGENTS;
1886
1859
  var isRuntimeAgent = isCodingRuntimeAgent;
1887
1860
  var AGENT_MODEL_DEFAULTS = {
@@ -2066,31 +2039,31 @@ function refreshAgentModels(agent2) {
2066
2039
  function parseAgent(raw) {
2067
2040
  const trimmed = raw.trim();
2068
2041
  if (!isRuntimeAgent(trimmed)) {
2069
- console.error(chalk11.red(`Invalid agent "${raw}" (expected one of ${RUNTIME_AGENTS.join(", ")})`));
2042
+ console.error(chalk10.red(`Invalid agent "${raw}" (expected one of ${RUNTIME_AGENTS.join(", ")})`));
2070
2043
  process.exit(1);
2071
2044
  }
2072
2045
  return trimmed;
2073
2046
  }
2074
- var models = new Command11("models").description("Manage cached agent model lists");
2047
+ var models = new Command10("models").description("Manage cached agent model lists");
2075
2048
  models.command("refresh").description("Refresh cached agent model lists into local config").option("-a, --agent <agent>", "Refresh a single agent only (claude-code, codex, cursor)").action((opts) => {
2076
2049
  const agent2 = opts.agent ? parseAgent(opts.agent) : void 0;
2077
2050
  try {
2078
2051
  const summary = refreshAgentModels(agent2);
2079
2052
  for (const result of summary.results) {
2080
- const source2 = result.source === "command" ? chalk11.cyan("command") : chalk11.dim("builtin");
2081
- console.log(chalk11.green(`Refreshed ${result.agent}`) + ` ${source2} ${result.modelCount} models`);
2053
+ const source2 = result.source === "command" ? chalk10.cyan("command") : chalk10.dim("builtin");
2054
+ console.log(chalk10.green(`Refreshed ${result.agent}`) + ` ${source2} ${result.modelCount} models`);
2082
2055
  if (result.fetchCommand) {
2083
- console.log(chalk11.dim(` ${result.fetchCommand}`));
2056
+ console.log(chalk10.dim(` ${result.fetchCommand}`));
2084
2057
  }
2085
2058
  }
2086
2059
  for (const failure of summary.failures) {
2087
- console.error(chalk11.red(`Failed ${failure.agent}: ${failure.error}`));
2060
+ console.error(chalk10.red(`Failed ${failure.agent}: ${failure.error}`));
2088
2061
  }
2089
2062
  if (summary.failures.length > 0) {
2090
2063
  process.exit(1);
2091
2064
  }
2092
2065
  } catch (error2) {
2093
- console.error(chalk11.red(error2 instanceof Error ? error2.message : String(error2)));
2066
+ console.error(chalk10.red(error2 instanceof Error ? error2.message : String(error2)));
2094
2067
  process.exit(1);
2095
2068
  }
2096
2069
  });
@@ -2114,70 +2087,73 @@ models.command("default <agent>").description("Get or set default model / effort
2114
2087
  if (wroteModel || wroteEffort) {
2115
2088
  saveConfig(config);
2116
2089
  }
2117
- console.log(chalk11.green(agent2));
2118
- console.log(` model: ${entry.defaultModel ?? chalk11.dim("(unset)")}`);
2119
- console.log(` effort: ${entry.defaultEffort ?? chalk11.dim("(unset)")}`);
2090
+ console.log(chalk10.green(agent2));
2091
+ console.log(` model: ${entry.defaultModel ?? chalk10.dim("(unset)")}`);
2092
+ console.log(` effort: ${entry.defaultEffort ?? chalk10.dim("(unset)")}`);
2120
2093
  });
2121
2094
 
2122
- // src/commands/runtime.ts
2123
- import { Command as Command12 } from "commander";
2124
- import chalk12 from "chalk";
2125
- import { spawn as spawn3 } from "child_process";
2126
- var runtime = new Command12("runtime").description("Inspect and control runtimes");
2127
- runtime.command("list").description("List active runtimes").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
2095
+ // src/commands/agent-run.ts
2096
+ import fs15 from "fs";
2097
+ import path14 from "path";
2098
+ import { Command as Command11 } from "commander";
2099
+ import chalk11 from "chalk";
2100
+ import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
2101
+ import yaml6 from "js-yaml";
2102
+ var agentRun = new Command11("agent-run").description("Inspect and control agent runs");
2103
+ agentRun.command("list").description("List active agent runs").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
2128
2104
  try {
2129
- const { runtimes } = await api.get("/api/runtimes");
2105
+ const { agent_runs: runtimes } = await api.get("/api/agent-runs");
2130
2106
  const filtered = opts.task ? runtimes.filter((r) => r.taskId === opts.task) : runtimes;
2131
2107
  if (opts.json) {
2132
2108
  console.log(JSON.stringify(filtered, null, 2));
2133
2109
  return;
2134
2110
  }
2135
2111
  if (filtered.length === 0) {
2136
- console.log(chalk12.dim("No runtimes."));
2112
+ console.log(chalk11.dim("No agent runs."));
2137
2113
  return;
2138
2114
  }
2139
2115
  const objectIdWidth = Math.max(...filtered.map((r) => (r.objectId || "").length), 8);
2140
2116
  for (const r of filtered) {
2141
- const statusColor2 = r.status === "done" ? chalk12.green : r.status === "error" ? chalk12.red : r.status === "running" ? chalk12.cyan : chalk12.dim;
2142
- const objectId = chalk12.cyan((r.objectId || "-").padEnd(objectIdWidth));
2143
- console.log(`${statusColor2(r.status.padEnd(8))} ${objectId} ${chalk12.dim(r.type.padEnd(12))} ${r.taskId.padEnd(30)} ${chalk12.dim(r.id)}`);
2117
+ const statusColor2 = r.status === "done" ? chalk11.green : r.status === "error" ? chalk11.red : r.status === "running" ? chalk11.cyan : chalk11.dim;
2118
+ const objectId = chalk11.cyan((r.objectId || "-").padEnd(objectIdWidth));
2119
+ console.log(`${statusColor2(r.status.padEnd(8))} ${objectId} ${chalk11.dim(r.type.padEnd(12))} ${r.taskId.padEnd(30)} ${chalk11.dim(r.id)}`);
2144
2120
  }
2145
2121
  } catch (err) {
2146
- console.error(chalk12.red(err.message));
2122
+ console.error(chalk11.red(err.message));
2147
2123
  process.exit(1);
2148
2124
  }
2149
2125
  });
2150
- runtime.command("show <id>").description("Show runtime details").option("--json", "Output JSON").action(async (id, opts) => {
2126
+ agentRun.command("show <id>").description("Show agent run details").option("--json", "Output JSON").action(async (id, opts) => {
2151
2127
  try {
2152
- const r = await getRuntime(id);
2128
+ const r = await getAgentRun(id);
2153
2129
  if (opts.json) {
2154
2130
  console.log(JSON.stringify(r, null, 2));
2155
2131
  return;
2156
2132
  }
2157
- console.log(`${chalk12.bold("id:")} ${r.id}`);
2158
- if (r.objectId) console.log(`${chalk12.bold("object_id:")} ${r.objectId}`);
2159
- console.log(`${chalk12.bold("type:")} ${r.type}`);
2160
- console.log(`${chalk12.bold("agent:")} ${r.agent}`);
2161
- console.log(`${chalk12.bold("task:")} ${r.taskId}`);
2162
- console.log(`${chalk12.bold("status:")} ${r.status}`);
2163
- if (r.phase) console.log(`${chalk12.bold("phase:")} ${r.phase}`);
2164
- if (r.error) console.log(`${chalk12.red("error:")} ${r.error}`);
2165
- console.log(`${chalk12.bold("tmux:")} ${r.tmuxSession} (alive: ${r.sessionAlive ?? "unknown"})`);
2133
+ console.log(`${chalk11.bold("id:")} ${r.id}`);
2134
+ if (r.objectId) console.log(`${chalk11.bold("object_id:")} ${r.objectId}`);
2135
+ console.log(`${chalk11.bold("type:")} ${r.type}`);
2136
+ console.log(`${chalk11.bold("agent:")} ${r.agent}`);
2137
+ console.log(`${chalk11.bold("task:")} ${r.taskId}`);
2138
+ console.log(`${chalk11.bold("status:")} ${r.status}`);
2139
+ if (r.phase) console.log(`${chalk11.bold("phase:")} ${r.phase}`);
2140
+ if (r.error) console.log(`${chalk11.red("error:")} ${r.error}`);
2141
+ console.log(`${chalk11.bold("tmux:")} ${r.tmuxSession} (alive: ${r.sessionAlive ?? "unknown"})`);
2166
2142
  if (r.result) {
2167
- console.log(chalk12.bold("result:"));
2143
+ console.log(chalk11.bold("result:"));
2168
2144
  console.log(" " + JSON.stringify(r.result, null, 2).split("\n").join("\n "));
2169
2145
  }
2170
2146
  } catch (err) {
2171
- console.error(chalk12.red(err.message));
2147
+ console.error(chalk11.red(err.message));
2172
2148
  process.exit(1);
2173
2149
  }
2174
2150
  });
2175
- runtime.command("wait <id>").description("Block until runtime reaches done or error").option("--timeout <seconds>", "Max seconds to wait", "1800").option("--json", "Output final JSON").action(async (id, opts) => {
2151
+ agentRun.command("wait <id>").description("Block until agent run reaches done or error").option("--timeout <seconds>", "Max seconds to wait", "1800").option("--json", "Output final JSON").action(async (id, opts) => {
2176
2152
  try {
2177
- const final = await waitForRuntime(id, {
2153
+ const final = await waitForAgentRun(id, {
2178
2154
  timeoutMs: Number(opts.timeout) * 1e3,
2179
2155
  onTick: (s) => {
2180
- if (!opts.json) console.log(chalk12.dim(`[${s.status}] ${s.phase ?? ""}`));
2156
+ if (!opts.json) console.log(chalk11.dim(`[${s.status}] ${s.phase ?? ""}`));
2181
2157
  }
2182
2158
  });
2183
2159
  if (opts.json) {
@@ -2185,41 +2161,120 @@ runtime.command("wait <id>").description("Block until runtime reaches done or er
2185
2161
  }
2186
2162
  process.exit(final.status === "done" ? 0 : 1);
2187
2163
  } catch (err) {
2188
- console.error(chalk12.red(err.message));
2164
+ console.error(chalk11.red(err.message));
2189
2165
  process.exit(1);
2190
2166
  }
2191
2167
  });
2192
- runtime.command("cancel <id>").description("Cancel a runtime").action(async (id) => {
2168
+ agentRun.command("cancel <id>").description("Cancel an agent run").action(async (id) => {
2193
2169
  try {
2194
- await api.post(`/api/runtimes/${encodeURIComponent(id)}/cancel`);
2195
- console.log(chalk12.green(`Canceled ${id}`));
2170
+ await api.post(`/api/agent-runs/${encodeURIComponent(id)}/cancel`);
2171
+ console.log(chalk11.green(`Canceled ${id}`));
2196
2172
  } catch (err) {
2197
- console.error(chalk12.red(err.message));
2173
+ console.error(chalk11.red(err.message));
2198
2174
  process.exit(1);
2199
2175
  }
2200
2176
  });
2201
- runtime.command("attach <id>").description("Attach to the tmux session backing a runtime").action(async (id) => {
2177
+ agentRun.command("attach <id>").description("Attach to the tmux session backing an agent run").action(async (id) => {
2202
2178
  try {
2203
- const r = await getRuntime(id);
2179
+ const r = await getAgentRun(id);
2204
2180
  if (!r.tmuxSession) {
2205
- console.error(chalk12.red("No tmux session recorded for this runtime."));
2181
+ console.error(chalk11.red("No tmux session recorded for this agent run."));
2206
2182
  process.exit(1);
2207
2183
  }
2208
- const child = spawn3("tmux", ["attach", "-t", r.tmuxSession], { stdio: "inherit" });
2184
+ const child = spawn2("tmux", ["attach", "-t", r.tmuxSession], { stdio: "inherit" });
2209
2185
  child.on("exit", (code) => process.exit(code ?? 0));
2210
2186
  child.on("error", (err) => {
2211
- console.error(chalk12.red(`Failed to attach: ${err.message}`));
2187
+ console.error(chalk11.red(`Failed to attach: ${err.message}`));
2212
2188
  process.exit(1);
2213
2189
  });
2214
2190
  } catch (err) {
2215
- console.error(chalk12.red(err.message));
2191
+ console.error(chalk11.red(err.message));
2216
2192
  process.exit(1);
2217
2193
  }
2218
2194
  });
2195
+ function failProfile(message) {
2196
+ console.error(chalk11.red(message));
2197
+ process.exit(1);
2198
+ }
2199
+ function formatProfile(r) {
2200
+ const tag = r.system ? chalk11.dim("(system)") : r.scope ? chalk11.dim(`(${r.scope})`) : "";
2201
+ return `${chalk11.bold(r.slug.padEnd(20))} ${chalk11.dim(r.object_id.padEnd(14))} ${r.kind.padEnd(5)} ${chalk11.dim(r.exec.command.padEnd(20))} ${tag}`;
2202
+ }
2203
+ var profile = new Command11("profile").description("Manage RuntimeProfile templates (the executables / endpoints agents bind to)");
2204
+ profile.command("list").description("List visible runtime profiles (project + user + built-in cascade)").option("--include-system", "Include built-in system profiles (default true)").option("--json", "Output JSON").action(async (opts) => {
2205
+ try {
2206
+ const qs = opts.includeSystem === false ? "?include_system=false" : "";
2207
+ const result = await api.get(`/api/runtime-profiles${qs}`);
2208
+ if (opts.json) return console.log(JSON.stringify(result.runtimes, null, 2));
2209
+ if (!result.runtimes.length) {
2210
+ console.log(chalk11.dim("(no runtime profiles)"));
2211
+ return;
2212
+ }
2213
+ for (const r of result.runtimes) console.log(formatProfile(r));
2214
+ } catch (err) {
2215
+ failProfile(err.message);
2216
+ }
2217
+ });
2218
+ profile.command("get <ref>").description("Show one runtime profile by slug or object_id").option("--json", "Output JSON").action(async (ref, opts) => {
2219
+ try {
2220
+ const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
2221
+ if (opts.json) return console.log(JSON.stringify(result.runtime, null, 2));
2222
+ console.log(formatProfile(result.runtime));
2223
+ console.log();
2224
+ console.log(chalk11.dim("--- exec ---"));
2225
+ console.log(yaml6.dump(result.runtime.exec, { lineWidth: 100 }));
2226
+ } catch (err) {
2227
+ const apiErr = err;
2228
+ if (apiErr.status === 404) failProfile(`not found: ${ref}`);
2229
+ failProfile(err.message);
2230
+ }
2231
+ });
2232
+ profile.command("create").description("Create a runtime profile from a YAML spec file").requiredOption("--from-file <file>", "YAML spec file with kind, slug, exec").option("--scope <scope>", "Storage scope: user (default) or project", "user").option("--project-root <path>", "Project root (required for --scope project)").action(async (opts) => {
2233
+ let parsed;
2234
+ try {
2235
+ parsed = yaml6.load(fs15.readFileSync(path14.resolve(opts.fromFile), "utf-8"));
2236
+ } catch (err) {
2237
+ failProfile(`cannot read ${opts.fromFile}: ${err.message}`);
2238
+ }
2239
+ try {
2240
+ const body = { ...parsed, scope: opts.scope, project_root: opts.projectRoot };
2241
+ const result = await api.post("/api/runtime-profiles", body);
2242
+ console.log(chalk11.green(`created ${result.runtime.slug} (${result.runtime.object_id})`));
2243
+ } catch (err) {
2244
+ failProfile(err.message);
2245
+ }
2246
+ });
2247
+ profile.command("edit <ref>").description("Open the runtime profile YAML in $EDITOR and save changes").action(async (ref) => {
2248
+ try {
2249
+ const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
2250
+ if (result.runtime.system) failProfile("cannot edit a system runtime profile");
2251
+ const tmp = path14.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
2252
+ fs15.writeFileSync(tmp, yaml6.dump(result.runtime, { lineWidth: 100 }), "utf-8");
2253
+ const editor = process.env.EDITOR || "vi";
2254
+ const r = spawnSync3(editor, [tmp], { stdio: "inherit" });
2255
+ if (r.status !== 0) failProfile(`editor exited with status ${r.status}`);
2256
+ const updated = yaml6.load(fs15.readFileSync(tmp, "utf-8"));
2257
+ await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
2258
+ fs15.unlinkSync(tmp);
2259
+ console.log(chalk11.green(`updated ${ref}`));
2260
+ } catch (err) {
2261
+ failProfile(err.message);
2262
+ }
2263
+ });
2264
+ profile.command("delete <ref>").description("Delete a runtime profile").action(async (ref) => {
2265
+ try {
2266
+ const result = await api.del(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
2267
+ if (!result.deleted) failProfile(`not deleted: ${ref}`);
2268
+ console.log(chalk11.green(`deleted ${ref}`));
2269
+ } catch (err) {
2270
+ failProfile(err.message);
2271
+ }
2272
+ });
2273
+ agentRun.addCommand(profile);
2219
2274
 
2220
2275
  // src/commands/plan.ts
2221
- import { Command as Command13 } from "commander";
2222
- import chalk13 from "chalk";
2276
+ import { Command as Command12 } from "commander";
2277
+ import chalk12 from "chalk";
2223
2278
  import fs16 from "fs";
2224
2279
  import path15 from "path";
2225
2280
  init_task_state2();
@@ -2228,27 +2283,27 @@ function resolveSkillFilePath3(projectRoot, skillName) {
2228
2283
  const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2229
2284
  return fs16.existsSync(p) ? p : null;
2230
2285
  }
2231
- var plan = new Command13("plan").description("Generate and refine plans");
2286
+ var plan = new Command12("plan").description("Generate and refine plans");
2232
2287
  plan.command("generate <objectId>").description("Generate plan(s) from an IDEA file \u2014 supports agent fan-out").option("-a, --agents <list>", "Comma-separated agents (claude-code,codex,cursor)", "codex,claude-code").option("--model <id>", "Model id or alias \u2014 only with a single agent; use `task0 models default` for fan-out").option("--effort <level>", "Reasoning effort \u2014 only with a single agent; use `task0 models default` for fan-out").option("--idea <file>", "IDEA file name (default: latest IDEA-NN.md)").option("--additional-prompt <text>", "Extra prompt content").option("--wait", "Wait for completion").option("--force", "Overwrite existing plan files").option("--json", "Output JSON").action(async (objectId, opts) => {
2233
2288
  try {
2234
2289
  const loc = resolveTaskByObjectId(objectId);
2235
2290
  const ideaFile = opts.idea || latestArtifact(loc.taskDir, /^IDEA-\d+\.md$/);
2236
2291
  if (!ideaFile) {
2237
- console.error(chalk13.red(`No IDEA-NN.md found in ${loc.taskDir}. Pass --idea.`));
2292
+ console.error(chalk12.red(`No IDEA-NN.md found in ${loc.taskDir}. Pass --idea.`));
2238
2293
  process.exit(1);
2239
2294
  }
2240
2295
  const agents = opts.agents.split(",").map((a) => a.trim()).filter(Boolean);
2241
2296
  if (agents.length === 0) {
2242
- console.error(chalk13.red("No agents specified."));
2297
+ console.error(chalk12.red("No agents specified."));
2243
2298
  process.exit(1);
2244
2299
  }
2245
2300
  if ((opts.model || opts.effort) && agents.length > 1) {
2246
- console.error(chalk13.red(
2301
+ console.error(chalk12.red(
2247
2302
  `--model/--effort only works with a single agent; got ${agents.length} (${agents.join(", ")}). Use \`task0 models default <agent>\` to set per-agent defaults for fan-out.`
2248
2303
  ));
2249
2304
  process.exit(1);
2250
2305
  }
2251
- const warn = opts.json ? void 0 : (m) => console.error(chalk13.dim(m));
2306
+ const warn = opts.json ? void 0 : (m) => console.error(chalk12.dim(m));
2252
2307
  const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_GENERATE_SKILL_NAME);
2253
2308
  if (opts.model || opts.effort) {
2254
2309
  resolveModelOptions(agents[0], { model: opts.model, effort: opts.effort }, warn);
@@ -2271,43 +2326,56 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
2271
2326
  prompt: promptForAgent(agent2),
2272
2327
  ...skillFilePath ? { skill_file_path: skillFilePath } : {}
2273
2328
  };
2274
- try {
2275
- const resp = await api.post(
2276
- `/api/agents/${encodeURIComponent(agent2)}/run`,
2277
- body
2278
- );
2279
- return {
2280
- agent: agent2,
2281
- runtimeId: resp.runtime.id,
2282
- sessionName: "",
2283
- error: null
2284
- };
2285
- } catch (err) {
2286
- return { agent: agent2, runtimeId: "", sessionName: "", error: err.message };
2329
+ const maxAttempts = 6;
2330
+ const backoffMs = 2e3;
2331
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2332
+ try {
2333
+ const resp = await api.post(
2334
+ `/api/agents/${encodeURIComponent(agent2)}/run`,
2335
+ body
2336
+ );
2337
+ return {
2338
+ agent: agent2,
2339
+ agentRunId: resp.runtime.id,
2340
+ sessionName: "",
2341
+ error: null
2342
+ };
2343
+ } catch (err) {
2344
+ const e = err;
2345
+ const busy = e.status === 409 && e.body?.error === "agent_busy";
2346
+ if (busy && attempt < maxAttempts) {
2347
+ if (!opts.json) console.error(chalk12.dim(`[${agent2}] busy, retry ${attempt}/${maxAttempts - 1}`));
2348
+ await new Promise((r) => setTimeout(r, backoffMs));
2349
+ continue;
2350
+ }
2351
+ const msg = e.body?.message || err.message;
2352
+ return { agent: agent2, agentRunId: "", sessionName: "", error: msg };
2353
+ }
2287
2354
  }
2355
+ return { agent: agent2, agentRunId: "", sessionName: "", error: "agent_busy: exhausted retries" };
2288
2356
  }));
2289
2357
  const launched = kicks.filter((k) => !k.error);
2290
2358
  const failed = kicks.filter((k) => k.error);
2291
2359
  if (!opts.json) {
2292
2360
  for (const k of launched) {
2293
- console.log(chalk13.green(`[${k.agent}]`) + ` runtime ${k.runtimeId}`);
2361
+ console.log(chalk12.green(`[${k.agent}]`) + ` runtime ${k.agentRunId}`);
2294
2362
  }
2295
2363
  for (const k of failed) {
2296
- console.error(chalk13.red(`[${k.agent}] ${k.error}`));
2364
+ console.error(chalk12.red(`[${k.agent}] ${k.error}`));
2297
2365
  }
2298
2366
  }
2299
- const runtimeIds = {};
2300
- for (const k of launched) runtimeIds[k.agent] = k.runtimeId;
2367
+ const agentRunIds = {};
2368
+ for (const k of launched) agentRunIds[k.agent] = k.agentRunId;
2301
2369
  await updateWorkflow(loc.taskYml, {
2302
- runtimes: { plan: runtimeIds }
2370
+ agent_runs: { plan: agentRunIds }
2303
2371
  });
2304
2372
  const planFiles = [];
2305
2373
  if (opts.wait) {
2306
2374
  const results = await Promise.all(launched.map(async (k) => {
2307
2375
  try {
2308
- const final = await waitForRuntime(k.runtimeId, {
2376
+ const final = await waitForAgentRun(k.agentRunId, {
2309
2377
  onTick: (s) => {
2310
- if (!opts.json) console.log(chalk13.dim(`[${k.agent}] ${s.status}${s.phase ? " " + s.phase : ""}`));
2378
+ if (!opts.json) console.log(chalk12.dim(`[${k.agent}] ${s.status}${s.phase ? " " + s.phase : ""}`));
2311
2379
  }
2312
2380
  });
2313
2381
  return { agent: k.agent, final, error: null };
@@ -2320,10 +2388,10 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
2320
2388
  const planFile = r.final.result?.planFile;
2321
2389
  if (planFile) {
2322
2390
  planFiles.push(planFile);
2323
- if (!opts.json) console.log(chalk13.green(`[${r.agent}] ${planFile}`));
2391
+ if (!opts.json) console.log(chalk12.green(`[${r.agent}] ${planFile}`));
2324
2392
  }
2325
2393
  } else {
2326
- if (!opts.json) console.error(chalk13.red(`[${r.agent}] failed: ${r.error || r.final?.error || "unknown"}`));
2394
+ if (!opts.json) console.error(chalk12.red(`[${r.agent}] failed: ${r.error || r.final?.error || "unknown"}`));
2327
2395
  }
2328
2396
  }
2329
2397
  const existingPlanFiles = readWorkflow(loc.taskYml).plan_files || [];
@@ -2337,7 +2405,7 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
2337
2405
  }
2338
2406
  if (failed.length > 0) process.exit(1);
2339
2407
  } catch (err) {
2340
- console.error(chalk13.red(err.message));
2408
+ console.error(chalk12.red(err.message));
2341
2409
  process.exit(1);
2342
2410
  }
2343
2411
  });
@@ -2348,7 +2416,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2348
2416
  const files = fs16.readdirSync(loc.taskDir);
2349
2417
  const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
2350
2418
  if (planFiles.length === 0) {
2351
- console.error(chalk13.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
2419
+ console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
2352
2420
  process.exit(1);
2353
2421
  }
2354
2422
  const idx = planFiles[0].match(/^PLAN-(\d+)/)?.[1] || nextArtifactIndex(loc.taskDir, "PLAN");
@@ -2357,7 +2425,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2357
2425
  resolveModelOptions(
2358
2426
  opts.agent,
2359
2427
  { model: opts.model, effort: opts.effort },
2360
- opts.json ? void 0 : (m) => console.error(chalk13.dim(m))
2428
+ opts.json ? void 0 : (m) => console.error(chalk12.dim(m))
2361
2429
  );
2362
2430
  }
2363
2431
  const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_REFINE_SKILL_NAME);
@@ -2376,15 +2444,15 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2376
2444
  `/api/agents/${encodeURIComponent(opts.agent)}/run`,
2377
2445
  body
2378
2446
  );
2379
- const runtimeId = resp.runtime.id;
2380
- if (!opts.json) console.log(chalk13.green(`refine runtime ${runtimeId}`));
2447
+ const agentRunId = resp.runtime.id;
2448
+ if (!opts.json) console.log(chalk12.green(`refine runtime ${agentRunId}`));
2381
2449
  await updateWorkflow(loc.taskYml, {
2382
- runtimes: { refine: runtimeId }
2450
+ agent_runs: { refine: agentRunId }
2383
2451
  });
2384
2452
  if (opts.wait) {
2385
- const final = await waitForRuntime(runtimeId, {
2453
+ const final = await waitForAgentRun(agentRunId, {
2386
2454
  onTick: (s) => {
2387
- if (!opts.json) console.log(chalk13.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
2455
+ if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
2388
2456
  }
2389
2457
  });
2390
2458
  const refinedPath = path15.join(loc.taskDir, refinedFile);
@@ -2394,29 +2462,29 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2394
2462
  phase: "refined",
2395
2463
  refined_plan_file: refinedFile
2396
2464
  });
2397
- if (!opts.json) console.log(chalk13.green(refinedFile));
2398
- if (opts.json) console.log(JSON.stringify({ runtimeId, refinedFile }, null, 2));
2465
+ if (!opts.json) console.log(chalk12.green(refinedFile));
2466
+ if (opts.json) console.log(JSON.stringify({ agentRunId, refinedFile }, null, 2));
2399
2467
  return;
2400
2468
  }
2401
2469
  if (final.status !== "done") {
2402
- console.error(chalk13.red(`refine failed: ${final.error || "unknown"}`));
2470
+ console.error(chalk12.red(`refine failed: ${final.error || "unknown"}`));
2403
2471
  } else if (!wrote) {
2404
- console.error(chalk13.red(`refine completed but ${refinedFile} was not written`));
2472
+ console.error(chalk12.red(`refine completed but ${refinedFile} was not written`));
2405
2473
  }
2406
2474
  process.exit(1);
2407
2475
  }
2408
- if (opts.json) console.log(JSON.stringify({ runtimeId, refinedFile }, null, 2));
2476
+ if (opts.json) console.log(JSON.stringify({ agentRunId, refinedFile }, null, 2));
2409
2477
  } catch (err) {
2410
- console.error(chalk13.red(err.message));
2478
+ console.error(chalk12.red(err.message));
2411
2479
  process.exit(1);
2412
2480
  }
2413
2481
  });
2414
2482
 
2415
2483
  // src/commands/run.ts
2416
2484
  init_task_state2();
2417
- import { Command as Command14 } from "commander";
2418
- import chalk14 from "chalk";
2419
- import { spawnSync as spawnSync3 } from "child_process";
2485
+ import { Command as Command13 } from "commander";
2486
+ import chalk13 from "chalk";
2487
+ import { spawnSync as spawnSync4 } from "child_process";
2420
2488
  var PHASES = ["triage", "plan", "refine", "exec"];
2421
2489
  var PAUSE_AFTER = /* @__PURE__ */ new Set(["refine"]);
2422
2490
  var CHECKPOINT_EXIT = 78;
@@ -2435,13 +2503,13 @@ function nextPhaseFromWorkflow(wf) {
2435
2503
  return null;
2436
2504
  }
2437
2505
  }
2438
- var run = new Command14("run").description("Run the full task orchestration (triage \u2192 plan \u2192 refine \u2192 exec)").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--from <phase>", "Start from phase (triage|plan|refine|exec)").option("--to <phase>", "Stop after phase").option("--auto", "Skip pauses (no exit 78 at checkpoints)").option("--planners <list>", "Comma-separated plan agents", "codex,claude-code").option("--executor <agent>", "Executor agent", "claude-code").option("--triage-agent <agent>", "Triage agent", "claude-code").option("--refine-agent <agent>", "Refine agent", "claude-code").option("--json", "Output JSON summary at end").action(async (objectId, opts) => {
2506
+ var run = new Command13("run").description("Run the full task orchestration (triage \u2192 plan \u2192 refine \u2192 exec)").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--from <phase>", "Start from phase (triage|plan|refine|exec)").option("--to <phase>", "Stop after phase").option("--auto", "Skip pauses (no exit 78 at checkpoints)").option("--planners <list>", "Comma-separated plan agents", "codex,claude-code").option("--executor <agent>", "Executor agent", "claude-code").option("--triage-agent <agent>", "Triage agent", "claude-code").option("--refine-agent <agent>", "Refine agent", "claude-code").option("--json", "Output JSON summary at end").action(async (objectId, opts) => {
2439
2507
  try {
2440
2508
  const loc = resolveTaskByObjectId(objectId);
2441
2509
  const wf = readWorkflow(loc.taskYml);
2442
2510
  const fromCandidate = opts.from ?? nextPhaseFromWorkflow(wf);
2443
2511
  if (!fromCandidate) {
2444
- if (!opts.json) console.log(chalk14.dim(`Nothing to do \u2014 workflow phase is "${wf.phase}".`));
2512
+ if (!opts.json) console.log(chalk13.dim(`Nothing to do \u2014 workflow phase is "${wf.phase}".`));
2445
2513
  return;
2446
2514
  }
2447
2515
  const from = fromCandidate;
@@ -2452,23 +2520,23 @@ var run = new Command14("run").description("Run the full task orchestration (tri
2452
2520
  const toIdx = PHASES.indexOf(to);
2453
2521
  if (fromIdx > toIdx) throw new Error(`--from (${from}) is past --to (${to})`);
2454
2522
  if (!opts.json) {
2455
- console.log(chalk14.bold(`task0 run ${objectId}`) + chalk14.dim(` \u2014 ${from} \u2192 ${to}`));
2523
+ console.log(chalk13.bold(`task0 run ${objectId}`) + chalk13.dim(` \u2014 ${from} \u2192 ${to}`));
2456
2524
  }
2457
2525
  for (let i = fromIdx; i <= toIdx; i++) {
2458
2526
  const phase = PHASES[i];
2459
- if (!opts.json) console.log(chalk14.bold.cyan(`
2527
+ if (!opts.json) console.log(chalk13.bold.cyan(`
2460
2528
  \u25B6 ${phase}`));
2461
2529
  const code = runPhase(phase, objectId, opts);
2462
2530
  if (code !== 0) {
2463
- console.error(chalk14.red(`Phase "${phase}" failed (exit ${code}). Resume with: task0 run ${objectId} --from ${phase}`));
2531
+ console.error(chalk13.red(`Phase "${phase}" failed (exit ${code}). Resume with: task0 run ${objectId} --from ${phase}`));
2464
2532
  process.exit(code);
2465
2533
  }
2466
2534
  if (!opts.auto && PAUSE_AFTER.has(phase) && i < toIdx) {
2467
2535
  const next = PHASES[i + 1];
2468
2536
  if (!opts.json) {
2469
- console.log(chalk14.yellow(`
2537
+ console.log(chalk13.yellow(`
2470
2538
  \u23F8 checkpoint after ${phase}. Review and resume with:`));
2471
- console.log(chalk14.yellow(` task0 run ${objectId} --from ${next}`));
2539
+ console.log(chalk13.yellow(` task0 run ${objectId} --from ${next}`));
2472
2540
  }
2473
2541
  process.exit(CHECKPOINT_EXIT);
2474
2542
  }
@@ -2477,11 +2545,11 @@ var run = new Command14("run").description("Run the full task orchestration (tri
2477
2545
  if (opts.json) {
2478
2546
  console.log(JSON.stringify({ objectId, from, to, workflow: finalWf }, null, 2));
2479
2547
  } else {
2480
- console.log(chalk14.green(`
2548
+ console.log(chalk13.green(`
2481
2549
  \u2713 done (phase: ${finalWf.phase})`));
2482
2550
  }
2483
2551
  } catch (err) {
2484
- console.error(chalk14.red(err.message));
2552
+ console.error(chalk13.red(err.message));
2485
2553
  process.exit(1);
2486
2554
  }
2487
2555
  });
@@ -2503,7 +2571,7 @@ function runPhase(phase, objectId, opts) {
2503
2571
  args = ["task", "exec", objectId, "--wait", "--agent", opts.executor];
2504
2572
  break;
2505
2573
  }
2506
- const r = spawnSync3(node, [cli, ...args], { stdio: "inherit" });
2574
+ const r = spawnSync4(node, [cli, ...args], { stdio: "inherit" });
2507
2575
  return r.status ?? 1;
2508
2576
  }
2509
2577
  run.command("status <objectId>").description("Show current workflow state").option("--json", "Output JSON").action((objectId, opts) => {
@@ -2514,29 +2582,30 @@ run.command("status <objectId>").description("Show current workflow state").opti
2514
2582
  console.log(JSON.stringify(wf, null, 2));
2515
2583
  return;
2516
2584
  }
2517
- console.log(`${chalk14.bold("phase:")} ${wf.phase ?? chalk14.dim("(not started)")}`);
2585
+ console.log(`${chalk13.bold("phase:")} ${wf.phase ?? chalk13.dim("(not started)")}`);
2518
2586
  if (wf.issue_overview) {
2519
2587
  const extra = wf.issue_files?.length ? ` (+${wf.issue_files.length} details)` : "";
2520
- console.log(`${chalk14.bold("issues:")} ${wf.issue_overview}${extra}`);
2521
- }
2522
- if (wf.plan_files?.length) console.log(`${chalk14.bold("plans:")} ${wf.plan_files.join(", ")}`);
2523
- if (wf.refined_plan_file) console.log(`${chalk14.bold("refined:")} ${wf.refined_plan_file}`);
2524
- if (wf.runtimes) {
2525
- console.log(chalk14.bold("runtimes:"));
2526
- for (const [k, v] of Object.entries(wf.runtimes)) {
2588
+ console.log(`${chalk13.bold("issues:")} ${wf.issue_overview}${extra}`);
2589
+ }
2590
+ if (wf.plan_files?.length) console.log(`${chalk13.bold("plans:")} ${wf.plan_files.join(", ")}`);
2591
+ if (wf.refined_plan_file) console.log(`${chalk13.bold("refined:")} ${wf.refined_plan_file}`);
2592
+ const phaseRuns = wf.agent_runs ?? wf.runtimes;
2593
+ if (phaseRuns) {
2594
+ console.log(chalk13.bold("agent_runs:"));
2595
+ for (const [k, v] of Object.entries(phaseRuns)) {
2527
2596
  console.log(` ${k.padEnd(8)} ${typeof v === "string" ? v : JSON.stringify(v)}`);
2528
2597
  }
2529
2598
  }
2530
- if (wf.updated_at) console.log(chalk14.dim(`updated_at: ${wf.updated_at}`));
2599
+ if (wf.updated_at) console.log(chalk13.dim(`updated_at: ${wf.updated_at}`));
2531
2600
  } catch (err) {
2532
- console.error(chalk14.red(err.message));
2601
+ console.error(chalk13.red(err.message));
2533
2602
  process.exit(1);
2534
2603
  }
2535
2604
  });
2536
2605
 
2537
2606
  // src/commands/okr.ts
2538
- import { Command as Command15 } from "commander";
2539
- import chalk15 from "chalk";
2607
+ import { Command as Command14 } from "commander";
2608
+ import chalk14 from "chalk";
2540
2609
 
2541
2610
  // src/lib/project.ts
2542
2611
  import path16 from "path";
@@ -2569,7 +2638,7 @@ function resolveProjectName(opts) {
2569
2638
 
2570
2639
  // src/commands/okr.ts
2571
2640
  function fail3(message) {
2572
- console.error(chalk15.red(message));
2641
+ console.error(chalk14.red(message));
2573
2642
  process.exit(1);
2574
2643
  }
2575
2644
  function parseNumber(value) {
@@ -2606,11 +2675,11 @@ function countKRs(plan3) {
2606
2675
  return plan3.objectives.reduce((sum, o) => sum + o.key_results.length, 0);
2607
2676
  }
2608
2677
  function printPlanSummary(plan3) {
2609
- const oid = chalk15.cyan((plan3.object_id || "-").padEnd(10));
2678
+ const oid = chalk14.cyan((plan3.object_id || "-").padEnd(10));
2610
2679
  const id = plan3.id.padEnd(16);
2611
- const status = chalk15.yellow(plan3.status.padEnd(8));
2680
+ const status = chalk14.yellow(plan3.status.padEnd(8));
2612
2681
  const range = `${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`;
2613
- const counts = chalk15.dim(
2682
+ const counts = chalk14.dim(
2614
2683
  `${plan3.objectives.length} obj \xB7 ${countKRs(plan3)} kr \xB7 ${plan3.milestones.length} ms`
2615
2684
  );
2616
2685
  console.log(`${oid} ${id} ${status} ${range} ${counts} ${plan3.title}`);
@@ -2629,37 +2698,37 @@ function krProgress(kr2) {
2629
2698
  }
2630
2699
  function printPlanDetail(plan3) {
2631
2700
  console.log(
2632
- `${chalk15.bold(plan3.id)} ${chalk15.dim(plan3.object_id || "")} ${chalk15.yellow(plan3.status)} ${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`
2701
+ `${chalk14.bold(plan3.id)} ${chalk14.dim(plan3.object_id || "")} ${chalk14.yellow(plan3.status)} ${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`
2633
2702
  );
2634
2703
  if (plan3.title) console.log(` ${plan3.title}`);
2635
2704
  for (const obj of plan3.objectives) {
2636
2705
  console.log(
2637
- ` ${chalk15.bold(obj.id)} ${obj.title} ${chalk15.dim(`(${obj.status})`)} ${chalk15.dim(obj.object_id || "")}`
2706
+ ` ${chalk14.bold(obj.id)} ${obj.title} ${chalk14.dim(`(${obj.status})`)} ${chalk14.dim(obj.object_id || "")}`
2638
2707
  );
2639
2708
  for (const kr2 of obj.key_results) {
2640
2709
  const unit = kr2.unit ? kr2.unit : "";
2641
2710
  const vals = kr2.target_value !== null ? `${kr2.start_value ?? 0}${unit}\u2192${kr2.target_value}${unit} now ${kr2.current_value ?? 0}${unit}` : "\u2014";
2642
2711
  console.log(
2643
- ` ${kr2.id.padEnd(20)} ${vals} ${krProgress(kr2)} ${chalk15.dim(`(${kr2.status}/${kr2.confidence})`)} ${chalk15.dim(kr2.object_id || "")}`
2712
+ ` ${kr2.id.padEnd(20)} ${vals} ${krProgress(kr2)} ${chalk14.dim(`(${kr2.status}/${kr2.confidence})`)} ${chalk14.dim(kr2.object_id || "")}`
2644
2713
  );
2645
2714
  }
2646
2715
  }
2647
2716
  for (const ms of plan3.milestones) {
2648
- const links = ms.linked_kr_ids.length ? chalk15.dim(`\u2192 ${ms.linked_kr_ids.join(",")}`) : "";
2717
+ const links = ms.linked_kr_ids.length ? chalk14.dim(`\u2192 ${ms.linked_kr_ids.join(",")}`) : "";
2649
2718
  console.log(
2650
- ` ${chalk15.bold(ms.id)} ${dateShort(ms.due_at)} ${ms.title} ${chalk15.dim(`(${ms.status})`)} ${links} ${chalk15.dim(ms.object_id || "")}`
2719
+ ` ${chalk14.bold(ms.id)} ${dateShort(ms.due_at)} ${ms.title} ${chalk14.dim(`(${ms.status})`)} ${links} ${chalk14.dim(ms.object_id || "")}`
2651
2720
  );
2652
2721
  }
2653
2722
  if (plan3.task_kr_links.length) {
2654
- console.log(chalk15.dim("links:"));
2723
+ console.log(chalk14.dim("links:"));
2655
2724
  for (const link of plan3.task_kr_links) {
2656
2725
  console.log(
2657
- ` ${link.task_id} \u2192 ${link.kr_id} ${chalk15.dim(`${link.contribution} w=${link.weight}`)}`
2726
+ ` ${link.task_id} \u2192 ${link.kr_id} ${chalk14.dim(`${link.contribution} w=${link.weight}`)}`
2658
2727
  );
2659
2728
  }
2660
2729
  }
2661
2730
  }
2662
- var okr = new Command15("okr").description(
2731
+ var okr = new Command14("okr").description(
2663
2732
  "Manage OKR plans, objectives, key results, milestones"
2664
2733
  );
2665
2734
  var plan2 = okr.command("plan").description("OKR plans");
@@ -2693,7 +2762,7 @@ plan2.command("create <plan-id>").description("Create a new OKR plan").requiredO
2693
2762
  };
2694
2763
  const { plan: view } = await api.post(planBasePath(name), body);
2695
2764
  if (maybeJson(view, opts)) return;
2696
- console.log(chalk15.green("ok"), view.id, chalk15.dim(view.object_id || ""));
2765
+ console.log(chalk14.green("ok"), view.id, chalk14.dim(view.object_id || ""));
2697
2766
  printPlanSummary(view);
2698
2767
  });
2699
2768
  }
@@ -2712,7 +2781,7 @@ plan2.command("update <plan-id>").description("Update plan fields").option("-t,
2712
2781
  body
2713
2782
  );
2714
2783
  if (maybeJson(view, opts)) return;
2715
- console.log(chalk15.green("updated"), view.id, chalk15.dim(view.object_id || ""));
2784
+ console.log(chalk14.green("updated"), view.id, chalk14.dim(view.object_id || ""));
2716
2785
  printPlanSummary(view);
2717
2786
  });
2718
2787
  }
@@ -2735,10 +2804,10 @@ objective.command("create <plan-id> <objective-id>").description("Create an obje
2735
2804
  );
2736
2805
  if (maybeJson(view, opts)) return;
2737
2806
  console.log(
2738
- chalk15.green("ok"),
2807
+ chalk14.green("ok"),
2739
2808
  view.id,
2740
- chalk15.dim(view.object_id || ""),
2741
- chalk15.dim(`(${view.status})`),
2809
+ chalk14.dim(view.object_id || ""),
2810
+ chalk14.dim(`(${view.status})`),
2742
2811
  view.title
2743
2812
  );
2744
2813
  });
@@ -2759,7 +2828,7 @@ objective.command("update <plan-id> <objective-id>").description("Update objecti
2759
2828
  body
2760
2829
  );
2761
2830
  if (maybeJson(view, opts)) return;
2762
- console.log(chalk15.green("updated"), view.id, chalk15.dim(`(${view.status})`), view.title);
2831
+ console.log(chalk14.green("updated"), view.id, chalk14.dim(`(${view.status})`), view.title);
2763
2832
  });
2764
2833
  }
2765
2834
  );
@@ -2787,10 +2856,10 @@ kr.command("create <plan-id> <objective-id> <kr-id>").description("Create a key
2787
2856
  );
2788
2857
  if (maybeJson(view, opts)) return;
2789
2858
  console.log(
2790
- chalk15.green("ok"),
2859
+ chalk14.green("ok"),
2791
2860
  view.id,
2792
- chalk15.dim(view.object_id || ""),
2793
- chalk15.dim(`(${view.status}/${view.confidence})`),
2861
+ chalk14.dim(view.object_id || ""),
2862
+ chalk14.dim(`(${view.status}/${view.confidence})`),
2794
2863
  view.title
2795
2864
  );
2796
2865
  });
@@ -2818,9 +2887,9 @@ kr.command("update <plan-id> <kr-id>").description("Update a key result").option
2818
2887
  );
2819
2888
  if (maybeJson(view, opts)) return;
2820
2889
  console.log(
2821
- chalk15.green("updated"),
2890
+ chalk14.green("updated"),
2822
2891
  view.id,
2823
- chalk15.dim(`(${view.status}/${view.confidence})`),
2892
+ chalk14.dim(`(${view.status}/${view.confidence})`),
2824
2893
  view.title
2825
2894
  );
2826
2895
  });
@@ -2840,10 +2909,10 @@ kr.command("progress <plan-id> <kr-id>").description("Update key result progress
2840
2909
  );
2841
2910
  if (maybeJson(view, opts)) return;
2842
2911
  console.log(
2843
- chalk15.green("progress"),
2912
+ chalk14.green("progress"),
2844
2913
  view.id,
2845
2914
  krProgress(view),
2846
- chalk15.dim(`(${view.status}/${view.confidence})`)
2915
+ chalk14.dim(`(${view.status}/${view.confidence})`)
2847
2916
  );
2848
2917
  });
2849
2918
  }
@@ -2867,11 +2936,11 @@ milestone.command("create <plan-id> <milestone-id>").description("Create a miles
2867
2936
  );
2868
2937
  if (maybeJson(view, opts)) return;
2869
2938
  console.log(
2870
- chalk15.green("ok"),
2939
+ chalk14.green("ok"),
2871
2940
  view.id,
2872
- chalk15.dim(view.object_id || ""),
2941
+ chalk14.dim(view.object_id || ""),
2873
2942
  dateShort(view.due_at),
2874
- chalk15.dim(`(${view.status})`),
2943
+ chalk14.dim(`(${view.status})`),
2875
2944
  view.title
2876
2945
  );
2877
2946
  });
@@ -2894,10 +2963,10 @@ milestone.command("update <plan-id> <milestone-id>").description("Update a miles
2894
2963
  );
2895
2964
  if (maybeJson(view, opts)) return;
2896
2965
  console.log(
2897
- chalk15.green("updated"),
2966
+ chalk14.green("updated"),
2898
2967
  view.id,
2899
2968
  dateShort(view.due_at),
2900
- chalk15.dim(`(${view.status})`),
2969
+ chalk14.dim(`(${view.status})`),
2901
2970
  view.title
2902
2971
  );
2903
2972
  });
@@ -2919,9 +2988,9 @@ okr.command("link <plan-id>").description("Link a task to a key result").require
2919
2988
  );
2920
2989
  if (maybeJson(link, opts)) return;
2921
2990
  console.log(
2922
- chalk15.green("linked"),
2991
+ chalk14.green("linked"),
2923
2992
  `${link.task_id} \u2192 ${link.kr_id}`,
2924
- chalk15.dim(`${link.contribution} w=${link.weight}`)
2993
+ chalk14.dim(`${link.contribution} w=${link.weight}`)
2925
2994
  );
2926
2995
  });
2927
2996
  }
@@ -2933,15 +3002,15 @@ okr.command("unlink <plan-id>").description("Unlink a task from a key result").r
2933
3002
  `${planEncodedPath(name, planId)}/task-kr-links/${encodeURIComponent(opts.task)}/${encodeURIComponent(opts.kr)}`
2934
3003
  );
2935
3004
  if (maybeJson(result, opts)) return;
2936
- console.log(chalk15.green("unlinked"), `${opts.task} \u2715 ${opts.kr}`);
3005
+ console.log(chalk14.green("unlinked"), `${opts.task} \u2715 ${opts.kr}`);
2937
3006
  });
2938
3007
  }
2939
3008
  );
2940
3009
 
2941
3010
  // src/commands/note.ts
2942
- import { Command as Command16 } from "commander";
2943
- import chalk16 from "chalk";
2944
- var note = new Command16("note").description("Manage inbox notes");
3011
+ import { Command as Command15 } from "commander";
3012
+ import chalk15 from "chalk";
3013
+ var note = new Command15("note").description("Manage inbox notes");
2945
3014
  async function fetchAllInboxNotes() {
2946
3015
  const { inboxes } = await api.get("/api/inboxes");
2947
3016
  const results = [];
@@ -2983,33 +3052,33 @@ function preview2(body, width = 60) {
2983
3052
  return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
2984
3053
  }
2985
3054
  function printNoteRow(n, inboxName) {
2986
- const oid = chalk16.cyan((n.object_id || "-").padEnd(14));
2987
- const id = chalk16.dim(n.id.padEnd(10));
2988
- const tags = n.tags.length ? chalk16.yellow(n.tags.join(",")) : chalk16.dim("-");
2989
- const linked = n.linked_task_id ? chalk16.magenta(n.linked_task_id) : chalk16.dim("-");
2990
- const inboxPart = inboxName ? chalk16.dim(`[${inboxName}] `) : "";
2991
- console.log(`${oid} ${id} ${inboxPart}${preview2(n.body)} ${tags} ${linked} ${chalk16.dim(n.updated_at)}`);
3055
+ const oid = chalk15.cyan((n.object_id || "-").padEnd(14));
3056
+ const id = chalk15.dim(n.id.padEnd(10));
3057
+ const tags = n.tags.length ? chalk15.yellow(n.tags.join(",")) : chalk15.dim("-");
3058
+ const linked = n.linked_task_id ? chalk15.magenta(n.linked_task_id) : chalk15.dim("-");
3059
+ const inboxPart = inboxName ? chalk15.dim(`[${inboxName}] `) : "";
3060
+ console.log(`${oid} ${id} ${inboxPart}${preview2(n.body)} ${tags} ${linked} ${chalk15.dim(n.updated_at)}`);
2992
3061
  }
2993
3062
  function printNoteDetail(n, inboxId) {
2994
- if (n.object_id) console.log(`${chalk16.bold("object_id:")} ${n.object_id}`);
2995
- console.log(`${chalk16.bold("id:")} ${n.id}`);
2996
- if (inboxId) console.log(`${chalk16.bold("inbox:")} ${inboxId}`);
2997
- console.log(`${chalk16.bold("tags:")} ${n.tags.length ? n.tags.join(", ") : "-"}`);
2998
- if (n.linked_task_id) console.log(`${chalk16.bold("linked_task:")} ${n.linked_task_id}`);
2999
- if (n.linked_project) console.log(`${chalk16.bold("linked_project:")} ${n.linked_project}`);
3000
- console.log(`${chalk16.bold("created_at:")} ${n.created_at}`);
3001
- console.log(`${chalk16.bold("updated_at:")} ${n.updated_at}`);
3063
+ if (n.object_id) console.log(`${chalk15.bold("object_id:")} ${n.object_id}`);
3064
+ console.log(`${chalk15.bold("id:")} ${n.id}`);
3065
+ if (inboxId) console.log(`${chalk15.bold("inbox:")} ${inboxId}`);
3066
+ console.log(`${chalk15.bold("tags:")} ${n.tags.length ? n.tags.join(", ") : "-"}`);
3067
+ if (n.linked_task_id) console.log(`${chalk15.bold("linked_task:")} ${n.linked_task_id}`);
3068
+ if (n.linked_project) console.log(`${chalk15.bold("linked_project:")} ${n.linked_project}`);
3069
+ console.log(`${chalk15.bold("created_at:")} ${n.created_at}`);
3070
+ console.log(`${chalk15.bold("updated_at:")} ${n.updated_at}`);
3002
3071
  if (n.published_to?.length) {
3003
- console.log(chalk16.bold("published_to:"));
3072
+ console.log(chalk15.bold("published_to:"));
3004
3073
  for (const p of n.published_to) {
3005
- console.log(` - ${p.source_type}:${p.source_name} ${p.identifier} ${chalk16.dim(p.url)}`);
3074
+ console.log(` - ${p.source_type}:${p.source_name} ${p.identifier} ${chalk15.dim(p.url)}`);
3006
3075
  }
3007
3076
  }
3008
- console.log(chalk16.bold("body:"));
3077
+ console.log(chalk15.bold("body:"));
3009
3078
  console.log(n.body.split("\n").map((l) => " " + l).join("\n"));
3010
3079
  }
3011
3080
  function fail4(err) {
3012
- console.error(chalk16.red(err.message));
3081
+ console.error(chalk15.red(err.message));
3013
3082
  process.exit(1);
3014
3083
  }
3015
3084
  note.command("list").description("List inbox notes").option("--inbox <id>", "Restrict to a specific inbox").option("--json", "Output JSON").action(async (opts) => {
@@ -3023,7 +3092,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
3023
3092
  return;
3024
3093
  }
3025
3094
  if (!notes.length) {
3026
- console.log(chalk16.dim("No notes."));
3095
+ console.log(chalk15.dim("No notes."));
3027
3096
  return;
3028
3097
  }
3029
3098
  for (const n of notes) printNoteRow(n);
@@ -3036,7 +3105,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
3036
3105
  }
3037
3106
  const flat = all.flatMap(({ inbox, notes }) => notes.map((n) => ({ n, inboxName: inbox.name })));
3038
3107
  if (!flat.length) {
3039
- console.log(chalk16.dim("No notes."));
3108
+ console.log(chalk15.dim("No notes."));
3040
3109
  return;
3041
3110
  }
3042
3111
  for (const { n, inboxName } of flat) printNoteRow(n, inboxName);
@@ -3069,7 +3138,7 @@ note.command("create").description("Create a note in an inbox").requiredOption("
3069
3138
  console.log(JSON.stringify({ note: created }, null, 2));
3070
3139
  return;
3071
3140
  }
3072
- console.log(chalk16.green(`Created ${created.object_id || created.id}`));
3141
+ console.log(chalk15.green(`Created ${created.object_id || created.id}`));
3073
3142
  printNoteDetail(created, opts.inbox);
3074
3143
  } catch (err) {
3075
3144
  fail4(err);
@@ -3085,7 +3154,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
3085
3154
  if (opts.linkedTask !== void 0) payload.linked_task_id = opts.linkedTask;
3086
3155
  if (opts.linkedProject !== void 0) payload.linked_project = opts.linkedProject;
3087
3156
  if (!Object.keys(payload).length) {
3088
- console.error(chalk16.yellow("Nothing to update. Pass --body, --tag, --clear-tags, --linked-task, or --linked-project."));
3157
+ console.error(chalk15.yellow("Nothing to update. Pass --body, --tag, --clear-tags, --linked-task, or --linked-project."));
3089
3158
  process.exit(1);
3090
3159
  }
3091
3160
  const { note: updated } = await api.patch(
@@ -3096,7 +3165,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
3096
3165
  console.log(JSON.stringify({ note: updated }, null, 2));
3097
3166
  return;
3098
3167
  }
3099
- console.log(chalk16.green(`Updated ${updated.object_id || updated.id}`));
3168
+ console.log(chalk15.green(`Updated ${updated.object_id || updated.id}`));
3100
3169
  printNoteDetail(updated, inboxId);
3101
3170
  } catch (err) {
3102
3171
  fail4(err);
@@ -3108,7 +3177,7 @@ note.command("delete <id>").description("Delete a note (accepts short id or note
3108
3177
  await api.del(
3109
3178
  `/api/inboxes/${encodeURIComponent(inboxId)}/notes/${encodeURIComponent(existing.id)}`
3110
3179
  );
3111
- console.log(chalk16.green(`Deleted ${existing.object_id || existing.id}`));
3180
+ console.log(chalk15.green(`Deleted ${existing.object_id || existing.id}`));
3112
3181
  } catch (err) {
3113
3182
  fail4(err);
3114
3183
  }
@@ -3126,8 +3195,8 @@ note.command("publish <id>").description("Publish a note to a github/linear sour
3126
3195
  console.log(JSON.stringify({ published }, null, 2));
3127
3196
  return;
3128
3197
  }
3129
- console.log(chalk16.green(`Published to ${published.source_type}:${published.source_name} ${published.identifier}`));
3130
- console.log(` ${chalk16.dim(published.url)}`);
3198
+ console.log(chalk15.green(`Published to ${published.source_type}:${published.source_name} ${published.identifier}`));
3199
+ console.log(` ${chalk15.dim(published.url)}`);
3131
3200
  } catch (err) {
3132
3201
  fail4(err);
3133
3202
  }
@@ -3143,16 +3212,16 @@ note.command("convert <id>").description("Convert a note into a task under a pro
3143
3212
  console.log(JSON.stringify({ task: task2 }, null, 2));
3144
3213
  return;
3145
3214
  }
3146
- console.log(chalk16.green(`Converted to task ${task2.object_id} (${task2.id}) in ${task2.project}`));
3215
+ console.log(chalk15.green(`Converted to task ${task2.object_id} (${task2.id}) in ${task2.project}`));
3147
3216
  } catch (err) {
3148
3217
  fail4(err);
3149
3218
  }
3150
3219
  });
3151
3220
 
3152
3221
  // src/commands/object.ts
3153
- import { Command as Command17 } from "commander";
3154
- import chalk17 from "chalk";
3155
- var object = new Command17("object").description("Resolve any object by its object_id");
3222
+ import { Command as Command16 } from "commander";
3223
+ import chalk16 from "chalk";
3224
+ var object = new Command16("object").description("Resolve any object by its object_id");
3156
3225
  function pickFirst(resource, keys) {
3157
3226
  for (const k of keys) {
3158
3227
  const value = resource[k];
@@ -3162,16 +3231,16 @@ function pickFirst(resource, keys) {
3162
3231
  }
3163
3232
  function printSummary(result) {
3164
3233
  const { type, resource } = result;
3165
- console.log(`${chalk17.bold("type:")} ${type}`);
3234
+ console.log(`${chalk16.bold("type:")} ${type}`);
3166
3235
  const title = pickFirst(resource, ["title", "name"]);
3167
3236
  const id = pickFirst(resource, ["object_id", "objectId", "id"]);
3168
3237
  const status = pickFirst(resource, ["status"]);
3169
- if (id) console.log(`${chalk17.bold("object_id:")} ${id}`);
3170
- if (title) console.log(`${chalk17.bold("title:")} ${title}`);
3171
- if (status) console.log(`${chalk17.bold("status:")} ${status}`);
3238
+ if (id) console.log(`${chalk16.bold("object_id:")} ${id}`);
3239
+ if (title) console.log(`${chalk16.bold("title:")} ${title}`);
3240
+ if (status) console.log(`${chalk16.bold("status:")} ${status}`);
3172
3241
  const updatedAt = pickFirst(resource, ["updated_at", "updatedAt"]);
3173
- if (updatedAt) console.log(`${chalk17.bold("updated_at:")} ${updatedAt}`);
3174
- console.log(chalk17.dim("(pass --json for full payload)"));
3242
+ if (updatedAt) console.log(`${chalk16.bold("updated_at:")} ${updatedAt}`);
3243
+ console.log(chalk16.dim("(pass --json for full payload)"));
3175
3244
  }
3176
3245
  object.command("get <object-id>").description("Resolve an object by object_id (tsk_\u2026, note_\u2026, obj_\u2026, rt_\u2026, \u2026)").option("--json", "Output full JSON envelope").action(async (objectId, opts) => {
3177
3246
  try {
@@ -3184,17 +3253,17 @@ object.command("get <object-id>").description("Resolve an object by object_id (t
3184
3253
  } catch (err) {
3185
3254
  const apiErr = err;
3186
3255
  if (apiErr.status === 404) {
3187
- console.error(chalk17.red(`not found: ${objectId}`));
3256
+ console.error(chalk16.red(`not found: ${objectId}`));
3188
3257
  process.exit(1);
3189
3258
  }
3190
- console.error(chalk17.red(err.message));
3259
+ console.error(chalk16.red(err.message));
3191
3260
  process.exit(1);
3192
3261
  }
3193
3262
  });
3194
3263
 
3195
3264
  // src/commands/issue.ts
3196
- import { Command as Command18 } from "commander";
3197
- import chalk18 from "chalk";
3265
+ import { Command as Command17 } from "commander";
3266
+ import chalk17 from "chalk";
3198
3267
 
3199
3268
  // src/core/issue/decision.ts
3200
3269
  init_node();
@@ -3368,7 +3437,7 @@ async function propose(opts) {
3368
3437
  kicks.push({
3369
3438
  issue: issue2.file,
3370
3439
  agent: agent2,
3371
- runtimeId: "",
3440
+ agentRunId: "",
3372
3441
  sessionName: "",
3373
3442
  decisionFile,
3374
3443
  error: null,
@@ -3378,7 +3447,7 @@ async function propose(opts) {
3378
3447
  kicks.push({
3379
3448
  issue: issue2.file,
3380
3449
  agent: agent2,
3381
- runtimeId: "",
3450
+ agentRunId: "",
3382
3451
  sessionName: "",
3383
3452
  decisionFile,
3384
3453
  error: `${decisionFile} already exists (pass --force to overwrite)`
@@ -3402,7 +3471,7 @@ async function propose(opts) {
3402
3471
  kicks.push({
3403
3472
  issue: issue2.file,
3404
3473
  agent: agent2,
3405
- runtimeId: resp.runtime.id,
3474
+ agentRunId: resp.runtime.id,
3406
3475
  sessionName: "",
3407
3476
  decisionFile,
3408
3477
  error: null
@@ -3411,7 +3480,7 @@ async function propose(opts) {
3411
3480
  kicks.push({
3412
3481
  issue: issue2.file,
3413
3482
  agent: agent2,
3414
- runtimeId: "",
3483
+ agentRunId: "",
3415
3484
  sessionName: "",
3416
3485
  decisionFile,
3417
3486
  error: err.message
@@ -3419,25 +3488,25 @@ async function propose(opts) {
3419
3488
  }
3420
3489
  }
3421
3490
  const runtimesByAgent = {};
3422
- for (const k of kicks) if (!k.error && k.runtimeId) runtimesByAgent[k.agent] = k.runtimeId;
3491
+ for (const k of kicks) if (!k.error && k.agentRunId) runtimesByAgent[k.agent] = k.agentRunId;
3423
3492
  const proposalFiles = kicks.filter((k) => !k.error).map((k) => k.decisionFile);
3424
3493
  if (Object.keys(runtimesByAgent).length > 0 || proposalFiles.length > 0) {
3425
3494
  await updateWorkflow(loc.taskYml, {
3426
3495
  decisions: {
3427
3496
  [issue2.file]: {
3428
3497
  proposal_files: proposalFiles.length > 0 ? proposalFiles : void 0,
3429
- proposal_runtimes: Object.keys(runtimesByAgent).length > 0 ? runtimesByAgent : void 0
3498
+ proposal_agent_runs: Object.keys(runtimesByAgent).length > 0 ? runtimesByAgent : void 0
3430
3499
  }
3431
3500
  }
3432
3501
  });
3433
3502
  }
3434
3503
  if (opts.wait) {
3435
3504
  for (const k of kicks) {
3436
- if (k.error || !k.runtimeId) continue;
3505
+ if (k.error || !k.agentRunId) continue;
3437
3506
  try {
3438
- const final = await waitForRuntime(k.runtimeId);
3507
+ const final = await waitForAgentRun(k.agentRunId);
3439
3508
  if (final.status !== "done") {
3440
- k.error = final.error || `runtime ${k.runtimeId} ended with ${final.status}`;
3509
+ k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
3441
3510
  } else if (!fs18.existsSync(path17.join(loc.taskDir, k.decisionFile))) {
3442
3511
  k.error = `runtime completed but ${k.decisionFile} was not written`;
3443
3512
  }
@@ -3467,7 +3536,7 @@ async function consolidate(opts) {
3467
3536
  if (proposalFiles.length === 0) {
3468
3537
  kicks.push({
3469
3538
  issue: issue2.file,
3470
- runtimeId: "",
3539
+ agentRunId: "",
3471
3540
  sessionName: "",
3472
3541
  consolidatedFile: consolidatedFileName(issue2),
3473
3542
  error: `no DECISION-${issue2.index}-<agent>.md proposals found. Run \`task0 issue propose\` first.`
@@ -3486,28 +3555,28 @@ async function consolidate(opts) {
3486
3555
  prompt
3487
3556
  }
3488
3557
  );
3489
- const runtimeId = resp.runtime.id;
3558
+ const agentRunId = resp.runtime.id;
3490
3559
  await updateWorkflow(loc.taskYml, {
3491
3560
  decisions: {
3492
3561
  [issue2.file]: {
3493
3562
  consolidated_file: consolidatedFile,
3494
- consolidate_runtime: runtimeId
3563
+ consolidate_agent_run: agentRunId
3495
3564
  }
3496
3565
  }
3497
3566
  });
3498
3567
  const kick = {
3499
3568
  issue: issue2.file,
3500
- runtimeId,
3569
+ agentRunId,
3501
3570
  sessionName: "",
3502
3571
  consolidatedFile,
3503
3572
  error: null
3504
3573
  };
3505
3574
  if (opts.wait) {
3506
3575
  try {
3507
- const final = await waitForRuntime(runtimeId);
3576
+ const final = await waitForAgentRun(agentRunId);
3508
3577
  const wrote = fs18.existsSync(path17.join(loc.taskDir, consolidatedFile));
3509
3578
  if (final.status !== "done") {
3510
- kick.error = final.error || `runtime ${runtimeId} ended with ${final.status}`;
3579
+ kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
3511
3580
  } else if (!wrote) {
3512
3581
  kick.error = `runtime completed but ${consolidatedFile} was not written`;
3513
3582
  }
@@ -3520,7 +3589,7 @@ async function consolidate(opts) {
3520
3589
  } catch (err) {
3521
3590
  kicks.push({
3522
3591
  issue: issue2.file,
3523
- runtimeId: "",
3592
+ agentRunId: "",
3524
3593
  sessionName: "",
3525
3594
  consolidatedFile,
3526
3595
  error: err.message
@@ -3580,7 +3649,7 @@ function buildReferenceFiles(taskDir, issue2) {
3580
3649
  }
3581
3650
 
3582
3651
  // src/commands/issue.ts
3583
- var issue = new Command18("issue").description(
3652
+ var issue = new Command17("issue").description(
3584
3653
  "Resolve blocking ISSUE Open Questions via multi-agent decision proposals"
3585
3654
  );
3586
3655
  issue.command("propose <taskId>").description(`Fan out agents to propose decisions for the task's blocking "## Open Questions"`).option("--issue <file>", "Specific ISSUE file (ISSUE-NN, NN, or ISSUE-NN.md); defaults to all blocking ISSUEs").option("-a, --agents <list>", "Comma-separated agents", "codex,claude-code").option("--additional-prompt <text>", "Extra prompt content appended to the default propose prompt").option("--model <id>", "Model id or alias (only valid with a single agent)").option("--effort <level>", "Reasoning effort (only valid with a single agent)").option("--wait", "Wait for each runtime to finish").option("--force", "Overwrite existing DECISION files").option("--if-needed", "Idempotent: skip issues with no Open Questions and agents whose DECISION file already exists (exit 0)").option("--json", "Output JSON").action(async (taskId, opts) => {
@@ -3596,7 +3665,7 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
3596
3665
  wait: opts.wait,
3597
3666
  force: opts.force,
3598
3667
  ifNeeded: opts.ifNeeded,
3599
- warn: opts.json ? void 0 : (m) => console.error(chalk18.dim(m))
3668
+ warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
3600
3669
  });
3601
3670
  if (opts.json) {
3602
3671
  console.log(JSON.stringify(result, null, 2));
@@ -3605,26 +3674,26 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
3605
3674
  return;
3606
3675
  }
3607
3676
  if (opts.ifNeeded && result.issues.length === 0) {
3608
- console.log(chalk18.dim("no blocking Open Questions; nothing to propose"));
3677
+ console.log(chalk17.dim("no blocking Open Questions; nothing to propose"));
3609
3678
  return;
3610
3679
  }
3611
3680
  let anyFailed = false;
3612
3681
  for (const item of result.issues) {
3613
- console.log(chalk18.bold(item.issue));
3682
+ console.log(chalk17.bold(item.issue));
3614
3683
  for (const k of item.kicks) {
3615
3684
  if (k.error) {
3616
3685
  anyFailed = true;
3617
- console.error(chalk18.red(` [${k.agent}] ${k.error}`));
3686
+ console.error(chalk17.red(` [${k.agent}] ${k.error}`));
3618
3687
  } else if (k.skipped === "already-exists") {
3619
- console.log(chalk18.dim(` [${k.agent}] skipped (${k.decisionFile} exists)`));
3688
+ console.log(chalk17.dim(` [${k.agent}] skipped (${k.decisionFile} exists)`));
3620
3689
  } else {
3621
- console.log(chalk18.green(` [${k.agent}]`) + ` runtime ${k.runtimeId} \u2192 ${k.decisionFile}`);
3690
+ console.log(chalk17.green(` [${k.agent}]`) + ` runtime ${k.agentRunId} \u2192 ${k.decisionFile}`);
3622
3691
  }
3623
3692
  }
3624
3693
  }
3625
3694
  if (anyFailed) process.exit(1);
3626
3695
  } catch (err) {
3627
- console.error(chalk18.red(err.message));
3696
+ console.error(chalk17.red(err.message));
3628
3697
  process.exit(1);
3629
3698
  }
3630
3699
  });
@@ -3637,7 +3706,7 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
3637
3706
  model: opts.model,
3638
3707
  effort: opts.effort,
3639
3708
  wait: opts.wait,
3640
- warn: opts.json ? void 0 : (m) => console.error(chalk18.dim(m))
3709
+ warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
3641
3710
  });
3642
3711
  if (opts.json) {
3643
3712
  console.log(JSON.stringify(result, null, 2));
@@ -3649,16 +3718,16 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
3649
3718
  for (const k of result.kicks) {
3650
3719
  if (k.error) {
3651
3720
  anyFailed = true;
3652
- console.error(chalk18.red(`[${k.issue}] ${k.error}`));
3721
+ console.error(chalk17.red(`[${k.issue}] ${k.error}`));
3653
3722
  } else {
3654
3723
  console.log(
3655
- chalk18.green(`[${k.issue}]`) + ` runtime ${k.runtimeId} \u2192 ${k.consolidatedFile}` + (k.wrote === false ? chalk18.yellow(" (pending write)") : "")
3724
+ chalk17.green(`[${k.issue}]`) + ` runtime ${k.agentRunId} \u2192 ${k.consolidatedFile}` + (k.wrote === false ? chalk17.yellow(" (pending write)") : "")
3656
3725
  );
3657
3726
  }
3658
3727
  }
3659
3728
  if (anyFailed) process.exit(1);
3660
3729
  } catch (err) {
3661
- console.error(chalk18.red(err.message));
3730
+ console.error(chalk17.red(err.message));
3662
3731
  process.exit(1);
3663
3732
  }
3664
3733
  });
@@ -3670,10 +3739,10 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
3670
3739
  return;
3671
3740
  }
3672
3741
  for (const u of result.updated) {
3673
- console.log(chalk18.green(`approved ${u.issueFile}`) + ` (${u.questionsReplaced} decisions \u2190 ${u.consolidatedFile})`);
3742
+ console.log(chalk17.green(`approved ${u.issueFile}`) + ` (${u.questionsReplaced} decisions \u2190 ${u.consolidatedFile})`);
3674
3743
  }
3675
3744
  } catch (err) {
3676
- console.error(chalk18.red(err.message));
3745
+ console.error(chalk17.red(err.message));
3677
3746
  process.exit(1);
3678
3747
  }
3679
3748
  });
@@ -3681,23 +3750,37 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
3681
3750
  // src/commands/agent.ts
3682
3751
  import fs19 from "fs";
3683
3752
  import path18 from "path";
3684
- import { spawnSync as spawnSync4 } from "child_process";
3685
- import { Command as Command19 } from "commander";
3686
- import chalk19 from "chalk";
3687
- import yaml6 from "js-yaml";
3753
+ import { spawnSync as spawnSync5 } from "child_process";
3754
+ import { Command as Command18 } from "commander";
3755
+ import chalk18 from "chalk";
3756
+ import yaml7 from "js-yaml";
3757
+ function statusBadge(s) {
3758
+ if (s === "working") return chalk18.cyan("\u25CF");
3759
+ if (s === "error") return chalk18.red("\u25CF");
3760
+ if (s === "idle") return chalk18.green("\u25CF");
3761
+ return chalk18.dim("\u25CB");
3762
+ }
3763
+ function formatDuration(ms) {
3764
+ if (ms === null || !Number.isFinite(ms)) return "\u2014";
3765
+ if (ms < 1e3) return `${ms}ms`;
3766
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
3767
+ if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
3768
+ return `${(ms / 36e5).toFixed(1)}h`;
3769
+ }
3688
3770
  function fail5(message, code = 1) {
3689
- console.error(chalk19.red(message));
3771
+ console.error(chalk18.red(message));
3690
3772
  process.exit(code);
3691
3773
  }
3692
3774
  function formatAgent(a, withDetails = false) {
3693
- const tag = a.system ? chalk19.dim("(system)") : a.scope ? chalk19.dim(`(${a.scope})`) : "";
3694
- const head = `${chalk19.bold(a.slug.padEnd(24))} ${chalk19.dim(a.object_id.padEnd(16))} ${a.kind.padEnd(10)} ${tag}`;
3775
+ const tag = a.system ? chalk18.dim("(system)") : a.scope ? chalk18.dim(`(${a.scope})`) : "";
3776
+ const dot = statusBadge(a.status);
3777
+ const head = `${dot} ${chalk18.bold(a.slug.padEnd(24))} ${chalk18.dim(a.object_id.padEnd(16))} ${a.kind.padEnd(10)} ${tag}`;
3695
3778
  if (!withDetails) return head;
3696
3779
  const lines = [head];
3697
- if (a.description) lines.push(chalk19.dim(" " + a.description));
3780
+ if (a.description) lines.push(chalk18.dim(" " + a.description));
3698
3781
  return lines.join("\n");
3699
3782
  }
3700
- var agent = new Command19("agent").description("Manage agents and run them against tasks");
3783
+ var agent = new Command18("agent").description("Manage agents and run them against tasks");
3701
3784
  agent.command("list").description("List visible agents (project + user + built-in cascade)").option("--kind <kind>", "Filter by kind: coding | llm-api | workflow").option("--include-system", "Include built-in system agents (default true)").option("--json", "Output JSON").action(async (opts) => {
3702
3785
  try {
3703
3786
  const params = new URLSearchParams();
@@ -3707,7 +3790,7 @@ agent.command("list").description("List visible agents (project + user + built-i
3707
3790
  const result = await api.get(`/api/agents${qs}`);
3708
3791
  if (opts.json) return console.log(JSON.stringify(result.agents, null, 2));
3709
3792
  if (!result.agents.length) {
3710
- console.log(chalk19.dim("(no agents)"));
3793
+ console.log(chalk18.dim("(no agents)"));
3711
3794
  return;
3712
3795
  }
3713
3796
  for (const a of result.agents) console.log(formatAgent(a, true));
@@ -3718,11 +3801,23 @@ agent.command("list").description("List visible agents (project + user + built-i
3718
3801
  agent.command("get <ref>").description("Show one agent by object_id or slug").option("--json", "Output JSON").action(async (ref, opts) => {
3719
3802
  try {
3720
3803
  const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
3721
- if (opts.json) return console.log(JSON.stringify(result.agent, null, 2));
3804
+ if (opts.json) return console.log(JSON.stringify(result, null, 2));
3722
3805
  console.log(formatAgent(result.agent, true));
3806
+ const limit = result.agent.max_concurrent_tasks;
3807
+ const inFlight = result.in_flight ?? 0;
3808
+ if (limit !== void 0) {
3809
+ const at = inFlight >= limit ? chalk18.yellow : chalk18.dim;
3810
+ console.log(at(` concurrency: ${inFlight}/${limit}`));
3811
+ } else if (inFlight > 0) {
3812
+ console.log(chalk18.dim(` concurrency: ${inFlight}/\u221E`));
3813
+ }
3723
3814
  console.log();
3724
- console.log(chalk19.dim("--- spec ---"));
3725
- console.log(yaml6.dump(result.agent.spec, { lineWidth: 100 }));
3815
+ console.log(chalk18.dim("--- spec ---"));
3816
+ console.log(yaml7.dump(result.agent.spec, { lineWidth: 100 }));
3817
+ if (result.agent.mcp_config) {
3818
+ console.log(chalk18.dim("--- mcp_config ---"));
3819
+ console.log(yaml7.dump(result.agent.mcp_config, { lineWidth: 100 }));
3820
+ }
3726
3821
  } catch (err) {
3727
3822
  const apiErr = err;
3728
3823
  if (apiErr.status === 404) fail5(`not found: ${ref}`);
@@ -3733,14 +3828,14 @@ agent.command("create").description("Create an agent from a YAML spec file").req
3733
3828
  let parsed;
3734
3829
  try {
3735
3830
  const raw = fs19.readFileSync(path18.resolve(opts.fromFile), "utf-8");
3736
- parsed = yaml6.load(raw);
3831
+ parsed = yaml7.load(raw);
3737
3832
  } catch (err) {
3738
3833
  fail5(`cannot read ${opts.fromFile}: ${err.message}`);
3739
3834
  }
3740
3835
  try {
3741
3836
  const body = { ...parsed, scope: opts.scope, project_root: opts.projectRoot };
3742
3837
  const result = await api.post("/api/agents", body);
3743
- console.log(chalk19.green(`created ${result.agent.slug} (${result.agent.object_id})`));
3838
+ console.log(chalk18.green(`created ${result.agent.slug} (${result.agent.object_id})`));
3744
3839
  } catch (err) {
3745
3840
  fail5(err.message);
3746
3841
  }
@@ -3753,14 +3848,14 @@ agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save
3753
3848
  process.env.TMPDIR || "/tmp",
3754
3849
  `task0-agent-${result.agent.slug}-${Date.now()}.yml`
3755
3850
  );
3756
- fs19.writeFileSync(tmp, yaml6.dump(result.agent, { lineWidth: 100 }), "utf-8");
3851
+ fs19.writeFileSync(tmp, yaml7.dump(result.agent, { lineWidth: 100 }), "utf-8");
3757
3852
  const editor = process.env.EDITOR || "vi";
3758
- const r = spawnSync4(editor, [tmp], { stdio: "inherit" });
3853
+ const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
3759
3854
  if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
3760
- const updated = yaml6.load(fs19.readFileSync(tmp, "utf-8"));
3855
+ const updated = yaml7.load(fs19.readFileSync(tmp, "utf-8"));
3761
3856
  await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
3762
3857
  fs19.unlinkSync(tmp);
3763
- console.log(chalk19.green(`updated ${ref}`));
3858
+ console.log(chalk18.green(`updated ${ref}`));
3764
3859
  } catch (err) {
3765
3860
  fail5(err.message);
3766
3861
  }
@@ -3769,8 +3864,42 @@ agent.command("delete <ref>").description("Delete an agent").action(async (ref)
3769
3864
  try {
3770
3865
  const result = await api.del(`/api/agents/${encodeURIComponent(ref)}`);
3771
3866
  if (!result.deleted) fail5(`not deleted: ${ref}`);
3772
- console.log(chalk19.green(`deleted ${ref}`));
3867
+ console.log(chalk18.green(`deleted ${ref}`));
3868
+ } catch (err) {
3869
+ fail5(err.message);
3870
+ }
3871
+ });
3872
+ agent.command("activity <ref>").description("Show recent activity (runs, status, duration) for an agent").option("--days <n>", "Time window in days (default 30)", "30").option("--limit <n>", "Limit recent rows shown (default 10)", "10").option("--json", "Output JSON").action(async (ref, opts) => {
3873
+ try {
3874
+ const days = encodeURIComponent(opts.days);
3875
+ const result = await api.get(`/api/agents/${encodeURIComponent(ref)}/activity?days=${days}`);
3876
+ if (opts.json) return console.log(JSON.stringify(result, null, 2));
3877
+ const a = result.agent;
3878
+ const limitStr = a.max_concurrent_tasks === null ? "\u221E" : String(a.max_concurrent_tasks);
3879
+ console.log(`${statusBadge(result.status)} ${chalk18.bold(a.slug)} ${chalk18.dim(a.object_id)} ${a.kind}`);
3880
+ console.log(chalk18.dim(` concurrency: ${result.in_flight}/${limitStr} status: ${result.status}`));
3881
+ console.log();
3882
+ console.log(chalk18.dim(`--- last ${result.range_days}d ---`));
3883
+ console.log(
3884
+ ` total: ${result.runs_total} ${chalk18.green("ok")}: ${result.runs_success} ${chalk18.red("err")}: ${result.runs_failed} ${chalk18.cyan("running")}: ${result.runs_running} avg: ${formatDuration(result.avg_duration_ms)} last: ${result.last_run_at ?? "\u2014"}`
3885
+ );
3886
+ const limit = Math.max(1, Number(opts.limit) || 10);
3887
+ const recent = result.recent.slice(0, limit);
3888
+ if (recent.length === 0) {
3889
+ console.log(chalk18.dim(" (no recent runs)"));
3890
+ return;
3891
+ }
3892
+ console.log();
3893
+ console.log(chalk18.dim("--- recent ---"));
3894
+ for (const r of recent) {
3895
+ const dotStatus = r.status === "done" ? "idle" : r.status === "error" ? "error" : "working";
3896
+ console.log(
3897
+ ` ${statusBadge(dotStatus)} ${r.started_at} ${r.status.padEnd(8)} ${formatDuration(r.duration_ms).padStart(7)} ${chalk18.dim(r.type)} ${chalk18.dim(r.task_id)}`
3898
+ );
3899
+ }
3773
3900
  } catch (err) {
3901
+ const apiErr = err;
3902
+ if (apiErr.status === 404) fail5(`not found: ${ref}`);
3774
3903
  fail5(err.message);
3775
3904
  }
3776
3905
  });
@@ -3780,27 +3909,54 @@ agent.command("run <ref>").description("Run an agent against a task with a user
3780
3909
  `/api/agents/${encodeURIComponent(ref)}/run`,
3781
3910
  { task_id: opts.task, prompt: opts.prompt }
3782
3911
  );
3783
- console.log(chalk19.green(`runtime ${result.runtime.id} started`));
3784
- if (result.runtime.objectId) console.log(chalk19.dim(`object_id: ${result.runtime.objectId}`));
3912
+ console.log(chalk18.green(`runtime ${result.runtime.id} started`));
3913
+ if (result.runtime.objectId) console.log(chalk18.dim(`object_id: ${result.runtime.objectId}`));
3785
3914
  if (opts.stream) {
3786
3915
  await streamOutput(ref, result.runtime.id);
3787
3916
  }
3788
3917
  if (opts.attach) {
3789
- console.log(chalk19.dim("--attach not yet wired; runtime is visible in dashboard"));
3918
+ console.log(chalk18.dim("--attach not yet wired; runtime is visible in dashboard"));
3790
3919
  }
3791
3920
  } catch (err) {
3792
3921
  fail5(err.message);
3793
3922
  }
3794
3923
  });
3795
- async function streamOutput(ref, runtimeId) {
3924
+ var mcp = new Command18("mcp").description("Manage per-agent MCP config (mcp_config field)");
3925
+ mcp.command("set <ref>").description("Set mcp_config from a JSON file (replaces existing config)").requiredOption("--from-file <file>", "JSON file containing the mcp_config object").action(async (ref, opts) => {
3926
+ let parsed;
3927
+ try {
3928
+ parsed = JSON.parse(fs19.readFileSync(path18.resolve(opts.fromFile), "utf-8"));
3929
+ } catch (err) {
3930
+ fail5(`cannot parse ${opts.fromFile}: ${err.message}`);
3931
+ }
3932
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3933
+ fail5("mcp_config must be a JSON object");
3934
+ }
3935
+ try {
3936
+ await api.put(`/api/agents/${encodeURIComponent(ref)}`, { mcp_config: parsed });
3937
+ console.log(chalk18.green(`mcp_config set on ${ref}`));
3938
+ } catch (err) {
3939
+ fail5(err.message);
3940
+ }
3941
+ });
3942
+ mcp.command("clear <ref>").description("Remove mcp_config from an agent").action(async (ref) => {
3943
+ try {
3944
+ await api.put(`/api/agents/${encodeURIComponent(ref)}`, { mcp_config: null });
3945
+ console.log(chalk18.green(`mcp_config cleared on ${ref}`));
3946
+ } catch (err) {
3947
+ fail5(err.message);
3948
+ }
3949
+ });
3950
+ agent.addCommand(mcp);
3951
+ async function streamOutput(ref, agentRunId) {
3796
3952
  let lastCount = 0;
3797
3953
  for (let i = 0; i < 600; i++) {
3798
3954
  const result = await api.get(
3799
- `/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(runtimeId)}/output`
3955
+ `/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(agentRunId)}/output`
3800
3956
  );
3801
3957
  for (const line of result.lines.slice(lastCount)) {
3802
3958
  if (line.error?.message) {
3803
- console.error(chalk19.red(`[error] ${line.error.message}`));
3959
+ console.error(chalk18.red(`[error] ${line.error.message}`));
3804
3960
  return;
3805
3961
  }
3806
3962
  const text = line.message?.content?.map((c) => c.text ?? "").join("") ?? "";
@@ -3812,47 +3968,115 @@ async function streamOutput(ref, runtimeId) {
3812
3968
  }
3813
3969
 
3814
3970
  // src/commands/daemon.ts
3815
- import os6 from "os";
3816
- import { Command as Command20 } from "commander";
3817
- import chalk20 from "chalk";
3971
+ import os7 from "os";
3972
+ import { Command as Command19 } from "commander";
3973
+ import chalk19 from "chalk";
3818
3974
  import WebSocket from "ws";
3819
3975
 
3820
- // src/core/daemon-config.ts
3976
+ // src/core/admin-token.ts
3821
3977
  import fs20 from "fs";
3822
3978
  import os4 from "os";
3823
3979
  import path19 from "path";
3824
3980
  var CONFIG_DIR2 = path19.join(os4.homedir(), ".config", "task0");
3825
- var CONFIG_FILE2 = path19.join(CONFIG_DIR2, "daemon.json");
3981
+ var TOKEN_FILE = path19.join(CONFIG_DIR2, "admin.token");
3982
+ var cached = null;
3983
+ var AdminTokenUnavailableError = class extends Error {
3984
+ constructor() {
3985
+ super(
3986
+ `Admin token not found.
3987
+ \u2022 If the task0 server runs on this host, run the task0-server binary once \u2014 the token will be generated at ${TOKEN_FILE}.
3988
+ \u2022 Otherwise, copy the token from the server host and set TASK0_ADMIN_TOKEN.`
3989
+ );
3990
+ this.name = "AdminTokenUnavailableError";
3991
+ }
3992
+ };
3993
+ function readAdminToken() {
3994
+ if (cached) return cached;
3995
+ const fromEnv = process.env.TASK0_ADMIN_TOKEN?.trim();
3996
+ if (fromEnv) {
3997
+ cached = fromEnv;
3998
+ return cached;
3999
+ }
4000
+ if (fs20.existsSync(TOKEN_FILE)) {
4001
+ const v = fs20.readFileSync(TOKEN_FILE, "utf-8").trim();
4002
+ if (v) {
4003
+ cached = v;
4004
+ return cached;
4005
+ }
4006
+ }
4007
+ throw new AdminTokenUnavailableError();
4008
+ }
4009
+ function adminAuthHeader() {
4010
+ return { authorization: `Bearer ${readAdminToken()}` };
4011
+ }
4012
+
4013
+ // src/core/daemon-config.ts
4014
+ import fs21 from "fs";
4015
+ import os5 from "os";
4016
+ import path20 from "path";
4017
+ var CONFIG_DIR3 = path20.join(os5.homedir(), ".config", "task0");
4018
+ var CONFIG_FILE2 = path20.join(CONFIG_DIR3, "daemon.json");
3826
4019
  function daemonConfigPath() {
3827
4020
  return CONFIG_FILE2;
3828
4021
  }
3829
4022
  function readDaemonIdentity() {
3830
- if (!fs20.existsSync(CONFIG_FILE2)) return null;
4023
+ if (!fs21.existsSync(CONFIG_FILE2)) return null;
3831
4024
  try {
3832
- const raw = fs20.readFileSync(CONFIG_FILE2, "utf-8");
4025
+ const raw = fs21.readFileSync(CONFIG_FILE2, "utf-8");
3833
4026
  return JSON.parse(raw);
3834
4027
  } catch {
3835
4028
  return null;
3836
4029
  }
3837
4030
  }
3838
4031
  function writeDaemonIdentity(identity) {
3839
- fs20.mkdirSync(CONFIG_DIR2, { recursive: true });
3840
- fs20.writeFileSync(CONFIG_FILE2, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
4032
+ fs21.mkdirSync(CONFIG_DIR3, { recursive: true });
4033
+ fs21.writeFileSync(CONFIG_FILE2, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
3841
4034
  try {
3842
- fs20.chmodSync(CONFIG_FILE2, 384);
4035
+ fs21.chmodSync(CONFIG_FILE2, 384);
3843
4036
  } catch {
3844
4037
  }
3845
4038
  }
3846
4039
  function clearDaemonIdentity() {
3847
- if (!fs20.existsSync(CONFIG_FILE2)) return false;
3848
- fs20.unlinkSync(CONFIG_FILE2);
4040
+ if (!fs21.existsSync(CONFIG_FILE2)) return false;
4041
+ fs21.unlinkSync(CONFIG_FILE2);
3849
4042
  return true;
3850
4043
  }
3851
4044
 
4045
+ // src/core/register-auth.ts
4046
+ var RegisterAuthUnavailableError = class extends Error {
4047
+ constructor() {
4048
+ super(
4049
+ `No registration credential available.
4050
+ \u2022 Pass --token <apit_...> with an API token created in the dashboard, or
4051
+ \u2022 Set TASK0_API_TOKEN in the environment, or
4052
+ \u2022 Fall back to the server's admin token (TASK0_ADMIN_TOKEN or ~/.config/task0/admin.token).`
4053
+ );
4054
+ this.name = "RegisterAuthUnavailableError";
4055
+ }
4056
+ };
4057
+ function pickRegisterAuth(flagToken) {
4058
+ const fromFlag = flagToken?.trim();
4059
+ if (fromFlag) {
4060
+ return { header: { authorization: `Bearer ${fromFlag}` }, source: "flag" };
4061
+ }
4062
+ const fromEnv = process.env.TASK0_API_TOKEN?.trim();
4063
+ if (fromEnv) {
4064
+ return { header: { authorization: `Bearer ${fromEnv}` }, source: "env" };
4065
+ }
4066
+ try {
4067
+ return { header: { authorization: `Bearer ${readAdminToken()}` }, source: "admin" };
4068
+ } catch (error2) {
4069
+ if (error2 instanceof AdminTokenUnavailableError) {
4070
+ throw new RegisterAuthUnavailableError();
4071
+ }
4072
+ throw error2;
4073
+ }
4074
+ }
4075
+
3852
4076
  // src/core/daemon-rpc-handlers.ts
3853
4077
  init_node();
3854
- import fs21 from "fs";
3855
- import path20 from "path";
4078
+ import fs22 from "fs";
4079
+ import path21 from "path";
3856
4080
  var MAX_FILE_BYTES = 1 * 1024 * 1024;
3857
4081
  function ensureString(value, name) {
3858
4082
  if (typeof value !== "string" || value.length === 0) {
@@ -3865,7 +4089,7 @@ var rpcHandlers = {
3865
4089
  // in-process server's /api/tasks scanProject() returns.
3866
4090
  async scan_project(params) {
3867
4091
  const rootPath = ensureString(params.rootPath, "rootPath");
3868
- const name = typeof params.name === "string" && params.name ? params.name : path20.basename(rootPath);
4092
+ const name = typeof params.name === "string" && params.name ? params.name : path21.basename(rootPath);
3869
4093
  return scanProject(rootPath, name);
3870
4094
  },
3871
4095
  // Read a file from disk on the daemon's host. The dashboard uses this to
@@ -3875,7 +4099,7 @@ var rpcHandlers = {
3875
4099
  const filePath = ensureString(params.path, "path");
3876
4100
  let stat;
3877
4101
  try {
3878
- stat = fs21.statSync(filePath);
4102
+ stat = fs22.statSync(filePath);
3879
4103
  } catch {
3880
4104
  throw Object.assign(new Error("file not found"), { code: "not_found" });
3881
4105
  }
@@ -3885,23 +4109,23 @@ var rpcHandlers = {
3885
4109
  if (stat.size > MAX_FILE_BYTES) {
3886
4110
  throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
3887
4111
  }
3888
- const content = fs21.readFileSync(filePath, "utf-8");
4112
+ const content = fs22.readFileSync(filePath, "utf-8");
3889
4113
  return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
3890
4114
  }
3891
4115
  };
3892
4116
 
3893
4117
  // src/core/daemon-service/launchd.ts
3894
- import { spawnSync as spawnSync5 } from "child_process";
3895
- import fs23 from "fs";
3896
- import path22 from "path";
4118
+ import { spawnSync as spawnSync6 } from "child_process";
4119
+ import fs24 from "fs";
4120
+ import path23 from "path";
3897
4121
 
3898
4122
  // src/core/daemon-service/binary.ts
3899
- import fs22 from "fs";
4123
+ import fs23 from "fs";
3900
4124
  import { fileURLToPath } from "url";
3901
4125
  function resolveTask0Invocation() {
3902
4126
  const node = process.execPath;
3903
4127
  const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
3904
- const main2 = fs22.realpathSync(argv1);
4128
+ const main2 = fs23.realpathSync(argv1);
3905
4129
  if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
3906
4130
  throw new Error(
3907
4131
  `Refusing to install autostart service pointing at ${main2}.
@@ -3917,8 +4141,8 @@ function isInstalledBuild(p) {
3917
4141
  }
3918
4142
 
3919
4143
  // src/core/daemon-service/paths.ts
3920
- import os5 from "os";
3921
- import path21 from "path";
4144
+ import os6 from "os";
4145
+ import path22 from "path";
3922
4146
 
3923
4147
  // src/core/daemon-service/types.ts
3924
4148
  var SERVICE_LABEL = "cc.cy0.task0";
@@ -3927,21 +4151,21 @@ var SERVICE_LABEL = "cc.cy0.task0";
3927
4151
  function unitPath(scope) {
3928
4152
  const platform = process.platform;
3929
4153
  if (platform === "darwin") {
3930
- return scope === "user" ? path21.join(os5.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) : path21.join("/", "Library", "LaunchDaemons", `${SERVICE_LABEL}.plist`);
4154
+ return scope === "user" ? path22.join(os6.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) : path22.join("/", "Library", "LaunchDaemons", `${SERVICE_LABEL}.plist`);
3931
4155
  }
3932
4156
  if (platform === "linux") {
3933
- return scope === "user" ? path21.join(os5.homedir(), ".config", "systemd", "user", `${SERVICE_LABEL}.service`) : path21.join("/", "etc", "systemd", "system", `${SERVICE_LABEL}.service`);
4157
+ return scope === "user" ? path22.join(os6.homedir(), ".config", "systemd", "user", `${SERVICE_LABEL}.service`) : path22.join("/", "etc", "systemd", "system", `${SERVICE_LABEL}.service`);
3934
4158
  }
3935
4159
  throw new Error(`Unsupported platform for service install: ${platform}`);
3936
4160
  }
3937
4161
  function logDir() {
3938
- return path21.join(os5.homedir(), ".task0", "logs");
4162
+ return path22.join(os6.homedir(), ".task0", "logs");
3939
4163
  }
3940
4164
  function logPaths() {
3941
4165
  const dir = logDir();
3942
4166
  return {
3943
- out: path21.join(dir, "daemon.out.log"),
3944
- err: path21.join(dir, "daemon.err.log")
4167
+ out: path22.join(dir, "daemon.out.log"),
4168
+ err: path22.join(dir, "daemon.err.log")
3945
4169
  };
3946
4170
  }
3947
4171
 
@@ -3994,7 +4218,7 @@ function serviceTarget(scope) {
3994
4218
  return `${domainTarget(scope)}/${SERVICE_LABEL}`;
3995
4219
  }
3996
4220
  function run2(cmd, args) {
3997
- const res = spawnSync5(cmd, args, { encoding: "utf-8" });
4221
+ const res = spawnSync6(cmd, args, { encoding: "utf-8" });
3998
4222
  return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
3999
4223
  }
4000
4224
  function createLaunchdManager(scope) {
@@ -4002,8 +4226,8 @@ function createLaunchdManager(scope) {
4002
4226
  const logs = logPaths();
4003
4227
  async function install() {
4004
4228
  const inv = resolveTask0Invocation();
4005
- fs23.mkdirSync(logDir(), { recursive: true });
4006
- fs23.mkdirSync(path22.dirname(file), { recursive: true });
4229
+ fs24.mkdirSync(logDir(), { recursive: true });
4230
+ fs24.mkdirSync(path23.dirname(file), { recursive: true });
4007
4231
  const body = renderPlist({
4008
4232
  node: inv.node,
4009
4233
  main: inv.main,
@@ -4012,7 +4236,7 @@ function createLaunchdManager(scope) {
4012
4236
  out: logs.out,
4013
4237
  err: logs.err
4014
4238
  });
4015
- fs23.writeFileSync(file, body, { mode: 420 });
4239
+ fs24.writeFileSync(file, body, { mode: 420 });
4016
4240
  const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
4017
4241
  if (bootstrap.code !== 0) {
4018
4242
  const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
@@ -4038,9 +4262,9 @@ function createLaunchdManager(scope) {
4038
4262
  }
4039
4263
  async function uninstall() {
4040
4264
  run2("launchctl", ["bootout", serviceTarget(scope)]);
4041
- if (fs23.existsSync(file)) {
4265
+ if (fs24.existsSync(file)) {
4042
4266
  run2("launchctl", ["unload", file]);
4043
- fs23.unlinkSync(file);
4267
+ fs24.unlinkSync(file);
4044
4268
  }
4045
4269
  }
4046
4270
  async function start() {
@@ -4059,7 +4283,7 @@ function createLaunchdManager(scope) {
4059
4283
  }
4060
4284
  }
4061
4285
  async function status() {
4062
- if (!fs23.existsSync(file)) return "absent";
4286
+ if (!fs24.existsSync(file)) return "absent";
4063
4287
  const printed = run2("launchctl", ["print", serviceTarget(scope)]);
4064
4288
  if (printed.code !== 0) return "installed";
4065
4289
  const out = printed.stdout;
@@ -4081,9 +4305,9 @@ function createLaunchdManager(scope) {
4081
4305
  }
4082
4306
 
4083
4307
  // src/core/daemon-service/systemd.ts
4084
- import { spawnSync as spawnSync6 } from "child_process";
4085
- import fs24 from "fs";
4086
- import path23 from "path";
4308
+ import { spawnSync as spawnSync7 } from "child_process";
4309
+ import fs25 from "fs";
4310
+ import path24 from "path";
4087
4311
  function shellEscape(s) {
4088
4312
  if (!/[\s"\\$]/.test(s)) return s;
4089
4313
  return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
@@ -4113,7 +4337,7 @@ function scopeFlag(scope) {
4113
4337
  return scope === "user" ? ["--user"] : [];
4114
4338
  }
4115
4339
  function run3(cmd, args) {
4116
- const res = spawnSync6(cmd, args, { encoding: "utf-8" });
4340
+ const res = spawnSync7(cmd, args, { encoding: "utf-8" });
4117
4341
  return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
4118
4342
  }
4119
4343
  function createSystemdManager(scope) {
@@ -4122,8 +4346,8 @@ function createSystemdManager(scope) {
4122
4346
  const unitName = `${SERVICE_LABEL}.service`;
4123
4347
  async function install() {
4124
4348
  const inv = resolveTask0Invocation();
4125
- fs24.mkdirSync(logDir(), { recursive: true });
4126
- fs24.mkdirSync(path23.dirname(file), { recursive: true });
4349
+ fs25.mkdirSync(logDir(), { recursive: true });
4350
+ fs25.mkdirSync(path24.dirname(file), { recursive: true });
4127
4351
  const body = renderUnit({
4128
4352
  node: inv.node,
4129
4353
  main: inv.main,
@@ -4132,7 +4356,7 @@ function createSystemdManager(scope) {
4132
4356
  err: logs.err,
4133
4357
  scope
4134
4358
  });
4135
- fs24.writeFileSync(file, body, { mode: 420 });
4359
+ fs25.writeFileSync(file, body, { mode: 420 });
4136
4360
  const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
4137
4361
  if (reload.code !== 0) {
4138
4362
  throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
@@ -4141,8 +4365,8 @@ function createSystemdManager(scope) {
4141
4365
  }
4142
4366
  async function uninstall() {
4143
4367
  run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
4144
- if (fs24.existsSync(file)) {
4145
- fs24.unlinkSync(file);
4368
+ if (fs25.existsSync(file)) {
4369
+ fs25.unlinkSync(file);
4146
4370
  }
4147
4371
  run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
4148
4372
  }
@@ -4159,7 +4383,7 @@ function createSystemdManager(scope) {
4159
4383
  }
4160
4384
  }
4161
4385
  async function status() {
4162
- if (!fs24.existsSync(file)) return "absent";
4386
+ if (!fs25.existsSync(file)) return "absent";
4163
4387
  const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
4164
4388
  const out = res.stdout.trim();
4165
4389
  if (out === "active") return "running";
@@ -4215,7 +4439,7 @@ function sendRpc(ws, payload) {
4215
4439
  }
4216
4440
  }
4217
4441
  function fail6(message, code = 1) {
4218
- console.error(chalk20.red(message));
4442
+ console.error(chalk19.red(message));
4219
4443
  process.exit(code);
4220
4444
  }
4221
4445
  function loadRequiredIdentity() {
@@ -4229,10 +4453,18 @@ function serverBase(identity) {
4229
4453
  if (identity) return identity.server_url.replace(/\/$/, "");
4230
4454
  return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
4231
4455
  }
4456
+ function adminHeaders() {
4457
+ try {
4458
+ return adminAuthHeader();
4459
+ } catch (error2) {
4460
+ if (error2 instanceof AdminTokenUnavailableError) fail6(error2.message);
4461
+ throw error2;
4462
+ }
4463
+ }
4232
4464
  async function jsonGet(url) {
4233
4465
  let res;
4234
4466
  try {
4235
- res = await fetch(url);
4467
+ res = await fetch(url, { headers: adminHeaders() });
4236
4468
  } catch (error2) {
4237
4469
  fail6(`Cannot reach ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
4238
4470
  }
@@ -4245,16 +4477,16 @@ function requireRootIfSystem(scope, rerunHint) {
4245
4477
  if (scope !== "system") return;
4246
4478
  const uid = process.getuid?.();
4247
4479
  if (uid === 0) return;
4248
- console.error(chalk20.red("--system requires root."));
4480
+ console.error(chalk19.red("--system requires root."));
4249
4481
  console.error("Re-run with sudo, preserving env:");
4250
- console.error(chalk20.cyan(` sudo -E ${rerunHint}`));
4482
+ console.error(chalk19.cyan(` sudo -E ${rerunHint}`));
4251
4483
  process.exit(1);
4252
4484
  }
4253
4485
  function rerunArgv() {
4254
4486
  return ["task0", ...process.argv.slice(2)].join(" ");
4255
4487
  }
4256
- var daemonCmd = new Command20("daemon").description("Manage this host as a task0 daemon registered with a central server");
4257
- daemonCmd.command("register").description("Register this host with a central server, install the autostart service, and start it").requiredOption("-s, --server <url>", "Central server URL (e.g. https://central.example.com:4318)").option("-n, --name <name>", "Display name for this daemon (defaults to hostname)").option("--force", "Overwrite existing identity if present").option("--system", "Install at the system layer (LaunchDaemons / /etc/systemd/system, requires sudo)").option("--no-install", "Skip installing the autostart service unit").option("--no-start", "Install the service unit but do not start it now").action(async (opts) => {
4488
+ var daemonCmd = new Command19("daemon").description("Manage this host as a task0 daemon registered with a central server");
4489
+ daemonCmd.command("register").description("Register this host with a central server, install the autostart service, and start it").requiredOption("-s, --server <url>", "Central server URL (e.g. https://central.example.com:4318)").option("-n, --name <name>", "Display name for this daemon (defaults to hostname)").option("-t, --token <token>", "API token (apit_...) for registration. Falls back to TASK0_API_TOKEN, then admin token.").option("--force", "Overwrite existing identity if present").option("--system", "Install at the system layer (LaunchDaemons / /etc/systemd/system, requires sudo)").option("--no-install", "Skip installing the autostart service unit").option("--no-start", "Install the service unit but do not start it now").action(async (opts) => {
4258
4490
  const scope = opts.system ? "system" : "user";
4259
4491
  if (opts.install) {
4260
4492
  requireRootIfSystem(scope, rerunArgv());
@@ -4264,8 +4496,19 @@ daemonCmd.command("register").description("Register this host with a central ser
4264
4496
  fail6(`Already registered as ${existing.daemon_id}. Pass --force to re-register.`);
4265
4497
  }
4266
4498
  const base = opts.server.replace(/\/$/, "");
4499
+ let authHeader;
4500
+ try {
4501
+ const choice = pickRegisterAuth(opts.token);
4502
+ authHeader = choice.header;
4503
+ if (choice.source === "admin") {
4504
+ console.log(chalk19.dim("Registering with admin token (legacy). Prefer an API token from the dashboard."));
4505
+ }
4506
+ } catch (error2) {
4507
+ if (error2 instanceof RegisterAuthUnavailableError) fail6(error2.message);
4508
+ throw error2;
4509
+ }
4267
4510
  const body = {
4268
- hostname: os6.hostname(),
4511
+ hostname: os7.hostname(),
4269
4512
  platform: process.platform,
4270
4513
  name: opts.name
4271
4514
  };
@@ -4273,7 +4516,7 @@ daemonCmd.command("register").description("Register this host with a central ser
4273
4516
  try {
4274
4517
  res = await fetch(`${base}/api/daemons/register`, {
4275
4518
  method: "POST",
4276
- headers: { "content-type": "application/json" },
4519
+ headers: { "content-type": "application/json", ...authHeader },
4277
4520
  body: JSON.stringify(body)
4278
4521
  });
4279
4522
  } catch (error2) {
@@ -4293,16 +4536,16 @@ daemonCmd.command("register").description("Register this host with a central ser
4293
4536
  registered_at: data.daemon.registered_at
4294
4537
  };
4295
4538
  writeDaemonIdentity(identity);
4296
- console.log(chalk20.green(`Registered as ${data.daemon.object_id} (${data.daemon.name})`));
4539
+ console.log(chalk19.green(`Registered as ${data.daemon.object_id} (${data.daemon.name})`));
4297
4540
  console.log(`Identity saved to ${daemonConfigPath()}`);
4298
4541
  if (!opts.install) {
4299
- console.log(chalk20.dim("Skipping service install (--no-install)."));
4300
- console.log(chalk20.dim(`Run \`task0 daemon run\` to start the WebSocket loop in foreground.`));
4542
+ console.log(chalk19.dim("Skipping service install (--no-install)."));
4543
+ console.log(chalk19.dim(`Run \`task0 daemon run\` to start the WebSocket loop in foreground.`));
4301
4544
  return;
4302
4545
  }
4303
4546
  if (!isPlatformSupported()) {
4304
4547
  console.log(
4305
- chalk20.yellow(
4548
+ chalk19.yellow(
4306
4549
  `Autostart service is not supported on ${process.platform}. Run \`task0 daemon run\` under a supervisor of your choice.`
4307
4550
  )
4308
4551
  );
@@ -4312,18 +4555,18 @@ daemonCmd.command("register").description("Register this host with a central ser
4312
4555
  try {
4313
4556
  const { unitPath: unitPath2 } = await svc.install({ identity });
4314
4557
  const logs = svc.logPaths();
4315
- console.log(chalk20.green(`Installed service unit at ${unitPath2}`));
4316
- console.log(chalk20.dim(`Logs: ${logs.out}`));
4317
- console.log(chalk20.dim(` ${logs.err}`));
4558
+ console.log(chalk19.green(`Installed service unit at ${unitPath2}`));
4559
+ console.log(chalk19.dim(`Logs: ${logs.out}`));
4560
+ console.log(chalk19.dim(` ${logs.err}`));
4318
4561
  if (opts.start) {
4319
4562
  await svc.start();
4320
- console.log(chalk20.green("Service started."));
4563
+ console.log(chalk19.green("Service started."));
4321
4564
  } else {
4322
- console.log(chalk20.dim("Skipping start (--no-start). Run `task0 daemon start` when ready."));
4565
+ console.log(chalk19.dim("Skipping start (--no-start). Run `task0 daemon start` when ready."));
4323
4566
  }
4324
4567
  } catch (error2) {
4325
- console.error(chalk20.red(`Service install failed: ${error2 instanceof Error ? error2.message : String(error2)}`));
4326
- console.error(chalk20.dim("Identity is saved; rerun `task0 daemon register --force` once the issue is resolved."));
4568
+ console.error(chalk19.red(`Service install failed: ${error2 instanceof Error ? error2.message : String(error2)}`));
4569
+ console.error(chalk19.dim("Identity is saved; rerun `task0 daemon register --force` once the issue is resolved."));
4327
4570
  process.exit(1);
4328
4571
  }
4329
4572
  });
@@ -4336,7 +4579,7 @@ daemonCmd.command("start").description("Start the installed autostart service vi
4336
4579
  const svc = getServiceManager(scope);
4337
4580
  try {
4338
4581
  await svc.start();
4339
- console.log(chalk20.green("Service started."));
4582
+ console.log(chalk19.green("Service started."));
4340
4583
  } catch (error2) {
4341
4584
  fail6(error2 instanceof Error ? error2.message : String(error2));
4342
4585
  }
@@ -4350,7 +4593,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
4350
4593
  const svc = getServiceManager(scope);
4351
4594
  try {
4352
4595
  await svc.stop();
4353
- console.log(chalk20.green("Service stopped."));
4596
+ console.log(chalk19.green("Service stopped."));
4354
4597
  } catch (error2) {
4355
4598
  fail6(error2 instanceof Error ? error2.message : String(error2));
4356
4599
  }
@@ -4358,7 +4601,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
4358
4601
  daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
4359
4602
  const identity = loadRequiredIdentity();
4360
4603
  const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
4361
- console.log(chalk20.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
4604
+ console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
4362
4605
  let reconnectDelay = 1e3;
4363
4606
  let shouldRun = true;
4364
4607
  let activeWs = null;
@@ -4369,7 +4612,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4369
4612
  activeWs = ws;
4370
4613
  ws.on("open", () => {
4371
4614
  reconnectDelay = 1e3;
4372
- console.log(chalk20.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
4615
+ console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
4373
4616
  const hello = {
4374
4617
  type: "hello",
4375
4618
  daemon_id: identity.daemon_id,
@@ -4381,7 +4624,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4381
4624
  const projects = loadConfig().sources.filter((source2) => source2.type === "project").map((source2) => ({ name: source2.name, path: source2.path, enabled: source2.enabled }));
4382
4625
  const manifest = { type: "manifest", projects };
4383
4626
  ws.send(JSON.stringify(manifest));
4384
- console.log(chalk20.dim(`pushed manifest: ${projects.length} project(s)`));
4627
+ console.log(chalk19.dim(`pushed manifest: ${projects.length} project(s)`));
4385
4628
  });
4386
4629
  ws.on("message", (raw) => {
4387
4630
  let msg;
@@ -4391,12 +4634,12 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4391
4634
  return;
4392
4635
  }
4393
4636
  if (msg.type === "welcome") {
4394
- console.log(chalk20.green(`welcomed ${msg.server_time}`));
4637
+ console.log(chalk19.green(`welcomed ${msg.server_time}`));
4395
4638
  } else if (msg.type === "ping") {
4396
4639
  const beat = { type: "heartbeat", ts: (/* @__PURE__ */ new Date()).toISOString() };
4397
4640
  if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(beat));
4398
4641
  } else if (msg.type === "error") {
4399
- console.error(chalk20.yellow(`server error: ${msg.message}`));
4642
+ console.error(chalk19.yellow(`server error: ${msg.message}`));
4400
4643
  } else if (msg.type === "rpc_request") {
4401
4644
  void dispatchRpc(ws, msg.id, msg.method, msg.params);
4402
4645
  }
@@ -4404,9 +4647,9 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4404
4647
  ws.on("close", (code, reason) => {
4405
4648
  activeWs = null;
4406
4649
  const reasonText = reason.toString("utf-8") || "no reason";
4407
- console.log(chalk20.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
4650
+ console.log(chalk19.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
4408
4651
  if (code === 4001) {
4409
- console.error(chalk20.red("superseded by another daemon connection; exiting"));
4652
+ console.error(chalk19.red("superseded by another daemon connection; exiting"));
4410
4653
  process.exit(0);
4411
4654
  }
4412
4655
  if (shouldRun) {
@@ -4415,7 +4658,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4415
4658
  }
4416
4659
  });
4417
4660
  ws.on("error", (err) => {
4418
- console.error(chalk20.red(`ws error: ${err.message}`));
4661
+ console.error(chalk19.red(`ws error: ${err.message}`));
4419
4662
  });
4420
4663
  }
4421
4664
  const shutdown = () => {
@@ -4440,9 +4683,9 @@ daemonCmd.command("list").description("List daemons registered on the configured
4440
4683
  return;
4441
4684
  }
4442
4685
  for (const d of data.daemons) {
4443
- const status = d.status === "online" ? chalk20.green(d.status) : chalk20.dim(d.status);
4686
+ const status = d.status === "online" ? chalk19.green(d.status) : chalk19.dim(d.status);
4444
4687
  console.log(
4445
- `${chalk20.bold(d.object_id)} ${d.name.padEnd(20)} ${d.hostname.padEnd(24)} ${d.platform.padEnd(8)} ${status}`
4688
+ `${chalk19.bold(d.object_id)} ${d.name.padEnd(20)} ${d.hostname.padEnd(24)} ${d.platform.padEnd(8)} ${status}`
4446
4689
  );
4447
4690
  }
4448
4691
  });
@@ -4455,9 +4698,9 @@ daemonCmd.command("show [daemonId]").description("Show local daemon identity (no
4455
4698
  try {
4456
4699
  const svc = getServiceManager(scope);
4457
4700
  const state2 = await svc.status();
4458
- console.log(chalk20.dim(`service (${scope}): ${state2} \u2192 ${svc.unitPath()}`));
4701
+ console.log(chalk19.dim(`service (${scope}): ${state2} \u2192 ${svc.unitPath()}`));
4459
4702
  } catch (error2) {
4460
- console.log(chalk20.dim(`service status unavailable: ${error2 instanceof Error ? error2.message : String(error2)}`));
4703
+ console.log(chalk19.dim(`service status unavailable: ${error2 instanceof Error ? error2.message : String(error2)}`));
4461
4704
  }
4462
4705
  }
4463
4706
  return;
@@ -4474,21 +4717,169 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
4474
4717
  const svc = getServiceManager(scope);
4475
4718
  try {
4476
4719
  await svc.uninstall();
4477
- console.log(chalk20.dim(`Service unit removed (${scope}).`));
4720
+ console.log(chalk19.dim(`Service unit removed (${scope}).`));
4478
4721
  } catch (error2) {
4479
4722
  console.error(
4480
- chalk20.yellow(`Service uninstall warning: ${error2 instanceof Error ? error2.message : String(error2)}`)
4723
+ chalk19.yellow(`Service uninstall warning: ${error2 instanceof Error ? error2.message : String(error2)}`)
4481
4724
  );
4482
4725
  }
4483
4726
  }
4484
4727
  }
4485
4728
  if (clearDaemonIdentity()) {
4486
- console.log(chalk20.green("Daemon identity cleared."));
4729
+ console.log(chalk19.green("Daemon identity cleared."));
4487
4730
  } else {
4488
4731
  console.log("No daemon identity to clear.");
4489
4732
  }
4490
4733
  });
4491
4734
 
4735
+ // src/commands/user.ts
4736
+ import { Command as Command20 } from "commander";
4737
+ import chalk20 from "chalk";
4738
+ var DEFAULT_BASE2 = (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
4739
+ function serverBase2() {
4740
+ return DEFAULT_BASE2;
4741
+ }
4742
+ function fail7(message, code = 1) {
4743
+ console.error(chalk20.red(message));
4744
+ process.exit(code);
4745
+ }
4746
+ async function callServer(path26, init = {}) {
4747
+ const url = `${serverBase2()}${path26}`;
4748
+ let auth;
4749
+ try {
4750
+ auth = adminAuthHeader();
4751
+ } catch (error2) {
4752
+ if (error2 instanceof AdminTokenUnavailableError) fail7(error2.message);
4753
+ throw error2;
4754
+ }
4755
+ const headers = { ...auth, ...init.headers };
4756
+ let res;
4757
+ try {
4758
+ res = await fetch(url, { ...init, headers });
4759
+ } catch (error2) {
4760
+ fail7(`Cannot reach ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
4761
+ }
4762
+ if (res.status === 204) return { status: 204, body: null };
4763
+ const text = await res.text();
4764
+ let body = null;
4765
+ if (text.length > 0) {
4766
+ try {
4767
+ body = JSON.parse(text);
4768
+ } catch {
4769
+ body = null;
4770
+ }
4771
+ }
4772
+ return { status: res.status, body };
4773
+ }
4774
+ function promptHidden(prompt) {
4775
+ return new Promise((resolve, reject) => {
4776
+ const stdin = process.stdin;
4777
+ const stdout = process.stdout;
4778
+ if (!stdin.isTTY) {
4779
+ reject(new Error("stdin is not a TTY; password input requires an interactive terminal"));
4780
+ return;
4781
+ }
4782
+ stdout.write(prompt);
4783
+ stdin.setRawMode(true);
4784
+ stdin.resume();
4785
+ stdin.setEncoding("utf8");
4786
+ let buf = "";
4787
+ const onData = (raw) => {
4788
+ for (const ch of raw) {
4789
+ const code = ch.charCodeAt(0);
4790
+ if (ch === "\n" || ch === "\r" || code === 4) {
4791
+ stdin.removeListener("data", onData);
4792
+ stdin.setRawMode(false);
4793
+ stdin.pause();
4794
+ stdout.write("\n");
4795
+ resolve(buf);
4796
+ return;
4797
+ }
4798
+ if (code === 3) {
4799
+ stdin.removeListener("data", onData);
4800
+ stdin.setRawMode(false);
4801
+ stdin.pause();
4802
+ stdout.write("\n");
4803
+ process.exit(130);
4804
+ }
4805
+ if (code === 127 || code === 8) {
4806
+ buf = buf.slice(0, -1);
4807
+ continue;
4808
+ }
4809
+ if (code < 32) continue;
4810
+ buf += ch;
4811
+ }
4812
+ };
4813
+ stdin.on("data", onData);
4814
+ });
4815
+ }
4816
+ async function readPasswordWithConfirm() {
4817
+ const a = await promptHidden("Password: ");
4818
+ if (!a) fail7("Password must not be empty.");
4819
+ const b = await promptHidden("Confirm password: ");
4820
+ if (a !== b) fail7("Passwords do not match.");
4821
+ return a;
4822
+ }
4823
+ var userCmd = new Command20("user").description("Manage dashboard login accounts");
4824
+ userCmd.command("create <username>").description("Create a new dashboard login account (prompts for password)").action(async (username) => {
4825
+ const password = await readPasswordWithConfirm();
4826
+ const { status, body } = await callServer(
4827
+ "/api/auth/users",
4828
+ {
4829
+ method: "POST",
4830
+ headers: { "content-type": "application/json" },
4831
+ body: JSON.stringify({ username, password })
4832
+ }
4833
+ );
4834
+ if (status === 201 && body?.user) {
4835
+ console.log(chalk20.green(`Created user ${body.user.username} (${body.user.object_id})`));
4836
+ return;
4837
+ }
4838
+ if (status === 409) fail7(body?.error || "username already exists");
4839
+ fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
4840
+ });
4841
+ userCmd.command("list").description("List dashboard login accounts").action(async () => {
4842
+ const { status, body } = await callServer("/api/auth/users");
4843
+ if (status !== 200 || !body) fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
4844
+ if (body.users.length === 0) {
4845
+ console.log("No users. Use `task0 user create <username>` to create one.");
4846
+ return;
4847
+ }
4848
+ const nameWidth = Math.max(...body.users.map((u) => u.username.length), 4);
4849
+ for (const u of body.users) {
4850
+ console.log(`${chalk20.cyan(u.object_id)} ${u.username.padEnd(nameWidth)} ${chalk20.dim(u.created_at)}`);
4851
+ }
4852
+ });
4853
+ userCmd.command("delete <username>").description("Delete a dashboard login account").action(async (username) => {
4854
+ const { status, body } = await callServer(
4855
+ `/api/auth/users/${encodeURIComponent(username)}`,
4856
+ { method: "DELETE" }
4857
+ );
4858
+ if (status === 204) {
4859
+ console.log(chalk20.green(`Deleted user ${username}`));
4860
+ return;
4861
+ }
4862
+ if (status === 404) fail7(body?.error || "user not found");
4863
+ fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
4864
+ });
4865
+ userCmd.command("passwd <username>").description("Reset a user's password (prompts for new password)").action(async (username) => {
4866
+ const password = await readPasswordWithConfirm();
4867
+ const { status, body } = await callServer(
4868
+ `/api/auth/users/${encodeURIComponent(username)}/password`,
4869
+ {
4870
+ method: "POST",
4871
+ headers: { "content-type": "application/json" },
4872
+ body: JSON.stringify({ password })
4873
+ }
4874
+ );
4875
+ if (status === 204) {
4876
+ console.log(chalk20.green(`Updated password for ${username}`));
4877
+ return;
4878
+ }
4879
+ if (status === 404) fail7(body?.error || "user not found");
4880
+ fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
4881
+ });
4882
+
4492
4883
  // src/commands/error.ts
4493
4884
  init_node();
4494
4885
  import { Command as Command21 } from "commander";
@@ -4653,6 +5044,209 @@ error.command("prune").description("Remove captured error reports by age, count,
4653
5044
  console.log(`Removed ${res.removed.length} report(s); ${res.kept} remaining.`);
4654
5045
  });
4655
5046
 
5047
+ // src/commands/automation.ts
5048
+ import { Command as Command22 } from "commander";
5049
+ import chalk22 from "chalk";
5050
+ function fail8(message) {
5051
+ console.error(chalk22.red(message));
5052
+ process.exit(1);
5053
+ }
5054
+ function shortIso(value) {
5055
+ if (!value) return "\u2014";
5056
+ return value.replace("T", " ").replace(/\.\d{3}Z$/, "Z").replace(/Z$/, " UTC");
5057
+ }
5058
+ function buildPresetRrule(preset, opts) {
5059
+ switch (preset) {
5060
+ case "hourly": {
5061
+ const minute = clampInt(opts.minute ?? "0", 0, 59);
5062
+ return `RRULE:FREQ=HOURLY;BYMINUTE=${minute};BYSECOND=0`;
5063
+ }
5064
+ case "daily": {
5065
+ const { hour, minute } = parseHHMM(opts.time ?? "09:00");
5066
+ return `RRULE:FREQ=DAILY;BYHOUR=${hour};BYMINUTE=${minute};BYSECOND=0`;
5067
+ }
5068
+ case "weekdays": {
5069
+ const { hour, minute } = parseHHMM(opts.time ?? "09:00");
5070
+ return `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=${hour};BYMINUTE=${minute};BYSECOND=0`;
5071
+ }
5072
+ case "weekly": {
5073
+ const { hour, minute } = parseHHMM(opts.time ?? "09:00");
5074
+ const codes = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
5075
+ const idx = clampInt(opts.weekday ?? "1", 0, 6);
5076
+ return `RRULE:FREQ=WEEKLY;BYDAY=${codes[idx]};BYHOUR=${hour};BYMINUTE=${minute};BYSECOND=0`;
5077
+ }
5078
+ case "custom": {
5079
+ if (!opts.custom) throw new Error("custom preset requires --rrule");
5080
+ const trimmed = opts.custom.trim();
5081
+ if (!trimmed.startsWith("RRULE:")) throw new Error('custom rrule must start with "RRULE:"');
5082
+ return trimmed;
5083
+ }
5084
+ }
5085
+ }
5086
+ function parseHHMM(value) {
5087
+ const m = value.match(/^(\d{1,2}):(\d{2})$/);
5088
+ if (!m) throw new Error(`invalid --time, expected HH:MM, got: ${value}`);
5089
+ return {
5090
+ hour: clampInt(m[1], 0, 23),
5091
+ minute: clampInt(m[2], 0, 59)
5092
+ };
5093
+ }
5094
+ function clampInt(value, lo, hi) {
5095
+ const n = Number(value);
5096
+ if (!Number.isFinite(n)) return lo;
5097
+ return Math.max(lo, Math.min(hi, Math.floor(n)));
5098
+ }
5099
+ function printAutomation(a) {
5100
+ const status = a.status === "active" ? chalk22.green("active") : chalk22.yellow("paused");
5101
+ console.log(
5102
+ `${chalk22.cyan(a.object_id.padEnd(10))} ${status.padEnd(15)} ${a.schedule_preset.padEnd(8)} next=${shortIso(a.next_run_at)}`
5103
+ );
5104
+ console.log(` ${chalk22.bold(a.title)}`);
5105
+ console.log(
5106
+ ` project=${a.project_name} agent=${a.agent_object_id} skill=${a.skill_file_path}`
5107
+ );
5108
+ console.log(` rrule=${chalk22.dim(a.rrule)} tz=${a.timezone}`);
5109
+ }
5110
+ function printRun(r) {
5111
+ const status = (() => {
5112
+ switch (r.status) {
5113
+ case "succeeded":
5114
+ return chalk22.green(r.status);
5115
+ case "failed":
5116
+ return chalk22.red(r.status);
5117
+ case "running":
5118
+ return chalk22.yellow(r.status);
5119
+ case "pending":
5120
+ return chalk22.dim(r.status);
5121
+ }
5122
+ })();
5123
+ console.log(
5124
+ `${chalk22.cyan(r.object_id.padEnd(10))} ${status.padEnd(18)} ${r.trigger.padEnd(10)} started=${shortIso(r.started_at)} finished=${shortIso(r.finished_at)}`
5125
+ );
5126
+ console.log(` ${chalk22.dim(r.title_snapshot)}`);
5127
+ if (r.agent_run_object_id) console.log(` agent_run=${chalk22.dim(r.agent_run_object_id)}`);
5128
+ if (r.error_message) console.log(` ${chalk22.red("error: " + r.error_message)}`);
5129
+ }
5130
+ function maybeJson2(view, opts) {
5131
+ if (opts.json) {
5132
+ console.log(JSON.stringify(view, null, 2));
5133
+ return true;
5134
+ }
5135
+ return false;
5136
+ }
5137
+ var automation = new Command22("automation").description(
5138
+ "Manage automations \u2014 scheduled or manual AgentRuns"
5139
+ );
5140
+ automation.command("list").description("List automations").option("-p, --project <name>", "Filter by project (defaults to cwd if no other filter)").option("--status <status>", "active|paused").option("--json", "Output JSON").option("--all", "Do not filter by project").action(async (opts) => {
5141
+ const query = new URLSearchParams();
5142
+ if (!opts.all) {
5143
+ const projectName = opts.project ?? safeResolveProject();
5144
+ if (projectName) query.set("project", projectName);
5145
+ }
5146
+ if (opts.status) query.set("status", opts.status);
5147
+ const qs = query.toString();
5148
+ const url = qs ? `/api/automations?${qs}` : "/api/automations";
5149
+ const { automations } = await api.get(url);
5150
+ if (maybeJson2(automations, opts)) return;
5151
+ if (automations.length === 0) {
5152
+ console.log(chalk22.dim("No automations"));
5153
+ return;
5154
+ }
5155
+ for (const a of automations) printAutomation(a);
5156
+ });
5157
+ function safeResolveProject() {
5158
+ try {
5159
+ return resolveProjectName({});
5160
+ } catch {
5161
+ return void 0;
5162
+ }
5163
+ }
5164
+ automation.command("create").description("Create a new automation").requiredOption("--title <title>", "Display name").option("-p, --project <name>", "Project name (defaults to cwd)").requiredOption("--agent <object_id>", "Agent object id (agt_*)").requiredOption("--skill <path>", "Absolute path to the skill markdown file").requiredOption("--prompt <text>", "Additional prompt sent to the agent").requiredOption("--schedule <preset>", "hourly|daily|weekdays|weekly|custom").option("--time <HH:MM>", "Local time for daily/weekdays/weekly presets", "09:00").option("--weekday <0-6>", "Day of week for weekly preset (Sun=0)", "1").option("--minute <0-59>", "Minute for hourly preset", "0").option("--rrule <rrule>", "Custom RRULE string (required when --schedule=custom)").option("--timezone <tz>", "IANA timezone", "UTC").option("--dtstart <iso>", "Schedule anchor (ISO 8601)").option("--model <name>", "Override agent model").option("--paused", "Create in paused status").option("--json", "Output JSON").action(async (opts) => {
5165
+ const presets = ["hourly", "daily", "weekdays", "weekly", "custom"];
5166
+ if (!presets.includes(opts.schedule)) {
5167
+ fail8(`--schedule must be one of ${presets.join("|")}`);
5168
+ }
5169
+ const preset = opts.schedule;
5170
+ let rrule;
5171
+ try {
5172
+ rrule = buildPresetRrule(preset, {
5173
+ time: opts.time,
5174
+ weekday: opts.weekday,
5175
+ minute: opts.minute,
5176
+ custom: opts.rrule
5177
+ });
5178
+ } catch (err) {
5179
+ fail8(err instanceof Error ? err.message : "failed to build rrule");
5180
+ }
5181
+ const projectName = opts.project ?? safeResolveProject();
5182
+ if (!projectName) fail8("Cannot determine project \u2014 pass --project or run inside a registered project");
5183
+ const { automation: automation2 } = await api.post("/api/automations", {
5184
+ title: opts.title,
5185
+ project_name: projectName,
5186
+ agent_object_id: opts.agent,
5187
+ skill_file_path: opts.skill,
5188
+ prompt: opts.prompt,
5189
+ schedule_preset: preset,
5190
+ rrule,
5191
+ timezone: opts.timezone,
5192
+ dtstart: opts.dtstart,
5193
+ model: opts.model ?? null,
5194
+ status: opts.paused ? "paused" : "active"
5195
+ });
5196
+ if (maybeJson2(automation2, opts)) return;
5197
+ console.log(chalk22.green("created:"));
5198
+ printAutomation(automation2);
5199
+ });
5200
+ automation.command("get <id>").description("Show automation detail including recent runs").option("--json", "Output JSON").action(async (id, opts) => {
5201
+ const { automation: automation2 } = await api.get(`/api/automations/${encodeURIComponent(id)}`);
5202
+ if (maybeJson2(automation2, opts)) return;
5203
+ printAutomation(automation2);
5204
+ console.log("");
5205
+ console.log(chalk22.dim(`recent_runs (${automation2.recent_runs.length}):`));
5206
+ for (const run4 of automation2.recent_runs) printRun(run4);
5207
+ });
5208
+ automation.command("update <id>").description("Patch an automation").option("--title <title>").option("--prompt <text>").option("--status <status>", "active|paused").option("--rrule <rrule>").option("--timezone <tz>").option("--dtstart <iso>").option("--model <name>").option("--json", "Output JSON").action(async (id, opts) => {
5209
+ const patch = {};
5210
+ if (opts.title) patch.title = opts.title;
5211
+ if (opts.prompt) patch.prompt = opts.prompt;
5212
+ if (opts.status) patch.status = opts.status;
5213
+ if (opts.rrule) patch.rrule = opts.rrule;
5214
+ if (opts.timezone) patch.timezone = opts.timezone;
5215
+ if (opts.dtstart) patch.dtstart = opts.dtstart;
5216
+ if (opts.model !== void 0) patch.model = opts.model || null;
5217
+ const { automation: automation2 } = await api.patch(
5218
+ `/api/automations/${encodeURIComponent(id)}`,
5219
+ patch
5220
+ );
5221
+ if (maybeJson2(automation2, opts)) return;
5222
+ console.log(chalk22.green("updated:"));
5223
+ printAutomation(automation2);
5224
+ });
5225
+ automation.command("delete <id>").description("Delete an automation and its run history").action(async (id) => {
5226
+ await api.del(`/api/automations/${encodeURIComponent(id)}`);
5227
+ console.log(chalk22.green(`deleted ${id}`));
5228
+ });
5229
+ automation.command("run <id>").description("Trigger an automation immediately (manual)").option("--json", "Output JSON").action(async (id, opts) => {
5230
+ const { automation_run } = await api.post(
5231
+ `/api/automations/${encodeURIComponent(id)}/run`
5232
+ );
5233
+ if (maybeJson2(automation_run, opts)) return;
5234
+ console.log(chalk22.green("started:"));
5235
+ printRun(automation_run);
5236
+ });
5237
+ automation.command("runs <id>").description("List recent runs for an automation").option("--limit <n>", "Max rows to return", "20").option("--json", "Output JSON").action(async (id, opts) => {
5238
+ const limit = Number(opts.limit ?? "20");
5239
+ const { automation_runs } = await api.get(
5240
+ `/api/automations/${encodeURIComponent(id)}/runs?limit=${limit}`
5241
+ );
5242
+ if (maybeJson2(automation_runs, opts)) return;
5243
+ if (automation_runs.length === 0) {
5244
+ console.log(chalk22.dim("No runs"));
5245
+ return;
5246
+ }
5247
+ for (const run4 of automation_runs) printRun(run4);
5248
+ });
5249
+
4656
5250
  // src/core/error-capture.ts
4657
5251
  init_node();
4658
5252
  var DEFAULT_KEEP = 50;
@@ -4753,30 +5347,31 @@ function captureTopLevel(err, options) {
4753
5347
  var TASK0_VERSION = readVersion();
4754
5348
  function readVersion() {
4755
5349
  try {
4756
- const here = path24.dirname(fileURLToPath2(import.meta.url));
4757
- const pkg = JSON.parse(readFileSync(path24.resolve(here, "..", "package.json"), "utf-8"));
5350
+ const here = path25.dirname(fileURLToPath2(import.meta.url));
5351
+ const pkg = JSON.parse(readFileSync(path25.resolve(here, "..", "package.json"), "utf-8"));
4758
5352
  return pkg.version ?? "unknown";
4759
5353
  } catch {
4760
5354
  return "unknown";
4761
5355
  }
4762
5356
  }
4763
- var program = new Command22().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
5357
+ var program = new Command23().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
4764
5358
  program.addCommand(source);
4765
5359
  program.addCommand(project);
4766
5360
  program.addCommand(task);
4767
- program.addCommand(runtime);
5361
+ program.addCommand(agentRun);
4768
5362
  program.addCommand(plan);
4769
5363
  program.addCommand(okr);
4770
5364
  program.addCommand(note);
4771
5365
  program.addCommand(run);
4772
5366
  program.addCommand(ui);
4773
- program.addCommand(serve);
4774
5367
  program.addCommand(models);
4775
5368
  program.addCommand(object);
4776
5369
  program.addCommand(issue);
4777
5370
  program.addCommand(agent);
4778
5371
  program.addCommand(daemonCmd);
5372
+ program.addCommand(userCmd);
4779
5373
  program.addCommand(error);
5374
+ program.addCommand(automation);
4780
5375
  async function main() {
4781
5376
  installErrorCapture({ version: TASK0_VERSION });
4782
5377
  try {