@pipod/cli 0.1.12 → 0.1.14
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 +120 -76
- package/dist/cli.js.map +1 -1
- package/docs/config-simplification.md +6 -6
- package/man/pipod-config.5 +54 -3
- package/man/pipod-config.5.md +53 -2
- package/man/pipod.1 +26 -16
- package/man/pipod.1.md +25 -15
- package/package.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -11829,7 +11829,7 @@ function isRecord2(value) {
|
|
|
11829
11829
|
|
|
11830
11830
|
// src/account/gateway-rpc.ts
|
|
11831
11831
|
import { randomBytes as randomBytes5, randomUUID as randomUUID4 } from "crypto";
|
|
11832
|
-
import
|
|
11832
|
+
import WebSocket3 from "ws";
|
|
11833
11833
|
|
|
11834
11834
|
// src/client/stream-events.ts
|
|
11835
11835
|
function clone(value) {
|
|
@@ -12384,6 +12384,29 @@ function asleepReasonFromError(error2) {
|
|
|
12384
12384
|
return "idle_stop";
|
|
12385
12385
|
}
|
|
12386
12386
|
|
|
12387
|
+
// src/account/session-websocket.ts
|
|
12388
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
12389
|
+
import { getProxyForUrl } from "proxy-from-env";
|
|
12390
|
+
import WebSocket2 from "ws";
|
|
12391
|
+
function createSessionWebSocket(url) {
|
|
12392
|
+
const target = new URL(url);
|
|
12393
|
+
const transportUrl = new URL(url);
|
|
12394
|
+
transportUrl.protocol = target.protocol === "wss:" ? "https:" : "http:";
|
|
12395
|
+
const proxy = getProxyForUrl(target.href) || getProxyForUrl(transportUrl.href);
|
|
12396
|
+
if (!proxy) return new WebSocket2(url);
|
|
12397
|
+
let agent;
|
|
12398
|
+
try {
|
|
12399
|
+
const proxyUrl = new URL(proxy);
|
|
12400
|
+
if (proxyUrl.protocol !== "http:" && proxyUrl.protocol !== "https:") {
|
|
12401
|
+
throw new Error("unsupported proxy protocol");
|
|
12402
|
+
}
|
|
12403
|
+
agent = new HttpsProxyAgent(proxyUrl);
|
|
12404
|
+
} catch {
|
|
12405
|
+
throw new Error("Invalid session proxy configuration: expected an HTTP(S) proxy URL");
|
|
12406
|
+
}
|
|
12407
|
+
return new WebSocket2(url, { agent });
|
|
12408
|
+
}
|
|
12409
|
+
|
|
12387
12410
|
// src/account/gateway-rpc.ts
|
|
12388
12411
|
var GATEWAY_MODELS_TIMEOUT_MS = 5e3;
|
|
12389
12412
|
var FILE_LIST_MAX_ENTRIES = 50;
|
|
@@ -12797,7 +12820,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
12797
12820
|
*/
|
|
12798
12821
|
async wake() {
|
|
12799
12822
|
if (this.userClosed) throw new PiPodError("connection to the pi session was closed");
|
|
12800
|
-
if (!this.closed && this.ws?.readyState ===
|
|
12823
|
+
if (!this.closed && this.ws?.readyState === WebSocket3.OPEN) return;
|
|
12801
12824
|
if (this.waking) {
|
|
12802
12825
|
await this.waitUntilConnected();
|
|
12803
12826
|
return;
|
|
@@ -12924,7 +12947,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
12924
12947
|
}
|
|
12925
12948
|
pingWatchdogTick() {
|
|
12926
12949
|
const ws = this.ws;
|
|
12927
|
-
if (!ws || ws.readyState !==
|
|
12950
|
+
if (!ws || ws.readyState !== WebSocket3.OPEN) return;
|
|
12928
12951
|
const deadlineMs = this.opts.pongDeadlineMs ?? PONG_DEADLINE_MS;
|
|
12929
12952
|
if (this.now() - this.lastActivityAt > deadlineMs) {
|
|
12930
12953
|
debug("gateway-rpc: pong deadline exceeded; terminating half-open socket");
|
|
@@ -13057,7 +13080,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
13057
13080
|
ticket,
|
|
13058
13081
|
...this.lastSessionId ? { fromSeq: this.lastSeq, fromSession: this.lastSessionId } : {}
|
|
13059
13082
|
});
|
|
13060
|
-
const create = this.opts.createWebSocket ??
|
|
13083
|
+
const create = this.opts.createWebSocket ?? createSessionWebSocket;
|
|
13061
13084
|
const ws = create(url);
|
|
13062
13085
|
this.connectingSockets.add(ws);
|
|
13063
13086
|
let settled = false;
|
|
@@ -13542,7 +13565,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
13542
13565
|
async waitUntilConnected() {
|
|
13543
13566
|
const deadline = this.now() + (this.opts.dropRecoveryMs ?? DROP_RECOVERY_MS);
|
|
13544
13567
|
while (this.now() < deadline) {
|
|
13545
|
-
if (!this.closed && this.ws?.readyState ===
|
|
13568
|
+
if (!this.closed && this.ws?.readyState === WebSocket3.OPEN) return;
|
|
13546
13569
|
if (this.userClosed) throw new Error("connection to the pi session was closed");
|
|
13547
13570
|
await new Promise((r) => setTimeout(r, 50));
|
|
13548
13571
|
}
|
|
@@ -13599,7 +13622,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
13599
13622
|
this.pendingSemantic.get(replyType)?.accept(value);
|
|
13600
13623
|
}
|
|
13601
13624
|
wsSend(message) {
|
|
13602
|
-
if (!this.ws || this.ws.readyState !==
|
|
13625
|
+
if (!this.ws || this.ws.readyState !== WebSocket3.OPEN) {
|
|
13603
13626
|
throw this.connectionClosedError();
|
|
13604
13627
|
}
|
|
13605
13628
|
this.ws.send(JSON.stringify(message));
|
|
@@ -18958,7 +18981,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18958
18981
|
const ref = layer.name ?? source.templateRef;
|
|
18959
18982
|
if (!ref) {
|
|
18960
18983
|
throw new PiPodError("this project does not pin a template", {
|
|
18961
|
-
hint: "name one
|
|
18984
|
+
hint: "name one after `template`, as in `template <name>`; a first launch offers to bootstrap and pin one"
|
|
18962
18985
|
});
|
|
18963
18986
|
}
|
|
18964
18987
|
const template = await findTemplate(client, ref);
|
|
@@ -18968,6 +18991,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18968
18991
|
bundle: templateBundle(template),
|
|
18969
18992
|
version,
|
|
18970
18993
|
secretNames: listed2.secrets.map((entry) => entry.name),
|
|
18994
|
+
name: `template ${template.name}`,
|
|
18971
18995
|
label: `template ${template.name}${version !== void 0 ? ` v${version}` : ""}`,
|
|
18972
18996
|
template,
|
|
18973
18997
|
scope: "template",
|
|
@@ -18980,6 +19004,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18980
19004
|
bundle: settingsBundle(current2),
|
|
18981
19005
|
version: current2.version,
|
|
18982
19006
|
secretNames: [],
|
|
19007
|
+
name: "org policy",
|
|
18983
19008
|
label: `org policy v${current2.version}`,
|
|
18984
19009
|
scope: "policy",
|
|
18985
19010
|
scopeId: client.orgId
|
|
@@ -18992,11 +19017,25 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18992
19017
|
bundle: settingsBundle(current),
|
|
18993
19018
|
version: current.version,
|
|
18994
19019
|
secretNames: listed.secrets.map((entry) => entry.name),
|
|
19020
|
+
name: `${layer.kind} settings`,
|
|
18995
19021
|
label: `${layer.kind} settings v${current.version}`,
|
|
18996
19022
|
scope: layer.kind,
|
|
18997
19023
|
scopeId
|
|
18998
19024
|
};
|
|
18999
19025
|
}
|
|
19026
|
+
async function writeLayerConfig(client, remote, config) {
|
|
19027
|
+
if (remote.scope === "template") {
|
|
19028
|
+
const updated = await client.updateTemplate(remote.scopeId, {
|
|
19029
|
+
...remote.version !== void 0 ? { expectedVersion: remote.version } : {},
|
|
19030
|
+
config
|
|
19031
|
+
});
|
|
19032
|
+
return updated.version;
|
|
19033
|
+
}
|
|
19034
|
+
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19035
|
+
const body = { config, version: remote.version };
|
|
19036
|
+
const written = remote.scope === "policy" ? await client.putOrgPolicy(body) : remote.scope === "user" ? await client.putUserSettings(body) : await client.putOrgSettings(body);
|
|
19037
|
+
return written.version;
|
|
19038
|
+
}
|
|
19000
19039
|
function localLabel(layer, flags) {
|
|
19001
19040
|
if (layer.kind === "template") return "project";
|
|
19002
19041
|
if (layer.kind === "user") return "~/.pi-pod + ~/.pi/agent";
|
|
@@ -19097,12 +19136,12 @@ async function runPush(args, flags) {
|
|
|
19097
19136
|
await syncEnvSecrets(flags.client, updated, source.env, { withSecrets: parsed.withSecrets, reportStale: true });
|
|
19098
19137
|
return 0;
|
|
19099
19138
|
}
|
|
19100
|
-
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19101
19139
|
if (remote.scope === "policy") {
|
|
19102
|
-
const
|
|
19103
|
-
info(`pushed ${local} to org policy v${
|
|
19140
|
+
const version = await writeLayerConfig(flags.client, remote, source.config);
|
|
19141
|
+
info(`pushed ${local} to org policy v${version}`);
|
|
19104
19142
|
return 0;
|
|
19105
19143
|
}
|
|
19144
|
+
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19106
19145
|
const body = {
|
|
19107
19146
|
config: source.config,
|
|
19108
19147
|
version: remote.version,
|
|
@@ -19147,53 +19186,93 @@ import * as fs32 from "fs";
|
|
|
19147
19186
|
import * as os7 from "os";
|
|
19148
19187
|
import * as path30 from "path";
|
|
19149
19188
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
19150
|
-
var USAGE = "usage: pipod settings
|
|
19151
|
-
|
|
19152
|
-
|
|
19189
|
+
var USAGE = "usage: pipod settings [user | org | policy | template [<name>]] [show | edit | set <key> <value> | unset <key>]";
|
|
19190
|
+
var ACTIONS = ["show", "edit", "set", "unset"];
|
|
19191
|
+
async function runSettings(args, flags) {
|
|
19192
|
+
const { layer, rest } = parseLayer(args);
|
|
19193
|
+
const command = parseCommand(layer, rest);
|
|
19194
|
+
const remote = await fetchRemoteLayer(flags.client, layer, {
|
|
19195
|
+
templateRef: layer.kind === "template" && layer.name === void 0 ? pinnedTemplateRef(flags) : null
|
|
19196
|
+
});
|
|
19197
|
+
if (command.action === "show") {
|
|
19198
|
+
out(JSON.stringify({ ...remote.bundle, version: remote.version }, null, 2));
|
|
19199
|
+
return 0;
|
|
19200
|
+
}
|
|
19201
|
+
const config = structuredClone(remote.bundle.config);
|
|
19202
|
+
if (command.action === "set") setDotPath(config, command.key, command.value);
|
|
19203
|
+
if (command.action === "unset") unsetDotPath(config, command.key);
|
|
19204
|
+
const next = command.action === "edit" ? editInEditor(config, flags) : config;
|
|
19205
|
+
assertConfigSendable(remote, next);
|
|
19206
|
+
const version = await writeLayerConfig(flags.client, remote, next);
|
|
19207
|
+
const done = command.action === "edit" ? "updated" : command.action;
|
|
19208
|
+
const key = command.action === "edit" ? "" : ` ${command.key}`;
|
|
19209
|
+
info(`${done} ${remote.name}${key}${version === void 0 ? "" : ` (v${version})`}`);
|
|
19210
|
+
return 0;
|
|
19211
|
+
}
|
|
19212
|
+
function parseLayer(args) {
|
|
19213
|
+
const [scope = "user", ...rest] = args;
|
|
19214
|
+
if (scope === "user" || scope === "org" || scope === "policy") return { layer: { kind: scope }, rest };
|
|
19215
|
+
if (scope === "template") {
|
|
19216
|
+
const [name, ...afterName] = rest;
|
|
19217
|
+
return name === void 0 || ACTIONS.includes(name) ? { layer: { kind: "template" }, rest } : { layer: { kind: "template", name }, rest: afterName };
|
|
19218
|
+
}
|
|
19219
|
+
throw new PiPodError(`unknown settings layer "${scope}"`, { hint: USAGE });
|
|
19220
|
+
}
|
|
19221
|
+
function parseCommand(layer, args) {
|
|
19222
|
+
const words = layer.kind === "template" && layer.name !== void 0 ? `template ${layer.name}` : layer.kind;
|
|
19223
|
+
const [action = "show", ...rest] = args;
|
|
19224
|
+
switch (action) {
|
|
19225
|
+
case "show":
|
|
19226
|
+
case "edit":
|
|
19227
|
+
if (rest.length > 0) throw new PiPodError(`usage: pipod settings ${words} ${action}`);
|
|
19228
|
+
return { action };
|
|
19229
|
+
case "set": {
|
|
19230
|
+
const [key, raw, ...extra] = rest;
|
|
19231
|
+
if (!key || raw === void 0 || extra.length > 0) throw new PiPodError(`usage: pipod settings ${words} set <key> <value>`);
|
|
19232
|
+
let value;
|
|
19233
|
+
try {
|
|
19234
|
+
value = JSON.parse(raw);
|
|
19235
|
+
} catch {
|
|
19236
|
+
value = raw;
|
|
19237
|
+
}
|
|
19238
|
+
return { action, key, value };
|
|
19239
|
+
}
|
|
19240
|
+
case "unset": {
|
|
19241
|
+
const [key, ...extra] = rest;
|
|
19242
|
+
if (!key || extra.length > 0) throw new PiPodError(`usage: pipod settings ${words} unset <key>`);
|
|
19243
|
+
return { action, key };
|
|
19244
|
+
}
|
|
19245
|
+
default:
|
|
19246
|
+
throw new PiPodError(`unknown settings action "${action}"`, { hint: "actions: show, edit, set, unset" });
|
|
19247
|
+
}
|
|
19153
19248
|
}
|
|
19154
|
-
function
|
|
19155
|
-
|
|
19249
|
+
function pinnedTemplateRef(flags) {
|
|
19250
|
+
const configPath = findConfigPath(flags.cwd ?? process.cwd(), { home: flags.home });
|
|
19251
|
+
if (!configPath) return null;
|
|
19252
|
+
const raw = readObject(configPath);
|
|
19253
|
+
return takeClientOnlyKeys(raw).template || null;
|
|
19156
19254
|
}
|
|
19157
|
-
function
|
|
19255
|
+
function assertConfigSendable(remote, config) {
|
|
19256
|
+
if (remote.scope === "policy") return;
|
|
19158
19257
|
const dropped = nonBundleKeysFound(config);
|
|
19159
19258
|
if (dropped.length > 0) {
|
|
19160
19259
|
throw new PiPodError(`not sent: the server bundle no longer accepts ${dropped.join(", ")}`, {
|
|
19161
19260
|
hint: "local-only keys stay in local files (`template`, `secretResolver`, `pi.chords`); pass --reuse at launch; retired keys must be deleted"
|
|
19162
19261
|
});
|
|
19163
19262
|
}
|
|
19164
|
-
}
|
|
19165
|
-
function assertBundleConfigValid(config, scope) {
|
|
19166
19263
|
const report = validateConfig(config);
|
|
19167
|
-
for (const warning of report.warnings) warn(`${
|
|
19264
|
+
for (const warning of report.warnings) warn(`${remote.name}: ${warning}`);
|
|
19168
19265
|
if (report.errors.length > 0) {
|
|
19169
|
-
throw new PiPodError(`invalid ${
|
|
19266
|
+
throw new PiPodError(`invalid ${remote.name} config: ${report.errors.join("; ")}`, {
|
|
19170
19267
|
hint: "fix the config or edit it back \u2014 nothing was sent"
|
|
19171
19268
|
});
|
|
19172
19269
|
}
|
|
19173
19270
|
}
|
|
19174
|
-
|
|
19175
|
-
const [scope = "user", action = "show", ...rest] = args;
|
|
19176
|
-
if (scope !== "user" && scope !== "org") {
|
|
19177
|
-
throw new PiPodError(`unknown settings scope "${scope}"`, {
|
|
19178
|
-
hint: `${USAGE}; templates are managed with \`pipod templates\`, and org policy with \`pipod pull|diff|push policy\``
|
|
19179
|
-
});
|
|
19180
|
-
}
|
|
19181
|
-
if (action === "show") {
|
|
19182
|
-
if (rest.length > 0) throw new PiPodError(`usage: pipod settings ${scope}`);
|
|
19183
|
-
out(JSON.stringify(await readBundle(flags.client, scope), null, 2));
|
|
19184
|
-
return 0;
|
|
19185
|
-
}
|
|
19186
|
-
if (action === "edit") return editSettings(scope, flags);
|
|
19187
|
-
if (action === "set") return setSetting(scope, rest, flags.client);
|
|
19188
|
-
if (action === "unset") return unsetSetting(scope, rest, flags.client);
|
|
19189
|
-
throw new PiPodError(`unknown settings action "${action}"`, { hint: "actions: show, edit, set, unset" });
|
|
19190
|
-
}
|
|
19191
|
-
async function editSettings(scope, flags) {
|
|
19192
|
-
const current = await readBundle(flags.client, scope);
|
|
19271
|
+
function editInEditor(config, flags) {
|
|
19193
19272
|
const directory = fs32.mkdtempSync(path30.join(os7.tmpdir(), "pi-pod-settings-"));
|
|
19194
19273
|
const file = path30.join(directory, "config.json");
|
|
19195
19274
|
try {
|
|
19196
|
-
fs32.writeFileSync(file, `${JSON.stringify(
|
|
19275
|
+
fs32.writeFileSync(file, `${JSON.stringify(config, null, 2)}
|
|
19197
19276
|
`, { mode: 384 });
|
|
19198
19277
|
const editor = flags.editor ?? process.env["VISUAL"] ?? process.env["EDITOR"];
|
|
19199
19278
|
if (!editor) throw new PiPodError("$EDITOR is not set");
|
|
@@ -19203,46 +19282,11 @@ async function editSettings(scope, flags) {
|
|
|
19203
19282
|
});
|
|
19204
19283
|
if (edited.error) throw new PiPodError(`could not run editor: ${edited.error.message}`);
|
|
19205
19284
|
if (edited.status !== 0) throw new PiPodError(`editor exited with status ${edited.status ?? "unknown"}`);
|
|
19206
|
-
|
|
19207
|
-
assertBundleConfigSendable(config);
|
|
19208
|
-
assertBundleConfigValid(config, scope);
|
|
19209
|
-
const result = await writeBundle(flags.client, scope, { config, version: current.version });
|
|
19210
|
-
info(`updated ${scope} settings v${result.version}`);
|
|
19211
|
-
return 0;
|
|
19285
|
+
return readObject(file);
|
|
19212
19286
|
} finally {
|
|
19213
19287
|
fs32.rmSync(directory, { recursive: true, force: true });
|
|
19214
19288
|
}
|
|
19215
19289
|
}
|
|
19216
|
-
async function setSetting(scope, args, client) {
|
|
19217
|
-
const [dotPath, rawValue, ...extra] = args;
|
|
19218
|
-
if (!dotPath || rawValue === void 0 || extra.length > 0) {
|
|
19219
|
-
throw new PiPodError(`usage: pipod settings ${scope} set <key> <value>`);
|
|
19220
|
-
}
|
|
19221
|
-
let value;
|
|
19222
|
-
try {
|
|
19223
|
-
value = JSON.parse(rawValue);
|
|
19224
|
-
} catch {
|
|
19225
|
-
value = rawValue;
|
|
19226
|
-
}
|
|
19227
|
-
const version = await updateConfig(scope, client, (config) => setDotPath(config, dotPath, value));
|
|
19228
|
-
info(`set ${scope} setting ${dotPath} (v${version})`);
|
|
19229
|
-
return 0;
|
|
19230
|
-
}
|
|
19231
|
-
async function unsetSetting(scope, args, client) {
|
|
19232
|
-
const [dotPath, ...extra] = args;
|
|
19233
|
-
if (!dotPath || extra.length > 0) throw new PiPodError(`usage: pipod settings ${scope} unset <key>`);
|
|
19234
|
-
const version = await updateConfig(scope, client, (config) => unsetDotPath(config, dotPath));
|
|
19235
|
-
info(`unset ${scope} setting ${dotPath} (v${version})`);
|
|
19236
|
-
return 0;
|
|
19237
|
-
}
|
|
19238
|
-
async function updateConfig(scope, client, change) {
|
|
19239
|
-
const current = await readBundle(client, scope);
|
|
19240
|
-
const config = structuredClone(current.config);
|
|
19241
|
-
change(config);
|
|
19242
|
-
assertBundleConfigSendable(config);
|
|
19243
|
-
assertBundleConfigValid(config, scope);
|
|
19244
|
-
return (await writeBundle(client, scope, { config, version: current.version })).version;
|
|
19245
|
-
}
|
|
19246
19290
|
function unsetDotPath(target, dotPath) {
|
|
19247
19291
|
const [head, ...rest] = dotPath.split(".");
|
|
19248
19292
|
const child = target[head];
|