@pipod/cli 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli.js +252 -583
- package/dist/cli.js.map +1 -1
- package/man/pipod-config.5 +150 -0
- package/man/pipod-config.5.md +148 -0
- package/man/pipod.1 +865 -0
- package/man/pipod.1.md +856 -0
- package/package.json +11 -4
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
|
|
10
|
-
import * as
|
|
9
|
+
import * as fs36 from "fs";
|
|
10
|
+
import * as path34 from "path";
|
|
11
11
|
|
|
12
12
|
// src/errors.ts
|
|
13
13
|
var EXIT = {
|
|
@@ -583,7 +583,7 @@ import * as path5 from "path";
|
|
|
583
583
|
import * as fs from "fs";
|
|
584
584
|
import * as path from "path";
|
|
585
585
|
var HOST_PI_AGENT_SUBPATH = path.join(".pi", "agent");
|
|
586
|
-
var HOST_COUPLED_SETTINGS_KEYS = ["hooks", "mcpServers"];
|
|
586
|
+
var HOST_COUPLED_SETTINGS_KEYS = ["hooks", "mcpServers", "deviceId"];
|
|
587
587
|
var DEFAULT_HOST_CONFIG = {
|
|
588
588
|
settings: true,
|
|
589
589
|
packages: true
|
|
@@ -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((
|
|
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
|
|
6278
|
+
for (const path35 of paths) managedArgs.push(flag, path35);
|
|
6279
6279
|
}
|
|
6280
6280
|
}
|
|
6281
6281
|
return {
|
|
@@ -6520,7 +6520,6 @@ import * as path17 from "path";
|
|
|
6520
6520
|
import { promisify as promisify2 } from "util";
|
|
6521
6521
|
|
|
6522
6522
|
// src/client/runtime/local-extensions.ts
|
|
6523
|
-
import { randomUUID } from "crypto";
|
|
6524
6523
|
import * as fs17 from "fs";
|
|
6525
6524
|
import * as path16 from "path";
|
|
6526
6525
|
|
|
@@ -7002,19 +7001,19 @@ function branchSessionStats(entries, state) {
|
|
|
7002
7001
|
function branchUsageTotals(entries) {
|
|
7003
7002
|
const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
7004
7003
|
for (const entry of entries) {
|
|
7005
|
-
let
|
|
7004
|
+
let usage2;
|
|
7006
7005
|
if (entry.type === "message") {
|
|
7007
7006
|
const message = entry.message;
|
|
7008
|
-
if (message.role === "assistant" || message.role === "toolResult")
|
|
7007
|
+
if (message.role === "assistant" || message.role === "toolResult") usage2 = message.usage;
|
|
7009
7008
|
} else if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
7010
|
-
|
|
7009
|
+
usage2 = entry.usage;
|
|
7011
7010
|
}
|
|
7012
|
-
if (!
|
|
7013
|
-
totals.input +=
|
|
7014
|
-
totals.output +=
|
|
7015
|
-
totals.cacheRead +=
|
|
7016
|
-
totals.cacheWrite +=
|
|
7017
|
-
totals.cost +=
|
|
7011
|
+
if (!usage2) continue;
|
|
7012
|
+
totals.input += usage2.input ?? 0;
|
|
7013
|
+
totals.output += usage2.output ?? 0;
|
|
7014
|
+
totals.cacheRead += usage2.cacheRead ?? 0;
|
|
7015
|
+
totals.cacheWrite += usage2.cacheWrite ?? 0;
|
|
7016
|
+
totals.cost += usage2.cost?.total ?? 0;
|
|
7018
7017
|
}
|
|
7019
7018
|
return totals;
|
|
7020
7019
|
}
|
|
@@ -7208,7 +7207,7 @@ function branchOf(entries, fromId) {
|
|
|
7208
7207
|
}
|
|
7209
7208
|
async function createLocalExtensionHost(opts) {
|
|
7210
7209
|
if (opts.paths.length === 0) return null;
|
|
7211
|
-
const { cache, bridge
|
|
7210
|
+
const { cache, bridge } = opts;
|
|
7212
7211
|
const notifyWhenBound = (message, type) => {
|
|
7213
7212
|
bridge.onUiBound(() => bridge.notify(message, type));
|
|
7214
7213
|
};
|
|
@@ -7271,48 +7270,6 @@ async function createLocalExtensionHost(opts) {
|
|
|
7271
7270
|
resetLeaf: throwMutator("resetLeaf")
|
|
7272
7271
|
};
|
|
7273
7272
|
const runner = new ExtensionRunnerClass(kept, runtime, opts.hostCwd, sessionManagerFacade, opts.modelRegistry);
|
|
7274
|
-
let appendEntryWarned = false;
|
|
7275
|
-
const appendEntryUnavailable = (reason) => {
|
|
7276
|
-
if (appendEntryWarned) return;
|
|
7277
|
-
appendEntryWarned = true;
|
|
7278
|
-
notifyWhenBound(`pod extension appendEntry: ${reason} \u2014 the entry was not recorded`, "warning");
|
|
7279
|
-
};
|
|
7280
|
-
const supportsAppendEntry = () => cache.commands.some((command) => {
|
|
7281
|
-
const candidate = command;
|
|
7282
|
-
return candidate.name === APPEND_ENTRY_COMMAND && candidate.source === "extension";
|
|
7283
|
-
});
|
|
7284
|
-
const sendAppendEntry = (customType, data) => {
|
|
7285
|
-
if (typeof customType !== "string" || customType.length === 0 || customType.length > APPEND_ENTRY_MAX_CUSTOM_TYPE_LENGTH) {
|
|
7286
|
-
appendEntryUnavailable(`invalid custom type ${JSON.stringify(customType).slice(0, 64)}`);
|
|
7287
|
-
return;
|
|
7288
|
-
}
|
|
7289
|
-
if (!supportsAppendEntry()) {
|
|
7290
|
-
appendEntryUnavailable("this pod runs an older pi pod extension (restart Pi in the pod to update it)");
|
|
7291
|
-
return;
|
|
7292
|
-
}
|
|
7293
|
-
const request = {
|
|
7294
|
-
v: APPEND_ENTRY_PROTOCOL_VERSION,
|
|
7295
|
-
id: randomUUID(),
|
|
7296
|
-
op: "append-entry",
|
|
7297
|
-
customType,
|
|
7298
|
-
...data !== void 0 ? { data } : {}
|
|
7299
|
-
};
|
|
7300
|
-
let encoded;
|
|
7301
|
-
try {
|
|
7302
|
-
encoded = Buffer.from(JSON.stringify(request), "utf8").toString("base64url");
|
|
7303
|
-
} catch (error2) {
|
|
7304
|
-
appendEntryUnavailable(`entry data is not serializable (${error2 instanceof Error ? error2.message : String(error2)})`);
|
|
7305
|
-
return;
|
|
7306
|
-
}
|
|
7307
|
-
if (Buffer.byteLength(encoded, "utf8") > TREE_BRIDGE_MAX_ENCODED_REQUEST_BYTES) {
|
|
7308
|
-
appendEntryUnavailable("entry data exceeds the bridge size limit");
|
|
7309
|
-
return;
|
|
7310
|
-
}
|
|
7311
|
-
void rpc.prompt(`/${APPEND_ENTRY_COMMAND} ${encoded}`).then(() => cache.refreshTree().then(() => void 0)).catch((error2) => {
|
|
7312
|
-
appendEntryUnavailable(error2 instanceof Error ? error2.message : String(error2));
|
|
7313
|
-
debug(`append-entry bridge failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7314
|
-
});
|
|
7315
|
-
};
|
|
7316
7273
|
const actions = {
|
|
7317
7274
|
sendMessage: unavailable("pi.sendMessage", "conversation context is built in the pod"),
|
|
7318
7275
|
sendUserMessage: (content, sendOptions) => {
|
|
@@ -7324,7 +7281,7 @@ async function createLocalExtensionHost(opts) {
|
|
|
7324
7281
|
)
|
|
7325
7282
|
);
|
|
7326
7283
|
},
|
|
7327
|
-
appendEntry:
|
|
7284
|
+
appendEntry: opts.appendEntry,
|
|
7328
7285
|
setSessionName: (name) => void opts.setSessionName(name),
|
|
7329
7286
|
getSessionName: () => cache.state.sessionName,
|
|
7330
7287
|
setLabel: (entryId, label2) => opts.setLabel(entryId, label2),
|
|
@@ -8514,6 +8471,54 @@ import {
|
|
|
8514
8471
|
resolveModelScopeWithDiagnostics
|
|
8515
8472
|
} from "@earendil-works/pi-coding-agent";
|
|
8516
8473
|
|
|
8474
|
+
// src/client/runtime/append-entry-bridge.ts
|
|
8475
|
+
import { randomUUID } from "crypto";
|
|
8476
|
+
function createAppendEntryBridge(opts) {
|
|
8477
|
+
const { rpc, cache, bridge } = opts;
|
|
8478
|
+
let warned = false;
|
|
8479
|
+
const unavailable3 = (reason) => {
|
|
8480
|
+
if (warned) return;
|
|
8481
|
+
warned = true;
|
|
8482
|
+
bridge.onUiBound(() => bridge.notify(`session entry not recorded: ${reason}`, "warning"));
|
|
8483
|
+
};
|
|
8484
|
+
const supported = () => cache.commands.some((command) => {
|
|
8485
|
+
const candidate = command;
|
|
8486
|
+
return candidate.name === APPEND_ENTRY_COMMAND && candidate.source === "extension";
|
|
8487
|
+
});
|
|
8488
|
+
return (customType, data) => {
|
|
8489
|
+
if (typeof customType !== "string" || customType.length === 0 || customType.length > APPEND_ENTRY_MAX_CUSTOM_TYPE_LENGTH) {
|
|
8490
|
+
unavailable3(`invalid custom type ${JSON.stringify(customType).slice(0, 64)}`);
|
|
8491
|
+
return;
|
|
8492
|
+
}
|
|
8493
|
+
if (!supported()) {
|
|
8494
|
+
unavailable3("this pod runs an older pi pod extension (restart Pi in the pod to update it)");
|
|
8495
|
+
return;
|
|
8496
|
+
}
|
|
8497
|
+
const request = {
|
|
8498
|
+
v: APPEND_ENTRY_PROTOCOL_VERSION,
|
|
8499
|
+
id: randomUUID(),
|
|
8500
|
+
op: "append-entry",
|
|
8501
|
+
customType,
|
|
8502
|
+
...data !== void 0 ? { data } : {}
|
|
8503
|
+
};
|
|
8504
|
+
let encoded;
|
|
8505
|
+
try {
|
|
8506
|
+
encoded = Buffer.from(JSON.stringify(request), "utf8").toString("base64url");
|
|
8507
|
+
} catch (error2) {
|
|
8508
|
+
unavailable3(`entry data is not serializable (${error2 instanceof Error ? error2.message : String(error2)})`);
|
|
8509
|
+
return;
|
|
8510
|
+
}
|
|
8511
|
+
if (Buffer.byteLength(encoded, "utf8") > TREE_BRIDGE_MAX_ENCODED_REQUEST_BYTES) {
|
|
8512
|
+
unavailable3("entry data exceeds the bridge size limit");
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
void rpc.prompt(`/${APPEND_ENTRY_COMMAND} ${encoded}`).then(() => cache.refreshTree().then(() => void 0)).catch((error2) => {
|
|
8516
|
+
unavailable3(error2 instanceof Error ? error2.message : String(error2));
|
|
8517
|
+
debug(`append-entry bridge failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
8518
|
+
});
|
|
8519
|
+
};
|
|
8520
|
+
}
|
|
8521
|
+
|
|
8517
8522
|
// src/client/runtime/bridge.ts
|
|
8518
8523
|
var HostBridge = class {
|
|
8519
8524
|
/** Set exactly once, by the first ending path to fire; later writers lose. */
|
|
@@ -8632,11 +8637,11 @@ function extractAtPrefix(text) {
|
|
|
8632
8637
|
const tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
|
|
8633
8638
|
return text[tokenStart] === "@" ? text.slice(tokenStart) : null;
|
|
8634
8639
|
}
|
|
8635
|
-
function buildCompletionValue(
|
|
8636
|
-
return isQuotedPrefix ||
|
|
8640
|
+
function buildCompletionValue(path35, isQuotedPrefix) {
|
|
8641
|
+
return isQuotedPrefix || path35.includes(" ") ? `@"${path35}"` : `@${path35}`;
|
|
8637
8642
|
}
|
|
8638
|
-
function basename7(
|
|
8639
|
-
const trimmed =
|
|
8643
|
+
function basename7(path35) {
|
|
8644
|
+
const trimmed = path35.endsWith("/") ? path35.slice(0, -1) : path35;
|
|
8640
8645
|
const cut = trimmed.lastIndexOf("/");
|
|
8641
8646
|
return cut === -1 ? trimmed : trimmed.slice(cut + 1);
|
|
8642
8647
|
}
|
|
@@ -10588,6 +10593,7 @@ function createRemoteRuntime(opts) {
|
|
|
10588
10593
|
const { rpc, cwd } = opts;
|
|
10589
10594
|
const bridge = new HostBridge();
|
|
10590
10595
|
const cache = new RemoteStateCache(rpc);
|
|
10596
|
+
const appendEntry = createAppendEntryBridge({ rpc, cache, bridge });
|
|
10591
10597
|
const refreshThinkingLevels = async () => {
|
|
10592
10598
|
cache.availableThinkingLevels = null;
|
|
10593
10599
|
if (!opts.getAvailableThinkingLevels) return;
|
|
@@ -11017,6 +11023,8 @@ function createRemoteRuntime(opts) {
|
|
|
11017
11023
|
// (including Mermaid) still compose — InteractiveMode appends these to its own chain.
|
|
11018
11024
|
// Pod extension transforms remain honestly unavailable; client extensions supply theirs here.
|
|
11019
11025
|
getMarkdownTransformers: () => localHost?.runner.getMarkdownTransformers() ?? [],
|
|
11026
|
+
// Tool renderers from client extensions wrap the built-in one, as in-process.
|
|
11027
|
+
resolveToolRenderers: (toolName, base) => localHost ? localHost.runner.resolveToolRenderers(toolName, base) : base(),
|
|
11020
11028
|
getCommandDiagnostics: () => localHost?.runner.getCommandDiagnostics() ?? [],
|
|
11021
11029
|
getShortcutDiagnostics: () => localHost?.runner.getShortcutDiagnostics() ?? [],
|
|
11022
11030
|
getModelRegistry: () => localHost ? extensionModelRegistry : void 0,
|
|
@@ -11062,6 +11070,8 @@ function createRemoteRuntime(opts) {
|
|
|
11062
11070
|
const sessionManager = {
|
|
11063
11071
|
getCwd: () => cwd,
|
|
11064
11072
|
getEntries: () => cache.entries,
|
|
11073
|
+
getBranch: () => cache.entries,
|
|
11074
|
+
getEntryCount: () => cache.entries.length,
|
|
11065
11075
|
getLeafId: () => cache.leafId ?? void 0,
|
|
11066
11076
|
getSessionDir: () => cwd,
|
|
11067
11077
|
getSessionFile: () => cache.state.sessionFile,
|
|
@@ -11071,6 +11081,8 @@ function createRemoteRuntime(opts) {
|
|
|
11071
11081
|
usesDefaultSessionDir: () => true,
|
|
11072
11082
|
isPersisted: () => cache.state.sessionFile !== void 0,
|
|
11073
11083
|
appendLabelChange: (entryId, label2) => trackLabelMutation(entryId, label2),
|
|
11084
|
+
/** pi's `/bug` records the report here; the entry belongs in the pod's session file. */
|
|
11085
|
+
appendCustomEntry: (customType, data) => appendEntry(customType, data),
|
|
11074
11086
|
buildContextEntries: () => cache.entries
|
|
11075
11087
|
};
|
|
11076
11088
|
const session = {
|
|
@@ -11152,6 +11164,14 @@ function createRemoteRuntime(opts) {
|
|
|
11152
11164
|
get hasPendingBashMessages() {
|
|
11153
11165
|
return false;
|
|
11154
11166
|
},
|
|
11167
|
+
// RPC reports neither the cache warmer nor where a virtual model routed a request, so the
|
|
11168
|
+
// footer and /settings show what pi shows when they are unavailable.
|
|
11169
|
+
get cacheWarmingStatus() {
|
|
11170
|
+
return void 0;
|
|
11171
|
+
},
|
|
11172
|
+
get routedModel() {
|
|
11173
|
+
return void 0;
|
|
11174
|
+
},
|
|
11155
11175
|
// local-by-nature services
|
|
11156
11176
|
settingsManager: remoteSettingsManager,
|
|
11157
11177
|
sessionManager,
|
|
@@ -11203,6 +11223,8 @@ function createRemoteRuntime(opts) {
|
|
|
11203
11223
|
abortBranchSummary: () => {
|
|
11204
11224
|
},
|
|
11205
11225
|
setAutoCompactionEnabled: (enabled) => mutate(() => rpc.setAutoCompaction(enabled)),
|
|
11226
|
+
/** Persisted like the /transport toggle; the warmer itself runs in the pod. */
|
|
11227
|
+
setCacheWarmingMode: (mode) => remoteSettingsManager.setCacheWarmingMode(mode),
|
|
11206
11228
|
setAutoRetryEnabled: (enabled) => mutate(() => rpc.setAutoRetry(enabled)),
|
|
11207
11229
|
abortRetry: () => rpc.abortRetry(),
|
|
11208
11230
|
/** `!command` bash: streamed chunks arrive as bash_execution_update events with our id (§8). */
|
|
@@ -11423,7 +11445,7 @@ function createRemoteRuntime(opts) {
|
|
|
11423
11445
|
hostCwd: cwd,
|
|
11424
11446
|
cache,
|
|
11425
11447
|
bridge,
|
|
11426
|
-
|
|
11448
|
+
appendEntry,
|
|
11427
11449
|
getScopedModels: () => scopedModels,
|
|
11428
11450
|
prompt,
|
|
11429
11451
|
setModel: (model) => setRemoteModel(() => rpc.setModel(model.provider, model.id)),
|
|
@@ -16560,8 +16582,8 @@ async function allJobs(client) {
|
|
|
16560
16582
|
beforeId = last.id;
|
|
16561
16583
|
}
|
|
16562
16584
|
}
|
|
16563
|
-
function assertArgCount(args, expected,
|
|
16564
|
-
if (args.length !== expected) throw new PiPodError(`usage: ${
|
|
16585
|
+
function assertArgCount(args, expected, usage2) {
|
|
16586
|
+
if (args.length !== expected) throw new PiPodError(`usage: ${usage2}`);
|
|
16565
16587
|
}
|
|
16566
16588
|
function shortSchedule(trigger) {
|
|
16567
16589
|
if (trigger.type === "cron") return trigger.cron;
|
|
@@ -16747,8 +16769,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
16769
|
var CHANGE_USAGE = "pipod billing change --plan standard|pro [--yes]";
|
|
16748
16770
|
var CHANGE_PREVIEW_USAGE = "pipod billing change preview --plan standard|pro";
|
|
16749
16771
|
var CHANGE_CONFIRM_USAGE = "pipod billing change confirm --quote <quoteId> [--yes]";
|
|
16750
|
-
function usageError(message,
|
|
16751
|
-
return new PiPodError(message, { hint: `usage: ${
|
|
16772
|
+
function usageError(message, usage2) {
|
|
16773
|
+
return new PiPodError(message, { hint: `usage: ${usage2}`, exitCode: EXIT.USAGE });
|
|
16752
16774
|
}
|
|
16753
16775
|
function parseChangeArgs(args) {
|
|
16754
16776
|
let form = "flow";
|
|
@@ -17945,11 +17967,11 @@ function formatStatus(status) {
|
|
|
17945
17967
|
const expires = status.state === "ready" && status.expiresAt ? ` (expires ${status.expiresAt})` : "";
|
|
17946
17968
|
return `${status.providerId} ${status.state.replaceAll("_", " ")}${expires}`;
|
|
17947
17969
|
}
|
|
17948
|
-
function rejectExtra(args,
|
|
17949
|
-
if (args.length > 0) throw new PiPodError(
|
|
17970
|
+
function rejectExtra(args, usage2) {
|
|
17971
|
+
if (args.length > 0) throw new PiPodError(usage2);
|
|
17950
17972
|
}
|
|
17951
|
-
function rejectProviderArgs(args,
|
|
17952
|
-
if (args.length !== 1 || args[0].startsWith("-")) throw new PiPodError(
|
|
17973
|
+
function rejectProviderArgs(args, usage2) {
|
|
17974
|
+
if (args.length !== 1 || args[0].startsWith("-")) throw new PiPodError(usage2);
|
|
17953
17975
|
}
|
|
17954
17976
|
function terminalInteraction() {
|
|
17955
17977
|
const controller = new AbortController();
|
|
@@ -19188,9 +19210,111 @@ function setDotPath(target, dotPath, value) {
|
|
|
19188
19210
|
cursor[parts.at(-1)] = value;
|
|
19189
19211
|
}
|
|
19190
19212
|
|
|
19191
|
-
// src/
|
|
19213
|
+
// src/manual.ts
|
|
19192
19214
|
import * as fs33 from "fs";
|
|
19193
19215
|
import * as path31 from "path";
|
|
19216
|
+
var MAIN_PAGE = "pipod.1.md";
|
|
19217
|
+
var FENCE = "```";
|
|
19218
|
+
function usage(command) {
|
|
19219
|
+
const entry = command === null ? section(mainPage(), 2, "SYNOPSIS") : commandEntry(command);
|
|
19220
|
+
if (entry === null) throw new PiPodError(`the pipod manual has no entry for ${command}`);
|
|
19221
|
+
const block = openingBlock(entry);
|
|
19222
|
+
const more = command !== null && hasMoreThanBlock(entry) ? `
|
|
19223
|
+
More: pipod help ${command}
|
|
19224
|
+
` : "";
|
|
19225
|
+
return `${block}${more}`;
|
|
19226
|
+
}
|
|
19227
|
+
function manualEntry(name) {
|
|
19228
|
+
const entry = commandEntry(name) ?? topicSection(name) ?? topicPage(name);
|
|
19229
|
+
if (entry === null) {
|
|
19230
|
+
throw new PiPodError(`no manual entry for "${name}"`, {
|
|
19231
|
+
hint: "`pipod help` lists the commands and topics",
|
|
19232
|
+
exitCode: EXIT.USAGE
|
|
19233
|
+
});
|
|
19234
|
+
}
|
|
19235
|
+
return plainText(entry);
|
|
19236
|
+
}
|
|
19237
|
+
function commandEntry(command) {
|
|
19238
|
+
const commands = section(mainPage(), 2, "COMMANDS");
|
|
19239
|
+
return commands === null ? null : section(commands, 3, `pipod ${command}`);
|
|
19240
|
+
}
|
|
19241
|
+
function topicSection(topic) {
|
|
19242
|
+
return /^[a-z]+$/.test(topic) ? section(mainPage(), 2, topic.toUpperCase()) : null;
|
|
19243
|
+
}
|
|
19244
|
+
function topicPage(topic) {
|
|
19245
|
+
const prefix = `pipod-${topic}.`;
|
|
19246
|
+
const file = fs33.readdirSync(manDir()).find(
|
|
19247
|
+
(name) => name.startsWith(prefix) && /^\d+\.md$/.test(name.slice(prefix.length))
|
|
19248
|
+
);
|
|
19249
|
+
return file === void 0 ? null : read(path31.join(manDir(), file));
|
|
19250
|
+
}
|
|
19251
|
+
function section(markdown, level, title) {
|
|
19252
|
+
const lines = markdown.split("\n");
|
|
19253
|
+
const heading = `${"#".repeat(level)} ${title}`;
|
|
19254
|
+
let start = -1;
|
|
19255
|
+
let fenced = false;
|
|
19256
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19257
|
+
const line = lines[i];
|
|
19258
|
+
if (line.startsWith(FENCE)) fenced = !fenced;
|
|
19259
|
+
if (fenced) continue;
|
|
19260
|
+
if (start < 0) {
|
|
19261
|
+
if (line === heading) start = i;
|
|
19262
|
+
continue;
|
|
19263
|
+
}
|
|
19264
|
+
const depth2 = /^(#+) /.exec(line)?.[1]?.length;
|
|
19265
|
+
if (depth2 !== void 0 && depth2 <= level) return `${lines.slice(start, i).join("\n").trimEnd()}
|
|
19266
|
+
`;
|
|
19267
|
+
}
|
|
19268
|
+
return start < 0 ? null : `${lines.slice(start).join("\n").trimEnd()}
|
|
19269
|
+
`;
|
|
19270
|
+
}
|
|
19271
|
+
function openingBlock(entry) {
|
|
19272
|
+
const lines = entry.split("\n");
|
|
19273
|
+
const open = lines.findIndex((line) => line.startsWith(FENCE));
|
|
19274
|
+
const close = lines.findIndex((line, i) => i > open && line.startsWith(FENCE));
|
|
19275
|
+
if (open < 0 || close < 0) throw new PiPodError("a pipod manual entry does not open with a usage block");
|
|
19276
|
+
return `${lines.slice(open + 1, close).join("\n")}
|
|
19277
|
+
`;
|
|
19278
|
+
}
|
|
19279
|
+
function hasMoreThanBlock(entry) {
|
|
19280
|
+
const lines = entry.split("\n");
|
|
19281
|
+
const fences = lines.flatMap((line, i) => line.startsWith(FENCE) ? [i] : []);
|
|
19282
|
+
return lines.slice((fences[1] ?? lines.length) + 1).some((line) => line.trim() !== "");
|
|
19283
|
+
}
|
|
19284
|
+
function plainText(markdown) {
|
|
19285
|
+
const out2 = [];
|
|
19286
|
+
let fenced = false;
|
|
19287
|
+
for (const line of markdown.split("\n")) {
|
|
19288
|
+
if (line.startsWith(FENCE)) {
|
|
19289
|
+
fenced = !fenced;
|
|
19290
|
+
continue;
|
|
19291
|
+
}
|
|
19292
|
+
if (fenced) out2.push(line === "" ? "" : ` ${line}`);
|
|
19293
|
+
else out2.push(line.replace(/^#+ /, ""));
|
|
19294
|
+
}
|
|
19295
|
+
return `${out2.join("\n").trimEnd()}
|
|
19296
|
+
`;
|
|
19297
|
+
}
|
|
19298
|
+
function mainPage() {
|
|
19299
|
+
return read(path31.join(manDir(), MAIN_PAGE));
|
|
19300
|
+
}
|
|
19301
|
+
function manDir() {
|
|
19302
|
+
return path31.join(packageRoot(), "man");
|
|
19303
|
+
}
|
|
19304
|
+
function read(file) {
|
|
19305
|
+
try {
|
|
19306
|
+
return fs33.readFileSync(file, "utf8");
|
|
19307
|
+
} catch (cause) {
|
|
19308
|
+
throw new PiPodError(`the pipod manual is missing from this install (${file})`, {
|
|
19309
|
+
hint: "reinstall pipod: `npm install -g @pipod/cli`, or `npm run build` in a checkout",
|
|
19310
|
+
cause
|
|
19311
|
+
});
|
|
19312
|
+
}
|
|
19313
|
+
}
|
|
19314
|
+
|
|
19315
|
+
// src/scaffold.ts
|
|
19316
|
+
import * as fs34 from "fs";
|
|
19317
|
+
import * as path32 from "path";
|
|
19194
19318
|
function configTemplate() {
|
|
19195
19319
|
return `{
|
|
19196
19320
|
// .pi-pod/config.json \u2014 optional server-side launch overrides for this project.
|
|
@@ -19232,8 +19356,8 @@ set -euo pipefail
|
|
|
19232
19356
|
echo "init complete"
|
|
19233
19357
|
`;
|
|
19234
19358
|
function scaffold(opts) {
|
|
19235
|
-
const dir =
|
|
19236
|
-
|
|
19359
|
+
const dir = path32.join(opts.projectRoot, CONFIG_DIR);
|
|
19360
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
19237
19361
|
const files = [
|
|
19238
19362
|
{ name: "config.json", contents: configTemplate() },
|
|
19239
19363
|
{ name: "env.example", contents: ENV_EXAMPLE_TEMPLATE },
|
|
@@ -19242,32 +19366,32 @@ function scaffold(opts) {
|
|
|
19242
19366
|
const created = [];
|
|
19243
19367
|
const skipped = [];
|
|
19244
19368
|
for (const file of files) {
|
|
19245
|
-
const target =
|
|
19246
|
-
if (
|
|
19247
|
-
skipped.push(
|
|
19369
|
+
const target = path32.join(dir, file.name);
|
|
19370
|
+
if (fs34.existsSync(target) && !opts.force) {
|
|
19371
|
+
skipped.push(path32.join(CONFIG_DIR, file.name));
|
|
19248
19372
|
continue;
|
|
19249
19373
|
}
|
|
19250
|
-
|
|
19251
|
-
if (file.mode)
|
|
19252
|
-
created.push(
|
|
19374
|
+
fs34.writeFileSync(target, file.contents, file.mode ? { mode: file.mode } : void 0);
|
|
19375
|
+
if (file.mode) fs34.chmodSync(target, file.mode);
|
|
19376
|
+
created.push(path32.join(CONFIG_DIR, file.name));
|
|
19253
19377
|
}
|
|
19254
19378
|
const gitignoreUpdated = ensureGitignoreEntry(opts.projectRoot, `${CONFIG_DIR}/env`);
|
|
19255
|
-
const envPath =
|
|
19256
|
-
const envCreated = !
|
|
19379
|
+
const envPath = path32.join(dir, "env");
|
|
19380
|
+
const envCreated = !fs34.existsSync(envPath);
|
|
19257
19381
|
if (envCreated) {
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
created.push(
|
|
19382
|
+
fs34.writeFileSync(envPath, ENV_TEMPLATE, { mode: 384 });
|
|
19383
|
+
fs34.chmodSync(envPath, 384);
|
|
19384
|
+
created.push(path32.join(CONFIG_DIR, "env"));
|
|
19261
19385
|
}
|
|
19262
19386
|
return { created, skipped, gitignoreUpdated, envCreated };
|
|
19263
19387
|
}
|
|
19264
19388
|
function ensureGitignoreEntry(projectRoot, entry) {
|
|
19265
|
-
const gitignorePath =
|
|
19266
|
-
const existing =
|
|
19389
|
+
const gitignorePath = path32.join(projectRoot, ".gitignore");
|
|
19390
|
+
const existing = fs34.existsSync(gitignorePath) ? fs34.readFileSync(gitignorePath, "utf8") : "";
|
|
19267
19391
|
const alreadyPresent = existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === entry || line === `/${entry}`);
|
|
19268
19392
|
if (alreadyPresent) return false;
|
|
19269
19393
|
const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
19270
|
-
|
|
19394
|
+
fs34.appendFileSync(gitignorePath, `${prefix}
|
|
19271
19395
|
# pi pod secrets \u2014 never commit
|
|
19272
19396
|
${entry}
|
|
19273
19397
|
`);
|
|
@@ -19281,7 +19405,7 @@ function printInitResult(result, projectRoot) {
|
|
|
19281
19405
|
const steps = [
|
|
19282
19406
|
[`edit ${CONFIG_DIR}/config.json and init.sh`, "what this project's pods get"],
|
|
19283
19407
|
[`edit ${CONFIG_DIR}/env`, "this project's own keys"],
|
|
19284
|
-
["pipod", `launch a pod for ${
|
|
19408
|
+
["pipod", `launch a pod for ${path32.basename(projectRoot)}; the first launch offers to keep these in a template`]
|
|
19285
19409
|
].map(([command, why]) => `${command.padEnd(40)} ${color.dim(`# ${why}`)}`);
|
|
19286
19410
|
plain("");
|
|
19287
19411
|
plain("Next steps:");
|
|
@@ -19291,8 +19415,8 @@ function printInitResult(result, projectRoot) {
|
|
|
19291
19415
|
|
|
19292
19416
|
// src/update.ts
|
|
19293
19417
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
19294
|
-
import * as
|
|
19295
|
-
import * as
|
|
19418
|
+
import * as fs35 from "fs";
|
|
19419
|
+
import * as path33 from "path";
|
|
19296
19420
|
function run(cmd, args, cwd) {
|
|
19297
19421
|
const r = spawnSync3(cmd, args, { cwd, encoding: "utf8" });
|
|
19298
19422
|
return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
@@ -19301,13 +19425,13 @@ function runVisible(cmd, args, cwd) {
|
|
|
19301
19425
|
return spawnSync3(cmd, args, { cwd, stdio: "inherit" }).status === 0;
|
|
19302
19426
|
}
|
|
19303
19427
|
function detectInstall(root = packageRoot()) {
|
|
19304
|
-
if (
|
|
19305
|
-
if (!root.split(
|
|
19428
|
+
if (fs35.existsSync(path33.join(root, ".git"))) return { kind: "git", root };
|
|
19429
|
+
if (!root.split(path33.sep).includes("node_modules") && run("git", ["rev-parse", "--is-inside-work-tree"], root).out === "true") {
|
|
19306
19430
|
return { kind: "git", root };
|
|
19307
19431
|
}
|
|
19308
|
-
if (root.split(
|
|
19432
|
+
if (root.split(path33.sep).includes("node_modules")) {
|
|
19309
19433
|
try {
|
|
19310
|
-
const pkg = JSON.parse(
|
|
19434
|
+
const pkg = JSON.parse(fs35.readFileSync(path33.join(root, "package.json"), "utf8"));
|
|
19311
19435
|
if (pkg.name) return { kind: "npm", root, name: pkg.name };
|
|
19312
19436
|
} catch {
|
|
19313
19437
|
}
|
|
@@ -19316,7 +19440,7 @@ function detectInstall(root = packageRoot()) {
|
|
|
19316
19440
|
}
|
|
19317
19441
|
function versionAt(root) {
|
|
19318
19442
|
try {
|
|
19319
|
-
const pkg = JSON.parse(
|
|
19443
|
+
const pkg = JSON.parse(fs35.readFileSync(path33.join(root, "package.json"), "utf8"));
|
|
19320
19444
|
return pkg.version ?? "0.0.0";
|
|
19321
19445
|
} catch {
|
|
19322
19446
|
return "0.0.0";
|
|
@@ -19391,16 +19515,16 @@ async function updateCheckout(root, opts) {
|
|
|
19391
19515
|
return rebuild(root, before);
|
|
19392
19516
|
}
|
|
19393
19517
|
function distIsStale(root) {
|
|
19394
|
-
const srcDir =
|
|
19395
|
-
if (!
|
|
19396
|
-
const dist =
|
|
19397
|
-
if (!
|
|
19398
|
-
const distTime =
|
|
19518
|
+
const srcDir = path33.join(root, "src");
|
|
19519
|
+
if (!fs35.existsSync(srcDir)) return false;
|
|
19520
|
+
const dist = path33.join(root, "dist", "cli.js");
|
|
19521
|
+
if (!fs35.existsSync(dist)) return true;
|
|
19522
|
+
const distTime = fs35.statSync(dist).mtimeMs;
|
|
19399
19523
|
const newest = (dir) => {
|
|
19400
19524
|
let max = 0;
|
|
19401
|
-
for (const entry of
|
|
19402
|
-
const p =
|
|
19403
|
-
max = Math.max(max, entry.isDirectory() ? newest(p) :
|
|
19525
|
+
for (const entry of fs35.readdirSync(dir, { withFileTypes: true })) {
|
|
19526
|
+
const p = path33.join(dir, entry.name);
|
|
19527
|
+
max = Math.max(max, entry.isDirectory() ? newest(p) : fs35.statSync(p).mtimeMs);
|
|
19404
19528
|
}
|
|
19405
19529
|
return max;
|
|
19406
19530
|
};
|
|
@@ -19421,8 +19545,8 @@ function rebuild(root, before) {
|
|
|
19421
19545
|
const head = run("git", ["rev-parse", "--short", "HEAD"], root).out;
|
|
19422
19546
|
plain("");
|
|
19423
19547
|
info(after === before ? `now at ${color.bold(after)} (${head})` : `updated ${before} \u2192 ${color.bold(after)} (${head})`);
|
|
19424
|
-
const upgrade =
|
|
19425
|
-
if (
|
|
19548
|
+
const upgrade = path33.join(run("git", ["rev-parse", "--show-toplevel"], root).out, "selfhost", "upgrade");
|
|
19549
|
+
if (fs35.existsSync(upgrade)) out(`if this checkout runs your server, upgrade it to match: ${upgrade}`);
|
|
19426
19550
|
out("run `pipod doctor` to check the new version against this repo");
|
|
19427
19551
|
}
|
|
19428
19552
|
async function updateFromRegistry(root, name, opts) {
|
|
@@ -19490,472 +19614,6 @@ var ALIASES = {
|
|
|
19490
19614
|
a: "attach",
|
|
19491
19615
|
ls: "list"
|
|
19492
19616
|
};
|
|
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
19617
|
var SELF_PARSING_SUBCOMMANDS = /* @__PURE__ */ new Set(["credentials", "secrets", "templates", "push", "pull", "diff", "settings", "billing"]);
|
|
19960
19618
|
var UNIVERSAL_FLAGS = /* @__PURE__ */ new Set(["--verbose", "--version", "--help"]);
|
|
19961
19619
|
var FLAG_SCOPE = {
|
|
@@ -20131,15 +19789,22 @@ function parseArgs(argv) {
|
|
|
20131
19789
|
});
|
|
20132
19790
|
}
|
|
20133
19791
|
}
|
|
20134
|
-
let commandPositionals = positionals;
|
|
20135
19792
|
if (positionals[0] === "help") {
|
|
20136
19793
|
flags.help = true;
|
|
20137
19794
|
seenFlags.add("--help");
|
|
20138
|
-
|
|
19795
|
+
const name = positionals[1];
|
|
19796
|
+
return {
|
|
19797
|
+
subcommand: null,
|
|
19798
|
+
subcommandArgs: [],
|
|
19799
|
+
flags,
|
|
19800
|
+
piArgs,
|
|
19801
|
+
seenFlags,
|
|
19802
|
+
...name !== void 0 ? { helpEntry: resolveSubcommand(name) ?? name } : {}
|
|
19803
|
+
};
|
|
20139
19804
|
}
|
|
20140
|
-
const subcommand = resolveSubcommand(
|
|
20141
|
-
if (!subcommand &&
|
|
20142
|
-
throw new PiPodError(`unknown pi pod command "${
|
|
19805
|
+
const subcommand = resolveSubcommand(positionals[0]);
|
|
19806
|
+
if (!subcommand && positionals.length > 0) {
|
|
19807
|
+
throw new PiPodError(`unknown pi pod command "${positionals[0]}"`, {
|
|
20143
19808
|
hint: 'pass all Pi arguments after `--`, for example: pipod -- "review this change"',
|
|
20144
19809
|
exitCode: EXIT.USAGE
|
|
20145
19810
|
});
|
|
@@ -20152,7 +19817,7 @@ function parseArgs(argv) {
|
|
|
20152
19817
|
}
|
|
20153
19818
|
return {
|
|
20154
19819
|
subcommand,
|
|
20155
|
-
subcommandArgs: subcommand ?
|
|
19820
|
+
subcommandArgs: subcommand ? positionals.slice(1) : [],
|
|
20156
19821
|
flags,
|
|
20157
19822
|
piArgs,
|
|
20158
19823
|
seenFlags
|
|
@@ -20175,10 +19840,14 @@ function canonicalFlagName(name) {
|
|
|
20175
19840
|
}
|
|
20176
19841
|
}
|
|
20177
19842
|
async function main(argv) {
|
|
20178
|
-
const { subcommand, subcommandArgs, flags, piArgs, seenFlags } = parseArgs(argv);
|
|
19843
|
+
const { subcommand, subcommandArgs, flags, piArgs, seenFlags, helpEntry } = parseArgs(argv);
|
|
20179
19844
|
setVerbose(flags.verbose);
|
|
20180
19845
|
if (flags.help) {
|
|
20181
|
-
process.stdout.write(
|
|
19846
|
+
if (helpEntry !== void 0) process.stdout.write(manualEntry(helpEntry));
|
|
19847
|
+
else if (subcommand) process.stdout.write(usage(subcommand));
|
|
19848
|
+
else process.stdout.write(`pipod ${VERSION} \u2014 run pi sessions in server-managed pods
|
|
19849
|
+
|
|
19850
|
+
${usage(null)}`);
|
|
20182
19851
|
return EXIT.OK;
|
|
20183
19852
|
}
|
|
20184
19853
|
for (const flag of checkFlagScope(subcommand, seenFlags)) {
|
|
@@ -20291,7 +19960,7 @@ function announceUserConfig() {
|
|
|
20291
19960
|
function runInit(flags) {
|
|
20292
19961
|
const cwd = process.cwd();
|
|
20293
19962
|
const existing = findConfigPath(cwd);
|
|
20294
|
-
if (existing !== null &&
|
|
19963
|
+
if (existing !== null && path34.dirname(path34.dirname(existing)) !== fs36.realpathSync(cwd)) {
|
|
20295
19964
|
info(`note: ${color.bold(displayPath(existing))} defines an enclosing project; this scaffold shadows it here`);
|
|
20296
19965
|
}
|
|
20297
19966
|
const result = scaffold({ projectRoot: cwd, force: flags.force });
|
|
@@ -20319,7 +19988,7 @@ function reportError(errorValue) {
|
|
|
20319
19988
|
if (errorValue instanceof Error && errorValue.stack) plain(color.dim(errorValue.stack));
|
|
20320
19989
|
return EXIT.FAILURE;
|
|
20321
19990
|
}
|
|
20322
|
-
var invokedDirectly = process.argv[1] !== void 0 && ["cli.js", "cli.ts", "pipod", "pi-pod", "pp"].includes(
|
|
19991
|
+
var invokedDirectly = process.argv[1] !== void 0 && ["cli.js", "cli.ts", "pipod", "pi-pod", "pp"].includes(path34.basename(process.argv[1]));
|
|
20323
19992
|
if (invokedDirectly) {
|
|
20324
19993
|
for (const stream of [process.stdout, process.stderr]) {
|
|
20325
19994
|
stream.on("error", (failure) => {
|