@marcoscale98/piewf-cli 5.14.1-fork.1 → 5.17.0-fork.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @marcoscale98/piewf-cli
2
+
3
+ Terminal commands for [pi-extensible-workflows](https://github.com/vekexasia/pi-extensible-workflows): `pi-role` starts Pi as a role, `piewf` operates workflows.
4
+
5
+ Requires Node.js 22.19 or newer and the `pi` command on `PATH`. This is trusted host code with the same filesystem and process access as Pi.
6
+
7
+ ```sh
8
+ npm install --global @marcoscale98/piewf-cli@fork
9
+ ```
10
+
11
+ ## pi-role
12
+
13
+ Start a regular Pi session with a role's model, tools, skills, extensions, and system prompt. Everything after the role name goes to Pi unchanged.
14
+
15
+ ```sh
16
+ pi-role # list the available roles
17
+ pi-role reviewer # interactive Pi as the reviewer role
18
+ pi-role scout -p "Where is the retry logic?" # one-shot prompt
19
+ pi-role developer --continue # resume the last session as the developer role
20
+ pi-role reviewer --approve # trust the project so its roles and settings apply
21
+ npx -p @marcoscale98/piewf-cli@fork pi-role oracle # without installing
22
+ ```
23
+
24
+ Roles come from the bundled starter roles, `~/.pi/agent/pi-extensible-workflows/roles/*.md`, and, in trusted projects, `.pi/pi-extensible-workflows/roles/*.md`. The [roles guide](https://vekexasia.github.io/pi-extensible-workflows/roles.html#pi-role) lists what each role field becomes on the Pi command line and the launcher's limits; the [role file reference](https://vekexasia.github.io/pi-extensible-workflows/roles.html#files) covers writing your own.
25
+
26
+ ## piewf
27
+
28
+ ```sh
29
+ piewf doctor [--role <role>] [--prompt <text>] [--json]
30
+ piewf doctor cleanup [--older-than-days <days>] [--yes]
31
+ piewf inspect [session-id] [--json|--summary] [--failed]
32
+ piewf transcript <session-file>
33
+ piewf run <workflow-name> [workflow arguments]
34
+ piewf run --script <workflow.js> [--name <workflow-name>] [--input <json>]
35
+ piewf export <workflow-name> [--name <command>] [--output <path>] [--force]
36
+ piewf bundle <workflow-name> [--name <command>] [--output <directory>] [--force]
37
+ ```
38
+
39
+ See [CLI operations](https://vekexasia.github.io/pi-extensible-workflows/developers.html#operations).
40
+
41
+ ## License
42
+
43
+ MIT
package/dist/src/cli.js CHANGED
@@ -8,7 +8,8 @@ import { ProjectTrustStore, SessionManager, SettingsManager, createAgentSessionF
8
8
  import { Value } from "typebox/value";
9
9
  import { doctor, doctorExitCode, formatDoctorReport } from "./doctor.js";
10
10
  import { doctorCleanup, doctorCleanupExitCode, formatDoctorCleanupReport } from "./doctor-cleanup.js";
11
- import workflowExtension, { errorText, formatWorkflowProgress, isNodeError, jsonValue, loadAgentDefinitions, object, registeredWorkflowFunctionSources, sameFilesystemPath, truncateWorkflowProgress, workflowCatalog, workflowSettingsPath } from "@marcoscale98/pi-extensible-workflows";
11
+ import { loadAgentDefinitions } from "@marcoscale98/pi-extensible-workflows/roles";
12
+ import workflowExtension, { errorText, formatWorkflowProgress, isNodeError, jsonValue, object, registeredWorkflowFunctionSources, sameFilesystemPath, truncateWorkflowProgress, workflowCatalog, workflowSettingsPath } from "@marcoscale98/pi-extensible-workflows";
12
13
  import { CLI_PACKAGE_NAME, portableEngineVersion, portablePiVersion, writePortableWorkflowBundle } from "./bundles.js";
13
14
  import { runSessionInspector, transcriptFileLines } from "./session-inspector.js";
14
15
  import { isPersistedRun, listPersistedSessionIds, listRunIds } from "@marcoscale98/pi-extensible-workflows/persistence";
@@ -2,7 +2,8 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { basename, dirname, extname, join } from "node:path";
3
3
  import { InMemoryCredentialStore, InMemoryModelsStore } from "@earendil-works/pi-ai";
4
4
  import { ModelRuntime, createAgentSessionFromServices, createAgentSessionServices, getAgentDir, hasTrustRequiringProjectResources, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
5
- import { DEFAULT_SETTINGS, canonicalPath, createLocalPiSession, errorText, isNodeError, isObject, loadSettings, prepareAgentSetupForInspection, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, resourcePatternHasMagic, parseThinking, parseRoleMarkdown, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowRoleDirectories, workflowProjectSettingsPath, workflowSettingsPath, } from "@marcoscale98/pi-extensible-workflows";
5
+ import { DEFAULT_SETTINGS, canonicalPath, createLocalPiSession, errorText, isNodeError, isObject, loadSettings, prepareAgentSetupForInspection, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, resourcePatternHasMagic, parseThinking, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowProjectSettingsPath, workflowSettingsPath, } from "@marcoscale98/pi-extensible-workflows";
6
+ import { parseRoleMarkdown, workflowRoleDirectories } from "@marcoscale98/pi-extensible-workflows/roles";
6
7
  import { loadingRegistry } from "@marcoscale98/pi-extensible-workflows";
7
8
  import { selectResourcesByLayers, unmatchedResourcePatterns, mergeWorkflowExtensionSettings } from "@marcoscale98/pi-extensible-workflows";
8
9
  const THINKING_HINT = "Use off, minimal, low, medium, high, xhigh, or max.";
@@ -397,6 +398,7 @@ export async function doctor(options = {}) {
397
398
  ].sort((left, right) => left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind));
398
399
  const roles = [];
399
400
  const definitions = new Map();
401
+ // Keep this scan local because doctor reports every invalid and duplicate file; discoverRoles intentionally fails closed on the complete set.
400
402
  const extensionScan = scanExtensionRoleFiles(registeredWorkflowRoleDirectoryRegistrations());
401
403
  for (const { registration, error } of extensionScan.errors) {
402
404
  const message = errorText(error);
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { type ResolvedRole } from "@marcoscale98/pi-extensible-workflows/roles";
3
+ export declare function piArguments(role: ResolvedRole, skillPaths: ReadonlyMap<string, string>, rest: readonly string[]): string[];
4
+ export declare function resolvePiArguments(name: string, rest: readonly string[], cwd?: string, agentDir?: string): Promise<string[]>;
5
+ export declare function runPiRole(argv: readonly string[]): Promise<number>;
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { constants } from "node:os";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { DefaultPackageManager, DefaultResourceLoader, ProjectTrustStore, SettingsManager, getAgentDir } from "@earendil-works/pi-coding-agent";
7
+ import { discoverRoles, loadRole, resolveRole } from "@marcoscale98/pi-extensible-workflows/roles";
8
+ import { CONTEXT_FILE_SCOPES, WorkflowError, errorText, resourcePatternHasMagic, sameFilesystemPath } from "@marcoscale98/pi-extensible-workflows";
9
+ // Pi builtin tools stand in for the workflow session boundary; extension tool names selected by the role pass through.
10
+ const PI_BUILTIN_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
11
+ function starterRoleDirectories() {
12
+ const core = dirname(fileURLToPath(import.meta.resolve("@marcoscale98/pi-extensible-workflows")));
13
+ return [{ path: resolve(core, "../starter/roles"), extension: { version: "0.0.0", headline: "Starter roles" }, builtin: true }];
14
+ }
15
+ function projectTrust(cwd, agentDir, args) {
16
+ const end = args.indexOf("--");
17
+ const flag = args.slice(0, end < 0 ? args.length : end).filter((arg) => ["--approve", "-a", "--no-approve", "-na"].includes(arg)).at(-1); // last wins, as in pi
18
+ if (flag)
19
+ return flag === "--approve" || flag === "-a";
20
+ return new ProjectTrustStore(agentDir).get(cwd) ?? false; // no saved decision: project roles, settings, and resources stay out
21
+ }
22
+ export function piArguments(role, skillPaths, rest) {
23
+ const argv = [];
24
+ if (role.model)
25
+ argv.push("--model", `${role.model.provider}/${role.model.model}${role.model.thinking ? `:${role.model.thinking}` : ""}`);
26
+ if (role.selectorLayers.tools.some((layer) => layer !== undefined))
27
+ argv.push("--tools", (role.tools ?? []).join(","));
28
+ argv.push("--no-skills");
29
+ for (const name of role.selectedSkills ?? [])
30
+ argv.push("--skill", skillPaths.get(name) ?? name);
31
+ argv.push("--no-extensions");
32
+ for (const path of role.selectedExtensions ?? [])
33
+ argv.push("--extension", path);
34
+ if (role.systemPrompt.text)
35
+ argv.push(role.systemPrompt.mode === "override" ? "--system-prompt" : "--append-system-prompt", role.systemPrompt.text);
36
+ if (role.contextFiles !== undefined && new Set(role.contextFiles).size < CONTEXT_FILE_SCOPES.length) {
37
+ if (role.contextFiles.length > 0)
38
+ throw new Error(`Role ${role.name ?? ""} selects a subset of context file scopes (${role.contextFiles.join(", ")}); pi-role only supports all scopes or none`);
39
+ argv.push("--no-context-files");
40
+ }
41
+ return [...argv, ...rest];
42
+ }
43
+ export async function resolvePiArguments(name, rest, cwd = process.cwd(), agentDir = getAgentDir()) {
44
+ const projectTrusted = projectTrust(cwd, agentDir, rest);
45
+ const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
46
+ settingsManager.setProjectTrusted(projectTrusted);
47
+ const discovered = await new DefaultPackageManager({ cwd, agentDir, settingsManager }).resolve();
48
+ const visible = ({ enabled, metadata }) => enabled && (projectTrusted || metadata.scope !== "project");
49
+ const extensions = [...new Set(discovered.extensions.filter(visible).map(({ path }) => path))];
50
+ const loader = new DefaultResourceLoader({ cwd, agentDir, settingsManager, noExtensions: true, noSkills: true, additionalSkillPaths: [...new Set(discovered.skills.filter(visible).map(({ path }) => path))] });
51
+ await loader.reload();
52
+ const skillPaths = new Map(loader.getSkills().skills.map(({ name: skill, filePath }) => [skill, filePath]));
53
+ const discovery = { cwd, agentDir, projectTrusted, extensionRoleDirectories: starterRoleDirectories() };
54
+ const resolveWith = (definition) => {
55
+ const requestedTools = resolveRole(name, { ...discovery, definition }).selectorLayers.tools.flatMap((layer) => layer ?? []).filter((selector) => !selector.startsWith("!") && !resourcePatternHasMagic(selector));
56
+ return resolveRole(name, { ...discovery, definition, resources: { extensions, skills: [...skillPaths.keys()], tools: [...new Set([...PI_BUILTIN_TOOLS, ...requestedTools])] } });
57
+ };
58
+ const definition = loadRole(name, discovery);
59
+ let role;
60
+ try {
61
+ role = resolveWith(definition);
62
+ }
63
+ catch (error) {
64
+ // Starter roles use aliases the workflow extension resolves against the launching session's model; standalone, pi's default model plays that part.
65
+ if (!(error instanceof WorkflowError) || error.code !== "UNKNOWN_MODEL")
66
+ throw error;
67
+ process.stderr.write(`pi-role: ${errorText(error)}; starting pi with its default model\n`);
68
+ const withoutModel = { ...definition };
69
+ delete withoutModel.model;
70
+ role = resolveWith(withoutModel);
71
+ }
72
+ return piArguments(role, skillPaths, rest);
73
+ }
74
+ function usage(cwd, agentDir) {
75
+ const roles = discoverRoles({ cwd, agentDir, projectTrusted: projectTrust(cwd, agentDir, []), extensionRoleDirectories: starterRoleDirectories() });
76
+ const lines = Object.entries(roles).map(([name, { description }]) => ` ${name.padEnd(16)}${description ?? ""}`);
77
+ return `Usage: pi-role <role> [pi arguments...]\n\nRoles:\n${lines.join("\n")}\n\nProject roles need pi's saved trust decision or --approve.\n`;
78
+ }
79
+ export async function runPiRole(argv) {
80
+ const [name, ...rest] = argv;
81
+ if (!name || name === "--help" || name === "-h") {
82
+ process.stdout.write(usage(process.cwd(), getAgentDir()));
83
+ return name ? 0 : 1;
84
+ }
85
+ let args;
86
+ try {
87
+ args = await resolvePiArguments(name, rest);
88
+ }
89
+ catch (error) {
90
+ process.stderr.write(`pi-role: ${errorText(error)}\n`);
91
+ return 1;
92
+ }
93
+ return new Promise((done) => {
94
+ const child = spawn("pi", args, { stdio: "inherit" });
95
+ process.on("SIGINT", () => { }); // the child owns the terminal; it handles Ctrl+C and exits
96
+ child.on("error", (error) => { process.stderr.write(`pi-role: ${errorText(error)}\n`); done(1); });
97
+ child.on("exit", (code, signal) => { done(signal ? 128 + constants.signals[signal] : code ?? 1); });
98
+ });
99
+ }
100
+ if (process.argv[1] && sameFilesystemPath(fileURLToPath(import.meta.url), process.argv[1])) {
101
+ process.exitCode = await runPiRole(process.argv.slice(2));
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcoscale98/piewf-cli",
3
- "version": "5.14.1-fork.1",
3
+ "version": "5.17.0-fork.1",
4
4
  "description": "CLI for pi-extensible-workflows",
5
5
  "homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
6
6
  "repository": {
@@ -10,16 +10,18 @@
10
10
  },
11
11
  "type": "module",
12
12
  "exports": {
13
- ".": "./dist/src/cli.js"
13
+ ".": "./dist/src/cli.js",
14
+ "./pi-role": "./dist/src/pi-role.js"
14
15
  },
15
16
  "bin": {
16
- "piewf": "./dist/src/cli.js"
17
+ "piewf": "./dist/src/cli.js",
18
+ "pi-role": "./dist/src/pi-role.js"
17
19
  },
18
20
  "files": [
19
21
  "dist/src"
20
22
  ],
21
23
  "scripts": {
22
- "build": "npm --prefix ../.. run build --workspace=../core && rm -rf dist && tsc -p tsconfig.json && chmod +x dist/src/cli.js",
24
+ "build": "npm --prefix ../.. run build --workspace=../core && rm -rf dist && tsc -p tsconfig.json && chmod +x dist/src/cli.js dist/src/pi-role.js",
23
25
  "inspect": "node dist/src/cli.js inspect",
24
26
  "test": "npm run build && TEST_FILES='dist/test/*.test.js' npm run test:run",
25
27
  "test:run": "files=${TEST_FILES:-dist/test/*.test.js}; printf '%s\\n' $files | xargs -n1 -P20 sh -c 'tmp=$(mktemp -d); TMPDIR=\"$tmp\" node --test --test-concurrency=1 --test-timeout=60000 --test-force-exit --test-reporter=dot \"$1\"; status=$?; rm -rf \"$tmp\"; exit $status' sh",
@@ -43,7 +45,7 @@
43
45
  "@earendil-works/pi-coding-agent": "0.85.0",
44
46
  "@earendil-works/pi-server": "0.85.0",
45
47
  "@earendil-works/pi-tui": "0.85.0",
46
- "@marcoscale98/pi-extensible-workflows": "5.14.1-fork.1",
48
+ "@marcoscale98/pi-extensible-workflows": "5.17.0-fork.1",
47
49
  "typebox": "1.3.7"
48
50
  },
49
51
  "publishConfig": {