@ekkos/cli 1.3.1 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capture/jsonl-rewriter.d.ts +1 -1
- package/dist/capture/jsonl-rewriter.js +3 -3
- package/dist/capture/transcript-repair.d.ts +2 -2
- package/dist/capture/transcript-repair.js +2 -2
- package/dist/commands/claw.d.ts +13 -0
- package/dist/commands/claw.js +253 -0
- package/dist/commands/dashboard.js +742 -118
- package/dist/commands/doctor.d.ts +3 -3
- package/dist/commands/doctor.js +6 -79
- package/dist/commands/gemini.d.ts +19 -0
- package/dist/commands/gemini.js +193 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +56 -41
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +288 -263
- package/dist/commands/scan.d.ts +21 -0
- package/dist/commands/scan.js +386 -0
- package/dist/commands/status.d.ts +4 -1
- package/dist/commands/status.js +165 -27
- package/dist/commands/swarm-dashboard.js +156 -28
- package/dist/commands/swarm.d.ts +1 -1
- package/dist/commands/swarm.js +1 -1
- package/dist/commands/test-claude.d.ts +2 -2
- package/dist/commands/test-claude.js +3 -3
- package/dist/deploy/index.d.ts +0 -2
- package/dist/deploy/index.js +0 -2
- package/dist/deploy/settings.d.ts +6 -5
- package/dist/deploy/settings.js +64 -16
- package/dist/deploy/skills.js +1 -2
- package/dist/index.js +86 -96
- package/dist/lib/usage-parser.d.ts +1 -1
- package/dist/lib/usage-parser.js +9 -6
- package/dist/local/index.d.ts +14 -0
- package/dist/local/index.js +28 -0
- package/dist/local/local-embeddings.d.ts +49 -0
- package/dist/local/local-embeddings.js +232 -0
- package/dist/local/offline-fallback.d.ts +44 -0
- package/dist/local/offline-fallback.js +159 -0
- package/dist/local/sqlite-store.d.ts +126 -0
- package/dist/local/sqlite-store.js +393 -0
- package/dist/local/sync-engine.d.ts +42 -0
- package/dist/local/sync-engine.js +223 -0
- package/dist/utils/platform.d.ts +5 -1
- package/dist/utils/platform.js +24 -4
- package/dist/utils/proxy-url.d.ts +21 -0
- package/dist/utils/proxy-url.js +34 -0
- package/dist/utils/state.d.ts +1 -1
- package/dist/utils/state.js +11 -3
- package/dist/utils/templates.js +1 -1
- package/package.json +11 -4
- package/templates/CLAUDE.md +49 -107
- package/dist/agent/daemon.d.ts +0 -130
- package/dist/agent/daemon.js +0 -606
- package/dist/agent/health-check.d.ts +0 -35
- package/dist/agent/health-check.js +0 -243
- package/dist/agent/pty-runner.d.ts +0 -53
- package/dist/agent/pty-runner.js +0 -190
- package/dist/commands/agent.d.ts +0 -50
- package/dist/commands/agent.js +0 -544
- package/dist/commands/setup-remote.d.ts +0 -20
- package/dist/commands/setup-remote.js +0 -582
- package/dist/utils/verify-remote-terminal.d.ts +0 -10
- package/dist/utils/verify-remote-terminal.js +0 -415
- package/templates/README.md +0 -378
- package/templates/claude-plugins/PHASE2_COMPLETION.md +0 -346
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +0 -1776
- package/templates/claude-plugins/README.md +0 -587
- package/templates/claude-plugins/agents/code-reviewer.json +0 -14
- package/templates/claude-plugins/agents/debug-detective.json +0 -15
- package/templates/claude-plugins/agents/git-companion.json +0 -14
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/blog-manager/commands/blog.md +0 -691
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +0 -434
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +0 -282
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +0 -181
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/pattern-coach/commands/forge.md +0 -365
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +0 -582
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +0 -819
- package/templates/claude-plugins-admin/README.md +0 -446
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +0 -595
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +0 -798
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +0 -554
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +0 -881
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +0 -85
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +0 -569
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +0 -863
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +0 -732
- package/templates/commands/continue.md +0 -47
- package/templates/cursor-rules/ekkos-memory.md +0 -127
- package/templates/ekkos-manifest.json +0 -223
- package/templates/helpers/json-parse.cjs +0 -101
- package/templates/hooks-node/lib/state.js +0 -187
- package/templates/hooks-node/stop.js +0 -416
- package/templates/hooks-node/user-prompt-submit.js +0 -337
- package/templates/plan-template.md +0 -306
- package/templates/rules/00-hooks-contract.mdc +0 -89
- package/templates/rules/30-ekkos-core.mdc +0 -188
- package/templates/rules/31-ekkos-messages.mdc +0 -78
- package/templates/shared/hooks-enabled.json +0 -22
- package/templates/shared/session-words.json +0 -45
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +0 -282
- package/templates/skills/ekkOS_Learn/Skill.md +0 -265
- package/templates/skills/ekkOS_Memory_First/Skill.md +0 -206
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +0 -302
- package/templates/skills/ekkOS_Preferences/Skill.md +0 -247
- package/templates/skills/ekkOS_Reflect/Skill.md +0 -257
- package/templates/skills/ekkOS_Safety/Skill.md +0 -265
- package/templates/skills/ekkOS_Schema/Skill.md +0 -251
- package/templates/skills/ekkOS_Summary/Skill.md +0 -257
- package/templates/spec-template.md +0 -159
- package/templates/windsurf-rules/ekkos-memory.md +0 -127
- package/templates/windsurf-skills/README.md +0 -58
- package/templates/windsurf-skills/ekkos-continue/SKILL.md +0 -81
- package/templates/windsurf-skills/ekkos-golden-loop/SKILL.md +0 -225
- package/templates/windsurf-skills/ekkos-insights/SKILL.md +0 -138
- package/templates/windsurf-skills/ekkos-recall/SKILL.md +0 -96
- package/templates/windsurf-skills/ekkos-safety/SKILL.md +0 -89
- package/templates/windsurf-skills/ekkos-vault/SKILL.md +0 -86
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ekkos scan
|
|
3
|
+
*
|
|
4
|
+
* Scans the current directory's repo structure, discovers systems,
|
|
5
|
+
* and seeds them into ekkOS system_registry via the memory API.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ekkos scan Scan cwd and seed registry
|
|
9
|
+
* ekkos scan --compile Also trigger a compile pass after seeding
|
|
10
|
+
* ekkos scan --dry-run Show what would be seeded without calling API
|
|
11
|
+
* ekkos scan --path /my/repo Scan a specific directory
|
|
12
|
+
*
|
|
13
|
+
* Reuses discovery logic from apps/memory/workers/context-compiler/registry-seed.ts
|
|
14
|
+
*/
|
|
15
|
+
interface ScanOptions {
|
|
16
|
+
compile?: boolean;
|
|
17
|
+
dryRun?: boolean;
|
|
18
|
+
path?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function scan(options: ScanOptions): Promise<void>;
|
|
21
|
+
export {};
|
|
@@ -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
|
+
}
|
package/dist/commands/status.js
CHANGED
|
@@ -11,40 +11,108 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
11
11
|
const ora_1 = __importDefault(require("ora"));
|
|
12
12
|
const EKKOS_API_URL = 'https://mcp.ekkos.dev';
|
|
13
13
|
const CONFIG_FILE = (0, path_1.join)((0, os_1.homedir)(), '.ekkos', 'config.json');
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.log(chalk_1.default.red('✗ Not configured'));
|
|
22
|
-
console.log(chalk_1.default.gray(' Run: npx @ekkos/cli setup'));
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
let config;
|
|
14
|
+
const METRICS_FILE = (0, path_1.join)((0, os_1.homedir)(), '.ekkos', 'session-metrics.json');
|
|
15
|
+
/** Age (ms) after which the metrics file is considered stale (no active session). */
|
|
16
|
+
const METRICS_STALE_MS = 5 * 60 * 1000; // 5 minutes
|
|
17
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
18
|
+
function readMetrics() {
|
|
19
|
+
if (!(0, fs_1.existsSync)(METRICS_FILE))
|
|
20
|
+
return null;
|
|
26
21
|
try {
|
|
27
|
-
|
|
22
|
+
return JSON.parse((0, fs_1.readFileSync)(METRICS_FILE, 'utf-8'));
|
|
28
23
|
}
|
|
29
24
|
catch {
|
|
30
|
-
|
|
31
|
-
process.exit(1);
|
|
25
|
+
return null;
|
|
32
26
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
}
|
|
28
|
+
function isMetricsStale(m) {
|
|
29
|
+
const updatedAt = new Date(m.updated_at).getTime();
|
|
30
|
+
return Date.now() - updatedAt > METRICS_STALE_MS;
|
|
31
|
+
}
|
|
32
|
+
function formatDuration(isoStart) {
|
|
33
|
+
const ms = Date.now() - new Date(isoStart).getTime();
|
|
34
|
+
const totalSec = Math.floor(ms / 1000);
|
|
35
|
+
const h = Math.floor(totalSec / 3600);
|
|
36
|
+
const m = Math.floor((totalSec % 3600) / 60);
|
|
37
|
+
const s = totalSec % 60;
|
|
38
|
+
if (h > 0)
|
|
39
|
+
return `${h}h ${m}m`;
|
|
40
|
+
if (m > 0)
|
|
41
|
+
return `${m}m ${s}s`;
|
|
42
|
+
return `${s}s`;
|
|
43
|
+
}
|
|
44
|
+
function formatTokens(n) {
|
|
45
|
+
if (n >= 1000000)
|
|
46
|
+
return `${(n / 1000000).toFixed(1)}M`;
|
|
47
|
+
if (n >= 1000)
|
|
48
|
+
return `${Math.round(n / 1000)}K`;
|
|
49
|
+
return String(n);
|
|
50
|
+
}
|
|
51
|
+
// ─── Session panel ────────────────────────────────────────────────────────────
|
|
52
|
+
function printSessionPanel(m) {
|
|
53
|
+
const sessionLabel = m.session_name || m.session_id;
|
|
54
|
+
const duration = formatDuration(m.started_at);
|
|
55
|
+
const turnLabel = m.turn > 0 ? `Turn ${m.turn}` : `${m.tool_calls} calls`;
|
|
56
|
+
const lines = [
|
|
57
|
+
`ekkOS Session: ${sessionLabel}`,
|
|
58
|
+
`Duration: ${duration} · ${turnLabel}`,
|
|
59
|
+
null, // separator
|
|
60
|
+
`Patterns recalled: ${m.patterns_recalled}`,
|
|
61
|
+
`Patterns forged: ${m.patterns_forged}`,
|
|
62
|
+
`Directives active: ${m.directives_active}`,
|
|
63
|
+
`Cache: ${m.cache_backend} (${m.cache_patterns} patterns)`,
|
|
64
|
+
null, // separator
|
|
65
|
+
`Tokens: ${formatTokens(m.tokens_in)} in · ${formatTokens(m.tokens_out)} out`,
|
|
66
|
+
];
|
|
67
|
+
// Compute box width from longest content line
|
|
68
|
+
const contentLines = lines.filter(Boolean);
|
|
69
|
+
const maxLen = Math.max(...contentLines.map(l => l.length));
|
|
70
|
+
const innerWidth = Math.max(39, maxLen + 4); // 2 spaces padding each side
|
|
71
|
+
const pad = (text) => {
|
|
72
|
+
const spaces = innerWidth - text.length - 2;
|
|
73
|
+
return ` ${text}${' '.repeat(Math.max(0, spaces))}`;
|
|
74
|
+
};
|
|
75
|
+
const top = `┌${'─'.repeat(innerWidth)}┐`;
|
|
76
|
+
const sep = `├${'─'.repeat(innerWidth)}┤`;
|
|
77
|
+
const bottom = `└${'─'.repeat(innerWidth)}┘`;
|
|
78
|
+
const row = (text) => `│${pad(text)}│`;
|
|
79
|
+
const sepRow = sep;
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(chalk_1.default.cyan(top));
|
|
82
|
+
let firstSep = false;
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
if (line === null) {
|
|
85
|
+
// Separator after header block, then again before token block
|
|
86
|
+
if (!firstSep) {
|
|
87
|
+
console.log(chalk_1.default.cyan(sepRow));
|
|
88
|
+
firstSep = true;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(chalk_1.default.cyan(sepRow));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (!firstSep) {
|
|
95
|
+
// Header rows — bold
|
|
96
|
+
console.log(chalk_1.default.cyan('│') + chalk_1.default.bold(pad(line)) + chalk_1.default.cyan('│'));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(chalk_1.default.cyan(row(line)));
|
|
100
|
+
}
|
|
37
101
|
}
|
|
102
|
+
console.log(chalk_1.default.cyan(bottom));
|
|
103
|
+
console.log('');
|
|
104
|
+
}
|
|
105
|
+
// ─── Memory API stats (original status content) ───────────────────────────────
|
|
106
|
+
async function printMemoryStats(apiKey, config) {
|
|
38
107
|
const spinner = (0, ora_1.default)('Fetching memory stats...').start();
|
|
39
108
|
try {
|
|
40
|
-
// Get pattern stats
|
|
41
109
|
const patternsResponse = await fetch(`${EKKOS_API_URL}/api/v1/patterns/query`, {
|
|
42
110
|
method: 'POST',
|
|
43
111
|
headers: {
|
|
44
112
|
'Authorization': `Bearer ${apiKey}`,
|
|
45
|
-
'Content-Type': 'application/json'
|
|
113
|
+
'Content-Type': 'application/json',
|
|
46
114
|
},
|
|
47
|
-
body: JSON.stringify({ query: '', k: 100 })
|
|
115
|
+
body: JSON.stringify({ query: '', k: 100 }),
|
|
48
116
|
});
|
|
49
117
|
let patternCount = 0;
|
|
50
118
|
let avgSuccessRate = 0;
|
|
@@ -60,13 +128,11 @@ async function status() {
|
|
|
60
128
|
}
|
|
61
129
|
}
|
|
62
130
|
spinner.stop();
|
|
63
|
-
// Display stats
|
|
64
131
|
console.log(chalk_1.default.cyan('Patterns:'));
|
|
65
132
|
console.log(` Total: ${chalk_1.default.bold(patternCount)}`);
|
|
66
133
|
console.log(` Success Rate: ${chalk_1.default.bold((avgSuccessRate * 100).toFixed(1) + '%')}`);
|
|
67
134
|
console.log(` Applications: ${chalk_1.default.bold(totalApplications)}`);
|
|
68
135
|
console.log('');
|
|
69
|
-
// IDE status
|
|
70
136
|
console.log(chalk_1.default.cyan('Connected IDEs:'));
|
|
71
137
|
const ides = config.installedIDEs || [];
|
|
72
138
|
if (ides.length === 0) {
|
|
@@ -78,13 +144,11 @@ async function status() {
|
|
|
78
144
|
}
|
|
79
145
|
}
|
|
80
146
|
console.log('');
|
|
81
|
-
// Config info
|
|
82
147
|
console.log(chalk_1.default.cyan('Configuration:'));
|
|
83
148
|
console.log(` Config File: ${chalk_1.default.gray(CONFIG_FILE)}`);
|
|
84
149
|
console.log(` Installed: ${chalk_1.default.gray(config.installedAt || 'Unknown')}`);
|
|
85
150
|
console.log(` API Key: ${chalk_1.default.gray(apiKey.substring(0, 10) + '...')}`);
|
|
86
151
|
console.log('');
|
|
87
|
-
// Golden Loop status
|
|
88
152
|
console.log(chalk_1.default.cyan('Golden Loop:'));
|
|
89
153
|
const loopActive = patternCount > 0 || totalApplications > 0;
|
|
90
154
|
if (loopActive) {
|
|
@@ -95,7 +159,6 @@ async function status() {
|
|
|
95
159
|
console.log(chalk_1.default.yellow(' ○ INITIALIZING - Start coding to build memory'));
|
|
96
160
|
}
|
|
97
161
|
console.log('');
|
|
98
|
-
// Footer
|
|
99
162
|
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
100
163
|
console.log('');
|
|
101
164
|
console.log(`Dashboard: ${chalk_1.default.cyan('https://ekkos.dev/dashboard')}`);
|
|
@@ -107,3 +170,78 @@ async function status() {
|
|
|
107
170
|
process.exit(1);
|
|
108
171
|
}
|
|
109
172
|
}
|
|
173
|
+
// ─── Single render pass ───────────────────────────────────────────────────────
|
|
174
|
+
function renderOnce(opts) {
|
|
175
|
+
const metrics = readMetrics();
|
|
176
|
+
if (opts.json) {
|
|
177
|
+
if (!metrics || isMetricsStale(metrics)) {
|
|
178
|
+
console.log(JSON.stringify({ active: false, reason: 'No active session or session stale' }));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(JSON.stringify({ active: true, ...metrics }));
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!metrics || isMetricsStale(metrics)) {
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(chalk_1.default.gray(' No active ekkOS session detected.'));
|
|
188
|
+
console.log(chalk_1.default.gray(' Run `ekkos run` to start a session.'));
|
|
189
|
+
console.log('');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
printSessionPanel(metrics);
|
|
193
|
+
}
|
|
194
|
+
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
195
|
+
async function status(opts = {}) {
|
|
196
|
+
// Watch mode: render every 2s, clear between renders
|
|
197
|
+
if (opts.watch) {
|
|
198
|
+
// Initial render
|
|
199
|
+
process.stdout.write('\x1B[2J\x1B[0f'); // clear screen
|
|
200
|
+
renderOnce({ json: false, skipMemory: true });
|
|
201
|
+
setInterval(() => {
|
|
202
|
+
process.stdout.write('\x1B[2J\x1B[0f');
|
|
203
|
+
renderOnce({ json: false, skipMemory: true });
|
|
204
|
+
}, 2000);
|
|
205
|
+
// Keep alive until Ctrl-C
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// JSON mode: just dump the metrics file
|
|
209
|
+
if (opts.json) {
|
|
210
|
+
renderOnce({ json: true, skipMemory: false });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Normal mode: show session panel (if active) then memory stats
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(chalk_1.default.cyan.bold('ekkOS Memory Status'));
|
|
216
|
+
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
217
|
+
// Session panel (live metrics from MCP server)
|
|
218
|
+
const metrics = readMetrics();
|
|
219
|
+
if (metrics && !isMetricsStale(metrics)) {
|
|
220
|
+
printSessionPanel(metrics);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log(chalk_1.default.gray(' No active session'));
|
|
225
|
+
console.log('');
|
|
226
|
+
}
|
|
227
|
+
// Memory API stats (requires config + network)
|
|
228
|
+
if (!(0, fs_1.existsSync)(CONFIG_FILE)) {
|
|
229
|
+
console.log(chalk_1.default.red(' ✗ Not configured — run: ekkos init'));
|
|
230
|
+
console.log('');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
let config;
|
|
234
|
+
try {
|
|
235
|
+
config = JSON.parse((0, fs_1.readFileSync)(CONFIG_FILE, 'utf-8'));
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
console.log(chalk_1.default.red(' ✗ Invalid configuration'));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const apiKey = config.apiKey || process.env.EKKOS_API_KEY;
|
|
242
|
+
if (!apiKey) {
|
|
243
|
+
console.log(chalk_1.default.red(' ✗ No API key — run: ekkos init'));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
await printMemoryStats(apiKey, config);
|
|
247
|
+
}
|