@ekkos/cli 0.2.18 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/agent/daemon.d.ts +27 -0
- package/dist/agent/daemon.js +254 -29
- package/dist/agent/health-check.d.ts +35 -0
- package/dist/agent/health-check.js +243 -0
- package/dist/agent/pty-runner.d.ts +1 -0
- package/dist/agent/pty-runner.js +6 -1
- package/dist/capture/eviction-client.d.ts +139 -0
- package/dist/capture/eviction-client.js +454 -0
- package/dist/capture/index.d.ts +2 -0
- package/dist/capture/index.js +2 -0
- package/dist/capture/jsonl-rewriter.d.ts +96 -0
- package/dist/capture/jsonl-rewriter.js +1369 -0
- package/dist/capture/transcript-repair.d.ts +51 -0
- package/dist/capture/transcript-repair.js +319 -0
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +244 -0
- package/dist/commands/dashboard.d.ts +25 -0
- package/dist/commands/dashboard.js +1175 -0
- package/dist/commands/doctor.js +23 -1
- package/dist/commands/run.d.ts +5 -0
- package/dist/commands/run.js +1605 -516
- package/dist/commands/setup-remote.js +146 -37
- package/dist/commands/swarm-dashboard.d.ts +20 -0
- package/dist/commands/swarm-dashboard.js +735 -0
- package/dist/commands/swarm-setup.d.ts +10 -0
- package/dist/commands/swarm-setup.js +956 -0
- package/dist/commands/swarm.d.ts +46 -0
- package/dist/commands/swarm.js +441 -0
- package/dist/commands/test-claude.d.ts +16 -0
- package/dist/commands/test-claude.js +156 -0
- package/dist/commands/usage/blocks.d.ts +8 -0
- package/dist/commands/usage/blocks.js +60 -0
- package/dist/commands/usage/daily.d.ts +9 -0
- package/dist/commands/usage/daily.js +96 -0
- package/dist/commands/usage/dashboard.d.ts +8 -0
- package/dist/commands/usage/dashboard.js +104 -0
- package/dist/commands/usage/formatters.d.ts +41 -0
- package/dist/commands/usage/formatters.js +147 -0
- package/dist/commands/usage/index.d.ts +13 -0
- package/dist/commands/usage/index.js +87 -0
- package/dist/commands/usage/monthly.d.ts +8 -0
- package/dist/commands/usage/monthly.js +66 -0
- package/dist/commands/usage/session.d.ts +11 -0
- package/dist/commands/usage/session.js +193 -0
- package/dist/commands/usage/weekly.d.ts +9 -0
- package/dist/commands/usage/weekly.js +61 -0
- package/dist/commands/usage.d.ts +7 -0
- package/dist/commands/usage.js +214 -0
- package/dist/cron/index.d.ts +7 -0
- package/dist/cron/index.js +13 -0
- package/dist/cron/promoter.d.ts +70 -0
- package/dist/cron/promoter.js +403 -0
- package/dist/deploy/instructions.d.ts +5 -2
- package/dist/deploy/instructions.js +11 -8
- package/dist/index.js +262 -5
- package/dist/lib/tmux-scrollbar.d.ts +14 -0
- package/dist/lib/tmux-scrollbar.js +296 -0
- package/dist/lib/usage-monitor.d.ts +47 -0
- package/dist/lib/usage-monitor.js +124 -0
- package/dist/lib/usage-parser.d.ts +162 -0
- package/dist/lib/usage-parser.js +583 -0
- package/dist/restore/RestoreOrchestrator.d.ts +4 -0
- package/dist/restore/RestoreOrchestrator.js +118 -30
- package/dist/utils/log-rotate.d.ts +18 -0
- package/dist/utils/log-rotate.js +74 -0
- package/dist/utils/platform.d.ts +2 -0
- package/dist/utils/platform.js +3 -1
- package/dist/utils/session-binding.d.ts +5 -0
- package/dist/utils/session-binding.js +46 -0
- package/dist/utils/state.js +4 -0
- package/dist/utils/verify-remote-terminal.d.ts +10 -0
- package/dist/utils/verify-remote-terminal.js +415 -0
- package/package.json +9 -2
- package/templates/CLAUDE.md +135 -23
- package/templates/ekkos-manifest.json +5 -5
- package/templates/hooks/lib/contract.sh +43 -31
- package/templates/hooks/lib/count-tokens.cjs +86 -0
- package/templates/hooks/lib/ekkos-reminders.sh +98 -0
- package/templates/hooks/lib/state.sh +53 -1
- package/templates/hooks/stop.sh +150 -388
- package/templates/hooks/user-prompt-submit.sh +353 -443
- package/templates/windsurf-hooks/README.md +212 -0
- package/templates/windsurf-hooks/hooks.json +9 -2
- package/templates/windsurf-hooks/install.sh +148 -0
- package/templates/windsurf-hooks/lib/contract.sh +2 -0
- package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
- package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
- package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
- package/templates/agents/README.md +0 -182
- package/templates/agents/code-reviewer.md +0 -166
- package/templates/agents/debug-detective.md +0 -169
- package/templates/agents/ekkOS_Vercel.md +0 -99
- package/templates/agents/extension-manager.md +0 -229
- package/templates/agents/git-companion.md +0 -185
- package/templates/agents/github-test-agent.md +0 -321
- package/templates/agents/railway-manager.md +0 -215
- package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
package/dist/index.js
CHANGED
|
@@ -42,12 +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");
|
|
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");
|
|
51
57
|
const chalk_1 = __importDefault(require("chalk"));
|
|
52
58
|
const fs = __importStar(require("fs"));
|
|
53
59
|
const path = __importStar(require("path"));
|
|
@@ -58,7 +64,158 @@ commander_1.program
|
|
|
58
64
|
.name('ekkos')
|
|
59
65
|
.description('ekkOS memory CLI for AI coding assistants')
|
|
60
66
|
.version(pkg.version)
|
|
61
|
-
.addHelpText('
|
|
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
|
+
});
|
|
62
219
|
// Main init command (combined auth + setup)
|
|
63
220
|
commander_1.program
|
|
64
221
|
.command('init')
|
|
@@ -88,15 +245,39 @@ commander_1.program
|
|
|
88
245
|
.option('-v, --verbose', 'Show debug output')
|
|
89
246
|
.option('-d, --doctor', 'Run diagnostics before starting')
|
|
90
247
|
.option('-r, --research', 'Auto-run research agent on startup (scans arXiv for new AI papers)')
|
|
91
|
-
.option('--
|
|
248
|
+
.option('--skip-inject', 'Monitor-only mode (detect context wall but print instructions instead of auto-inject)')
|
|
249
|
+
.option('--skip-dna', 'Skip ccDNA injection (bypass Claude Code patching)')
|
|
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)')
|
|
92
254
|
.action((options) => {
|
|
93
255
|
(0, run_1.run)({
|
|
94
256
|
session: options.session,
|
|
95
257
|
bypass: options.bypass,
|
|
96
258
|
verbose: options.verbose,
|
|
97
259
|
doctor: options.doctor,
|
|
98
|
-
noInject: options.
|
|
99
|
-
research: options.research
|
|
260
|
+
noInject: options.skipInject,
|
|
261
|
+
research: options.research,
|
|
262
|
+
noDna: options.skipDna,
|
|
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,
|
|
100
281
|
});
|
|
101
282
|
});
|
|
102
283
|
// Doctor command - check system prerequisites
|
|
@@ -169,6 +350,10 @@ hooksCmd
|
|
|
169
350
|
.action((options) => {
|
|
170
351
|
(0, hooks_1.hooksStatus)({ verbose: options.verbose });
|
|
171
352
|
});
|
|
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);
|
|
172
357
|
// Sessions command - list active Claude Code sessions (swarm support)
|
|
173
358
|
commander_1.program
|
|
174
359
|
.command('sessions')
|
|
@@ -201,7 +386,7 @@ commander_1.program
|
|
|
201
386
|
}
|
|
202
387
|
}
|
|
203
388
|
});
|
|
204
|
-
// Deprecated setup command (redirects to init)
|
|
389
|
+
// Deprecated setup command (redirects to init) — hidden from root help by custom formatHelp
|
|
205
390
|
commander_1.program
|
|
206
391
|
.command('setup')
|
|
207
392
|
.description('[DEPRECATED] Use "ekkos init" instead')
|
|
@@ -290,4 +475,76 @@ agentCmd
|
|
|
290
475
|
.action((options) => {
|
|
291
476
|
(0, agent_1.agentLogs)({ follow: options.follow });
|
|
292
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
|
+
}
|
|
293
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 {};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* tmux-scrollbar.ts
|
|
5
|
+
*
|
|
6
|
+
* A thin visual scrollbar (2 columns wide) that runs in its own narrow tmux pane.
|
|
7
|
+
* It tracks the scroll position of a target pane (Claude Code) and renders a
|
|
8
|
+
* proportional scrollbar thumb. Supports click-to-jump and drag-to-scroll.
|
|
9
|
+
*
|
|
10
|
+
* Usage: node tmux-scrollbar.js <target-pane>
|
|
11
|
+
* Example: node tmux-scrollbar.js :.0
|
|
12
|
+
*
|
|
13
|
+
* Layout: [Claude Code (68%)] [Scrollbar (2col)] [Dashboard (30%)]
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
const child_process_1 = require("child_process");
|
|
17
|
+
const TARGET_PANE = process.argv[2] || ':.0';
|
|
18
|
+
const POLL_MS = 200;
|
|
19
|
+
// ── ANSI escape sequences ──
|
|
20
|
+
const ESC = '\x1b';
|
|
21
|
+
const CSI = `${ESC}[`;
|
|
22
|
+
const ansi = {
|
|
23
|
+
clear: `${CSI}2J`,
|
|
24
|
+
home: `${CSI}H`,
|
|
25
|
+
hideCursor: `${CSI}?25l`,
|
|
26
|
+
showCursor: `${CSI}?25h`,
|
|
27
|
+
// SGR extended mouse protocol: supports coordinates > 223
|
|
28
|
+
enableMouse: `${CSI}?1000h${CSI}?1002h${CSI}?1006h`,
|
|
29
|
+
disableMouse: `${CSI}?1000l${CSI}?1002l${CSI}?1006l`,
|
|
30
|
+
reset: `${CSI}0m`,
|
|
31
|
+
moveTo: (row, col) => `${CSI}${row};${col}H`,
|
|
32
|
+
// 256-color mode
|
|
33
|
+
fg256: (n) => `${CSI}38;5;${n}m`,
|
|
34
|
+
bg256: (n) => `${CSI}48;5;${n}m`,
|
|
35
|
+
};
|
|
36
|
+
// ── Color scheme (matches dashboard cyan theme) ──
|
|
37
|
+
const COLORS = {
|
|
38
|
+
thumbFg: ansi.fg256(44), // Cyan thumb
|
|
39
|
+
thumbBg: ansi.bg256(23), // Dark cyan background
|
|
40
|
+
thumbActiveFg: ansi.fg256(51), // Bright cyan when dragging
|
|
41
|
+
thumbActiveBg: ansi.bg256(30), // Brighter dark cyan when dragging
|
|
42
|
+
trackFg: ansi.fg256(236), // Very dark gray track dots
|
|
43
|
+
trackBg: ansi.bg256(233), // Near-black background
|
|
44
|
+
labelFg: ansi.fg256(240), // Dim label text
|
|
45
|
+
};
|
|
46
|
+
// ── State ──
|
|
47
|
+
let lastRenderedFrame = '';
|
|
48
|
+
let isDragging = false;
|
|
49
|
+
let pollTimer;
|
|
50
|
+
function getHeight() {
|
|
51
|
+
return process.stdout.rows || 40;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Query tmux for the target pane's scroll position.
|
|
55
|
+
* Returns { inMode, scrollPos, historySize, paneHeight }
|
|
56
|
+
*/
|
|
57
|
+
function getPaneInfo() {
|
|
58
|
+
try {
|
|
59
|
+
const raw = (0, child_process_1.execSync)(`tmux display-message -p -t "${TARGET_PANE}" ` +
|
|
60
|
+
`'#{pane_in_mode}|#{scroll_position}|#{history_size}|#{pane_height}'`, { encoding: 'utf-8', timeout: 800, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
61
|
+
const [mode, pos, hist, ph] = raw.split('|');
|
|
62
|
+
return {
|
|
63
|
+
inMode: mode === '1',
|
|
64
|
+
scrollPos: parseInt(pos) || 0,
|
|
65
|
+
historySize: parseInt(hist) || 0,
|
|
66
|
+
paneHeight: parseInt(ph) || 40,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { inMode: false, scrollPos: 0, historySize: 0, paneHeight: 40 };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Render the scrollbar to stdout using ANSI escape codes.
|
|
75
|
+
* Only redraws if the frame has changed (diffing).
|
|
76
|
+
*/
|
|
77
|
+
function render() {
|
|
78
|
+
const height = getHeight();
|
|
79
|
+
const info = getPaneInfo();
|
|
80
|
+
// Total scrollable content
|
|
81
|
+
const totalLines = info.historySize + info.paneHeight;
|
|
82
|
+
if (totalLines <= 0)
|
|
83
|
+
return;
|
|
84
|
+
// Thumb size proportional to viewport/total
|
|
85
|
+
const viewportRatio = Math.min(1, info.paneHeight / totalLines);
|
|
86
|
+
const thumbSize = Math.max(2, Math.round(viewportRatio * height));
|
|
87
|
+
// Thumb position
|
|
88
|
+
let thumbTop;
|
|
89
|
+
if (!info.inMode || info.historySize === 0) {
|
|
90
|
+
// Live output — thumb at bottom
|
|
91
|
+
thumbTop = height - thumbSize;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Copy mode — scroll_position = lines from bottom
|
|
95
|
+
const scrollFraction = Math.min(1, info.scrollPos / Math.max(1, info.historySize));
|
|
96
|
+
thumbTop = Math.round((1 - scrollFraction) * (height - thumbSize));
|
|
97
|
+
}
|
|
98
|
+
// Clamp
|
|
99
|
+
thumbTop = Math.max(0, Math.min(height - thumbSize, thumbTop));
|
|
100
|
+
// Build frame string
|
|
101
|
+
const fgColor = isDragging ? COLORS.thumbActiveFg : COLORS.thumbFg;
|
|
102
|
+
const bgColor = isDragging ? COLORS.thumbActiveBg : COLORS.thumbBg;
|
|
103
|
+
let frame = ansi.home;
|
|
104
|
+
for (let y = 0; y < height; y++) {
|
|
105
|
+
const isThumb = y >= thumbTop && y < thumbTop + thumbSize;
|
|
106
|
+
frame += ansi.moveTo(y + 1, 1);
|
|
107
|
+
if (isThumb) {
|
|
108
|
+
frame += `${fgColor}${bgColor}██${ansi.reset}`;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
frame += `${COLORS.trackFg}${COLORS.trackBg}░░${ansi.reset}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Only write if changed
|
|
115
|
+
if (frame !== lastRenderedFrame) {
|
|
116
|
+
process.stdout.write(frame);
|
|
117
|
+
lastRenderedFrame = frame;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Scroll the target pane to a position based on scrollbar click location.
|
|
122
|
+
*/
|
|
123
|
+
function scrollToPosition(y) {
|
|
124
|
+
const height = getHeight();
|
|
125
|
+
const info = getPaneInfo();
|
|
126
|
+
if (info.historySize <= 0)
|
|
127
|
+
return;
|
|
128
|
+
const ratio = Math.max(0, Math.min(1, y / Math.max(1, height - 1)));
|
|
129
|
+
// If clicking at the very bottom, exit copy-mode for live output
|
|
130
|
+
if (ratio >= 0.95) {
|
|
131
|
+
try {
|
|
132
|
+
if (info.inMode) {
|
|
133
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${TARGET_PANE}" -X cancel`, {
|
|
134
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch { /* ignore */ }
|
|
139
|
+
lastRenderedFrame = '';
|
|
140
|
+
render();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Target line from top of scrollback
|
|
144
|
+
const targetLine = Math.round(ratio * info.historySize);
|
|
145
|
+
try {
|
|
146
|
+
// Enter copy-mode if not already
|
|
147
|
+
if (!info.inMode) {
|
|
148
|
+
(0, child_process_1.execSync)(`tmux copy-mode -t "${TARGET_PANE}"`, {
|
|
149
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Jump to target line using goto-line (tmux 3.1+)
|
|
153
|
+
// Fallback: history-top + cursor-down for older tmux
|
|
154
|
+
try {
|
|
155
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${TARGET_PANE}" -X goto-line ${targetLine}`, {
|
|
156
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Fallback for older tmux: go to top, then move down
|
|
161
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${TARGET_PANE}" -X history-top`, {
|
|
162
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
163
|
+
});
|
|
164
|
+
if (targetLine > 0) {
|
|
165
|
+
// Move in chunks to avoid command-line length limits
|
|
166
|
+
let remaining = targetLine;
|
|
167
|
+
while (remaining > 0) {
|
|
168
|
+
const chunk = Math.min(remaining, 5000);
|
|
169
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${TARGET_PANE}" -X -N ${chunk} cursor-down`, {
|
|
170
|
+
timeout: 1000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
171
|
+
});
|
|
172
|
+
remaining -= chunk;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch { /* ignore scroll errors */ }
|
|
178
|
+
lastRenderedFrame = '';
|
|
179
|
+
render();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Refocus the target pane (Claude Code) after scrollbar interaction.
|
|
183
|
+
*/
|
|
184
|
+
function refocusTarget() {
|
|
185
|
+
try {
|
|
186
|
+
(0, child_process_1.execSync)(`tmux select-pane -t "${TARGET_PANE}"`, {
|
|
187
|
+
timeout: 300, stdio: ['pipe', 'pipe', 'pipe'],
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch { /* ignore */ }
|
|
191
|
+
}
|
|
192
|
+
// ── Mouse event handling ──
|
|
193
|
+
function handleInput(data) {
|
|
194
|
+
const str = data.toString();
|
|
195
|
+
// Parse SGR mouse events: ESC [ < button ; x ; y M (press) / m (release)
|
|
196
|
+
const events = str.matchAll(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/g);
|
|
197
|
+
for (const match of events) {
|
|
198
|
+
const button = parseInt(match[1]);
|
|
199
|
+
const y = parseInt(match[3]) - 1; // Convert to 0-indexed
|
|
200
|
+
const isPress = match[4] === 'M';
|
|
201
|
+
if (button === 0) {
|
|
202
|
+
// Left mouse button
|
|
203
|
+
if (isPress) {
|
|
204
|
+
isDragging = true;
|
|
205
|
+
scrollToPosition(y);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Release
|
|
209
|
+
if (isDragging) {
|
|
210
|
+
isDragging = false;
|
|
211
|
+
lastRenderedFrame = '';
|
|
212
|
+
render();
|
|
213
|
+
// Return focus to Claude Code pane after click
|
|
214
|
+
refocusTarget();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (button === 32 && isDragging) {
|
|
219
|
+
// Left button drag (SGR reports button 32 for motion with button 0 held)
|
|
220
|
+
scrollToPosition(y);
|
|
221
|
+
}
|
|
222
|
+
else if (button === 64) {
|
|
223
|
+
// Scroll wheel up — forward to target pane
|
|
224
|
+
try {
|
|
225
|
+
const info = getPaneInfo();
|
|
226
|
+
if (!info.inMode) {
|
|
227
|
+
(0, child_process_1.execSync)(`tmux copy-mode -t "${TARGET_PANE}" -e`, {
|
|
228
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${TARGET_PANE}" -X -N 5 scroll-up`, {
|
|
232
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch { /* ignore */ }
|
|
236
|
+
lastRenderedFrame = '';
|
|
237
|
+
render();
|
|
238
|
+
}
|
|
239
|
+
else if (button === 65) {
|
|
240
|
+
// Scroll wheel down — forward to target pane
|
|
241
|
+
try {
|
|
242
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${TARGET_PANE}" -X -N 5 scroll-down`, {
|
|
243
|
+
timeout: 500, stdio: ['pipe', 'pipe', 'pipe'],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch { /* ignore */ }
|
|
247
|
+
lastRenderedFrame = '';
|
|
248
|
+
render();
|
|
249
|
+
}
|
|
250
|
+
return; // Processed mouse event
|
|
251
|
+
}
|
|
252
|
+
// Keyboard input
|
|
253
|
+
if (str === 'q' || str === '\x03') {
|
|
254
|
+
// Quit on 'q' or Ctrl-C
|
|
255
|
+
shutdown();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// ── Lifecycle ──
|
|
259
|
+
function setup() {
|
|
260
|
+
// Hide cursor, enable mouse tracking, clear screen
|
|
261
|
+
process.stdout.write(ansi.hideCursor + ansi.enableMouse + ansi.clear);
|
|
262
|
+
// Raw mode for stdin (capture mouse events)
|
|
263
|
+
if (process.stdin.setRawMode) {
|
|
264
|
+
process.stdin.setRawMode(true);
|
|
265
|
+
}
|
|
266
|
+
process.stdin.resume();
|
|
267
|
+
process.stdin.on('data', handleInput);
|
|
268
|
+
// Handle terminal resize
|
|
269
|
+
process.stdout.on('resize', () => {
|
|
270
|
+
lastRenderedFrame = '';
|
|
271
|
+
render();
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function cleanup() {
|
|
275
|
+
process.stdout.write(ansi.showCursor + ansi.disableMouse + ansi.reset + ansi.clear);
|
|
276
|
+
if (process.stdin.setRawMode) {
|
|
277
|
+
try {
|
|
278
|
+
process.stdin.setRawMode(false);
|
|
279
|
+
}
|
|
280
|
+
catch { /* ignore */ }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function shutdown() {
|
|
284
|
+
clearInterval(pollTimer);
|
|
285
|
+
cleanup();
|
|
286
|
+
process.exit(0);
|
|
287
|
+
}
|
|
288
|
+
// Graceful exit
|
|
289
|
+
process.on('exit', cleanup);
|
|
290
|
+
process.on('SIGINT', shutdown);
|
|
291
|
+
process.on('SIGTERM', shutdown);
|
|
292
|
+
process.on('SIGHUP', shutdown);
|
|
293
|
+
// ── Main ──
|
|
294
|
+
setup();
|
|
295
|
+
render();
|
|
296
|
+
pollTimer = setInterval(render, POLL_MS);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple usage monitoring for ekkOS embedding costs
|
|
3
|
+
* Tracks daily usage to prevent runaway costs
|
|
4
|
+
*/
|
|
5
|
+
interface UsageRecord {
|
|
6
|
+
date: string;
|
|
7
|
+
embeddings: number;
|
|
8
|
+
cost: number;
|
|
9
|
+
model: string;
|
|
10
|
+
}
|
|
11
|
+
interface DailyUsage {
|
|
12
|
+
date: string;
|
|
13
|
+
totalEmbeddings: number;
|
|
14
|
+
totalCost: number;
|
|
15
|
+
byModel: Record<string, {
|
|
16
|
+
count: number;
|
|
17
|
+
cost: number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load usage records
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadUsage(): Promise<UsageRecord[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Record an embedding generation
|
|
26
|
+
*/
|
|
27
|
+
export declare function recordEmbedding(model?: string, tokens?: number): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Get usage for a specific date
|
|
30
|
+
*/
|
|
31
|
+
export declare function getUsageForDate(date: string): Promise<DailyUsage>;
|
|
32
|
+
/**
|
|
33
|
+
* Get usage for the last N days
|
|
34
|
+
*/
|
|
35
|
+
export declare function getUsageForDays(days: number): Promise<DailyUsage[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Check if usage exceeds threshold
|
|
38
|
+
*/
|
|
39
|
+
export declare function checkThreshold(dailyLimit?: number, costLimit?: number): Promise<{
|
|
40
|
+
exceeded: boolean;
|
|
41
|
+
usage: DailyUsage;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Clear old usage data (keep last 90 days)
|
|
45
|
+
*/
|
|
46
|
+
export declare function pruneOldUsage(): Promise<void>;
|
|
47
|
+
export {};
|