@ekkos/cli 1.3.7 → 1.3.9

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.
@@ -69,16 +69,24 @@ const usage_parser_js_1 = require("../lib/usage-parser.js");
69
69
  const state_js_1 = require("../utils/state.js");
70
70
  const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
71
71
  // ── Pricing ──
72
- // Pricing per MTok from https://platform.claude.com/docs/en/about-claude/pricing
72
+ // Pricing per MTok from https://docs.anthropic.com/en/docs/about-claude/pricing
73
+ // Includes prompt caching rates (5m cache write=1.25x input, cache read=0.1x input).
73
74
  const MODEL_PRICING = {
74
75
  'claude-opus-4-6-20260514': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
75
76
  'claude-opus-4-6': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
77
+ 'claude-opus-4-5-20251101': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
78
+ 'claude-opus-4-5': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
79
+ 'claude-opus-4-20250514': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
80
+ 'claude-opus-4': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
76
81
  'claude-sonnet-4-6-20260514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
77
- 'claude-opus-4-5-20250620': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
78
82
  'claude-sonnet-4-6': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
79
83
  'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
84
+ 'claude-sonnet-4-5': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
80
85
  'claude-sonnet-4-5-20250514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
86
+ 'claude-sonnet-4-20250514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
87
+ 'claude-sonnet-4': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
81
88
  'claude-haiku-4-5-20251001': { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 },
89
+ 'claude-haiku-4-5': { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 },
82
90
  };
