@bigking67/pi-67 0.10.3 → 0.10.4

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.
@@ -13,6 +13,10 @@ export async function publishCheckCommand(ctx, argv) {
13
13
  const { options } = parseCommandOptions(argv, {
14
14
  bools: ["json", "no-remote", "no-pack", "quiet", "strict", "allow-first-publish"],
15
15
  });
16
+ if (options.help) {
17
+ printPublishCheckHelp();
18
+ return;
19
+ }
16
20
  const json = ctx.json || options.json;
17
21
  const quiet = options.quiet;
18
22
  const noRemote = ctx.noRemote || options.noRemote;
@@ -246,6 +250,26 @@ function workflowCheck(file) {
246
250
  };
247
251
  }
248
252
 
253
+ function printPublishCheckHelp() {
254
+ process.stdout.write(`pi-67 publish-check - verify npm publish readiness
255
+
256
+ Usage:
257
+ pi-67 publish-check [--strict] [--json] [--no-remote] [--no-pack]
258
+
259
+ Options:
260
+ --strict Exit non-zero when blockers are present.
261
+ --json Emit machine-readable publish readiness JSON.
262
+ --no-remote Skip npm registry/scope/auth remote probes.
263
+ --no-pack Skip npm pack dry-run.
264
+ --quiet Suppress human output; status is in the exit code.
265
+ --allow-first-publish Explicitly allow first-publish local gate probing.
266
+
267
+ Examples:
268
+ pi-67 publish-check
269
+ pi-67 publish-check --strict --no-pack
270
+ `);
271
+ }
272
+
249
273
  function npmAuthCheck(options) {
250
274
  if (options.noRemote) return skipped("remote auth check skipped");
251
275
  const result = captureCommand("npm", ["whoami"], { timeoutMs: 10000 });
@@ -7,6 +7,10 @@ export async function reportCommand(ctx, argv) {
7
7
  bools: ["dry-run", "no-doctor"],
8
8
  strings: ["operation", "output"],
9
9
  });
10
+ if (options.help) {
11
+ printReportHelp();
12
+ return;
13
+ }
10
14
  const args = isWindows()
11
15
  ? ["-AgentDir", ctx.agentDir, "-RepoRoot", ctx.repoRoot, "-SkillsDir", ctx.skillsDir]
12
16
  : ["--agent-dir", ctx.agentDir, "--repo-root", ctx.repoRoot, "--skills-dir", ctx.skillsDir];
@@ -18,3 +22,21 @@ export async function reportCommand(ctx, argv) {
18
22
  dryRun: false,
19
23
  });
20
24
  }
25
+
26
+ function printReportHelp() {
27
+ process.stdout.write(`pi-67 report - generate pi67-report.json
28
+
29
+ Usage:
30
+ pi-67 report [--operation NAME] [--output FILE] [--no-doctor] [--dry-run]
31
+
32
+ Options:
33
+ --operation NAME Operation label to embed in the report.
34
+ --output FILE Output path. Defaults to the distro report location.
35
+ --no-doctor Skip doctor data collection where supported.
36
+ --dry-run Print the script invocation without running it.
37
+
38
+ Examples:
39
+ pi-67 report
40
+ pi-67 report --operation update
41
+ `);
42
+ }
@@ -5,6 +5,10 @@ import { info, pass } from "../lib/output.mjs";
5
5
 
6
6
  export async function selfUpdateCommand(ctx, argv) {
7
7
  const { options } = parseCommandOptions(argv, { bools: ["dry-run"] });
8
+ if (options.help) {
9
+ printSelfUpdateHelp();
10
+ return;
11
+ }
8
12
  const pkg = readCliPackageJson();
9
13
  const dryRun = ctx.dryRun || options.dryRun;
10
14
  runCommand("npm", ["install", "-g", `${pkg.name}@latest`], { dryRun });
@@ -14,3 +18,18 @@ export async function selfUpdateCommand(ctx, argv) {
14
18
  }
15
19
  pass(`${pkg.name} manager updated. Run \`pi-67 version\` to verify the active PATH entry.`);
16
20
  }
