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.
Files changed (50) hide show
  1. package/README.md +659 -0
  2. package/dist/acp-client.d.ts +76 -0
  3. package/dist/acp-client.js +507 -0
  4. package/dist/context-manager.d.ts +45 -0
  5. package/dist/context-manager.js +405 -0
  6. package/dist/core.d.ts +41 -0
  7. package/dist/core.js +76 -0
  8. package/dist/event-bus.d.ts +140 -0
  9. package/dist/event-bus.js +79 -0
  10. package/dist/executor.d.ts +31 -0
  11. package/dist/executor.js +116 -0
  12. package/dist/extension-loader.d.ts +16 -0
  13. package/dist/extension-loader.js +164 -0
  14. package/dist/extensions/file-autocomplete.d.ts +2 -0
  15. package/dist/extensions/file-autocomplete.js +63 -0
  16. package/dist/extensions/shell-recall.d.ts +9 -0
  17. package/dist/extensions/shell-recall.js +8 -0
  18. package/dist/extensions/slash-commands.d.ts +2 -0
  19. package/dist/extensions/slash-commands.js +105 -0
  20. package/dist/extensions/tui-renderer.d.ts +2 -0
  21. package/dist/extensions/tui-renderer.js +354 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +159 -0
  24. package/dist/input-handler.d.ts +48 -0
  25. package/dist/input-handler.js +302 -0
  26. package/dist/output-parser.d.ts +55 -0
  27. package/dist/output-parser.js +166 -0
  28. package/dist/shell.d.ts +54 -0
  29. package/dist/shell.js +219 -0
  30. package/dist/types.d.ts +71 -0
  31. package/dist/types.js +1 -0
  32. package/dist/utils/ansi.d.ts +12 -0
  33. package/dist/utils/ansi.js +23 -0
  34. package/dist/utils/box-frame.d.ts +21 -0
  35. package/dist/utils/box-frame.js +60 -0
  36. package/dist/utils/diff-renderer.d.ts +20 -0
  37. package/dist/utils/diff-renderer.js +506 -0
  38. package/dist/utils/diff.d.ts +24 -0
  39. package/dist/utils/diff.js +122 -0
  40. package/dist/utils/file-watcher.d.ts +31 -0
  41. package/dist/utils/file-watcher.js +101 -0
  42. package/dist/utils/markdown.d.ts +39 -0
  43. package/dist/utils/markdown.js +248 -0
  44. package/dist/utils/palette.d.ts +32 -0
  45. package/dist/utils/palette.js +36 -0
  46. package/dist/utils/tool-display.d.ts +33 -0
  47. package/dist/utils/tool-display.js +141 -0
  48. package/examples/extensions/interactive-prompts.ts +161 -0
  49. package/examples/extensions/solarized-theme.ts +27 -0
  50. 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
+ }