aem-ext-daemon 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/shell.d.ts +6 -8
- package/dist/capabilities/shell.js +59 -126
- package/dist/dispatcher.js +1 -3
- package/package.json +1 -1
|
@@ -18,13 +18,11 @@ export declare function checkAio(): string;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function runAio(args: string[], cwd: string, requestId: string, connection: DaemonConnection): Promise<string>;
|
|
20
20
|
/**
|
|
21
|
-
* Start an interactive command
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Start an interactive command by opening a real terminal window on the
|
|
22
|
+
* user's machine. Returns immediately with instructions — the agent
|
|
23
|
+
* should poll for completion (e.g., check for .aio file).
|
|
24
|
+
*
|
|
25
|
+
* This is needed because inquirer-based CLIs (like `aio app create`)
|
|
26
|
+
* require a real TTY for their interactive prompts.
|
|
24
27
|
*/
|
|
25
28
|
export declare function spawnInteractive(command: string, cwd: string): Promise<string>;
|
|
26
|
-
/**
|
|
27
|
-
* Send input to the active interactive process. Waits for the process
|
|
28
|
-
* to produce more output (or go quiet again), then returns the new output.
|
|
29
|
-
*/
|
|
30
|
-
export declare function writeToProcess(input: string): Promise<string>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { execSync, spawn } from "node:child_process";
|
|
10
10
|
import fs from "node:fs";
|
|
11
11
|
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
12
13
|
const EXEC_TIMEOUT = 120_000; // 2 minutes
|
|
13
14
|
const MAX_BUFFER = 5 * 1024 * 1024; // 5MB
|
|
14
15
|
/** Resolve the user's login shell (falls back to /bin/sh). */
|
|
@@ -146,138 +147,70 @@ export function runAio(args, cwd, requestId, connection) {
|
|
|
146
147
|
});
|
|
147
148
|
}
|
|
148
149
|
// ─── Interactive process support ────────────────────────────
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
/** The single active interactive process (one per daemon). */
|
|
152
|
-
let activeProcess = null;
|
|
153
|
-
/** How long to wait for more output before assuming the process is waiting for input. */
|
|
154
|
-
const SILENCE_TIMEOUT = 3_000;
|
|
150
|
+
// Opens interactive commands in a real terminal window since
|
|
151
|
+
// inquirer-based CLIs require a TTY for their prompts.
|
|
155
152
|
/**
|
|
156
|
-
* Start an interactive command
|
|
157
|
-
*
|
|
158
|
-
*
|
|
153
|
+
* Start an interactive command by opening a real terminal window on the
|
|
154
|
+
* user's machine. Returns immediately with instructions — the agent
|
|
155
|
+
* should poll for completion (e.g., check for .aio file).
|
|
156
|
+
*
|
|
157
|
+
* This is needed because inquirer-based CLIs (like `aio app create`)
|
|
158
|
+
* require a real TTY for their interactive prompts.
|
|
159
159
|
*/
|
|
160
160
|
export function spawnInteractive(command, cwd) {
|
|
161
|
-
// Kill any existing interactive process
|
|
162
|
-
if (activeProcess && !activeProcess.done) {
|
|
163
|
-
activeProcess.child.kill("SIGTERM");
|
|
164
|
-
}
|
|
165
|
-
activeProcess = null;
|
|
166
161
|
if (!fs.existsSync(cwd)) {
|
|
167
162
|
fs.mkdirSync(cwd, { recursive: true });
|
|
168
163
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
proc.buffer += `\n[Process exited with code ${code ?? 1}]`;
|
|
211
|
-
flushAndReturn();
|
|
212
|
-
});
|
|
213
|
-
child.on("error", (err) => {
|
|
214
|
-
proc.done = true;
|
|
215
|
-
clearTimeout(silenceTimer);
|
|
216
|
-
proc.buffer += `\n[Error: ${err.message}]`;
|
|
217
|
-
flushAndReturn();
|
|
218
|
-
});
|
|
219
|
-
// Start the silence timer
|
|
220
|
-
resetTimer();
|
|
221
|
-
// Overall timeout: 5 minutes
|
|
222
|
-
setTimeout(() => {
|
|
223
|
-
if (!proc.done) {
|
|
224
|
-
child.kill("SIGTERM");
|
|
225
|
-
proc.done = true;
|
|
226
|
-
proc.buffer += "\n[Timed out after 5 minutes]";
|
|
227
|
-
flushAndReturn();
|
|
164
|
+
const platform = os.platform();
|
|
165
|
+
if (platform === "darwin") {
|
|
166
|
+
// macOS: write a temp script and open it in Terminal.app
|
|
167
|
+
const scriptPath = path.join(os.tmpdir(), `aio-interactive-${Date.now()}.sh`);
|
|
168
|
+
fs.writeFileSync(scriptPath, [
|
|
169
|
+
"#!/bin/bash",
|
|
170
|
+
`cd "${cwd}"`,
|
|
171
|
+
command,
|
|
172
|
+
'echo ""',
|
|
173
|
+
'echo "✓ Done! You can close this terminal window."',
|
|
174
|
+
'read -p "Press Enter to close..."',
|
|
175
|
+
].join("\n"), { mode: 0o755 });
|
|
176
|
+
try {
|
|
177
|
+
execSync(`open -a Terminal.app "${scriptPath}"`, {
|
|
178
|
+
encoding: "utf-8",
|
|
179
|
+
timeout: 10_000,
|
|
180
|
+
});
|
|
181
|
+
return Promise.resolve(`[INTERACTIVE] Opened a new Terminal window to run: ${command}\n` +
|
|
182
|
+
`Working directory: ${cwd}\n\n` +
|
|
183
|
+
`The user needs to complete the interactive prompts in that Terminal window.\n` +
|
|
184
|
+
`After they finish, check for the .aio file to confirm: ls -la .aio\n` +
|
|
185
|
+
`DO NOT proceed until the .aio file exists.`);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return Promise.resolve(`Error opening Terminal: ${err.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (platform === "linux") {
|
|
192
|
+
// Try common Linux terminal emulators
|
|
193
|
+
const terminals = ["gnome-terminal", "xfce4-terminal", "konsole", "xterm"];
|
|
194
|
+
for (const term of terminals) {
|
|
195
|
+
try {
|
|
196
|
+
execSync(`which ${term}`, { encoding: "utf-8" });
|
|
197
|
+
execSync(`${term} -- bash -c 'cd "${cwd}" && ${command} && echo Done && read -p "Press Enter..."'`, {
|
|
198
|
+
encoding: "utf-8",
|
|
199
|
+
timeout: 10_000,
|
|
200
|
+
});
|
|
201
|
+
return Promise.resolve(`[INTERACTIVE] Opened ${term} to run: ${command}\n` +
|
|
202
|
+
`Working directory: ${cwd}\n\n` +
|
|
203
|
+
`The user needs to complete the interactive prompts in that terminal.\n` +
|
|
204
|
+
`After they finish, check for the .aio file: ls -la .aio`);
|
|
228
205
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
* Send input to the active interactive process. Waits for the process
|
|
234
|
-
* to produce more output (or go quiet again), then returns the new output.
|
|
235
|
-
*/
|
|
236
|
-
export function writeToProcess(input) {
|
|
237
|
-
if (!activeProcess || activeProcess.done) {
|
|
238
|
-
return Promise.resolve(activeProcess
|
|
239
|
-
? `[Process already exited with code ${activeProcess.exitCode}]`
|
|
240
|
-
: "[No interactive process running. Use interactive_bash to start one.]");
|
|
206
|
+
catch {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
241
210
|
}
|
|
242
|
-
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (resolved)
|
|
248
|
-
return;
|
|
249
|
-
resolved = true;
|
|
250
|
-
clearTimeout(silenceTimer);
|
|
251
|
-
proc.child.stdout?.off("data", onData);
|
|
252
|
-
proc.child.stderr?.off("data", onData);
|
|
253
|
-
proc.child.off("close", onClose);
|
|
254
|
-
resolve(output);
|
|
255
|
-
};
|
|
256
|
-
const resetTimer = () => {
|
|
257
|
-
clearTimeout(silenceTimer);
|
|
258
|
-
silenceTimer = setTimeout(() => {
|
|
259
|
-
if (!proc.done) {
|
|
260
|
-
const output = proc.buffer;
|
|
261
|
-
proc.buffer = "";
|
|
262
|
-
finish(output || "(waiting for input...)");
|
|
263
|
-
}
|
|
264
|
-
}, SILENCE_TIMEOUT);
|
|
265
|
-
};
|
|
266
|
-
const onData = () => resetTimer();
|
|
267
|
-
const onClose = () => {
|
|
268
|
-
// Small delay to capture any final output
|
|
269
|
-
setTimeout(() => {
|
|
270
|
-
const output = proc.buffer;
|
|
271
|
-
proc.buffer = "";
|
|
272
|
-
finish(output || "(no output)");
|
|
273
|
-
}, 200);
|
|
274
|
-
};
|
|
275
|
-
proc.child.stdout?.on("data", onData);
|
|
276
|
-
proc.child.stderr?.on("data", onData);
|
|
277
|
-
proc.child.once("close", onClose);
|
|
278
|
-
// Write the input
|
|
279
|
-
proc.child.stdin?.write(input + "\n");
|
|
280
|
-
// Start silence timer
|
|
281
|
-
resetTimer();
|
|
282
|
-
});
|
|
211
|
+
// Fallback: tell the user to run it manually
|
|
212
|
+
return Promise.resolve(`[INTERACTIVE] This command requires interactive input and must be run in a terminal.\n\n` +
|
|
213
|
+
`Please ask the user to open their terminal and run:\n\n` +
|
|
214
|
+
` cd "${cwd}"\n ${command}\n\n` +
|
|
215
|
+
`After they finish, check for the .aio file: ls -la .aio`);
|
|
283
216
|
}
|
package/dist/dispatcher.js
CHANGED
|
@@ -119,9 +119,7 @@ async function dispatchInner(command, payload, connection) {
|
|
|
119
119
|
: getWorkspaceRoot();
|
|
120
120
|
return shellCap.spawnInteractive(payload.command, cwd);
|
|
121
121
|
}
|
|
122
|
-
|
|
123
|
-
return shellCap.writeToProcess(payload.input);
|
|
124
|
-
}
|
|
122
|
+
// shell:stdin removed — interactive commands now open a terminal window
|
|
125
123
|
// ─── Skills ────────────────────────────────────────
|
|
126
124
|
case "skills:sync": {
|
|
127
125
|
const workspace = getWorkspaceRoot();
|