@task0/cli 0.2.0 → 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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/main.js +1033 -426
  4. 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 || {} };
@@ -901,7 +918,10 @@ var init_task_state2 = __esm({
901
918
  });
902
919
 
903
920
  // src/main.ts
904
- import { Command as Command22 } from "commander";
921
+ import { readFileSync } from "fs";
922
+ import { fileURLToPath as fileURLToPath2 } from "url";
923
+ import path25 from "path";
924
+ import { Command as Command23 } from "commander";
905
925
 
906
926
  // src/commands/source.ts
907
927
  import { Command } from "commander";
@@ -1119,7 +1139,7 @@ async function request(method, pathname, body) {
1119
1139
  });
1120
1140
  } catch (error2) {
1121
1141
  const err = new Error(
1122
- `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.`
1123
1143
  );
1124
1144
  err.cause = error2;
1125
1145
  throw err;
@@ -1141,11 +1161,11 @@ async function request(method, pathname, body) {
1141
1161
  return parsed;
1142
1162
  }
1143
1163
  var api = {
1144
- get: (path24) => request("GET", path24),
1145
- post: (path24, body) => request("POST", path24, body ?? {}),
1146
- put: (path24, body) => request("PUT", path24, body ?? {}),
1147
- patch: (path24, body) => request("PATCH", path24, body ?? {}),
1148
- del: (path24) => request("DELETE", path24)
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)
1149
1169
  };
1150
1170
 
1151
1171
  // src/commands/task/triage.ts
@@ -1154,23 +1174,23 @@ import chalk3 from "chalk";
1154
1174
  import fs10 from "fs";
1155
1175
  import path10 from "path";
1156
1176
 
1157
- // src/core/runtime-wait.ts
1158
- async function getRuntime(id) {
1159
- 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)}`);
1160
1180
  }
1161
- async function waitForRuntime(id, opts = {}) {
1181
+ async function waitForAgentRun(id, opts = {}) {
1162
1182
  const interval = opts.intervalMs ?? 2e3;
1163
1183
  const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : null;
1164
1184
  let lastStatus = "";
1165
1185
  while (true) {
1166
- const snap = await getRuntime(id);
1186
+ const snap = await getAgentRun(id);
1167
1187
  if (snap.status !== lastStatus) {
1168
1188
  lastStatus = snap.status;
1169
1189
  opts.onTick?.(snap);
1170
1190
  }
1171
1191
  if (snap.status === "done" || snap.status === "error") return snap;
1172
1192
  if (deadline && Date.now() > deadline) {
1173
- 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})`);
1174
1194
  }
1175
1195
  await new Promise((r) => setTimeout(r, interval));
1176
1196
  }
@@ -1248,11 +1268,11 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1248
1268
  `/api/agents/${encodeURIComponent(opts.agent)}/run`,
1249
1269
  body
1250
1270
  );
1251
- const runtimeId = resp.runtime.id;
1252
- if (!opts.json) console.log(chalk3.green(`triage runtime ${runtimeId}`));
1253
- 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 } });
1254
1274
  if (opts.wait) {
1255
- const final = await waitForRuntime(runtimeId, {
1275
+ const final = await waitForAgentRun(agentRunId, {
1256
1276
  onTick: (s) => {
1257
1277
  if (!opts.json) console.log(chalk3.dim(`[triage] ${s.status}${s.phase ? " " + s.phase : ""}`));
1258
1278
  }
@@ -1285,7 +1305,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1285
1305
  console.log(chalk3.dim(`blockingQuestionCount: ${blockingQuestionCount}`));
1286
1306
  } else {
1287
1307
  console.log(JSON.stringify({
1288
- runtimeId,
1308
+ agentRunId,
1289
1309
  issueOverview: "ISSUE.md",
1290
1310
  issueFiles,
1291
1311
  blockingQuestionCount
@@ -1293,7 +1313,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1293
1313
  }
1294
1314
  process.exit(blockingQuestionCount > 0 ? 2 : 0);
1295
1315
  }
1296
- if (opts.json) console.log(JSON.stringify({ runtimeId }, null, 2));
1316
+ if (opts.json) console.log(JSON.stringify({ agentRunId }, null, 2));
1297
1317
  } catch (err) {
1298
1318
  console.error(chalk3.red(err.message));
1299
1319
  process.exit(1);
@@ -1358,24 +1378,24 @@ var exec = new Command4("exec").description("Execute a plan against the task (cw
1358
1378
  `/api/agents/${encodeURIComponent(opts.agent)}/run`,
1359
1379
  body
1360
1380
  );
1361
- const runtimeId = resp.runtime.id;
1362
- if (!opts.json) console.log(chalk4.green(`exec runtime ${runtimeId} (plan: ${planFile})`));
1363
- 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 } });
1364
1384
  if (opts.wait) {
1365
- const final = await waitForRuntime(runtimeId, {
1385
+ const final = await waitForAgentRun(agentRunId, {
1366
1386
  onTick: (s) => {
1367
1387
  if (!opts.json) console.log(chalk4.dim(`[exec] ${s.status}${s.phase ? " " + s.phase : ""}`));
1368
1388
  }
1369
1389
  });
1370
1390
  if (final.status === "done") {
1371
1391
  if (!opts.json) console.log(chalk4.green("exec completed"));
1372
- 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));
1373
1393
  return;
1374
1394
  }
1375
1395
  console.error(chalk4.red(`exec failed: ${final.error || "unknown"}`));
1376
1396
  process.exit(1);
1377
1397
  }
1378
- if (opts.json) console.log(JSON.stringify({ runtimeId, planFile }, null, 2));
1398
+ if (opts.json) console.log(JSON.stringify({ agentRunId, planFile }, null, 2));
1379
1399
  } catch (err) {
1380
1400
  console.error(chalk4.red(err.message));
1381
1401
  process.exit(1);
@@ -1432,13 +1452,16 @@ function preview(body, width = 60) {
1432
1452
  const single = body.replace(/\s+/g, " ").trim();
1433
1453
  return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
1434
1454
  }
1435
- function printRow(c) {
1455
+ function printRow(c, indent = "") {
1436
1456
  const id = chalk6.cyan(c.id.padEnd(14));
1437
1457
  const author = chalk6.magenta(c.author.padEnd(12));
1438
- 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)}`);
1439
1459
  }
1440
1460
  function printDetail(c) {
1441
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
+ }
1442
1465
  console.log(`${chalk6.bold("author:")} ${c.author}`);
1443
1466
  console.log(`${chalk6.bold("created:")} ${c.created_at}`);
1444
1467
  console.log(`${chalk6.bold("updated:")} ${c.updated_at}`);
@@ -1466,17 +1489,22 @@ comment.command("list <taskId>").description("List comments on a task (taskId is
1466
1489
  console.log(chalk6.dim("No comments."));
1467
1490
  return;
1468
1491
  }
1469
- 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
+ }
1470
1498
  } catch (err) {
1471
1499
  fail(err);
1472
1500
  }
1473
1501
  });
1474
- 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) => {
1475
1503
  try {
1476
1504
  const body = readBodyFromOpts(opts);
1477
1505
  const { comment: created } = await api.post(
1478
1506
  `/api/tasks/${encodeURIComponent(taskId)}/comments`,
1479
- { body, author: opts.author }
1507
+ { body, author: opts.author, parent_comment_id: opts.replyTo }
1480
1508
  );
1481
1509
  if (opts.json) {
1482
1510
  console.log(JSON.stringify({ comment: created }, null, 2));
@@ -1811,61 +1839,9 @@ var ui = new Command9("ui").description("Launch the dashboard").option("-p, --po
1811
1839
  process.on("SIGTERM", () => child.kill("SIGTERM"));
1812
1840
  });
1813
1841
 
1814
- // src/commands/serve.ts
1842
+ // src/commands/models.ts
1815
1843
  import { Command as Command10 } from "commander";
1816
- import { spawn as spawn2 } from "child_process";
1817
- import fs15 from "fs";
1818
- import path14 from "path";
1819
1844
  import chalk10 from "chalk";
1820
- var SERVER_DIR = path14.resolve(
1821
- import.meta.dirname,
1822
- "..",
1823
- "..",
1824
- "..",
1825
- "server"
1826
- );
1827
- var HTTP_ENTRY = path14.join(SERVER_DIR, "src", "main.ts");
1828
- var CLI_TSX = path14.resolve(
1829
- import.meta.dirname,
1830
- "..",
1831
- "..",
1832
- "node_modules",
1833
- ".bin",
1834
- "tsx"
1835
- );
1836
- 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) => {
1837
- if (!fs15.existsSync(HTTP_ENTRY)) {
1838
- console.error(chalk10.red(`Server entry not found: ${HTTP_ENTRY}`));
1839
- process.exit(1);
1840
- }
1841
- if (!fs15.existsSync(CLI_TSX)) {
1842
- console.error(chalk10.red(`tsx not found at ${CLI_TSX}. Run \`pnpm install\` in the CLI package.`));
1843
- process.exit(1);
1844
- }
1845
- console.log(chalk10.green(`Starting task0 API on http://${opts.host}:${opts.port}`));
1846
- const child = spawn2(CLI_TSX, [HTTP_ENTRY], {
1847
- cwd: SERVER_DIR,
1848
- stdio: "inherit",
1849
- env: {
1850
- ...process.env,
1851
- TASK0_API_PORT: opts.port,
1852
- TASK0_API_HOST: opts.host
1853
- }
1854
- });
1855
- child.on("error", (err) => {
1856
- console.error(chalk10.red(`Failed to start server: ${err.message}`));
1857
- process.exit(1);
1858
- });
1859
- child.on("exit", (code) => {
1860
- process.exit(code ?? 0);
1861
- });
1862
- process.on("SIGINT", () => child.kill("SIGINT"));
1863
- process.on("SIGTERM", () => child.kill("SIGTERM"));
1864
- });
1865
-
1866
- // src/commands/models.ts
1867
- import { Command as Command11 } from "commander";
1868
- import chalk11 from "chalk";
1869
1845
 
1870
1846
  // ../../packages/shared/dist/types/agent.js
1871
1847
  var AGENT_KINDS = ["coding", "llm_api", "workflow"];
@@ -1877,8 +1853,8 @@ function isCodingRuntimeAgent(value) {
1877
1853
  }
1878
1854
 
1879
1855
  // ../../packages/shared/dist/types/runtime.js
1880
- var RUNTIME_STATUS_VALUES = ["starting", "running", "done", "error"];
1881
- 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);
1882
1858
  var RUNTIME_AGENTS = CODING_RUNTIME_AGENTS;
1883
1859
  var isRuntimeAgent = isCodingRuntimeAgent;
1884
1860
  var AGENT_MODEL_DEFAULTS = {
@@ -2063,31 +2039,31 @@ function refreshAgentModels(agent2) {
2063
2039
  function parseAgent(raw) {
2064
2040
  const trimmed = raw.trim();
2065
2041
  if (!isRuntimeAgent(trimmed)) {
2066
- 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(", ")})`));
2067
2043
  process.exit(1);
2068
2044
  }
2069
2045
  return trimmed;
2070
2046
  }
2071
- var models = new Command11("models").description("Manage cached agent model lists");
2047
+ var models = new Command10("models").description("Manage cached agent model lists");
2072
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) => {
2073
2049
  const agent2 = opts.agent ? parseAgent(opts.agent) : void 0;
2074
2050
  try {
2075
2051
  const summary = refreshAgentModels(agent2);
2076
2052
  for (const result of summary.results) {
2077
- const source2 = result.source === "command" ? chalk11.cyan("command") : chalk11.dim("builtin");
2078
- 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`);
2079
2055
  if (result.fetchCommand) {
2080
- console.log(chalk11.dim(` ${result.fetchCommand}`));
2056
+ console.log(chalk10.dim(` ${result.fetchCommand}`));
2081
2057
  }
2082
2058
  }
