@piewf/cli 5.8.0 → 5.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.d.ts CHANGED
@@ -15,6 +15,15 @@ export interface CliOptions extends DoctorOptions {
15
15
  }
16
16
  export declare function formatWorkflowCliHelp(fn: WorkflowCatalogFunction, command?: string): string;
17
17
  export declare function parseWorkflowCliArgs(schema: JsonSchema, rawArgs: readonly string[]): Record<string, JsonValue>;
18
+ type ScriptWorkflowCliArgs = {
19
+ help: true;
20
+ } | {
21
+ help: false;
22
+ scriptPath: string;
23
+ name: string;
24
+ args: JsonValue;
25
+ };
26
+ export declare function parseScriptWorkflowCliArgs(rawArgs: readonly string[]): ScriptWorkflowCliArgs;
18
27
  export declare function parseDoctorArgs(rawArgs: readonly string[]): {
19
28
  role?: string;
20
29
  prompt?: string;
@@ -22,3 +31,4 @@ export declare function parseDoctorArgs(rawArgs: readonly string[]): {
22
31
  };
23
32
  export declare function parseDoctorCleanupArgs(rawArgs: readonly string[]): Required<Pick<DoctorCleanupOptions, "olderThanDays" | "yes">>;
24
33
  export declare function runCli(args: readonly string[], options?: CliOptions, write?: (text: string) => void): Promise<number>;
34
+ export {};
package/dist/src/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { chmodSync, linkSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
- import { dirname, join } from "node:path";
5
+ import { basename, dirname, extname, join } from "node:path";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { ProjectTrustStore, SessionManager, SettingsManager, createAgentSessionFromServices, createAgentSessionServices, getAgentDir, hasTrustRequiringProjectResources } from "@earendil-works/pi-coding-agent";
8
8
  import { Value } from "typebox/value";
@@ -11,7 +11,8 @@ import { doctorCleanup, doctorCleanupExitCode, formatDoctorCleanupReport } from
11
11
  import workflowExtension, { errorText, formatWorkflowProgress, isNodeError, jsonValue, loadAgentDefinitions, registeredWorkflowFunctions, truncateWorkflowProgress, workflowCatalog, workflowSettingsPath } from "pi-extensible-workflows";
12
12
  import { portableEngineVersion, portablePiVersion, writePortableWorkflowBundle } from "./bundles.js";
13
13
  import { runSessionInspector, transcriptFileLines } from "./session-inspector.js";
14
- import { isPersistedRun } from "pi-extensible-workflows/persistence";
14
+ import { isPersistedRun, listPersistedSessionIds, listRunIds } from "pi-extensible-workflows/persistence";
15
+ import { shareTrajectoryRun } from "pi-extensible-workflows/trajectory";
15
16
  function object(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
16
17
  function has(value, key) { return Object.prototype.hasOwnProperty.call(value, key); }
17
18
  function requiredArg(args, index) {
@@ -213,7 +214,53 @@ function launcherHelpLines() {
213
214
  " --".padEnd(28) + "End launcher option parsing; pass later tokens to workflow input",
214
215
  ];
215
216
  }
216
- function workflowUsage() { return [`Usage: piewf run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force]`, "", "Launcher options:", ...launcherHelpLines()].join("\n") + "\n"; }
217
+ function workflowUsage() { return [`Usage: piewf run <workflow-name> [workflow arguments] | run --script <path> [--name <workflow-name>] [--input <json>] | export <workflow-name> [--name <command>] [--output <path>] [--force]`, "", "Launcher options:", ...launcherHelpLines()].join("\n") + "\n"; }
218
+ function scriptWorkflowUsage() { return [`Usage: piewf run --script <path> [--name <workflow-name>] [--input <json>]`, "", "Options:", ...launcherHelpLines().slice(0, 2), " -h, --help".padEnd(28) + "Show this help"].join("\n") + "\n"; }
219
+ function scriptWorkflowName(scriptPath) {
220
+ const filename = basename(scriptPath);
221
+ const extension = extname(filename);
222
+ return extension ? filename.slice(0, -extension.length) : filename;
223
+ }
224
+ export function parseScriptWorkflowCliArgs(rawArgs) {
225
+ let scriptPath;
226
+ let name;
227
+ let input;
228
+ for (let index = 0; index < rawArgs.length; index += 1) {
229
+ const token = requiredArg(rawArgs, index);
230
+ if (token === "--help" || token === "-h")
231
+ return { help: true };
232
+ const equals = token.indexOf("=");
233
+ const option = equals >= 0 ? token.slice(0, equals) : token;
234
+ if (option === "--script" || option === "--name" || option === "--input") {
235
+ const value = equals >= 0 ? token.slice(equals + 1) : rawArgs[++index];
236
+ if (!value?.trim())
237
+ throw new Error(`Missing value for ${option}`);
238
+ if (option === "--script") {
239
+ if (scriptPath !== undefined)
240
+ throw new Error("--script may only be provided once");
241
+ scriptPath = value;
242
+ }
243
+ else if (option === "--name") {
244
+ if (name !== undefined)
245
+ throw new Error("--name may only be provided once");
246
+ name = value;
247
+ }
248
+ else {
249
+ if (input !== undefined)
250
+ throw new Error("--input may only be provided once");
251
+ input = parseJsonInput(value);
252
+ }
253
+ continue;
254
+ }
255
+ throw new Error(`Unknown option: ${token}`);
256
+ }
257
+ if (scriptPath === undefined)
258
+ throw new Error("Missing required option: --script");
259
+ const workflowName = name === undefined ? scriptWorkflowName(scriptPath) : name.trim();
260
+ if (!workflowName)
261
+ throw new Error("Workflow name must be non-empty");
262
+ return { help: false, scriptPath, name: workflowName, args: input ?? null };
263
+ }
217
264
  function exportUsage() { return [`Usage: piewf export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]`, "", "Launcher options:", ...launcherHelpLines()].join("\n") + "\n"; }
218
265
  function bundleUsage() { return [`Usage: piewf bundle <workflow-name> [--name <command>] [--output <directory>] [--force]`, "", "The bundle contains a launcher, manifest, workflow payload, and external-runtime setup instructions.", "Repeat --role, --alias, --tool, --command, or --environment to declare recipient requirements.", "Use --extension, --skill, --resource, and --dependency to copy selected payload resources."].join("\n") + "\n"; }
219
266
  function parseInspectArgs(rawArgs) {
@@ -523,15 +570,20 @@ class CliProgress {
523
570
  this.#run = undefined;
524
571
  }
525
572
  }
526
- async function invokeWorkflow(fn, args, runtime, options, context) {
527
- if (!Value.Check(fn.input, args))
528
- throw new Error(`Invalid input for ${fn.name}`);
573
+ async function invokeWorkflow(launch, runtime, options, context) {
574
+ if (launch.fn && (!object(launch.args) || !Value.Check(launch.fn.input, launch.args)))
575
+ throw new Error(`Invalid input for ${launch.fn.name}`);
576
+ if (!launch.fn && launch.scriptPath === undefined)
577
+ throw new Error("Workflow launch has no source");
529
578
  let announcedRunId;
530
579
  const announceRunId = (runId) => { if (announcedRunId === runId)
531
580
  return; announcedRunId = runId; options.stderr(`Run ID: ${runId}\n`); };
532
581
  const progress = new CliProgress(options.stderr, options.isTTY ?? process.stderr.isTTY, announceRunId);
533
582
  try {
534
- const result = await runtime.workflowTool.execute(randomUUID(), { name: fn.name, script: `return await ${fn.name}(args);`, args, foreground: true }, options.signal, (update) => { if (object(update) && object(update.details) && isPersistedRun(update.details.run))
583
+ const params = launch.scriptPath === undefined
584
+ ? { name: launch.name, script: `return await ${launch.fn?.name ?? launch.name}(args);`, args: launch.args, foreground: true }
585
+ : { name: launch.name, scriptPath: launch.scriptPath, args: launch.args, foreground: true };
586
+ const result = await runtime.workflowTool.execute(randomUUID(), params, options.signal, (update) => { if (object(update) && object(update.details) && isPersistedRun(update.details.run))
535
587
  progress.update(update.details.run); }, context);
536
588
  if (!isHeadlessWorkflowResult(result))
537
589
  throw new Error("Workflow returned an invalid result");
@@ -588,8 +640,22 @@ async function runWorkflowCli(rawArgs, options) {
588
640
  options.write(workflowUsage());
589
641
  return args.length ? 0 : 1;
590
642
  }
643
+ const runtimeOptions = { ...options, ...(parsed.trustOverride !== undefined ? { trustOverride: parsed.trustOverride } : {}) };
644
+ const scriptMode = args[0] === "--script" || args[0]?.startsWith("--script=") || args[0]?.startsWith("--") && args.some((arg) => arg === "--script" || arg.startsWith("--script="));
645
+ if (scriptMode) {
646
+ const script = parseScriptWorkflowCliArgs(args);
647
+ if (script.help) {
648
+ options.write(scriptWorkflowUsage());
649
+ return 0;
650
+ }
651
+ return withWorkflowRuntime(runtimeOptions, async (runtime, context) => {
652
+ const result = await invokeWorkflow({ name: script.name, scriptPath: script.scriptPath, args: script.args }, runtime, options, context);
653
+ options.write(`${JSON.stringify(result.value)}\n`);
654
+ return 0;
655
+ });
656
+ }
591
657
  const name = requiredArg(args, 0);
592
- return withWorkflowRuntime({ ...options, ...(parsed.trustOverride !== undefined ? { trustOverride: parsed.trustOverride } : {}) }, async (runtime, context) => {
658
+ return withWorkflowRuntime(runtimeOptions, async (runtime, context) => {
593
659
  const help = args.slice(1).some((arg) => arg === "--help" || arg === "-h");
594
660
  const fn = runtime.catalog.functions.find((candidate) => candidate.name === name);
595
661
  if (!fn)
@@ -599,7 +665,7 @@ async function runWorkflowCli(rawArgs, options) {
599
665
  return 0;
600
666
  }
601
667
  const input = parseWorkflowCliArgs(fn.input, args.slice(1));
602
- const result = await invokeWorkflow(fn, input, runtime, options, context);
668
+ const result = await invokeWorkflow({ name: fn.name, fn, args: input }, runtime, options, context);
603
669
  options.write(`${JSON.stringify(result.value)}\n`);
604
670
  return 0;
605
671
  });
@@ -801,6 +867,34 @@ export async function runCli(args, options = {}, write = (text) => { process.std
801
867
  return 1;
802
868
  }
803
869
  }
870
+ if (args[0] === "share") {
871
+ if (args.length !== 2 || args[1] === "--help" || args[1] === "-h") {
872
+ write("Usage: piewf share <run-id>\n\nExports the run as a static Trajectory report and uploads it as a secret GitHub gist via the gh CLI.\nSecret gists are unlisted, not private: anyone with the link can read the full report.\n");
873
+ return args.some((arg) => arg === "--help" || arg === "-h") ? 0 : 1;
874
+ }
875
+ try {
876
+ const runId = requiredArg(args, 1);
877
+ const cwd = options.cwd ?? process.cwd();
878
+ let sessionId;
879
+ for (const candidate of await listPersistedSessionIds(cwd)) {
880
+ if ((await listRunIds(cwd, candidate, homedir(), false)).includes(runId)) {
881
+ sessionId = candidate;
882
+ break;
883
+ }
884
+ }
885
+ if (!sessionId) {
886
+ stderr(`Error: workflow run ${runId} was not found under ${cwd}\n`);
887
+ return 1;
888
+ }
889
+ const result = await shareTrajectoryRun({ cwd, sessionId, runId });
890
+ write(`Share URL: ${result.shareUrl}\nGist: ${result.gistUrl}\nSecret gist: anyone with the link can read the full report.\n`);
891
+ return 0;
892
+ }
893
+ catch (error) {
894
+ stderr(`Error: ${errorText(error)}\n`);
895
+ return 1;
896
+ }
897
+ }
804
898
  if (args[0] === "bundle" || args[0] === "run" || args[0] === "export") {
805
899
  try {
806
900
  const workflowOptions = { write, stderr, ...(options.cwd !== undefined ? { cwd: options.cwd } : {}), ...(options.agentDir !== undefined ? { agentDir: options.agentDir } : {}), ...(options.signal ? { signal: options.signal } : {}), ...(options.trustOverride !== undefined ? { trustOverride: options.trustOverride } : {}), ...(options.isTTY !== undefined ? { isTTY: options.isTTY } : {}), ...(options.skillPaths?.length ? { skillPaths: [...options.skillPaths] } : {}) };
@@ -813,7 +907,7 @@ export async function runCli(args, options = {}, write = (text) => { process.std
813
907
  return 1;
814
908
  }
815
909
  }
816
- write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>] [--json] | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
910
+ write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>] [--json] | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | share <run-id> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | run --script <path> [--name <workflow-name>] [--input <json>] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
817
911
  return 1;
818
912
  }
819
913
  if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piewf/cli",
3
- "version": "5.8.0",
3
+ "version": "5.10.0",
4
4
  "description": "CLI for pi-extensible-workflows",
5
5
  "homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
6
6
  "repository": {
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "license": "MIT",
48
48
  "dependencies": {
49
- "pi-extensible-workflows": "5.8.0"
49
+ "pi-extensible-workflows": "5.10.0"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"