@pipod/cli 0.1.6 → 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();
@@ -19188,9 +19188,111 @@ function setDotPath(target, dotPath, value) {
19188
19188
  cursor[parts.at(-1)] = value;
19189
19189
  }
19190
19190
 
19191
- // src/scaffold.ts
19191
+ // src/manual.ts
19192
19192
  import * as fs33 from "fs";
19193
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";
19194
19296
  function configTemplate() {
19195
19297
  return `{
19196
19298
  // .pi-pod/config.json \u2014 optional server-side launch overrides for this project.
@@ -19232,8 +19334,8 @@ set -euo pipefail
19232
19334
  echo "init complete"
19233
19335
  `;
19234
19336
  function scaffold(opts) {
19235
- const dir = path31.join(opts.projectRoot, CONFIG_DIR);
19236
- fs33.mkdirSync(dir, { recursive: true });
19337
+ const dir = path32.join(opts.projectRoot, CONFIG_DIR);
19338
+ fs34.mkdirSync(dir, { recursive: true });
19237
19339
  const files = [
19238
19340
  { name: "config.json", contents: configTemplate() },
19239
19341
  { name: "env.example", contents: ENV_EXAMPLE_TEMPLATE },
@@ -19242,32 +19344,32 @@ function scaffold(opts) {
19242
19344
  const created = [];
19243
19345
  const skipped = [];
19244
19346
  for (const file of files) {
19245
- const target = path31.join(dir, file.name);
19246
- if (fs33.existsSync(target) && !opts.force) {
19247
- 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));
19248
19350
  continue;
19249
19351
  }
19250
- fs33.writeFileSync(target, file.contents, file.mode ? { mode: file.mode } : void 0);
19251
- if (file.mode) fs33.chmodSync(target, file.mode);
19252
- 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));
19253
19355
  }
19254
19356
  const gitignoreUpdated = ensureGitignoreEntry(opts.projectRoot, `${CONFIG_DIR}/env`);
19255
- const envPath = path31.join(dir, "env");
19256
- const envCreated = !fs33.existsSync(envPath);
19357
+ const envPath = path32.join(dir, "env");
19358
+ const envCreated = !fs34.existsSync(envPath);
19257
19359
  if (envCreated) {
19258
- fs33.writeFileSync(envPath, ENV_TEMPLATE, { mode: 384 });
19259
- fs33.chmodSync(envPath, 384);
19260
- 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"));
19261
19363
  }
19262
19364
  return { created, skipped, gitignoreUpdated, envCreated };
19263
19365
  }
19264
19366
  function ensureGitignoreEntry(projectRoot, entry) {
19265
- const gitignorePath = path31.join(projectRoot, ".gitignore");
19266
- 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") : "";
19267
19369
  const alreadyPresent = existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === entry || line === `/${entry}`);
19268
19370
  if (alreadyPresent) return false;
19269
19371
  const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
