@danypops/papyrus 0.11.2 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.ts +100 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.11.2",
3
+ "version": "0.11.3",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
package/src/cli.ts CHANGED
@@ -82,7 +82,10 @@ const USAGE = `Usage:
82
82
  papyrus tasks reject <id> [--json]
83
83
  papyrus tasks retry <id> [--json]
84
84
  papyrus tasks cancel <id> [--json]
85
- papyrus tasks depend <id> <prerequisite-id> [--json]`;
85
+ papyrus tasks depend <id> <prerequisite-id> [--json]
86
+ papyrus tasks create --title <title> [--body <body>] [--status <status>] [--labels-json <json>] [--extra-json <json>] [--gates-json <json>] [--checklist-json <json>] [--template-id <id>] [--parent-id <id>] [--depends-on-json <json>] [--json]
87
+ papyrus tasks list [--status <status>] [--text <query>] [--limit <count>] [--json]
88
+ papyrus tasks show <id> [--json]`;
86
89
 
87
90
  function usage(): never {
88
91
  console.error(USAGE);
@@ -98,6 +101,20 @@ type CliCompletion = Omit<TaskCompletion, "artifact" | "blocked"> & {
98
101
  gates: GateResult[];
99
102
  };
100
103
 
104
+ function parseJsonObjectFlag(value: string | undefined, flag: string): Record<string, unknown> {
105
+ if (value === undefined) throw new Error(`${flag} requires a value`);
106
+ const parsed = JSON.parse(value) as unknown;
107
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${flag} must be a JSON object`);
108
+ return parsed as Record<string, unknown>;
109
+ }
110
+
111
+ function parseJsonStringArrayFlag(value: string | undefined, flag: string): string[] {
112
+ if (value === undefined) throw new Error(`${flag} requires a value`);
113
+ const parsed = JSON.parse(value) as unknown;
114
+ if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) throw new Error(`${flag} must be a JSON string array`);
115
+ return parsed as string[];
116
+ }
117
+
101
118
  function artifactLabel(artifact: CliArtifact): string {
102
119
  return `${artifact.id} ${artifact.title}`;
103
120
  }
