notsleep 1.0.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 (93) hide show
  1. package/.env.example +117 -0
  2. package/LICENSE +21 -0
  3. package/README.md +341 -0
  4. package/dashboard/index.html +893 -0
  5. package/dist/agent.d.ts +162 -0
  6. package/dist/agent.d.ts.map +1 -0
  7. package/dist/agent.js +1064 -0
  8. package/dist/agent.js.map +1 -0
  9. package/dist/api.d.ts +10 -0
  10. package/dist/api.d.ts.map +1 -0
  11. package/dist/api.js +39 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/checkpoint.d.ts +52 -0
  14. package/dist/checkpoint.d.ts.map +1 -0
  15. package/dist/checkpoint.js +234 -0
  16. package/dist/checkpoint.js.map +1 -0
  17. package/dist/config.d.ts +24 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +265 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/context.d.ts +15 -0
  22. package/dist/context.d.ts.map +1 -0
  23. package/dist/context.js +237 -0
  24. package/dist/context.js.map +1 -0
  25. package/dist/cost.d.ts +26 -0
  26. package/dist/cost.d.ts.map +1 -0
  27. package/dist/cost.js +161 -0
  28. package/dist/cost.js.map +1 -0
  29. package/dist/dashboard.d.ts +96 -0
  30. package/dist/dashboard.d.ts.map +1 -0
  31. package/dist/dashboard.js +292 -0
  32. package/dist/dashboard.js.map +1 -0
  33. package/dist/fileconfig.d.ts +129 -0
  34. package/dist/fileconfig.d.ts.map +1 -0
  35. package/dist/fileconfig.js +391 -0
  36. package/dist/fileconfig.js.map +1 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +882 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/init.d.ts +9 -0
  42. package/dist/init.d.ts.map +1 -0
  43. package/dist/init.js +221 -0
  44. package/dist/init.js.map +1 -0
  45. package/dist/logger.d.ts +33 -0
  46. package/dist/logger.d.ts.map +1 -0
  47. package/dist/logger.js +150 -0
  48. package/dist/logger.js.map +1 -0
  49. package/dist/modelsdev.d.ts +63 -0
  50. package/dist/modelsdev.d.ts.map +1 -0
  51. package/dist/modelsdev.js +265 -0
  52. package/dist/modelsdev.js.map +1 -0
  53. package/dist/parallel.d.ts +54 -0
  54. package/dist/parallel.d.ts.map +1 -0
  55. package/dist/parallel.js +139 -0
  56. package/dist/parallel.js.map +1 -0
  57. package/dist/pool.d.ts +17 -0
  58. package/dist/pool.d.ts.map +1 -0
  59. package/dist/pool.js +53 -0
  60. package/dist/pool.js.map +1 -0
  61. package/dist/provider.d.ts +60 -0
  62. package/dist/provider.d.ts.map +1 -0
  63. package/dist/provider.js +396 -0
  64. package/dist/provider.js.map +1 -0
  65. package/dist/registry.d.ts +60 -0
  66. package/dist/registry.d.ts.map +1 -0
  67. package/dist/registry.js +153 -0
  68. package/dist/registry.js.map +1 -0
  69. package/dist/safety.d.ts +21 -0
  70. package/dist/safety.d.ts.map +1 -0
  71. package/dist/safety.js +118 -0
  72. package/dist/safety.js.map +1 -0
  73. package/dist/schema.d.ts +9 -0
  74. package/dist/schema.d.ts.map +1 -0
  75. package/dist/schema.js +166 -0
  76. package/dist/schema.js.map +1 -0
  77. package/dist/test-dashboard.d.ts +5 -0
  78. package/dist/test-dashboard.d.ts.map +1 -0
  79. package/dist/test-dashboard.js +202 -0
  80. package/dist/test-dashboard.js.map +1 -0
  81. package/dist/tools.d.ts +27 -0
  82. package/dist/tools.d.ts.map +1 -0
  83. package/dist/tools.js +1212 -0
  84. package/dist/tools.js.map +1 -0
  85. package/dist/types.d.ts +180 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +14 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/worktree.d.ts +50 -0
  90. package/dist/worktree.d.ts.map +1 -0
  91. package/dist/worktree.js +186 -0
  92. package/dist/worktree.js.map +1 -0
  93. package/package.json +67 -0
