atom-lite 0.5.1 → 0.6.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/CHANGELOG.md +24 -1
- package/README.md +40 -107
- package/dist/agent/loop.d.ts +6 -0
- package/dist/agent/loop.js +45 -11
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +1 -0
- package/dist/agent/system-prompt.js +23 -0
- package/dist/agent/system-prompt.js.map +1 -0
- package/dist/agent/types.d.ts +11 -0
- package/dist/cli/app.js +203 -41
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/diff-view.d.ts +18 -0
- package/dist/cli/diff-view.js +66 -0
- package/dist/cli/diff-view.js.map +1 -0
- package/dist/cli/panel.js +8 -8
- package/dist/cli/panel.js.map +1 -1
- package/dist/cli/status-line.d.ts +2 -0
- package/dist/cli/status-line.js +9 -1
- package/dist/cli/status-line.js.map +1 -1
- package/dist/cli/tool-box.d.ts +1 -1
- package/dist/cli/tool-box.js +44 -39
- package/dist/cli/tool-box.js.map +1 -1
- package/dist/config/defaults.d.ts +4 -5
- package/dist/config/defaults.js +6 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/load.js +29 -3
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +10 -14
- package/dist/config/schema.js +4 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/context/manager.js +1 -1
- package/dist/context/manager.js.map +1 -1
- package/dist/runtime/modes.d.ts +3 -0
- package/dist/runtime/modes.js +8 -0
- package/dist/runtime/modes.js.map +1 -0
- package/dist/runtime/tool-executor.js +10 -0
- package/dist/runtime/tool-executor.js.map +1 -1
- package/dist/skills/registry.js +8 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/bash.js +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/glob.d.ts +2 -0
- package/dist/tools/glob.js +97 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +2 -0
- package/dist/tools/grep.js +290 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +2 -0
- package/dist/types.d.ts +0 -3
- package/dist/types.js +1 -4
- package/dist/types.js.map +1 -1
- package/package.json +3 -2
- package/skills/atom-lite-extensions/SKILL.md +240 -0
- package/skills/caveman/SKILL.md +89 -0
- package/skills/code-review/SKILL.md +46 -0
- package/skills/optimization/SKILL.md +51 -0
package/dist/cli/app.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { loadConfig } from "../config/load.js";
|
|
2
|
+
import { DEFAULT_SYSTEM_PROMPT } from "../agent/system-prompt.js";
|
|
2
3
|
import { defaultAgentDir, userConfigDir } from "../config/paths.js";
|
|
3
4
|
import { saveProviderSelection, saveTheme, } from "../config/write.js";
|
|
4
5
|
import { resolveApiKey, saveApiKey } from "../config/credentials.js";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { writeFileSync, existsSync } from "node:fs";
|
|
7
|
-
import { execSync } from "node:child_process";
|
|
8
|
+
import { execSync, spawn } from "node:child_process";
|
|
9
|
+
import { defaultShell, terminateProcessTree } from "../security/process.js";
|
|
8
10
|
import { openSearchablePicker } from "./picker.js";
|
|
9
11
|
import { openCommandPalette } from "./palette.js";
|
|
10
12
|
import { bareCommandName, createCommandRegistry } from "./commands.js";
|
|
@@ -13,7 +15,6 @@ import { SecretInput } from "./secret-input.js";
|
|
|
13
15
|
import { StatusLine } from "./status-line.js";
|
|
14
16
|
import { Activity } from "./activity.js";
|
|
15
17
|
import { ToolBox } from "./tool-box.js";
|
|
16
|
-
import { ApprovalPrompt } from "./approval-prompt.js";
|
|
17
18
|
import { Banner } from "./banner.js";
|
|
18
19
|
import { formatContextUsage, knownContextWindow, } from "../providers/context-window.js";
|
|
19
20
|
import { StreamText } from "./stream-text.js";
|
|
@@ -27,7 +28,7 @@ import { ProjectTrustPrompt } from "./trust-prompt.js";
|
|
|
27
28
|
import { formatSessionList, formatSessionSearch, searchSessions, sessionTitleFromEvents, } from "../sessions/search.js";
|
|
28
29
|
import { providerSwitchNotice, sessionHistoryLines, sessionSelectionFromEvents, } from "../sessions/history.js";
|
|
29
30
|
import { blockAtLine, createMouseCapture, handleMouseEvent, parseSgrMouse, shouldEnableMouse, } from "./mouse.js";
|
|
30
|
-
import { readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool } from "../tools/index.js";
|
|
31
|
+
import { readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool, grepTool, globTool } from "../tools/index.js";
|
|
31
32
|
import { McpManager, formatMcpStatus, formatMcpDoctor, createMcpTools } from "../mcp/index.js";
|
|
32
33
|
import { createProvider } from "../providers/factory.js";
|
|
33
34
|
import { availableProviderNames, getPreset, listModelsForProvider, materializeProviderConfig, probeApiKey, providerLabel, PROVIDER_PRESETS, } from "../providers/catalog.js";
|
|
@@ -124,7 +125,7 @@ export async function run(args) {
|
|
|
124
125
|
modelByProvider.set(provider.id, modelOverride);
|
|
125
126
|
const useTools = !positional.includes("__notools__");
|
|
126
127
|
const builtInTools = useTools
|
|
127
|
-
? [readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool]
|
|
128
|
+
? [readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool, grepTool, globTool]
|
|
128
129
|
: [];
|
|
129
130
|
const interactive = process.stdin.isTTY === true && !jsonMode;
|
|
130
131
|
// one-shot task from positional or stdin
|
|
@@ -244,7 +245,7 @@ export async function run(args) {
|
|
|
244
245
|
return results;
|
|
245
246
|
};
|
|
246
247
|
const contextManager = new ContextManager({
|
|
247
|
-
systemPrompt:
|
|
248
|
+
systemPrompt: DEFAULT_SYSTEM_PROMPT,
|
|
248
249
|
maxInputTokens: config.context.maxInputTokens ?? null,
|
|
249
250
|
reserveOutputTokens: config.context.reserveOutputTokens,
|
|
250
251
|
compactAtPercent: config.context.compactAtPercent,
|
|
@@ -256,7 +257,7 @@ export async function run(args) {
|
|
|
256
257
|
});
|
|
257
258
|
let sessionTrusted = false;
|
|
258
259
|
const approvals = {
|
|
259
|
-
mode:
|
|
260
|
+
mode: "ask",
|
|
260
261
|
// Installed below once the interactive UI helpers exist.
|
|
261
262
|
handler: undefined,
|
|
262
263
|
isTrusted: () => sessionTrusted,
|
|
@@ -271,6 +272,9 @@ export async function run(args) {
|
|
|
271
272
|
workspace: process.cwd(),
|
|
272
273
|
tools: allTools,
|
|
273
274
|
approvals,
|
|
275
|
+
mode: config.runtime.mode,
|
|
276
|
+
maxTokensPerSession: config.runtime.maxTokensPerSession,
|
|
277
|
+
maxTokensPerTurn: config.runtime.maxTokensPerTurn,
|
|
274
278
|
contextManager,
|
|
275
279
|
extensionDispatch,
|
|
276
280
|
bashConcurrency: config.runtime.bashConcurrency,
|
|
@@ -359,10 +363,12 @@ export async function run(args) {
|
|
|
359
363
|
const header = new Text(theme.tokens.dim(headerLabel()), 1, 0);
|
|
360
364
|
const statusBar = new StatusLine(theme);
|
|
361
365
|
const activity = new Activity({ theme, onChange: () => updateStatus() });
|
|
366
|
+
let currentMode = config.runtime.mode;
|
|
362
367
|
statusBar.setParts({
|
|
363
368
|
left: `${provider.id}/${provider.model ?? "default"}`,
|
|
364
369
|
tokens: "tokens: ?",
|
|
365
370
|
effort: "effort: medium",
|
|
371
|
+
mode: currentMode,
|
|
366
372
|
activity: activity.text(),
|
|
367
373
|
});
|
|
368
374
|
const selectListTheme = {
|
|
@@ -397,18 +403,59 @@ export async function run(args) {
|
|
|
397
403
|
return;
|
|
398
404
|
commandRegistry = createCommandRegistry({ skills: cachedSkillRegistry().list() });
|
|
399
405
|
registrySkillSignature = sig;
|
|
400
|
-
input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd()));
|
|
406
|
+
input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd(), fdPath));
|
|
401
407
|
}
|
|
402
|
-
|
|
408
|
+
const fdPath = (() => {
|
|
409
|
+
for (const bin of ["fd", "fdfind"]) {
|
|
410
|
+
try {
|
|
411
|
+
const found = execSync(`command -v ${bin}`, {
|
|
412
|
+
encoding: "utf8",
|
|
413
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
414
|
+
}).trim();
|
|
415
|
+
if (found)
|
|
416
|
+
return found;
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// ignore
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
})();
|
|
424
|
+
input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd(), fdPath));
|
|
425
|
+
// Skills should autocomplete (fill the input) rather than execute immediately
|
|
426
|
+
// when selected from the autocomplete list; Enter on a typed command still
|
|
427
|
+
// runs them. Convert a confirming Enter into a Tab completion for skills.
|
|
428
|
+
const originalHandleInput = input.handleInput.bind(input);
|
|
429
|
+
input.handleInput = (data) => {
|
|
430
|
+
try {
|
|
431
|
+
if ((data === "\r" || data === "\n") &&
|
|
432
|
+
input.autocompleteState &&
|
|
433
|
+
input.autocompleteList) {
|
|
434
|
+
const selected = input.autocompleteList.getSelectedItem?.();
|
|
435
|
+
const name = selected?.value ?? selected?.name ?? selected?.label;
|
|
436
|
+
const cmd = name ? commandRegistry.get(String(name).replace(/^\//, "")) : undefined;
|
|
437
|
+
if (cmd?.bareInvokable) {
|
|
438
|
+
originalHandleInput("\t");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// Fall through to default handling on any introspection failure.
|
|
445
|
+
}
|
|
446
|
+
originalHandleInput(data);
|
|
447
|
+
};
|
|
403
448
|
const root = new VStack([]);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
root.addChild(
|
|
407
|
-
root.addChild(
|
|
449
|
+
// Keep fixed chrome (spacer, input, status, header, dropdown) from being
|
|
450
|
+
// collapsed when the transcript grows; only the output region may shrink.
|
|
451
|
+
root.addChild(output, { grow: 1, shrink: 1, minSize: 0 });
|
|
452
|
+
root.addChild(new Spacer(1), { grow: 0, shrink: 0, minSize: 1 });
|
|
453
|
+
root.addChild(input, { grow: 0, shrink: 0 });
|
|
454
|
+
root.addChild(statusBar, { grow: 0, shrink: 0 });
|
|
408
455
|
// folder:branch info below the status line, dimmed
|
|
409
|
-
root.addChild(header);
|
|
456
|
+
root.addChild(header, { grow: 0, shrink: 0 });
|
|
410
457
|
const dropdownContainer = new Container();
|
|
411
|
-
root.addChild(dropdownContainer);
|
|
458
|
+
root.addChild(dropdownContainer, { grow: 0, shrink: 0 });
|
|
412
459
|
tui.setLayoutRoot(root);
|
|
413
460
|
tui.setFocus(input);
|
|
414
461
|
if (!noColor) {
|
|
@@ -536,6 +583,7 @@ export async function run(args) {
|
|
|
536
583
|
streamText.finalize();
|
|
537
584
|
}
|
|
538
585
|
let activeTurnController = null;
|
|
586
|
+
let shellMode = false;
|
|
539
587
|
const messageQueue = [];
|
|
540
588
|
const steeringQueue = [];
|
|
541
589
|
runtime.setSteeringQueue(steeringQueue);
|
|
@@ -549,7 +597,11 @@ export async function run(args) {
|
|
|
549
597
|
await mcpManager.syncTools();
|
|
550
598
|
runtime.setTools([...builtInTools, ...extensionTools, ...createMcpTools(mcpManager)]);
|
|
551
599
|
}
|
|
552
|
-
await runtime.runAgentTurn(prompt, session, controller.signal);
|
|
600
|
+
const turnResult = await runtime.runAgentTurn(prompt, session, controller.signal);
|
|
601
|
+
if (turnResult === "TOKEN_BUDGET_REACHED") {
|
|
602
|
+
appendLine("token budget reached; stopping", theme.tokens.warning);
|
|
603
|
+
tui.requestRender();
|
|
604
|
+
}
|
|
553
605
|
}
|
|
554
606
|
catch (err) {
|
|
555
607
|
if (err instanceof Error && err.message === "USER_CANCELLED") {
|
|
@@ -569,12 +621,94 @@ export async function run(args) {
|
|
|
569
621
|
}
|
|
570
622
|
}
|
|
571
623
|
}
|
|
624
|
+
/** Run a shell command directly (shell mode / `!` prefix). */
|
|
625
|
+
async function runShellCommand(command) {
|
|
626
|
+
const shell = defaultShell();
|
|
627
|
+
const flag = process.platform === "win32" ? "/c" : "-c";
|
|
628
|
+
const start = Date.now();
|
|
629
|
+
const cap = 30000;
|
|
630
|
+
let stdout = "";
|
|
631
|
+
let stderr = "";
|
|
632
|
+
let truncated = false;
|
|
633
|
+
const append = (target, chunk) => {
|
|
634
|
+
const s = chunk.toString("utf8");
|
|
635
|
+
let accepted = s;
|
|
636
|
+
if (target === "out") {
|
|
637
|
+
if (stdout.length + s.length > cap) {
|
|
638
|
+
truncated = true;
|
|
639
|
+
accepted = s.slice(0, Math.max(0, cap - stdout.length));
|
|
640
|
+
stdout += accepted;
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
stdout += s;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
if (stderr.length + s.length > cap) {
|
|
648
|
+
truncated = true;
|
|
649
|
+
accepted = s.slice(0, Math.max(0, cap - stderr.length));
|
|
650
|
+
stderr += accepted;
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
stderr += s;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (accepted) {
|
|
657
|
+
tui.requestRender();
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
try {
|
|
661
|
+
const child = spawn(shell, [flag, command], {
|
|
662
|
+
cwd: process.cwd(),
|
|
663
|
+
env: process.env,
|
|
664
|
+
detached: process.platform !== "win32",
|
|
665
|
+
});
|
|
666
|
+
child.stdout?.on("data", (c) => append("out", c));
|
|
667
|
+
child.stderr?.on("data", (c) => append("err", c));
|
|
668
|
+
const exit = await new Promise((resolve) => {
|
|
669
|
+
const timer = setTimeout(() => {
|
|
670
|
+
terminateProcessTree(child, "SIGTERM");
|
|
671
|
+
setTimeout(() => terminateProcessTree(child, "SIGKILL"), 1000).unref();
|
|
672
|
+
resolve({ code: null, signal: "SIGTERM" });
|
|
673
|
+
}, 120000);
|
|
674
|
+
timer.unref();
|
|
675
|
+
child.on("exit", (code, signal) => {
|
|
676
|
+
clearTimeout(timer);
|
|
677
|
+
resolve({ code, signal });
|
|
678
|
+
});
|
|
679
|
+
child.on("error", (err) => {
|
|
680
|
+
clearTimeout(timer);
|
|
681
|
+
resolve({ code: null, signal: null });
|
|
682
|
+
stderr = `COMMAND_FAILED: ${err instanceof Error ? err.message : String(err)}`;
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
const parts = [];
|
|
686
|
+
parts.push(`exit_code: ${exit.code ?? "null"}`);
|
|
687
|
+
if (exit.signal)
|
|
688
|
+
parts.push(`signal: ${exit.signal}`);
|
|
689
|
+
if (truncated)
|
|
690
|
+
parts.push("OUTPUT_TRUNCATED");
|
|
691
|
+
parts.push(`duration_ms: ${Date.now() - start}`);
|
|
692
|
+
if (stdout)
|
|
693
|
+
parts.push(`stdout:\n${stdout}`);
|
|
694
|
+
if (stderr)
|
|
695
|
+
parts.push(`stderr:\n${stderr}`);
|
|
696
|
+
const content = parts.join("\n");
|
|
697
|
+
appendLine(content, exit.code === 0 && !exit.signal && !stderr
|
|
698
|
+
? theme.tokens.success
|
|
699
|
+
: theme.tokens.error);
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
appendLine(`shell error: ${err instanceof Error ? err.message : String(err)}`, theme.tokens.error);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
572
705
|
function updateStatus() {
|
|
573
706
|
const usage = runtime.getContextUsage();
|
|
574
707
|
statusBar.setParts({
|
|
575
708
|
left: `${provider.id}/${currentModelLabel()}`,
|
|
576
709
|
tokens: `tokens: ${formatContextUsage(usage.used, usage.window)}`,
|
|
577
710
|
effort: `effort: ${effortLevel}`,
|
|
711
|
+
mode: currentMode,
|
|
578
712
|
activity: activity.text(),
|
|
579
713
|
});
|
|
580
714
|
tui.requestRender();
|
|
@@ -1085,13 +1219,15 @@ export async function run(args) {
|
|
|
1085
1219
|
appendLine(`skill not found: ${name}`);
|
|
1086
1220
|
return false;
|
|
1087
1221
|
}
|
|
1222
|
+
if (extra) {
|
|
1223
|
+
appendLine(`> ${extra}`, theme.tokens.user);
|
|
1224
|
+
}
|
|
1088
1225
|
session.appendMessage({ role: "system", content });
|
|
1089
1226
|
if (typeof session.appendEvent === "function") {
|
|
1090
1227
|
session.appendEvent({ type: "skill_loaded", name });
|
|
1091
1228
|
}
|
|
1092
|
-
appendLine(`Loaded skill: ${name}
|
|
1229
|
+
appendLine(`Loaded skill: ${name}`, theme.tokens.dim);
|
|
1093
1230
|
if (extra) {
|
|
1094
|
-
appendLine(`> ${extra}`);
|
|
1095
1231
|
await runTurn(extra);
|
|
1096
1232
|
}
|
|
1097
1233
|
return true;
|
|
@@ -1359,6 +1495,7 @@ export async function run(args) {
|
|
|
1359
1495
|
for (const box of toolBoxes) {
|
|
1360
1496
|
box.setExpanded(expandTools);
|
|
1361
1497
|
}
|
|
1498
|
+
transcript.invalidate();
|
|
1362
1499
|
tui.requestRender();
|
|
1363
1500
|
}
|
|
1364
1501
|
async function reloadExtensions() {
|
|
@@ -1482,6 +1619,7 @@ export async function run(args) {
|
|
|
1482
1619
|
if (!(child instanceof ToolBox))
|
|
1483
1620
|
return false;
|
|
1484
1621
|
child.toggleExpanded();
|
|
1622
|
+
transcript.invalidate();
|
|
1485
1623
|
return true;
|
|
1486
1624
|
}
|
|
1487
1625
|
tui.addInputListener((data) => {
|
|
@@ -1516,11 +1654,28 @@ export async function run(args) {
|
|
|
1516
1654
|
toggleTools();
|
|
1517
1655
|
return { consume: true };
|
|
1518
1656
|
}
|
|
1519
|
-
//
|
|
1520
|
-
if (matchesKey(data, Key.
|
|
1521
|
-
|
|
1657
|
+
// Tab toggles plan/yolo mode.
|
|
1658
|
+
if (matchesKey(data, Key.tab) && !uiModalOpen) {
|
|
1659
|
+
currentMode = currentMode === "plan" ? "yolo" : "plan";
|
|
1660
|
+
runtime.setMode(currentMode);
|
|
1661
|
+
updateStatus();
|
|
1662
|
+
appendLine(`mode: ${currentMode}`, theme.tokens.dim);
|
|
1663
|
+
tui.requestRender();
|
|
1522
1664
|
return { consume: true };
|
|
1523
1665
|
}
|
|
1666
|
+
// Escape cancels the active turn, or exits shell mode.
|
|
1667
|
+
if (matchesKey(data, Key.escape) && !uiModalOpen) {
|
|
1668
|
+
if (activeTurnController) {
|
|
1669
|
+
activeTurnController.abort();
|
|
1670
|
+
return { consume: true };
|
|
1671
|
+
}
|
|
1672
|
+
if (shellMode) {
|
|
1673
|
+
shellMode = false;
|
|
1674
|
+
appendLine("shell mode: off", theme.tokens.dim);
|
|
1675
|
+
tui.requestRender();
|
|
1676
|
+
return { consume: true };
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1524
1679
|
return undefined;
|
|
1525
1680
|
});
|
|
1526
1681
|
runtime["onEvent"] = (e) => {
|
|
@@ -1560,6 +1715,10 @@ export async function run(args) {
|
|
|
1560
1715
|
else if (e.type === "context_usage") {
|
|
1561
1716
|
updateStatus();
|
|
1562
1717
|
}
|
|
1718
|
+
else if (e.type === "budget_warning") {
|
|
1719
|
+
appendLine(`budget warning (${e.scope}): ${e.used}/${e.limit} tokens`, theme.tokens.warning);
|
|
1720
|
+
tui.requestRender();
|
|
1721
|
+
}
|
|
1563
1722
|
else if (e.type === "model_switched") {
|
|
1564
1723
|
appendLine(`switched model to ${e.provider}/${e.model}`);
|
|
1565
1724
|
if (typeof session.appendEvent === "function") {
|
|
@@ -1581,6 +1740,7 @@ export async function run(args) {
|
|
|
1581
1740
|
if (box) {
|
|
1582
1741
|
activity.setPhase("working", e.name);
|
|
1583
1742
|
box.appendLive(e.text);
|
|
1743
|
+
transcript.invalidate();
|
|
1584
1744
|
tui.requestRender();
|
|
1585
1745
|
}
|
|
1586
1746
|
}
|
|
@@ -1806,6 +1966,27 @@ export async function run(args) {
|
|
|
1806
1966
|
tui.requestRender();
|
|
1807
1967
|
return;
|
|
1808
1968
|
}
|
|
1969
|
+
// Shell mode: every line is a shell command. `!` alone (or as first char)
|
|
1970
|
+
// enters shell mode; Escape exits it.
|
|
1971
|
+
if (shellMode) {
|
|
1972
|
+
const cmd = trimmed.startsWith("!") ? trimmed.slice(1).trim() : trimmed;
|
|
1973
|
+
appendLine(`$ ${cmd}`, theme.tokens.user);
|
|
1974
|
+
await runShellCommand(cmd);
|
|
1975
|
+
tui.requestRender();
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
// `!` as first character enters shell mode.
|
|
1979
|
+
if (trimmed === "!" || trimmed.startsWith("! ")) {
|
|
1980
|
+
shellMode = true;
|
|
1981
|
+
const rest = trimmed.slice(1).trim();
|
|
1982
|
+
appendLine("shell mode: on (Escape to exit)", theme.tokens.dim);
|
|
1983
|
+
if (rest) {
|
|
1984
|
+
appendLine(`$ ${rest}`, theme.tokens.user);
|
|
1985
|
+
await runShellCommand(rest);
|
|
1986
|
+
}
|
|
1987
|
+
tui.requestRender();
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1809
1990
|
refreshCommandRegistry();
|
|
1810
1991
|
if (trimmed === "/steer" || trimmed.startsWith("/steer ")) {
|
|
1811
1992
|
const text = trimmed.slice("/steer".length).trim();
|
|
@@ -1849,27 +2030,8 @@ export async function run(args) {
|
|
|
1849
2030
|
appendLine(`> ${trimmed}`, theme.tokens.user);
|
|
1850
2031
|
await runTurn(trimmed);
|
|
1851
2032
|
};
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
uiModalOpen = true;
|
|
1855
|
-
closeOverlay();
|
|
1856
|
-
dropdownContainer.clear();
|
|
1857
|
-
activity.setPhase("waiting");
|
|
1858
|
-
const prompt = new ApprovalPrompt(req, (decision) => {
|
|
1859
|
-
uiModalOpen = false;
|
|
1860
|
-
dropdownContainer.clear();
|
|
1861
|
-
tui.setFocus(input);
|
|
1862
|
-
activity.setPhase("working");
|
|
1863
|
-
if (decision === "trust-session") {
|
|
1864
|
-
appendLine("trusted this session: shell commands will run without approval");
|
|
1865
|
-
}
|
|
1866
|
-
resolve(decision);
|
|
1867
|
-
});
|
|
1868
|
-
dropdownContainer.addChild(prompt);
|
|
1869
|
-
tui.setFocus(prompt);
|
|
1870
|
-
tui.requestRender();
|
|
1871
|
-
})
|
|
1872
|
-
: undefined;
|
|
2033
|
+
// Approval prompts are superseded by plan/yolo modes.
|
|
2034
|
+
approvals.handler = undefined;
|
|
1873
2035
|
process.on("SIGTERM", () => {
|
|
1874
2036
|
void mcpManager.close().finally(() => {
|
|
1875
2037
|
tui.stop();
|