2083
2059
  for (const failure of summary.failures) {
2084
- console.error(chalk11.red(`Failed ${failure.agent}: ${failure.error}`));
2060
+ console.error(chalk10.red(`Failed ${failure.agent}: ${failure.error}`));
2085
2061
  }
2086
2062
  if (summary.failures.length > 0) {
2087
2063
  process.exit(1);
2088
2064
  }
2089
2065
  } catch (error2) {
2090
- console.error(chalk11.red(error2 instanceof Error ? error2.message : String(error2)));
2066
+ console.error(chalk10.red(error2 instanceof Error ? error2.message : String(error2)));
2091
2067
  process.exit(1);
2092
2068
  }
2093
2069
  });
@@ -2111,70 +2087,73 @@ models.command("default <agent>").description("Get or set default model / effort
2111
2087
  if (wroteModel || wroteEffort) {
2112
2088
  saveConfig(config);
2113
2089
  }
2114
- console.log(chalk11.green(agent2));
2115
- console.log(` model: ${entry.defaultModel ?? chalk11.dim("(unset)")}`);
2116
- 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)")}`);
2117
2093
  });
2118
2094
 
2119
- // src/commands/runtime.ts
2120
- import { Command as Command12 } from "commander";
2121
- import chalk12 from "chalk";
2122
- import { spawn as spawn3 } from "child_process";
2123
- var runtime = new Command12("runtime").description("Inspect and control runtimes");
2124
- 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) => {
2125
2104
  try {
2126
- const { runtimes } = await api.get("/api/runtimes");
2105
+ const { agent_runs: runtimes } = await api.get("/api/agent-runs");
2127
2106
  const filtered = opts.task ? runtimes.filter((r) => r.taskId === opts.task) : runtimes;
2128
2107
  if (opts.json) {
2129
2108
  console.log(JSON.stringify(filtered, null, 2));
2130
2109
  return;
2131
2110
  }
2132
2111
  if (filtered.length === 0) {
2133
- console.log(chalk12.dim("No runtimes."));
2112
+ console.log(chalk11.dim("No agent runs."));
2134
2113
  return;
2135
2114
  }
2136
2115
  const objectIdWidth = Math.max(...filtered.map((r) => (r.objectId || "").length), 8);
2137
2116
  for (const r of filtered) {
2138
- const statusColor2 = r.status === "done" ? chalk12.green : r.status === "error" ? chalk12.red : r.status === "running" ? chalk12.cyan : chalk12.dim;
2139
- const objectId = chalk12.cyan((r.objectId || "-").padEnd(objectIdWidth));
2140
- 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)}`);
2141
2120
  }
2142
2121
  } catch (err) {
2143
- console.error(chalk12.red(err.message));
2122
+ console.error(chalk11.red(err.message));
2144
2123
  process.exit(1);
2145
2124
  }
2146
2125
  });
2147
- 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) => {
2148
2127
  try {
2149
- const r = await getRuntime(id);
2128
+ const r = await getAgentRun(id);
2150
2129
  if (opts.json) {
2151
2130
  console.log(JSON.stringify(r, null, 2));
2152
2131
  return;
2153
2132
  }
2154
- console.log(`${chalk12.bold("id:")} ${r.id}`);
2155
- if (r.objectId) console.log(`${chalk12.bold("object_id:")} ${r.objectId}`);
2156
- console.log(`${chalk12.bold("type:")} ${r.type}`);
2157
- console.log(`${chalk12.bold("agent:")} ${r.agent}`);
2158
- console.log(`${chalk12.bold("task:")} ${r.taskId}`);
2159
- console.log(`${chalk12.bold("status:")} ${r.status}`);
2160
- if (r.phase) console.log(`${chalk12.bold("phase:")} ${r.phase}`);
2161
- if (r.error) console.log(`${chalk12.red("error:")} ${r.error}`);
2162
- 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"})`);
2163
2142
  if (r.result) {
2164
- console.log(chalk12.bold("result:"));
2143
+ console.log(chalk11.bold("result:"));
2165
2144
  console.log(" " + JSON.stringify(r.result, null, 2).split("\n").join("\n "));
2166
2145
  }
2167
2146
  } catch (err) {
2168
- console.error(chalk12.red(err.message));
2147
+ console.error(chalk11.red(err.message));
2169
2148
  process.exit(1);
2170
2149
  }
2171
2150
  });
2172
- 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) => {
2173
2152
  try {
2174
- const final = await waitForRuntime(id, {
2153
+ const final = await waitForAgentRun(id, {
2175
2154
  timeoutMs: Number(opts.timeout) * 1e3,
2176
2155
  onTick: (s) => {
2177
- if (!opts.json) console.log(chalk12.dim(`[${s.status}] ${s.phase ?? ""}`));
2156
+ if (!opts.json) console.log(chalk11.dim(`[${s.status}] ${s.phase ?? ""}`));
2178
2157
  }
2179
2158
  });
2180
2159
  if (opts.json) {
@@ -2182,41 +2161,120 @@ runtime.command("wait <id>").description("Block until runtime reaches done or er
2182
2161
  }
2183
2162
  process.exit(final.status === "done" ? 0 : 1);
2184
2163
  } catch (err) {
2185
- console.error(chalk12.red(err.message));
2164
+ console.error(chalk11.red(err.message));
2186
2165
  process.exit(1);
2187
2166
  }
2188
2167
  });
2189
- runtime.command("cancel <id>").description("Cancel a runtime").action(async (id) => {
2168
+ agentRun.command("cancel <id>").description("Cancel an agent run").action(async (id) => {
2190
2169
  try {
2191
- await api.post(`/api/runtimes/${encodeURIComponent(id)}/cancel`);
2192
- console.log(chalk12.green(`Canceled ${id}`));
2170
+ await api.post(`/api/agent-runs/${encodeURIComponent(id)}/cancel`);
2171
+ console.log(chalk11.green(`Canceled ${id}`));
2193
2172
  } catch (err) {
2194
- console.error(chalk12.red(err.message));
2173
+ console.error(chalk11.red(err.message));
2195
2174
  process.exit(1);
2196
2175
  }
2197
2176
  });
2198
- 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) => {
2199
2178
  try {
2200
- const r = await getRuntime(id);
2179
+ const r = await getAgentRun(id);
2201
2180
  if (!r.tmuxSession) {
2202
- console.error(chalk12.red("No tmux session recorded for this runtime."));
2181
+ console.error(chalk11.red("No tmux session recorded for this agent run."));
2203
2182
  process.exit(1);
2204
2183
  }
2205
- const child = spawn3("tmux", ["attach", "-t", r.tmuxSession], { stdio: "inherit" });
2184
+ const child = spawn2("tmux", ["attach", "-t", r.tmuxSession], { stdio: "inherit" });
2206
2185
  child.on("exit", (code) => process.exit(code ?? 0));
2207
2186
  child.on("error", (err) => {
2208
- console.error(chalk12.red(`Failed to attach: ${err.message}`));
2187
+ console.error(chalk11.red(`Failed to attach: ${err.message}`));
2209
2188
  process.exit(1);
2210
2189
  });
2211
2190
  } catch (err) {
2212
- console.error(chalk12.red(err.message));
2191
+ console.error(chalk11.red(err.message));
2213
2192
  process.exit(1);
2214
2193
  }
2215
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);
2216
2274
 
2217
2275
  // src/commands/plan.ts
2218
- import { Command as Command13 } from "commander";
2219
- import chalk13 from "chalk";
2276
+ import { Command as Command12 } from "commander";
2277
+ import chalk12 from "chalk";
2220
2278
  import fs16 from "fs";
2221
2279
  import path15 from "path";
2222
2280
  init_task_state2();
@@ -2225,27 +2283,27 @@ function resolveSkillFilePath3(projectRoot, skillName) {
2225
2283
  const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2226
2284
  return fs16.existsSync(p) ? p : null;
2227
2285
  }
2228
- var plan = new Command13("plan").description("Generate and refine plans");
2286
+ var plan = new Command12("plan").description("Generate and refine plans");
2229
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) => {
2230
2288
  try {
2231
2289
  const loc = resolveTaskByObjectId(objectId);
2232
2290
  const ideaFile = opts.idea || latestArtifact(loc.taskDir, /^IDEA-\d+\.md$/);
2233
2291
  if (!ideaFile) {
2234
- 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.`));
2235
2293
  process.exit(1);
2236
2294
  }
2237
2295
  const agents = opts.agents.split(",").map((a) => a.trim()).filter(Boolean);
2238
2296
  if (agents.length === 0) {
2239
- console.error(chalk13.red("No agents specified."));
2297
+ console.error(chalk12.red("No agents specified."));
2240
2298
  process.exit(1);
2241
2299
  }
2242
2300
  if ((opts.model || opts.effort) && agents.length > 1) {
2243
- console.error(chalk13.red(
2301
+ console.error(chalk12.red(
2244
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.`
2245
2303
  ));
2246
2304
  process.exit(1);
2247
2305
  }
2248
- 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));
2249
2307
  const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_GENERATE_SKILL_NAME);
2250
2308
  if (opts.model || opts.effort) {
2251
2309
  resolveModelOptions(agents[0], { model: opts.model, effort: opts.effort }, warn);
@@ -2268,43 +2326,56 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
2268
2326
  prompt: promptForAgent(agent2),
2269
2327
  ...skillFilePath ? { skill_file_path: skillFilePath } : {}
2270
2328
  };
2271
- try {
2272
- const resp = await api.post(
2273
- `/api/agents/${encodeURIComponent(agent2)}/run`,
2274
- body
2275
- );
2276
- return {
2277
- agent: agent2,
2278
- runtimeId: resp.runtime.id,
2279
- sessionName: "",
2280
- error: null
2281
- };
2282
- } catch (err) {
2283
- 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
+ }
2284
2354
  }
2355
+ return { agent: agent2, agentRunId: "", sessionName: "", error: "agent_busy: exhausted retries" };
2285
2356
  }));
2286
2357
  const launched = kicks.filter((k) => !k.error);
2287
2358
  const failed = kicks.filter((k) => k.error);
2288
2359
  if (!opts.json) {
2289
2360
  for (const k of launched) {
2290
- console.log(chalk13.green(`[${k.agent}]`) + ` runtime ${k.runtimeId}`);
2361
+ console.log(chalk12.green(`[${k.agent}]`) + ` runtime ${k.agentRunId}`);
2291
2362
  }
2292
2363
  for (const k of failed) {
2293
- console.error(chalk13.red(`[${k.agent}] ${k.error}`));
2364
+ console.error(chalk12.red(`[${k.agent}] ${k.error}`));
2294
2365
  }
2295
2366
  }
2296
- const runtimeIds = {};
2297
- for (const k of launched) runtimeIds[k.agent] = k.runtimeId;
2367
+ const agentRunIds = {};
2368
+ for (const k of launched) agentRunIds[k.agent] = k.agentRunId;
2298
2369
  await updateWorkflow(loc.taskYml, {
2299
- runtimes: { plan: runtimeIds }
2370
+ agent_runs: { plan: agentRunIds }
2300
2371
  });
2301
2372
  const planFiles = [];
