anon-pi 0.7.0 → 0.9.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/README.md +43 -9
- package/dist/anon-pi.d.ts +94 -6
- package/dist/anon-pi.d.ts.map +1 -1
- package/dist/anon-pi.js +243 -15
- package/dist/anon-pi.js.map +1 -1
- package/dist/cli.js +144 -34
- package/dist/cli.js.map +1 -1
- package/package.json +4 -3
- package/src/anon-pi.ts +290 -17
- package/src/cli.ts +165 -35
package/src/anon-pi.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// init's proxy detect/verify decisions). cli.ts owns only the impure edges (fs,
|
|
31
31
|
// the interactive TUI, the netcage query, the spawn).
|
|
32
32
|
|
|
33
|
-
import {existsSync} from 'node:fs';
|
|
33
|
+
import {existsSync, readFileSync} from 'node:fs';
|
|
34
34
|
import {homedir} from 'node:os';
|
|
35
35
|
import {dirname, join, resolve} from 'node:path';
|
|
36
36
|
import {fileURLToPath} from 'node:url';
|
|
@@ -433,7 +433,9 @@ export const ROOT_TOKEN = '.';
|
|
|
433
433
|
* reserved-name concept is explicit and extendable. `--mount`'s `/work` is a
|
|
434
434
|
* CONTAINER path, not a name in this namespace, so it needs no reservation.
|
|
435
435
|
*/
|
|
436
|
-
export const RESERVED_NAMES: readonly string[] = ['.', '..'];
|
|
436
|
+
export const RESERVED_NAMES: readonly string[] = ['.', '..', 'pi'];
|
|
437
|
+
// NOTE: `pi` is reserved so the `anon-pi pi <args…>` passthrough token
|
|
438
|
+
// (PI_PASSTHROUGH_TOKEN) can never be shadowed by a project named `pi`.
|
|
437
439
|
|
|
438
440
|
/** What a name names, for a clear validation error. */
|
|
439
441
|
export type NameKind = 'machine' | 'project';
|
|
@@ -523,6 +525,24 @@ export function resolveCwd(kind: RootKind, token: string): string {
|
|
|
523
525
|
return `${rootCwd(kind)}/${validateName(token, 'project')}`;
|
|
524
526
|
}
|
|
525
527
|
|
|
528
|
+
/**
|
|
529
|
+
* PURE: the launch cwd for a resolved (mode, rootKind, project). With a project
|
|
530
|
+
* token it resolves under the active root (resolveCwd). With NO project: a
|
|
531
|
+
* `shell` sits at the machine home (`/root`) — the "sit on the machine" mode —
|
|
532
|
+
* while `pi` (a `--session`/`--resume` launch that pi cwd-switches itself) starts
|
|
533
|
+
* at the projects root (`rootCwd`), a real pi launch position. `menu` never
|
|
534
|
+
* reaches here (it is argv-less). Shared by resolveRunPlan + keptContainerKey so
|
|
535
|
+
* the run cwd and the container-identity key always agree.
|
|
536
|
+
*/
|
|
537
|
+
export function launchCwd(
|
|
538
|
+
mode: LaunchMode,
|
|
539
|
+
kind: RootKind,
|
|
540
|
+
project: string | undefined,
|
|
541
|
+
): string {
|
|
542
|
+
if (project !== undefined) return resolveCwd(kind, project);
|
|
543
|
+
return mode === 'shell' ? CONTAINER_HOME_ROOT : rootCwd(kind);
|
|
544
|
+
}
|
|
545
|
+
|
|
526
546
|
/** Parsed shape of config.json. All fields optional (a hand-edited file may omit any). */
|
|
527
547
|
export interface AnonPiConfig {
|
|
528
548
|
/** socks5h proxy URL. */
|
|
@@ -586,14 +606,54 @@ function nonEmpty(v: string | undefined): string | undefined {
|
|
|
586
606
|
return v && v.trim() !== '' ? v.trim() : undefined;
|
|
587
607
|
}
|
|
588
608
|
|
|
609
|
+
/**
|
|
610
|
+
* PURE: expand a leading `~` / `~/` in a user-supplied HOST path to the given
|
|
611
|
+
* home (`node:path.resolve` does NOT do this — it would produce a literal `~`
|
|
612
|
+
* directory). Only a LEADING `~` (bare or before a separator) is expanded; a `~`
|
|
613
|
+
* elsewhere is left alone. Used everywhere anon-pi takes a host path from a
|
|
614
|
+
* human (the projects root, `--mount`), so `~/dev/x` means `$HOME/dev/x`.
|
|
615
|
+
*/
|
|
616
|
+
export function expandTilde(p: string, home: string): string {
|
|
617
|
+
if (p === '~') return home;
|
|
618
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) {
|
|
619
|
+
return join(home, p.slice(2));
|
|
620
|
+
}
|
|
621
|
+
return p;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* netcage's default podman graphroot (podman's global `--root`). Since netcage
|
|
626
|
+
* v0.7.0 (host-identity hardening, ADR-0013) EVERY netcage podman call runs
|
|
627
|
+
* against a private, username-free store at this path, NOT the operator's
|
|
628
|
+
* default rootless store. So an image anon-pi builds with a plain `podman build`
|
|
629
|
+
* lands in the WRONG store and `netcage run` cannot see it (it tries to pull the
|
|
630
|
+
* `localhost/…` ref and fails). anon-pi must place a built image into THIS store.
|
|
631
|
+
* Overridable via NETCAGE_GRAPHROOT (netcage's own test-seam env), so a caller
|
|
632
|
+
* that points netcage elsewhere stays in sync.
|
|
633
|
+
*/
|
|
634
|
+
export const NETCAGE_DEFAULT_GRAPHROOT = '/var/tmp/netcage-storage';
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* PURE: resolve netcage's podman graphroot the same way netcage does: the
|
|
638
|
+
* NETCAGE_GRAPHROOT env override when set, else the fixed default. anon-pi builds
|
|
639
|
+
* images into this store so `netcage run` finds them. This is a temporary
|
|
640
|
+
* coupling to a netcage-internal path; it goes away once netcage exposes a
|
|
641
|
+
* `build`/`load` verb (then anon-pi delegates to netcage instead).
|
|
642
|
+
*/
|
|
643
|
+
export function resolveNetcageGraphroot(
|
|
644
|
+
penv: Record<string, string | undefined>,
|
|
645
|
+
): string {
|
|
646
|
+
const p = penv.NETCAGE_GRAPHROOT;
|
|
647
|
+
return p && p.trim() !== '' ? p.trim() : NETCAGE_DEFAULT_GRAPHROOT;
|
|
648
|
+
}
|
|
649
|
+
|
|
589
650
|
/**
|
|
590
651
|
* PURE: resolve the projects root (the host dir mounted at /projects) with the
|
|
591
652
|
* decided precedence, highest first:
|
|
592
653
|
* --mount (CLI) > env ANON_PI_PROJECTS > machine.json.projects >
|
|
593
654
|
* config.json.projects > built-in <home>/projects
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* host parent). A relative override is resolved to an absolute path.
|
|
655
|
+
* A leading `~` in any override is expanded to $HOME; a relative override is
|
|
656
|
+
* resolved to an absolute path.
|
|
597
657
|
*/
|
|
598
658
|
export function resolveProjectsRoot(args: {
|
|
599
659
|
env: AnonPiEnv;
|
|
@@ -608,7 +668,7 @@ export function resolveProjectsRoot(args: {
|
|
|
608
668
|
nonEmpty(env.projects) ??
|
|
609
669
|
nonEmpty(machine?.projects) ??
|
|
610
670
|
nonEmpty(config?.projects);
|
|
611
|
-
if (pick !== undefined) return resolve(pick);
|
|
671
|
+
if (pick !== undefined) return resolve(expandTilde(pick, env.home));
|
|
612
672
|
return builtinProjectsRoot(env);
|
|
613
673
|
}
|
|
614
674
|
|
|
@@ -782,14 +842,105 @@ export interface ParsedLaunch {
|
|
|
782
842
|
piArgs?: string[];
|
|
783
843
|
}
|
|
784
844
|
|
|
845
|
+
/**
|
|
846
|
+
* pi flags that make sense with NO anon-pi project, so `anon-pi <flag> ...`
|
|
847
|
+
* launches pi (at the projects root) and forwards this flag + everything after
|
|
848
|
+
* it verbatim. Two families:
|
|
849
|
+
* - SESSION selection (`--session <id>` etc.): pi finds the session file (in the
|
|
850
|
+
* always-mounted machine home) and switches to its own project cwd, so no
|
|
851
|
+
* project is needed. Mirrors pi's own resume hint (`pi --session <id>`), so
|
|
852
|
+
* pasting `anon-pi --session <id>` just works.
|
|
853
|
+
* - QUERY (`--list-models`/`--models`): pi prints + exits, no project relevant.
|
|
854
|
+
* For arbitrary pi flags with no project (e.g. `--model x`), use the explicit
|
|
855
|
+
* `anon-pi pi <args…>` passthrough instead.
|
|
856
|
+
*/
|
|
857
|
+
const PI_NO_PROJECT_FLAGS: ReadonlySet<string> = new Set([
|
|
858
|
+
// session selection
|
|
859
|
+
'--session',
|
|
860
|
+
'--session-id',
|
|
861
|
+
'--resume',
|
|
862
|
+
'-r',
|
|
863
|
+
'--continue',
|
|
864
|
+
'-c',
|
|
865
|
+
'--fork',
|
|
866
|
+
// query-and-exit
|
|
867
|
+
'--list-models',
|
|
868
|
+
'--models',
|
|
869
|
+
]);
|
|
870
|
+
|
|
871
|
+
/** True iff `a` is a pi flag anon-pi accepts with no project (see PI_NO_PROJECT_FLAGS). */
|
|
872
|
+
function isPiNoProjectFlag(a: string): boolean {
|
|
873
|
+
return PI_NO_PROJECT_FLAGS.has(a);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* The explicit pi-passthrough token: `anon-pi pi <args…>` runs pi with the given
|
|
878
|
+
* args and NO project (the general escape hatch for any pi flag). It is a
|
|
879
|
+
* RESERVED project name (see RESERVED_NAMES) so a project can never shadow it.
|
|
880
|
+
*/
|
|
881
|
+
export const PI_PASSTHROUGH_TOKEN = 'pi';
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* PURE: whether forwarded pi args request pi's NON-INTERACTIVE (print) mode,
|
|
885
|
+
* i.e. contain `-p`/`--print`. This is the ONLY headless shape (it needs no
|
|
886
|
+
* TTY): other forwarded args (`--session <id>`, `--model x`, ...) are still
|
|
887
|
+
* INTERACTIVE and need a TTY + `-it`. Shared by the CLI's no-TTY discipline and
|
|
888
|
+
* the RunPlan's `-it` decision so they agree.
|
|
889
|
+
*/
|
|
890
|
+
export function isHeadlessPiArgs(
|
|
891
|
+
piArgs: readonly string[] | undefined,
|
|
892
|
+
): boolean {
|
|
893
|
+
return !!piArgs && piArgs.some((a) => a === '-p' || a === '--print');
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Finish parsing a NO-PROJECT pi launch (`anon-pi --session <id> ...`,
|
|
898
|
+
* `anon-pi --list-models`, or the explicit `anon-pi pi <args…>`): pi mode, NO
|
|
899
|
+
* project (pi picks its own cwd / prints + exits), the flag(s) + rest forwarded.
|
|
900
|
+
* `--shell` is incompatible (a shell forwards no pi args).
|
|
901
|
+
*/
|
|
902
|
+
function finishPiNoProjectLaunch(args: {
|
|
903
|
+
machine: string;
|
|
904
|
+
machineExplicit: boolean;
|
|
905
|
+
mountParent?: string;
|
|
906
|
+
keep: boolean;
|
|
907
|
+
rm: boolean;
|
|
908
|
+
shell: boolean;
|
|
909
|
+
piArgs: string[];
|
|
910
|
+
fail: (msg: string) => never;
|
|
911
|
+
}): ParsedLaunch {
|
|
912
|
+
if (args.keep && args.rm) {
|
|
913
|
+
args.fail(
|
|
914
|
+
'--keep and --rm are contradictory (pick one; --rm is the default)',
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
if (args.shell) {
|
|
918
|
+
args.fail(
|
|
919
|
+
'--shell forwards no pi args (a shell has no session/query). Drop --shell.',
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
mode: 'pi',
|
|
924
|
+
machine: args.machine,
|
|
925
|
+
machineExplicit: args.machineExplicit,
|
|
926
|
+
project: undefined,
|
|
927
|
+
mountParent: args.mountParent,
|
|
928
|
+
keep: args.keep,
|
|
929
|
+
piArgs: args.piArgs,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
785
933
|
/**
|
|
786
934
|
* PURE: parse grammar A into a ParsedLaunch. Consumes the anon-pi flags
|
|
787
935
|
* (`-m <machine>`, `--shell`, `--mount <parent>`, `--keep`/`--rm`) LEFT of the
|
|
788
936
|
* project positional; the FIRST bare positional is the project (`.` allowed as
|
|
789
937
|
* the root token). In pi mode every token AFTER the project is forwarded to pi
|
|
790
938
|
* verbatim (so `anon-pi recon -p '...'` works) — anon-pi flags must come before
|
|
791
|
-
* the project.
|
|
792
|
-
*
|
|
939
|
+
* the project. A pi session-resume flag (`--session <id>`, `--continue`,
|
|
940
|
+
* `--resume`, `--fork <id>`) in the project position starts a NO-project pi
|
|
941
|
+
* launch that forwards to pi (pi resolves the session + cwd itself). In
|
|
942
|
+
* shell/menu mode a stray extra positional is an error (bash has no
|
|
943
|
+
* forwarded-args grammar; the menu takes no project).
|
|
793
944
|
*
|
|
794
945
|
* Validates the project name and the `-m` machine name via validateName (the
|
|
795
946
|
* reserved-name guard); `--mount <parent>` is a HOST path in its own namespace,
|
|
@@ -845,6 +996,44 @@ export function parseLaunchArgs(args: readonly string[]): ParsedLaunch {
|
|
|
845
996
|
i++;
|
|
846
997
|
break;
|
|
847
998
|
}
|
|
999
|
+
if (a === PI_PASSTHROUGH_TOKEN) {
|
|
1000
|
+
// `anon-pi pi <args…>`: the explicit passthrough. Run pi with the
|
|
1001
|
+
// following args and NO project (pi picks its own cwd, or prints + exits
|
|
1002
|
+
// for a query). The general escape hatch for ANY pi flag with no project
|
|
1003
|
+
// (`anon-pi pi --model x`, `anon-pi pi --export out.html --session <id>`).
|
|
1004
|
+
return finishPiNoProjectLaunch({
|
|
1005
|
+
machine,
|
|
1006
|
+
machineExplicit: machineSet,
|
|
1007
|
+
mountParent,
|
|
1008
|
+
keep: keepSeen,
|
|
1009
|
+
rm: rmSeen,
|
|
1010
|
+
shell,
|
|
1011
|
+
piArgs: args.slice(i + 1),
|
|
1012
|
+
fail,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
if (isPiNoProjectFlag(a)) {
|
|
1016
|
+
// A pi flag that needs NO anon-pi project (`--session <id>`/`--continue`/
|
|
1017
|
+
// `--fork` resume by id; `--list-models`/`--models` print + exit). pi
|
|
1018
|
+
// resolves its own cwd (or just prints), so anon-pi launches pi at the
|
|
1019
|
+
// projects root and forwards this flag + everything after it verbatim.
|
|
1020
|
+
// This makes pi's own "To resume: pi --session <id>" hint usable as
|
|
1021
|
+
// `anon-pi --session <id>`. (For ARBITRARY pi flags with no project, use
|
|
1022
|
+
// the explicit `anon-pi pi <args…>` passthrough.)
|
|
1023
|
+
piArgs = args.slice(i);
|
|
1024
|
+
project = undefined;
|
|
1025
|
+
i = args.length;
|
|
1026
|
+
return finishPiNoProjectLaunch({
|
|
1027
|
+
machine,
|
|
1028
|
+
machineExplicit: machineSet,
|
|
1029
|
+
mountParent,
|
|
1030
|
+
keep: keepSeen,
|
|
1031
|
+
rm: rmSeen,
|
|
1032
|
+
shell,
|
|
1033
|
+
piArgs,
|
|
1034
|
+
fail,
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
848
1037
|
if (a.startsWith('-')) {
|
|
849
1038
|
fail(`unknown option: ${a}`);
|
|
850
1039
|
}
|
|
@@ -959,10 +1148,7 @@ export function resolveRunPlan(
|
|
|
959
1148
|
// Which root the cwd resolves under: /work when --mount, else /projects.
|
|
960
1149
|
const rootKind: RootKind = mounted ? 'mount' : 'projects';
|
|
961
1150
|
|
|
962
|
-
|
|
963
|
-
// project token (a name or `.`) resolves under the active root uniformly.
|
|
964
|
-
const cwd =
|
|
965
|
-
project === undefined ? CONTAINER_HOME_ROOT : resolveCwd(rootKind, project);
|
|
1151
|
+
const cwd = launchCwd(mode, rootKind, project);
|
|
966
1152
|
|
|
967
1153
|
const fresh = homeFresh(machine.home);
|
|
968
1154
|
const seedVersion = intent.seedVersion ?? SEED_VERSION;
|
|
@@ -974,7 +1160,7 @@ export function resolveRunPlan(
|
|
|
974
1160
|
// (podman fails to allocate a TTY on a non-tty stdin). The CLI's broader
|
|
975
1161
|
// no-TTY discipline (erroring when an interactive mode has no TTY) is a later
|
|
976
1162
|
// task; here the argv simply omits -it for the one headless shape.
|
|
977
|
-
const headless = mode === 'pi' &&
|
|
1163
|
+
const headless = mode === 'pi' && isHeadlessPiArgs(intent.piArgs);
|
|
978
1164
|
|
|
979
1165
|
const netcageArgs: string[] = ['run'];
|
|
980
1166
|
// --rm by DEFAULT (throwaway); --keep leaves the container kept.
|
|
@@ -1134,13 +1320,12 @@ export type RunVsStart = {action: 'run'} | {action: 'start'; ref: string};
|
|
|
1134
1320
|
* its internal shape is not a contract (compare only keys this function makes).
|
|
1135
1321
|
*/
|
|
1136
1322
|
export function keptContainerKey(intent: LaunchIntent): string {
|
|
1137
|
-
const {machine, projectsRoot, project, mountParent} = intent;
|
|
1323
|
+
const {machine, mode, projectsRoot, project, mountParent} = intent;
|
|
1138
1324
|
const mounted = nonEmpty(mountParent) !== undefined;
|
|
1139
1325
|
const rootKind: RootKind = mounted ? 'mount' : 'projects';
|
|
1140
1326
|
// The same cwd resolution resolveRunPlan uses, so the key names the exact
|
|
1141
1327
|
// container a matching launch would run in (its conversation key).
|
|
1142
|
-
const cwd =
|
|
1143
|
-
project === undefined ? CONTAINER_HOME_ROOT : resolveCwd(rootKind, project);
|
|
1328
|
+
const cwd = launchCwd(mode, rootKind, project);
|
|
1144
1329
|
return [
|
|
1145
1330
|
`machine=${machine.name}`,
|
|
1146
1331
|
`projectsRoot=${projectsRoot}`,
|
|
@@ -1889,6 +2074,73 @@ export interface ProxyFinding {
|
|
|
1889
2074
|
processHint?: string;
|
|
1890
2075
|
}
|
|
1891
2076
|
|
|
2077
|
+
/** What `netcage detect-proxy --json` reports (the fields anon-pi consumes). */
|
|
2078
|
+
export interface NetcageDetectProxy {
|
|
2079
|
+
schemaVersion?: number;
|
|
2080
|
+
candidates?: Array<{
|
|
2081
|
+
port?: number;
|
|
2082
|
+
open?: boolean;
|
|
2083
|
+
socks5?: boolean;
|
|
2084
|
+
processHint?: string;
|
|
2085
|
+
}>;
|
|
2086
|
+
exitIP?: string;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
/**
|
|
2090
|
+
* PURE: map a parsed `netcage detect-proxy --json` result into anon-pi's
|
|
2091
|
+
* ProxyFinding[] (so init can REUSE netcage's SOCKS scanner instead of its own
|
|
2092
|
+
* probe, and both render through the same formatProxyFindings). The host is
|
|
2093
|
+
* 127.0.0.1 (detect-proxy probes loopback); the socks5 boolean becomes a
|
|
2094
|
+
* SocksHandshake verdict; the structural port hint is attached from
|
|
2095
|
+
* DEFAULT_SOCKS_PROBE_PORTS by port. Tolerates missing/garbage (returns []).
|
|
2096
|
+
* The per-candidate processHint is NOT copied onto each finding (it is host-wide;
|
|
2097
|
+
* the CLI passes it once as formatProxyFindings' note).
|
|
2098
|
+
*/
|
|
2099
|
+
export function findingsFromNetcageDetect(
|
|
2100
|
+
raw: NetcageDetectProxy | undefined,
|
|
2101
|
+
): ProxyFinding[] {
|
|
2102
|
+
const rows = raw?.candidates;
|
|
2103
|
+
if (!Array.isArray(rows)) return [];
|
|
2104
|
+
const hintByPort = new Map(
|
|
2105
|
+
DEFAULT_SOCKS_PROBE_PORTS.map((p) => [p.port, p.hint]),
|
|
2106
|
+
);
|
|
2107
|
+
const out: ProxyFinding[] = [];
|
|
2108
|
+
for (const c of rows) {
|
|
2109
|
+
if (!c || typeof c.port !== 'number') continue;
|
|
2110
|
+
const open = c.open === true;
|
|
2111
|
+
const handshake: SocksHandshake | undefined = !open
|
|
2112
|
+
? undefined
|
|
2113
|
+
: c.socks5 === true
|
|
2114
|
+
? {socks5: true, method: 0}
|
|
2115
|
+
: {socks5: false, reason: 'not SOCKS5'};
|
|
2116
|
+
out.push({
|
|
2117
|
+
host: '127.0.0.1',
|
|
2118
|
+
port: c.port,
|
|
2119
|
+
open,
|
|
2120
|
+
handshake,
|
|
2121
|
+
portHint: hintByPort.get(c.port),
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
return out;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
/**
|
|
2128
|
+
* PURE: the host-wide process note from a `netcage detect-proxy --json` result:
|
|
2129
|
+
* the FIRST candidate that carries a `processHint` (they are all the same
|
|
2130
|
+
* host-wide hint). Returns undefined when none. Rendered ONCE by the CLI (not
|
|
2131
|
+
* per port), same as the local-probe path.
|
|
2132
|
+
*/
|
|
2133
|
+
export function processNoteFromNetcageDetect(
|
|
2134
|
+
raw: NetcageDetectProxy | undefined,
|
|
2135
|
+
): string | undefined {
|
|
2136
|
+
for (const c of raw?.candidates ?? []) {
|
|
2137
|
+
if (c && typeof c.processHint === 'string' && c.processHint.trim() !== '') {
|
|
2138
|
+
return c.processHint.trim();
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return undefined;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
1892
2144
|
/**
|
|
1893
2145
|
* The set of substrings a findings line must NEVER contain: known exit-provider
|
|
1894
2146
|
* / VPN brand names. This is the machine-checkable half of the never-label rule
|
|
@@ -2096,6 +2348,22 @@ function shippedFile(rel: string): string | undefined {
|
|
|
2096
2348
|
return undefined;
|
|
2097
2349
|
}
|
|
2098
2350
|
|
|
2351
|
+
/**
|
|
2352
|
+
* anon-pi's own version, read from the package.json shipped in the package root
|
|
2353
|
+
* (resolved via shippedFile). Returns undefined if it cannot be found/parsed, so
|
|
2354
|
+
* `--version` can fall back to a placeholder. Read-only.
|
|
2355
|
+
*/
|
|
2356
|
+
export function anonPiVersion(): string | undefined {
|
|
2357
|
+
const pkg = shippedFile('package.json');
|
|
2358
|
+
if (!pkg) return undefined;
|
|
2359
|
+
try {
|
|
2360
|
+
const parsed = JSON.parse(readFileSync(pkg, 'utf8')) as {version?: unknown};
|
|
2361
|
+
return typeof parsed.version === 'string' ? parsed.version : undefined;
|
|
2362
|
+
} catch {
|
|
2363
|
+
return undefined;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2099
2367
|
// --- The `machine {create,list,set-image,rm}` verbs (pure parts) -------------
|
|
2100
2368
|
//
|
|
2101
2369
|
// Machines are first-class: an image + a persistent host home
|
|
@@ -2281,7 +2549,12 @@ export const HELP = `anon-pi - run pi on anonymized, jailed machines (netcage: f
|
|
|
2281
2549
|
USAGE
|
|
2282
2550
|
anon-pi MENU: pick a project (pi), a shell, or a new project
|
|
2283
2551
|
anon-pi <project> pi in the project (${CONTAINER_PROJECTS_ROOT}/<project>); exit pi -> host
|
|
2284
|
-
anon-pi <project> <pi-args…> forward args to pi (headless
|
|
2552
|
+
anon-pi <project> <pi-args…> forward args to pi (e.g. -p for a headless one-shot)
|
|
2553
|
+
anon-pi --session <id> resume a pi session by id (forwarded to pi; no project needed)
|
|
2554
|
+
anon-pi --continue continue your most recent pi session (also -r/--resume, --fork)
|
|
2555
|
+
anon-pi --list-models list the models pi sees (also --models; no project needed)
|
|
2556
|
+
anon-pi pi <pi-args…> run pi with ANY args and no project (the passthrough)
|
|
2557
|
+
anon-pi --version print anon-pi's version (also -V)
|
|
2285
2558
|
anon-pi --shell [<project>] a jailed bash (at ~, or cd'd into <project>) - the project-hopper
|
|
2286
2559
|
anon-pi -m <machine> [<p>] the same, on <machine> (its own image + home + conversations)
|
|
2287
2560
|
anon-pi --mount <parent> [<p>] root at a HOST parent folder instead of the projects root
|