anon-pi 0.4.0 → 0.6.0
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/Dockerfile.pi +21 -9
- package/README.md +202 -64
- package/dist/anon-pi.d.ts +989 -76
- package/dist/anon-pi.d.ts.map +1 -1
- package/dist/anon-pi.js +1428 -251
- package/dist/anon-pi.js.map +1 -1
- package/dist/cli.js +1512 -112
- package/dist/cli.js.map +1 -1
- package/examples/Dockerfile.pi-webveil +9 -3
- package/package.json +1 -1
- package/src/anon-pi.ts +2052 -346
- package/src/cli.ts +1808 -123
package/dist/anon-pi.d.ts
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The jail cwd root for the projects-root launch: the projects root is mounted
|
|
3
|
+
* here and a project `<name>` is `/projects/<name>` (pi keys a conversation by
|
|
4
|
+
* its launch cwd, so `/projects/<name>` is the conversation key). This is the
|
|
5
|
+
* machines + projects mount (distinct from `--mount`'s /work).
|
|
6
|
+
*/
|
|
7
|
+
export declare const CONTAINER_PROJECTS_ROOT = "/projects";
|
|
8
|
+
/**
|
|
9
|
+
* The jail cwd root for a `--mount <parent>` launch: the HOST parent is mounted
|
|
10
|
+
* here (kept DISTINCT from /projects so the two roots never collide), and a
|
|
11
|
+
* project `<name>` is `/work/<name>`. See ADR-0001 (`--mount` keeps `/work`).
|
|
12
|
+
*/
|
|
13
|
+
export declare const CONTAINER_MOUNT_ROOT = "/work";
|
|
14
|
+
/**
|
|
15
|
+
* The jail cwd root for a machine (its persistent home, bind-mounted at /root).
|
|
16
|
+
* A machine root has no named subfolders: only the root token `.` (a scratch pi
|
|
17
|
+
* / shell at `~`) is valid. Written as `~` so it reads as "the machine home".
|
|
18
|
+
*/
|
|
19
|
+
export declare const CONTAINER_MACHINE_HOME = "~";
|
|
20
|
+
/**
|
|
21
|
+
* The REAL container path the machine home is bind-mounted at (the source is
|
|
22
|
+
* the host `machineHomeDir`). This is what a shell-at-`~` launch actually cwds
|
|
23
|
+
* into (`-w /root`), distinct from CONTAINER_MACHINE_HOME (`~`), which is the
|
|
24
|
+
* human-readable menu token. It is the parent of CONTAINER_AGENT_DIR
|
|
25
|
+
* (`/root/.pi/agent`); the seed-if-fresh promotes the image's `/root` defaults +
|
|
26
|
+
* pi staging into the mounted home here.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CONTAINER_HOME_ROOT = "/root";
|
|
3
29
|
/**
|
|
4
30
|
* The container path pi uses as its config+state home. anon-pi mounts a
|
|
5
31
|
* PERSISTENT host dir here (Model B), so everything pi writes, sessions,
|
|
6
32
|
* history, settings (your model choice), `pi install`ed extensions, downloaded
|
|
7
|
-
* bin/fd, survives across launches. Statefulness is the default
|
|
8
|
-
* mounts a throwaway dir here instead.
|
|
33
|
+
* bin/fd, survives across launches. Statefulness is the default.
|
|
9
34
|
*/
|
|
10
35
|
export declare const CONTAINER_AGENT_DIR = "/root/.pi/agent";
|
|
11
36
|
/**
|
|
@@ -21,10 +46,25 @@ export declare const CONTAINER_STAGE_DIR = "/opt/anon-pi-seed/agent";
|
|
|
21
46
|
* staged defaults. Read-only: the container never writes back to the host seed.
|
|
22
47
|
*/
|
|
23
48
|
export declare const CONTAINER_MODELS_SEED = "/anon-pi-seed/models.json";
|
|
49
|
+
/**
|
|
50
|
+
* Where anon-pi mounts the generated settings SEED (the local-model default
|
|
51
|
+
* selection: defaultProvider/defaultModel/enabledModels) read-only, so the
|
|
52
|
+
* first-launch seed can MERGE it into the fresh home's settings.json (never
|
|
53
|
+
* clobbering image-staged packages/extensions).
|
|
54
|
+
*/
|
|
55
|
+
export declare const CONTAINER_SETTINGS_SEED = "/anon-pi-seed/settings.json";
|
|
24
56
|
/** Marker file written into the agent dir after seeding; holds the seed version. */
|
|
25
57
|
export declare const SEED_MARKER = ".anon-pi-seed";
|
|
26
|
-
/** The
|
|
58
|
+
/** The file the host-side seed carries: pi's model/provider registry. */
|
|
27
59
|
export declare const MODELS_FILE = "models.json";
|
|
60
|
+
/** pi's settings file (holds defaultModel/defaultProvider/enabledModels + more). */
|
|
61
|
+
export declare const SETTINGS_FILE = "settings.json";
|
|
62
|
+
/**
|
|
63
|
+
* The settings SEED file anon-pi writes next to a machine (the local-model
|
|
64
|
+
* selection fragment). Distinct name so it never collides with a real
|
|
65
|
+
* settings.json; the seed MERGES it into the home's settings on first launch.
|
|
66
|
+
*/
|
|
67
|
+
export declare const SETTINGS_SEED_FILE = "settings-seed.json";
|
|
28
68
|
/**
|
|
29
69
|
* containerRunCmd builds the container command: on a FRESH home (no seed
|
|
30
70
|
* marker), promote the image's staged defaults + the mounted models.json into
|
|
@@ -44,16 +84,26 @@ export interface AnonPiEnv {
|
|
|
44
84
|
home: string;
|
|
45
85
|
/** socks5h proxy URL. REQUIRED (no default: the proxy is what anonymizes). */
|
|
46
86
|
proxy?: string;
|
|
47
|
-
/** The anon-pi home dir. Default
|
|
87
|
+
/** The anon-pi home dir. Default ~/.anon-pi (NOT under ~/.config). */
|
|
48
88
|
anonPiHome?: string;
|
|
49
|
-
/**
|
|
50
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Projects-root override from env (ANON_PI_PROJECTS). Sits above
|
|
91
|
+
* machine.json/config.json in the projects-root chain, below the later
|
|
92
|
+
* --mount CLI override. See resolveProjectsRoot.
|
|
93
|
+
*/
|
|
94
|
+
projects?: string;
|
|
51
95
|
/** The container image that has `pi` on PATH. REQUIRED. */
|
|
52
96
|
image?: string;
|
|
53
97
|
/** The RFC1918/link-local IP[:port] of the local model. REQUIRED. */
|
|
54
98
|
llmDirect?: string;
|
|
55
99
|
/** XDG_CONFIG_HOME, if set (used to derive the default anon-pi home). */
|
|
56
100
|
xdgConfigHome?: string;
|
|
101
|
+
/**
|
|
102
|
+
* The host pi agent dir (PI_CODING_AGENT_DIR), used ONLY to locate the host
|
|
103
|
+
* `~/.pi/agent/models.json` that `init` reads the matching local provider
|
|
104
|
+
* from. Defaults to ~/.pi/agent. Never written.
|
|
105
|
+
*/
|
|
106
|
+
piAgentDir?: string;
|
|
57
107
|
/**
|
|
58
108
|
* Absolute path to the Dockerfile.pi that ships with anon-pi, used only to
|
|
59
109
|
* make the missing-image error's build command concrete. cli.ts resolves it
|
|
@@ -66,43 +116,591 @@ export interface AnonPiEnv {
|
|
|
66
116
|
* + SearXNG), used to make the missing-image error mention the fuller build.
|
|
67
117
|
*/
|
|
68
118
|
webveilDockerfilePath?: string;
|
|
69
|
-
/** `import` source models.json override (ANON_PI_SOURCE_MODELS). */
|
|
70
|
-
sourceModels?: string;
|
|
71
|
-
/** The host pi agent dir override (PI_CODING_AGENT_DIR), used to find models.json. */
|
|
72
|
-
piAgentDir?: string;
|
|
73
|
-
/** When true, use a throwaway state home (no persistence). Default false. */
|
|
74
|
-
ephemeral?: boolean;
|
|
75
119
|
/** The seed version anon-pi stamps into a fresh home. Default SEED_VERSION. */
|
|
76
120
|
seedVersion?: string;
|
|
77
121
|
}
|
|
78
|
-
/**
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
|
|
122
|
+
/** A user-facing error whose message is meant to be printed verbatim (no stack). */
|
|
123
|
+
export declare class AnonPiError extends Error {
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The verbatim guidance printed when no proxy is supplied. Kept as a single
|
|
127
|
+
* source so the fail-closed path (resolveProxy) emits byte-identical
|
|
128
|
+
* copy-pasteable guidance. The proxy is REQUIRED and never guessed: it is what
|
|
129
|
+
* anonymizes egress (fail-closed is the anonymity invariant).
|
|
130
|
+
*/
|
|
131
|
+
export declare const PROXY_REQUIRED_MESSAGE: string;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the anon-pi home dir: the dedicated, browsable workspace folder
|
|
134
|
+
* (`~/.anon-pi/`, NOT under `~/.config`), holding config.json, machines/<M>/,
|
|
135
|
+
* and the default global projects root. Overridable via ANON_PI_HOME.
|
|
136
|
+
*/
|
|
137
|
+
export declare function resolveAnonPiHome(env: AnonPiEnv): string;
|
|
138
|
+
/** A machine's directory: <home>/machines/<name> (holds machine.json + home/). */
|
|
139
|
+
export declare function machineDir(env: AnonPiEnv, name: string): string;
|
|
140
|
+
/** A machine's persistent HOST home: <home>/machines/<name>/home (bind-mounted at /root). */
|
|
141
|
+
export declare function machineHomeDir(env: AnonPiEnv, name: string): string;
|
|
142
|
+
/** A machine's machine.json path: <home>/machines/<name>/machine.json. */
|
|
143
|
+
export declare function machineJsonPath(env: AnonPiEnv, name: string): string;
|
|
144
|
+
/** The sessions dirname pi keeps its per-cwd conversation dirs under (in the agent dir). */
|
|
145
|
+
export declare const SESSIONS_DIRNAME = "sessions";
|
|
146
|
+
/**
|
|
147
|
+
* A machine's HOST pi agent dir: the host side of the container's
|
|
148
|
+
* CONTAINER_AGENT_DIR (`/root/.pi/agent`, since the home is bind-mounted at
|
|
149
|
+
* /root). i.e. <machineHome>/.pi/agent. Where pi's config + sessions live.
|
|
150
|
+
*/
|
|
151
|
+
export declare function machineAgentDir(env: AnonPiEnv, name: string): string;
|
|
152
|
+
/**
|
|
153
|
+
* A machine's HOST pi sessions dir: <machineAgentDir>/sessions. Each per-cwd
|
|
154
|
+
* conversation is a slug-named subdir here (projectSessionSlug for a project).
|
|
155
|
+
*/
|
|
156
|
+
export declare function machineSessionsDir(env: AnonPiEnv, name: string): string;
|
|
157
|
+
/**
|
|
158
|
+
* The HOST session dir a given project's conversation occupies in a given
|
|
159
|
+
* machine's home: <machineSessionsDir>/<projectSessionSlug>. Because the slug is
|
|
160
|
+
* MACHINE-INVARIANT (pi keys by the `/projects/<name>` cwd, identical on every
|
|
161
|
+
* machine), the SAME shared project has this dir in each machine that used it.
|
|
162
|
+
* Validates the project name (rejecting traversal) via projectSessionSlug.
|
|
163
|
+
*/
|
|
164
|
+
export declare function machineProjectSessionDir(env: AnonPiEnv, machine: string, project: string): string;
|
|
165
|
+
/** The built-in default global projects root: <home>/projects. */
|
|
166
|
+
export declare function builtinProjectsRoot(env: AnonPiEnv): string;
|
|
167
|
+
/** The affected-path plan for `--delete-home <machine>`. */
|
|
168
|
+
export interface DeleteHomePlan {
|
|
169
|
+
/** The machine whose home is dropped. */
|
|
170
|
+
machine: string;
|
|
171
|
+
/**
|
|
172
|
+
* The single dir removed: the machine's persistent HOST home
|
|
173
|
+
* (machineHomeDir). The machine dir's machine.json (its image pin) is KEPT, so
|
|
174
|
+
* the machine can be relaunched to seed a FRESH home.
|
|
175
|
+
*/
|
|
176
|
+
home: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* PURE: resolve the affected path for `--delete-home <machine>`: the machine's
|
|
180
|
+
* HOME dir only (config + convos + shell env), NOT the whole machine dir, so the
|
|
181
|
+
* image pin (machine.json) survives a re-seed. Validates the machine name
|
|
182
|
+
* (rejecting traversal) via machineHomeDir's join being under a validated name;
|
|
183
|
+
* we validate explicitly here so the plan itself is a safe single segment.
|
|
184
|
+
*/
|
|
185
|
+
export declare function resolveDeleteHome(env: AnonPiEnv, machine: string): DeleteHomePlan;
|
|
186
|
+
/** The affected-path plan for `--delete-project <project>`. */
|
|
187
|
+
export interface DeleteProjectPlan {
|
|
188
|
+
/** The project whose files + per-machine sessions are dropped. */
|
|
189
|
+
project: string;
|
|
190
|
+
/** The project's files: <projectsRoot>/<project> (the host folder). */
|
|
191
|
+
folder: string;
|
|
82
192
|
/**
|
|
83
|
-
* The
|
|
84
|
-
*
|
|
85
|
-
*
|
|
193
|
+
* The per-machine session dirs for this project's (machine-invariant) slug,
|
|
194
|
+
* ONE per supplied machine, in the SUPPLIED order. The homes themselves are
|
|
195
|
+
* kept; only these slug dirs are dropped. The CLI supplies the machine names
|
|
196
|
+
* (readdir of machines/) and skips any that do not exist on disk.
|
|
86
197
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
198
|
+
sessions: string[];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* PURE: resolve the affected paths for `--delete-project <project>`: the
|
|
202
|
+
* project's files under the RESOLVED projects root, plus that project's session
|
|
203
|
+
* dir in each SUPPLIED machine home (the machine-invariant slug). Validates the
|
|
204
|
+
* project name (rejecting traversal) so both the folder join and every session
|
|
205
|
+
* join stay inside their roots. The homes are NOT targeted (only the per-project
|
|
206
|
+
* slug dir inside each), matching the prd behaviour table.
|
|
207
|
+
*/
|
|
208
|
+
export declare function resolveDeleteProject(args: {
|
|
209
|
+
env: AnonPiEnv;
|
|
210
|
+
project: string;
|
|
211
|
+
/** The resolved projects root (host dir mounted at /projects). */
|
|
212
|
+
projectsRoot: string;
|
|
213
|
+
/** The machine names whose homes may hold this project's session dir. */
|
|
214
|
+
machines: readonly string[];
|
|
215
|
+
}): DeleteProjectPlan;
|
|
216
|
+
/**
|
|
217
|
+
* The project token meaning "the root itself": cwd `/projects` (projects root),
|
|
218
|
+
* `/work` (`--mount`), or `~` (a machine home). It is NOT a valid machine or
|
|
219
|
+
* project name (validateName rejects it) so a folder can never shadow it.
|
|
220
|
+
*/
|
|
221
|
+
export declare const ROOT_TOKEN = ".";
|
|
222
|
+
/**
|
|
223
|
+
* Reserved names that a machine/project may NOT take (case-sensitive). Kept
|
|
224
|
+
* DELIBERATELY minimal: only the two structural path tokens. `.` is the root
|
|
225
|
+
* token (see ROOT_TOKEN); `..` is parent-traversal. Both are also rejected by
|
|
226
|
+
* the leading-dot / `..` structural checks below, but are listed here so the
|
|
227
|
+
* reserved-name concept is explicit and extendable. `--mount`'s `/work` is a
|
|
228
|
+
* CONTAINER path, not a name in this namespace, so it needs no reservation.
|
|
229
|
+
*/
|
|
230
|
+
export declare const RESERVED_NAMES: readonly string[];
|
|
231
|
+
/** What a name names, for a clear validation error. */
|
|
232
|
+
export type NameKind = 'machine' | 'project';
|
|
233
|
+
/**
|
|
234
|
+
* PURE: validate a machine or project name as a safe single path segment, and
|
|
235
|
+
* return it unchanged on success. Rejects (with AnonPiError):
|
|
236
|
+
* - empty
|
|
237
|
+
* - a path separator `/` or `\`, or a colon `:`
|
|
238
|
+
* - the traversal token `..` (and any leading dot, incl. `.`)
|
|
239
|
+
* - any whitespace
|
|
240
|
+
* - a reserved name (RESERVED_NAMES)
|
|
241
|
+
* A valid name is thus a single folder segment safe to join under the projects
|
|
242
|
+
* root or the machines dir with no traversal or drive/scheme surprises.
|
|
243
|
+
*/
|
|
244
|
+
export declare function validateName(name: string, kind: NameKind): string;
|
|
245
|
+
/**
|
|
246
|
+
* PURE: map a validated project `<name>` to its host folder under the resolved
|
|
247
|
+
* projects root (the parent from resolveProjectsRoot / a `--mount` parent).
|
|
248
|
+
* Validates the name (rejecting traversal) so the join stays inside the root.
|
|
249
|
+
*/
|
|
250
|
+
export declare function projectHostDir(projectsRoot: string, name: string): string;
|
|
251
|
+
/**
|
|
252
|
+
* PURE: the jail cwd for a validated project `<name>`: `/projects/<name>`. This
|
|
253
|
+
* is pi's conversation key (pi keys a session by its launch cwd). Validates the
|
|
254
|
+
* name. For the `--mount` root use resolveCwd('mount', name) (=> /work/<name>).
|
|
255
|
+
*/
|
|
256
|
+
export declare function projectContainerCwd(name: string): string;
|
|
257
|
+
/** Which mounted root a launch cwds into (see the CONTAINER_* root constants). */
|
|
258
|
+
export type RootKind = 'projects' | 'mount' | 'machine';
|
|
259
|
+
/** True iff `token` is exactly the root token `.` ("the root itself"). */
|
|
260
|
+
export declare function isRootToken(token: string | undefined): boolean;
|
|
261
|
+
/** PURE: the jail cwd of a root itself: /projects, /work (mount), or ~ (machine). */
|
|
262
|
+
export declare function rootCwd(kind: RootKind): string;
|
|
263
|
+
/**
|
|
264
|
+
* PURE: resolve a launch's jail cwd UNIFORMLY from a `token` and its root kind.
|
|
265
|
+
* The root token `.` means "the root itself" (rootCwd) in every context; any
|
|
266
|
+
* other token is a project name resolved to `<root>/<name>` (validated). A
|
|
267
|
+
* machine root has no named subfolders (projects live at /projects or /work,
|
|
268
|
+
* never under the machine home), so a non-`.` token for a machine is rejected.
|
|
269
|
+
* This is the one seam so `anon-pi --mount <p> .` and a menu "here" entry agree.
|
|
270
|
+
*/
|
|
271
|
+
export declare function resolveCwd(kind: RootKind, token: string): string;
|
|
272
|
+
/** Parsed shape of config.json. All fields optional (a hand-edited file may omit any). */
|
|
273
|
+
export interface AnonPiConfig {
|
|
274
|
+
/** socks5h proxy URL. */
|
|
275
|
+
proxy?: string;
|
|
276
|
+
/** The local-model direct target (host[:port]). */
|
|
277
|
+
llm?: string;
|
|
278
|
+
/** The machine bare `anon-pi` launches by default. */
|
|
279
|
+
defaultMachine?: string;
|
|
280
|
+
/** Override the projects root (host dir mounted at /projects). */
|
|
281
|
+
projects?: string;
|
|
282
|
+
}
|
|
283
|
+
/** Parsed shape of a per-machine machine.json. All fields optional. */
|
|
284
|
+
export interface MachineConfig {
|
|
285
|
+
/** The container image with `pi` on PATH for this machine. */
|
|
286
|
+
image?: string;
|
|
287
|
+
/** Per-machine projects-root override (above config, below env/--mount). */
|
|
288
|
+
projects?: string;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* PURE: parse an already-JSON-decoded config.json value into an AnonPiConfig,
|
|
292
|
+
* keeping only the known string fields (defensive against a hand-edited file).
|
|
293
|
+
* Tolerates undefined/null/partial input (an absent config is `{}`).
|
|
294
|
+
*/
|
|
295
|
+
export declare function parseConfigJson(raw: unknown): AnonPiConfig;
|
|
296
|
+
/**
|
|
297
|
+
* PURE: parse an already-JSON-decoded machine.json value into a MachineConfig.
|
|
298
|
+
* Tolerates undefined/null/partial input (an absent machine.json is `{}`).
|
|
299
|
+
*/
|
|
300
|
+
export declare function parseMachineJson(raw: unknown): MachineConfig;
|
|
301
|
+
/**
|
|
302
|
+
* PURE: resolve the projects root (the host dir mounted at /projects) with the
|
|
303
|
+
* decided precedence, highest first:
|
|
304
|
+
* --mount (CLI) > env ANON_PI_PROJECTS > machine.json.projects >
|
|
305
|
+
* config.json.projects > built-in <home>/projects
|
|
306
|
+
* This task delivers the config/env/machine layers; `mountParent` is the
|
|
307
|
+
* documented top slot the later --mount CLI task threads in (pass the resolved
|
|
308
|
+
* host parent). A relative override is resolved to an absolute path.
|
|
309
|
+
*/
|
|
310
|
+
export declare function resolveProjectsRoot(args: {
|
|
311
|
+
env: AnonPiEnv;
|
|
312
|
+
config?: AnonPiConfig;
|
|
313
|
+
machine?: MachineConfig;
|
|
314
|
+
/** The later --mount CLI override (a HOST parent path); top of the chain. */
|
|
315
|
+
mountParent?: string;
|
|
316
|
+
}): string;
|
|
317
|
+
/**
|
|
318
|
+
* PURE: resolve the proxy with env-over-config precedence, REQUIRED /
|
|
319
|
+
* fail-closed. Throws AnonPiError with the verbatim PROXY_REQUIRED_MESSAGE when
|
|
320
|
+
* neither env nor config supplies a non-empty proxy (never a guessed default:
|
|
321
|
+
* fail-closed is the anonymity invariant).
|
|
322
|
+
*/
|
|
323
|
+
export declare function resolveProxy(args: {
|
|
324
|
+
config?: AnonPiConfig;
|
|
325
|
+
env: {
|
|
326
|
+
proxy?: string;
|
|
327
|
+
};
|
|
328
|
+
}): string;
|
|
329
|
+
/**
|
|
330
|
+
* PURE: resolve the local-model direct target with env-over-config precedence.
|
|
331
|
+
* Unlike the proxy this is NOT fail-closed here (a launch with no local model
|
|
332
|
+
* is a later decision); returns undefined when neither supplies one.
|
|
333
|
+
*/
|
|
334
|
+
export declare function resolveLlm(args: {
|
|
335
|
+
config?: AnonPiConfig;
|
|
336
|
+
env: {
|
|
337
|
+
llmDirect?: string;
|
|
338
|
+
};
|
|
339
|
+
}): string | undefined;
|
|
340
|
+
/** A resolved machine: its host home (bind-mounted at /root) + its image. */
|
|
341
|
+
export interface Machine {
|
|
342
|
+
/** The machine's name (already validated by validateName elsewhere). */
|
|
343
|
+
name: string;
|
|
344
|
+
/** The persistent HOST home dir (machineHomeDir), bind-mounted at /root. */
|
|
345
|
+
home: string;
|
|
346
|
+
/** The container image with `pi` on PATH for this machine. */
|
|
347
|
+
image: string;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* What a launch runs. `menu` is the BARE launch: no target is chosen yet, so no
|
|
351
|
+
* netcage argv is composed (the host-side TUI picks a project/shell, THEN a
|
|
352
|
+
* fresh intent is resolved into a launch plan). `pi` runs pi (optionally with
|
|
353
|
+
* forwarded args); `shell` runs bash (the project-hopper, since pi cannot cd).
|
|
354
|
+
*/
|
|
355
|
+
export type LaunchMode = 'menu' | 'pi' | 'shell';
|
|
356
|
+
/**
|
|
357
|
+
* A parsed launch intent, injected so the resolver stays pure. The proxy + the
|
|
358
|
+
* direct-hole llm are threaded in RESOLVED (via resolveProxy/resolveLlm); the
|
|
359
|
+
* resolver re-asserts them non-empty so a plan can NEVER be produced without the
|
|
360
|
+
* forced-egress flags (fail-closed is the anonymity invariant).
|
|
361
|
+
*/
|
|
362
|
+
export interface LaunchIntent {
|
|
363
|
+
/** The machine to launch on (home + image). */
|
|
364
|
+
machine: Machine;
|
|
365
|
+
/** menu (bare) | pi | shell. */
|
|
366
|
+
mode: LaunchMode;
|
|
367
|
+
/**
|
|
368
|
+
* The resolved HOST projects root, bind-mounted at /projects. One of the two
|
|
369
|
+
* invariant mounts, present on every launch regardless of --mount.
|
|
370
|
+
*/
|
|
371
|
+
projectsRoot: string;
|
|
372
|
+
/**
|
|
373
|
+
* The project token: a validated project name, the root token `.`, or
|
|
374
|
+
* undefined (shell-at-home / menu). Resolves the cwd via resolveCwd.
|
|
375
|
+
*/
|
|
376
|
+
project?: string;
|
|
377
|
+
/**
|
|
378
|
+
* `--mount <parent>`: a resolved HOST parent path. When set it adds EXACTLY
|
|
379
|
+
* one mount (<parent>:/work) and re-roots the cwd there (/work[/<project>]);
|
|
380
|
+
* it changes nothing else (the two invariant mounts stay). Sidesteps podman
|
|
381
|
+
* mount immutability (we never remount a running box).
|
|
382
|
+
*/
|
|
383
|
+
mountParent?: string;
|
|
384
|
+
/** Extra args forwarded to `pi` (headless/one-shot). Ignored for shell. */
|
|
385
|
+
piArgs?: string[];
|
|
386
|
+
/**
|
|
387
|
+
* `--keep`: omit `--rm` so the container is left KEPT (its filesystem
|
|
388
|
+
* survives the apt-install/re-enter flow). Default (false) => `--rm`
|
|
389
|
+
* (throwaway); the machine home persists regardless (it is a host mount).
|
|
390
|
+
*/
|
|
391
|
+
keep?: boolean;
|
|
392
|
+
/** The resolved socks5h proxy (REQUIRED; the resolver fails closed without it). */
|
|
393
|
+
proxy: string;
|
|
394
|
+
/** The resolved local-model direct target (REQUIRED: the one --allow-direct hole). */
|
|
395
|
+
llmDirect: string;
|
|
396
|
+
/**
|
|
397
|
+
* The host models.json to mount read-only for the first-launch seed, keyed to
|
|
398
|
+
* THIS machine (e.g. <machine-dir>/models.json). Omitted => no seed mount (pi
|
|
399
|
+
* starts with no models; you add them in-session).
|
|
400
|
+
*/
|
|
401
|
+
modelsSeed?: string;
|
|
402
|
+
/**
|
|
403
|
+
* The settings SEED to mount read-only for the first-launch seed (the
|
|
404
|
+
* local-model default selection, e.g. <machine-dir>/settings-seed.json).
|
|
405
|
+
* Omitted => no settings seed (no default model is pre-selected).
|
|
406
|
+
*/
|
|
407
|
+
settingsSeed?: string;
|
|
408
|
+
/** The seed version stamped into a fresh home's marker. Default SEED_VERSION. */
|
|
409
|
+
seedVersion?: string;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* The resolved launch plan. A discriminated union so the BARE `menu` mode is a
|
|
413
|
+
* distinct, argv-less marker (the host-side TUI runs first) while every real
|
|
414
|
+
* launch carries a composed netcage argv. The forced-egress invariant is
|
|
415
|
+
* asserted on the `launch` variant's netcageArgs by construction.
|
|
416
|
+
*/
|
|
417
|
+
export type LaunchPlan = {
|
|
418
|
+
/** Bare launch: run the host-side menu, then re-resolve into a launch. */
|
|
419
|
+
kind: 'menu';
|
|
420
|
+
machine: Machine;
|
|
421
|
+
} | {
|
|
422
|
+
kind: 'launch';
|
|
423
|
+
machine: Machine;
|
|
424
|
+
/** The jail cwd (`-w`): /projects[/<p>], /work[/<p>] (--mount), or /root (shell ~). */
|
|
425
|
+
cwd: string;
|
|
426
|
+
/** True when the machine home is fresh (informational; the seed is marker-guarded). */
|
|
91
427
|
fresh: boolean;
|
|
92
428
|
/** The argv passed to `netcage` (after the `netcage` program name). */
|
|
93
429
|
netcageArgs: string[];
|
|
430
|
+
};
|
|
431
|
+
/** The machine bare `anon-pi` launches when no `-m` and no config default. */
|
|
432
|
+
export declare const DEFAULT_MACHINE = "default";
|
|
433
|
+
/**
|
|
434
|
+
* A parsed grammar-A launch. `mode` is `menu` when no project/shell target was
|
|
435
|
+
* chosen (bare `anon-pi`, or `-m <machine>` / `--mount <parent>` with no
|
|
436
|
+
* project): the CLI runs the host-side menu. `pi`/`shell` carry the chosen
|
|
437
|
+
* target. `project` is a validated project name, the `.` root token, or
|
|
438
|
+
* undefined (menu / shell-at-home). `mountParent` is the `--mount` HOST parent
|
|
439
|
+
* (a path, NOT a name-namespaced token). `keep` is `--keep` (default false =>
|
|
440
|
+
* throwaway `--rm`). `piArgs` are the trailing tokens forwarded to pi (pi mode
|
|
441
|
+
* only; undefined otherwise).
|
|
442
|
+
*/
|
|
443
|
+
export interface ParsedLaunch {
|
|
444
|
+
mode: LaunchMode;
|
|
445
|
+
machine: string;
|
|
446
|
+
/**
|
|
447
|
+
* True iff `-m`/`--machine` was given explicitly (so the CLI can let an
|
|
448
|
+
* explicit `-m default` win over `config.defaultMachine`, rather than treat
|
|
449
|
+
* the DEFAULT_MACHINE value as "unset").
|
|
450
|
+
*/
|
|
451
|
+
machineExplicit: boolean;
|
|
452
|
+
project?: string;
|
|
453
|
+
mountParent?: string;
|
|
454
|
+
keep: boolean;
|
|
455
|
+
piArgs?: string[];
|
|
94
456
|
}
|
|
95
|
-
/**
|
|
96
|
-
|
|
457
|
+
/**
|
|
458
|
+
* PURE: parse grammar A into a ParsedLaunch. Consumes the anon-pi flags
|
|
459
|
+
* (`-m <machine>`, `--shell`, `--mount <parent>`, `--keep`/`--rm`) LEFT of the
|
|
460
|
+
* project positional; the FIRST bare positional is the project (`.` allowed as
|
|
461
|
+
* the root token). In pi mode every token AFTER the project is forwarded to pi
|
|
462
|
+
* verbatim (so `anon-pi recon -p '...'` works) — anon-pi flags must come before
|
|
463
|
+
* the project. In shell/menu mode a stray extra positional is an error (bash has
|
|
464
|
+
* no forwarded-args grammar; the menu takes no project).
|
|
465
|
+
*
|
|
466
|
+
* Validates the project name and the `-m` machine name via validateName (the
|
|
467
|
+
* reserved-name guard); `--mount <parent>` is a HOST path in its own namespace,
|
|
468
|
+
* distinct from the project-name namespace (NAME vs `--mount` exclusivity), so
|
|
469
|
+
* it is NOT name-validated here. Throws AnonPiError for an unknown option, a
|
|
470
|
+
* missing `-m`/`--mount` argument, a contradictory `--keep --rm`, or a bad name.
|
|
471
|
+
*/
|
|
472
|
+
export declare function parseLaunchArgs(args: readonly string[]): ParsedLaunch;
|
|
473
|
+
/**
|
|
474
|
+
* PURE: resolve a LaunchIntent into a LaunchPlan, composing the netcage argv for
|
|
475
|
+
* every mode. Never spawns, never touches the filesystem: `homeFresh` reports
|
|
476
|
+
* whether the machine home has been seeded (so `fresh` is known) and is the only
|
|
477
|
+
* capability injected.
|
|
478
|
+
*
|
|
479
|
+
* Invariants held on EVERY composed argv:
|
|
480
|
+
* - the two mounts <home>:/root and <projectsRoot>:/projects, always;
|
|
481
|
+
* - --mount adds EXACTLY <parent>:/work and re-roots cwd, nothing else;
|
|
482
|
+
* - --proxy <p> + exactly one --allow-direct <llm> (forced egress, fail-closed);
|
|
483
|
+
* - --rm by default, omitted only under --keep.
|
|
484
|
+
*
|
|
485
|
+
* Throws AnonPiError (a plan is NEVER produced) when the image, the machine
|
|
486
|
+
* home, the proxy, or the direct-hole llm is missing.
|
|
487
|
+
*/
|
|
488
|
+
export declare function resolveRunPlan(intent: LaunchIntent, homeFresh: (machineHome: string) => boolean): LaunchPlan;
|
|
489
|
+
/**
|
|
490
|
+
* A kept `netcage.managed` container, as the CLI's netcage query surfaces it to
|
|
491
|
+
* the pure decision. Only the two fields the DECISION needs are typed:
|
|
492
|
+
* - `key`: the anon-pi launch-identity key (keptContainerKey) the CLI stamped
|
|
493
|
+
* onto the container at `run` time (a netcage label / container name) and
|
|
494
|
+
* reads back from the label; this is what a launch matches against.
|
|
495
|
+
* - `ref`: how to address the container for `netcage start` (its id or name).
|
|
496
|
+
* The CLI is free to carry more; the pure rule reads only these.
|
|
497
|
+
*/
|
|
498
|
+
export interface KeptContainer {
|
|
499
|
+
/** The anon-pi launch-identity key stamped on the container (keptContainerKey). */
|
|
500
|
+
key: string;
|
|
501
|
+
/** The container ref (id or name) to pass to `netcage start`. */
|
|
502
|
+
ref: string;
|
|
97
503
|
}
|
|
98
|
-
/** Resolve the anon-pi home dir (holds the seed). */
|
|
99
|
-
export declare function resolveAnonPiHome(env: AnonPiEnv): string;
|
|
100
504
|
/**
|
|
101
|
-
* The
|
|
102
|
-
*
|
|
103
|
-
*
|
|
505
|
+
* The run-vs-start decision. `run` = `netcage run` a fresh container (WITHOUT
|
|
506
|
+
* `--rm` under `--keep`, so it is left kept; the run argv itself is
|
|
507
|
+
* resolveRunPlan's job). `start` = `netcage start <ref>` an existing kept
|
|
508
|
+
* container whose identity matches this launch.
|
|
104
509
|
*/
|
|
105
|
-
export
|
|
510
|
+
export type RunVsStart = {
|
|
511
|
+
action: 'run';
|
|
512
|
+
} | {
|
|
513
|
+
action: 'start';
|
|
514
|
+
ref: string;
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* PURE: the launch-identity match key for a kept container, derived ENTIRELY
|
|
518
|
+
* from the (machine, projects-root, project) identity (ADR-0002). It is what
|
|
519
|
+
* decides whether an existing kept `netcage.managed` container IS the one a
|
|
520
|
+
* `--keep` launch should resume.
|
|
521
|
+
*
|
|
522
|
+
* The fields, and why each is load-bearing:
|
|
523
|
+
* - `machine.name`: a kept container mounts THIS machine's home at /root; a
|
|
524
|
+
* same-project container on another machine is a different environment.
|
|
525
|
+
* - `projectsRoot`: the host dir mounted at /projects; two launches with the
|
|
526
|
+
* same project name but different roots are different working trees.
|
|
527
|
+
* - `mountParent` (or '' when absent): `--mount` re-roots into a DIFFERENT
|
|
528
|
+
* host parent at /work, so a `--mount` launch is a distinct identity from
|
|
529
|
+
* the projects-root launch of the same name.
|
|
530
|
+
* - the resolved container `cwd`: this already encodes the project token
|
|
531
|
+
* (`/projects/<p>`, `/work/<p>`, `.` -> a root, or /root for a bare shell)
|
|
532
|
+
* AND which root it sits under, so it is pi's conversation key too. Using
|
|
533
|
+
* the cwd keeps the container identity aligned with the conversation the
|
|
534
|
+
* kept container hosts.
|
|
535
|
+
*
|
|
536
|
+
* DELIBERATELY EXCLUDED (not part of identity): `--keep`/`--rm` (the throwaway
|
|
537
|
+
* choice for THIS run), the proxy + the direct-hole llm (forced-egress inputs),
|
|
538
|
+
* forwarded pi args, and the seed. Two launches that differ only in those must
|
|
539
|
+
* resolve to the SAME kept container.
|
|
540
|
+
*
|
|
541
|
+
* The key is a single opaque string (a `\n`-joined, field-tagged record) so the
|
|
542
|
+
* CLI can stamp it verbatim onto a netcage label and match on string equality;
|
|
543
|
+
* its internal shape is not a contract (compare only keys this function makes).
|
|
544
|
+
*/
|
|
545
|
+
export declare function keptContainerKey(intent: LaunchIntent): string;
|
|
546
|
+
/**
|
|
547
|
+
* PURE: decide run-vs-start for a launch given a SUPPLIED listing of kept
|
|
548
|
+
* `netcage.managed` containers (the CLI's netcage query result).
|
|
549
|
+
*
|
|
550
|
+
* - `--rm` (throwaway, `intent.keep !== true`): ALWAYS a fresh `run`. The
|
|
551
|
+
* listing is NOT consulted (a throwaway launch never resumes a kept box).
|
|
552
|
+
* - `--keep`: a kept container whose `key` equals this launch's
|
|
553
|
+
* keptContainerKey is present -> `start` it (by its `ref`); else -> `run`
|
|
554
|
+
* (resolveRunPlan leaves it kept because `--keep` omits `--rm`).
|
|
555
|
+
*
|
|
556
|
+
* Never spawns, never queries netcage: the listing is injected, so the whole
|
|
557
|
+
* decision is a pure function of (intent, listing).
|
|
558
|
+
*/
|
|
559
|
+
export declare function resolveRunVsStart(intent: LaunchIntent, kept: readonly KeptContainer[]): RunVsStart;
|
|
560
|
+
/**
|
|
561
|
+
* PURE: the pi session-dir slug for a project, i.e. pathSlug of its jail cwd
|
|
562
|
+
* `/projects/<name>`. Because the cwd is the SAME on every machine (files are
|
|
563
|
+
* global, the projects root is mounted at /projects everywhere), this slug is
|
|
564
|
+
* MACHINE-INVARIANT: the same shared project is recognised in each machine's
|
|
565
|
+
* sessions dir. Validates the name (rejecting traversal) as projectContainerCwd
|
|
566
|
+
* does. e.g. `alpha` -> `--projects-alpha--`.
|
|
567
|
+
*/
|
|
568
|
+
export declare function projectSessionSlug(name: string): string;
|
|
569
|
+
/**
|
|
570
|
+
* The pure choice-list the bare-launch menu renders. `projects` are the
|
|
571
|
+
* folder-safe project names (sorted, case-insensitive) offered as pi launches;
|
|
572
|
+
* `here` is the `.` root token (a scratch pi at the root itself); `canNew` /
|
|
573
|
+
* `canShell` gate the `+ new project…` and `shell` affordances. It carries NO
|
|
574
|
+
* usage annotation (that is deriveProjectUsage, keyed by project name), so a
|
|
575
|
+
* caller can render the list alone or joined with usage.
|
|
576
|
+
*/
|
|
577
|
+
export interface MenuChoiceList {
|
|
578
|
+
/** The folder-safe project names, sorted case-insensitively for a stable menu. */
|
|
579
|
+
projects: string[];
|
|
580
|
+
/** The `.` "here" entry: a scratch pi at the root itself (ROOT_TOKEN). */
|
|
581
|
+
here: string;
|
|
582
|
+
/** Whether the `+ new project…` affordance is offered (always true today). */
|
|
583
|
+
canNew: boolean;
|
|
584
|
+
/** Whether the `shell` affordance is offered (always true today). */
|
|
585
|
+
canShell: boolean;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* PURE: build the menu choice-list from a SUPPLIED projects-root listing (the
|
|
589
|
+
* CLI's real `readdir` of the projects root). Entries that are not folder-safe
|
|
590
|
+
* project names (dotfiles like `.git`, `..`, path-separator names, whitespace,
|
|
591
|
+
* reserved tokens) are DROPPED silently: they can never be a valid project
|
|
592
|
+
* launch (validateName would reject them), and the `.` root is the separate
|
|
593
|
+
* `here` entry, not a listed project. The surviving names are sorted
|
|
594
|
+
* case-insensitively so the menu order is stable regardless of dir-read order.
|
|
595
|
+
*
|
|
596
|
+
* `canNew` / `canShell` default TRUE (both affordances are always offered
|
|
597
|
+
* today); they are fields so a later policy can gate them without a signature
|
|
598
|
+
* change. An empty projects root still offers here / new / shell.
|
|
599
|
+
*/
|
|
600
|
+
export declare function buildMenuChoiceList(args: {
|
|
601
|
+
projects: readonly string[];
|
|
602
|
+
canNew?: boolean;
|
|
603
|
+
canShell?: boolean;
|
|
604
|
+
}): MenuChoiceList;
|
|
605
|
+
/**
|
|
606
|
+
* A per-machine session-dir listing: for each machine name, the slugs present
|
|
607
|
+
* under machines/<M>/home/.pi/agent/sessions/. The CLI derives this by reading
|
|
608
|
+
* each machine home's sessions dir; the pure derivation takes it as input. Only
|
|
609
|
+
* the project session slugs (projectSessionSlug) are matched; any other slug
|
|
610
|
+
* (e.g. a `.`/`~`/`--mount` scratch session) is simply not a project so it does
|
|
611
|
+
* not appear in the usage record.
|
|
612
|
+
*/
|
|
613
|
+
export type SessionDirListing = Record<string, readonly string[]>;
|
|
614
|
+
/** The usage record for ONE project: which machines used it + a current-new flag. */
|
|
615
|
+
export interface ProjectUsage {
|
|
616
|
+
/** The project name (as supplied; validated). */
|
|
617
|
+
project: string;
|
|
618
|
+
/**
|
|
619
|
+
* The machine names whose home contains this project's session dir, sorted
|
|
620
|
+
* (a stable, machine-invariant "used on" list derived from session presence).
|
|
621
|
+
*/
|
|
622
|
+
machines: string[];
|
|
623
|
+
/**
|
|
624
|
+
* True when the CURRENT machine has NO session dir for this project yet (it is
|
|
625
|
+
* new for this machine, even if other machines have used the shared files).
|
|
626
|
+
*/
|
|
627
|
+
currentMachineIsNew: boolean;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* PURE: derive the per-machine project-usage record from SUPPLIED session-dir
|
|
631
|
+
* presence (no marker file). For each supplied project, in the SUPPLIED order,
|
|
632
|
+
* it reports which machines' homes contain that project's (machine-invariant)
|
|
633
|
+
* session slug, and whether the CURRENT machine is new for it.
|
|
634
|
+
*
|
|
635
|
+
* The project ORDER is preserved (the caller orders the menu, e.g. via
|
|
636
|
+
* buildMenuChoiceList); only the per-project `machines` list is sorted, so the
|
|
637
|
+
* "used on" annotation is stable. Validates each project name (rejecting
|
|
638
|
+
* traversal) via projectSessionSlug.
|
|
639
|
+
*/
|
|
640
|
+
export declare function deriveProjectUsage(args: {
|
|
641
|
+
projects: readonly string[];
|
|
642
|
+
currentMachine: string;
|
|
643
|
+
sessions: SessionDirListing;
|
|
644
|
+
}): ProjectUsage[];
|
|
645
|
+
/**
|
|
646
|
+
* What ONE selectable menu row launches, so the CLI can dispatch a chosen entry
|
|
647
|
+
* without re-deriving anything:
|
|
648
|
+
* - `project` -> pi in `/projects/<project>` (the `anon-pi <project>` launch);
|
|
649
|
+
* - `here` -> a scratch pi at the root itself (the `.` root token launch);
|
|
650
|
+
* - `new` -> prompt+validate a new project name, then launch it as pi;
|
|
651
|
+
* - `shell` -> the `--shell` jailed-bash launch.
|
|
652
|
+
*/
|
|
653
|
+
export type MenuEntryKind = 'project' | 'here' | 'new' | 'shell';
|
|
654
|
+
/** One rendered, selectable menu row: what it launches + its human label. */
|
|
655
|
+
export interface MenuEntry {
|
|
656
|
+
/** Which launch this row dispatches to (project | here | new | shell). */
|
|
657
|
+
kind: MenuEntryKind;
|
|
658
|
+
/**
|
|
659
|
+
* The project token this row launches: a validated project name (`project`),
|
|
660
|
+
* the root token `.` (`here`), or undefined (`new` prompts for it, `shell`
|
|
661
|
+
* takes none). This is exactly the `project` field a launch dispatch feeds
|
|
662
|
+
* back into the grammar, so no re-parsing is needed.
|
|
663
|
+
*/
|
|
664
|
+
project?: string;
|
|
665
|
+
/**
|
|
666
|
+
* The rendered row text the selector prints: the project name plus its
|
|
667
|
+
* used-on / new-here annotation (project rows), or the fixed affordance label
|
|
668
|
+
* (here / new / shell). The annotation is the ONLY place the usage record
|
|
669
|
+
* surfaces to the user, so the wording lives here (pure) not in the TUI.
|
|
670
|
+
*/
|
|
671
|
+
label: string;
|
|
672
|
+
}
|
|
673
|
+
/** The fixed labels for the non-project affordances (one source, so the TUI + its test agree). */
|
|
674
|
+
export declare const MENU_HERE_LABEL = ". (here: a scratch pi at the root)";
|
|
675
|
+
export declare const MENU_NEW_LABEL = "+ new project\u2026";
|
|
676
|
+
export declare const MENU_SHELL_LABEL = "shell (a jailed bash on this machine)";
|
|
677
|
+
/**
|
|
678
|
+
* PURE: render ONE project row's annotation from its usage record. Files are
|
|
679
|
+
* global but conversations are per-machine, so the row tells the user where a
|
|
680
|
+
* conversation for this project already lives (`used on: <machines>`) and
|
|
681
|
+
* whether the CURRENT machine has none yet (`new here`). An unused project on a
|
|
682
|
+
* fresh machine is just `new here` (no machine list). This is the whole
|
|
683
|
+
* user-visible surface of the derived usage record, kept pure + testable.
|
|
684
|
+
*/
|
|
685
|
+
export declare function formatProjectAnnotation(usage: ProjectUsage): string;
|
|
686
|
+
/**
|
|
687
|
+
* PURE: assemble the ordered, labelled, selectable menu rows from the choice-
|
|
688
|
+
* list + the per-project usage record. The order is: the projects (in the
|
|
689
|
+
* choice-list's stable sorted order), then the `.` "here" scratch entry, then
|
|
690
|
+
* `+ new project\u2026` (when `canNew`), then `shell` (when `canShell`). Each
|
|
691
|
+
* project row's label carries its used-on / new-here annotation
|
|
692
|
+
* (formatProjectAnnotation). This holds ALL the menu's logic (order + wording)
|
|
693
|
+
* so the raw-mode selector only renders these rows and dispatches the picked
|
|
694
|
+
* one by its `kind`/`project`.
|
|
695
|
+
*
|
|
696
|
+
* The `usage` list is expected to be keyed to `choiceList.projects` (same order,
|
|
697
|
+
* as deriveProjectUsage produces from the choice-list's projects); a project
|
|
698
|
+
* with no matching usage entry gets a bare, unannotated row rather than erroring.
|
|
699
|
+
*/
|
|
700
|
+
export declare function buildMenuEntries(args: {
|
|
701
|
+
choiceList: MenuChoiceList;
|
|
702
|
+
usage: readonly ProjectUsage[];
|
|
703
|
+
}): MenuEntry[];
|
|
106
704
|
/**
|
|
107
705
|
* Encode an absolute path into a directory name using pi's OWN convention (see
|
|
108
706
|
* pi coding-agent session-manager: `--${cwd without leading slash, / \ : -> -}--`),
|
|
@@ -110,18 +708,160 @@ export declare function resolveConfigSeed(env: AnonPiEnv): string;
|
|
|
110
708
|
* hash). e.g. /home/u/dev/x -> --home-u-dev-x--
|
|
111
709
|
*/
|
|
112
710
|
export declare function pathSlug(absPath: string): string;
|
|
113
|
-
/**
|
|
114
|
-
* The persistent per-workdir state dir on the host (mounted at the container's
|
|
115
|
-
* ~/.pi/agent). Keyed by the workdir via pi's path-slug convention:
|
|
116
|
-
* <anonPiHome>/state/<slug>/agent
|
|
117
|
-
*/
|
|
118
|
-
export declare function stateAgentDir(env: AnonPiEnv, absWorkdir: string): string;
|
|
119
711
|
/**
|
|
120
712
|
* Normalise a proxy-less host:port key from an ANON_PI_LLM value or a provider
|
|
121
713
|
* baseUrl, so `192.168.1.150:8080` matches `http://192.168.1.150:8080/v1`.
|
|
122
714
|
* Returns `host` (no port) or `host:port`, lowercased, scheme/path stripped.
|
|
123
715
|
*/
|
|
124
716
|
export declare function hostPortKey(value: string): string;
|
|
717
|
+
/**
|
|
718
|
+
* The provider key anon-pi gives the single local provider it generates. A
|
|
719
|
+
* neutral, host-agnostic name (matches the CONTEXT glossary's "local model"):
|
|
720
|
+
* it carries NO host identity, unlike the old `import` path which kept the
|
|
721
|
+
* host's own provider key.
|
|
722
|
+
*/
|
|
723
|
+
export declare const LOCAL_PROVIDER_NAME = "local";
|
|
724
|
+
/**
|
|
725
|
+
* The pi `api` dialect the generated local provider speaks. Local model servers
|
|
726
|
+
* (llama.cpp, ollama, LM Studio, vLLM, ...) are overwhelmingly OpenAI-compatible
|
|
727
|
+
* and serve the completions API under `/v1`, so this is the safe default for an
|
|
728
|
+
* endpoint captured by `init` (there is no host models.json to copy a dialect
|
|
729
|
+
* from anymore). See the ## Decisions note in the done record.
|
|
730
|
+
*/
|
|
731
|
+
export declare const LOCAL_PROVIDER_API = "openai-completions";
|
|
732
|
+
/**
|
|
733
|
+
* A benign, non-secret apiKey for the local provider (a LAN model rarely needs a
|
|
734
|
+
* real key). It is one of the values pi never flags as a real secret.
|
|
735
|
+
*/
|
|
736
|
+
export declare const LOCAL_PROVIDER_API_KEY = "none";
|
|
737
|
+
/**
|
|
738
|
+
* apiKey values that are NOT real secrets (safe to carry into the anonymized
|
|
739
|
+
* seed verbatim). Anything else is treated as a REAL secret: `init` refuses to
|
|
740
|
+
* seed it (which would put a host credential into the anon home) unless the
|
|
741
|
+
* operator passes `--force-allow-local-llm-api-key`.
|
|
742
|
+
*/
|
|
743
|
+
export declare const BENIGN_API_KEYS: ReadonlySet<string>;
|
|
744
|
+
/** PURE: whether an apiKey looks like a REAL secret (i.e. not in the benign set). */
|
|
745
|
+
export declare function apiKeyLooksReal(apiKey: string | undefined): boolean;
|
|
746
|
+
/**
|
|
747
|
+
* A pi model entry as anon-pi seeds it for the local provider. pi keys a model
|
|
748
|
+
* by `id`; `name` is the display label and `cost` is all-zero (a LAN model is
|
|
749
|
+
* free). A "server"-sourced entry is minimal (id/name/cost); a "configured"
|
|
750
|
+
* entry (imported from the host models.json) preserves whatever extra fields it
|
|
751
|
+
* carried (`contextWindow`, `maxTokens`, `reasoning`, `input`, ...) via the
|
|
752
|
+
* index signature.
|
|
753
|
+
*/
|
|
754
|
+
export interface GeneratedModel {
|
|
755
|
+
id: string;
|
|
756
|
+
name: string;
|
|
757
|
+
cost?: {
|
|
758
|
+
input: number;
|
|
759
|
+
output: number;
|
|
760
|
+
cacheRead: number;
|
|
761
|
+
cacheWrite: number;
|
|
762
|
+
};
|
|
763
|
+
[k: string]: unknown;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* PURE: a candidate model for the `init` picker. `configured` means it came from
|
|
767
|
+
* the host `~/.pi/agent/models.json` provider that matches the endpoint (a
|
|
768
|
+
* well-tuned entry with its real config); otherwise it was only reported by the
|
|
769
|
+
* endpoint's `/v1/models` (a bare id we synthesize a minimal entry for). The
|
|
770
|
+
* picker marks configured ones so the user knows which are more likely correct.
|
|
771
|
+
*/
|
|
772
|
+
export interface ModelCandidate {
|
|
773
|
+
id: string;
|
|
774
|
+
configured: boolean;
|
|
775
|
+
/** The full pi model entry to seed (rich for configured, minimal otherwise). */
|
|
776
|
+
entry: GeneratedModel;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* PURE: turn a discovered model `id` into a minimal-but-valid pi model entry.
|
|
780
|
+
* `name` defaults to the id; a LAN model is free, so every cost is 0.
|
|
781
|
+
*/
|
|
782
|
+
export declare function localModelEntry(id: string): GeneratedModel;
|
|
783
|
+
/**
|
|
784
|
+
* PURE: extract the model ids from a parsed OpenAI-compatible `/v1/models`
|
|
785
|
+
* response (`{ data: [{ id }, ...] }`, as llama.cpp / vLLM / LM Studio serve).
|
|
786
|
+
* Tolerates a bare array, a `models` key, missing/garbage input (returns []), so
|
|
787
|
+
* `init` can feed whatever the endpoint returned straight in.
|
|
788
|
+
*/
|
|
789
|
+
export declare function parseModelsListing(raw: unknown): string[];
|
|
790
|
+
/** The result of scanning a host models.json for the endpoint's provider. */
|
|
791
|
+
export interface HostProviderMatch {
|
|
792
|
+
/** The matching provider's models as full pi entries (verbatim host config). */
|
|
793
|
+
models: GeneratedModel[];
|
|
794
|
+
/** The matching provider's apiKey (verbatim), for the benign/real check. */
|
|
795
|
+
apiKey?: string;
|
|
796
|
+
/** True iff that apiKey looks like a REAL secret (init refuses without --force). */
|
|
797
|
+
apiKeyLooksReal: boolean;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* PURE: find, in a parsed host `~/.pi/agent/models.json`, the provider whose
|
|
801
|
+
* `baseUrl` points at `llmEndpoint` (matched via hostPortKey), and return ONLY
|
|
802
|
+
* that provider's models + apiKey. This is the anonymity-critical scoping: the
|
|
803
|
+
* ONLY provider considered is the one served by the `--allow-direct` endpoint,
|
|
804
|
+
* so no other provider (etherplay/google/a paid API) — and no other provider's
|
|
805
|
+
* key — can ever enter the seed. Returns undefined when no provider matches.
|
|
806
|
+
*
|
|
807
|
+
* The `--allow-direct` target and this match both go through hostPortKey, so a
|
|
808
|
+
* URL / ip:port / bare-ip host config all match the same endpoint.
|
|
809
|
+
*/
|
|
810
|
+
export declare function pickLocalProviderModels(hostModels: PiModelsFile, llmEndpoint: string): HostProviderMatch | undefined;
|
|
811
|
+
/**
|
|
812
|
+
* PURE: merge the host-config models (rich, `configured: true`) with the
|
|
813
|
+
* endpoint's live `/v1/models` ids (`configured: false` for any the host did not
|
|
814
|
+
* already carry), into ONE deduped, sorted candidate list. Host config wins on
|
|
815
|
+
* an id present in both (it has the real config). Every candidate here is served
|
|
816
|
+
* by the endpoint, so every one is `--allow-direct`-reachable; the merge just
|
|
817
|
+
* unions "what you already configured" with "what the server also offers".
|
|
818
|
+
*/
|
|
819
|
+
export declare function mergeModelSources(hostModels: readonly GeneratedModel[], serverIds: readonly string[]): ModelCandidate[];
|
|
820
|
+
/**
|
|
821
|
+
* PURE: synthesize a pi `models.json` for the local provider from an endpoint
|
|
822
|
+
* and the CHOSEN model entries. It normalises the endpoint with hostPortKey and
|
|
823
|
+
* returns a models.json carrying exactly ONE provider (named LOCAL_PROVIDER_NAME
|
|
824
|
+
* — a neutral name, no host fingerprint) pointed at that endpoint.
|
|
825
|
+
*
|
|
826
|
+
* `apiKey` defaults to the benign LOCAL_PROVIDER_API_KEY. A caller may pass the
|
|
827
|
+
* host provider's real key ONLY under an explicit force flag; the benign/real
|
|
828
|
+
* decision (and the refusal) lives in `init`, not here — this pure function just
|
|
829
|
+
* writes what it is given.
|
|
830
|
+
*
|
|
831
|
+
* Accepts either full model entries (from the host config) or bare id strings
|
|
832
|
+
* (which it turns into minimal entries). Empty models => a provider pointed at
|
|
833
|
+
* the endpoint with no pickable model (the degraded fallback).
|
|
834
|
+
*/
|
|
835
|
+
export declare function generateModelsJson(llmEndpoint: string, models?: readonly (GeneratedModel | string)[], apiKey?: string): PiModelsFile;
|
|
836
|
+
/** The pi settings.json keys anon-pi sets for the local-model default selection. */
|
|
837
|
+
export interface ModelSelection {
|
|
838
|
+
defaultProvider: string;
|
|
839
|
+
defaultModel: string;
|
|
840
|
+
enabledModels: string[];
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* PURE: the model-selection settings.json fragment for the seeded local
|
|
844
|
+
* provider: `defaultProvider` = LOCAL_PROVIDER_NAME, `defaultModel` = the chosen
|
|
845
|
+
* default id, `enabledModels` = `local/<id>` for each imported model (pi's
|
|
846
|
+
* `<provider>/<id>` convention). The caller MERGES this into any existing
|
|
847
|
+
* settings so image-staged settings (packages/extensions) are preserved.
|
|
848
|
+
*/
|
|
849
|
+
export declare function generateModelSelection(modelIds: readonly string[], defaultId: string): ModelSelection;
|
|
850
|
+
/**
|
|
851
|
+
* PURE: shallow-merge the local-model selection into an existing (parsed)
|
|
852
|
+
* settings.json object, returning the merged object. Only the three selection
|
|
853
|
+
* keys are overwritten; every other key the user/image had (packages,
|
|
854
|
+
* extensions, thinking level, ...) is preserved. `existing` undefined/garbage is
|
|
855
|
+
* treated as `{}`.
|
|
856
|
+
*/
|
|
857
|
+
export declare function mergeModelSelection(existing: unknown, selection: ModelSelection): Record<string, unknown>;
|
|
858
|
+
/**
|
|
859
|
+
* The host `~/.pi/agent/models.json` path `init` reads the matching local
|
|
860
|
+
* provider from. Uses the container-less host HOME (or PI_CODING_AGENT_DIR when
|
|
861
|
+
* the user relocated pi's agent dir). This is READ-ONLY (init copies only the
|
|
862
|
+
* ONE matching provider's models); it is never written.
|
|
863
|
+
*/
|
|
864
|
+
export declare function resolveHostModelsPath(env: AnonPiEnv): string;
|
|
125
865
|
/**
|
|
126
866
|
* A pi provider entry (as it appears under models.json `providers[name]`). Only
|
|
127
867
|
* the fields anon-pi reads are typed; the rest is preserved verbatim.
|
|
@@ -138,51 +878,170 @@ export interface PiModelsFile {
|
|
|
138
878
|
providers?: Record<string, PiProvider>;
|
|
139
879
|
[k: string]: unknown;
|
|
140
880
|
}
|
|
141
|
-
/** The result of picking the ANON_PI_LLM provider out of a host models.json. */
|
|
142
|
-
export interface ImportResult {
|
|
143
|
-
/** The provider key (e.g. "llamacpp-router"). */
|
|
144
|
-
name: string;
|
|
145
|
-
/** The barebones models.json to write (just the matched provider). */
|
|
146
|
-
models: PiModelsFile;
|
|
147
|
-
/** True if the matched provider's apiKey looks like a REAL secret (warn). */
|
|
148
|
-
apiKeyLooksReal: boolean;
|
|
149
|
-
}
|
|
150
881
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
882
|
+
* The default SOCKS ports `init` probes, each with a WEAK, structural hint (the
|
|
883
|
+
* conventional tool that DEFAULTS to that port). The hint names a local tool a
|
|
884
|
+
* port is CONVENTIONALLY used by, NOT the exit provider: `9050`/`9150` are Tor's
|
|
885
|
+
* own listeners (Tor IS the tool, so naming it is honest), `1080` is the generic
|
|
886
|
+
* SOCKS default (wireproxy / `ssh -D` / other), which is why its hint stays
|
|
887
|
+
* provider-agnostic ("wireproxy / ssh -D / generic"): behind a `1080` wireproxy
|
|
888
|
+
* could be ANY WireGuard VPN, and we never guess which. See the ADR / Decisions.
|
|
156
889
|
*/
|
|
157
|
-
export declare
|
|
890
|
+
export declare const DEFAULT_SOCKS_PROBE_PORTS: readonly {
|
|
891
|
+
port: number;
|
|
892
|
+
hint: string;
|
|
893
|
+
}[];
|
|
158
894
|
/**
|
|
159
|
-
* The
|
|
160
|
-
*
|
|
161
|
-
*
|
|
895
|
+
* The SOCKS5 method-selection greeting `init` sends to CONFIRM a port really
|
|
896
|
+
* speaks SOCKS5 (RFC 1928 §3): version 5, one method offered, `0x00`
|
|
897
|
+
* (no-authentication). A real SOCKS5 server replies with two bytes
|
|
898
|
+
* `[0x05, <method>]`; anything else is not SOCKS5. Exposed as a constant so the
|
|
899
|
+
* probe I/O and the handshake test send byte-identical bytes.
|
|
162
900
|
*/
|
|
163
|
-
export declare
|
|
901
|
+
export declare const SOCKS5_METHOD_SELECTOR: readonly number[];
|
|
902
|
+
/** How a SOCKS5 handshake probe against a port came out (the pure verdict). */
|
|
903
|
+
export type SocksHandshake = {
|
|
904
|
+
/** The server replied with a well-formed SOCKS5 method-selection reply. */
|
|
905
|
+
socks5: true;
|
|
906
|
+
/** The selected method byte the server chose (informational). */
|
|
907
|
+
method: number;
|
|
908
|
+
} | {
|
|
909
|
+
/** The reply was absent, too short, or not a SOCKS5 version-5 reply. */
|
|
910
|
+
socks5: false;
|
|
911
|
+
/** A terse, provider-agnostic reason (for the findings line). */
|
|
912
|
+
reason: string;
|
|
913
|
+
};
|
|
164
914
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
915
|
+
* PURE: interpret a SOCKS5 method-selection REPLY (the bytes read back after
|
|
916
|
+
* sending SOCKS5_METHOD_SELECTOR). A valid reply is EXACTLY the two bytes
|
|
917
|
+
* `[0x05, <method>]` where `<method> != 0xff` (0xff = "no acceptable methods",
|
|
918
|
+
* i.e. the server IS SOCKS5 but rejected no-auth; that is still a SOCKS5 server,
|
|
919
|
+
* but for a bare no-auth probe we treat it as a soft failure so the finding does
|
|
920
|
+
* not imply the port is usable no-auth). Any non-5 first byte, a short reply, or
|
|
921
|
+
* an empty reply is NOT SOCKS5.
|
|
168
922
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
|
|
173
|
-
|
|
923
|
+
* Reply in -> verdict out; the socket read is cli.ts's job. The reason strings
|
|
924
|
+
* are deliberately structural ("no reply", "not SOCKS5") and NEVER name a
|
|
925
|
+
* provider.
|
|
926
|
+
*/
|
|
927
|
+
export declare function interpretSocks5Handshake(reply: readonly number[] | Uint8Array | Buffer): SocksHandshake;
|
|
928
|
+
/**
|
|
929
|
+
* A weak process hint: a LOCAL tool whose presence SUGGESTS what a port is
|
|
930
|
+
* (e.g. a `tor` process -> likely Tor). It is a hint about the LOCAL software
|
|
931
|
+
* only, never a claim about the EXIT provider. cli.ts supplies the observed
|
|
932
|
+
* process name (e.g. from `ps`/`/proc`); the pure mapping stays testable.
|
|
933
|
+
*/
|
|
934
|
+
export interface ProcessHint {
|
|
935
|
+
/** The observed local process name (as cli.ts read it). */
|
|
936
|
+
process: string;
|
|
937
|
+
/** The weak, hedged hint text ("a `tor` process is running -> likely Tor"). */
|
|
938
|
+
hint: string;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* PURE: map an observed local process name to a WEAK, hedged hint, or undefined
|
|
942
|
+
* when we have nothing honest to say. The ONLY confident mapping is `tor` ->
|
|
943
|
+
* "likely Tor", because Tor is a LOCAL tool that runs its OWN SOCKS listener (so
|
|
944
|
+
* seeing `tor` is real evidence the port is Tor). We do NOT map anything to an
|
|
945
|
+
* EXIT provider (Mullvad/Proton/...): a `wireproxy` process only tells us the
|
|
946
|
+
* SOCKS front-end, never which VPN sits behind it, so its hint stays
|
|
947
|
+
* provider-agnostic. Every returned hint is HEDGED ("likely", "-> a SOCKS
|
|
948
|
+
* front-end") and never states the exit provider.
|
|
949
|
+
*/
|
|
950
|
+
export declare function processHint(processName: string): ProcessHint | undefined;
|
|
951
|
+
/**
|
|
952
|
+
* One probed SOCKS candidate, as `init` gathers it for the findings display. All
|
|
953
|
+
* fields are EVIDENCE the probe actually observed; there is DELIBERATELY no
|
|
954
|
+
* "provider" field, so the type itself cannot carry a provider label.
|
|
955
|
+
*/
|
|
956
|
+
export interface ProxyFinding {
|
|
957
|
+
/** The host that was probed (usually 127.0.0.1). */
|
|
958
|
+
host: string;
|
|
959
|
+
/** The port that was probed. */
|
|
960
|
+
port: number;
|
|
961
|
+
/** Whether the TCP port was open (a connection succeeded). */
|
|
962
|
+
open: boolean;
|
|
963
|
+
/** The SOCKS5 handshake verdict (only meaningful when `open`). */
|
|
964
|
+
handshake?: SocksHandshake;
|
|
965
|
+
/** The port's structural hint (DEFAULT_SOCKS_PROBE_PORTS), if any. */
|
|
966
|
+
portHint?: string;
|
|
967
|
+
/** Any weak LOCAL process hint (processHint), if one was observed. */
|
|
968
|
+
processHint?: string;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* The set of substrings a findings line must NEVER contain: known exit-provider
|
|
972
|
+
* / VPN brand names. This is the machine-checkable half of the never-label rule
|
|
973
|
+
* (a test asserts formatProxyFindings' output contains NONE of these for any
|
|
974
|
+
* input). It is not exhaustive of every brand, but it pins the obvious ones so a
|
|
975
|
+
* regression that starts labelling providers is caught. `tor` is NOT here: Tor
|
|
976
|
+
* is the LOCAL tool we legitimately hint at, not an opaque exit provider.
|
|
977
|
+
*/
|
|
978
|
+
export declare const FORBIDDEN_PROVIDER_LABELS: readonly string[];
|
|
979
|
+
/**
|
|
980
|
+
* PURE: format the probe findings into the human-readable block `init` shows
|
|
981
|
+
* before asking the user to CHOOSE a proxy. It renders EVIDENCE ONLY: for each
|
|
982
|
+
* candidate, the `host:port`, whether it is open, the SOCKS5 handshake verdict,
|
|
983
|
+
* and the structural PORT hint. It NEVER emits an exit-provider label (a SOCKS
|
|
984
|
+
* proxy does not announce its provider; a false label is a dangerous lie). The
|
|
985
|
+
* `## Decisions` note + a test assert the output never contains a
|
|
986
|
+
* FORBIDDEN_PROVIDER_LABELS substring for any input.
|
|
174
987
|
*
|
|
175
|
-
* `
|
|
176
|
-
*
|
|
177
|
-
*
|
|
988
|
+
* `processNote` is the HOST-WIDE weak process hint (a running `tor`/`wireproxy`
|
|
989
|
+
* LOCAL process), shown ONCE as a general note rather than glued onto every port
|
|
990
|
+
* line: the observation is host-wide, not per-port, so repeating it on each
|
|
991
|
+
* candidate (including closed ports the process is unrelated to) reads as noise.
|
|
992
|
+
* A per-finding `processHint`, if still set, is also honoured inline for
|
|
993
|
+
* backward compatibility, but `init` now passes the host-wide note instead.
|
|
178
994
|
*
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*
|
|
995
|
+
* Findings in -> display string out; the socket probes are cli.ts's job.
|
|
996
|
+
*/
|
|
997
|
+
export declare function formatProxyFindings(findings: readonly ProxyFinding[], processNote?: string): string;
|
|
998
|
+
/**
|
|
999
|
+
* PURE: the `socks5h://<host:port>` URL `init` hands to `netcage verify` and
|
|
1000
|
+
* writes into config.json. Only socks5h:// is accepted downstream (plain
|
|
1001
|
+
* socks5:// resolves DNS locally and leaks), so `init` always emits socks5h.
|
|
1002
|
+
* A value that already carries a scheme is normalised to its host:port first
|
|
1003
|
+
* (via hostPortKey) so `socks5h://socks5h://...` can never be produced.
|
|
1004
|
+
*/
|
|
1005
|
+
export declare function socks5hUrl(hostPort: string): string;
|
|
1006
|
+
/**
|
|
1007
|
+
* PURE: extract the exit IP `netcage verify` reported from its combined output.
|
|
1008
|
+
* `netcage verify` prints the jail's forced-egress exit IP (an IPv4/IPv6 line)
|
|
1009
|
+
* as PROOF the egress leaves via the proxy (not the host IP). We scan the output
|
|
1010
|
+
* for the first plausible IP literal and return it; undefined if none is found
|
|
1011
|
+
* (the caller then shows the raw output and lets the user judge). This is a
|
|
1012
|
+
* best-effort PARSE of another tool's text, kept pure + tested so a format tweak
|
|
1013
|
+
* is caught by a unit test, not only in the field.
|
|
1014
|
+
*/
|
|
1015
|
+
export declare function parseVerifyExitIp(output: string): string | undefined;
|
|
1016
|
+
/**
|
|
1017
|
+
* The image-menu choices `init` offers for the default machine's image. `[1]`
|
|
1018
|
+
* and `[2]` build a SHIPPED Dockerfile via `podman build`; `[3]` takes an
|
|
1019
|
+
* existing image ref verbatim; `[4]` skips (the machine is created imageless and
|
|
1020
|
+
* pinned later). The pure list keeps the menu wording testable; cli.ts renders
|
|
1021
|
+
* it, runs `podman build`, and writes the machine.
|
|
1022
|
+
*/
|
|
1023
|
+
export type InitImageChoice = 'basic' | 'webveil' | 'existing' | 'skip';
|
|
1024
|
+
/** One rendered image-menu entry: its choice tag + the human label. */
|
|
1025
|
+
export interface InitImageMenuEntry {
|
|
1026
|
+
choice: InitImageChoice;
|
|
1027
|
+
label: string;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* PURE: the ordered image-menu entries `init` shows. `[1]` basic pi
|
|
1031
|
+
* (Dockerfile.pi), `[2]` pi + webveil/SearXNG (examples/Dockerfile.pi-webveil),
|
|
1032
|
+
* `[3]` an existing image ref, `[4]` skip. A single source so the prompt and its
|
|
1033
|
+
* test agree on the order + wording.
|
|
1034
|
+
*/
|
|
1035
|
+
export declare function initImageMenu(): InitImageMenuEntry[];
|
|
1036
|
+
/**
|
|
1037
|
+
* PURE: build the `config.json` body `init` writes, keeping only the non-empty
|
|
1038
|
+
* fields (a skipped image / llm is simply omitted, never written as ""). Emits
|
|
1039
|
+
* pretty-printed JSON (tab indent, trailing newline) matching
|
|
1040
|
+
* serializeMachineJson, so a browsed ~/.anon-pi/config.json reads cleanly. The
|
|
1041
|
+
* proxy is REQUIRED (init only reaches here after a verified proxy), so it is
|
|
1042
|
+
* always present; llm / defaultMachine / projects are included when set.
|
|
184
1043
|
*/
|
|
185
|
-
export declare function
|
|
1044
|
+
export declare function serializeConfigJson(config: AnonPiConfig): string;
|
|
186
1045
|
/**
|
|
187
1046
|
* Absolute path to the Dockerfile.pi that ships with anon-pi, resolved from this
|
|
188
1047
|
* module's location (package root, one level up from dist/anon-pi.js), or
|
|
@@ -195,8 +1054,62 @@ export declare function shippedDockerfilePath(): string | undefined;
|
|
|
195
1054
|
* anon-pi (examples/Dockerfile.pi-webveil), or undefined if not found.
|
|
196
1055
|
*/
|
|
197
1056
|
export declare function shippedWebveilDockerfilePath(): string | undefined;
|
|
1057
|
+
/**
|
|
1058
|
+
* A parsed `machine <verb> …` command. A discriminated union so the CLI
|
|
1059
|
+
* dispatches on `verb` with the already-validated fields:
|
|
1060
|
+
* - `create <name> [--image <ref>]`: name validated; image optional here (the
|
|
1061
|
+
* CLI prompts for it when absent, on a TTY).
|
|
1062
|
+
* - `list`: no args.
|
|
1063
|
+
* - `set-image <name> <ref>`: name validated; the new image ref (non-empty).
|
|
1064
|
+
* - `rm <name> [--yes]`: name validated; `yes` skips the confirm (the CLI
|
|
1065
|
+
* still enforces the non-TTY abort when `yes` is false).
|
|
1066
|
+
*/
|
|
1067
|
+
export type MachineCommand = {
|
|
1068
|
+
verb: 'create';
|
|
1069
|
+
name: string;
|
|
1070
|
+
image?: string;
|
|
1071
|
+
} | {
|
|
1072
|
+
verb: 'list';
|
|
1073
|
+
} | {
|
|
1074
|
+
verb: 'set-image';
|
|
1075
|
+
name: string;
|
|
1076
|
+
image: string;
|
|
1077
|
+
} | {
|
|
1078
|
+
verb: 'rm';
|
|
1079
|
+
name: string;
|
|
1080
|
+
yes: boolean;
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* PURE: parse the tokens AFTER `machine` into a MachineCommand. Validates the
|
|
1084
|
+
* machine name via validateName (the reserved-name / traversal guard) so the CLI
|
|
1085
|
+
* only ever joins a safe segment under the machines dir. Throws AnonPiError
|
|
1086
|
+
* (printed verbatim, exit 1) for an unknown/missing verb, a missing or extra
|
|
1087
|
+
* positional, an unknown flag, or a bad name.
|
|
1088
|
+
*
|
|
1089
|
+
* The grammar is deliberately small and flag-light (mirrors the launch grammar's
|
|
1090
|
+
* `--yes` / `--image` shape): `--image <ref>` on create, `--yes` on rm; no other
|
|
1091
|
+
* flags. This keeps `machine` a thin, predictable dispatch surface.
|
|
1092
|
+
*/
|
|
1093
|
+
export declare function parseMachineArgs(args: readonly string[]): MachineCommand;
|
|
1094
|
+
/**
|
|
1095
|
+
* PURE: the JSON body a machine.json carries, given the pinned image (and an
|
|
1096
|
+
* optional per-machine projects override, preserved on a re-pin). A single
|
|
1097
|
+
* source so create + set-image write byte-identical, pretty-printed JSON (tab
|
|
1098
|
+
* indent, trailing newline) that reads cleanly when the user browses
|
|
1099
|
+
* ~/.anon-pi/machines/<M>/machine.json.
|
|
1100
|
+
*/
|
|
1101
|
+
export declare function serializeMachineJson(config: MachineConfig): string;
|
|
1102
|
+
/**
|
|
1103
|
+
* PURE: the compatibility WARNING `machine set-image` prints after re-pinning
|
|
1104
|
+
* the image. Re-pinning does NOT reseed or touch the home: the home's pi
|
|
1105
|
+
* extensions / downloaded bin were built against the OLD image, so a mismatched
|
|
1106
|
+
* new image may misbehave. The message tells the user the two remedies (re-run
|
|
1107
|
+
* `pi install` inside the machine, or delete the home to reseed) WITHOUT doing
|
|
1108
|
+
* either automatically. See the ## Decisions note (set-image warning wording).
|
|
1109
|
+
*/
|
|
1110
|
+
export declare function setImageWarning(name: string, oldImage: string | undefined, newImage: string): string;
|
|
198
1111
|
/** Read the AnonPiEnv from a process env map (kept separate so tests inject one). */
|
|
199
1112
|
export declare function envFromProcess(penv: Record<string, string | undefined>): AnonPiEnv;
|
|
200
1113
|
/** The --help text (kept here so it is covered by the same module). */
|
|
201
|
-
export declare const HELP = "anon-pi -
|
|
1114
|
+
export declare const HELP = "anon-pi - run pi on anonymized, jailed machines (netcage: forced egress + one direct local model)\n\nUSAGE\n anon-pi MENU: pick a project (pi), a shell, or a new project\n anon-pi <project> pi in the project (/projects/<project>); exit pi -> host\n anon-pi <project> <pi-args\u2026> forward args to pi (headless/one-shot; no TTY needed)\n anon-pi --shell [<project>] a jailed bash (at ~, or cd'd into <project>) - the project-hopper\n anon-pi -m <machine> [<p>] the same, on <machine> (its own image + home + conversations)\n anon-pi --mount <parent> [<p>] root at a HOST parent folder instead of the projects root\n anon-pi init onboard: verify your proxy, capture your local model, pick an image\n anon-pi machine \u2026 manage machines (create / list / set-image / rm)\n anon-pi --delete-home [<m>] delete a machine's home (config + convos); keep its image pin + files\n anon-pi --delete-project <p> delete a project's files + its per-machine sessions; keep the homes\n\n <project> a folder under the projects root (mounted at /projects; pi's cwd). `.` means\n the root itself (a scratch pi at /projects, /work for --mount, or ~).\n\n [--rm] throwaway container this run (the DEFAULT; deleted on exit).\n [--keep] leave the container KEPT so its filesystem survives (apt install,\n quit, re-enter). anon-pi finds it by netcage's managed label and\n `netcage start`s it on re-entry.\n\nWHAT IT DOES\n Runs pi inside netcage with all web/DNS egress forced through the socks5h proxy\n (fail-closed) and ONE direct hole to your local model (ANON_PI_LLM). A MACHINE\n is an image + a persistent HOST home (bind-mounted at /root) holding your pi\n config, extensions, and conversations; the container is disposable, so `--rm`\n loses nothing. Files (projects) are global by default; conversations are\n per-machine. On a FRESH machine home the image's staged defaults + your\n models.json are seeded in once; after that pi owns the home. Requires `netcage`.\n\nENVIRONMENT\n ANON_PI_PROXY (required) socks5h URL of your proxy (Tor/wireproxy/ssh -D).\n No default: the proxy is what anonymizes, so it is never guessed.\n ANON_PI_LLM (required) RFC1918/link-local IP[:port] of the local model\n ANON_PI_IMAGE image with `pi` on PATH, used when a machine has no image set.\n No image yet? See the README (Providing a pi image).\n ANON_PI_HOME anon-pi workspace dir (default ~/.anon-pi; NOT under ~/.config)\n ANON_PI_PROJECTS projects root override (host dir mounted at /projects)\n\nPLATFORM\n Linux only (via netcage's netns/nft jail). On macOS/Windows it works only\n inside a Linux VM, where --allow-direct to a LAN model is VM-boundary-sensitive.\n";
|
|
202
1115
|
//# sourceMappingURL=anon-pi.d.ts.map
|