2302
2373
  if (opts.wait) {
2303
2374
  const results = await Promise.all(launched.map(async (k) => {
2304
2375
  try {
2305
- const final = await waitForRuntime(k.runtimeId, {
2376
+ const final = await waitForAgentRun(k.agentRunId, {
2306
2377
  onTick: (s) => {
2307
- 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 : ""}`));
2308
2379
  }
2309
2380
  });
2310
2381
  return { agent: k.agent, final, error: null };
@@ -2317,10 +2388,10 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
2317
2388
  const planFile = r.final.result?.planFile;
2318
2389
  if (planFile) {
2319
2390
  planFiles.push(planFile);
2320
- if (!opts.json) console.log(chalk13.green(`[${r.agent}] ${planFile}`));
2391
+ if (!opts.json) console.log(chalk12.green(`[${r.agent}] ${planFile}`));
2321
2392
  }
2322
2393
  } else {
2323
- 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"}`));
2324
2395
  }
2325
2396
  }
2326
2397
  const existingPlanFiles = readWorkflow(loc.taskYml).plan_files || [];
@@ -2334,7 +2405,7 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
2334
2405
  }
2335
2406
  if (failed.length > 0) process.exit(1);
2336
2407
  } catch (err) {
2337
- console.error(chalk13.red(err.message));
2408
+ console.error(chalk12.red(err.message));
2338
2409
  process.exit(1);
2339
2410
  }
2340
2411
  });
@@ -2345,7 +2416,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2345
2416
  const files = fs16.readdirSync(loc.taskDir);
2346
2417
  const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
2347
2418
  if (planFiles.length === 0) {
2348
- 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."));
2349
2420
  process.exit(1);
2350
2421
  }
2351
2422
  const idx = planFiles[0].match(/^PLAN-(\d+)/)?.[1] || nextArtifactIndex(loc.taskDir, "PLAN");
@@ -2354,7 +2425,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2354
2425
  resolveModelOptions(
2355
2426
  opts.agent,
2356
2427
  { model: opts.model, effort: opts.effort },
2357
- opts.json ? void 0 : (m) => console.error(chalk13.dim(m))
2428
+ opts.json ? void 0 : (m) => console.error(chalk12.dim(m))
2358
2429
  );
2359
2430
  }
2360
2431
  const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_REFINE_SKILL_NAME);
@@ -2373,15 +2444,15 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2373
2444
  `/api/agents/${encodeURIComponent(opts.agent)}/run`,
2374
2445
  body
2375
2446
  );
2376
- const runtimeId = resp.runtime.id;
2377
- 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}`));
2378
2449
  await updateWorkflow(loc.taskYml, {
2379
- runtimes: { refine: runtimeId }
2450
+ agent_runs: { refine: agentRunId }
2380
2451
  });
2381
2452
  if (opts.wait) {
2382
- const final = await waitForRuntime(runtimeId, {
2453
+ const final = await waitForAgentRun(agentRunId, {
2383
2454
  onTick: (s) => {
2384
- 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 : ""}`));
2385
2456
  }
2386
2457
  });
2387
2458
  const refinedPath = path15.join(loc.taskDir, refinedFile);
@@ -2391,29 +2462,29 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
2391
2462
  phase: "refined",
2392
2463
  refined_plan_file: refinedFile
2393
2464
  });
2394
- if (!opts.json) console.log(chalk13.green(refinedFile));
2395
- 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));
2396
2467
  return;
2397
2468
  }
2398
2469
  if (final.status !== "done") {
2399
- console.error(chalk13.red(`refine failed: ${final.error || "unknown"}`));
2470
+ console.error(chalk12.red(`refine failed: ${final.error || "unknown"}`));
2400
2471
  } else if (!wrote) {
2401
- console.error(chalk13.red(`refine completed but ${refinedFile} was not written`));
2472
+ console.error(chalk12.red(`refine completed but ${refinedFile} was not written`));
2402
2473
  }
2403
2474
  process.exit(1);
2404
2475
  }
2405
- if (opts.json) console.log(JSON.stringify({ runtimeId, refinedFile }, null, 2));
2476
+ if (opts.json) console.log(JSON.stringify({ agentRunId, refinedFile }, null, 2));
2406
2477
  } catch (err) {
2407
- console.error(chalk13.red(err.message));
2478
+ console.error(chalk12.red(err.message));
2408
2479
  process.exit(1);
2409
2480
  }
2410
2481
  });
2411
2482
 
2412
2483
  // src/commands/run.ts
2413
2484
  init_task_state2();
2414
- import { Command as Command14 } from "commander";
2415
- import chalk14 from "chalk";
2416
- 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";
2417
2488
  var PHASES = ["triage", "plan", "refine", "exec"];
2418
2489
  var PAUSE_AFTER = /* @__PURE__ */ new Set(["refine"]);
2419
2490
  var CHECKPOINT_EXIT = 78;
@@ -2432,13 +2503,13 @@ function nextPhaseFromWorkflow(wf) {
2432
2503
  return null;
2433
2504
  }
2434
2505
  }
2435
- 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) => {
2436
2507
  try {
2437
2508
  const loc = resolveTaskByObjectId(objectId);
2438
2509
  const wf = readWorkflow(loc.taskYml);
2439
2510
  const fromCandidate = opts.from ?? nextPhaseFromWorkflow(wf);
2440
2511
  if (!fromCandidate) {
2441
- 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}".`));
2442
2513
  return;
2443
2514
  }
2444
2515
  const from = fromCandidate;
@@ -2449,23 +2520,23 @@ var run = new Command14("run").description("Run the full task orchestration (tri
2449
2520
  const toIdx = PHASES.indexOf(to);
2450
2521
  if (fromIdx > toIdx) throw new Error(`--from (${from}) is past --to (${to})`);
2451
2522
  if (!opts.json) {
2452
- 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}`));
2453
2524
  }
2454
2525
  for (let i = fromIdx; i <= toIdx; i++) {
2455
2526
  const phase = PHASES[i];
2456
- if (!opts.json) console.log(chalk14.bold.cyan(`
2527
+ if (!opts.json) console.log(chalk13.bold.cyan(`
2457
2528
  \u25B6 ${phase}`));
2458
2529
  const code = runPhase(phase, objectId, opts);
2459
2530
  if (code !== 0) {
2460
- 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}`));
2461
2532
  process.exit(code);
2462
2533
  }
2463
2534
  if (!opts.auto && PAUSE_AFTER.has(phase) && i < toIdx) {
2464
2535
  const next = PHASES[i + 1];
2465
2536
  if (!opts.json) {
2466
- console.log(chalk14.yellow(`
2537
+ console.log(chalk13.yellow(`
2467
2538
  \u23F8 checkpoint after ${phase}. Review and resume with:`));
2468
- console.log(chalk14.yellow(` task0 run ${objectId} --from ${next}`));
2539
+ console.log(chalk13.yellow(` task0 run ${objectId} --from ${next}`));
2469
2540
  }
2470
2541
  process.exit(CHECKPOINT_EXIT);
2471
2542
  }
@@ -2474,11 +2545,11 @@ var run = new Command14("run").description("Run the full task orchestration (tri
2474
2545
  if (opts.json) {
2475
2546
  console.log(JSON.stringify({ objectId, from, to, workflow: finalWf }, null, 2));
2476
2547
  } else {
2477
- console.log(chalk14.green(`
2548
+ console.log(chalk13.green(`
2478
2549
  \u2713 done (phase: ${finalWf.phase})`));
2479
2550
  }
2480
2551
  } catch (err) {
2481
- console.error(chalk14.red(err.message));
2552
+ console.error(chalk13.red(err.message));
2482
2553
  process.exit(1);
2483
2554
  }
2484
2555
  });
@@ -2500,7 +2571,7 @@ function runPhase(phase, objectId, opts) {
2500
2571
  args = ["task", "exec", objectId, "--wait", "--agent", opts.executor];
2501
2572
  break;
2502
2573
  }
2503
- const r = spawnSync3(node, [cli, ...args], { stdio: "inherit" });
2574
+ const r = spawnSync4(node, [cli, ...args], { stdio: "inherit" });
2504
2575
  return r.status ?? 1;
2505
2576
  }
2506
2577
  run.command("status <objectId>").description("Show current workflow state").option("--json", "Output JSON").action((objectId, opts) => {
@@ -2511,29 +2582,30 @@ run.command("status <objectId>").description("Show current workflow state").opti
2511
2582
  console.log(JSON.stringify(wf, null, 2));
2512
2583
  return;
2513
2584
  }
2514
- console.log(`${chalk14.bold("phase:")} ${wf.phase ?? chalk14.dim("(not started)")}`);
2585
+ console.log(`${chalk13.bold("phase:")} ${wf.phase ?? chalk13.dim("(not started)")}`);
2515
2586
  if (wf.issue_overview) {
2516
2587
  const extra = wf.issue_files?.length ? ` (+${wf.issue_files.length} details)` : "";
2517
- console.log(`${chalk14.bold("issues:")} ${wf.issue_overview}${extra}`);
2518
- }
2519
- if (wf.plan_files?.length) console.log(`${chalk14.bold("plans:")} ${wf.plan_files.join(", ")}`);
2520
- if (wf.refined_plan_file) console.log(`${chalk14.bold("refined:")} ${wf.refined_plan_file}`);
2521
- if (wf.runtimes) {
2522
- console.log(chalk14.bold("runtimes:"));
2523
- 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)) {
2524
2596
  console.log(` ${k.padEnd(8)} ${typeof v === "string" ? v : JSON.stringify(v)}`);
2525
2597
  }
2526
2598
  }
2527
- 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}`));
2528
2600
  } catch (err) {
2529
- console.error(chalk14.red(err.message));
2601
+ console.error(chalk13.red(err.message));
2530
2602
  process.exit(1);
2531
2603
  }
2532
2604
  });
2533
2605
 
2534
2606
  // src/commands/okr.ts
2535
- import { Command as Command15 } from "commander";
2536
- import chalk15 from "chalk";
2607
+ import { Command as Command14 } from "commander";
2608
+ import chalk14 from "chalk";
2537
2609
 
2538
2610
  // src/lib/project.ts
2539
2611
  import path16 from "path";
@@ -2566,7 +2638,7 @@ function resolveProjectName(opts) {
2566
2638
 
2567
2639
  // src/commands/okr.ts
2568
2640
  function fail3(message) {
2569
- console.error(chalk15.red(message));
2641
+ console.error(chalk14.red(message));
2570
2642
  process.exit(1);
2571
2643
  }
2572
2644
  function parseNumber(value) {
@@ -2603,11 +2675,11 @@ function countKRs(plan3) {
2603
2675
  return plan3.objectives.reduce((sum, o) => sum + o.key_results.length, 0);
2604
2676
  }
2605
2677
  function printPlanSummary(plan3) {
2606
- const oid = chalk15.cyan((plan3.object_id || "-").padEnd(10));
2678
+ const oid = chalk14.cyan((plan3.object_id || "-").padEnd(10));
2607
2679
  const id = plan3.id.padEnd(16);
2608
- const status = chalk15.yellow(plan3.status.padEnd(8));
2680
+ const status = chalk14.yellow(plan3.status.padEnd(8));
2609
2681
  const range = `${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`;
2610
- const counts = chalk15.dim(
2682
+ const counts = chalk14.dim(
2611
2683
  `${plan3.objectives.length} obj \xB7 ${countKRs(plan3)} kr \xB7 ${plan3.milestones.length} ms`
2612
2684
  );
2613
2685
  console.log(`${oid} ${id} ${status} ${range} ${counts} ${plan3.title}`);
@@ -2626,37 +2698,37 @@ function krProgress(kr2) {
2626
2698
  }
2627
2699
  function printPlanDetail(plan3) {
2628
2700
  console.log(
2629
- `${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)}`
2630
2702
  );
2631
2703
  if (plan3.title) console.log(` ${plan3.title}`);
2632
2704
  for (const obj of plan3.objectives) {
2633
2705
  console.log(
2634
- ` ${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 || "")}`
2635
2707
  );
2636
2708
  for (const kr2 of obj.key_results) {
2637
2709
  const unit = kr2.unit ? kr2.unit : "";
2638
2710
  const vals = kr2.target_value !== null ? `${kr2.start_value ?? 0}${unit}\u2192${kr2.target_value}${unit} now ${kr2.current_value ?? 0}${unit}` : "\u2014";
2639
2711
  console.log(
2640
- ` ${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 || "")}`
2641
2713
  );
2642
2714
  }
2643
2715
  }
2644
2716
  for (const ms of plan3.milestones) {
2645
- 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(",")}`) : "";
2646
2718
  console.log(
2647
- ` ${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 || "")}`
2648
2720
  );
2649
2721
  }
2650
2722
  if (plan3.task_kr_links.length) {
2651
- console.log(chalk15.dim("links:"));
2723
+ console.log(chalk14.dim("links:"));
2652
2724
  for (const link of plan3.task_kr_links) {
2653
2725
  console.log(
2654
- ` ${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}`)}`
2655
2727
  );
2656
2728
  }
2657
2729
  }
2658
2730
  }
