anon-pi 0.9.0 → 0.10.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 +18 -7
- package/dist/anon-pi.d.ts +44 -1
- package/dist/anon-pi.d.ts.map +1 -1
- package/dist/anon-pi.js +149 -17
- package/dist/anon-pi.js.map +1 -1
- package/dist/cli.js +72 -7
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/anon-pi.ts +165 -17
- package/src/cli.ts +74 -6
package/src/cli.ts
CHANGED
|
@@ -56,6 +56,8 @@ import {
|
|
|
56
56
|
parseConfigJson,
|
|
57
57
|
parseLaunchArgs,
|
|
58
58
|
isHeadlessPiArgs,
|
|
59
|
+
resumeSessionId,
|
|
60
|
+
sessionHeaderCwd,
|
|
59
61
|
anonPiVersion,
|
|
60
62
|
parseMachineArgs,
|
|
61
63
|
parseMachineJson,
|
|
@@ -294,6 +296,15 @@ function runLaunch(parsed: ParsedLaunch): number {
|
|
|
294
296
|
modelsSeed,
|
|
295
297
|
settingsSeed,
|
|
296
298
|
};
|
|
299
|
+
|
|
300
|
+
// RESUME family with NO project: resolve the session's recorded cwd from the
|
|
301
|
+
// host store and cd there, so pi resumes in place (no fork prompt). A given
|
|
302
|
+
// project is trusted verbatim (pi guards a mismatch); an unresolvable id
|
|
303
|
+
// leaves the cwd at the projects root (pi decides), so this is pure upside.
|
|
304
|
+
if (parsed.mode === 'pi' && parsed.project === undefined) {
|
|
305
|
+
const sessionCwd = resolveSessionCwd(env, machineName, parsed.piArgs);
|
|
306
|
+
if (sessionCwd !== undefined) intent.sessionCwd = sessionCwd;
|
|
307
|
+
}
|
|
297
308
|
} catch (e) {
|
|
298
309
|
return reportAnonPiError(e);
|
|
299
310
|
}
|
|
@@ -1848,11 +1859,11 @@ function detectProxyViaNetcage(): NetcageDetectProxy | undefined {
|
|
|
1848
1859
|
* (`--root <graphroot>`), NOT the operator's default rootless store, so a plain
|
|
1849
1860
|
* `podman build` would put the image where `netcage run` cannot see it (it would
|
|
1850
1861
|
* try to pull the `localhost/…` ref and fail). We therefore:
|
|
1851
|
-
* 1. PREFER `netcage build` when netcage exposes it (
|
|
1852
|
-
* its store + graphroot, no path hardcoded here); else
|
|
1862
|
+
* 1. PREFER `netcage build` when netcage exposes it (netcage >= 0.7.1; netcage
|
|
1863
|
+
* owns its store + graphroot, no path hardcoded here); else
|
|
1853
1864
|
* 2. `podman build` into the default store, then `podman save | podman --root
|
|
1854
|
-
* <graphroot> load` to copy it into netcage's store (the
|
|
1855
|
-
*
|
|
1865
|
+
* <graphroot> load` to copy it into netcage's store (the fallback for an
|
|
1866
|
+
* older netcage without the build/load verbs).
|
|
1856
1867
|
* Streams output (inherited stdio) so the user sees the build. Returns true on
|
|
1857
1868
|
* success. The build CONTEXT is the Dockerfile's own directory.
|
|
1858
1869
|
*/
|
|
@@ -1901,8 +1912,8 @@ function buildImage(dockerfile: string, tag: string): boolean {
|
|
|
1901
1912
|
* Copy a locally-built image (in the default podman store) INTO netcage's
|
|
1902
1913
|
* private graphroot store, so `netcage run <tag>` finds it without a pull. Uses
|
|
1903
1914
|
* `podman save <tag> | podman --root <graphroot> load`. Best-effort: on failure
|
|
1904
|
-
* it warns (the image still exists in the default store;
|
|
1905
|
-
* build`/`load`
|
|
1915
|
+
* it warns (the image still exists in the default store; on netcage >= 0.7.1 the
|
|
1916
|
+
* `netcage build`/`load` verbs remove this dance). Returns true on success.
|
|
1906
1917
|
*/
|
|
1907
1918
|
function loadImageIntoNetcageStore(tag: string): boolean {
|
|
1908
1919
|
const graphroot = resolveNetcageGraphroot(process.env);
|
|
@@ -2093,8 +2104,65 @@ function withKeyLabel(netcageArgs: string[], key: string): string[] {
|
|
|
2093
2104
|
return out;
|
|
2094
2105
|
}
|
|
2095
2106
|
|
|
2107
|
+
/**
|
|
2108
|
+
* Best-effort: resolve a RESUME-family launch's session cwd from the host
|
|
2109
|
+
* session store, so the CLI can cd there (intent.sessionCwd) and pi resumes in
|
|
2110
|
+
* place instead of prompting to fork. Globs the machine's sessions dir for a
|
|
2111
|
+
* file whose name carries `<id>` (pi names them `<ts>_<id>.jsonl`), reads the
|
|
2112
|
+
* HEADER line, and returns its recorded cwd (pure sessionHeaderCwd). Returns
|
|
2113
|
+
* undefined on any miss (no id, id not found, unreadable, no cwd): the caller
|
|
2114
|
+
* then leaves the cwd at the projects root and lets pi decide, as before. NEVER
|
|
2115
|
+
* throws (a resume must not fail on a store-read hiccup).
|
|
2116
|
+
*/
|
|
2117
|
+
function resolveSessionCwd(
|
|
2118
|
+
env: AnonPiEnv,
|
|
2119
|
+
machine: string,
|
|
2120
|
+
piArgs: readonly string[] | undefined,
|
|
2121
|
+
): string | undefined {
|
|
2122
|
+
const id = resumeSessionId(piArgs);
|
|
2123
|
+
if (id === undefined) return undefined;
|
|
2124
|
+
const sessionsRoot = machineSessionsDir(env, machine);
|
|
2125
|
+
if (!existsSync(sessionsRoot)) return undefined;
|
|
2126
|
+
try {
|
|
2127
|
+
// sessions/<slug>/<ts>_<id>.jsonl: scan each slug dir for a file whose name
|
|
2128
|
+
// contains the id. The id is a UUID (no path/glob metachars), so a substring
|
|
2129
|
+
// match is safe and cheap for the short session lists here.
|
|
2130
|
+
for (const slug of readdirSync(sessionsRoot, {withFileTypes: true})) {
|
|
2131
|
+
if (!slug.isDirectory()) continue;
|
|
2132
|
+
const slugDir = join(sessionsRoot, slug.name);
|
|
2133
|
+
for (const f of readdirSync(slugDir)) {
|
|
2134
|
+
if (!f.endsWith('.jsonl') || !f.includes(id)) continue;
|
|
2135
|
+
const header = firstLine(join(slugDir, f));
|
|
2136
|
+
if (header === undefined) return undefined;
|
|
2137
|
+
return sessionHeaderCwd(header);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
} catch {
|
|
2141
|
+
return undefined;
|
|
2142
|
+
}
|
|
2143
|
+
return undefined;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/** Read a file's FIRST line (up to a newline), or undefined if unreadable. */
|
|
2147
|
+
function firstLine(path: string): string | undefined {
|
|
2148
|
+
try {
|
|
2149
|
+
const text = readFileSync(path, 'utf8');
|
|
2150
|
+
const nl = text.indexOf('\n');
|
|
2151
|
+
return nl === -1 ? text : text.slice(0, nl);
|
|
2152
|
+
} catch {
|
|
2153
|
+
return undefined;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2096
2157
|
/** Spawn netcage with inherited stdio; propagate its exit code. */
|
|
2097
2158
|
function spawnNetcage(netcageArgs: string[]): number {
|
|
2159
|
+
// Explain the pause: netcage sets up the jail (netns, firewall, DNS, container
|
|
2160
|
+
// start) BEFORE pi paints, so without this line the user sees only a blinking
|
|
2161
|
+
// cursor. Goes to stderr so it never pollutes any piped stdout, and is
|
|
2162
|
+
// transient (pi typically clears the screen when its TUI comes up).
|
|
2163
|
+
process.stderr.write(
|
|
2164
|
+
'anon-pi: entering the netcage jail (setting up forced-egress)\u2026\n',
|
|
2165
|
+
);
|
|
2098
2166
|
const res = spawnSync('netcage', netcageArgs, {stdio: 'inherit'});
|
|
2099
2167
|
if (res.error) {
|
|
2100
2168
|
process.stderr.write(
|