@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.
@@ -35,7 +35,7 @@ drops everything a bundle payload must not contain: the rejected keys
35
35
  `envFile`/`initScript`/`bakeScript`; `pi.hostConfig.skills`/`extensions`) plus
36
36
  client-only metadata (`$schema`, `secretResolver`, `pi.chords`/`pi.sessionNaming`/`pi.podExtensions`).
37
37
  Covered paths: project/user/org-dir compilation, the launch bootstrap preview,
38
- `templates create/edit --config`, and `settings user|org edit|set` — the settings
38
+ `templates create/edit --config`, and `settings user|org|template edit|set` — the settings
39
39
  verbs validate the edited config locally first (types and retired keys fail
40
40
  before any write request) and throw on non-bundle keys with the fix, so an
41
41
  explicit `set reuse true` fails visibly rather than vanishing.
@@ -126,11 +126,11 @@ ignored, except `--dry-run`/`--on`-with-fork which are usage errors.
126
126
  server scripts with no local file warn instead of writing.
127
127
  - `diff [layer]`: same sources, secrets by name only; exit 1 on drift (`--dir`
128
128
  for org and policy; no `-y` — it never writes).
129
- - `settings <user|org> [show|edit|set <key> <json-value>]`: config-only
130
- compare-and-swap edits (scripts/Pi files untouched — that is `push`).
131
- `show` prints JSON; `edit` uses `$VISUAL`/`$EDITOR`; `set` takes a dotted key
132
- plus a JSON value. Templates are not a settings scope (hint points at
133
- `templates`).
129
+ - `settings [user|org|policy|template [<name>]] [show|edit|set <key> <json-value>|unset <key>]`:
130
+ config-only compare-and-swap edits of the layers `push` replaces whole
131
+ (scripts/Pi files untouched — that is `push`). `show` prints JSON; `edit` uses
132
+ `$VISUAL`/`$EDITOR`; `set` takes a dotted key plus a JSON value. `template`
133
+ without a name is the project's pin. The policy is checked by the server only.
134
134
  - `login`: Zitadel PKCE in the browser; where none can open (SSH, headless), the
135
135
  device flow with a code finished on another device. `--server`, `--issuer`
136
136
  (self-hosted), `--org` (domain pin), `--device` (force the code), `--token`
@@ -1,4 +1,4 @@
1
- .TH "PIPOD\-CONFIG" "5" "October 2026" "0.1.12"
1
+ .TH "PIPOD\-CONFIG" "5" "October 2026" "0.1.14"
2
2
  .SH "NAME"
3
3
  \fBpipod-config\fR \- pi pod configuration files
4
4
  .SH SYNOPSIS
@@ -7,6 +7,7 @@
7
7
  \|\.pi\-pod/config\.json a project: the source of its template
8
8
  ~/\.pi\-pod/config\.json this machine's preferences, and your user layer
9
9
  ~/\.pi\-pod/org/config\.json the organization layer
10
+ ~/\.pi\-pod/org/policy\.json the organization policy (POLICY KEYS)
10
11
  .fi
11
12
  .RE
12
13
  .SH DESCRIPTION
@@ -14,11 +15,23 @@
14
15
  Each file is one settings layer's config, written as JSON with comments allowed\. The server
15
16
  merges the layers for each launch (\fBpipod help layers\fP), so a file names only what its layer
16
17
  must decide; anything left out comes from the layers below it\. The keys are the same in every
17
- file\. \fBpipod push\fP uploads a file and \fBpipod settings <user|org> set <key> <value>\fP changes
18
- one key on the server\.
18
+ file but policy\.json, which holds limits on the merged result instead\. \fBpipod push\fP uploads a
19
+ file and \fBpipod settings <layer> set <key> <value>\fP changes one key on the server\.
19
20
  .P
20
21
  A key of the wrong type fails the command that reads it, naming the key\. An unknown key is
21
22
  ignored with a warning\.
