agent-sh 0.15.0 → 0.15.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/agent/agent-loop.js +11 -8
- package/dist/agent/events.d.ts +4 -0
- package/docs/README.md +14 -0
- package/docs/agent.md +398 -0
- package/docs/architecture.md +196 -0
- package/docs/context-management.md +200 -0
- package/docs/extensions.md +951 -0
- package/docs/library.md +84 -0
- package/docs/troubleshooting.md +65 -0
- package/docs/tui-composition.md +294 -0
- package/docs/usage.md +306 -0
- package/examples/extensions/ash-scheme/package.json +1 -1
- package/examples/extensions/ashi/EXTENDING.md +2 -2
- package/examples/extensions/ashi/README.md +2 -2
- package/examples/extensions/ashi/docs/ui-surface-protocol.md +1 -1
- package/examples/extensions/ashi/package.json +5 -3
- package/examples/extensions/ashi/src/chat/tool-group.ts +3 -2
- package/examples/extensions/ashi/src/cli.ts +9 -8
- package/examples/extensions/ashi/src/dialogs.ts +16 -1
- package/examples/extensions/ashi/src/events.ts +1 -0
- package/examples/extensions/ashi/src/frontend.ts +26 -6
- package/examples/extensions/ashi/src/renderer.ts +24 -4
- package/examples/extensions/ashi/src/renderers/pi-tui/schema-mount.ts +4 -3
- package/examples/extensions/ashi/src/renderers/pi-tui/tool-group.ts +5 -8
- package/examples/extensions/ashi/src/ui.ts +11 -0
- package/examples/extensions/ashi-ink/package.json +2 -2
- package/examples/extensions/claude-code-bridge/package.json +1 -1
- package/examples/extensions/opencode-bridge/package.json +1 -1
- package/package.json +3 -1
- package/src/agent/agent-loop.ts +1566 -0
- package/src/agent/entry-format.ts +19 -0
- package/src/agent/events.ts +153 -0
- package/src/agent/extensions/rolling-history/constants.ts +1 -0
- package/src/agent/extensions/rolling-history/index.ts +202 -0
- package/src/agent/extensions/rolling-history/recall.ts +131 -0
- package/src/agent/extensions/rolling-history/strategy.ts +404 -0
- package/src/agent/host-types.ts +192 -0
- package/src/agent/index.ts +591 -0
- package/src/agent/live-view.ts +279 -0
- package/src/agent/llm-client.ts +111 -0
- package/src/agent/llm-facade.ts +43 -0
- package/src/agent/normalize-args.ts +61 -0
- package/src/agent/nuclear-form.ts +382 -0
- package/src/agent/providers/deepseek.ts +39 -0
- package/src/agent/providers/ollama.ts +92 -0
- package/src/agent/providers/openai-compatible.ts +36 -0
- package/src/agent/providers/openai.ts +52 -0
- package/src/agent/providers/opencode.ts +142 -0
- package/src/agent/providers/openrouter.ts +105 -0
- package/src/agent/providers/zai-coding-plan.ts +33 -0
- package/src/agent/session-store.ts +336 -0
- package/src/agent/skills.ts +228 -0
- package/src/agent/store.ts +310 -0
- package/src/agent/subagent.ts +305 -0
- package/src/agent/system-prompt.ts +151 -0
- package/src/agent/token-budget.ts +12 -0
- package/src/agent/tool-protocol.ts +722 -0
- package/src/agent/tool-registry.ts +66 -0
- package/src/agent/tools/bash.ts +95 -0
- package/src/agent/tools/edit-file.ts +154 -0
- package/src/agent/tools/expand-home.ts +7 -0
- package/src/agent/tools/glob.ts +108 -0
- package/src/agent/tools/grep.ts +228 -0
- package/src/agent/tools/list-skills.ts +37 -0
- package/src/agent/tools/ls.ts +81 -0
- package/src/agent/tools/pwsh.ts +140 -0
- package/src/agent/tools/read-file.ts +164 -0
- package/src/agent/tools/write-file.ts +72 -0
- package/src/agent/types.ts +149 -0
- package/src/cli/args.ts +91 -0
- package/src/cli/auth/cli.ts +244 -0
- package/src/cli/auth/discover.ts +52 -0
- package/src/cli/auth/keys.ts +143 -0
- package/src/cli/index.ts +295 -0
- package/src/cli/init.ts +74 -0
- package/src/cli/install.ts +439 -0
- package/src/cli/shell-env.ts +68 -0
- package/src/cli/subcommands.ts +24 -0
- package/src/core/event-bus.ts +252 -0
- package/src/core/extension-loader.ts +347 -0
- package/src/core/index.ts +152 -0
- package/src/core/settings.ts +398 -0
- package/src/core/types.ts +61 -0
- package/src/extensions/file-autocomplete.ts +71 -0
- package/src/extensions/index.ts +38 -0
- package/src/extensions/slash-commands/events.ts +14 -0
- package/src/extensions/slash-commands/index.ts +269 -0
- package/src/shell/events.ts +73 -0
- package/src/shell/host-types.ts +150 -0
- package/src/shell/index.ts +159 -0
- package/src/shell/input-handler.ts +505 -0
- package/src/shell/output-parser.ts +156 -0
- package/src/shell/shell-context.ts +193 -0
- package/src/shell/shell.ts +414 -0
- package/src/shell/strategies/bash.ts +83 -0
- package/src/shell/strategies/fish.ts +77 -0
- package/src/shell/strategies/index.ts +24 -0
- package/src/shell/strategies/types.ts +64 -0
- package/src/shell/strategies/zsh.ts +92 -0
- package/src/shell/terminal.ts +124 -0
- package/src/shell/tui-input-view.ts +222 -0
- package/src/shell/tui-renderer.ts +1126 -0
- package/src/utils/ansi.ts +140 -0
- package/src/utils/box-frame.ts +138 -0
- package/src/utils/compositor.ts +157 -0
- package/src/utils/diff-renderer.ts +829 -0
- package/src/utils/diff.ts +244 -0
- package/src/utils/executor.ts +305 -0
- package/src/utils/file-watcher.ts +110 -0
- package/src/utils/floating-panel.ts +1160 -0
- package/src/utils/handler-registry.ts +110 -0
- package/src/utils/line-editor.ts +636 -0
- package/src/utils/markdown.ts +437 -0
- package/src/utils/message-utils.ts +113 -0
- package/src/utils/package-version.ts +12 -0
- package/src/utils/palette.ts +64 -0
- package/src/utils/ref-counter.ts +9 -0
- package/src/utils/ripgrep-path.ts +17 -0
- package/src/utils/shell-output-spill.ts +76 -0
- package/src/utils/stream-transform.ts +292 -0
- package/src/utils/terminal-buffer.ts +213 -0
- package/src/utils/tool-display.ts +315 -0
- package/src/utils/tool-interactive.ts +71 -0
- package/src/utils/tty.ts +14 -0
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { activateShell, registerShellHandlers, type ShellHandle } from "../shell/index.js";
|
|
3
|
+
import { activateAgent } from "../agent/index.js";
|
|
4
|
+
import { createCore } from "../core/index.js";
|
|
5
|
+
import { palette as p } from "../utils/palette.js";
|
|
6
|
+
import { loadBuiltinExtensions } from "../extensions/index.js";
|
|
7
|
+
import activateRollingHistory from "../agent/extensions/rolling-history/index.js";
|
|
8
|
+
import { loadExtensions } from "../core/extension-loader.js";
|
|
9
|
+
import { getSettings } from "../core/settings.js";
|
|
10
|
+
import { dispatchSubcommand } from "./subcommands.js";
|
|
11
|
+
import { suggestBridgeFor } from "./install.js";
|
|
12
|
+
import { anyProviderConfigured } from "./auth/keys.js";
|
|
13
|
+
import { clearOpost } from "../utils/tty.js";
|
|
14
|
+
import { parseArgs } from "./args.js";
|
|
15
|
+
import { captureShellEnvAsync, mergeShellEnv } from "./shell-env.js";
|
|
16
|
+
|
|
17
|
+
declare module "../core/event-bus.js" {
|
|
18
|
+
interface BusEvents {
|
|
19
|
+
/** Startup banner collection (sync pipe). Extensions contribute
|
|
20
|
+
* labeled item lists; the CLI renders them between the product
|
|
21
|
+
* name and the help hint. */
|
|
22
|
+
"banner:collect": {
|
|
23
|
+
sections: Array<{ label: string; items: string[] }>;
|
|
24
|
+
/** Name of the backend being launched. Extensions should gate
|
|
25
|
+
* per-backend sections on this rather than settings.defaultBackend. */
|
|
26
|
+
activeBackend?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function main(): Promise<void> {
|
|
32
|
+
const rawArgs = process.argv.slice(2);
|
|
33
|
+
if (await dispatchSubcommand(rawArgs)) return;
|
|
34
|
+
|
|
35
|
+
if (process.env.AGENT_SH) {
|
|
36
|
+
console.error("agent-sh: already running inside an agent-sh session (nested sessions are not supported).");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.on("SIGTTOU", () => {});
|
|
41
|
+
process.on("SIGTTIN", () => {});
|
|
42
|
+
|
|
43
|
+
const config = parseArgs(rawArgs);
|
|
44
|
+
|
|
45
|
+
// Capture user's full shell environment
|
|
46
|
+
const baseEnv: Record<string, string> = {};
|
|
47
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
48
|
+
if (v !== undefined) baseEnv[k] = v;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const shellPath = config.shell || process.env.SHELL || "/bin/bash";
|
|
52
|
+
try {
|
|
53
|
+
const shellEnv = await captureShellEnvAsync(shellPath);
|
|
54
|
+
if (Object.keys(shellEnv).length > 0) {
|
|
55
|
+
Object.assign(baseEnv, mergeShellEnv(baseEnv, shellEnv));
|
|
56
|
+
// Expose captured env vars to process.env so extensions can read them.
|
|
57
|
+
// Only add vars not already present to avoid clobbering runtime state.
|
|
58
|
+
for (const [k, v] of Object.entries(baseEnv)) {
|
|
59
|
+
if (process.env[k] === undefined) {
|
|
60
|
+
process.env[k] = v;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (process.env.DEBUG) {
|
|
64
|
+
console.error('[agent-sh] Shell environment captured');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Ignore errors, we already have process.env as fallback
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const selectedBackend = config.backend ?? getSettings().defaultBackend ?? "ash";
|
|
72
|
+
if (selectedBackend === "ash" && !config.apiKey && !config.provider && !anyProviderConfigured()) {
|
|
73
|
+
console.error(
|
|
74
|
+
"\nagent-sh: no LLM provider configured.\n\n" +
|
|
75
|
+
" Run `agent-sh auth login` to store an API key, or\n" +
|
|
76
|
+
" export OPENAI_API_KEY / OPENROUTER_API_KEY / DEEPSEEK_API_KEY, or\n" +
|
|
77
|
+
" run `agent-sh init` for a settings.json template.\n",
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Core (frontend-agnostic) ──────────────────────────────────
|
|
83
|
+
const core = createCore(config);
|
|
84
|
+
const { bus } = core;
|
|
85
|
+
|
|
86
|
+
let agentInfo: { name: string; version: string; model?: string; provider?: string } | null = null;
|
|
87
|
+
bus.on("agent:info", (info) => {
|
|
88
|
+
agentInfo = info;
|
|
89
|
+
// Redraw so late agent:info emits (opencode-bridge after session.create) reach the prompt.
|
|
90
|
+
bus.emit("config:changed", {});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// tui-renderer subscribes to ui:error inside activateShell, after backend
|
|
94
|
+
// activation — pipe to stderr until the shell is up so boot failures surface.
|
|
95
|
+
const bootUiError = (e: { message: string }) => {
|
|
96
|
+
process.stderr.write(`agent-sh: ${e.message}\n`);
|
|
97
|
+
};
|
|
98
|
+
bus.on("ui:error", bootUiError);
|
|
99
|
+
|
|
100
|
+
// ── Interactive frontend ──────────────────────────────────────
|
|
101
|
+
if (process.env.DEBUG) {
|
|
102
|
+
console.error('[agent-sh] Setting up interactive frontend...');
|
|
103
|
+
}
|
|
104
|
+
process.stdout.write(`\x1b]0;agent-sh\x07`);
|
|
105
|
+
|
|
106
|
+
const cols = process.stdout.columns || 80;
|
|
107
|
+
const rows = process.stdout.rows || 24;
|
|
108
|
+
|
|
109
|
+
// Bound after activateShell — cleanup is wired into extCtx.quit before the
|
|
110
|
+
// shell exists, so the closure captures the var by reference.
|
|
111
|
+
let shell: ShellHandle | null = null;
|
|
112
|
+
|
|
113
|
+
const cleanup = () => {
|
|
114
|
+
core.kill();
|
|
115
|
+
shell?.kill();
|
|
116
|
+
if (process.stdin.isTTY) {
|
|
117
|
+
process.stdin.setRawMode(false);
|
|
118
|
+
}
|
|
119
|
+
process.exit(0);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const extCtx = core.extensionContext({ quit: cleanup });
|
|
123
|
+
|
|
124
|
+
// Before loadExtensions: extensions look up shell handlers at activation.
|
|
125
|
+
registerShellHandlers(extCtx);
|
|
126
|
+
activateAgent(extCtx);
|
|
127
|
+
|
|
128
|
+
// Load before spawning the shell so PS1 lands below the banner.
|
|
129
|
+
const settings = getSettings();
|
|
130
|
+
await loadBuiltinExtensions(extCtx, settings.disabledBuiltins);
|
|
131
|
+
activateRollingHistory(extCtx);
|
|
132
|
+
const loadExtensionsTimeoutMs = 10000;
|
|
133
|
+
let loadedExtensions: string[] = [];
|
|
134
|
+
await Promise.race([
|
|
135
|
+
loadExtensions(extCtx, config.extensions).then((names) => { loadedExtensions = names; }),
|
|
136
|
+
new Promise<void>((_, reject) =>
|
|
137
|
+
setTimeout(() => reject(new Error(`Extension loading timeout after ${loadExtensionsTimeoutMs}ms`)), loadExtensionsTimeoutMs)
|
|
138
|
+
),
|
|
139
|
+
]).catch((err) => {
|
|
140
|
+
console.error(`Warning: ${err.message}`);
|
|
141
|
+
});
|
|
142
|
+
core.bus.emit("core:extensions-loaded", { names: loadedExtensions });
|
|
143
|
+
|
|
144
|
+
const { names: backendNames } = core.bus.emitPipe("config:get-backends", { names: [] as string[], active: null as string | null });
|
|
145
|
+
if (backendNames.length === 0) {
|
|
146
|
+
console.error("\nagent-sh: no agent backend available.\n\n" +
|
|
147
|
+
" Export OPENROUTER_API_KEY or OPENAI_API_KEY for zero-config launch, or\n" +
|
|
148
|
+
" pass --api-key on the command line, or\n" +
|
|
149
|
+
" run `agent-sh init` for a settings.json template, or\n" +
|
|
150
|
+
" run `agent-sh install <bridge>` (e.g. pi-bridge, claude-code-bridge) to use a non-ash backend.\n");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
if (config.backend && !backendNames.includes(config.backend)) {
|
|
154
|
+
const bridge = suggestBridgeFor(config.backend);
|
|
155
|
+
const hint = bridge
|
|
156
|
+
? ` Try: agent-sh install ${bridge}\n`
|
|
157
|
+
: ` Run \`agent-sh install\` to see bundled bridge extensions.\n`;
|
|
158
|
+
console.error(`\nagent-sh: backend "${config.backend}" is not available.\n\n` +
|
|
159
|
+
` Available backends: ${backendNames.join(", ")}\n` +
|
|
160
|
+
hint);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (settings.startupBanner !== false) {
|
|
165
|
+
const termW = process.stdout.columns || 80;
|
|
166
|
+
const bannerW = Math.min(termW, 60);
|
|
167
|
+
|
|
168
|
+
const productName = `${p.accent}${p.bold}agent-sh${p.reset}`;
|
|
169
|
+
|
|
170
|
+
const backendName = config.backend && backendNames.includes(config.backend)
|
|
171
|
+
? config.backend
|
|
172
|
+
: settings.defaultBackend && backendNames.includes(settings.defaultBackend)
|
|
173
|
+
? settings.defaultBackend
|
|
174
|
+
: backendNames[0]!;
|
|
175
|
+
|
|
176
|
+
let sections = "";
|
|
177
|
+
sections += `\n\n ${p.muted}Backend:${p.reset} ${p.dim}${backendName}${p.reset}`;
|
|
178
|
+
|
|
179
|
+
const extSections = bus.emitPipe("banner:collect", { sections: [], activeBackend: backendName }).sections;
|
|
180
|
+
for (const sec of extSections) {
|
|
181
|
+
sections += `\n\n ${p.muted}${sec.label}:${p.reset}`;
|
|
182
|
+
for (const item of sec.items) {
|
|
183
|
+
sections += `\n ${p.dim}${item}${p.reset}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const hint = `${p.muted}Type ${p.warning}>${p.muted} to ask AI · ${p.warning}>/help${p.muted} for commands${p.reset}`;
|
|
188
|
+
const borderLine = `${p.muted}${"─".repeat(bannerW)}${p.reset}`;
|
|
189
|
+
|
|
190
|
+
process.stdout.write(
|
|
191
|
+
"\n" + borderLine + "\n" +
|
|
192
|
+
" " + productName +
|
|
193
|
+
sections + "\n" +
|
|
194
|
+
"\n " + hint + "\n" +
|
|
195
|
+
borderLine + "\n\n",
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await core.activateBackend(config.backend);
|
|
200
|
+
|
|
201
|
+
// 100ms sidesteps macOS SIGTTOU during fg-pgrp handoff.
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
203
|
+
shell = activateShell(extCtx, {
|
|
204
|
+
cols,
|
|
205
|
+
rows,
|
|
206
|
+
shellPath: config.shell || process.env.SHELL || "/bin/bash",
|
|
207
|
+
cwd: process.cwd(),
|
|
208
|
+
onShowAgentInfo: () => {
|
|
209
|
+
if (agentInfo) {
|
|
210
|
+
return { info: `${p.dim}${agentInfo.name}${agentInfo.model ? ` (${agentInfo.model})` : ""}${p.reset}` };
|
|
211
|
+
}
|
|
212
|
+
return { info: "" };
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
bus.off("ui:error", bootUiError);
|
|
216
|
+
|
|
217
|
+
bus.emit("input-mode:register", {
|
|
218
|
+
id: "agent",
|
|
219
|
+
trigger: ">",
|
|
220
|
+
label: "agent",
|
|
221
|
+
promptIcon: "❯",
|
|
222
|
+
indicator: "●",
|
|
223
|
+
onSubmit(query, b) {
|
|
224
|
+
b.emit("agent:submit", { query });
|
|
225
|
+
},
|
|
226
|
+
returnToSelf: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ── Terminal lifecycle ────────────────────────────────────────
|
|
230
|
+
process.on("SIGTERM", cleanup);
|
|
231
|
+
process.on("SIGHUP", cleanup);
|
|
232
|
+
|
|
233
|
+
process.on("SIGTSTP", () => {
|
|
234
|
+
if (process.stdin.isTTY) {
|
|
235
|
+
try {
|
|
236
|
+
process.stdin.setRawMode(false);
|
|
237
|
+
} catch {
|
|
238
|
+
// Ignore
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
process.kill(process.pid!, "SIGSTOP");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
process.on("SIGCONT", () => {
|
|
245
|
+
if (process.stdin.isTTY) {
|
|
246
|
+
try {
|
|
247
|
+
process.stdin.setRawMode(true);
|
|
248
|
+
clearOpost();
|
|
249
|
+
} catch {
|
|
250
|
+
// May fail if stdin is not a TTY
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// resize forwarding is set up inside activateShell; nothing to wire here.
|
|
256
|
+
|
|
257
|
+
shell!.onExit((e) => {
|
|
258
|
+
core.kill();
|
|
259
|
+
if (process.stdin.isTTY) {
|
|
260
|
+
process.stdin.setRawMode(false);
|
|
261
|
+
}
|
|
262
|
+
process.exit(e.exitCode);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (process.env.DEBUG) {
|
|
266
|
+
console.error('[agent-sh] Resuming stdin...');
|
|
267
|
+
}
|
|
268
|
+
process.stdin.resume();
|
|
269
|
+
|
|
270
|
+
if (process.stdin.isTTY) {
|
|
271
|
+
if (process.env.DEBUG) {
|
|
272
|
+
console.error('[agent-sh] Setting raw mode...');
|
|
273
|
+
}
|
|
274
|
+
setImmediate(() => {
|
|
275
|
+
try {
|
|
276
|
+
process.stdin.setRawMode(true);
|
|
277
|
+
if (process.env.DEBUG) {
|
|
278
|
+
console.error('[agent-sh] Raw mode enabled');
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (process.env.DEBUG) {
|
|
282
|
+
console.error(`[agent-sh] Failed to set raw mode: ${err}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (process.env.DEBUG) {
|
|
288
|
+
console.error('[agent-sh] Startup complete');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
main().catch((err) => {
|
|
293
|
+
console.error("Fatal:", err);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
});
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { CONFIG_DIR } from "../core/settings.js";
|
|
4
|
+
const EXTENSIONS_DIR = path.join(CONFIG_DIR, "extensions");
|
|
5
|
+
const SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json");
|
|
6
|
+
const EXAMPLE_PATH = path.join(CONFIG_DIR, "settings.example.json");
|
|
7
|
+
const AGENTS_PATH = path.join(CONFIG_DIR, "AGENTS.md");
|
|
8
|
+
|
|
9
|
+
// Shape-discoverable stub — all fields present, none filled in.
|
|
10
|
+
const STARTER_SETTINGS = {
|
|
11
|
+
defaultProvider: null,
|
|
12
|
+
providers: {},
|
|
13
|
+
extensions: [],
|
|
14
|
+
disabledBuiltins: [],
|
|
15
|
+
disabledExtensions: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Not loaded at runtime — users copy blocks from here into settings.json.
|
|
19
|
+
const EXAMPLE_SETTINGS = {
|
|
20
|
+
defaultProvider: "openrouter",
|
|
21
|
+
providers: {
|
|
22
|
+
openrouter: {
|
|
23
|
+
apiKey: "$OPENROUTER_API_KEY",
|
|
24
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
25
|
+
defaultModel: "anthropic/claude-sonnet-4.6",
|
|
26
|
+
},
|
|
27
|
+
openai: {
|
|
28
|
+
apiKey: "$OPENAI_API_KEY",
|
|
29
|
+
defaultModel: "gpt-5",
|
|
30
|
+
},
|
|
31
|
+
anthropic: {
|
|
32
|
+
apiKey: "$ANTHROPIC_API_KEY",
|
|
33
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
34
|
+
defaultModel: "claude-sonnet-4-5",
|
|
35
|
+
},
|
|
36
|
+
ollama: {
|
|
37
|
+
apiKey: "ollama",
|
|
38
|
+
baseURL: "http://localhost:11434/v1",
|
|
39
|
+
defaultModel: "llama3.3",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
extensions: [],
|
|
43
|
+
disabledBuiltins: [],
|
|
44
|
+
disabledExtensions: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function writeIfMissing(filePath: string, content: string, force: boolean): "written" | "kept" {
|
|
48
|
+
if (!force && fs.existsSync(filePath)) return "kept";
|
|
49
|
+
fs.writeFileSync(filePath, content);
|
|
50
|
+
return "written";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function runInit(opts: { force: boolean }): void {
|
|
54
|
+
fs.mkdirSync(EXTENSIONS_DIR, { recursive: true });
|
|
55
|
+
|
|
56
|
+
const settingsResult = writeIfMissing(SETTINGS_PATH, JSON.stringify(STARTER_SETTINGS, null, 2) + "\n", opts.force);
|
|
57
|
+
// Always refreshed — reference material, not user state.
|
|
58
|
+
fs.writeFileSync(EXAMPLE_PATH, JSON.stringify(EXAMPLE_SETTINGS, null, 2) + "\n");
|
|
59
|
+
|
|
60
|
+
console.log(`agent-sh initialized at ${CONFIG_DIR}`);
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(` settings.json ${settingsResult}${opts.force ? "" : settingsResult === "kept" ? " (exists — pass --force to overwrite)" : ""}`);
|
|
63
|
+
console.log(` settings.example.json refreshed`);
|
|
64
|
+
console.log(` extensions/ ready`);
|
|
65
|
+
console.log();
|
|
66
|
+
console.log("Next steps:");
|
|
67
|
+
console.log(` 1. Open ${SETTINGS_PATH}`);
|
|
68
|
+
console.log(` 2. Copy a provider block from settings.example.json into \`providers\` and set \`defaultProvider\`.`);
|
|
69
|
+
console.log(` 3. Export the referenced env var (e.g. \`export OPENROUTER_API_KEY=...\`).`);
|
|
70
|
+
console.log(` 4. Run \`agent-sh\`.`);
|
|
71
|
+
console.log();
|
|
72
|
+
console.log(`Optional: create ${AGENTS_PATH} with standing instructions`);
|
|
73
|
+
console.log(`(code style, commands to avoid, etc.) to load them into every session.`);
|
|
74
|
+
}
|