@@ -237,24 +254,48 @@ export async function runNoteCli(args: string[], client: TaskCliClient, projectR
237
254
  export async function runTaskCli(args: string[], client: TaskCliClient, projectRoot: string = process.cwd()): Promise<string> {
238
255
  const json = args.includes("--json");
239
256
  const positional: string[] = [];
240
- const updateInput: { title?: string; body?: string; labels?: string[]; status?: "todo" } = {};
241
257
  let reason: string | undefined;
258
+ let title: string | undefined;
259
+ let body: string | undefined;
260
+ let labels: string[] | undefined;
261
+ // Deliberately unrestricted here -- tasks update alone restricts this to "todo" (accidental-
262
+ // creation recovery only), enforced in that case body, not in parsing shared by every action.
263
+ let status: string | undefined;
264
+ let extra: Record<string, unknown> | undefined;
265
+ let gates: unknown[] | undefined;
266
+ let checklist: Record<string, unknown> | undefined;
267
+ let templateId: string | undefined;
268
+ let parentId: string | undefined;
269
+ let dependsOn: string[] | undefined;
270
+ let text: string | undefined;
271
+ let limit: number | undefined;
242
272
  for (let index = 0; index < args.length; index++) {
243
273
  const argument = args[index]!;
244
274
  if (argument === "--json") continue;
245
- if (argument === "--title" || argument === "--body" || argument === "--labels-json" || argument === "--status" || argument === "--reason") {
275
+ if (argument === "--title" || argument === "--body" || argument === "--labels-json" || argument === "--status" || argument === "--reason"
276
+ || argument === "--extra-json" || argument === "--gates-json" || argument === "--checklist-json" || argument === "--template-id"
277
+ || argument === "--parent-id" || argument === "--depends-on-json" || argument === "--text" || argument === "--limit") {
246
278
  const value = args[++index];
247
279
  if (value === undefined) throw new Error(`${argument} requires a value`);
248
- if (argument === "--title") updateInput.title = value;
249
- else if (argument === "--body") updateInput.body = value;
280
+ if (argument === "--title") title = value;
281
+ else if (argument === "--body") body = value;
250
282
  else if (argument === "--reason") reason = value;
251
- else if (argument === "--status") {
252
- if (value !== "todo") throw new Error("--status only supports todo for accidental creation recovery");
253
- updateInput.status = value;
254
- } else {
283
+ else if (argument === "--status") status = value;
284
+ else if (argument === "--extra-json") extra = parseJsonObjectFlag(value, "--extra-json");
285
+ else if (argument === "--checklist-json") checklist = parseJsonObjectFlag(value, "--checklist-json");
286
+ else if (argument === "--template-id") templateId = value;
287
+ else if (argument === "--parent-id") parentId = value;
288
+ else if (argument === "--depends-on-json") dependsOn = parseJsonStringArrayFlag(value, "--depends-on-json");
289
+ else if (argument === "--text") text = value;
290
+ else if (argument === "--limit") {
291
+ if (Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
292
+ limit = Number(value);
293
+ } else if (argument === "--gates-json") {
255
294
  const parsed = JSON.parse(value) as unknown;
256
- if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) throw new Error("--labels-json requires a JSON string array");
257
- updateInput.labels = parsed as string[];
295
+ if (!Array.isArray(parsed)) throw new Error("--gates-json must be a JSON array");
296
+ gates = parsed;
297
+ } else {
298
+ labels = parseJsonStringArrayFlag(value, "--labels-json");
258
299
  }
259
300
  continue;
260
301
  }
@@ -297,6 +338,14 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
297
338
  }
298
339
  case "update": {
299
340
  if (!id || dependencyId) throw new Error("tasks update requires exactly one task id");
341
+ const updateInput: { title?: string; body?: string; labels?: string[]; status?: "todo" } = {};
342
+ if (title !== undefined) updateInput.title = title;
343
+ if (body !== undefined) updateInput.body = body;
344
+ if (labels !== undefined) updateInput.labels = labels;
345
+ if (status !== undefined) {
346
+ if (status !== "todo") throw new Error("--status only supports todo for accidental creation recovery");
347
+ updateInput.status = status;
348
+ }
300
349
  if (Object.keys(updateInput).length === 0) throw new Error("tasks update requires --title, --body, --labels-json, or --status todo");
301
350
  if (updateInput.status !== undefined && !reason?.trim()) throw new Error("tasks update --status requires --reason");
302
351
  if (reason !== undefined && updateInput.status === undefined) throw new Error("tasks update --reason requires --status todo");
@@ -307,6 +356,45 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
307
356
  human = `Updated: ${artifactLabel(artifact)}`;
308
357
  break;
309
358
  }
359
+ case "create": {
360
+ if (id) throw new Error("tasks create accepts no positional arguments");
361
+ if (!title) throw new Error("tasks create requires --title");
362
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.create", {
363
+ title,
364
+ ...(body !== undefined ? { body } : {}),
365
+ ...(status !== undefined ? { status } : {}),
366
+ ...(labels !== undefined ? { labels } : {}),
367
+ ...(extra !== undefined ? { extra } : {}),
368
+ ...(gates !== undefined ? { gates } : {}),
369
+ ...(checklist !== undefined ? { checklist } : {}),
370
+ ...(templateId !== undefined ? { template_id: templateId } : {}),
371
+ ...(parentId !== undefined ? { parent_id: parentId } : {}),
372
+ ...(dependsOn !== undefined ? { depends_on: dependsOn } : {}),
373
+ project_root: projectRoot, actor: "user", source: "cli",
374
+ });
375
+ result = artifact;
376
+ human = `Created task: ${artifactLabel(artifact)}`;
377
+ break;
378
+ }
379
+ case "list": {
380
+ if (id) throw new Error("tasks list accepts no positional arguments");
381
+ const rows = await client.call<Record<string, unknown>, CliArtifact[]>("tasks.list", {
382
+ ...(status !== undefined ? { status } : {}),
383
+ ...(text !== undefined ? { text } : {}),
384
+ ...(limit !== undefined ? { limit } : {}),
385
+ project_root: projectRoot,
386
+ });
387
+ result = rows;
388
+ human = rows.length === 0 ? "No tasks found." : rows.map((row) => artifactLabel(row)).join("\n");
389
+ break;
390
+ }
391
+ case "show": {
392
+ if (!id || dependencyId) throw new Error("tasks show requires exactly one task id");
393
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.show", { id });
394
+ result = artifact;
395
+ human = `${artifactLabel(artifact)}\n\n${artifact.body ?? ""}`;
396
+ break;
397
+ }
310
398
  case "history": {
311
399
  if (!id || dependencyId) throw new Error("tasks history requires exactly one task id");
312
400
  const page = await client.call<{ id: string; direction: "desc" }, import("./domain/task-event.ts").TaskHistoryPage>("tasks.history", { id, direction: "desc" });
@@ -415,7 +503,7 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
415
503
  break;
416
504
  }
417
505
  default:
418
- throw new Error("tasks action must be active, focused, focus, pause, unpause, clear-focus, update, graph, plan, history, scope, assign-project, complete, start, submit, reject, retry, cancel, or depend");
506
+ throw new Error("tasks action must be create, list, show, active, focused, focus, pause, unpause, clear-focus, update, graph, plan, history, scope, assign-project, complete, start, submit, reject, retry, cancel, or depend");
419
507
  }
420
508
  return json ? JSON.stringify(result) : human;
421
509
  }