@ekkos/cli 1.3.2 → 1.3.6
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 +617 -83
- 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.js +2 -25
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +147 -241
- package/dist/commands/scan.d.ts +21 -0
- package/dist/commands/scan.js +386 -0
- 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 +2 -2
- package/dist/deploy/settings.js +42 -4
- package/dist/deploy/skills.js +1 -2
- package/dist/index.js +79 -19
- package/dist/lib/usage-parser.js +5 -4
- package/dist/utils/proxy-url.d.ts +12 -1
- package/dist/utils/proxy-url.js +16 -1
- package/dist/utils/templates.js +1 -1
- package/package.json +4 -6
- 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/commands/synk.d.ts +0 -7
- package/dist/commands/synk.js +0 -339
- package/dist/cron/index.d.ts +0 -7
- package/dist/cron/index.js +0 -13
- package/dist/cron/promoter.d.ts +0 -70
- package/dist/cron/promoter.js +0 -403
- package/dist/synk/api.d.ts +0 -22
- package/dist/synk/api.js +0 -133
- package/dist/synk/auth.d.ts +0 -7
- package/dist/synk/auth.js +0 -30
- package/dist/synk/config.d.ts +0 -18
- package/dist/synk/config.js +0 -37
- package/dist/synk/daemon/control-client.d.ts +0 -11
- package/dist/synk/daemon/control-client.js +0 -101
- package/dist/synk/daemon/control-server.d.ts +0 -24
- package/dist/synk/daemon/control-server.js +0 -91
- package/dist/synk/daemon/run.d.ts +0 -14
- package/dist/synk/daemon/run.js +0 -338
- package/dist/synk/encryption.d.ts +0 -17
- package/dist/synk/encryption.js +0 -133
- package/dist/synk/index.d.ts +0 -13
- package/dist/synk/index.js +0 -36
- package/dist/synk/machine-client.d.ts +0 -42
- package/dist/synk/machine-client.js +0 -218
- package/dist/synk/persistence.d.ts +0 -51
- package/dist/synk/persistence.js +0 -211
- package/dist/synk/qr.d.ts +0 -5
- package/dist/synk/qr.js +0 -33
- package/dist/synk/session-bridge.d.ts +0 -58
- package/dist/synk/session-bridge.js +0 -171
- package/dist/synk/session-client.d.ts +0 -46
- package/dist/synk/session-client.js +0 -240
- package/dist/synk/types.d.ts +0 -574
- package/dist/synk/types.js +0 -74
- 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/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/plan-template.md +0 -306
- 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
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ekkos scan
|
|
4
|
+
*
|
|
5
|
+
* Scans the current directory's repo structure, discovers systems,
|
|
6
|
+
* and seeds them into ekkOS system_registry via the memory API.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ekkos scan Scan cwd and seed registry
|
|
10
|
+
* ekkos scan --compile Also trigger a compile pass after seeding
|
|
11
|
+
* ekkos scan --dry-run Show what would be seeded without calling API
|
|
12
|
+
* ekkos scan --path /my/repo Scan a specific directory
|
|
13
|
+
*
|
|
14
|
+
* Reuses discovery logic from apps/memory/workers/context-compiler/registry-seed.ts
|
|
15
|
+
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.scan = scan;
|
|
21
|
+
const fs_1 = require("fs");
|
|
22
|
+
const path_1 = require("path");
|
|
23
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
24
|
+
const ora_1 = __importDefault(require("ora"));
|
|
25
|
+
const platform_js_1 = require("../utils/platform.js");
|
|
26
|
+
// ── Excluded directories ─────────────────────────────────────────────────
|
|
27
|
+
const EXCLUDED_DIRS = new Set([
|
|
28
|
+
'node_modules', '.next', 'dist', 'out', 'build', '.turbo', '.cache',
|
|
29
|
+
'.git', '.github', '.vscode', '.idea', '.claude', '.windsurf',
|
|
30
|
+
'coverage', '__pycache__', '.pytest_cache', '.mypy_cache',
|
|
31
|
+
'vendor', '.vercel', '.svelte-kit', 'target', 'tmp', 'temp',
|
|
32
|
+
'logs', 'downloads', 'favicon', 'public', 'resources',
|
|
33
|
+
]);
|
|
34
|
+
const EXCLUDED_PREFIXES = ['.', '_'];
|
|
35
|
+
// ── Domain detection ─────────────────────────────────────────────────────
|
|
36
|
+
const DOMAIN_MAP = {
|
|
37
|
+
apps: 'platform',
|
|
38
|
+
packages: 'packages',
|
|
39
|
+
workers: 'workers',
|
|
40
|
+
extensions: 'extensions',
|
|
41
|
+
scripts: 'infra',
|
|
42
|
+
services: 'services',
|
|
43
|
+
lib: 'memory',
|
|
44
|
+
supabase: 'infra',
|
|
45
|
+
'mcp-servers': 'tools',
|
|
46
|
+
templates: 'tools',
|
|
47
|
+
docs: 'content',
|
|
48
|
+
content: 'content',
|
|
49
|
+
monitoring: 'infra',
|
|
50
|
+
tests: 'infra',
|
|
51
|
+
e2e: 'infra',
|
|
52
|
+
src: 'core',
|
|
53
|
+
api: 'api',
|
|
54
|
+
components: 'frontend',
|
|
55
|
+
pages: 'frontend',
|
|
56
|
+
};
|
|
57
|
+
function detectDomain(dirPath) {
|
|
58
|
+
const topLevel = dirPath.split('/')[0];
|
|
59
|
+
return DOMAIN_MAP[topLevel] || 'other';
|
|
60
|
+
}
|
|
61
|
+
// ── System ID generation ─────────────────────────────────────────────────
|
|
62
|
+
function toSystemId(dirPath) {
|
|
63
|
+
return dirPath
|
|
64
|
+
.toLowerCase()
|
|
65
|
+
.replace(/\//g, '-')
|
|
66
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
67
|
+
.replace(/-+/g, '-')
|
|
68
|
+
.replace(/^-|-$/g, '')
|
|
69
|
+
.slice(0, 64);
|
|
70
|
+
}
|
|
71
|
+
function toHumanName(dirPath) {
|
|
72
|
+
const parts = dirPath.split('/');
|
|
73
|
+
return parts[parts.length - 1]
|
|
74
|
+
.replace(/-/g, ' ')
|
|
75
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
76
|
+
}
|
|
77
|
+
// ── Directory scanner ────────────────────────────────────────────────────
|
|
78
|
+
function isIncludable(fullPath) {
|
|
79
|
+
const name = (0, path_1.basename)(fullPath);
|
|
80
|
+
if (EXCLUDED_DIRS.has(name))
|
|
81
|
+
return false;
|
|
82
|
+
if (EXCLUDED_PREFIXES.some(p => name.startsWith(p)))
|
|
83
|
+
return false;
|
|
84
|
+
// Must contain at least 1 source file or meaningful config
|
|
85
|
+
try {
|
|
86
|
+
const entries = (0, fs_1.readdirSync)(fullPath);
|
|
87
|
+
const sourceFiles = entries.filter(e => {
|
|
88
|
+
const ext = e.split('.').pop()?.toLowerCase();
|
|
89
|
+
return ['ts', 'tsx', 'js', 'jsx', 'mjs', 'py', 'rs', 'go', 'sql', 'mts'].includes(ext || '');
|
|
90
|
+
});
|
|
91
|
+
const configFiles = entries.filter(e => ['package.json', 'tsconfig.json', 'Cargo.toml', 'pyproject.toml', 'wrangler.toml', 'go.mod'].includes(e));
|
|
92
|
+
return sourceFiles.length >= 1 || configFiles.length >= 1;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Container directories that always scan children
|
|
99
|
+
const CONTAINER_DIRS = new Set([
|
|
100
|
+
'apps', 'packages', 'workers', 'extensions', 'services', 'mcp-servers',
|
|
101
|
+
]);
|
|
102
|
+
function scanDirectory(repoRoot, currentPath, depth, maxDepth, parentId) {
|
|
103
|
+
if (depth > maxDepth)
|
|
104
|
+
return [];
|
|
105
|
+
const results = [];
|
|
106
|
+
const relPath = (0, path_1.relative)(repoRoot, currentPath) || '.';
|
|
107
|
+
const dirName = (0, path_1.basename)(currentPath);
|
|
108
|
+
// Skip root — just descend into children
|
|
109
|
+
if (relPath === '.') {
|
|
110
|
+
let entries;
|
|
111
|
+
try {
|
|
112
|
+
entries = (0, fs_1.readdirSync)(currentPath);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
const full = (0, path_1.join)(currentPath, entry);
|
|
119
|
+
try {
|
|
120
|
+
if ((0, fs_1.statSync)(full).isDirectory() && !EXCLUDED_DIRS.has(entry) && !entry.startsWith('.')) {
|
|
121
|
+
results.push(...scanDirectory(repoRoot, full, depth + 1, maxDepth, null));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch { /* permission denied, etc. */ }
|
|
125
|
+
}
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
// Container directories (apps/, packages/, etc.) — always scan children, never register themselves
|
|
129
|
+
const isContainer = CONTAINER_DIRS.has(relPath) || CONTAINER_DIRS.has(dirName);
|
|
130
|
+
if (isContainer && depth <= 1) {
|
|
131
|
+
try {
|
|
132
|
+
const entries = (0, fs_1.readdirSync)(currentPath);
|
|
133
|
+
for (const e of entries) {
|
|
134
|
+
const full = (0, path_1.join)(currentPath, e);
|
|
135
|
+
try {
|
|
136
|
+
if ((0, fs_1.statSync)(full).isDirectory() && !EXCLUDED_DIRS.has(e) && !e.startsWith('.')) {
|
|
137
|
+
results.push(...scanDirectory(repoRoot, full, depth + 1, maxDepth, null));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch { /* ignore */ }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch { /* ignore */ }
|
|
144
|
+
return results;
|
|
145
|
+
}
|
|
146
|
+
if (!isIncludable(currentPath))
|
|
147
|
+
return [];
|
|
148
|
+
const systemId = toSystemId(relPath);
|
|
149
|
+
const domain = detectDomain(relPath);
|
|
150
|
+
// Detect description from package.json if available
|
|
151
|
+
let description = `System at ${relPath}`;
|
|
152
|
+
const pkgPath = (0, path_1.join)(currentPath, 'package.json');
|
|
153
|
+
if ((0, fs_1.existsSync)(pkgPath)) {
|
|
154
|
+
try {
|
|
155
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
156
|
+
if (pkg.description)
|
|
157
|
+
description = pkg.description;
|
|
158
|
+
}
|
|
159
|
+
catch { /* ignore */ }
|
|
160
|
+
}
|
|
161
|
+
const entry = {
|
|
162
|
+
system_id: systemId,
|
|
163
|
+
name: toHumanName(relPath),
|
|
164
|
+
description,
|
|
165
|
+
directory_path: relPath,
|
|
166
|
+
domain,
|
|
167
|
+
status: 'active',
|
|
168
|
+
parent_system_id: parentId,
|
|
169
|
+
metadata: {},
|
|
170
|
+
tags: [domain],
|
|
171
|
+
aliases: [],
|
|
172
|
+
};
|
|
173
|
+
results.push(entry);
|
|
174
|
+
// Scan subdirectories for nested systems
|
|
175
|
+
const topLevel = relPath.split('/')[0];
|
|
176
|
+
const isNestedContainer = CONTAINER_DIRS.has(topLevel);
|
|
177
|
+
if (isNestedContainer && depth <= 3) {
|
|
178
|
+
try {
|
|
179
|
+
const entries = (0, fs_1.readdirSync)(currentPath);
|
|
180
|
+
for (const e of entries) {
|
|
181
|
+
const full = (0, path_1.join)(currentPath, e);
|
|
182
|
+
try {
|
|
183
|
+
if ((0, fs_1.statSync)(full).isDirectory() && !EXCLUDED_DIRS.has(e) && !e.startsWith('.')) {
|
|
184
|
+
results.push(...scanDirectory(repoRoot, full, depth + 1, maxDepth, systemId));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch { /* ignore */ }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch { /* ignore */ }
|
|
191
|
+
}
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
// ── Boundary Precedence ──────────────────────────────────────────────────
|
|
195
|
+
function assignParentSystems(systems) {
|
|
196
|
+
const sorted = [...systems].sort((a, b) => {
|
|
197
|
+
const depthA = a.directory_path.split('/').length;
|
|
198
|
+
const depthB = b.directory_path.split('/').length;
|
|
199
|
+
if (depthA !== depthB)
|
|
200
|
+
return depthA - depthB;
|
|
201
|
+
return a.system_id.localeCompare(b.system_id);
|
|
202
|
+
});
|
|
203
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
204
|
+
const system = sorted[i];
|
|
205
|
+
if (system.parent_system_id)
|
|
206
|
+
continue;
|
|
207
|
+
let nearestParent = null;
|
|
208
|
+
let nearestDepth = -1;
|
|
209
|
+
for (let j = 0; j < sorted.length; j++) {
|
|
210
|
+
if (i === j)
|
|
211
|
+
continue;
|
|
212
|
+
const candidate = sorted[j];
|
|
213
|
+
const candidateDepth = candidate.directory_path.split('/').length;
|
|
214
|
+
if (system.directory_path.startsWith(candidate.directory_path + '/') &&
|
|
215
|
+
candidateDepth > nearestDepth) {
|
|
216
|
+
nearestParent = candidate;
|
|
217
|
+
nearestDepth = candidateDepth;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (nearestParent) {
|
|
221
|
+
system.parent_system_id = nearestParent.system_id;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return sorted;
|
|
225
|
+
}
|
|
226
|
+
// ── Load API key from config ─────────────────────────────────────────────
|
|
227
|
+
function loadApiKey() {
|
|
228
|
+
try {
|
|
229
|
+
if ((0, fs_1.existsSync)(platform_js_1.EKKOS_CONFIG)) {
|
|
230
|
+
const config = JSON.parse((0, fs_1.readFileSync)(platform_js_1.EKKOS_CONFIG, 'utf-8'));
|
|
231
|
+
return config.apiKey || null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch { /* ignore */ }
|
|
235
|
+
// Fall back to env var
|
|
236
|
+
return process.env.EKKOS_API_KEY || null;
|
|
237
|
+
}
|
|
238
|
+
// ── Detect git repo root ─────────────────────────────────────────────────
|
|
239
|
+
function findGitRoot(startPath) {
|
|
240
|
+
let current = (0, path_1.resolve)(startPath);
|
|
241
|
+
while (current !== '/') {
|
|
242
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(current, '.git'))) {
|
|
243
|
+
return current;
|
|
244
|
+
}
|
|
245
|
+
const parent = (0, path_1.resolve)(current, '..');
|
|
246
|
+
if (parent === current)
|
|
247
|
+
break;
|
|
248
|
+
current = parent;
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
// ── Main scan command ────────────────────────────────────────────────────
|
|
253
|
+
async function scan(options) {
|
|
254
|
+
const startTime = Date.now();
|
|
255
|
+
const targetPath = (0, path_1.resolve)(options.path || process.cwd());
|
|
256
|
+
const isDryRun = options.dryRun ?? false;
|
|
257
|
+
const shouldCompile = options.compile ?? false;
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(chalk_1.default.cyan.bold(' ekkOS Scan'));
|
|
260
|
+
console.log(chalk_1.default.gray(' ─'.repeat(25)));
|
|
261
|
+
console.log('');
|
|
262
|
+
// Check if in a git repo
|
|
263
|
+
const gitRoot = findGitRoot(targetPath);
|
|
264
|
+
const repoRoot = gitRoot || targetPath;
|
|
265
|
+
if (gitRoot) {
|
|
266
|
+
console.log(chalk_1.default.gray(` Git root: ${gitRoot}`));
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
console.log(chalk_1.default.yellow(` No git repo found — scanning ${targetPath}`));
|
|
270
|
+
}
|
|
271
|
+
console.log('');
|
|
272
|
+
// Phase 1: Scan repo structure
|
|
273
|
+
const scanSpinner = (0, ora_1.default)('Scanning repo structure...').start();
|
|
274
|
+
let systems;
|
|
275
|
+
try {
|
|
276
|
+
const rawSystems = scanDirectory(repoRoot, repoRoot, 0, 4, null);
|
|
277
|
+
systems = assignParentSystems(rawSystems);
|
|
278
|
+
scanSpinner.succeed(`Found ${chalk_1.default.bold(systems.length.toString())} systems`);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
scanSpinner.fail('Scan failed');
|
|
282
|
+
console.error(chalk_1.default.red(` ${err instanceof Error ? err.message : err}`));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
// Show discovered systems
|
|
286
|
+
if (systems.length > 0) {
|
|
287
|
+
console.log('');
|
|
288
|
+
// Group by domain
|
|
289
|
+
const byDomain = new Map();
|
|
290
|
+
for (const s of systems) {
|
|
291
|
+
const group = byDomain.get(s.domain) || [];
|
|
292
|
+
group.push(s);
|
|
293
|
+
byDomain.set(s.domain, group);
|
|
294
|
+
}
|
|
295
|
+
for (const [domain, entries] of byDomain) {
|
|
296
|
+
console.log(chalk_1.default.cyan(` ${domain}`));
|
|
297
|
+
for (const s of entries) {
|
|
298
|
+
const indent = s.parent_system_id ? ' ' : ' ';
|
|
299
|
+
const arrow = s.parent_system_id ? '└─' : '──';
|
|
300
|
+
console.log(chalk_1.default.gray(`${indent}${arrow} `) + chalk_1.default.white(s.system_id) + chalk_1.default.gray(` → ${s.directory_path}`));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
console.log('');
|
|
304
|
+
}
|
|
305
|
+
// Dry run — stop here
|
|
306
|
+
if (isDryRun) {
|
|
307
|
+
console.log(chalk_1.default.yellow(' Dry run — no changes made'));
|
|
308
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
309
|
+
console.log(chalk_1.default.gray(` Done in ${duration}s`));
|
|
310
|
+
console.log('');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Phase 2: Load API key
|
|
314
|
+
const apiKey = loadApiKey();
|
|
315
|
+
if (!apiKey) {
|
|
316
|
+
console.log(chalk_1.default.red(' No API key found.'));
|
|
317
|
+
console.log(chalk_1.default.gray(' Run `ekkos init` first, or set EKKOS_API_KEY env var.'));
|
|
318
|
+
console.log('');
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
// Phase 3: Seed registry via API
|
|
322
|
+
const seedSpinner = (0, ora_1.default)('Seeding registry...').start();
|
|
323
|
+
try {
|
|
324
|
+
const apiUrl = process.env.EKKOS_API_URL || platform_js_1.MCP_API_URL;
|
|
325
|
+
const response = await fetch(`${apiUrl}/api/v1/living-docs/seed`, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: {
|
|
328
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
329
|
+
'Content-Type': 'application/json',
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({
|
|
332
|
+
systems,
|
|
333
|
+
compile: shouldCompile,
|
|
334
|
+
}),
|
|
335
|
+
});
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
const errBody = await response.text();
|
|
338
|
+
seedSpinner.fail('Seed failed');
|
|
339
|
+
console.error(chalk_1.default.red(` API returned ${response.status}: ${errBody}`));
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
const result = await response.json();
|
|
343
|
+
if (!result.ok) {
|
|
344
|
+
seedSpinner.fail('Seed failed');
|
|
345
|
+
console.error(chalk_1.default.red(` ${result.error || 'Unknown error'}`));
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
seedSpinner.succeed(`Seeded ${chalk_1.default.bold(result.total.toString())} systems` +
|
|
349
|
+
(result.inserted > 0 ? chalk_1.default.green(` (${result.inserted} new)`) : '') +
|
|
350
|
+
(result.updated > 0 ? chalk_1.default.gray(` (${result.updated} updated)`) : ''));
|
|
351
|
+
// Show any errors
|
|
352
|
+
if (result.errors && result.errors.length > 0) {
|
|
353
|
+
console.log(chalk_1.default.yellow(` ${result.errors.length} errors:`));
|
|
354
|
+
for (const e of result.errors.slice(0, 5)) {
|
|
355
|
+
console.log(chalk_1.default.gray(` - ${e}`));
|
|
356
|
+
}
|
|
357
|
+
if (result.errors.length > 5) {
|
|
358
|
+
console.log(chalk_1.default.gray(` ... and ${result.errors.length - 5} more`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Compile pass result
|
|
362
|
+
if (result.compile) {
|
|
363
|
+
if (result.compile.triggered) {
|
|
364
|
+
console.log(chalk_1.default.green(' Compile pass triggered'));
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
console.log(chalk_1.default.yellow(` Compile skipped: ${result.compile.reason || result.compile.error || 'unknown'}`));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
seedSpinner.fail('Seed failed');
|
|
373
|
+
if (err instanceof TypeError && err.cause?.code === 'ECONNREFUSED') {
|
|
374
|
+
console.error(chalk_1.default.red(' Could not connect to ekkOS API. Is it running?'));
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
console.error(chalk_1.default.red(` ${err instanceof Error ? err.message : err}`));
|
|
378
|
+
}
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
// Summary
|
|
382
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
383
|
+
console.log('');
|
|
384
|
+
console.log(chalk_1.default.green(` Done in ${duration}s`));
|
|
385
|
+
console.log('');
|
|
386
|
+
}
|
|
@@ -63,11 +63,12 @@ const commander_1 = require("commander");
|
|
|
63
63
|
const state_js_1 = require("../utils/state.js");
|
|
64
64
|
// ── Pricing (per MTok) ──
|
|
65
65
|
const MODEL_PRICING = {
|
|
66
|
-
'claude-opus-4-6': { input:
|
|
67
|
-
'claude-opus-4-5-20250620': { input:
|
|
66
|
+
'claude-opus-4-6': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
67
|
+
'claude-opus-4-5-20250620': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
68
|
+
'claude-sonnet-4-6': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
68
69
|
'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
69
70
|
'claude-sonnet-4-5-20250514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
70
|
-
'claude-haiku-4-5-20251001': { input:
|
|
71
|
+
'claude-haiku-4-5-20251001': { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 },
|
|
71
72
|
};
|
|
72
73
|
function getModelPricing(modelId) {
|
|
73
74
|
if (MODEL_PRICING[modelId])
|
|
@@ -332,11 +333,20 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
332
333
|
const USAGE_H = 4;
|
|
333
334
|
const FOOTER_H = 3;
|
|
334
335
|
const FIXED_H = HEADER_H + WORKERS_H + USAGE_H + FOOTER_H;
|
|
336
|
+
function resolveChartRatio(height) {
|
|
337
|
+
if (height >= 62)
|
|
338
|
+
return 0.22;
|
|
339
|
+
if (height >= 48)
|
|
340
|
+
return 0.25;
|
|
341
|
+
if (height >= 36)
|
|
342
|
+
return 0.28;
|
|
343
|
+
return 0.32;
|
|
344
|
+
}
|
|
335
345
|
function calcLayout() {
|
|
336
|
-
const H = Math.max(30, screen.height);
|
|
346
|
+
const H = Math.max(30, screen.height || 30);
|
|
337
347
|
const remaining = Math.max(10, H - FIXED_H);
|
|
338
|
-
const chartH = Math.max(CHART_H_MIN, Math.floor(remaining *
|
|
339
|
-
const tableH = Math.max(
|
|
348
|
+
const chartH = Math.max(CHART_H_MIN, Math.floor(remaining * resolveChartRatio(H)));
|
|
349
|
+
const tableH = Math.max(5, remaining - chartH);
|
|
340
350
|
return {
|
|
341
351
|
header: { top: 0, height: HEADER_H },
|
|
342
352
|
workers: { top: HEADER_H, height: WORKERS_H },
|
|
@@ -368,15 +378,20 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
368
378
|
border: { type: 'line' },
|
|
369
379
|
label: ' Workers ',
|
|
370
380
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
381
|
+
function createTokenChart(top, left, width, height) {
|
|
382
|
+
return contrib.line({
|
|
383
|
+
top, left, width, height,
|
|
384
|
+
label: ' Tokens/Turn (K) ',
|
|
385
|
+
showLegend: true,
|
|
386
|
+
legend: { width: 12 },
|
|
387
|
+
style: { line: 'green', text: 'white', baseline: 'white', border: { fg: 'yellow' } },
|
|
388
|
+
border: { type: 'line', fg: 'yellow' },
|
|
389
|
+
xLabelPadding: 0, xPadding: 1, wholeNumbersOnly: false,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
let tokenChart = createTokenChart(layout.chart.top, 0, W, layout.chart.height);
|
|
393
|
+
let chartLayoutW = 0;
|
|
394
|
+
let chartLayoutH = 0;
|
|
380
395
|
const turnBox = blessed.box({
|
|
381
396
|
top: layout.table.top, left: 0, width: W, height: layout.table.height,
|
|
382
397
|
content: '',
|
|
@@ -413,22 +428,77 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
413
428
|
screen.append(footerBox);
|
|
414
429
|
function applyLayout() {
|
|
415
430
|
layout = calcLayout();
|
|
431
|
+
const fullWidth = Math.max(24, screen.width || 80);
|
|
432
|
+
const H_PAD = 1;
|
|
433
|
+
const contentWidth = Math.max(20, fullWidth - (H_PAD * 2));
|
|
416
434
|
headerBox.top = layout.header.top;
|
|
435
|
+
headerBox.left = H_PAD;
|
|
436
|
+
headerBox.width = contentWidth;
|
|
417
437
|
headerBox.height = layout.header.height;
|
|
418
438
|
workersBox.top = layout.workers.top;
|
|
439
|
+
workersBox.left = H_PAD;
|
|
440
|
+
workersBox.width = contentWidth;
|
|
419
441
|
workersBox.height = layout.workers.height;
|
|
420
|
-
|
|
421
|
-
|
|
442
|
+
if (chartLayoutW !== contentWidth || chartLayoutH !== layout.chart.height) {
|
|
443
|
+
try {
|
|
444
|
+
screen.remove(tokenChart);
|
|
445
|
+
}
|
|
446
|
+
catch { }
|
|
447
|
+
try {
|
|
448
|
+
tokenChart.destroy?.();
|
|
449
|
+
}
|
|
450
|
+
catch { }
|
|
451
|
+
tokenChart = createTokenChart(layout.chart.top, H_PAD, contentWidth, layout.chart.height);
|
|
452
|
+
chartLayoutW = contentWidth;
|
|
453
|
+
chartLayoutH = layout.chart.height;
|
|
454
|
+
screen.append(tokenChart);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
tokenChart.top = layout.chart.top;
|
|
458
|
+
tokenChart.left = H_PAD;
|
|
459
|
+
tokenChart.width = contentWidth;
|
|
460
|
+
tokenChart.height = layout.chart.height;
|
|
461
|
+
}
|
|
422
462
|
turnBox.top = layout.table.top;
|
|
463
|
+
turnBox.left = H_PAD;
|
|
464
|
+
turnBox.width = contentWidth;
|
|
423
465
|
turnBox.height = layout.table.height;
|
|
424
466
|
windowBox.top = layout.usage.top;
|
|
467
|
+
windowBox.left = H_PAD;
|
|
468
|
+
windowBox.width = contentWidth;
|
|
425
469
|
windowBox.height = layout.usage.height;
|
|
426
470
|
footerBox.top = layout.footer.top;
|
|
471
|
+
footerBox.left = H_PAD;
|
|
472
|
+
footerBox.width = contentWidth;
|
|
427
473
|
footerBox.height = layout.footer.height;
|
|
474
|
+
if (lastChartSeries) {
|
|
475
|
+
try {
|
|
476
|
+
tokenChart.setData(lastChartSeries);
|
|
477
|
+
}
|
|
478
|
+
catch { }
|
|
479
|
+
}
|
|
428
480
|
}
|
|
429
481
|
// ── State ──
|
|
430
482
|
let lastWorkers = [];
|
|
483
|
+
let lastChartSeries = null;
|
|
431
484
|
let sparkleTimer;
|
|
485
|
+
let lastLayoutW = screen.width || 0;
|
|
486
|
+
let lastLayoutH = screen.height || 0;
|
|
487
|
+
function ensureLayoutSynced() {
|
|
488
|
+
const w = screen.width || 0;
|
|
489
|
+
const h = screen.height || 0;
|
|
490
|
+
if (w === lastLayoutW && h === lastLayoutH)
|
|
491
|
+
return;
|
|
492
|
+
lastLayoutW = w;
|
|
493
|
+
lastLayoutH = h;
|
|
494
|
+
try {
|
|
495
|
+
screen.realloc?.();
|
|
496
|
+
}
|
|
497
|
+
catch { }
|
|
498
|
+
applyLayout();
|
|
499
|
+
}
|
|
500
|
+
// Apply once so initial render uses the final, padded geometry.
|
|
501
|
+
applyLayout();
|
|
432
502
|
// ── Logo animation ──
|
|
433
503
|
function renderLogoWave() {
|
|
434
504
|
try {
|
|
@@ -446,11 +516,11 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
446
516
|
const rawLogoLen = 14; // " ekkOS_ Swarm " roughly
|
|
447
517
|
const infoStr = ` ${lastWorkers.length}W ${totalTurns}t $${totalCost.toFixed(2)} ${durStr} `;
|
|
448
518
|
const rawInfoLen = infoStr.length + 1;
|
|
449
|
-
const boxW =
|
|
519
|
+
const boxW = Math.max(10, headerBox.width - 2);
|
|
450
520
|
const pad = Math.max(1, boxW - rawLogoLen - rawInfoLen);
|
|
451
521
|
headerBox.setLabel(logoStr + '\u2500'.repeat(pad) + infoStr);
|
|
452
522
|
// Header content: task description
|
|
453
|
-
const maxW =
|
|
523
|
+
const maxW = Math.max(8, headerBox.width - 6);
|
|
454
524
|
const truncTask = taskStr.length > maxW ? taskStr.slice(0, maxW - 3) + '...' : taskStr;
|
|
455
525
|
headerBox.setContent(` {gray-fg}Task:{/gray-fg} ${truncTask}`);
|
|
456
526
|
waveOffset = (waveOffset + 1) % WAVE_COLORS.length;
|
|
@@ -471,7 +541,7 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
471
541
|
workersBox.setContent(' {gray-fg}No workers detected yet...{/gray-fg}');
|
|
472
542
|
return;
|
|
473
543
|
}
|
|
474
|
-
const boxW = Math.max(20,
|
|
544
|
+
const boxW = Math.max(20, workersBox.width - 2);
|
|
475
545
|
const WORKER_COLORS = ['magenta', 'blue', 'green', 'yellow', 'cyan', 'red', 'white', 'gray'];
|
|
476
546
|
const lines = [];
|
|
477
547
|
// Row 1: Worker names + context bars
|
|
@@ -510,8 +580,9 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
510
580
|
// ── Combined turn table ──
|
|
511
581
|
function renderTurnTable(workers) {
|
|
512
582
|
const WORKER_COLORS = ['magenta', 'blue', 'green', 'yellow', 'cyan', 'red', 'white', 'gray'];
|
|
513
|
-
const w = Math.max(
|
|
583
|
+
const w = Math.max(18, turnBox.width - 4);
|
|
514
584
|
const div = '{gray-fg}|{/gray-fg}';
|
|
585
|
+
const lastScrollPerc = turnBox.getScrollPerc();
|
|
515
586
|
// Columns: W(2) Turn(4) Model(7) Context(7) CacheRd(flex) CacheWr(flex) Out(flex) Cost(7)
|
|
516
587
|
const colW = 2;
|
|
517
588
|
const colNum = 4;
|
|
@@ -540,8 +611,7 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
540
611
|
}
|
|
541
612
|
// Sort by timestamp descending (newest first)
|
|
542
613
|
allTurns.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
543
|
-
const
|
|
544
|
-
const rows = allTurns.slice(0, visibleRows).map(t => {
|
|
614
|
+
const rows = allTurns.map(t => {
|
|
545
615
|
const wColor = WORKER_COLORS[(t.workerIndex - 1) % WORKER_COLORS.length];
|
|
546
616
|
const mTag = modelTag(t.routedModel);
|
|
547
617
|
const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
|
|
@@ -557,6 +627,9 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
557
627
|
costFlag + rpad(`$${t.cost.toFixed(2)}`, colCost) + costEnd);
|
|
558
628
|
});
|
|
559
629
|
turnBox.setContent([header, separator, ...rows].join('\n'));
|
|
630
|
+
if (lastScrollPerc > 0) {
|
|
631
|
+
turnBox.setScrollPerc(lastScrollPerc);
|
|
632
|
+
}
|
|
560
633
|
}
|
|
561
634
|
// ── Combined chart ──
|
|
562
635
|
function renderChart(workers) {
|
|
@@ -576,16 +649,68 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
576
649
|
});
|
|
577
650
|
}
|
|
578
651
|
if (series.length > 0) {
|
|
652
|
+
lastChartSeries = series;
|
|
579
653
|
tokenChart.setData(series);
|
|
580
654
|
}
|
|
581
655
|
}
|
|
582
656
|
// ── Usage window (Anthropic OAuth) ──
|
|
657
|
+
function extractClaudeOauthAccessToken(rawBlob) {
|
|
658
|
+
const raw = (rawBlob || '').trim();
|
|
659
|
+
if (!raw)
|
|
660
|
+
return null;
|
|
661
|
+
const candidates = new Set([raw]);
|
|
662
|
+
if (/^[0-9a-fA-F]+$/.test(raw) && raw.length % 2 === 0) {
|
|
663
|
+
const decoded = Buffer.from(raw, 'hex').toString('utf8');
|
|
664
|
+
candidates.add(decoded);
|
|
665
|
+
candidates.add(decoded.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').trim());
|
|
666
|
+
}
|
|
667
|
+
for (const candidate of candidates) {
|
|
668
|
+
if (!candidate)
|
|
669
|
+
continue;
|
|
670
|
+
try {
|
|
671
|
+
const parsed = JSON.parse(candidate);
|
|
672
|
+
const token = parsed?.claudeAiOauth?.accessToken;
|
|
673
|
+
if (typeof token === 'string' && token.length > 0)
|
|
674
|
+
return token;
|
|
675
|
+
}
|
|
676
|
+
catch { }
|
|
677
|
+
try {
|
|
678
|
+
const parsed = JSON.parse(`{${candidate}}`);
|
|
679
|
+
const token = parsed?.claudeAiOauth?.accessToken;
|
|
680
|
+
if (typeof token === 'string' && token.length > 0)
|
|
681
|
+
return token;
|
|
682
|
+
}
|
|
683
|
+
catch { }
|
|
684
|
+
const oauthMatch = candidate.match(/"claudeAiOauth"\s*:\s*(\{[\s\S]*?\})/);
|
|
685
|
+
if (oauthMatch) {
|
|
686
|
+
try {
|
|
687
|
+
const oauth = JSON.parse(oauthMatch[1]);
|
|
688
|
+
const token = oauth?.accessToken;
|
|
689
|
+
if (typeof token === 'string' && token.length > 0)
|
|
690
|
+
return token;
|
|
691
|
+
}
|
|
692
|
+
catch { }
|
|
693
|
+
const tokenMatch = oauthMatch[1].match(/"accessToken"\s*:\s*"([^"]+)"/);
|
|
694
|
+
if (tokenMatch?.[1])
|
|
695
|
+
return tokenMatch[1];
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
583
700
|
async function fetchAnthropicUsage() {
|
|
584
701
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
702
|
+
let token = null;
|
|
703
|
+
if (process.platform === 'darwin') {
|
|
704
|
+
const { execSync } = require('child_process');
|
|
705
|
+
const credsBlob = execSync('security find-generic-password -s "Claude Code-credentials" -w', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
706
|
+
token = extractClaudeOauthAccessToken(credsBlob);
|
|
707
|
+
}
|
|
708
|
+
if (!token) {
|
|
709
|
+
const credsPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
710
|
+
if (fs.existsSync(credsPath)) {
|
|
711
|
+
token = extractClaudeOauthAccessToken(fs.readFileSync(credsPath, 'utf-8'));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
589
714
|
if (!token)
|
|
590
715
|
return null;
|
|
591
716
|
const resp = await fetch('https://api.anthropic.com/api/oauth/usage', {
|
|
@@ -664,6 +789,7 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
664
789
|
// ── Main update loop ──
|
|
665
790
|
function updateDashboard() {
|
|
666
791
|
try {
|
|
792
|
+
ensureLayoutSynced();
|
|
667
793
|
const workers = discoverWorkers(launchTs);
|
|
668
794
|
lastWorkers = workers;
|
|
669
795
|
renderWorkerCards(workers);
|
|
@@ -691,7 +817,9 @@ async function launchSwarmDashboard(launchTs, refreshMs) {
|
|
|
691
817
|
screen.on('resize', () => {
|
|
692
818
|
try {
|
|
693
819
|
screen.realloc?.();
|
|
694
|
-
|
|
820
|
+
lastLayoutW = 0;
|
|
821
|
+
lastLayoutH = 0;
|
|
822
|
+
ensureLayoutSynced();
|
|
695
823
|
updateDashboard();
|
|
696
824
|
}
|
|
697
825
|
catch { }
|