@ekkos/cli 1.3.6 → 1.3.8

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.
@@ -6,25 +6,52 @@ exports.getInstructionsContent = getInstructionsContent;
6
6
  const fs_1 = require("fs");
7
7
  const platform_1 = require("../utils/platform");
8
8
  const templates_1 = require("../utils/templates");
9
+ const BEGIN_MARKER = '<!-- ekkOS:begin — managed by ekkos init, do not remove this marker -->';
10
+ const END_MARKER = '<!-- ekkOS:end -->';
9
11
  /**
10
- * Deploy CLAUDE.md to ~/.claude/CLAUDE.md
12
+ * Deploy ekkOS block into ~/.claude/CLAUDE.md.
13
+ *
14
+ * Strategy:
15
+ * - If file doesn't exist → create it with just the ekkOS block.
16
+ * - If file exists and already has markers → replace the block between markers.
17
+ * - If file exists with no markers → prepend ekkOS block above existing content.
18
+ *
19
+ * Never destroys user content outside the markers.
11
20
  */
12
21
  function deployInstructions() {
13
- // Ensure .claude directory exists
14
22
  if (!(0, fs_1.existsSync)(platform_1.CLAUDE_DIR)) {
15
23
  (0, fs_1.mkdirSync)(platform_1.CLAUDE_DIR, { recursive: true });
16
24
  }
17
- // Copy CLAUDE.md template
18
- (0, templates_1.copyTemplateFile)('CLAUDE.md', platform_1.CLAUDE_MD);
25
+ const ekkosBlock = (0, templates_1.readTemplate)('CLAUDE.md');
26
+ if (!(0, fs_1.existsSync)(platform_1.CLAUDE_MD)) {
27
+ // No file — create fresh
28
+ (0, fs_1.writeFileSync)(platform_1.CLAUDE_MD, ekkosBlock);
29
+ return;
30
+ }
31
+ const existing = (0, fs_1.readFileSync)(platform_1.CLAUDE_MD, 'utf-8');
32
+ const beginIdx = existing.indexOf(BEGIN_MARKER);
33
+ const endIdx = existing.indexOf(END_MARKER);
34
+ if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
35
+ // Markers found — replace in-place
36
+ const before = existing.slice(0, beginIdx);
37
+ const after = existing.slice(endIdx + END_MARKER.length);
38
+ (0, fs_1.writeFileSync)(platform_1.CLAUDE_MD, before + ekkosBlock + after);
39
+ return;
40
+ }
41
+ // No markers — prepend ekkOS block, preserve everything below
42
+ (0, fs_1.writeFileSync)(platform_1.CLAUDE_MD, ekkosBlock + '\n\n' + existing);
19
43
  }
20
44
  /**
21
- * Check if CLAUDE.md is deployed
45
+ * Check if CLAUDE.md has the ekkOS block
22
46
  */
23
47
  function isInstructionsDeployed() {
24
- return (0, fs_1.existsSync)(platform_1.CLAUDE_MD);
48
+ if (!(0, fs_1.existsSync)(platform_1.CLAUDE_MD))
49
+ return false;
50
+ const content = (0, fs_1.readFileSync)(platform_1.CLAUDE_MD, 'utf-8');
51
+ return content.includes(BEGIN_MARKER) && content.includes(END_MARKER);
25
52
  }
26
53
  /**
27
- * Get the CLAUDE.md content (for preview)
54
+ * Get the ekkOS CLAUDE.md template content
28
55
  */
