@ekkos/cli 1.3.6 → 1.3.7

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ekkOS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -71,7 +71,9 @@ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12
71
71
  // ── Pricing ──
72
72
  // Pricing per MTok from https://platform.claude.com/docs/en/about-claude/pricing
73
73
  const MODEL_PRICING = {
74
+ 'claude-opus-4-6-20260514': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
74
75
  'claude-opus-4-6': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
76
+ 'claude-sonnet-4-6-20260514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
75
77
  'claude-opus-4-5-20250620': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
76
78
  'claude-sonnet-4-6': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
77
79
  'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
@@ -96,12 +98,23 @@ function calculateTurnCost(model, usage) {
96
98
  (usage.cache_creation_tokens / 1000000) * p.cacheWrite +
97
99
  (usage.cache_read_tokens / 1000000) * p.cacheRead);
98
100
  }
99
- function getModelCtxSize(model) {
100
- if (model.includes('opus'))
101
+ function getModelCtxSize(model, contextTierHint) {
102
+ const normalized = (model || '').toLowerCase();
103
+ if (contextTierHint === '1m')
104
+ return 1000000;
105
+ // Claude 4.6 has GA 1M context in Claude Code/API.
106
+ if (/^claude-(?:opus|sonnet)-4-6(?:$|-)/.test(normalized))
107
+ return 1000000;
108
+ // Keep Gemini on the 1M class in dashboard context math.
109
+ if (normalized.startsWith('gemini-'))
110
+ return 1000000;
111
+ if (normalized.includes('1m'))
112
+ return 1000000;
113
+ if (normalized.includes('opus'))
101
114
  return 200000;
102
- if (model.includes('haiku'))
115
+ if (normalized.includes('haiku'))
103
116
  return 200000;
104
- if (model.includes('sonnet'))
117
+ if (normalized.includes('sonnet'))
105
118
  return 200000;
106
119
  return 200000; // Default Anthropic context
107
120
  }
@@ -183,12 +196,15 @@ function parseJsonlFile(jsonlPath, sessionName) {
183
196
  const evictionState = typeof entry.message._ekkos_eviction_state === 'string'
184
197
  ? entry.message._ekkos_eviction_state
185
198
  : (parseCacheHintValue(cacheHint, 'eviction') || 'unknown');
199
+ const contextTierHint = typeof entry.message._ekkos_context_tier === 'string'
200
+ ? entry.message._ekkos_context_tier.trim().toLowerCase()
201
+ : undefined;
186
202
  const inputTokens = usage.input_tokens || 0;
187
203
  const outputTokens = usage.output_tokens || 0;
188
204
  const cacheReadTokens = usage.cache_read_input_tokens || 0;
189
205
  const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
190
206
  const contextTokens = inputTokens + cacheReadTokens + cacheCreationTokens;
191
- const modelCtxSize = getModelCtxSize(model);
207
+ const modelCtxSize = getModelCtxSize(routedModel, contextTierHint);
192
208
  const contextPct = (contextTokens / modelCtxSize) * 100;
193
209
  const ts = entry.timestamp || new Date().toISOString();
194
210
  if (!startedAt)
@@ -215,6 +231,7 @@ function parseJsonlFile(jsonlPath, sessionName) {
215
231
  const turnData = {
216
232
  turn: turnNum,
217
233
  contextPct,
234
+ modelContextSize: modelCtxSize,
218
235
  input: inputTokens,
219
236
  cacheRead: cacheReadTokens,
220
237
  cacheCreate: cacheCreationTokens,
@@ -267,7 +284,9 @@ function parseJsonlFile(jsonlPath, sessionName) {
267
284
  const currentContextTokens = lastTurn
268
285
  ? lastTurn.input + lastTurn.cacheRead + lastTurn.cacheCreate
269
286
  : 0;
270
- const modelContextSize = getModelCtxSize(model);
287
+ const modelContextSize = lastTurn
288
+ ? lastTurn.modelContextSize
289
+ : getModelCtxSize(model);
271
290
  return {
272
291
  sessionName,
273
292
  model,
@@ -1100,8 +1119,8 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
1100
1119
  try {
1101
1120
  const ctxPct = Math.min(data.currentContextPct, 100);
1102
1121
  const ctxColor = ctxPct < 50 ? 'green' : ctxPct < 80 ? 'yellow' : 'red';
1103
- const tokensK = Math.round(data.currentContextTokens / 1000);
1104
- const maxK = Math.round(data.modelContextSize / 1000);
1122
+ const tokensLabel = fmtK(data.currentContextTokens);
1123
+ const maxLabel = fmtK(data.modelContextSize);
1105
1124
  // Visual progress bar (fills available width)
1106
1125
  const contextInnerWidth = Math.max(10, contextBox.width - 2);
1107
1126
  // Extend bar slightly closer to mascot while keeping a small visual gap.
@@ -1121,7 +1140,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
1121
1140
  const hitColor = data.cacheHitRate >= 80 ? 'green' : data.cacheHitRate >= 50 ? 'yellow' : 'red';
1122
1141
  const cappedMax = Math.min(data.maxContextPct, 100);
1123
1142
  contextBox.setContent(` ${bar}\n` +
1124
- ` {${ctxColor}-fg}${ctxPct.toFixed(0)}%{/${ctxColor}-fg} ${tokensK}K/${maxK}K` +
1143
+ ` {${ctxColor}-fg}${ctxPct.toFixed(0)}%{/${ctxColor}-fg} ${tokensLabel}/${maxLabel}` +
1125
1144
  ` {white-fg}Input{/white-fg} $${breakdown.input.toFixed(2)}` +
1126
1145
  ` {green-fg}Read{/green-fg} $${breakdown.read.toFixed(2)}` +
1127
1146
  ` {yellow-fg}Write{/yellow-fg} $${breakdown.write.toFixed(2)}` +
@@ -1766,7 +1785,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
1766
1785
  const tier = cachedProfile.organization?.rate_limit_tier ?? 'unknown';
1767
1786
  const status = cachedProfile.organization?.subscription_status ?? 'unknown';
1768
1787
  const extraUsage = cachedProfile.organization?.has_extra_usage_enabled
1769
- ? ' {green-fg}extra usage on{/green-fg}'
1788
+ ? ' {green-fg}extra usage on{/green-fg} {gray-fg}(legacy >200k only){/gray-fg}'
1770
1789
  : '';
1771
1790
  line2 =
1772
1791
  ` {bold}Plan:{/bold} ${plan} {bold}Tier:{/bold} ${tier} {bold}Sub:{/bold} ${status}${extraUsage}`;
@@ -154,8 +154,19 @@ async function gemini(options = {}) {
154
154
  console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
155
155
  }
156
156
  console.log('');
157
+ // Extract any trailing arguments meant for the inner CLI
158
+ // We look for 'gemini' in process.argv and pass everything after it.
159
+ // If the user did `ekkos gemini -m gemini-3.1-pro-preview`, we want to pass `-m gemini-3.1-pro-preview`
160
+ let extraArgs = [];
161
+ const geminiIdx = process.argv.indexOf('gemini');
162
+ if (geminiIdx !== -1) {
163
+ extraArgs = process.argv.slice(geminiIdx + 1).filter(a => {
164
+ // Filter out ekkos wrapper options
165
+ return !['--skip-proxy', '-v', '--verbose'].includes(a) && !(a === '-s' || a === '--session' || process.argv[process.argv.indexOf(a) - 1] === '-s' || process.argv[process.argv.indexOf(a) - 1] === '--session');
166
+ });
167
+ }
157
168
  // Spawn Gemini CLI — stdio: inherit for full terminal passthrough
158
- const child = (0, child_process_1.spawn)(geminiPath, [], {
169
+ const child = (0, child_process_1.spawn)(geminiPath, extraArgs, {
159
170
  stdio: 'inherit',
160
171
  env,
161
172
  cwd: process.cwd(),
package/dist/index.js CHANGED
@@ -59,6 +59,7 @@ const scan_1 = require("./commands/scan");
59
59
  const chalk_1 = __importDefault(require("chalk"));
60
60
  const fs = __importStar(require("fs"));
61
61
  const path = __importStar(require("path"));
62
+ const child_process_1 = require("child_process");
62
63
  // Get version from package.json (CommonJS compatible)
63
64
  const pkgPath = path.resolve(__dirname, '../package.json');
64
65
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
@@ -75,16 +76,12 @@ commander_1.program
75
76
  .addHelpText('after', [
76
77
  '',
77
78
  chalk_1.default.cyan.bold('Examples:'),
78
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default: run)')}`,
79
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos init')} ${chalk_1.default.gray('First-time setup authenticate + configure your IDE')}`,
80
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos scan')} ${chalk_1.default.gray('Scan repo structure and seed system registry')}`,
81
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos scan --compile')} ${chalk_1.default.gray('Scan, seed, and trigger compile pass')}`,
82
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run --dashboard')} ${chalk_1.default.gray('Launch with live usage dashboard (tmux split)')}`,
83
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run -b')} ${chalk_1.default.gray('Launch with bypass permissions mode')}`,
84
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos doctor --fix')} ${chalk_1.default.gray('Check and auto-fix system prerequisites')}`,
85
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos usage daily')} ${chalk_1.default.gray("View today's token usage and costs")}`,
86
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos gemini')} ${chalk_1.default.gray('Launch Gemini CLI with ekkOS memory proxy')}`,
87
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos swarm launch -t "build X"')} ${chalk_1.default.gray('Launch parallel workers on a task')}`,
79
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default)')}`,
80
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos codex')} ${chalk_1.default.gray('Start Codex (OpenAI) mode')}`,
81
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos daemon start')} ${chalk_1.default.gray('Start the background mobile sync service')}`,
82
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos connect gemini')} ${chalk_1.default.gray('Securely store your Gemini API key in the cloud')}`,
83
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos agent create --path ./repo')} ${chalk_1.default.gray('Spawn a headless remote agent in a directory')}`,
84
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run --dashboard')} ${chalk_1.default.gray('Launch Claude with live usage dashboard')}`,
88
85
  '',
89
86
  chalk_1.default.gray(' Run ') + chalk_1.default.white('ekkos <command> --help') + chalk_1.default.gray(' for detailed options on any command.'),
90
87
  '',
@@ -152,38 +149,48 @@ commander_1.program
152
149
  title: 'Getting Started',
153
150
  icon: '▸',
154
151
  commands: [
155
- { name: 'init', desc: 'Authenticate and configure your IDE (Claude, Cursor, Windsurf)' },
152
+ { name: 'init', desc: 'First-time setup authenticate and configure IDEs' },
156
153
  { name: 'scan', desc: 'Scan repo structure and seed ekkOS system registry' },
157
- { name: 'status', desc: 'Show memory status and installation info' },
158
- { name: 'test', desc: 'Test connection to ekkOS memory API' },
159
- { name: 'doctor', desc: 'Check system prerequisites (Node, PTY, Claude, MCP)' },
154
+ { name: 'status', desc: 'Show overall system status (Memory, Sync Daemon, Auth)' },
155
+ { name: 'doctor', desc: 'Check system prerequisites and fix runaway processes' },
160
156
  ],
161
157
  },
162
158
  {
163
- title: 'Running',
159
+ title: 'Launch Agents (Local + Mobile Synced)',
164
160
  icon: '▸',
165
161
  commands: [
166
- { name: 'run', desc: 'Launch Claude Code with ekkOS memory', note: 'default' },
167
- { name: 'test-claude', desc: 'Launch Claude with proxy only (no PTY) for debugging' },
168
- { name: 'sessions', desc: 'List active Claude Code sessions' },
169
- { name: 'claw', desc: 'OpenClaw integration status and upgrade controls' },
162
+ { name: 'run', desc: 'Launch Claude Code with memory and mobile control', note: 'default' },
163
+ { name: 'codex', desc: 'Launch Codex (OpenAI) with mobile control' },
164
+ { name: 'gemini', desc: 'Launch Gemini mode (ACP) with mobile control' },
165
+ { name: 'acp', desc: 'Launch a generic ACP-compatible agent' },
170
166
  ],
171
167
  },
172
168
  {
173
- title: 'Monitoring & Usage',
169
+ title: 'Mobile Sync & Cloud (Synk)',
174
170
  icon: '▸',
175
171
  commands: [
176
- { name: 'usage', desc: 'Token usage and cost tracking (daily, weekly, monthly, session)' },
177
- { name: 'dashboard', desc: 'Live TUI dashboard for session monitoring' },
178
- { name: 'stream', desc: 'Stream capture status and management' },
179
- { name: 'hooks', desc: '[DEPRECATED] Hooks no longer needed use `ekkos run`' },
172
+ { name: 'daemon', desc: 'Manage background mobile sync service (start, stop, status)' },
173
+ { name: 'connect', desc: 'Securely store AI vendor API keys in the cloud' },
174
+ { name: 'sandbox', desc: 'Configure OS-level sandboxing for agent execution' },
175
+ { name: 'notify', desc: 'Send push notifications to your Synk mobile app' },
180
176
  ],
181
177
  },
182
178
  {
183
- title: 'Swarm (Multi-Agent)',
179
+ title: 'Headless Agents (Remote Control)',
184
180
  icon: '▸',
185
181
  commands: [
186
- { name: 'swarm', desc: 'Parallel workers, Q-learning routing, swarm dashboard' },
182
+ { name: 'agent', desc: 'Manage remote headless agents (create, list, stop)' },
183
+ { name: 'swarm', desc: 'Parallel workers, Q-learning routing, and swarm dashboard' },
184
+ ],
185
+ },
186
+ {
187
+ title: 'Monitoring & Usage',
188
+ icon: '▸',
189
+ commands: [
190
+ { name: 'auth', desc: 'Manage authentication for Memory, Agents, and Mobile App' },
191
+ { name: 'usage', desc: 'Token usage and cost tracking (daily, weekly, monthly, session)' },
192
+ { name: 'dashboard', desc: 'Live TUI dashboard for session monitoring' },
193
+ { name: 'sessions', desc: 'List active local CLI sessions' },
187
194
  ],
188
195
  },
189
196
  ];
@@ -501,6 +508,74 @@ swarmCmd
501
508
  (0, swarm_setup_1.swarmSetup)();
502
509
  });
503
510
  swarmCmd.addCommand(swarm_dashboard_1.swarmDashboardCommand);
511
+ // --- Remote & Agent Wrapper Helpers ---
512
+ function runRemoteCommand(command, ...args) {
513
+ const isAgent = command === 'agent';
514
+ const pkgName = isAgent ? 'ekkos-agent' : 'ekkos-remote';
515
+ const binName = isAgent ? 'ekkos-agent.mjs' : 'ekkos-synk.mjs';
516
+ // Try resolving local monorepo first
517
+ let binPath = path.resolve(__dirname, '../../' + pkgName + '/bin/' + binName);
518
+ // If we are installed globally (dist folder structure), resolve relatively
519
+ if (!fs.existsSync(binPath)) {
520
+ try {
521
+ const globalPath = require.resolve('@ekkos/' + (isAgent ? 'agent' : 'remote') + '/package.json');
522
+ binPath = path.join(path.dirname(globalPath), 'bin', binName);
523
+ }
524
+ catch (e) {
525
+ // Fallback relative to current dist
526
+ binPath = path.resolve(__dirname, '../../' + pkgName + '/bin/' + binName);
527
+ }
528
+ }
529
+ const p = (0, child_process_1.spawn)('node', [binPath, ...args], {
530
+ stdio: 'inherit',
531
+ env: process.env
532
+ });
533
+ p.on('exit', (code) => {
534
+ process.exit(code || 0);
535
+ });
536
+ }
537
+ // Mobile Sync & Cloud (Synk) Commands
538
+ commander_1.program
539
+ .command('auth')
540
+ .description('Manage authentication for Memory, Agents, and Mobile App')
541
+ .allowUnknownOption()
542
+ .action(() => runRemoteCommand('synk', 'auth', ...process.argv.slice(3)));
543
+ commander_1.program
544
+ .command('codex')
545
+ .description('Launch Codex (OpenAI) with mobile control')
546
+ .allowUnknownOption()
547
+ .action(() => runRemoteCommand('synk', 'codex', ...process.argv.slice(3)));
548
+ commander_1.program
549
+ .command('acp')
550
+ .description('Launch a generic ACP-compatible agent')
551
+ .allowUnknownOption()
552
+ .action(() => runRemoteCommand('synk', 'acp', ...process.argv.slice(3)));
553
+ commander_1.program
554
+ .command('daemon')
555
+ .description('Manage background mobile sync service (start, stop, status)')
556
+ .allowUnknownOption()
557
+ .action(() => runRemoteCommand('synk', 'daemon', ...process.argv.slice(3)));
558
+ commander_1.program
559
+ .command('connect')
560
+ .description('Securely store AI vendor API keys in the cloud')
561
+ .allowUnknownOption()
562
+ .action(() => runRemoteCommand('synk', 'connect', ...process.argv.slice(3)));
563
+ commander_1.program
564
+ .command('sandbox')
565
+ .description('Configure OS-level sandboxing for agent execution')
566
+ .allowUnknownOption()
567
+ .action(() => runRemoteCommand('synk', 'sandbox', ...process.argv.slice(3)));
568
+ commander_1.program
569
+ .command('notify')
570
+ .description('Send push notifications to your Synk mobile app')
571
+ .allowUnknownOption()
572
+ .action(() => runRemoteCommand('synk', 'notify', ...process.argv.slice(3)));
573
+ // Headless Agents (Remote Control)
574
+ commander_1.program
575
+ .command('agent')
576
+ .description('Manage remote headless agents (create, list, stop)')
577
+ .allowUnknownOption()
578
+ .action(() => runRemoteCommand('agent', ...process.argv.slice(3)));
504
579
  // Handle `-help` (single dash) — rewrite to `--help` for Commander compatibility
505
580
  const helpIdx = process.argv.indexOf('-help');
506
581
  if (helpIdx !== -1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -8,13 +8,6 @@
8
8
  "cli": "dist/index.js",
9
9
  "ekkos-capture": "dist/cache/capture.js"
10
10
  },
11
- "scripts": {
12
- "build": "tsc",
13
- "dev": "tsx src/index.ts",
14
- "prepack": "node scripts/build-templates.js prepack",
15
- "postpack": "node scripts/build-templates.js postpack",
16
- "prepublishOnly": "npm run build"
17
- },
18
11
  "keywords": [
19
12
  "ekkos",
20
13
  "ai",
@@ -58,5 +51,9 @@
58
51
  "!dist/cron",
59
52
  "templates/CLAUDE.md",
60
53
  "templates/skills"
61
- ]
62
- }
54
+ ],
55
+ "scripts": {
56
+ "build": "tsc",
57
+ "dev": "tsx src/index.ts"
58
+ }
59
+ }