@pipod/cli 0.1.12 → 0.1.13
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 +91 -70
- package/dist/cli.js.map +1 -1
- package/docs/config-simplification.md +6 -6
- package/man/pipod-config.5 +42 -3
- package/man/pipod-config.5.md +40 -2
- package/man/pipod.1 +26 -16
- package/man/pipod.1.md +25 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -18958,7 +18958,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18958
18958
|
const ref = layer.name ?? source.templateRef;
|
|
18959
18959
|
if (!ref) {
|
|
18960
18960
|
throw new PiPodError("this project does not pin a template", {
|
|
18961
|
-
hint: "name one
|
|
18961
|
+
hint: "name one after `template`, as in `template <name>`; a first launch offers to bootstrap and pin one"
|
|
18962
18962
|
});
|
|
18963
18963
|
}
|
|
18964
18964
|
const template = await findTemplate(client, ref);
|
|
@@ -18968,6 +18968,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18968
18968
|
bundle: templateBundle(template),
|
|
18969
18969
|
version,
|
|
18970
18970
|
secretNames: listed2.secrets.map((entry) => entry.name),
|
|
18971
|
+
name: `template ${template.name}`,
|
|
18971
18972
|
label: `template ${template.name}${version !== void 0 ? ` v${version}` : ""}`,
|
|
18972
18973
|
template,
|
|
18973
18974
|
scope: "template",
|
|
@@ -18980,6 +18981,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18980
18981
|
bundle: settingsBundle(current2),
|
|
18981
18982
|
version: current2.version,
|
|
18982
18983
|
secretNames: [],
|
|
18984
|
+
name: "org policy",
|
|
18983
18985
|
label: `org policy v${current2.version}`,
|
|
18984
18986
|
scope: "policy",
|
|
18985
18987
|
scopeId: client.orgId
|
|
@@ -18992,11 +18994,25 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18992
18994
|
bundle: settingsBundle(current),
|
|
18993
18995
|
version: current.version,
|
|
18994
18996
|
secretNames: listed.secrets.map((entry) => entry.name),
|
|
18997
|
+
name: `${layer.kind} settings`,
|
|
18995
18998
|
label: `${layer.kind} settings v${current.version}`,
|
|
18996
18999
|
scope: layer.kind,
|
|
18997
19000
|
scopeId
|
|
18998
19001
|
};
|
|
18999
19002
|
}
|
|
19003
|
+
async function writeLayerConfig(client, remote, config) {
|
|
19004
|
+
if (remote.scope === "template") {
|
|
19005
|
+
const updated = await client.updateTemplate(remote.scopeId, {
|
|
19006
|
+
...remote.version !== void 0 ? { expectedVersion: remote.version } : {},
|
|
19007
|
+
config
|
|
19008
|
+
});
|
|
19009
|
+
return updated.version;
|
|
19010
|
+
}
|
|
19011
|
+
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19012
|
+
const body = { config, version: remote.version };
|
|
19013
|
+
const written = remote.scope === "policy" ? await client.putOrgPolicy(body) : remote.scope === "user" ? await client.putUserSettings(body) : await client.putOrgSettings(body);
|
|
19014
|
+
return written.version;
|
|
19015
|
+
}
|
|
19000
19016
|
function localLabel(layer, flags) {
|
|
19001
19017
|
if (layer.kind === "template") return "project";
|
|
19002
19018
|
if (layer.kind === "user") return "~/.pi-pod + ~/.pi/agent";
|
|
@@ -19097,12 +19113,12 @@ async function runPush(args, flags) {
|
|
|
19097
19113
|
await syncEnvSecrets(flags.client, updated, source.env, { withSecrets: parsed.withSecrets, reportStale: true });
|
|
19098
19114
|
return 0;
|
|
19099
19115
|
}
|
|
19100
|
-
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19101
19116
|
if (remote.scope === "policy") {
|
|
19102
|
-
const
|
|
19103
|
-
info(`pushed ${local} to org policy v${
|
|
19117
|
+
const version = await writeLayerConfig(flags.client, remote, source.config);
|
|
19118
|
+
info(`pushed ${local} to org policy v${version}`);
|
|
19104
19119
|
return 0;
|
|
19105
19120
|
}
|
|
19121
|
+
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19106
19122
|
const body = {
|
|
19107
19123
|
config: source.config,
|
|
19108
19124
|
version: remote.version,
|
|
@@ -19147,53 +19163,93 @@ import * as fs32 from "fs";
|
|
|
19147
19163
|
import * as os7 from "os";
|
|
19148
19164
|
import * as path30 from "path";
|
|
19149
19165
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
19150
|
-
var USAGE = "usage: pipod settings
|
|
19151
|
-
|
|
19152
|
-
|
|
19166
|
+
var USAGE = "usage: pipod settings [user | org | policy | template [<name>]] [show | edit | set <key> <value> | unset <key>]";
|
|
19167
|
+
var ACTIONS = ["show", "edit", "set", "unset"];
|
|
19168
|
+
async function runSettings(args, flags) {
|
|
19169
|
+
const { layer, rest } = parseLayer(args);
|
|
19170
|
+
const command = parseCommand(layer, rest);
|
|
19171
|
+
const remote = await fetchRemoteLayer(flags.client, layer, {
|
|
19172
|
+
templateRef: layer.kind === "template" && layer.name === void 0 ? pinnedTemplateRef(flags) : null
|
|
19173
|
+
});
|
|
19174
|
+
if (command.action === "show") {
|
|
19175
|
+
out(JSON.stringify({ ...remote.bundle, version: remote.version }, null, 2));
|
|
19176
|
+
return 0;
|
|
19177
|
+
}
|
|
19178
|
+
const config = structuredClone(remote.bundle.config);
|
|
19179
|
+
if (command.action === "set") setDotPath(config, command.key, command.value);
|
|
19180
|
+
if (command.action === "unset") unsetDotPath(config, command.key);
|
|
19181
|
+
const next = command.action === "edit" ? editInEditor(config, flags) : config;
|
|
19182
|
+
assertConfigSendable(remote, next);
|
|
19183
|
+
const version = await writeLayerConfig(flags.client, remote, next);
|
|
19184
|
+
const done = command.action === "edit" ? "updated" : command.action;
|
|
19185
|
+
const key = command.action === "edit" ? "" : ` ${command.key}`;
|
|
19186
|
+
info(`${done} ${remote.name}${key}${version === void 0 ? "" : ` (v${version})`}`);
|
|
19187
|
+
return 0;
|
|
19188
|
+
}
|
|
19189
|
+
function parseLayer(args) {
|
|
19190
|
+
const [scope = "user", ...rest] = args;
|
|
19191
|
+
if (scope === "user" || scope === "org" || scope === "policy") return { layer: { kind: scope }, rest };
|
|
19192
|
+
if (scope === "template") {
|
|
19193
|
+
const [name, ...afterName] = rest;
|
|
19194
|
+
return name === void 0 || ACTIONS.includes(name) ? { layer: { kind: "template" }, rest } : { layer: { kind: "template", name }, rest: afterName };
|
|
19195
|
+
}
|
|
19196
|
+
throw new PiPodError(`unknown settings layer "${scope}"`, { hint: USAGE });
|
|
19197
|
+
}
|
|
19198
|
+
function parseCommand(layer, args) {
|
|
19199
|
+
const words = layer.kind === "template" && layer.name !== void 0 ? `template ${layer.name}` : layer.kind;
|
|
19200
|
+
const [action = "show", ...rest] = args;
|
|
19201
|
+
switch (action) {
|
|
19202
|
+
case "show":
|
|
19203
|
+
case "edit":
|
|
19204
|
+
if (rest.length > 0) throw new PiPodError(`usage: pipod settings ${words} ${action}`);
|
|
19205
|
+
return { action };
|
|
19206
|
+
case "set": {
|
|
19207
|
+
const [key, raw, ...extra] = rest;
|
|
19208
|
+
if (!key || raw === void 0 || extra.length > 0) throw new PiPodError(`usage: pipod settings ${words} set <key> <value>`);
|
|
19209
|
+
let value;
|
|
19210
|
+
try {
|
|
19211
|
+
value = JSON.parse(raw);
|
|
19212
|
+
} catch {
|
|
19213
|
+
value = raw;
|
|
19214
|
+
}
|
|
19215
|
+
return { action, key, value };
|
|
19216
|
+
}
|
|
19217
|
+
case "unset": {
|
|
19218
|
+
const [key, ...extra] = rest;
|
|
19219
|
+
if (!key || extra.length > 0) throw new PiPodError(`usage: pipod settings ${words} unset <key>`);
|
|
19220
|
+
return { action, key };
|
|
19221
|
+
}
|
|
19222
|
+
default:
|
|
19223
|
+
throw new PiPodError(`unknown settings action "${action}"`, { hint: "actions: show, edit, set, unset" });
|
|
19224
|
+
}
|
|
19153
19225
|
}
|
|
19154
|
-
function
|
|
19155
|
-
|
|
19226
|
+
function pinnedTemplateRef(flags) {
|
|
19227
|
+
const configPath = findConfigPath(flags.cwd ?? process.cwd(), { home: flags.home });
|
|
19228
|
+
if (!configPath) return null;
|
|
19229
|
+
const raw = readObject(configPath);
|
|
19230
|
+
return takeClientOnlyKeys(raw).template || null;
|
|
19156
19231
|
}
|
|
19157
|
-
function
|
|
19232
|
+
function assertConfigSendable(remote, config) {
|
|
19233
|
+
if (remote.scope === "policy") return;
|
|
19158
19234
|
const dropped = nonBundleKeysFound(config);
|
|
19159
19235
|
if (dropped.length > 0) {
|
|
19160
19236
|
throw new PiPodError(`not sent: the server bundle no longer accepts ${dropped.join(", ")}`, {
|
|
19161
19237
|
hint: "local-only keys stay in local files (`template`, `secretResolver`, `pi.chords`); pass --reuse at launch; retired keys must be deleted"
|
|
19162
19238
|
});
|
|
19163
19239
|
}
|
|
19164
|
-
}
|
|
19165
|
-
function assertBundleConfigValid(config, scope) {
|
|
19166
19240
|
const report = validateConfig(config);
|
|
19167
|
-
for (const warning of report.warnings) warn(`${
|
|
19241
|
+
for (const warning of report.warnings) warn(`${remote.name}: ${warning}`);
|
|
19168
19242
|
if (report.errors.length > 0) {
|
|
19169
|
-
throw new PiPodError(`invalid ${
|
|
19243
|
+
throw new PiPodError(`invalid ${remote.name} config: ${report.errors.join("; ")}`, {
|
|
19170
19244
|
hint: "fix the config or edit it back \u2014 nothing was sent"
|
|
19171
19245
|
});
|
|
19172
19246
|
}
|
|
19173
19247
|
}
|
|
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);
|
|
19248
|
+
function editInEditor(config, flags) {
|
|
19193
19249
|
const directory = fs32.mkdtempSync(path30.join(os7.tmpdir(), "pi-pod-settings-"));
|
|
19194
19250
|
const file = path30.join(directory, "config.json");
|
|
19195
19251
|
try {
|
|
19196
|
-
fs32.writeFileSync(file, `${JSON.stringify(
|
|
19252
|
+
fs32.writeFileSync(file, `${JSON.stringify(config, null, 2)}
|
|
19197
19253
|
`, { mode: 384 });
|
|
19198
19254
|
const editor = flags.editor ?? process.env["VISUAL"] ?? process.env["EDITOR"];
|
|
19199
19255
|
if (!editor) throw new PiPodError("$EDITOR is not set");
|
|
@@ -19203,46 +19259,11 @@ async function editSettings(scope, flags) {
|
|
|
19203
19259
|
});
|
|
19204
19260
|
if (edited.error) throw new PiPodError(`could not run editor: ${edited.error.message}`);
|
|
19205
19261
|
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;
|
|
19262
|
+
return readObject(file);
|
|
19212
19263
|
} finally {
|
|
19213
19264
|
fs32.rmSync(directory, { recursive: true, force: true });
|
|
19214
19265
|
}
|
|
19215
19266
|
}
|
|
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
19267
|
function unsetDotPath(target, dotPath) {
|
|
19247
19268
|
const [head, ...rest] = dotPath.split(".");
|
|
19248
19269
|
const child = target[head];
|