agent-sh 0.13.6 → 0.13.7
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/cli/args.d.ts +2 -0
- package/dist/cli/args.js +90 -0
- package/dist/cli/index.js +3 -154
- package/dist/cli/shell-env.d.ts +2 -0
- package/dist/cli/shell-env.js +61 -0
- package/dist/core/index.js +3 -2
- package/package.json +2 -1
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
2
|
+
const HELP_TEXT = `agent-sh — a shell-first terminal where AI is one keystroke away
|
|
3
|
+
|
|
4
|
+
Usage: agent-sh [options]
|
|
5
|
+
agent-sh init [--force] Scaffold ~/.agent-sh/ (settings, examples, AGENTS.md)
|
|
6
|
+
agent-sh install <spec> [--force] Install an extension (bundled name, file:, npm:, github:)
|
|
7
|
+
agent-sh uninstall <name> Remove an installed extension
|
|
8
|
+
agent-sh list List installed extensions
|
|
9
|
+
agent-sh auth login [provider] Store an API key for a built-in provider
|
|
10
|
+
agent-sh auth logout <provider> Remove a stored key
|
|
11
|
+
agent-sh auth list Show configured providers
|
|
12
|
+
|
|
13
|
+
Provider Profiles:
|
|
14
|
+
--provider <name> Use a provider from ~/.agent-sh/settings.json
|
|
15
|
+
--model <name> Override default model
|
|
16
|
+
|
|
17
|
+
Direct LLM API:
|
|
18
|
+
--api-key <key> API key for OpenAI-compatible provider (or set OPENAI_API_KEY)
|
|
19
|
+
--base-url <url> Base URL for API (or set OPENAI_BASE_URL)
|
|
20
|
+
|
|
21
|
+
General Options:
|
|
22
|
+
--backend <name> Agent backend to launch (e.g. ash, pi); overrides settings.defaultBackend for this session
|
|
23
|
+
--shell <path> Shell to use (default: $SHELL or /bin/bash)
|
|
24
|
+
-e, --extensions Extensions to load (comma-separated, repeatable)
|
|
25
|
+
-h, --help Show this help
|
|
26
|
+
-V, --version Print version and exit
|
|
27
|
+
|
|
28
|
+
Environment Variables:
|
|
29
|
+
OPENAI_API_KEY API key for LLM provider
|
|
30
|
+
OPENAI_BASE_URL Base URL override (e.g., http://localhost:11434/v1 for Ollama)
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
# Use a configured provider
|
|
34
|
+
agent-sh --provider openai
|
|
35
|
+
|
|
36
|
+
# Direct API access
|
|
37
|
+
agent-sh --api-key "$KEY" --model gpt-4o
|
|
38
|
+
|
|
39
|
+
# Local model via Ollama
|
|
40
|
+
agent-sh --base-url http://localhost:11434/v1 --model llama3
|
|
41
|
+
|
|
42
|
+
Inside the shell:
|
|
43
|
+
Type normally Commands run in your real shell
|
|
44
|
+
> <query> Ask the AI agent (it decides how to help)
|
|
45
|
+
> /help Show available slash commands
|
|
46
|
+
Ctrl-C Cancel agent response (or signal shell as usual)
|
|
47
|
+
`;
|
|
48
|
+
export function parseArgs(argv, env = process.env) {
|
|
49
|
+
let model;
|
|
50
|
+
let extensions;
|
|
51
|
+
let provider;
|
|
52
|
+
let backend;
|
|
53
|
+
let shell = env.SHELL || "/bin/bash";
|
|
54
|
+
let apiKey = env.OPENAI_API_KEY;
|
|
55
|
+
let baseURL = env.OPENAI_BASE_URL;
|
|
56
|
+
for (let i = 0; i < argv.length; i++) {
|
|
57
|
+
const arg = argv[i];
|
|
58
|
+
if (arg === "--model" && argv[i + 1]) {
|
|
59
|
+
model = argv[++i];
|
|
60
|
+
}
|
|
61
|
+
else if (arg === "--api-key" && argv[i + 1]) {
|
|
62
|
+
apiKey = argv[++i];
|
|
63
|
+
}
|
|
64
|
+
else if (arg === "--base-url" && argv[i + 1]) {
|
|
65
|
+
baseURL = argv[++i];
|
|
66
|
+
}
|
|
67
|
+
else if (arg === "--provider" && argv[i + 1]) {
|
|
68
|
+
provider = argv[++i];
|
|
69
|
+
}
|
|
70
|
+
else if (arg === "--backend" && argv[i + 1]) {
|
|
71
|
+
backend = argv[++i];
|
|
72
|
+
}
|
|
73
|
+
else if (arg === "--shell" && argv[i + 1]) {
|
|
74
|
+
shell = argv[++i];
|
|
75
|
+
}
|
|
76
|
+
else if ((arg === "--extensions" || arg === "-e") && argv[i + 1]) {
|
|
77
|
+
const exts = argv[++i].split(",").map((s) => s.trim());
|
|
78
|
+
extensions = extensions ? [...extensions, ...exts] : exts;
|
|
79
|
+
}
|
|
80
|
+
else if (arg === "--version" || arg === "-V") {
|
|
81
|
+
console.log(PACKAGE_VERSION);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
else if (arg === "--help" || arg === "-h") {
|
|
85
|
+
console.log(HELP_TEXT);
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { shell, model, extensions, apiKey, baseURL, provider, backend };
|
|
90
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
2
|
import { activateShell, registerShellHandlers } from "../shell/index.js";
|
|
4
|
-
import { pickStrategy, FALLBACK_STRATEGY } from "../shell/strategies/index.js";
|
|
5
3
|
import { activateAgent } from "../agent/index.js";
|
|
6
4
|
import { createCore } from "../core/index.js";
|
|
7
5
|
import { palette as p } from "../utils/palette.js";
|
|
@@ -11,158 +9,9 @@ import { getSettings } from "../core/settings.js";
|
|
|
11
9
|
import { dispatchSubcommand } from "./subcommands.js";
|
|
12
10
|
import { suggestBridgeFor } from "./install.js";
|
|
13
11
|
import { anyProviderConfigured } from "./auth/keys.js";
|
|
14
|
-
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
15
12
|
import { clearOpost } from "../utils/tty.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* This picks up env vars exported in .zshrc/.bashrc that the
|
|
19
|
-
* Node.js process doesn't have (e.g. when launched from an IDE).
|
|
20
|
-
*/
|
|
21
|
-
async function captureShellEnvAsync(shell) {
|
|
22
|
-
return new Promise((resolve) => {
|
|
23
|
-
let settled = false;
|
|
24
|
-
const done = (result) => {
|
|
25
|
-
if (settled)
|
|
26
|
-
return;
|
|
27
|
-
settled = true;
|
|
28
|
-
resolve(result);
|
|
29
|
-
};
|
|
30
|
-
try {
|
|
31
|
-
const strategy = pickStrategy(shell) ?? FALLBACK_STRATEGY;
|
|
32
|
-
const captureCmd = strategy.envCaptureCommand();
|
|
33
|
-
const child = spawn(shell, ["-l", "-c", captureCmd], {
|
|
34
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
35
|
-
timeout: 5000,
|
|
36
|
-
});
|
|
37
|
-
let output = "";
|
|
38
|
-
child.stdout?.on("data", (data) => {
|
|
39
|
-
output += data.toString("utf-8");
|
|
40
|
-
});
|
|
41
|
-
child.on("close", (code) => {
|
|
42
|
-
clearTimeout(timer);
|
|
43
|
-
if (code !== 0 || !output) {
|
|
44
|
-
done({});
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const env = {};
|
|
48
|
-
for (const entry of output.split("\0")) {
|
|
49
|
-
const eq = entry.indexOf("=");
|
|
50
|
-
if (eq > 0)
|
|
51
|
-
env[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
52
|
-
}
|
|
53
|
-
done(env);
|
|
54
|
-
});
|
|
55
|
-
child.on("error", () => {
|
|
56
|
-
clearTimeout(timer);
|
|
57
|
-
done({});
|
|
58
|
-
});
|
|
59
|
-
const timer = setTimeout(() => {
|
|
60
|
-
child.kill("SIGTERM");
|
|
61
|
-
done({});
|
|
62
|
-
}, 5000);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
done({});
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
function mergeShellEnv(baseEnv, shellEnv) {
|
|
70
|
-
const merged = { ...baseEnv };
|
|
71
|
-
for (const [key, value] of Object.entries(shellEnv)) {
|
|
72
|
-
if (!(key in merged) || !merged[key]) {
|
|
73
|
-
merged[key] = value;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return merged;
|
|
77
|
-
}
|
|
78
|
-
function parseArgs(argv) {
|
|
79
|
-
let model;
|
|
80
|
-
let extensions;
|
|
81
|
-
let provider;
|
|
82
|
-
let backend;
|
|
83
|
-
let shell = process.env.SHELL || "/bin/bash";
|
|
84
|
-
let apiKey = process.env.OPENAI_API_KEY;
|
|
85
|
-
let baseURL = process.env.OPENAI_BASE_URL;
|
|
86
|
-
for (let i = 0; i < argv.length; i++) {
|
|
87
|
-
const arg = argv[i];
|
|
88
|
-
if (arg === "--model" && argv[i + 1]) {
|
|
89
|
-
model = argv[++i];
|
|
90
|
-
}
|
|
91
|
-
else if (arg === "--api-key" && argv[i + 1]) {
|
|
92
|
-
apiKey = argv[++i];
|
|
93
|
-
}
|
|
94
|
-
else if (arg === "--base-url" && argv[i + 1]) {
|
|
95
|
-
baseURL = argv[++i];
|
|
96
|
-
}
|
|
97
|
-
else if (arg === "--provider" && argv[i + 1]) {
|
|
98
|
-
provider = argv[++i];
|
|
99
|
-
}
|
|
100
|
-
else if (arg === "--backend" && argv[i + 1]) {
|
|
101
|
-
backend = argv[++i];
|
|
102
|
-
}
|
|
103
|
-
else if (arg === "--shell" && argv[i + 1]) {
|
|
104
|
-
shell = argv[++i];
|
|
105
|
-
}
|
|
106
|
-
else if ((arg === "--extensions" || arg === "-e") && argv[i + 1]) {
|
|
107
|
-
const exts = argv[++i].split(",").map(s => s.trim());
|
|
108
|
-
extensions = extensions ? [...extensions, ...exts] : exts;
|
|
109
|
-
}
|
|
110
|
-
else if (arg === "--version" || arg === "-V") {
|
|
111
|
-
console.log(PACKAGE_VERSION);
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
else if (arg === "--help" || arg === "-h") {
|
|
115
|
-
console.log(`agent-sh — a shell-first terminal where AI is one keystroke away
|
|
116
|
-
|
|
117
|
-
Usage: agent-sh [options]
|
|
118
|
-
agent-sh init [--force] Scaffold ~/.agent-sh/ (settings, examples, AGENTS.md)
|
|
119
|
-
agent-sh install <spec> [--force] Install an extension (bundled name, file:, npm:, github:)
|
|
120
|
-
agent-sh uninstall <name> Remove an installed extension
|
|
121
|
-
agent-sh list List installed extensions
|
|
122
|
-
agent-sh auth login [provider] Store an API key for a built-in provider
|
|
123
|
-
agent-sh auth logout <provider> Remove a stored key
|
|
124
|
-
agent-sh auth list Show configured providers
|
|
125
|
-
|
|
126
|
-
Provider Profiles:
|
|
127
|
-
--provider <name> Use a provider from ~/.agent-sh/settings.json
|
|
128
|
-
--model <name> Override default model
|
|
129
|
-
|
|
130
|
-
Direct LLM API:
|
|
131
|
-
--api-key <key> API key for OpenAI-compatible provider (or set OPENAI_API_KEY)
|
|
132
|
-
--base-url <url> Base URL for API (or set OPENAI_BASE_URL)
|
|
133
|
-
|
|
134
|
-
General Options:
|
|
135
|
-
--backend <name> Agent backend to launch (e.g. ash, pi); overrides settings.defaultBackend for this session
|
|
136
|
-
--shell <path> Shell to use (default: $SHELL or /bin/bash)
|
|
137
|
-
-e, --extensions Extensions to load (comma-separated, repeatable)
|
|
138
|
-
-h, --help Show this help
|
|
139
|
-
-V, --version Print version and exit
|
|
140
|
-
|
|
141
|
-
Environment Variables:
|
|
142
|
-
OPENAI_API_KEY API key for LLM provider
|
|
143
|
-
OPENAI_BASE_URL Base URL override (e.g., http://localhost:11434/v1 for Ollama)
|
|
144
|
-
|
|
145
|
-
Examples:
|
|
146
|
-
# Use a configured provider
|
|
147
|
-
agent-sh --provider openai
|
|
148
|
-
|
|
149
|
-
# Direct API access
|
|
150
|
-
agent-sh --api-key "$KEY" --model gpt-4o
|
|
151
|
-
|
|
152
|
-
# Local model via Ollama
|
|
153
|
-
agent-sh --base-url http://localhost:11434/v1 --model llama3
|
|
154
|
-
|
|
155
|
-
Inside the shell:
|
|
156
|
-
Type normally Commands run in your real shell
|
|
157
|
-
> <query> Ask the AI agent (it decides how to help)
|
|
158
|
-
> /help Show available slash commands
|
|
159
|
-
Ctrl-C Cancel agent response (or signal shell as usual)
|
|
160
|
-
`);
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { shell, model, extensions, apiKey, baseURL, provider, backend };
|
|
165
|
-
}
|
|
13
|
+
import { parseArgs } from "./args.js";
|
|
14
|
+
import { captureShellEnvAsync, mergeShellEnv } from "./shell-env.js";
|
|
166
15
|
async function main() {
|
|
167
16
|
const rawArgs = process.argv.slice(2);
|
|
168
17
|
if (await dispatchSubcommand(rawArgs))
|
|
@@ -293,6 +142,7 @@ async function main() {
|
|
|
293
142
|
"\n " + hint + "\n" +
|
|
294
143
|
borderLine + "\n\n");
|
|
295
144
|
}
|
|
145
|
+
await core.activateBackend(config.backend);
|
|
296
146
|
// 100ms sidesteps macOS SIGTTOU during fg-pgrp handoff.
|
|
297
147
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
298
148
|
shell = activateShell(extCtx, {
|
|
@@ -318,7 +168,6 @@ async function main() {
|
|
|
318
168
|
},
|
|
319
169
|
returnToSelf: true,
|
|
320
170
|
});
|
|
321
|
-
core.activateBackend(config.backend);
|
|
322
171
|
// ── Terminal lifecycle ────────────────────────────────────────
|
|
323
172
|
process.on("SIGTERM", cleanup);
|
|
324
173
|
process.on("SIGHUP", cleanup);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { pickStrategy, FALLBACK_STRATEGY } from "../shell/strategies/index.js";
|
|
3
|
+
export async function captureShellEnvAsync(shell) {
|
|
4
|
+
if (process.env.AGENT_SH_SKIP_SHELL_ENV)
|
|
5
|
+
return {};
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
let settled = false;
|
|
8
|
+
const done = (result) => {
|
|
9
|
+
if (settled)
|
|
10
|
+
return;
|
|
11
|
+
settled = true;
|
|
12
|
+
resolve(result);
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
const strategy = pickStrategy(shell) ?? FALLBACK_STRATEGY;
|
|
16
|
+
const captureCmd = strategy.envCaptureCommand();
|
|
17
|
+
const child = spawn(shell, ["-l", "-c", captureCmd], {
|
|
18
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
19
|
+
timeout: 5000,
|
|
20
|
+
});
|
|
21
|
+
let output = "";
|
|
22
|
+
child.stdout?.on("data", (data) => {
|
|
23
|
+
output += data.toString("utf-8");
|
|
24
|
+
});
|
|
25
|
+
child.on("close", (code) => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
if (code !== 0 || !output) {
|
|
28
|
+
done({});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const env = {};
|
|
32
|
+
for (const entry of output.split("\0")) {
|
|
33
|
+
const eq = entry.indexOf("=");
|
|
34
|
+
if (eq > 0)
|
|
35
|
+
env[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
36
|
+
}
|
|
37
|
+
done(env);
|
|
38
|
+
});
|
|
39
|
+
child.on("error", () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
done({});
|
|
42
|
+
});
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
child.kill("SIGTERM");
|
|
45
|
+
done({});
|
|
46
|
+
}, 5000);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
done({});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function mergeShellEnv(baseEnv, shellEnv) {
|
|
54
|
+
const merged = { ...baseEnv };
|
|
55
|
+
for (const [key, value] of Object.entries(shellEnv)) {
|
|
56
|
+
if (!(key in merged) || !merged[key]) {
|
|
57
|
+
merged[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return merged;
|
|
61
|
+
}
|
package/dist/core/index.js
CHANGED
|
@@ -56,8 +56,9 @@ export function createCore(config) {
|
|
|
56
56
|
bus.emit("ui:error", { message: `Unknown backend: ${name}` });
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
for (const [otherName, otherBackend] of backends) {
|
|
60
|
+
if (otherName !== name)
|
|
61
|
+
otherBackend.kill();
|
|
61
62
|
}
|
|
62
63
|
await backend.start?.();
|
|
63
64
|
activeBackendName = name;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sh",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.7",
|
|
4
4
|
"description": "A shell-first terminal where AI is one keystroke away",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
"dev": "tsx src/cli/index.ts",
|
|
122
122
|
"build": "tsc",
|
|
123
123
|
"start": "node dist/cli/index.js",
|
|
124
|
+
"test": "npm run build && node --import tsx --test $(find tests -name '*.test.ts' -type f)",
|
|
124
125
|
"prepare": "test -d dist || npm run build"
|
|
125
126
|
},
|
|
126
127
|
"keywords": [
|