23
+ .SH SESSION PROXIES
24
+ .P
25
+ Session WebSockets honor \fBWSS_PROXY\fP (\fBWS_PROXY\fP for plain WebSockets), generic
26
+ \fBALL_PROXY\fP settings, and then fall back to \fBHTTPS_PROXY\fP (\fBHTTP_PROXY\fP for plain
27
+ WebSockets)\. Lowercase names take precedence over uppercase names; npm proxy
28
+ settings follow \fBproxy\-from\-env\fP conventions\. \fBNO_PROXY\fP supports hostnames,
29
+ domain suffixes, optional ports, and \fB*\fP for direct connections\.
30
+ .P
31
+ Proxy URLs must use HTTP or HTTPS\. Session connections use CONNECT tunnels and
32
+ retain TLS certificate verification for both the proxy and the destination\.
33
+ These variables apply to initial attachment and reconnection, without requiring
34
+ \fBNODE_USE_ENV_PROXY\fP\|\. With no matching proxy setting, sessions connect directly\.
22
35
  .SH POD KEYS
23
36
  .P
24
37
  These travel to the server and decide what a pod gets\. The defaults are the built\-in layer's\.
@@ -101,6 +114,36 @@ pi\.podExtensions a pod's Pi extensions run on this machine, as you, to
101
114
  $schema ignored
102
115
  .fi
103
116
  .RE
117
+ .SH POLICY KEYS
118
+ .P
119
+ policy\.json holds the organization's policy: limits the server applies after every other
120
+ layer, whatever a person's settings, template, or launch ask for\. Every key is optional, and
121
+ one left out sets no limit\. The server checks every change to it and names any key or value it
122
+ refuses\.
123
+ .RS 2
124
+ .nf
125
+ maxIdleTimeoutMinutes the longest idleTimeoutMinutes a pod gets; a longer one,
126
+ or 0, becomes this
127
+ maxArchiveAfterMinutes the same for archiveAfterMinutes
128
+ maxConcurrentPods pods the whole organization may have awake at once; the
129
+ server's own limit per person still applies
130
+ allowedProviders the providers a launch may use; others are refused
131
+ requireTemplate true: every launch must use a template
132
+ requireEgressMode "allowlist": a pod set to "open" gets "allowlist" instead
133
+ forbiddenEgressHosts hosts removed from every egress\.allow
134
+ nestedPods\.enabled false: pods may not launch pods or schedule jobs
135
+ (default true)
136
+ nestedPods\.maxDepth how deep pods may launch pods, 0 to 8 (default 2)
137
+ nestedPods\.maxChildrenPerPod awake child pods per pod, 0 to 100 (default 5)
138
+ nestedPods\.maxPodsPerLineage awake pods in one family of pods, 1 to 500 (default 10)
139
+ nestedPods\.allowFileSend false: pods may not send files to other pods (default true)
140
+ nestedPods\.allowFileReceive false: pods may not copy files out of other pods
141
+ (default true)
142
+ notifications\.redacted true: push notifications leave out pod names and summaries
143
+ .fi
144
+ .RE
145
+ .P
146
+ Each clamp a launch gets is reported by \fBpipod doctor\fP and \fBpipod \-\-dry\-run\fP\|\.
104
147
  .SH SCRIPTS
105
148
  .P
106
149
  A layer's two scripts sit beside its config: \.pi\-pod/init\.sh and \.pi\-pod/bake\.sh in a
@@ -143,6 +186,14 @@ pipod settings org set resources\.memoryGB 8
143
186
  pipod settings org set idleTimeoutMinutes 30
144
187
  .fi
145
188
  .RE
189
+ .P
190
+ An organization policy that caps idle time and keeps pods from launching pods:
191
+ .RS 2
192
+ .nf
193
+ pipod settings policy set maxIdleTimeoutMinutes 60
194
+ pipod settings policy set nestedPods\.enabled false
195
+ .fi
196
+ .RE
146
197
  .SH RETIRED KEYS
147
198
  .P
148
199
  \fBenvFile\fP, \fBinitScript\fP, \fBbakeScript\fP, and \fBreuse\fP fail with a message saying what replaced