package/dist/index.js ADDED
@@ -0,0 +1,882 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // ============================================================
4
+ // NotSleep - CLI Entry Point
5
+ // ============================================================
6
+ //
7
+ // Two modes:
8
+ // notsleep → Interactive REPL (like OpenCode)
9
+ // notsleep "Build an API" → One-shot autonomous agent
10
+ //
11
+ // ============================================================
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ const commander_1 = require("commander");
50
+ const fs = __importStar(require("fs"));
51
+ const readline = __importStar(require("readline"));
52
+ const chalk_1 = __importDefault(require("chalk"));
53
+ const config_1 = require("./config");
54
+ const agent_1 = require("./agent");
55
+ const logger_1 = require("./logger");
56
+ const dashboard_1 = require("./dashboard");
57
+ const context_1 = require("./context");
58
+ const checkpoint_1 = require("./checkpoint");
59
+ const provider_1 = require("./provider");
60
+ const types_1 = require("./types");
61
+ const init_1 = require("./init");
62
+ const fileconfig_1 = require("./fileconfig");
63
+ const parallel_1 = require("./parallel");
64
+ const registry_1 = require("./registry");
65
+ const modelsdev_1 = require("./modelsdev");
66
+ const schema_1 = require("./schema");
67
+ const path = __importStar(require("path"));
68
+ const VERSION = '1.0.0';
69
+ const BANNER = `
70
+ ${chalk_1.default.bold.cyan('╔══════════════════════════════════════════════════════════╗')}
71
+ ${chalk_1.default.bold.cyan('║')} ${chalk_1.default.bold.white('NotSleep')} - Long Horizon Autonomous AI Agent ${chalk_1.default.bold.cyan('║')}
72
+ ${chalk_1.default.bold.cyan('║')} ${chalk_1.default.dim('"Do not stop until I woke up"')} ${chalk_1.default.bold.cyan('║')}
73
+ ${chalk_1.default.bold.cyan('║')} ${chalk_1.default.dim(`v${VERSION} | OpenAI + Anthropic Compatible`)} ${chalk_1.default.bold.cyan('║')}
74
+ ${chalk_1.default.bold.cyan('╚══════════════════════════════════════════════════════════╝')}
75
+ `;
76
+ const HELP_TEXT = `
77
+ ${chalk_1.default.bold('Commands:')}
78
+ ${chalk_1.default.cyan('/help')} Show this help
79
+ ${chalk_1.default.cyan('/run <task>')} Run agent on a task (autonomous, long-horizon)
80
+ ${chalk_1.default.cyan('/status')} Show agent status & stats
81
+ ${chalk_1.default.cyan('/context')} Show detected project context
82
+ ${chalk_1.default.cyan('/config')} Show resolved configuration & sources
83
+ ${chalk_1.default.cyan('/checkpoints')} List saved checkpoints you can resume from
84
+ ${chalk_1.default.cyan('/resume [task]')} Resume the latest checkpoint (autonomous)
85
+ ${chalk_1.default.cyan('/clear')} Clear conversation history
86
+ ${chalk_1.default.cyan('/model')} Show current model info
87
+ ${chalk_1.default.cyan('/exit')} Exit NotSleep
88
+
89
+ ${chalk_1.default.dim('Or just type normally to chat with the AI.')}
90
+ ${chalk_1.default.dim('The AI can see your project files and run commands.')}
91
+ `;
92
+ async function main() {
93
+ const program = new commander_1.Command();
94
+ // ── Subcommand: init ──
95
+ program
96
+ .command('init')
97
+ .description('Create a notsleep.json config file (interactive wizard)')
98
+ .option('-g, --global', 'Write to the global config (~/.notsleep/config.json)', false)
99
+ .option('-y, --yes', 'Skip prompts and write defaults', false)
100
+ .action(async (opts) => {
101
+ console.log(BANNER);
102
+ await (0, init_1.runInit)({ global: opts.global, yes: opts.yes });
103
+ process.exit(0);
104
+ });
105
+ // ── Subcommand: config ──
106
+ program
107
+ .command('config')
108
+ .description('Show the resolved configuration and where it came from')
109
+ .action(async () => {
110
+ console.log(BANNER);
111
+ // Refresh live model metadata so the shown context limit is accurate.
112
+ await (0, modelsdev_1.ensureModelCatalog)({ log: (m) => console.log(chalk_1.default.dim(` ${m}`)) });
113
+ await showConfig();
114
+ process.exit(0);
115
+ });
116
+ program
117
+ .command('schema')
118
+ .description('Write the notsleep.json JSON Schema to disk (for IDE autocomplete)')
119
+ .option('-o, --out <path>', 'Output path', schema_1.DEFAULT_SCHEMA_FILENAME)
120
+ .action(async (options) => {
121
+ const written = (0, schema_1.writeSchemaFile)(options.out);
122
+ console.log(chalk_1.default.green(` JSON Schema written to ${written}`));
123
+ console.log(chalk_1.default.dim(` Reference it in notsleep.json: "$schema": "./${schema_1.DEFAULT_SCHEMA_FILENAME}"`));
124
+ process.exit(0);
125
+ });
126
+ program
127
+ .name('notsleep')
128
+ .version(VERSION)
129
+ .description('Long-horizon autonomous AI agent. Interactive REPL or one-shot mode.')
130
+ .argument('[task...]', 'Task description. If omitted, starts interactive mode.')
131
+ .option('-p, --provider <provider>', 'LLM provider: openai or anthropic (default: openai, or from config)')
132
+ .option('-m, --model <model>', 'Model to use (overrides .env). Accepts provider/model, e.g. groq/llama-3.3-70b')
133
+ .option('--small-model <model>', 'Cheaper model for internal calls (summarize, task-gen). provider/model or a bare id')
134
+ .option('--resume', 'Resume from the latest checkpoint', false)
135
+ .option('--task-file <path>', 'Read the task description from a file')
136
+ .option('--max-iterations <n>', 'Maximum iterations (default: 1000; use 0 for unlimited)')
137
+ .option('--unlimited', 'Run with no iteration cap (same as --max-iterations 0)', false)
138
+ .option('--max-iterations-per-task <n>', 'Auto-advance to the next queued task after N iterations on one task (0 = no cap)')
139
+ .option('--context-limit <n>', 'Context window token limit (default: 120000, or from config)')
140
+ .option('--verbose', 'Enable verbose/debug logging', false)
141
+ .option('--openai-key <key>', 'OpenAI API key (overrides .env)')
142
+ .option('--anthropic-key <key>', 'Anthropic API key (overrides .env)')
143
+ .option('--base-url <url>', 'Custom API base URL')
144
+ .option('--system-prompt <prompt>', 'Custom system prompt')
145
+ .option('--system-prompt-file <path>', 'Read system prompt from a file')
146
+ .option('-c, --continuous', 'Continuous mode: keep picking up new tasks', false)
147
+ .option('--stop-file <path>', 'File path that signals the agent to stop')
148
+ .option('--max-cost <usd>', 'Stop when estimated cost exceeds this USD budget (e.g. 5.00)')
149
+ .option('--auto-commit', 'Create a local git commit after each completed task (never pushes)', false)
150
+ .option('--parallel <n>', 'Run tasks concurrently (n at a time), each in an isolated git worktree/branch')
151
+ .option('--tasks-file <path>', 'File with one task per line (used with --parallel)')
152
+ .option('--merge', 'After --parallel, merge each successful task branch back into the current branch', false)
153
+ .option('--no-cleanup', 'Keep task worktree directories after a --parallel run (for debugging)')
154
+ .option('--ui', 'Enable web dashboard UI', false)
155
+ .option('--port <port>', 'Dashboard port')
156
+ .option('--host <host>', 'Dashboard bind address (default 127.0.0.1; use 0.0.0.0 to expose to your network)')
157
+ .action(async (taskArgs, options) => {
158
+ console.log(BANNER);
159
+ // Sync live model metadata (context window + pricing) from models.dev
160
+ // BEFORE building config, so the context-window limit is auto-detected
161
+ // from the real model window rather than the static fallback table.
162
+ await (0, modelsdev_1.ensureModelCatalog)({
163
+ log: options.verbose ? (m) => console.log(chalk_1.default.dim(` ${m}`)) : undefined,
164
+ });
165
+ // Build config
166
+ const config = buildConfig(options);
167
+ // Layer 2: if the catalog didn't know this model (static fallback), ask
168
+ // the provider's own /models endpoint for the real context window.
169
+ await refineContextLimitFromEndpoint(config, options.verbose);
170
+ // Determine mode
171
+ let taskDescription = '';
172
+ if (options.taskFile) {
173
+ try {
174
+ taskDescription = fs.readFileSync(options.taskFile, 'utf-8').trim();
175
+ }
176
+ catch (err) {
177
+ console.error(chalk_1.default.red(`Error reading task file: ${err.message}`));
178
+ process.exit(1);
179
+ }
180
+ }
181
+ else if (taskArgs.length > 0) {
182
+ taskDescription = taskArgs.join(' ');
183
+ }
184
+ if (options.parallel) {
185
+ // ─── Parallel Mode ──────────────────────────────
186
+ await runParallel(config, options);
187
+ }
188
+ else if (taskDescription || options.resume) {
189
+ // ─── One-Shot Mode ──────────────────────────────
190
+ await runOneShot(config, taskDescription, options);
191
+ }
192
+ else {
193
+ // ─── Interactive REPL Mode ──────────────────────
194
+ await runInteractive(config, options);
195
+ }
196
+ });
197
+ await program.parseAsync(process.argv);
198
+ }
199
+ // ─────────────────────────────────────────────────────────────
200
+ // Config Builder
201
+ // ─────────────────────────────────────────────────────────────
202
+ function buildConfig(options) {
203
+ const overrides = {};
204
+ if (options.provider)
205
+ overrides.provider = options.provider;
206
+ if (options.openaiKey)
207
+ overrides.openai = { apiKey: options.openaiKey };
208
+ if (options.anthropicKey)
209
+ overrides.anthropic = { apiKey: options.anthropicKey };
210
+ // Resolve the EFFECTIVE provider so --model / --base-url land on the right
211
+ // block. Priority mirrors config.ts: CLI flag > file config > env > default.
212
+ // We must NOT assume 'openai' here — that would misroute overrides when the
213
+ // active provider comes from notsleep.json or the environment.
214
+ const { config: fileCfg } = (0, fileconfig_1.loadFileConfig)();
215
+ const effectiveProvider = (options.provider ||
216
+ fileCfg.provider ||
217
+ process.env.NOTSLEEP_PROVIDER ||
218
+ 'openai');
219
+ if (options.model) {
220
+ if ((0, registry_1.isModelRef)(options.model)) {
221
+ // Unified "provider/model" registry selector (e.g. groq/llama-3.3-70b).
222
+ // Resolved against the provider registry in loadConfig().
223
+ overrides.model = options.model;
224
+ }
225
+ else if (effectiveProvider === 'anthropic') {
226
+ overrides.anthropic = { ...overrides.anthropic, model: options.model };
227
+ }
228
+ else {
229
+ overrides.openai = { ...overrides.openai, model: options.model };
230
+ }
231
+ }
232
+ // Small model ref (provider/model or bare id) — resolved in loadConfig().
233
+ if (options.smallModel)
234
+ overrides.smallModelRef = options.smallModel;
235
+ if (options.baseUrl) {
236
+ if (effectiveProvider === 'anthropic') {
237
+ overrides.anthropic = { ...overrides.anthropic, baseURL: options.baseUrl };
238
+ }
239
+ else {
240
+ overrides.openai = { ...overrides.openai, baseURL: options.baseUrl };
241
+ }
242
+ }
243
+ if (options.unlimited) {
244
+ overrides.maxIterations = 0; // 0 = unlimited
245
+ }
246
+ else if (options.maxIterations !== undefined) {
247
+ overrides.maxIterations = parseInt(options.maxIterations, 10);
248
+ }
249
+ if (options.maxIterationsPerTask !== undefined) {
250
+ overrides.maxIterationsPerTask = parseInt(options.maxIterationsPerTask, 10);
251
+ }
252
+ if (options.contextLimit)
253
+ overrides.contextWindowLimit = parseInt(options.contextLimit, 10);
254
+ if (options.systemPromptFile) {
255
+ try {
256
+ overrides.systemPrompt = fs.readFileSync(options.systemPromptFile, 'utf-8').trim();
257
+ }
258
+ catch (err) {
259
+ console.error(chalk_1.default.red(`Error reading system prompt file: ${err.message}`));
260
+ process.exit(1);
261
+ }
262
+ }
263
+ else if (options.systemPrompt) {
264
+ overrides.systemPrompt = options.systemPrompt;
265
+ }
266
+ if (options.continuous)
267
+ overrides.continuous = true;
268
+ if (options.stopFile)
269
+ overrides.stopFile = options.stopFile;
270
+ if (options.autoCommit)
271
+ overrides.autoCommit = true;
272
+ if (options.maxCost) {
273
+ const c = parseFloat(options.maxCost);
274
+ if (!isNaN(c) && c > 0)
275
+ overrides.maxCostUsd = c;
276
+ }
277
+ try {
278
+ return (0, config_1.loadConfig)(overrides);
279
+ }
280
+ catch (err) {
281
+ console.error(chalk_1.default.red(`Configuration error: ${err.message}`));
282
+ process.exit(1);
283
+ }
284
+ }
285
+ // ─────────────────────────────────────────────────────────────
286
+ // One-Shot Mode (autonomous agent, like before)
287
+ // ─────────────────────────────────────────────────────────────
288
+ async function runOneShot(config, taskDescription, options) {
289
+ const logLevel = options.verbose ? logger_1.LogLevel.DEBUG : logger_1.LogLevel.INFO;
290
+ const logger = new logger_1.Logger(config.logDir, logLevel);
291
+ printConfigWarnings();
292
+ // Detect project context
293
+ const ctx = (0, context_1.detectProjectContext)();
294
+ logger.info(`Project: ${chalk_1.default.bold(ctx.language)} / ${chalk_1.default.bold(ctx.framework)}`);
295
+ logger.info(`Provider: ${chalk_1.default.bold(getProviderLabel(config))} | Model: ${chalk_1.default.bold(getModelName(config))}`);
296
+ logger.info(`Mode: ${chalk_1.default.bold(config.continuous ? 'CONTINUOUS' : 'SINGLE TASK')}`);
297
+ if (ctx.gitBranch) {
298
+ logger.info(`Git: ${chalk_1.default.bold(ctx.gitBranch)}${ctx.gitDirty ? chalk_1.default.yellow(' (dirty)') : chalk_1.default.green(' (clean)')}`);
299
+ }
300
+ // Inject project context into system prompt
301
+ config.systemPrompt += `\n\nPROJECT CONTEXT:\n${ctx.summary}`;
302
+ // Create agent
303
+ const agent = new agent_1.Agent(config, logger);
304
+ // Start dashboard if --ui flag OR enabled in config
305
+ let dashboard = null;
306
+ if (options.ui || config.dashboard.enabled) {
307
+ const port = options.port ? parseInt(options.port, 10) : config.dashboard.port;
308
+ dashboard = new dashboard_1.DashboardServer(port, logger, options.host || '127.0.0.1');
309
+ agent.setDashboard(dashboard);
310
+ try {
311
+ await dashboard.start();
312
+ logger.separator();
313
+ console.log(chalk_1.default.bold.green(` Dashboard: http://localhost:${port}`));
314
+ logger.separator();
315
+ }
316
+ catch (err) {
317
+ logger.error(`Failed to start dashboard: ${err.message}`);
318
+ }
319
+ }
320
+ // Run
321
+ const result = await agent.run(taskDescription, options.resume);
322
+ if (dashboard)
323
+ await dashboard.stop();
324
+ if (result.success) {
325
+ console.log(chalk_1.default.green.bold(`\n Task completed! (${result.stats.tasksCompleted} task(s))`));
326
+ console.log(chalk_1.default.white(result.summary));
327
+ process.exit(0);
328
+ }
329
+ else {
330
+ console.log(chalk_1.default.yellow.bold('\n Task did not complete.'));
331
+ console.log(chalk_1.default.white(result.summary));
332
+ console.log(chalk_1.default.dim(' Use --resume to continue from the last checkpoint.'));
333
+ process.exit(1);
334
+ }
335
+ }
336
+ // ─────────────────────────────────────────────────────────────
337
+ // Parallel Mode (multiple tasks concurrently, isolated worktrees)
338
+ // ─────────────────────────────────────────────────────────────
339
+ async function runParallel(config, options) {
340
+ const logLevel = options.verbose ? logger_1.LogLevel.DEBUG : logger_1.LogLevel.INFO;
341
+ const logger = new logger_1.Logger(config.logDir, logLevel);
342
+ printConfigWarnings();
343
+ const concurrency = parseInt(options.parallel, 10);
344
+ if (!Number.isFinite(concurrency) || concurrency < 1) {
345
+ console.error(chalk_1.default.red('--parallel needs a positive integer (e.g. --parallel 3).'));
346
+ process.exit(1);
347
+ }
348
+ // Gather tasks: one per line from --tasks-file.
349
+ let tasks = [];
350
+ if (options.tasksFile) {
351
+ let raw = '';
352
+ try {
353
+ raw = fs.readFileSync(options.tasksFile, 'utf-8');
354
+ }
355
+ catch (err) {
356
+ console.error(chalk_1.default.red(`Error reading tasks file: ${err.message}`));
357
+ process.exit(1);
358
+ }
359
+ tasks = raw
360
+ .split('\n')
361
+ .map((l) => l.trim())
362
+ .filter((l) => l.length > 0 && !l.startsWith('#'))
363
+ .map((description, i) => ({ id: `task-${i + 1}`, description }));
364
+ }
365
+ if (tasks.length === 0) {
366
+ console.error(chalk_1.default.red('No tasks found. Provide --tasks-file <path> with one task per line.'));
367
+ process.exit(1);
368
+ }
369
+ logger.info(`Loaded ${tasks.length} task(s) for parallel execution.`);
370
+ const runner = new parallel_1.ParallelRunner(process.cwd(), logger);
371
+ // Each task runs the built one-shot CLI as a child process, in its worktree.
372
+ // Forward the safety/limit flags so child agents behave like this invocation.
373
+ const cliEntry = path.resolve(__dirname, 'index.js');
374
+ const forwarded = [];
375
+ if (options.maxIterations !== undefined)
376
+ forwarded.push('--max-iterations', String(options.maxIterations));
377
+ if (options.unlimited)
378
+ forwarded.push('--unlimited');
379
+ if (options.maxCost !== undefined)
380
+ forwarded.push('--max-cost', String(options.maxCost));
381
+ if (options.autoCommit)
382
+ forwarded.push('--auto-commit');
383
+ if (options.provider)
384
+ forwarded.push('--provider', String(options.provider));
385
+ if (options.model)
386
+ forwarded.push('--model', String(options.model));
387
+ if (options.smallModel)
388
+ forwarded.push('--small-model', String(options.smallModel));
389
+ const taskRunner = (0, parallel_1.spawnAgentInWorktree)(process.execPath, cliEntry, forwarded, logger);
390
+ let outcomes;
391
+ try {
392
+ outcomes = await runner.run(tasks, taskRunner, {
393
+ concurrency,
394
+ merge: options.merge === true,
395
+ cleanup: options.cleanup !== false, // commander sets cleanup=false for --no-cleanup
396
+ });
397
+ }
398
+ catch (err) {
399
+ console.error(chalk_1.default.red(`Parallel run could not start: ${err.message}`));
400
+ process.exit(1);
401
+ }
402
+ // Summary
403
+ console.log('');
404
+ logger.separator();
405
+ console.log(chalk_1.default.bold(' Parallel run summary:'));
406
+ for (const o of outcomes) {
407
+ const status = o.ok ? chalk_1.default.green('ok') : chalk_1.default.red('failed');
408
+ const mergeNote = o.merged ? chalk_1.default.green(' merged') : o.conflict ? chalk_1.default.yellow(' conflict (kept branch)') : '';
409
+ console.log(` [${status}] ${o.task.id} -> ${o.branch}${mergeNote}`);
410
+ if (o.error)
411
+ console.log(chalk_1.default.dim(` ${o.error}`));
412
+ }
413
+ logger.separator();
414
+ const failed = outcomes.filter((o) => !o.ok).length;
415
+ process.exit(failed > 0 ? 1 : 0);
416
+ }
417
+ // ─────────────────────────────────────────────────────────────
418
+ // Interactive REPL Mode (like OpenCode)
419
+ // ─────────────────────────────────────────────────────────────
420
+ async function runInteractive(config, options) {
421
+ const logLevel = options.verbose ? logger_1.LogLevel.DEBUG : logger_1.LogLevel.INFO;
422
+ const logger = new logger_1.Logger(config.logDir, logLevel);
423
+ // Detect project context
424
+ const ctx = (0, context_1.detectProjectContext)();
425
+ printConfigWarnings();
426
+ // Show project info
427
+ console.log(chalk_1.default.dim(' Scanning project...'));
428
+ console.log(` ${chalk_1.default.bold('Project:')} ${ctx.language} / ${ctx.framework}`);
429
+ console.log(` ${chalk_1.default.bold('Provider:')} ${getProviderLabel(config)} / ${getModelName(config)}`);
430
+ if (ctx.gitBranch) {
431
+ console.log(` ${chalk_1.default.bold('Git:')} ${ctx.gitBranch}${ctx.gitDirty ? chalk_1.default.yellow(' (dirty)') : chalk_1.default.green(' (clean)')}`);
432
+ }
433
+ console.log(` ${chalk_1.default.bold('CWD:')} ${ctx.cwd}`);
434
+ console.log('');
435
+ console.log(chalk_1.default.dim(' Type a message to chat, /run <task> for autonomous mode, /help for commands.'));
436
+ console.log('');
437
+ // Build provider for direct chat
438
+ const provider = new provider_1.LLMProvider(config, logger);
439
+ // Inject project context into system prompt
440
+ const systemPrompt = config.systemPrompt +
441
+ `\n\nPROJECT CONTEXT:\n${ctx.summary}` +
442
+ `\n\nYou are in INTERACTIVE mode. The user is chatting with you directly. ` +
443
+ `You can use tools to help them. Be concise but helpful. ` +
444
+ `If they ask you to do something complex, use tools step by step.`;
445
+ // Conversation history
446
+ const messages = [
447
+ { role: 'system', content: systemPrompt },
448
+ ];
449
+ // Tools (from the agent's built-in tools + any user-defined custom tools)
450
+ const { createBuiltinTools, createCustomTools } = require('./tools');
451
+ const taskQueue = [];
452
+ const tools = createBuiltinTools(logger, config.toolTimeoutMs, taskQueue, process.cwd());
453
+ const customTools = createCustomTools(config.customTools, logger, config.toolTimeoutMs);
454
+ if (customTools.length > 0)
455
+ tools.push(...customTools);
456
+ // Remove task_complete from interactive mode - not needed
457
+ const interactiveTools = tools.filter((t) => t.name !== 'task_complete');
458
+ // Real prompt-token count from the API's last reply (0 until the first call),
459
+ // so /status reports the true context size instead of a chars/4 guess.
460
+ let lastPromptTokens = 0;
461
+ const chatTracked = async (msgs, toolsArg) => {
462
+ const r = await provider.chat(msgs, toolsArg);
463
+ if (r.usage && r.usage.prompt_tokens > 0)
464
+ lastPromptTokens = r.usage.prompt_tokens;
465
+ return r;
466
+ };
467
+ // Readline interface
468
+ const rl = readline.createInterface({
469
+ input: process.stdin,
470
+ output: process.stdout,
471
+ prompt: chalk_1.default.cyan('you > '),
472
+ });
473
+ rl.prompt();
474
+ rl.on('line', async (line) => {
475
+ const input = line.trim();
476
+ if (!input) {
477
+ rl.prompt();
478
+ return;
479
+ }
480
+ // ── Slash Commands ──
481
+ if (input.startsWith('/')) {
482
+ const [cmd, ...args] = input.split(' ');
483
+ switch (cmd) {
484
+ case '/help':
485
+ console.log(HELP_TEXT);
486
+ break;
487
+ case '/exit':
488
+ case '/quit':
489
+ console.log(chalk_1.default.dim(' Goodbye!'));
490
+ process.exit(0);
491
+ break; // unreachable (process.exit), but makes the fallthrough explicit
492
+ case '/clear':
493
+ messages.length = 0;
494
+ messages.push({ role: 'system', content: systemPrompt });
495
+ console.log(chalk_1.default.dim(' Conversation cleared.'));
496
+ break;
497
+ case '/context':
498
+ console.log(chalk_1.default.dim('─'.repeat(60)));
499
+ console.log(ctx.summary);
500
+ console.log(chalk_1.default.dim('─'.repeat(60)));
501
+ break;
502
+ case '/model':
503
+ console.log(` Provider: ${chalk_1.default.bold(config.provider)}`);
504
+ console.log(` Model: ${chalk_1.default.bold(getModelName(config))}`);
505
+ console.log(` Context: ${chalk_1.default.bold(config.contextWindowLimit.toLocaleString())} tokens`);
506
+ break;
507
+ case '/config':
508
+ await showConfig();
509
+ break;
510
+ case '/status':
511
+ console.log(` Messages in context: ${chalk_1.default.bold(String(messages.length))}`);
512
+ if (lastPromptTokens > 0) {
513
+ console.log(` Context tokens: ${chalk_1.default.bold(lastPromptTokens.toLocaleString())} ${chalk_1.default.dim('(from API)')}`);
514
+ }
515
+ else {
516
+ const estimatedTokens = Math.round(messages.reduce((sum, m) => sum + (m.content?.length || 0), 0) / 4);
517
+ console.log(` Context tokens: ${chalk_1.default.bold('~' + estimatedTokens.toLocaleString())} ${chalk_1.default.dim('(estimated, no API call yet)')}`);
518
+ }
519
+ console.log(` Tasks in queue: ${chalk_1.default.bold(String(taskQueue.length))}`);
520
+ break;
521
+ case '/run': {
522
+ const task = args.join(' ');
523
+ if (!task) {
524
+ console.log(chalk_1.default.red(' Usage: /run <task description>'));
525
+ break;
526
+ }
527
+ console.log(chalk_1.default.dim(` Starting autonomous agent for: "${task}"...`));
528
+ console.log(chalk_1.default.dim(' Press Ctrl+C to interrupt.\n'));
529
+ // Run as autonomous agent
530
+ const agentConfig = { ...config };
531
+ agentConfig.systemPrompt += `\n\nPROJECT CONTEXT:\n${ctx.summary}`;
532
+ const agentLogger = new logger_1.Logger(config.logDir, logLevel);
533
+ const agent = new agent_1.Agent(agentConfig, agentLogger);
534
+ // Start dashboard if --ui flag OR enabled in config
535
+ let dashboard = null;
536
+ if (options.ui || config.dashboard.enabled) {
537
+ const port = options.port ? parseInt(options.port, 10) : config.dashboard.port;
538
+ dashboard = new dashboard_1.DashboardServer(port, agentLogger, options.host || '127.0.0.1');
539
+ agent.setDashboard(dashboard);
540
+ try {
541
+ await dashboard.start();
542
+ console.log(chalk_1.default.bold.green(` Dashboard: http://localhost:${port}\n`));
543
+ }
544
+ catch { /* ignore */ }
545
+ }
546
+ const result = await agent.run(task, false);
547
+ if (dashboard)
548
+ await dashboard.stop();
549
+ if (result.success) {
550
+ console.log(chalk_1.default.green.bold(`\n Task completed! (${result.stats.tasksCompleted} task(s))`));
551
+ }
552
+ else {
553
+ console.log(chalk_1.default.yellow.bold('\n Task did not complete.'));
554
+ }
555
+ console.log(chalk_1.default.white(' ' + result.summary.substring(0, 300)));
556
+ console.log('');
557
+ break;
558
+ }
559
+ case '/checkpoints': {
560
+ const mgr = new checkpoint_1.CheckpointManager(config.checkpointDir, new logger_1.Logger(config.logDir, logger_1.LogLevel.ERROR));
561
+ const list = mgr.list();
562
+ if (list.length === 0) {
563
+ console.log(chalk_1.default.dim(' No checkpoints found. They are created automatically while an agent runs.'));
564
+ }
565
+ else {
566
+ console.log(chalk_1.default.bold(` ${list.length} checkpoint(s) (newest first):`));
567
+ for (const cp of list) {
568
+ console.log(` ${chalk_1.default.cyan(cp.timestamp)} iteration ${chalk_1.default.bold(String(cp.iteration))}`);
569
+ }
570
+ console.log(chalk_1.default.dim(' Use /resume to continue from the latest checkpoint.'));
571
+ }
572
+ break;
573
+ }
574
+ case '/resume': {
575
+ const mgr = new checkpoint_1.CheckpointManager(config.checkpointDir, new logger_1.Logger(config.logDir, logger_1.LogLevel.ERROR));
576
+ const latest = mgr.loadLatest();
577
+ if (!latest) {
578
+ console.log(chalk_1.default.yellow(' No checkpoint to resume from.'));
579
+ break;
580
+ }
581
+ const task = args.join(' ') || latest.taskDescription;
582
+ console.log(chalk_1.default.dim(` Resuming from checkpoint at iteration ${latest.iteration}...`));
583
+ console.log(chalk_1.default.dim(` Task: ${task.substring(0, 100)}`));
584
+ console.log(chalk_1.default.dim(' Press Ctrl+C to interrupt.\n'));
585
+ const agentConfig = { ...config };
586
+ agentConfig.systemPrompt += `\n\nPROJECT CONTEXT:\n${ctx.summary}`;
587
+ const agentLogger = new logger_1.Logger(config.logDir, logLevel);
588
+ const agent = new agent_1.Agent(agentConfig, agentLogger);
589
+ let dashboard = null;
590
+ if (options.ui || config.dashboard.enabled) {
591
+ const port = options.port ? parseInt(options.port, 10) : config.dashboard.port;
592
+ dashboard = new dashboard_1.DashboardServer(port, agentLogger, options.host || '127.0.0.1');
593
+ agent.setDashboard(dashboard);
594
+ try {
595
+ await dashboard.start();
596
+ console.log(chalk_1.default.bold.green(` Dashboard: http://localhost:${port}\n`));
597
+ }
598
+ catch { /* ignore */ }
599
+ }
600
+ const result = await agent.run(task, true); // resume=true
601
+ if (dashboard)
602
+ await dashboard.stop();
603
+ if (result.success) {
604
+ console.log(chalk_1.default.green.bold(`\n Task completed! (${result.stats.tasksCompleted} task(s))`));
605
+ }
606
+ else {
607
+ console.log(chalk_1.default.yellow.bold('\n Task did not complete.'));
608
+ }
609
+ console.log(chalk_1.default.white(' ' + result.summary.substring(0, 300)));
610
+ console.log('');
611
+ break;
612
+ }
613
+ default:
614
+ console.log(chalk_1.default.red(` Unknown command: ${cmd}. Type /help for available commands.`));
615
+ }
616
+ rl.prompt();
617
+ return;
618
+ }
619
+ // ── Normal Chat ──
620
+ messages.push({ role: 'user', content: input });
621
+ // Show thinking indicator
622
+ process.stdout.write(chalk_1.default.dim(' thinking...'));
623
+ try {
624
+ // Call LLM
625
+ const response = await chatTracked(messages, interactiveTools);
626
+ // Clear thinking indicator
627
+ process.stdout.write('\r' + ' '.repeat(30) + '\r');
628
+ // Handle tool calls
629
+ if (response.tool_calls.length > 0) {
630
+ // Add assistant message with tool calls
631
+ messages.push({
632
+ role: 'assistant',
633
+ content: response.content || '',
634
+ tool_calls: response.tool_calls,
635
+ });
636
+ if (response.content) {
637
+ console.log(chalk_1.default.green(' ai > ') + response.content);
638
+ }
639
+ // Execute tools
640
+ for (const tc of response.tool_calls) {
641
+ const tool = interactiveTools.find((t) => t.name === tc.function.name);
642
+ if (!tool) {
643
+ console.log(chalk_1.default.red(` Unknown tool: ${tc.function.name}`));
644
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: 'Unknown tool' });
645
+ continue;
646
+ }
647
+ let args;
648
+ try {
649
+ args = JSON.parse(tc.function.arguments);
650
+ }
651
+ catch {
652
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: 'Invalid JSON arguments' });
653
+ continue;
654
+ }
655
+ console.log(chalk_1.default.magenta(` [${tc.function.name}]`) + chalk_1.default.dim(` ${JSON.stringify(args).substring(0, 100)}`));
656
+ try {
657
+ const result = await tool.execute(args);
658
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
659
+ // Show truncated result
660
+ const displayResult = result.length > 500 ? result.substring(0, 500) + '...' : result;
661
+ console.log(chalk_1.default.dim(' ' + displayResult.split('\n').join('\n ')));
662
+ }
663
+ catch (err) {
664
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: `Error: ${err.message}` });
665
+ console.log(chalk_1.default.red(` Error: ${err.message}`));
666
+ }
667
+ }
668
+ // Get follow-up response after tool results
669
+ const followUp = await chatTracked(messages, interactiveTools);
670
+ // Always show the follow-up text, but only push a content-only
671
+ // assistant message when there are NO tool calls. If tool_calls are
672
+ // present, the loop below pushes the assistant message together with
673
+ // its tool_calls — pushing here too would duplicate the turn and
674
+ // orphan the text from its tool calls, corrupting the history.
675
+ if (followUp.content) {
676
+ console.log('');
677
+ console.log(chalk_1.default.green(' ai > ') + followUp.content);
678
+ if (followUp.tool_calls.length === 0) {
679
+ messages.push({ role: 'assistant', content: followUp.content });
680
+ }
681
+ }
682
+ // Handle nested tool calls (up to 5 rounds)
683
+ let depth = 0;
684
+ let currentResponse = followUp;
685
+ while (currentResponse.tool_calls.length > 0 && depth < 5) {
686
+ depth++;
687
+ messages.push({
688
+ role: 'assistant',
689
+ content: currentResponse.content || '',
690
+ tool_calls: currentResponse.tool_calls,
691
+ });
692
+ for (const tc of currentResponse.tool_calls) {
693
+ const tool = interactiveTools.find((t) => t.name === tc.function.name);
694
+ if (!tool) {
695
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: 'Unknown tool' });
696
+ continue;
697
+ }
698
+ let args;
699
+ try {
700
+ args = JSON.parse(tc.function.arguments);
701
+ }
702
+ catch {
703
+ args = {};
704
+ }
705
+ console.log(chalk_1.default.magenta(` [${tc.function.name}]`) + chalk_1.default.dim(` ${JSON.stringify(args).substring(0, 100)}`));
706
+ try {
707
+ const result = await tool.execute(args);
708
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
709
+ const displayResult = result.length > 500 ? result.substring(0, 500) + '...' : result;
710
+ console.log(chalk_1.default.dim(' ' + displayResult.split('\n').join('\n ')));
711
+ }
712
+ catch (err) {
713
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: `Error: ${err.message}` });
714
+ }
715
+ }
716
+ currentResponse = await chatTracked(messages, interactiveTools);
717
+ // Same rule as above: only push content-only here. If this response
718
+ // has tool_calls, the next loop iteration pushes it with them.
719
+ if (currentResponse.content) {
720
+ console.log('');
721
+ console.log(chalk_1.default.green(' ai > ') + currentResponse.content);
722
+ if (currentResponse.tool_calls.length === 0) {
723
+ messages.push({ role: 'assistant', content: currentResponse.content });
724
+ }
725
+ }
726
+ }
727
+ }
728
+ else {
729
+ // Simple text response
730
+ messages.push({ role: 'assistant', content: response.content || '' });
731
+ console.log(chalk_1.default.green(' ai > ') + (response.content || ''));
732
+ }
733
+ }
734
+ catch (err) {
735
+ process.stdout.write('\r' + ' '.repeat(30) + '\r');
736
+ console.log(chalk_1.default.red(` Error: ${err.message}`));
737
+ }
738
+ console.log('');
739
+ rl.prompt();
740
+ });
741
+ rl.on('close', () => {
742
+ console.log(chalk_1.default.dim('\n Goodbye!'));
743
+ process.exit(0);
744
+ });
745
+ }
746
+ // ─────────────────────────────────────────────────────────────
747
+ // Helpers
748
+ // ─────────────────────────────────────────────────────────────
749
+ function getModelName(config) {
750
+ // When a unified "provider/model" selector is in use, show it verbatim
751
+ // (e.g. "groq/llama-3.3-70b-versatile") — that's what the user typed.
752
+ if ((0, registry_1.isModelRef)(config.model))
753
+ return config.model;
754
+ if (config.provider === 'openai')
755
+ return config.openai?.model || 'gpt-4o';
756
+ return config.anthropic?.model || 'claude-sonnet-4-20250514';
757
+ }
758
+ /** Display label for the active provider: registry name if set, else protocol. */
759
+ function getProviderLabel(config) {
760
+ if ((0, registry_1.isModelRef)(config.model))
761
+ return config.model.slice(0, config.model.indexOf('/'));
762
+ return config.provider;
763
+ }
764
+ /**
765
+ * Layer 2 of the context-window sync: when the model wasn't found in the
766
+ * models.dev catalog (contextLimitSource === 'static') and the user didn't pin
767
+ * a limit, ask the provider's own OpenAI-style /models endpoint for the real
768
+ * context window. Useful for custom proxies (e.g. LimitRouter) and models the
769
+ * catalog doesn't list. Best-effort and quiet: any failure leaves the static
770
+ * fallback untouched. Skips native Anthropic (no baseURL / no /models).
771
+ */
772
+ async function refineContextLimitFromEndpoint(config, verbose) {
773
+ if ((0, config_1.getConfigSources)().contextLimitSource !== 'static')
774
+ return;
775
+ const baseURL = config.provider === 'openai' ? config.openai?.baseURL : config.anthropic?.baseURL;
776
+ const apiKey = config.provider === 'openai' ? config.openai?.apiKey : config.anthropic?.apiKey;
777
+ const modelId = config.provider === 'openai' ? config.openai?.model : config.anthropic?.model;
778
+ if (!baseURL || !modelId)
779
+ return;
780
+ const live = await (0, modelsdev_1.fetchEndpointContextLimit)(baseURL, apiKey, modelId);
781
+ if (live && live > 0) {
782
+ config.contextWindowLimit = Math.floor(live * 0.9);
783
+ (0, config_1.markContextLimitSource)('endpoint');
784
+ if (verbose) {
785
+ console.log(chalk_1.default.dim(` Context window ${live.toLocaleString()} tokens from ${baseURL}/models`));
786
+ }
787
+ }
788
+ }
789
+ /** Print any config warnings collected during loadConfig(). */
790
+ function printConfigWarnings() {
791
+ const { warnings } = (0, config_1.getConfigSources)();
792
+ if (warnings.length > 0) {
793
+ console.log(chalk_1.default.yellow.bold(' Config warnings:'));
794
+ for (const w of warnings) {
795
+ console.log(chalk_1.default.yellow(` ! ${w}`));
796
+ }
797
+ console.log('');
798
+ }
799
+ }
800
+ /** Mask a secret, showing only the last 4 chars. */
801
+ function maskSecret(secret) {
802
+ if (!secret)
803
+ return chalk_1.default.red('(not set)');
804
+ if (secret.length <= 4)
805
+ return '****';
806
+ return '****' + secret.slice(-4);
807
+ }
808
+ /** Print the resolved config and its sources (for `notsleep config`). */
809
+ async function showConfig() {
810
+ // Show config file locations first (before validation can throw).
811
+ const globalPath = (0, fileconfig_1.getGlobalConfigPath)();
812
+ const projectPath = (0, fileconfig_1.findProjectConfig)();
813
+ console.log(chalk_1.default.bold(' Config files:'));
814
+ console.log(` Global: ${fs.existsSync(globalPath) ? globalPath : chalk_1.default.dim(globalPath + ' (none)')}`);
815
+ console.log(` Project: ${projectPath ? projectPath : chalk_1.default.dim('(none found)')}`);
816
+ console.log('');
817
+ let config;
818
+ try {
819
+ config = (0, config_1.loadConfig)();
820
+ }
821
+ catch (err) {
822
+ // loadConfig() ran far enough to populate (filtered) warnings — show them.
823
+ printConfigWarnings();
824
+ // Still show what we can even if validation fails.
825
+ console.log(chalk_1.default.yellow(` Warning: ${err.message}`));
826
+ const { config: file } = (0, fileconfig_1.loadFileConfig)();
827
+ console.log('');
828
+ console.log(chalk_1.default.bold(' Partial config (from files/env):'));
829
+ console.log(' ' + JSON.stringify(file, null, 2).split('\n').join('\n '));
830
+ return;
831
+ }
832
+ // Layer 2 endpoint fallback, same as a real run, so the shown limit matches.
833
+ await refineContextLimitFromEndpoint(config, false);
834
+ // Surface filtered config warnings (malformed env refs, missing active-provider vars).
835
+ printConfigWarnings();
836
+ const sources = (0, config_1.getConfigSources)();
837
+ const limitSourceLabel = sources.contextLimitSource === 'catalog' ? chalk_1.default.dim(' (models.dev)')
838
+ : sources.contextLimitSource === 'endpoint' ? chalk_1.default.dim(' (/models endpoint)')
839
+ : sources.contextLimitSource === 'explicit' ? chalk_1.default.dim(' (set)')
840
+ : chalk_1.default.dim(' (static table)');
841
+ const key = config.provider === 'openai' ? config.openai?.apiKey : config.anthropic?.apiKey;
842
+ console.log(chalk_1.default.bold(' Resolved configuration:'));
843
+ console.log(` Provider: ${chalk_1.default.cyan(getProviderLabel(config))}`);
844
+ console.log(` Model: ${chalk_1.default.cyan(getModelName(config))}`);
845
+ if (config.smallModel) {
846
+ console.log(` Small model: ${chalk_1.default.cyan(config.smallModel.model)} ${chalk_1.default.dim('(summarize / task-gen)')}`);
847
+ }
848
+ console.log(` API key: ${maskSecret(key || '')}`);
849
+ const baseURL = config.provider === 'openai' ? config.openai?.baseURL : config.anthropic?.baseURL;
850
+ if (baseURL)
851
+ console.log(` Base URL: ${baseURL}`);
852
+ console.log(` Max iterations: ${(0, types_1.isUnlimitedIterations)(config.maxIterations) ? 'unlimited' : config.maxIterations}`);
853
+ console.log(` Max iter/task: ${config.maxIterationsPerTask > 0 ? config.maxIterationsPerTask : 'no cap'}`);
854
+ console.log(` Context limit: ${config.contextWindowLimit.toLocaleString()} tokens${limitSourceLabel}`);
855
+ console.log(` Summarize at: ${Math.round(config.summarizeThreshold * 100)}%`);
856
+ console.log(` Max retries: ${config.maxRetries}`);
857
+ console.log(` Tool timeout: ${config.toolTimeoutMs}ms`);
858
+ console.log(` Continuous mode: ${config.continuous ? chalk_1.default.green('on') : chalk_1.default.dim('off')}`);
859
+ console.log(` Max cost (USD): ${config.maxCostUsd ? chalk_1.default.cyan('$' + config.maxCostUsd) : chalk_1.default.dim('no limit')}`);
860
+ console.log(` Auto-commit: ${config.autoCommit ? chalk_1.default.green('on (local commits per task)') : chalk_1.default.dim('off')}`);
861
+ console.log(` Dashboard: ${config.dashboard.enabled ? chalk_1.default.green(`on (port ${config.dashboard.port})`) : chalk_1.default.dim('off')}`);
862
+ console.log(` Checkpoint dir: ${config.checkpointDir}`);
863
+ console.log(` Log dir: ${config.logDir}`);
864
+ console.log(` Stop file: ${config.stopFile}`);
865
+ if (sources.instructionFiles.length > 0) {
866
+ console.log(` Instructions: ${chalk_1.default.cyan(sources.instructionFiles.join(', '))}`);
867
+ }
868
+ console.log('');
869
+ console.log(chalk_1.default.dim(` .env loaded: ${sources.envLoaded ? 'yes' : 'no'}`));
870
+ if (sources.fileConfigSources.length > 0) {
871
+ console.log(chalk_1.default.dim(` Config sources: ${sources.fileConfigSources.join(', ')}`));
872
+ }
873
+ console.log('');
874
+ console.log(chalk_1.default.dim(' Run "notsleep init" to create or update your config file.'));
875
+ }
876
+ // Run
877
+ main().catch((err) => {
878
+ console.error(chalk_1.default.red(`Fatal error: ${err.message}`));
879
+ console.error(err.stack);
880
+ process.exit(1);
881
+ });
882
+ //# sourceMappingURL=index.js.map