2659
- var okr = new Command15("okr").description(
2731
+ var okr = new Command14("okr").description(
2660
2732
  "Manage OKR plans, objectives, key results, milestones"
2661
2733
  );
2662
2734
  var plan2 = okr.command("plan").description("OKR plans");
@@ -2690,7 +2762,7 @@ plan2.command("create <plan-id>").description("Create a new OKR plan").requiredO
2690
2762
  };
2691
2763
  const { plan: view } = await api.post(planBasePath(name), body);
2692
2764
  if (maybeJson(view, opts)) return;
2693
- 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 || ""));
2694
2766
  printPlanSummary(view);
2695
2767
  });
2696
2768
  }
@@ -2709,7 +2781,7 @@ plan2.command("update <plan-id>").description("Update plan fields").option("-t,
2709
2781
  body
2710
2782
  );
2711
2783
  if (maybeJson(view, opts)) return;
2712
- 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 || ""));
2713
2785
  printPlanSummary(view);
2714
2786
  });
2715
2787
  }
@@ -2732,10 +2804,10 @@ objective.command("create <plan-id> <objective-id>").description("Create an obje
2732
2804
  );
2733
2805
  if (maybeJson(view, opts)) return;
2734
2806
  console.log(
2735
- chalk15.green("ok"),
2807
+ chalk14.green("ok"),
2736
2808
  view.id,
2737
- chalk15.dim(view.object_id || ""),
2738
- chalk15.dim(`(${view.status})`),
2809
+ chalk14.dim(view.object_id || ""),
2810
+ chalk14.dim(`(${view.status})`),
2739
2811
  view.title
2740
2812
  );
2741
2813
  });
@@ -2756,7 +2828,7 @@ objective.command("update <plan-id> <objective-id>").description("Update objecti
2756
2828
  body
2757
2829
  );
2758
2830
  if (maybeJson(view, opts)) return;
2759
- 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);
2760
2832
  });
2761
2833
  }
2762
2834
  );
@@ -2784,10 +2856,10 @@ kr.command("create <plan-id> <objective-id> <kr-id>").description("Create a key
2784
2856
  );
2785
2857
  if (maybeJson(view, opts)) return;
2786
2858
  console.log(
2787
- chalk15.green("ok"),
2859
+ chalk14.green("ok"),
2788
2860
  view.id,
2789
- chalk15.dim(view.object_id || ""),
2790
- chalk15.dim(`(${view.status}/${view.confidence})`),
2861
+ chalk14.dim(view.object_id || ""),
2862
+ chalk14.dim(`(${view.status}/${view.confidence})`),
2791
2863
  view.title
2792
2864
  );
2793
2865
  });
@@ -2815,9 +2887,9 @@ kr.command("update <plan-id> <kr-id>").description("Update a key result").option
2815
2887
  );
2816
2888
  if (maybeJson(view, opts)) return;
2817
2889
  console.log(
2818
- chalk15.green("updated"),
2890
+ chalk14.green("updated"),
2819
2891
  view.id,
2820
- chalk15.dim(`(${view.status}/${view.confidence})`),
2892
+ chalk14.dim(`(${view.status}/${view.confidence})`),
2821
2893
  view.title
2822
2894
  );
2823
2895
  });
@@ -2837,10 +2909,10 @@ kr.command("progress <plan-id> <kr-id>").description("Update key result progress
2837
2909
  );
2838
2910
  if (maybeJson(view, opts)) return;
2839
2911
  console.log(
2840
- chalk15.green("progress"),
2912
+ chalk14.green("progress"),
2841
2913
  view.id,
2842
2914
  krProgress(view),
2843
- chalk15.dim(`(${view.status}/${view.confidence})`)
2915
+ chalk14.dim(`(${view.status}/${view.confidence})`)
2844
2916
  );
2845
2917
  });
2846
2918
  }
@@ -2864,11 +2936,11 @@ milestone.command("create <plan-id> <milestone-id>").description("Create a miles
2864
2936
  );
2865
2937
  if (maybeJson(view, opts)) return;
2866
2938
  console.log(
2867
- chalk15.green("ok"),
2939
+ chalk14.green("ok"),
2868
2940
  view.id,
2869
- chalk15.dim(view.object_id || ""),
2941
+ chalk14.dim(view.object_id || ""),
2870
2942
  dateShort(view.due_at),
2871
- chalk15.dim(`(${view.status})`),
2943
+ chalk14.dim(`(${view.status})`),
2872
2944
  view.title
2873
2945
  );
2874
2946
  });
@@ -2891,10 +2963,10 @@ milestone.command("update <plan-id> <milestone-id>").description("Update a miles
2891
2963
  );
2892
2964
  if (maybeJson(view, opts)) return;
2893
2965
  console.log(
2894
- chalk15.green("updated"),
2966
+ chalk14.green("updated"),
2895
2967
  view.id,
2896
2968
  dateShort(view.due_at),
2897
- chalk15.dim(`(${view.status})`),
2969
+ chalk14.dim(`(${view.status})`),
2898
2970
  view.title
2899
2971
  );
2900
2972
  });
@@ -2916,9 +2988,9 @@ okr.command("link <plan-id>").description("Link a task to a key result").require
2916
2988
  );
2917
2989
  if (maybeJson(link, opts)) return;
2918
2990
  console.log(
2919
- chalk15.green("linked"),
2991
+ chalk14.green("linked"),
2920
2992
  `${link.task_id} \u2192 ${link.kr_id}`,
2921
- chalk15.dim(`${link.contribution} w=${link.weight}`)
2993
+ chalk14.dim(`${link.contribution} w=${link.weight}`)
2922
2994
  );
2923
2995
  });
2924
2996
  }
@@ -2930,15 +3002,15 @@ okr.command("unlink <plan-id>").description("Unlink a task from a key result").r
2930
3002
  `${planEncodedPath(name, planId)}/task-kr-links/${encodeURIComponent(opts.task)}/${encodeURIComponent(opts.kr)}`
2931
3003
  );
2932
3004
  if (maybeJson(result, opts)) return;
2933
- console.log(chalk15.green("unlinked"), `${opts.task} \u2715 ${opts.kr}`);
3005
+ console.log(chalk14.green("unlinked"), `${opts.task} \u2715 ${opts.kr}`);
2934
3006
  });
2935
3007
  }
2936
3008
  );
2937
3009
 
2938
3010
  // src/commands/note.ts
2939
- import { Command as Command16 } from "commander";
2940
- import chalk16 from "chalk";
2941
- 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");
2942
3014
  async function fetchAllInboxNotes() {
2943
3015
  const { inboxes } = await api.get("/api/inboxes");
2944
3016
  const results = [];
@@ -2980,33 +3052,33 @@ function preview2(body, width = 60) {
2980
3052
  return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
2981
3053
  }
2982
3054
  function printNoteRow(n, inboxName) {
2983
- const oid = chalk16.cyan((n.object_id || "-").padEnd(14));
2984
- const id = chalk16.dim(n.id.padEnd(10));
2985
- const tags = n.tags.length ? chalk16.yellow(n.tags.join(",")) : chalk16.dim("-");
2986
- const linked = n.linked_task_id ? chalk16.magenta(n.linked_task_id) : chalk16.dim("-");
2987
- const inboxPart = inboxName ? chalk16.dim(`[${inboxName}] `) : "";
2988
- 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)}`);
2989
3061
  }
2990
3062
  function printNoteDetail(n, inboxId) {
2991
- if (n.object_id) console.log(`${chalk16.bold("object_id:")} ${n.object_id}`);
2992
- console.log(`${chalk16.bold("id:")} ${n.id}`);
2993
- if (inboxId) console.log(`${chalk16.bold("inbox:")} ${inboxId}`);
2994
- console.log(`${chalk16.bold("tags:")} ${n.tags.length ? n.tags.join(", ") : "-"}`);
2995
- if (n.linked_task_id) console.log(`${chalk16.bold("linked_task:")} ${n.linked_task_id}`);
2996
- if (n.linked_project) console.log(`${chalk16.bold("linked_project:")} ${n.linked_project}`);
2997
- console.log(`${chalk16.bold("created_at:")} ${n.created_at}`);
2998
- 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}`);
2999
3071
  if (n.published_to?.length) {
3000
- console.log(chalk16.bold("published_to:"));
3072
+ console.log(chalk15.bold("published_to:"));
3001
3073
  for (const p of n.published_to) {
3002
- 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)}`);
3003
3075
  }
3004
3076
  }
3005
- console.log(chalk16.bold("body:"));
3077
+ console.log(chalk15.bold("body:"));
3006
3078
  console.log(n.body.split("\n").map((l) => " " + l).join("\n"));
3007
3079
  }
3008
3080
  function fail4(err) {
3009
- console.error(chalk16.red(err.message));
3081
+ console.error(chalk15.red(err.message));
3010
3082
  process.exit(1);
3011
3083
  }
3012
3084
  note.command("list").description("List inbox notes").option("--inbox <id>", "Restrict to a specific inbox").option("--json", "Output JSON").action(async (opts) => {
@@ -3020,7 +3092,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
3020
3092
  return;
3021
3093
  }
3022
3094
  if (!notes.length) {
3023
- console.log(chalk16.dim("No notes."));
3095
+ console.log(chalk15.dim("No notes."));
3024
3096
  return;
3025
3097
  }
3026
3098
  for (const n of notes) printNoteRow(n);
@@ -3033,7 +3105,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
3033
3105
  }
3034
3106
  const flat = all.flatMap(({ inbox, notes }) => notes.map((n) => ({ n, inboxName: inbox.name })));
3035
3107
  if (!flat.length) {
3036
- console.log(chalk16.dim("No notes."));
3108
+ console.log(chalk15.dim("No notes."));
3037
3109
  return;
3038
3110
  }
3039
3111
  for (const { n, inboxName } of flat) printNoteRow(n, inboxName);
@@ -3066,7 +3138,7 @@ note.command("create").description("Create a note in an inbox").requiredOption("
3066
3138
  console.log(JSON.stringify({ note: created }, null, 2));
3067
3139
  return;
3068
3140
  }
3069
- console.log(chalk16.green(`Created ${created.object_id || created.id}`));
3141
+ console.log(chalk15.green(`Created ${created.object_id || created.id}`));
3070
3142
  printNoteDetail(created, opts.inbox);
3071
3143
  } catch (err) {
3072
3144
  fail4(err);
@@ -3082,7 +3154,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
3082
3154
  if (opts.linkedTask !== void 0) payload.linked_task_id = opts.linkedTask;
3083
3155
  if (opts.linkedProject !== void 0) payload.linked_project = opts.linkedProject;
3084
3156
  if (!Object.keys(payload).length) {
3085
- 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."));
3086
3158
  process.exit(1);
3087
3159
  }
3088
3160
  const { note: updated } = await api.patch(
@@ -3093,7 +3165,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
3093
3165
  console.log(JSON.stringify({ note: updated }, null, 2));
3094
3166
  return;
3095
3167
  }
3096
- console.log(chalk16.green(`Updated ${updated.object_id || updated.id}`));
3168
+ console.log(chalk15.green(`Updated ${updated.object_id || updated.id}`));
3097
3169
  printNoteDetail(updated, inboxId);
3098
3170
  } catch (err) {
3099
3171
  fail4(err);
@@ -3105,7 +3177,7 @@ note.command("delete <id>").description("Delete a note (accepts short id or note
3105
3177
  await api.del(
3106
3178
  `/api/inboxes/${encodeURIComponent(inboxId)}/notes/${encodeURIComponent(existing.id)}`
3107
3179
  );
3108
- console.log(chalk16.green(`Deleted ${existing.object_id || existing.id}`));
3180
+ console.log(chalk15.green(`Deleted ${existing.object_id || existing.id}`));
3109
3181
  } catch (err) {
3110
3182
  fail4(err);
3111
3183
  }
@@ -3123,8 +3195,8 @@ note.command("publish <id>").description("Publish a note to a github/linear sour
3123
3195
  console.log(JSON.stringify({ published }, null, 2));
3124
3196
  return;
3125
3197
  }
3126
- console.log(chalk16.green(`Published to ${published.source_type}:${published.source_name} ${published.identifier}`));
3127
- 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)}`);
3128
3200
  } catch (err) {
3129
3201
  fail4(err);
3130
3202
  }
