anon-pi 0.1.1 → 0.3.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/dist/cli.js CHANGED
@@ -1,22 +1,35 @@
1
1
  #!/usr/bin/env node
2
- // anon-pi CLI: resolve the run plan (pure), do the one filesystem side-effect
3
- // (seed the session config if absent), then exec `netcage run ...` with inherited
4
- // stdio so the interactive pi session (-it) passes through the terminal cleanly.
5
- import { cpSync, existsSync, mkdirSync } from 'node:fs';
2
+ // anon-pi CLI. Two commands:
3
+ // anon-pi [WORKDIR] resolve the run plan (pure) and exec `netcage run ...`
4
+ // with inherited stdio (so -it is a real interactive TTY).
5
+ // The seed models.json is mounted read-only and copied
6
+ // into the container's ~/.pi/agent by the run command, so
7
+ // it layers onto the image's config (extensions survive).
8
+ // anon-pi import generate the seed models.json from the host models.json,
9
+ // carrying only the provider that serves ANON_PI_LLM.
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
6
11
  import { spawnSync } from 'node:child_process';
7
- import { dirname } from 'node:path';
8
- import { AnonPiError, buildRunPlan, envFromProcess, HELP } from './anon-pi.js';
12
+ import { join } from 'node:path';
13
+ import { AnonPiError, buildRunPlan, envFromProcess, HELP, MODELS_FILE, pickProviderForLlm, resolveConfigSeed, resolveSourceModelsPath, } from './anon-pi.js';
9
14
  function main(argv) {
10
15
  const args = argv.slice(2);
11
16
  if (args.includes('--help') || args.includes('-h')) {
12
17
  process.stdout.write(HELP);
13
18
  return 0;
14
19
  }
15
- // The only positional is the optional workdir. Reject stray flags so a typo
16
- // (e.g. --allow-direct) is not silently swallowed: anon-pi owns the netcage
17
- // argv, extra flags are not passed through.
20
+ // Subcommand dispatch: the first bare token may be `import`.
21
+ if (args[0] === 'import') {
22
+ return runImport(args.slice(1));
23
+ }
24
+ return runLaunch(args);
25
+ }
26
+ // --- anon-pi [WORKDIR] : launch pi jailed -----------------------------------
27
+ function runLaunch(args) {
28
+ // One optional positional (the workdir) + the --ephemeral flag. Reject other
29
+ // flags so a typo is not silently swallowed: anon-pi owns the netcage argv.
30
+ const ephemeralFlag = args.includes('--ephemeral') || args.includes('--eph');
18
31
  const positionals = args.filter((a) => !a.startsWith('-'));
19
- const flags = args.filter((a) => a.startsWith('-'));
32
+ const flags = args.filter((a) => a.startsWith('-') && a !== '--ephemeral' && a !== '--eph');
20
33
  if (flags.length > 0) {
21
34
  process.stderr.write(`anon-pi: unknown option(s): ${flags.join(' ')}\nRun \`anon-pi --help\`.\n`);
22
35
  return 2;
@@ -26,6 +39,8 @@ function main(argv) {
26
39
  return 2;
27
40
  }
28
41
  const env = envFromProcess(process.env);
42
+ if (ephemeralFlag)
43
+ env.ephemeral = true;
29
44
  let plan;
30
45
  try {
31
46
  plan = buildRunPlan(env, positionals[0], existsSync, existsSync);
@@ -43,15 +58,19 @@ function main(argv) {
43
58
  '(https://github.com/wighawag/netcage). Linux only.\n');
44
59
  return 1;
45
60
  }
46
- // The one side-effect: seed the per-session config from the canonical seed the
47
- // FIRST time this workdir is used. Reuse-if-present, seed-if-absent.
48
- if (plan.needsSeed) {
49
- mkdirSync(dirname(plan.sessionAgentDir), { recursive: true });
50
- cpSync(plan.configSeed, plan.sessionAgentDir, { recursive: true });
51
- process.stderr.write(`anon-pi: seeded session config -> ${plan.sessionAgentDir}\n`);
52
- }
53
- // Ensure the workdir exists (a fresh named folder is fine).
54
61
  mkdirSync(plan.workdir, { recursive: true });
62
+ if (env.ephemeral) {
63
+ // No host state dir: pi writes to the container's own --rm layer, so the
64
+ // session leaves NO trace on the host and there is nothing to clean up.
65
+ process.stderr.write('anon-pi: ephemeral session (nothing persisted; no host state)\n');
66
+ }
67
+ else {
68
+ // Persistent mode: create the per-workdir state home to mount.
69
+ mkdirSync(plan.stateDir, { recursive: true });
70
+ if (plan.fresh) {
71
+ process.stderr.write(`anon-pi: new session home ${plan.stateDir} (seeding on first launch)\n`);
72
+ }
73
+ }
55
74
  // Hand off to netcage with inherited stdio so -it is a real interactive TTY.
56
75
  const res = spawnSync('netcage', plan.netcageArgs, { stdio: 'inherit' });
57
76
  if (res.error) {
@@ -61,6 +80,61 @@ function main(argv) {
61
80
  // Propagate netcage's exit code (which itself propagates the tool's).
62
81
  return res.status ?? 1;
63
82
  }
83
+ // --- anon-pi import : write the seed models.json ----------------------------
84
+ function runImport(args) {
85
+ const force = args.includes('--force') || args.includes('-f');
86
+ const stray = args.filter((a) => a.startsWith('-') && a !== '--force' && a !== '-f');
87
+ if (stray.length > 0) {
88
+ process.stderr.write(`anon-pi import: unknown option(s): ${stray.join(' ')}\nRun \`anon-pi --help\`.\n`);
89
+ return 2;
90
+ }
91
+ const env = envFromProcess(process.env);
92
+ if (!env.llmDirect || env.llmDirect.trim() === '') {
93
+ process.stderr.write('anon-pi import: set ANON_PI_LLM to the RFC1918/link-local IP[:port] of the local\n' +
94
+ 'model whose provider should be imported (e.g. ANON_PI_LLM=192.168.1.150:8080).\n');
95
+ return 1;
96
+ }
97
+ const source = resolveSourceModelsPath(env);
98
+ if (!existsSync(source)) {
99
+ process.stderr.write(`anon-pi import: host models.json not found at ${source}.\n` +
100
+ 'Set ANON_PI_SOURCE_MODELS to your pi models.json, or run pi once to create it.\n');
101
+ return 1;
102
+ }
103
+ let hostModels;
104
+ try {
105
+ hostModels = JSON.parse(readFileSync(source, 'utf8'));
106
+ }
107
+ catch (e) {
108
+ process.stderr.write(`anon-pi import: could not parse ${source}: ${e.message}\n`);
109
+ return 1;
110
+ }
111
+ let result;
112
+ try {
113
+ result = pickProviderForLlm(hostModels, env.llmDirect);
114
+ }
115
+ catch (e) {
116
+ if (e instanceof AnonPiError) {
117
+ process.stderr.write(e.message + '\n');
118
+ return 1;
119
+ }
120
+ throw e;
121
+ }
122
+ const seedDir = resolveConfigSeed(env);
123
+ const dest = join(seedDir, MODELS_FILE);
124
+ if (existsSync(dest) && !force) {
125
+ process.stderr.write(`anon-pi import: ${dest} already exists. Re-run with --force to overwrite.\n`);
126
+ return 1;
127
+ }
128
+ if (result.apiKeyLooksReal) {
129
+ process.stderr.write(`anon-pi import: WARNING: provider "${result.name}" carries a real-looking apiKey; it\n` +
130
+ 'will be written into the seed. For a local model this is usually fine, but review\n' +
131
+ `${dest} if that key identifies you.\n`);
132
+ }
133
+ mkdirSync(seedDir, { recursive: true });
134
+ writeFileSync(dest, JSON.stringify(result.models, null, 2) + '\n');
135
+ process.stderr.write(`anon-pi import: wrote ${dest} (provider "${result.name}"). Run \`anon-pi\` to launch.\n`);
136
+ return 0;
137
+ }
64
138
  function hasNetcage() {
65
139
  const which = spawnSync(process.platform === 'win32' ? 'where' : 'command', ['-v', 'netcage'], {
66
140
  stdio: 'ignore',
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,8EAA8E;AAC9E,kFAAkF;AAClF,iFAAiF;AAEjF,OAAO,EAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AACtD,OAAO,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAClC,OAAO,EAAC,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAC,MAAM,cAAc,CAAC;AAE7E,SAAS,IAAI,CAAC,IAAc;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE3B,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC;IACV,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,4CAA4C;IAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,+BAA+B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAC3E,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,sFAAsF,CACtF,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IAED,MAAM,GAAG,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAExC,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACJ,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;YACvC,OAAO,CAAC,CAAC;QACV,CAAC;QACD,MAAM,CAAC,CAAC;IACT,CAAC;IAED,oEAAoE;IACpE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,6FAA6F;YAC5F,sDAAsD,CACvD,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IAED,+EAA+E;IAC/E,qEAAqE;IACrE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACpB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,qCAAqC,IAAI,CAAC,eAAe,IAAI,CAC7D,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;IAE3C,6EAA6E;IAC7E,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,EAAE,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;IACvE,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,mCAAmC,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,CACxD,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IACD,sEAAsE;IACtE,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,UAAU;IAClB,MAAM,KAAK,GAAG,SAAS,CACtB,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAClD,CAAC,IAAI,EAAE,SAAS,CAAC,EACjB;QACC,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;KACnC,CACD,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,uCAAuC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAC,KAAK,EAAE,QAAQ,EAAC,CAAC,CAAC;IAClE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;AACrB,CAAC;AAED,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ # A fuller ANON_PI_IMAGE example: pi + the pi-webveil extension + a local SearXNG
2
+ # (over a Unix socket) for anonymized web search, baked into ONE image (the
3
+ # capability layer). Modelled on a real working host setup (Unix socket,
4
+ # http-socket, json+limiter:false, unix: baseUrl, egress: direct).
5
+ #
6
+ # STATUS: a documented REFERENCE, not a CI-built image. The topology matches a
7
+ # working host, but pin versions and test the build for your platform before
8
+ # relying on it (SearXNG's pip install + engine set drift over time).
9
+ #
10
+ # WHY THE USUAL SearXNG ANONYMITY CAVEAT DOES NOT APPLY HERE:
11
+ # webveil warns that a LOCAL SearXNG crawls the web from its OWN process egress
12
+ # (direct), so a socks5 backend hop to 127.0.0.1 is FALSE confidence. Under
13
+ # anon-pi that risk is gone: netcage forces ALL TCP+DNS egress from EVERY
14
+ # process in the jail through the socks5h proxy, fail-closed. SearXNG's crawl is
15
+ # anonymized by the network layer regardless. So we use webveil's plain
16
+ # `egress: direct` and let netcage do the anonymizing.
17
+ #
18
+ # WHY A UNIX SOCKET (matches the host setup, avoids the loopback-TCP guard):
19
+ # webveil's `unix:` baseUrl speaks HTTP over a Unix socket, so uWSGI must use
20
+ # `http-socket = <path>` (NOT `socket = <path>`, which is the native uwsgi
21
+ # protocol webveil cannot speak). SearXNG also needs `json` in search.formats
22
+ # and `server.limiter: false` for a local instance.
23
+ #
24
+ # Build:
25
+ # podman build -t localhost/anon-pi-webveil:latest -f Dockerfile.pi-webveil .
26
+ # export ANON_PI_IMAGE=localhost/anon-pi-webveil:latest
27
+ # Then: `anon-pi import` (your local model), then `anon-pi <workdir>`.
28
+ FROM node:24-bookworm-slim
29
+
30
+ # --- system deps: pi's tools + SearXNG's Python/uwsgi runtime ----------------
31
+ RUN apt-get update \
32
+ && apt-get install -y --no-install-recommends \
33
+ bash ca-certificates git ripgrep \
34
+ python3 python3-venv python3-pip python3-dev \
35
+ build-essential libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev \
36
+ uwsgi uwsgi-plugin-python3 \
37
+ && rm -rf /var/lib/apt/lists/*
38
+
39
+ # --- pi + the pi-webveil extension --------------------------------------------
40
+ # Install pi globally, then install the extension into the STAGING dir
41
+ # (PI_CODING_AGENT_DIR), NOT ~/.pi/agent: anon-pi mounts a persistent home over
42
+ # ~/.pi/agent and promotes the staging dir into it on first launch. Installing
43
+ # straight into ~/.pi/agent would be shadowed by that mount.
44
+ RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
45
+ ENV ANON_PI_STAGE=/opt/anon-pi-seed/agent
46
+ RUN mkdir -p "$ANON_PI_STAGE" \
47
+ && PI_CODING_AGENT_DIR="$ANON_PI_STAGE" pi install npm:pi-webveil
48
+
49
+ # --- SearXNG in a venv -------------------------------------------------------
50
+ ENV SEARXNG_HOME=/opt/searxng
51
+ RUN python3 -m venv "$SEARXNG_HOME/venv" \
52
+ && "$SEARXNG_HOME/venv/bin/pip" install --no-cache-dir -U pip setuptools wheel \
53
+ && "$SEARXNG_HOME/venv/bin/pip" install --no-cache-dir searxng
54
+
55
+ # settings.yml: JSON API on + limiter off (both required for a local instance).
56
+ RUN mkdir -p /etc/searxng \
57
+ && printf '%s\n' \
58
+ 'use_default_settings: true' \
59
+ 'server:' \
60
+ ' secret_key: "change-me-anon-pi"' \
61
+ ' limiter: false' \
62
+ ' public_instance: false' \
63
+ 'search:' \
64
+ ' formats: [html, json]' \
65
+ > /etc/searxng/settings.yml
66
+ ENV SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
67
+
68
+ # webveil.json + trust.json go in the STAGING dir (promoted into the persistent
69
+ # home on first launch). webveil points at the Unix socket, backend hop DIRECT
70
+ # (netcage anonymizes it): no webveil-side proxy config is needed in-jail because
71
+ # netcage forces every process's egress through the proxy.
72
+ RUN printf '%s\n' \
73
+ '{' \
74
+ ' "backend": "searxng",' \
75
+ ' "baseUrl": "unix:/run/searxng/socket",' \
76
+ ' "egress": { "mode": "direct" }' \
77
+ '}' \
78
+ > "$ANON_PI_STAGE/webveil.json" \
79
+ && printf '{"/work": true}\n' > "$ANON_PI_STAGE/trust.json"
80
+
81
+ # --- entrypoint: start SearXNG (HTTP over the Unix socket), then exec CMD ------
82
+ # anon-pi passes `sh -c 'cp ... && exec pi'` as the CMD; a Dockerfile ENTRYPOINT
83
+ # is PREPENDED to it (OCI), so this wrapper starts SearXNG then execs anon-pi's
84
+ # command. `http-socket` (not `socket`) makes uWSGI speak HTTP over the socket,
85
+ # which is what webveil's `unix:` baseUrl requires.
86
+ RUN mkdir -p /run/searxng && printf '%s\n' \
87
+ '#!/bin/sh' \
88
+ 'set -e' \
89
+ 'uwsgi --master --plugin python3,http \' \
90
+ ' --http-socket /run/searxng/socket --chmod-socket=666 \' \
91
+ ' --venv "$SEARXNG_HOME/venv" --module searx.webapp \' \
92
+ ' --daemonize /tmp/searxng.log' \
93
+ '# Wait for the socket to appear (bounded).' \
94
+ 'i=0; while [ ! -S /run/searxng/socket ] && [ $i -lt 30 ]; do i=$((i+1)); sleep 0.5; done' \
95
+ 'exec "$@"' \
96
+ > /usr/local/bin/anon-pi-entrypoint \
97
+ && chmod +x /usr/local/bin/anon-pi-entrypoint
98
+ ENTRYPOINT ["/usr/local/bin/anon-pi-entrypoint"]
99
+
100
+ WORKDIR /work
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anon-pi",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Launch pi inside a netcage: anonymized web egress through a socks5h proxy, one direct hole for a local model, seeded pi config on the host.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "keywords": [
@@ -40,7 +40,8 @@
40
40
  "dist",
41
41
  "src",
42
42
  "LICENSE",
43
- "Dockerfile.pi"
43
+ "Dockerfile.pi",
44
+ "examples"
44
45
  ],
45
46
  "engines": {
46
47
  "node": ">=20"