@ekkos/cli 0.3.3 → 1.0.1

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 (81) hide show
  1. package/README.md +57 -0
  2. package/dist/agent/daemon.d.ts +27 -0
  3. package/dist/agent/daemon.js +254 -29
  4. package/dist/agent/health-check.d.ts +35 -0
  5. package/dist/agent/health-check.js +243 -0
  6. package/dist/agent/pty-runner.d.ts +1 -0
  7. package/dist/agent/pty-runner.js +6 -1
  8. package/dist/capture/transcript-repair.d.ts +1 -0
  9. package/dist/capture/transcript-repair.js +12 -1
  10. package/dist/commands/agent.d.ts +6 -0
  11. package/dist/commands/agent.js +244 -0
  12. package/dist/commands/dashboard.d.ts +25 -0
  13. package/dist/commands/dashboard.js +1175 -0
  14. package/dist/commands/run.d.ts +3 -0
  15. package/dist/commands/run.js +503 -350
  16. package/dist/commands/setup-remote.js +146 -37
  17. package/dist/commands/swarm-dashboard.d.ts +20 -0
  18. package/dist/commands/swarm-dashboard.js +735 -0
  19. package/dist/commands/swarm-setup.d.ts +10 -0
  20. package/dist/commands/swarm-setup.js +956 -0
  21. package/dist/commands/swarm.d.ts +46 -0
  22. package/dist/commands/swarm.js +441 -0
  23. package/dist/commands/test-claude.d.ts +16 -0
  24. package/dist/commands/test-claude.js +156 -0
  25. package/dist/commands/usage/blocks.d.ts +8 -0
  26. package/dist/commands/usage/blocks.js +60 -0
  27. package/dist/commands/usage/daily.d.ts +9 -0
  28. package/dist/commands/usage/daily.js +96 -0
  29. package/dist/commands/usage/dashboard.d.ts +8 -0
  30. package/dist/commands/usage/dashboard.js +104 -0
  31. package/dist/commands/usage/formatters.d.ts +41 -0
  32. package/dist/commands/usage/formatters.js +147 -0
  33. package/dist/commands/usage/index.d.ts +13 -0
  34. package/dist/commands/usage/index.js +87 -0
  35. package/dist/commands/usage/monthly.d.ts +8 -0
  36. package/dist/commands/usage/monthly.js +66 -0
  37. package/dist/commands/usage/session.d.ts +11 -0
  38. package/dist/commands/usage/session.js +193 -0
  39. package/dist/commands/usage/weekly.d.ts +9 -0
  40. package/dist/commands/usage/weekly.js +61 -0
  41. package/dist/deploy/instructions.d.ts +5 -2
  42. package/dist/deploy/instructions.js +11 -8
  43. package/dist/index.js +256 -20
  44. package/dist/lib/tmux-scrollbar.d.ts +14 -0
  45. package/dist/lib/tmux-scrollbar.js +296 -0
  46. package/dist/lib/usage-parser.d.ts +95 -5
  47. package/dist/lib/usage-parser.js +416 -71
  48. package/dist/utils/log-rotate.d.ts +18 -0
  49. package/dist/utils/log-rotate.js +74 -0
  50. package/dist/utils/platform.d.ts +2 -0
  51. package/dist/utils/platform.js +3 -1
  52. package/dist/utils/session-binding.d.ts +5 -0
  53. package/dist/utils/session-binding.js +46 -0
  54. package/dist/utils/state.js +4 -0
  55. package/dist/utils/verify-remote-terminal.d.ts +10 -0
  56. package/dist/utils/verify-remote-terminal.js +415 -0
  57. package/package.json +16 -11
  58. package/templates/CLAUDE.md +135 -23
  59. package/templates/cursor-hooks/after-agent-response.sh +0 -0
  60. package/templates/cursor-hooks/before-submit-prompt.sh +0 -0
  61. package/templates/cursor-hooks/stop.sh +0 -0
  62. package/templates/ekkos-manifest.json +5 -5
  63. package/templates/hooks/assistant-response.sh +0 -0
  64. package/templates/hooks/lib/contract.sh +43 -31
  65. package/templates/hooks/lib/count-tokens.cjs +86 -0
  66. package/templates/hooks/lib/ekkos-reminders.sh +98 -0
  67. package/templates/hooks/lib/state.sh +53 -1
  68. package/templates/hooks/session-start.sh +0 -0
  69. package/templates/hooks/stop.sh +150 -388
  70. package/templates/hooks/user-prompt-submit.sh +353 -443
  71. package/templates/plan-template.md +0 -0
  72. package/templates/spec-template.md +0 -0
  73. package/templates/windsurf-hooks/README.md +212 -0
  74. package/templates/windsurf-hooks/hooks.json +9 -2
  75. package/templates/windsurf-hooks/install.sh +148 -0
  76. package/templates/windsurf-hooks/lib/contract.sh +2 -0
  77. package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
  78. package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
  79. package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
  80. package/LICENSE +0 -21
  81. package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sessionCommand = sessionCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const usage_parser_js_1 = require("../../lib/usage-parser.js");