21
+
22
+ function printSelfUpdateHelp() {
23
+ process.stdout.write(`pi-67 self-update - update the npm manager package
24
+
25
+ Usage:
26
+ pi-67 self-update [--dry-run]
27
+
28
+ Options:
29
+ --dry-run Print the npm install command without running it.
30
+
31
+ Examples:
32
+ pi-67 self-update --dry-run
33
+ pi-67 self-update
34
+ `);
35
+ }
@@ -1,10 +1,16 @@
1
1
  import { parseCommandOptions } from "../lib/args.mjs";
2
- import { inventorySkills, syncSkills } from "../lib/skill-policy.mjs";
3
- import { CliError, info, keyValue, printJson, section, warn } from "../lib/output.mjs";
2
+ import { diffSkill, inventorySkills, planSkills, syncSkills } from "../lib/skill-policy.mjs";
3
+ import { CliError, info, keyValue, pass, printJson, section, warn } from "../lib/output.mjs";
4
4
 
5
5
  export async function skillsCommand(ctx, argv) {
6
6
  const [sub = "inventory", ...rest] = argv;
7
+ if (sub === "-h" || sub === "--help" || sub === "help") {
8
+ printSkillsHelp();
9
+ return;
10
+ }
7
11
  if (sub === "inventory") return inventory(ctx, rest);
12
+ if (sub === "plan") return plan(ctx, rest);
13
+ if (sub === "diff") return diff(ctx, rest);
8
14
  if (sub === "sync") return sync(ctx, rest);
9
15
  if (sub === "migrate") return migrate(ctx, rest);
10
16
  throw new CliError(`unknown skills command: ${sub}`, 2);
@@ -12,6 +18,7 @@ export async function skillsCommand(ctx, argv) {
12
18
 
13
19
  function inventory(ctx, argv) {
14
20
  const { options } = parseCommandOptions(argv, { bools: ["json"] });
21
+ if (options.help) return printSkillsHelp();
15
22
  const data = inventorySkills(ctx);
16
23
  if (ctx.json || options.json) return printJson(data);
17
24
  section("pi-67 shared skills inventory");
@@ -26,9 +33,56 @@ function inventory(ctx, argv) {
26
33
  }
27
34
  }
28
35
 
36
+ function plan(ctx, argv) {
37
+ const { options, positionals } = parseCommandOptions(argv, { bools: ["json"] });
38
+ if (options.help) return printSkillsHelp();
39
+ const data = planSkills(ctx, { names: positionals });
40
+ if (ctx.json || options.json) return printJson(data);
41
+ section("pi-67 shared skills plan");
42
+ keyValue("Source", data.sourceRoot);
43
+ keyValue("Target", data.skillsDir);
44
+ keyValue("Selected", data.selected.length === 0 ? "all changed skills" : data.selected.join(", "));
45
+ keyValue("Missing", data.actions.filter((item) => item.action === "copy-missing").length);
46
+ keyValue("Conflicts", data.actions.filter((item) => item.action === "preserve-conflict").length);
47
+ for (const action of data.actions) {
48
+ const command = action.conflict
49
+ ? `inspect: pi-67 skills diff ${action.name}; explicit sync: pi-67 skills sync ${action.name} --dry-run`
50
+ : `sync: pi-67 skills sync ${action.name}`;
51
+ info(`${action.name}: ${action.action}; ${command}`);
52
+ }
53
+ if (data.actions.length === 0) pass("no shared skill action is required");
54
+ }
55
+
56
+ function diff(ctx, argv) {
57
+ const { options, positionals } = parseCommandOptions(argv, { bools: ["json"] });
58
+ if (options.help) return printSkillsHelp();
59
+ const name = positionals[0];
60
+ if (!name) throw new CliError("skills diff requires a skill name", 2);
61
+ const data = diffSkill(ctx, name);
62
+ if (ctx.json || options.json) return printJson(data);
63
+ section(`pi-67 shared skill diff: ${name}`);
64
+ keyValue("Source", data.source);
65
+ keyValue("Target", data.target);
66
+ keyValue("Target exists", data.targetExists ? "yes" : "no");
67
+ keyValue("Identical", data.identical ? "yes" : "no");
68
+ keyValue("Added files", data.diff.added.length);
69
+ keyValue("Removed files", data.diff.removed.length);
70
+ keyValue("Modified files", data.diff.modified.length);
71
+ for (const file of data.diff.added.slice(0, 20)) info(`added in source: ${file}`);
72
+ for (const file of data.diff.removed.slice(0, 20)) warn(`missing from source: ${file}`);
73
+ for (const file of data.diff.modified.slice(0, 20)) info(`modified: ${file}`);
74
+ const omitted = data.diff.added.length + data.diff.removed.length + data.diff.modified.length - 60;
75
+ if (omitted > 0) warn(`${omitted} additional file diffs omitted; rerun with --json for full metadata`);
76
+ }
77
+
29
78
  function sync(ctx, argv) {
30
- const { options } = parseCommandOptions(argv, { bools: ["json", "dry-run"] });
31
- const data = syncSkills(ctx, { dryRun: ctx.dryRun || options.dryRun });
79
+ const { options, positionals } = parseCommandOptions(argv, { bools: ["json", "dry-run", "yes"] });
80
+ if (options.help) return printSkillsHelp();
81
+ const data = syncSkills(ctx, {
82
+ dryRun: ctx.dryRun || options.dryRun,
83
+ names: positionals,
84
+ yes: ctx.yes || options.yes,
85
+ });
32
86
  if (ctx.json || options.json) return printJson(data);
33
87
  section("pi-67 shared skills sync");
34
88
  for (const action of data.actions) {
@@ -39,6 +93,7 @@ function sync(ctx, argv) {
39
93
 
40
94
  function migrate(ctx, argv) {
41
95
  const { options } = parseCommandOptions(argv, { bools: ["dry-run", "json"] });
96
+ if (options.help) return printSkillsHelp();
42
97
  const data = syncSkills(ctx, { dryRun: true });
43
98
  data.schema = "pi67.skills-migrate-preview.v1";
44
99
  if (ctx.json || options.json) return printJson(data);
@@ -47,3 +102,27 @@ function migrate(ctx, argv) {
47
102
  keyValue("Missing", data.summary.missing);
48
103
  keyValue("Conflicts", data.summary.conflicts);
49
104
  }
105
+
106
+ function printSkillsHelp() {
107
+ process.stdout.write(`pi-67 skills - inspect and safely sync shared skills
108
+
109
+ Usage:
110
+ pi-67 skills inventory [--json]
111
+ pi-67 skills plan [skill...] [--json]
112
+ pi-67 skills diff <skill> [--json]
113
+ pi-67 skills sync [skill...] [--dry-run] [--yes] [--json]
114
+ pi-67 skills migrate [--dry-run] [--json]
115
+
116
+ Safety:
117
+ Missing skills are copied by default. Existing different global skills are
118
+ preserved unless you name the skill explicitly and pass --yes. Bulk conflict
119
+ overwrite is intentionally blocked.
120
+
121
+ Examples:
122
+ pi-67 skills inventory
123
+ pi-67 skills plan
124
+ pi-67 skills diff lark-doc
125
+ pi-67 skills sync lark-doc --dry-run
126
+ pi-67 skills sync lark-doc --yes
127
+ `);
128
+ }
@@ -6,6 +6,10 @@ export async function smokeCommand(ctx, argv) {
6
6
  const { options } = parseCommandOptions(argv, {
7
7
  bools: ["ci", "quick", "dry-run"],
8
8
  });
9
+ if (options.help) {
10
+ printSmokeHelp();
11
+ return;
12
+ }
9
13
  const args = [];
10
14
  if (isWindows()) {
11
15
  if (options.ci || options.quick) args.push("-Ci");
@@ -16,3 +20,20 @@ export async function smokeCommand(ctx, argv) {
16
20
  dryRun: ctx.dryRun || options.dryRun,
17
21
  });
18
22
  }
23
+
24
+ function printSmokeHelp() {
25
+ process.stdout.write(`pi-67 smoke - run repository smoke gates
26
+
27
+ Usage:
28
+ pi-67 smoke [--ci|--quick] [--dry-run]
29
+
30
+ Options:
31
+ --ci Run the CI smoke profile.
32
+ --quick Alias for the CI smoke profile.
33
+ --dry-run Print the script invocation without running it.
34
+
35
+ Examples:
36
+ pi-67 smoke
37
+ pi-67 smoke --ci
38
+ `);
39
+ }
@@ -6,6 +6,10 @@ export async function statusCommand(ctx, argv) {
6
6
  const { options } = parseCommandOptions(argv, {
7
7
  bools: ["json", "no-remote"],
8
8
  });
9
+ if (options.help) {
10
+ printStatusHelp();
11
+ return;
12
+ }
9
13
  const plan = await buildUpdatePlan(ctx, { noRemote: ctx.noRemote || options.noRemote });
10
14
  if (ctx.json || options.json) {
11
15
  printJson(plan);
@@ -19,3 +23,19 @@ export async function statusCommand(ctx, argv) {
19
23
  keyValue("Theme", plan.settings.theme || "unset");
20
24
  keyValue("Shared skills", `${plan.skills.identical} ok, ${plan.skills.missing} missing, ${plan.skills.conflicts} conflicts`);
21
25
  }
26
+
27
+ function printStatusHelp() {
28
+ process.stdout.write(`pi-67 status - read-only status summary
29
+
30
+ Usage:
31
+ pi-67 status [--json] [--no-remote]
32
+
33
+ Options:
34
+ --json Emit the full update-plan status JSON.
35
+ --no-remote Skip remote git/npm registry checks.
36
+
37
+ Examples:
38
+ pi-67 status
39
+ pi-67 status --json --no-remote
40
+ `);
41
+ }
@@ -7,6 +7,10 @@ import { CliError, keyValue, printJson, section, pass, fail, info } from "../lib
7
7
 
8
8
  export async function themesCommand(ctx, argv) {
9
9
  const [sub = "current", ...rest] = argv;
10
+ if (sub === "-h" || sub === "--help" || sub === "help") {
11
+ printThemesHelp();
12
+ return;
13
+ }
10
14
  if (sub === "current") return current(ctx, rest);
11
15
  if (sub === "list") return list(ctx, rest);
12
16
  if (sub === "doctor") return doctor(ctx, rest);
@@ -16,6 +20,7 @@ export async function themesCommand(ctx, argv) {
16
20
 
17
21
  function current(ctx, argv) {
18
22
  const { options } = parseCommandOptions(argv, { bools: ["json"] });
23
+ if (options.help) return printThemesHelp();
19
24
  const value = currentTheme(ctx);
20
25
  const data = {
21
26
  schema: "pi67.theme-current.v1",
@@ -29,6 +34,7 @@ function current(ctx, argv) {
29
34
 
30
35
  function list(ctx, argv) {
31
36
  const { options } = parseCommandOptions(argv, { bools: ["json"] });
37
+ if (options.help) return printThemesHelp();
32
38
  const themes = listThemes(ctx);
33
39
  if (ctx.json || options.json) return printJson({ schema: "pi67.theme-list.v1", themes });
34
40
  section("Available themes");
@@ -37,6 +43,7 @@ function list(ctx, argv) {
37
43
 
38
44
  function doctor(ctx, argv) {
39
45
  const { options } = parseCommandOptions(argv, { bools: ["json"] });
46
+ if (options.help) return printThemesHelp();
40
47
  const theme = currentTheme(ctx);
41
48
  const installed = theme ? hasTheme(ctx, theme) : false;
42
49
  const data = { schema: "pi67.theme-doctor.v1", theme, installed };
@@ -48,6 +55,7 @@ function doctor(ctx, argv) {
48
55
 
49
56
  function setTheme(ctx, argv) {
50
57
  const { options, positionals } = parseCommandOptions(argv, { bools: ["force", "dry-run"] });
58
+ if (options.help) return printThemesHelp();
51
59
  const name = positionals[0];
52
60
  if (!name) throw new CliError("themes set requires a theme name", 2);
53
61
  if (!options.force && !hasTheme(ctx, name)) {
@@ -71,3 +79,24 @@ function setTheme(ctx, argv) {
71
79
  pass(`theme set: ${previous || "unset"} -> ${name}`);
72
80
  info(`Preserved runtime backup: ${backupDir}`);
73
81
  }
82
+
83
+ function printThemesHelp() {
84
+ process.stdout.write(`pi-67 themes - inspect and explicitly set themes
85
+
86
+ Usage:
87
+ pi-67 themes current [--json]
88
+ pi-67 themes list [--json]
89
+ pi-67 themes doctor [--json]
90
+ pi-67 themes set <name> [--force] [--dry-run]
91
+
92
+ Safety:
93
+ Updates install theme assets but never change the selected theme. Only
94
+ \`pi-67 themes set <name>\` changes settings.json, and it writes a runtime
95
+ backup first unless --dry-run is used.
96
+
97
+ Examples:
98
+ pi-67 themes current
99
+ pi-67 themes list
100
+ pi-67 themes set gruvbox-dark --dry-run
101
+ `);
102
+ }
@@ -1,6 +1,6 @@
1
1
  import { parseCommandOptions } from "../lib/args.mjs";
2
2
  import { buildUpdatePlan } from "../lib/update-plan.mjs";
3
- import { printJson, section, keyValue, pass, warn, info } from "../lib/output.mjs";
3
+ import { CliError, printJson, section, keyValue, pass, warn, info } from "../lib/output.mjs";
4
4
  import { runDistroScript } from "../lib/distro-scripts.mjs";
5
5
  import { isWindows } from "../lib/platform.mjs";
6
6
  import { runCommand } from "../lib/shell-runner.mjs";
@@ -24,6 +24,10 @@ export async function updateCommand(ctx, argv) {
24
24
  "strict-shared-skills",
25
25
  ],
26
26
  });
27
+ if (options.help) {
28
+ printUpdateHelp();
29
+ return;
30
+ }
27
31
  const dryRun = ctx.dryRun || options.dryRun;
28
32
  const json = ctx.json || options.json;
29
33
  if (options.check) {
@@ -43,10 +47,12 @@ export async function updateCommand(ctx, argv) {
43
47
  noRemote: ctx.noRemote || options.noRemote,
44
48
  strictSharedSkills: options.strictSharedSkills,
45
49
  });
50
+ assertPlanCanProceed(plan, { allowDirty: options.allowDirty });
46
51
  const lifecycle = beginUpdateLifecycle(ctx, {
47
52
  operation: options.repair ? "repair" : "update",
48
53
  dryRun,
49
54
  plan,
55
+ backupRuntime: false,
50
56
  });
51
57
  if (!dryRun && lifecycle.backedUp.length > 0) {
52
58
  info(`Preserved runtime backup: ${lifecycle.backupDir}`);
@@ -86,6 +92,12 @@ function buildBashUpdateArgs(ctx, options, dryRun) {
86
92
  return args;
87
93
  }
88
94
 
95
+ function assertPlanCanProceed(plan, options = {}) {
96
+ if (options.allowDirty || !plan.blocked?.length) return;
97
+ for (const item of plan.blocked) warn(`${item.id}: ${item.reason}`);
98
+ throw new CliError("pi-67 update is blocked; run `pi-67 update --check` for the full plan or rerun with --allow-dirty if you accept the risk", 2);
99
+ }
100
+
89
101
  function buildWindowsUpdateArgs(ctx, options, dryRun) {
90
102
  const args = ["-AgentDir", ctx.agentDir, "-RepoRoot", ctx.repoRoot, "-SkillsDir", ctx.skillsDir];
91
103
  if (dryRun) args.push("-DryRun");
@@ -138,3 +150,37 @@ function printPlan(plan) {
138
150
  for (const item of plan.recommendations) info(item);
139
151
  if (!plan.git?.dirty) pass("update check completed without local dirty blocker");
140
152
  }
153
+
154
+ function printUpdateHelp() {
155
+ process.stdout.write(`pi-67 update - update pi-67 safely
156
+
157
+ Usage:
158
+ pi-67 update [--repair] [--dry-run] [--no-remote] [--no-npm]
159
+ pi-67 update --check [--json] [--no-remote]
160
+
161
+ Options:
162
+ --check Print the read-only update plan and exit.
163
+ --repair Re-run owned asset/config repair during update.
164
+ --dry-run Print planned script actions without changing files.
165
+ --no-remote Skip remote git/npm registry checks where supported.
166
+ --no-npm Skip npm package sync in the distro updater.
167
+ --allow-dirty Let the script-level updater handle non-runtime dirty files.
168
+ --strict-shared-skills Treat differing shared skills as blocking.
169
+ --include-pi Also run upstream \`pi update --all\` when paired with --yes.
170
+ --include-external Report explicit external repo update commands.
171
+ --all Alias for --include-pi --include-external.
172
+ --yes Confirm explicit opt-in actions.
173
+ --json Emit JSON for --check.
174
+
175
+ Safety:
176
+ Runtime config backup/restore is owned by the platform updater script when
177
+ preserved runtime files are dirty. The npm manager owns the update lock and
178
+ never creates a runtime backup for --help or a blocked update plan.
179
+
180
+ Examples:
181
+ pi-67 update --check
182
+ pi-67 update --check --json --no-remote
183
+ pi-67 update
184
+ pi-67 update --repair
185
+ `);
186
+ }
@@ -4,10 +4,16 @@ import { currentTheme } from "../lib/theme-policy.mjs";
4
4
  import { keyValue, printJson } from "../lib/output.mjs";
5
5
  import { platformName } from "../lib/platform.mjs";
6
6
  import { readCliPackageJson, readTextIfExists } from "../lib/paths.mjs";
7
+ import { parseCommandOptions } from "../lib/args.mjs";
7
8
  import path from "node:path";
8
9
 
9
10
  export async function versionCommand(ctx, argv) {
10
- const json = ctx.json || argv.includes("--json");
11
+ const { options } = parseCommandOptions(argv, { bools: ["json"] });
12
+ if (options.help) {
13
+ printVersionHelp();
14
+ return;
15
+ }
16
+ const json = ctx.json || options.json;
11
17
  const pkg = readCliPackageJson();
12
18
  const git = gitStatus(ctx.repoRoot);
13
19
  const pi = captureCommand("pi", ["--version"]);
@@ -49,3 +55,18 @@ export async function versionCommand(ctx, argv) {
49
55
  keyValue("agentDir", data.paths.agentDir);
50
56
  keyValue("theme", data.theme || "unset");
51
57
  }
58
+
59
+ function printVersionHelp() {
60
+ process.stdout.write(`pi-67 version - print manager and distro versions
61
+
62
+ Usage:
63
+ pi-67 version [--json]
64
+
65
+ Options:
66
+ --json Emit machine-readable version metadata.
67
+
68
+ Examples:
69
+ pi-67 version
70
+ pi-67 version --json
71
+ `);
72
+ }
@@ -6,17 +6,25 @@ import { CliError } from "../lib/output.mjs";
6
6
 
7
7
  export async function xtalpiCommand(ctx, argv) {
8
8
  const [sub = "health", ...rest] = argv;
9
+ if (sub === "-h" || sub === "--help" || sub === "help") {
10
+ printXtalpiHelp();
11
+ return;
12
+ }
9
13
  if (sub === "health") return health(ctx, rest);
10
14
  if (sub === "smoke") return smoke(ctx, rest);
11
15
  if (sub === "capability") return capability(ctx, rest);
16
+ if (sub === "trend") return trend(ctx, rest);
17
+ if (sub === "drift") return drift(ctx, rest);
18
+ if (sub === "stress") return stress(ctx, rest);
12
19
  throw new CliError(`unknown xtalpi command: ${sub}`, 2);
13
20
  }
14
21
 
15
22
  function health(ctx, argv) {
16
23
  const { options } = parseCommandOptions(argv, {
17
24
  strings: ["model", "provider", "timeout-ms", "attempts"],
18
- bools: ["dry-run"],
25
+ bools: ["dry-run", "json"],
19
26
  });
27
+ if (options.help) return printXtalpiHelp();
20
28
  const args = [scriptPath(ctx, "pi67-xtalpi-provider-health.mjs"), "--agent-dir", ctx.agentDir];
21
29
  if (options.provider) args.push("--provider", options.provider);
22
30
  if (options.model) args.push("--model", options.model);
@@ -30,6 +38,7 @@ function smoke(ctx, argv) {
30
38
  strings: ["case", "profile"],
31
39
  bools: ["quick", "extension-low-risk", "extension-expanded", "self-test", "dry-run"],
32
40
  });
41
+ if (options.help) return printXtalpiHelp();
33
42
  if (isWindows()) {
34
43
  const pwsh = findPowerShell();
35
44
  if (!pwsh) throw new CliError("PowerShell executable not found");
@@ -50,8 +59,9 @@ function smoke(ctx, argv) {
50
59
  function capability(ctx, argv) {
51
60
  const { options } = parseCommandOptions(argv, {
52
61
  strings: ["model", "provider", "timeout-ms"],
53
- bools: ["dry-run", "self-test"],
62
+ bools: ["dry-run", "self-test", "json"],
54
63
  });
64
+ if (options.help) return printXtalpiHelp();
55
65
  const args = [scriptPath(ctx, "pi67-xtalpi-provider-capability-probe.mjs"), "--agent-dir", ctx.agentDir];
56
66
  if (options.selfTest) args.push("--self-test");
57
67
  if (options.provider) args.push("--provider", options.provider);
@@ -60,6 +70,46 @@ function capability(ctx, argv) {
60
70
  runCommand("node", args, { cwd: ctx.repoRoot, dryRun: ctx.dryRun || options.dryRun });
61
71
  }
62
72
 
73
+ function trend(ctx, argv) {
74
+ const { options } = parseCommandOptions(argv, {
75
+ strings: ["limit", "profile", "out-dir"],
76
+ bools: ["json", "dry-run"],
77
+ });
78
+ if (options.help) return printXtalpiHelp();
79
+ const limit = options.limit || "3";
80
+ const args = [scriptPath(ctx, "pi67-xtalpi-pi-tools-debug-summary.sh"), "--trend-gate", limit];
81
+ if (options.profile) args.push("--profile", options.profile);
82
+ else args.push("--profile", "full-suite-strict");
83
+ if (ctx.json || options.json) args.push("--json");
84
+ if (options.outDir) args.push(options.outDir);
85
+ runCommand("bash", args, { cwd: ctx.repoRoot, dryRun: ctx.dryRun || options.dryRun });
86
+ }
87
+
88
+ function drift(ctx, argv) {
89
+ const { options } = parseCommandOptions(argv, {
90
+ strings: ["limit", "run-kind", "out-dir"],
91
+ bools: ["json", "dry-run"],
92
+ });
93
+ if (options.help) return printXtalpiHelp();
94
+ const limit = options.limit || "10";
95
+ const args = [scriptPath(ctx, "pi67-xtalpi-pi-tools-debug-summary.sh"), "--drift", limit];
96
+ args.push("--run-kind", options.runKind || "full-suite");
97
+ if (ctx.json || options.json) args.push("--json");
98
+ if (options.outDir) args.push(options.outDir);
99
+ runCommand("bash", args, { cwd: ctx.repoRoot, dryRun: ctx.dryRun || options.dryRun });
100
+ }
101
+
102
+ function stress(ctx, argv) {
103
+ const { options } = parseCommandOptions(argv, {
104
+ strings: ["case", "profile"],
105
+ bools: ["until-done", "dry-run"],
106
+ });
107
+ if (options.help) return printXtalpiHelp();
108
+ const smokeCase = options.untilDone ? "until-done-continuation" : options.case;
109
+ if (smokeCase) return smoke(ctx, ["--case", smokeCase, ...(options.dryRun ? ["--dry-run"] : [])]);
110
+ return smoke(ctx, ["--profile", options.profile || "full-suite", ...(options.dryRun ? ["--dry-run"] : [])]);
111
+ }
112
+
63
113
  function profileFromOptions(options) {
64
114
  if (options.profile) return options.profile;
65
115
  if (options.extensionLowRisk) return "extension-low-risk";
@@ -67,3 +117,32 @@ function profileFromOptions(options) {
67
117
  if (options.quick) return "quick";
68
118
  return "quick";
69
119
  }
120
+
121
+ function printXtalpiHelp() {
122
+ process.stdout.write(`pi-67 xtalpi - xtalpi health, smoke, and artifact helpers
123
+
124
+ Usage:
125
+ pi-67 xtalpi health [--provider ID] [--model NAME] [--timeout-ms N] [--attempts N]
126
+ pi-67 xtalpi smoke [--quick|--extension-low-risk|--extension-expanded|--profile NAME]
127
+ pi-67 xtalpi smoke --case NAME
128
+ pi-67 xtalpi capability [--self-test] [--provider ID] [--model NAME]
129
+ pi-67 xtalpi trend [--limit N] [--profile NAME] [--json] [--out-dir DIR]
130
+ pi-67 xtalpi drift [--limit N] [--run-kind LIST] [--json] [--out-dir DIR]
131
+ pi-67 xtalpi stress --until-done
132
+
133
+ Notes:
134
+ xtalpi-pi-tools treats xtalpi as plain chat-completions transport. Pi local
135
+ code owns tool protocol parsing, validation, repair, retry classification,
136
+ tool execution, and smoke gates.
137
+ xtalpi drift defaults to --run-kind full-suite so targeted one-off smoke
138
+ artifacts do not create expected case-set drift noise.
139
+
140
+ Examples:
141
+ pi-67 xtalpi health
142
+ pi-67 xtalpi smoke --quick
143
+ pi-67 xtalpi smoke --case until-done-continuation
144
+ pi-67 xtalpi trend --json
145
+ pi-67 xtalpi drift --json
146
+ pi-67 xtalpi stress --until-done
147
+ `);
148
+ }
package/src/lib/args.mjs CHANGED
@@ -6,14 +6,20 @@ const BOOL_GLOBALS = new Set(["json", "dry-run", "yes", "help", "no-remote"]);
6
6
  export function splitGlobalArgs(argv) {
7
7
  const globals = {};
8
8
  const rest = [];
9
+ let commandSeen = false;
9
10
  for (let index = 0; index < argv.length; index += 1) {
10
11
  const arg = argv[index];
12
+ if (commandSeen) {
13
+ rest.push(arg);
14
+ continue;
15
+ }
11
16
  if (arg === "-h") {
12
17
  globals.help = true;
13
18
  continue;
14
19
  }
15
20
  if (!arg.startsWith("--")) {
16
21
  rest.push(arg);
22
+ commandSeen = true;
17
23
  continue;
18
24
  }
19
25
 
@@ -48,7 +54,7 @@ export function parseCommandOptions(argv, spec = {}) {
48
54
 
49
55
  for (let index = 0; index < argv.length; index += 1) {
50
56
  const arg = argv[index];
51
- if (arg === "-h") {
57
+ if (arg === "-h" || arg === "--help") {
52
58
  options.help = true;
53
59
  continue;
54
60
  }