@@ -6,6 +6,7 @@
6
6
  .pi-pod/config.json a project: the source of its template
7
7
  ~/.pi-pod/config.json this machine's preferences, and your user layer
8
8
  ~/.pi-pod/org/config.json the organization layer
9
+ ~/.pi-pod/org/policy.json the organization policy (POLICY KEYS)
9
10
  ```
10
11
 
11
12
  ## DESCRIPTION
@@ -13,12 +14,25 @@
13
14
  Each file is one settings layer's config, written as JSON with comments allowed. The server
14
15
  merges the layers for each launch (`pipod help layers`), so a file names only what its layer
15
16
  must decide; anything left out comes from the layers below it. The keys are the same in every
16
- file. `pipod push` uploads a file and `pipod settings <user|org> set <key> <value>` changes
17
- one key on the server.
17
+ file but policy.json, which holds limits on the merged result instead. `pipod push` uploads a
18
+ file and `pipod settings <layer> set <key> <value>` changes one key on the server.
18
19
 
19
20
  A key of the wrong type fails the command that reads it, naming the key. An unknown key is
20
21
  ignored with a warning.
21
22
 
23
+ ## SESSION PROXIES
24
+
25
+ Session WebSockets honor `WSS_PROXY` (`WS_PROXY` for plain WebSockets), generic
26
+ `ALL_PROXY` settings, and then fall back to `HTTPS_PROXY` (`HTTP_PROXY` for plain
27
+ WebSockets). Lowercase names take precedence over uppercase names; npm proxy
28
+ settings follow `proxy-from-env` conventions. `NO_PROXY` supports hostnames,
29
+ domain suffixes, optional ports, and `*` for direct connections.
30
+
31
+ Proxy URLs must use HTTP or HTTPS. Session connections use CONNECT tunnels and
32
+ retain TLS certificate verification for both the proxy and the destination.
33
+ These variables apply to initial attachment and reconnection, without requiring
34
+ `NODE_USE_ENV_PROXY`. With no matching proxy setting, sessions connect directly.
35
+
22
36
  ## POD KEYS
23
37
 
24
38
  These travel to the server and decide what a pod gets. The defaults are the built-in layer's.
@@ -98,6 +112,36 @@ pi.podExtensions a pod's Pi extensions run on this machine, as you, to r
98
112
  $schema ignored
99
113
  ```
100
114
 
115
+ ## POLICY KEYS
116
+
117
+ policy.json holds the organization's policy: limits the server applies after every other
118
+ layer, whatever a person's settings, template, or launch ask for. Every key is optional, and
119
+ one left out sets no limit. The server checks every change to it and names any key or value it
120
+ refuses.
121
+
122
+ ```
123
+ maxIdleTimeoutMinutes the longest idleTimeoutMinutes a pod gets; a longer one,
124
+ or 0, becomes this
125
+ maxArchiveAfterMinutes the same for archiveAfterMinutes
126
+ maxConcurrentPods pods the whole organization may have awake at once; the
127
+ server's own limit per person still applies
128
+ allowedProviders the providers a launch may use; others are refused
129
+ requireTemplate true: every launch must use a template
130
+ requireEgressMode "allowlist": a pod set to "open" gets "allowlist" instead
131
+ forbiddenEgressHosts hosts removed from every egress.allow
132
+ nestedPods.enabled false: pods may not launch pods or schedule jobs
133
+ (default true)
134
+ nestedPods.maxDepth how deep pods may launch pods, 0 to 8 (default 2)
135
+ nestedPods.maxChildrenPerPod awake child pods per pod, 0 to 100 (default 5)
136
+ nestedPods.maxPodsPerLineage awake pods in one family of pods, 1 to 500 (default 10)
137
+ nestedPods.allowFileSend false: pods may not send files to other pods (default true)
138
+ nestedPods.allowFileReceive false: pods may not copy files out of other pods
139
+ (default true)
140
+ notifications.redacted true: push notifications leave out pod names and summaries
141
+ ```
142
+
143
+ Each clamp a launch gets is reported by `pipod doctor` and `pipod --dry-run`.
144
+
101
145
  ## SCRIPTS