29
56
  function getInstructionsContent() {
30
57
  try {
package/dist/index.js CHANGED
@@ -38,27 +38,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const commander_1 = require("commander");
41
- const init_1 = require("./commands/init");
42
- const test_1 = require("./commands/test");
43
- const status_1 = require("./commands/status");
41
+ // The `run` command is the default (bare `ekkos`), so it stays eager.
42
+ // `dashboardCommand` is eager because `run --dashboard` spawns `ekkos dashboard` in tmux.
43
+ // All other command modules are loaded lazily inside .action() callbacks.
44
44
  const run_1 = require("./commands/run");
45
- const gemini_1 = require("./commands/gemini");
46
- const test_claude_1 = require("./commands/test-claude");
47
- const doctor_1 = require("./commands/doctor");
48
- const stream_1 = require("./commands/stream");
49
- const claw_1 = require("./commands/claw");
50
- // DEPRECATED: Hooks removed in hookless architecture migration
51
- // hooksInstall, hooksVerify, hooksStatus no longer called — `hooks` command prints a deprecation notice.
52
- const state_1 = require("./utils/state");
53
- const index_1 = require("./commands/usage/index");
54
45
  const dashboard_1 = require("./commands/dashboard");
55
- const swarm_1 = require("./commands/swarm");
56
- const swarm_dashboard_1 = require("./commands/swarm-dashboard");
57
- const swarm_setup_1 = require("./commands/swarm-setup");
58
- const scan_1 = require("./commands/scan");
59
46
  const chalk_1 = __importDefault(require("chalk"));
60
47
  const fs = __importStar(require("fs"));
61
48
  const path = __importStar(require("path"));
49
+ const child_process_1 = require("child_process");
62
50
  // Get version from package.json (CommonJS compatible)
63
51
  const pkgPath = path.resolve(__dirname, '../package.json');
64
52
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
@@ -75,16 +63,13 @@ commander_1.program
75
63
  .addHelpText('after', [
76
64
  '',
77
65
  chalk_1.default.cyan.bold('Examples:'),
78
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default: run)')}`,
79
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos init')} ${chalk_1.default.gray('First-time setup authenticate + configure your IDE')}`,
80
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos scan')} ${chalk_1.default.gray('Scan repo structure and seed system registry')}`,
81
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos scan --compile')} ${chalk_1.default.gray('Scan, seed, and trigger compile pass')}`,
82
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run --dashboard')} ${chalk_1.default.gray('Launch with live usage dashboard (tmux split)')}`,
83
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run -b')} ${chalk_1.default.gray('Launch with bypass permissions mode')}`,
84
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos doctor --fix')} ${chalk_1.default.gray('Check and auto-fix system prerequisites')}`,
85
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos usage daily')} ${chalk_1.default.gray("View today's token usage and costs")}`,
86
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos gemini')} ${chalk_1.default.gray('Launch Gemini CLI with ekkOS memory proxy')}`,
87
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos swarm launch -t "build X"')} ${chalk_1.default.gray('Launch parallel workers on a task')}`,
66
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default)')}`,
67
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos codex')} ${chalk_1.default.gray('Start Codex (OpenAI) mode')}`,
68
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos daemon start')} ${chalk_1.default.gray('Start the background mobile sync service')}`,
69
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos docs watch')} ${chalk_1.default.gray('Keep local ekkOS_CONTEXT.md files updated on disk')}`,
70
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos connect gemini')} ${chalk_1.default.gray('Securely store your Gemini API key in the cloud')}`,
71
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos agent create --path ./repo')} ${chalk_1.default.gray('Spawn a headless remote agent in a directory')}`,
72
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run --dashboard')} ${chalk_1.default.gray('Launch Claude with live usage dashboard')}`,
88
73
  '',
89
74
  chalk_1.default.gray(' Run ') + chalk_1.default.white('ekkos <command> --help') + chalk_1.default.gray(' for detailed options on any command.'),
90
75
  '',
@@ -152,38 +137,49 @@ commander_1.program
152
137
  title: 'Getting Started',
153
138
  icon: '▸',
154
139
  commands: [
155
- { name: 'init', desc: 'Authenticate and configure your IDE (Claude, Cursor, Windsurf)' },
140
+ { name: 'init', desc: 'First-time setup authenticate and configure IDEs' },
156
141
  { name: 'scan', desc: 'Scan repo structure and seed ekkOS system registry' },
157
- { name: 'status', desc: 'Show memory status and installation info' },
158
- { name: 'test', desc: 'Test connection to ekkOS memory API' },
159
- { name: 'doctor', desc: 'Check system prerequisites (Node, PTY, Claude, MCP)' },
142
+ { name: 'docs', desc: 'Watch and regenerate local ekkOS_CONTEXT.md files' },
143
+ { name: 'status', desc: 'Show overall system status (Memory, Sync Daemon, Auth)' },
144
+ { name: 'doctor', desc: 'Check system prerequisites and fix runaway processes' },
160
145
  ],
161
146
  },
162
147
  {
163
- title: 'Running',
148
+ title: 'Launch Agents (Local + Mobile Synced)',
164
149
  icon: '▸',
165
150
  commands: [
166
- { name: 'run', desc: 'Launch Claude Code with ekkOS memory', note: 'default' },
167
- { name: 'test-claude', desc: 'Launch Claude with proxy only (no PTY) for debugging' },
168
- { name: 'sessions', desc: 'List active Claude Code sessions' },
169
- { name: 'claw', desc: 'OpenClaw integration status and upgrade controls' },
151
+ { name: 'run', desc: 'Launch Claude Code with memory and mobile control', note: 'default' },
152
+ { name: 'codex', desc: 'Launch Codex (OpenAI) with mobile control' },
153
+ { name: 'gemini', desc: 'Launch Gemini mode (ACP) with mobile control' },
154
+ { name: 'acp', desc: 'Launch a generic ACP-compatible agent' },
170
155
  ],
171
156
  },
172
157
  {
173
- title: 'Monitoring & Usage',
158
+ title: 'Mobile Sync & Cloud (Synk)',
174
159
  icon: '▸',
175
160
  commands: [
176
- { name: 'usage', desc: 'Token usage and cost tracking (daily, weekly, monthly, session)' },
177
- { name: 'dashboard', desc: 'Live TUI dashboard for session monitoring' },
178
- { name: 'stream', desc: 'Stream capture status and management' },
179
- { name: 'hooks', desc: '[DEPRECATED] Hooks no longer needed use `ekkos run`' },
161
+ { name: 'daemon', desc: 'Manage background mobile sync service (start, stop, status)' },
162
+ { name: 'connect', desc: 'Securely store AI vendor API keys in the cloud' },
163
+ { name: 'sandbox', desc: 'Configure OS-level sandboxing for agent execution' },
164
+ { name: 'notify', desc: 'Send push notifications to your Synk mobile app' },
180
165
  ],
181
166
  },
182
167
  {
183
- title: 'Swarm (Multi-Agent)',
168
+ title: 'Headless Agents (Remote Control)',
184
169
  icon: '▸',
185
170
  commands: [
186
- { name: 'swarm', desc: 'Parallel workers, Q-learning routing, swarm dashboard' },
171
+ { name: 'agent', desc: 'Manage remote headless agents (create, list, stop)' },
172
+ { name: 'swarm', desc: 'Parallel workers, Q-learning routing, and swarm dashboard' },
173
+ ],
174
+ },
175
+ {
176
+ title: 'Monitoring & Usage',
177
+ icon: '▸',
178
+ commands: [
179
+ { name: 'auth', desc: 'Manage authentication for Memory, Agents, and Mobile App' },
180
+ { name: 'usage', desc: 'Token usage and cost tracking (daily, weekly, monthly, session)' },
181
+ { name: 'dashboard', desc: 'Live TUI dashboard for session monitoring' },
182
+ { name: 'sessions', desc: 'List active local CLI sessions' },
187
183
  ],
188
184
  },
189
185
  ];