9
+ const formatters_js_1 = require("./formatters.js");
10
+ /**
11
+ * ekkos usage session [id] [--list] [--instance id] [--json]
12
+ *
13
+ * Accepts ekkOS 3-word names (lit-lex-zip) or ccusage project paths.
14
+ */
15
+ async function sessionCommand(options) {
16
+ const instanceId = options.instance;
17
+ if (options.list) {
18
+ await listSessions(instanceId, options.json);
19
+ return;
20
+ }
21
+ if (!options.sessionId) {
22
+ await listSessions(instanceId, options.json);
23
+ return;
24
+ }
25
+ try {
26
+ let usage = null;
27
+ // Detect ekkOS 3-word session names and resolve via active-sessions.json
28
+ if ((0, usage_parser_js_1.isEkkosSessionName)(options.sessionId)) {
29
+ usage = await (0, usage_parser_js_1.getSessionUsageByName)(options.sessionId);
30
+ if (!usage) {
31
+ if (options.json) {
32
+ console.log(JSON.stringify({ error: `ekkOS session "${options.sessionId}" not found` }));
33
+ return;
34
+ }
35
+ console.log(chalk_1.default.yellow(` ekkOS session "${options.sessionId}" not found.`));
36
+ console.log(chalk_1.default.gray(' Check available sessions with: ekkos usage session -l'));
37
+ return;
38
+ }
39
+ }
40
+ else {
41
+ // Fallback to ccusage project-path lookup
42
+ usage = await (0, usage_parser_js_1.getSessionUsage)(options.sessionId, instanceId);
43
+ }
44
+ if (!usage) {
45
+ if (options.json) {
46
+ console.log(JSON.stringify({ error: 'Session not found' }));
47
+ return;
48
+ }
49
+ console.log(chalk_1.default.yellow(` No usage data found for session: ${options.sessionId}`));
50
+ return;
51
+ }
52
+ if (options.json) {
53
+ console.log(JSON.stringify(usage, null, 2));
54
+ return;
55
+ }
56
+ displaySessionUsage(usage);
57
+ }
58
+ catch (err) {
59
+ console.log(chalk_1.default.red(` Error: ${err.message}`));
60
+ }
61
+ }
62
+ /** Display session usage in formatted table */
63
+ function displaySessionUsage(usage) {
64
+ (0, formatters_js_1.titleBar)(`Session: ${usage.session_name}`, usage.session_id);
65
+ (0, formatters_js_1.sectionHeader)('Summary');
66
+ console.log(` ${chalk_1.default.bold('Session ID:')} ${usage.session_id}`);
67
+ console.log(` ${chalk_1.default.bold('Turn Count:')} ${chalk_1.default.green(usage.turn_count.toString())}`);
68
+ console.log(` ${chalk_1.default.bold('Total Tokens:')} ${(0, formatters_js_1.formatNumber)(usage.total_tokens)}`);
69
+ console.log(` ${chalk_1.default.bold('Avg Context:')} ${(0, formatters_js_1.formatPercentage)(usage.avg_context_percentage)}`);
70
+ console.log(` ${chalk_1.default.bold('Max Context:')} ${(0, formatters_js_1.formatPercentage)(usage.max_context_percentage)}`);
71
+ console.log(` ${chalk_1.default.bold('Started:')} ${(0, formatters_js_1.formatTimestamp)(usage.started_at)}`);
72
+ console.log(` ${chalk_1.default.bold('Last Activity:')} ${(0, formatters_js_1.formatTimestamp)(usage.last_activity)}`);
73
+ console.log();
74
+ (0, formatters_js_1.sectionHeader)('Token Breakdown');
75
+ console.log(` ${chalk_1.default.bold('Input Tokens:')} ${(0, formatters_js_1.formatNumber)(usage.total_input_tokens)}`);
76
+ console.log(` ${chalk_1.default.bold('Output Tokens:')} ${(0, formatters_js_1.formatNumber)(usage.total_output_tokens)}`);
77
+ console.log(` ${chalk_1.default.bold('Cache Read:')} ${(0, formatters_js_1.formatNumber)(usage.total_cache_read_tokens)} ${chalk_1.default.gray('(90% discount)')}`);
78
+ console.log(` ${chalk_1.default.bold('Cache Creation:')} ${(0, formatters_js_1.formatNumber)(usage.total_cache_creation_tokens)} ${chalk_1.default.gray('(25% premium)')}`);
79
+ console.log();
80
+ (0, formatters_js_1.sectionHeader)('Cost');
81
+ console.log(` ${chalk_1.default.bold('Total Cost:')} ${chalk_1.default.green((0, formatters_js_1.formatCost)(usage.total_cost))}`);
82
+ console.log(` ${chalk_1.default.bold('Models Used:')} ${chalk_1.default.cyan(usage.models_used.join(', '))}`);
83
+ console.log();
84
+ // ekkOS pattern metrics
85
+ if (usage.patterns_retrieved !== undefined) {
86
+ (0, formatters_js_1.sectionHeader)('ekkOS Pattern Metrics');
87
+ console.log(` ${chalk_1.default.bold('Patterns Retrieved:')} ${chalk_1.default.cyan(usage.patterns_retrieved.toString())}`);
88
+ console.log(` ${chalk_1.default.bold('Patterns Applied:')} ${chalk_1.default.green(usage.patterns_applied?.toString() || '0')}`);
89
+ console.log(` ${chalk_1.default.bold('Patterns Learned:')} ${chalk_1.default.yellow(usage.patterns_learned?.toString() || '0')}`);
90
+ console.log(` ${chalk_1.default.bold('Confidence Gain:')} ${chalk_1.default.magenta(`+${((usage.confidence_gain || 0) * 100).toFixed(1)}%`)}`);
91
+ console.log();
92
+ }
93
+ // Turn-by-turn breakdown
94
+ if (usage.turns.length > 0) {
95
+ (0, formatters_js_1.sectionHeader)('Turn-by-Turn Breakdown');
96
+ console.log(chalk_1.default.gray(' Turn │ Context % │ Input │ Output │ Cache Read │ Total'));
97
+ console.log(chalk_1.default.gray('─'.repeat(80)));
98
+ for (const turn of usage.turns) {
99
+ const turnStr = turn.turn_number.toString().padStart(4);
100
+ const contextStr = (0, formatters_js_1.formatPercentage)(turn.context_percentage).padStart(10);
101
+ const inputStr = (0, formatters_js_1.formatNumber)(turn.input_tokens).padStart(7);
102
+ const outputStr = (0, formatters_js_1.formatNumber)(turn.output_tokens).padStart(7);
103
+ const cacheStr = (0, formatters_js_1.formatNumber)(turn.cache_read_tokens).padStart(11);
104
+ const totalStr = (0, formatters_js_1.formatNumber)(turn.total_tokens).padStart(8);
105
+ const contextColor = (0, formatters_js_1.getContextColor)(turn.context_percentage);
106
+ console.log(` ${turnStr} │ ${contextColor(contextStr)} │ ${inputStr} │ ${outputStr} │ ${cacheStr} │ ${totalStr}`);
107
+ }
108
+ console.log();
109
+ }
110
+ // VM validation
111
+ if (usage.turn_count >= 5) {
112
+ const contextValues = usage.turns.map(t => t.context_percentage);
113
+ const range = Math.max(...contextValues) - Math.min(...contextValues);
114
+ if (range < 15) {
115
+ console.log(chalk_1.default.green.bold(' VM Working: Context staying in constant band'));
116
+ }
117
+ else {
118
+ console.log(chalk_1.default.yellow.bold(' VM Warning: Context growing linearly (check proxy eviction)'));
119
+ }
120
+ console.log();
121
+ }
122
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
123
+ console.log();
124
+ }
125
+ /** List available sessions - shows ekkOS sessions by default, falls back to ccusage */
126
+ async function listSessions(instanceId, json) {
127
+ // Try ekkOS sessions first (individual sessions with 3-word names)
128
+ const ekkosSessions = await (0, usage_parser_js_1.listEkkosSessions)(30);
129
+ if (ekkosSessions.length > 0) {
130
+ if (json) {
131
+ console.log(JSON.stringify(ekkosSessions, null, 2));
132
+ return;
133
+ }
134
+ (0, formatters_js_1.titleBar)('ekkOS Sessions', `${ekkosSessions.length} recent`);
135
+ // Find max cost for bar rendering
136
+ const maxCost = Math.max(...ekkosSessions.map(s => s.cost), 0.01);
137
+ console.log(chalk_1.default.gray(' Session Name │ Turns │ Cost │ Tokens │ Models │ Started'));
138
+ console.log(chalk_1.default.gray(' ' + '─'.repeat(100)));
139
+ for (const s of ekkosSessions) {
140
+ const name = chalk_1.default.bold.cyan(s.name.padEnd(20));
141
+ const turns = s.turnCount.toString().padStart(5);
142
+ const cost = chalk_1.default.green((0, formatters_js_1.formatCost)(s.cost).padStart(7));
143
+ const tokens = (0, formatters_js_1.formatCompact)(s.tokens).padStart(8);
144
+ const models = chalk_1.default.gray(s.models
145
+ .filter(m => !m.includes('synthetic'))
146
+ .map(m => {
147
+ if (m.includes('opus-4-6'))
148
+ return 'opus-4.6';
149
+ if (m.includes('opus-4-5'))
150
+ return 'opus-4.5';
151
+ if (m.includes('sonnet-4-5'))
152
+ return 'sonnet-4.5';
153
+ if (m.includes('haiku-4-5'))
154
+ return 'haiku-4.5';
155
+ return m;
156
+ }).join(', ').padEnd(24));
157
+ const started = chalk_1.default.gray((0, formatters_js_1.formatTimestamp)(s.startedAt));
158
+ const bar = (0, formatters_js_1.renderBar)(s.cost, maxCost, 8);
159
+ console.log(` ${name}│ ${turns} │ ${cost} │ ${tokens} │ ${models}│ ${started} ${bar}`);
160
+ }
161
+ console.log();
162
+ console.log(chalk_1.default.gray(' Use: ekkos usage session <name> for detailed breakdown'));
163
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
164
+ console.log();
165
+ return;
166
+ }
167
+ // Fallback to ccusage project-level sessions
168
+ const sessions = await (0, usage_parser_js_1.getAllSessions)(instanceId);
169
+ if (sessions.length === 0) {
170
+ if (json) {
171
+ console.log(JSON.stringify([]));
172
+ return;
173
+ }
174
+ (0, formatters_js_1.noData)('session');
175
+ return;
176
+ }
177
+ if (json) {
178
+ console.log(JSON.stringify(sessions, null, 2));
179
+ return;
180
+ }
181
+ (0, formatters_js_1.titleBar)('Sessions', `${sessions.length} found`);
182
+ const sorted = [...sessions].sort((a, b) => (b.last_activity || '').localeCompare(a.last_activity || ''));
183
+ for (const session of sorted) {
184
+ console.log(` ${chalk_1.default.bold(session.session_id)}`);
185
+ console.log(` ${chalk_1.default.gray('Cost:')} ${chalk_1.default.green((0, formatters_js_1.formatCost)(session.total_cost))}` +
186
+ ` ${chalk_1.default.gray('Tokens:')} ${(0, formatters_js_1.formatCompact)(session.total_tokens)}` +
187
+ ` ${chalk_1.default.gray('Last:')} ${session.last_activity}` +
188
+ ` ${chalk_1.default.gray('Models:')} ${chalk_1.default.cyan(session.models_used.join(', '))}`);
189
+ console.log();
190
+ }
191
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
192
+ console.log();
193
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * ekkos usage weekly [--weeks N] [--json]
3
+ *
4
+ * Show weekly usage breakdown
5
+ */
6
+ export declare function weeklyCommand(options: {
7
+ weeks?: number;
8
+ json?: boolean;
9
+ }): Promise<void>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.weeklyCommand = weeklyCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const usage_parser_js_1 = require("../../lib/usage-parser.js");
9
+ const formatters_js_1 = require("./formatters.js");
10
+ /**
11
+ * ekkos usage weekly [--weeks N] [--json]
12
+ *
13
+ * Show weekly usage breakdown
14
+ */
15
+ async function weeklyCommand(options) {
16
+ const weeks = options.weeks || 8;
17
+ const data = await (0, usage_parser_js_1.getWeeklyUsage)();
18
+ if (data.length === 0) {
19
+ if (options.json) {
20
+ console.log(JSON.stringify([]));
21
+ return;
22
+ }
23
+ (0, formatters_js_1.noData)('weekly usage');
24
+ return;
25
+ }
26
+ // Sort by week date descending
27
+ const sorted = [...data].sort((a, b) => (b.week || '').localeCompare(a.week || ''));
28
+ const recent = sorted.slice(0, weeks);
29
+ if (options.json) {
30
+ console.log(JSON.stringify(recent, null, 2));
31
+ return;
32
+ }
33
+ (0, formatters_js_1.titleBar)('Weekly Usage Report', `Last ${Math.min(weeks, recent.length)} weeks`);
34
+ const maxCost = Math.max(...recent.map(w => w.totalCost || 0));
35
+ let totalCost = 0;
36
+ console.log(chalk_1.default.gray(' Week of'.padEnd(22)) +
37
+ chalk_1.default.gray('│ ') +
38
+ chalk_1.default.gray('Cost'.padStart(10)) +
39
+ chalk_1.default.gray(' │ ') +
40
+ chalk_1.default.gray('Input'.padStart(8)) +
41
+ chalk_1.default.gray(' │ ') +
42
+ chalk_1.default.gray('Output'.padStart(8)) +
43
+ chalk_1.default.gray(' │ ') +
44
+ chalk_1.default.gray('Cache'.padStart(8)) +
45
+ chalk_1.default.gray(' │ ') +
46
+ chalk_1.default.gray('Chart'));
47
+ console.log(chalk_1.default.gray('─'.repeat(80)));
48
+ for (const week of recent) {
49
+ const cost = week.totalCost || 0;
50
+ totalCost += cost;
51
+ const weekDate = (0, formatters_js_1.formatDate)(week.week || '');
52
+ const bar = (0, formatters_js_1.renderBar)(cost, maxCost, 14);
53
+ console.log(` ${chalk_1.default.white(weekDate.padEnd(20))} │ ${chalk_1.default.green((0, formatters_js_1.formatCost)(cost).padStart(10))} │ ${(0, formatters_js_1.formatCompact)(week.inputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(week.outputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(week.cacheReadTokens || 0).padStart(8)} │ ${bar}`);
54
+ }
55
+ console.log(chalk_1.default.gray('─'.repeat(80)));
56
+ console.log(chalk_1.default.bold(' TOTAL'.padEnd(22)) +
57
+ `│ ${chalk_1.default.green.bold((0, formatters_js_1.formatCost)(totalCost).padStart(10))} │`);
58
+ console.log();
59
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
60
+ console.log();
61
+ }
@@ -1,9 +1,12 @@
1
1
  /**
2
- * Deploy CLAUDE.md to ~/.claude/CLAUDE.md
2
+ * Deploy ekkOS instructions to ~/.claude/rules/ekkos.md
3
+ *
4
+ * Uses Claude Code's user-level rules directory — auto-loaded for all projects
5
+ * without touching the user's existing CLAUDE.md.
3
6
  */
4
7
  export declare function deployInstructions(): void;
5
8
  /**
6
- * Check if CLAUDE.md is deployed
9
+ * Check if ekkOS instructions are deployed
7
10
  */
8
11
  export declare function isInstructionsDeployed(): boolean;
9
12
  /**
@@ -7,21 +7,24 @@ const fs_1 = require("fs");
7
7
  const platform_1 = require("../utils/platform");
8
8
  const templates_1 = require("../utils/templates");
9
9
  /**
10
- * Deploy CLAUDE.md to ~/.claude/CLAUDE.md
10
+ * Deploy ekkOS instructions to ~/.claude/rules/ekkos.md
11
+ *
12
+ * Uses Claude Code's user-level rules directory — auto-loaded for all projects
13
+ * without touching the user's existing CLAUDE.md.
11
14
  */
12
15
  function deployInstructions() {
13
- // Ensure .claude directory exists
14
- if (!(0, fs_1.existsSync)(platform_1.CLAUDE_DIR)) {
15
- (0, fs_1.mkdirSync)(platform_1.CLAUDE_DIR, { recursive: true });
16
+ // Ensure ~/.claude/rules/ exists
17
+ if (!(0, fs_1.existsSync)(platform_1.CLAUDE_RULES_DIR)) {
18
+ (0, fs_1.mkdirSync)(platform_1.CLAUDE_RULES_DIR, { recursive: true });
16
19
  }
17
- // Copy CLAUDE.md template
18
- (0, templates_1.copyTemplateFile)('CLAUDE.md', platform_1.CLAUDE_MD);
20
+ // Deploy to rules/ekkos.md (safe to overwrite — this is our file)
21
+ (0, templates_1.copyTemplateFile)('CLAUDE.md', platform_1.CLAUDE_EKKOS_RULES);
19
22
  }
20
23
  /**
21
- * Check if CLAUDE.md is deployed
24
+ * Check if ekkOS instructions are deployed
22
25
  */
23
26
  function isInstructionsDeployed() {
24
- return (0, fs_1.existsSync)(platform_1.CLAUDE_MD);
27
+ return (0, fs_1.existsSync)(platform_1.CLAUDE_EKKOS_RULES);
25
28
  }
26
29
  /**
27
30
  * Get the CLAUDE.md content (for preview)
package/dist/index.js CHANGED
@@ -42,13 +42,18 @@ const init_1 = require("./commands/init");
42
42
  const test_1 = require("./commands/test");
43
43
  const status_1 = require("./commands/status");
44
44
  const run_1 = require("./commands/run");
45
+ const test_claude_1 = require("./commands/test-claude");
45
46
  const doctor_1 = require("./commands/doctor");
46
47
  const stream_1 = require("./commands/stream");
47
48
  const hooks_1 = require("./commands/hooks");
48
49
  const setup_remote_1 = require("./commands/setup-remote");
49
50
  const agent_1 = require("./commands/agent");
50
51
  const state_1 = require("./utils/state");
51
- const usage_1 = require("./commands/usage");
52
+ const index_1 = require("./commands/usage/index");
53
+ const dashboard_1 = require("./commands/dashboard");
54
+ const swarm_1 = require("./commands/swarm");
55
+ const swarm_dashboard_1 = require("./commands/swarm-dashboard");
56
+ const swarm_setup_1 = require("./commands/swarm-setup");
52
57
  const chalk_1 = __importDefault(require("chalk"));
53
58
  const fs = __importStar(require("fs"));
54
59
  const path = __importStar(require("path"));
@@ -59,7 +64,158 @@ commander_1.program
59
64
  .name('ekkos')
60
65
  .description('ekkOS memory CLI for AI coding assistants')
61
66
  .version(pkg.version)
62
- .addHelpText('beforeAll', chalk_1.default.cyan('\n made by ekkOS_ with ❤️\n'));
67
+ .addHelpText('before', [
68
+ '',
69
+ chalk_1.default.cyan.bold(' ekkOS_') + chalk_1.default.gray(` v${pkg.version}`) + chalk_1.default.cyan(' — Memory for AI coding assistants'),
70
+ chalk_1.default.gray(' https://docs.ekkos.dev'),
71
+ '',
72
+ ].join('\n'))
73
+ .addHelpText('after', [
74
+ '',
75
+ chalk_1.default.cyan.bold('Examples:'),
76
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default: run)')}`,
77
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos init')} ${chalk_1.default.gray('First-time setup — authenticate + configure your IDE')}`,
78
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run --dashboard')} ${chalk_1.default.gray('Launch with live usage dashboard (tmux split)')}`,
79
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run -b')} ${chalk_1.default.gray('Launch with bypass permissions mode')}`,
80
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos doctor --fix')} ${chalk_1.default.gray('Check and auto-fix system prerequisites')}`,
81
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos usage daily')} ${chalk_1.default.gray("View today's token usage and costs")}`,
82
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos swarm launch -t "build X"')} ${chalk_1.default.gray('Launch parallel workers on a task')}`,
83
+ '',
84
+ chalk_1.default.gray(' Run ') + chalk_1.default.white('ekkos <command> --help') + chalk_1.default.gray(' for detailed options on any command.'),
85
+ '',
86
+ ].join('\n'))
87
+ .configureHelp({
88
+ formatHelp: (cmd, helper) => {
89
+ // Only customize root help — subcommands use default formatting
90
+ if (cmd.parent) {
91
+ // Fall back to default formatting for subcommands
92
+ const termWidth = helper.padWidth(cmd, helper);
93
+ const helpWidth = helper.helpWidth || 80;
94
+ const itemIndentWidth = 2;
95
+ const itemSeparatorWidth = 2;
96
+ let output = '';
97
+ // Description
98
+ const desc = helper.commandDescription(cmd);
99
+ if (desc) {
100
+ output += desc + '\n\n';
101
+ }
102
+ // Usage
103
+ output += chalk_1.default.cyan.bold('Usage:') + ' ' + helper.commandUsage(cmd) + '\n';
104
+ // Arguments
105
+ const args = helper.visibleArguments(cmd);
106
+ if (args.length > 0) {
107
+ output += '\n' + chalk_1.default.cyan.bold('Arguments:') + '\n';
108
+ for (const arg of args) {
109
+ const term = helper.argumentTerm(arg);
110
+ const desc = helper.argumentDescription(arg);
111
+ output += ` ${chalk_1.default.green(term.padEnd(termWidth + itemSeparatorWidth))}${desc}\n`;
112
+ }
113
+ }
114
+ // Options
115
+ const opts = helper.visibleOptions(cmd);
116
+ if (opts.length > 0) {
117
+ output += '\n' + chalk_1.default.cyan.bold('Options:') + '\n';
118
+ for (const opt of opts) {
119
+ const term = helper.optionTerm(opt);
120
+ const desc = helper.optionDescription(opt);
121
+ output += ` ${chalk_1.default.green(term.padEnd(termWidth + itemSeparatorWidth))}${desc}\n`;
122
+ }
123
+ }
124
+ // Subcommands
125
+ const cmds = helper.visibleCommands(cmd);
126
+ if (cmds.length > 0) {
127
+ output += '\n' + chalk_1.default.cyan.bold('Commands:') + '\n';
128
+ for (const sub of cmds) {
129
+ const term = helper.subcommandTerm(sub);
130
+ const desc = helper.subcommandDescription(sub);
131
+ output += ` ${chalk_1.default.green(term.padEnd(termWidth + itemSeparatorWidth))}${desc}\n`;
132
+ }
133
+ }
134
+ return output;
135
+ }
136
+ // ── Root help: grouped command listing ──
137
+ const commands = helper.visibleCommands(cmd);
138
+ const cmdMap = new Map();
139
+ for (const sub of commands) {
140
+ cmdMap.set(sub.name(), {
141
+ term: helper.subcommandTerm(sub),
142
+ desc: helper.subcommandDescription(sub),
143
+ });
144
+ }
145
+ const groups = [
146
+ {
147
+ title: 'Getting Started',
148
+ icon: '▸',
149
+ commands: [
150
+ { name: 'init', desc: 'Authenticate and configure your IDE (Claude, Cursor, Windsurf)' },
151
+ { name: 'status', desc: 'Show memory status and installation info' },
152
+ { name: 'test', desc: 'Test connection to ekkOS memory API' },
153
+ { name: 'doctor', desc: 'Check system prerequisites (Node, PTY, Claude, MCP)' },
154
+ ],
155
+ },
156
+ {
157
+ title: 'Running',
158
+ icon: '▸',
159
+ commands: [
160
+ { name: 'run', desc: 'Launch Claude Code with ekkOS memory + auto-continue', note: 'default' },
161
+ { name: 'test-claude', desc: 'Launch Claude with proxy only (no ccDNA/PTY) for debugging' },
162
+ { name: 'sessions', desc: 'List active Claude Code sessions' },
163
+ ],
164
+ },
165
+ {
166
+ title: 'Monitoring & Usage',
167
+ icon: '▸',
168
+ commands: [
169
+ { name: 'usage', desc: 'Token usage and cost tracking (daily, weekly, monthly, session)' },
170
+ { name: 'dashboard', desc: 'Live TUI dashboard for session monitoring' },
171
+ { name: 'stream', desc: 'Stream capture status and management' },
172
+ { name: 'hooks', desc: 'Install, verify, and manage ekkOS hooks' },
173
+ ],
174
+ },
175
+ {
176
+ title: 'Remote Terminal',
177
+ icon: '▸',
178
+ commands: [
179
+ { name: 'setup-remote', desc: 'Set up remote access (run Claude on your PC from anywhere)' },
180
+ { name: 'agent', desc: 'Manage the remote terminal agent (start, stop, status, logs)' },
181
+ ],
182
+ },
183
+ {
184
+ title: 'Swarm (Multi-Agent)',
185
+ icon: '▸',
186
+ commands: [
187
+ { name: 'swarm', desc: 'Parallel workers, Q-learning routing, swarm dashboard' },
188
+ ],
189
+ },
190
+ ];
191
+ const padCmd = 18;
192
+ let output = '';
193
+ output += chalk_1.default.cyan.bold('Usage:') + ` ekkos ${chalk_1.default.gray('[command] [options]')}\n`;
194
+ for (const group of groups) {
195
+ output += '\n' + chalk_1.default.cyan.bold(`${group.icon} ${group.title}`) + '\n';
196
+ for (const c of group.commands) {
197
+ const nameStr = c.note
198
+ ? chalk_1.default.green(c.name) + chalk_1.default.gray(` (${c.note})`)
199
+ : chalk_1.default.green(c.name);
200
+ // Compute visible length for padding (strip ANSI)
201
+ const visLen = c.note ? c.name.length + ` (${c.note})`.length : c.name.length;
202
+ const pad = Math.max(1, padCmd - visLen);
203
+ output += ` ${nameStr}${' '.repeat(pad)}${chalk_1.default.gray(c.desc)}\n`;
204
+ }
205
+ }
206
+ // Options
207
+ const opts = helper.visibleOptions(cmd);
208
+ if (opts.length > 0) {
209
+ output += '\n' + chalk_1.default.cyan.bold('Options:') + '\n';
210
+ for (const opt of opts) {
211
+ const term = helper.optionTerm(opt);
212
+ const desc = helper.optionDescription(opt);
213
+ output += ` ${chalk_1.default.green(term.padEnd(22))}${desc}\n`;
214
+ }
215
+ }
216
+ return output;
217
+ },
218
+ });
63
219
  // Main init command (combined auth + setup)
64
220
  commander_1.program
65
221
  .command('init')
@@ -92,6 +248,9 @@ commander_1.program
92
248
  .option('--skip-inject', 'Monitor-only mode (detect context wall but print instructions instead of auto-inject)')
93
249
  .option('--skip-dna', 'Skip ccDNA injection (bypass Claude Code patching)')
94
250
  .option('--skip-proxy', 'Skip API proxy (use direct Anthropic API, disables seamless context eviction)')
251
+ .option('--dashboard', 'Launch with live usage dashboard in an isolated 60/40 tmux split (requires tmux)')
252
+ .option('--kickstart', 'Auto-send "test" on load to create session immediately (used internally by --dashboard)')
253
+ .option('--add-dir <dirs...>', 'Additional directories Claude Code can access (outside working directory)')
95
254
  .action((options) => {
96
255
  (0, run_1.run)({
97
256
  session: options.session,
@@ -101,7 +260,24 @@ commander_1.program
101
260
  noInject: options.skipInject,
102
261
  research: options.research,
103
262
  noDna: options.skipDna,
104
- noProxy: options.skipProxy
263
+ noProxy: options.skipProxy,
264
+ dashboard: options.dashboard,
265
+ kickstart: options.kickstart,
266
+ addDirs: options.addDir,
267
+ });
268
+ });
269
+ // Test Claude — bare proxy test (no CLI wrapper)
270
+ commander_1.program
271
+ .command('test-claude')
272
+ .description('Launch Claude Code with proxy only (no ccDNA, no PTY, no injections) for debugging')
273
+ .option('--no-proxy', 'Skip proxy too (completely vanilla Claude)')
274
+ .option('--no-hooks', 'Temporarily disable all hooks during test')
275
+ .option('-v, --verbose', 'Show debug output')
276
+ .action((options) => {
277
+ (0, test_claude_1.testClaude)({
278
+ noProxy: options.proxy === false,
279
+ noHooks: options.hooks === false,
280
+ verbose: options.verbose,
105
281
  });
106
282
  });
107
283
  // Doctor command - check system prerequisites
@@ -174,22 +350,10 @@ hooksCmd
174
350
  .action((options) => {
175
351
  (0, hooks_1.hooksStatus)({ verbose: options.verbose });
176
352
  });
177
- // Usage command - track session token usage
178
- commander_1.program
179
- .command('usage [session-id]')
180
- .description('Track Claude Code session token usage and context percentage')
181
- .option('--instance <id>', 'Instance ID (project path identifier)')
182
- .option('--list', 'List available sessions')
183
- .action((sessionId, options) => {
184
- const args = [];
185
- if (sessionId)
186
- args.push(sessionId);
187
- if (options.instance)
188
- args.push('--instance', options.instance);
189
- if (options.list)
190
- args.push('--list');
191
- (0, usage_1.usageCommand)(args);
192
- });
353
+ // Usage command - track Claude Code token usage and costs (powered by ccusage)
354
+ (0, index_1.registerUsageCommand)(commander_1.program);
355
+ // Dashboard command - live TUI for monitoring session usage
356
+ commander_1.program.addCommand(dashboard_1.dashboardCommand);
193
357
  // Sessions command - list active Claude Code sessions (swarm support)
194
358
  commander_1.program
195
359
  .command('sessions')
@@ -222,7 +386,7 @@ commander_1.program
222
386
  }
223
387
  }
224
388
  });
225
- // Deprecated setup command (redirects to init)
389
+ // Deprecated setup command (redirects to init) — hidden from root help by custom formatHelp
226
390
  commander_1.program
227
391
  .command('setup')
228
392
  .description('[DEPRECATED] Use "ekkos init" instead')
@@ -311,4 +475,76 @@ agentCmd
311
475
  .action((options) => {
312
476
  (0, agent_1.agentLogs)({ follow: options.follow });
313
477
  });
478
+ agentCmd
479
+ .command('health')
480
+ .description('Check agent daemon health and diagnose connection issues')
481
+ .option('-j, --json', 'Output machine-readable JSON')
482
+ .action((options) => {
483
+ (0, agent_1.agentHealth)({ json: options.json });
484
+ });
485
+ // Swarm command - manage Q-learning routing
486
+ const swarmCmd = commander_1.program
487
+ .command('swarm')
488
+ .description('Manage Swarm Q-learning model routing');
489
+ swarmCmd
490
+ .command('status')
491
+ .description('Show Q-table stats (states, visits, epsilon, top actions)')
492
+ .action(swarm_1.swarmStatus);
493
+ swarmCmd
494
+ .command('reset')
495
+ .description('Clear Q-table from Redis (routing reverts to static rules)')
496
+ .action(swarm_1.swarmReset);
497
+ swarmCmd
498
+ .command('export')
499
+ .description('Export Q-table to .swarm/q-learning-model.json')
500
+ .action(swarm_1.swarmExport);
501
+ swarmCmd
502
+ .command('import')
503
+ .description('Import Q-table from .swarm/q-learning-model.json into Redis')
504
+ .action(swarm_1.swarmImport);
505
+ swarmCmd
506
+ .command('launch')
507
+ .description('Launch parallel workers on a decomposed task (opens wizard if --task is omitted)')
508
+ .option('-w, --workers <count>', 'Number of parallel workers (2-8)', parseInt)
509
+ .option('-t, --task <task>', 'Task description to decompose and execute')
510
+ .option('--no-bypass', 'Disable bypass permissions mode')
511
+ .option('--no-decompose', 'Skip AI decomposition (send same task to all workers)')
512
+ .option('--no-queen', 'Skip launching the Python Queen coordinator')
513
+ .option('--queen-strategy <strategy>', 'Queen strategy (adaptive-default, hierarchical-cascade, mesh-consensus)')
514
+ .option('-v, --verbose', 'Show debug output')
515
+ .action((options) => {
516
+ // Auto-open wizard when --task is missing
517
+ if (!options.task) {
518
+ (0, swarm_setup_1.swarmSetup)();
519
+ return;
520
+ }
521
+ (0, swarm_1.swarmLaunch)({
522
+ workers: options.workers || 4,
523
+ task: options.task,
524
+ bypass: options.bypass !== false,
525
+ noDecompose: options.decompose === false,
526
+ noQueen: options.queen === false,
527
+ queenStrategy: options.queenStrategy,
528
+ verbose: options.verbose,
529
+ });
530
+ });
531
+ swarmCmd
532
+ .command('setup')
533
+ .description('Interactive TUI wizard for configuring and launching a swarm')
534
+ .action(() => {
535
+ (0, swarm_setup_1.swarmSetup)();
536
+ });
537
+ swarmCmd.addCommand(swarm_dashboard_1.swarmDashboardCommand);
538
+ // Handle `-help` (single dash) — rewrite to `--help` for Commander compatibility
539
+ const helpIdx = process.argv.indexOf('-help');
540
+ if (helpIdx !== -1) {
541
+ process.argv[helpIdx] = '--help';
542
+ }
543
+ // Default to `run` if no command specified (e.g. `ekkos`, `ekkos -b --dashboard`)
544
+ const knownCommands = commander_1.program.commands.map(c => c.name());
545
+ const userArgs = process.argv.slice(2);
546
+ const hasCommand = userArgs.some(a => knownCommands.includes(a) || a === 'help' || a === '--help' || a === '-h' || a === '--version' || a === '-V');
547
+ if (!hasCommand) {
548
+ process.argv.splice(2, 0, 'run');
549
+ }
314
550
  commander_1.program.parse();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * tmux-scrollbar.ts
4
+ *
5
+ * A thin visual scrollbar (2 columns wide) that runs in its own narrow tmux pane.
6
+ * It tracks the scroll position of a target pane (Claude Code) and renders a
7
+ * proportional scrollbar thumb. Supports click-to-jump and drag-to-scroll.
8
+ *
9
+ * Usage: node tmux-scrollbar.js <target-pane>
10
+ * Example: node tmux-scrollbar.js :.0
11
+ *
12
+ * Layout: [Claude Code (68%)] [Scrollbar (2col)] [Dashboard (30%)]
13
+ */
14
+ export {};