@danypops/papyrus 0.42.0 → 0.42.2
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/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 +3 -3
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +785 -179
- package/src/client.ts +46 -20
- package/src/constants.ts +15 -4
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +119 -98
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +17 -4
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +26 -31
- 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 +241 -110
- 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 +33 -21
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +88 -29
- 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 +131 -68
- package/src/playbook-definition.ts +56 -17
- package/src/playbook-execution.ts +13 -3
- 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 +143 -89
- 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 +167 -72
- package/src/vehicle/artifact-trash-vehicle.ts +25 -13
- package/src/vehicle/artifact-vehicle-shared.ts +28 -7
- package/src/vehicle/docs-vehicle.ts +48 -16
- package/src/vehicle/notes-vehicle.ts +24 -6
- package/src/vehicle/papyrus-vehicle.ts +14 -3
- package/src/vehicle/playbooks-vehicle.ts +87 -18
- 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 +71 -52
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
|
|
|
@@ -199,7 +199,15 @@ function usage(): never {
|
|
|
199
199
|
type TaskCliClient = Pick<PapyrusClient, "call">;
|
|
200
200
|
type MigrationResult = { from: number; to: number; applied: string[] };
|
|
201
201
|
type CliArtifact = { id: string; title: string; status: string; body?: string };
|
|
202
|
-
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
|
+
};
|
|
203
211
|
type CliCompletion = Omit<TaskCompletion, "artifact" | "blocked"> & {
|
|
204
212
|
artifact: CliArtifact;
|
|
205
213
|
blocked: Array<Omit<TaskBlockage, "artifact"> & { artifact: CliArtifact }>;
|
|
@@ -288,7 +296,11 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
288
296
|
const mirrorPath = flag("out");
|
|
289
297
|
if (!mirrorPath) throw new Error("migrate-ids mirror requires --out <path>");
|
|
290
298
|
const sourceDb = openDb(source);
|
|
291
|
-
try {
|
|
299
|
+
try {
|
|
300
|
+
mirrorDatabase(sourceDb, mirrorPath);
|
|
301
|
+
} finally {
|
|
302
|
+
sourceDb.close();
|
|
303
|
+
}
|
|
292
304
|
|
|
293
305
|
const mirror = openDb(mirrorPath);
|
|
294
306
|
let plan: IdMigrationPlan;
|
|
@@ -318,9 +330,14 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
318
330
|
const plan = readIdMap(flag("idmap") ?? `${mirrorPath}.idmap.json`);
|
|
319
331
|
const mirror = openDb(mirrorPath);
|
|
320
332
|
let result: ReturnType<typeof verifyIdMigration>;
|
|
321
|
-
try {
|
|
333
|
+
try {
|
|
334
|
+
result = verifyIdMigration(mirror, plan);
|
|
335
|
+
} finally {
|
|
336
|
+
mirror.close();
|
|
337
|
+
}
|
|
322
338
|
if (json) return JSON.stringify(result);
|
|
323
|
-
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}`;
|
|
324
341
|
return `Validation FAILED:\n${result.problems.map((problem) => ` - ${problem}`).join("\n")}\nDo not promote this mirror.`;
|
|
325
342
|
}
|
|
326
343
|
|
|
@@ -330,14 +347,22 @@ export function runIdMigrationCli(args: string[]): string {
|
|
|
330
347
|
const target = flag("db") ?? dbPath();
|
|
331
348
|
const force = rest.includes("--force");
|
|
332
349
|
if (isDaemonActive() && !force) {
|
|
333
|
-
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
|
+
);
|
|
334
353
|
}
|
|
335
354
|
const plan = readIdMap(flag("idmap") ?? `${mirrorPath}.idmap.json`);
|
|
336
355
|
const mirror = openDb(mirrorPath);
|
|
337
356
|
let result: ReturnType<typeof verifyIdMigration>;
|
|
338
|
-
try {
|
|
357
|
+
try {
|
|
358
|
+
result = verifyIdMigration(mirror, plan);
|
|
359
|
+
} finally {
|
|
360
|
+
mirror.close();
|
|
361
|
+
}
|
|
339
362
|
if (!result.ok) {
|
|
340
|
-
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
|
+
);
|
|
341
366
|
}
|
|
342
367
|
let backupPath: string | undefined;
|
|
343
368
|
if (existsSync(target)) {
|
|
@@ -377,7 +402,10 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
|
|
|
377
402
|
const argument = args[index]!;
|
|
378
403
|
if (argument === "--json") continue;
|
|
379
404
|
const flags: Record<string, string> = {
|
|
380
|
-
"--id": "id",
|
|
405
|
+
"--id": "id",
|
|
406
|
+
"--actor": "actor",
|
|
407
|
+
"--session-id": "session_id",
|
|
408
|
+
"--since": "since",
|
|
381
409
|
"--direction": "direction",
|
|
382
410
|
};
|
|
383
411
|
if (argument in flags) {
|
|
@@ -415,13 +443,20 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
|
|
|
415
443
|
}
|
|
416
444
|
if (action === "unlink") {
|
|
417
445
|
if (positional.length !== 4) throw new Error("graph unlink requires <from> <relation> <to>");
|
|
418
|
-
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
|
+
});
|
|
419
451
|
if (json) return JSON.stringify(result);
|
|
420
452
|
return result.removed ? `Unlinked ${first} --${second}--> ${third}` : `No such relationship: ${first} --${second}--> ${third}`;
|
|
421
453
|
}
|
|
422
454
|
if (action === "tree") {
|
|
423
455
|
if (positional.length !== 2) throw new Error("graph tree requires exactly one artifact id");
|
|
424
|
-
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 });
|
|
425
460
|
if (json) return JSON.stringify(artifact);
|
|
426
461
|
const edges = artifact.edges ?? [];
|
|
427
462
|
return edges.length === 0
|
|
@@ -437,11 +472,13 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
|
|
|
437
472
|
const page = await client.call<Record<string, unknown>, import("./domain/artifact-event.ts").ArtifactEventPage>("graph.history", input);
|
|
438
473
|
if (json) return JSON.stringify(page);
|
|
439
474
|
if (page.events.length === 0) return "No recorded events.";
|
|
440
|
-
return page.events
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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");
|
|
445
482
|
}
|
|
446
483
|
|
|
447
484
|
export async function runDocsCli(args: string[], client: TaskCliClient): Promise<string> {
|
|
@@ -460,15 +497,49 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
460
497
|
for (let index = 0; index < args.length; index++) {
|
|
461
498
|
const argument = args[index]!;
|
|
462
499
|
if (argument === "--json") continue;
|
|
463
|
-
if (argument === "--title") {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (argument === "--
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
+
}
|
|
472
543
|
if (argument === "--limit") {
|
|
473
544
|
const value = args[++index];
|
|
474
545
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -485,14 +556,27 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
485
556
|
case "create": {
|
|
486
557
|
if (id) throw new Error("docs create accepts no positional arguments");
|
|
487
558
|
if (!title) throw new Error("docs create requires --title");
|
|
488
|
-
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
|
+
});
|
|
489
568
|
result = artifact;
|
|
490
569
|
human = `Created document: ${artifactLabel(artifact)}`;
|
|
491
570
|
break;
|
|
492
571
|
}
|
|
493
572
|
case "list": {
|
|
494
573
|
if (id) throw new Error("docs list accepts no positional arguments");
|
|
495
|
-
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
|
+
});
|
|
496
580
|
result = rows;
|
|
497
581
|
human = rows.length === 0 ? "No documents found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
498
582
|
break;
|
|
@@ -529,7 +613,8 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
529
613
|
}
|
|
530
614
|
case "update": {
|
|
531
615
|
if (!id || second) throw new Error("docs update requires exactly one document id");
|
|
532
|
-
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");
|
|
533
618
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.update", { id, title, body, labels });
|
|
534
619
|
result = artifact;
|
|
535
620
|
human = `${artifactLabel(artifact)}`;
|
|
@@ -558,16 +643,54 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
558
643
|
for (let index = 0; index < args.length; index++) {
|
|
559
644
|
const argument = args[index]!;
|
|
560
645
|
if (argument === "--json") continue;
|
|
561
|
-
if (argument === "--title") {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (argument === "--
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
+
}
|
|
571
694
|
if (argument === "--limit") {
|
|
572
695
|
const value = args[++index];
|
|
573
696
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -584,14 +707,28 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
584
707
|
case "create": {
|
|
585
708
|
if (id) throw new Error("rules create accepts no positional arguments");
|
|
586
709
|
if (!title) throw new Error("rules create requires --title");
|
|
587
|
-
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
|
+
});
|
|
588
720
|
result = artifact;
|
|
589
721
|
human = `Created rule: ${artifactLabel(artifact)}`;
|
|
590
722
|
break;
|
|
591
723
|
}
|
|
592
724
|
case "list": {
|
|
593
725
|
if (id) throw new Error("rules list accepts no positional arguments");
|
|
594
|
-
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
|
+
});
|
|
595
732
|
result = rows;
|
|
596
733
|
human = rows.length === 0 ? "No rules found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
597
734
|
break;
|
|
@@ -641,7 +778,8 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
641
778
|
}
|
|
642
779
|
case "update": {
|
|
643
780
|
if (!id || second) throw new Error("rules update requires exactly one rule id");
|
|
644
|
-
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");
|
|
645
783
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.update", { id, title, body, labels });
|
|
646
784
|
result = artifact;
|
|
647
785
|
human = `${artifactLabel(artifact)}`;
|
|
@@ -672,18 +810,61 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
672
810
|
for (let index = 0; index < args.length; index++) {
|
|
673
811
|
const argument = args[index]!;
|
|
674
812
|
if (argument === "--json") continue;
|
|
675
|
-
if (argument === "--title") {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (argument === "--
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (argument === "--
|
|
686
|
-
|
|
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
|
+
}
|
|
687
868
|
if (argument === "--limit") {
|
|
688
869
|
const value = args[++index];
|
|
689
870
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -700,14 +881,29 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
700
881
|
case "create": {
|
|
701
882
|
if (id) throw new Error("playbooks create accepts no positional arguments");
|
|
702
883
|
if (!title) throw new Error("playbooks create requires --title");
|
|
703
|
-
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
|
+
});
|
|
704
895
|
result = artifact;
|
|
705
896
|
human = `Created playbook: ${artifactLabel(artifact)}`;
|
|
706
897
|
break;
|
|
707
898
|
}
|
|
708
899
|
case "list": {
|
|
709
900
|
if (id) throw new Error("playbooks list accepts no positional arguments");
|
|
710
|
-
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
|
+
});
|
|
711
907
|
result = rows;
|
|
712
908
|
human = rows.length === 0 ? "No playbooks found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
713
909
|
break;
|
|
@@ -728,7 +924,10 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
728
924
|
}
|
|
729
925
|
case "invoke": {
|
|
730
926
|
if (!id || second) throw new Error("playbooks invoke requires exactly one playbook id");
|
|
731
|
-
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
|
+
);
|
|
732
931
|
result = invocation;
|
|
733
932
|
human = invocation.missingArguments
|
|
734
933
|
? `Missing required argument(s): ${invocation.missingArguments.join(", ")}.`
|
|
@@ -744,7 +943,7 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
744
943
|
break;
|
|
745
944
|
}
|
|
746
945
|
case "assign-project": {
|
|
747
|
-
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]");
|
|
748
947
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.assign_project", { id, project_root: second });
|
|
749
948
|
result = artifact;
|
|
750
949
|
human = second ? `Assigned ${id} to ${second}` : `Unscoped ${id}`;
|
|
@@ -752,7 +951,8 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
752
951
|
}
|
|
753
952
|
case "update": {
|
|
754
953
|
if (!id || second) throw new Error("playbooks update requires exactly one playbook id");
|
|
755
|
-
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");
|
|
756
956
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.update", { id, title, body, labels });
|
|
757
957
|
result = artifact;
|
|
758
958
|
human = `${artifactLabel(artifact)}`;
|
|
@@ -787,7 +987,9 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
787
987
|
break;
|
|
788
988
|
}
|
|
789
989
|
default:
|
|
790
|
-
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
|
+
);
|
|
791
993
|
}
|
|
792
994
|
return json ? JSON.stringify(result) : human;
|
|
793
995
|
}
|
|
@@ -811,16 +1013,54 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
811
1013
|
for (let index = 0; index < args.length; index++) {
|
|
812
1014
|
const argument = args[index]!;
|
|
813
1015
|
if (argument === "--json") continue;
|
|
814
|
-
if (argument === "--kind") {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
if (argument === "--
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
+
}
|
|
824
1064
|
if (argument === "--limit") {
|
|
825
1065
|
const value = args[++index];
|
|
826
1066
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -850,7 +1090,14 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
850
1090
|
if (id) throw new Error("artifact create accepts no positional arguments");
|
|
851
1091
|
if (!kind && !templateId) throw new Error("artifact create requires --kind (or --template-id)");
|
|
852
1092
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("artifact.create", {
|
|
853
|
-
kind,
|
|
1093
|
+
kind,
|
|
1094
|
+
title,
|
|
1095
|
+
body,
|
|
1096
|
+
status,
|
|
1097
|
+
subtype,
|
|
1098
|
+
labels,
|
|
1099
|
+
extra,
|
|
1100
|
+
template_id: templateId,
|
|
854
1101
|
...(kind === "task" ? { project_root: projectRoot } : {}),
|
|
855
1102
|
});
|
|
856
1103
|
result = artifact;
|
|
@@ -873,14 +1120,20 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
873
1120
|
}
|
|
874
1121
|
case "remove": {
|
|
875
1122
|
if (!id) throw new Error("artifact remove requires exactly one artifact id");
|
|
876
|
-
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 });
|
|
877
1127
|
result = record;
|
|
878
1128
|
human = `Trashed ${record.artifactId}: eligible for purge at ${record.purgeAfter}`;
|
|
879
1129
|
break;
|
|
880
1130
|
}
|
|
881
1131
|
case "remove-subtree": {
|
|
882
1132
|
if (!id) throw new Error("artifact remove-subtree requires exactly one artifact id");
|
|
883
|
-
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
|
+
});
|
|
884
1137
|
result = outcome;
|
|
885
1138
|
human = `Trashed ${outcome.removed.length} artifact(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-trashed` : ""}.`;
|
|
886
1139
|
break;
|
|
@@ -894,16 +1147,25 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
|
|
|
894
1147
|
}
|
|
895
1148
|
case "trash-status": {
|
|
896
1149
|
if (!id) throw new Error("artifact trash-status requires exactly one artifact id");
|
|
897
|
-
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 });
|
|
898
1154
|
result = record;
|
|
899
|
-
human = record
|
|
1155
|
+
human = record
|
|
1156
|
+
? `${record.artifactId}: trashed at ${record.trashedAt}, purge eligible at ${record.purgeAfter}`
|
|
1157
|
+
: `${id} is not trashed`;
|
|
900
1158
|
break;
|
|
901
1159
|
}
|
|
902
1160
|
case "trash-list": {
|
|
903
1161
|
if (id) throw new Error("artifact trash-list accepts no positional arguments");
|
|
904
|
-
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", {});
|
|
905
1166
|
result = rows;
|
|
906
|
-
human =
|
|
1167
|
+
human =
|
|
1168
|
+
rows.length === 0 ? "Trash is empty." : rows.map((row) => `${row.artifactId}: purge eligible at ${row.purgeAfter}`).join("\n");
|
|
907
1169
|
break;
|
|
908
1170
|
}
|
|
909
1171
|
default:
|
|
@@ -931,8 +1193,15 @@ export async function runGraphProjectionCli(args: string[], client: TaskCliClien
|
|
|
931
1193
|
for (let index = 0; index < args.length; index++) {
|
|
932
1194
|
const argument = args[index]!;
|
|
933
1195
|
if (argument === "--json") continue;
|
|
934
|
-
if (argument === "--batch-json") {
|
|
935
|
-
|
|
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
|
+
}
|
|
936
1205
|
positional.push(argument);
|
|
937
1206
|
}
|
|
938
1207
|
const [action] = positional;
|
|
@@ -967,9 +1236,27 @@ export async function runLogCli(args: string[], client: TaskCliClient, projectRo
|
|
|
967
1236
|
for (let index = 0; index < args.length; index++) {
|
|
968
1237
|
const argument = args[index]!;
|
|
969
1238
|
if (argument === "--json") continue;
|
|
970
|
-
if (argument === "--global") {
|
|
971
|
-
|
|
972
|
-
|
|
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
|
+
) {
|
|
973
1260
|
const value = args[++index];
|
|
974
1261
|
if (value === undefined) throw new Error(`${argument} requires a value`);
|
|
975
1262
|
if (argument === "--source") source = value;
|
|
@@ -996,17 +1283,25 @@ export async function runLogCli(args: string[], client: TaskCliClient, projectRo
|
|
|
996
1283
|
if (!message) throw new Error("log append requires --message");
|
|
997
1284
|
if (!operationId) throw new Error("log append requires --operation-id");
|
|
998
1285
|
const result = await client.call("logs.append", {
|
|
999
|
-
source_id: source,
|
|
1286
|
+
source_id: source,
|
|
1287
|
+
...(sourceLabel ? { source_label: sourceLabel } : {}),
|
|
1000
1288
|
...(global ? {} : { project_root: projectRoot }),
|
|
1001
|
-
level,
|
|
1002
|
-
|
|
1289
|
+
level,
|
|
1290
|
+
message,
|
|
1291
|
+
operation_id: operationId,
|
|
1292
|
+
...(fields ? { fields } : {}),
|
|
1293
|
+
...(sessionId ? { session_id: sessionId } : {}),
|
|
1294
|
+
...(occurredAt ? { occurred_at: occurredAt } : {}),
|
|
1003
1295
|
});
|
|
1004
1296
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1005
1297
|
}
|
|
1006
1298
|
if (action === "query") {
|
|
1007
1299
|
if (!source) throw new Error("log query requires --source");
|
|
1008
1300
|
const result = await client.call<Record<string, unknown>, { entries: unknown[]; truncated: boolean }>("logs.query", {
|
|
1009
|
-
source_id: source,
|
|
1301
|
+
source_id: source,
|
|
1302
|
+
...(since ? { since } : {}),
|
|
1303
|
+
...(level ? { level } : {}),
|
|
1304
|
+
...(limit === undefined ? {} : { limit }),
|
|
1010
1305
|
});
|
|
1011
1306
|
if (json) return JSON.stringify(result);
|
|
1012
1307
|
const lines = result.entries.map((entry) => JSON.stringify(entry));
|
|
@@ -1023,20 +1318,31 @@ export async function runSessionIdentityCli(args: string[], client: TaskCliClien
|
|
|
1023
1318
|
for (let index = 0; index < args.length; index++) {
|
|
1024
1319
|
const argument = args[index]!;
|
|
1025
1320
|
if (argument === "--json") continue;
|
|
1026
|
-
if (argument === "--session-id") {
|
|
1027
|
-
|
|
1321
|
+
if (argument === "--session-id") {
|
|
1322
|
+
sessionId = args[++index];
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (argument === "--session-secret") {
|
|
1326
|
+
secret = args[++index];
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1028
1329
|
if (argument.startsWith("--")) throw new Error(`unknown session option ${argument}`);
|
|
1029
1330
|
positional.push(argument);
|
|
1030
1331
|
}
|
|
1031
1332
|
const [action] = positional;
|
|
1032
1333
|
if (action === "register") {
|
|
1033
1334
|
if (!sessionId) throw new Error("session register requires --session-id");
|
|
1034
|
-
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
|
+
});
|
|
1035
1338
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1036
1339
|
}
|
|
1037
1340
|
if (action === "release") {
|
|
1038
1341
|
if (!sessionId) throw new Error("session release requires --session-id");
|
|
1039
|
-
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
|
+
});
|
|
1040
1346
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1041
1347
|
}
|
|
1042
1348
|
throw new Error("session action must be register or release");
|
|
@@ -1064,20 +1370,71 @@ export async function runDiscussCli(args: string[], client: TaskCliClient): Prom
|
|
|
1064
1370
|
for (let index = 0; index < args.length; index++) {
|
|
1065
1371
|
const argument = args[index]!;
|
|
1066
1372
|
if (argument === "--json") continue;
|
|
1067
|
-
if (argument === "--title") {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (argument === "--
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (argument === "--
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
+
}
|
|
1081
1438
|
if (argument === "--after-round") {
|
|
1082
1439
|
const value = args[++index];
|
|
1083
1440
|
if (!value || Number.isNaN(Number(value))) throw new Error("--after-round requires a numeric value");
|
|
@@ -1097,12 +1454,30 @@ export async function runDiscussCli(args: string[], client: TaskCliClient): Prom
|
|
|
1097
1454
|
switch (action) {
|
|
1098
1455
|
case "open": {
|
|
1099
1456
|
if (id) throw new Error("discuss open accepts no positional arguments");
|
|
1100
|
-
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
|
+
});
|
|
1101
1468
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1102
1469
|
}
|
|
1103
1470
|
case "reply": {
|
|
1104
1471
|
if (!id) throw new Error("discuss reply requires exactly one discussion id");
|
|
1105
|
-
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
|
+
});
|
|
1106
1481
|
return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
1107
1482
|
}
|
|
1108
1483
|
case "defer": {
|
|
@@ -1182,34 +1557,77 @@ export async function runNoteCli(args: string[], client: TaskCliClient, projectR
|
|
|
1182
1557
|
let human: string;
|
|
1183
1558
|
if (action === "capture") {
|
|
1184
1559
|
if (!id || target) throw new Error("notes capture requires exactly one request argument");
|
|
1185
|
-
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;
|
|
1186
1567
|
human = `Captured: ${artifactLabel(result)}`;
|
|
1187
1568
|
} else if (action === "list") {
|
|
1188
1569
|
if (id) throw new Error("notes list accepts no positional arguments");
|
|
1189
|
-
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[];
|
|
1190
1576
|
human = result.length > 0 ? result.map((note) => `[${note.status}] ${artifactLabel(note)}`).join("\n") : "No open notes.";
|
|
1191
1577
|
} else if (action === "show") {
|
|
1192
1578
|
if (!id || target) throw new Error("notes show requires exactly one note id");
|
|
1193
|
-
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;
|
|
1194
1580
|
human = `${artifactLabel(result)}\n\n${result.body ?? ""}`.trimEnd();
|
|
1195
1581
|
} else if (action === "history") {
|
|
1196
1582
|
if (!id || target) throw new Error("notes history requires exactly one note id");
|
|
1197
|
-
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
|
+
});
|
|
1198
1589
|
result = page;
|
|
1199
|
-
human =
|
|
1200
|
-
|
|
1201
|
-
|
|
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");
|
|
1202
1600
|
} else if (action === "consume") {
|
|
1203
1601
|
if (!id || target) throw new Error("notes consume requires exactly one note id");
|
|
1204
|
-
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;
|
|
1205
1609
|
human = `Consumed: ${artifactLabel(result)}`;
|
|
1206
1610
|
} else if (action === "promote") {
|
|
1207
1611
|
if (!id || !target || positional.length !== 3) throw new Error("notes promote requires a note id and target artifact id");
|
|
1208
|
-
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;
|
|
1209
1620
|
human = `Promoted: ${artifactLabel(result)} → ${target}`;
|
|
1210
1621
|
} else if (action === "archive") {
|
|
1211
1622
|
if (!id || !target || positional.length !== 3) throw new Error("notes archive requires a note id and disposition");
|
|
1212
|
-
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;
|
|
1213
1631
|
human = `Archived: ${artifactLabel(result)} · ${target}`;
|
|
1214
1632
|
} else {
|
|
1215
1633
|
throw new Error("notes action must be capture, list, show, history, consume, promote, or archive");
|
|
@@ -1257,12 +1675,22 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1257
1675
|
if (!sessionSecret) throw new Error("--session-secret requires a value");
|
|
1258
1676
|
continue;
|
|
1259
1677
|
}
|
|
1260
|
-
if (
|
|
1678
|
+
if (
|
|
1679
|
+
argument === "--title" ||
|
|
1680
|
+
argument === "--body" ||
|
|
1681
|
+
argument === "--labels-json" ||
|
|
1682
|
+
argument === "--status" ||
|
|
1683
|
+
argument === "--reason"
|
|
1684
|
+
) {
|
|
1261
1685
|
const value = args[++index];
|
|
1262
1686
|
if (value === undefined) throw new Error(`${argument} requires a value`);
|
|
1263
|
-
if (argument === "--title") {
|
|
1264
|
-
|
|
1265
|
-
|
|
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;
|
|
1266
1694
|
else if (argument === "--status") {
|
|
1267
1695
|
status = value;
|
|
1268
1696
|
if (value === "todo") updateInput.status = value;
|
|
@@ -1272,7 +1700,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1272
1700
|
}
|
|
1273
1701
|
continue;
|
|
1274
1702
|
}
|
|
1275
|
-
if (argument === "--extra-json") {
|
|
1703
|
+
if (argument === "--extra-json") {
|
|
1704
|
+
extra = parseJsonObjectFlag(args[++index]!, "--extra-json");
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1276
1707
|
if (argument === "--gates-json") {
|
|
1277
1708
|
const value = args[++index];
|
|
1278
1709
|
if (!value) throw new Error("--gates-json requires a value");
|
|
@@ -1281,11 +1712,29 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1281
1712
|
gates = parsed;
|
|
1282
1713
|
continue;
|
|
1283
1714
|
}
|
|
1284
|
-
if (argument === "--checklist-json") {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
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
|
+
}
|
|
1289
1738
|
if (argument === "--limit") {
|
|
1290
1739
|
const value = args[++index];
|
|
1291
1740
|
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
@@ -1298,10 +1747,26 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1298
1747
|
listScope = value;
|
|
1299
1748
|
continue;
|
|
1300
1749
|
}
|
|
1301
|
-
if (argument === "--root-task-id") {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
+
}
|
|
1305
1770
|
if (argument === "--ttl-ms") {
|
|
1306
1771
|
const value = args[++index];
|
|
1307
1772
|
if (!value || Number.isNaN(Number(value))) throw new Error("--ttl-ms requires a numeric value");
|
|
@@ -1314,13 +1779,17 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1314
1779
|
cursor = Number(value);
|
|
1315
1780
|
continue;
|
|
1316
1781
|
}
|
|
1317
|
-
if (argument === "--event-types-json") {
|
|
1782
|
+
if (argument === "--event-types-json") {
|
|
1783
|
+
eventTypes = parseJsonStringArrayFlag(args[++index]!, "--event-types-json");
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1318
1786
|
if (argument.startsWith("--")) throw new Error(`unknown tasks option ${argument}`);
|
|
1319
1787
|
positional.push(argument);
|
|
1320
1788
|
}
|
|
1321
1789
|
const [action, id, dependencyId] = positional;
|
|
1322
1790
|
const reasonSupportedActions = new Set(["update", "depend", "undepend", "contain", "uncontain"]);
|
|
1323
|
-
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");
|
|
1324
1793
|
const sessionScope = sessionId ? { session_id: sessionId } : {};
|
|
1325
1794
|
// Only meaningful alongside a registered session_id (see session.register); required by the
|
|
1326
1795
|
// daemon only for the specific Focus-mutating operations below (focus/pause/unpause/clear_focus)
|
|
@@ -1331,14 +1800,20 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1331
1800
|
switch (action) {
|
|
1332
1801
|
case "active": {
|
|
1333
1802
|
if (id) throw new Error("tasks active accepts no positional arguments");
|
|
1334
|
-
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
|
+
});
|
|
1335
1807
|
result = active;
|
|
1336
1808
|
human = active ? `Active: ${artifactLabel(active)}` : "No active task.";
|
|
1337
1809
|
break;
|
|
1338
1810
|
}
|
|
1339
1811
|
case "focused": {
|
|
1340
1812
|
if (id) throw new Error("tasks focused accepts no positional arguments");
|
|
1341
|
-
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 });
|
|
1342
1817
|
result = focus;
|
|
1343
1818
|
human = focus ? `Focused (${focus.status}): ${artifactLabel(focus.artifact)}` : "No focused task.";
|
|
1344
1819
|
break;
|
|
@@ -1347,14 +1822,24 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1347
1822
|
case "unpause": {
|
|
1348
1823
|
if (id) throw new Error(`tasks ${action} accepts no positional arguments`);
|
|
1349
1824
|
const operation = action === "pause" ? "tasks.pause" : "tasks.unpause";
|
|
1350
|
-
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
|
+
});
|
|
1351
1831
|
result = focus;
|
|
1352
1832
|
human = `Focused (${focus.status}): ${artifactLabel(focus.artifact)}`;
|
|
1353
1833
|
break;
|
|
1354
1834
|
}
|
|
1355
1835
|
case "clear-focus": {
|
|
1356
1836
|
if (id) throw new Error("tasks clear-focus accepts no positional arguments");
|
|
1357
|
-
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
|
+
});
|
|
1358
1843
|
result = cleared;
|
|
1359
1844
|
human = cleared.cleared ? "Task focus cleared." : "No focused task.";
|
|
1360
1845
|
break;
|
|
@@ -1406,20 +1891,36 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1406
1891
|
}
|
|
1407
1892
|
case "event-feed": {
|
|
1408
1893
|
if (id) throw new Error("tasks event-feed accepts no positional arguments");
|
|
1409
|
-
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
|
+
});
|
|
1410
1899
|
result = page;
|
|
1411
|
-
human =
|
|
1412
|
-
|
|
1413
|
-
|
|
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");
|
|
1414
1904
|
break;
|
|
1415
1905
|
}
|
|
1416
1906
|
case "create": {
|
|
1417
1907
|
if (id) throw new Error("tasks create accepts no positional arguments");
|
|
1418
1908
|
if (!title) throw new Error("tasks create requires --title");
|
|
1419
1909
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.create", {
|
|
1420
|
-
title,
|
|
1421
|
-
|
|
1422
|
-
|
|
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,
|
|
1423
1924
|
});
|
|
1424
1925
|
result = artifact;
|
|
1425
1926
|
human = `Created task: ${artifactLabel(artifact)}`;
|
|
@@ -1428,7 +1929,14 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1428
1929
|
case "list": {
|
|
1429
1930
|
if (id) throw new Error("tasks list accepts no positional arguments");
|
|
1430
1931
|
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("tasks.list", {
|
|
1431
|
-
status,
|
|
1932
|
+
status,
|
|
1933
|
+
text,
|
|
1934
|
+
limit,
|
|
1935
|
+
labels,
|
|
1936
|
+
project_root: projectRoot,
|
|
1937
|
+
scope: listScope,
|
|
1938
|
+
root_task_id: rootTaskId,
|
|
1939
|
+
...sessionScope,
|
|
1432
1940
|
});
|
|
1433
1941
|
result = rows;
|
|
1434
1942
|
human = rows.length === 0 ? "No tasks found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
@@ -1445,9 +1953,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1445
1953
|
if (!id || dependencyId) throw new Error("tasks run-gates requires exactly one task id");
|
|
1446
1954
|
const results = await client.call<Record<string, unknown>, GateResult[]>("tasks.run_gates", { id, actor: "user", source: "cli" });
|
|
1447
1955
|
result = results;
|
|
1448
|
-
human =
|
|
1449
|
-
|
|
1450
|
-
|
|
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");
|
|
1451
1960
|
break;
|
|
1452
1961
|
}
|
|
1453
1962
|
case "set-checklist": {
|
|
@@ -1469,7 +1978,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1469
1978
|
case "context": {
|
|
1470
1979
|
if (id) throw new Error("tasks context accepts no positional arguments");
|
|
1471
1980
|
const summary = await client.call<Record<string, unknown>, string | null>("tasks.context", {
|
|
1472
|
-
project_root: projectRoot,
|
|
1981
|
+
project_root: projectRoot,
|
|
1982
|
+
scope: listScope,
|
|
1983
|
+
root_task_id: rootTaskId,
|
|
1984
|
+
...sessionScope,
|
|
1473
1985
|
});
|
|
1474
1986
|
result = summary;
|
|
1475
1987
|
human = summary ?? "No open tasks.";
|
|
@@ -1478,7 +1990,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1478
1990
|
case "contain": {
|
|
1479
1991
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks contain requires a parent id and child id");
|
|
1480
1992
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.contain", {
|
|
1481
|
-
parent_id: id,
|
|
1993
|
+
parent_id: id,
|
|
1994
|
+
child_id: dependencyId,
|
|
1995
|
+
actor: "user",
|
|
1996
|
+
source: "cli",
|
|
1997
|
+
...(reason ? { reason } : {}),
|
|
1998
|
+
...sessionScope,
|
|
1482
1999
|
});
|
|
1483
2000
|
result = artifact;
|
|
1484
2001
|
human = `Contained: ${dependencyId} → ${artifactLabel(artifact)}`;
|
|
@@ -1487,7 +2004,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1487
2004
|
case "uncontain": {
|
|
1488
2005
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks uncontain requires a parent id and child id");
|
|
1489
2006
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.uncontain", {
|
|
1490
|
-
parent_id: id,
|
|
2007
|
+
parent_id: id,
|
|
2008
|
+
child_id: dependencyId,
|
|
2009
|
+
actor: "user",
|
|
2010
|
+
source: "cli",
|
|
2011
|
+
...(reason ? { reason } : {}),
|
|
2012
|
+
...sessionScope,
|
|
1491
2013
|
});
|
|
1492
2014
|
result = artifact;
|
|
1493
2015
|
human = `Removed ${dependencyId} from ${artifactLabel(artifact)}`;
|
|
@@ -1495,12 +2017,17 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1495
2017
|
}
|
|
1496
2018
|
case "update": {
|
|
1497
2019
|
if (!id || dependencyId) throw new Error("tasks update requires exactly one task id");
|
|
1498
|
-
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");
|
|
1499
2022
|
if (Object.keys(updateInput).length === 0) throw new Error("tasks update requires --title, --body, --labels-json, or --status todo");
|
|
1500
2023
|
if (updateInput.status !== undefined && !reason?.trim()) throw new Error("tasks update --status requires --reason");
|
|
1501
2024
|
if (reason !== undefined && updateInput.status === undefined) throw new Error("tasks update --reason requires --status todo");
|
|
1502
2025
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.update", {
|
|
1503
|
-
id,
|
|
2026
|
+
id,
|
|
2027
|
+
...updateInput,
|
|
2028
|
+
...(reason ? { reason } : {}),
|
|
2029
|
+
actor: "user",
|
|
2030
|
+
source: "cli",
|
|
1504
2031
|
});
|
|
1505
2032
|
result = artifact;
|
|
1506
2033
|
human = `Updated: ${artifactLabel(artifact)}`;
|
|
@@ -1508,16 +2035,28 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1508
2035
|
}
|
|
1509
2036
|
case "history": {
|
|
1510
2037
|
if (!id || dependencyId) throw new Error("tasks history requires exactly one task id");
|
|
1511
|
-
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
|
+
});
|
|
1512
2042
|
result = page;
|
|
1513
|
-
human =
|
|
1514
|
-
|
|
1515
|
-
|
|
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");
|
|
1516
2053
|
break;
|
|
1517
2054
|
}
|
|
1518
2055
|
case "scope": {
|
|
1519
2056
|
if (!id) {
|
|
1520
|
-
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
|
+
});
|
|
1521
2060
|
result = selection;
|
|
1522
2061
|
human = `Task scope: ${selection.label}`;
|
|
1523
2062
|
break;
|
|
@@ -1548,17 +2087,33 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1548
2087
|
}
|
|
1549
2088
|
case "focus": {
|
|
1550
2089
|
if (!id || dependencyId) throw new Error("tasks focus requires exactly one task id");
|
|
1551
|
-
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
|
+
});
|
|
1552
2097
|
result = active;
|
|
1553
2098
|
human = `Active: ${artifactLabel(active)}`;
|
|
1554
2099
|
break;
|
|
1555
2100
|
}
|
|
1556
2101
|
case "graph": {
|
|
1557
2102
|
if (id) throw new Error("tasks graph accepts no positional arguments");
|
|
1558
|
-
const graph = await client.call<
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
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
|
+
});
|
|
1562
2117
|
result = graph;
|
|
1563
2118
|
const dependencies = graph.nodes.reduce((count, node) => count + node.dependencyIds.length, 0);
|
|
1564
2119
|
const children = graph.nodes.reduce((count, node) => count + node.childIds.length, 0);
|
|
@@ -1567,19 +2122,29 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1567
2122
|
}
|
|
1568
2123
|
case "plan": {
|
|
1569
2124
|
if (id) throw new Error("tasks plan accepts no positional arguments");
|
|
1570
|
-
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
|
+
});
|
|
1571
2129
|
result = plan;
|
|
1572
2130
|
human = planText(plan);
|
|
1573
2131
|
break;
|
|
1574
2132
|
}
|
|
1575
2133
|
case "complete": {
|
|
1576
2134
|
if (!id || dependencyId) throw new Error("tasks complete requires exactly one task id");
|
|
1577
|
-
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
|
+
});
|
|
1578
2141
|
result = completion;
|
|
1579
2142
|
const lines = [`${completion.completed ? "Completed" : "Rejected"}: ${artifactLabel(completion.artifact)}`];
|
|
1580
2143
|
if (completion.focused) lines.push(`Active: ${artifactLabel(completion.focused)}`);
|
|
1581
2144
|
if (completion.blocked.length > 0) {
|
|
1582
|
-
lines.push(
|
|
2145
|
+
lines.push(
|
|
2146
|
+
`Blocked: ${completion.blocked.map((entry) => `${artifactLabel(entry.artifact)} waits for ${entry.dependencyIds.join(", ")}`).join("; ")}`,
|
|
2147
|
+
);
|
|
1583
2148
|
}
|
|
1584
2149
|
for (const gate of completion.gates) lines.push(`${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`);
|
|
1585
2150
|
human = lines.join("\n");
|
|
@@ -1587,7 +2152,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1587
2152
|
}
|
|
1588
2153
|
case "start": {
|
|
1589
2154
|
if (!id || dependencyId) throw new Error("tasks start requires exactly one task id");
|
|
1590
|
-
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
|
+
});
|
|
1591
2161
|
result = artifact;
|
|
1592
2162
|
human = `Started: ${artifactLabel(artifact)}`;
|
|
1593
2163
|
break;
|
|
@@ -1598,14 +2168,24 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1598
2168
|
case "cancel": {
|
|
1599
2169
|
if (!id || dependencyId) throw new Error(`tasks ${action} requires exactly one task id`);
|
|
1600
2170
|
const operation = `tasks.${action}` as "tasks.submit" | "tasks.reject" | "tasks.retry" | "tasks.cancel";
|
|
1601
|
-
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
|
+
});
|
|
1602
2177
|
result = artifact;
|
|
1603
2178
|
human = `${action[0]!.toUpperCase()}${action.slice(1)}: ${artifactLabel(artifact)}`;
|
|
1604
2179
|
break;
|
|
1605
2180
|
}
|
|
1606
2181
|
case "cancel-subtree": {
|
|
1607
2182
|
if (!id || dependencyId) throw new Error("tasks cancel-subtree requires exactly one task id");
|
|
1608
|
-
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
|
+
});
|
|
1609
2189
|
result = outcome;
|
|
1610
2190
|
human = `Canceled ${outcome.canceled.length} task(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-terminal` : ""}.`;
|
|
1611
2191
|
break;
|
|
@@ -1613,7 +2193,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1613
2193
|
case "depend": {
|
|
1614
2194
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks depend requires a task id and prerequisite id");
|
|
1615
2195
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.depend", {
|
|
1616
|
-
id,
|
|
2196
|
+
id,
|
|
2197
|
+
dependency_id: dependencyId,
|
|
2198
|
+
actor: "user",
|
|
2199
|
+
source: "cli",
|
|
2200
|
+
...(reason ? { reason } : {}),
|
|
2201
|
+
...sessionScope,
|
|
1617
2202
|
});
|
|
1618
2203
|
result = artifact;
|
|
1619
2204
|
human = `Dependency added: ${artifactLabel(artifact)} waits for ${dependencyId}`;
|
|
@@ -1622,21 +2207,31 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
|
|
|
1622
2207
|
case "undepend": {
|
|
1623
2208
|
if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks undepend requires a task id and prerequisite id");
|
|
1624
2209
|
const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.undepend", {
|
|
1625
|
-
id,
|
|
2210
|
+
id,
|
|
2211
|
+
dependency_id: dependencyId,
|
|
2212
|
+
actor: "user",
|
|
2213
|
+
source: "cli",
|
|
2214
|
+
...(reason ? { reason } : {}),
|
|
2215
|
+
...sessionScope,
|
|
1626
2216
|
});
|
|
1627
2217
|
result = artifact;
|
|
1628
2218
|
human = `Dependency removed: ${artifactLabel(artifact)} no longer waits for ${dependencyId}`;
|
|
1629
2219
|
break;
|
|
1630
2220
|
}
|
|
1631
2221
|
default:
|
|
1632
|
-
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
|
+
);
|
|
1633
2225
|
}
|
|
1634
2226
|
return json ? JSON.stringify(result) : human;
|
|
1635
2227
|
}
|
|
1636
2228
|
|
|
1637
2229
|
export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
1638
2230
|
const [command, action] = args;
|
|
1639
|
-
if (command === "serve") {
|
|
2231
|
+
if (command === "serve") {
|
|
2232
|
+
serveMain();
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
1640
2235
|
if (command === "tasks") {
|
|
1641
2236
|
const client = await connectPapyrusClient();
|
|
1642
2237
|
console.log(await runTaskCli(args.slice(1), client));
|
|
@@ -1708,12 +2303,23 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1708
2303
|
}
|
|
1709
2304
|
if (command !== "service") usage();
|
|
1710
2305
|
switch (action) {
|
|
1711
|
-
case "install":
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
case "
|
|
1715
|
-
|
|
1716
|
-
|
|
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();
|
|
1717
2323
|
}
|
|
1718
2324
|
}
|
|
1719
2325
|
|