102
146
 
103
147
  A layer's two scripts sit beside its config: .pi-pod/init.sh and .pi-pod/bake.sh in a
@@ -140,6 +184,13 @@ pipod settings org set resources.memoryGB 8
140
184
  pipod settings org set idleTimeoutMinutes 30
141
185
  ```
142
186
 
187
+ An organization policy that caps idle time and keeps pods from launching pods:
188
+
189
+ ```
190
+ pipod settings policy set maxIdleTimeoutMinutes 60
191
+ pipod settings policy set nestedPods.enabled false
192
+ ```
193
+
143
194
  ## RETIRED KEYS
144
195
 
145
196
  `envFile`, `initScript`, `bakeScript`, and `reuse` fail with a message saying what replaced
package/man/pipod.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH "PIPOD" "1" "October 2026" "0.1.12"
1
+ .TH "PIPOD" "1" "October 2026" "0.1.14"
2
2
  .SH "NAME"
3
3
  \fBpipod\fR \- run pi coding\-agent sessions in server\-managed pods
4
4
  .SH SYNOPSIS
@@ -138,8 +138,9 @@ pipod push org
138
138
  .P
139
139
  Values for secrets the whole organization shares: \fBpipod secrets set org <NAME>\fP (person)\.
140
140
  .P
141
- Organization policy (limits rather than defaults) needs the policy:write permission\. Its source
142
- is policy\.json beside the organization layer's files:
141
+ Organization policy (limits rather than defaults) needs the policy:write permission\. Change
142
+ one limit with \fBpipod settings policy set <key> <value>\fP (its keys are in pipod\-config(5)), or
143
+ the whole policy from its source, policy\.json beside the organization layer's files:
143
144
  .RS 2
144
145
  .nf
145
146
  pipod pull policy # writes ~/\.pi\-pod/org/policy\.json
@@ -240,9 +241,10 @@ org ~/\.pi\-pod/org/: config\.json, init\.sh, bake\.sh, pi/, env
240
241
  user ~/\.pi\-pod/config\.json and ~/\.pi/agent/
241
242
  pipod pull|diff|push user; pipod settings user set
242
243
  template a project: \.pi\-pod/config\.json, init\.sh, bake\.sh, env, and \.pi/
243
- pipod pull|diff|push [template [<name>]]; pipod templates
244
+ pipod pull|diff|push [template [<name>]]; pipod settings template set;
245
+ pipod templates
244
246
  policy ~/\.pi\-pod/org/policy\.json
245
- pipod pull|diff|push policy (policy:write)
247
+ pipod pull|diff|push policy; pipod settings policy set (policy:write)
246
248
  .fi
247
249
  .RE
248
250
  .P
@@ -589,17 +591,23 @@ or \fBsync\fP reads a dotenv file kept at mode 600\. op:// references resolve th
589
591
  .SS pipod settings
590
592
  .RS 2
591
593
  .nf
592
- Usage: pipod settings <user|org> [show | edit | set <key> <value> | unset <key>]
594
+ Usage: pipod settings [user | org | policy | template [<name>]]
595
+ [show | edit | set <key> <value> | unset <key>]
593
596
 
594
- Show or edit a persistent settings bundle on the server: your own user bundle,
595
- or the org\-wide defaults (org scope needs the org:manage permission)\. Edits
596
- change only the config; scripts and Pi files stay as they are — replace the
597
- whole bundle from local files with `pipod push user` or `pipod push org`\.
598
- Edits use compare\-and\-swap on the bundle version, so concurrent changes fail
599
- cleanly instead of overwriting each other\.
597
+ Show or edit one server layer's config directly, the same layers `pipod push`
598
+ replaces whole:
599
+ user Your own settings (the default)
600
+ org The organization's defaults (needs org:manage)
601
+ policy The organization's policy (needs policy:write)
602
+ template [<name>] A template; without <name>, the one this project pins
603
+
604
+ Edits change only the config; scripts and Pi files stay as they are — replace
605
+ a whole layer from local files with `pipod push`\. Edits use compare\-and\-swap
606
+ on the layer's version, so concurrent changes fail cleanly instead of
607
+ overwriting each other\.
600
608
 
601
609
  Actions:
602
- show Print the settings bundle as JSON (default)
610
+ show Print the layer as JSON (default)
603
611
  edit Edit the config in $VISUAL/$EDITOR, then save
604
612
  set <key> <value> Set one dotted key to a JSON value, or to the text as
605
613
  a string: pipod settings user set pi\.model provider/model
@@ -607,9 +615,11 @@ Actions:
607
615
  .fi
608
616
  .RE
609
617
  .P
610
- Keys are those of pipod\-config(5)\. \fBset\fP changes the server only: a later \fBpipod push\fP of the
611
- same layer replaces it with the local source, so after a \fBset\fP, run \fBpipod pull\fP before
612
- editing that source\.
618
+ Keys are those of pipod\-config(5): its pod keys for user, org, and template, and its policy
619
+ keys for policy, which the server checks\. \fBset\fP changes the server only: a later \fBpipod push\fP
620
+ of the same layer replaces it with the local source, so after a \fBset\fP, run \fBpipod pull\fP before
621
+ editing that source\. A template named like an action (show, edit, set, unset) is named by its
622
+ id instead\.
613
623
  .SS pipod jobs
614
624
  .RS 2
615
625
  .nf
package/man/pipod.1.md CHANGED
@@ -140,8 +140,9 @@ pipod push org
140
140
 
141
141
  Values for secrets the whole organization shares: `pipod secrets set org <NAME>` (person).
142
142
 
143
- Organization policy (limits rather than defaults) needs the policy:write permission. Its source
144
- is policy.json beside the organization layer's files:
143
+ Organization policy (limits rather than defaults) needs the policy:write permission. Change
144
+ one limit with `pipod settings policy set <key> <value>` (its keys are in pipod-config(5)), or
145
+ the whole policy from its source, policy.json beside the organization layer's files:
145
146
 
146
147
  ```
