@stan-chen/simple-cli 0.2.1 → 0.2.3

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 (61) hide show
  1. package/README.md +55 -238
  2. package/dist/claw/jit.d.ts +5 -0
  3. package/dist/claw/jit.js +138 -0
  4. package/dist/claw/management.d.ts +3 -0
  5. package/dist/claw/management.js +107 -0
  6. package/dist/cli.js +306 -61
  7. package/dist/commands/git/commit.js +2 -1
  8. package/dist/commands/index.js +3 -2
  9. package/dist/context.js +13 -3
  10. package/dist/lib/agent.d.ts +4 -3
  11. package/dist/lib/agent.js +49 -17
  12. package/dist/lib/git.js +6 -1
  13. package/dist/lib/shim.d.ts +4 -0
  14. package/dist/lib/shim.js +30 -0
  15. package/dist/lib/ui.js +25 -0
  16. package/dist/mcp/manager.js +5 -1
  17. package/dist/prompts/provider.js +1 -0
  18. package/dist/providers/index.d.ts +21 -5
  19. package/dist/providers/index.js +75 -64
  20. package/dist/providers/multi.d.ts +2 -1
  21. package/dist/registry.d.ts +5 -0
  22. package/dist/registry.js +86 -22
  23. package/dist/repoMap.js +18 -18
  24. package/dist/router.js +21 -11
  25. package/dist/skills.js +10 -10
  26. package/dist/swarm/worker.d.ts +2 -0
  27. package/dist/swarm/worker.js +85 -15
  28. package/dist/tools/analyze_file.d.ts +16 -0
  29. package/dist/tools/analyze_file.js +43 -0
  30. package/dist/tools/clawBrain.d.ts +23 -0
  31. package/dist/tools/clawBrain.js +136 -0
  32. package/dist/tools/claw_brain.d.ts +23 -0
  33. package/dist/tools/claw_brain.js +139 -0
  34. package/dist/tools/deleteFile.d.ts +19 -0
  35. package/dist/tools/deleteFile.js +36 -0
  36. package/dist/tools/delete_file.d.ts +19 -0
  37. package/dist/tools/delete_file.js +36 -0
  38. package/dist/tools/fileOps.d.ts +22 -0
  39. package/dist/tools/fileOps.js +43 -0
  40. package/dist/tools/file_ops.d.ts +22 -0
  41. package/dist/tools/file_ops.js +43 -0
  42. package/dist/tools/grep.d.ts +2 -2
  43. package/dist/tools/linter.js +85 -27
  44. package/dist/tools/list_dir.d.ts +29 -0
  45. package/dist/tools/list_dir.js +50 -0
  46. package/dist/tools/organizer.d.ts +1 -0
  47. package/dist/tools/organizer.js +65 -0
  48. package/dist/tools/read_files.d.ts +25 -0
  49. package/dist/tools/read_files.js +31 -0
  50. package/dist/tools/reload_tools.d.ts +11 -0
  51. package/dist/tools/reload_tools.js +22 -0
  52. package/dist/tools/run_command.d.ts +32 -0
  53. package/dist/tools/run_command.js +103 -0
  54. package/dist/tools/scheduler.d.ts +25 -0
  55. package/dist/tools/scheduler.js +65 -0
  56. package/dist/tools/writeFiles.js +1 -1
  57. package/dist/tools/write_files.d.ts +84 -0
  58. package/dist/tools/write_files.js +91 -0
  59. package/dist/tools/write_to_file.d.ts +15 -0
  60. package/dist/tools/write_to_file.js +21 -0
  61. package/package.json +84 -78
package/dist/cli.js CHANGED
@@ -13,38 +13,57 @@ import { routeTask, loadTierConfig } from './router.js';
13
13
  import { executeCommand } from './commands.js';
14
14
  import { getMCPManager } from './mcp/manager.js';
15
15
  import { listSkills, setActiveSkill, getActiveSkill } from './skills.js';