19270
- fs33.appendFileSync(gitignorePath, `${prefix}
19372
+ fs34.appendFileSync(gitignorePath, `${prefix}
19271
19373
  # pi pod secrets \u2014 never commit
19272
19374
  ${entry}
19273
19375
  `);
@@ -19281,7 +19383,7 @@ function printInitResult(result, projectRoot) {
19281
19383
  const steps = [
19282
19384
  [`edit ${CONFIG_DIR}/config.json and init.sh`, "what this project's pods get"],
19283
19385
  [`edit ${CONFIG_DIR}/env`, "this project's own keys"],
19284
- ["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`]
19285
19387
  ].map(([command, why]) => `${command.padEnd(40)} ${color.dim(`# ${why}`)}`);
19286
19388
  plain("");
19287
19389
  plain("Next steps:");
@@ -19291,8 +19393,8 @@ function printInitResult(result, projectRoot) {
19291
19393
 
19292
19394
  // src/update.ts
19293
19395
  import { spawnSync as spawnSync3 } from "child_process";
19294
- import * as fs34 from "fs";
19295
- import * as path32 from "path";
19396
+ import * as fs35 from "fs";
19397
+ import * as path33 from "path";
19296
19398
  function run(cmd, args, cwd) {
19297
19399
  const r = spawnSync3(cmd, args, { cwd, encoding: "utf8" });
19298
19400
  return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
@@ -19301,13 +19403,13 @@ function runVisible(cmd, args, cwd) {
19301
19403
  return spawnSync3(cmd, args, { cwd, stdio: "inherit" }).status === 0;
19302
19404
  }
19303
19405
  function detectInstall(root = packageRoot()) {
19304
- if (fs34.existsSync(path32.join(root, ".git"))) return { kind: "git", root };
19305
- 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") {
19306
19408
  return { kind: "git", root };
19307
19409
  }
19308
- if (root.split(path32.sep).includes("node_modules")) {
19410
+ if (root.split(path33.sep).includes("node_modules")) {
19309
19411
  try {
19310
- 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"));
19311
19413
  if (pkg.name) return { kind: "npm", root, name: pkg.name };
19312
19414
  } catch {
19313
19415
  }
@@ -19316,7 +19418,7 @@ function detectInstall(root = packageRoot()) {
19316
19418
  }
19317
19419
  function versionAt(root) {
19318
19420
  try {
19319
- 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"));
19320
19422
  return pkg.version ?? "0.0.0";
19321
19423
  } catch {
19322
19424
  return "0.0.0";
@@ -19391,16 +19493,16 @@ async function updateCheckout(root, opts) {
19391
19493
  return rebuild(root, before);
19392
19494
  }
19393
19495
  function distIsStale(root) {
19394
- const srcDir = path32.join(root, "src");
19395
- if (!fs34.existsSync(srcDir)) return false;
19396
- const dist = path32.join(root, "dist", "cli.js");
19397
- if (!fs34.existsSync(dist)) return true;
19398
- 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;
19399
19501
  const newest = (dir) => {
19400
19502
  let max = 0;
19401
- for (const entry of fs34.readdirSync(dir, { withFileTypes: true })) {
19402
- const p = path32.join(dir, entry.name);
19403
- 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);
19404
19506
  }
19405
19507
  return max;
19406
19508
  };
@@ -19421,8 +19523,8 @@ function rebuild(root, before) {
19421
19523
  const head = run("git", ["rev-parse", "--short", "HEAD"], root).out;
19422
19524
  plain("");
19423
19525
  info(after === before ? `now at ${color.bold(after)} (${head})` : `updated ${before} \u2192 ${color.bold(after)} (${head})`);
19424
- const upgrade = path32.join(run("git", ["rev-parse", "--show-toplevel"], root).out, "selfhost", "upgrade");
19425
- 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}`);
19426
19528
  out("run `pipod doctor` to check the new version against this repo");
19427
19529
  }
19428
19530
  async function updateFromRegistry(root, name, opts) {
@@ -19490,472 +19592,6 @@ var ALIASES = {
19490
19592
  a: "attach",
19491
19593
  ls: "list"
19492
19594
  };
19493
- var HELP = `pipod ${VERSION} \u2014 run pi sessions in server-managed pods
19494
-
19495
- Usage:
19496
- pipod [options] launch a pod for this project and start pi
19497
- pipod [options] -- <pi args\u2026> launch and pass arguments to pi: prompt words,
19498
- --model <id>, --thinking <level>, --tui-mode <mode>,
19499
- -e/--extension <path>, --skill <path>,
19500
- --prompt-template <path> (each repeatable)
19501
- pipod <command> [options] run a command
19502
- pipod help [command] show help (same as --help)
19503
-
19504
- Sign in first with \`pipod login\`. A pod-scoped PI_POD_SERVER_TOKEN also counts as
19505
- signed in. While signed out, only login, whoami, init, doctor, update, help, and
19506
- version are available.
19507
-
19508
- Pod commands:
19509
- list [pod] List pods; \`list <pod>\` shows one machine's group (ls)
19510
- attach [pod] Reconnect to a pod session (a)
19511
- stop [pod\u2026] Stop pods now (files kept; attach restarts them)
19512
- fork <pod> Launch a new pod forked from a pod's session
19513
- rename <pod> <name> Rename a pod
19514
- send [pod] <path> Copy host files into a pod
19515
- receive [pod] <path> Copy pod files into the current directory
19516
- archive [pod\u2026] Hide pods from the list (files kept; restore brings them back)
19517
- restore [pod\u2026] Bring archived pods back
19518
- gc Report retention state; --delete removes reclaimable pods
19519
-
19520
- Account commands:
19521
- login Sign in to a pi pod server via Zitadel
19522
- logout Revoke this session and delete the stored credentials
19523
- whoami Show the signed-in account
19524
- account Open the Zitadel self-service console
19525
- org-admin Open the Zitadel Console for this organization
19526
- billing Open Stripe Checkout or the billing portal (authenticated)
19527
- credentials Connect account model-provider sign-ins
19528
-
19529
- Configuration commands:
19530
- init Scaffold .pi-pod/ config, env, env.example, and init.sh
19531
- templates Manage account templates
19532
- push Upload a local config layer to its server bundle
19533
- pull Write a server bundle into its local config layer
19534
- diff Show local vs server drift for a config layer
19535
- secrets Manage server and project secret scopes
19536
- settings Show or edit one server settings config directly
19537
- jobs Manage server-scheduled jobs
19538
-
19539
- Maintenance commands:
19540
- doctor Check auth, server, org, and launch-resolution layers
19541
- update Upgrade pi pod itself
19542
-
19543
- Launch options:
19544
- --template <name|id> Launch from an account template
19545
- --on <pod|self> Co-locate the new pod on an existing pod's machine
19546
- --reuse Restart this project's newest stopped pod instead of creating one
19547
- --no-seed Start the pod with an empty workspace (skip workspace seeding)
19548
- --dry-run Resolve and print the launch plan; create nothing
19549
-
19550
- General options:
19551
- -y, --yes Skip supported confirmations
19552
- -v, --verbose Debug logging (secrets redacted)
19553
- --version Print CLI, Node, and bundled Pi versions
19554
- -h, --help Show help
19555
-
19556
- Settings resolve on the server: built-in \u2192 org \u2192 org-template \u2192 user \u2192 user-template
19557
- \u2192 org policy. Each bundle has a local directory as its source of truth (see
19558
- \`pipod push --help\`); local project settings apply after \`pipod push\`, and the
19559
- first launch in a project offers to bootstrap a template.
19560
-
19561
- Per-launch Pi resources (--extension, --skill, --prompt-template) name files or
19562
- directories inside the pod as absolute paths and add to the pod's configured Pi
19563
- resources for that launch; anything else Pi-related belongs in the pi.args config.
19564
-
19565
- Workspace seeding: launching from a directory with no .pi-pod/config.json seeds the
19566
- new pod from that directory (its git repository root when inside one). A clean,
19567
- pushed checkout whose remote tip matches HEAD is cloned at that exact commit;
19568
- anything else \u2014 dirty trees, unpushed commits, private remotes without usable
19569
- credentials, plain directories \u2014 is streamed in as a tar archive (git-ignored
19570
- paths, node_modules and .pi-pod/env excluded). Private-remote credentials are
19571
- forwarded only after a prompt (\`--yes\` approves it) and are never stored.
19572
- \`--dry-run\` prints the decision without prompting or creating a pod. Forks,
19573
- reused pods, \`--on\` co-location and \`--no-seed\` never seed.
19574
-
19575
- Run \`pipod <command> --help\` for command-specific usage.
19576
- `;
19577
- var SUBCOMMAND_HELP = {
19578
- login: `Usage: pipod login [--server <url>] [--issuer <url>] [--org <alias>] [--device] [--token <jwt>]
19579
-
19580
- Sign this machine in to a pi pod server via Zitadel (authorization code + PKCE,
19581
- in your browser). Where no browser can open here (over SSH, on a headless
19582
- server), it prints a page and a code to confirm on any other device instead.
19583
- The session is stored in ~/.pi-pod/auth.json.
19584
-
19585
- Options:
19586
- --server <url> Server to sign in to (default: the previous session's server,
19587
- $PI_POD_ACCOUNT_URL, or the production server)
19588
- --issuer <url> OIDC issuer (default: $PI_POD_ISSUER, else the one the
19589
- server publishes); needed only for a server too old to
19590
- publish its own
19591
- --org <alias> Only sign in to the organization with this primary domain;
19592
- omit it to sign in to your own organization
19593
- --device Sign in with a code on another device even if a browser
19594
- could open here
19595
- --token <jwt> Use an existing access token instead of the browser flow (dev/CI)
19596
- `,
19597
- account: `Usage: pipod account
19598
-
19599
- Open the Zitadel self-service console in your browser to manage profile,
19600
- password, MFA, and active sessions.
19601
- `,
19602
- "org-admin": `Usage: pipod org-admin
19603
-
19604
- Open the Zitadel Console in your browser. Organization owners manage members
19605
- and role grants there.
19606
- `,
19607
- billing: `Usage: pipod billing checkout [--plan standard|pro] [--trial]
19608
- pipod billing portal
19609
- pipod billing change --plan standard|pro [--yes]
19610
- pipod billing change preview --plan standard|pro
19611
- pipod billing change confirm --quote <quoteId> [--yes]
19612
-
19613
- Authenticated Stripe Checkout and billing portal. Checkout sends trial:false
19614
- (paid) unless --trial. Closed public trial refuses --trial with 402. Change
19615
- previews a Standard<->Pro plan change and confirms it only after an explicit
19616
- yes; stale quotes are never auto-confirmed. Return
19617
- pages do not grant a plan. Public npm is not a supported install path; use a
19618
- GitHub release tarball or git clone + npm run build.
19619
- `,
19620
- logout: `Usage: pipod logout
19621
-
19622
- Revoke the current account session and delete ~/.pi-pod/auth.json. Pod
19623
- operations are unavailable until the next \`pipod login\`.
19624
- `,
19625
- whoami: `Usage: pipod whoami
19626
-
19627
- Show the signed-in server, user, permissions, active organization, and console
19628
- links. Exits 1 when signed out.
19629
- `,
19630
- init: `Usage: pipod init [--force]
19631
-
19632
- Scaffold project configuration under .pi-pod/ in the current directory:
19633
- config.json, env.example, env (mode 600), and init.sh.
19634
-
19635
- Options:
19636
- --force Overwrite scaffold files that already exist
19637
- `,
19638
- doctor: `Usage: pipod doctor [--template <name|id>]
19639
-
19640
- Check each layer a launch depends on: account auth, server reachability and
19641
- version, organization membership and roles, secret references, and the
19642
- server-resolved settings bundle chain for this directory. Works while signed
19643
- out to diagnose auth problems. Exits non-zero when a layer fails.
19644
-
19645
- Options:
19646
- --template <name|id> Plan the checked launch with this template
19647
- `,
19648
- list: `Usage: pipod list [pod] [--all] [--archived] [--template <name|id>] [--quiet]
19649
-
19650
- Alias: ls
19651
-
19652
- List pods on the signed-in server with stable short refs (p-\u2026). The project is
19653
- the directory's git repository (or the directory, or its .pi-pod/ config): a
19654
- listing shows that project's pods when it has any \u2014 its pinned template's when
19655
- it pins one \u2014 and every pod otherwise. Co-located pods are grouped under the pod
19656
- whose machine they share, with an ON column naming it.
19657
- With a pod argument, show just that machine's group: the pod and everything
19658
- co-located on it.
19659
-
19660
- Status says where a pod's files are: a stopped pod keeps them on disk and restarts on
19661
- attach; an archived workspace is restored on next use, which takes longer the bigger it is.
19662
- Pods you archive stay hidden unless --archived is passed.
19663
-
19664
- Options:
19665
- -a, --all Every pod; ignore the current project and its template pin
19666
- --archived Include logically archived pods
19667
- --template <name|id> Only pods launched from that template (whole account)
19668
- -q, --quiet Print pod ids only, one per line
19669
- `,
19670
- attach: `Usage: pipod attach [pod] [--yes] [-- <prompt\u2026> [--tui-mode <mode>]]
19671
-
19672
- Alias: a
19673
-
19674
- Reconnect to a pod session through the server gateway. Name the pod by id,
19675
- short ref (p-\u2026), or name; omit it when this project has exactly one pod.
19676
- Quitting detaches: the pod keeps running until the server's idle policy reaps
19677
- it or \`pipod stop\` stops it. Raw pod shells are not available.
19678
- Attaching to an archived pod offers to restore it first.
19679
-
19680
- After --:
19681
- <prompt\u2026> Words are sent to the session as a startup prompt
19682
- --tui-mode <mode> Local TUI layout for this attach: regular or fullscreen
19683
-
19684
- Options:
19685
- -y, --yes Restore an archived pod without asking
19686
- `,
19687
- stop: `Usage: pipod stop [pod\u2026]
19688
-
19689
- Stop pods now by id, short ref (p-\u2026), or name; omit the argument when this
19690
- project has exactly one pod. Stopped pods keep their files and \`pipod attach\`
19691
- restarts them. Stopping a pod that hosts co-located pods stops those too.
19692
- `,
19693
- fork: `Usage: pipod fork <pod> [--session <path>] [launch options] [-- <pi args\u2026>]
19694
-
19695
- Launch a new pod whose session starts as a fork of <pod>'s current session.
19696
- <pod> is an id, short ref (p-\u2026), or name. The workspace is not copied \u2014
19697
- commit and push first if the new pod needs those changes. --on is not
19698
- supported: a fork launches a new sandbox, not a co-located pod.
19699
-
19700
- Options:
19701
- --session <path> Fork a specific session file instead of the newest
19702
- --template <name|id> Launch the new pod from an account template
19703
- --dry-run Resolve and print the launch plan; create nothing
19704
- -y, --yes Skip supported confirmations
19705
- `,
19706
- rename: `Usage: pipod rename <pod> <name>
19707
-
19708
- Rename a server-managed pod. <pod> is an id, short ref (p-\u2026), or the current
19709
- name.
19710
- `,
19711
- send: `Usage: pipod send [pod] <path> [--yes]
19712
-
19713
- Copy a host file or directory into a pod through the server. Omit the pod when
19714
- this project has exactly one. Sending to an archived pod offers to restore it
19715
- first.
19716
-
19717
- Options:
19718
- -y, --yes Restore an archived pod without asking
19719
- `,
19720
- receive: `Usage: pipod receive [pod] <path> [--yes]
19721
-
19722
- Copy a file or directory from a pod into the current local directory. <path>
19723
- may be a workdir-relative path or an absolute path inside that workdir, such
19724
- as foo or /workspace/foo. The destination must not already exist. Omit the
19725
- pod when this project has exactly one. Receiving from an archived pod offers
19726
- to restore it first.
19727
-
19728
- Options:
19729
- -y, --yes Restore an archived pod without asking
19730
- `,
19731
- archive: `Usage: pipod archive [pod\u2026] [--all] [--template <name|id>] [--idle <duration>] [--dry-run] [--yes]
19732
-
19733
- Archive pods by id, short ref (p-\u2026), or name. Archiving hides pods from
19734
- \`pipod list\` (\`pipod list --archived\` shows them); it never deletes work, and
19735
- \`pipod restore\` brings them back. Archiving a pod that hosts co-located pods
19736
- archives those too.
19737
-
19738
- Options:
19739
- -a, --all Archive every pod (cannot be combined with pod refs)
19740
- --template <name|id> Only pods launched from that template (whole account)
19741
- --idle <duration> Only pods whose last activity is older than this (4h, 2d, 30m)
19742
- --dry-run Print the matching pods; archive nothing
19743
- -y, --yes Skip the bulk confirmation
19744
- `,
19745
- restore: `Usage: pipod restore [pod\u2026] [--all] [--yes]
19746
-
19747
- Bring archived pods back by id, short ref (p-\u2026), or name. Restoring a host
19748
- restores the co-located pods archived with it. \`pipod attach\` then starts the
19749
- pod; a workspace in cold storage takes longer the bigger it is.
19750
-
19751
- Options:
19752
- -a, --all Restore every archived pod (cannot be combined with pod refs)
19753
- -y, --yes Skip the --all confirmation
19754
- `,
19755
- gc: `Usage: pipod gc [--delete] [--yes]
19756
-
19757
- List the archived and failed pods that can be deleted for good; --delete deletes
19758
- them. (The server stops idle pods and moves stopped workspaces to cold storage on
19759
- its own schedule; that never deletes anything.)
19760
-
19761
- Options:
19762
- --delete Permanently delete the reclaimable pods
19763
- -y, --yes Delete without a confirmation prompt
19764
- `,
19765
- jobs: `Usage: pipod jobs [action] [args] [--quiet] [--yes] [--project] [--org]
19766
-
19767
- Manage scheduled jobs the pi pod server runs for you: at each trigger it
19768
- launches a pod from the job's template and sends the job's prompt. Local job
19769
- files in .pi-pod/jobs/ (project) or ~/.pi-pod/jobs/ (user) are the reviewable
19770
- source; \`jobs push\` promotes them to the server. <job> is an id, short ref
19771
- (j-\u2026), or name.
19772
-
19773
- Actions:
19774
- list List server jobs (default)
19775
- show <job> Show a job's schedule, template, model, and prompt
19776
- runs <job> List a job's recent runs
19777
- run <job> Run an active job once, now; its schedule is unchanged
19778
- activate <job> Activate a job
19779
- pause <job> Pause a job's schedule
19780
- resume <job> Resume a paused job
19781
- rm <job> Remove a job; its schedule stops firing
19782
- push [job\u2026] Create or update server jobs from local job files
19783
- pull <job> Write a server job to a local job file
19784
- diff [job\u2026] Show local vs server drift; exits 1 when drifted
19785
-
19786
- Options:
19787
- --org push: create org-scoped jobs or promote matching user jobs
19788
- (promotion is one-way)
19789
- --project pull: write into this project's .pi-pod/jobs/ instead of
19790
- ~/.pi-pod/jobs/
19791
- -q, --quiet list: print job ids only, one per line
19792
- -y, --yes Skip confirmations; pull: overwrite an existing local file
19793
- `,
19794
- credentials: `Usage: pipod credentials [action]
19795
-
19796
- Manage account-scoped model-provider sign-ins. Connected credentials apply to
19797
- every pod you launch; OAuth refresh tokens stay in server custody and never
19798
- reach pods or this machine.
19799
-
19800
- Actions:
19801
- list Saved credentials and connectable providers (default)
19802
- connect Connect every provider your local pi is signed in to:
19803
- API keys are imported, everything else is a fresh
19804
- account login; ready credentials are left alone
19805
- connect <provider> Sign in to a provider (OAuth by default)
19806
- reconnect <provider> Redo the sign-in for a saved credential
19807
- test <provider> Verify a saved credential against the provider
19808
- remove <provider> [--yes] Delete a saved credential (alias: rm)
19809
-
19810
- connect options:
19811
- --api-key Enter an API key instead of the OAuth flow
19812
- --api-key --from-pi Import the API key your local pi resolves for that
19813
- provider (auth.json or env) instead of typing it.
19814
- OAuth grants are never imported.
19815
- `,
19816
- secrets: `Usage: pipod secrets [action]
19817
-
19818
- Manage the secret layers pods see. Requires account or pod-token auth. Server
19819
- scopes are org, user, and template/<name>; project is this project's dotenv
19820
- file, the highest-precedence layer for launches from the project.
19821
-
19822
- Actions:
19823
- list Every secret a pod launched here would see,
19824
- which layer wins, and who set it (default)
19825
- set <scope> <NAME> Store a secret, its value read from a hidden
19826
- prompt or stdin (never the command line)
19827
- rm <scope> <NAME> Remove a secret (aliases: remove, unset)
19828
- sync <scope> Upload a dotenv file to a server scope
19829
-
19830
- sync options (scope must be org, user, or template/<name>):
19831
- --file <path> Dotenv file to upload (default: ~/.pi-pod/secrets/<scope>.env)
19832
- --prune Also delete server secrets that are not in the file
19833
- --dry-run Show what would change without writing
19834
-
19835
- Values may be op:// references; they resolve just-in-time (at upload for
19836
- server scopes, at launch for the project file) and the references themselves
19837
- are never stored on the server.
19838
- `,
19839
- templates: `Usage: pipod templates [action]
19840
-
19841
- Manage pod templates on the pi pod server. A template stores config, init and
19842
- bake scripts, Pi settings files, named secrets, and agent instructions.
19843
- Templates are personal by default: only their owner sees and launches them.
19844
- --org at creation or \`templates share\` makes one org-wide.
19845
-
19846
- Agent instructions are added to the agent's system prompt in every pod launched
19847
- from the template, with the hosts and secrets the pod actually has: say what
19848
- access the template is meant to have. They guide the agent and enforce nothing;
19849
- the template's secrets and egress decide what a pod can reach. A pod's child
19850
- pods and the jobs it schedules launch from the pod's own template.
19851
-
19852
- Actions:
19853
- list List templates with scope and key counts (default)
19854
- show <name|id> Show config, scripts, agent instructions, Pi settings, and
19855
- secret names
19856
- create <name> Create a template
19857
- edit <name|id> Update a template's name, description, config, scripts, or
19858
- agent instructions
19859
- share <name|id> Make a personal template org-wide (cannot be undone)
19860
- rm <name|id> Delete a template (alias: delete)
19861
-
19862
- create/edit options:
19863
- --name <name> New name (edit only)
19864
- --description <text> Set the description
19865
- --config <file> Store this config file (same shape as .pi-pod/config.json)
19866
- --from-here create only: store this project's authored config,
19867
- scripts, and sanitized Pi files (contradicts --config)
19868
- --init-script <file> Store this init script
19869
- --bake-script <file> Store this bake script
19870
- --agent-instructions <file>
19871
- Store this file as the agent instructions (an empty
19872
- file removes them); \`pipod push\` leaves them alone
19873
- --org create only: make the template org-wide from the start
19874
- --with-secrets With --from-here: also upload project env values as
19875
- template secrets
19876
- -y, --yes Skip confirmations (rm)
19877
- `,
19878
- push: `Usage: pipod push [template [<name>] | user | org] [--dir <path>]
19879
- [--with-secrets] [--yes]
19880
-
19881
- Replace a server config bundle with its local source, whole. The layer names
19882
- both sides; edit the local files with any editor, then push. A diff preview
19883
- precedes the confirmation.
19884
-
19885
- Layers and their local source:
19886
- template [<name>] This project: .pi-pod/config.json, its init and bake
19887
- scripts, Pi files under .pi/, and the project env file
19888
- (secrets, with --with-secrets or a prompt). Without
19889
- <name> the project's pinned template is used.
19890
- user ~/.pi-pod/config.json and ~/.pi/agent/ (settings.json,
19891
- models.json, mcporter.json, subagents.json, agents/)
19892
- org ~/.pi-pod/org/: config.json, init.sh, bake.sh, pi/\u2026, and
19893
- an optional env file whose values become org secrets
19894
-
19895
- Inside a project the layer defaults to template; elsewhere it is required.
19896
- Provider credentials, hooks, mcpServers, and model credential values never
19897
- leave this machine.
19898
-
19899
- Options:
19900
- --dir <path> org only: read the org source from <path> instead
19901
- --with-secrets Also upload the layer's env values as secrets
19902
- -y, --yes Apply the replacement without the confirmation prompt
19903
- `,
19904
- pull: `Usage: pipod pull [template [<name>] | user | org] [--dir <path>] [--yes]
19905
-
19906
- Write a server config bundle into its local source files (see
19907
- \`pipod push --help\` for the layer \u2192 directory table). A diff preview
19908
- precedes the confirmation; nothing is written when the two already match.
19909
-
19910
- What pull keeps from the local files:
19911
- config.json client-only keys: $schema, template, secretResolver,
19912
- pi.chords, pi.sessionNaming
19913
- settings.json hooks and mcpServers (and packages when hostConfig.packages
19914
- is off)
19915
- models.json credential values the push had dropped
19916
- agents/ files the bundle no longer contains are removed
19917
-
19918
- Secrets stay on the server; their names are listed. The user layer has no
19919
- local script files, so user bundle scripts are shown but not written.
19920
-
19921
- Options:
19922
- --dir <path> org only: write the org source to <path> instead
19923
- -y, --yes Apply the local changes without the confirmation prompt
19924
- `,
19925
- diff: `Usage: pipod diff [template [<name>] | user | org] [--dir <path>]
19926
-
19927
- Compare a config layer's local source with its server bundle (see
19928
- \`pipod push --help\` for the layer \u2192 directory table). Secrets compare by
19929
- name only. Exits 1 when they differ, 0 when they match.
19930
-
19931
- Options:
19932
- --dir <path> org only: read the org source from <path> instead
19933
- `,
19934
- settings: `Usage: pipod settings <user|org> [show | edit | set <key> <value> | unset <key>]
19935
-
19936
- Show or edit a persistent settings bundle on the server: your own user bundle,
19937
- or the org-wide defaults (org scope needs the org:manage permission). Edits
19938
- change only the config; scripts and Pi files stay as they are \u2014 replace the
19939
- whole bundle from local files with \`pipod push user\` or \`pipod push org\`.
19940
- Edits use compare-and-swap on the bundle version, so concurrent changes fail
19941
- cleanly instead of overwriting each other.
19942
-
19943
- Actions:
19944
- show Print the settings bundle as JSON (default)
19945
- edit Edit the config in $VISUAL/$EDITOR, then save
19946
- set <key> <value> Set one dotted key to a JSON value, or to the text as
19947
- a string: pipod settings user set pi.model provider/model
19948
- unset <key> Remove one dotted key, so the layers below decide it
19949
- `,
19950
- update: `Usage: pipod update [--dry-run]
19951
-
19952
- Upgrade pi pod itself: a registry install updates to the latest published
19953
- version; a git checkout pulls its upstream and rebuilds.
19954
-
19955
- Options:
19956
- --dry-run Report what an update would do without changing anything
19957
- `
19958
- };
19959
19595
  var SELF_PARSING_SUBCOMMANDS = /* @__PURE__ */ new Set(["credentials", "secrets", "templates", "push", "pull", "diff", "settings", "billing"]);
19960
19596
  var UNIVERSAL_FLAGS = /* @__PURE__ */ new Set(["--verbose", "--version", "--help"]);
19961
19597
  var FLAG_SCOPE = {
@@ -20131,15 +19767,22 @@ function parseArgs(argv) {
20131
19767
  });
20132
19768
  }
20133
19769
  }
20134
- let commandPositionals = positionals;
20135
19770
  if (positionals[0] === "help") {
20136
19771
  flags.help = true;
20137
19772
  seenFlags.add("--help");
20138
- 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
+ };
20139
19782
  }
20140
- const subcommand = resolveSubcommand(commandPositionals[0]);
20141
- if (!subcommand && commandPositionals.length > 0) {
20142
- 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]}"`, {
20143
19786
  hint: 'pass all Pi arguments after `--`, for example: pipod -- "review this change"',
20144
19787
  exitCode: EXIT.USAGE
20145
19788
  });
@@ -20152,7 +19795,7 @@ function parseArgs(argv) {
20152
19795
  }
20153
19796
  return {
20154
19797
  subcommand,
20155
- subcommandArgs: subcommand ? commandPositionals.slice(1) : [],
19798
+ subcommandArgs: subcommand ? positionals.slice(1) : [],
20156
19799
  flags,
20157
19800
  piArgs,
20158
19801
  seenFlags
@@ -20175,10 +19818,14 @@ function canonicalFlagName(name) {
20175
19818
  }
20176
19819
  }
20177
19820
  async function main(argv) {
20178
- const { subcommand, subcommandArgs, flags, piArgs, seenFlags } = parseArgs(argv);
19821
+ const { subcommand, subcommandArgs, flags, piArgs, seenFlags, helpEntry } = parseArgs(argv);
20179
19822
  setVerbose(flags.verbose);
20180
19823
  if (flags.help) {
20181
- 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)}`);
20182
19829
  return EXIT.OK;
20183
19830
  }
20184
19831
  for (const flag of checkFlagScope(subcommand, seenFlags)) {
@@ -20291,7 +19938,7 @@ function announceUserConfig() {
20291
19938
  function runInit(flags) {
20292
19939
  const cwd = process.cwd();
20293
19940
  const existing = findConfigPath(cwd);
20294
- if (existing !== null && path33.dirname(path33.dirname(existing)) !== fs35.realpathSync(cwd)) {
19941
+ if (existing !== null && path34.dirname(path34.dirname(existing)) !== fs36.realpathSync(cwd)) {
20295
19942
  info(`note: ${color.bold(displayPath(existing))} defines an enclosing project; this scaffold shadows it here`);
20296
19943
  }
20297
19944
  const result = scaffold({ projectRoot: cwd, force: flags.force });
@@ -20319,7 +19966,7 @@ function reportError(errorValue) {
20319
19966
  if (errorValue instanceof Error && errorValue.stack) plain(color.dim(errorValue.stack));
20320
19967
  return EXIT.FAILURE;
20321
19968
  }
20322
- 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]));
20323
19970
  if (invokedDirectly) {
20324
19971
  for (const stream of [process.stdout, process.stderr]) {
20325
19972
  stream.on("error", (failure) => {