147
148
  pipod pull policy # writes ~/.pi-pod/org/policy.json
@@ -229,9 +230,10 @@ org ~/.pi-pod/org/: config.json, init.sh, bake.sh, pi/, env
229
230
  user ~/.pi-pod/config.json and ~/.pi/agent/
230
231
  pipod pull|diff|push user; pipod settings user set
231
232
  template a project: .pi-pod/config.json, init.sh, bake.sh, env, and .pi/
232
- pipod pull|diff|push [template [<name>]]; pipod templates
233
+ pipod pull|diff|push [template [<name>]]; pipod settings template set;
234
+ pipod templates
233
235
  policy ~/.pi-pod/org/policy.json
234
- pipod pull|diff|push policy (policy:write)
236
+ pipod pull|diff|push policy; pipod settings policy set (policy:write)
235
237
  ```
236
238
 
237
239
  What belongs where: the organization layer holds what everyone needs (pod size, network
@@ -580,26 +582,34 @@ or `sync` reads a dotenv file kept at mode 600. op:// references resolve through
580
582
  ### pipod settings
581
583
 
582
584
  ```
583
- Usage: pipod settings <user|org> [show | edit | set <key> <value> | unset <key>]
585
+ Usage: pipod settings [user | org | policy | template [<name>]]
586
+ [show | edit | set <key> <value> | unset <key>]
584
587
 
