agent-sh 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +659 -0
- package/dist/acp-client.d.ts +76 -0
- package/dist/acp-client.js +507 -0
- package/dist/context-manager.d.ts +45 -0
- package/dist/context-manager.js +405 -0
- package/dist/core.d.ts +41 -0
- package/dist/core.js +76 -0
- package/dist/event-bus.d.ts +140 -0
- package/dist/event-bus.js +79 -0
- package/dist/executor.d.ts +31 -0
- package/dist/executor.js +116 -0
- package/dist/extension-loader.d.ts +16 -0
- package/dist/extension-loader.js +164 -0
- package/dist/extensions/file-autocomplete.d.ts +2 -0
- package/dist/extensions/file-autocomplete.js +63 -0
- package/dist/extensions/shell-recall.d.ts +9 -0
- package/dist/extensions/shell-recall.js +8 -0
- package/dist/extensions/slash-commands.d.ts +2 -0
- package/dist/extensions/slash-commands.js +105 -0
- package/dist/extensions/tui-renderer.d.ts +2 -0
- package/dist/extensions/tui-renderer.js +354 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +159 -0
- package/dist/input-handler.d.ts +48 -0
- package/dist/input-handler.js +302 -0
- package/dist/output-parser.d.ts +55 -0
- package/dist/output-parser.js +166 -0
- package/dist/shell.d.ts +54 -0
- package/dist/shell.js +219 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.js +1 -0
- package/dist/utils/ansi.d.ts +12 -0
- package/dist/utils/ansi.js +23 -0
- package/dist/utils/box-frame.d.ts +21 -0
- package/dist/utils/box-frame.js +60 -0
- package/dist/utils/diff-renderer.d.ts +20 -0
- package/dist/utils/diff-renderer.js +506 -0
- package/dist/utils/diff.d.ts +24 -0
- package/dist/utils/diff.js +122 -0
- package/dist/utils/file-watcher.d.ts +31 -0
- package/dist/utils/file-watcher.js +101 -0
- package/dist/utils/markdown.d.ts +39 -0
- package/dist/utils/markdown.js +248 -0
- package/dist/utils/palette.d.ts +32 -0
- package/dist/utils/palette.js +36 -0
- package/dist/utils/tool-display.d.ts +33 -0
- package/dist/utils/tool-display.js +141 -0
- package/examples/extensions/interactive-prompts.ts +161 -0
- package/examples/extensions/solarized-theme.ts +27 -0
- package/package.json +72 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive permission prompts extension.
|
|
3
|
+
*
|
|
4
|
+
* Adds permission gates for tool calls and file writes.
|
|
5
|
+
* Without this extension, agent-sh runs in yolo mode (auto-approve).
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* # Load by short name (built-in):
|
|
9
|
+
* agent-sh --extensions interactive-prompts
|
|
10
|
+
*
|
|
11
|
+
* # Or copy to ~/.agent-sh/extensions/ for permanent use:
|
|
12
|
+
* cp examples/extensions/interactive-prompts.ts ~/.agent-sh/extensions/
|
|
13
|
+
*
|
|
14
|
+
* # Or install as an npm package and load by name:
|
|
15
|
+
* agent-sh --extensions my-prompts-package
|
|
16
|
+
*/
|
|
17
|
+
import { renderDiff } from "agent-sh/utils/diff-renderer.js";
|
|
18
|
+
import { renderBoxFrame } from "agent-sh/utils/box-frame.js";
|
|
19
|
+
import { palette as p } from "agent-sh/utils/palette.js";
|
|
20
|
+
import type { ExtensionContext } from "agent-sh/types";
|
|
21
|
+
|
|
22
|
+
export default function activate({ bus }: ExtensionContext) {
|
|
23
|
+
let autoApproveWrites = false;
|
|
24
|
+
|
|
25
|
+
bus.onPipeAsync("permission:request", async (payload) => {
|
|
26
|
+
switch (payload.kind) {
|
|
27
|
+
case "tool-call":
|
|
28
|
+
return handleToolCallPermission(payload);
|
|
29
|
+
case "file-write": {
|
|
30
|
+
if (autoApproveWrites) {
|
|
31
|
+
return { ...payload, decision: { approved: true } };
|
|
32
|
+
}
|
|
33
|
+
const result = await handleFileWritePermission(payload);
|
|
34
|
+
if (result.decision.autoApprove) {
|
|
35
|
+
autoApproveWrites = true;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
return payload;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function handleToolCallPermission(payload) {
|
|
46
|
+
const options = payload.metadata.options;
|
|
47
|
+
const answer = await promptPermission(payload.title);
|
|
48
|
+
|
|
49
|
+
if (answer === "approve" || answer === "approve_all") {
|
|
50
|
+
const option = answer === "approve_all"
|
|
51
|
+
? options.find((o) => o.kind === "allow_always") ?? options.find((o) => o.kind === "allow_once")
|
|
52
|
+
: options.find((o) => o.kind === "allow_once" || o.kind === "allow_always");
|
|
53
|
+
if (option) {
|
|
54
|
+
return { ...payload, decision: { outcome: "selected", optionId: option.optionId } };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { ...payload, decision: { outcome: "cancelled" } };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function handleFileWritePermission(payload) {
|
|
61
|
+
const diff = payload.metadata.diff;
|
|
62
|
+
const filePath = payload.metadata.path;
|
|
63
|
+
const answer = await previewDiff({ path: filePath, diff });
|
|
64
|
+
if (answer === "approve") {
|
|
65
|
+
return { ...payload, decision: { approved: true } };
|
|
66
|
+
}
|
|
67
|
+
if (answer === "approve_all") {
|
|
68
|
+
return { ...payload, decision: { approved: true, autoApprove: true } };
|
|
69
|
+
}
|
|
70
|
+
return { ...payload, decision: { approved: false } };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function promptPermission(title) {
|
|
74
|
+
const termW = process.stdout.columns || 80;
|
|
75
|
+
const boxW = Math.min(84, termW);
|
|
76
|
+
|
|
77
|
+
const framed = renderBoxFrame(
|
|
78
|
+
[`${p.bold}⚠ ${title}${p.reset}`],
|
|
79
|
+
{
|
|
80
|
+
width: boxW,
|
|
81
|
+
style: "rounded",
|
|
82
|
+
borderColor: p.warning,
|
|
83
|
+
title: "Permission required",
|
|
84
|
+
footer: [` ${p.dim}[y]es / [n]o / [a]llow all${p.reset}`],
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
process.stdout.write("\n");
|
|
89
|
+
for (const line of framed) {
|
|
90
|
+
process.stdout.write(line + "\n");
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(" ");
|
|
93
|
+
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
const handler = (data) => {
|
|
96
|
+
const ch = data.toString("utf-8").toLowerCase();
|
|
97
|
+
process.stdin.removeListener("data", handler);
|
|
98
|
+
process.stdout.write("\n");
|
|
99
|
+
|
|
100
|
+
if (ch === "y") resolve("approve");
|
|
101
|
+
else if (ch === "a") resolve("approve_all");
|
|
102
|
+
else resolve(null);
|
|
103
|
+
};
|
|
104
|
+
process.stdin.on("data", handler);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function previewDiff(opts) {
|
|
109
|
+
const termW = process.stdout.columns || 80;
|
|
110
|
+
const boxW = Math.min(84, termW);
|
|
111
|
+
const contentW = boxW - 4;
|
|
112
|
+
const MAX_DISPLAY = 25;
|
|
113
|
+
|
|
114
|
+
const stats = opts.diff.isNewFile
|
|
115
|
+
? `(+${opts.diff.added} lines)`
|
|
116
|
+
: `(+${opts.diff.added} / -${opts.diff.removed})`;
|
|
117
|
+
const title = opts.diff.isNewFile
|
|
118
|
+
? `new: ${opts.path} ${stats}`
|
|
119
|
+
: `${opts.path} ${stats}`;
|
|
120
|
+
|
|
121
|
+
const diffLines = renderDiff(opts.diff, {
|
|
122
|
+
width: contentW,
|
|
123
|
+
filePath: opts.path,
|
|
124
|
+
maxLines: MAX_DISPLAY,
|
|
125
|
+
trueColor: true,
|
|
126
|
+
mode: "unified",
|
|
127
|
+
});
|
|
128
|
+
const content = ["", ...diffLines.slice(1), ""];
|
|
129
|
+
|
|
130
|
+
const framed = renderBoxFrame(content, {
|
|
131
|
+
width: boxW,
|
|
132
|
+
style: "rounded",
|
|
133
|
+
borderColor: p.warning,
|
|
134
|
+
title,
|
|
135
|
+
footer: [` ${p.bold}[y] Apply [n] Skip [a] Don't ask again${p.reset}`],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
process.stdout.write("\n");
|
|
139
|
+
for (const line of framed) {
|
|
140
|
+
process.stdout.write(line + "\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
const handler = (data) => {
|
|
145
|
+
const ch = data.toString("utf-8").toLowerCase();
|
|
146
|
+
process.stdin.removeListener("data", handler);
|
|
147
|
+
|
|
148
|
+
if (ch === "y") {
|
|
149
|
+
process.stdout.write(` ${p.success}✓ Applied${p.reset}\n`);
|
|
150
|
+
resolve("approve");
|
|
151
|
+
} else if (ch === "a") {
|
|
152
|
+
process.stdout.write(` ${p.success}✓ Applied (auto-approve on)${p.reset}\n`);
|
|
153
|
+
resolve("approve_all");
|
|
154
|
+
} else {
|
|
155
|
+
process.stdout.write(` ${p.error}✗ Skipped${p.reset}\n`);
|
|
156
|
+
resolve("reject");
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
process.stdin.on("data", handler);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solarized Dark theme extension.
|
|
3
|
+
*
|
|
4
|
+
* Overrides the default color palette with Solarized Dark colors.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* agent-sh -e ./examples/extensions/solarized-theme.ts
|
|
8
|
+
*
|
|
9
|
+
* # Or copy to ~/.agent-sh/extensions/ for permanent use:
|
|
10
|
+
* cp examples/extensions/solarized-theme.ts ~/.agent-sh/extensions/
|
|
11
|
+
*/
|
|
12
|
+
import type { ExtensionContext } from "agent-sh/types";
|
|
13
|
+
|
|
14
|
+
export default function activate({ setPalette }: ExtensionContext) {
|
|
15
|
+
setPalette({
|
|
16
|
+
accent: "\x1b[38;2;38;139;210m", // blue (#268bd2)
|
|
17
|
+
success: "\x1b[38;2;133;153;0m", // green (#859900)
|
|
18
|
+
warning: "\x1b[38;2;181;137;0m", // yellow (#b58900)
|
|
19
|
+
error: "\x1b[38;2;220;50;47m", // red (#dc322f)
|
|
20
|
+
muted: "\x1b[38;2;88;110;117m", // base01 (#586e75)
|
|
21
|
+
|
|
22
|
+
successBg: "\x1b[48;2;7;54;66m", // base03 with green tint
|
|
23
|
+
errorBg: "\x1b[48;2;42;30;30m", // base03 with red tint
|
|
24
|
+
successBgEmph: "\x1b[48;2;20;70;50m", // stronger green tint
|
|
25
|
+
errorBgEmph: "\x1b[48;2;70;30;30m", // stronger red tint
|
|
26
|
+
});
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-sh",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A shell-first terminal where any ACP-compatible AI agent is one keystroke away",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/core.js",
|
|
7
|
+
"types": "dist/core.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"agent-sh": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/core.d.ts",
|
|
14
|
+
"default": "./dist/core.js"
|
|
15
|
+
},
|
|
16
|
+
"./core": {
|
|
17
|
+
"types": "./dist/core.d.ts",
|
|
18
|
+
"default": "./dist/core.js"
|
|
19
|
+
},
|
|
20
|
+
"./utils/*": "./dist/utils/*",
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./dist/types.d.ts",
|
|
23
|
+
"default": "./dist/types.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"examples"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "tsx src/index.ts",
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"start": "node dist/index.js",
|
|
34
|
+
"pi": "node dist/index.js --agent pi-acp",
|
|
35
|
+
"claude": "node dist/index.js --agent claude-agent-acp",
|
|
36
|
+
"prepublishOnly": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"terminal",
|
|
40
|
+
"shell",
|
|
41
|
+
"agent",
|
|
42
|
+
"ai",
|
|
43
|
+
"acp",
|
|
44
|
+
"cli",
|
|
45
|
+
"pty",
|
|
46
|
+
"llm"
|
|
47
|
+
],
|
|
48
|
+
"author": "Yilun Guan",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/guanyilun/agent-sh.git"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/guanyilun/agent-sh",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/guanyilun/agent-sh/issues"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@agentclientprotocol/sdk": "^0.18.1",
|
|
63
|
+
"cli-highlight": "^2.1.11",
|
|
64
|
+
"marked": "^17.0.6",
|
|
65
|
+
"node-pty": "^1.2.0-beta.12",
|
|
66
|
+
"tsx": "^4.19.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^22.0.0",
|
|
70
|
+
"typescript": "^5.7.0"
|
|
71
|
+
}
|
|
72
|
+
}
|