@ekkos/cli 1.3.1 โ†’ 1.3.5

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 (131) hide show
  1. package/dist/capture/jsonl-rewriter.d.ts +1 -1
  2. package/dist/capture/jsonl-rewriter.js +3 -3
  3. package/dist/capture/transcript-repair.d.ts +2 -2
  4. package/dist/capture/transcript-repair.js +2 -2
  5. package/dist/commands/claw.d.ts +13 -0
  6. package/dist/commands/claw.js +253 -0
  7. package/dist/commands/dashboard.js +742 -118
  8. package/dist/commands/doctor.d.ts +3 -3
  9. package/dist/commands/doctor.js +6 -79
  10. package/dist/commands/gemini.d.ts +19 -0
  11. package/dist/commands/gemini.js +193 -0
  12. package/dist/commands/init.d.ts +1 -0
  13. package/dist/commands/init.js +56 -41
  14. package/dist/commands/run.d.ts +0 -1
  15. package/dist/commands/run.js +288 -263
  16. package/dist/commands/scan.d.ts +21 -0
  17. package/dist/commands/scan.js +386 -0
  18. package/dist/commands/status.d.ts +4 -1
  19. package/dist/commands/status.js +165 -27
  20. package/dist/commands/swarm-dashboard.js +156 -28
  21. package/dist/commands/swarm.d.ts +1 -1
  22. package/dist/commands/swarm.js +1 -1
  23. package/dist/commands/test-claude.d.ts +2 -2
  24. package/dist/commands/test-claude.js +3 -3
  25. package/dist/deploy/index.d.ts +0 -2
  26. package/dist/deploy/index.js +0 -2
  27. package/dist/deploy/settings.d.ts +6 -5
  28. package/dist/deploy/settings.js +64 -16
  29. package/dist/deploy/skills.js +1 -2
  30. package/dist/index.js +86 -96
  31. package/dist/lib/usage-parser.d.ts +1 -1
  32. package/dist/lib/usage-parser.js +9 -6
  33. package/dist/local/index.d.ts +14 -0
  34. package/dist/local/index.js +28 -0
  35. package/dist/local/local-embeddings.d.ts +49 -0
  36. package/dist/local/local-embeddings.js +232 -0
  37. package/dist/local/offline-fallback.d.ts +44 -0
  38. package/dist/local/offline-fallback.js +159 -0
  39. package/dist/local/sqlite-store.d.ts +126 -0
  40. package/dist/local/sqlite-store.js +393 -0
  41. package/dist/local/sync-engine.d.ts +42 -0
  42. package/dist/local/sync-engine.js +223 -0
  43. package/dist/utils/platform.d.ts +5 -1
  44. package/dist/utils/platform.js +24 -4
  45. package/dist/utils/proxy-url.d.ts +21 -0
  46. package/dist/utils/proxy-url.js +34 -0
  47. package/dist/utils/state.d.ts +1 -1
  48. package/dist/utils/state.js +11 -3
  49. package/dist/utils/templates.js +1 -1
  50. package/package.json +11 -4
  51. package/templates/CLAUDE.md +49 -107
  52. package/dist/agent/daemon.d.ts +0 -130
  53. package/dist/agent/daemon.js +0 -606
  54. package/dist/agent/health-check.d.ts +0 -35
  55. package/dist/agent/health-check.js +0 -243
  56. package/dist/agent/pty-runner.d.ts +0 -53
  57. package/dist/agent/pty-runner.js +0 -190
  58. package/dist/commands/agent.d.ts +0 -50
  59. package/dist/commands/agent.js +0 -544
  60. package/dist/commands/setup-remote.d.ts +0 -20
  61. package/dist/commands/setup-remote.js +0 -582
  62. package/dist/utils/verify-remote-terminal.d.ts +0 -10
  63. package/dist/utils/verify-remote-terminal.js +0 -415
  64. package/templates/README.md +0 -378
  65. package/templates/claude-plugins/PHASE2_COMPLETION.md +0 -346
  66. package/templates/claude-plugins/PLUGIN_PROPOSALS.md +0 -1776
  67. package/templates/claude-plugins/README.md +0 -587
  68. package/templates/claude-plugins/agents/code-reviewer.json +0 -14
  69. package/templates/claude-plugins/agents/debug-detective.json +0 -15
  70. package/templates/claude-plugins/agents/git-companion.json +0 -14
  71. package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +0 -8
  72. package/templates/claude-plugins/blog-manager/commands/blog.md +0 -691
  73. package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +0 -8
  74. package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +0 -434
  75. package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +0 -8
  76. package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +0 -282
  77. package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +0 -8
  78. package/templates/claude-plugins/memory-lens/commands/memory-search.md +0 -181
  79. package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +0 -8
  80. package/templates/claude-plugins/pattern-coach/commands/forge.md +0 -365
  81. package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +0 -8
  82. package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +0 -582
  83. package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +0 -819
  84. package/templates/claude-plugins-admin/README.md +0 -446
  85. package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +0 -8
  86. package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +0 -595
  87. package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +0 -8
  88. package/templates/claude-plugins-admin/backend-agent/commands/backend.md +0 -798
  89. package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +0 -8
  90. package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +0 -554
  91. package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +0 -8
  92. package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +0 -881
  93. package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +0 -8
  94. package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +0 -85
  95. package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +0 -8
  96. package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +0 -569
  97. package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +0 -8
  98. package/templates/claude-plugins-admin/qa-agent/commands/qa.md +0 -863
  99. package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +0 -8
  100. package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +0 -732
  101. package/templates/commands/continue.md +0 -47
  102. package/templates/cursor-rules/ekkos-memory.md +0 -127
  103. package/templates/ekkos-manifest.json +0 -223
  104. package/templates/helpers/json-parse.cjs +0 -101
  105. package/templates/hooks-node/lib/state.js +0 -187
  106. package/templates/hooks-node/stop.js +0 -416
  107. package/templates/hooks-node/user-prompt-submit.js +0 -337
  108. package/templates/plan-template.md +0 -306
  109. package/templates/rules/00-hooks-contract.mdc +0 -89
  110. package/templates/rules/30-ekkos-core.mdc +0 -188
  111. package/templates/rules/31-ekkos-messages.mdc +0 -78
  112. package/templates/shared/hooks-enabled.json +0 -22
  113. package/templates/shared/session-words.json +0 -45
  114. package/templates/skills/ekkOS_Deep_Recall/Skill.md +0 -282
  115. package/templates/skills/ekkOS_Learn/Skill.md +0 -265
  116. package/templates/skills/ekkOS_Memory_First/Skill.md +0 -206
  117. package/templates/skills/ekkOS_Plan_Assist/Skill.md +0 -302
  118. package/templates/skills/ekkOS_Preferences/Skill.md +0 -247
  119. package/templates/skills/ekkOS_Reflect/Skill.md +0 -257
  120. package/templates/skills/ekkOS_Safety/Skill.md +0 -265
  121. package/templates/skills/ekkOS_Schema/Skill.md +0 -251
  122. package/templates/skills/ekkOS_Summary/Skill.md +0 -257
  123. package/templates/spec-template.md +0 -159
  124. package/templates/windsurf-rules/ekkos-memory.md +0 -127
  125. package/templates/windsurf-skills/README.md +0 -58
  126. package/templates/windsurf-skills/ekkos-continue/SKILL.md +0 -81
  127. package/templates/windsurf-skills/ekkos-golden-loop/SKILL.md +0 -225
  128. package/templates/windsurf-skills/ekkos-insights/SKILL.md +0 -138
  129. package/templates/windsurf-skills/ekkos-recall/SKILL.md +0 -96
  130. package/templates/windsurf-skills/ekkos-safety/SKILL.md +0 -89
  131. package/templates/windsurf-skills/ekkos-vault/SKILL.md +0 -86