83
91
  function getModelPricing(modelId) {
84
92
  if (MODEL_PRICING[modelId])
@@ -86,13 +94,33 @@ function getModelPricing(modelId) {
86
94
  if (modelId.includes('opus'))
87
95
  return MODEL_PRICING['claude-opus-4-6'];
88
96
  if (modelId.includes('sonnet'))
89
- return MODEL_PRICING['claude-sonnet-4-5-20250929'];
97
+ return MODEL_PRICING['claude-sonnet-4-6'];
90
98
  if (modelId.includes('haiku'))
91
99
  return MODEL_PRICING['claude-haiku-4-5-20251001'];
92
- return MODEL_PRICING['claude-sonnet-4-5-20250929'];
100
+ return MODEL_PRICING['claude-sonnet-4-6'];
93
101
  }
94
- function calculateTurnCost(model, usage) {
95
- const p = getModelPricing(model);
102
+ // Claude Sonnet 4/4.5 has legacy long-context premium rates when context-1m
103
+ // is enabled and input exceeds 200k. Sonnet/Opus 4.6 is GA 1M at standard rates.
104
+ function isLegacyLongContextPremiumModel(modelId) {
105
+ const normalized = (modelId || '').toLowerCase();
106
+ if (/^claude-sonnet-4-6(?:$|-)/.test(normalized))
107
+ return false;
108
+ return /^claude-sonnet-4(?:$|-)/.test(normalized);
109
+ }
110
+ function calculateTurnCost(model, usage, options) {
111
+ const base = getModelPricing(model);
112
+ const longContextPremium = options?.longContextPremium === true
113
+ && isLegacyLongContextPremiumModel(model);
114
+ const p = longContextPremium
115
+ ? {
116
+ // Long context pricing modifiers:
117
+ // Input/cache bucket = 2x, output = 1.5x.
118
+ input: base.input * 2,
119
+ output: base.output * 1.5,
120
+ cacheWrite: base.cacheWrite * 2,
121
+ cacheRead: base.cacheRead * 2,
122
+ }
123
+ : base;
96
124
  return ((usage.input_tokens / 1000000) * p.input +
97
125
  (usage.output_tokens / 1000000) * p.output +
98
126
  (usage.cache_creation_tokens / 1000000) * p.cacheWrite +
@@ -199,6 +227,9 @@ function parseJsonlFile(jsonlPath, sessionName) {
199
227
  const contextTierHint = typeof entry.message._ekkos_context_tier === 'string'
200
228
  ? entry.message._ekkos_context_tier.trim().toLowerCase()
201
229
  : undefined;
230
+ const explicitExtraUsage = entry.message._ekkos_extra_usage === true
231
+ || entry.message._ekkos_extra_usage === '1'
232
+ || entry.message._ekkos_extra_usage === 'true';
202
233
  const inputTokens = usage.input_tokens || 0;
203
234
  const outputTokens = usage.output_tokens || 0;
204
235
  const cacheReadTokens = usage.cache_read_input_tokens || 0;
@@ -215,11 +246,19 @@ function parseJsonlFile(jsonlPath, sessionName) {
215
246
  cache_read_tokens: cacheReadTokens,
216
247
  cache_creation_tokens: cacheCreationTokens,
217
248
  };
249
+ const legacyLongContextPremium = isLegacyLongContextPremiumModel(routedModel) && (explicitExtraUsage
250
+ || (contextTierHint === '1m' && contextTokens > 200000));
218
251
  // Cost at actual model pricing (Haiku if routed)
219
- const turnCost = calculateTurnCost(routedModel, usageData);
252
+ const turnCost = calculateTurnCost(routedModel, usageData, {
253
+ longContextPremium: legacyLongContextPremium,
254
+ });
220
255
  // Cost if it had been Opus (for savings calculation)
256
+ const requestedModelPremium = isLegacyLongContextPremiumModel(model) && (explicitExtraUsage
257
+ || (contextTierHint === '1m' && contextTokens > 200000));
221
258
  const opusCost = routedModel !== model
222
- ? calculateTurnCost(model, usageData)
259
+ ? calculateTurnCost(model, usageData, {
260
+ longContextPremium: requestedModelPremium,
261
+ })
223
262
  : turnCost;
224
263
  const savings = opusCost - turnCost;
225
264
  const msgTools = toolsByMessage.get(msgId);
@@ -388,7 +427,20 @@ function resolveJsonlPath(sessionName, createdAfterMs) {
388
427
  if (fs.existsSync(activeSessionsPath)) {
389
428
  try {
390
429
  const sessions = JSON.parse(fs.readFileSync(activeSessionsPath, 'utf-8'));
391
- const match = sessions.find((s) => s.sessionName === sessionName);
430
+ // Skip stale entries whose PID is dead (prevents cross-binding after restart)
431
+ const match = sessions.find((s) => {
432
+ if (s.sessionName !== sessionName)
433
+ return false;
434
+ if (s.pid && s.pid > 1) {
435
+ try {
436
+ process.kill(s.pid, 0);
437
+ }
438
+ catch {
439
+ return false;
440
+ }
441
+ }
442
+ return true;
443
+ });
392
444
  if (match?.projectPath) {
393
445
  if (isStableSessionId(match.sessionId)) {
394
446
  // Prefer exact sessionId lookup, but if that file does not exist yet
@@ -2165,10 +2217,14 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
2165
2217
  if (!sessionName)
2166
2218
  process.exit(0);
2167
2219
  }
2168
- const jsonlPath = resolveJsonlPath(sessionName);
2220
+ // Use launch timestamp as lower bound so initial resolution never picks up
2221
+ // stale JSONL files from previous sessions (the lazy resolution already does
2222
+ // this correctly — this ensures the INITIAL resolve matches).
2223
+ const launchTs = Date.now();
2224
+ const jsonlPath = resolveJsonlPath(sessionName, launchTs);
2169
2225
  if (!jsonlPath) {
2170
2226
  // JSONL may not exist yet (session just started) — launch with lazy resolution
2171
2227
  console.log(chalk_1.default.gray(`Waiting for JSONL for "${sessionName}"...`));
2172
2228
  }
2173
- await launchDashboard(sessionName, jsonlPath || null, refreshMs, null, Date.now());
2229
+ await launchDashboard(sessionName, jsonlPath || null, refreshMs, null, launchTs);
2174
2230
  });
@@ -59,11 +59,53 @@ const proxy_url_1 = require("../utils/proxy-url");
59
59
  let cliSessionName = null;
60
60
  let cliSessionId = null;
61
61
  const isWindows = process.platform === 'win32';
62
+ const PULSE_LOADED_TEXT = ' 🧠 ekkOS_Pulse Loaded!';
63
+ const PULSE_SHINE_FRAME_MS = 50;
64
+ const PULSE_SHINE_SWEEPS = 2;
65
+ function sleep(ms) {
66
+ return new Promise(resolve => setTimeout(resolve, ms));
67
+ }
68
+ function renderPulseShineFrame(text, shineIndex) {
69
+ const chars = Array.from(text);
70
+ return chars.map((ch, i) => {
71
+ const dist = Math.abs(i - shineIndex);
72
+ if (dist === 0)
73
+ return chalk_1.default.whiteBright.bold(ch);
74
+ if (dist === 1)
75
+ return chalk_1.default.yellowBright.bold(ch);
76
+ if (dist === 2)
77
+ return chalk_1.default.yellowBright(ch);
78
+ return chalk_1.default.yellow(ch);
79
+ }).join('');
80
+ }
81
+ async function showPulseLoadedBanner() {
82
+ if (!process.stdout.isTTY) {
83
+ console.log(chalk_1.default.cyan(PULSE_LOADED_TEXT));
84
+ return;
85
+ }
86
+ const charCount = Array.from(PULSE_LOADED_TEXT).length;
87
+ for (let sweep = 0; sweep < PULSE_SHINE_SWEEPS; sweep++) {
88
+ for (let shineIndex = -3; shineIndex < charCount + 3; shineIndex++) {
89
+ process.stdout.write(`\r${renderPulseShineFrame(PULSE_LOADED_TEXT, shineIndex)}`);
90
+ await sleep(PULSE_SHINE_FRAME_MS);
91
+ }
92
+ }
93
+ process.stdout.write(`\r${chalk_1.default.yellow(PULSE_LOADED_TEXT)}\n`);
94
+ }
62
95
  /**
63
96
  * Resolve Gemini CLI binary path.
64
- * Checks common locations then falls back to PATH lookup.
97
+ * Checks PATH first (which/where), then falls back to common locations.
65
98
  */
66
99
  function resolveGeminiPath() {
100
+ // 1. PATH lookup first to respect nvm/volta/npm-global settings
101
+ const whichCmd = isWindows ? 'where gemini' : 'which gemini';
102
+ try {
103
+ const result = (0, child_process_1.execSync)(whichCmd, { encoding: 'utf-8', stdio: 'pipe' }).trim();
104
+ if (result)
105
+ return result.split('\n')[0];
106
+ }
107
+ catch { /* not found in PATH */ }
108
+ // 2. Fallback to common global install locations
67
109
  if (!isWindows) {
68
110
  const pathsToCheck = [
69
111
  '/opt/homebrew/bin/gemini',
@@ -76,15 +118,7 @@ function resolveGeminiPath() {
76
118
  return p;
77
119
  }
78
120
  }
79
- // PATH lookup
80
- const whichCmd = isWindows ? 'where gemini' : 'which gemini';
81
- try {
82
- const result = (0, child_process_1.execSync)(whichCmd, { encoding: 'utf-8', stdio: 'pipe' }).trim();
83
- if (result)
84
- return result.split('\n')[0];
85
- }
86
- catch { /* not found */ }
87
- // Fallback — let spawn resolve it (will error with helpful message)
121
+ // 3. Ultimate fallback — let spawn resolve it (will error with helpful message)
88
122
  return 'gemini';
89
123
  }
90
124
  /**
@@ -118,13 +152,6 @@ function buildGeminiEnv(options) {
118
152
  const proxyUrl = (0, proxy_url_1.buildGeminiProxyUrl)(userId, cliSessionName, process.cwd(), cliSessionId);
119
153
  env.GOOGLE_GEMINI_BASE_URL = proxyUrl;
120
154
  env.GOOGLE_VERTEX_BASE_URL = proxyUrl;
121
- // User must bring their own Gemini API key — ekkOS just proxies the traffic
122
- // for IPC compression and pattern injection (same model as Claude).
123
- if (!env.GEMINI_API_KEY && !env.GOOGLE_API_KEY) {
124
- console.warn(chalk_1.default.yellow(' ⚠ GEMINI_API_KEY not set.'));
125
- console.warn(chalk_1.default.gray(' Get one at: https://aistudio.google.com/apikey'));
126
- console.warn(chalk_1.default.gray(' Then: export GEMINI_API_KEY=your_key'));
127
- }
128
155
  if (options.verbose) {
129
156
  // Redact userId from log
130
157
  const safeUrl = proxyUrl.replace(/\/proxy\/[^/]+\//, '/proxy/[user]/');
@@ -151,7 +178,7 @@ async function gemini(options = {}) {
151
178
  const sessionName = cliSessionName || 'gemini-session';
152
179
  (0, state_1.registerActiveSession)(sessionId, sessionName, process.cwd());
153
180
  if (!options.noProxy) {
154
- console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
181
+ await showPulseLoadedBanner();
155
182
  }
156
183
  console.log('');
157
184
  // Extract any trailing arguments meant for the inner CLI
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.init = init;
7
7
  const fs_1 = require("fs");
8
+ const child_process_1 = require("child_process");
9
+ const path_1 = require("path");
8
10
  const chalk_1 = __importDefault(require("chalk"));
9
11
  const inquirer_1 = __importDefault(require("inquirer"));
10
12
  const ora_1 = __importDefault(require("ora"));
@@ -15,6 +17,7 @@ const settings_1 = require("../deploy/settings");
15
17
  // DEPRECATED: Hooks removed in hookless architecture migration
16
18
  // import { deployHooks } from '../deploy/hooks';
17
19
  const skills_1 = require("../deploy/skills");
20
+ const agents_1 = require("../deploy/agents");
18
21
  const instructions_1 = require("../deploy/instructions");
19
22
  const templates_1 = require("../utils/templates");
20
23
  // ═══════════════════════════════════════════════════════════════════════════
@@ -34,15 +37,20 @@ async function pollForApproval(code, expiresIn) {
34
37
  const pollInterval = 3000; // 3 seconds
35
38
  const maxAttempts = Math.floor((expiresIn * 1000) / pollInterval);
36
39
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
37
- const response = await fetch(`${platform_1.PLATFORM_URL}/api/device-auth/codes/${code}/status`, { method: 'GET' });
38
- if (response.status === 410) {
39
- return { status: 'expired' };
40
- }
41
- if (response.ok) {
42
- const data = await response.json();
43
- if (data.status !== 'pending') {
44
- return data;
40
+ try {
41
+ const response = await fetch(`${platform_1.PLATFORM_URL}/api/device-auth/codes/${code}/status`, { method: 'GET' });
42
+ if (response.status === 410) {
43
+ return { status: 'expired' };
45
44
  }
45
+ if (response.ok) {
46
+ const data = await response.json();
47
+ if (data.status !== 'pending') {
48
+ return data;
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ // Network error — silently retry instead of killing the flow
46
54
  }
47
55
  // Wait before next poll
48
56
  await new Promise(resolve => setTimeout(resolve, pollInterval));
@@ -77,17 +85,33 @@ async function deviceAuth() {
77
85
  throw error;
78
86
  }
79
87
  console.log('');
80
- console.log(chalk_1.default.white.bold(`Your code: ${chalk_1.default.cyan.bold(deviceCode.code)}`));
88
+ console.log(chalk_1.default.white.bold(` Your code: ${chalk_1.default.cyan.bold(deviceCode.code)}`));
89
+ // Auto-copy code to clipboard
90
+ try {
91
+ if (process.platform === 'darwin') {
92
+ (0, child_process_1.execSync)(`echo -n "${deviceCode.code}" | pbcopy`, { stdio: 'ignore' });
93
+ }
94
+ else if (process.platform === 'win32') {
95
+ (0, child_process_1.execSync)(`echo|set /p="${deviceCode.code}" | clip`, { stdio: 'ignore' });
96
+ }
97
+ else {
98
+ (0, child_process_1.execSync)(`echo -n "${deviceCode.code}" | xclip -selection clipboard`, { stdio: 'ignore' });
99
+ }
100
+ console.log(chalk_1.default.green(' ✓ Copied to clipboard'));
101
+ }
102
+ catch {
103
+ // Clipboard not available — no problem, code is displayed
104
+ }
81
105
  console.log('');
82
106
  // Open browser
83
107
  const verificationUrl = deviceCode.verificationUrl || `${platform_1.PLATFORM_URL}/activate`;
84
- console.log(chalk_1.default.gray(`Opening browser to: ${verificationUrl}`));
108
+ console.log(chalk_1.default.gray(` Opening browser ${verificationUrl}`));
85
109
  try {
86
110
  await (0, open_1.default)(verificationUrl);
87
111
  }
88
112
  catch {
89
- console.log(chalk_1.default.yellow('Could not open browser automatically.'));
90
- console.log(chalk_1.default.gray(`Please visit: ${verificationUrl}`));
113
+ console.log(chalk_1.default.yellow(' Could not open browser automatically.'));
114
+ console.log(chalk_1.default.gray(` Please visit: ${verificationUrl}`));
91
115
  }
92
116
  console.log('');
93
117
  // Poll for approval
@@ -217,13 +241,13 @@ async function selectIDEs(autoSelect = false) {
217
241
  }
218
242
  console.log('');
219
243
  }
220
- // Auto-select when exactly one IDE is detected or autoSelect flag is set
244
+ // Auto-select: configure ALL detected IDEs (no prompt)
221
245
  if (autoSelect || detectedList.length === 1) {
222
- const autoIDE = detectedList.length === 1 ? detectedList[0] : (current ?? detectedList[0] ?? 'claude');
223
- const ideName = autoIDE === 'claude' ? 'Claude Code' : autoIDE === 'cursor' ? 'Cursor' : 'Windsurf';
224
- console.log(chalk_1.default.green(`✓ Auto-detected: ${chalk_1.default.bold(ideName)}`));
246
+ const toSetup = detectedList.length > 0 ? detectedList : [current ?? 'claude'];
247
+ const names = toSetup.map(id => id === 'claude' ? 'Claude Code' : id === 'cursor' ? 'Cursor' : 'Windsurf');
248
+ console.log(chalk_1.default.green(`✓ Auto-detected: ${chalk_1.default.bold(names.join(', '))}`));
225
249
  console.log('');
226
- return [autoIDE];
250
+ return toSetup;
227
251
  }
228
252
  const ideChoices = [
229
253
  { name: 'Claude Code', value: 'claude', checked: detectedList.includes('claude') || current === 'claude' },
@@ -249,6 +273,7 @@ async function deployForClaude(apiKey, userId, options) {
249
273
  mcp: false,
250
274
  settings: false,
251
275
  skills: { count: 0, skills: [] },
276
+ agents: { count: 0, agents: [] },
252
277
  instructions: false
253
278
  };
254
279
  // MCP configuration
@@ -283,12 +308,21 @@ async function deployForClaude(apiKey, userId, options) {
283
308
  spinner.fail('Skills deployment failed');
284
309
  }
285
310
  }
286
- // CLAUDE.md
311
+ // Agents (prune, rewind, scout, trace)
312
+ spinner = (0, ora_1.default)('Deploying ekkOS agents...').start();
313
+ try {
314
+ result.agents = (0, agents_1.deployAgents)();
315
+ spinner.succeed(`Agents (${result.agents.agents.join(', ')})`);
316
+ }
317
+ catch (error) {
318
+ spinner.fail('Agent deployment failed');
319
+ }
320
+ // CLAUDE.md (merge — never overwrites existing user content)
287
321
  spinner = (0, ora_1.default)('Deploying global instructions...').start();
288
322
  try {
289
323
  (0, instructions_1.deployInstructions)();
290
324
  result.instructions = true;
291
- spinner.succeed('Global instructions (CLAUDE.md)');
325
+ spinner.succeed('Global instructions (CLAUDE.md — merged)');
292
326
  }
293
327
  catch (error) {
294
328
  spinner.fail('Global instructions failed');
@@ -461,6 +495,40 @@ async function init(options) {
461
495
  catch {
462
496
  verifySpinner.warn('Could not reach ekkOS API — check your network');
463
497
  }
498
+ // Phase: Scaffold ekkos.yml if not present in the current directory or git root
499
+ const cwd = process.cwd();
500
+ let projectRoot = cwd;
501
+ try {
502
+ projectRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
503
+ }
504
+ catch { /* not a git repo — use cwd */ }
505
+ const ekkosYmlPath = (0, path_1.join)(projectRoot, 'ekkos.yml');
506
+ if (!(0, fs_1.existsSync)(ekkosYmlPath)) {
507
+ const projectName = (0, path_1.basename)(projectRoot);
508
+ const hasPackageJson = (0, fs_1.existsSync)((0, path_1.join)(projectRoot, 'package.json'));
509
+ const hasCargo = (0, fs_1.existsSync)((0, path_1.join)(projectRoot, 'Cargo.toml'));
510
+ const buildCmd = hasPackageJson ? 'npm run build' : hasCargo ? 'cargo build' : '';
511
+ const testCmd = hasPackageJson ? 'npm test' : hasCargo ? 'cargo test' : '';
512
+ const lintCmd = hasPackageJson ? 'npm run lint' : hasCargo ? 'cargo clippy' : '';
513
+ const ymlLines = [
514
+ 'version: 1',
515
+ `project: "${projectName}"`,
516
+ '',
517
+ 'ans:',
518
+ ' tier: "R1"',
519
+ ];
520
+ if (buildCmd)
521
+ ymlLines.push(` build: "${buildCmd}"`);
522
+ if (testCmd)
523
+ ymlLines.push(` test: "${testCmd}"`);
524
+ if (lintCmd)
525
+ ymlLines.push(` lint: "${lintCmd}"`);
526
+ ymlLines.push('');
527
+ (0, fs_1.writeFileSync)(ekkosYmlPath, ymlLines.join('\n'), 'utf-8');
528
+ console.log('');
529
+ console.log(chalk_1.default.green(` Created ${chalk_1.default.bold('ekkos.yml')} in ${projectRoot}`));
530
+ console.log(chalk_1.default.gray(` Customize: ${chalk_1.default.cyan('https://platform.ekkos.dev/dashboard/settings/project')}`));
531
+ }
464
532
  // Summary with prominent next step
465
533
  const ideNames = installedIDEs.map(id => id === 'claude' ? 'Claude Code' : id === 'cursor' ? 'Cursor' : 'Windsurf');
466
534
  const mcpPaths = {
@@ -488,8 +556,11 @@ async function init(options) {
488
556
  console.log(chalk_1.default.yellow.bold(' NEXT STEPS:'));
489
557
  console.log('');
490
558
  console.log(chalk_1.default.white(` 1. Restart ${ideNames.join(' / ')}`));
491
- console.log(chalk_1.default.white(' 2. Run ') + chalk_1.default.cyan.bold('ekkos') + chalk_1.default.white(' to start coding with memory'));
559
+ console.log(chalk_1.default.white(' 2. Run ') + chalk_1.default.cyan.bold('ekkos scan') + chalk_1.default.white(' to map your project systems'));
560
+ console.log(chalk_1.default.white(' 3. Run ') + chalk_1.default.cyan.bold('ekkos') + chalk_1.default.white(' to start coding with memory'));
492
561
  console.log('');
493
- console.log(chalk_1.default.gray(` Dashboard: https://platform.ekkos.dev/dashboard`));
562
+ console.log(chalk_1.default.gray(` Dashboard: https://platform.ekkos.dev/dashboard`));
563
+ console.log(chalk_1.default.gray(` Configure ANS: https://platform.ekkos.dev/dashboard/settings/project`));
564
+ console.log(chalk_1.default.gray(` Connect vitals: https://platform.ekkos.dev/dashboard/settings/vitals`));
494
565
  console.log('');
495
566
  }
@@ -0,0 +1,8 @@
1
+ export interface WatchLivingDocsOptions {
2
+ path?: string;
3
+ timeZone?: string;
4
+ pollIntervalMs?: number;
5
+ debounceMs?: number;
6
+ noSeed?: boolean;
7
+ }
8
+ export declare function watchLivingDocs(options: WatchLivingDocsOptions): Promise<void>;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.watchLivingDocs = watchLivingDocs;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const path_1 = require("path");
9
+ const living_docs_manager_js_1 = require("../local/living-docs-manager.js");
10
+ const state_1 = require("../utils/state");
11
+ const platform_js_1 = require("../utils/platform.js");
12
+ function printStartupSummary(options) {
13
+ console.log('');
14
+ console.log(chalk_1.default.cyan.bold(' ekkOS Living Docs Watch'));
15
+ console.log(chalk_1.default.gray(' ─────────────────────────'));
16
+ console.log(chalk_1.default.gray(` Path: ${options.targetPath}`));
17
+ console.log(chalk_1.default.gray(` Timezone: ${options.timeZone}`));
18
+ console.log(chalk_1.default.gray(` Registry seed: ${options.seedingEnabled ? 'enabled' : 'disabled'}`));
19
+ console.log('');
20
+ console.log(chalk_1.default.gray(' Watching local files and rewriting ekkOS_CONTEXT.md on change.'));
21
+ console.log(chalk_1.default.gray(' Press Ctrl+C to stop.'));
22
+ console.log('');
23
+ }
24
+ async function watchLivingDocs(options) {
25
+ const targetPath = (0, path_1.resolve)(options.path || process.cwd());
26
+ const timeZone = options.timeZone || process.env.EKKOS_USER_TIMEZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;
27
+ const apiKey = options.noSeed ? null : (0, state_1.getAuthToken)();
28
+ const apiUrl = options.noSeed ? undefined : (process.env.EKKOS_API_URL || platform_js_1.MCP_API_URL);
29
+ const manager = new living_docs_manager_js_1.LocalLivingDocsManager({
30
+ targetPath,
31
+ apiUrl,
32
+ apiKey,
33
+ timeZone,
34
+ pollIntervalMs: options.pollIntervalMs,
35
+ flushDebounceMs: options.debounceMs,
36
+ onLog: message => console.log(chalk_1.default.gray(` ${message}`)),
37
+ });
38
+ printStartupSummary({
39
+ targetPath,
40
+ timeZone,
41
+ seedingEnabled: !!(apiUrl && apiKey),
42
+ });
43
+ manager.start();
44
+ await new Promise((resolvePromise) => {
45
+ let stopped = false;
46
+ const stop = () => {
47
+ if (stopped)
48
+ return;
49
+ stopped = true;
50
+ manager.stop();
51
+ process.off('SIGINT', handleSigInt);
52
+ process.off('SIGTERM', handleSigTerm);
53
+ resolvePromise();
54
+ };
55
+ const handleSigInt = () => {
56
+ console.log('');
57
+ console.log(chalk_1.default.gray(' Stopping living docs watcher...'));
58
+ stop();
59
+ };
60
+ const handleSigTerm = () => {
61
+ stop();
62
+ };
63
+ process.on('SIGINT', handleSigInt);
64
+ process.on('SIGTERM', handleSigTerm);
65
+ });
66
+ }
@@ -2,12 +2,17 @@ interface RunOptions {
2
2
  session?: string;
3
3
  verbose?: boolean;
4
4
  bypass?: boolean;
5
+ pulse?: boolean;
5
6
  doctor?: boolean;
6
7
  noInject?: boolean;
7
8
  research?: boolean;
8
9
  noProxy?: boolean;
9
10
  dashboard?: boolean;
10
11
  addDirs?: string[];
12
+ model?: string | boolean;
13
+ contextWindow?: string;
14
+ continueLast?: boolean;
15
+ resumeSession?: string;
11
16
  slashOpenDelayMs?: number;
12
17
  charDelayMs?: number;
13
18
  postEnterDelayMs?: number;