@bigking67/pi-67 0.10.3 → 0.10.5

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 installCommand(ctx, argv) {
13
13
  strings: ["repo", "branch"],
14
14
  bools: ["dry-run", "yes", "repair"],
15
15
  });
16
+ if (options.help) {
17
+ printInstallHelp();
18
+ return;
19
+ }
16
20
  const dryRun = ctx.dryRun || options.dryRun;
17
21
  const repo = options.repo || DEFAULT_REPO_URL;
18
22
 
@@ -46,3 +50,21 @@ export async function installCommand(ctx, argv) {
46
50
  if (!dryRun) writeState(ctx, "install");
47
51
  info("Install finished. Run `pi-67 doctor` next.");
48
52
  }
53
+
54
+ function printInstallHelp() {
55
+ process.stdout.write(`pi-67 install - clone/install pi-67 safely
56
+
57
+ Usage:
58
+ pi-67 install [--repo URL] [--branch NAME] [--dry-run] [--repair]
59
+
60
+ Options:
61
+ --repo URL Git repository URL. Defaults to the pi-67 upstream repo.
62
+ --branch NAME Clone a specific branch.
63
+ --dry-run Print planned writes without changing files.
64
+ --repair Force owned asset repair during the installer update phase.
65
+
66
+ Examples:
67
+ pi-67 install
68
+ pi-67 install --dry-run
69
+ `);
70
+ }
@@ -7,6 +7,10 @@ export async function manifestCommand(ctx, argv) {
7
7
  const { options } = parseCommandOptions(argv, {
8
8
  bools: ["json", "validate"],
9
9
  });
10
+ if (options.help) {
11
+ printManifestHelp();
12
+ return;
13
+ }
10
14
  const manifest = buildDistroManifest(ctx);
11
15
  const validation = validateExtensionRegistry(manifest.extensionRegistry, { manifest });
12
16
  if (ctx.json || options.json) {
@@ -72,3 +76,20 @@ function printValidation(validation) {
72
76
  }
73
77
  for (const warning of validation.warnings) warn(warning);
74
78
  }
79
+
80
+ function printManifestHelp() {
81
+ process.stdout.write(`pi-67 manifest - show managed ownership policy
82
+
83
+ Usage:
84
+ pi-67 manifest [--json]
85
+ pi-67 manifest --validate [--json]
86
+
87
+ Options:
88
+ --validate Validate the extension registry against the distro manifest.
89
+ --json Emit machine-readable manifest or validation JSON.
90
+
91
+ Examples:
92
+ pi-67 manifest
93
+ pi-67 manifest --validate
94
+ `);
95
+ }
@@ -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");
@@ -123,7 +135,9 @@ function printPlan(plan) {
123
135
  if (plan.actions?.length > 0) {
124
136
  section("Planned safe actions");
125
137
  for (const action of plan.actions) {
126
- info(`${action.id}: ${action.operation}; writes=${action.writes.join(", ")}; preserves=${action.preserves.join(", ")}`);
138
+ const writes = action.writes?.length ? action.writes.join(", ") : "none";
139
+ const suffix = action.backupCondition ? `; backup=${action.backupCondition}` : "";
140
+ info(`${action.id}: ${action.operation}; writes=${writes}; preserves=${action.preserves.join(", ")}${suffix}`);
127
141
  }
128
142
  }
129
143
  if (plan.blocked?.length > 0) {
@@ -138,3 +152,38 @@ function printPlan(plan) {
138
152
  for (const item of plan.recommendations) info(item);
139
153
  if (!plan.git?.dirty) pass("update check completed without local dirty blocker");
140
154
  }
155
+
156
+ function printUpdateHelp() {
157
+ process.stdout.write(`pi-67 update - update pi-67 safely
158
+
159
+ Usage:
160
+ pi-67 update [--repair] [--dry-run] [--no-remote] [--no-npm]
161
+ pi-67 update --check [--json] [--no-remote]
162
+
163
+ Options:
164
+ --check Print the read-only update plan and exit.
165
+ --repair Re-run owned asset/config repair during update.
166
+ --dry-run Print planned script actions without changing files.
167
+ --no-remote Skip remote git/npm registry checks where supported.
168
+ --no-npm Skip npm package sync in the distro updater.
169
+ --allow-dirty Let the script-level updater handle non-runtime dirty files.
170
+ --strict-shared-skills Treat differing shared skills as blocking.
171
+ --include-pi Also run upstream \`pi update --all\` when paired with --yes.
172
+ --include-external Report explicit external repo update commands.
173
+ --all Alias for --include-pi --include-external.
174
+ --yes Confirm explicit opt-in actions.
175
+ --json Emit JSON for --check.
176
+
177
+ Safety:
178
+ Runtime config backup/restore is owned by the platform updater script when
179
+ dirty preserved runtime files overlap incoming changed paths. The npm manager
180
+ owns the update lock and never creates a runtime backup for --help, a blocked
181
+ update plan, or an already-up-to-date update.
182
+
183
+ Examples:
184
+ pi-67 update --check
185
+ pi-67 update --check --json --no-remote
186
+ pi-67 update
187
+ pi-67 update --repair
188
+ `);
189
+ }
@@ -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
+ }