@@ -43,139 +43,6 @@ const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const os = __importStar(require("os"));
45
45
  const child_process_1 = require("child_process");
46
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
47
- // ccDNA AUTO-LOAD: Apply Claude Code patches before spawning
48
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
49
- const CCDNA_PATHS = [
50
- // Development path (DEV sibling directory)
51
- // From: EKKOS/packages/ekkos-cli/dist/commands/ โ†’ DEV/ekkos-ccdna/
52
- path.join(__dirname, '..', '..', '..', '..', '..', 'ekkos-ccdna', 'dist', 'index.mjs'),
53
- // User install path
54
- path.join(os.homedir(), '.ekkos', 'ccdna', 'dist', 'index.mjs'),
55
- // npm global (homebrew)
56
- '/opt/homebrew/lib/node_modules/ekkos-ccdna/dist/index.mjs',
57
- // npm global (standard)
58
- path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', 'ekkos-ccdna', 'dist', 'index.mjs'),
59
- ];
60
- /**
61
- * Find ccDNA installation path
62
- */
63
- function findCcdnaPath() {
64
- for (const p of CCDNA_PATHS) {
65
- if (fs.existsSync(p)) {
66
- return p;
67
- }
68
- }
69
- return null;
70
- }
71
- /**
72
- * Apply ccDNA patches silently before Claude spawns
73
- * Returns version string if patches were applied, null otherwise
74
- *
75
- * @param verbose - Show detailed output
76
- * @param claudePath - Path to Claude Code to patch (if different from default)
77
- */
78
- function applyCcdnaPatches(verbose, claudePath) {
79
- // DISABLED: ccDNA patching is currently corrupting cli.js (JSON parse error at position 7945)
80
- // See: https://github.com/anthropics/ekkos/issues/2856
81
- // The patching process is injecting code that breaks the minified cli.js
82
- // Temporarily disabled until ccDNA is fixed upstream
83
- if (verbose) {
84
- console.log(chalk_1.default.gray(' ccDNA patching disabled (see issue #2856)'));
85
- }
86
- return null;
87
- // Original implementation (disabled):
88
- /*
89
- const ccdnaPath = findCcdnaPath();
90
- if (!ccdnaPath) {
91
- if (verbose) {
92
- console.log(chalk.gray(' ccDNA not found - skipping patches'));
93
- }
94
- return null;
95
- }
96
-
97
- // Read ccDNA version from package.json FIRST
98
- let ccdnaVersion = 'unknown';
99
- try {
100
- const pkgPath = path.join(path.dirname(ccdnaPath), '..', 'package.json');
101
- if (fs.existsSync(pkgPath)) {
102
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
103
- ccdnaVersion = pkg.version || 'unknown';
104
- }
105
- } catch {
106
- // Ignore version detection errors
107
- }
108
-
109
- try {
110
- // Set env var to tell ccDNA which Claude to patch
111
- // eslint-disable-next-line no-restricted-syntax
112
- const env = { ...process.env };
113
- if (claudePath) {
114
- // ccDNA checks CCDNA_CC_INSTALLATION_PATH to override default detection
115
- env.CCDNA_CC_INSTALLATION_PATH = claudePath;
116
- }
117
-
118
- // Run ccDNA in apply mode (non-interactive)
119
- execSync(`node "${ccdnaPath}" -a`, {
120
- stdio: verbose ? 'inherit' : 'pipe',
121
- timeout: 30000, // 30 second timeout
122
- env,
123
- });
124
-
125
- if (verbose) {
126
- console.log(chalk.green(` โœ“ ccDNA v${ccdnaVersion} patches applied`));
127
- }
128
- return ccdnaVersion;
129
- } catch (err) {
130
- if (verbose) {
131
- console.log(chalk.yellow(` โš  ccDNA patch failed: ${(err as Error).message}`));
132
- }
133
- return null;
134
- }
135
- */
136
- }
137
- /**
138
- * Restore original Claude Code (remove ccDNA patches) on exit
139
- * This restores the ekkOS-managed installation (~/.ekkos/claude-code/) to its base state
140
- *
141
- * NOTE: We intentionally DON'T restore on exit anymore because:
142
- * 1. ekkOS uses a SEPARATE installation (~/.ekkos/claude-code/) from homebrew
143
- * 2. The homebrew `claude` command should always be vanilla (untouched)
144
- * 3. The ekkOS installation can stay patched - it's only used by `ekkos run`
145
- *
146
- * This function is kept for manual/explicit restore scenarios.
147
- */
148
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
149
- function restoreCcdnaPatches(verbose, claudePath) {
150
- const ccdnaPath = findCcdnaPath();
151
- if (!ccdnaPath) {
152
- return false;
153
- }
154
- try {
155
- // Set env var to tell ccDNA which Claude to restore
156
- // eslint-disable-next-line no-restricted-syntax
157
- const env = { ...process.env };
158
- if (claudePath) {
159
- env.CCDNA_CC_INSTALLATION_PATH = claudePath;
160
- }
161
- // Run ccDNA in restore mode (non-interactive)
162
- (0, child_process_1.execSync)(`node "${ccdnaPath}" -r`, {
163
- stdio: verbose ? 'inherit' : 'pipe',
164
- timeout: 30000, // 30 second timeout
165
- env,
166
- });
167
- if (verbose) {
168
- console.log(chalk_1.default.green(' โœ“ ccDNA patches removed (vanilla restored)'));
169
- }
170
- return true;
171
- }
172
- catch (err) {
173
- if (verbose) {
174
- console.log(chalk_1.default.yellow(` โš  ccDNA restore failed: ${err.message}`));
175
- }
176
- return false;
177
- }
178
- }
179
46
  const state_1 = require("../utils/state");
180
47
  const session_binding_1 = require("../utils/session-binding");
181
48
  const doctor_1 = require("./doctor");
@@ -214,7 +81,7 @@ function getConfig(options) {
214
81
  maxIdleWaitMs: parseInt(process.env.EKKOS_MAX_IDLE_WAIT_MS || '2000', 10), // was 10000
215
82
  debugLogPath: options.debugLogPath ??
216
83
  process.env.EKKOS_DEBUG_LOG_PATH ??
217
- path.join(os.homedir(), '.ekkos', 'auto-continue.debug.log')
84
+ path.join(os.homedir(), '.ekkos', 'ekkos-debug.log')
218
85
  };
219
86
  /* eslint-enable no-restricted-syntax */
220
87
  }
@@ -255,7 +122,7 @@ const PALETTE_INDICATOR_REGEX = /\/(clear|continue|compact|help|bug|config)/i;
255
122
  const SESSION_NAME_IN_STATUS_REGEX = /turn\s+\d+\s*ยท\s*([a-z]+-[a-z]+-[a-z]+)\s*ยท/i;
