@ekkos/cli 1.4.2 → 1.5.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.
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -104,7 +137,7 @@ async function deviceAuth() {
104
137
  }
105
138
  console.log('');
106
139
  // Open browser
107
- const verificationUrl = deviceCode.verificationUrl || `${platform_1.PLATFORM_URL}/activate`;
140
+ const verificationUrl = deviceCode.verificationUrl || `${platform_1.PLATFORM_URL}/verify/${deviceCode.code}`;
108
141
  console.log(chalk_1.default.gray(` Opening browser → ${verificationUrl}`));
109
142
  try {
110
143
  await (0, open_1.default)(verificationUrl);
@@ -502,6 +535,18 @@ async function init(options) {
502
535
  projectRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
503
536
  }
504
537
  catch { /* not a git repo — use cwd */ }
538
+ console.log(chalk_1.default.cyan('Step 4/4: Cortex Background Guardian'));
539
+ console.log(chalk_1.default.gray('─'.repeat(40)));
540
+ console.log('');
541
+ const { confirmRoot } = await inquirer_1.default.prompt([
542
+ {
543
+ type: 'input',
544
+ name: 'confirmRoot',
545
+ message: 'Confirm the root directory to watch for this project:',
546
+ default: projectRoot
547
+ }
548
+ ]);
549
+ projectRoot = (0, path_1.resolve)(confirmRoot);
505
550
  const ekkosYmlPath = (0, path_1.join)(projectRoot, 'ekkos.yml');
506
551
  if (!(0, fs_1.existsSync)(ekkosYmlPath)) {
507
552
  const projectName = (0, path_1.basename)(projectRoot);
@@ -525,9 +570,16 @@ async function init(options) {
525
570
  ymlLines.push(` lint: "${lintCmd}"`);
526
571
  ymlLines.push('');
527
572
  (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')}`));
573
+ console.log(chalk_1.default.green(` ✓ Created ${chalk_1.default.bold('ekkos.yml')} in ${projectRoot}`));
574
+ }
575
+ // Register with local daemon for background watching
576
+ try {
577
+ const { addWorkspace } = await Promise.resolve().then(() => __importStar(require('./workspaces')));
578
+ console.log(chalk_1.default.gray(' Registering with background guardian...'));
579
+ await addWorkspace(projectRoot);
580
+ }
581
+ catch {
582
+ console.log(chalk_1.default.yellow(' Note: Background guardian not active. Project will be watched on next CLI run.'));
531
583
  }
532
584
  // Summary with prominent next step
533
585
  const ideNames = installedIDEs.map(id => id === 'claude' ? 'Claude Code' : id === 'cursor' ? 'Cursor' : 'Windsurf');
@@ -559,6 +611,9 @@ async function init(options) {
559
611
  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
612
  console.log(chalk_1.default.white(' 3. Run ') + chalk_1.default.cyan.bold('ekkos') + chalk_1.default.white(' to start coding with memory'));
561
613
  console.log('');
614
+ console.log(chalk_1.default.cyan(' ✨ Cortex Guardian is now active in the background.'));
615
+ console.log(chalk_1.default.gray(' Every save will be semantically analyzed for regressions.'));
616
+ console.log('');
562
617
  console.log(chalk_1.default.gray(` Dashboard: https://platform.ekkos.dev/dashboard`));
563
618
  console.log(chalk_1.default.gray(` Configure ANS: https://platform.ekkos.dev/dashboard/settings/project`));
564
619
  console.log(chalk_1.default.gray(` Connect vitals: https://platform.ekkos.dev/dashboard/settings/vitals`));
@@ -4,5 +4,6 @@ export interface WatchLivingDocsOptions {
4
4
  pollIntervalMs?: number;
5
5
  debounceMs?: number;
6
6
  noSeed?: boolean;
7
+ rich?: boolean;
7
8
  }
8
9
  export declare function watchLivingDocs(options: WatchLivingDocsOptions): Promise<void>;
@@ -7,7 +7,7 @@ exports.watchLivingDocs = watchLivingDocs;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const path_1 = require("path");
9
9
  const living_docs_manager_js_1 = require("../local/living-docs-manager.js");
10
- const state_1 = require("../utils/state");
10
+ const state_js_1 = require("../utils/state.js");
11
11
  const platform_js_1 = require("../utils/platform.js");
12
12
  function printStartupSummary(options) {
13
13
  console.log('');
@@ -16,6 +16,7 @@ function printStartupSummary(options) {
16
16
  console.log(chalk_1.default.gray(` Path: ${options.targetPath}`));
17
17
  console.log(chalk_1.default.gray(` Timezone: ${options.timeZone}`));
18
18
  console.log(chalk_1.default.gray(` Registry seed: ${options.seedingEnabled ? 'enabled' : 'disabled'}`));
19
+ console.log(chalk_1.default.gray(` Rich Analysis: ${options.richEnabled ? chalk_1.default.green('enabled') : 'disabled'}`));
19
20
  console.log('');
20
21
  console.log(chalk_1.default.gray(' Watching local files and updating Cortex docs (ekkOS_CONTEXT.md) on change.'));
21
22
  console.log(chalk_1.default.gray(' Press Ctrl+C to stop.'));
@@ -24,7 +25,7 @@ function printStartupSummary(options) {
24
25
  async function watchLivingDocs(options) {
25
26
  const targetPath = (0, path_1.resolve)(options.path || process.cwd());
26
27
  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 apiKey = options.noSeed ? null : (0, state_js_1.getAuthToken)();
28
29
  const apiUrl = options.noSeed ? undefined : (process.env.EKKOS_API_URL || platform_js_1.MCP_API_URL);
29
30
  const manager = new living_docs_manager_js_1.LocalLivingDocsManager({
30
31
  targetPath,
@@ -33,12 +34,14 @@ async function watchLivingDocs(options) {
33
34
  timeZone,
34
35
  pollIntervalMs: options.pollIntervalMs,
35
36
  flushDebounceMs: options.debounceMs,
37
+ richAnalysis: options.rich !== false,
36
38
  onLog: message => console.log(chalk_1.default.gray(` ${message}`)),
37
39
  });
38
40
  printStartupSummary({
39
41
  targetPath,
40
42
  timeZone,
41
43
  seedingEnabled: !!(apiUrl && apiKey),
44
+ richEnabled: options.rich !== false,
42
45
  });
43
46
  manager.start();
44
47
  await new Promise((resolvePromise) => {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * logout.ts — Log out of ekkOS
3
+ *
4
+ * Clears API credentials from ~/.ekkos/config.json and optionally
5
+ * clears Synk mobile sync credentials from ~/.ekkos/synk/.
6
+ */
7
+ export declare function logout(options: {
8
+ all?: boolean;
9
+ }): Promise<void>;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * logout.ts — Log out of ekkOS
4
+ *
5
+ * Clears API credentials from ~/.ekkos/config.json and optionally
6
+ * clears Synk mobile sync credentials from ~/.ekkos/synk/.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.logout = logout;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const readline = __importStar(require("readline"));
49
+ const chalk_1 = __importDefault(require("chalk"));
50
+ const state_1 = require("../utils/state");
51
+ const SYNK_DIR = path.join(state_1.EKKOS_DIR, 'synk');
52
+ async function logout(options) {
53
+ const config = (0, state_1.getConfig)();
54
+ if (!config?.apiKey && !config?.hookApiKey) {
55
+ console.log(chalk_1.default.yellow('Not currently logged in.'));
56
+ console.log(chalk_1.default.gray('Run "ekkos init" to authenticate.'));
57
+ return;
58
+ }
59
+ console.log('');
60
+ console.log(chalk_1.default.blue('Current session:'));
61
+ if (config.userId)
62
+ console.log(chalk_1.default.gray(` User ID: ${config.userId}`));
63
+ console.log('');
64
+ console.log(chalk_1.default.yellow('This will clear your ekkOS API credentials from this machine.'));
65
+ const hasSynk = fs.existsSync(SYNK_DIR);
66
+ if (options.all && hasSynk) {
67
+ console.log(chalk_1.default.yellow(' --all flag: Synk mobile sync credentials will also be cleared.'));
68
+ }
69
+ console.log('');
70
+ const rl = readline.createInterface({
71
+ input: process.stdin,
72
+ output: process.stdout,
73
+ });
74
+ const answer = await new Promise((resolve) => {
75
+ rl.question(chalk_1.default.yellow('Are you sure you want to log out? (y/N): '), resolve);
76
+ });
77
+ rl.close();
78
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
79
+ console.log(chalk_1.default.blue('Logout cancelled.'));
80
+ return;
81
+ }
82
+ // Clear main ekkOS config
83
+ try {
84
+ if (fs.existsSync(state_1.CONFIG_FILE)) {
85
+ fs.unlinkSync(state_1.CONFIG_FILE);
86
+ }
87
+ console.log(chalk_1.default.green('✓ ekkOS API credentials cleared'));
88
+ }
89
+ catch (err) {
90
+ console.error(chalk_1.default.red(`Failed to clear config: ${err.message}`));
91
+ }
92
+ // Optionally clear Synk credentials
93
+ if (options.all && hasSynk) {
94
+ try {
95
+ fs.rmSync(SYNK_DIR, { recursive: true, force: true });
96
+ console.log(chalk_1.default.green('✓ Synk mobile sync credentials cleared'));
97
+ }
98
+ catch (err) {
99
+ console.error(chalk_1.default.red(`Failed to clear Synk data: ${err.message}`));
100
+ }
101
+ }
102
+ console.log('');
103
+ console.log(chalk_1.default.gray('Run "ekkos init" to log in again.'));
104
+ }
@@ -45,6 +45,7 @@ const os = __importStar(require("os"));
45
45
  const child_process_1 = require("child_process");
46
46
  const state_1 = require("../utils/state");
47
47
  const session_binding_1 = require("../utils/session-binding");
48
+ const stdin_relay_1 = require("../utils/stdin-relay");
48
49
  const doctor_1 = require("./doctor");
49
50
  const stream_tailer_1 = require("../capture/stream-tailer");
50
51
  const jsonl_rewriter_1 = require("../capture/jsonl-rewriter");
@@ -87,7 +88,7 @@ function getConfig(options) {
87
88
  /* eslint-enable no-restricted-syntax */
88
89
  }
89
90
  const MAX_OUTPUT_1M_MODELS = '64000';
90
- const MAX_OUTPUT_200K_OPUS_SONNET = '32768';
91
+ const MAX_OUTPUT_200K_OPUS_SONNET = '16384';
91
92
  let runtimeClaudeCodeVersion = null;
92
93
  let runtimeClaudeContextWindow = 'auto';
93
94
  let runtimeClaudeLaunchModel;
@@ -239,9 +240,9 @@ function renderClaudeLaunchSelectorIntro() {
239
240
  console.log(neonCyan('╔══════════════════════════════════════════════════════════════════════╗'));
240
241
  console.log(line(chalk_1.default.white.bold('ekkOS.dev // PULSE')));
241
242
  console.log(neonCyan('╠══════════════════════════════════════════════════════════════════════╣'));
242
- console.log(line(`${acidGreen('200K')} ${steel('compat lane')} ${chalk_1.default.white('// Opus/Sonnet 4.5/4.6 => 32,768 output')}`));
243
- console.log(line(`${signalAmber(' 1M')} ${steel('wide lane')} ${chalk_1.default.white('// Opus/Sonnet 4.6 => 64,000 output')}`));
244
- console.log(line(steel('Pick a launch vector, then a fixed runtime profile. No hidden context prompt.')));
243
+ console.log(line(`${acidGreen('200K')} ${steel('compat lane')} ${chalk_1.default.white('// 16,384 output // eviction + replay at 90%')}`));
244
+ console.log(line(`${signalAmber(' 1M')} ${steel('wide lane')} ${chalk_1.default.white('// 64,000 output // 5x headroom')}`));
245
+ console.log(line(steel('98%+ cache hit rate // 30 msg keep on eviction // 50% target')));
245
246
  console.log(neonCyan('╚══════════════════════════════════════════════════════════════════════╝'));
246
247
  console.log('');
247
248
  }
@@ -267,31 +268,31 @@ function buildLaunchModelChoices() {
267
268
  const green = chalk_1.default.hex('#00ff88');
268
269
  return [
269
270
  {
270
- name: `${cyan.bold('Auto route')} ${chalk_1.default.gray('// Claude decides profile')}`,
271
+ name: `${cyan.bold('Auto route')} ${chalk_1.default.gray('// Opus 4.6 · 200K · 16,384 output')}`,
271
272
  value: 'default',
272
273
  },
273
274
  {
274
- name: `${cyan.bold('Claude Opus 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
275
+ name: `${cyan.bold('Claude Opus 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// 16,384 output')}`,
275
276
  value: 'claude-opus-4-5',
276
277
  },
277
278
  {
278
- name: `${cyan.bold('Claude Opus 4.6')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
279
+ name: `${cyan.bold('Claude Opus 4.6')} ${green('[200K]')} ${chalk_1.default.gray('// 16,384 output')}`,
279
280
  value: 'claude-opus-4-6-200k',
280
281
  },
281
282
  {
282
- name: `${cyan.bold('Claude Opus 4.6')} ${amber('[1M]')} ${chalk_1.default.gray('// out 64,000')}`,
283
+ name: `${cyan.bold('Claude Opus 4.6')} ${amber('[1M]')} ${chalk_1.default.gray('// 64,000 output')}`,
283
284
  value: 'claude-opus-4-6-1m',
284
285
  },
285
286
  {
286
- name: `${cyan.bold('Claude Sonnet 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
287
+ name: `${cyan.bold('Claude Sonnet 4.5')} ${green('[200K]')} ${chalk_1.default.gray('// 16,384 output')}`,
287
288
  value: 'claude-sonnet-4-5',
288
289
  },
289
290
  {
290
- name: `${cyan.bold('Claude Sonnet 4.6')} ${green('[200K]')} ${chalk_1.default.gray('// out 32,768')}`,
291
+ name: `${cyan.bold('Claude Sonnet 4.6')} ${green('[200K]')} ${chalk_1.default.gray('// 16,384 output')}`,
291
292
  value: 'claude-sonnet-4-6-200k',
292
293
  },
293
294
  {
294
- name: `${cyan.bold('Claude Sonnet 4.6')} ${amber('[1M]')} ${chalk_1.default.gray('// out 64,000')}`,
295
+ name: `${cyan.bold('Claude Sonnet 4.6')} ${amber('[1M]')} ${chalk_1.default.gray('// 64,000 output')}`,
295
296
  value: 'claude-sonnet-4-6-1m',
296
297
  },
297
298
  {
@@ -312,7 +313,12 @@ async function resolveClaudeLaunchSelection(options) {
312
313
  && process.stdout.isTTY === true
313
314
  && process.env.EKKOS_DISABLE_LAUNCH_WINDOW !== '1';
314
315
  if (!shouldShowLaunchWindow) {
315
- if (!modelSupportsOneMillionContext(model)) {
316
+ // Default to newest Opus 200k when no model explicitly set
317
+ if (!model) {
318
+ model = 'claude-opus-4-6';
319
+ contextWindow = '200k';
320
+ }
321
+ else if (!modelSupportsOneMillionContext(model)) {
316
322
  contextWindow = '200k';
317
323
  }
318
324
  return {
@@ -346,7 +352,7 @@ async function resolveClaudeLaunchSelection(options) {
346
352
  },
347
353
  ]);
348
354
  const selectedProfile = normalizeRequestedLaunchModel(firstPrompt.model);
349
- model = firstPrompt.model === 'default' ? undefined : selectedProfile.model;
355
+ model = firstPrompt.model === 'default' ? 'claude-opus-4-6' : selectedProfile.model;
350
356
  continueLast = firstPrompt.launchMode === 'continue';
351
357
  resumeSession = firstPrompt.launchMode === 'resume' ? resumeSession : '';
352
358
  if (firstPrompt.launchMode === 'resume') {
@@ -362,7 +368,9 @@ async function resolveClaudeLaunchSelection(options) {
362
368
  resumeSession = resumePrompt.resumeSession.trim();
363
369
  }
364
370
  if (firstPrompt.model === 'default') {
365
- contextWindow = 'auto';
371
+ // Auto route → default to newest Opus with 200k context
372
+ model = 'claude-opus-4-6';
373
+ contextWindow = '200k';
366
374
  }
367
375
  else if (selectedProfile.syntheticContextWindow) {
368
376
  contextWindow = selectedProfile.syntheticContextWindow;
@@ -624,7 +632,7 @@ function getEkkosEnv() {
624
632
  // Only ekkOS-wrapped sessions get this; vanilla `claude` keeps autocompact on.
625
633
  DISABLE_AUTO_COMPACT: 'true',
626
634
  // Align Claude's advertised output ceiling with the chosen model/context profile.
627
- // 1M Opus/Sonnet gets 128K; 200K Opus/Sonnet 4.5/4.6 is forced to 32,768.
635
+ // 1M Opus/Sonnet gets 64K; 200K Opus/Sonnet 4.5/4.6 is forced to 16,384.
628
636
  // The proxy also enforces this server-side in case Claude Code ignores the env var.
629
637
  CLAUDE_CODE_MAX_OUTPUT_TOKENS: maxOutputTokens,
630
638
  };
@@ -913,12 +921,12 @@ function formatPulseModel(model) {
913
921
  }
914
922
  function formatPulseContextLine(contextWindow) {
915
923
  if (contextWindow === '200k') {
916
- return '200K // 200,000 token window // forced 32,768 output';
924
+ return '200K // 200,000 tokens // 16,384 max output // eviction at 90%';
917
925
  }
918
926
  if (contextWindow === '1m') {
919
- return '1M // 1,000,000 token window // safe 64,000 output cap';
927
+ return '1M // 1,000,000 tokens // 64,000 max output // no eviction until 90%';
920
928
  }
921
- return 'AUTO // ekkOS resolves 200K vs 1M from the selected profile';
929
+ return 'AUTO // resolved from model profile';
922
930
  }
923
931
  async function showPulseLaunchLoader(options) {
924
932
  const cyan = chalk_1.default.hex('#00f0ff');
@@ -962,8 +970,9 @@ async function showPulseLaunchLoader(options) {
962
970
  console.log(buildPanelLine(amber, 'WINDOW', formatPulseContextLine(normalizeContextWindowOption(options.contextWindow))));
963
971
  console.log(buildPanelLine(steel, 'PATH', 'selector -> dashboard -> claude -> proxy'));
964
972
  console.log(cyan(' ╚══════════════════════════════════════════════════════════════════════════════╝'));
965
- console.log(` ${green('200K')} ${steel('// Opus/Sonnet 4.5/4.6 => 32,768 output')}`);
966
- console.log(` ${amber('1M ')} ${steel('// Opus/Sonnet 4.6 => 64,000 output')}`);
973
+ console.log(` ${green('200K')} ${steel('// 16,384 output // eviction + replay at 90% // target 50%')}`);
974
+ console.log(` ${amber('1M ')} ${steel('// 64,000 output // 5x headroom // eviction deferred')}`);
975
+ console.log(` ${steel(' // 98%+ cache hit rate // 30 msg keep on eviction')}`);
967
976
  console.log('');
968
977
  for (const stage of stages) {
969
978
  for (let filled = 0; filled <= barWidth; filled += 1) {
@@ -1418,7 +1427,7 @@ async function run(options) {
1418
1427
  proxyModeEnabled = !(options.noProxy || false);
1419
1428
  if (proxyModeEnabled) {
1420
1429
  if (!suppressPreClaudeOutput && !options.pulse) {
1421
- console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
1430
+ console.log(chalk_1.default.cyan(' 🧠 ekkOS_Pulse // proxy active'));
1422
1431
  }
1423
1432
  }
1424
1433
  else if (verbose && !suppressPreClaudeOutput) {
@@ -1803,7 +1812,7 @@ async function run(options) {
1803
1812
  // ══════════════════════════════════════════════════════════════════════════
1804
1813
  // MAGIC MOMENT: Morning dreams right after sparkle, before Claude appears
1805
1814
  // ══════════════════════════════════════════════════════════════════════════
1806
- if (!suppressPreClaudeOutput) {
1815
+ if (!suppressPreClaudeOutput && !options.pulse) {
1807
1816
  await showMorningDreamsIfNeeded();
1808
1817
  }
1809
1818
  // ══════════════════════════════════════════════════════════════════════════
@@ -1855,6 +1864,7 @@ async function run(options) {
1855
1864
  let isAutoClearInProgress = false;
1856
1865
  let transcriptPath = null;
1857
1866
  let currentSessionId = null;
1867
+ let stdinRelayHandle = null;
1858
1868
  // ══════════════════════════════════════════════════════════════════════════
1859
1869
  // PER-TURN BANNER STATE
1860
1870
  // Tracks idle→active transitions to print the session banner once per turn
@@ -2010,7 +2020,10 @@ async function run(options) {
2010
2020
  // and shown in "Continuum Loaded". Re-deriving from JSONL UUID produces a
2011
2021
  // different name since Claude Code's UUID ≠ the CLI-generated UUID.
2012
2022
  currentSession = cliSessionName || (0, state_1.uuidToWords)(sessionId);
2013
- (0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession, getClaudeSessionMetadata(options));
2023
+ (0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession, {
2024
+ ...getClaudeSessionMetadata(options),
2025
+ transcriptPath: fullPath,
2026
+ });
2014
2027
  (0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
2015
2028
  writeSessionFiles(currentSessionId, currentSession, getClaudeSessionMetadata(options));
2016
2029
  bindRealSessionToProxy(currentSession, 'fast-transcript', currentSessionId);
@@ -2423,6 +2436,24 @@ async function run(options) {
2423
2436
  }
2424
2437
  process.stdin.resume();
2425
2438
  process.stdin.on('data', onStdinData);
2439
+ // ══════════════════════════════════════════════════════════════════════════
2440
+ // UNIVERSAL SESSION ATTACH — stdin relay for remote input injection
2441
+ // Starts a localhost HTTP server so the daemon can pipe in remote input
2442
+ // ══════════════════════════════════════════════════════════════════════════
2443
+ (0, stdin_relay_1.startStdinRelay)({
2444
+ write: (text) => shell.write(text),
2445
+ onAttach: (remoteInfo) => {
2446
+ dlog(`[session-attach] Remote client attached: ${remoteInfo}`);
2447
+ },
2448
+ dlog,
2449
+ }).then(handle => {
2450
+ stdinRelayHandle = handle;
2451
+ dlog(`[session-attach] stdin relay started on port ${handle.port}`);
2452
+ // Update active session with relay info
2453
+ (0, state_1.updateCurrentProcessSession)(currentSessionId || initialSessionId, currentSession || initialSessionName, { stdinPort: handle.port, stdinToken: handle.token });
2454
+ }).catch(err => {
2455
+ dlog(`[session-attach] stdin relay failed to start: ${err.message}`);
2456
+ });
2426
2457
  // Helper to get current output buffer (for readiness checks)
2427
2458
  const getOutputBuffer = () => outputBuffer;
2428
2459
  // Handle context wall detection
@@ -3233,6 +3264,7 @@ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
3233
3264
  (0, state_1.clearAutoClearFlag)();
3234
3265
  localLivingDocsManager?.stop();
3235
3266
  stopStreamTailer(); // Stop stream capture
3267
+ stdinRelayHandle?.stop().catch(() => { }); // Stop stdin relay
3236
3268
  (0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
3237
3269
  cleanupInstanceFile(instanceId); // Clean up instance file
3238
3270
  // NOTE: No restore needed - ekkOS uses separate installation from homebrew
@@ -3253,6 +3285,7 @@ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
3253
3285
  (0, state_1.clearAutoClearFlag)();
3254
3286
  localLivingDocsManager?.stop();
3255
3287
  stopStreamTailer(); // Stop stream capture
3288
+ stdinRelayHandle?.stop().catch(() => { }); // Stop stdin relay
3256
3289
  (0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
3257
3290
  cleanupInstanceFile(instanceId); // Clean up instance file
3258
3291
  // NOTE: No restore needed - ekkOS uses separate installation from homebrew
@@ -0,0 +1,4 @@
1
+ export declare function listWorkspaces(): Promise<void>;
2
+ export declare function addWorkspace(path: string): Promise<void>;
3
+ export declare function removeWorkspace(path: string): Promise<void>;
4
+ export declare function manageWorkspaces(): Promise<void>;
@@ -0,0 +1,153 @@
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.listWorkspaces = listWorkspaces;
7
+ exports.addWorkspace = addWorkspace;
8
+ exports.removeWorkspace = removeWorkspace;
9
+ exports.manageWorkspaces = manageWorkspaces;
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const paths_js_1 = require("../utils/paths.js");
13
+ const inquirer_1 = __importDefault(require("inquirer"));
14
+ /**
15
+ * ekkos cortex workspaces
16
+ *
17
+ * Tool for managing projects watched by the Cortex universal background watcher.
18
+ */
19
+ async function getDaemonPort() {
20
+ const portPath = (0, paths_js_1.getDaemonPortPath)();
21
+ if (!fs_1.default.existsSync(portPath))
22
+ return null;
23
+ const port = fs_1.default.readFileSync(portPath, 'utf-8').trim();
24
+ return port ? Number(port) : null;
25
+ }
26
+ async function listWorkspaces() {
27
+ const port = await getDaemonPort();
28
+ if (!port) {
29
+ console.log(chalk_1.default.yellow(' Daemon is not running. Start it with `ekkos daemon start`.'));
30
+ return;
31
+ }
32
+ try {
33
+ const res = await fetch(`http://127.0.0.1:${port}/list-workspaces`);
34
+ if (!res.ok)
35
+ throw new Error(`HTTP ${res.status}`);
36
+ const { workspaces } = await res.json();
37
+ console.log('');
38
+ console.log(chalk_1.default.cyan.bold(' Watched Workspaces (Cortex)'));
39
+ console.log(chalk_1.default.gray(' ───────────────────────────'));
40
+ if (workspaces.length === 0) {
41
+ console.log(chalk_1.default.gray(' No workspaces currently being watched.'));
42
+ }
43
+ else {
44
+ for (const w of workspaces) {
45
+ console.log(` ${chalk_1.default.green('•')} ${chalk_1.default.white(w.path)}`);
46
+ }
47
+ }
48
+ console.log('');
49
+ }
50
+ catch (err) {
51
+ console.log(chalk_1.default.red(` Failed to list workspaces: ${err.message}`));
52
+ }
53
+ }
54
+ async function addWorkspace(path) {
55
+ const port = await getDaemonPort();
56
+ if (!port) {
57
+ console.log(chalk_1.default.yellow(' Daemon is not running. Cannot register workspace.'));
58
+ return;
59
+ }
60
+ try {
61
+ const res = await fetch(`http://127.0.0.1:${port}/register-workspace`, {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({ path }),
65
+ });
66
+ if (res.ok) {
67
+ console.log(chalk_1.default.green(` ✓ Successfully registered workspace: ${path}`));
68
+ }
69
+ else {
70
+ const data = await res.json();
71
+ console.log(chalk_1.default.red(` ✗ Failed to register workspace: ${data.error || res.statusText}`));
72
+ }
73
+ }
74
+ catch (err) {
75
+ console.log(chalk_1.default.red(` ✗ Error connecting to daemon: ${err.message}`));
76
+ }
77
+ }
78
+ async function removeWorkspace(path) {
79
+ const port = await getDaemonPort();
80
+ if (!port) {
81
+ console.log(chalk_1.default.yellow(' Daemon is not running. Cannot unregister workspace.'));
82
+ return;
83
+ }
84
+ try {
85
+ const res = await fetch(`http://127.0.0.1:${port}/unregister-workspace`, {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({ path }),
89
+ });
90
+ if (res.ok) {
91
+ console.log(chalk_1.default.green(` ✓ Successfully unregistered workspace: ${path}`));
92
+ }
93
+ else {
94
+ const data = await res.json();
95
+ console.log(chalk_1.default.red(` ✗ Failed to unregister workspace: ${data.error || res.statusText}`));
96
+ }
97
+ }
98
+ catch (err) {
99
+ console.log(chalk_1.default.red(` ✗ Error connecting to daemon: ${err.message}`));
100
+ }
101
+ }
102
+ async function manageWorkspaces() {
103
+ const port = await getDaemonPort();
104
+ if (!port) {
105
+ console.log(chalk_1.default.yellow('\n Daemon is not running. Start it with `ekkos daemon start` first.'));
106
+ return;
107
+ }
108
+ try {
109
+ const res = await fetch(`http://127.0.0.1:${port}/list-workspaces`);
110
+ if (!res.ok)
111
+ throw new Error(`HTTP ${res.status}`);
112
+ const { workspaces } = await res.json();
113
+ const { action } = await inquirer_1.default.prompt([
114
+ {
115
+ type: 'list',
116
+ name: 'action',
117
+ message: 'Manage Cortex Workspaces',
118
+ choices: [
119
+ { name: 'List all watched projects', value: 'list' },
120
+ { name: 'Stop watching a project', value: 'remove' },
121
+ { name: 'Add current directory', value: 'add_cwd' },
122
+ { name: 'Exit', value: 'exit' }
123
+ ]
124
+ }
125
+ ]);
126
+ if (action === 'list') {
127
+ await listWorkspaces();
128
+ }
129
+ else if (action === 'remove') {
130
+ if (workspaces.length === 0) {
131
+ console.log(chalk_1.default.yellow('\n No workspaces to remove.'));
132
+ return;
133
+ }
134
+ const { toRemove } = await inquirer_1.default.prompt([
135
+ {
136
+ type: 'checkbox',
137
+ name: 'toRemove',
138
+ message: 'Select workspaces to stop watching:',
139
+ choices: workspaces.map(w => w.path)
140
+ }
141
+ ]);
142
+ for (const p of toRemove) {
143
+ await removeWorkspace(p);
144
+ }
145
+ }
146
+ else if (action === 'add_cwd') {
147
+ await addWorkspace(process.cwd());
148
+ }
149
+ }
150
+ catch (err) {
151
+ console.log(chalk_1.default.red(`\n Error: ${err.message}`));
152
+ }
153
+ }