585
- Show or edit a persistent settings bundle on the server: your own user bundle,
586
- or the org-wide defaults (org scope needs the org:manage permission). Edits
587
- change only the config; scripts and Pi files stay as they are — replace the
588
- whole bundle from local files with `pipod push user` or `pipod push org`.
589
- Edits use compare-and-swap on the bundle version, so concurrent changes fail
590
- cleanly instead of overwriting each other.
588
+ Show or edit one server layer's config directly, the same layers `pipod push`
589
+ replaces whole:
590
+ user Your own settings (the default)
591
+ org The organization's defaults (needs org:manage)
592
+ policy The organization's policy (needs policy:write)
593
+ template [<name>] A template; without <name>, the one this project pins
594
+
595
+ Edits change only the config; scripts and Pi files stay as they are — replace
596
+ a whole layer from local files with `pipod push`. Edits use compare-and-swap
597
+ on the layer's version, so concurrent changes fail cleanly instead of
598
+ overwriting each other.
591
599
 
592
600
  Actions:
593
- show Print the settings bundle as JSON (default)
601
+ show Print the layer as JSON (default)
594
602
  edit Edit the config in $VISUAL/$EDITOR, then save
595
603
  set <key> <value> Set one dotted key to a JSON value, or to the text as
596
604
  a string: pipod settings user set pi.model provider/model
597
605
  unset <key> Remove one dotted key, so the layers below decide it
598
606
  ```
599
607
 
600
- Keys are those of pipod-config(5). `set` changes the server only: a later `pipod push` of the
601
- same layer replaces it with the local source, so after a `set`, run `pipod pull` before
602
- editing that source.
608
+ Keys are those of pipod-config(5): its pod keys for user, org, and template, and its policy
609
+ keys for policy, which the server checks. `set` changes the server only: a later `pipod push`
610
+ of the same layer replaces it with the local source, so after a `set`, run `pipod pull` before
611
+ editing that source. A template named like an action (show, edit, set, unset) is named by its
612
+ id instead.
603
613
 
604
614
  ### pipod jobs
605
615
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipod/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Run pi coding-agent sessions in server-managed pods",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -34,7 +34,7 @@
34
34
  "build:man": "marked-man --no-breaks man/pipod.1.md -o man/pipod.1 && marked-man --no-breaks man/pipod-config.5.md -o man/pipod-config.5",
35
35
  "typecheck": "tsc --noEmit",
36
36
  "lint": "node --import tsx scripts/check-runtime-surface.ts",
37
- "test:integration": "node --import tsx --test --test-concurrency=1 test/integration/account-cli.test.ts test/integration/account-login-me-http.test.ts test/integration/billing-cli.test.ts test/integration/capacity-wait-http.test.ts test/integration/cli-main.test.ts test/integration/local-extensions.test.ts test/integration/nested-pods.test.ts test/integration/oidc-security.test.ts test/integration/secrets-cli.test.ts test/integration/workspace-seeding.test.ts test/integration/workstation-wake-http.test.ts",
37
+ "test:integration": "node --import tsx --test --test-concurrency=1 test/integration/account-cli.test.ts test/integration/account-login-me-http.test.ts test/integration/billing-cli.test.ts test/integration/capacity-wait-http.test.ts test/integration/cli-main.test.ts test/integration/local-extensions.test.ts test/integration/nested-pods.test.ts test/integration/oidc-security.test.ts test/integration/secrets-cli.test.ts test/integration/session-websocket-proxy.test.ts test/integration/workspace-seeding.test.ts test/integration/workstation-wake-http.test.ts",
38
38
  "check": "npm run lint && npm run typecheck",
39
39
  "prepublishOnly": "npm run build"
40
40
  },
@@ -42,11 +42,14 @@
42
42
  "@earendil-works/pi-coding-agent": "1.0.2",
43
43
  "@earendil-works/pi-tui": "1.0.2",
44
44
  "cron-parser": "^5.10.0",
45
+ "https-proxy-agent": "^9.1.0",
45
46
  "jose": "^5.10.0",
47
+ "proxy-from-env": "^1.1.0",
46
48
  "ws": "^8.22.0"
47
49
  },
48
50
  "devDependencies": {
49
51
  "@types/node": "^22.10.0",
52
+ "@types/proxy-from-env": "^1.0.4",
50
53
  "@types/ws": "^8.18.1",
51
54
  "marked-man": "^2.1.1",
52
55
  "tsup": "^8.3.5",