@pipod/cli 0.1.5 → 0.1.7

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/cli.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-GNKF75X7.js";
7
7
 
8
8
  // src/cli.ts
9
- import * as fs35 from "fs";
10
- import * as path33 from "path";
9
+ import * as fs36 from "fs";
10
+ import * as path34 from "path";
11
11
 
12
12
  // src/errors.ts
13
13
  var EXIT = {
@@ -4436,7 +4436,7 @@ function parseResourcePath(flag, value) {
4436
4436
  function sameResourceList(requested, echoed) {
4437
4437
  const want = requested ?? [];
4438
4438
  const got = echoed ?? [];
4439
- return want.length === got.length && want.every((path34, index) => got[index] === path34);
4439
+ return want.length === got.length && want.every((path35, index) => got[index] === path35);
4440
4440
  }
4441
4441
 
4442
4442
  // src/account/launch-seed.ts
@@ -6275,7 +6275,7 @@ function splitPiArgs(piArgs) {
6275
6275
  if (key === "model" || key === "thinking") continue;
6276
6276
  const flag = PI_RESOURCE_FLAGS[key];
6277
6277
  if (!flag || !Array.isArray(paths)) continue;
6278
- for (const path34 of paths) managedArgs.push(flag, path34);
6278
+ for (const path35 of paths) managedArgs.push(flag, path35);
6279
6279
  }
6280
6280
  }
6281
6281
  return {
@@ -7002,19 +7002,19 @@ function branchSessionStats(entries, state) {
7002
7002
  function branchUsageTotals(entries) {
7003
7003
  const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
7004
7004
  for (const entry of entries) {
7005
- let usage;
7005
+ let usage2;
7006
7006
  if (entry.type === "message") {
7007
7007
  const message = entry.message;
7008
- if (message.role === "assistant" || message.role === "toolResult") usage = message.usage;
7008
+ if (message.role === "assistant" || message.role === "toolResult") usage2 = message.usage;
7009
7009
  } else if (entry.type === "compaction" || entry.type === "branch_summary") {
7010
- usage = entry.usage;
7010
+ usage2 = entry.usage;
7011
7011
  }
7012
- if (!usage) continue;
7013
- totals.input += usage.input ?? 0;
7014
- totals.output += usage.output ?? 0;
7015
- totals.cacheRead += usage.cacheRead ?? 0;
7016
- totals.cacheWrite += usage.cacheWrite ?? 0;
7017
- totals.cost += usage.cost?.total ?? 0;
7012
+ if (!usage2) continue;
7013
+ totals.input += usage2.input ?? 0;
7014
+ totals.output += usage2.output ?? 0;
7015
+ totals.cacheRead += usage2.cacheRead ?? 0;
7016
+ totals.cacheWrite += usage2.cacheWrite ?? 0;
7017
+ totals.cost += usage2.cost?.total ?? 0;
7018
7018
  }
7019
7019
  return totals;
7020
7020
  }
@@ -8632,11 +8632,11 @@ function extractAtPrefix(text) {
8632
8632
  const tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
8633
8633
  return text[tokenStart] === "@" ? text.slice(tokenStart) : null;
8634
8634
  }
8635
- function buildCompletionValue(path34, isQuotedPrefix) {
8636
- return isQuotedPrefix || path34.includes(" ") ? `@"${path34}"` : `@${path34}`;
8635
+ function buildCompletionValue(path35, isQuotedPrefix) {
8636
+ return isQuotedPrefix || path35.includes(" ") ? `@"${path35}"` : `@${path35}`;
8637
8637
  }
8638
- function basename7(path34) {
8639
- const trimmed = path34.endsWith("/") ? path34.slice(0, -1) : path34;
8638
+ function basename7(path35) {
8639
+ const trimmed = path35.endsWith("/") ? path35.slice(0, -1) : path35;
8640
8640
  const cut = trimmed.lastIndexOf("/");
8641
8641
  return cut === -1 ? trimmed : trimmed.slice(cut + 1);
8642
8642
  }
@@ -16560,8 +16560,8 @@ async function allJobs(client) {
16560
16560
  beforeId = last.id;
16561
16561
  }
16562
16562
  }
16563
- function assertArgCount(args, expected, usage) {
16564
- if (args.length !== expected) throw new PiPodError(`usage: ${usage}`);
16563
+ function assertArgCount(args, expected, usage2) {
16564
+ if (args.length !== expected) throw new PiPodError(`usage: ${usage2}`);
16565
16565
  }
16566
16566
  function shortSchedule(trigger) {
16567
16567
  if (trigger.type === "cron") return trigger.cron;
@@ -16747,8 +16747,8 @@ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16747
16747
  var CHANGE_USAGE = "pipod billing change --plan standard|pro [--yes]";
16748
16748
  var CHANGE_PREVIEW_USAGE = "pipod billing change preview --plan standard|pro";
16749
16749
  var CHANGE_CONFIRM_USAGE = "pipod billing change confirm --quote <quoteId> [--yes]";
16750
- function usageError(message, usage) {
16751
- return new PiPodError(message, { hint: `usage: ${usage}`, exitCode: EXIT.USAGE });
16750
+ function usageError(message, usage2) {
16751
+ return new PiPodError(message, { hint: `usage: ${usage2}`, exitCode: EXIT.USAGE });
16752
16752
  }
16753
16753
  function parseChangeArgs(args) {
16754
16754
  let form = "flow";
@@ -17945,11 +17945,11 @@ function formatStatus(status) {
17945
17945
  const expires = status.state === "ready" && status.expiresAt ? ` (expires ${status.expiresAt})` : "";
17946
17946
  return `${status.providerId} ${status.state.replaceAll("_", " ")}${expires}`;
17947
17947
  }
17948
- function rejectExtra(args, usage) {
17949
- if (args.length > 0) throw new PiPodError(usage);
17948
+ function rejectExtra(args, usage2) {
17949
+ if (args.length > 0) throw new PiPodError(usage2);
17950
17950
  }
17951
- function rejectProviderArgs(args, usage) {
17952
- if (args.length !== 1 || args[0].startsWith("-")) throw new PiPodError(usage);
17951
+ function rejectProviderArgs(args, usage2) {
17952
+ if (args.length !== 1 || args[0].startsWith("-")) throw new PiPodError(usage2);
17953
17953
  }
17954
17954
  function terminalInteraction() {
17955
17955
  const controller = new AbortController();
@@ -18533,6 +18533,11 @@ async function showCmd(client, args) {
18533
18533
  info("bake script:");
18534
18534
  out(t.bakeScript);
18535
18535
  }
18536
+ if (t.agentInstructions) {
18537
+ out("");
18538
+ info("agent instructions:");
18539
+ out(t.agentInstructions);
18540
+ }
18536
18541
  if (t.piSettings && Object.keys(t.piSettings).length > 0) {
18537
18542
  info(`Pi settings ${describePiSettings(flatPiSettings(t.piSettings))}`);
18538
18543
  }
@@ -18611,6 +18616,12 @@ function parseOpts(args) {
18611
18616
  opts.bakeScript = fs30.readFileSync(file, "utf8");
18612
18617
  break;
18613
18618
  }
18619
+ case "--agent-instructions": {
18620
+ const file = next();
18621
+ if (!fs30.existsSync(file)) throw new PiPodError(`no such agent instructions file: ${file}`);
18622
+ opts.agentInstructions = fs30.readFileSync(file, "utf8");
18623
+ break;
18624
+ }
18614
18625
  default:
18615
18626
  rest.push(arg);
18616
18627
  }
@@ -18664,6 +18675,7 @@ async function createCmd(client, args, flags) {
18664
18675
  ...opts.description ? { description: opts.description } : {},
18665
18676
  ...initScript ? { initScript } : {},
18666
18677
  ...bakeScript ? { bakeScript } : {},
18678
+ ...opts.agentInstructions ? { agentInstructions: opts.agentInstructions } : {},
18667
18679
  // An explicit refresh is replacement, not merge: an empty project file set clears stale custody.
18668
18680
  ...opts.fromHere ? { piSettings: piSettings ?? {} } : {}
18669
18681
  });
@@ -18770,9 +18782,9 @@ async function editCmd(client, args) {
18770
18782
  });
18771
18783
  }
18772
18784
  const t = await findTemplate(client, rest[0]);
18773
- if (opts.name === void 0 && opts.description === void 0 && !opts.config && opts.initScript === void 0 && opts.bakeScript === void 0) {
18785
+ if (opts.name === void 0 && opts.description === void 0 && !opts.config && opts.initScript === void 0 && opts.bakeScript === void 0 && opts.agentInstructions === void 0) {
18774
18786
  throw new PiPodError("nothing to change", {
18775
- hint: "pass --name, --description, --config <file>, --init-script <file> or --bake-script <file>"
18787
+ hint: "pass --name, --description, --config <file>, --init-script <file>, --bake-script <file> or --agent-instructions <file>"
18776
18788
  });
18777
18789
  }
18778
18790
  const updated = await client.updateTemplate(t.id, {
@@ -18783,7 +18795,8 @@ async function editCmd(client, args) {
18783
18795
  ...opts.description !== void 0 ? { description: opts.description } : {},
18784
18796
  ...opts.config ? { config: opts.config } : {},
18785
18797
  ...opts.initScript !== void 0 ? { initScript: opts.initScript } : {},
18786
- ...opts.bakeScript !== void 0 ? { bakeScript: opts.bakeScript } : {}
18798
+ ...opts.bakeScript !== void 0 ? { bakeScript: opts.bakeScript } : {},
18799
+ ...opts.agentInstructions !== void 0 ? { agentInstructions: opts.agentInstructions } : {}
18787
18800
  });
18788
18801
  info(`updated template ${updated.name}`);
18789
18802
  await hintMissingCredentials(client);
@@ -19175,9 +19188,111 @@ function setDotPath(target, dotPath, value) {
19175
19188
  cursor[parts.at(-1)] = value;
19176
19189
  }
19177
19190
 
19178
- // src/scaffold.ts
19191
+ // src/manual.ts
19179
19192
  import * as fs33 from "fs";
19180
19193
  import * as path31 from "path";
19194
+ var MAIN_PAGE = "pipod.1.md";
19195
+ var FENCE = "```";
19196
+ function usage(command) {
19197
+ const entry = command === null ? section(mainPage(), 2, "SYNOPSIS") : commandEntry(command);
19198
+ if (entry === null) throw new PiPodError(`the pipod manual has no entry for ${command}`);
19199
+ const block = openingBlock(entry);
19200
+ const more = command !== null && hasMoreThanBlock(entry) ? `
19201
+ More: pipod help ${command}
19202
+ ` : "";
19203
+ return `${block}${more}`;
19204
+ }
19205
+ function manualEntry(name) {
19206
+ const entry = commandEntry(name) ?? topicSection(name) ?? topicPage(name);
19207
+ if (entry === null) {
19208
+ throw new PiPodError(`no manual entry for "${name}"`, {
19209
+ hint: "`pipod help` lists the commands and topics",
19210
+ exitCode: EXIT.USAGE
19211
+ });
19212
+ }
19213
+ return plainText(entry);
19214
+ }
19215
+ function commandEntry(command) {
19216
+ const commands = section(mainPage(), 2, "COMMANDS");
19217
+ return commands === null ? null : section(commands, 3, `pipod ${command}`);
19218
+ }
19219
+ function topicSection(topic) {
19220
+ return /^[a-z]+$/.test(topic) ? section(mainPage(), 2, topic.toUpperCase()) : null;
19221
+ }
19222
+ function topicPage(topic) {
19223
+ const prefix = `pipod-${topic}.`;
19224
+ const file = fs33.readdirSync(manDir()).find(
19225
+ (name) => name.startsWith(prefix) && /^\d+\.md$/.test(name.slice(prefix.length))
19226
+ );
19227
+ return file === void 0 ? null : read(path31.join(manDir(), file));
19228
+ }
19229
+ function section(markdown, level, title) {
19230
+ const lines = markdown.split("\n");
19231
+ const heading = `${"#".repeat(level)} ${title}`;
19232
+ let start = -1;
19233
+ let fenced = false;
19234
+ for (let i = 0; i < lines.length; i++) {
19235
+ const line = lines[i];
19236
+ if (line.startsWith(FENCE)) fenced = !fenced;
19237
+ if (fenced) continue;
19238
+ if (start < 0) {
19239
+ if (line === heading) start = i;
19240
+ continue;
19241
+ }
19242
+ const depth2 = /^(#+) /.exec(line)?.[1]?.length;
19243
+ if (depth2 !== void 0 && depth2 <= level) return `${lines.slice(start, i).join("\n").trimEnd()}
19244
+ `;
19245
+ }
19246
+ return start < 0 ? null : `${lines.slice(start).join("\n").trimEnd()}
19247
+ `;
19248
+ }
19249
+ function openingBlock(entry) {
19250
+ const lines = entry.split("\n");
19251
+ const open = lines.findIndex((line) => line.startsWith(FENCE));
19252
+ const close = lines.findIndex((line, i) => i > open && line.startsWith(FENCE));
19253
+ if (open < 0 || close < 0) throw new PiPodError("a pipod manual entry does not open with a usage block");
19254
+ return `${lines.slice(open + 1, close).join("\n")}
19255
+ `;
19256
+ }
19257
+ function hasMoreThanBlock(entry) {
19258
+ const lines = entry.split("\n");
19259
+ const fences = lines.flatMap((line, i) => line.startsWith(FENCE) ? [i] : []);
19260
+ return lines.slice((fences[1] ?? lines.length) + 1).some((line) => line.trim() !== "");
19261
+ }
19262
+ function plainText(markdown) {
19263
+ const out2 = [];
19264
+ let fenced = false;
19265
+ for (const line of markdown.split("\n")) {
19266
+ if (line.startsWith(FENCE)) {
19267
+ fenced = !fenced;
19268
+ continue;
19269
+ }
19270
+ if (fenced) out2.push(line === "" ? "" : ` ${line}`);
19271
+ else out2.push(line.replace(/^#+ /, ""));
19272
+ }
19273
+ return `${out2.join("\n").trimEnd()}
19274
+ `;
19275
+ }
19276
+ function mainPage() {
19277
+ return read(path31.join(manDir(), MAIN_PAGE));
19278
+ }
19279
+ function manDir() {
19280
+ return path31.join(packageRoot(), "man");
19281
+ }
19282
+ function read(file) {
19283
+ try {
19284
+ return fs33.readFileSync(file, "utf8");
19285
+ } catch (cause) {
19286
+ throw new PiPodError(`the pipod manual is missing from this install (${file})`, {
19287
+ hint: "reinstall pipod: `npm install -g @pipod/cli`, or `npm run build` in a checkout",
19288
+ cause
19289
+ });
19290
+ }
19291
+ }
19292
+
19293
+ // src/scaffold.ts
19294
+ import * as fs34 from "fs";
19295
+ import * as path32 from "path";
19181
19296
  function configTemplate() {
19182
19297
  return `{
19183
19298
  // .pi-pod/config.json \u2014 optional server-side launch overrides for this project.
@@ -19219,8 +19334,8 @@ set -euo pipefail
19219
19334
  echo "init complete"
19220
19335
  `;
19221
19336
  function scaffold(opts) {
19222
- const dir = path31.join(opts.projectRoot, CONFIG_DIR);
19223
- fs33.mkdirSync(dir, { recursive: true });
19337
+ const dir = path32.join(opts.projectRoot, CONFIG_DIR);
19338
+ fs34.mkdirSync(dir, { recursive: true });
19224
19339
  const files = [
19225
19340
  { name: "config.json", contents: configTemplate() },
19226
19341
  { name: "env.example", contents: ENV_EXAMPLE_TEMPLATE },
@@ -19229,32 +19344,32 @@ function scaffold(opts) {
19229
19344
  const created = [];
19230
19345
  const skipped = [];
19231
19346
  for (const file of files) {
19232
- const target = path31.join(dir, file.name);
19233
- if (fs33.existsSync(target) && !opts.force) {
19234
- skipped.push(path31.join(CONFIG_DIR, file.name));
19347
+ const target = path32.join(dir, file.name);
19348
+ if (fs34.existsSync(target) && !opts.force) {
19349
+ skipped.push(path32.join(CONFIG_DIR, file.name));
19235
19350
  continue;
19236
19351
  }
19237
- fs33.writeFileSync(target, file.contents, file.mode ? { mode: file.mode } : void 0);
19238
- if (file.mode) fs33.chmodSync(target, file.mode);
19239
- created.push(path31.join(CONFIG_DIR, file.name));
19352
+ fs34.writeFileSync(target, file.contents, file.mode ? { mode: file.mode } : void 0);
19353
+ if (file.mode) fs34.chmodSync(target, file.mode);
19354
+ created.push(path32.join(CONFIG_DIR, file.name));
19240
19355
  }
19241
19356
  const gitignoreUpdated = ensureGitignoreEntry(opts.projectRoot, `${CONFIG_DIR}/env`);
19242
- const envPath = path31.join(dir, "env");
19243
- const envCreated = !fs33.existsSync(envPath);
19357
+ const envPath = path32.join(dir, "env");
19358
+ const envCreated = !fs34.existsSync(envPath);
19244
19359
  if (envCreated) {
19245
- fs33.writeFileSync(envPath, ENV_TEMPLATE, { mode: 384 });
19246
- fs33.chmodSync(envPath, 384);
19247
- created.push(path31.join(CONFIG_DIR, "env"));
19360
+ fs34.writeFileSync(envPath, ENV_TEMPLATE, { mode: 384 });
19361
+ fs34.chmodSync(envPath, 384);
19362
+ created.push(path32.join(CONFIG_DIR, "env"));
19248
19363
  }
19249
19364
  return { created, skipped, gitignoreUpdated, envCreated };
19250
19365
  }
19251
19366
  function ensureGitignoreEntry(projectRoot, entry) {
19252
- const gitignorePath = path31.join(projectRoot, ".gitignore");
19253
- const existing = fs33.existsSync(gitignorePath) ? fs33.readFileSync(gitignorePath, "utf8") : "";
19367
+ const gitignorePath = path32.join(projectRoot, ".gitignore");
19368
+ const existing = fs34.existsSync(gitignorePath) ? fs34.readFileSync(gitignorePath, "utf8") : "";
19254
19369
  const alreadyPresent = existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === entry || line === `/${entry}`);
19255
19370
  if (alreadyPresent) return false;
19256
19371
  const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
19257
- fs33.appendFileSync(gitignorePath, `${prefix}
19372
+ fs34.appendFileSync(gitignorePath, `${prefix}
19258
19373
  # pi pod secrets \u2014 never commit
19259
19374
  ${entry}
19260
19375
  `);
@@ -19268,7 +19383,7 @@ function printInitResult(result, projectRoot) {
19268
19383
  const steps = [
19269
19384
  [`edit ${CONFIG_DIR}/config.json and init.sh`, "what this project's pods get"],
19270
19385
  [`edit ${CONFIG_DIR}/env`, "this project's own keys"],
19271
- ["pipod", `launch a pod for ${path31.basename(projectRoot)}; the first launch offers to keep these in a template`]
19386
+ ["pipod", `launch a pod for ${path32.basename(projectRoot)}; the first launch offers to keep these in a template`]
19272
19387
  ].map(([command, why]) => `${command.padEnd(40)} ${color.dim(`# ${why}`)}`);
19273
19388
  plain("");
19274
19389
  plain("Next steps:");
@@ -19278,8 +19393,8 @@ function printInitResult(result, projectRoot) {
19278
19393
 
19279
19394
  // src/update.ts
19280
19395
  import { spawnSync as spawnSync3 } from "child_process";
19281
- import * as fs34 from "fs";
19282
- import * as path32 from "path";
19396
+ import * as fs35 from "fs";
19397
+ import * as path33 from "path";
19283
19398
  function run(cmd, args, cwd) {
19284
19399
  const r = spawnSync3(cmd, args, { cwd, encoding: "utf8" });
19285
19400
  return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
@@ -19288,13 +19403,13 @@ function runVisible(cmd, args, cwd) {
19288
19403
  return spawnSync3(cmd, args, { cwd, stdio: "inherit" }).status === 0;
19289
19404
  }
19290
19405
  function detectInstall(root = packageRoot()) {
19291
- if (fs34.existsSync(path32.join(root, ".git"))) return { kind: "git", root };
19292
- if (!root.split(path32.sep).includes("node_modules") && run("git", ["rev-parse", "--is-inside-work-tree"], root).out === "true") {
19406
+ if (fs35.existsSync(path33.join(root, ".git"))) return { kind: "git", root };
19407
+ if (!root.split(path33.sep).includes("node_modules") && run("git", ["rev-parse", "--is-inside-work-tree"], root).out === "true") {
19293
19408
  return { kind: "git", root };
19294
19409
  }
19295
- if (root.split(path32.sep).includes("node_modules")) {
19410
+ if (root.split(path33.sep).includes("node_modules")) {
19296
19411
  try {
19297
- const pkg = JSON.parse(fs34.readFileSync(path32.join(root, "package.json"), "utf8"));
19412
+ const pkg = JSON.parse(fs35.readFileSync(path33.join(root, "package.json"), "utf8"));
19298
19413
  if (pkg.name) return { kind: "npm", root, name: pkg.name };
19299
19414
  } catch {
19300
19415
  }
@@ -19303,7 +19418,7 @@ function detectInstall(root = packageRoot()) {
19303
19418
  }
19304
19419
  function versionAt(root) {
19305
19420
  try {
19306
- const pkg = JSON.parse(fs34.readFileSync(path32.join(root, "package.json"), "utf8"));
19421
+ const pkg = JSON.parse(fs35.readFileSync(path33.join(root, "package.json"), "utf8"));
19307
19422
  return pkg.version ?? "0.0.0";
19308
19423
  } catch {
19309
19424
  return "0.0.0";
@@ -19378,16 +19493,16 @@ async function updateCheckout(root, opts) {
19378
19493
  return rebuild(root, before);
19379
19494
  }
19380
19495
  function distIsStale(root) {
19381
- const srcDir = path32.join(root, "src");
19382
- if (!fs34.existsSync(srcDir)) return false;
19383
- const dist = path32.join(root, "dist", "cli.js");
19384
- if (!fs34.existsSync(dist)) return true;
19385
- const distTime = fs34.statSync(dist).mtimeMs;
19496
+ const srcDir = path33.join(root, "src");
19497
+ if (!fs35.existsSync(srcDir)) return false;
19498
+ const dist = path33.join(root, "dist", "cli.js");
19499
+ if (!fs35.existsSync(dist)) return true;
19500
+ const distTime = fs35.statSync(dist).mtimeMs;
19386
19501
  const newest = (dir) => {
19387
19502
  let max = 0;
19388
- for (const entry of fs34.readdirSync(dir, { withFileTypes: true })) {
19389
- const p = path32.join(dir, entry.name);
19390
- max = Math.max(max, entry.isDirectory() ? newest(p) : fs34.statSync(p).mtimeMs);
19503
+ for (const entry of fs35.readdirSync(dir, { withFileTypes: true })) {
19504
+ const p = path33.join(dir, entry.name);
19505
+ max = Math.max(max, entry.isDirectory() ? newest(p) : fs35.statSync(p).mtimeMs);
19391
19506
  }
19392
19507
  return max;
19393
19508
  };
@@ -19408,8 +19523,8 @@ function rebuild(root, before) {
19408
19523
  const head = run("git", ["rev-parse", "--short", "HEAD"], root).out;
19409
19524
  plain("");
19410
19525
  info(after === before ? `now at ${color.bold(after)} (${head})` : `updated ${before} \u2192 ${color.bold(after)} (${head})`);
19411
- const upgrade = path32.join(run("git", ["rev-parse", "--show-toplevel"], root).out, "selfhost", "upgrade");
19412
- if (fs34.existsSync(upgrade)) out(`if this checkout runs your server, upgrade it to match: ${upgrade}`);
19526
+ const upgrade = path33.join(run("git", ["rev-parse", "--show-toplevel"], root).out, "selfhost", "upgrade");
19527
+ if (fs35.existsSync(upgrade)) out(`if this checkout runs your server, upgrade it to match: ${upgrade}`);
19413
19528
  out("run `pipod doctor` to check the new version against this repo");
19414
19529
  }
19415
19530
  async function updateFromRegistry(root, name, opts) {
@@ -19477,461 +19592,6 @@ var ALIASES = {
19477
19592
  a: "attach",
19478
19593
  ls: "list"
19479
19594
  };
19480
- var HELP = `pipod ${VERSION} \u2014 run pi sessions in server-managed pods
19481
-
19482
- Usage:
19483
- pipod [options] launch a pod for this project and start pi
19484
- pipod [options] -- <pi args\u2026> launch and pass arguments to pi: prompt words,
19485
- --model <id>, --thinking <level>, --tui-mode <mode>,
19486
- -e/--extension <path>, --skill <path>,
19487
- --prompt-template <path> (each repeatable)
19488
- pipod <command> [options] run a command
19489
- pipod help [command] show help (same as --help)
19490
-
19491
- Sign in first with \`pipod login\`. A pod-scoped PI_POD_SERVER_TOKEN also counts as
19492
- signed in. While signed out, only login, whoami, init, doctor, update, help, and
19493
- version are available.
19494
-
19495
- Pod commands:
19496
- list [pod] List pods; \`list <pod>\` shows one machine's group (ls)
19497
- attach [pod] Reconnect to a pod session (a)
19498
- stop [pod\u2026] Stop pods now (files kept; attach restarts them)
19499
- fork <pod> Launch a new pod forked from a pod's session
19500
- rename <pod> <name> Rename a pod
19501
- send [pod] <path> Copy host files into a pod
19502
- receive [pod] <path> Copy pod files into the current directory
19503
- archive [pod\u2026] Hide pods from the list (files kept; restore brings them back)
19504
- restore [pod\u2026] Bring archived pods back
19505
- gc Report retention state; --delete removes reclaimable pods
19506
-
19507
- Account commands:
19508
- login Sign in to a pi pod server via Zitadel
19509
- logout Revoke this session and delete the stored credentials
19510
- whoami Show the signed-in account
19511
- account Open the Zitadel self-service console
19512
- org-admin Open the Zitadel Console for this organization
19513
- billing Open Stripe Checkout or the billing portal (authenticated)
19514
- credentials Connect account model-provider sign-ins
19515
-
19516
- Configuration commands:
19517
- init Scaffold .pi-pod/ config, env, env.example, and init.sh
19518
- templates Manage account templates
19519
- push Upload a local config layer to its server bundle
19520
- pull Write a server bundle into its local config layer
19521
- diff Show local vs server drift for a config layer
19522
- secrets Manage server and project secret scopes
19523
- settings Show or edit one server settings config directly
19524
- jobs Manage server-scheduled jobs
19525
-
19526
- Maintenance commands:
19527
- doctor Check auth, server, org, and launch-resolution layers
19528
- update Upgrade pi pod itself
19529
-
19530
- Launch options:
19531
- --template <name|id> Launch from an account template
19532
- --on <pod|self> Co-locate the new pod on an existing pod's machine
19533
- --reuse Restart this project's newest stopped pod instead of creating one
19534
- --no-seed Start the pod with an empty workspace (skip workspace seeding)
19535
- --dry-run Resolve and print the launch plan; create nothing
19536
-
19537
- General options:
19538
- -y, --yes Skip supported confirmations
19539
- -v, --verbose Debug logging (secrets redacted)
19540
- --version Print CLI, Node, and bundled Pi versions
19541
- -h, --help Show help
19542
-
19543
- Settings resolve on the server: built-in \u2192 org \u2192 org-template \u2192 user \u2192 user-template
19544
- \u2192 org policy. Each bundle has a local directory as its source of truth (see
19545
- \`pipod push --help\`); local project settings apply after \`pipod push\`, and the
19546
- first launch in a project offers to bootstrap a template.
19547
-
19548
- Per-launch Pi resources (--extension, --skill, --prompt-template) name files or
19549
- directories inside the pod as absolute paths and add to the pod's configured Pi
19550
- resources for that launch; anything else Pi-related belongs in the pi.args config.
19551
-
19552
- Workspace seeding: launching from a directory with no .pi-pod/config.json seeds the
19553
- new pod from that directory (its git repository root when inside one). A clean,
19554
- pushed checkout whose remote tip matches HEAD is cloned at that exact commit;
19555
- anything else \u2014 dirty trees, unpushed commits, private remotes without usable
19556
- credentials, plain directories \u2014 is streamed in as a tar archive (git-ignored
19557
- paths, node_modules and .pi-pod/env excluded). Private-remote credentials are
19558
- forwarded only after a prompt (\`--yes\` approves it) and are never stored.
19559
- \`--dry-run\` prints the decision without prompting or creating a pod. Forks,
19560
- reused pods, \`--on\` co-location and \`--no-seed\` never seed.
19561
-
19562
- Run \`pipod <command> --help\` for command-specific usage.
19563
- `;
19564
- var SUBCOMMAND_HELP = {
19565
- login: `Usage: pipod login [--server <url>] [--issuer <url>] [--org <alias>] [--device] [--token <jwt>]
19566
-
19567
- Sign this machine in to a pi pod server via Zitadel (authorization code + PKCE,
19568
- in your browser). Where no browser can open here (over SSH, on a headless
19569
- server), it prints a page and a code to confirm on any other device instead.
19570
- The session is stored in ~/.pi-pod/auth.json.
19571
-
19572
- Options:
19573
- --server <url> Server to sign in to (default: the previous session's server,
19574
- $PI_POD_ACCOUNT_URL, or the production server)
19575
- --issuer <url> OIDC issuer (default: $PI_POD_ISSUER, else the one the
19576
- server publishes); needed only for a server too old to
19577
- publish its own
19578
- --org <alias> Only sign in to the organization with this primary domain;
19579
- omit it to sign in to your own organization
19580
- --device Sign in with a code on another device even if a browser
19581
- could open here
19582
- --token <jwt> Use an existing access token instead of the browser flow (dev/CI)
19583
- `,
19584
- account: `Usage: pipod account
19585
-
19586
- Open the Zitadel self-service console in your browser to manage profile,
19587
- password, MFA, and active sessions.
19588
- `,
19589
- "org-admin": `Usage: pipod org-admin
19590
-
19591
- Open the Zitadel Console in your browser. Organization owners manage members
19592
- and role grants there.
19593
- `,
19594
- billing: `Usage: pipod billing checkout [--plan standard|pro] [--trial]
19595
- pipod billing portal
19596
- pipod billing change --plan standard|pro [--yes]
19597
- pipod billing change preview --plan standard|pro
19598
- pipod billing change confirm --quote <quoteId> [--yes]
19599
-
19600
- Authenticated Stripe Checkout and billing portal. Checkout sends trial:false
19601
- (paid) unless --trial. Closed public trial refuses --trial with 402. Change
19602
- previews a Standard<->Pro plan change and confirms it only after an explicit
19603
- yes; stale quotes are never auto-confirmed. Return
19604
- pages do not grant a plan. Public npm is not a supported install path; use a
19605
- GitHub release tarball or git clone + npm run build.
19606
- `,
19607
- logout: `Usage: pipod logout
19608
-
19609
- Revoke the current account session and delete ~/.pi-pod/auth.json. Pod
19610
- operations are unavailable until the next \`pipod login\`.
19611
- `,
19612
- whoami: `Usage: pipod whoami
19613
-
19614
- Show the signed-in server, user, permissions, active organization, and console
19615
- links. Exits 1 when signed out.
19616
- `,
19617
- init: `Usage: pipod init [--force]
19618
-
19619
- Scaffold project configuration under .pi-pod/ in the current directory:
19620
- config.json, env.example, env (mode 600), and init.sh.
19621
-
19622
- Options:
19623
- --force Overwrite scaffold files that already exist
19624
- `,
19625
- doctor: `Usage: pipod doctor [--template <name|id>]
19626
-
19627
- Check each layer a launch depends on: account auth, server reachability and
19628
- version, organization membership and roles, secret references, and the
19629
- server-resolved settings bundle chain for this directory. Works while signed
19630
- out to diagnose auth problems. Exits non-zero when a layer fails.
19631
-
19632
- Options:
19633
- --template <name|id> Plan the checked launch with this template
19634
- `,
19635
- list: `Usage: pipod list [pod] [--all] [--archived] [--template <name|id>] [--quiet]
19636
-
19637
- Alias: ls
19638
-
19639
- List pods on the signed-in server with stable short refs (p-\u2026). The project is
19640
- the directory's git repository (or the directory, or its .pi-pod/ config): a
19641
- listing shows that project's pods when it has any \u2014 its pinned template's when
19642
- it pins one \u2014 and every pod otherwise. Co-located pods are grouped under the pod
19643
- whose machine they share, with an ON column naming it.
19644
- With a pod argument, show just that machine's group: the pod and everything
19645
- co-located on it.
19646
-
19647
- Status says where a pod's files are: a stopped pod keeps them on disk and restarts on
19648
- attach; an archived workspace is restored on next use, which takes longer the bigger it is.
19649
- Pods you archive stay hidden unless --archived is passed.
19650
-
19651
- Options:
19652
- -a, --all Every pod; ignore the current project and its template pin
19653
- --archived Include logically archived pods
19654
- --template <name|id> Only pods launched from that template (whole account)
19655
- -q, --quiet Print pod ids only, one per line
19656
- `,
19657
- attach: `Usage: pipod attach [pod] [--yes] [-- <prompt\u2026> [--tui-mode <mode>]]
19658
-
19659
- Alias: a
19660
-
19661
- Reconnect to a pod session through the server gateway. Name the pod by id,
19662
- short ref (p-\u2026), or name; omit it when this project has exactly one pod.
19663
- Quitting detaches: the pod keeps running until the server's idle policy reaps
19664
- it or \`pipod stop\` stops it. Raw pod shells are not available.
19665
- Attaching to an archived pod offers to restore it first.
19666
-
19667
- After --:
19668
- <prompt\u2026> Words are sent to the session as a startup prompt
19669
- --tui-mode <mode> Local TUI layout for this attach: regular or fullscreen
19670
-
19671
- Options:
19672
- -y, --yes Restore an archived pod without asking
19673
- `,
19674
- stop: `Usage: pipod stop [pod\u2026]
19675
-
19676
- Stop pods now by id, short ref (p-\u2026), or name; omit the argument when this
19677
- project has exactly one pod. Stopped pods keep their files and \`pipod attach\`
19678
- restarts them. Stopping a pod that hosts co-located pods stops those too.
19679
- `,
19680
- fork: `Usage: pipod fork <pod> [--session <path>] [launch options] [-- <pi args\u2026>]
19681
-
19682
- Launch a new pod whose session starts as a fork of <pod>'s current session.
19683
- <pod> is an id, short ref (p-\u2026), or name. The workspace is not copied \u2014
19684
- commit and push first if the new pod needs those changes. --on is not
19685
- supported: a fork launches a new sandbox, not a co-located pod.
19686
-
19687
- Options:
19688
- --session <path> Fork a specific session file instead of the newest
19689
- --template <name|id> Launch the new pod from an account template
19690
- --dry-run Resolve and print the launch plan; create nothing
19691
- -y, --yes Skip supported confirmations
19692
- `,
19693
- rename: `Usage: pipod rename <pod> <name>
19694
-
19695
- Rename a server-managed pod. <pod> is an id, short ref (p-\u2026), or the current
19696
- name.
19697
- `,
19698
- send: `Usage: pipod send [pod] <path> [--yes]
19699
-
19700
- Copy a host file or directory into a pod through the server. Omit the pod when
19701
- this project has exactly one. Sending to an archived pod offers to restore it
19702
- first.
19703
-
19704
- Options:
19705
- -y, --yes Restore an archived pod without asking
19706
- `,
19707
- receive: `Usage: pipod receive [pod] <path> [--yes]
19708
-
19709
- Copy a file or directory from a pod into the current local directory. <path>
19710
- may be a workdir-relative path or an absolute path inside that workdir, such
19711
- as foo or /workspace/foo. The destination must not already exist. Omit the
19712
- pod when this project has exactly one. Receiving from an archived pod offers
19713
- to restore it first.
19714
-
19715
- Options:
19716
- -y, --yes Restore an archived pod without asking
19717
- `,
19718
- archive: `Usage: pipod archive [pod\u2026] [--all] [--template <name|id>] [--idle <duration>] [--dry-run] [--yes]
19719
-
19720
- Archive pods by id, short ref (p-\u2026), or name. Archiving hides pods from
19721
- \`pipod list\` (\`pipod list --archived\` shows them); it never deletes work, and
19722
- \`pipod restore\` brings them back. Archiving a pod that hosts co-located pods
19723
- archives those too.
19724
-
19725
- Options:
19726
- -a, --all Archive every pod (cannot be combined with pod refs)
19727
- --template <name|id> Only pods launched from that template (whole account)
19728
- --idle <duration> Only pods whose last activity is older than this (4h, 2d, 30m)
19729
- --dry-run Print the matching pods; archive nothing
19730
- -y, --yes Skip the bulk confirmation
19731
- `,
19732
- restore: `Usage: pipod restore [pod\u2026] [--all] [--yes]
19733
-
19734
- Bring archived pods back by id, short ref (p-\u2026), or name. Restoring a host
19735
- restores the co-located pods archived with it. \`pipod attach\` then starts the
19736
- pod; a workspace in cold storage takes longer the bigger it is.
19737
-
19738
- Options:
19739
- -a, --all Restore every archived pod (cannot be combined with pod refs)
19740
- -y, --yes Skip the --all confirmation
19741
- `,
19742
- gc: `Usage: pipod gc [--delete] [--yes]
19743
-
19744
- List the archived and failed pods that can be deleted for good; --delete deletes
19745
- them. (The server stops idle pods and moves stopped workspaces to cold storage on
19746
- its own schedule; that never deletes anything.)
19747
-
19748
- Options:
19749
- --delete Permanently delete the reclaimable pods
19750
- -y, --yes Delete without a confirmation prompt
19751
- `,
19752
- jobs: `Usage: pipod jobs [action] [args] [--quiet] [--yes] [--project] [--org]
19753
-
19754
- Manage scheduled jobs the pi pod server runs for you: at each trigger it
19755
- launches a pod from the job's template and sends the job's prompt. Local job
19756
- files in .pi-pod/jobs/ (project) or ~/.pi-pod/jobs/ (user) are the reviewable
19757
- source; \`jobs push\` promotes them to the server. <job> is an id, short ref
19758
- (j-\u2026), or name.
19759
-
19760
- Actions:
19761
- list List server jobs (default)
19762
- show <job> Show a job's schedule, template, model, and prompt
19763
- runs <job> List a job's recent runs
19764
- run <job> Run an active job once, now; its schedule is unchanged
19765
- activate <job> Activate a job
19766
- pause <job> Pause a job's schedule
19767
- resume <job> Resume a paused job
19768
- rm <job> Remove a job; its schedule stops firing
19769
- push [job\u2026] Create or update server jobs from local job files
19770
- pull <job> Write a server job to a local job file
19771
- diff [job\u2026] Show local vs server drift; exits 1 when drifted
19772
-
19773
- Options:
19774
- --org push: create org-scoped jobs or promote matching user jobs
19775
- (promotion is one-way)
19776
- --project pull: write into this project's .pi-pod/jobs/ instead of
19777
- ~/.pi-pod/jobs/
19778
- -q, --quiet list: print job ids only, one per line
19779
- -y, --yes Skip confirmations; pull: overwrite an existing local file
19780
- `,
19781
- credentials: `Usage: pipod credentials [action]
19782
-
19783
- Manage account-scoped model-provider sign-ins. Connected credentials apply to
19784
- every pod you launch; OAuth refresh tokens stay in server custody and never
19785
- reach pods or this machine.
19786
-
19787
- Actions:
19788
- list Saved credentials and connectable providers (default)
19789
- connect Connect every provider your local pi is signed in to:
19790
- API keys are imported, everything else is a fresh
19791
- account login; ready credentials are left alone
19792
- connect <provider> Sign in to a provider (OAuth by default)
19793
- reconnect <provider> Redo the sign-in for a saved credential
19794
- test <provider> Verify a saved credential against the provider
19795
- remove <provider> [--yes] Delete a saved credential (alias: rm)
19796
-
19797
- connect options:
19798
- --api-key Enter an API key instead of the OAuth flow
19799
- --api-key --from-pi Import the API key your local pi resolves for that
19800
- provider (auth.json or env) instead of typing it.
19801
- OAuth grants are never imported.
19802
- `,
19803
- secrets: `Usage: pipod secrets [action]
19804
-
19805
- Manage the secret layers pods see. Requires account or pod-token auth. Server
19806
- scopes are org, user, and template/<name>; project is this project's dotenv
19807
- file, the highest-precedence layer for launches from the project.
19808
-
19809
- Actions:
19810
- list Every secret a pod launched here would see,
19811
- which layer wins, and who set it (default)
19812
- set <scope> <NAME> Store a secret, its value read from a hidden
19813
- prompt or stdin (never the command line)
19814
- rm <scope> <NAME> Remove a secret (aliases: remove, unset)
19815
- sync <scope> Upload a dotenv file to a server scope
19816
-
19817
- sync options (scope must be org, user, or template/<name>):
19818
- --file <path> Dotenv file to upload (default: ~/.pi-pod/secrets/<scope>.env)
19819
- --prune Also delete server secrets that are not in the file
19820
- --dry-run Show what would change without writing
19821
-
19822
- Values may be op:// references; they resolve just-in-time (at upload for
19823
- server scopes, at launch for the project file) and the references themselves
19824
- are never stored on the server.
19825
- `,
19826
- templates: `Usage: pipod templates [action]
19827
-
19828
- Manage pod templates on the pi pod server. A template stores config, init and
19829
- bake scripts, Pi settings files, and named secrets. Templates are personal by
19830
- default: only their owner sees and launches them. --org at creation or
19831
- \`templates share\` makes one org-wide.
19832
-
19833
- Actions:
19834
- list List templates with scope and key counts (default)
19835
- show <name|id> Show config, scripts, Pi settings, and secret names
19836
- create <name> Create a template
19837
- edit <name|id> Update a template's name, description, config, or scripts
19838
- share <name|id> Make a personal template org-wide (cannot be undone)
19839
- rm <name|id> Delete a template (alias: delete)
19840
-
19841
- create/edit options:
19842
- --name <name> New name (edit only)
19843
- --description <text> Set the description
19844
- --config <file> Store this config file (same shape as .pi-pod/config.json)
19845
- --from-here create only: store this project's authored config,
19846
- scripts, and sanitized Pi files (contradicts --config)
19847
- --init-script <file> Store this init script
19848
- --bake-script <file> Store this bake script
19849
- --org create only: make the template org-wide from the start
19850
- --with-secrets With --from-here: also upload project env values as
19851
- template secrets
19852
- -y, --yes Skip confirmations (rm)
19853
- `,
19854
- push: `Usage: pipod push [template [<name>] | user | org] [--dir <path>]
19855
- [--with-secrets] [--yes]
19856
-
19857
- Replace a server config bundle with its local source, whole. The layer names
19858
- both sides; edit the local files with any editor, then push. A diff preview
19859
- precedes the confirmation.
19860
-
19861
- Layers and their local source:
19862
- template [<name>] This project: .pi-pod/config.json, its init and bake
19863
- scripts, Pi files under .pi/, and the project env file
19864
- (secrets, with --with-secrets or a prompt). Without
19865
- <name> the project's pinned template is used.
19866
- user ~/.pi-pod/config.json and ~/.pi/agent/ (settings.json,
19867
- models.json, mcporter.json, subagents.json, agents/)
19868
- org ~/.pi-pod/org/: config.json, init.sh, bake.sh, pi/\u2026, and
19869
- an optional env file whose values become org secrets
19870
-
19871
- Inside a project the layer defaults to template; elsewhere it is required.
19872
- Provider credentials, hooks, mcpServers, and model credential values never
19873
- leave this machine.
19874
-
19875
- Options:
19876
- --dir <path> org only: read the org source from <path> instead
19877
- --with-secrets Also upload the layer's env values as secrets
19878
- -y, --yes Apply the replacement without the confirmation prompt
19879
- `,
19880
- pull: `Usage: pipod pull [template [<name>] | user | org] [--dir <path>] [--yes]
19881
-
19882
- Write a server config bundle into its local source files (see
19883
- \`pipod push --help\` for the layer \u2192 directory table). A diff preview
19884
- precedes the confirmation; nothing is written when the two already match.
19885
-
19886
- What pull keeps from the local files:
19887
- config.json client-only keys: $schema, template, secretResolver,
19888
- pi.chords, pi.sessionNaming
19889
- settings.json hooks and mcpServers (and packages when hostConfig.packages
19890
- is off)
19891
- models.json credential values the push had dropped
19892
- agents/ files the bundle no longer contains are removed
19893
-
19894
- Secrets stay on the server; their names are listed. The user layer has no
19895
- local script files, so user bundle scripts are shown but not written.
19896
-
19897
- Options:
19898
- --dir <path> org only: write the org source to <path> instead
19899
- -y, --yes Apply the local changes without the confirmation prompt
19900
- `,
19901
- diff: `Usage: pipod diff [template [<name>] | user | org] [--dir <path>]
19902
-
19903
- Compare a config layer's local source with its server bundle (see
19904
- \`pipod push --help\` for the layer \u2192 directory table). Secrets compare by
19905
- name only. Exits 1 when they differ, 0 when they match.
19906
-
19907
- Options:
19908
- --dir <path> org only: read the org source from <path> instead
19909
- `,
19910
- settings: `Usage: pipod settings <user|org> [show | edit | set <key> <value> | unset <key>]
19911
-
19912
- Show or edit a persistent settings bundle on the server: your own user bundle,
19913
- or the org-wide defaults (org scope needs the org:manage permission). Edits
19914
- change only the config; scripts and Pi files stay as they are \u2014 replace the
19915
- whole bundle from local files with \`pipod push user\` or \`pipod push org\`.
19916
- Edits use compare-and-swap on the bundle version, so concurrent changes fail
19917
- cleanly instead of overwriting each other.
19918
-
19919
- Actions:
19920
- show Print the settings bundle as JSON (default)
19921
- edit Edit the config in $VISUAL/$EDITOR, then save
19922
- set <key> <value> Set one dotted key to a JSON value, or to the text as
19923
- a string: pipod settings user set pi.model provider/model
19924
- unset <key> Remove one dotted key, so the layers below decide it
19925
- `,
19926
- update: `Usage: pipod update [--dry-run]
19927
-
19928
- Upgrade pi pod itself: a registry install updates to the latest published
19929
- version; a git checkout pulls its upstream and rebuilds.
19930
-
19931
- Options:
19932
- --dry-run Report what an update would do without changing anything
19933
- `
19934
- };
19935
19595
  var SELF_PARSING_SUBCOMMANDS = /* @__PURE__ */ new Set(["credentials", "secrets", "templates", "push", "pull", "diff", "settings", "billing"]);
19936
19596
  var UNIVERSAL_FLAGS = /* @__PURE__ */ new Set(["--verbose", "--version", "--help"]);
19937
19597
  var FLAG_SCOPE = {
@@ -20107,15 +19767,22 @@ function parseArgs(argv) {
20107
19767
  });
20108
19768
  }
20109
19769
  }
20110
- let commandPositionals = positionals;
20111
19770
  if (positionals[0] === "help") {
20112
19771
  flags.help = true;
20113
19772
  seenFlags.add("--help");
20114
- commandPositionals = positionals.slice(1);
19773
+ const name = positionals[1];
19774
+ return {
19775
+ subcommand: null,
19776
+ subcommandArgs: [],
19777
+ flags,
19778
+ piArgs,
19779
+ seenFlags,
19780
+ ...name !== void 0 ? { helpEntry: resolveSubcommand(name) ?? name } : {}
19781
+ };
20115
19782
  }
20116
- const subcommand = resolveSubcommand(commandPositionals[0]);
20117
- if (!subcommand && commandPositionals.length > 0) {
20118
- throw new PiPodError(`unknown pi pod command "${commandPositionals[0]}"`, {
19783
+ const subcommand = resolveSubcommand(positionals[0]);
19784
+ if (!subcommand && positionals.length > 0) {
19785
+ throw new PiPodError(`unknown pi pod command "${positionals[0]}"`, {
20119
19786
  hint: 'pass all Pi arguments after `--`, for example: pipod -- "review this change"',
20120
19787
  exitCode: EXIT.USAGE
20121
19788
  });
@@ -20128,7 +19795,7 @@ function parseArgs(argv) {
20128
19795
  }
20129
19796
  return {
20130
19797
  subcommand,
20131
- subcommandArgs: subcommand ? commandPositionals.slice(1) : [],
19798
+ subcommandArgs: subcommand ? positionals.slice(1) : [],
20132
19799
  flags,
20133
19800
  piArgs,
20134
19801
  seenFlags
@@ -20151,10 +19818,14 @@ function canonicalFlagName(name) {
20151
19818
  }
20152
19819
  }
20153
19820
  async function main(argv) {
20154
- const { subcommand, subcommandArgs, flags, piArgs, seenFlags } = parseArgs(argv);
19821
+ const { subcommand, subcommandArgs, flags, piArgs, seenFlags, helpEntry } = parseArgs(argv);
20155
19822
  setVerbose(flags.verbose);
20156
19823
  if (flags.help) {
20157
- process.stdout.write(subcommand ? SUBCOMMAND_HELP[subcommand] : HELP);
19824
+ if (helpEntry !== void 0) process.stdout.write(manualEntry(helpEntry));
19825
+ else if (subcommand) process.stdout.write(usage(subcommand));
19826
+ else process.stdout.write(`pipod ${VERSION} \u2014 run pi sessions in server-managed pods
19827
+
19828
+ ${usage(null)}`);
20158
19829
  return EXIT.OK;
20159
19830
  }
20160
19831
  for (const flag of checkFlagScope(subcommand, seenFlags)) {
@@ -20267,7 +19938,7 @@ function announceUserConfig() {
20267
19938
  function runInit(flags) {
20268
19939
  const cwd = process.cwd();
20269
19940
  const existing = findConfigPath(cwd);
20270
- if (existing !== null && path33.dirname(path33.dirname(existing)) !== fs35.realpathSync(cwd)) {
19941
+ if (existing !== null && path34.dirname(path34.dirname(existing)) !== fs36.realpathSync(cwd)) {
20271
19942
  info(`note: ${color.bold(displayPath(existing))} defines an enclosing project; this scaffold shadows it here`);
20272
19943
  }
20273
19944
  const result = scaffold({ projectRoot: cwd, force: flags.force });
@@ -20295,7 +19966,7 @@ function reportError(errorValue) {
20295
19966
  if (errorValue instanceof Error && errorValue.stack) plain(color.dim(errorValue.stack));
20296
19967
  return EXIT.FAILURE;
20297
19968
  }
20298
- var invokedDirectly = process.argv[1] !== void 0 && ["cli.js", "cli.ts", "pipod", "pi-pod", "pp"].includes(path33.basename(process.argv[1]));
19969
+ var invokedDirectly = process.argv[1] !== void 0 && ["cli.js", "cli.ts", "pipod", "pi-pod", "pp"].includes(path34.basename(process.argv[1]));
20299
19970
  if (invokedDirectly) {
20300
19971
  for (const stream of [process.stdout, process.stderr]) {
20301
19972
  stream.on("error", (failure) => {