@danypops/papyrus 0.41.0 → 0.42.1
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.
- package/README.md +8 -11
- package/package.json +2 -2
- package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
- package/src/adapters/sqlite-artifact-store.ts +7 -5
- package/src/adapters/sqlite-discussion-round-store.ts +26 -17
- package/src/adapters/sqlite-gate-runner.ts +1 -1
- package/src/adapters/sqlite-graph-projection-store.ts +14 -10
- package/src/adapters/sqlite-log-store.ts +36 -17
- package/src/adapters/sqlite-note-event-store.ts +20 -16
- package/src/adapters/sqlite-session-identity-store.ts +13 -7
- package/src/adapters/sqlite-task-event-store.ts +29 -21
- package/src/adapters/sqlite-task-focus-store.ts +36 -10
- package/src/adapters/sqlite-task-lease-store.ts +12 -6
- package/src/adapters/sqlite-task-scope-store.ts +25 -14
- package/src/artifact-relationship-view.ts +4 -4
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +794 -354
- package/src/client.ts +6 -3
- package/src/constants.ts +34 -56
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +153 -105
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +18 -5
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +268 -0
- package/src/domain/checklist.ts +20 -17
- package/src/domain/discussion.ts +37 -18
- package/src/domain/gate.ts +7 -7
- package/src/domain/log-entry.ts +1 -1
- package/src/domain/note-event.ts +20 -7
- package/src/domain/task-event.ts +17 -7
- package/src/domain-services.ts +362 -288
- package/src/graph-projection-service.ts +34 -8
- package/src/id-migration.ts +17 -4
- package/src/index.ts +16 -11
- package/src/log-service.ts +6 -5
- package/src/log.ts +19 -0
- package/src/modules/discuss.ts +63 -28
- package/src/modules/docs.ts +74 -17
- package/src/modules/graph-projection.ts +20 -9
- package/src/modules/logs.ts +34 -22
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +93 -32
- package/src/modules/rules.ts +57 -15
- package/src/modules/session-identity.ts +6 -2
- package/src/modules/tasks.ts +142 -67
- package/src/note-service.ts +11 -7
- package/src/ops.ts +134 -69
- package/src/playbook-definition.ts +124 -39
- package/src/playbook-execution.ts +18 -25
- package/src/ports/artifact-scope-store.ts +1 -1
- package/src/ports/note-event-store.ts +6 -4
- package/src/ports/task-event-store.ts +9 -6
- package/src/ports/task-focus-store.ts +17 -4
- package/src/ports/task-lease-store.ts +9 -4
- package/src/ports/task-scope-store.ts +3 -1
- package/src/service.ts +148 -110
- package/src/session-identity-service.ts +10 -2
- package/src/task-context.ts +28 -16
- package/src/task-execution.ts +4 -12
- package/src/task-graph-view.ts +12 -12
- package/src/task-relationship-view.ts +1 -3
- package/src/task-service.ts +168 -73
- package/src/vehicle/artifact-trash-vehicle.ts +26 -14
- package/src/vehicle/artifact-vehicle-shared.ts +32 -13
- package/src/vehicle/docs-vehicle.ts +50 -18
- package/src/vehicle/notes-vehicle.ts +26 -8
- package/src/vehicle/papyrus-vehicle.ts +16 -8
- package/src/vehicle/playbooks-vehicle.ts +88 -19
- package/src/vehicle/rules-vehicle.ts +58 -21
- package/src/vehicle/tasks-vehicle.ts +366 -54
- package/src/version.ts +1 -1
- package/src/workflow-execution.ts +198 -109
- package/src/domain/skill-definition.ts +0 -270
- package/src/modules/skills.ts +0 -158
- package/src/vehicle/skills-vehicle.ts +0 -194
package/src/cli.ts
CHANGED
|
@@ -6,11 +6,11 @@ import { join } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { createNodeServiceInstallDeps, generateSystemdUnit, installUserService, type ServiceSpec } from "@danypops/vehicle-server/service";
|
|
8
8
|
import { connectPapyrusClient, type PapyrusClient } from "./client.ts";
|
|
9
|
-
import { DAEMON_UNIT_NAME,
|
|
9
|
+
import { DAEMON_UNIT_NAME, dbPath, TASK_EXECUTION_MAX_NODES } from "./constants.ts";
|
|
10
10
|
import { serveMain } from "./daemon.ts";
|
|
11
11
|
import { openDb } from "./db.ts";
|
|
12
|
-
import { applyIdMigration, mirrorDatabase, planIdMigration, verifyIdMigration, type IdMigrationPlan } from "./id-migration.ts";
|
|
13
12
|
import type { GateResult } from "./domain/gate.ts";
|
|
13
|
+
import { applyIdMigration, type IdMigrationPlan, mirrorDatabase, planIdMigration, verifyIdMigration } from "./id-migration.ts";
|
|
14
14
|
import type { TaskExecutionPlan } from "./task-execution.ts";
|
|
15
15
|
import type { TaskBlockage, TaskCompletion } from "./task-service.ts";
|
|
16
16
|
|
|
@@ -52,7 +52,7 @@ export function renderSystemdUnit(options: SystemdUnitOptions): string {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function unitPath(): string {
|
|
55
|
-
const configHome = process.env
|
|
55
|
+
const configHome = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
56
56
|
return join(configHome, "systemd", "user", DAEMON_UNIT_NAME);
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -118,18 +118,8 @@ const USAGE = `Usage:
|
|
|
118
118
|
papyrus rules injectable [--json]
|
|
119
119
|
papyrus rules assign-project <id> [project-root] [--json]
|
|
120
120
|
papyrus rules update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
121
|
-
papyrus
|
|
122
|
-
papyrus
|
|
123
|
-
papyrus skills create-template --title <title> --target-kind <kind> [--defaults-json <json>] [--required-json <json>] [--body <body>] [--labels-json <json>] [--project-root <path>] [--json]
|
|
124
|
-
papyrus skills list [--status <status>] [--text <query>] [--limit <count>] [--project-root <path>] [--json]
|
|
125
|
-
papyrus skills show <id> [--json]
|
|
126
|
-
papyrus skills invoke <id> [--json]
|
|
127
|
-
papyrus skills enable|disable <id> [--json]
|
|
128
|
-
papyrus skills instantiate <template-id> [--title <title>] [--body <body>] [--status <status>] [--labels-json <json>] [--extra-json <json>] [--json]
|
|
129
|
-
papyrus skills assign-project <id> [project-root] [--json]
|
|
130
|
-
papyrus skills update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
131
|
-
papyrus playbooks create --title <title> [--body <body>] [--trigger <text>] [--steps-json <json>] [--tools-json <json>] [--labels-json <json>] [--extra-json <json>] [--arguments-json <json array>] [--project-root <path>] [--json]
|
|
132
|
-
papyrus playbooks invoke <id> [--arguments-json <json object>] [--project-root <path>] [--json] # materializes real tasks (contains/depends_on-wired) and focuses the entry task
|
|
121
|
+
papyrus playbooks create --title <title> [--body <body>] [--trigger <text>] [--steps-json <json array of strings and/or {kind:'doc'|'rule'|'call'|'task',...} objects>] [--tools-json <json>] [--labels-json <json>] [--extra-json <json>] [--arguments-json <json array, each optionally {type,enum,default}>] [--project-root <path>] [--json]
|
|
122
|
+
papyrus playbooks invoke <id> [--arguments-json <json object>] [--run-id <id>] [--project-root <path>] [--json] # materializes real tasks (contains/depends_on-wired) and focuses the entry task
|
|
133
123
|
papyrus playbooks preview <id> [--arguments-json <json object>] [--json] # renders text only, creates nothing
|
|
134
124
|
papyrus playbooks list [--status <status>] [--text <query>] [--limit <count>] [--project-root <path>] [--json]
|
|
135
125
|
papyrus playbooks show <id> [--json]
|
|
@@ -209,7 +199,15 @@ function usage(): never {
|
|
|
209
199
|
type TaskCliClient = Pick<PapyrusClient, "call">;
|
|
210
200
|
type MigrationResult = { from: number; to: number; applied: string[] };
|
|
211
201
|
type CliArtifact = { id: string; title: string; status: string; body?: string };
|
|
212
|
-
type CliTaskLease = {
|
|
202
|
+
type CliTaskLease = {
|
|
203
|
+
taskId: string;
|
|
204
|
+
owner: string;
|
|
205
|
+
token: string;
|
|
206
|
+
claimedAt: string;
|
|
207
|
+
leaseExpiresAt: string;
|
|
208
|
+
heartbeatAt?: string;
|
|
209
|
+
note?: string;
|
|
210
|
+
};
|
|
213
211
|
type CliCompletion = Omit<TaskCompletion, "artifact" | "blocked"> & {
|
|
214
212
|
artifact: CliArtifact;
|
|
215
213
|
blocked: Array<Omit<TaskBlockage, "artifact"> & { artifact: CliArtifact }>;
|
|
@@ -231,10 +229,11 @@ function parseJsonStringArrayFlag(value: string | undefined, flag: string): stri
|
|
|
231
229
|
}
|
|
232
230
|
|
|
233
231
|
/**
|
|
234
|
-
* No shape assertion here --
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
232
|
+
* No shape assertion here -- playbooks --arguments-json is genuinely polymorphic (an array on
|
|
233
|
+
* create, a {name: value} map on invoke, sharing one flag-parsing pass in runPlaybooksCli), and
|
|
234
|
+
* --steps-json accepts a mix of plain prose strings and structured step objects (doc/rule/call/
|
|
235
|
+
* task) that a single string-array assertion would wrongly reject. The service validates the
|
|
236
|
+
* real shape for whichever operation actually receives it.
|
|
238
237
|
*/
|
|
239
238
|
function parseJsonAnyFlag(value: string | undefined, flag: string): unknown {
|
|
240
239
|
if (value === undefined) throw new Error(`${flag} requires a value`);
|
|
@@ -297,7 +296,11 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
297
296
|
const mirrorPath = flag("out");
|
|
298
297
|
if (!mirrorPath) throw new Error("migrate-ids mirror requires --out <path>");
|
|
299
298
|
const sourceDb = openDb(source);
|
|
300
|
-
try {
|
|
299
|
+
try {
|
|
300
|
+
mirrorDatabase(sourceDb, mirrorPath);
|
|
301
|
+
} finally {
|
|
302
|
+
sourceDb.close();
|
|
303
|
+
}
|
|
301
304
|
|
|
302
305
|
const mirror = openDb(mirrorPath);
|
|
303
306
|
let plan: IdMigrationPlan;
|
|
@@ -327,9 +330,14 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
327
330
|
const plan = readIdMap(flag("idmap") ?? `${mirrorPath}.idmap.json`);
|
|
328
331
|
const mirror = openDb(mirrorPath);
|
|
329
332
|
let result: ReturnType<typeof verifyIdMigration>;
|
|
330
|
-
try {
|
|
333
|
+
try {
|
|
334
|
+
result = verifyIdMigration(mirror, plan);
|
|
335
|
+
} finally {
|
|
336
|
+
mirror.close();
|
|
337
|
+
}
|
|
331
338
|
if (json) return JSON.stringify(result);
|
|
332
|
-
if (result.ok)
|
|
339
|
+
if (result.ok)
|
|
340
|
+
return `Validation passed: ${plan.idMap.size} artifact id(s) correctly migrated.\nNext: papyrus migrate-ids promote --mirror ${mirrorPath}`;
|
|
333
341
|
return `Validation FAILED:\n${result.problems.map((problem) => ` - ${problem}`).join("\n")}\nDo not promote this mirror.`;
|
|
334
342
|
}
|
|
335
343
|
|
|
@@ -339,14 +347,22 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
339
347
|
const target = flag("db") ?? dbPath();
|
|
340
348
|
const force = rest.includes("--force");
|
|
341
349
|
if (isDaemonActive() && !force) {
|
|
342
|
-
throw new Error(
|
|
350
|
+
throw new Error(
|
|
351
|
+
`refusing to promote while ${DAEMON_UNIT_NAME} is active -- stop it first (papyrus service stop), or pass --force if you have already verified it is safe`,
|
|
352
|
+
);
|
|
343
353
|
}
|
|
344
354
|
const plan = readIdMap(flag("idmap") ?? `${mirrorPath}.idmap.json`);
|
|
345
355
|
const mirror = openDb(mirrorPath);
|
|
346
356
|
let result: ReturnType<typeof verifyIdMigration>;
|
|
347
|
-
try {
|
|
357
|
+
try {
|
|
358
|
+
result = verifyIdMigration(mirror, plan);
|
|
359
|
+
} finally {
|
|
360
|
+
mirror.close();
|
|
361
|
+
}
|
|
348
362
|
if (!result.ok) {
|
|
349
|
-
throw new Error(
|
|
363
|
+
throw new Error(
|
|
364
|
+
`refusing to promote: mirror failed validation (${result.problems.length} problem(s)) -- run migrate-ids validate for details`,
|
|
365
|
+
);
|
|
350
366
|
}
|
|
351
367
|
let backupPath: string | undefined;
|
|
352
368
|
if (existsSync(target)) {
|
|
@@ -376,160 +392,6 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
376
392
|
throw new Error("migrate-ids requires one of: mirror, validate, promote");
|
|
377
393
|
}
|
|
378
394
|
|
|
379
|
-
export async function runSkillCli(args: string[], client: TaskCliClient, projectRoot: string = process.cwd()): Promise<string> {
|
|
380
|
-
const json = args.includes("--json");
|
|
381
|
-
const positional: string[] = [];
|
|
382
|
-
let runId: string | undefined;
|
|
383
|
-
let arguments_: Record<string, unknown> = {};
|
|
384
|
-
let title: string | undefined;
|
|
385
|
-
let body: string | undefined;
|
|
386
|
-
let trigger: string | undefined;
|
|
387
|
-
let steps: string[] | undefined;
|
|
388
|
-
let tools: string[] | undefined;
|
|
389
|
-
let definition: unknown;
|
|
390
|
-
let labels: string[] | undefined;
|
|
391
|
-
let extra: Record<string, unknown> | undefined;
|
|
392
|
-
let targetKind: string | undefined;
|
|
393
|
-
let defaults: Record<string, unknown> | undefined;
|
|
394
|
-
let required: string[] | undefined;
|
|
395
|
-
let status: string | undefined;
|
|
396
|
-
let text: string | undefined;
|
|
397
|
-
let limit: number | undefined;
|
|
398
|
-
let skillProjectRoot: string | undefined;
|
|
399
|
-
for (let index = 0; index < args.length; index++) {
|
|
400
|
-
const argument = args[index]!;
|
|
401
|
-
if (argument === "--json") continue;
|
|
402
|
-
if (argument === "--run-id") { runId = args[++index]; if (!runId) throw new Error("--run-id requires a value"); continue; }
|
|
403
|
-
if (argument === "--arguments-json") { arguments_ = parseJsonObjectFlag(args[++index], "--arguments-json"); continue; }
|
|
404
|
-
if (argument === "--title") { title = args[++index]; if (title === undefined) throw new Error("--title requires a value"); continue; }
|
|
405
|
-
if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
|
|
406
|
-
if (argument === "--trigger") { trigger = args[++index]; if (trigger === undefined) throw new Error("--trigger requires a value"); continue; }
|
|
407
|
-
if (argument === "--steps-json") { steps = parseJsonStringArrayFlag(args[++index], "--steps-json"); continue; }
|
|
408
|
-
if (argument === "--tools-json") { tools = parseJsonStringArrayFlag(args[++index], "--tools-json"); continue; }
|
|
409
|
-
if (argument === "--definition-json") {
|
|
410
|
-
const value = args[++index];
|
|
411
|
-
if (!value) throw new Error("--definition-json requires a value");
|
|
412
|
-
definition = JSON.parse(value);
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
|
|
416
|
-
if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
|
|
417
|
-
if (argument === "--target-kind") { targetKind = args[++index]; if (!targetKind) throw new Error("--target-kind requires a value"); continue; }
|
|
418
|
-
if (argument === "--defaults-json") { defaults = parseJsonObjectFlag(args[++index], "--defaults-json"); continue; }
|
|
419
|
-
if (argument === "--required-json") { required = parseJsonStringArrayFlag(args[++index], "--required-json"); continue; }
|
|
420
|
-
if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
|
|
421
|
-
if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
|
|
422
|
-
if (argument === "--project-root") { skillProjectRoot = args[++index]; if (!skillProjectRoot) throw new Error("--project-root requires a value"); continue; }
|
|
423
|
-
if (argument === "--limit") {
|
|
424
|
-
const value = args[++index];
|
|
425
|
-
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
426
|
-
limit = Number(value);
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
if (argument.startsWith("--")) throw new Error(`unknown skills option ${argument}`);
|
|
430
|
-
positional.push(argument);
|
|
431
|
-
}
|
|
432
|
-
const [action, id, second] = positional;
|
|
433
|
-
if (action === "run") {
|
|
434
|
-
if (positional.length !== 2) throw new Error("skills requires `run <id>`");
|
|
435
|
-
const input: Record<string, unknown> = { id, arguments: arguments_, project_root: projectRoot };
|
|
436
|
-
if (runId) input["run_id"] = runId;
|
|
437
|
-
const result = await client.call<Record<string, unknown>, {
|
|
438
|
-
runId: string;
|
|
439
|
-
created: { tasks: string[]; rules: string[]; docs: string[] };
|
|
440
|
-
rootTaskIds: string[];
|
|
441
|
-
execution: TaskExecutionPlan;
|
|
442
|
-
}>("skills.run", input);
|
|
443
|
-
if (json) return JSON.stringify(result);
|
|
444
|
-
return [
|
|
445
|
-
`Created Skill run ${result.runId}: ${result.created.tasks.length} tasks, ${result.created.rules.length} rules, ${result.created.docs.length} docs`,
|
|
446
|
-
`Ready roots: ${result.rootTaskIds.join(", ") || "none"}`,
|
|
447
|
-
`Context docs: ${result.created.docs.join(", ") || "none"}`,
|
|
448
|
-
`Scoped rules: ${result.created.rules.join(", ") || "none"}`,
|
|
449
|
-
...result.execution.nodes.map((node) => `[${node.state}] ${node.id} ${node.title}`),
|
|
450
|
-
].join("\n");
|
|
451
|
-
}
|
|
452
|
-
let result: unknown;
|
|
453
|
-
let human: string;
|
|
454
|
-
switch (action) {
|
|
455
|
-
case "create": {
|
|
456
|
-
if (id) throw new Error("skills create accepts no positional arguments");
|
|
457
|
-
if (!title) throw new Error("skills create requires --title");
|
|
458
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.create", { title, body, trigger, steps, tools, definition, labels, extra, project_root: skillProjectRoot });
|
|
459
|
-
result = artifact;
|
|
460
|
-
human = `Created skill: ${artifactLabel(artifact)}`;
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
case "create-template": {
|
|
464
|
-
if (id) throw new Error("skills create-template accepts no positional arguments");
|
|
465
|
-
if (!title || !targetKind) throw new Error("skills create-template requires --title and --target-kind");
|
|
466
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.create_template", {
|
|
467
|
-
title, target_kind: targetKind, defaults, required, body, labels, project_root: skillProjectRoot,
|
|
468
|
-
});
|
|
469
|
-
result = artifact;
|
|
470
|
-
human = `Created template: ${artifactLabel(artifact)}`;
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
case "list": {
|
|
474
|
-
if (id) throw new Error("skills list accepts no positional arguments");
|
|
475
|
-
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("skills.list", { status, text, limit, project_root: skillProjectRoot });
|
|
476
|
-
result = rows;
|
|
477
|
-
human = rows.length === 0 ? "No skills found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
case "assign-project": {
|
|
481
|
-
if (!id) throw new Error("skills assign-project requires <id> [project-root]");
|
|
482
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.assign_project", { id, project_root: second });
|
|
483
|
-
result = artifact;
|
|
484
|
-
human = second ? `Assigned ${id} to ${second}` : `Unscoped ${id}`;
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
|
-
case "show": {
|
|
488
|
-
if (!id) throw new Error("skills show requires exactly one skill id");
|
|
489
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.show", { id });
|
|
490
|
-
result = artifact;
|
|
491
|
-
human = `${artifactLabel(artifact)}\n\n${artifact.body ?? ""}`;
|
|
492
|
-
break;
|
|
493
|
-
}
|
|
494
|
-
case "invoke": {
|
|
495
|
-
if (!id) throw new Error("skills invoke requires exactly one skill id");
|
|
496
|
-
const invocation = await client.call<Record<string, unknown>, string>("skills.invoke", { id });
|
|
497
|
-
result = invocation;
|
|
498
|
-
human = invocation;
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
case "enable":
|
|
502
|
-
case "disable": {
|
|
503
|
-
if (!id) throw new Error(`skills ${action} requires exactly one skill id`);
|
|
504
|
-
const operation = action === "enable" ? "skills.enable" : "skills.disable";
|
|
505
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>(operation, { id });
|
|
506
|
-
result = artifact;
|
|
507
|
-
human = `${artifactLabel(artifact)}`;
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
case "instantiate": {
|
|
511
|
-
if (!id) throw new Error("skills instantiate requires exactly one template id");
|
|
512
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.instantiate", {
|
|
513
|
-
template_id: id, title, body, status, labels, extra, project_root: projectRoot,
|
|
514
|
-
});
|
|
515
|
-
result = artifact;
|
|
516
|
-
human = `Created: ${artifactLabel(artifact)}`;
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
case "update": {
|
|
520
|
-
if (!id || second) throw new Error("skills update requires exactly one skill id");
|
|
521
|
-
if (title === undefined && body === undefined && labels === undefined) throw new Error("skills update requires --title, --body, or --labels-json");
|
|
522
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.update", { id, title, body, labels });
|
|
523
|
-
result = artifact;
|
|
524
|
-
human = `${artifactLabel(artifact)}`;
|
|
525
|
-
break;
|
|
526
|
-
}
|
|
527
|
-
default:
|
|
528
|
-
throw new Error("skills action must be run, create, create-template, list, show, invoke, enable, disable, instantiate, assign-project, or update");
|
|
529
|
-
}
|
|
530
|
-
return json ? JSON.stringify(result) : human;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
395
|
export async function runGraphCli(args: string[], client: TaskCliClient): Promise<string> {
|
|
534
396
|
const json = args.includes("--json");
|
|
535
397
|
const positional: string[] = [];
|
|
@@ -540,7 +402,10 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
|
|
|
540
402
|
const argument = args[index]!;
|
|
541
403
|
if (argument === "--json") continue;
|
|
542
404
|
const flags: Record<string, string> = {
|
|
543
|
-
"--id": "id",
|
|
405
|
+
"--id": "id",
|
|
406
|
+
"--actor": "actor",
|
|
407
|
+
"--session-id": "session_id",
|
|
408
|
+
"--since": "since",
|
|
544
409
|
"--direction": "direction",
|
|
545
410
|
};
|
|
546
411
|
if (argument in flags) {
|
|
@@ -578,13 +443,20 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
|
|
|
578
443
|
}
|
|
579
444
|
if (action === "unlink") {
|
|
580
445
|
if (positional.length !== 4) throw new Error("graph unlink requires <from> <relation> <to>");
|
|
581
|
-
const result = await client.call<Record<string, unknown>, { removed: boolean }>("graph.unlink", {
|
|
446
|
+
const result = await client.call<Record<string, unknown>, { removed: boolean }>("graph.unlink", {
|
|
447
|
+
from: first,
|
|
448
|
+
relation: second,
|
|
449
|
+
to: third,
|
|
450
|
+
});
|
|
582
451
|
if (json) return JSON.stringify(result);
|
|
583
452
|
return result.removed ? `Unlinked ${first} --${second}--> ${third}` : `No such relationship: ${first} --${second}--> ${third}`;
|
|
584
453
|
}
|
|
585
454
|
if (action === "tree") {
|
|
586
455
|
if (positional.length !== 2) throw new Error("graph tree requires exactly one artifact id");
|
|
587
|
-
const artifact = await client.call<
|
|
456
|
+
const artifact = await client.call<
|
|
457
|
+
Record<string, unknown>,
|
|
458
|
+
CliArtifact & { edges?: Array<{ from: string; relation: string; to: string }> }
|
|
459
|
+
>("graph.tree", { id: first, depth, max_nodes: maxNodes });
|
|
588
460
|
if (json) return JSON.stringify(artifact);
|
|
589
461
|
const edges = artifact.edges ?? [];
|
|
590
462
|
return edges.length === 0
|
|
@@ -600,11 +472,13 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
|
|
|
600
472
|
const page = await client.call<Record<string, unknown>, import("./domain/artifact-event.ts").ArtifactEventPage>("graph.history", input);
|
|
601
473
|
if (json) return JSON.stringify(page);
|
|
602
474
|
if (page.events.length === 0) return "No recorded events.";
|
|
603
|
-
return page.events
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
475
|
+
return page.events
|
|
476
|
+
.map((event) => {
|
|
477
|
+
const transition = event.fromStatus || event.toStatus ? ` ${event.fromStatus ?? "\u2205"} \u2192 ${event.toStatus ?? "\u2205"}` : "";
|
|
478
|
+
const relation = event.relation ? ` ${event.relation} \u2192 ${event.relatedId}` : "";
|
|
479
|
+
return `${event.occurredAt} ${event.artifactId} ${event.type}${transition}${relation} \u00b7 ${event.actor}/${event.source}${event.sessionId ? ` \u00b7 ${event.sessionId}` : ""}`;
|
|
480
|
+
})
|
|
481
|
+
.join("\n");
|
|
608
482
|
}
|
|
609
483
|
|
|
610
484
|
export async function runDocsCli(args: string[], client: TaskCliClient): Promise<string> {
|
|
@@ -623,15 +497,49 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
623
497
|
for (let index = 0; index < args.length; index++) {
|
|
624
498
|
const argument = args[index]!;
|
|
625
499
|
if (argument === "--json") continue;
|
|
626
|
-
if (argument === "--title") {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (argument === "--
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
500
|
+
if (argument === "--title") {
|
|
501
|
+
title = args[++index];
|
|
502
|
+
if (title === undefined) throw new Error("--title requires a value");
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (argument === "--body") {
|
|
506
|
+
body = args[++index];
|
|
507
|
+
if (body === undefined) throw new Error("--body requires a value");
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (argument === "--subtype") {
|
|
511
|
+
subtype = args[++index];
|
|
512
|
+
if (!subtype) throw new Error("--subtype requires a value");
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (argument === "--labels-json") {
|
|
516
|
+
labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (argument === "--extra-json") {
|
|
520
|
+
extra = parseJsonObjectFlag(args[++index], "--extra-json");
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (argument === "--template-id") {
|
|
524
|
+
templateId = args[++index];
|
|
525
|
+
if (!templateId) throw new Error("--template-id requires a value");
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (argument === "--status") {
|
|
529
|
+
status = args[++index];
|
|
530
|
+
if (!status) throw new Error("--status requires a value");
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (argument === "--text") {
|
|
534
|
+
text = args[++index];
|
|
535
|
+
if (text === undefined) throw new Error("--text requires a value");
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (argument === "--project-root") {
|
|
539
|
+
projectRoot = args[++index];
|
|
540
|
+
if (!projectRoot) throw new Error("--project-root requires a value");
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
635
543
|
if (argument === "--limit") {
|
|
636
544
|
const value = args[++index];
|
|
637
545
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -648,14 +556,27 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
648
556
|
case "create": {
|
|
649
557
|
if (id) throw new Error("docs create accepts no positional arguments");
|
|
650
558
|
if (!title) throw new Error("docs create requires --title");
|
|
651
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.create", {
|
|
559
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.create", {
|
|
560
|
+
title,
|
|
561
|
+
body,
|
|
562
|
+
subtype,
|
|
563
|
+
labels,
|
|
564
|
+
extra,
|
|
565
|
+
template_id: templateId,
|
|
566
|
+
project_root: projectRoot,
|
|
567
|
+
});
|
|
652
568
|
result = artifact;
|
|
653
569
|
human = `Created document: ${artifactLabel(artifact)}`;
|
|
654
570
|
break;
|
|
655
571
|
}
|
|
656
572
|
case "list": {
|
|
657
573
|
if (id) throw new Error("docs list accepts no positional arguments");
|
|
658
|
-
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("docs.list", {
|
|
574
|
+
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("docs.list", {
|
|
575
|
+
status,
|
|
576
|
+
text,
|
|
577
|
+
limit,
|
|
578
|
+
project_root: projectRoot,
|
|
579
|
+
});
|
|
659
580
|
result = rows;
|
|
660
581
|
human = rows.length === 0 ? "No documents found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
661
582
|
break;
|
|
@@ -692,7 +613,8 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
692
613
|
}
|
|
693
614
|
case "update": {
|
|
694
615
|
if (!id || second) throw new Error("docs update requires exactly one document id");
|
|
695
|
-
if (title === undefined && body === undefined && labels === undefined)
|
|
616
|
+
if (title === undefined && body === undefined && labels === undefined)
|
|
617
|
+
throw new Error("docs update requires --title, --body, or --labels-json");
|
|
696
618
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.update", { id, title, body, labels });
|
|
697
619
|
result = artifact;
|
|
698
620
|
human = `${artifactLabel(artifact)}`;
|
|
@@ -721,16 +643,54 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
721
643
|
for (let index = 0; index < args.length; index++) {
|
|
722
644
|
const argument = args[index]!;
|
|
723
645
|
if (argument === "--json") continue;
|
|
724
|
-
if (argument === "--title") {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (argument === "--
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
646
|
+
if (argument === "--title") {
|
|
647
|
+
title = args[++index];
|
|
648
|
+
if (title === undefined) throw new Error("--title requires a value");
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (argument === "--body") {
|
|
652
|
+
body = args[++index];
|
|
653
|
+
if (body === undefined) throw new Error("--body requires a value");
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (argument === "--condition") {
|
|
657
|
+
condition = args[++index];
|
|
658
|
+
if (condition === undefined) throw new Error("--condition requires a value");
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (argument === "--rule-action") {
|
|
662
|
+
ruleAction = args[++index];
|
|
663
|
+
if (ruleAction === undefined) throw new Error("--rule-action requires a value");
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (argument === "--severity") {
|
|
667
|
+
severity = args[++index];
|
|
668
|
+
if (!severity) throw new Error("--severity requires a value");
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (argument === "--labels-json") {
|
|
672
|
+
labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (argument === "--extra-json") {
|
|
676
|
+
extra = parseJsonObjectFlag(args[++index], "--extra-json");
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (argument === "--status") {
|
|
680
|
+
status = args[++index];
|
|
681
|
+
if (!status) throw new Error("--status requires a value");
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (argument === "--text") {
|
|
685
|
+
text = args[++index];
|
|
686
|
+
if (text === undefined) throw new Error("--text requires a value");
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (argument === "--project-root") {
|
|
690
|
+
ruleProjectRoot = args[++index];
|
|
691
|
+
if (!ruleProjectRoot) throw new Error("--project-root requires a value");
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
734
694
|
if (argument === "--limit") {
|
|
735
695
|
const value = args[++index];
|
|
736
696
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -747,14 +707,28 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
747
707
|
case "create": {
|
|
748
708
|
if (id) throw new Error("rules create accepts no positional arguments");
|
|
749
709
|
if (!title) throw new Error("rules create requires --title");
|
|
750
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.create", {
|
|
710
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.create", {
|
|
711
|
+
title,
|
|
712
|
+
body,
|
|
713
|
+
condition,
|
|
714
|
+
rule_action: ruleAction,
|
|
715
|
+
severity,
|
|
716
|
+
labels,
|
|
717
|
+
extra,
|
|
718
|
+
project_root: ruleProjectRoot,
|
|
719
|
+
});
|
|
751
720
|
result = artifact;
|
|
752
721
|
human = `Created rule: ${artifactLabel(artifact)}`;
|
|
753
722
|
break;
|
|
754
723
|
}
|
|
755
724
|
case "list": {
|
|
756
725
|
if (id) throw new Error("rules list accepts no positional arguments");
|
|
757
|
-
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("rules.list", {
|
|
726
|
+
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("rules.list", {
|
|
727
|
+
status,
|
|
728
|
+
text,
|
|
729
|
+
limit,
|
|
730
|
+
project_root: ruleProjectRoot,
|
|
731
|
+
});
|
|
758
732
|
result = rows;
|
|
759
733
|
human = rows.length === 0 ? "No rules found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
760
734
|
break;
|
|
@@ -804,7 +778,8 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
804
778
|
}
|
|
805
779
|
case "update": {
|
|
806
780
|
if (!id || second) throw new Error("rules update requires exactly one rule id");
|
|
807
|
-
if (title === undefined && body === undefined && labels === undefined)
|
|
781
|
+
if (title === undefined && body === undefined && labels === undefined)
|
|
782
|
+
throw new Error("rules update requires --title, --body, or --labels-json");
|
|
808
783
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.update", { id, title, body, labels });
|
|
809
784
|
result = artifact;
|
|
810
785
|
human = `${artifactLabel(artifact)}`;
|
|
@@ -822,11 +797,12 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
822
797
|
let title: string | undefined;
|
|
823
798
|
let body: string | undefined;
|
|
824
799
|
let trigger: string | undefined;
|
|
825
|
-
let steps:
|
|
800
|
+
let steps: unknown;
|
|
826
801
|
let tools: string[] | undefined;
|
|
827
802
|
let labels: string[] | undefined;
|
|
828
803
|
let extra: Record<string, unknown> | undefined;
|
|
829
804
|
let playbookArguments: unknown;
|
|
805
|
+
let runId: string | undefined;
|
|
830
806
|
let status: string | undefined;
|
|
831
807
|
let text: string | undefined;
|
|
832
808
|
let limit: number | undefined;
|
|
@@ -834,17 +810,61 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
834
810
|
for (let index = 0; index < args.length; index++) {
|
|
835
811
|
const argument = args[index]!;
|
|
836
812
|
if (argument === "--json") continue;
|
|
837
|
-
if (argument === "--title") {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
if (argument === "--
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (argument === "--
|
|
813
|
+
if (argument === "--title") {
|
|
814
|
+
title = args[++index];
|
|
815
|
+
if (title === undefined) throw new Error("--title requires a value");
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
if (argument === "--body") {
|
|
819
|
+
body = args[++index];
|
|
820
|
+
if (body === undefined) throw new Error("--body requires a value");
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (argument === "--trigger") {
|
|
824
|
+
trigger = args[++index];
|
|
825
|
+
if (trigger === undefined) throw new Error("--trigger requires a value");
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
if (argument === "--steps-json") {
|
|
829
|
+
steps = parseJsonAnyFlag(args[++index], "--steps-json");
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (argument === "--tools-json") {
|
|
833
|
+
tools = parseJsonStringArrayFlag(args[++index], "--tools-json");
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (argument === "--labels-json") {
|
|
837
|
+
labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (argument === "--extra-json") {
|
|
841
|
+
extra = parseJsonObjectFlag(args[++index], "--extra-json");
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (argument === "--arguments-json") {
|
|
845
|
+
playbookArguments = parseJsonAnyFlag(args[++index], "--arguments-json");
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (argument === "--run-id") {
|
|
849
|
+
runId = args[++index];
|
|
850
|
+
if (!runId) throw new Error("--run-id requires a value");
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
if (argument === "--status") {
|
|
854
|
+
status = args[++index];
|
|
855
|
+
if (!status) throw new Error("--status requires a value");
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (argument === "--text") {
|
|
859
|
+
text = args[++index];
|
|
860
|
+
if (text === undefined) throw new Error("--text requires a value");
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (argument === "--project-root") {
|
|
864
|
+
playbookProjectRoot = args[++index];
|
|
865
|
+
if (!playbookProjectRoot) throw new Error("--project-root requires a value");
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
848
868
|
if (argument === "--limit") {
|
|
849
869
|
const value = args[++index];
|
|
850
870
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -861,14 +881,29 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
861
881
|
case "create": {
|
|
862
882
|
if (id) throw new Error("playbooks create accepts no positional arguments");
|
|
863
883
|
if (!title) throw new Error("playbooks create requires --title");
|
|
864
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.create", {
|
|
884
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.create", {
|
|
885
|
+
title,
|
|
886
|
+
body,
|
|
887
|
+
trigger,
|
|
888
|
+
steps,
|
|
889
|
+
tools,
|
|
890
|
+
labels,
|
|
891
|
+
extra,
|
|
892
|
+
arguments: playbookArguments,
|
|
893
|
+
project_root: playbookProjectRoot,
|
|
894
|
+
});
|
|
865
895
|
result = artifact;
|
|
866
896
|
human = `Created playbook: ${artifactLabel(artifact)}`;
|
|
867
897
|
break;
|
|
868
898
|
}
|
|
869
899
|
case "list": {
|
|
870
900
|
if (id) throw new Error("playbooks list accepts no positional arguments");
|
|
871
|
-
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("playbooks.list", {
|
|
901
|
+
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("playbooks.list", {
|
|
902
|
+
status,
|
|
903
|
+
text,
|
|
904
|
+
limit,
|
|
905
|
+
project_root: playbookProjectRoot,
|
|
906
|
+
});
|
|
872
907
|
result = rows;
|
|
873
908
|
human = rows.length === 0 ? "No playbooks found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
874
909
|
break;
|
|
@@ -889,7 +924,10 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
889
924
|
}
|
|
890
925
|
case "invoke": {
|
|
891
926
|
if (!id || second) throw new Error("playbooks invoke requires exactly one playbook id");
|
|
892
|
-
const invocation = await client.call<Record<string, unknown>, { entryTaskId: string; missingArguments?: string[] }>(
|
|
927
|
+
const invocation = await client.call<Record<string, unknown>, { entryTaskId: string; missingArguments?: string[] }>(
|
|
928
|
+
"playbooks.invoke",
|
|
929
|
+
{ id, arguments: playbookArguments, run_id: runId, project_root: playbookProjectRoot },
|
|
930
|
+
);
|
|
893
931
|
result = invocation;
|
|
894
932
|
human = invocation.missingArguments
|
|
895
933
|
? `Missing required argument(s): ${invocation.missingArguments.join(", ")}.`
|
|
@@ -905,7 +943,7 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
905
943
|
break;
|
|
906
944
|
}
|
|
907
945
|
case "assign-project": {
|
|
908
|
-
if (!id || second === undefined && positional.length > 2) throw new Error("playbooks assign-project requires <id> [project-root]");
|
|
946
|
+
if (!id || (second === undefined && positional.length > 2)) throw new Error("playbooks assign-project requires <id> [project-root]");
|
|
909
947
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.assign_project", { id, project_root: second });
|
|
910
948
|
result = artifact;
|
|
911
949
|
human = second ? `Assigned ${id} to ${second}` : `Unscoped ${id}`;
|
|
@@ -913,7 +951,8 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
913
951
|
}
|
|
914
952
|
case "update": {
|
|
915
953
|
if (!id || second) throw new Error("playbooks update requires exactly one playbook id");
|
|
916
|
-
if (title === undefined && body === undefined && labels === undefined)
|
|
954
|
+
if (title === undefined && body === undefined && labels === undefined)
|
|
955
|
+
throw new Error("playbooks update requires --title, --body, or --labels-json");
|
|
917
956
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.update", { id, title, body, labels });
|
|
918
957
|
result = artifact;
|
|
919
958
|
human = `${artifactLabel(artifact)}`;
|
|
@@ -948,7 +987,9 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
948
987
|
break;
|
|
949
988
|
}
|
|
950
989
|
default:
|
|
951
|
-
throw new Error(
|
|
990
|
+
throw new Error(
|
|
991
|
+
"playbooks action must be create, list, show, invoke, preview, enable, disable, assign-project, update, contain, uncontain, depend, or undepend",
|
|
992
|
+
);
|
|
952
993
|
}
|
|
953
994
|
return json ? JSON.stringify(result) : human;
|
|
954
995
|
}
|
|
@@ -972,16 +1013,54 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
972
1013
|
for (let index = 0; index < args.length; index++) {
|
|
973
1014
|
const argument = args[index]!;
|
|
974
1015
|
if (argument === "--json") continue;
|
|
975
|
-
if (argument === "--kind") {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (argument === "--
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1016
|
+
if (argument === "--kind") {
|
|
1017
|
+
kind = args[++index];
|
|
1018
|
+
if (!kind) throw new Error("--kind requires a value");
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
if (argument === "--title") {
|
|
1022
|
+
title = args[++index];
|
|
1023
|
+
if (title === undefined) throw new Error("--title requires a value");
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (argument === "--body") {
|
|
1027
|
+
body = args[++index];
|
|
1028
|
+
if (body === undefined) throw new Error("--body requires a value");
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
if (argument === "--status") {
|
|
1032
|
+
status = args[++index];
|
|
1033
|
+
if (!status) throw new Error("--status requires a value");
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (argument === "--subtype") {
|
|
1037
|
+
subtype = args[++index];
|
|
1038
|
+
if (!subtype) throw new Error("--subtype requires a value");
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (argument === "--labels-json") {
|
|
1042
|
+
labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (argument === "--extra-json") {
|
|
1046
|
+
extra = parseJsonObjectFlag(args[++index], "--extra-json");
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (argument === "--template-id") {
|
|
1050
|
+
templateId = args[++index];
|
|
1051
|
+
if (!templateId) throw new Error("--template-id requires a value");
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (argument === "--reason") {
|
|
1055
|
+
reason = args[++index];
|
|
1056
|
+
if (reason === undefined) throw new Error("--reason requires a value");
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (argument === "--text") {
|
|
1060
|
+
text = args[++index];
|
|
1061
|
+
if (text === undefined) throw new Error("--text requires a value");
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
985
1064
|
if (argument === "--limit") {
|
|
986
1065
|
const value = args[++index];
|
|
987
1066
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -1011,7 +1090,14 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
1011
1090
|
if (id) throw new Error("artifact create accepts no positional arguments");
|
|
1012
1091
|
if (!kind && !templateId) throw new Error("artifact create requires --kind (or --template-id)");
|
|
1013
1092
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("artifact.create", {
|
|
1014
|
-
kind,
|
|
1093
|
+
kind,
|
|
1094
|
+
title,
|
|
1095
|
+
body,
|
|
1096
|
+
status,
|
|
1097
|
+
subtype,
|
|
1098
|
+
labels,
|
|
1099
|
+
extra,
|
|
1100
|
+
template_id: templateId,
|
|
1015
1101
|
...(kind === "task" ? { project_root: projectRoot } : {}),
|
|
1016
1102
|
});
|
|
1017
1103
|
result = artifact;
|
|
@@ -1034,14 +1120,20 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
1034
1120
|
}
|
|
1035
1121
|
case "remove": {
|
|
1036
1122
|
if (!id) throw new Error("artifact remove requires exactly one artifact id");
|
|
1037
|
-
const record = await client.call<
|
|
1123
|
+
const record = await client.call<
|
|
1124
|
+
Record<string, unknown>,
|
|
1125
|
+
{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }
|
|
1126
|
+
>("artifact.remove", { id, reason });
|
|
1038
1127
|
result = record;
|
|
1039
1128
|
human = `Trashed ${record.artifactId}: eligible for purge at ${record.purgeAfter}`;
|
|
1040
1129
|
break;
|
|
1041
1130
|
}
|
|
1042
1131
|
case "remove-subtree": {
|
|
1043
1132
|
if (!id) throw new Error("artifact remove-subtree requires exactly one artifact id");
|
|
1044
|
-
const outcome = await client.call<Record<string, unknown>, { removed: string[]; skipped: string[] }>("artifact.remove_subtree", {
|
|
1133
|
+
const outcome = await client.call<Record<string, unknown>, { removed: string[]; skipped: string[] }>("artifact.remove_subtree", {
|
|
1134
|
+
id,
|
|
1135
|
+
reason,
|
|
1136
|
+
});
|
|
1045
1137
|
result = outcome;
|
|
1046
1138
|
human = `Trashed ${outcome.removed.length} artifact(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-trashed` : ""}.`;
|
|
1047
1139
|
break;
|
|
@@ -1055,16 +1147,25 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
1055
1147
|
}
|
|
1056
1148
|
case "trash-status": {
|
|
1057
1149
|
if (!id) throw new Error("artifact trash-status requires exactly one artifact id");
|
|
1058
|
-
const record = await client.call<
|
|
1150
|
+
const record = await client.call<
|
|
1151
|
+
Record<string, unknown>,
|
|
1152
|
+
{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string } | null
|
|
1153
|
+
>("artifact.trash_status", { id });
|
|
1059
1154
|
result = record;
|
|
1060
|
-
human = record
|
|
1155
|
+
human = record
|
|
1156
|
+
? `${record.artifactId}: trashed at ${record.trashedAt}, purge eligible at ${record.purgeAfter}`
|
|
1157
|
+
: `${id} is not trashed`;
|
|
1061
1158
|
break;
|
|
1062
1159
|
}
|
|
1063
1160
|
case "trash-list": {
|
|
1064
1161
|
if (id) throw new Error("artifact trash-list accepts no positional arguments");
|
|
1065
|
-
const rows = await client.call<
|
|
1162
|
+
const rows = await client.call<
|
|
1163
|
+
Record<string, unknown>,
|
|
1164
|
+
Array<{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }>
|
|
1165
|
+
>("artifact.trash_list", {});
|
|
1066
1166
|
result = rows;
|
|
1067
|
-
human =
|
|
1167
|
+
human =
|
|
1168
|
+
rows.length === 0 ? "Trash is empty." : rows.map((row) => `${row.artifactId}: purge eligible at ${row.purgeAfter}`).join("\n");
|
|
1068
1169
|
break;
|
|
1069
1170
|
}
|
|
1070
1171
|
default:
|
|
@@ -1092,8 +1193,15 @@ export async function runGraphProjectionCli(args: string[], client: TaskCliClien
|
|
|
1092
1193
|
for (let index = 0; index < args.length; index++) {
|
|
1093
1194
|
const argument = args[index]!;
|
|
1094
1195
|
if (argument === "--json") continue;
|
|
1095
|
-
if (argument === "--batch-json") {
|
|
1096
|
-
|
|
1196
|
+
if (argument === "--batch-json") {
|
|
1197
|
+
batch = parseJsonObjectFlag(args[++index], "--batch-json");
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (argument === "--producer-id") {
|
|
1201
|
+
producerId = args[++index];
|
|
1202
|
+
if (!producerId) throw new Error("--producer-id requires a value");
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1097
1205
|
positional.push(argument);
|
|
1098
1206
|
}
|
|
1099
1207
|
const [action] = positional;
|
|
@@ -1128,9 +1236,27 @@ export async function runLogCli(args: string[], client: TaskCliClient, projectRo
|
|
|
1128
1236
|
for (let index = 0; index < args.length; index++) {
|
|
1129
1237
|
const argument = args[index]!;
|
|
1130
1238
|
if (argument === "--json") continue;
|
|
1131
|
-
if (argument === "--global") {
|
|
1132
|
-
|
|
1133
|
-
|
|
1239
|
+
if (argument === "--global") {
|
|
1240
|
+
global = true;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
if (argument === "--fields-json") {
|
|
1244
|
+
fields = parseJsonObjectFlag(args[++index], "--fields-json");
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (
|
|
1248
|
+
[
|
|
1249
|
+
"--source",
|
|
1250
|
+
"--source-label",
|
|
1251
|
+
"--level",
|
|
1252
|
+
"--message",
|
|
1253
|
+
"--operation-id",
|
|
1254
|
+
"--session-id",
|
|
1255
|
+
"--occurred-at",
|
|
1256
|
+
"--since",
|
|
1257
|
+
"--limit",
|
|
1258
|
+
].includes(argument)
|
|
1259
|
+
) {
|
|
1134
1260
|
const value = args[++index];
|
|
1135
1261
|
if (value === undefined) throw new Error(`${argument} requires a value`);
|
|
1136
1262
|
if (argument === "--source") source = value;
|
|
@@ -1157,17 +1283,25 @@ export async function runLogCli(args: string[], client: TaskCliClient, projectRo
|
|
|
1157
1283
|
if (!message) throw new Error("log append requires --message");
|
|
1158
1284
|
if (!operationId) throw new Error("log append requires --operation-id");
|
|
1159
1285
|
const result = await client.call("logs.append", {
|
|
1160
|
-
source_id: source,
|
|
1286
|
+
source_id: source,
|
|
1287
|
+
...(sourceLabel ? { source_label: sourceLabel } : {}),
|
|
1161
1288
|
...(global ? {} : { project_root: projectRoot }),
|
|
1162
|
-
level,
|
|
1163
|
-
|
|
1289
|
+
level,
|
|
1290
|
+
message,
|
|
1291
|
+
operation_id: operationId,
|
|
1292
|
+
...(fields ? { fields } : {}),
|
|
1293
|
+
...(sessionId ? { session_id: sessionId } : {}),
|
|
1294
|
+
...(occurredAt ? { occurred_at: occurredAt } : {}),
|
|
1164
1295
|
});
|
|
1165
1296
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1166
1297
|
}
|
|
1167
1298
|
if (action === "query") {
|
|
1168
1299
|
if (!source) throw new Error("log query requires --source");
|
|
1169
1300
|
const result = await client.call<Record<string, unknown>, { entries: unknown[]; truncated: boolean }>("logs.query", {
|
|
1170
|
-
source_id: source,
|
|
1301
|
+
source_id: source,
|
|
1302
|
+
...(since ? { since } : {}),
|
|
1303
|
+
...(level ? { level } : {}),
|
|
1304
|
+
...(limit === undefined ? {} : { limit }),
|
|
1171
1305
|
});
|
|
1172
1306
|
if (json) return JSON.stringify(result);
|
|
1173
1307
|
const lines = result.entries.map((entry) => JSON.stringify(entry));
|
|
@@ -1184,20 +1318,31 @@ export async function runSessionIdentityCli(args: string[], client: TaskCliClien
|
|
|
1184
1318
|
for (let index = 0; index < args.length; index++) {
|
|
1185
1319
|
const argument = args[index]!;
|
|
1186
1320
|
if (argument === "--json") continue;
|
|
1187
|
-
if (argument === "--session-id") {
|
|
1188
|
-
|
|
1321
|
+
if (argument === "--session-id") {
|
|
1322
|
+
sessionId = args[++index];
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (argument === "--session-secret") {
|
|
1326
|
+
secret = args[++index];
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1189
1329
|
if (argument.startsWith("--")) throw new Error(`unknown session option ${argument}`);
|
|
1190
1330
|
positional.push(argument);
|
|
1191
1331
|
}
|
|
1192
1332
|
const [action] = positional;
|
|
1193
1333
|
if (action === "register") {
|
|
1194
1334
|
if (!sessionId) throw new Error("session register requires --session-id");
|
|
1195
|
-
const result = await client.call<Record<string, unknown>, { sessionId: string; secret: string }>("session.register", {
|
|
1335
|
+
const result = await client.call<Record<string, unknown>, { sessionId: string; secret: string }>("session.register", {
|
|
1336
|
+
session_id: sessionId,
|
|
1337
|
+
});
|
|
1196
1338
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1197
1339
|
}
|
|
1198
1340
|
if (action === "release") {
|
|
1199
1341
|
if (!sessionId) throw new Error("session release requires --session-id");
|
|
1200
|
-
const result = await client.call<Record<string, unknown>, { released: boolean }>("session.release", {
|
|
1342
|
+
const result = await client.call<Record<string, unknown>, { released: boolean }>("session.release", {
|
|
1343
|
+
session_id: sessionId,
|
|
1344
|
+
...(secret ? { session_secret: secret } : {}),
|
|
1345
|
+
});
|
|
1201
1346
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1202
1347
|
}
|
|
1203
1348
|
throw new Error("session action must be register or release");
|
|
@@ -1225,20 +1370,71 @@ export async function runDiscussCli(args: string[], client: TaskCliClient): Prom
|
|
|
1225
1370
|
for (let index = 0; index < args.length; index++) {
|
|
1226
1371
|
const argument = args[index]!;
|
|
1227
1372
|
if (argument === "--json") continue;
|
|
1228
|
-
if (argument === "--title") {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (argument === "--
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
if (argument === "--
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1373
|
+
if (argument === "--title") {
|
|
1374
|
+
title = args[++index];
|
|
1375
|
+
if (!title) throw new Error("--title requires a value");
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
if (argument === "--actor") {
|
|
1379
|
+
actor = args[++index];
|
|
1380
|
+
if (!actor) throw new Error("--actor requires a value");
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
if (argument === "--content") {
|
|
1384
|
+
content = args[++index];
|
|
1385
|
+
if (content === undefined) throw new Error("--content requires a value");
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
if (argument === "--body") {
|
|
1389
|
+
body = args[++index];
|
|
1390
|
+
if (body === undefined) throw new Error("--body requires a value");
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (argument === "--labels-json") {
|
|
1394
|
+
labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (argument === "--blocks-json") {
|
|
1398
|
+
blocksTaskIds = parseJsonStringArrayFlag(args[++index], "--blocks-json");
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
if (argument === "--task-id") {
|
|
1402
|
+
taskId = args[++index];
|
|
1403
|
+
if (!taskId) throw new Error("--task-id requires a value");
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (argument === "--options-json") {
|
|
1407
|
+
options = parseJsonStringArrayFlag(args[++index], "--options-json");
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (argument === "--options-mode") {
|
|
1411
|
+
optionsMode = args[++index];
|
|
1412
|
+
if (!optionsMode) throw new Error("--options-mode requires a value");
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
if (argument === "--option-descriptions-json") {
|
|
1416
|
+
optionDescriptions = parseJsonStringArrayFlag(args[++index], "--option-descriptions-json");
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if (argument === "--selected-json") {
|
|
1420
|
+
selected = parseJsonStringArrayFlag(args[++index], "--selected-json");
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
if (argument === "--reason") {
|
|
1424
|
+
reason = args[++index];
|
|
1425
|
+
if (reason === undefined) throw new Error("--reason requires a value");
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (argument === "--settlement") {
|
|
1429
|
+
settlement = args[++index];
|
|
1430
|
+
if (!settlement) throw new Error("--settlement requires a value");
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (argument === "--state") {
|
|
1434
|
+
state = args[++index];
|
|
1435
|
+
if (!state) throw new Error("--state requires a value");
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1242
1438
|
if (argument === "--after-round") {
|
|
1243
1439
|
const value = args[++index];
|
|
1244
1440
|
if (!value || Number.isNaN(Number(value))) throw new Error("--after-round requires a numeric value");
|
|
@@ -1258,12 +1454,30 @@ export async function runDiscussCli(args: string[], client: TaskCliClient): Prom
|
|
|
1258
1454
|
switch (action) {
|
|
1259
1455
|
case "open": {
|
|
1260
1456
|
if (id) throw new Error("discuss open accepts no positional arguments");
|
|
1261
|
-
const result = await client.call<Record<string, unknown>, unknown>("discuss.open", {
|
|
1457
|
+
const result = await client.call<Record<string, unknown>, unknown>("discuss.open", {
|
|
1458
|
+
title,
|
|
1459
|
+
actor,
|
|
1460
|
+
content,
|
|
1461
|
+
body,
|
|
1462
|
+
labels,
|
|
1463
|
+
blocks_task_ids: blocksTaskIds,
|
|
1464
|
+
options,
|
|
1465
|
+
options_mode: optionsMode,
|
|
1466
|
+
option_descriptions: optionDescriptions,
|
|
1467
|
+
});
|
|
1262
1468
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1263
1469
|
}
|
|
1264
1470
|
case "reply": {
|
|
1265
1471
|
if (!id) throw new Error("discuss reply requires exactly one discussion id");
|
|
1266
|
-
const result = await client.call<Record<string, unknown>, unknown>("discuss.reply", {
|
|
1472
|
+
const result = await client.call<Record<string, unknown>, unknown>("discuss.reply", {
|
|
1473
|
+
id,
|
|
1474
|
+
actor,
|
|
1475
|
+
content,
|
|
1476
|
+
selected,
|
|
1477
|
+
options,
|
|
1478
|
+
options_mode: optionsMode,
|
|
1479
|
+
option_descriptions: optionDescriptions,
|
|
1480
|
+
});
|
|
1267
1481
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1268
1482
|
}
|
|
1269
1483
|
case "defer": {
|
|
@@ -1343,34 +1557,77 @@ export async function runNoteCli(args: string[], client: TaskCliClient, projectR
|
|
|
1343
1557
|
let human: string;
|
|
1344
1558
|
if (action === "capture") {
|
|
1345
1559
|
if (!id || target) throw new Error("notes capture requires exactly one request argument");
|
|
1346
|
-
result = await client.call("notes.capture", {
|
|
1560
|
+
result = (await client.call("notes.capture", {
|
|
1561
|
+
body: id,
|
|
1562
|
+
...(title ? { title } : {}),
|
|
1563
|
+
project_root: projectRoot,
|
|
1564
|
+
actor: "human",
|
|
1565
|
+
source: "cli",
|
|
1566
|
+
})) as CliArtifact;
|
|
1347
1567
|
human = `Captured: ${artifactLabel(result)}`;
|
|
1348
1568
|
} else if (action === "list") {
|
|
1349
1569
|
if (id) throw new Error("notes list accepts no positional arguments");
|
|
1350
|
-
result = await client.call("notes.list", {
|
|
1570
|
+
result = (await client.call("notes.list", {
|
|
1571
|
+
project_root: projectRoot,
|
|
1572
|
+
...(status ? { status } : {}),
|
|
1573
|
+
...(text ? { text } : {}),
|
|
1574
|
+
...(limit === undefined ? {} : { limit }),
|
|
1575
|
+
})) as CliArtifact[];
|
|
1351
1576
|
human = result.length > 0 ? result.map((note) => `[${note.status}] ${artifactLabel(note)}`).join("\n") : "No open notes.";
|
|
1352
1577
|
} else if (action === "show") {
|
|
1353
1578
|
if (!id || target) throw new Error("notes show requires exactly one note id");
|
|
1354
|
-
result = await client.call("notes.show", { id, project_root: projectRoot }) as CliArtifact;
|
|
1579
|
+
result = (await client.call("notes.show", { id, project_root: projectRoot })) as CliArtifact;
|
|
1355
1580
|
human = `${artifactLabel(result)}\n\n${result.body ?? ""}`.trimEnd();
|
|
1356
1581
|
} else if (action === "history") {
|
|
1357
1582
|
if (!id || target) throw new Error("notes history requires exactly one note id");
|
|
1358
|
-
const page = await client.call<Record<string, unknown>, import("./domain/note-event.ts").NoteHistoryPage>("notes.history", {
|
|
1583
|
+
const page = await client.call<Record<string, unknown>, import("./domain/note-event.ts").NoteHistoryPage>("notes.history", {
|
|
1584
|
+
id,
|
|
1585
|
+
project_root: projectRoot,
|
|
1586
|
+
direction: "desc",
|
|
1587
|
+
...(limit === undefined ? {} : { limit }),
|
|
1588
|
+
});
|
|
1359
1589
|
result = page;
|
|
1360
|
-
human =
|
|
1361
|
-
|
|
1362
|
-
|
|
1590
|
+
human =
|
|
1591
|
+
page.events.length === 0
|
|
1592
|
+
? `No recorded history for ${id}.`
|
|
1593
|
+
: [...page.events]
|
|
1594
|
+
.reverse()
|
|
1595
|
+
.map(
|
|
1596
|
+
(event) =>
|
|
1597
|
+
`${event.occurredAt} ${event.type} · ${event.actor}/${event.source}${event.relatedId ? ` · ${event.relatedId}` : ""}${event.disposition ? ` · ${event.disposition}` : ""}${event.reason ? ` · ${event.reason}` : ""}`,
|
|
1598
|
+
)
|
|
1599
|
+
.join("\n");
|
|
1363
1600
|
} else if (action === "consume") {
|
|
1364
1601
|
if (!id || target) throw new Error("notes consume requires exactly one note id");
|
|
1365
|
-
result = await client.call("notes.consume", {
|
|
1602
|
+
result = (await client.call("notes.consume", {
|
|
1603
|
+
id,
|
|
1604
|
+
project_root: projectRoot,
|
|
1605
|
+
actor: "agent",
|
|
1606
|
+
source: "cli",
|
|
1607
|
+
...(reason ? { reason } : {}),
|
|
1608
|
+
})) as CliArtifact;
|
|
1366
1609
|
human = `Consumed: ${artifactLabel(result)}`;
|
|
1367
1610
|
} else if (action === "promote") {
|
|
1368
1611
|
if (!id || !target || positional.length !== 3) throw new Error("notes promote requires a note id and target artifact id");
|
|
1369
|
-
result = await client.call("notes.promote", {
|
|
1612
|
+
result = (await client.call("notes.promote", {
|
|
1613
|
+
id,
|
|
1614
|
+
target_id: target,
|
|
1615
|
+
project_root: projectRoot,
|
|
1616
|
+
actor: "agent",
|
|
1617
|
+
source: "cli",
|
|
1618
|
+
...(reason ? { reason } : {}),
|
|
1619
|
+
})) as CliArtifact;
|
|
1370
1620
|
human = `Promoted: ${artifactLabel(result)} → ${target}`;
|
|
1371
1621
|
} else if (action === "archive") {
|
|
1372
1622
|
if (!id || !target || positional.length !== 3) throw new Error("notes archive requires a note id and disposition");
|
|
1373
|
-
result = await client.call("notes.archive", {
|
|
1623
|
+
result = (await client.call("notes.archive", {
|
|
1624
|
+
id,
|
|
1625
|
+
disposition: target,
|
|
1626
|
+
project_root: projectRoot,
|
|
1627
|
+
actor: "human",
|
|
1628
|
+
source: "cli",
|
|
1629
|
+
...(reason ? { reason } : {}),
|
|
1630
|
+
})) as CliArtifact;
|
|
1374
1631
|
human = `Archived: ${artifactLabel(result)} · ${target}`;
|
|
1375
1632
|
} else {
|
|
1376
1633
|
throw new Error("notes action must be capture, list, show, history, consume, promote, or archive");
|
|
@@ -1418,12 +1675,22 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1418
1675
|
if (!sessionSecret) throw new Error("--session-secret requires a value");
|
|
1419
1676
|
continue;
|
|
1420
1677
|
}
|
|
1421
|
-
if (
|
|
1678
|
+
if (
|
|
1679
|
+
argument === "--title" ||
|
|
1680
|
+
argument === "--body" ||
|
|
1681
|
+
argument === "--labels-json" ||
|
|
1682
|
+
argument === "--status" ||
|
|
1683
|
+
argument === "--reason"
|
|
1684
|
+
) {
|
|
1422
1685
|
const value = args[++index];
|
|
1423
1686
|
if (value === undefined) throw new Error(`${argument} requires a value`);
|
|
1424
|
-
if (argument === "--title") {
|
|
1425
|
-
|
|
1426
|
-
|
|
1687
|
+
if (argument === "--title") {
|
|
1688
|
+
updateInput.title = value;
|
|
1689
|
+
title = value;
|
|
1690
|
+
} else if (argument === "--body") {
|
|
1691
|
+
updateInput.body = value;
|
|
1692
|
+
body = value;
|
|
1693
|
+
} else if (argument === "--reason") reason = value;
|
|
1427
1694
|
else if (argument === "--status") {
|
|
1428
1695
|
status = value;
|
|
1429
1696
|
if (value === "todo") updateInput.status = value;
|
|
@@ -1433,7 +1700,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1433
1700
|
}
|
|
1434
1701
|
continue;
|
|
1435
1702
|
}
|
|
1436
|
-
if (argument === "--extra-json") {
|
|
1703
|
+
if (argument === "--extra-json") {
|
|
1704
|
+
extra = parseJsonObjectFlag(args[++index]!, "--extra-json");
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1437
1707
|
if (argument === "--gates-json") {
|
|
1438
1708
|
const value = args[++index];
|
|
1439
1709
|
if (!value) throw new Error("--gates-json requires a value");
|
|
@@ -1442,11 +1712,29 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1442
1712
|
gates = parsed;
|
|
1443
1713
|
continue;
|
|
1444
1714
|
}
|
|
1445
|
-
if (argument === "--checklist-json") {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
if (argument === "--
|
|
1715
|
+
if (argument === "--checklist-json") {
|
|
1716
|
+
checklist = parseJsonObjectFlag(args[++index]!, "--checklist-json");
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
if (argument === "--template-id") {
|
|
1720
|
+
templateId = args[++index];
|
|
1721
|
+
if (!templateId) throw new Error("--template-id requires a value");
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
if (argument === "--parent-id") {
|
|
1725
|
+
parentId = args[++index];
|
|
1726
|
+
if (!parentId) throw new Error("--parent-id requires a value");
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
if (argument === "--depends-on-json") {
|
|
1730
|
+
dependsOn = parseJsonStringArrayFlag(args[++index]!, "--depends-on-json");
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
if (argument === "--text") {
|
|
1734
|
+
text = args[++index];
|
|
1735
|
+
if (text === undefined) throw new Error("--text requires a value");
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1450
1738
|
if (argument === "--limit") {
|
|
1451
1739
|
const value = args[++index];
|
|
1452
1740
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -1459,10 +1747,26 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1459
1747
|
listScope = value;
|
|
1460
1748
|
continue;
|
|
1461
1749
|
}
|
|
1462
|
-
if (argument === "--root-task-id") {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1750
|
+
if (argument === "--root-task-id") {
|
|
1751
|
+
rootTaskId = args[++index];
|
|
1752
|
+
if (!rootTaskId) throw new Error("--root-task-id requires a value");
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
if (argument === "--owner") {
|
|
1756
|
+
owner = args[++index];
|
|
1757
|
+
if (!owner) throw new Error("--owner requires a value");
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
if (argument === "--token") {
|
|
1761
|
+
token = args[++index];
|
|
1762
|
+
if (!token) throw new Error("--token requires a value");
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
if (argument === "--note") {
|
|
1766
|
+
note = args[++index];
|
|
1767
|
+
if (note === undefined) throw new Error("--note requires a value");
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1466
1770
|
if (argument === "--ttl-ms") {
|
|
1467
1771
|
const value = args[++index];
|
|
1468
1772
|
if (!value || Number.isNaN(Number(value))) throw new Error("--ttl-ms requires a numeric value");
|
|
@@ -1475,13 +1779,17 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1475
1779
|
cursor = Number(value);
|
|
1476
1780
|
continue;
|
|
1477
1781
|
}
|
|
1478
|
-
if (argument === "--event-types-json") {
|
|
1782
|
+
if (argument === "--event-types-json") {
|
|
1783
|
+
eventTypes = parseJsonStringArrayFlag(args[++index]!, "--event-types-json");
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1479
1786
|
if (argument.startsWith("--")) throw new Error(`unknown tasks option ${argument}`);
|
|
1480
1787
|
positional.push(argument);
|
|
1481
1788
|
}
|
|
1482
1789
|
const [action, id, dependencyId] = positional;
|
|
1483
1790
|
const reasonSupportedActions = new Set(["update", "depend", "undepend", "contain", "uncontain"]);
|
|
1484
|
-
if (reason !== undefined && !reasonSupportedActions.has(action ?? ""))
|
|
1791
|
+
if (reason !== undefined && !reasonSupportedActions.has(action ?? ""))
|
|
1792
|
+
throw new Error("--reason is only supported by tasks update, depend, undepend, contain, and uncontain");
|
|
1485
1793
|
const sessionScope = sessionId ? { session_id: sessionId } : {};
|
|
1486
1794
|
// Only meaningful alongside a registered session_id (see session.register); required by the
|
|
1487
1795
|
// daemon only for the specific Focus-mutating operations below (focus/pause/unpause/clear_focus)
|
|
@@ -1492,14 +1800,20 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1492
1800
|
switch (action) {
|
|
1493
1801
|
case "active": {
|
|
1494
1802
|
if (id) throw new Error("tasks active accepts no positional arguments");
|
|
1495
|
-
const active = await client.call<Record<string, unknown>, CliArtifact | null>("tasks.active", {
|
|
1803
|
+
const active = await client.call<Record<string, unknown>, CliArtifact | null>("tasks.active", {
|
|
1804
|
+
project_root: projectRoot,
|
|
1805
|
+
...sessionScope,
|
|
1806
|
+
});
|
|
1496
1807
|
result = active;
|
|
1497
1808
|
human = active ? `Active: ${artifactLabel(active)}` : "No active task.";
|
|
1498
1809
|
break;
|
|
1499
1810
|
}
|
|
1500
1811
|
case "focused": {
|
|
1501
1812
|
if (id) throw new Error("tasks focused accepts no positional arguments");
|
|
1502
|
-
const focus = await client.call<
|
|
1813
|
+
const focus = await client.call<
|
|
1814
|
+
Record<string, unknown>,
|
|
1815
|
+
{ artifact: CliArtifact; status: "active" | "paused"; updatedAt: string } | null
|
|
1816
|
+
>("tasks.focused", { project_root: projectRoot, ...sessionScope });
|
|
1503
1817
|
result = focus;
|
|
1504
1818
|
human = focus ? `Focused (${focus.status}): ${artifactLabel(focus.artifact)}` : "No focused task.";
|
|
1505
1819
|
break;
|
|
@@ -1508,14 +1822,24 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1508
1822
|
case "unpause": {
|
|
1509
1823
|
if (id) throw new Error(`tasks ${action} accepts no positional arguments`);
|
|
1510
1824
|
const operation = action === "pause" ? "tasks.pause" : "tasks.unpause";
|
|
1511
|
-
const focus = await client.call<Record<string, unknown>, { artifact: CliArtifact; status: string }>(operation, {
|
|
1825
|
+
const focus = await client.call<Record<string, unknown>, { artifact: CliArtifact; status: string }>(operation, {
|
|
1826
|
+
actor: "user",
|
|
1827
|
+
source: "cli",
|
|
1828
|
+
...sessionScope,
|
|
1829
|
+
...sessionSecretField,
|
|
1830
|
+
});
|
|
1512
1831
|
result = focus;
|
|
1513
1832
|
human = `Focused (${focus.status}): ${artifactLabel(focus.artifact)}`;
|
|
1514
1833
|
break;
|
|
1515
1834
|
}
|
|
1516
1835
|
case "clear-focus": {
|
|
1517
1836
|
if (id) throw new Error("tasks clear-focus accepts no positional arguments");
|
|
1518
|
-
const cleared = await client.call<Record<string, unknown>, { cleared: boolean }>("tasks.clear_focus", {
|
|
1837
|
+
const cleared = await client.call<Record<string, unknown>, { cleared: boolean }>("tasks.clear_focus", {
|
|
1838
|
+
actor: "user",
|
|
1839
|
+
source: "cli",
|
|
1840
|
+
...sessionScope,
|
|
1841
|
+
...sessionSecretField,
|
|
1842
|
+
});
|
|
1519
1843
|
result = cleared;
|
|
1520
1844
|
human = cleared.cleared ? "Task focus cleared." : "No focused task.";
|
|
1521
1845
|
break;
|
|
@@ -1567,20 +1891,36 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1567
1891
|
}
|
|
1568
1892
|
case "event-feed": {
|
|
1569
1893
|
if (id) throw new Error("tasks event-feed accepts no positional arguments");
|
|
1570
|
-
const page = await client.call<Record<string, unknown>, import("./domain/task-event.ts").TaskEventFeedPage>("tasks.event_feed", {
|
|
1894
|
+
const page = await client.call<Record<string, unknown>, import("./domain/task-event.ts").TaskEventFeedPage>("tasks.event_feed", {
|
|
1895
|
+
cursor,
|
|
1896
|
+
limit,
|
|
1897
|
+
event_types: eventTypes,
|
|
1898
|
+
});
|
|
1571
1899
|
result = page;
|
|
1572
|
-
human =
|
|
1573
|
-
|
|
1574
|
-
|
|
1900
|
+
human =
|
|
1901
|
+
page.events.length === 0
|
|
1902
|
+
? "No events."
|
|
1903
|
+
: page.events.map((event) => `${event.id} ${event.occurredAt} ${event.taskId} ${event.type}`).join("\n");
|
|
1575
1904
|
break;
|
|
1576
1905
|
}
|
|
1577
1906
|
case "create": {
|
|
1578
1907
|
if (id) throw new Error("tasks create accepts no positional arguments");
|
|
1579
1908
|
if (!title) throw new Error("tasks create requires --title");
|
|
1580
1909
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.create", {
|
|
1581
|
-
title,
|
|
1582
|
-
|
|
1583
|
-
|
|
1910
|
+
title,
|
|
1911
|
+
body,
|
|
1912
|
+
status,
|
|
1913
|
+
labels,
|
|
1914
|
+
extra,
|
|
1915
|
+
gates,
|
|
1916
|
+
checklist,
|
|
1917
|
+
template_id: templateId,
|
|
1918
|
+
parent_id: parentId,
|
|
1919
|
+
depends_on: dependsOn,
|
|
1920
|
+
project_root: projectRoot,
|
|
1921
|
+
actor: "user",
|
|
1922
|
+
source: "cli",
|
|
1923
|
+
...sessionScope,
|
|
1584
1924
|
});
|
|
1585
1925
|
result = artifact;
|
|
1586
1926
|
human = `Created task: ${artifactLabel(artifact)}`;
|
|
@@ -1589,7 +1929,14 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1589
1929
|
case "list": {
|
|
1590
1930
|
if (id) throw new Error("tasks list accepts no positional arguments");
|
|
1591
1931
|
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("tasks.list", {
|
|
1592
|
-
status,
|
|
1932
|
+
status,
|
|
1933
|
+
text,
|
|
1934
|
+
limit,
|
|
1935
|
+
labels,
|
|
1936
|
+
project_root: projectRoot,
|
|
1937
|
+
scope: listScope,
|
|
1938
|
+
root_task_id: rootTaskId,
|
|
1939
|
+
...sessionScope,
|
|
1593
1940
|
});
|
|
1594
1941
|
result = rows;
|
|
1595
1942
|
human = rows.length === 0 ? "No tasks found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
@@ -1606,9 +1953,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1606
1953
|
if (!id || dependencyId) throw new Error("tasks run-gates requires exactly one task id");
|
|
1607
1954
|
const results = await client.call<Record<string, unknown>, GateResult[]>("tasks.run_gates", { id, actor: "user", source: "cli" });
|
|
1608
1955
|
result = results;
|
|
1609
|
-
human =
|
|
1610
|
-
|
|
1611
|
-
|
|
1956
|
+
human =
|
|
1957
|
+
results.length === 0
|
|
1958
|
+
? "No gates configured."
|
|
1959
|
+
: results.map((gate) => `${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`).join("\n");
|
|
1612
1960
|
break;
|
|
1613
1961
|
}
|
|
1614
1962
|
case "set-checklist": {
|
|
@@ -1630,7 +1978,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1630
1978
|
case "context": {
|
|
1631
1979
|
if (id) throw new Error("tasks context accepts no positional arguments");
|
|
1632
1980
|
const summary = await client.call<Record<string, unknown>, string | null>("tasks.context", {
|
|
1633
|
-
project_root: projectRoot,
|
|
1981
|
+
project_root: projectRoot,
|
|
1982
|
+
scope: listScope,
|
|
1983
|
+
root_task_id: rootTaskId,
|
|
1984
|
+
...sessionScope,
|
|
1634
1985
|
});
|
|
1635
1986
|
result = summary;
|
|
1636
1987
|
human = summary ?? "No open tasks.";
|
|
@@ -1639,7 +1990,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1639
1990
|
case "contain": {
|
|
1640
1991
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks contain requires a parent id and child id");
|
|
1641
1992
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.contain", {
|
|
1642
|
-
parent_id: id,
|
|
1993
|
+
parent_id: id,
|
|
1994
|
+
child_id: dependencyId,
|
|
1995
|
+
actor: "user",
|
|
1996
|
+
source: "cli",
|
|
1997
|
+
...(reason ? { reason } : {}),
|
|
1998
|
+
...sessionScope,
|
|
1643
1999
|
});
|
|
1644
2000
|
result = artifact;
|
|
1645
2001
|
human = `Contained: ${dependencyId} → ${artifactLabel(artifact)}`;
|
|
@@ -1648,7 +2004,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1648
2004
|
case "uncontain": {
|
|
1649
2005
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks uncontain requires a parent id and child id");
|
|
1650
2006
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.uncontain", {
|
|
1651
|
-
parent_id: id,
|
|
2007
|
+
parent_id: id,
|
|
2008
|
+
child_id: dependencyId,
|
|
2009
|
+
actor: "user",
|
|
2010
|
+
source: "cli",
|
|
2011
|
+
...(reason ? { reason } : {}),
|
|
2012
|
+
...sessionScope,
|
|
1652
2013
|
});
|
|
1653
2014
|
result = artifact;
|
|
1654
2015
|
human = `Removed ${dependencyId} from ${artifactLabel(artifact)}`;
|
|
@@ -1656,12 +2017,17 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1656
2017
|
}
|
|
1657
2018
|
case "update": {
|
|
1658
2019
|
if (!id || dependencyId) throw new Error("tasks update requires exactly one task id");
|
|
1659
|
-
if (status !== undefined && status !== "todo")
|
|
2020
|
+
if (status !== undefined && status !== "todo")
|
|
2021
|
+
throw new Error("tasks update --status only supports todo for accidental creation recovery");
|
|
1660
2022
|
if (Object.keys(updateInput).length === 0) throw new Error("tasks update requires --title, --body, --labels-json, or --status todo");
|
|
1661
2023
|
if (updateInput.status !== undefined && !reason?.trim()) throw new Error("tasks update --status requires --reason");
|
|
1662
2024
|
if (reason !== undefined && updateInput.status === undefined) throw new Error("tasks update --reason requires --status todo");
|
|
1663
2025
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.update", {
|
|
1664
|
-
id,
|
|
2026
|
+
id,
|
|
2027
|
+
...updateInput,
|
|
2028
|
+
...(reason ? { reason } : {}),
|
|
2029
|
+
actor: "user",
|
|
2030
|
+
source: "cli",
|
|
1665
2031
|
});
|
|
1666
2032
|
result = artifact;
|
|
1667
2033
|
human = `Updated: ${artifactLabel(artifact)}`;
|
|
@@ -1669,16 +2035,28 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1669
2035
|
}
|
|
1670
2036
|
case "history": {
|
|
1671
2037
|
if (!id || dependencyId) throw new Error("tasks history requires exactly one task id");
|
|
1672
|
-
const page = await client.call<{ id: string; direction: "desc" }, import("./domain/task-event.ts").TaskHistoryPage>("tasks.history", {
|
|
2038
|
+
const page = await client.call<{ id: string; direction: "desc" }, import("./domain/task-event.ts").TaskHistoryPage>("tasks.history", {
|
|
2039
|
+
id,
|
|
2040
|
+
direction: "desc",
|
|
2041
|
+
});
|
|
1673
2042
|
result = page;
|
|
1674
|
-
human =
|
|
1675
|
-
|
|
1676
|
-
|
|
2043
|
+
human =
|
|
2044
|
+
page.events.length === 0
|
|
2045
|
+
? `No recorded history for ${id}.`
|
|
2046
|
+
: [...page.events]
|
|
2047
|
+
.reverse()
|
|
2048
|
+
.map(
|
|
2049
|
+
(event) =>
|
|
2050
|
+
`${event.occurredAt} ${event.type} ${event.fromStatus ?? "∅"} → ${event.toStatus ?? "∅"} · ${event.actor}/${event.source}${event.reason ? ` · ${event.reason}` : ""}`,
|
|
2051
|
+
)
|
|
2052
|
+
.join("\n");
|
|
1677
2053
|
break;
|
|
1678
2054
|
}
|
|
1679
2055
|
case "scope": {
|
|
1680
2056
|
if (!id) {
|
|
1681
|
-
const selection = await client.call<Record<string, string>, import("./domain/task-scope.ts").TaskViewSelection>("tasks.scope", {
|
|
2057
|
+
const selection = await client.call<Record<string, string>, import("./domain/task-scope.ts").TaskViewSelection>("tasks.scope", {
|
|
2058
|
+
project_root: projectRoot,
|
|
2059
|
+
});
|
|
1682
2060
|
result = selection;
|
|
1683
2061
|
human = `Task scope: ${selection.label}`;
|
|
1684
2062
|
break;
|
|
@@ -1709,17 +2087,33 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1709
2087
|
}
|
|
1710
2088
|
case "focus": {
|
|
1711
2089
|
if (!id || dependencyId) throw new Error("tasks focus requires exactly one task id");
|
|
1712
|
-
const active = await client.call<Record<string, unknown>, CliArtifact>("tasks.focus", {
|
|
2090
|
+
const active = await client.call<Record<string, unknown>, CliArtifact>("tasks.focus", {
|
|
2091
|
+
id,
|
|
2092
|
+
actor: "user",
|
|
2093
|
+
source: "cli",
|
|
2094
|
+
...sessionScope,
|
|
2095
|
+
...sessionSecretField,
|
|
2096
|
+
});
|
|
1713
2097
|
result = active;
|
|
1714
2098
|
human = `Active: ${artifactLabel(active)}`;
|
|
1715
2099
|
break;
|
|
1716
2100
|
}
|
|
1717
2101
|
case "graph": {
|
|
1718
2102
|
if (id) throw new Error("tasks graph accepts no positional arguments");
|
|
1719
|
-
const graph = await client.call<
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2103
|
+
const graph = await client.call<
|
|
2104
|
+
Record<string, unknown>,
|
|
2105
|
+
{
|
|
2106
|
+
nodes: Array<{ dependencyIds: string[]; childIds: string[] }>;
|
|
2107
|
+
rootIds: string[];
|
|
2108
|
+
}
|
|
2109
|
+
>("tasks.graph", {
|
|
2110
|
+
limit: TASK_EXECUTION_MAX_NODES + 1,
|
|
2111
|
+
labels,
|
|
2112
|
+
project_root: projectRoot,
|
|
2113
|
+
scope: listScope,
|
|
2114
|
+
root_task_id: rootTaskId,
|
|
2115
|
+
...sessionScope,
|
|
2116
|
+
});
|
|
1723
2117
|
result = graph;
|
|
1724
2118
|
const dependencies = graph.nodes.reduce((count, node) => count + node.dependencyIds.length, 0);
|
|
1725
2119
|
const children = graph.nodes.reduce((count, node) => count + node.childIds.length, 0);
|
|
@@ -1728,19 +2122,29 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1728
2122
|
}
|
|
1729
2123
|
case "plan": {
|
|
1730
2124
|
if (id) throw new Error("tasks plan accepts no positional arguments");
|
|
1731
|
-
const plan = await client.call<Record<string, unknown>, TaskExecutionPlan>("tasks.plan", {
|
|
2125
|
+
const plan = await client.call<Record<string, unknown>, TaskExecutionPlan>("tasks.plan", {
|
|
2126
|
+
project_root: projectRoot,
|
|
2127
|
+
...sessionScope,
|
|
2128
|
+
});
|
|
1732
2129
|
result = plan;
|
|
1733
2130
|
human = planText(plan);
|
|
1734
2131
|
break;
|
|
1735
2132
|
}
|
|
1736
2133
|
case "complete": {
|
|
1737
2134
|
if (!id || dependencyId) throw new Error("tasks complete requires exactly one task id");
|
|
1738
|
-
const completion = await client.call<Record<string, unknown>, CliCompletion>("tasks.complete", {
|
|
2135
|
+
const completion = await client.call<Record<string, unknown>, CliCompletion>("tasks.complete", {
|
|
2136
|
+
id,
|
|
2137
|
+
actor: "user",
|
|
2138
|
+
source: "cli",
|
|
2139
|
+
...sessionScope,
|
|
2140
|
+
});
|
|
1739
2141
|
result = completion;
|
|
1740
2142
|
const lines = [`${completion.completed ? "Completed" : "Rejected"}: ${artifactLabel(completion.artifact)}`];
|
|
1741
2143
|
if (completion.focused) lines.push(`Active: ${artifactLabel(completion.focused)}`);
|
|
1742
2144
|
if (completion.blocked.length > 0) {
|
|
1743
|
-
lines.push(
|
|
2145
|
+
lines.push(
|
|
2146
|
+
`Blocked: ${completion.blocked.map((entry) => `${artifactLabel(entry.artifact)} waits for ${entry.dependencyIds.join(", ")}`).join("; ")}`,
|
|
2147
|
+
);
|
|
1744
2148
|
}
|
|
1745
2149
|
for (const gate of completion.gates) lines.push(`${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`);
|
|
1746
2150
|
human = lines.join("\n");
|
|
@@ -1748,7 +2152,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1748
2152
|
}
|
|
1749
2153
|
case "start": {
|
|
1750
2154
|
if (!id || dependencyId) throw new Error("tasks start requires exactly one task id");
|
|
1751
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.start", {
|
|
2155
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.start", {
|
|
2156
|
+
id,
|
|
2157
|
+
actor: "user",
|
|
2158
|
+
source: "cli",
|
|
2159
|
+
...sessionScope,
|
|
2160
|
+
});
|
|
1752
2161
|
result = artifact;
|
|
1753
2162
|
human = `Started: ${artifactLabel(artifact)}`;
|
|
1754
2163
|
break;
|
|
@@ -1759,14 +2168,24 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1759
2168
|
case "cancel": {
|
|
1760
2169
|
if (!id || dependencyId) throw new Error(`tasks ${action} requires exactly one task id`);
|
|
1761
2170
|
const operation = `tasks.${action}` as "tasks.submit" | "tasks.reject" | "tasks.retry" | "tasks.cancel";
|
|
1762
|
-
const artifact = await client.call<Record<string, unknown>, CliArtifact>(operation, {
|
|
2171
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>(operation, {
|
|
2172
|
+
id,
|
|
2173
|
+
actor: "user",
|
|
2174
|
+
source: "cli",
|
|
2175
|
+
...sessionScope,
|
|
2176
|
+
});
|
|
1763
2177
|
result = artifact;
|
|
1764
2178
|
human = `${action[0]!.toUpperCase()}${action.slice(1)}: ${artifactLabel(artifact)}`;
|
|
1765
2179
|
break;
|
|
1766
2180
|
}
|
|
1767
2181
|
case "cancel-subtree": {
|
|
1768
2182
|
if (!id || dependencyId) throw new Error("tasks cancel-subtree requires exactly one task id");
|
|
1769
|
-
const outcome = await client.call<Record<string, unknown>, { canceled: string[]; skipped: string[] }>("tasks.cancel_subtree", {
|
|
2183
|
+
const outcome = await client.call<Record<string, unknown>, { canceled: string[]; skipped: string[] }>("tasks.cancel_subtree", {
|
|
2184
|
+
id,
|
|
2185
|
+
actor: "user",
|
|
2186
|
+
source: "cli",
|
|
2187
|
+
...sessionScope,
|
|
2188
|
+
});
|
|
1770
2189
|
result = outcome;
|
|
1771
2190
|
human = `Canceled ${outcome.canceled.length} task(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-terminal` : ""}.`;
|
|
1772
2191
|
break;
|
|
@@ -1774,7 +2193,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1774
2193
|
case "depend": {
|
|
1775
2194
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks depend requires a task id and prerequisite id");
|
|
1776
2195
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.depend", {
|
|
1777
|
-
id,
|
|
2196
|
+
id,
|
|
2197
|
+
dependency_id: dependencyId,
|
|
2198
|
+
actor: "user",
|
|
2199
|
+
source: "cli",
|
|
2200
|
+
...(reason ? { reason } : {}),
|
|
2201
|
+
...sessionScope,
|
|
1778
2202
|
});
|
|
1779
2203
|
result = artifact;
|
|
1780
2204
|
human = `Dependency added: ${artifactLabel(artifact)} waits for ${dependencyId}`;
|
|
@@ -1783,29 +2207,34 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1783
2207
|
case "undepend": {
|
|
1784
2208
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks undepend requires a task id and prerequisite id");
|
|
1785
2209
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.undepend", {
|
|
1786
|
-
id,
|
|
2210
|
+
id,
|
|
2211
|
+
dependency_id: dependencyId,
|
|
2212
|
+
actor: "user",
|
|
2213
|
+
source: "cli",
|
|
2214
|
+
...(reason ? { reason } : {}),
|
|
2215
|
+
...sessionScope,
|
|
1787
2216
|
});
|
|
1788
2217
|
result = artifact;
|
|
1789
2218
|
human = `Dependency removed: ${artifactLabel(artifact)} no longer waits for ${dependencyId}`;
|
|
1790
2219
|
break;
|
|
1791
2220
|
}
|
|
1792
2221
|
default:
|
|
1793
|
-
throw new Error(
|
|
2222
|
+
throw new Error(
|
|
2223
|
+
"tasks action must be create, list, show, active, focused, focus, pause, unpause, clear-focus, update, graph, plan, context, history, scope, assign-project, complete, start, submit, reject, retry, cancel, cancel-subtree, depend, undepend, contain, uncontain, run-gates, set-checklist, or set-gates",
|
|
2224
|
+
);
|
|
1794
2225
|
}
|
|
1795
2226
|
return json ? JSON.stringify(result) : human;
|
|
1796
2227
|
}
|
|
1797
2228
|
|
|
1798
2229
|
export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
1799
2230
|
const [command, action] = args;
|
|
1800
|
-
if (command === "serve") {
|
|
1801
|
-
|
|
1802
|
-
const client = await connectPapyrusClient();
|
|
1803
|
-
console.log(await runTaskCli(args.slice(1), client));
|
|
2231
|
+
if (command === "serve") {
|
|
2232
|
+
serveMain();
|
|
1804
2233
|
return;
|
|
1805
2234
|
}
|
|
1806
|
-
if (command === "
|
|
2235
|
+
if (command === "tasks") {
|
|
1807
2236
|
const client = await connectPapyrusClient();
|
|
1808
|
-
console.log(await
|
|
2237
|
+
console.log(await runTaskCli(args.slice(1), client));
|
|
1809
2238
|
return;
|
|
1810
2239
|
}
|
|
1811
2240
|
if (command === "playbooks") {
|
|
@@ -1874,12 +2303,23 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1874
2303
|
}
|
|
1875
2304
|
if (command !== "service") usage();
|
|
1876
2305
|
switch (action) {
|
|
1877
|
-
case "install":
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
case "
|
|
1881
|
-
|
|
1882
|
-
|
|
2306
|
+
case "install":
|
|
2307
|
+
installService();
|
|
2308
|
+
break;
|
|
2309
|
+
case "start":
|
|
2310
|
+
systemctl("start", DAEMON_UNIT_NAME);
|
|
2311
|
+
break;
|
|
2312
|
+
case "stop":
|
|
2313
|
+
systemctl("stop", DAEMON_UNIT_NAME);
|
|
2314
|
+
break;
|
|
2315
|
+
case "restart":
|
|
2316
|
+
systemctl("restart", DAEMON_UNIT_NAME);
|
|
2317
|
+
break;
|
|
2318
|
+
case "status":
|
|
2319
|
+
systemctl("status", DAEMON_UNIT_NAME);
|
|
2320
|
+
break;
|
|
2321
|
+
default:
|
|
2322
|
+
usage();
|
|
1883
2323
|
}
|
|
1884
2324
|
}
|
|
1885
2325
|
|