@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.
- package/dist/capture/jsonl-rewriter.d.ts +1 -1
- package/dist/capture/jsonl-rewriter.js +3 -3
- package/dist/capture/transcript-repair.d.ts +2 -2
- package/dist/capture/transcript-repair.js +2 -2
- package/dist/commands/claw.d.ts +13 -0
- package/dist/commands/claw.js +253 -0
- package/dist/commands/dashboard.js +742 -118
- package/dist/commands/doctor.d.ts +3 -3
- package/dist/commands/doctor.js +6 -79
- package/dist/commands/gemini.d.ts +19 -0
- package/dist/commands/gemini.js +193 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +56 -41
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +288 -263
- package/dist/commands/scan.d.ts +21 -0
- package/dist/commands/scan.js +386 -0
- package/dist/commands/status.d.ts +4 -1
- package/dist/commands/status.js +165 -27
- package/dist/commands/swarm-dashboard.js +156 -28
- package/dist/commands/swarm.d.ts +1 -1
- package/dist/commands/swarm.js +1 -1
- package/dist/commands/test-claude.d.ts +2 -2
- package/dist/commands/test-claude.js +3 -3
- package/dist/deploy/index.d.ts +0 -2
- package/dist/deploy/index.js +0 -2
- package/dist/deploy/settings.d.ts +6 -5
- package/dist/deploy/settings.js +64 -16
- package/dist/deploy/skills.js +1 -2
- package/dist/index.js +86 -96
- package/dist/lib/usage-parser.d.ts +1 -1
- package/dist/lib/usage-parser.js +9 -6
- package/dist/local/index.d.ts +14 -0
- package/dist/local/index.js +28 -0
- package/dist/local/local-embeddings.d.ts +49 -0
- package/dist/local/local-embeddings.js +232 -0
- package/dist/local/offline-fallback.d.ts +44 -0
- package/dist/local/offline-fallback.js +159 -0
- package/dist/local/sqlite-store.d.ts +126 -0
- package/dist/local/sqlite-store.js +393 -0
- package/dist/local/sync-engine.d.ts +42 -0
- package/dist/local/sync-engine.js +223 -0
- package/dist/utils/platform.d.ts +5 -1
- package/dist/utils/platform.js +24 -4
- package/dist/utils/proxy-url.d.ts +21 -0
- package/dist/utils/proxy-url.js +34 -0
- package/dist/utils/state.d.ts +1 -1
- package/dist/utils/state.js +11 -3
- package/dist/utils/templates.js +1 -1
- package/package.json +11 -4
- package/templates/CLAUDE.md +49 -107
- package/dist/agent/daemon.d.ts +0 -130
- package/dist/agent/daemon.js +0 -606
- package/dist/agent/health-check.d.ts +0 -35
- package/dist/agent/health-check.js +0 -243
- package/dist/agent/pty-runner.d.ts +0 -53
- package/dist/agent/pty-runner.js +0 -190
- package/dist/commands/agent.d.ts +0 -50
- package/dist/commands/agent.js +0 -544
- package/dist/commands/setup-remote.d.ts +0 -20
- package/dist/commands/setup-remote.js +0 -582
- package/dist/utils/verify-remote-terminal.d.ts +0 -10
- package/dist/utils/verify-remote-terminal.js +0 -415
- package/templates/README.md +0 -378
- package/templates/claude-plugins/PHASE2_COMPLETION.md +0 -346
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +0 -1776
- package/templates/claude-plugins/README.md +0 -587
- package/templates/claude-plugins/agents/code-reviewer.json +0 -14
- package/templates/claude-plugins/agents/debug-detective.json +0 -15
- package/templates/claude-plugins/agents/git-companion.json +0 -14
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/blog-manager/commands/blog.md +0 -691
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +0 -434
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +0 -282
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +0 -181
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/pattern-coach/commands/forge.md +0 -365
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +0 -582
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +0 -819
- package/templates/claude-plugins-admin/README.md +0 -446
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +0 -595
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +0 -798
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +0 -554
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +0 -881
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +0 -85
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +0 -569
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +0 -863
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +0 -732
- package/templates/commands/continue.md +0 -47
- package/templates/cursor-rules/ekkos-memory.md +0 -127
- package/templates/ekkos-manifest.json +0 -223
- package/templates/helpers/json-parse.cjs +0 -101
- package/templates/hooks-node/lib/state.js +0 -187
- package/templates/hooks-node/stop.js +0 -416
- package/templates/hooks-node/user-prompt-submit.js +0 -337
- package/templates/plan-template.md +0 -306
- package/templates/rules/00-hooks-contract.mdc +0 -89
- package/templates/rules/30-ekkos-core.mdc +0 -188
- package/templates/rules/31-ekkos-messages.mdc +0 -78
- package/templates/shared/hooks-enabled.json +0 -22
- package/templates/shared/session-words.json +0 -45
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +0 -282
- package/templates/skills/ekkOS_Learn/Skill.md +0 -265
- package/templates/skills/ekkOS_Memory_First/Skill.md +0 -206
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +0 -302
- package/templates/skills/ekkOS_Preferences/Skill.md +0 -247
- package/templates/skills/ekkOS_Reflect/Skill.md +0 -257
- package/templates/skills/ekkOS_Safety/Skill.md +0 -265
- package/templates/skills/ekkOS_Schema/Skill.md +0 -251
- package/templates/skills/ekkOS_Summary/Skill.md +0 -257
- package/templates/spec-template.md +0 -159
- package/templates/windsurf-rules/ekkos-memory.md +0 -127
- package/templates/windsurf-skills/README.md +0 -58
- package/templates/windsurf-skills/ekkos-continue/SKILL.md +0 -81
- package/templates/windsurf-skills/ekkos-golden-loop/SKILL.md +0 -225
- package/templates/windsurf-skills/ekkos-insights/SKILL.md +0 -138
- package/templates/windsurf-skills/ekkos-recall/SKILL.md +0 -96
- package/templates/windsurf-skills/ekkos-safety/SKILL.md +0 -89
- package/templates/windsurf-skills/ekkos-vault/SKILL.md +0 -86
package/dist/commands/run.js
CHANGED
|
@@ -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', '
|
|
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
|
|
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', '
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
841
|
-
|
|
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
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
//
|
|
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-${
|
|
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
|
|
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
|
|
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
|
-
|
|
1405
|
-
currentSession || 'initializing'
|
|
1406
|
-
|
|
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 =
|
|
1423
|
-
let
|
|
1424
|
-
let lastBannerTime =
|
|
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
|
|
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
|
|
1804
|
+
// Show Claude version
|
|
1858
1805
|
const ccVersion = pinnedVersion || PINNED_CLAUDE_VERSION;
|
|
1859
|
-
const versionStr =
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
2132
|
-
//
|
|
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
|
-
|
|
2176
|
-
|
|
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
|
-
|
|
2198
|
-
|
|
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
|
-
|
|
2147
|
+
dlog(`โ
Disk repaired via ROLLBACK to backup`);
|
|
2202
2148
|
}
|
|
2203
2149
|
else if (repair.action === 'surgical_repair') {
|
|
2204
|
-
|
|
2150
|
+
dlog(`โ
Disk repaired via SURGICAL removal (${repair.removedLines} lines removed)`);
|
|
2205
2151
|
}
|
|
2206
2152
|
else {
|
|
2207
|
-
|
|
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
|
-
|
|
2222
|
-
|
|
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', '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
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
|
|
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
|
}
|