256
123
  // Weaker signal: any 3-word slug (word-word-word pattern)
257
124
  const SESSION_NAME_REGEX = /\b([a-z]+-[a-z]+-[a-z]+)\b/i;
258
- // Orphan tool_result marker emitted by ccDNA validate mode
125
+ // Orphan tool_result marker
259
126
  // Example: [ekkOS] ORPHAN_TOOL_RESULT {"idx":0,"tool_use_id":"toolu_01...","block_idx":0}
260
127
  const ORPHAN_MARKER_REGEX = /\[ekkOS\]\s+ORPHAN_TOOL_RESULT\s+(\{.*?\})/gi;
261
128
  // Cooldown to prevent thrashing if output repeats the marker
@@ -282,7 +149,7 @@ function isValidSessionName(name) {
282
149
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
283
150
  // LOGGING (FILE ONLY DURING TUI - NO TERMINAL CORRUPTION)
284
151
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
285
- let _debugLogPath = path.join(os.homedir(), '.ekkos', 'auto-continue.debug.log');
152
+ let _debugLogPath = path.join(os.homedir(), '.ekkos', 'ekkos-debug.log');
286
153
  /**
287
154
  * Set the debug log path (called once during init)
288
155
  */
@@ -428,9 +295,7 @@ const PINNED_CLAUDE_VERSION = 'latest';
428
295
  // Opus 4.5 supports up to 64k - set EKKOS_MAX_OUTPUT_TOKENS=32768 or =65536 to use higher limits
429
296
  // Configurable via environment variable
430
297
  const EKKOS_MAX_OUTPUT_TOKENS = process.env.EKKOS_MAX_OUTPUT_TOKENS || '16384';
431
- // Default proxy URL for context eviction
432
- // eslint-disable-next-line no-restricted-syntax -- Config URL, not API key
433
- const EKKOS_PROXY_URL = process.env.EKKOS_PROXY_URL || 'https://proxy.ekkos.dev';
298
+ const proxy_url_1 = require("../utils/proxy-url");
434
299
  // Track proxy mode for getEkkosEnv (set by run() based on options)
435
300
  let proxyModeEnabled = true;
436
301
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -459,7 +324,14 @@ function getEkkosEnv() {
459
324
  /* eslint-disable no-restricted-syntax -- System env spreading, not API key access */
460
325
  const env = {
461
326
  ...process.env,
462
- // Let Claude Code use its own default max_tokens (don't override)
327
+ // Disable Claude's built-in auto-memory โ€” ekkOS replaces it with 11-layer memory.
328
+ // This env var overrides all other settings (settings.json, /memory toggle).
329
+ // Saves ~1K tokens/turn of redundant context and avoids duplicate memory writes.
330
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
331
+ // Disable auto-compact โ€” ekkOS handles context preservation via PreserveContext/RestoreContext.
332
+ // Native autocompact would compact without ekkOS saving state first, causing knowledge loss.
333
+ // Only ekkOS-wrapped sessions get this; vanilla `claude` keeps autocompact on.
334
+ DISABLE_AUTO_COMPACT: 'true',
463
335
  };
464
336
  /* eslint-enable no-restricted-syntax */
465
337
  // Check if proxy is disabled via env var or options
@@ -493,13 +365,8 @@ function getEkkosEnv() {
493
365
  }
494
366
  // CRITICAL: Embed user/session in URL path since ANTHROPIC_HEADERS doesn't work
495
367
  // Claude Code SDK doesn't forward custom headers, but it DOES use ANTHROPIC_BASE_URL
496
- // Format: https://mcp.ekkos.dev/proxy/{userId}/{sessionName}?project={base64(cwd)}
497
368
  // Gateway extracts from URL: /proxy/{userId}/{sessionName}/v1/messages
498
- // Project path is base64-encoded to handle special chars safely
499
- const projectPath = process.cwd();
500
- const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
501
- const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
502
- const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}&sid=${encodeURIComponent(cliSessionId)}&tz=${encodeURIComponent(userTz)}`;
369
+ const proxyUrl = (0, proxy_url_1.buildProxyUrl)(userId, cliSessionName, process.cwd(), cliSessionId);
503
370
  env.ANTHROPIC_BASE_URL = proxyUrl;
504
371
  // Proxy URL contains userId + project path โ€” don't leak to terminal
505
372
  }
@@ -708,6 +575,116 @@ function resolveGlobalClaudePath() {
708
575
  function sleep(ms) {
709
576
  return new Promise(resolve => setTimeout(resolve, ms));
710
577
  }
578
+ /**
579
+ * Show morning dreams with WOW factor right after sparkle animation.
580
+ * Colorful, lively, readable โ€” real magic moment. Then pause for Enter.
581
+ * Uses ~/.ekkos/.last-dream-shown marker (once per day, zero clutter on repeat).
582
+ */
583
+ async function showMorningDreamsIfNeeded() {
584
+ const markerPath = path.join(state_1.EKKOS_DIR, '.last-dream-shown');
585
+ const today = new Date().toISOString().split('T')[0];
586
+ // Fast path: already shown today
587
+ try {
588
+ if (fs.existsSync(markerPath)) {
589
+ const lastShown = fs.readFileSync(markerPath, 'utf-8').trim();
590
+ if (lastShown === today)
591
+ return;
592
+ }
593
+ }
594
+ catch { }
595
+ // Fetch digest from memory API (2s timeout โ€” never blocks startup)
596
+ try {
597
+ const authToken = (0, state_1.getAuthToken)();
598
+ if (!authToken)
599
+ return;
600
+ const controller = new AbortController();
601
+ const fetchTimeout = setTimeout(() => controller.abort(), 2000);
602
+ const res = await fetch(`${MEMORY_API_URL}/api/v1/dreams/digest`, {
603
+ headers: { Authorization: `Bearer ${authToken}` },
604
+ signal: controller.signal,
605
+ });
606
+ clearTimeout(fetchTimeout);
607
+ if (!res.ok)
608
+ return;
609
+ const digest = await res.json();
610
+ if (!digest.dreams || digest.dreams.length === 0)
611
+ return;
612
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
613
+ // WOW FACTOR RENDERING โ€” colorful, alive, readable
614
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
615
+ const dreamTime = new Date(digest.dreams[0].dreamedAt);
616
+ const timeStr = dreamTime.toLocaleTimeString('en-US', {
617
+ hour: 'numeric', minute: '2-digit', hour12: true,
618
+ }).toLowerCase();
619
+ console.log('');
620
+ console.log(chalk_1.default.cyan.bold(' ' + 'โ•'.repeat(66)));
621
+ console.log(chalk_1.default.hex('#FF69B4').bold(` ๐ŸŒ™ While you slept, I had ${digest.dreams.length} idea${digest.dreams.length === 1 ? '' : 's'} for you`) +
622
+ chalk_1.default.gray(` (forged at ${timeStr})`));
623
+ console.log(chalk_1.default.gray.dim(' From your personal knowledge graph โ€” concepts you\'ve never combined'));
624
+ console.log(chalk_1.default.cyan.bold(' ' + 'โ•'.repeat(66)));
625
+ console.log('');
626
+ digest.dreams.forEach((dream, i) => {
627
+ const filled = Math.round(dream.creativityScore * 5);
628
+ const stars = chalk_1.default.yellow('โ˜…'.repeat(filled)) + chalk_1.default.gray('โ˜†'.repeat(5 - filled));
629
+ // Dream title line with creativity stars
630
+ console.log(chalk_1.default.hex('#7C6BFF').bold(` ๐Ÿ’ญ [Dream ${i + 1}] `) +
631
+ chalk_1.default.white.bold(`"${dream.title}"`) +
632
+ ' ' + stars);
633
+ // Solution insight (up to 140 chars, two lines max)
634
+ const solution = dream.solution.length > 140
635
+ ? dream.solution.slice(0, 137) + '...'
636
+ : dream.solution;
637
+ console.log(chalk_1.default.gray(` ${solution}`));
638
+ // Lineage: origin โ†” discovery with domain colors
639
+ const crossDomain = dream.originDomain !== dream.discoveryDomain;
640
+ const lineage = crossDomain
641
+ ? chalk_1.default.cyan(dream.originConcept) +
642
+ chalk_1.default.hex('#FFA500')(' โšก ') +
643
+ chalk_1.default.hex('#DA70D6')(dream.discoveryConcept) +
644
+ chalk_1.default.gray.dim(` (${dream.originDomain} ร— ${dream.discoveryDomain})`)
645
+ : chalk_1.default.cyan(dream.originConcept) +
646
+ chalk_1.default.gray(' โ†” ') +
647
+ chalk_1.default.cyan(dream.discoveryConcept);
648
+ console.log(` ${lineage}`);
649
+ console.log('');
650
+ });
651
+ // Action hints
652
+ console.log(chalk_1.default.gray.dim(` Reply ${chalk_1.default.white('"promote 1"')} to make it permanent, ` +
653
+ `${chalk_1.default.white('"dismiss 1"')} to kill it, or just ignore โ€” it'll decay naturally.`));
654
+ if (digest.streak > 1) {
655
+ console.log(chalk_1.default.hex('#FF8800')(` ๐Ÿ”ฅ ${digest.streak}-night dream streak`) + chalk_1.default.gray.dim(` (${digest.totalDreamsEver} total dreams)`));
656
+ }
657
+ // Clickable link to expanded dashboard (OSC 8 hyperlink โ€” supported by iTerm2, Warp, Ghostty, etc.)
658
+ const dashUrl = 'https://platform.ekkos.dev/dashboard/dreams';
659
+ console.log('');
660
+ console.log(chalk_1.default.gray.dim(' ๐Ÿ“Š Full Dream Journal: ') + chalk_1.default.cyan.underline(`\x1B]8;;${dashUrl}\x07${dashUrl}\x1B]8;;\x07`));
661
+ console.log('');
662
+ console.log(chalk_1.default.hex('#FFA500').bold(' Press Enter to continue to Claude...'));
663
+ console.log('');
664
+ // Wait for any keypress โ€” clean, non-blocking
665
+ await new Promise(resolve => {
666
+ if (process.stdin.isTTY) {
667
+ process.stdin.setRawMode(true);
668
+ process.stdin.resume();
669
+ process.stdin.once('data', () => {
670
+ process.stdin.setRawMode(false);
671
+ process.stdin.pause();
672
+ resolve();
673
+ });
674
+ }
675
+ else {
676
+ // Non-TTY (piped input) โ€” don't block
677
+ resolve();
678
+ }
679
+ });
680
+ // Mark as shown for today
681
+ fs.writeFileSync(markerPath, today);
682
+ dlog(`๐ŸŒ™ Wow-factor morning dreams shown for ${today}`);
683
+ }
684
+ catch (err) {
685
+ dlog(`Morning dreams fetch failed (silent): ${err.message}`);
686
+ }
687
+ }
711
688
  /**
712
689
  * Emergency capture before clear - saves current state to ekkOS
713
690
  */
