@inneranimalmedia/agentsam-sdk 2.2.0 → 2.3.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/docs/CLI_SHELL.md +10 -1
- package/package.json +1 -1
- package/packages/identity/package.json +1 -1
- package/src/cli.js +3 -38
- package/src/commands/shell.js +253 -0
- package/src/lib/slash-commands.js +2 -1
- package/test/shell.test.mjs +60 -0
- package/test/smoke.mjs +2 -2
package/docs/CLI_SHELL.md
CHANGED
|
@@ -21,7 +21,15 @@ Runs the optional Python Rich renderer. `--install` creates an isolated `.agents
|
|
|
21
21
|
agentsam shell
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Starts the interactive Agent Sam slash-command shell. Once the `agentsam>` prompt is visible, commands such as `/help`, `/status`, `/pwd`, and `/git` are handled by Agent Sam instead of the host shell (PowerShell, bash, or zsh). Use `/exit` to return to the host terminal.
|
|
25
|
+
|
|
26
|
+
Do not type `/help` directly at a PowerShell/bash prompt; enter `agentsam shell` first.
|
|
27
|
+
|
|
28
|
+
For scripts and regression tests, a single slash command can be dispatched without opening the REPL:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
agentsam shell --command /help
|
|
32
|
+
```
|
|
25
33
|
|
|
26
34
|
## Architecture
|
|
27
35
|
|
|
@@ -92,6 +100,7 @@ Gorilla is intentionally not scaffolded by default.
|
|
|
92
100
|
/logs local execution events
|
|
93
101
|
/tui terminal presentation
|
|
94
102
|
/deploy intentionally add a cloud adapter
|
|
103
|
+
/exit exit Agent Sam shell and return to the host terminal
|
|
95
104
|
```
|
|
96
105
|
|
|
97
106
|
Provider-specific commands such as `/claude` or `/codex` are not part of the generic shell contract. Model routing belongs behind Agent Sam.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inneranimalmedia/agentsam-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Portable AgentSam SDK and CLI kits for local scaffolding, repository intelligence, incremental indexing, identity adapters, and verified dependency maintenance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { runContext } from './commands/context.js';
|
|
|
18
18
|
import { runDb } from './commands/db.js';
|
|
19
19
|
import { runStatus } from './commands/status.js';
|
|
20
20
|
import { runTui } from './commands/tui.js';
|
|
21
|
+
import { runShell } from './commands/shell.js';
|
|
21
22
|
import { runDockerize } from './commands/dockerize.js';
|
|
22
23
|
import { runMini } from './commands/mini.js';
|
|
23
24
|
import { runMerkle } from './commands/merkle.js';
|
|
@@ -26,7 +27,6 @@ import { runSecurity } from './commands/security.js';
|
|
|
26
27
|
import { runRecon } from './commands/recon.js';
|
|
27
28
|
import { applyPresetSelection, runAdd, runCapabilities, runDev, runInspect } from './commands/product.js';
|
|
28
29
|
import { listPresets, resolvePreset } from './presets/index.js';
|
|
29
|
-
import { SLASH_COMMANDS, SHELL_PHASES } from './lib/slash-commands.js';
|
|
30
30
|
import fs from 'node:fs';
|
|
31
31
|
import { repositoryRoot } from './knowledge/config.js';
|
|
32
32
|
|
|
@@ -70,7 +70,7 @@ function printHelp() {
|
|
|
70
70
|
agentsam tui Zero-dependency ANSI Agent Sam dashboard
|
|
71
71
|
agentsam tui rich Optional Python Rich dashboard (--install for local venv)
|
|
72
72
|
agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
|
|
73
|
-
agentsam shell
|
|
73
|
+
agentsam shell Interactive Agent Sam slash-command shell
|
|
74
74
|
agentsam tunnel Explicitly expose local PTY when remote access is wanted
|
|
75
75
|
agentsam deploy Graduate to Cloudflare / GCP when ready
|
|
76
76
|
agentsam dockerize Build/run app, knowledge, or CAD containers (--help)
|
|
@@ -257,41 +257,6 @@ async function initFromArgs(argv) {
|
|
|
257
257
|
await runLocalInit({ ...opts, prompt: null });
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
async function runShellInfo(argv = []) {
|
|
261
|
-
const sub = argv[0] || 'list';
|
|
262
|
-
if (sub === 'demo' || sub === 'ansi') {
|
|
263
|
-
await runTui(['ansi', ...argv.slice(1)]);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (sub === 'rich') {
|
|
267
|
-
await runTui(['rich', ...argv.slice(1)]);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
if (sub !== 'list' && sub !== 'status') {
|
|
271
|
-
throw new Error(`unknown shell command: ${sub}`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const next = SHELL_PHASES.find((p) => p.status === 'next' || p.status === 'current');
|
|
275
|
-
console.log(`
|
|
276
|
-
╔═══════════════════════════════╗
|
|
277
|
-
║ Agent Sam Terminal ║
|
|
278
|
-
╚════════════════════════════════╝
|
|
279
|
-
|
|
280
|
-
Local PTY agentsam start-local ws://127.0.0.1:3099
|
|
281
|
-
ANSI TUI agentsam tui zero-dependency Node UI
|
|
282
|
-
Rich TUI agentsam tui rich optional richer Python UI
|
|
283
|
-
agentsam tui rich --install
|
|
284
|
-
DB agentsam db status local SQLite
|
|
285
|
-
|
|
286
|
-
Current milestone: ${next?.label ?? 'local terminal experience'}
|
|
287
|
-
|
|
288
|
-
Slash commands (${SLASH_COMMANDS.length} registered):
|
|
289
|
-
`);
|
|
290
|
-
for (const row of SLASH_COMMANDS) {
|
|
291
|
-
console.log(` ${row.cmd.padEnd(14)} ${row.description}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
260
|
const command = process.argv[2];
|
|
296
261
|
const rest = process.argv.slice(3);
|
|
297
262
|
|
|
@@ -353,7 +318,7 @@ if (command === '--version' || command === '-v') {
|
|
|
353
318
|
}
|
|
354
319
|
} else if (command === 'shell') {
|
|
355
320
|
try {
|
|
356
|
-
await
|
|
321
|
+
await runShell(rest);
|
|
357
322
|
} catch (e) {
|
|
358
323
|
console.error(`\n ✗ ${e?.message || e}\n`);
|
|
359
324
|
process.exit(1);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { SLASH_COMMANDS, SHELL_PHASES } from '../lib/slash-commands.js';
|
|
6
|
+
import { runContext } from './context.js';
|
|
7
|
+
import { runDb } from './db.js';
|
|
8
|
+
import { runDeploy } from './deploy.js';
|
|
9
|
+
import { runStatus } from './status.js';
|
|
10
|
+
import { runTui } from './tui.js';
|
|
11
|
+
|
|
12
|
+
function writeLine(write, value = '') {
|
|
13
|
+
write(`${value}\n`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function tokenizeShellLine(input = '') {
|
|
17
|
+
const source = String(input);
|
|
18
|
+
const tokens = [];
|
|
19
|
+
let token = '';
|
|
20
|
+
let quote = '';
|
|
21
|
+
let started = false;
|
|
22
|
+
|
|
23
|
+
const flush = () => {
|
|
24
|
+
if (!started) return;
|
|
25
|
+
tokens.push(token);
|
|
26
|
+
token = '';
|
|
27
|
+
started = false;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
31
|
+
const ch = source[i];
|
|
32
|
+
if (quote) {
|
|
33
|
+
if (ch === quote) {
|
|
34
|
+
quote = '';
|
|
35
|
+
} else {
|
|
36
|
+
token += ch;
|
|
37
|
+
}
|
|
38
|
+
started = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === '"' || ch === "'") {
|
|
42
|
+
quote = ch;
|
|
43
|
+
started = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (/\s/.test(ch)) {
|
|
47
|
+
flush();
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
token += ch;
|
|
51
|
+
started = true;
|
|
52
|
+
}
|
|
53
|
+
flush();
|
|
54
|
+
return tokens;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderShellCatalog() {
|
|
58
|
+
const next = SHELL_PHASES.find((phase) => phase.status === 'next' || phase.status === 'current');
|
|
59
|
+
const rows = SLASH_COMMANDS.map((row) => ` ${row.cmd.padEnd(14)} ${row.description}`).join('\n');
|
|
60
|
+
return `
|
|
61
|
+
╔════════════════════════════════╗
|
|
62
|
+
║ Agent Sam Terminal ║
|
|
63
|
+
╚════════════════════════════════╝
|
|
64
|
+
|
|
65
|
+
Local PTY agentsam start-local ws://127.0.0.1:3099
|
|
66
|
+
ANSI TUI agentsam tui zero-dependency Node UI
|
|
67
|
+
Rich TUI agentsam tui rich optional richer Python UI
|
|
68
|
+
agentsam tui rich --install
|
|
69
|
+
DB agentsam db status local SQLite
|
|
70
|
+
|
|
71
|
+
Current milestone: ${next?.label ?? 'local terminal experience'}
|
|
72
|
+
|
|
73
|
+
Slash commands (${SLASH_COMMANDS.length} registered):
|
|
74
|
+
${rows}
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseDeployOptions(args, cwd) {
|
|
79
|
+
const opts = { cwd, target: '', accountId: '' };
|
|
80
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
81
|
+
const arg = args[i];
|
|
82
|
+
if (arg === '--target') opts.target = args[++i] || '';
|
|
83
|
+
else if (arg === '--account-id') opts.accountId = args[++i] || '';
|
|
84
|
+
else throw new Error(`unknown /deploy option: ${arg}`);
|
|
85
|
+
}
|
|
86
|
+
return opts;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function withProcessCwd(cwd, fn) {
|
|
90
|
+
const previous = process.cwd();
|
|
91
|
+
process.chdir(cwd);
|
|
92
|
+
try {
|
|
93
|
+
return await fn();
|
|
94
|
+
} finally {
|
|
95
|
+
process.chdir(previous);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runLocalAgent(goal, write) {
|
|
100
|
+
if (!goal) {
|
|
101
|
+
writeLine(write, ' Usage: /agent <goal>');
|
|
102
|
+
writeLine(write, ' Requires the local Agent Sam dev server (default http://127.0.0.1:8787).');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const base = String(process.env.AGENTSAM_LOCAL_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = await fetch(`${base}/api/agentsam/message`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'content-type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ message: goal }),
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(`local Agent Sam unavailable at ${base} — run \`npm run dev\` first (${error?.message || error})`);
|
|
115
|
+
}
|
|
116
|
+
const text = await response.text();
|
|
117
|
+
if (!response.ok) throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`);
|
|
118
|
+
try {
|
|
119
|
+
writeLine(write, JSON.stringify(JSON.parse(text), null, 2));
|
|
120
|
+
} catch {
|
|
121
|
+
writeLine(write, text);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function showLocalLogs(cwd, write) {
|
|
126
|
+
const dbPath = path.join(cwd, '.agentsam', 'data', 'agentsam.sqlite');
|
|
127
|
+
if (!fs.existsSync(dbPath)) {
|
|
128
|
+
writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const { createLocalSqliteDatabase } = await import('../local/sqlite.js');
|
|
132
|
+
const db = await createLocalSqliteDatabase(dbPath);
|
|
133
|
+
try {
|
|
134
|
+
const calls = await db
|
|
135
|
+
.prepare('SELECT id, session_id, tool_name, status, created_at, completed_at FROM agent_tool_calls ORDER BY created_at DESC LIMIT 20')
|
|
136
|
+
.all();
|
|
137
|
+
if (!calls.results.length) {
|
|
138
|
+
writeLine(write, ' No local Agent Sam tool-call events yet.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
writeLine(write, '');
|
|
142
|
+
writeLine(write, ' Recent Agent Sam tool calls');
|
|
143
|
+
for (const row of calls.results) {
|
|
144
|
+
writeLine(write, ` ${String(row.created_at || '').padEnd(20)} ${String(row.status || '').padEnd(10)} ${row.tool_name}`);
|
|
145
|
+
}
|
|
146
|
+
writeLine(write, '');
|
|
147
|
+
} finally {
|
|
148
|
+
db.close();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function dispatchShellLine(line, state = {}) {
|
|
153
|
+
const tokens = tokenizeShellLine(line);
|
|
154
|
+
const write = state.write || ((text) => process.stdout.write(text));
|
|
155
|
+
state.cwd = path.resolve(state.cwd || process.cwd());
|
|
156
|
+
if (!tokens.length) return { handled: true, exit: false, cwd: state.cwd };
|
|
157
|
+
|
|
158
|
+
const [command, ...args] = tokens;
|
|
159
|
+
try {
|
|
160
|
+
switch (command.toLowerCase()) {
|
|
161
|
+
case '/help':
|
|
162
|
+
write(renderShellCatalog());
|
|
163
|
+
return { handled: true, exit: false, cwd: state.cwd };
|
|
164
|
+
case '/exit':
|
|
165
|
+
case '/quit':
|
|
166
|
+
return { handled: true, exit: true, cwd: state.cwd };
|
|
167
|
+
case '/status':
|
|
168
|
+
await runStatus(args, { cwd: state.cwd });
|
|
169
|
+
break;
|
|
170
|
+
case '/context':
|
|
171
|
+
await runContext(['--cwd', state.cwd, ...args]);
|
|
172
|
+
break;
|
|
173
|
+
case '/pwd':
|
|
174
|
+
writeLine(write, state.cwd);
|
|
175
|
+
break;
|
|
176
|
+
case '/cd': {
|
|
177
|
+
const destination = args.length ? args.join(' ') : process.env.HOME || process.env.USERPROFILE || state.cwd;
|
|
178
|
+
const next = path.resolve(state.cwd, destination);
|
|
179
|
+
if (!fs.existsSync(next) || !fs.statSync(next).isDirectory()) throw new Error(`directory not found: ${next}`);
|
|
180
|
+
state.cwd = next;
|
|
181
|
+
writeLine(write, state.cwd);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case '/git': {
|
|
185
|
+
const gitArgs = args.length ? args : ['status', '--short', '--branch'];
|
|
186
|
+
const result = spawnSync('git', gitArgs, { cwd: state.cwd, stdio: 'inherit', shell: false });
|
|
187
|
+
if (result.error) throw result.error;
|
|
188
|
+
if (result.status !== 0) throw new Error(`git exited ${result.status}`);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case '/db':
|
|
192
|
+
await runDb(args.length ? args : ['status'], { cwd: state.cwd });
|
|
193
|
+
break;
|
|
194
|
+
case '/agent':
|
|
195
|
+
await runLocalAgent(args.join(' '), write);
|
|
196
|
+
break;
|
|
197
|
+
case '/logs':
|
|
198
|
+
await showLocalLogs(state.cwd, write);
|
|
199
|
+
break;
|
|
200
|
+
case '/tui':
|
|
201
|
+
await withProcessCwd(state.cwd, () => runTui(args));
|
|
202
|
+
break;
|
|
203
|
+
case '/deploy':
|
|
204
|
+
await runDeploy(parseDeployOptions(args, state.cwd));
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
writeLine(write, ` Unknown Agent Sam command: ${command}`);
|
|
208
|
+
writeLine(write, ' Type /help for available commands.');
|
|
209
|
+
return { handled: false, exit: false, cwd: state.cwd };
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
writeLine(write, ` ✗ ${error?.message || error}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { handled: true, exit: false, cwd: state.cwd };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function runShell(argv = [], options = {}) {
|
|
219
|
+
const write = options.write || ((text) => process.stdout.write(text));
|
|
220
|
+
const state = { cwd: path.resolve(options.cwd || process.cwd()), write };
|
|
221
|
+
const sub = argv[0] || '';
|
|
222
|
+
|
|
223
|
+
if (sub === 'list' || sub === 'status') {
|
|
224
|
+
write(renderShellCatalog());
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (sub === '--command' || sub === '--once') {
|
|
228
|
+
const line = argv.slice(1).join(' ');
|
|
229
|
+
if (!line) throw new Error(`${sub} requires a slash command`);
|
|
230
|
+
await dispatchShellLine(line, state);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (sub) throw new Error(`unknown shell option: ${sub}`);
|
|
234
|
+
|
|
235
|
+
write(renderShellCatalog());
|
|
236
|
+
writeLine(write, ' Interactive shell ready. Type /help for commands; /exit to return to your host shell.');
|
|
237
|
+
writeLine(write, '');
|
|
238
|
+
|
|
239
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
|
|
240
|
+
if (rl.terminal) {
|
|
241
|
+
rl.setPrompt('agentsam> ');
|
|
242
|
+
rl.prompt();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for await (const line of rl) {
|
|
246
|
+
const result = await dispatchShellLine(line, state);
|
|
247
|
+
if (result.exit) {
|
|
248
|
+
rl.close();
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (rl.terminal) rl.prompt();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Canonical slash-command surface for Agent Sam SDK CLI / shell UX.
|
|
3
|
-
* Consumed by the
|
|
3
|
+
* Consumed by the interactive `agentsam shell` REPL and presentation layers.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const SHELL_THEMES = ['NIGHT', 'DAY', 'LAVA', 'VOID'];
|
|
@@ -18,6 +18,7 @@ export const SLASH_COMMANDS = [
|
|
|
18
18
|
{ cmd: '/logs', description: 'Show local Agent Sam execution events', lane: 'observability' },
|
|
19
19
|
{ cmd: '/tui', description: 'Switch or preview terminal presentation', lane: 'terminal' },
|
|
20
20
|
{ cmd: '/deploy', description: 'Add a cloud adapter and deploy intentionally', lane: 'deploy' },
|
|
21
|
+
{ cmd: '/exit', description: 'Exit Agent Sam shell and return to the host terminal' },
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
/** Shell UX rollout phases (gorilla-shell → SDK default CLI experience). */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import { dispatchShellLine, renderShellCatalog, tokenizeShellLine } from '../src/commands/shell.js';
|
|
8
|
+
|
|
9
|
+
const repoRoot = path.resolve(new URL('..', import.meta.url).pathname);
|
|
10
|
+
|
|
11
|
+
test('shell tokenizer preserves Windows paths and quoted arguments', () => {
|
|
12
|
+
assert.deepEqual(tokenizeShellLine('/cd C:\\Users\\conno\\fuelnreetime'), ['/cd', 'C:\\Users\\conno\\fuelnreetime']);
|
|
13
|
+
assert.deepEqual(tokenizeShellLine('/cd "C:\\Users\\Connor Smith\\repo"'), ['/cd', 'C:\\Users\\Connor Smith\\repo']);
|
|
14
|
+
assert.deepEqual(tokenizeShellLine('/agent "inspect this repo"'), ['/agent', 'inspect this repo']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('shell catalog advertises commands that the REPL owns', () => {
|
|
18
|
+
const catalog = renderShellCatalog();
|
|
19
|
+
assert.match(catalog, /\/help\s+Show Agent Sam commands/);
|
|
20
|
+
assert.match(catalog, /\/status\s+Local project/);
|
|
21
|
+
assert.match(catalog, /\/exit\s+Exit Agent Sam shell/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('dispatch handles help, pwd, cd, and exit without falling through to host shell', async () => {
|
|
25
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-shell-'));
|
|
26
|
+
const child = path.join(root, 'child folder');
|
|
27
|
+
fs.mkdirSync(child);
|
|
28
|
+
let output = '';
|
|
29
|
+
const state = { cwd: root, write: (text) => { output += text; } };
|
|
30
|
+
|
|
31
|
+
let result = await dispatchShellLine('/help', state);
|
|
32
|
+
assert.equal(result.handled, true);
|
|
33
|
+
assert.equal(result.exit, false);
|
|
34
|
+
assert.match(output, /Slash commands/);
|
|
35
|
+
|
|
36
|
+
output = '';
|
|
37
|
+
result = await dispatchShellLine('/pwd', state);
|
|
38
|
+
assert.equal(result.handled, true);
|
|
39
|
+
assert.equal(output.trim(), root);
|
|
40
|
+
|
|
41
|
+
output = '';
|
|
42
|
+
result = await dispatchShellLine('/cd "child folder"', state);
|
|
43
|
+
assert.equal(result.handled, true);
|
|
44
|
+
assert.equal(state.cwd, child);
|
|
45
|
+
assert.equal(output.trim(), child);
|
|
46
|
+
|
|
47
|
+
result = await dispatchShellLine('/exit', state);
|
|
48
|
+
assert.equal(result.exit, true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('CLI supports a deterministic one-shot slash command for regression tests', () => {
|
|
52
|
+
const result = spawnSync(process.execPath, ['src/cli.js', 'shell', '--command', '/help'], {
|
|
53
|
+
cwd: repoRoot,
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
});
|
|
56
|
+
assert.equal(result.status, 0, result.stderr);
|
|
57
|
+
assert.match(result.stdout, /Agent Sam Terminal/);
|
|
58
|
+
assert.match(result.stdout, /\/help/);
|
|
59
|
+
assert.match(result.stdout, /\/exit/);
|
|
60
|
+
});
|
package/test/smoke.mjs
CHANGED
|
@@ -57,8 +57,8 @@ assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/db'));
|
|
|
57
57
|
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/agent'));
|
|
58
58
|
assert.deepEqual(
|
|
59
59
|
listSlashCommands({ lane: 'deploy' }).map(({ cmd }) => cmd),
|
|
60
|
-
['/help', '/deploy'],
|
|
61
|
-
'deploy lane keeps global
|
|
60
|
+
['/help', '/deploy', '/exit'],
|
|
61
|
+
'deploy lane keeps global shell controls and excludes commands from other lanes',
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
printContextSummary({
|