@orcareplay/shell-shim 0.1.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.
@@ -0,0 +1,4 @@
1
+ export * from './resolve.js';
2
+ export * from './runner.js';
3
+ export * from './install.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './resolve.js';
2
+ export * from './runner.js';
3
+ export * from './install.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { ShellFrame } from './runner.js';
2
+ /** Shells an agent actually reaches for. Shimming more binaries buys detail and costs blast radius. */
3
+ export declare const DEFAULT_SHIMS: readonly ["sh", "bash"];
4
+ export interface InstallOptions {
5
+ runDir: string;
6
+ /** Defaults to `<runDir>/shell-frames.jsonl`. */
7
+ framesPath?: string;
8
+ shims?: readonly string[];
9
+ }
10
+ export interface InstalledShim {
11
+ /** Prepend this to PATH. */
12
+ dir: string;
13
+ framesPath: string;
14
+ shimmed: string[];
15
+ /** Environment overlay the child needs for the shims to work. */
16
+ env: Record<string, string>;
17
+ }
18
+ /**
19
+ * Write a directory of shim executables to prepend to PATH.
20
+ *
21
+ * On POSIX, each shim is a small shell script whose shebang names an absolute interpreter, so it
22
+ * never consults PATH to start — and PATH is exactly where our own shims live. Windows cannot
23
+ * execute an extensionless script from PATH, so it gets a `.cmd` shim with the same argv contract.
24
+ */
25
+ export declare function installShellShim(options: InstallOptions): Promise<InstalledShim>;
26
+ /** Read back what the shims observed. Tolerates a partial final line, like events.jsonl. */
27
+ export declare function readShellFrames(framesPath: string): Promise<ShellFrame[]>;
28
+ /**
29
+ * Locate the compiled shim entry point.
30
+ *
31
+ * This module is loaded from `dist/` in a real install but from `src/` when the workspace aliases
32
+ * packages to source, and only the compiled `.js` can actually be exec'd. Resolving by probing
33
+ * rather than assuming means the shim works in both, and fails with a sentence instead of an
34
+ * empty stdout when it works in neither.
35
+ */
36
+ export declare function resolveRunnerBin(): Promise<string>;
37
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,uGAAuG;AACvG,eAAO,MAAM,aAAa,yBAA0B,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAyCtF;AAED,4FAA4F;AAC5F,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAY/E;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAkBxD"}
@@ -0,0 +1,97 @@
1
+ import { access, chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { join } from 'node:path';
4
+ /** Shells an agent actually reaches for. Shimming more binaries buys detail and costs blast radius. */
5
+ export const DEFAULT_SHIMS = ['sh', 'bash'];
6
+ /**
7
+ * Write a directory of shim executables to prepend to PATH.
8
+ *
9
+ * On POSIX, each shim is a small shell script whose shebang names an absolute interpreter, so it
10
+ * never consults PATH to start — and PATH is exactly where our own shims live. Windows cannot
11
+ * execute an extensionless script from PATH, so it gets a `.cmd` shim with the same argv contract.
12
+ */
13
+ export async function installShellShim(options) {
14
+ const dir = join(options.runDir, 'shims');
15
+ const framesPath = options.framesPath ?? join(options.runDir, 'shell-frames.jsonl');
16
+ const shims = options.shims ?? DEFAULT_SHIMS;
17
+ await mkdir(dir, { recursive: true });
18
+ const runner = await resolveRunnerBin();
19
+ const node = process.execPath;
20
+ const windows = process.platform === 'win32';
21
+ for (const name of shims) {
22
+ const script = windows
23
+ ? [
24
+ '@echo off',
25
+ 'rem Written by orca record. Runs the real binary and notes what happened.',
26
+ `${quoteCmd(node)} ${quoteCmd(runner)} ${quoteCmd(name)} ${quoteCmd(dir)} ${quoteCmd(framesPath)} -- %*`,
27
+ 'exit /b %ERRORLEVEL%',
28
+ '',
29
+ ].join('\r\n')
30
+ : [
31
+ '#!/bin/sh',
32
+ '# Written by orca record. Runs the real binary and notes what happened.',
33
+ `exec ${quotePosix(node)} ${quotePosix(runner)} ${quotePosix(name)} ${quotePosix(dir)} ${quotePosix(framesPath)} -- "$@"`,
34
+ '',
35
+ ].join('\n');
36
+ const path = join(dir, windows ? `${name}.cmd` : name);
37
+ await writeFile(path, script, { mode: 0o755 });
38
+ await chmod(path, 0o755);
39
+ }
40
+ await writeFile(framesPath, '', { flag: 'a', mode: 0o600 }).catch(() => {
41
+ // An unwritable frames file must not stop the run; the shim swallows write errors too.
42
+ });
43
+ return {
44
+ dir,
45
+ framesPath,
46
+ shimmed: [...shims],
47
+ env: { ORCA_SHIM_DIR: dir, ORCA_SHIM_FRAMES: framesPath },
48
+ };
49
+ }
50
+ /** Read back what the shims observed. Tolerates a partial final line, like events.jsonl. */
51
+ export async function readShellFrames(framesPath) {
52
+ const raw = await readFile(framesPath, 'utf8').catch(() => '');
53
+ const frames = [];
54
+ for (const line of raw.split('\n')) {
55
+ if (line.trim() === '')
56
+ continue;
57
+ try {
58
+ frames.push(JSON.parse(line));
59
+ }
60
+ catch {
61
+ // A process killed mid-write leaves a partial line. That is the run we most want to read.
62
+ }
63
+ }
64
+ return frames;
65
+ }
66
+ /**
67
+ * Locate the compiled shim entry point.
68
+ *
69
+ * This module is loaded from `dist/` in a real install but from `src/` when the workspace aliases
70
+ * packages to source, and only the compiled `.js` can actually be exec'd. Resolving by probing
71
+ * rather than assuming means the shim works in both, and fails with a sentence instead of an
72
+ * empty stdout when it works in neither.
73
+ */
74
+ export async function resolveRunnerBin() {
75
+ const candidates = [
76
+ new URL('./runner-bin.js', import.meta.url),
77
+ new URL('../dist/runner-bin.js', import.meta.url),
78
+ ].map((url) => fileURLToPath(url));
79
+ for (const candidate of candidates) {
80
+ try {
81
+ await access(candidate);
82
+ return candidate;
83
+ }
84
+ catch {
85
+ // try the next layout
86
+ }
87
+ }
88
+ throw new Error(`orca shell-shim: no compiled runner found (looked in ${candidates.join(', ')})\n` +
89
+ ' run `npm run build` before recording with shell capture');
90
+ }
91
+ function quotePosix(value) {
92
+ return `'${value.replace(/'/g, `'\\''`)}'`;
93
+ }
94
+ function quoteCmd(value) {
95
+ return `"${value.replace(/"/g, '""')}"`;
96
+ }
97
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Find the real binary a shim is standing in for.
3
+ *
4
+ * The shim directory is prepended to PATH, so the one thing this must never do is return the
5
+ * shim itself: that is an exec loop, and it would happen inside the user's agent run rather than
6
+ * anywhere convenient. Comparison is by resolved real path, because PATH routinely contains the
7
+ * same directory spelled several ways (`/x`, `/x/.`, a symlink) and a string compare misses those.
8
+ */
9
+ export declare function resolveRealBinary(name: string, pathVar: string, shimDir: string): Promise<string | undefined>;
10
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B"}
@@ -0,0 +1,62 @@
1
+ import { access, constants, realpath, stat } from 'node:fs/promises';
2
+ import { delimiter, extname, join, resolve } from 'node:path';
3
+ /**
4
+ * Find the real binary a shim is standing in for.
5
+ *
6
+ * The shim directory is prepended to PATH, so the one thing this must never do is return the
7
+ * shim itself: that is an exec loop, and it would happen inside the user's agent run rather than
8
+ * anywhere convenient. Comparison is by resolved real path, because PATH routinely contains the
9
+ * same directory spelled several ways (`/x`, `/x/.`, a symlink) and a string compare misses those.
10
+ */
11
+ export async function resolveRealBinary(name, pathVar, shimDir) {
12
+ const shimReal = await safeRealpath(resolve(shimDir));
13
+ for (const entry of pathVar.split(delimiter)) {
14
+ if (entry === '')
15
+ continue;
16
+ const dirReal = await safeRealpath(resolve(entry));
17
+ if (dirReal !== undefined && shimReal !== undefined && dirReal === shimReal)
18
+ continue;
19
+ for (const candidateName of candidateNames(name)) {
20
+ const candidate = join(entry, candidateName);
21
+ if (await isExecutableFile(candidate))
22
+ return candidate;
23
+ }
24
+ }
25
+ // Deliberately undefined rather than a guess: exec'ing the wrong binary is worse than a clear
26
+ // error the caller can report.
27
+ return undefined;
28
+ }
29
+ async function safeRealpath(path) {
30
+ try {
31
+ return await realpath(path);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ async function isExecutableFile(path) {
38
+ try {
39
+ // Windows does not expose POSIX execute bits. The extension/PATHEXT lookup above is the
40
+ // executable check there; stat keeps a valid .exe/.cmd/.bat discoverable on that platform.
41
+ if (process.platform === 'win32')
42
+ return (await stat(path)).isFile();
43
+ await access(path, constants.X_OK);
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ function candidateNames(name) {
51
+ if (process.platform !== 'win32' || extname(name) !== '')
52
+ return [name];
53
+ const pathext = process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD';
54
+ return [
55
+ name,
56
+ ...pathext
57
+ .split(';')
58
+ .filter(Boolean)
59
+ .map((ext) => `${name}${ext.toLowerCase()}`),
60
+ ];
61
+ }
62
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,OAAe,EACf,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAEtD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,IAAI,KAAK,KAAK,EAAE;YAAE,SAAS;QAC3B,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACnD,IAAI,OAAO,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ;YAAE,SAAS;QAEtF,KAAK,MAAM,aAAa,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;YAC7C,IAAI,MAAM,gBAAgB,CAAC,SAAS,CAAC;gBAAE,OAAO,SAAS,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,8FAA8F;IAC9F,+BAA+B;IAC/B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,wFAAwF;QACxF,2FAA2F;QAC3F,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACrE,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,qBAAqB,CAAC;IAC7D,OAAO;QACL,IAAI;QACJ,GAAG,OAAO;aACP,KAAK,CAAC,GAAG,CAAC;aACV,MAAM,CAAC,OAAO,CAAC;aACf,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=runner-bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-bin.d.ts","sourceRoot":"","sources":["../src/runner-bin.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from './runner.js';
3
+ process.exitCode = await main(process.argv.slice(2));
4
+ //# sourceMappingURL=runner-bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-bin.js","sourceRoot":"","sources":["../src/runner-bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,OAAO,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * What the shim records about one shell invocation.
3
+ *
4
+ * Deliberately not the output itself. The model already sees a rendering of stdout via the tool
5
+ * result; what it never sees — and what the protocol layer therefore cannot recover — is the exit
6
+ * code, the wall duration, the real argv, and which stream each byte came out of.
7
+ */
8
+ export interface ShellFrame {
9
+ name: string;
10
+ argv: string[];
11
+ cwd: string;
12
+ exitCode: number;
13
+ signal: string | null;
14
+ startedAt: string;
15
+ durationMs: number;
16
+ stdoutBytes: number;
17
+ stderrBytes: number;
18
+ }
19
+ export interface RunShimOptions {
20
+ name: string;
21
+ argv: string[];
22
+ shimDir: string;
23
+ framesPath: string;
24
+ env: NodeJS.ProcessEnv;
25
+ }
26
+ /**
27
+ * Stand in for one shell binary: run the real one, forward everything, write down what happened.
28
+ *
29
+ * Two rules, both about not being noticed. The passthrough must be byte-faithful, so stdio is
30
+ * inherited outright rather than piped — piping would let us count bytes but would also strip the
31
+ * TTY, and a harness that checks `isatty` would change its behaviour because we were watching.
32
+ * And every capture-side failure is swallowed: losing a frame costs a line in the trace, whereas
33
+ * throwing here breaks the command the user was actually running.
34
+ */
35
+ export declare function runShim(options: RunShimOptions): Promise<number>;
36
+ /**
37
+ * Entry point the generated shim scripts call:
38
+ * `node runner-bin.js <name> <shimDir> <framesPath> -- [args...]`
39
+ *
40
+ * The three shim parameters arrive as argv, not environment. An agent is entitled to sanitise the
41
+ * environment of the commands it runs, and if it did, an env-carried shim would keep working while
42
+ * silently capturing nothing — the exact failure mode this whole layer exists to avoid.
43
+ */
44
+ export declare function main(argv: string[]): Promise<number>;
45
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoEtE;AAYD;;;;;;;GAOG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmB1D"}
package/dist/runner.js ADDED
@@ -0,0 +1,105 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { appendFileSync } from 'node:fs';
3
+ import { basename } from 'node:path';
4
+ import { resolveRealBinary } from './resolve.js';
5
+ /**
6
+ * Stand in for one shell binary: run the real one, forward everything, write down what happened.
7
+ *
8
+ * Two rules, both about not being noticed. The passthrough must be byte-faithful, so stdio is
9
+ * inherited outright rather than piped — piping would let us count bytes but would also strip the
10
+ * TTY, and a harness that checks `isatty` would change its behaviour because we were watching.
11
+ * And every capture-side failure is swallowed: losing a frame costs a line in the trace, whereas
12
+ * throwing here breaks the command the user was actually running.
13
+ */
14
+ export async function runShim(options) {
15
+ const real = await resolveRealBinary(options.name, options.env.PATH ?? '', options.shimDir);
16
+ if (real === undefined) {
17
+ process.stderr.write(`orca shell-shim: cannot find the real "${options.name}" on PATH.\n` +
18
+ ` the shim directory is ${options.shimDir}\n` +
19
+ ` re-run without recording, or report this with your PATH\n`);
20
+ return 127;
21
+ }
22
+ const startedAt = new Date();
23
+ const startedNs = process.hrtime.bigint();
24
+ // Counting bytes requires piping, which removes the TTY. Only do it when there is no TTY to
25
+ // remove — under `orca record` the agent has already given us pipes, which is the case we care
26
+ // about, and an interactive terminal keeps its exact behaviour.
27
+ const canCount = !process.stdout.isTTY && !process.stderr.isTTY;
28
+ const child = spawn(real, options.argv, {
29
+ env: options.env,
30
+ stdio: canCount ? ['inherit', 'pipe', 'pipe'] : 'inherit',
31
+ });
32
+ let stdoutBytes = 0;
33
+ let stderrBytes = 0;
34
+ if (canCount) {
35
+ child.stdout?.on('data', (chunk) => {
36
+ stdoutBytes += chunk.length;
37
+ process.stdout.write(chunk);
38
+ });
39
+ child.stderr?.on('data', (chunk) => {
40
+ stderrBytes += chunk.length;
41
+ process.stderr.write(chunk);
42
+ });
43
+ }
44
+ const forward = (signal) => () => child.kill(signal);
45
+ const onInt = forward('SIGINT');
46
+ const onTerm = forward('SIGTERM');
47
+ process.on('SIGINT', onInt);
48
+ process.on('SIGTERM', onTerm);
49
+ const { code, signal } = await new Promise((resolve) => {
50
+ child.on('error', (err) => {
51
+ process.stderr.write(`orca shell-shim: could not run ${real}: ${String(err)}\n`);
52
+ resolve({ code: 127, signal: null });
53
+ });
54
+ child.on('close', (c, s) => resolve({ code: c ?? 0, signal: s }));
55
+ });
56
+ process.off('SIGINT', onInt);
57
+ process.off('SIGTERM', onTerm);
58
+ record(options.framesPath, {
59
+ name: options.name,
60
+ argv: options.argv,
61
+ cwd: process.cwd(),
62
+ exitCode: code,
63
+ signal,
64
+ startedAt: startedAt.toISOString(),
65
+ durationMs: Number((process.hrtime.bigint() - startedNs) / 1000000n),
66
+ stdoutBytes,
67
+ stderrBytes,
68
+ });
69
+ return code;
70
+ }
71
+ function record(framesPath, frame) {
72
+ try {
73
+ // Synchronous and append-only: the process is about to exit, and a lost write here would be
74
+ // invisible. One line per invocation keeps the file greppable like events.jsonl.
75
+ appendFileSync(framesPath, `${JSON.stringify(frame)}\n`, { mode: 0o600 });
76
+ }
77
+ catch {
78
+ // Capture is best-effort by design. See the note on runShim.
79
+ }
80
+ }
81
+ /**
82
+ * Entry point the generated shim scripts call:
83
+ * `node runner-bin.js <name> <shimDir> <framesPath> -- [args...]`
84
+ *
85
+ * The three shim parameters arrive as argv, not environment. An agent is entitled to sanitise the
86
+ * environment of the commands it runs, and if it did, an env-carried shim would keep working while
87
+ * silently capturing nothing — the exact failure mode this whole layer exists to avoid.
88
+ */
89
+ export async function main(argv) {
90
+ const separator = argv.indexOf('--');
91
+ const [name, shimDir, framesPath] = separator === -1 ? [argv[0], argv[1], argv[2]] : argv.slice(0, separator);
92
+ const rest = separator === -1 ? argv.slice(3) : argv.slice(separator + 1);
93
+ if (!name || !shimDir || !framesPath) {
94
+ process.stderr.write('orca shell-shim: invoked without its shim parameters; not launched by orca record\n');
95
+ return 127;
96
+ }
97
+ return runShim({
98
+ name: basename(name),
99
+ argv: rest,
100
+ shimDir,
101
+ framesPath,
102
+ env: process.env,
103
+ });
104
+ }
105
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@orcareplay/shell-shim",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "dependencies": {
15
+ "@orcareplay/schema": "0.1.0"
16
+ }
17
+ }