16
- import { statSync } from 'fs';
17
- import { resolve } from 'path';
16
+ import { readFileSync, existsSync, statSync } from 'fs';
17
+ import fs from 'fs';
18
+ import { writeFile, mkdir } from 'fs/promises';
19
+ import { resolve, join, dirname } from 'path';
20
+ import { fileURLToPath } from 'url';
18
21
  import { runSwarm, parseSwarmArgs, printSwarmHelp } from './commands/swarm.js';
22
+ import { runDeterministicOrganizer } from './tools/organizer.js';
19
23
  import { jsonrepair } from 'jsonrepair';
24
+ const __filename = fileURLToPath(import.meta.url);
25
+ const __dirname = dirname(__filename);
26
+ // Deterministic organizer moved to src/tools/organizer.ts
20
27
  // CLI flags
21
- const YOLO_MODE = process.argv.includes('--yolo');
22
28
  const MOE_MODE = process.argv.includes('--moe');
23
29
  const SWARM_MODE = process.argv.includes('--swarm');
24
30
  const CLAW_MODE = process.argv.includes('--claw') || process.argv.includes('-claw');
31
+ const GHOST_MODE = process.argv.includes('--ghost');
32
+ const YOLO_MODE = process.argv.includes('--yolo') || CLAW_MODE || GHOST_MODE;
25
33
  const DEBUG = process.argv.includes('--debug') || process.env.DEBUG === 'true';
26
- const VERSION = '0.2.0';
27
- // Handle --claw mode (JIT Agent Generation)
28
- if (CLAW_MODE) {
29
- const { execSync } = await import('child_process');
30
- const args = process.argv.slice(2).filter(a => !a.startsWith('-'));
31
- const intent = args.join(' ') || 'unspecified task';
32
- console.log(pc.cyan('🧬 Initiating JIT Agent Generation...'));
33
- console.log(pc.dim(`Intent: "${intent}"`));
34
- try {
35
- const output = execSync(`npx tsx tools/claw.ts run clawJit intent="${intent}"`, {
36
- cwd: process.cwd(),
37
- encoding: 'utf-8',
38
- stdio: 'inherit'
39
- });
40
- console.log(pc.green('\n✅ JIT Agent ready. Run `simple` to begin.'));
41
- process.exit(0);
42
- }
43
- catch (error) {
44
- console.error(pc.red('❌ Failed to initialize Claw mode:'), error);
45
- process.exit(1);
46
- }
34
+ const VERSION = '0.2.2';
35
+ // Non-interactive detection (tests and CI)
36
+ const NON_INTERACTIVE = process.env.VITEST === 'true' || process.env.TEST === 'true' || !process.stdin.isTTY;
37
+ // Handle --version and --help immediately
38
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
39
+ console.log(`Simple-CLI v${VERSION}`);
40
+ process.exit(0);
41
+ }
42
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
43
+ console.log(`
44
+ ${pc.bgCyan(pc.black(' SIMPLE-CLI '))} ${pc.dim(`v${VERSION}`)}
45
+
46
+ ${pc.bold('Usage:')}
47
+ simple [target_dir] [prompt] [options]
48
+
49
+ ${pc.bold('Options:')}
50
+ --version, -v Show version
51
+ --help, -h Show help
52
+ --yolo Skip all confirmation prompts
53
+ --moe Enable Mixture of Experts (multi-model)
54
+ --swarm Enable Swarm orchestration mode
55
+ --claw "intent" Enable OpenClaw JIT agent generation
56
+ --debug Enable debug logging
57
+
58
+ ${pc.bold('Examples:')}
59
+ simple . "Build a login page"
60
+ simple --claw "Security audit this project"
61
+ simple --moe "Refactor this entire folder"
62
+ `);
63
+ process.exit(0);
47
64
  }
65
+ // Claw mode will be handled after directory change in main()
66
+ let clawIntent = null;
48
67
  // Handle --swarm mode