@@ -836,10 +813,12 @@ function writeSessionFiles(sessionId, sessionName) {
836
813
  function launchWithDashboard(options) {
837
814
  const tmuxSession = `ekkos-${Date.now().toString(36)}`;
838
815
  const launchTime = Date.now();
816
+ // Pre-generate session name so dashboard can start immediately (no polling).
817
+ // Uses the user-supplied name if provided, otherwise mints a fresh one.
818
+ const sessionName = options.session || (0, state_1.uuidToWords)(crypto.randomUUID());
839
819
  // Build the ekkos run command WITHOUT --dashboard (prevent recursion)
840
- const runArgs = ['run'];
841
- if (options.session)
842
- runArgs.push('-s', options.session);
820
+ // Always pass -s so run reuses the same session name we gave the dashboard
821
+ const runArgs = ['run', '-s', sessionName];
843
822
  if (options.bypass)
844
823
  runArgs.push('-b');
845
824
  if (options.verbose)
@@ -850,23 +829,17 @@ function launchWithDashboard(options) {
850
829
  runArgs.push('-r');
851
830
  if (options.noInject)
852
831
  runArgs.push('--skip-inject');
853
- if (options.noDna)
854
- runArgs.push('--skip-dna');
855
832
  if (options.noProxy)
856
833
  runArgs.push('--skip-proxy');
857
834
  const ekkosCmd = process.argv[1]; // Path to ekkos CLI
858
835
  const cwd = process.cwd();
859
836
  const termCols = process.stdout.columns ?? 160;
860
837
  const termRows = process.stdout.rows ?? 48;
861
- // Write a marker file with launch timestamp + CWD so dashboard knows to wait for NEW session
862
- const markerPath = path.join(state_1.EKKOS_DIR, '.dashboard-launch-ts');
863
- try {
864
- fs.writeFileSync(markerPath, `${launchTime}\n${cwd}`);
865
- }
866
- catch { }
867
- const runCommand = `node "${ekkosCmd}" ${runArgs.join(' ')}`;
868
- // Use --wait-for-new flag to wait for a session that started AFTER this launch
869
- const dashCommand = `node "${ekkosCmd}" dashboard --wait-for-new --refresh 2000`;
838
+ // Write session hint immediately so dashboard can find JSONL as it appears
839
+ writeSessionFiles(crypto.randomUUID(), sessionName);
840
+ const runCommand = `EKKOS_NO_SPLASH=1 node "${ekkosCmd}" ${runArgs.join(' ')}`;
841
+ // Pass session name directly โ€” dashboard starts rendering immediately (lazy JSONL resolution)
842
+ const dashCommand = `node "${ekkosCmd}" dashboard "${sessionName}" --refresh 2000`;
870
843
  try {
871
844
  // Pane 0 (left): start with inert command (no interactive shell startup noise).
872
845
  // Claude is launched AFTER split so Ink gets final pane geometry at startup.
@@ -881,8 +854,15 @@ function launchWithDashboard(options) {
881
854
  }
882
855
  }
883
856
  };
857
+ // Terminal type and escape sequence passthrough for Ink TUI rendering
858
+ applyTmuxOpt(`set-option -t "${tmuxSession}" default-terminal "xterm-256color"`);
859
+ applyTmuxOpt(`set-option -sa -t "${tmuxSession}" terminal-overrides ",xterm-256color:Tc:smcup@:rmcup@"`);
884
860
  // Session/window isolation and quality-of-life settings
885
861
  applyTmuxOpt(`set-option -t "${tmuxSession}" mouse on`);
862
+ // Auto-cleanup on disconnect without breaking startup:
863
+ // kill this tmux session only when the last client detaches.
864
+ const detachCleanupHook = `if-shell -F '#{==:#{session_attached},0}' 'kill-session -t ${tmuxSession}'`;
865
+ applyTmuxOpt(`set-hook -t "${tmuxSession}" client-detached "${detachCleanupHook}"`);
886
866
  applyTmuxOpt(`set-window-option -t "${tmuxSession}" history-limit 100000`);
887
867
  applyTmuxOpt(`set-window-option -t "${tmuxSession}" mode-keys vi`);
888
868
  applyTmuxOpt(`set-window-option -t "${tmuxSession}:claude" synchronize-panes off`);
@@ -917,18 +897,13 @@ function launchWithDashboard(options) {
917
897
  */
918
898
  function launchWithWindowsTerminal(options) {
919
899
  const cwd = process.cwd();
920
- const launchTime = Date.now();
921
- // Write marker file so dashboard knows to wait for a NEW session
922
- const markerPath = path.join(state_1.EKKOS_DIR, '.dashboard-launch-ts');
923
- try {
924
- fs.writeFileSync(markerPath, `${launchTime}\n${cwd}`);
925
- }
926
- catch { }
900
+ // Pre-generate session name so dashboard can start immediately
901
+ const sessionName = options.session || (0, state_1.uuidToWords)(crypto.randomUUID());
902
+ // Write session hint immediately
903
+ writeSessionFiles(crypto.randomUUID(), sessionName);
927
904
  // Build ekkos run args WITHOUT --dashboard (prevent recursion)
928
- // Uses `ekkos` directly โ€” it's in PATH since user ran `ekkos --dashboard`
929
- const runArgs = ['run'];
930
- if (options.session)
931
- runArgs.push('-s', options.session);
905
+ // Always pass -s so run reuses the same session name we gave the dashboard
906
+ const runArgs = ['run', '-s', sessionName];
932
907
  if (options.bypass)
933
908
  runArgs.push('-b');
934
909
  if (options.verbose)
@@ -939,16 +914,14 @@ function launchWithWindowsTerminal(options) {
939
914
  runArgs.push('-r');
940
915
  if (options.noInject)
941
916
  runArgs.push('--skip-inject');
942
- if (options.noDna)
943
- runArgs.push('--skip-dna');
944
917
  if (options.noProxy)
945
918
  runArgs.push('--skip-proxy');
946
919
  // Write a temp batch file to avoid all quoting issues
947
920
  // wt.exe doesn't resolve PATH for npm global bins โ€” must use `cmd /c`
948
- const batPath = path.join(os.tmpdir(), `ekkos-wt-${launchTime}.cmd`);
921
+ const batPath = path.join(os.tmpdir(), `ekkos-wt-${Date.now()}.cmd`);
949
922
  const batContent = [
950
923
  '@echo off',
951
- `wt --title "Claude Code" -d "${cwd}" cmd /c ekkos ${runArgs.join(' ')} ; split-pane -V -s 0.4 --title "ekkOS Dashboard" -d "${cwd}" cmd /c ekkos dashboard --wait-for-new --refresh 2000`,
924
+ `wt --title "Claude Code" -d "${cwd}" cmd /c ekkos ${runArgs.join(' ')} ; split-pane -V -s 0.4 --title "ekkOS Dashboard" -d "${cwd}" cmd /c ekkos dashboard "${sessionName}" --refresh 2000`,
952
925
  ].join('\r\n');
953
926
  try {
954
927
  fs.writeFileSync(batPath, batContent);
@@ -990,6 +963,13 @@ async function run(options) {
990
963
  const verbose = options.verbose || false;
991
964
  const bypass = options.bypass || false;
992
965
  const noInject = options.noInject || false;
966
+ // Honour -s flag: lock the session name before getEkkosEnv() can mint a new one.
967
+ // This is critical for --dashboard mode where launchWithDashboard() pre-generates
968
+ // a session name and passes it via -s so the dashboard and run command agree.
969
+ if (options.session && !cliSessionName) {
970
+ cliSessionName = options.session;
971
+ cliSessionId = crypto.randomUUID(); // pair an ID with it
972
+ }
993
973
  // Set proxy mode based on options (used by getEkkosEnv)
994
974
  proxyModeEnabled = !(options.noProxy || false);
995
975
  if (proxyModeEnabled) {
@@ -1063,43 +1043,9 @@ async function run(options) {
1063
1043
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1064
1044
  (0, state_1.ensureEkkosDir)();
1065
1045
  (0, state_1.clearAutoClearFlag)();
1066
- // Resolve Claude path FIRST so ccDNA patches the RIGHT installation
1046
+ // Resolve Claude path
1067
1047
  const rawClaudePath = resolveClaudePath();
1068
1048
  const isNpxMode = rawClaudePath.startsWith('npx:');
1069
- // Get the actual CLI path for ccDNA to patch
1070
- // CRITICAL: ONLY patch the ekkOS-managed installation, NEVER touch Homebrew/global!
1071
- let claudeCliPath;
1072
- // Always target the ekkOS-managed installation for patching
1073
- // Even if we're running from Homebrew, we only patch our own installation
1074
- if (fs.existsSync(EKKOS_CLAUDE_BIN)) {
1075
- try {
1076
- const realPath = fs.realpathSync(EKKOS_CLAUDE_BIN);
1077
- if (realPath.endsWith('.js') && fs.existsSync(realPath)) {
1078
- claudeCliPath = realPath;
1079
- }
1080
- }
1081
- catch {
1082
- // Ignore - will use default detection
1083
- }
1084
- }
1085
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1086
- // ccDNA AUTO-PATCH: Apply Claude Code customizations before spawn
1087
- // This patches the context warning, themes, and other ccDNA features
1088
- // Skip if --no-dna flag is set
1089
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1090
- const noDna = options.noDna || false;
1091
- let ccdnaVersion = null;
1092
- if (noDna) {
1093
- if (verbose) {
1094
- console.log(chalk_1.default.yellow(' โญ๏ธ Skipping ccDNA injection (--no-dna)'));
1095
- }
1096
- }
1097
- else {
1098
- if (verbose && claudeCliPath) {
1099
- console.log(chalk_1.default.gray(` ๐Ÿ”ง Patching: ${claudeCliPath}`));
1100
- }
1101
- ccdnaVersion = applyCcdnaPatches(verbose, claudeCliPath);
1102
- }
1103
1049
  const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
1104
1050
  const claudePath = isNpxMode ? 'npx' : rawClaudePath;
1105
1051
  // Build args early
@@ -1169,7 +1115,7 @@ async function run(options) {
1169
1115
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1170
1116
  // STARTUP BANNER WITH COLOR PULSE ANIMATION
1171
1117
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1172
- const skipFancyIntro = process.env.EKKOS_REMOTE_SESSION === '1' || process.env.EKKOS_NO_SPLASH === '1';
1118
+ const skipFancyIntro = process.env.EKKOS_REMOTE_SESSION === '1' || process.env.EKKOS_NO_SPLASH === '1' || !!process.env.TMUX;
1173
1119
  if (!skipFancyIntro) {
1174
1120
  const logoLines = [
1175
1121
  ' โ–„โ–„โ–„โ–„โ–„ โ–„โ–„โ–„โ–„โ–„โ–„โ–„ โ–„โ–„โ–„ โ–„โ–„ โ–„โ–„',
@@ -1345,9 +1291,6 @@ async function run(options) {
1345
1291
  if (bypass) {
1346
1292
  console.log(chalk_1.default.yellow(' โšก Bypass permissions mode enabled'));
1347
1293
  }
1348
- if (noDna) {
1349
- console.log(chalk_1.default.yellow(' โญ๏ธ ccDNA injection skipped (--no-dna)'));
1350
- }
1351
1294
  if (verbose) {
1352
1295
  console.log(chalk_1.default.gray(` ๐Ÿ“ Debug log: ${config.debugLogPath}`));
1353
1296
  console.log(chalk_1.default.gray(` โฑ Timing: clear=${config.clearWaitMs}ms, idleMax=${config.maxIdleWaitMs}ms (~${Math.round((config.clearWaitMs + config.maxIdleWaitMs * 2 + 1700) / 1000)}s total)`));
@@ -1362,18 +1305,21 @@ async function run(options) {
1362
1305
  if (bypass) {
1363
1306
  console.log(chalk_1.default.yellow(' โšก Bypass permissions mode enabled'));
1364
1307
  }
1365
- if (noDna) {
1366
- console.log(chalk_1.default.yellow(' โญ๏ธ ccDNA injection skipped (--no-dna)'));
1367
- }
1368
1308
  if (verbose) {
1369
1309
  console.log(chalk_1.default.gray(` ๐Ÿ“ Debug log: ${config.debugLogPath}`));
1370
1310
  }
1371
1311
  console.log('');
1372
1312
  }
1373
1313
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1314
+ // MAGIC MOMENT: Morning dreams right after sparkle, before Claude appears
1315
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1316
+ await showMorningDreamsIfNeeded();
1317
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1374
1318
  // ANIMATION COMPLETE: Mark ready and flush buffered Claude output
1375
1319
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1376
1320
  animationComplete = true;
1321
+ // Clear terminal to prevent startup banner artifacts bleeding into Claude Code's Ink TUI
1322
+ process.stdout.write('\x1B[2J\x1B[H');
1377
1323
  dlog(`Animation complete. shellReady=${shellReady}, buffered=${bufferedOutput.length} chunks`);
1378
1324
  // Show loading indicator if Claude is still initializing
1379
1325
  if (shellReady && bufferedOutput.length === 0) {
@@ -1401,9 +1347,10 @@ async function run(options) {
1401
1347
  // MULTI-SESSION SUPPORT: Register this process as an active session
1402
1348
  // This prevents state collision when multiple Claude Code instances run
1403
1349
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1404
- (0, state_1.registerActiveSession)('pending', // Session ID not yet known
1405
- currentSession || 'initializing', process.cwd());
1406
- writeSessionFiles(cliSessionId || 'pending', currentSession || 'initializing');
1350
+ const initialSessionId = cliSessionId || 'pending';
1351
+ const initialSessionName = currentSession || 'initializing';
1352
+ (0, state_1.registerActiveSession)(initialSessionId, initialSessionName, process.cwd());
1353
+ writeSessionFiles(initialSessionId, initialSessionName);
1407
1354
  dlog(`Registered active session (PID ${process.pid})`);
1408
1355
  // Show active sessions count if verbose
1409
1356
  if (verbose) {
@@ -1419,9 +1366,9 @@ async function run(options) {
1419
1366
  // PER-TURN BANNER STATE
1420
1367
  // Tracks idleโ†’active transitions to print the session banner once per turn
1421
1368
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1422
- let wasIdle = true; // Start as idle (no prompt submitted yet)
1423
- let _turnCount = 0; // Incremented each time a new turn banner prints (unused beyond banner)
1424
- let lastBannerTime = 0; // Epoch ms of last banner print (debounce guard)
1369
+ let wasIdle = false; // Start as NOT idle โ€” first idleโ†’active fires after startup
1370
+ let turnCount = 0; // Incremented each time a new turn banner prints
1371
+ let lastBannerTime = Date.now(); // Grace period so startup output doesn't trigger banner
1425
1372
  // Stream tailer for mid-turn context capture (must be declared before polling code)
1426
1373
  let streamTailer = null;
1427
1374
  const streamCacheDir = path.join(os.homedir(), '.ekkos', 'cache', 'sessions', instanceId);
@@ -1614,7 +1561,7 @@ async function run(options) {
1614
1561
  // JSONL eviction tracking - prevent rapid re-eviction
1615
1562
  let lastEvictionTime = 0;
1616
1563
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1617
- // ORPHAN TOOL_RESULT RECOVERY - React to ccDNA validate mode markers
1564
+ // ORPHAN TOOL_RESULT RECOVERY
1618
1565
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1619
1566
  let lastOrphanDetectionTime = 0;
1620
1567
  let isOrphanRecoveryInProgress = false;
@@ -1854,11 +1801,9 @@ async function run(options) {
1854
1801
  console.log(chalk_1.default.yellow(' Monitor-only mode (--no-inject)'));
1855
1802
  }
1856
1803
  if (verbose) {
1857
- // Show Claude version with ccDNA version if patched
1804
+ // Show Claude version
1858
1805
  const ccVersion = pinnedVersion || PINNED_CLAUDE_VERSION;
1859
- const versionStr = ccdnaVersion
1860
- ? `Claude Code v${ccVersion} + ekkOS_Continuum v${ccdnaVersion}`
1861
- : `Claude Code v${ccVersion}`;
1806
+ const versionStr = `Claude Code v${ccVersion}`;
1862
1807
  console.log(chalk_1.default.gray(` ๐Ÿค– ${versionStr}`));
1863
1808
  if (currentSession) {
1864
1809
  console.log(chalk_1.default.green(` ๐Ÿ“ Session: ${currentSession}`));
@@ -1866,6 +1811,7 @@ async function run(options) {
1866
1811
  console.log('');
1867
1812
  }
1868
1813
  let shell;
1814
+ const sessionStartTime = Date.now();
1869
1815
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1870
1816
  // USE EARLY-SPAWNED SHELL OR CREATE NEW ONE
1871
1817
  // If we spawned Claude during animation, reuse it. Otherwise spawn now.
@@ -1941,7 +1887,7 @@ async function run(options) {
1941
1887
  }
1942
1888
  else {
1943
1889
  // PTY not available - use spawn with stdio inherit (clean pass-through)
1944
- // This mode doesn't support auto-continue but provides full Claude Code experience
1890
+ // PTY not available โ€” spawn with stdio inherit (clean pass-through)
1945
1891
  dlog('PTY not available, using spawn pass-through mode');
1946
1892
  const spawnedProcess = (0, child_process_1.spawn)(claudePath, args, {
1947
1893
  stdio: 'inherit',
@@ -2123,13 +2069,13 @@ async function run(options) {
2123
2069
  }
2124
2070
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
2125
2071
  // ORPHAN TOOL_RESULT RECOVERY
2126
- // When ccDNA validate mode detects orphan tool_results before an API call,
2072
+ // When orphan tool_results are detected in the terminal output,
2127
2073
  // it emits [ekkOS] ORPHAN_TOOL_RESULT to the terminal. We detect this marker
2128
2074
  // and repair the transcript (rollback or surgical).
2129
2075
  //
2130
2076
  // NOTE: We do NOT run /clear + /continue anymore. That was leftover from when
2131
- // ccDNA was in evict mode (filtering in-memory messages). With ccDNA in validate
2132
- // mode and the JSONL rewriter as the single disk authority, orphans indicate a
2077
+ // NOTE: We do NOT run /clear + /continue anymore. The JSONL rewriter
2078
+ // is the single disk authority; orphans indicate a bug, not a state desync.
2133
2079
  // BUG in the sliding window - not a state desync that needs rebuilding.
2134
2080
  // The in-memory state is fine; we just fix the disk and log the bug.
2135
2081
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -2172,8 +2118,8 @@ async function run(options) {
2172
2118
  diagnosis: 'JSONL rewriter evicted tool_use without its tool_result, or vice versa',
2173
2119
  action: 'Attempting disk repair via rollback or surgical removal',
2174
2120
  });
2175
- console.log(`\n[ekkOS] ๐Ÿšจ BUG: Orphan tool_result detected (${orphan.tool_use_id})`);
2176
- console.log(`[ekkOS] ๐Ÿ”ง Repairing disk transcript...`);
2121
+ dlog(`๐Ÿšจ BUG: Orphan tool_result detected (${orphan.tool_use_id})`);
2122
+ dlog(`๐Ÿ”ง Repairing disk transcript...`);
2177
2123
  try {
2178
2124
  // Repair the disk transcript if we have one
2179
2125
  if (transcriptPath && validateTranscriptPath(transcriptPath)) {
@@ -2194,17 +2140,17 @@ async function run(options) {
2194
2140
  });
2195
2141
  dlog(`Orphan repair: ${repair.action} (orphans=${repair.orphansFound}, removed=${repair.removedLines ?? 0})`);
2196
2142
  if (repair.action === 'failed') {
2197
- console.log(`[ekkOS] โŒ WARNING: Orphan repair failed - session may be unstable`);
2198
- console.log(`[ekkOS] Reason: ${repair.reason}`);
2143
+ dlog(`โŒ WARNING: Orphan repair failed - session may be unstable`);
2144
+ dlog(` Reason: ${repair.reason}`);
2199
2145
  }
2200
2146
  else if (repair.action === 'rollback') {
2201
- console.log(`[ekkOS] โœ… Disk repaired via ROLLBACK to backup`);
2147
+ dlog(`โœ… Disk repaired via ROLLBACK to backup`);
2202
2148
  }
2203
2149
  else if (repair.action === 'surgical_repair') {
2204
- console.log(`[ekkOS] โœ… Disk repaired via SURGICAL removal (${repair.removedLines} lines removed)`);
2150
+ dlog(`โœ… Disk repaired via SURGICAL removal (${repair.removedLines} lines removed)`);
2205
2151
  }
2206
2152
  else {
2207
- console.log(`[ekkOS] โœ… No repair needed - transcript is healthy`);
2153
+ dlog(`โœ… No repair needed - transcript is healthy`);
2208
2154
  }
2209
2155
  // POST-REPAIR VALIDATION: Verify repair actually worked
2210
2156
  if (repair.action !== 'failed' && repair.action !== 'none') {
@@ -2218,8 +2164,8 @@ async function run(options) {
2218
2164
  postRepairOrphans,
2219
2165
  alert: 'REPAIR DID NOT FIX THE PROBLEM',
2220
2166
  });
2221
- console.log(`[ekkOS] โš ๏ธ Post-repair check: ${postRepairOrphans} orphan(s) still present!`);
2222
- console.log(`[ekkOS] Repair may have been incomplete - consider /clear + /continue`);
2167
+ dlog(`โš ๏ธ Post-repair check: ${postRepairOrphans} orphan(s) still present!`);
2168
+ dlog(` Repair may have been incomplete - consider /clear + /continue`);
2223
2169
  }
2224
2170
  else {
2225
2171
  evictionDebugLog('POST_REPAIR_VALIDATION_SUCCESS', 'โœ… Repair verified - no orphans remaining', {
@@ -2269,7 +2215,7 @@ async function run(options) {
2269
2215
  continue;
2270
2216
  }
2271
2217
  dlog(`Detected ORPHAN_TOOL_RESULT: ${orphan.tool_use_id}`);
2272
- evictionDebugLog('ORPHAN_MARKER_DETECTED', 'ccDNA reported orphan in PTY output', {
2218
+ evictionDebugLog('ORPHAN_MARKER_DETECTED', 'Orphan detected in PTY output', {
2273
2219
  orphan,
2274
2220
  bufferLen: orphanDetectionBuffer.length,
2275
2221
  scanCursor: orphanScanCursor,
@@ -2364,22 +2310,12 @@ async function run(options) {
2364
2310
  !trimmed.startsWith('โฏ') &&
2365
2311
  !trimmed.startsWith('%'); // zsh prompt artefact
2366
2312
  if (isSubstantialOutput && Date.now() - lastBannerTime > 3000) {
2367
- _turnCount++;
2368
- const now = new Date();
2369
- const timeStr = now.toLocaleTimeString('en-US', {
2370
- hour: 'numeric',
2371
- minute: '2-digit',
2372
- hour12: true,
2373
- timeZone: 'America/New_York',
2374
- });
2375
- const dateStr = now.toLocaleDateString('en-US', {
2376
- year: 'numeric',
2377
- month: '2-digit',
2378
- day: '2-digit',
2379
- timeZone: 'America/New_York',
2380
- });
2313
+ turnCount++;
2381
2314
  const sessionLabel = currentSession || cliSessionName || 'initializing';
2382
- process.stderr.write(`${chalk_1.default.cyan.bold('๐Ÿง  ekkOS Memory')} ${chalk_1.default.dim(`| ${sessionLabel} | ${dateStr} ${timeStr} EST`)}\n`);
2315
+ // LOG ONLY โ€” never write to stdout/stderr during Ink's render cycle.
2316
+ // stderr shares the same terminal as stdout; injecting text here
2317
+ // corrupts Ink's cursor positioning and causes TUI rendering artifacts.
2318
+ dlog(`[TURN ${turnCount}] ${sessionLabel}`);
2383
2319
  lastBannerTime = Date.now();
2384
2320
  wasIdle = false;
2385
2321
  }
@@ -2391,11 +2327,10 @@ async function run(options) {
2391
2327
  }
2392
2328
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
2393
2329
  // ORPHAN TOOL_RESULT DETECTION (LOCAL MODE ONLY)
2394
- // ccDNA validate mode emits [ekkOS] ORPHAN_TOOL_RESULT when it detects
2330
+ // [ekkOS] ORPHAN_TOOL_RESULT markers are emitted by the ekkOS memory system when it detects
2395
2331
  // tool_results without matching tool_uses. This triggers automatic repair.
2396
2332
  // DISABLED in proxy mode: proxy is sole eviction authority, CLI must not
2397
- // touch the local JSONL. Also requires ccDNA which is currently disabled.
2398
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
2333
+ // touch the local JSONL.
2399
2334
  if (!proxyModeEnabled && !isAutoClearInProgress && !isOrphanRecoveryInProgress) {
2400
2335
  orphanDetectionBuffer += stripAnsi(data);
2401
2336
  if (orphanDetectionBuffer.length > 10000) {
@@ -2685,19 +2620,109 @@ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
2685
2620
  }
2686
2621
  }, 4000); // Wait 4 seconds for Claude to fully initialize
2687
2622
  }
2623
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
2624
+ // SESSION-END VALUE REPORT
2625
+ // Prints a boxed summary of session stats before exit.
2626
+ // Fetches savings data from ekkOS API with a 2s timeout โ€” skips silently on failure.
2627
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
2628
+ async function printSessionEndReport() {
2629
+ try {
2630
+ const sessionName = currentSession || cliSessionName || 'unknown';
2631
+ // Compute duration
2632
+ const elapsedMs = Date.now() - sessionStartTime;
2633
+ const totalSec = Math.floor(elapsedMs / 1000);
2634
+ const hours = Math.floor(totalSec / 3600);
2635
+ const minutes = Math.floor((totalSec % 3600) / 60);
2636
+ const seconds = totalSec % 60;
2637
+ let duration;
2638
+ if (hours > 0) {
2639
+ duration = `${hours}h ${minutes}m ${seconds}s`;
2640
+ }
2641
+ else if (minutes > 0) {
2642
+ duration = `${minutes}m ${seconds}s`;
2643
+ }
2644
+ else {
2645
+ duration = `${seconds}s`;
2646
+ }
2647
+ // Fetch savings stats from ekkOS API (2-second timeout)
2648
+ let patternsRecalled = 0;
2649
+ let tokensSaved = 0;
2650
+ let costSaved = '0.00';
2651
+ try {
2652
+ const mcpUrl = process.env.EKKOS_MCP_URL || MEMORY_API_URL;
2653
+ const authToken = (0, state_1.getAuthToken)();
2654
+ if (authToken) {
2655
+ const controller = new AbortController();
2656
+ const timeout = setTimeout(() => controller.abort(), 2000);
2657
+ const res = await fetch(`${mcpUrl}/api/v1/savings/me?period=day`, {
2658
+ method: 'GET',
2659
+ headers: {
2660
+ 'Authorization': `Bearer ${authToken}`,
2661
+ 'Content-Type': 'application/json',
2662
+ },
2663
+ signal: controller.signal,
2664
+ });
2665
+ clearTimeout(timeout);
2666
+ if (res.ok) {
2667
+ const data = await res.json();
2668
+ patternsRecalled = data.patterns_recalled || 0;
2669
+ tokensSaved = data.tokens_saved || 0;
2670
+ costSaved = (data.cost_saved || 0).toFixed(2);
2671
+ }
2672
+ }
2673
+ }
2674
+ catch {
2675
+ // Silent fail โ€” API unavailable or timed out, just use defaults
2676
+ }
2677
+ // Format token count with commas
2678
+ const tokensFormatted = tokensSaved.toLocaleString('en-US');
2679
+ // Build the box content
2680
+ const title = `ekkOS_ Session ยท ${sessionName}`;
2681
+ const line1 = `Duration: ${duration} | Turns: ${turnCount}`;
2682
+ const line2 = `Patterns recalled: ${patternsRecalled}`;
2683
+ const line3 = `Tokens saved: ${tokensFormatted} (~$${costSaved})`;
2684
+ // Calculate box width (minimum 49, expand for long content)
2685
+ const contentLines = [title, line1, line2, line3];
2686
+ const maxContent = Math.max(...contentLines.map(l => l.length));
2687
+ const innerWidth = Math.max(47, maxContent + 4);
2688
+ const pad = (text) => {
2689
+ const padding = innerWidth - text.length - 2;
2690
+ return ` ${text}${' '.repeat(Math.max(0, padding))}`;
2691
+ };
2692
+ const top = `โ•ญ${'โ”€'.repeat(innerWidth)}โ•ฎ`;
2693
+ const sep = `โ”œ${'โ”€'.repeat(innerWidth)}โ”ค`;
2694
+ const bottom = `โ•ฐ${'โ”€'.repeat(innerWidth)}โ•ฏ`;
2695
+ const row = (text) => `โ”‚${pad(text)}โ”‚`;
2696
+ // Print the box
2697
+ console.log('');
2698
+ console.log(chalk_1.default.dim(top));
2699
+ console.log(chalk_1.default.dim('โ”‚') + chalk_1.default.bold(pad(title)) + chalk_1.default.dim('โ”‚'));
2700
+ console.log(chalk_1.default.dim(sep));
2701
+ console.log(chalk_1.default.dim('โ”‚') + pad(line1) + chalk_1.default.dim('โ”‚'));
2702
+ console.log(chalk_1.default.dim(row(line2)));
2703
+ console.log(chalk_1.default.dim('โ”‚') + chalk_1.default.green(pad(line3)) + chalk_1.default.dim('โ”‚'));
2704
+ console.log(chalk_1.default.dim(bottom));
2705
+ console.log('');
2706
+ }
2707
+ catch {
2708
+ // Never block exit โ€” silently skip the entire report on any error
2709
+ }
2710
+ }
2688
2711
  // Handle PTY exit
2689
- shell.onExit(({ exitCode }) => {
2712
+ shell.onExit(async ({ exitCode }) => {
2690
2713
  (0, state_1.clearAutoClearFlag)();
2691
2714
  stopStreamTailer(); // Stop stream capture
2692
2715
  (0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
2693
2716
  cleanupInstanceFile(instanceId); // Clean up instance file
2694
- // NOTE: No ccDNA restore needed - ekkOS uses separate installation from homebrew
2717
+ // NOTE: No restore needed - ekkOS uses separate installation from homebrew
2695
2718
  // ~/.ekkos/claude-code/ stays patched, homebrew `claude` is always vanilla
2696
2719
  // Restore terminal
2697
2720
  if (process.stdin.isTTY) {
2698
2721
  process.stdin.setRawMode(false);
2699
2722
  }
2700
2723
  process.stdin.pause();
2724
+ // Print session-end value report before exiting
2725
+ await printSessionEndReport();
2701
2726
  // Log exit to file (not terminal - TUI already gone at this point)
2702
2727
  dlog(`Claude exited with code ${exitCode}`);
2703
2728
  process.exit(exitCode);
@@ -2708,7 +2733,7 @@ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
2708
2733
  stopStreamTailer(); // Stop stream capture
2709
2734
  (0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
2710
2735
  cleanupInstanceFile(instanceId); // Clean up instance file
2711
- // NOTE: No ccDNA restore needed - ekkOS uses separate installation from homebrew
2736
+ // NOTE: No restore needed - ekkOS uses separate installation from homebrew
2712
2737
  if (process.stdin.isTTY) {
2713
2738
  process.stdin.setRawMode(false);
2714
2739
  }