@openape/apes 1.7.1 → 1.8.1

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.
@@ -13,6 +13,7 @@ import {
13
13
  saveAuth,
14
14
  saveConfig
15
15
  } from "./chunk-OBF7IMQ2.js";
16
+ import "./chunk-7OCVIDC7.js";
16
17
  export {
17
18
  AUTH_FILE,
18
19
  CONFIG_DIR,
@@ -27,4 +28,4 @@ export {
27
28
  saveAuth,
28
29
  saveConfig
29
30
  };
30
- //# sourceMappingURL=config-MOB5DJ6H.js.map
31
+ //# sourceMappingURL=config-DA2L3XOV.js.map
@@ -8,8 +8,9 @@ import {
8
8
  getAgentChallengeEndpoint,
9
9
  getDelegationsEndpoint,
10
10
  getGrantsEndpoint
11
- } from "./chunk-N3THIFIS.js";
11
+ } from "./chunk-QJJ7DG5C.js";
12
12
  import "./chunk-OBF7IMQ2.js";
13
+ import "./chunk-7OCVIDC7.js";
13
14
  export {
14
15
  ApiError,
15
16
  apiFetch,
@@ -20,4 +21,4 @@ export {
20
21
  getDelegationsEndpoint,
21
22
  getGrantsEndpoint
22
23
  };
23
- //# sourceMappingURL=http-5F7FX4V7.js.map
24
+ //# sourceMappingURL=http-JWS5LV2U.js.map
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  ApiError,
37
37
  apiFetch,
38
38
  discoverEndpoints
39
- } from "./chunk-N3THIFIS.js";
39
+ } from "./chunk-QJJ7DG5C.js";
40
40
  import {
41
41
  clearAuth,
42
42
  getAuthToken,
@@ -47,6 +47,7 @@ import {
47
47
  saveAuth,
48
48
  saveConfig
49
49
  } from "./chunk-OBF7IMQ2.js";
50
+ import "./chunk-7OCVIDC7.js";
50
51
  export {
51
52
  ApiError,
52
53
  CliError,
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ br,
4
+ qn
5
+ } from "./chunk-WGF3SPIH.js";
6
+ import "./chunk-7OCVIDC7.js";
7
+
8
+ // ../../node_modules/.pnpm/node-fetch-native@1.6.7/node_modules/node-fetch-native/dist/chunks/multipart-parser.mjs
9
+ import "http";
10
+ import "https";
11
+ import "zlib";
12
+ import "stream";
13
+ import "buffer";
14
+ import "util";
15
+ import "url";
16
+ import "net";
17
+ import "fs";
18
+ import "path";
19
+ var B = Object.defineProperty;
20
+ var E = (u, a) => B(u, "name", { value: a, configurable: true });
21
+ var D = 0;
22
+ var t = { START_BOUNDARY: D++, HEADER_FIELD_START: D++, HEADER_FIELD: D++, HEADER_VALUE_START: D++, HEADER_VALUE: D++, HEADER_VALUE_ALMOST_DONE: D++, HEADERS_ALMOST_DONE: D++, PART_DATA_START: D++, PART_DATA: D++, END: D++ };
23
+ var w = 1;
24
+ var R = { PART_BOUNDARY: w, LAST_BOUNDARY: w *= 2 };
25
+ var g = 10;
26
+ var N = 13;
27
+ var x = 32;
28
+ var P = 45;
29
+ var C = 58;
30
+ var I = 97;
31
+ var M = 122;
32
+ var $ = E((u) => u | 32, "lower");
33
+ var m = E(() => {
34
+ }, "noop");
35
+ var F = class F2 {
36
+ constructor(a) {
37
+ this.index = 0, this.flags = 0, this.onHeaderEnd = m, this.onHeaderField = m, this.onHeadersEnd = m, this.onHeaderValue = m, this.onPartBegin = m, this.onPartData = m, this.onPartEnd = m, this.boundaryChars = {}, a = `\r
38
+ --` + a;
39
+ const n = new Uint8Array(a.length);
40
+ for (let r = 0; r < a.length; r++) n[r] = a.charCodeAt(r), this.boundaryChars[n[r]] = true;
41
+ this.boundary = n, this.lookbehind = new Uint8Array(this.boundary.length + 8), this.state = t.START_BOUNDARY;
42
+ }
43
+ write(a) {
44
+ let n = 0;
45
+ const r = a.length;
46
+ let d = this.index, { lookbehind: l, boundary: c, boundaryChars: p, index: e, state: i, flags: A } = this;
47
+ const H = this.boundary.length, O = H - 1, y = a.length;
48
+ let o, L;
49
+ const f = E((h) => {
50
+ this[h + "Mark"] = n;
51
+ }, "mark"), s = E((h) => {
52
+ delete this[h + "Mark"];
53
+ }, "clear"), T = E((h, S, _, U) => {
54
+ (S === void 0 || S !== _) && this[h](U && U.subarray(S, _));
55
+ }, "callback"), b = E((h, S) => {
56
+ const _ = h + "Mark";
57
+ _ in this && (S ? (T(h, this[_], n, a), delete this[_]) : (T(h, this[_], a.length, a), this[_] = 0));
58
+ }, "dataCallback");
59
+ for (n = 0; n < r; n++) switch (o = a[n], i) {
60
+ case t.START_BOUNDARY:
61
+ if (e === c.length - 2) {
62
+ if (o === P) A |= R.LAST_BOUNDARY;
63
+ else if (o !== N) return;
64
+ e++;
65
+ break;
66
+ } else if (e - 1 === c.length - 2) {
67
+ if (A & R.LAST_BOUNDARY && o === P) i = t.END, A = 0;
68
+ else if (!(A & R.LAST_BOUNDARY) && o === g) e = 0, T("onPartBegin"), i = t.HEADER_FIELD_START;
69
+ else return;
70
+ break;
71
+ }
72
+ o !== c[e + 2] && (e = -2), o === c[e + 2] && e++;
73
+ break;
74
+ case t.HEADER_FIELD_START:
75
+ i = t.HEADER_FIELD, f("onHeaderField"), e = 0;
76
+ case t.HEADER_FIELD:
77
+ if (o === N) {
78
+ s("onHeaderField"), i = t.HEADERS_ALMOST_DONE;
79
+ break;
80
+ }
81
+ if (e++, o === P) break;
82
+ if (o === C) {
83
+ if (e === 1) return;
84
+ b("onHeaderField", true), i = t.HEADER_VALUE_START;
85
+ break;
86
+ }
87
+ if (L = $(o), L < I || L > M) return;
88
+ break;
89
+ case t.HEADER_VALUE_START:
90
+ if (o === x) break;
91
+ f("onHeaderValue"), i = t.HEADER_VALUE;
92
+ case t.HEADER_VALUE:
93
+ o === N && (b("onHeaderValue", true), T("onHeaderEnd"), i = t.HEADER_VALUE_ALMOST_DONE);
94
+ break;
95
+ case t.HEADER_VALUE_ALMOST_DONE:
96
+ if (o !== g) return;
97
+ i = t.HEADER_FIELD_START;
98
+ break;
99
+ case t.HEADERS_ALMOST_DONE:
100
+ if (o !== g) return;
101
+ T("onHeadersEnd"), i = t.PART_DATA_START;
102
+ break;
103
+ case t.PART_DATA_START:
104
+ i = t.PART_DATA, f("onPartData");
105
+ case t.PART_DATA:
106
+ if (d = e, e === 0) {
107
+ for (n += O; n < y && !(a[n] in p); ) n += H;
108
+ n -= O, o = a[n];
109
+ }
110
+ if (e < c.length) c[e] === o ? (e === 0 && b("onPartData", true), e++) : e = 0;
111
+ else if (e === c.length) e++, o === N ? A |= R.PART_BOUNDARY : o === P ? A |= R.LAST_BOUNDARY : e = 0;
112
+ else if (e - 1 === c.length) if (A & R.PART_BOUNDARY) {
113
+ if (e = 0, o === g) {
114
+ A &= ~R.PART_BOUNDARY, T("onPartEnd"), T("onPartBegin"), i = t.HEADER_FIELD_START;
115
+ break;
116
+ }
117
+ } else A & R.LAST_BOUNDARY && o === P ? (T("onPartEnd"), i = t.END, A = 0) : e = 0;
118
+ if (e > 0) l[e - 1] = o;
119
+ else if (d > 0) {
120
+ const h = new Uint8Array(l.buffer, l.byteOffset, l.byteLength);
121
+ T("onPartData", 0, d, h), d = 0, f("onPartData"), n--;
122
+ }
123
+ break;
124
+ case t.END:
125
+ break;
126
+ default:
127
+ throw new Error(`Unexpected state entered: ${i}`);
128
+ }
129
+ b("onHeaderField"), b("onHeaderValue"), b("onPartData"), this.index = e, this.state = i, this.flags = A;
130
+ }
131
+ end() {
132
+ if (this.state === t.HEADER_FIELD_START && this.index === 0 || this.state === t.PART_DATA && this.index === this.boundary.length) this.onPartEnd();
133
+ else if (this.state !== t.END) throw new Error("MultipartParser.end(): stream ended unexpectedly");
134
+ }
135
+ };
136
+ E(F, "MultipartParser");
137
+ var k = F;
138
+ function v(u) {
139
+ const a = u.match(/\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i);
140
+ if (!a) return;
141
+ const n = a[2] || a[3] || "";
142
+ let r = n.slice(n.lastIndexOf("\\") + 1);
143
+ return r = r.replace(/%22/g, '"'), r = r.replace(/&#(\d{4});/g, (d, l) => String.fromCharCode(l)), r;
144
+ }
145
+ E(v, "_fileName");
146
+ async function Z(u, a) {
147
+ if (!/multipart/i.test(a)) throw new TypeError("Failed to fetch");
148
+ const n = a.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
149
+ if (!n) throw new TypeError("no or bad content-type header, no multipart boundary");
150
+ const r = new k(n[1] || n[2]);
151
+ let d, l, c, p, e, i;
152
+ const A = [], H = new br(), O = E((s) => {
153
+ c += f.decode(s, { stream: true });
154
+ }, "onPartData"), y = E((s) => {
155
+ A.push(s);
156
+ }, "appendToFile"), o = E(() => {
157
+ const s = new qn(A, i, { type: e });
158
+ H.append(p, s);
159
+ }, "appendFileToFormData"), L = E(() => {
160
+ H.append(p, c);
161
+ }, "appendEntryToFormData"), f = new TextDecoder("utf-8");
162
+ f.decode(), r.onPartBegin = function() {
163
+ r.onPartData = O, r.onPartEnd = L, d = "", l = "", c = "", p = "", e = "", i = null, A.length = 0;
164
+ }, r.onHeaderField = function(s) {
165
+ d += f.decode(s, { stream: true });
166
+ }, r.onHeaderValue = function(s) {
167
+ l += f.decode(s, { stream: true });
168
+ }, r.onHeaderEnd = function() {
169
+ if (l += f.decode(), d = d.toLowerCase(), d === "content-disposition") {
170
+ const s = l.match(/\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i);
171
+ s && (p = s[2] || s[3] || ""), i = v(l), i && (r.onPartData = y, r.onPartEnd = o);
172
+ } else d === "content-type" && (e = l);
173
+ l = "", d = "";
174
+ };
175
+ for await (const s of u) r.write(s);
176
+ return r.end(), H;
177
+ }
178
+ E(Z, "toFormData");
179
+ export {
180
+ Z as toFormData
181
+ };
182
+ //# sourceMappingURL=multipart-parser-FVZBRBXW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../node_modules/.pnpm/node-fetch-native@1.6.7/node_modules/node-fetch-native/dist/chunks/multipart-parser.mjs"],"sourcesContent":["var B=Object.defineProperty;var E=(u,a)=>B(u,\"name\",{value:a,configurable:!0});import{FormData as V,File as Y}from\"../node.mjs\";import\"node:http\";import\"node:https\";import\"node:zlib\";import\"node:stream\";import\"node:buffer\";import\"node:util\";import\"../shared/node-fetch-native.DfbY2q-x.mjs\";import\"node:url\";import\"node:net\";import\"node:fs\";import\"node:path\";let D=0;const t={START_BOUNDARY:D++,HEADER_FIELD_START:D++,HEADER_FIELD:D++,HEADER_VALUE_START:D++,HEADER_VALUE:D++,HEADER_VALUE_ALMOST_DONE:D++,HEADERS_ALMOST_DONE:D++,PART_DATA_START:D++,PART_DATA:D++,END:D++};let w=1;const R={PART_BOUNDARY:w,LAST_BOUNDARY:w*=2},g=10,N=13,x=32,P=45,C=58,I=97,M=122,$=E(u=>u|32,\"lower\"),m=E(()=>{},\"noop\"),F=class F{constructor(a){this.index=0,this.flags=0,this.onHeaderEnd=m,this.onHeaderField=m,this.onHeadersEnd=m,this.onHeaderValue=m,this.onPartBegin=m,this.onPartData=m,this.onPartEnd=m,this.boundaryChars={},a=`\\r\n--`+a;const n=new Uint8Array(a.length);for(let r=0;r<a.length;r++)n[r]=a.charCodeAt(r),this.boundaryChars[n[r]]=!0;this.boundary=n,this.lookbehind=new Uint8Array(this.boundary.length+8),this.state=t.START_BOUNDARY}write(a){let n=0;const r=a.length;let d=this.index,{lookbehind:l,boundary:c,boundaryChars:p,index:e,state:i,flags:A}=this;const H=this.boundary.length,O=H-1,y=a.length;let o,L;const f=E(h=>{this[h+\"Mark\"]=n},\"mark\"),s=E(h=>{delete this[h+\"Mark\"]},\"clear\"),T=E((h,S,_,U)=>{(S===void 0||S!==_)&&this[h](U&&U.subarray(S,_))},\"callback\"),b=E((h,S)=>{const _=h+\"Mark\";_ in this&&(S?(T(h,this[_],n,a),delete this[_]):(T(h,this[_],a.length,a),this[_]=0))},\"dataCallback\");for(n=0;n<r;n++)switch(o=a[n],i){case t.START_BOUNDARY:if(e===c.length-2){if(o===P)A|=R.LAST_BOUNDARY;else if(o!==N)return;e++;break}else if(e-1===c.length-2){if(A&R.LAST_BOUNDARY&&o===P)i=t.END,A=0;else if(!(A&R.LAST_BOUNDARY)&&o===g)e=0,T(\"onPartBegin\"),i=t.HEADER_FIELD_START;else return;break}o!==c[e+2]&&(e=-2),o===c[e+2]&&e++;break;case t.HEADER_FIELD_START:i=t.HEADER_FIELD,f(\"onHeaderField\"),e=0;case t.HEADER_FIELD:if(o===N){s(\"onHeaderField\"),i=t.HEADERS_ALMOST_DONE;break}if(e++,o===P)break;if(o===C){if(e===1)return;b(\"onHeaderField\",!0),i=t.HEADER_VALUE_START;break}if(L=$(o),L<I||L>M)return;break;case t.HEADER_VALUE_START:if(o===x)break;f(\"onHeaderValue\"),i=t.HEADER_VALUE;case t.HEADER_VALUE:o===N&&(b(\"onHeaderValue\",!0),T(\"onHeaderEnd\"),i=t.HEADER_VALUE_ALMOST_DONE);break;case t.HEADER_VALUE_ALMOST_DONE:if(o!==g)return;i=t.HEADER_FIELD_START;break;case t.HEADERS_ALMOST_DONE:if(o!==g)return;T(\"onHeadersEnd\"),i=t.PART_DATA_START;break;case t.PART_DATA_START:i=t.PART_DATA,f(\"onPartData\");case t.PART_DATA:if(d=e,e===0){for(n+=O;n<y&&!(a[n]in p);)n+=H;n-=O,o=a[n]}if(e<c.length)c[e]===o?(e===0&&b(\"onPartData\",!0),e++):e=0;else if(e===c.length)e++,o===N?A|=R.PART_BOUNDARY:o===P?A|=R.LAST_BOUNDARY:e=0;else if(e-1===c.length)if(A&R.PART_BOUNDARY){if(e=0,o===g){A&=~R.PART_BOUNDARY,T(\"onPartEnd\"),T(\"onPartBegin\"),i=t.HEADER_FIELD_START;break}}else A&R.LAST_BOUNDARY&&o===P?(T(\"onPartEnd\"),i=t.END,A=0):e=0;if(e>0)l[e-1]=o;else if(d>0){const h=new Uint8Array(l.buffer,l.byteOffset,l.byteLength);T(\"onPartData\",0,d,h),d=0,f(\"onPartData\"),n--}break;case t.END:break;default:throw new Error(`Unexpected state entered: ${i}`)}b(\"onHeaderField\"),b(\"onHeaderValue\"),b(\"onPartData\"),this.index=e,this.state=i,this.flags=A}end(){if(this.state===t.HEADER_FIELD_START&&this.index===0||this.state===t.PART_DATA&&this.index===this.boundary.length)this.onPartEnd();else if(this.state!==t.END)throw new Error(\"MultipartParser.end(): stream ended unexpectedly\")}};E(F,\"MultipartParser\");let k=F;function v(u){const a=u.match(/\\bfilename=(\"(.*?)\"|([^()<>@,;:\\\\\"/[\\]?={}\\s\\t]+))($|;\\s)/i);if(!a)return;const n=a[2]||a[3]||\"\";let r=n.slice(n.lastIndexOf(\"\\\\\")+1);return r=r.replace(/%22/g,'\"'),r=r.replace(/&#(\\d{4});/g,(d,l)=>String.fromCharCode(l)),r}E(v,\"_fileName\");async function Z(u,a){if(!/multipart/i.test(a))throw new TypeError(\"Failed to fetch\");const n=a.match(/boundary=(?:\"([^\"]+)\"|([^;]+))/i);if(!n)throw new TypeError(\"no or bad content-type header, no multipart boundary\");const r=new k(n[1]||n[2]);let d,l,c,p,e,i;const A=[],H=new V,O=E(s=>{c+=f.decode(s,{stream:!0})},\"onPartData\"),y=E(s=>{A.push(s)},\"appendToFile\"),o=E(()=>{const s=new Y(A,i,{type:e});H.append(p,s)},\"appendFileToFormData\"),L=E(()=>{H.append(p,c)},\"appendEntryToFormData\"),f=new TextDecoder(\"utf-8\");f.decode(),r.onPartBegin=function(){r.onPartData=O,r.onPartEnd=L,d=\"\",l=\"\",c=\"\",p=\"\",e=\"\",i=null,A.length=0},r.onHeaderField=function(s){d+=f.decode(s,{stream:!0})},r.onHeaderValue=function(s){l+=f.decode(s,{stream:!0})},r.onHeaderEnd=function(){if(l+=f.decode(),d=d.toLowerCase(),d===\"content-disposition\"){const s=l.match(/\\bname=(\"([^\"]*)\"|([^()<>@,;:\\\\\"/[\\]?={}\\s\\t]+))/i);s&&(p=s[2]||s[3]||\"\"),i=v(l),i&&(r.onPartData=y,r.onPartEnd=o)}else d===\"content-type\"&&(e=l);l=\"\",d=\"\"};for await(const s of u)r.write(s);return r.end(),H}E(Z,\"toFormData\");export{Z as toFormData};\n"],"mappings":"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","names":["F"]}
@@ -18,10 +18,11 @@ import {
18
18
  import {
19
19
  apiFetch,
20
20
  getGrantsEndpoint
21
- } from "./chunk-N3THIFIS.js";
21
+ } from "./chunk-QJJ7DG5C.js";
22
22
  import {
23
23
  loadAuth
24
24
  } from "./chunk-OBF7IMQ2.js";
25
+ import "./chunk-7OCVIDC7.js";
25
26
 
26
27
  // src/shell/orchestrator.ts
27
28
  import { hostname } from "os";
@@ -777,4 +778,4 @@ async function runInteractiveShell() {
777
778
  export {
778
779
  runInteractiveShell
779
780
  };
780
- //# sourceMappingURL=orchestrator-EH6V5ATG.js.map
781
+ //# sourceMappingURL=orchestrator-2QS5KXFL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell/orchestrator.ts","../src/shell/grant-dispatch.ts","../src/shell/meta-commands.ts","../src/shell/pty-bridge.ts","../src/shell/repl.ts","../src/shell/multi-line.ts","../src/shell/session.ts"],"sourcesContent":["import { hostname } from 'node:os'\nimport consola from 'consola'\nimport { loadAuth } from '../config.js'\nimport { requestGrantForShellLine } from './grant-dispatch.js'\nimport { createMetaCommandHandler } from './meta-commands.js'\nimport { PtyBridge } from './pty-bridge.js'\nimport { ShellRepl } from './repl.js'\nimport { ShellSession } from './session.js'\n\n/**\n * Orchestrates the interactive ape-shell session by wiring the user-facing\n * REPL to a persistent bash child via the PtyBridge. Keeps both components\n * decoupled so each can be unit-tested in isolation.\n *\n * Flow per user-entered line:\n * 1. ShellRepl emits onLine with the completed (possibly multi-line) buffer\n * 2. Line goes through the grant flow. If denied: session logs the denial,\n * we surface the reason, and return without touching bash.\n * 3. On approval we enter \"output mode\" (raw stdin passthrough so TUI apps\n * like vim/less/top receive raw keystrokes) and write the line to bash.\n * 4. bash output streams back to stdout live via PtyBridge.onOutput\n * 5. When the marker-bearing PS1 reappears we resolve the pending promise,\n * readline takes back the terminal, and the next prompt is drawn.\n *\n * The orchestrator also owns the lifecycle niceties:\n * - SIGWINCH is forwarded to the pty so TUI apps re-render at the right size\n * - SIGTERM / SIGHUP trigger a clean shutdown (kill bash, restore TTY mode,\n * close the audit session)\n * - An emergency process.on('exit') handler restores the terminal out of\n * raw mode in case a crash left it there\n *\n * Every line is recorded in the audit log via the ShellSession — granted,\n * denied, and done events with seq numbers correlating each event to its\n * parent line within the session.\n */\nexport async function runInteractiveShell(): Promise<void> {\n /**\n * When a line is written into the bash pty, handleLine stashes a\n * resolver here so the PtyBridge's onLineDone can wake it up.\n */\n let pendingResolve: (() => void) | null = null\n let lastExitCode = 0\n let shuttingDown = false\n // Set to true while `:reset` is tearing down the current bash child and\n // spinning up a replacement. The old bridge's `onExit` callback checks\n // this and returns early so the user does not see a spurious\n // \"bash exited\" message or have the REPL torn down mid-reset.\n let resetting = false\n // Forward reference to the REPL so the bridge's onExit can stop it when\n // bash dies. Assigned after construction below.\n let repl: ShellRepl | null = null\n\n /**\n * Build a fresh PtyBridge with the standard lifecycle callbacks. Extracted\n * into a factory so `:reset` can spawn a replacement child without\n * duplicating the callback wiring.\n */\n const createBridge = (): PtyBridge => new PtyBridge(\n {\n onOutput: (chunk) => {\n // Live streaming to the user's terminal. Always written straight\n // through so TUI apps (vim, less, top) get their escape sequences\n // on time.\n process.stdout.write(chunk)\n },\n onLineDone: (frame) => {\n if (pendingResolve) {\n const r = pendingResolve\n pendingResolve = null\n lastExitCode = frame.exitCode\n r()\n }\n },\n onExit: (exitCode) => {\n // `:reset` is intentionally killing this bridge; the replacement\n // will own the user-facing lifecycle from here.\n if (resetting)\n return\n // bash itself died — typically because the user ran `exit`.\n // Tear down the REPL so run() returns cleanly.\n if (!shuttingDown) {\n process.stdout.write(`\\n[bash exited with code ${exitCode}]\\n`)\n repl?.stop()\n }\n },\n },\n )\n\n // Spawn the bash child up-front so the REPL can write to it as soon as the\n // first line is accepted. Output from bash goes straight to the terminal.\n // `bridge` is `let` because `:reset` swaps in a replacement.\n let bridge = createBridge()\n\n await bridge.waitForReady()\n\n const targetHost = hostname()\n const auth = loadAuth()\n const session = new ShellSession({\n host: targetHost,\n requester: auth?.email ?? 'unknown',\n })\n\n const handleMetaCommand = createMetaCommandHandler({\n getBridge: () => bridge,\n resetBridge: async () => {\n resetting = true\n try {\n bridge.kill('SIGKILL')\n }\n catch {}\n bridge = createBridge()\n await bridge.waitForReady()\n resetting = false\n },\n session,\n getAuth: loadAuth,\n targetHost,\n isPending: () => pendingResolve !== null,\n write: s => process.stdout.write(s),\n })\n\n repl = new ShellRepl(\n {\n onMetaCommand: handleMetaCommand,\n onLine: async (line) => {\n // --- 1. Gate the line through the grant flow BEFORE bash sees it ---\n const grant = await requestGrantForShellLine(line, {\n targetHost,\n approval: 'once',\n })\n\n if (grant.kind === 'denied') {\n session.logLineDenied({ line, reason: grant.reason })\n consola.error(grant.reason)\n return\n }\n\n const seq = session.logLineGranted({\n line,\n grantId: grant.grantId,\n grantMode: grant.mode,\n })\n\n // --- 2. Raw-mode stdin passthrough while bash runs the line ---\n const wasRaw = process.stdin.isTTY && (process.stdin as NodeJS.ReadStream).isRaw\n if (process.stdin.isTTY && !wasRaw)\n (process.stdin as NodeJS.ReadStream).setRawMode(true)\n\n const forward = (chunk: Buffer) => {\n bridge.writeRaw(chunk.toString())\n }\n const rawInputAvailable = process.stdin.isTTY === true\n if (rawInputAvailable)\n process.stdin.on('data', forward)\n\n try {\n await new Promise<void>((resolve) => {\n pendingResolve = resolve\n bridge.writeLine(line)\n })\n }\n finally {\n if (rawInputAvailable) {\n process.stdin.off('data', forward)\n if (!wasRaw && process.stdin.isTTY)\n (process.stdin as NodeJS.ReadStream).setRawMode(false)\n }\n }\n\n session.logLineDone({ seq, exitCode: lastExitCode })\n\n if (lastExitCode !== 0) {\n consola.debug(`(exit ${lastExitCode})`)\n }\n },\n onExit: () => {\n shuttingDown = true\n session.close()\n try {\n bridge.kill()\n }\n catch {}\n },\n },\n )\n\n // SIGWINCH forwarding so TUI apps re-render at the right dimensions.\n const onResize = () => {\n const cols = process.stdout.columns ?? 80\n const rows = process.stdout.rows ?? 24\n try {\n bridge.resize(cols, rows)\n }\n catch {}\n }\n process.stdout.on('resize', onResize)\n\n // Emergency TTY restore — if a crash leaves stdin in raw mode, subsequent\n // terminal input becomes unusable until the user manually runs `reset`.\n // This handler fires on clean exit and on uncaught exceptions.\n const restoreTty = () => {\n if (process.stdin.isTTY && (process.stdin as NodeJS.ReadStream).isRaw) {\n try {\n (process.stdin as NodeJS.ReadStream).setRawMode(false)\n }\n catch {}\n }\n }\n process.on('exit', restoreTty)\n\n // Clean shutdown on termination signals. kill() is idempotent in node-pty,\n // and session.close() swallows audit-log errors internally, so this is safe\n // to call even if already shutting down.\n const gracefulShutdown = () => {\n if (shuttingDown)\n return\n shuttingDown = true\n restoreTty()\n try {\n session.close()\n }\n catch {}\n try {\n bridge.kill()\n }\n catch {}\n try {\n repl?.stop()\n }\n catch {}\n }\n process.on('SIGTERM', gracefulShutdown)\n process.on('SIGHUP', gracefulShutdown)\n\n try {\n await repl.run()\n }\n finally {\n process.stdout.off('resize', onResize)\n process.off('exit', restoreTty)\n process.off('SIGTERM', gracefulShutdown)\n process.off('SIGHUP', gracefulShutdown)\n }\n}\n","import { basename } from 'node:path'\nimport consola from 'consola'\nimport { loadAuth } from '../config.js'\nimport { apiFetch, getGrantsEndpoint } from '../http.js'\nimport { notifyGrantPending } from '../notifications.js'\nimport {\n createShapesGrant,\n fetchGrantToken,\n findExistingGrant,\n loadOrInstallAdapter,\n parseShellCommand,\n resolveCommand,\n verifyAndConsume,\n waitForGrantStatus,\n} from '../shapes/index.js'\nimport { checkSudoRejection, isApesSelfDispatch } from './apes-self-dispatch.js'\n\n/**\n * Result of attempting to obtain a grant for a shell line. On success the\n * REPL may proceed to execute the line in its persistent bash pty. On\n * failure the caller should surface `reason` and discard the line.\n *\n * The `self` mode means the line was a trusted `apes` self-invocation\n * (e.g. `apes grants run <id>`, `apes whoami`, `apes config set ...`) that\n * bypasses the grant flow entirely — see the self-dispatch shortcut in\n * `requestGrantForShellLine` for the rationale.\n */\nexport type GrantLineResult =\n | { kind: 'approved', grantId: string, mode: 'adapter' | 'session' | 'self' }\n | { kind: 'denied', reason: string }\n\n// The APES_GATED_SUBCOMMANDS blocklist + `isApesSelfDispatch` helper\n// live in `./apes-self-dispatch.ts` so the same rule is shared with the\n// one-shot `ape-shell -c` path in `commands/run.ts runShellMode`. See\n// that module for the full rationale.\n\n/**\n * Options the orchestrator passes to `requestGrantForShellLine`. They\n * mirror what the `apes run --shell` path reads from its citty args, but\n * come directly from the shell session context instead.\n */\nexport interface GrantLineOptions {\n /** Target host name. Usually `os.hostname()`. */\n targetHost: string\n /** Approval mode — almost always 'once' for interactive shell lines. */\n approval?: 'once' | 'timed' | 'always'\n}\n\n/**\n * Obtain a grant for a shell line so the interactive REPL may execute it\n * against its persistent bash child.\n *\n * Dispatch strategy mirrors the existing one-shot `tryAdapterModeFromShell`\n * flow:\n * 1. Try to resolve the line as an adapter-backed command (structured\n * grant with resource chain / permission).\n * 2. If the adapter path succeeds, reuse an existing matching grant or\n * request a new one, verify + consume it.\n * 3. If the adapter path fails (compound line, no matching adapter,\n * resolve failure) fall back to a generic `ape-shell` session grant\n * for the target host.\n *\n * Returns without executing anything. The caller (the orchestrator)\n * decides how to run the line — typically by writing it to bash's pty.\n */\nexport async function requestGrantForShellLine(\n line: string,\n options: GrantLineOptions,\n): Promise<GrantLineResult> {\n const auth = loadAuth()\n if (!auth) {\n return { kind: 'denied', reason: 'Not logged in. Run `apes login` first.' }\n }\n const idp = auth.idp\n if (!idp) {\n return { kind: 'denied', reason: 'No IdP URL configured. Run `apes login` first.' }\n }\n\n // --- 0a. unconditional exit ---\n // `exit` and `exit <code>` always succeed without approval. Getting OUT of\n // the shell is a foot-gun if it requires a grant — agents and humans\n // alike should be able to leave reliably even if everything else is\n // broken (network down, IdP unreachable, expired token).\n const trimmedLine = line.trim().replace(/;+$/, '').trim()\n if (trimmedLine === 'exit' || /^exit \\d+$/.test(trimmedLine)) {\n return { kind: 'approved', grantId: 'shell-exit', mode: 'self' }\n }\n\n const parsed = parseShellCommand(line)\n\n // --- 0b. apes self-dispatch shortcut ---\n // `apes <subcmd>` invocations from inside the ape-shell REPL are the\n // shell's own control surface — not a new user-authored action that\n // needs approval. See `apes-self-dispatch.ts` for the full rationale.\n // Only `run`, `fetch`, `mcp` remain on the grant path.\n if (isApesSelfDispatch(parsed)) {\n return { kind: 'approved', grantId: 'shell-internal', mode: 'self' }\n }\n\n // --- 0b. sudo reject ---\n // Shared logic lives in `apes-self-dispatch.ts` so the one-shot path\n // in `commands/run.ts runShellMode` emits the same message.\n const sudoRejection = checkSudoRejection(parsed)\n if (sudoRejection) {\n return { kind: 'denied', reason: sudoRejection.reason }\n }\n\n // --- 1. Adapter path ---\n if (parsed && !parsed.isCompound) {\n try {\n const loaded = await loadOrInstallAdapter(parsed.executable)\n if (loaded) {\n const normalizedExecutable = basename(parsed.executable)\n const resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv])\n\n // Try to reuse an existing matching grant first.\n try {\n const existingGrantId = await findExistingGrant(resolved, idp)\n if (existingGrantId) {\n if (process.env.APES_QUIET_GRANT_REUSE !== '1')\n consola.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`)\n const token = await fetchGrantToken(idp, existingGrantId)\n await verifyAndConsume(token, resolved)\n return { kind: 'approved', grantId: existingGrantId, mode: 'adapter' }\n }\n }\n catch (err) {\n consola.debug(`ape-shell: findExistingGrant failed, will request new grant:`, err)\n }\n\n // Request a new adapter-backed grant.\n consola.info(`Requesting grant for: ${resolved.detail.display}`)\n const grant = await createShapesGrant(resolved, {\n idp,\n approval: options.approval ?? 'once',\n reason: `ape-shell: ${resolved.detail.display}`,\n })\n consola.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`)\n\n notifyGrantPending({\n grantId: grant.id,\n approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,\n command: resolved.detail?.display ?? line,\n audience: resolved.adapter?.cli?.audience ?? 'shapes',\n host: options.targetHost,\n })\n\n const status = await waitForGrantStatus(idp, grant.id)\n if (status !== 'approved') {\n return { kind: 'denied', reason: `Grant ${status}` }\n }\n consola.info(`Grant ${grant.id} approved — continuing`)\n\n const token = await fetchGrantToken(idp, grant.id)\n await verifyAndConsume(token, resolved)\n return { kind: 'approved', grantId: grant.id, mode: 'adapter' }\n }\n }\n catch (err) {\n // Adapter resolution failed — debug-log and fall through to the\n // generic session grant path.\n consola.debug(`ape-shell: adapter resolve failed, falling back to session grant:`, err)\n }\n }\n\n // --- 2. Generic session grant (ape-shell audience) ---\n const grantsUrl = await getGrantsEndpoint(idp)\n\n // Try to reuse an existing timed/always session grant first.\n try {\n const grants = await apiFetch<{ data: Array<{ id: string, status: string, request: { audience: string, target_host: string, grant_type: string } }> }>(\n `${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`,\n )\n const sessionGrant = grants.data.find(g =>\n g.request.audience === 'ape-shell'\n && g.request.target_host === options.targetHost\n && g.request.grant_type !== 'once',\n )\n if (sessionGrant) {\n if (process.env.APES_QUIET_GRANT_REUSE !== '1')\n consola.info(`Reusing ape-shell session grant ${sessionGrant.id} on ${options.targetHost}`)\n return { kind: 'approved', grantId: sessionGrant.id, mode: 'session' }\n }\n }\n catch (err) {\n consola.debug(`ape-shell: session grant lookup failed:`, err)\n }\n\n // Request a new session grant. The approver sees the literal shell line.\n consola.info(`Requesting ape-shell session grant on ${options.targetHost}`)\n try {\n const grant = await apiFetch<{ id: string, status: string }>(grantsUrl, {\n method: 'POST',\n body: {\n requester: auth.email,\n target_host: options.targetHost,\n audience: 'ape-shell',\n grant_type: options.approval ?? 'once',\n command: ['bash', '-c', line],\n reason: `Shell session: ${line.slice(0, 100)}`,\n },\n })\n consola.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`)\n\n notifyGrantPending({\n grantId: grant.id,\n approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,\n command: line.slice(0, 200),\n audience: 'ape-shell',\n host: options.targetHost,\n })\n\n const maxWait = 300_000\n const interval = 3_000\n const start = Date.now()\n\n while (Date.now() - start < maxWait) {\n const status = await apiFetch<{ status: string }>(`${grantsUrl}/${grant.id}`)\n if (status.status === 'approved') {\n consola.info(`Grant ${grant.id} approved — continuing`)\n return { kind: 'approved', grantId: grant.id, mode: 'session' }\n }\n if (status.status === 'denied' || status.status === 'revoked')\n return { kind: 'denied', reason: `Grant ${status.status}` }\n await new Promise(r => setTimeout(r, interval))\n }\n\n return { kind: 'denied', reason: 'Grant approval timed out after 5 minutes' }\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return { kind: 'denied', reason: `Grant request failed: ${msg}` }\n }\n}\n","import type { AuthData } from '../config.js'\nimport type { PtyBridge } from './pty-bridge.js'\nimport type { ShellSession } from './session.js'\n\n/**\n * Injected dependencies for the meta-command handler. Keeping these behind\n * an explicit interface lets tests drive the handler with plain objects and\n * `vi.fn()` spies, no pty or auth file required.\n */\nexport interface MetaDeps {\n getBridge: () => PtyBridge\n resetBridge: () => Promise<void>\n session: ShellSession\n getAuth: () => AuthData | null\n targetHost: string\n isPending: () => boolean\n write: (s: string) => void\n}\n\n/**\n * Short one-line descriptions printed by `:help`. Ordered alphabetically so\n * the list stays stable as we add more commands.\n */\nconst HELP_ENTRIES: Array<[string, string]> = [\n [':help', 'Show available meta-commands.'],\n [':reset', 'Kill and respawn the bash child (preserves grants + audit).'],\n [':status', 'Show session, host, bash pid, and auth state.'],\n]\n\n/**\n * Format a millisecond duration as `Nh Nm Ns` with components omitted only\n * from the left. We always show seconds so zero-ish uptimes still read sanely.\n */\nfunction formatUptime(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000))\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n const parts: string[] = []\n if (hours > 0)\n parts.push(`${hours}h`)\n if (hours > 0 || minutes > 0)\n parts.push(`${minutes}m`)\n parts.push(`${seconds}s`)\n return parts.join(' ')\n}\n\n/**\n * Build the async handler invoked by `ShellRepl.onMetaCommand`. Return value\n * semantics match the REPL contract: `true` means the line was consumed and\n * the REPL should redraw the prompt; `false` would fall through to shell\n * dispatch, but every branch here currently returns `true`.\n */\nexport function createMetaCommandHandler(deps: MetaDeps): (line: string) => Promise<boolean> {\n return async (line: string) => {\n const trimmed = line.trim()\n\n if (trimmed === ':help') {\n deps.write('Available meta-commands:\\n')\n for (const [name, desc] of HELP_ENTRIES)\n deps.write(` ${name.padEnd(10)} ${desc}\\n`)\n return true\n }\n\n if (trimmed === ':status') {\n const uptime = formatUptime(Date.now() - deps.session.startedAt)\n const pid = deps.getBridge().pid\n deps.write(`Session: ${deps.session.id} (uptime ${uptime})\\n`)\n deps.write(`Host: ${deps.targetHost}\\n`)\n deps.write(`Bash: pid ${pid}\\n`)\n\n const auth = deps.getAuth()\n if (!auth) {\n deps.write('User: (not logged in)\\n')\n return true\n }\n deps.write(`User: ${auth.email}\\n`)\n deps.write(`IdP: ${auth.idp}\\n`)\n const expiresMs = auth.expires_at * 1000\n if (expiresMs <= Date.now()) {\n deps.write('Token: EXPIRED\\n')\n }\n else {\n const iso = new Date(expiresMs).toISOString()\n deps.write(`Token: valid until ${iso}\\n`)\n }\n return true\n }\n\n if (trimmed === ':reset') {\n if (deps.isPending()) {\n deps.write('Cannot reset while a command is running. Wait or press Ctrl-C.\\n')\n return true\n }\n await deps.resetBridge()\n const newPid = deps.getBridge().pid\n deps.write(`Bash reset. New pid: ${newPid}\\n`)\n return true\n }\n\n deps.write(`Unknown meta-command \\`${trimmed}\\`. Try \\`:help\\`.\\n`)\n return true\n }\n}\n","import { randomBytes } from 'node:crypto'\nimport type { IPty } from '@lydell/node-pty'\nimport * as pty from '@lydell/node-pty'\n\n/**\n * Frame returned when the bash child finishes executing a line and is ready\n * for the next one. The `output` is everything bash printed since the last\n * frame, with the marker line stripped.\n */\nexport interface CompletedLineFrame {\n output: string\n exitCode: number\n}\n\n/**\n * Callbacks the PtyBridge delivers to its owner. `onOutput` fires with\n * streaming pty output chunks (marker content already stripped). `onLineDone`\n * fires once per completed shell line with the cumulative output and the\n * exit code extracted from the prompt marker. `onExit` fires when the bash\n * child itself has terminated (user typed `exit`, or the process died).\n */\nexport interface PtyBridgeEvents {\n onOutput: (chunk: string) => void\n onLineDone: (frame: CompletedLineFrame) => void\n onExit: (exitCode: number, signal: number | undefined) => void\n}\n\n/**\n * Wraps a persistent bash child running inside a pty. Each time the frontend\n * feeds a line via `writeLine`, the bridge streams bash's stdout back through\n * `onOutput` until it detects the marker-bearing PS1 — at which point the\n * line is considered complete and `onLineDone` fires with the captured\n * output and the exit code.\n *\n * The marker format embedded in PS1 is:\n * __APES_<random-hex>__:<exit-code>:__END__\n * The leading token is a 32-char random hex so output collisions are\n * effectively impossible. `<exit-code>` is `$?` at prompt render time.\n *\n * The marker itself is stripped from the output stream before it is\n * forwarded to the caller, so consumers never see it on their terminal.\n */\nexport class PtyBridge {\n private readonly marker: string\n /** Compiled once. Captures the exit code in group 1. */\n private readonly markerRegex: RegExp\n private readonly term: IPty\n private readonly events: PtyBridgeEvents\n /** Bytes received since the last completed line. Searched for the marker. */\n private pending = ''\n /**\n * Accumulated output for the current in-flight line. Streams to `onOutput`\n * live as chunks arrive, and is handed to `onLineDone` in full when the\n * marker is matched. Resets at that point.\n */\n private currentLineBuffer = ''\n /** True until bash prints its first marker-prompt. */\n private readyForFirstLine = false\n private awaitingInitialPrompt: ((value: void) => void) | null = null\n\n constructor(events: PtyBridgeEvents, options: { cols?: number, rows?: number, cwd?: string } = {}) {\n this.events = events\n this.marker = randomBytes(16).toString('hex')\n // Example match: __APES_abc123…__:0:__END__\n // The `\\r?\\n?` tolerates line endings around the marker depending on\n // how bash renders PS1 on a fresh line.\n this.markerRegex = new RegExp(\n `__APES_${this.marker}__:(-?\\\\d+):__END__\\\\r?\\\\n?`,\n )\n\n const cols = options.cols ?? process.stdout.columns ?? 80\n const rows = options.rows ?? process.stdout.rows ?? 24\n\n // We spawn bash as `--login -i` so the user's ~/.bash_profile / ~/.bashrc\n // runs and they get their aliases/functions. Trade-off: if the user\n // overrides PS1 in their rcfile, our marker detection breaks. We protect\n // against that by re-exporting PS1 via PROMPT_COMMAND on every prompt.\n //\n // PROMPT_COMMAND also runs `stty -echo` so the pty line discipline stops\n // echoing user input back to us. Without this, every line the frontend\n // writes to the pty gets echoed into the output stream — causing the\n // command to appear twice in the user's terminal (once from readline,\n // once from the pty echo). The frontend already owns its own display of\n // what the user typed; the pty echo is redundant and surprising.\n // Interactive TUI apps (vim/less/top) set their own termios when they\n // start, so they are unaffected.\n // Strip APES_SHELL_WRAPPER so nested `apes` invocations inside the pty\n // don't self-detect as ape-shell mode and reject their argv. The wrapper\n // script sets this marker on the parent ape-shell process; leaking it\n // into bash would cause `apes <subcommand>` at the REPL prompt to print\n // \"unsupported invocation\" instead of running.\n const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env\n this.term = pty.spawn('bash', ['--login', '-i'], {\n name: 'xterm-256color',\n cols,\n rows,\n cwd: options.cwd ?? process.cwd(),\n env: {\n ...inheritedEnv,\n // Force our marker PS1 on every prompt and keep pty echo off —\n // both survive .bashrc overrides because PROMPT_COMMAND runs\n // before each prompt.\n PROMPT_COMMAND: `stty -echo 2>/dev/null; PS1='__APES_${this.marker}__:$?:__END__'`,\n // Also set it initially so the very first prompt carries the marker.\n PS1: `__APES_${this.marker}__:$?:__END__`,\n PS2: '> ',\n // Silence bash-specific onboarding that would pollute our output.\n BASH_SILENCE_DEPRECATION_WARNING: '1',\n },\n })\n\n this.term.onData(chunk => this.handleData(chunk))\n this.term.onExit(({ exitCode, signal }) => {\n this.events.onExit(exitCode, signal)\n })\n }\n\n /**\n * Resolves once bash has printed its very first marker-bearing prompt,\n * which means it has finished sourcing ~/.bashrc and is ready to accept\n * input. Callers should await this before sending the first line.\n */\n waitForReady(): Promise<void> {\n if (this.readyForFirstLine)\n return Promise.resolve()\n return new Promise((resolve) => {\n this.awaitingInitialPrompt = resolve\n })\n }\n\n /**\n * Write a shell command line to bash's stdin. The caller must ensure the\n * line has already been approved by the grant flow. The bridge does NOT\n * validate or filter the line.\n */\n writeLine(line: string): void {\n // Trim trailing newlines and always append exactly one, so pasting a\n // multi-line string works naturally.\n const clean = line.replace(/\\r?\\n+$/, '')\n this.term.write(`${clean}\\n`)\n }\n\n /**\n * Raw passthrough write — used for forwarding user keystrokes during\n * interactive output mode (e.g. while vim is running).\n */\n writeRaw(data: string): void {\n this.term.write(data)\n }\n\n /** Resize the underlying pty. Called on SIGWINCH. */\n resize(cols: number, rows: number): void {\n this.term.resize(cols, rows)\n }\n\n /** Kill the bash child. Called on Ctrl-D / shell exit. */\n kill(signal?: string): void {\n this.term.kill(signal)\n }\n\n /** Process id of the bash child, for logging / debugging. */\n get pid(): number {\n return this.term.pid\n }\n\n /** Exposed for tests that want to look at the raw marker. */\n getMarkerForTest(): string {\n return this.marker\n }\n\n // ----- internals -----\n\n private handleData(chunk: string): void {\n this.pending += chunk\n\n // Walk through any complete marker matches. Each marker ends the current\n // in-flight line; the `before` slice is its final output.\n for (;;) {\n const match = this.pending.match(this.markerRegex)\n if (!match || match.index === undefined)\n break\n\n const before = this.pending.slice(0, match.index)\n const exitCode = Number(match[1])\n // Stream the pre-marker portion live (for REPL display) AND fold it\n // into the per-line buffer so the `onLineDone` frame is complete.\n if (before.length > 0) {\n this.currentLineBuffer += before\n if (this.readyForFirstLine)\n this.events.onOutput(before)\n }\n // Drop the matched marker and everything before it from the buffer\n this.pending = this.pending.slice(match.index + match[0].length)\n\n if (!this.readyForFirstLine) {\n // Bootstrap prompt: discard any bash startup noise we captured and\n // signal readiness. onLineDone does NOT fire for the implicit\n // first prompt — that would confuse consumers.\n this.readyForFirstLine = true\n this.currentLineBuffer = ''\n const resolve = this.awaitingInitialPrompt\n this.awaitingInitialPrompt = null\n if (resolve)\n resolve()\n continue\n }\n\n // Real command completion: hand over the accumulated line buffer.\n const frame = { output: this.currentLineBuffer, exitCode }\n this.currentLineBuffer = ''\n this.events.onLineDone(frame)\n }\n\n // No more markers in the buffer. If we're past bootstrap, stream any\n // complete lines that have accumulated so the user sees live output,\n // and keep any partial tail in `pending` in case the marker is still\n // mid-chunk.\n if (!this.readyForFirstLine)\n return\n\n const lastNewline = this.pending.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const ready = this.pending.slice(0, lastNewline + 1)\n this.pending = this.pending.slice(lastNewline + 1)\n if (ready.length > 0) {\n this.currentLineBuffer += ready\n this.events.onOutput(ready)\n }\n }\n }\n}\n","import { createInterface } from 'node:readline'\nimport type { Interface as ReadlineInterface } from 'node:readline'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport consola from 'consola'\nimport { checkMultiLineStatus } from './multi-line.js'\n\n/**\n * Where the REPL persists its input history across sessions. Lives alongside\n * the existing apes config so we don't add a new top-level dotfile.\n */\nconst HISTORY_FILE = join(homedir(), '.config', 'apes', 'shell-history')\n\n/**\n * Primary and continuation prompts. PS1 shows when the REPL is ready for a\n * fresh command; PS2 shows when bash syntax is incomplete and the user needs\n * to keep typing (e.g., inside a for-loop or heredoc).\n */\nconst PS1 = 'apes$ '\nconst PS2 = '> '\n\n/**\n * Event sink for the REPL. Keeps the loop decoupled from I/O sinks so tests\n * can feed lines and observe outcomes without touching real stdin/stdout.\n */\nexport interface ReplEvents {\n /**\n * Fires when a complete shell line has been accumulated (possibly across\n * multiple input lines via continuation). The handler is responsible for\n * actually running the line — typically by handing it to the grant flow\n * and then to the pty bridge. In M2 this is just logged; the real wiring\n * lands in M3/M4.\n */\n onLine: (line: string) => void | Promise<void>\n\n /**\n * Fires when the REPL is about to exit (user pressed Ctrl-D or called\n * `stop`). Gives owners a chance to tear down resources.\n */\n onExit: () => void | Promise<void>\n\n /**\n * Called when the user enters a line starting with `:` while not in the\n * middle of a multi-line buffer. Return true if handled (REPL skips shell\n * dispatch and redraws the prompt). Return false/undefined to fall through\n * to normal shell dispatch.\n */\n onMetaCommand?: (line: string) => boolean | Promise<boolean>\n}\n\n/**\n * Optional overrides for tests. Real usage defaults to `process.stdin`,\n * `process.stdout`, and the standard history file path.\n */\nexport interface ReplOptions {\n input?: NodeJS.ReadableStream\n output?: NodeJS.WritableStream\n historyFile?: string\n /** Disables the session-start banner — handy in tests. */\n quiet?: boolean\n}\n\n/**\n * Interactive REPL loop for `ape-shell`. Implements the state machine:\n *\n * PROMPT → (line entered) → MULTILINE? → (continue) → PROMPT\n * │\n * └─── complete → emit onLine → PROMPT\n *\n * The loop uses node:readline for line editing, history, and signal\n * handling. Multi-line detection runs a `bash -n` dry-parse on Enter to\n * decide whether to fold the input into a longer buffer or submit it.\n *\n * The REPL does NOT know about pty, grants, or bash internals. Owners wire\n * those in via the `onLine` callback. This keeps M2 independently testable.\n */\nexport class ShellRepl {\n private readonly events: ReplEvents\n private readonly input: NodeJS.ReadableStream\n private readonly output: NodeJS.WritableStream\n private readonly quiet: boolean\n private rl: ReadlineInterface | null = null\n /** Accumulated input across multi-line continuations. */\n private buffer = ''\n /** Whether the REPL has called `stop`. */\n private stopped = false\n\n constructor(events: ReplEvents, options: ReplOptions = {}) {\n this.events = events\n this.input = options.input ?? process.stdin\n this.output = options.output ?? process.stdout\n this.quiet = options.quiet ?? false\n }\n\n /**\n * Start the REPL. Resolves when the user ends the session (Ctrl-D) or\n * `stop()` is called from outside. Errors bubble out of `onLine` and\n * cause the line to be rejected, but do NOT tear down the REPL.\n */\n async run(): Promise<void> {\n if (this.stopped)\n return\n\n this.rl = createInterface({\n input: this.input,\n output: this.output,\n prompt: PS1,\n historySize: 1000,\n // Enable tab completion fallback (file names + history) via default.\n terminal: true,\n })\n\n if (!this.quiet)\n this.writeBanner()\n\n this.safePrompt(PS1)\n\n return new Promise<void>((resolve) => {\n this.rl!.on('line', async (line) => {\n try {\n await this.handleLine(line)\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.error(`Shell error: ${msg}`)\n this.resetBuffer()\n this.safePrompt(PS1)\n }\n })\n\n this.rl!.on('SIGINT', () => {\n // Ctrl-C in prompt mode: clear the buffer, draw a new prompt.\n if (this.buffer.length > 0)\n this.output.write('\\n')\n this.resetBuffer()\n this.safePrompt(PS1)\n })\n\n this.rl!.on('close', async () => {\n // Ctrl-D or stop() — we're done.\n this.stopped = true\n await this.events.onExit()\n resolve()\n })\n })\n }\n\n /**\n * Request the REPL to stop cleanly. Equivalent to the user pressing\n * Ctrl-D. Typically called during shutdown or after a fatal error.\n */\n stop(): void {\n if (this.rl)\n this.rl.close()\n }\n\n // ----- internals -----\n\n private writeBanner(): void {\n this.output.write('apes interactive shell\\n')\n this.output.write('Ctrl-D to exit.\\n')\n this.output.write('\\n')\n }\n\n private async handleLine(rawLine: string): Promise<void> {\n // Meta-commands are single-line, bypass multi-line + grant + shell\n // entirely. Only fire from an empty buffer (never mid-multiline) so\n // `:foo` inside a heredoc/for-loop body is still treated as bash input.\n if (\n this.buffer.length === 0\n && rawLine.trim().startsWith(':')\n && this.events.onMetaCommand\n ) {\n const handled = await this.events.onMetaCommand(rawLine.trim())\n if (handled) {\n this.safePrompt(PS1)\n return\n }\n }\n\n // Append the new line to the existing buffer. Use a literal newline so\n // bash sees the multi-line structure correctly on `bash -n`.\n this.buffer = this.buffer.length === 0\n ? rawLine\n : `${this.buffer}\\n${rawLine}`\n\n const status = checkMultiLineStatus(this.buffer)\n\n if (status.kind === 'continue') {\n this.safePrompt(PS2)\n return\n }\n\n if (status.kind === 'error') {\n this.output.write(`${status.message}\\n`)\n this.resetBuffer()\n this.safePrompt(PS1)\n return\n }\n\n // Complete. Hand off to the owner.\n const completeLine = this.buffer\n this.resetBuffer()\n\n // If the buffer was pure whitespace, skip the callback and re-prompt.\n if (completeLine.trim().length === 0) {\n this.safePrompt(PS1)\n return\n }\n\n await this.events.onLine(completeLine)\n\n // Back to prompt mode. Owners of onLine can await and drive their own\n // output before we show the next prompt.\n this.safePrompt(PS1)\n }\n\n /**\n * Draw a prompt, but only if the readline interface is still alive.\n * The onLine callback may have triggered `stop()` (e.g., bash exited),\n * in which case setPrompt/prompt would throw ERR_USE_AFTER_CLOSE.\n */\n private safePrompt(prompt: string): void {\n if (this.stopped || !this.rl)\n return\n try {\n this.rl.setPrompt(prompt)\n this.rl.prompt()\n }\n catch (err) {\n // Swallow ERR_USE_AFTER_CLOSE which races with shutdown.\n const code = (err as NodeJS.ErrnoException)?.code\n if (code !== 'ERR_USE_AFTER_CLOSE')\n throw err\n }\n }\n\n private resetBuffer(): void {\n this.buffer = ''\n }\n}\n\nexport { HISTORY_FILE }\n","import { spawnSync } from 'node:child_process'\n\n/**\n * Result of checking whether a shell command buffer is syntactically\n * complete. The REPL uses this to decide whether to submit the line for\n * execution or to keep reading more lines with a continuation prompt.\n */\nexport type MultiLineStatus =\n | { kind: 'complete' }\n | { kind: 'continue' }\n | { kind: 'error', message: string }\n\n/**\n * Patterns that bash writes to stderr when it encounters a syntax error\n * caused by an incomplete construct (missing `done`, unterminated quote,\n * unclosed `$(` or heredoc, etc.). If any of these patterns match, the\n * buffer is treated as incomplete and the REPL shows a continuation prompt\n * instead of reporting an error.\n */\nconst CONTINUE_PATTERNS: RegExp[] = [\n /syntax error: unexpected end of file/i,\n /unexpected end of file/i,\n /here-document.+delimited by end-of-file/i,\n /unexpected EOF while looking for matching/i,\n]\n\n/**\n * Detects an unterminated here-document in the buffer. `bash -n` accepts\n * these as \"complete\" (it treats end-of-input as the delimiter) so we have\n * to catch them ourselves to match interactive bash's behavior of showing\n * a continuation prompt until the user types the delimiter on its own line.\n *\n * Matches `<<` or `<<-` followed by an optionally-quoted identifier. For\n * each match we scan the remaining lines of the buffer for a line whose\n * content (after leading tabs, which `<<-` strips) equals the delimiter.\n * If no closing line is found, the heredoc is unterminated → continue.\n */\nfunction hasUnterminatedHeredoc(buffer: string): boolean {\n // Match <<-? optional whitespace, optional ' or \" around the word.\n // We deliberately don't try to parse bash quoting fully — this is a\n // best-effort heuristic that catches the common cases.\n const pattern = /<<(-?)\\s*(['\"]?)([A-Z_]\\w*)\\2/gi\n for (const match of buffer.matchAll(pattern)) {\n const stripTabs = match[1] === '-'\n const delimiter = match[3]!\n // Look at every line AFTER the match start\n const afterMatch = buffer.slice((match.index ?? 0) + match[0].length)\n const lines = afterMatch.split('\\n').slice(1) // skip the rest of the opening line\n const terminated = lines.some((line) => {\n const compare = stripTabs ? line.replace(/^\\t+/, '') : line\n return compare === delimiter\n })\n if (!terminated)\n return true\n }\n return false\n}\n\n/**\n * Check whether a (possibly multi-line) shell command buffer forms a\n * complete statement. Runs `bash -n -c <buffer>` in a separate process —\n * `-n` makes bash parse without executing, so there are no side effects.\n *\n * Design note: we spawn bash once per check. That's typically <10ms on\n * modern machines and only happens when the user presses Enter, not per\n * keystroke, so the cost is negligible.\n */\nexport function checkMultiLineStatus(buffer: string): MultiLineStatus {\n if (buffer.trim().length === 0)\n return { kind: 'complete' } // empty line is a no-op, treat as complete\n\n // Heredoc check first — bash -n accepts unterminated heredocs but an\n // interactive shell should ask for more input until the delimiter line.\n if (hasUnterminatedHeredoc(buffer))\n return { kind: 'continue' }\n\n const result = spawnSync('bash', ['-n', '-c', buffer], {\n stdio: ['ignore', 'ignore', 'pipe'],\n encoding: 'utf-8',\n })\n\n if (result.error) {\n // Bash itself couldn't be spawned — treat as a hard error so the REPL\n // can surface it. This should be vanishingly rare.\n return { kind: 'error', message: `Failed to spawn bash for syntax check: ${result.error.message}` }\n }\n\n if (result.status === 0)\n return { kind: 'complete' }\n\n const stderr = result.stderr || ''\n for (const pattern of CONTINUE_PATTERNS) {\n if (pattern.test(stderr))\n return { kind: 'continue' }\n }\n\n // Anything else is a genuine syntax error — caller should show it and\n // reset the buffer instead of accumulating more lines.\n return { kind: 'error', message: stderr.trim() || 'Syntax error' }\n}\n","import { randomBytes } from 'node:crypto'\nimport { appendAuditLog } from '../shapes/index.js'\n\n/**\n * A single interactive ape-shell session. Tracks a stable session id plus a\n * monotonic sequence number so individual lines can be correlated back to\n * their session in the audit log.\n */\nexport class ShellSession {\n readonly id: string\n readonly startedAt: number\n private lineSeq = 0\n\n constructor(options: { host: string, requester: string }) {\n this.id = randomBytes(8).toString('hex')\n this.startedAt = Date.now()\n appendAuditLog({\n action: 'shell-session-start',\n session_id: this.id,\n host: options.host,\n requester: options.requester,\n })\n }\n\n /**\n * Record a granted line that was approved for execution. Called after the\n * grant flow returns approval but before (or right after) bash runs the\n * command. Stores the grant id so the line can be traced back to the\n * specific grant that authorized it.\n */\n logLineGranted(params: {\n line: string\n grantId: string\n grantMode: 'adapter' | 'session' | 'self'\n }): number {\n const seq = ++this.lineSeq\n appendAuditLog({\n action: 'shell-session-line',\n session_id: this.id,\n seq,\n line: params.line,\n grant_id: params.grantId,\n grant_mode: params.grantMode,\n status: 'executing',\n })\n return seq\n }\n\n /** Record the final exit code of a previously-granted line. */\n logLineDone(params: { seq: number, exitCode: number }): void {\n appendAuditLog({\n action: 'shell-session-line-done',\n session_id: this.id,\n seq: params.seq,\n exit_code: params.exitCode,\n })\n }\n\n /** Record that a line was denied by the grant flow and never reached bash. */\n logLineDenied(params: { line: string, reason: string }): void {\n const seq = ++this.lineSeq\n appendAuditLog({\n action: 'shell-session-line',\n session_id: this.id,\n seq,\n line: params.line,\n status: 'denied',\n reason: params.reason,\n })\n }\n\n /** Record session termination. Fires on clean Ctrl-D or bash death. */\n close(): void {\n appendAuditLog({\n action: 'shell-session-end',\n session_id: this.id,\n duration_ms: Date.now() - this.startedAt,\n lines: this.lineSeq,\n })\n }\n}\n"],"mappings":"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","names":["consola","token","consola","consola","randomBytes","randomBytes","consola"]}
1
+ {"version":3,"sources":["../src/shell/orchestrator.ts","../src/shell/grant-dispatch.ts","../src/shell/meta-commands.ts","../src/shell/pty-bridge.ts","../src/shell/repl.ts","../src/shell/multi-line.ts","../src/shell/session.ts"],"sourcesContent":["import { hostname } from 'node:os'\nimport consola from 'consola'\nimport { loadAuth } from '../config.js'\nimport { requestGrantForShellLine } from './grant-dispatch.js'\nimport { createMetaCommandHandler } from './meta-commands.js'\nimport { PtyBridge } from './pty-bridge.js'\nimport { ShellRepl } from './repl.js'\nimport { ShellSession } from './session.js'\n\n/**\n * Orchestrates the interactive ape-shell session by wiring the user-facing\n * REPL to a persistent bash child via the PtyBridge. Keeps both components\n * decoupled so each can be unit-tested in isolation.\n *\n * Flow per user-entered line:\n * 1. ShellRepl emits onLine with the completed (possibly multi-line) buffer\n * 2. Line goes through the grant flow. If denied: session logs the denial,\n * we surface the reason, and return without touching bash.\n * 3. On approval we enter \"output mode\" (raw stdin passthrough so TUI apps\n * like vim/less/top receive raw keystrokes) and write the line to bash.\n * 4. bash output streams back to stdout live via PtyBridge.onOutput\n * 5. When the marker-bearing PS1 reappears we resolve the pending promise,\n * readline takes back the terminal, and the next prompt is drawn.\n *\n * The orchestrator also owns the lifecycle niceties:\n * - SIGWINCH is forwarded to the pty so TUI apps re-render at the right size\n * - SIGTERM / SIGHUP trigger a clean shutdown (kill bash, restore TTY mode,\n * close the audit session)\n * - An emergency process.on('exit') handler restores the terminal out of\n * raw mode in case a crash left it there\n *\n * Every line is recorded in the audit log via the ShellSession — granted,\n * denied, and done events with seq numbers correlating each event to its\n * parent line within the session.\n */\nexport async function runInteractiveShell(): Promise<void> {\n /**\n * When a line is written into the bash pty, handleLine stashes a\n * resolver here so the PtyBridge's onLineDone can wake it up.\n */\n let pendingResolve: (() => void) | null = null\n let lastExitCode = 0\n let shuttingDown = false\n // Set to true while `:reset` is tearing down the current bash child and\n // spinning up a replacement. The old bridge's `onExit` callback checks\n // this and returns early so the user does not see a spurious\n // \"bash exited\" message or have the REPL torn down mid-reset.\n let resetting = false\n // Forward reference to the REPL so the bridge's onExit can stop it when\n // bash dies. Assigned after construction below.\n let repl: ShellRepl | null = null\n\n /**\n * Build a fresh PtyBridge with the standard lifecycle callbacks. Extracted\n * into a factory so `:reset` can spawn a replacement child without\n * duplicating the callback wiring.\n */\n const createBridge = (): PtyBridge => new PtyBridge(\n {\n onOutput: (chunk) => {\n // Live streaming to the user's terminal. Always written straight\n // through so TUI apps (vim, less, top) get their escape sequences\n // on time.\n process.stdout.write(chunk)\n },\n onLineDone: (frame) => {\n if (pendingResolve) {\n const r = pendingResolve\n pendingResolve = null\n lastExitCode = frame.exitCode\n r()\n }\n },\n onExit: (exitCode) => {\n // `:reset` is intentionally killing this bridge; the replacement\n // will own the user-facing lifecycle from here.\n if (resetting)\n return\n // bash itself died — typically because the user ran `exit`.\n // Tear down the REPL so run() returns cleanly.\n if (!shuttingDown) {\n process.stdout.write(`\\n[bash exited with code ${exitCode}]\\n`)\n repl?.stop()\n }\n },\n },\n )\n\n // Spawn the bash child up-front so the REPL can write to it as soon as the\n // first line is accepted. Output from bash goes straight to the terminal.\n // `bridge` is `let` because `:reset` swaps in a replacement.\n let bridge = createBridge()\n\n await bridge.waitForReady()\n\n const targetHost = hostname()\n const auth = loadAuth()\n const session = new ShellSession({\n host: targetHost,\n requester: auth?.email ?? 'unknown',\n })\n\n const handleMetaCommand = createMetaCommandHandler({\n getBridge: () => bridge,\n resetBridge: async () => {\n resetting = true\n try {\n bridge.kill('SIGKILL')\n }\n catch {}\n bridge = createBridge()\n await bridge.waitForReady()\n resetting = false\n },\n session,\n getAuth: loadAuth,\n targetHost,\n isPending: () => pendingResolve !== null,\n write: s => process.stdout.write(s),\n })\n\n repl = new ShellRepl(\n {\n onMetaCommand: handleMetaCommand,\n onLine: async (line) => {\n // --- 1. Gate the line through the grant flow BEFORE bash sees it ---\n const grant = await requestGrantForShellLine(line, {\n targetHost,\n approval: 'once',\n })\n\n if (grant.kind === 'denied') {\n session.logLineDenied({ line, reason: grant.reason })\n consola.error(grant.reason)\n return\n }\n\n const seq = session.logLineGranted({\n line,\n grantId: grant.grantId,\n grantMode: grant.mode,\n })\n\n // --- 2. Raw-mode stdin passthrough while bash runs the line ---\n const wasRaw = process.stdin.isTTY && (process.stdin as NodeJS.ReadStream).isRaw\n if (process.stdin.isTTY && !wasRaw)\n (process.stdin as NodeJS.ReadStream).setRawMode(true)\n\n const forward = (chunk: Buffer) => {\n bridge.writeRaw(chunk.toString())\n }\n const rawInputAvailable = process.stdin.isTTY === true\n if (rawInputAvailable)\n process.stdin.on('data', forward)\n\n try {\n await new Promise<void>((resolve) => {\n pendingResolve = resolve\n bridge.writeLine(line)\n })\n }\n finally {\n if (rawInputAvailable) {\n process.stdin.off('data', forward)\n if (!wasRaw && process.stdin.isTTY)\n (process.stdin as NodeJS.ReadStream).setRawMode(false)\n }\n }\n\n session.logLineDone({ seq, exitCode: lastExitCode })\n\n if (lastExitCode !== 0) {\n consola.debug(`(exit ${lastExitCode})`)\n }\n },\n onExit: () => {\n shuttingDown = true\n session.close()\n try {\n bridge.kill()\n }\n catch {}\n },\n },\n )\n\n // SIGWINCH forwarding so TUI apps re-render at the right dimensions.\n const onResize = () => {\n const cols = process.stdout.columns ?? 80\n const rows = process.stdout.rows ?? 24\n try {\n bridge.resize(cols, rows)\n }\n catch {}\n }\n process.stdout.on('resize', onResize)\n\n // Emergency TTY restore — if a crash leaves stdin in raw mode, subsequent\n // terminal input becomes unusable until the user manually runs `reset`.\n // This handler fires on clean exit and on uncaught exceptions.\n const restoreTty = () => {\n if (process.stdin.isTTY && (process.stdin as NodeJS.ReadStream).isRaw) {\n try {\n (process.stdin as NodeJS.ReadStream).setRawMode(false)\n }\n catch {}\n }\n }\n process.on('exit', restoreTty)\n\n // Clean shutdown on termination signals. kill() is idempotent in node-pty,\n // and session.close() swallows audit-log errors internally, so this is safe\n // to call even if already shutting down.\n const gracefulShutdown = () => {\n if (shuttingDown)\n return\n shuttingDown = true\n restoreTty()\n try {\n session.close()\n }\n catch {}\n try {\n bridge.kill()\n }\n catch {}\n try {\n repl?.stop()\n }\n catch {}\n }\n process.on('SIGTERM', gracefulShutdown)\n process.on('SIGHUP', gracefulShutdown)\n\n try {\n await repl.run()\n }\n finally {\n process.stdout.off('resize', onResize)\n process.off('exit', restoreTty)\n process.off('SIGTERM', gracefulShutdown)\n process.off('SIGHUP', gracefulShutdown)\n }\n}\n","import { basename } from 'node:path'\nimport consola from 'consola'\nimport { loadAuth } from '../config.js'\nimport { apiFetch, getGrantsEndpoint } from '../http.js'\nimport { notifyGrantPending } from '../notifications.js'\nimport {\n createShapesGrant,\n fetchGrantToken,\n findExistingGrant,\n loadOrInstallAdapter,\n parseShellCommand,\n resolveCommand,\n verifyAndConsume,\n waitForGrantStatus,\n} from '../shapes/index.js'\nimport { checkSudoRejection, isApesSelfDispatch } from './apes-self-dispatch.js'\n\n/**\n * Result of attempting to obtain a grant for a shell line. On success the\n * REPL may proceed to execute the line in its persistent bash pty. On\n * failure the caller should surface `reason` and discard the line.\n *\n * The `self` mode means the line was a trusted `apes` self-invocation\n * (e.g. `apes grants run <id>`, `apes whoami`, `apes config set ...`) that\n * bypasses the grant flow entirely — see the self-dispatch shortcut in\n * `requestGrantForShellLine` for the rationale.\n */\nexport type GrantLineResult =\n | { kind: 'approved', grantId: string, mode: 'adapter' | 'session' | 'self' }\n | { kind: 'denied', reason: string }\n\n// The APES_GATED_SUBCOMMANDS blocklist + `isApesSelfDispatch` helper\n// live in `./apes-self-dispatch.ts` so the same rule is shared with the\n// one-shot `ape-shell -c` path in `commands/run.ts runShellMode`. See\n// that module for the full rationale.\n\n/**\n * Options the orchestrator passes to `requestGrantForShellLine`. They\n * mirror what the `apes run --shell` path reads from its citty args, but\n * come directly from the shell session context instead.\n */\nexport interface GrantLineOptions {\n /** Target host name. Usually `os.hostname()`. */\n targetHost: string\n /** Approval mode — almost always 'once' for interactive shell lines. */\n approval?: 'once' | 'timed' | 'always'\n}\n\n/**\n * Obtain a grant for a shell line so the interactive REPL may execute it\n * against its persistent bash child.\n *\n * Dispatch strategy mirrors the existing one-shot `tryAdapterModeFromShell`\n * flow:\n * 1. Try to resolve the line as an adapter-backed command (structured\n * grant with resource chain / permission).\n * 2. If the adapter path succeeds, reuse an existing matching grant or\n * request a new one, verify + consume it.\n * 3. If the adapter path fails (compound line, no matching adapter,\n * resolve failure) fall back to a generic `ape-shell` session grant\n * for the target host.\n *\n * Returns without executing anything. The caller (the orchestrator)\n * decides how to run the line — typically by writing it to bash's pty.\n */\nexport async function requestGrantForShellLine(\n line: string,\n options: GrantLineOptions,\n): Promise<GrantLineResult> {\n const auth = loadAuth()\n if (!auth) {\n return { kind: 'denied', reason: 'Not logged in. Run `apes login` first.' }\n }\n const idp = auth.idp\n if (!idp) {\n return { kind: 'denied', reason: 'No IdP URL configured. Run `apes login` first.' }\n }\n\n // --- 0a. unconditional exit ---\n // `exit` and `exit <code>` always succeed without approval. Getting OUT of\n // the shell is a foot-gun if it requires a grant — agents and humans\n // alike should be able to leave reliably even if everything else is\n // broken (network down, IdP unreachable, expired token).\n const trimmedLine = line.trim().replace(/;+$/, '').trim()\n if (trimmedLine === 'exit' || /^exit \\d+$/.test(trimmedLine)) {\n return { kind: 'approved', grantId: 'shell-exit', mode: 'self' }\n }\n\n const parsed = parseShellCommand(line)\n\n // --- 0b. apes self-dispatch shortcut ---\n // `apes <subcmd>` invocations from inside the ape-shell REPL are the\n // shell's own control surface — not a new user-authored action that\n // needs approval. See `apes-self-dispatch.ts` for the full rationale.\n // Only `run`, `fetch`, `mcp` remain on the grant path.\n if (isApesSelfDispatch(parsed)) {\n return { kind: 'approved', grantId: 'shell-internal', mode: 'self' }\n }\n\n // --- 0b. sudo reject ---\n // Shared logic lives in `apes-self-dispatch.ts` so the one-shot path\n // in `commands/run.ts runShellMode` emits the same message.\n const sudoRejection = checkSudoRejection(parsed)\n if (sudoRejection) {\n return { kind: 'denied', reason: sudoRejection.reason }\n }\n\n // --- 1. Adapter path ---\n if (parsed && !parsed.isCompound) {\n try {\n const loaded = await loadOrInstallAdapter(parsed.executable)\n if (loaded) {\n const normalizedExecutable = basename(parsed.executable)\n const resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv])\n\n // Try to reuse an existing matching grant first.\n try {\n const existingGrantId = await findExistingGrant(resolved, idp)\n if (existingGrantId) {\n if (process.env.APES_QUIET_GRANT_REUSE !== '1')\n consola.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`)\n const token = await fetchGrantToken(idp, existingGrantId)\n await verifyAndConsume(token, resolved)\n return { kind: 'approved', grantId: existingGrantId, mode: 'adapter' }\n }\n }\n catch (err) {\n consola.debug(`ape-shell: findExistingGrant failed, will request new grant:`, err)\n }\n\n // Request a new adapter-backed grant.\n consola.info(`Requesting grant for: ${resolved.detail.display}`)\n const grant = await createShapesGrant(resolved, {\n idp,\n approval: options.approval ?? 'once',\n reason: `ape-shell: ${resolved.detail.display}`,\n })\n consola.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`)\n\n notifyGrantPending({\n grantId: grant.id,\n approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,\n command: resolved.detail?.display ?? line,\n audience: resolved.adapter?.cli?.audience ?? 'shapes',\n host: options.targetHost,\n })\n\n const status = await waitForGrantStatus(idp, grant.id)\n if (status !== 'approved') {\n return { kind: 'denied', reason: `Grant ${status}` }\n }\n consola.info(`Grant ${grant.id} approved — continuing`)\n\n const token = await fetchGrantToken(idp, grant.id)\n await verifyAndConsume(token, resolved)\n return { kind: 'approved', grantId: grant.id, mode: 'adapter' }\n }\n }\n catch (err) {\n // Adapter resolution failed — debug-log and fall through to the\n // generic session grant path.\n consola.debug(`ape-shell: adapter resolve failed, falling back to session grant:`, err)\n }\n }\n\n // --- 2. Generic session grant (ape-shell audience) ---\n const grantsUrl = await getGrantsEndpoint(idp)\n\n // Try to reuse an existing timed/always session grant first.\n try {\n const grants = await apiFetch<{ data: Array<{ id: string, status: string, request: { audience: string, target_host: string, grant_type: string } }> }>(\n `${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`,\n )\n const sessionGrant = grants.data.find(g =>\n g.request.audience === 'ape-shell'\n && g.request.target_host === options.targetHost\n && g.request.grant_type !== 'once',\n )\n if (sessionGrant) {\n if (process.env.APES_QUIET_GRANT_REUSE !== '1')\n consola.info(`Reusing ape-shell session grant ${sessionGrant.id} on ${options.targetHost}`)\n return { kind: 'approved', grantId: sessionGrant.id, mode: 'session' }\n }\n }\n catch (err) {\n consola.debug(`ape-shell: session grant lookup failed:`, err)\n }\n\n // Request a new session grant. The approver sees the literal shell line.\n consola.info(`Requesting ape-shell session grant on ${options.targetHost}`)\n try {\n const grant = await apiFetch<{ id: string, status: string }>(grantsUrl, {\n method: 'POST',\n body: {\n requester: auth.email,\n target_host: options.targetHost,\n audience: 'ape-shell',\n grant_type: options.approval ?? 'once',\n command: ['bash', '-c', line],\n reason: `Shell session: ${line.slice(0, 100)}`,\n },\n })\n consola.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`)\n\n notifyGrantPending({\n grantId: grant.id,\n approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,\n command: line.slice(0, 200),\n audience: 'ape-shell',\n host: options.targetHost,\n })\n\n const maxWait = 300_000\n const interval = 3_000\n const start = Date.now()\n\n while (Date.now() - start < maxWait) {\n const status = await apiFetch<{ status: string }>(`${grantsUrl}/${grant.id}`)\n if (status.status === 'approved') {\n consola.info(`Grant ${grant.id} approved — continuing`)\n return { kind: 'approved', grantId: grant.id, mode: 'session' }\n }\n if (status.status === 'denied' || status.status === 'revoked')\n return { kind: 'denied', reason: `Grant ${status.status}` }\n await new Promise(r => setTimeout(r, interval))\n }\n\n return { kind: 'denied', reason: 'Grant approval timed out after 5 minutes' }\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return { kind: 'denied', reason: `Grant request failed: ${msg}` }\n }\n}\n","import type { AuthData } from '../config.js'\nimport type { PtyBridge } from './pty-bridge.js'\nimport type { ShellSession } from './session.js'\n\n/**\n * Injected dependencies for the meta-command handler. Keeping these behind\n * an explicit interface lets tests drive the handler with plain objects and\n * `vi.fn()` spies, no pty or auth file required.\n */\nexport interface MetaDeps {\n getBridge: () => PtyBridge\n resetBridge: () => Promise<void>\n session: ShellSession\n getAuth: () => AuthData | null\n targetHost: string\n isPending: () => boolean\n write: (s: string) => void\n}\n\n/**\n * Short one-line descriptions printed by `:help`. Ordered alphabetically so\n * the list stays stable as we add more commands.\n */\nconst HELP_ENTRIES: Array<[string, string]> = [\n [':help', 'Show available meta-commands.'],\n [':reset', 'Kill and respawn the bash child (preserves grants + audit).'],\n [':status', 'Show session, host, bash pid, and auth state.'],\n]\n\n/**\n * Format a millisecond duration as `Nh Nm Ns` with components omitted only\n * from the left. We always show seconds so zero-ish uptimes still read sanely.\n */\nfunction formatUptime(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000))\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n const parts: string[] = []\n if (hours > 0)\n parts.push(`${hours}h`)\n if (hours > 0 || minutes > 0)\n parts.push(`${minutes}m`)\n parts.push(`${seconds}s`)\n return parts.join(' ')\n}\n\n/**\n * Build the async handler invoked by `ShellRepl.onMetaCommand`. Return value\n * semantics match the REPL contract: `true` means the line was consumed and\n * the REPL should redraw the prompt; `false` would fall through to shell\n * dispatch, but every branch here currently returns `true`.\n */\nexport function createMetaCommandHandler(deps: MetaDeps): (line: string) => Promise<boolean> {\n return async (line: string) => {\n const trimmed = line.trim()\n\n if (trimmed === ':help') {\n deps.write('Available meta-commands:\\n')\n for (const [name, desc] of HELP_ENTRIES)\n deps.write(` ${name.padEnd(10)} ${desc}\\n`)\n return true\n }\n\n if (trimmed === ':status') {\n const uptime = formatUptime(Date.now() - deps.session.startedAt)\n const pid = deps.getBridge().pid\n deps.write(`Session: ${deps.session.id} (uptime ${uptime})\\n`)\n deps.write(`Host: ${deps.targetHost}\\n`)\n deps.write(`Bash: pid ${pid}\\n`)\n\n const auth = deps.getAuth()\n if (!auth) {\n deps.write('User: (not logged in)\\n')\n return true\n }\n deps.write(`User: ${auth.email}\\n`)\n deps.write(`IdP: ${auth.idp}\\n`)\n const expiresMs = auth.expires_at * 1000\n if (expiresMs <= Date.now()) {\n deps.write('Token: EXPIRED\\n')\n }\n else {\n const iso = new Date(expiresMs).toISOString()\n deps.write(`Token: valid until ${iso}\\n`)\n }\n return true\n }\n\n if (trimmed === ':reset') {\n if (deps.isPending()) {\n deps.write('Cannot reset while a command is running. Wait or press Ctrl-C.\\n')\n return true\n }\n await deps.resetBridge()\n const newPid = deps.getBridge().pid\n deps.write(`Bash reset. New pid: ${newPid}\\n`)\n return true\n }\n\n deps.write(`Unknown meta-command \\`${trimmed}\\`. Try \\`:help\\`.\\n`)\n return true\n }\n}\n","import { randomBytes } from 'node:crypto'\nimport type { IPty } from '@lydell/node-pty'\nimport * as pty from '@lydell/node-pty'\n\n/**\n * Frame returned when the bash child finishes executing a line and is ready\n * for the next one. The `output` is everything bash printed since the last\n * frame, with the marker line stripped.\n */\nexport interface CompletedLineFrame {\n output: string\n exitCode: number\n}\n\n/**\n * Callbacks the PtyBridge delivers to its owner. `onOutput` fires with\n * streaming pty output chunks (marker content already stripped). `onLineDone`\n * fires once per completed shell line with the cumulative output and the\n * exit code extracted from the prompt marker. `onExit` fires when the bash\n * child itself has terminated (user typed `exit`, or the process died).\n */\nexport interface PtyBridgeEvents {\n onOutput: (chunk: string) => void\n onLineDone: (frame: CompletedLineFrame) => void\n onExit: (exitCode: number, signal: number | undefined) => void\n}\n\n/**\n * Wraps a persistent bash child running inside a pty. Each time the frontend\n * feeds a line via `writeLine`, the bridge streams bash's stdout back through\n * `onOutput` until it detects the marker-bearing PS1 — at which point the\n * line is considered complete and `onLineDone` fires with the captured\n * output and the exit code.\n *\n * The marker format embedded in PS1 is:\n * __APES_<random-hex>__:<exit-code>:__END__\n * The leading token is a 32-char random hex so output collisions are\n * effectively impossible. `<exit-code>` is `$?` at prompt render time.\n *\n * The marker itself is stripped from the output stream before it is\n * forwarded to the caller, so consumers never see it on their terminal.\n */\nexport class PtyBridge {\n private readonly marker: string\n /** Compiled once. Captures the exit code in group 1. */\n private readonly markerRegex: RegExp\n private readonly term: IPty\n private readonly events: PtyBridgeEvents\n /** Bytes received since the last completed line. Searched for the marker. */\n private pending = ''\n /**\n * Accumulated output for the current in-flight line. Streams to `onOutput`\n * live as chunks arrive, and is handed to `onLineDone` in full when the\n * marker is matched. Resets at that point.\n */\n private currentLineBuffer = ''\n /** True until bash prints its first marker-prompt. */\n private readyForFirstLine = false\n private awaitingInitialPrompt: ((value: void) => void) | null = null\n\n constructor(events: PtyBridgeEvents, options: { cols?: number, rows?: number, cwd?: string } = {}) {\n this.events = events\n this.marker = randomBytes(16).toString('hex')\n // Example match: __APES_abc123…__:0:__END__\n // The `\\r?\\n?` tolerates line endings around the marker depending on\n // how bash renders PS1 on a fresh line.\n this.markerRegex = new RegExp(\n `__APES_${this.marker}__:(-?\\\\d+):__END__\\\\r?\\\\n?`,\n )\n\n const cols = options.cols ?? process.stdout.columns ?? 80\n const rows = options.rows ?? process.stdout.rows ?? 24\n\n // We spawn bash as `--login -i` so the user's ~/.bash_profile / ~/.bashrc\n // runs and they get their aliases/functions. Trade-off: if the user\n // overrides PS1 in their rcfile, our marker detection breaks. We protect\n // against that by re-exporting PS1 via PROMPT_COMMAND on every prompt.\n //\n // PROMPT_COMMAND also runs `stty -echo` so the pty line discipline stops\n // echoing user input back to us. Without this, every line the frontend\n // writes to the pty gets echoed into the output stream — causing the\n // command to appear twice in the user's terminal (once from readline,\n // once from the pty echo). The frontend already owns its own display of\n // what the user typed; the pty echo is redundant and surprising.\n // Interactive TUI apps (vim/less/top) set their own termios when they\n // start, so they are unaffected.\n // Strip APES_SHELL_WRAPPER so nested `apes` invocations inside the pty\n // don't self-detect as ape-shell mode and reject their argv. The wrapper\n // script sets this marker on the parent ape-shell process; leaking it\n // into bash would cause `apes <subcommand>` at the REPL prompt to print\n // \"unsupported invocation\" instead of running.\n const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env\n this.term = pty.spawn('bash', ['--login', '-i'], {\n name: 'xterm-256color',\n cols,\n rows,\n cwd: options.cwd ?? process.cwd(),\n env: {\n ...inheritedEnv,\n // Force our marker PS1 on every prompt and keep pty echo off —\n // both survive .bashrc overrides because PROMPT_COMMAND runs\n // before each prompt.\n PROMPT_COMMAND: `stty -echo 2>/dev/null; PS1='__APES_${this.marker}__:$?:__END__'`,\n // Also set it initially so the very first prompt carries the marker.\n PS1: `__APES_${this.marker}__:$?:__END__`,\n PS2: '> ',\n // Silence bash-specific onboarding that would pollute our output.\n BASH_SILENCE_DEPRECATION_WARNING: '1',\n },\n })\n\n this.term.onData(chunk => this.handleData(chunk))\n this.term.onExit(({ exitCode, signal }) => {\n this.events.onExit(exitCode, signal)\n })\n }\n\n /**\n * Resolves once bash has printed its very first marker-bearing prompt,\n * which means it has finished sourcing ~/.bashrc and is ready to accept\n * input. Callers should await this before sending the first line.\n */\n waitForReady(): Promise<void> {\n if (this.readyForFirstLine)\n return Promise.resolve()\n return new Promise((resolve) => {\n this.awaitingInitialPrompt = resolve\n })\n }\n\n /**\n * Write a shell command line to bash's stdin. The caller must ensure the\n * line has already been approved by the grant flow. The bridge does NOT\n * validate or filter the line.\n */\n writeLine(line: string): void {\n // Trim trailing newlines and always append exactly one, so pasting a\n // multi-line string works naturally.\n const clean = line.replace(/\\r?\\n+$/, '')\n this.term.write(`${clean}\\n`)\n }\n\n /**\n * Raw passthrough write — used for forwarding user keystrokes during\n * interactive output mode (e.g. while vim is running).\n */\n writeRaw(data: string): void {\n this.term.write(data)\n }\n\n /** Resize the underlying pty. Called on SIGWINCH. */\n resize(cols: number, rows: number): void {\n this.term.resize(cols, rows)\n }\n\n /** Kill the bash child. Called on Ctrl-D / shell exit. */\n kill(signal?: string): void {\n this.term.kill(signal)\n }\n\n /** Process id of the bash child, for logging / debugging. */\n get pid(): number {\n return this.term.pid\n }\n\n /** Exposed for tests that want to look at the raw marker. */\n getMarkerForTest(): string {\n return this.marker\n }\n\n // ----- internals -----\n\n private handleData(chunk: string): void {\n this.pending += chunk\n\n // Walk through any complete marker matches. Each marker ends the current\n // in-flight line; the `before` slice is its final output.\n for (;;) {\n const match = this.pending.match(this.markerRegex)\n if (!match || match.index === undefined)\n break\n\n const before = this.pending.slice(0, match.index)\n const exitCode = Number(match[1])\n // Stream the pre-marker portion live (for REPL display) AND fold it\n // into the per-line buffer so the `onLineDone` frame is complete.\n if (before.length > 0) {\n this.currentLineBuffer += before\n if (this.readyForFirstLine)\n this.events.onOutput(before)\n }\n // Drop the matched marker and everything before it from the buffer\n this.pending = this.pending.slice(match.index + match[0].length)\n\n if (!this.readyForFirstLine) {\n // Bootstrap prompt: discard any bash startup noise we captured and\n // signal readiness. onLineDone does NOT fire for the implicit\n // first prompt — that would confuse consumers.\n this.readyForFirstLine = true\n this.currentLineBuffer = ''\n const resolve = this.awaitingInitialPrompt\n this.awaitingInitialPrompt = null\n if (resolve)\n resolve()\n continue\n }\n\n // Real command completion: hand over the accumulated line buffer.\n const frame = { output: this.currentLineBuffer, exitCode }\n this.currentLineBuffer = ''\n this.events.onLineDone(frame)\n }\n\n // No more markers in the buffer. If we're past bootstrap, stream any\n // complete lines that have accumulated so the user sees live output,\n // and keep any partial tail in `pending` in case the marker is still\n // mid-chunk.\n if (!this.readyForFirstLine)\n return\n\n const lastNewline = this.pending.lastIndexOf('\\n')\n if (lastNewline >= 0) {\n const ready = this.pending.slice(0, lastNewline + 1)\n this.pending = this.pending.slice(lastNewline + 1)\n if (ready.length > 0) {\n this.currentLineBuffer += ready\n this.events.onOutput(ready)\n }\n }\n }\n}\n","import { createInterface } from 'node:readline'\nimport type { Interface as ReadlineInterface } from 'node:readline'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport consola from 'consola'\nimport { checkMultiLineStatus } from './multi-line.js'\n\n/**\n * Where the REPL persists its input history across sessions. Lives alongside\n * the existing apes config so we don't add a new top-level dotfile.\n */\nconst HISTORY_FILE = join(homedir(), '.config', 'apes', 'shell-history')\n\n/**\n * Primary and continuation prompts. PS1 shows when the REPL is ready for a\n * fresh command; PS2 shows when bash syntax is incomplete and the user needs\n * to keep typing (e.g., inside a for-loop or heredoc).\n */\nconst PS1 = 'apes$ '\nconst PS2 = '> '\n\n/**\n * Event sink for the REPL. Keeps the loop decoupled from I/O sinks so tests\n * can feed lines and observe outcomes without touching real stdin/stdout.\n */\nexport interface ReplEvents {\n /**\n * Fires when a complete shell line has been accumulated (possibly across\n * multiple input lines via continuation). The handler is responsible for\n * actually running the line — typically by handing it to the grant flow\n * and then to the pty bridge. In M2 this is just logged; the real wiring\n * lands in M3/M4.\n */\n onLine: (line: string) => void | Promise<void>\n\n /**\n * Fires when the REPL is about to exit (user pressed Ctrl-D or called\n * `stop`). Gives owners a chance to tear down resources.\n */\n onExit: () => void | Promise<void>\n\n /**\n * Called when the user enters a line starting with `:` while not in the\n * middle of a multi-line buffer. Return true if handled (REPL skips shell\n * dispatch and redraws the prompt). Return false/undefined to fall through\n * to normal shell dispatch.\n */\n onMetaCommand?: (line: string) => boolean | Promise<boolean>\n}\n\n/**\n * Optional overrides for tests. Real usage defaults to `process.stdin`,\n * `process.stdout`, and the standard history file path.\n */\nexport interface ReplOptions {\n input?: NodeJS.ReadableStream\n output?: NodeJS.WritableStream\n historyFile?: string\n /** Disables the session-start banner — handy in tests. */\n quiet?: boolean\n}\n\n/**\n * Interactive REPL loop for `ape-shell`. Implements the state machine:\n *\n * PROMPT → (line entered) → MULTILINE? → (continue) → PROMPT\n * │\n * └─── complete → emit onLine → PROMPT\n *\n * The loop uses node:readline for line editing, history, and signal\n * handling. Multi-line detection runs a `bash -n` dry-parse on Enter to\n * decide whether to fold the input into a longer buffer or submit it.\n *\n * The REPL does NOT know about pty, grants, or bash internals. Owners wire\n * those in via the `onLine` callback. This keeps M2 independently testable.\n */\nexport class ShellRepl {\n private readonly events: ReplEvents\n private readonly input: NodeJS.ReadableStream\n private readonly output: NodeJS.WritableStream\n private readonly quiet: boolean\n private rl: ReadlineInterface | null = null\n /** Accumulated input across multi-line continuations. */\n private buffer = ''\n /** Whether the REPL has called `stop`. */\n private stopped = false\n\n constructor(events: ReplEvents, options: ReplOptions = {}) {\n this.events = events\n this.input = options.input ?? process.stdin\n this.output = options.output ?? process.stdout\n this.quiet = options.quiet ?? false\n }\n\n /**\n * Start the REPL. Resolves when the user ends the session (Ctrl-D) or\n * `stop()` is called from outside. Errors bubble out of `onLine` and\n * cause the line to be rejected, but do NOT tear down the REPL.\n */\n async run(): Promise<void> {\n if (this.stopped)\n return\n\n this.rl = createInterface({\n input: this.input,\n output: this.output,\n prompt: PS1,\n historySize: 1000,\n // Enable tab completion fallback (file names + history) via default.\n terminal: true,\n })\n\n if (!this.quiet)\n this.writeBanner()\n\n this.safePrompt(PS1)\n\n return new Promise<void>((resolve) => {\n this.rl!.on('line', async (line) => {\n try {\n await this.handleLine(line)\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.error(`Shell error: ${msg}`)\n this.resetBuffer()\n this.safePrompt(PS1)\n }\n })\n\n this.rl!.on('SIGINT', () => {\n // Ctrl-C in prompt mode: clear the buffer, draw a new prompt.\n if (this.buffer.length > 0)\n this.output.write('\\n')\n this.resetBuffer()\n this.safePrompt(PS1)\n })\n\n this.rl!.on('close', async () => {\n // Ctrl-D or stop() — we're done.\n this.stopped = true\n await this.events.onExit()\n resolve()\n })\n })\n }\n\n /**\n * Request the REPL to stop cleanly. Equivalent to the user pressing\n * Ctrl-D. Typically called during shutdown or after a fatal error.\n */\n stop(): void {\n if (this.rl)\n this.rl.close()\n }\n\n // ----- internals -----\n\n private writeBanner(): void {\n this.output.write('apes interactive shell\\n')\n this.output.write('Ctrl-D to exit.\\n')\n this.output.write('\\n')\n }\n\n private async handleLine(rawLine: string): Promise<void> {\n // Meta-commands are single-line, bypass multi-line + grant + shell\n // entirely. Only fire from an empty buffer (never mid-multiline) so\n // `:foo` inside a heredoc/for-loop body is still treated as bash input.\n if (\n this.buffer.length === 0\n && rawLine.trim().startsWith(':')\n && this.events.onMetaCommand\n ) {\n const handled = await this.events.onMetaCommand(rawLine.trim())\n if (handled) {\n this.safePrompt(PS1)\n return\n }\n }\n\n // Append the new line to the existing buffer. Use a literal newline so\n // bash sees the multi-line structure correctly on `bash -n`.\n this.buffer = this.buffer.length === 0\n ? rawLine\n : `${this.buffer}\\n${rawLine}`\n\n const status = checkMultiLineStatus(this.buffer)\n\n if (status.kind === 'continue') {\n this.safePrompt(PS2)\n return\n }\n\n if (status.kind === 'error') {\n this.output.write(`${status.message}\\n`)\n this.resetBuffer()\n this.safePrompt(PS1)\n return\n }\n\n // Complete. Hand off to the owner.\n const completeLine = this.buffer\n this.resetBuffer()\n\n // If the buffer was pure whitespace, skip the callback and re-prompt.\n if (completeLine.trim().length === 0) {\n this.safePrompt(PS1)\n return\n }\n\n await this.events.onLine(completeLine)\n\n // Back to prompt mode. Owners of onLine can await and drive their own\n // output before we show the next prompt.\n this.safePrompt(PS1)\n }\n\n /**\n * Draw a prompt, but only if the readline interface is still alive.\n * The onLine callback may have triggered `stop()` (e.g., bash exited),\n * in which case setPrompt/prompt would throw ERR_USE_AFTER_CLOSE.\n */\n private safePrompt(prompt: string): void {\n if (this.stopped || !this.rl)\n return\n try {\n this.rl.setPrompt(prompt)\n this.rl.prompt()\n }\n catch (err) {\n // Swallow ERR_USE_AFTER_CLOSE which races with shutdown.\n const code = (err as NodeJS.ErrnoException)?.code\n if (code !== 'ERR_USE_AFTER_CLOSE')\n throw err\n }\n }\n\n private resetBuffer(): void {\n this.buffer = ''\n }\n}\n\nexport { HISTORY_FILE }\n","import { spawnSync } from 'node:child_process'\n\n/**\n * Result of checking whether a shell command buffer is syntactically\n * complete. The REPL uses this to decide whether to submit the line for\n * execution or to keep reading more lines with a continuation prompt.\n */\nexport type MultiLineStatus =\n | { kind: 'complete' }\n | { kind: 'continue' }\n | { kind: 'error', message: string }\n\n/**\n * Patterns that bash writes to stderr when it encounters a syntax error\n * caused by an incomplete construct (missing `done`, unterminated quote,\n * unclosed `$(` or heredoc, etc.). If any of these patterns match, the\n * buffer is treated as incomplete and the REPL shows a continuation prompt\n * instead of reporting an error.\n */\nconst CONTINUE_PATTERNS: RegExp[] = [\n /syntax error: unexpected end of file/i,\n /unexpected end of file/i,\n /here-document.+delimited by end-of-file/i,\n /unexpected EOF while looking for matching/i,\n]\n\n/**\n * Detects an unterminated here-document in the buffer. `bash -n` accepts\n * these as \"complete\" (it treats end-of-input as the delimiter) so we have\n * to catch them ourselves to match interactive bash's behavior of showing\n * a continuation prompt until the user types the delimiter on its own line.\n *\n * Matches `<<` or `<<-` followed by an optionally-quoted identifier. For\n * each match we scan the remaining lines of the buffer for a line whose\n * content (after leading tabs, which `<<-` strips) equals the delimiter.\n * If no closing line is found, the heredoc is unterminated → continue.\n */\nfunction hasUnterminatedHeredoc(buffer: string): boolean {\n // Match <<-? optional whitespace, optional ' or \" around the word.\n // We deliberately don't try to parse bash quoting fully — this is a\n // best-effort heuristic that catches the common cases.\n const pattern = /<<(-?)\\s*(['\"]?)([A-Z_]\\w*)\\2/gi\n for (const match of buffer.matchAll(pattern)) {\n const stripTabs = match[1] === '-'\n const delimiter = match[3]!\n // Look at every line AFTER the match start\n const afterMatch = buffer.slice((match.index ?? 0) + match[0].length)\n const lines = afterMatch.split('\\n').slice(1) // skip the rest of the opening line\n const terminated = lines.some((line) => {\n const compare = stripTabs ? line.replace(/^\\t+/, '') : line\n return compare === delimiter\n })\n if (!terminated)\n return true\n }\n return false\n}\n\n/**\n * Check whether a (possibly multi-line) shell command buffer forms a\n * complete statement. Runs `bash -n -c <buffer>` in a separate process —\n * `-n` makes bash parse without executing, so there are no side effects.\n *\n * Design note: we spawn bash once per check. That's typically <10ms on\n * modern machines and only happens when the user presses Enter, not per\n * keystroke, so the cost is negligible.\n */\nexport function checkMultiLineStatus(buffer: string): MultiLineStatus {\n if (buffer.trim().length === 0)\n return { kind: 'complete' } // empty line is a no-op, treat as complete\n\n // Heredoc check first — bash -n accepts unterminated heredocs but an\n // interactive shell should ask for more input until the delimiter line.\n if (hasUnterminatedHeredoc(buffer))\n return { kind: 'continue' }\n\n const result = spawnSync('bash', ['-n', '-c', buffer], {\n stdio: ['ignore', 'ignore', 'pipe'],\n encoding: 'utf-8',\n })\n\n if (result.error) {\n // Bash itself couldn't be spawned — treat as a hard error so the REPL\n // can surface it. This should be vanishingly rare.\n return { kind: 'error', message: `Failed to spawn bash for syntax check: ${result.error.message}` }\n }\n\n if (result.status === 0)\n return { kind: 'complete' }\n\n const stderr = result.stderr || ''\n for (const pattern of CONTINUE_PATTERNS) {\n if (pattern.test(stderr))\n return { kind: 'continue' }\n }\n\n // Anything else is a genuine syntax error — caller should show it and\n // reset the buffer instead of accumulating more lines.\n return { kind: 'error', message: stderr.trim() || 'Syntax error' }\n}\n","import { randomBytes } from 'node:crypto'\nimport { appendAuditLog } from '../shapes/index.js'\n\n/**\n * A single interactive ape-shell session. Tracks a stable session id plus a\n * monotonic sequence number so individual lines can be correlated back to\n * their session in the audit log.\n */\nexport class ShellSession {\n readonly id: string\n readonly startedAt: number\n private lineSeq = 0\n\n constructor(options: { host: string, requester: string }) {\n this.id = randomBytes(8).toString('hex')\n this.startedAt = Date.now()\n appendAuditLog({\n action: 'shell-session-start',\n session_id: this.id,\n host: options.host,\n requester: options.requester,\n })\n }\n\n /**\n * Record a granted line that was approved for execution. Called after the\n * grant flow returns approval but before (or right after) bash runs the\n * command. Stores the grant id so the line can be traced back to the\n * specific grant that authorized it.\n */\n logLineGranted(params: {\n line: string\n grantId: string\n grantMode: 'adapter' | 'session' | 'self'\n }): number {\n const seq = ++this.lineSeq\n appendAuditLog({\n action: 'shell-session-line',\n session_id: this.id,\n seq,\n line: params.line,\n grant_id: params.grantId,\n grant_mode: params.grantMode,\n status: 'executing',\n })\n return seq\n }\n\n /** Record the final exit code of a previously-granted line. */\n logLineDone(params: { seq: number, exitCode: number }): void {\n appendAuditLog({\n action: 'shell-session-line-done',\n session_id: this.id,\n seq: params.seq,\n exit_code: params.exitCode,\n })\n }\n\n /** Record that a line was denied by the grant flow and never reached bash. */\n logLineDenied(params: { line: string, reason: string }): void {\n const seq = ++this.lineSeq\n appendAuditLog({\n action: 'shell-session-line',\n session_id: this.id,\n seq,\n line: params.line,\n status: 'denied',\n reason: params.reason,\n })\n }\n\n /** Record session termination. Fires on clean Ctrl-D or bash death. */\n close(): void {\n appendAuditLog({\n action: 'shell-session-end',\n session_id: this.id,\n duration_ms: Date.now() - this.startedAt,\n lines: this.lineSeq,\n })\n }\n}\n"],"mappings":"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","names":["consola","token","consola","consola","randomBytes","randomBytes","consola"]}
@@ -9,12 +9,13 @@ import {
9
9
  import {
10
10
  apiFetch,
11
11
  getGrantsEndpoint
12
- } from "./chunk-N3THIFIS.js";
12
+ } from "./chunk-QJJ7DG5C.js";
13
13
  import {
14
14
  getAuthToken,
15
15
  getIdpUrl,
16
16
  getRequesterIdentity
17
17
  } from "./chunk-OBF7IMQ2.js";
18
+ import "./chunk-7OCVIDC7.js";
18
19
 
19
20
  // src/commands/mcp/server.ts
20
21
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -303,7 +304,7 @@ function registerAdapterTools(server) {
303
304
  async function startMcpServer(transport, port) {
304
305
  const server = new McpServer({
305
306
  name: "apes",
306
- version: true ? "1.7.1" : "0.1.0"
307
+ version: true ? "1.8.1" : "0.1.0"
307
308
  });
308
309
  registerStaticTools(server);
309
310
  registerAdapterTools(server);
@@ -331,4 +332,4 @@ async function startMcpServer(transport, port) {
331
332
  export {
332
333
  startMcpServer
333
334
  };
334
- //# sourceMappingURL=server-JBBJXDDS.js.map
335
+ //# sourceMappingURL=server-EKUVZDZC.js.map