@pipod/cli 0.1.9 → 0.1.11
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 +97 -27
- package/dist/cli.js.map +1 -1
- package/docs/config-simplification.md +11 -8
- package/man/pipod-config.5 +5 -1
- package/man/pipod-config.5.md +4 -0
- package/man/pipod.1 +35 -15
- package/man/pipod.1.md +33 -14
- package/package.json +1 -1
- package/templates/user-config.jsonc +5 -1
package/dist/cli.js
CHANGED
|
@@ -1908,6 +1908,14 @@ var AccountClient = class {
|
|
|
1908
1908
|
putOrgSettings(body) {
|
|
1909
1909
|
return this.request(`/orgs/${encodeURIComponent(this.orgId)}/settings`, { method: "PUT", body });
|
|
1910
1910
|
}
|
|
1911
|
+
/** The organization's policy: its `config` holds the limits; the scripts and Pi files are always empty. */
|
|
1912
|
+
getOrgPolicy() {
|
|
1913
|
+
return this.request(`/orgs/${encodeURIComponent(this.orgId)}/policy`);
|
|
1914
|
+
}
|
|
1915
|
+
/** Replaces the whole policy, compare-and-swap on `version`. The server validates it. */
|
|
1916
|
+
putOrgPolicy(body) {
|
|
1917
|
+
return this.request(`/orgs/${encodeURIComponent(this.orgId)}/policy`, { method: "PUT", body });
|
|
1918
|
+
}
|
|
1911
1919
|
launch(body) {
|
|
1912
1920
|
return this.request("/pods", { method: "POST", body });
|
|
1913
1921
|
}
|
|
@@ -2209,10 +2217,12 @@ async function responseError(res, method, apiPath) {
|
|
|
2209
2217
|
hint: "an owner of your organization can grant it (`pipod org-admin` opens the console)"
|
|
2210
2218
|
});
|
|
2211
2219
|
}
|
|
2220
|
+
const issues = res.status === 400 && Array.isArray(body.detail) ? body.detail.filter((issue) => typeof issue === "string").slice(0, 10).map((issue) => issue.slice(0, 240)) : [];
|
|
2212
2221
|
return new PiPodError(`the pi pod server refused ${method} ${apiPath}: ${message}`, {
|
|
2213
2222
|
status: res.status,
|
|
2214
2223
|
...code ? { code } : {},
|
|
2215
|
-
...safeDetail !== void 0 ? { detail: safeDetail } : {}
|
|
2224
|
+
...safeDetail !== void 0 ? { detail: safeDetail } : {},
|
|
2225
|
+
...issues.length > 0 ? { hint: issues.join("\n") } : {}
|
|
2216
2226
|
});
|
|
2217
2227
|
}
|
|
2218
2228
|
function isSafeCapacityDetail(value) {
|
|
@@ -2349,7 +2359,7 @@ function retiredLocalKeysFound(raw) {
|
|
|
2349
2359
|
return found;
|
|
2350
2360
|
}
|
|
2351
2361
|
var CLIENT_ONLY_TOP_KEYS = ["$schema", "secretResolver"];
|
|
2352
|
-
var CLIENT_ONLY_PI_KEYS = ["chords", "sessionNaming"];
|
|
2362
|
+
var CLIENT_ONLY_PI_KEYS = ["chords", "sessionNaming", "podExtensions"];
|
|
2353
2363
|
function nonBundleKeysFound(config) {
|
|
2354
2364
|
const found = outgoingStrippedKeysFound(config);
|
|
2355
2365
|
for (const key of CLIENT_ONLY_TOP_KEYS) {
|
|
@@ -2442,7 +2452,8 @@ var DEFAULT_CONFIG = {
|
|
|
2442
2452
|
args: [],
|
|
2443
2453
|
hostConfig: DEFAULT_HOST_CONFIG,
|
|
2444
2454
|
chords: { enabled: true, prefix: DEFAULT_CHORD_PREFIX, bindings: DEFAULT_CHORD_BINDINGS },
|
|
2445
|
-
sessionNaming: "auto"
|
|
2455
|
+
sessionNaming: "auto",
|
|
2456
|
+
podExtensions: "ask"
|
|
2446
2457
|
},
|
|
2447
2458
|
idleTimeoutMinutes: 15,
|
|
2448
2459
|
archiveAfterMinutes: 60,
|
|
@@ -2746,7 +2757,8 @@ function validateConfig(raw) {
|
|
|
2746
2757
|
"args",
|
|
2747
2758
|
"hostConfig",
|
|
2748
2759
|
"chords",
|
|
2749
|
-
"sessionNaming"
|
|
2760
|
+
"sessionNaming",
|
|
2761
|
+
"podExtensions"
|
|
2750
2762
|
]);
|
|
2751
2763
|
const hostConfigRaw = v.object("pi.hostConfig", piRaw["hostConfig"]);
|
|
2752
2764
|
v.warnUnknownKeys("pi.hostConfig", hostConfigRaw, [
|
|
@@ -2810,6 +2822,12 @@ function validateConfig(raw) {
|
|
|
2810
2822
|
piRaw["sessionNaming"],
|
|
2811
2823
|
["auto", "off"],
|
|
2812
2824
|
d.pi.sessionNaming
|
|
2825
|
+
),
|
|
2826
|
+
podExtensions: v.enumValue(
|
|
2827
|
+
"pi.podExtensions",
|
|
2828
|
+
piRaw["podExtensions"],
|
|
2829
|
+
["ask", "run"],
|
|
2830
|
+
d.pi.podExtensions
|
|
2813
2831
|
)
|
|
2814
2832
|
},
|
|
2815
2833
|
// 0 is meaningful here (auto-stop off), so the floor is 0 rather than 1.
|
|
@@ -3694,7 +3712,7 @@ function resolveLaunchTemplateRef(flags, layers) {
|
|
|
3694
3712
|
function machineClientConfig(raw) {
|
|
3695
3713
|
const pi = isPlainObjectValue(raw["pi"]) ? raw["pi"] : {};
|
|
3696
3714
|
const clientPi = Object.fromEntries(
|
|
3697
|
-
Object.entries(pi).filter(([key]) =>
|
|
3715
|
+
Object.entries(pi).filter(([key]) => CLIENT_ONLY_PI_KEYS.includes(key))
|
|
3698
3716
|
);
|
|
3699
3717
|
return Object.keys(clientPi).length > 0 ? { pi: clientPi } : {};
|
|
3700
3718
|
}
|
|
@@ -3862,6 +3880,9 @@ function orgSourceDir(args) {
|
|
|
3862
3880
|
if (!home) throw new PiPodError("cannot locate ~/.pi-pod/org: HOME is unset", { hint: "pass --dir <path>" });
|
|
3863
3881
|
return path11.join(home, ORG_SOURCE_SUBDIR);
|
|
3864
3882
|
}
|
|
3883
|
+
function policySourcePath(args) {
|
|
3884
|
+
return path11.join(orgSourceDir(args), ORG_POLICY_FILE);
|
|
3885
|
+
}
|
|
3865
3886
|
function safePiDir(root, subpath) {
|
|
3866
3887
|
const directory = path11.join(root, subpath);
|
|
3867
3888
|
let stat;
|
|
@@ -3962,6 +3983,28 @@ async function readEnvSnapshot(envPath, mode, resolver, home) {
|
|
|
3962
3983
|
async function compileBundleSource(args) {
|
|
3963
3984
|
const cwd = args.cwd ?? process.cwd();
|
|
3964
3985
|
const envMode = args.includeEnv === false ? false : args.includeEnv === "names" ? "names" : true;
|
|
3986
|
+
if (args.source === "policy-file") {
|
|
3987
|
+
const configPath2 = policySourcePath({ dir: args.dir, home: args.home });
|
|
3988
|
+
if (!fs12.existsSync(configPath2)) {
|
|
3989
|
+
throw new PiPodError(`no org policy file at ${configPath2}`, {
|
|
3990
|
+
hint: "run `pipod pull policy` to create it from the server's policy, or pass --dir <path>"
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
kind: "policy-file",
|
|
3995
|
+
config: parseConfig(configPath2),
|
|
3996
|
+
initScript: "",
|
|
3997
|
+
bakeScript: "",
|
|
3998
|
+
piFiles: {},
|
|
3999
|
+
env: {},
|
|
4000
|
+
warnings: [],
|
|
4001
|
+
name: "org policy",
|
|
4002
|
+
templateRef: null,
|
|
4003
|
+
projectRoot: null,
|
|
4004
|
+
root: path11.dirname(configPath2),
|
|
4005
|
+
configPath: configPath2
|
|
4006
|
+
};
|
|
4007
|
+
}
|
|
3965
4008
|
if (args.source === "project") {
|
|
3966
4009
|
const configPath2 = findConfigPath(cwd, { home: args.home });
|
|
3967
4010
|
if (!configPath2) {
|
|
@@ -4061,6 +4104,7 @@ async function compileBundleSource(args) {
|
|
|
4061
4104
|
}
|
|
4062
4105
|
var ORG_SCRIPTS = { init: "init.sh", bake: "bake.sh" };
|
|
4063
4106
|
var ORG_ENV_FILE = "env";
|
|
4107
|
+
var ORG_POLICY_FILE = "policy.json";
|
|
4064
4108
|
function readPiFiles(root, config, kind) {
|
|
4065
4109
|
const files = {};
|
|
4066
4110
|
const warnings = [];
|
|
@@ -4176,7 +4220,6 @@ function settingsBundle(settings) {
|
|
|
4176
4220
|
return { ...settings, piFiles: settings.piFiles ?? {} };
|
|
4177
4221
|
}
|
|
4178
4222
|
var CLIENT_ONLY_TOP_KEYS2 = ["$schema", "template", "secretResolver"];
|
|
4179
|
-
var CLIENT_ONLY_PI_KEYS2 = ["chords", "sessionNaming"];
|
|
4180
4223
|
function isPlainObject2(value) {
|
|
4181
4224
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4182
4225
|
}
|
|
@@ -4194,7 +4237,7 @@ function mergePulledConfig(existing, pulled) {
|
|
|
4194
4237
|
..."template" in top ? { template: top["template"] } : {},
|
|
4195
4238
|
..."secretResolver" in top ? { secretResolver: top["secretResolver"] } : {}
|
|
4196
4239
|
};
|
|
4197
|
-
const clientPi = isPlainObject2(existing["pi"]) ? pick(existing["pi"],
|
|
4240
|
+
const clientPi = isPlainObject2(existing["pi"]) ? pick(existing["pi"], CLIENT_ONLY_PI_KEYS) : {};
|
|
4198
4241
|
const pi = { ...isPlainObject2(pod["pi"]) ? pod["pi"] : {}, ...clientPi };
|
|
4199
4242
|
if (Object.keys(pi).length > 0) next["pi"] = pi;
|
|
4200
4243
|
return next;
|
|
@@ -4316,6 +4359,15 @@ function writePulledAgents(writer, piDir, agents) {
|
|
|
4316
4359
|
}
|
|
4317
4360
|
function writeBundleSource(args) {
|
|
4318
4361
|
const { kind, bundle } = args;
|
|
4362
|
+
if (kind === "policy-file") {
|
|
4363
|
+
const policyPath = policySourcePath({ dir: args.dir, home: args.home });
|
|
4364
|
+
const writer2 = new LayerWriter(path11.dirname(policyPath));
|
|
4365
|
+
if (!fs12.existsSync(policyPath) || stable(parseConfig(policyPath)) !== stable(bundle.config)) {
|
|
4366
|
+
writer2.noteComments(policyPath);
|
|
4367
|
+
writer2.json(policyPath, bundle.config);
|
|
4368
|
+
}
|
|
4369
|
+
return { written: writer2.written, removed: writer2.removed, warnings: writer2.warnings, root: writer2.root };
|
|
4370
|
+
}
|
|
4319
4371
|
let root;
|
|
4320
4372
|
let configPath;
|
|
4321
4373
|
if (kind === "project") {
|
|
@@ -7456,6 +7508,7 @@ function attestedPackages(piSettings) {
|
|
|
7456
7508
|
return { specs, refused };
|
|
7457
7509
|
}
|
|
7458
7510
|
async function trustAttestedPackages(specs, opts) {
|
|
7511
|
+
if (opts.policy === "run") return { trusted: specs, declined: [] };
|
|
7459
7512
|
const file = path17.join(opts.cacheRoot, "trusted-extensions.json");
|
|
7460
7513
|
const key = (spec) => `${spec.name}@${spec.version}`;
|
|
7461
7514
|
let known = [];
|
|
@@ -14334,6 +14387,7 @@ async function runAccountInteractive(opts) {
|
|
|
14334
14387
|
}
|
|
14335
14388
|
const { trusted, declined } = await trustAttestedPackages(attested.specs, {
|
|
14336
14389
|
cacheRoot,
|
|
14390
|
+
policy: context.config.pi.podExtensions,
|
|
14337
14391
|
ask: (question) => confirm(question, { nonInteractiveDefault: false })
|
|
14338
14392
|
});
|
|
14339
14393
|
if (declined.length > 0) {
|
|
@@ -18854,7 +18908,7 @@ async function rmCmd2(client, args, flags) {
|
|
|
18854
18908
|
}
|
|
18855
18909
|
|
|
18856
18910
|
// src/commands/layers.ts
|
|
18857
|
-
var LAYER_USAGE = "template [<name>], user, or
|
|
18911
|
+
var LAYER_USAGE = "template [<name>], user, org, or policy";
|
|
18858
18912
|
function parseLayerArgs(verb, args, opts) {
|
|
18859
18913
|
const positionals = [];
|
|
18860
18914
|
let dir;
|
|
@@ -18881,18 +18935,29 @@ function parseLayerArgs(verb, args, opts) {
|
|
|
18881
18935
|
layer = { kind: "template" };
|
|
18882
18936
|
} else if (first === "template") {
|
|
18883
18937
|
layer = second === void 0 ? { kind: "template" } : { kind: "template", name: second };
|
|
18884
|
-
} else if (first === "user" || first === "org") {
|
|
18938
|
+
} else if (first === "user" || first === "org" || first === "policy") {
|
|
18885
18939
|
layer = { kind: first };
|
|
18886
18940
|
if (second !== void 0) extra.unshift(second);
|
|
18887
18941
|
} else {
|
|
18888
18942
|
throw new PiPodError(`unknown layer "${first}"`, { hint: `layers: ${LAYER_USAGE}` });
|
|
18889
18943
|
}
|
|
18890
18944
|
if (extra.length > 0) throw new PiPodError(`unexpected \`pipod ${verb}\` argument(s): ${extra.join(" ")}`);
|
|
18891
|
-
if (dir !== void 0 && layer.kind !== "org"
|
|
18945
|
+
if (dir !== void 0 && layer.kind !== "org" && layer.kind !== "policy") {
|
|
18946
|
+
throw new PiPodError("--dir only applies to the org and policy layers");
|
|
18947
|
+
}
|
|
18892
18948
|
return { layer, ...dir !== void 0 ? { dir } : {}, yes, withSecrets };
|
|
18893
18949
|
}
|
|
18894
18950
|
function sourceKindOf(layer) {
|
|
18895
|
-
|
|
18951
|
+
switch (layer.kind) {
|
|
18952
|
+
case "template":
|
|
18953
|
+
return "project";
|
|
18954
|
+
case "user":
|
|
18955
|
+
return "user-dir";
|
|
18956
|
+
case "org":
|
|
18957
|
+
return "org-dir";
|
|
18958
|
+
case "policy":
|
|
18959
|
+
return "policy-file";
|
|
18960
|
+
}
|
|
18896
18961
|
}
|
|
18897
18962
|
async function fetchRemoteLayer(client, layer, source) {
|
|
18898
18963
|
if (layer.kind === "template") {
|
|
@@ -18915,6 +18980,17 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18915
18980
|
scopeId: template.id
|
|
18916
18981
|
};
|
|
18917
18982
|
}
|
|
18983
|
+
if (layer.kind === "policy") {
|
|
18984
|
+
const current2 = await client.getOrgPolicy();
|
|
18985
|
+
return {
|
|
18986
|
+
bundle: settingsBundle(current2),
|
|
18987
|
+
version: current2.version,
|
|
18988
|
+
secretNames: [],
|
|
18989
|
+
label: `org policy v${current2.version}`,
|
|
18990
|
+
scope: "policy",
|
|
18991
|
+
scopeId: client.orgId
|
|
18992
|
+
};
|
|
18993
|
+
}
|
|
18918
18994
|
const current = layer.kind === "user" ? await client.getUserSettings() : await client.getOrgSettings();
|
|
18919
18995
|
const scopeId = layer.kind === "user" ? client.userId : client.orgId;
|
|
18920
18996
|
const listed = await client.listSecrets(layer.kind, scopeId).catch(() => ({ secrets: [] }));
|
|
@@ -18930,6 +19006,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18930
19006
|
function localLabel(layer, flags) {
|
|
18931
19007
|
if (layer.kind === "template") return "project";
|
|
18932
19008
|
if (layer.kind === "user") return "~/.pi-pod + ~/.pi/agent";
|
|
19009
|
+
if (layer.kind === "policy") return displayPath(policySourcePath({ dir: flags.dir, home: flags.home }), flags.home);
|
|
18933
19010
|
return displayPath(orgSourceDir({ dir: flags.dir, home: flags.home }), flags.home);
|
|
18934
19011
|
}
|
|
18935
19012
|
function printDiff(heading, lines) {
|
|
@@ -18963,22 +19040,10 @@ async function runPull(args, flags) {
|
|
|
18963
19040
|
const kind = sourceKindOf(parsed.layer);
|
|
18964
19041
|
let source;
|
|
18965
19042
|
let firstPull = false;
|
|
18966
|
-
|
|
19043
|
+
const localPath = kind === "org-dir" ? orgSourceDir({ dir: parsed.dir, home: flags.home }) : kind === "policy-file" ? policySourcePath({ dir: parsed.dir, home: flags.home }) : null;
|
|
19044
|
+
if (localPath !== null && !fs31.existsSync(localPath)) {
|
|
18967
19045
|
firstPull = true;
|
|
18968
|
-
source = {
|
|
18969
|
-
kind,
|
|
18970
|
-
config: {},
|
|
18971
|
-
initScript: "",
|
|
18972
|
-
bakeScript: "",
|
|
18973
|
-
piFiles: {},
|
|
18974
|
-
env: {},
|
|
18975
|
-
warnings: [],
|
|
18976
|
-
name: "org settings",
|
|
18977
|
-
templateRef: null,
|
|
18978
|
-
projectRoot: null,
|
|
18979
|
-
root: orgSourceDir({ dir: parsed.dir, home: flags.home }),
|
|
18980
|
-
configPath: ""
|
|
18981
|
-
};
|
|
19046
|
+
source = { config: {}, initScript: "", bakeScript: "", piFiles: {}, env: {}, warnings: [], templateRef: null };
|
|
18982
19047
|
} else {
|
|
18983
19048
|
source = await compileBundleSource({ source: kind, cwd, home: flags.home, includeEnv: false, dir: parsed.dir });
|
|
18984
19049
|
}
|
|
@@ -19039,6 +19104,11 @@ async function runPush(args, flags) {
|
|
|
19039
19104
|
return 0;
|
|
19040
19105
|
}
|
|
19041
19106
|
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19107
|
+
if (remote.scope === "policy") {
|
|
19108
|
+
const written2 = await flags.client.putOrgPolicy({ config: source.config, version: remote.version });
|
|
19109
|
+
info(`pushed ${local} to org policy v${written2.version}`);
|
|
19110
|
+
return 0;
|
|
19111
|
+
}
|
|
19042
19112
|
const body = {
|
|
19043
19113
|
config: source.config,
|
|
19044
19114
|
version: remote.version,
|
|
@@ -19111,7 +19181,7 @@ async function runSettings(args, flags) {
|
|
|
19111
19181
|
const [scope = "user", action = "show", ...rest] = args;
|
|
19112
19182
|
if (scope !== "user" && scope !== "org") {
|
|
19113
19183
|
throw new PiPodError(`unknown settings scope "${scope}"`, {
|
|
19114
|
-
hint: `${USAGE}; templates are managed with \`pipod templates\``
|
|
19184
|
+
hint: `${USAGE}; templates are managed with \`pipod templates\`, and org policy with \`pipod pull|diff|push policy\``
|
|
19115
19185
|
});
|
|
19116
19186
|
}
|
|
19117
19187
|
if (action === "show") {
|