@@ -3140,16 +3212,16 @@ note.command("convert <id>").description("Convert a note into a task under a pro
3140
3212
  console.log(JSON.stringify({ task: task2 }, null, 2));
3141
3213
  return;
3142
3214
  }
3143
- 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}`));
3144
3216
  } catch (err) {
3145
3217
  fail4(err);
3146
3218
  }
3147
3219
  });
3148
3220
 
3149
3221
  // src/commands/object.ts
3150
- import { Command as Command17 } from "commander";
3151
- import chalk17 from "chalk";
3152
- 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");
3153
3225
  function pickFirst(resource, keys) {
3154
3226
  for (const k of keys) {
3155
3227
  const value = resource[k];
@@ -3159,16 +3231,16 @@ function pickFirst(resource, keys) {
3159
3231
  }
3160
3232
  function printSummary(result) {
3161
3233
  const { type, resource } = result;
3162
- console.log(`${chalk17.bold("type:")} ${type}`);
3234
+ console.log(`${chalk16.bold("type:")} ${type}`);
3163
3235
  const title = pickFirst(resource, ["title", "name"]);
3164
3236
  const id = pickFirst(resource, ["object_id", "objectId", "id"]);
3165
3237
  const status = pickFirst(resource, ["status"]);
3166
- if (id) console.log(`${chalk17.bold("object_id:")} ${id}`);
3167
- if (title) console.log(`${chalk17.bold("title:")} ${title}`);
3168
- 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}`);
3169
3241
  const updatedAt = pickFirst(resource, ["updated_at", "updatedAt"]);
3170
- if (updatedAt) console.log(`${chalk17.bold("updated_at:")} ${updatedAt}`);
3171
- 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)"));
3172
3244
  }
3173
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) => {
3174
3246
  try {
@@ -3181,17 +3253,17 @@ object.command("get <object-id>").description("Resolve an object by object_id (t
3181
3253
  } catch (err) {
3182
3254
  const apiErr = err;
3183
3255
  if (apiErr.status === 404) {
3184
- console.error(chalk17.red(`not found: ${objectId}`));
3256
+ console.error(chalk16.red(`not found: ${objectId}`));
3185
3257
  process.exit(1);
3186
3258
  }
3187
- console.error(chalk17.red(err.message));
3259
+ console.error(chalk16.red(err.message));
3188
3260
  process.exit(1);
3189
3261
  }
3190
3262
  });
3191
3263
 
3192
3264
  // src/commands/issue.ts
3193
- import { Command as Command18 } from "commander";
3194
- import chalk18 from "chalk";
3265
+ import { Command as Command17 } from "commander";
3266
+ import chalk17 from "chalk";
3195
3267
 
3196
3268
  // src/core/issue/decision.ts
3197
3269
  init_node();
