@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 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]) => ["chords", "sessionNaming"].includes(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"], CLIENT_ONLY_PI_KEYS2) : {};
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 org";
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") throw new PiPodError("--dir only applies to the org layer");
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
- return layer.kind === "template" ? "project" : layer.kind === "user" ? "user-dir" : "org-dir";
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
- if (kind === "org-dir" && !fs31.existsSync(orgSourceDir({ dir: parsed.dir, home: flags.home }))) {
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") {