49
68
  if (SWARM_MODE) {
50
69
  if (process.argv.includes('--help')) {
@@ -62,15 +81,45 @@ else {
62
81
  main().catch(console.error);
63
82
  }
64
83
  function parseResponse(response) {
84
+ // Try parsing as pure JSON first (for JSON mode)
85
+ try {
86
+ const trimmed = response.trim();
87
+ const parsed = JSON.parse(jsonrepair(trimmed));
88
+ const tool = parsed.tool;
89
+ if (tool) {
90
+ return {
91
+ thought: parsed.thought || '',
92
+ action: {
93
+ tool: tool,
94
+ message: parsed.message || '',
95
+ args: parsed.args || parsed.parameters || parsed.input || parsed
96
+ }
97
+ };
98
+ }
99
+ }
100
+ catch { /* Fall through to legacy format */ }
101
+ // Legacy format with <thought> tags
65
102
  const thought = response.match(/<thought>([\s\S]*?)<\/thought>/)?.[1]?.trim() || '';
66
- const jsonMatch = response.match(/\{[\s\S]*"tool"[\s\S]*\}/);
103
+ // Clean thought from response for message parsing
104
+ let cleanResponse = response.replace(/<thought>[\s\S]*?<\/thought>/, '').trim();
105
+ const jsonMatch = cleanResponse.match(/\{[\s\S]*"tool"[\s\S]*\}/);
67
106
  let action = { tool: 'none', message: '', args: {} };
68
107
  if (jsonMatch) {
69
108
  try {
70
109
  action = JSON.parse(jsonrepair(jsonMatch[0]));
110
+ // normalize tool names to snake_case for consistency with tool registry
111
+ if (action && action.tool && typeof action.tool === 'string') {
112
+ action.tool = String(action.tool).replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
113
+ }
114
+ // Remove JSON block for the remaining message
115
+ cleanResponse = cleanResponse.replace(jsonMatch[0], '').trim();
71
116
  }
72
117
  catch { /* skip */ }
73
118
  }
119
+ // If we still have text and no message, use the remaining text
120
+ if (!action.message && cleanResponse) {
121
+ action.message = cleanResponse;
122
+ }
74
123
  return { thought, action };
75
124
  }
76
125
  async function confirm(tool, args, ctx) {
@@ -79,6 +128,8 @@ async function confirm(tool, args, ctx) {
79
128
  const t = ctx.getTools().get(tool);
80
129
  if (!t || t.permission === 'read')
81
130
  return true;
131
+ if (NON_INTERACTIVE)
132
+ return true; // auto-approve in non-interactive/test environments
82
133
  const confirmed = await clackConfirm({
83
134
  message: `Allow ${pc.cyan(tool)} with args ${pc.dim(JSON.stringify(args))}?`,
84
135
  initialValue: true,
@@ -98,9 +149,54 @@ async function executeTool(name, args, ctx) {
98
149
  }
99
150
  }
100
151
  async function main() {
101
- console.clear();
152
+ // console.clear();
153
+ const originalCwd = process.cwd(); // Save original cwd before any directory changes
102
154
  const args = process.argv.slice(2).filter(arg => !arg.startsWith('-'));
103
- let targetDir = process.cwd();
155
+ let targetDir = originalCwd;
156
+ // Handle Claw Management Flags
157
+ if (process.argv.includes('--list')) {
158
+ const { listClawAssets } = await import('./claw/management.js');
159
+ await listClawAssets();
160
+ process.exit(0);
161
+ }
162
+ if (process.argv.includes('--logs')) {
163
+ const { showGhostLogs } = await import('./claw/management.js');
164
+ const id = process.argv[process.argv.indexOf('--logs') + 1];
165
+ await showGhostLogs(id && !id.startsWith('-') ? id : undefined);
166
+ process.exit(0);
167
+ }
168
+ if (process.argv.includes('--kill')) {
169
+ const { killGhostTask } = await import('./claw/management.js');
170
+ const id = process.argv[process.argv.indexOf('--kill') + 1];
171
+ if (!id || id.startsWith('-')) {
172
+ console.error(pc.red('Error: Task ID required for --kill'));
173
+ process.exit(1);
174
+ }
175
+ await killGhostTask(id);
176
+ process.exit(0);
177
+ }
178
+ if (process.argv.includes('--invoke') || process.argv.includes('--invoke-json')) {
179
+ const isJson = process.argv.includes('--invoke-json');
180
+ const idx = process.argv.indexOf(isJson ? '--invoke-json' : '--invoke');
181
+ const toolName = process.argv[idx + 1];
182
+ const toolArgsStr = process.argv[idx + 2] || '{}';
183
+ if (!toolName) {
184
+ console.error(pc.red('Error: Tool name required for --invoke'));
185
+ process.exit(1);
186
+ }
187
+ try {
188
+ const ctx = getContextManager(process.cwd());
189
+ await ctx.initialize();
190
+ const toolArgs = JSON.parse(toolArgsStr);
191
+ const result = await executeTool(toolName, toolArgs, ctx);
192
+ console.log(result);
193
+ process.exit(0);
194
+ }
195
+ catch (error) {
196
+ console.error(pc.red(`Error invoking tool ${toolName}:`), error);
197
+ process.exit(1);
198
+ }
199
+ }
104
200
  if (args.length > 0) {
105
201
  try {
106
202
  if (statSync(args[0]).isDirectory()) {
@@ -114,11 +210,56 @@ async function main() {
114
210
  }
115
211
  catch { /* ignored */ }
116
212
  }
117
- console.log(`\n ${pc.bgCyan(pc.black(' SIMPLE-CLI '))} ${pc.dim(`v${VERSION}`)} ${pc.green('●')} ${pc.cyan(targetDir)}\n`);
118
- console.log(`${pc.dim('○')} Initializing...`);
213
+ // Handle --claw mode AFTER directory change
214
+ if (CLAW_MODE) {
215
+ const { execSync } = await import('child_process');
216
+ // args now has directory removed, so join the rest as intent
217
+ clawIntent = args.join(' ') || 'unspecified task';
218
+ console.log(pc.cyan('🧬 Initiating JIT Agent Generation...'));
219
+ console.log(pc.dim(`Intent: "${clawIntent}"`));
220
+ console.log(pc.dim(`Working Directory: ${targetDir}`));
221
+ try {
222
+ const { generateJitAgent } = await import('./claw/jit.js');
223
+ await generateJitAgent(clawIntent, targetDir);
224
+ // If the generated AGENT.md doesn't contain actionable tool instructions,
225
+ // run the deterministic organizer immediately so live demos are deterministic.
226
+ try {
227
+ const agentFile = join(targetDir, '.simple', 'workdir', 'AGENT.md');
228
+ let agentContent = '';
229
+ try {
230
+ agentContent = readFileSync(agentFile, 'utf-8');
231
+ }
232
+ catch {
233
+ agentContent = '';
234
+ }
235
+ const actionable = /list_dir|move_file|move files|move_file|write_to_file|list files|scheduler|schedule|extract total|move\b/i.test(agentContent);
236
+ if (!actionable) {
237
+ console.log(pc.yellow('AGENT.md lacks actionable steps — running deterministic organizer fallback.'));
238
+ runDeterministicOrganizer(targetDir);
239
+ }
240
+ }
241
+ catch (err) {
242
+ console.error('Error checking AGENT.md for actions:', err);
243
+ }
244
+ console.log(pc.green('\n✅ JIT Agent soul ready. Starting autopilot loop...\n'));
245
+ // Inject autonomous environment variables
246
+ process.env.CLAW_WORKSPACE = targetDir;
247
+ process.env.CLAW_SKILL_PATH = join(targetDir, 'skills');
248
+ process.env.CLAW_DATA_DIR = join(targetDir, '.simple/workdir/memory');
249
+ }
250
+ catch (error) {
251
+ console.error(pc.red('❌ Failed to initialize Claw mode:'), error);
252
+ process.exit(1);
253
+ }
254
+ }
255
+ if (!GHOST_MODE) {
256
+ console.log(`\n ${pc.bgCyan(pc.black(' SIMPLE-CLI '))} ${pc.dim(`v${VERSION}`)} ${pc.green('●')} ${pc.cyan(targetDir)}\n`);
257
+ console.log(`${pc.dim('○')} Initializing...`);
258
+ }
119
259
  const ctx = getContextManager(targetDir);
120
260
  await ctx.initialize();
121
- console.log(`${pc.green('●')} Ready.`);
261
+ if (!GHOST_MODE)
262
+ console.log(`${pc.green('●')} Ready.`);
122
263
  const mcpManager = getMCPManager();
123
264
  try {
124
265
  const configs = await mcpManager.loadConfig();
@@ -137,7 +278,8 @@ async function main() {
137
278
  const fullPrompt = await ctx.buildSystemPrompt();
138
279
  if (MOE_MODE && multiProvider && tierConfigs) {
139
280
  const routing = await routeTask(input, async (prompt) => {
140
- return multiProvider.generateWithTier(1, prompt, [{ role: 'user', content: input }]);
281
+ const res = await multiProvider.generateWithTier(1, prompt, [{ role: 'user', content: input }]);
282
+ return res.raw || JSON.stringify(res);
141
283
  });
142
284
  if (DEBUG)
143
285
  console.log(pc.dim(`[Routing] Tier: ${routing.tier}`));
@@ -146,23 +288,31 @@ async function main() {
146
288
  return singleProvider.generateResponse(fullPrompt, history.map(m => ({ role: m.role, content: m.content })));
147
289
  };
148
290
  let isFirstPrompt = true;
291
+ const isAutonomousMode = CLAW_MODE && clawIntent;
292
+ let autonomousNudges = 0;
149
293
  while (true) {
150
294
  const skill = getActiveSkill();
151
295
  let input;
152
- // Support initial prompt from command line
153
- if (isFirstPrompt && args.length > 0) {
154
- input = args.join(' ');
296
+ // Support initial prompt from command line or claw intent
297
+ if (isFirstPrompt && (clawIntent || args.length > 0)) {
298
+ input = clawIntent || args.join(' ');
155
299
  console.log(`\n${pc.magenta('➤')} ${pc.bold(input)}`);
156
300
  }
157
301
  else {
158
- input = await text({
159
- message: pc.dim(`[@${skill.name}]`) + ' Chat with Simple-CLI',
160
- placeholder: 'Ask anything or use /help',
161
- validate(value) {
162
- if (value.trim().length === 0)
163
- return 'Input required';
164
- }
165
- });
302
+ if (NON_INTERACTIVE) {
303
+ // In non-interactive mode return empty string to allow tests to inject inputs
304
+ input = '';
305
+ }
306
+ else {
307
+ input = await text({
308
+ message: pc.dim(`[@${skill.name}]`) + ' Chat with Simple-CLI',
309
+ placeholder: 'Ask anything or use /help',
310
+ validate(value) {
311
+ if (value.trim().length === 0)
312
+ return 'Input required';
313
+ }
314
+ });
315
+ }
166
316
  }
167
317
  isFirstPrompt = false;
168
318
  if (isCancel(input)) {
@@ -203,11 +353,19 @@ async function main() {
203
353
  const skillName = trimmedInput.slice(1).trim();
204
354
  if (skillName === 'list') {
205
355
  const skills = listSkills();
206
- const selected = await select({
207
- message: 'Select a skill',
208
- options: skills.map(s => ({ label: `@${s.name} - ${s.description}`, value: s.name }))
209
- });
210
- if (!isCancel(selected)) {
356
+ let selected;
357
+ if (NON_INTERACTIVE) {
358
+ selected = skills.length > 0 ? skills[0].name : undefined;
359
+ }
360
+ else {
361
+ const sel = await select({
362
+ message: 'Select a skill',
363
+ options: skills.map(s => ({ label: `@${s.name} - ${s.description}`, value: s.name }))
364
+ });
365
+ if (!isCancel(sel))
366
+ selected = sel;
367
+ }
368
+ if (selected) {
211
369
  const newSkill = setActiveSkill(selected);
212
370
  if (newSkill)
213
371
  ctx.setSkill(newSkill);
@@ -226,34 +384,121 @@ async function main() {
226
384
  continue;
227
385
  }
228
386
  ctx.addMessage('user', trimmedInput);
387
+ let currentInput = trimmedInput;
388
+ if (CLAW_MODE || GHOST_MODE) {
389
+ currentInput = `MISSION START: ${trimmedInput}. Consult your persona in AGENT.md and perform the mission tasks immediately. Use list_dir to see what you are working with.`;
390
+ }
229
391
  let steps = 0;
392
+ let ghostLogFile = null;
393
+ if (GHOST_MODE) {
394
+ const logDir = join(targetDir, '.simple/workdir/memory/logs');
395
+ if (!existsSync(logDir))
396
+ await mkdir(logDir, { recursive: true });
397
+ ghostLogFile = join(logDir, `ghost-${Date.now()}.log`);
398
+ await writeFile(ghostLogFile, `[GHOST START] Intent: ${trimmedInput}\n`);
399
+ }
230
400
  while (steps < 15) {
231
- const response = await generate(trimmedInput);
232
- const { thought, action } = parseResponse(response);
401
+ const response = await generate(currentInput);
402
+ const { thought, tool, args, message } = response;
403
+ const action = { tool: tool || 'none', args: args || {}, message: message || '' };
404
+ const logMsg = (msg) => {
405
+ if (GHOST_MODE && ghostLogFile) {
406
+ fs.appendFileSync(ghostLogFile, msg + '\n');
407
+ }
408
+ else {
409
+ console.log(msg);
410
+ }
411
+ };
233
412
  if (thought)
234
- console.log(`\n${pc.dim('💭')} ${pc.cyan(thought)}`);
413
+ logMsg(`\n${pc.dim('💭')} ${pc.cyan(thought)}`);
235
414
  if (action.tool !== 'none') {
236
415
  if (await confirm(action.tool, action.args || {}, ctx)) {
237
- console.log(`${pc.yellow('⚙')} ${pc.dim(`Executing ${action.tool}...`)}`);
416
+ logMsg(`${pc.yellow('⚙')} ${pc.dim(`Executing ${action.tool}...`)}`);
238
417
  const result = await executeTool(action.tool, action.args || {}, ctx);
239
- console.log(`${pc.green('✔')} ${pc.dim(result.length > 500 ? result.slice(0, 500) + '...' : result)}`);
240
- ctx.addMessage('assistant', response);
241
- ctx.addMessage('user', `Tool result: ${result}`);
418
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
419
+ logMsg(`${pc.green('')} ${pc.dim(resultStr.length > 500 ? resultStr.slice(0, 500) + '...' : resultStr)}`);
420
+ const assistantMsg = response.raw || JSON.stringify(response);
421
+ ctx.addMessage('assistant', assistantMsg);
422
+ ctx.addMessage('user', `Tool result: ${resultStr}. Continue the mission.`);
423
+ currentInput = 'Continue the mission.';
242
424
  steps++;
425
+ // Autonomous Reflection & Status Check
426
+ if (CLAW_MODE || GHOST_MODE) {
427
+ const brain = ctx.getTools().get('claw_brain');
428
+ if (brain) {
429
+ await brain.execute({
430
+ action: 'log_reflection',
431
+ content: `Executed ${action.tool}. Result: ${resultStr.slice(0, 150)}...`
432
+ });
433
+ // Check if mission is marked completed
434
+ const summary = (await brain.execute({ action: 'get_summary' }));
435
+ if (summary.status === 'completed') {
436
+ logMsg(`\n${pc.green('📌')} Mission status: completed. Ending loop.`);
437
+ break;
438
+ }
439
+ }
440
+ }
441
+ continue;
243
442
  }
244
443
  else {
245
- console.log(`${pc.yellow('⚠')} Skipped.`);
246
- ctx.addMessage('assistant', response);
247
- break;
444
+ logMsg(`${pc.yellow('⚠')} Skipped.`);
445
+ const assistantMsg = response.raw || JSON.stringify(response);
446
+ ctx.addMessage('assistant', assistantMsg);
447
+ continue;
248
448
  }
249
449
  }
250
450
  else {
251
- const msg = action.message || response.replace(/<thought>[\s\S]*?<\/thought>/, '').trim();
252
- if (msg)
253
- console.log(`\n${pc.magenta('✦')} ${msg}`);
254
- ctx.addMessage('assistant', response);
451
+ // Fallback for empty message/tool to avoid "no reply"
452
+ const assistantMessage = action.message || response.raw || '';
453
+ if (assistantMessage) {
454
+ logMsg(`\n${pc.green('🤖')} ${assistantMessage}`);
455
+ ctx.addMessage('assistant', response.raw || assistantMessage);
456
+ }
457
+ else {
458
+ logMsg(`\n${pc.red('✖')} Agent returned an empty response.`);
459
+ }
255
460
  break;
256
461
  }
462
+ break;
257
463
  }
464
+ // In autonomous mode, if we haven't done any steps yet, nudge the agent
465
+ if (isAutonomousMode && steps === 0) {
466
+ autonomousNudges++;
467
+ console.log(pc.yellow(`⚡ Agent replied with text only. Forcing tool usage (nudge ${autonomousNudges}/2)...`));
468
+ if (autonomousNudges > 2) {
469
+ console.log(pc.yellow('⚠️ Agent did not act after several nudges — running deterministic fallback organizer...'));
470
+ try {
471
+ runDeterministicOrganizer(targetDir);
472
+ }
473
+ catch (err) {
474
+ console.error('Fallback organizer failed:', err);
475
+ }
476
+ // Exit autonomous mode after fallback
477
+ console.log(pc.green('✅'));
478
+ mcpManager.disconnectAll();
479
+ process.exit(0);
480
+ }
481
+ ctx.addMessage('user', 'Do not just explain. Use the tools (e.g., list_dir) to execute the plan immediately.');
482
+ continue;
483
+ }
484
+ // In autonomous mode, show summary and exit
485
+ if (isAutonomousMode) {
486
+ console.log(`\n${pc.dim('─'.repeat(60))}`);
487
+ console.log(`${pc.cyan('📊 Execution Summary:')}`);
488
+ console.log(`${pc.dim(' Steps taken:')} ${steps}`);
489
+ console.log(`${pc.dim(' Final status:')} Task completed`);
490
+ // Autonomous Pruning on Exit
491
+ const brain = ctx.getTools().get('claw_brain');
492
+ if (brain) {
493
+ console.log(pc.dim('🧠 Organizing memory...'));
494
+ await brain.execute({ action: 'prune' });
495
+ }
496
+ console.log(`\n${pc.green('✅')} Autonomous task completed.`);
497
+ console.log(`${pc.dim('Exiting autonomous mode...')}`);
498
+ mcpManager.disconnectAll();
499
+ process.exit(0);
500
+ }
501
+ // No break! Continue the chat loop
502
+ isFirstPrompt = false;
258
503
  }
259
504
  }
@@ -54,7 +54,8 @@ export default class GitCommit extends Command {
54
54
  const currentDiff = await git.stagedDiff() || await git.diff();
55
55
  message = await ui.spin('Generating commit message...', async () => {
56
56
  return generateCommitMessage(currentDiff, async (prompt) => {
57
- return provider.generateResponse(prompt, []);
57
+ const res = await provider.generateResponse(prompt, []);
58
+ return res.message || res.thought || res.raw || '';
58
59
  });
59
60
  });
60
61
  ui.log(`Generated message: ${message}`);
@@ -101,7 +101,8 @@ export default class Chat extends Command {
101
101
  if (flags.moe && multiProvider && tierConfigs) {
102
102
  const userMsg = messages.find(m => m.role === 'user')?.content || '';
103
103
  const routing = await routeTask(userMsg, async (prompt) => {
104
- return multiProvider.generateWithTier(1, prompt, [{ role: 'user', content: userMsg }]);
104
+ const res = await multiProvider.generateWithTier(1, prompt, [{ role: 'user', content: userMsg }]);
105
+ return res.message || res.thought || res.raw || '';
105
106
  });
106
107
  return multiProvider.generateWithTier(routing.tier, fullPrompt, llmMessages);
107
108
  }
@@ -144,7 +145,7 @@ export default class Chat extends Command {
144
145
  return { passed: result.passed, output: result.output };
145
146
  } : undefined,
146
147
  testFn: flags['auto-test'] && flags['test-cmd'] ? async () => {
147
- const { execute } = await import('../tools/runCommand.js');
148
+ const { execute } = await import('../tools/run_command.js');
148
149
  const result = await execute({ command: flags['test-cmd'] });
149
150
  return { passed: result.exitCode === 0, output: result.stdout + result.stderr };
150
151
  } : undefined,
package/dist/context.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Uses gpt-tokenizer for accurate token counting (with fallback)
5
5
  */
6
6
  import { readFile } from 'fs/promises';
7
- import { existsSync } from 'fs';
7
+ import { existsSync, readFileSync } from 'fs';
8
8
  import { relative, resolve } from 'path';
9
9
  import { generateRepoMap } from './repoMap.js';
10
10
  import { getActiveSkill } from './skills.js';
@@ -214,6 +214,15 @@ export class ContextManager {
214
214
  parts.push('... (truncated)');
215
215
  }
216
216
  }
217
+ // CLAW MODE: Inject JIT Agent Persona
218
+ const agentFile = resolve(this.cwd, '.simple', 'workdir', 'AGENT.md');
219
+ if ((process.argv.includes('--claw') || process.argv.includes('-claw')) && existsSync(agentFile)) {
220
+ try {
221
+ const agentPersona = readFileSync(agentFile, 'utf-8');
222
+ parts.push('\n\n' + agentPersona);
223
+ }
224
+ catch { /* ignore read errors */ }
225
+ }
217
226
  return parts.join('\n');
218
227
  }
219
228
  /**
@@ -241,8 +250,9 @@ export class ContextManager {
241
250
  for (const msg of this.history) {
242
251
  messages.push({ role: msg.role, content: msg.content });
243
252
  }
244
- // Current message
245
- messages.push({ role: 'user', content: userMessage });
253
+ // Current message with format reminder for JSON mode
254
+ const formatReminder = '\n\nCRITICAL: Respond with ONLY a JSON object. NO conversational text. No markdown wrappers. Use this format: {"thought": "...", "tool": "tool_name", "args": {...}}';
255
+ messages.push({ role: 'user', content: userMessage + formatReminder });
246
256
  return messages;
247
257
  }
248
258
  /**
@@ -5,6 +5,7 @@
5
5
  import { Message } from '../context.js';
6
6
  import { EditBlock, EditResult } from './editor.js';
7
7
  import { GitManager } from './git.js';
8
+ import type { TypeLLMResponse } from '@stan-chen/typellm';
8
9
  export interface AgentConfig {
9
10
  maxReflections: number;
10
11
  autoLint: boolean;
@@ -34,7 +35,7 @@ export interface ReflectionContext {
34
35
  /**
35
36
  * Parse LLM response into structured format
36
37
  */
37
- export declare function parseResponse(response: string): AgentResponse;
38
+ export declare function parseResponse(response: TypeLLMResponse | string): AgentResponse;
38
39
  /**
39
40
  * Build reflection prompt for retry
40
41
  */
@@ -60,7 +61,7 @@ export declare class Agent {
60
61
  constructor(options: {
61
62
  config: AgentConfig;
62
63
  git: GitManager;
63
- generateFn: (messages: Message[]) => Promise<string>;
64
+ generateFn: (messages: Message[]) => Promise<TypeLLMResponse>;
64
65
  executeTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
65
66
  lintFn?: (file: string) => Promise<{
66
67
  passed: boolean;
@@ -95,4 +96,4 @@ export declare class Agent {
95
96
  /**
96
97
  * Summarize conversation history to reduce tokens
97
98
  */
98
- export declare function summarizeHistory(history: Message[], generateFn: (messages: Message[]) => Promise<string>, maxMessages?: number): Promise<Message[]>;
99
+ export declare function summarizeHistory(history: Message[], generateFn: (messages: Message[]) => Promise<TypeLLMResponse>, maxMessages?: number): Promise<Message[]>;