@@ -3365,7 +3437,7 @@ async function propose(opts) {
3365
3437
  kicks.push({
3366
3438
  issue: issue2.file,
3367
3439
  agent: agent2,
3368
- runtimeId: "",
3440
+ agentRunId: "",
3369
3441
  sessionName: "",
3370
3442
  decisionFile,
3371
3443
  error: null,
@@ -3375,7 +3447,7 @@ async function propose(opts) {
3375
3447
  kicks.push({
3376
3448
  issue: issue2.file,
3377
3449
  agent: agent2,
3378
- runtimeId: "",
3450
+ agentRunId: "",
3379
3451
  sessionName: "",
3380
3452
  decisionFile,
3381
3453
  error: `${decisionFile} already exists (pass --force to overwrite)`
@@ -3399,7 +3471,7 @@ async function propose(opts) {
3399
3471
  kicks.push({
3400
3472
  issue: issue2.file,
3401
3473
  agent: agent2,
3402
- runtimeId: resp.runtime.id,
3474
+ agentRunId: resp.runtime.id,
3403
3475
  sessionName: "",
3404
3476
  decisionFile,
3405
3477
  error: null
@@ -3408,7 +3480,7 @@ async function propose(opts) {
3408
3480
  kicks.push({
3409
3481
  issue: issue2.file,
3410
3482
  agent: agent2,
3411
- runtimeId: "",
3483
+ agentRunId: "",
3412
3484
  sessionName: "",
3413
3485
  decisionFile,
3414
3486
  error: err.message
@@ -3416,25 +3488,25 @@ async function propose(opts) {
3416
3488
  }
3417
3489
  }
3418
3490
  const runtimesByAgent = {};
3419
- 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;
3420
3492
  const proposalFiles = kicks.filter((k) => !k.error).map((k) => k.decisionFile);
3421
3493
  if (Object.keys(runtimesByAgent).length > 0 || proposalFiles.length > 0) {
3422
3494
  await updateWorkflow(loc.taskYml, {
3423
3495
  decisions: {
3424
3496
  [issue2.file]: {
3425
3497
  proposal_files: proposalFiles.length > 0 ? proposalFiles : void 0,
3426
- proposal_runtimes: Object.keys(runtimesByAgent).length > 0 ? runtimesByAgent : void 0
3498
+ proposal_agent_runs: Object.keys(runtimesByAgent).length > 0 ? runtimesByAgent : void 0
3427
3499
  }
3428
3500
  }
3429
3501
  });
3430
3502
  }
3431
3503
  if (opts.wait) {
3432
3504
  for (const k of kicks) {
3433
- if (k.error || !k.runtimeId) continue;
3505
+ if (k.error || !k.agentRunId) continue;
3434
3506
  try {
3435
- const final = await waitForRuntime(k.runtimeId);
3507
+ const final = await waitForAgentRun(k.agentRunId);
3436
3508
  if (final.status !== "done") {
3437
- k.error = final.error || `runtime ${k.runtimeId} ended with ${final.status}`;
3509
+ k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
3438
3510
  } else if (!fs18.existsSync(path17.join(loc.taskDir, k.decisionFile))) {
3439
3511
  k.error = `runtime completed but ${k.decisionFile} was not written`;
3440
3512
  }
@@ -3464,7 +3536,7 @@ async function consolidate(opts) {
3464
3536
  if (proposalFiles.length === 0) {
3465
3537
  kicks.push({
3466
3538
  issue: issue2.file,
3467
- runtimeId: "",
3539
+ agentRunId: "",
3468
3540
  sessionName: "",
3469
3541
  consolidatedFile: consolidatedFileName(issue2),
3470
3542
  error: `no DECISION-${issue2.index}-<agent>.md proposals found. Run \`task0 issue propose\` first.`
@@ -3483,28 +3555,28 @@ async function consolidate(opts) {
3483
3555
  prompt
3484
3556
  }
3485
3557
  );
3486
- const runtimeId = resp.runtime.id;
3558
+ const agentRunId = resp.runtime.id;
3487
3559
  await updateWorkflow(loc.taskYml, {
3488
3560
  decisions: {
3489
3561
  [issue2.file]: {
3490
3562
  consolidated_file: consolidatedFile,
3491
- consolidate_runtime: runtimeId
3563
+ consolidate_agent_run: agentRunId
3492
3564
  }
3493
3565
  }
3494
3566
  });
3495
3567
  const kick = {
3496
3568
  issue: issue2.file,
3497
- runtimeId,
3569
+ agentRunId,
3498
3570
  sessionName: "",
3499
3571
  consolidatedFile,
3500
3572
  error: null
3501
3573
  };
3502
3574
  if (opts.wait) {
3503
3575
  try {
3504
- const final = await waitForRuntime(runtimeId);
3576
+ const final = await waitForAgentRun(agentRunId);
3505
3577
  const wrote = fs18.existsSync(path17.join(loc.taskDir, consolidatedFile));
3506
3578
  if (final.status !== "done") {
3507
- kick.error = final.error || `runtime ${runtimeId} ended with ${final.status}`;
3579
+ kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
3508
3580
  } else if (!wrote) {
3509
3581
  kick.error = `runtime completed but ${consolidatedFile} was not written`;
3510
3582
  }
@@ -3517,7 +3589,7 @@ async function consolidate(opts) {
3517
3589
  } catch (err) {
3518
3590
  kicks.push({
3519
3591
  issue: issue2.file,
3520
- runtimeId: "",
3592
+ agentRunId: "",
3521
3593
  sessionName: "",
3522
3594
  consolidatedFile,
3523
3595
  error: err.message
@@ -3577,7 +3649,7 @@ function buildReferenceFiles(taskDir, issue2) {
3577
3649
  }
3578
3650
 
3579
3651
  // src/commands/issue.ts
3580
- var issue = new Command18("issue").description(
3652
+ var issue = new Command17("issue").description(
3581
3653
  "Resolve blocking ISSUE Open Questions via multi-agent decision proposals"
3582
3654
  );
3583
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) => {
@@ -3593,7 +3665,7 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
3593
3665
  wait: opts.wait,
3594
3666
  force: opts.force,
3595
3667
  ifNeeded: opts.ifNeeded,
3596
- warn: opts.json ? void 0 : (m) => console.error(chalk18.dim(m))
3668
+ warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
3597
3669
  });
3598
3670
  if (opts.json) {
3599
3671
  console.log(JSON.stringify(result, null, 2));
@@ -3602,26 +3674,26 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
3602
3674
  return;
3603
3675
  }
3604
3676
  if (opts.ifNeeded && result.issues.length === 0) {
3605
- console.log(chalk18.dim("no blocking Open Questions; nothing to propose"));
3677
+ console.log(chalk17.dim("no blocking Open Questions; nothing to propose"));
3606
3678
  return;
3607
3679
  }
3608
3680
  let anyFailed = false;
3609
3681
  for (const item of result.issues) {
3610
- console.log(chalk18.bold(item.issue));
3682
+ console.log(chalk17.bold(item.issue));
3611
3683
  for (const k of item.kicks) {
3612
3684
  if (k.error) {
3613
3685
  anyFailed = true;
3614
- console.error(chalk18.red(` [${k.agent}] ${k.error}`));
3686
+ console.error(chalk17.red(` [${k.agent}] ${k.error}`));
3615
3687
  } else if (k.skipped === "already-exists") {
3616
- console.log(chalk18.dim(` [${k.agent}] skipped (${k.decisionFile} exists)`));
3688
+ console.log(chalk17.dim(` [${k.agent}] skipped (${k.decisionFile} exists)`));
3617
3689
  } else {
3618
- 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}`);
3619
3691
  }
3620
3692
  }
3621
3693
  }
3622
3694
  if (anyFailed) process.exit(1);
3623
3695
  } catch (err) {
3624
- console.error(chalk18.red(err.message));
3696
+ console.error(chalk17.red(err.message));
3625
3697
  process.exit(1);
3626
3698
  }
3627
3699
  });
@@ -3634,7 +3706,7 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
3634
3706
  model: opts.model,
3635
3707
  effort: opts.effort,
3636
3708
  wait: opts.wait,
3637
- warn: opts.json ? void 0 : (m) => console.error(chalk18.dim(m))
3709
+ warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
3638
3710
  });
3639
3711
  if (opts.json) {
3640
3712
  console.log(JSON.stringify(result, null, 2));
@@ -3646,16 +3718,16 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
3646
3718
  for (const k of result.kicks) {
3647
3719
  if (k.error) {
3648
3720
  anyFailed = true;
3649
- console.error(chalk18.red(`[${k.issue}] ${k.error}`));
3721
+ console.error(chalk17.red(`[${k.issue}] ${k.error}`));
3650
3722
  } else {
3651
3723
  console.log(
3652
- 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)") : "")
3653
3725
  );
3654
3726
  }
3655
3727
  }
3656
3728
  if (anyFailed) process.exit(1);
3657
3729
  } catch (err) {
3658
- console.error(chalk18.red(err.message));
3730
+ console.error(chalk17.red(err.message));
3659
3731
  process.exit(1);
3660
3732
  }
3661
3733
  });
@@ -3667,10 +3739,10 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
3667
3739
  return;
3668
3740
  }
3669
3741
  for (const u of result.updated) {
3670
- 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})`);
3671
3743
  }
3672
3744
  } catch (err) {
3673
- console.error(chalk18.red(err.message));
3745
+ console.error(chalk17.red(err.message));
3674
3746
  process.exit(1);
3675
3747
  }
3676
3748
  });
@@ -3678,23 +3750,37 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
3678
3750
  // src/commands/agent.ts
3679
3751
  import fs19 from "fs";
3680
3752
  import path18 from "path";
3681
- import { spawnSync as spawnSync4 } from "child_process";
3682
- import { Command as Command19 } from "commander";
3683
- import chalk19 from "chalk";
3684
- 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
+ }
3685
3770
  function fail5(message, code = 1) {
3686
- console.error(chalk19.red(message));
3771
+ console.error(chalk18.red(message));
3687
3772
  process.exit(code);
3688
3773
  }
3689
3774
  function formatAgent(a, withDetails = false) {
3690
- const tag = a.system ? chalk19.dim("(system)") : a.scope ? chalk19.dim(`(${a.scope})`) : "";
3691
- 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}`;
3692
3778
  if (!withDetails) return head;
3693
3779
  const lines = [head];
3694
- if (a.description) lines.push(chalk19.dim(" " + a.description));
3780
+ if (a.description) lines.push(chalk18.dim(" " + a.description));
3695
3781
  return lines.join("\n");
3696
3782
  }
3697
- 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");
3698
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) => {
3699
3785
  try {
3700
3786
  const params = new URLSearchParams();
@@ -3704,7 +3790,7 @@ agent.command("list").description("List visible agents (project + user + built-i
3704
3790
  const result = await api.get(`/api/agents${qs}`);
3705
3791
  if (opts.json) return console.log(JSON.stringify(result.agents, null, 2));
3706
3792
  if (!result.agents.length) {
3707
- console.log(chalk19.dim("(no agents)"));
3793
+ console.log(chalk18.dim("(no agents)"));
3708
3794
  return;
3709
3795
  }
3710
3796
  for (const a of result.agents) console.log(formatAgent(a, true));
@@ -3715,11 +3801,23 @@ agent.command("list").description("List visible agents (project + user + built-i
3715
3801
  agent.command("get <ref>").description("Show one agent by object_id or slug").option("--json", "Output JSON").action(async (ref, opts) => {
3716
3802
  try {
3717
3803
  const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
3718
- if (opts.json) return console.log(JSON.stringify(result.agent, null, 2));
3804
+ if (opts.json) return console.log(JSON.stringify(result, null, 2));
3719
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
+ }
3720
3814
  console.log();
3721
- console.log(chalk19.dim("--- spec ---"));
3722
- 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
+ }
3723
3821
  } catch (err) {
3724
3822
  const apiErr = err;
3725
3823
  if (apiErr.status === 404) fail5(`not found: ${ref}`);
@@ -3730,14 +3828,14 @@ agent.command("create").description("Create an agent from a YAML spec file").req
3730
3828
  let parsed;
3731
3829
  try {
3732
3830
  const raw = fs19.readFileSync(path18.resolve(opts.fromFile), "utf-8");
3733
- parsed = yaml6.load(raw);
3831
+ parsed = yaml7.load(raw);
3734
3832
  } catch (err) {
3735
3833
  fail5(`cannot read ${opts.fromFile}: ${err.message}`);
3736
3834
  }
3737
3835
  try {
3738
3836
  const body = { ...parsed, scope: opts.scope, project_root: opts.projectRoot };
3739
3837
  const result = await api.post("/api/agents", body);
3740
- 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})`));
3741
3839
  } catch (err) {
3742
3840
  fail5(err.message);
3743
3841
  }
@@ -3750,14 +3848,14 @@ agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save
3750
3848
  process.env.TMPDIR || "/tmp",
3751
3849
  `task0-agent-${result.agent.slug}-${Date.now()}.yml`
3752
3850
  );
3753
- fs19.writeFileSync(tmp, yaml6.dump(result.agent, { lineWidth: 100 }), "utf-8");
3851
+ fs19.writeFileSync(tmp, yaml7.dump(result.agent, { lineWidth: 100 }), "utf-8");
3754
3852
  const editor = process.env.EDITOR || "vi";
3755
- const r = spawnSync4(editor, [tmp], { stdio: "inherit" });
3853
+ const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
3756
3854
  if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
3757
- const updated = yaml6.load(fs19.readFileSync(tmp, "utf-8"));
3855
+ const updated = yaml7.load(fs19.readFileSync(tmp, "utf-8"));
3758
3856
  await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
3759
3857
  fs19.unlinkSync(tmp);
3760
- console.log(chalk19.green(`updated ${ref}`));
3858
+ console.log(chalk18.green(`updated ${ref}`));
3761
3859
  } catch (err) {
3762
3860
  fail5(err.message);
3763
3861
  }
@@ -3766,38 +3864,99 @@ agent.command("delete <ref>").description("Delete an agent").action(async (ref)
3766
3864
  try {
3767
3865
  const result = await api.del(`/api/agents/${encodeURIComponent(ref)}`);
3768
3866
  if (!result.deleted) fail5(`not deleted: ${ref}`);
3769
- console.log(chalk19.green(`deleted ${ref}`));
3867
+ console.log(chalk18.green(`deleted ${ref}`));
3770
3868
  } catch (err) {
3771
3869
  fail5(err.message);
3772
3870
  }
3773
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
+ }
3900
+ } catch (err) {
3901
+ const apiErr = err;
3902
+ if (apiErr.status === 404) fail5(`not found: ${ref}`);
3903
+ fail5(err.message);
3904
+ }
3905
+ });
3774
3906
  agent.command("run <ref>").description("Run an agent against a task with a user prompt").requiredOption("--task <task-id>", "Task id (tsk_\u2026 or directory name)").requiredOption("--prompt <text>", "User prompt to send the agent").option("--stream", "Stream output.jsonl to stdout as it arrives").option("--attach", "Open tmux session (coding agents only)").action(async (ref, opts) => {
3775
3907
  try {
3776
3908
  const result = await api.post(
3777
3909
  `/api/agents/${encodeURIComponent(ref)}/run`,
3778
3910
  { task_id: opts.task, prompt: opts.prompt }
3779
3911
  );
3780
- console.log(chalk19.green(`runtime ${result.runtime.id} started`));
3781
- 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}`));
3782
3914
  if (opts.stream) {
3783
3915
  await streamOutput(ref, result.runtime.id);
3784
3916
  }
3785
3917
  if (opts.attach) {
3786
- 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"));
3787
3919
  }
3788
3920
  } catch (err) {
3789
3921
  fail5(err.message);
3790
3922
  }
3791
3923
  });
3792
- 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) {
3793
3952
  let lastCount = 0;
3794
3953
  for (let i = 0; i < 600; i++) {
3795
3954
  const result = await api.get(
3796
- `/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(runtimeId)}/output`
3955
+ `/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(agentRunId)}/output`
3797
3956
  );
3798
3957
  for (const line of result.lines.slice(lastCount)) {
3799
3958
  if (line.error?.message) {
3800
- console.error(chalk19.red(`[error] ${line.error.message}`));
3959
+ console.error(chalk18.red(`[error] ${line.error.message}`));
3801
3960
  return;
3802
3961
  }
3803
3962
  const text = line.message?.content?.map((c) => c.text ?? "").join("") ?? "";
@@ -3809,47 +3968,115 @@ async function streamOutput(ref, runtimeId) {
3809
3968
  }
3810
3969
 
3811
3970
  // src/commands/daemon.ts
3812
- import os6 from "os";
3813
- import { Command as Command20 } from "commander";
3814
- import chalk20 from "chalk";
3971
+ import os7 from "os";
3972
+ import { Command as Command19 } from "commander";
3973
+ import chalk19 from "chalk";
3815
3974
  import WebSocket from "ws";
3816
3975
 
3817
- // src/core/daemon-config.ts
3976
+ // src/core/admin-token.ts
3818
3977
  import fs20 from "fs";
3819
3978
  import os4 from "os";
3820
3979
  import path19 from "path";
3821
3980
  var CONFIG_DIR2 = path19.join(os4.homedir(), ".config", "task0");
3822
- 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");
3823
4019
  function daemonConfigPath() {
3824
4020
  return CONFIG_FILE2;
3825
4021
  }
3826
4022
  function readDaemonIdentity() {
3827
- if (!fs20.existsSync(CONFIG_FILE2)) return null;
4023
+ if (!fs21.existsSync(CONFIG_FILE2)) return null;
3828
4024
  try {
3829
- const raw = fs20.readFileSync(CONFIG_FILE2, "utf-8");
4025
+ const raw = fs21.readFileSync(CONFIG_FILE2, "utf-8");
3830
4026
  return JSON.parse(raw);
3831
4027
  } catch {
3832
4028
  return null;
3833
4029
  }
3834
4030
  }
3835
4031
  function writeDaemonIdentity(identity) {
3836
- fs20.mkdirSync(CONFIG_DIR2, { recursive: true });
3837
- 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 });
3838
4034
  try {
3839
- fs20.chmodSync(CONFIG_FILE2, 384);
4035
+ fs21.chmodSync(CONFIG_FILE2, 384);
3840
4036
  } catch {
3841
4037
  }
3842
4038
  }
3843
4039
  function clearDaemonIdentity() {
3844
- if (!fs20.existsSync(CONFIG_FILE2)) return false;
3845
- fs20.unlinkSync(CONFIG_FILE2);
4040
+ if (!fs21.existsSync(CONFIG_FILE2)) return false;
4041
+ fs21.unlinkSync(CONFIG_FILE2);
3846
4042
  return true;
3847
4043
  }
3848
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
+
3849
4076
  // src/core/daemon-rpc-handlers.ts
3850
4077
  init_node();
3851
- import fs21 from "fs";
3852
- import path20 from "path";
4078
+ import fs22 from "fs";
4079
+ import path21 from "path";
3853
4080
  var MAX_FILE_BYTES = 1 * 1024 * 1024;
3854
4081
  function ensureString(value, name) {
3855
4082
  if (typeof value !== "string" || value.length === 0) {
@@ -3862,7 +4089,7 @@ var rpcHandlers = {
3862
4089
  // in-process server's /api/tasks scanProject() returns.
3863
4090
  async scan_project(params) {
3864
4091
  const rootPath = ensureString(params.rootPath, "rootPath");
3865
- 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);
3866
4093
  return scanProject(rootPath, name);
3867
4094
  },
3868
4095
  // Read a file from disk on the daemon's host. The dashboard uses this to
@@ -3872,7 +4099,7 @@ var rpcHandlers = {
3872
4099
  const filePath = ensureString(params.path, "path");
3873
4100
  let stat;
3874
4101
  try {
3875
- stat = fs21.statSync(filePath);
4102
+ stat = fs22.statSync(filePath);
3876
4103
  } catch {
3877
4104
  throw Object.assign(new Error("file not found"), { code: "not_found" });
3878
4105
  }
@@ -3882,23 +4109,23 @@ var rpcHandlers = {
3882
4109
  if (stat.size > MAX_FILE_BYTES) {
3883
4110
  throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
3884
4111
  }
3885
- const content = fs21.readFileSync(filePath, "utf-8");
4112
+ const content = fs22.readFileSync(filePath, "utf-8");
3886
4113
  return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
3887
4114
  }
3888
4115
  };
3889
4116
 
3890
4117
  // src/core/daemon-service/launchd.ts
3891
- import { spawnSync as spawnSync5 } from "child_process";
3892
- import fs23 from "fs";
3893
- import path22 from "path";
4118
+ import { spawnSync as spawnSync6 } from "child_process";
4119
+ import fs24 from "fs";
4120
+ import path23 from "path";
3894
4121
 
3895
4122
  // src/core/daemon-service/binary.ts
3896
- import fs22 from "fs";
4123
+ import fs23 from "fs";
3897
4124
  import { fileURLToPath } from "url";
3898
4125
  function resolveTask0Invocation() {
3899
4126
  const node = process.execPath;
3900
4127
  const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
3901
- const main2 = fs22.realpathSync(argv1);
4128
+ const main2 = fs23.realpathSync(argv1);
3902
4129
  if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
3903
4130
  throw new Error(
3904
4131
  `Refusing to install autostart service pointing at ${main2}.
@@ -3914,8 +4141,8 @@ function isInstalledBuild(p) {
3914
4141
  }
3915
4142
 
3916
4143
  // src/core/daemon-service/paths.ts
3917
- import os5 from "os";
3918
- import path21 from "path";
4144
+ import os6 from "os";
4145
+ import path22 from "path";
3919
4146
 
3920
4147
  // src/core/daemon-service/types.ts
3921
4148
  var SERVICE_LABEL = "cc.cy0.task0";
@@ -3924,21 +4151,21 @@ var SERVICE_LABEL = "cc.cy0.task0";
3924
4151
  function unitPath(scope) {
3925
4152
  const platform = process.platform;
3926
4153
  if (platform === "darwin") {
3927
- 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`);
3928
4155
  }
3929
4156
  if (platform === "linux") {
3930
- 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`);
3931
4158
  }
3932
4159
  throw new Error(`Unsupported platform for service install: ${platform}`);
3933
4160
  }
3934
4161
  function logDir() {
3935
- return path21.join(os5.homedir(), ".task0", "logs");
4162
+ return path22.join(os6.homedir(), ".task0", "logs");
3936
4163
  }
3937
4164
  function logPaths() {
3938
4165
  const dir = logDir();
3939
4166
  return {
3940
- out: path21.join(dir, "daemon.out.log"),
3941
- err: path21.join(dir, "daemon.err.log")
4167
+ out: path22.join(dir, "daemon.out.log"),
4168
+ err: path22.join(dir, "daemon.err.log")
3942
4169
  };
3943
4170
  }
3944
4171
 
@@ -3991,7 +4218,7 @@ function serviceTarget(scope) {
3991
4218
  return `${domainTarget(scope)}/${SERVICE_LABEL}`;
3992
4219
  }
3993
4220
  function run2(cmd, args) {
3994
- const res = spawnSync5(cmd, args, { encoding: "utf-8" });
4221
+ const res = spawnSync6(cmd, args, { encoding: "utf-8" });
3995
4222
  return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
3996
4223
  }
3997
4224
  function createLaunchdManager(scope) {
@@ -3999,8 +4226,8 @@ function createLaunchdManager(scope) {
3999
4226
  const logs = logPaths();
4000
4227
  async function install() {
4001
4228
  const inv = resolveTask0Invocation();
4002
- fs23.mkdirSync(logDir(), { recursive: true });
4003
- fs23.mkdirSync(path22.dirname(file), { recursive: true });
4229
+ fs24.mkdirSync(logDir(), { recursive: true });
4230
+ fs24.mkdirSync(path23.dirname(file), { recursive: true });
4004
4231
  const body = renderPlist({
4005
4232
  node: inv.node,
4006
4233
  main: inv.main,
@@ -4009,7 +4236,7 @@ function createLaunchdManager(scope) {
4009
4236
  out: logs.out,
4010
4237
  err: logs.err
4011
4238
  });
4012
- fs23.writeFileSync(file, body, { mode: 420 });
4239
+ fs24.writeFileSync(file, body, { mode: 420 });
4013
4240
  const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
4014
4241
  if (bootstrap.code !== 0) {
4015
4242
  const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
@@ -4035,9 +4262,9 @@ function createLaunchdManager(scope) {
4035
4262
  }
4036
4263
  async function uninstall() {
4037
4264
  run2("launchctl", ["bootout", serviceTarget(scope)]);
4038
- if (fs23.existsSync(file)) {
4265
+ if (fs24.existsSync(file)) {
4039
4266
  run2("launchctl", ["unload", file]);
4040
- fs23.unlinkSync(file);
4267
+ fs24.unlinkSync(file);
4041
4268
  }
4042
4269
  }
4043
4270
  async function start() {
@@ -4056,7 +4283,7 @@ function createLaunchdManager(scope) {
4056
4283
  }
4057
4284
  }
4058
4285
  async function status() {
4059
- if (!fs23.existsSync(file)) return "absent";
4286
+ if (!fs24.existsSync(file)) return "absent";
4060
4287
  const printed = run2("launchctl", ["print", serviceTarget(scope)]);
4061
4288
  if (printed.code !== 0) return "installed";
4062
4289
  const out = printed.stdout;
@@ -4078,9 +4305,9 @@ function createLaunchdManager(scope) {
4078
4305
  }
4079
4306
 
4080
4307
  // src/core/daemon-service/systemd.ts
4081
- import { spawnSync as spawnSync6 } from "child_process";
4082
- import fs24 from "fs";
4083
- import path23 from "path";
4308
+ import { spawnSync as spawnSync7 } from "child_process";
4309
+ import fs25 from "fs";
4310
+ import path24 from "path";
4084
4311
  function shellEscape(s) {
4085
4312
  if (!/[\s"\\$]/.test(s)) return s;
4086
4313
  return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
@@ -4110,7 +4337,7 @@ function scopeFlag(scope) {
4110
4337
  return scope === "user" ? ["--user"] : [];
4111
4338
  }
4112
4339
  function run3(cmd, args) {
4113
- const res = spawnSync6(cmd, args, { encoding: "utf-8" });
4340
+ const res = spawnSync7(cmd, args, { encoding: "utf-8" });
4114
4341
  return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
4115
4342
  }
4116
4343
  function createSystemdManager(scope) {
@@ -4119,8 +4346,8 @@ function createSystemdManager(scope) {
4119
4346
  const unitName = `${SERVICE_LABEL}.service`;
4120
4347
  async function install() {
4121
4348
  const inv = resolveTask0Invocation();
4122
- fs24.mkdirSync(logDir(), { recursive: true });
4123
- fs24.mkdirSync(path23.dirname(file), { recursive: true });
4349
+ fs25.mkdirSync(logDir(), { recursive: true });
4350
+ fs25.mkdirSync(path24.dirname(file), { recursive: true });
4124
4351
  const body = renderUnit({
4125
4352
  node: inv.node,
4126
4353
  main: inv.main,
@@ -4129,7 +4356,7 @@ function createSystemdManager(scope) {
4129
4356
  err: logs.err,
4130
4357
  scope
4131
4358
  });
4132
- fs24.writeFileSync(file, body, { mode: 420 });
4359
+ fs25.writeFileSync(file, body, { mode: 420 });
4133
4360
  const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
4134
4361
  if (reload.code !== 0) {
4135
4362
  throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
@@ -4138,8 +4365,8 @@ function createSystemdManager(scope) {
4138
4365
  }
4139
4366
  async function uninstall() {
4140
4367
  run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
4141
- if (fs24.existsSync(file)) {
4142
- fs24.unlinkSync(file);
4368
+ if (fs25.existsSync(file)) {
4369
+ fs25.unlinkSync(file);
4143
4370
  }
4144
4371
  run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
4145
4372
  }
@@ -4156,7 +4383,7 @@ function createSystemdManager(scope) {
4156
4383
  }
4157
4384
  }
4158
4385
  async function status() {
4159
- if (!fs24.existsSync(file)) return "absent";
4386
+ if (!fs25.existsSync(file)) return "absent";
4160
4387
  const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
4161
4388
  const out = res.stdout.trim();
4162
4389
  if (out === "active") return "running";
@@ -4212,7 +4439,7 @@ function sendRpc(ws, payload) {
4212
4439
  }
4213
4440
  }
4214
4441
  function fail6(message, code = 1) {
4215
- console.error(chalk20.red(message));
4442
+ console.error(chalk19.red(message));
4216
4443
  process.exit(code);
4217
4444
  }
4218
4445
  function loadRequiredIdentity() {
@@ -4226,10 +4453,18 @@ function serverBase(identity) {
4226
4453
  if (identity) return identity.server_url.replace(/\/$/, "");
4227
4454
  return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
4228
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
+ }
4229
4464
  async function jsonGet(url) {
4230
4465
  let res;
4231
4466
  try {
4232
- res = await fetch(url);
4467
+ res = await fetch(url, { headers: adminHeaders() });
4233
4468
  } catch (error2) {
4234
4469
  fail6(`Cannot reach ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
4235
4470
  }
@@ -4242,16 +4477,16 @@ function requireRootIfSystem(scope, rerunHint) {
4242
4477
  if (scope !== "system") return;
4243
4478
  const uid = process.getuid?.();
4244
4479
  if (uid === 0) return;
4245
- console.error(chalk20.red("--system requires root."));
4480
+ console.error(chalk19.red("--system requires root."));
4246
4481
  console.error("Re-run with sudo, preserving env:");
4247
- console.error(chalk20.cyan(` sudo -E ${rerunHint}`));
4482
+ console.error(chalk19.cyan(` sudo -E ${rerunHint}`));
4248
4483
  process.exit(1);
4249
4484
  }
4250
4485
  function rerunArgv() {
4251
4486
  return ["task0", ...process.argv.slice(2)].join(" ");
4252
4487
  }
4253
- var daemonCmd = new Command20("daemon").description("Manage this host as a task0 daemon registered with a central server");
4254
- 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) => {
4255
4490
  const scope = opts.system ? "system" : "user";
4256
4491
  if (opts.install) {
4257
4492
  requireRootIfSystem(scope, rerunArgv());
@@ -4261,8 +4496,19 @@ daemonCmd.command("register").description("Register this host with a central ser
4261
4496
  fail6(`Already registered as ${existing.daemon_id}. Pass --force to re-register.`);
4262
4497
  }
4263
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
+ }
4264
4510
  const body = {
4265
- hostname: os6.hostname(),
4511
+ hostname: os7.hostname(),
4266
4512
  platform: process.platform,
4267
4513
  name: opts.name
4268
4514
  };
@@ -4270,7 +4516,7 @@ daemonCmd.command("register").description("Register this host with a central ser
4270
4516
  try {
4271
4517
  res = await fetch(`${base}/api/daemons/register`, {
4272
4518
  method: "POST",
4273
- headers: { "content-type": "application/json" },
4519
+ headers: { "content-type": "application/json", ...authHeader },
4274
4520
  body: JSON.stringify(body)
4275
4521
  });
4276
4522
  } catch (error2) {
@@ -4290,16 +4536,16 @@ daemonCmd.command("register").description("Register this host with a central ser
4290
4536
  registered_at: data.daemon.registered_at
4291
4537
  };
4292
4538
  writeDaemonIdentity(identity);
4293
- 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})`));
4294
4540
  console.log(`Identity saved to ${daemonConfigPath()}`);
4295
4541
  if (!opts.install) {
4296
- console.log(chalk20.dim("Skipping service install (--no-install)."));
4297
- 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.`));
4298
4544
  return;
4299
4545
  }
4300
4546
  if (!isPlatformSupported()) {
4301
4547
  console.log(
4302
- chalk20.yellow(
4548
+ chalk19.yellow(
4303
4549
  `Autostart service is not supported on ${process.platform}. Run \`task0 daemon run\` under a supervisor of your choice.`
4304
4550
  )
4305
4551
  );
@@ -4309,18 +4555,18 @@ daemonCmd.command("register").description("Register this host with a central ser
4309
4555
  try {
4310
4556
  const { unitPath: unitPath2 } = await svc.install({ identity });
4311
4557
  const logs = svc.logPaths();
4312
- console.log(chalk20.green(`Installed service unit at ${unitPath2}`));
4313
- console.log(chalk20.dim(`Logs: ${logs.out}`));
4314
- 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}`));
4315
4561
  if (opts.start) {
4316
4562
  await svc.start();
4317
- console.log(chalk20.green("Service started."));
4563
+ console.log(chalk19.green("Service started."));
4318
4564
  } else {
4319
- 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."));
4320
4566
  }
4321
4567
  } catch (error2) {
4322
- console.error(chalk20.red(`Service install failed: ${error2 instanceof Error ? error2.message : String(error2)}`));
4323
- 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."));
4324
4570
  process.exit(1);
4325
4571
  }
4326
4572
  });
@@ -4333,7 +4579,7 @@ daemonCmd.command("start").description("Start the installed autostart service vi
4333
4579
  const svc = getServiceManager(scope);
4334
4580
  try {
4335
4581
  await svc.start();
4336
- console.log(chalk20.green("Service started."));
4582
+ console.log(chalk19.green("Service started."));
4337
4583
  } catch (error2) {
4338
4584
  fail6(error2 instanceof Error ? error2.message : String(error2));
4339
4585
  }
@@ -4347,7 +4593,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
4347
4593
  const svc = getServiceManager(scope);
4348
4594
  try {
4349
4595
  await svc.stop();
4350
- console.log(chalk20.green("Service stopped."));
4596
+ console.log(chalk19.green("Service stopped."));
4351
4597
  } catch (error2) {
4352
4598
  fail6(error2 instanceof Error ? error2.message : String(error2));
4353
4599
  }
@@ -4355,7 +4601,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
4355
4601
  daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
4356
4602
  const identity = loadRequiredIdentity();
4357
4603
  const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
4358
- console.log(chalk20.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
4604
+ console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
4359
4605
  let reconnectDelay = 1e3;
4360
4606
  let shouldRun = true;
4361
4607
  let activeWs = null;
@@ -4366,7 +4612,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4366
4612
  activeWs = ws;
4367
4613
  ws.on("open", () => {
4368
4614
  reconnectDelay = 1e3;
4369
- console.log(chalk20.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
4615
+ console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
4370
4616
  const hello = {
4371
4617
  type: "hello",
4372
4618
  daemon_id: identity.daemon_id,
@@ -4378,7 +4624,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4378
4624
  const projects = loadConfig().sources.filter((source2) => source2.type === "project").map((source2) => ({ name: source2.name, path: source2.path, enabled: source2.enabled }));
4379
4625
  const manifest = { type: "manifest", projects };
4380
4626
  ws.send(JSON.stringify(manifest));
4381
- console.log(chalk20.dim(`pushed manifest: ${projects.length} project(s)`));
4627
+ console.log(chalk19.dim(`pushed manifest: ${projects.length} project(s)`));
4382
4628
  });
4383
4629
  ws.on("message", (raw) => {
4384
4630
  let msg;
@@ -4388,12 +4634,12 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4388
4634
  return;
4389
4635
  }
4390
4636
  if (msg.type === "welcome") {
4391
- console.log(chalk20.green(`welcomed ${msg.server_time}`));
4637
+ console.log(chalk19.green(`welcomed ${msg.server_time}`));
4392
4638
  } else if (msg.type === "ping") {
4393
4639
  const beat = { type: "heartbeat", ts: (/* @__PURE__ */ new Date()).toISOString() };
4394
4640
  if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(beat));
4395
4641
  } else if (msg.type === "error") {
4396
- console.error(chalk20.yellow(`server error: ${msg.message}`));
4642
+ console.error(chalk19.yellow(`server error: ${msg.message}`));
4397
4643
  } else if (msg.type === "rpc_request") {
4398
4644
  void dispatchRpc(ws, msg.id, msg.method, msg.params);
4399
4645
  }
@@ -4401,9 +4647,9 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4401
4647
  ws.on("close", (code, reason) => {
4402
4648
  activeWs = null;
4403
4649
  const reasonText = reason.toString("utf-8") || "no reason";
4404
- 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})`));
4405
4651
  if (code === 4001) {
4406
- console.error(chalk20.red("superseded by another daemon connection; exiting"));
4652
+ console.error(chalk19.red("superseded by another daemon connection; exiting"));
4407
4653
  process.exit(0);
4408
4654
  }
4409
4655
  if (shouldRun) {
@@ -4412,7 +4658,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
4412
4658
  }
4413
4659
  });
4414
4660
  ws.on("error", (err) => {
4415
- console.error(chalk20.red(`ws error: ${err.message}`));
4661
+ console.error(chalk19.red(`ws error: ${err.message}`));
4416
4662
  });
4417
4663
  }
4418
4664
  const shutdown = () => {
@@ -4437,9 +4683,9 @@ daemonCmd.command("list").description("List daemons registered on the configured
4437
4683
  return;
4438
4684
  }
4439
4685
  for (const d of data.daemons) {
4440
- 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);
4441
4687
  console.log(
4442
- `${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}`
4443
4689
  );
4444
4690
  }
4445
4691
  });
@@ -4452,9 +4698,9 @@ daemonCmd.command("show [daemonId]").description("Show local daemon identity (no
4452
4698
  try {
4453
4699
  const svc = getServiceManager(scope);
4454
4700
  const state2 = await svc.status();
4455
- console.log(chalk20.dim(`service (${scope}): ${state2} \u2192 ${svc.unitPath()}`));
4701
+ console.log(chalk19.dim(`service (${scope}): ${state2} \u2192 ${svc.unitPath()}`));
4456
4702
  } catch (error2) {
4457
- 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)}`));
4458
4704
  }
4459
4705
  }
4460
4706
  return;
@@ -4471,21 +4717,169 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
4471
4717
  const svc = getServiceManager(scope);
4472
4718
  try {
4473
4719
  await svc.uninstall();
4474
- console.log(chalk20.dim(`Service unit removed (${scope}).`));
4720
+ console.log(chalk19.dim(`Service unit removed (${scope}).`));
4475
4721
  } catch (error2) {
4476
4722
  console.error(
4477
- 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)}`)
4478
4724
  );
4479
4725
  }
4480
4726
  }
4481
4727
  }
4482
4728
  if (clearDaemonIdentity()) {
4483
- console.log(chalk20.green("Daemon identity cleared."));
4729
+ console.log(chalk19.green("Daemon identity cleared."));
4484
4730
  } else {
4485
4731
  console.log("No daemon identity to clear.");
4486
4732
  }
4487
4733
  });
4488
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
+
4489
4883
  // src/commands/error.ts
4490
4884
  init_node();
4491
4885
  import { Command as Command21 } from "commander";
@@ -4650,6 +5044,209 @@ error.command("prune").description("Remove captured error reports by age, count,
4650
5044
  console.log(`Removed ${res.removed.length} report(s); ${res.kept} remaining.`);
4651
5045
  });
4652
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
+
4653
5250
  // src/core/error-capture.ts
4654
5251
  init_node();
4655
5252
  var DEFAULT_KEEP = 50;
@@ -4747,24 +5344,34 @@ function captureTopLevel(err, options) {
4747
5344
  }
4748
5345
 
4749
5346
  // src/main.ts
4750
- var TASK0_VERSION = "0.1.0";
4751
- var program = new Command22().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
5347
+ var TASK0_VERSION = readVersion();
5348
+ function readVersion() {
5349
+ try {
5350
+ const here = path25.dirname(fileURLToPath2(import.meta.url));
5351
+ const pkg = JSON.parse(readFileSync(path25.resolve(here, "..", "package.json"), "utf-8"));
5352
+ return pkg.version ?? "unknown";
5353
+ } catch {
5354
+ return "unknown";
5355
+ }
5356
+ }
5357
+ var program = new Command23().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
4752
5358
  program.addCommand(source);
4753
5359
  program.addCommand(project);
4754
5360
  program.addCommand(task);
4755
- program.addCommand(runtime);
5361
+ program.addCommand(agentRun);
4756
5362
  program.addCommand(plan);
4757
5363
  program.addCommand(okr);
4758
5364
  program.addCommand(note);
4759
5365
  program.addCommand(run);
4760
5366
  program.addCommand(ui);
4761
- program.addCommand(serve);
4762
5367
  program.addCommand(models);
4763
5368
  program.addCommand(object);
4764
5369
  program.addCommand(issue);
4765
5370
  program.addCommand(agent);
4766
5371
  program.addCommand(daemonCmd);
5372
+ program.addCommand(userCmd);
4767
5373
  program.addCommand(error);
5374
+ program.addCommand(automation);
4768
5375
  async function main() {
4769
5376
  installErrorCapture({ version: TASK0_VERSION });
4770
5377
  try {