@@ -225,7 +221,7 @@ commander_1.program
225
221
  .option('-q, --quick', 'Quick setup: auto-detect IDE, use defaults, only prompt for API key if missing')
226
222
  .option('--skip-hooks', '[DEPRECATED] Hooks are no longer deployed; this flag is a no-op')
227
223
  .option('--skip-skills', 'Skip skills deployment')
228
- .action(init_1.init);
224
+ .action(async (options) => { const { init } = await Promise.resolve().then(() => __importStar(require('./commands/init'))); await init(options); });
229
225
  // Scan command — discover repo systems and seed registry
230
226
  commander_1.program
231
227
  .command('scan')
@@ -233,21 +229,45 @@ commander_1.program
233
229
  .option('-c, --compile', 'Trigger a compile pass after seeding')
234
230
  .option('-n, --dry-run', 'Show discovered systems without seeding')
235
231
  .option('-p, --path <path>', 'Path to scan (default: current directory)')
236
- .action((options) => {
237
- (0, scan_1.scan)({
232
+ .action(async (options) => {
233
+ const { scan } = await Promise.resolve().then(() => __importStar(require('./commands/scan')));
234
+ scan({
238
235
  compile: options.compile,
239
236
  dryRun: options.dryRun,
240
237
  path: options.path,
241
238
  });
242
239
  });
240
+ // Local living docs command
241
+ const docsCmd = commander_1.program
242
+ .command('docs')
243
+ .description('Manage local ekkOS_CONTEXT.md living docs');
244
+ docsCmd
245
+ .command('watch')
246
+ .description('Watch the local workspace and keep ekkOS_CONTEXT.md files current on disk')
247
+ .option('-p, --path <path>', 'Path to watch (default: current directory)')
248
+ .option('--timezone <iana-tz>', 'IANA timezone for timestamps (default: local machine timezone)')
249
+ .option('--poll-interval-ms <ms>', 'Polling interval fallback in milliseconds', (value) => parseInt(value, 10))
250
+ .option('--debounce-ms <ms>', 'Debounce window before recompiling in milliseconds', (value) => parseInt(value, 10))
251
+ .option('--no-seed', 'Do not seed the platform registry while watching')
252
+ .action(async (options) => {
253
+ const { watchLivingDocs } = await Promise.resolve().then(() => __importStar(require('./commands/living-docs')));
254
+ watchLivingDocs({
255
+ path: options.path,
256
+ timeZone: options.timezone,
257
+ pollIntervalMs: options.pollIntervalMs,
258
+ debounceMs: options.debounceMs,
259
+ noSeed: options.seed === false,
260
+ });
261
+ });
243
262
  // Status command
244
263
  commander_1.program
245
264
  .command('status')
246
265
  .description('Show live session metrics and memory status')
247
266
  .option('-w, --watch', 'Watch mode — refresh session panel every 2s')
248
267
  .option('--json', 'Output raw metrics JSON (no memory API call)')
249
- .action((options) => {
250
- (0, status_1.status)({ watch: options.watch, json: options.json });
268
+ .action(async (options) => {
269
+ const { status } = await Promise.resolve().then(() => __importStar(require('./commands/status')));
270
+ status({ watch: options.watch, json: options.json });
251
271
  });
252
272
  // OpenClaw integration commands
253
273
  const clawCmd = commander_1.program
@@ -260,8 +280,9 @@ clawCmd
260
280
  .option('--proxy-url <url>', 'Expected ekkOS proxy URL (default: https://proxy.ekkos.dev)')
261
281
  .option('--model <model>', 'Expected primary OpenClaw model (default: ekkos-proxy/claude-sonnet-4-6)')
262
282
  .option('--workspace <path>', 'Expected OpenClaw workspace path')
263
- .action((options) => {
264
- (0, claw_1.clawStatus)({
283
+ .action(async (options) => {
284
+ const { clawStatus } = await Promise.resolve().then(() => __importStar(require('./commands/claw')));
285
+ clawStatus({
265
286
  json: options.json,
266
287
  proxyUrl: options.proxyUrl,
267
288
  model: options.model,
@@ -277,8 +298,9 @@ clawCmd
277
298
  .option('--proxy-url <url>', 'Target ekkOS proxy URL')
278
299
  .option('--model <model>', 'Target primary OpenClaw model')
279
300
  .option('--workspace <path>', 'Expected OpenClaw workspace path for status verification')
280
- .action((options) => {
281
- (0, claw_1.clawUpgrade)({
301
+ .action(async (options) => {
302
+ const { clawUpgrade } = await Promise.resolve().then(() => __importStar(require('./commands/claw')));
303
+ clawUpgrade({
282
304
  apply: options.apply,
283
305
  force: options.force,
284
306
  json: options.json,
@@ -291,7 +313,7 @@ clawCmd
291
313
  commander_1.program
292
314
  .command('test')
293
315
  .description('Test connection to ekkOS memory')
294
- .action(test_1.test);
316
+ .action(async () => { const { test } = await Promise.resolve().then(() => __importStar(require('./commands/test'))); await test(); });
295
317
  // Run command - launches Claude Code with ekkOS proxy
296
318
  commander_1.program
297
319
  .command('run')
@@ -325,8 +347,9 @@ commander_1.program
325
347
  .option('-s, --session <name>', 'Session name')
326
348
  .option('-v, --verbose', 'Show debug output')
327
349
  .option('--skip-proxy', 'Skip API proxy (use direct Gemini API)')
328
- .action((options) => {
329
- (0, gemini_1.gemini)({
350
+ .action(async (options) => {
351
+ const { gemini } = await Promise.resolve().then(() => __importStar(require('./commands/gemini')));
352
+ gemini({
330
353
  session: options.session,
331
354
  verbose: options.verbose,
332
355
  noProxy: options.skipProxy,
@@ -339,8 +362,9 @@ commander_1.program
339
362
  .option('--no-proxy', 'Skip proxy too (completely vanilla Claude)')
340
363
  .option('--no-hooks', 'Temporarily disable all hooks during test')
341
364
  .option('-v, --verbose', 'Show debug output')
342
- .action((options) => {
343
- (0, test_claude_1.testClaude)({
365
+ .action(async (options) => {
366
+ const { testClaude } = await Promise.resolve().then(() => __importStar(require('./commands/test-claude')));
367
+ testClaude({
344
368
  noProxy: options.proxy === false,
345
369
  noHooks: options.hooks === false,
346
370
  verbose: options.verbose,
@@ -352,8 +376,9 @@ commander_1.program
352
376
  .description('Check system prerequisites for ekkOS (Node, PTY, Claude, MCP)')
353
377
  .option('-f, --fix', 'Attempt safe auto-fixes and show commands for manual fixes')
354
378
  .option('-j, --json', 'Output machine-readable JSON report')
355
- .action((options) => {
356
- (0, doctor_1.doctor)({ fix: options.fix, json: options.json });
379
+ .action(async (options) => {
380
+ const { doctor } = await Promise.resolve().then(() => __importStar(require('./commands/doctor')));
381
+ doctor({ fix: options.fix, json: options.json });
357
382
  });
358
383
  // Stream command - stream capture status and management
359
384
  const streamCmd = commander_1.program
@@ -365,8 +390,9 @@ streamCmd
365
390
  .option('-s, --session <id>', 'Session ID to check')
366
391
  .option('-w, --watch', 'Watch mode - refresh every second')
367
392
  .option('-j, --json', 'Output machine-readable JSON')
368
- .action((options) => {
369
- (0, stream_1.streamStatus)({
393
+ .action(async (options) => {
394
+ const { streamStatus } = await Promise.resolve().then(() => __importStar(require('./commands/stream')));
395
+ streamStatus({
370
396
  session: options.session,
371
397
  watch: options.watch,
372
398
  json: options.json
@@ -375,8 +401,9 @@ streamCmd
375
401
  streamCmd
376
402
  .command('list')
377
403
  .description('List all sessions with stream data')
378
- .action(() => {
379
- (0, stream_1.streamList)();
404
+ .action(async () => {
405
+ const { streamList } = await Promise.resolve().then(() => __importStar(require('./commands/stream')));
406
+ streamList();
380
407
  });
381
408
  // DEPRECATED: Hooks removed in hookless architecture migration
382
409
  // The `hooks` command prints a deprecation notice directing users to `ekkos run`.
@@ -393,17 +420,28 @@ commander_1.program
393
420
  console.log(chalk_1.default.gray(' the ekkOS proxy — no shell hooks required.'));
394
421
  console.log('');
395
422
  });
396
- // Usage command - track Claude Code token usage and costs (powered by ccusage)
397
- (0, index_1.registerUsageCommand)(commander_1.program);
398
- // Dashboard command - live TUI for monitoring session usage
423
+ // Usage command lightweight stub, heavy handler loaded lazily
424
+ commander_1.program
425
+ .command('usage')
426
+ .description('Token usage and cost tracking (daily, weekly, monthly, session)')
427
+ .allowUnknownOption()
428
+ .action(async () => {
429
+ const { registerUsageCommand } = await Promise.resolve().then(() => __importStar(require('./commands/usage/index')));
430
+ const { Command } = await Promise.resolve().then(() => __importStar(require('commander')));
431
+ const usageProgram = new Command('ekkos');
432
+ registerUsageCommand(usageProgram);
433
+ usageProgram.parse(['node', 'ekkos', 'usage', ...process.argv.slice(3)]);
434
+ });
435
+ // Dashboard command — eager because `run --dashboard` spawns `ekkos dashboard` in tmux
399
436
  commander_1.program.addCommand(dashboard_1.dashboardCommand);
400
437
  // Sessions command - list active Claude Code sessions (swarm support)
401
438
  commander_1.program
402
439
  .command('sessions')
403
440
  .description('List active Claude Code sessions (for swarm/multi-session support)')
404
441
  .option('-j, --json', 'Output machine-readable JSON')
405
- .action((options) => {
406
- const sessions = (0, state_1.getActiveSessions)();
442
+ .action(async (options) => {
443
+ const { getActiveSessions } = await Promise.resolve().then(() => __importStar(require('./utils/state')));
444
+ const sessions = getActiveSessions();
407
445
  if (options.json) {
408
446
  console.log(JSON.stringify(sessions, null, 2));
409
447
  return;
@@ -443,7 +481,8 @@ commander_1.program
443
481
  console.log(chalk_1.default.gray('Running init with your options...'));
444
482
  console.log('');
445
483
  // Forward to init
446
- await (0, init_1.init)({
484
+ const { init } = await Promise.resolve().then(() => __importStar(require('./commands/init')));
485
+ await init({
447
486
  ide: options.ide,
448
487
  key: options.key
449
488
  });
@@ -455,19 +494,19 @@ const swarmCmd = commander_1.program
455
494
  swarmCmd
456
495
  .command('status')
457
496
  .description('Show Q-table stats (states, visits, epsilon, top actions)')
458
- .action(swarm_1.swarmStatus);
497
+ .action(async () => { const { swarmStatus } = await Promise.resolve().then(() => __importStar(require('./commands/swarm'))); swarmStatus(); });
459
498
  swarmCmd
460
499
  .command('reset')
461
500
  .description('Clear Q-table from Redis (routing reverts to static rules)')
462
- .action(swarm_1.swarmReset);
501
+ .action(async () => { const { swarmReset } = await Promise.resolve().then(() => __importStar(require('./commands/swarm'))); swarmReset(); });
463
502
  swarmCmd
464
503
  .command('export')
465
504
  .description('Export Q-table to .swarm/q-learning-model.json')
466
- .action(swarm_1.swarmExport);
505
+ .action(async () => { const { swarmExport } = await Promise.resolve().then(() => __importStar(require('./commands/swarm'))); swarmExport(); });
467
506
  swarmCmd
468
507
  .command('import')
469
508
  .description('Import Q-table from .swarm/q-learning-model.json into Redis')
470
- .action(swarm_1.swarmImport);
509
+ .action(async () => { const { swarmImport } = await Promise.resolve().then(() => __importStar(require('./commands/swarm'))); swarmImport(); });
471
510
  swarmCmd
472
511
  .command('launch')
473
512
  .description('Launch parallel workers on a decomposed task (opens wizard if --task is omitted)')
@@ -478,13 +517,14 @@ swarmCmd
478
517
  .option('--no-queen', 'Skip launching the Python Queen coordinator')
479
518
  .option('--queen-strategy <strategy>', 'Queen strategy (adaptive-default, hierarchical-cascade, mesh-consensus)')
480
519
  .option('-v, --verbose', 'Show debug output')
481
- .action((options) => {
482
- // Auto-open wizard when --task is missing
520
+ .action(async (options) => {
483
521
  if (!options.task) {
484
- (0, swarm_setup_1.swarmSetup)();
522
+ const { swarmSetup } = await Promise.resolve().then(() => __importStar(require('./commands/swarm-setup')));
523
+ swarmSetup();
485
524
  return;
486
525
  }
487
- (0, swarm_1.swarmLaunch)({
526
+ const { swarmLaunch } = await Promise.resolve().then(() => __importStar(require('./commands/swarm')));
527
+ swarmLaunch({
488
528
  workers: options.workers || 4,
489
529
  task: options.task,
490
530
  bypass: options.bypass !== false,
@@ -497,10 +537,83 @@ swarmCmd
497
537
  swarmCmd
498
538
  .command('setup')
499
539
  .description('Interactive TUI wizard for configuring and launching a swarm')
500
- .action(() => {
501
- (0, swarm_setup_1.swarmSetup)();
540
+ .action(async () => { const { swarmSetup } = await Promise.resolve().then(() => __importStar(require('./commands/swarm-setup'))); swarmSetup(); });
541
+ swarmCmd
542
+ .command('swarm-dashboard')
543
+ .description('Live swarm dashboard')
544
+ .allowUnknownOption()
545
+ .action(async () => {
546
+ const { swarmDashboardCommand } = await Promise.resolve().then(() => __importStar(require('./commands/swarm-dashboard')));
547
+ swarmDashboardCommand.parse(process.argv.slice(3));
502
548
  });
503
- swarmCmd.addCommand(swarm_dashboard_1.swarmDashboardCommand);
549
+ // --- Remote & Agent Wrapper Helpers ---
550
+ function runRemoteCommand(command, ...args) {
551
+ const isAgent = command === 'agent';
552
+ const pkgName = isAgent ? 'ekkos-agent' : 'ekkos-remote';
553
+ const binName = isAgent ? 'ekkos-agent.mjs' : 'ekkos-synk.mjs';
554
+ // Try resolving local monorepo first
555
+ let binPath = path.resolve(__dirname, '../../' + pkgName + '/bin/' + binName);
556
+ // If we are installed globally (dist folder structure), resolve relatively
557
+ if (!fs.existsSync(binPath)) {
558
+ try {
559
+ const globalPath = require.resolve('@ekkos/' + (isAgent ? 'agent' : 'remote') + '/package.json');
560
+ binPath = path.join(path.dirname(globalPath), 'bin', binName);
561
+ }
562
+ catch (e) {
563
+ // Fallback relative to current dist
564
+ binPath = path.resolve(__dirname, '../../' + pkgName + '/bin/' + binName);
565
+ }
566
+ }
567
+ const p = (0, child_process_1.spawn)('node', [binPath, ...args], {
568
+ stdio: 'inherit',
569
+ env: process.env
570
+ });
571
+ p.on('exit', (code) => {
572
+ process.exit(code || 0);
573
+ });
574
+ }
575
+ // Mobile Sync & Cloud (Synk) Commands
576
+ commander_1.program
577
+ .command('auth')
578
+ .description('Manage authentication for Memory, Agents, and Mobile App')
579
+ .allowUnknownOption()
580
+ .action(() => runRemoteCommand('synk', 'auth', ...process.argv.slice(3)));
581
+ commander_1.program
582
+ .command('codex')
583
+ .description('Launch Codex (OpenAI) with mobile control')
584
+ .allowUnknownOption()
585
+ .action(() => runRemoteCommand('synk', 'codex', ...process.argv.slice(3)));
586
+ commander_1.program
587
+ .command('acp')
588
+ .description('Launch a generic ACP-compatible agent')
589
+ .allowUnknownOption()
590
+ .action(() => runRemoteCommand('synk', 'acp', ...process.argv.slice(3)));
591
+ commander_1.program
592
+ .command('daemon')
593
+ .description('Manage background mobile sync service (start, stop, status)')
594
+ .allowUnknownOption()
595
+ .action(() => runRemoteCommand('synk', 'daemon', ...process.argv.slice(3)));
596
+ commander_1.program
597
+ .command('connect')
598
+ .description('Securely store AI vendor API keys in the cloud')
599
+ .allowUnknownOption()
600
+ .action(() => runRemoteCommand('synk', 'connect', ...process.argv.slice(3)));
601
+ commander_1.program
602
+ .command('sandbox')
603
+ .description('Configure OS-level sandboxing for agent execution')
604
+ .allowUnknownOption()
605
+ .action(() => runRemoteCommand('synk', 'sandbox', ...process.argv.slice(3)));
606
+ commander_1.program
607
+ .command('notify')
608
+ .description('Send push notifications to your Synk mobile app')
609
+ .allowUnknownOption()
610
+ .action(() => runRemoteCommand('synk', 'notify', ...process.argv.slice(3)));
611
+ // Headless Agents (Remote Control)
612
+ commander_1.program
613
+ .command('agent')
614
+ .description('Manage remote headless agents (create, list, stop)')
615
+ .allowUnknownOption()
616
+ .action(() => runRemoteCommand('agent', ...process.argv.slice(3)));
504
617
  // Handle `-help` (single dash) — rewrite to `--help` for Commander compatibility
505
618
  const helpIdx = process.argv.indexOf('-help');
506
619
  if (helpIdx !== -1) {
@@ -91,8 +91,24 @@ function resolveSessionName(name) {
91
91
  if (fs.existsSync(activeSessionsPath)) {
92
92
  try {
93
93
  const sessions = JSON.parse(fs.readFileSync(activeSessionsPath, 'utf-8'));
94
- // Find first entry with a valid UUID sessionId (skip "unknown" or empty)
95
- const match = sessions.find(s => s.sessionName === name && s.sessionId && s.sessionId !== 'unknown' && s.sessionId.length > 8);
94
+ // Find first entry with a valid UUID sessionId (skip "unknown" or empty).
95
+ // Also skip entries whose PID is dead (stale from prior runs / restarts).
96
+ const match = sessions.find(s => {
97
+ if (s.sessionName !== name)
98
+ return false;
99
+ if (!s.sessionId || s.sessionId === 'unknown' || s.sessionId.length <= 8)
100
+ return false;
101
+ // Prune dead PIDs — prevents stale cross-binding after restart
102
+ if (s.pid > 1) {
103
+ try {
104
+ process.kill(s.pid, 0);
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ return true;
111
+ });
96
112
  if (match) {
97
113
  return {
98
114
  uuid: match.sessionId,
@@ -12,3 +12,7 @@ export { SyncEngine, createSyncEngine, } from './sync-engine';
12
12
  export type { SyncResult, } from './sync-engine';
13
13
  export { generateLocalEmbedding, cosineSimilarity, searchByEmbedding, isEmbeddingAvailable, } from './local-embeddings';
14
14
  export type { SimilarityResult, } from './local-embeddings';
15
+ export { detectStack, } from './stack-detection';
16
+ export type { StackInfo, } from './stack-detection';
17
+ export { LANGUAGE_CONFIGS, BASE_KEY_FILES, BASE_EXCLUDED_DIRS, getKeyFilesForStack, getExcludedDirsForStack, getPromptHintsForStack, getSourceExtensionsForStack, getSystemBoundaryMarkersForStack, } from './language-config';
18
+ export type { LanguageConfig, } from './language-config';
@@ -6,7 +6,7 @@
6
6
  * Provides offline-capable memory, fallback logic, sync, and local embeddings.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.isEmbeddingAvailable = exports.searchByEmbedding = exports.cosineSimilarity = exports.generateLocalEmbedding = exports.createSyncEngine = exports.SyncEngine = exports.OFFLINE_CAPABLE_TOOLS = exports.isOfflineFallbackReady = exports.callWithFallback = exports.localStore = exports.LocalMemoryStore = void 0;
9
+ exports.getSystemBoundaryMarkersForStack = exports.getSourceExtensionsForStack = exports.getPromptHintsForStack = exports.getExcludedDirsForStack = exports.getKeyFilesForStack = exports.BASE_EXCLUDED_DIRS = exports.BASE_KEY_FILES = exports.LANGUAGE_CONFIGS = exports.detectStack = exports.isEmbeddingAvailable = exports.searchByEmbedding = exports.cosineSimilarity = exports.generateLocalEmbedding = exports.createSyncEngine = exports.SyncEngine = exports.OFFLINE_CAPABLE_TOOLS = exports.isOfflineFallbackReady = exports.callWithFallback = exports.localStore = exports.LocalMemoryStore = void 0;
10
10
  // SQLite memory store (Phase 6A)
11
11
  var sqlite_store_1 = require("./sqlite-store");
12
12
  Object.defineProperty(exports, "LocalMemoryStore", { enumerable: true, get: function () { return sqlite_store_1.LocalMemoryStore; } });
@@ -26,3 +26,16 @@ Object.defineProperty(exports, "generateLocalEmbedding", { enumerable: true, get
26
26
  Object.defineProperty(exports, "cosineSimilarity", { enumerable: true, get: function () { return local_embeddings_1.cosineSimilarity; } });
27
27
  Object.defineProperty(exports, "searchByEmbedding", { enumerable: true, get: function () { return local_embeddings_1.searchByEmbedding; } });
28
28
  Object.defineProperty(exports, "isEmbeddingAvailable", { enumerable: true, get: function () { return local_embeddings_1.isEmbeddingAvailable; } });
29
+ // Stack detection (Living Docs V4 — Phase 4)
30
+ var stack_detection_1 = require("./stack-detection");
31
+ Object.defineProperty(exports, "detectStack", { enumerable: true, get: function () { return stack_detection_1.detectStack; } });
32
+ // Language configuration (Living Docs V4 — Phase 4)
33
+ var language_config_1 = require("./language-config");
34
+ Object.defineProperty(exports, "LANGUAGE_CONFIGS", { enumerable: true, get: function () { return language_config_1.LANGUAGE_CONFIGS; } });
35
+ Object.defineProperty(exports, "BASE_KEY_FILES", { enumerable: true, get: function () { return language_config_1.BASE_KEY_FILES; } });
36
+ Object.defineProperty(exports, "BASE_EXCLUDED_DIRS", { enumerable: true, get: function () { return language_config_1.BASE_EXCLUDED_DIRS; } });
37
+ Object.defineProperty(exports, "getKeyFilesForStack", { enumerable: true, get: function () { return language_config_1.getKeyFilesForStack; } });
38
+ Object.defineProperty(exports, "getExcludedDirsForStack", { enumerable: true, get: function () { return language_config_1.getExcludedDirsForStack; } });
39
+ Object.defineProperty(exports, "getPromptHintsForStack", { enumerable: true, get: function () { return language_config_1.getPromptHintsForStack; } });
40
+ Object.defineProperty(exports, "getSourceExtensionsForStack", { enumerable: true, get: function () { return language_config_1.getSourceExtensionsForStack; } });
41
+ Object.defineProperty(exports, "getSystemBoundaryMarkersForStack", { enumerable: true, get: function () { return language_config_1.getSystemBoundaryMarkersForStack; } });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Language Configuration — data-driven config for each language ecosystem
3
+ *
4
+ * Pure functions with zero dependencies. Provides key files, excluded dirs,
5
+ * source extensions, system boundary markers, and Gemini prompt hints
6
+ * for each supported language.
7
+ *
8
+ * Living Docs V4 — Phase 4: Language-Adapted Discovery
9
+ */
10
+ import type { StackInfo } from './stack-detection.js';
11
+ export interface LanguageConfig {
12
+ /** Key file names to prioritize reading (for both CLI and server compiler) */
13
+ keyFiles: string[];
14
+ /** Directories to exclude from scanning */
15
+ excludedDirs: string[];
16
+ /** File extensions that count as source files */
17
+ sourceExtensions: string[];
18
+ /** Files that mark a system boundary */
19
+ systemBoundaryMarkers: string[];
20
+ /** Prompt hints for Gemini (what to ask about) */
21
+ promptHints: {
22
+ architecture: string[];
23
+ buildSystem: string[];
24
+ deployTarget: string[];
25
+ testFramework: string[];
26
+ conventions: string[];
27
+ };
28
+ }
29
+ export declare const BASE_KEY_FILES: string[];
30
+ export declare const BASE_EXCLUDED_DIRS: string[];
31
+ export declare const LANGUAGE_CONFIGS: Record<string, LanguageConfig>;
32
+ /**
33
+ * Get the merged key files list for a detected stack.
34
+ * Returns BASE_KEY_FILES + language-specific files (deduplicated).
35
+ */
36
+ export declare function getKeyFilesForStack(stack: StackInfo): string[];
37
+ /**
38
+ * Get the merged excluded dirs for a detected stack.
39
+ * Returns BASE_EXCLUDED_DIRS + language-specific dirs.
40
+ */
41
+ export declare function getExcludedDirsForStack(stack: StackInfo): Set<string>;
42
+ /**
43
+ * Get prompt hints for the Gemini compiler.
44
+ * Falls back to TypeScript hints for unknown languages.
45
+ */
46
+ export declare function getPromptHintsForStack(stack: StackInfo): LanguageConfig['promptHints'];
47
+ /**
48
+ * Get the source extensions for a detected stack.
49
+ */
50
+ export declare function getSourceExtensionsForStack(stack: StackInfo): Set<string>;
51
+ /**
52
+ * Get system boundary markers for a detected stack.
53
+ * These are files that indicate a "system root" directory.
54
+ */
55
+ export declare function getSystemBoundaryMarkersForStack(stack: StackInfo): string[];