@ekkos/cli 1.3.6 → 1.3.8
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/commands/dashboard.js +96 -21
- package/dist/commands/gemini.js +46 -9
- package/dist/commands/init.js +92 -21
- package/dist/commands/living-docs.d.ts +8 -0
- package/dist/commands/living-docs.js +66 -0
- package/dist/commands/run.js +55 -6
- package/dist/commands/scan.d.ts +68 -0
- package/dist/commands/scan.js +318 -22
- package/dist/commands/setup.js +2 -6
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/deploy/instructions.d.ts +10 -3
- package/dist/deploy/instructions.js +34 -7
- package/dist/index.js +192 -79
- package/dist/lib/usage-parser.js +18 -2
- package/dist/local/index.d.ts +4 -0
- package/dist/local/index.js +14 -1
- package/dist/local/language-config.d.ts +55 -0
- package/dist/local/language-config.js +729 -0
- package/dist/local/living-docs-manager.d.ts +59 -0
- package/dist/local/living-docs-manager.js +1084 -0
- package/dist/local/stack-detection.d.ts +21 -0
- package/dist/local/stack-detection.js +406 -0
- package/package.json +1 -1
- package/templates/CLAUDE.md +89 -99
|
@@ -69,14 +69,24 @@ const usage_parser_js_1 = require("../lib/usage-parser.js");
|
|
|
69
69
|
const state_js_1 = require("../utils/state.js");
|
|
70
70
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
71
71
|
// ── Pricing ──
|
|
72
|
-
// Pricing per MTok from https://
|
|
72
|
+
// Pricing per MTok from https://docs.anthropic.com/en/docs/about-claude/pricing
|
|
73
|
+
// Includes prompt caching rates (5m cache write=1.25x input, cache read=0.1x input).
|
|
73
74
|
const MODEL_PRICING = {
|
|
75
|
+
'claude-opus-4-6-20260514': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
74
76
|
'claude-opus-4-6': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
75
|
-
'claude-opus-4-5-
|
|
77
|
+
'claude-opus-4-5-20251101': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
78
|
+
'claude-opus-4-5': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
79
|
+
'claude-opus-4-20250514': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
80
|
+
'claude-opus-4': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
81
|
+
'claude-sonnet-4-6-20260514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
76
82
|
'claude-sonnet-4-6': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
77
83
|
'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
84
|
+
'claude-sonnet-4-5': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
78
85
|
'claude-sonnet-4-5-20250514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
86
|
+
'claude-sonnet-4-20250514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
87
|
+
'claude-sonnet-4': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
79
88
|
'claude-haiku-4-5-20251001': { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 },
|
|
89
|
+
'claude-haiku-4-5': { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 },
|
|
80
90
|
};
|
|
81
91
|
function getModelPricing(modelId) {
|
|
82
92
|
if (MODEL_PRICING[modelId])
|
|
@@ -84,24 +94,55 @@ function getModelPricing(modelId) {
|
|
|
84
94
|
if (modelId.includes('opus'))
|
|
85
95
|
return MODEL_PRICING['claude-opus-4-6'];
|
|
86
96
|
if (modelId.includes('sonnet'))
|
|
87
|
-
return MODEL_PRICING['claude-sonnet-4-
|
|
97
|
+
return MODEL_PRICING['claude-sonnet-4-6'];
|
|
88
98
|
if (modelId.includes('haiku'))
|
|
89
99
|
return MODEL_PRICING['claude-haiku-4-5-20251001'];
|
|
90
|
-
return MODEL_PRICING['claude-sonnet-4-
|
|
100
|
+
return MODEL_PRICING['claude-sonnet-4-6'];
|
|
91
101
|
}
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
// Claude Sonnet 4/4.5 has legacy long-context premium rates when context-1m
|
|
103
|
+
// is enabled and input exceeds 200k. Sonnet/Opus 4.6 is GA 1M at standard rates.
|
|
104
|
+
function isLegacyLongContextPremiumModel(modelId) {
|
|
105
|
+
const normalized = (modelId || '').toLowerCase();
|
|
106
|
+
if (/^claude-sonnet-4-6(?:$|-)/.test(normalized))
|
|
107
|
+
return false;
|
|
108
|
+
return /^claude-sonnet-4(?:$|-)/.test(normalized);
|
|
109
|
+
}
|
|
110
|
+
function calculateTurnCost(model, usage, options) {
|
|
111
|
+
const base = getModelPricing(model);
|
|
112
|
+
const longContextPremium = options?.longContextPremium === true
|
|
113
|
+
&& isLegacyLongContextPremiumModel(model);
|
|
114
|
+
const p = longContextPremium
|
|
115
|
+
? {
|
|
116
|
+
// Long context pricing modifiers:
|
|
117
|
+
// Input/cache bucket = 2x, output = 1.5x.
|
|
118
|
+
input: base.input * 2,
|
|
119
|
+
output: base.output * 1.5,
|
|
120
|
+
cacheWrite: base.cacheWrite * 2,
|
|
121
|
+
cacheRead: base.cacheRead * 2,
|
|
122
|
+
}
|
|
123
|
+
: base;
|
|
94
124
|
return ((usage.input_tokens / 1000000) * p.input +
|
|
95
125
|
(usage.output_tokens / 1000000) * p.output +
|
|
96
126
|
(usage.cache_creation_tokens / 1000000) * p.cacheWrite +
|
|
97
127
|
(usage.cache_read_tokens / 1000000) * p.cacheRead);
|
|
98
128
|
}
|
|
99
|
-
function getModelCtxSize(model) {
|
|
100
|
-
|
|
129
|
+
function getModelCtxSize(model, contextTierHint) {
|
|
130
|
+
const normalized = (model || '').toLowerCase();
|
|
131
|
+
if (contextTierHint === '1m')
|
|
132
|
+
return 1000000;
|
|
133
|
+
// Claude 4.6 has GA 1M context in Claude Code/API.
|
|
134
|
+
if (/^claude-(?:opus|sonnet)-4-6(?:$|-)/.test(normalized))
|
|
135
|
+
return 1000000;
|
|
136
|
+
// Keep Gemini on the 1M class in dashboard context math.
|
|
137
|
+
if (normalized.startsWith('gemini-'))
|
|
138
|
+
return 1000000;
|
|
139
|
+
if (normalized.includes('1m'))
|
|
140
|
+
return 1000000;
|
|
141
|
+
if (normalized.includes('opus'))
|
|
101
142
|
return 200000;
|
|
102
|
-
if (
|
|
143
|
+
if (normalized.includes('haiku'))
|
|
103
144
|
return 200000;
|
|
104
|
-
if (
|
|
145
|
+
if (normalized.includes('sonnet'))
|
|
105
146
|
return 200000;
|
|
106
147
|
return 200000; // Default Anthropic context
|
|
107
148
|
}
|
|
@@ -183,12 +224,18 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
183
224
|
const evictionState = typeof entry.message._ekkos_eviction_state === 'string'
|
|
184
225
|
? entry.message._ekkos_eviction_state
|
|
185
226
|
: (parseCacheHintValue(cacheHint, 'eviction') || 'unknown');
|
|
227
|
+
const contextTierHint = typeof entry.message._ekkos_context_tier === 'string'
|
|
228
|
+
? entry.message._ekkos_context_tier.trim().toLowerCase()
|
|
229
|
+
: undefined;
|
|
230
|
+
const explicitExtraUsage = entry.message._ekkos_extra_usage === true
|
|
231
|
+
|| entry.message._ekkos_extra_usage === '1'
|
|
232
|
+
|| entry.message._ekkos_extra_usage === 'true';
|
|
186
233
|
const inputTokens = usage.input_tokens || 0;
|
|
187
234
|
const outputTokens = usage.output_tokens || 0;
|
|
188
235
|
const cacheReadTokens = usage.cache_read_input_tokens || 0;
|
|
189
236
|
const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
|
|
190
237
|
const contextTokens = inputTokens + cacheReadTokens + cacheCreationTokens;
|
|
191
|
-
const modelCtxSize = getModelCtxSize(
|
|
238
|
+
const modelCtxSize = getModelCtxSize(routedModel, contextTierHint);
|
|
192
239
|
const contextPct = (contextTokens / modelCtxSize) * 100;
|
|
193
240
|
const ts = entry.timestamp || new Date().toISOString();
|
|
194
241
|
if (!startedAt)
|
|
@@ -199,11 +246,19 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
199
246
|
cache_read_tokens: cacheReadTokens,
|
|
200
247
|
cache_creation_tokens: cacheCreationTokens,
|
|
201
248
|
};
|
|
249
|
+
const legacyLongContextPremium = isLegacyLongContextPremiumModel(routedModel) && (explicitExtraUsage
|
|
250
|
+
|| (contextTierHint === '1m' && contextTokens > 200000));
|
|
202
251
|
// Cost at actual model pricing (Haiku if routed)
|
|
203
|
-
const turnCost = calculateTurnCost(routedModel, usageData
|
|
252
|
+
const turnCost = calculateTurnCost(routedModel, usageData, {
|
|
253
|
+
longContextPremium: legacyLongContextPremium,
|
|
254
|
+
});
|
|
204
255
|
// Cost if it had been Opus (for savings calculation)
|
|
256
|
+
const requestedModelPremium = isLegacyLongContextPremiumModel(model) && (explicitExtraUsage
|
|
257
|
+
|| (contextTierHint === '1m' && contextTokens > 200000));
|
|
205
258
|
const opusCost = routedModel !== model
|
|
206
|
-
? calculateTurnCost(model, usageData
|
|
259
|
+
? calculateTurnCost(model, usageData, {
|
|
260
|
+
longContextPremium: requestedModelPremium,
|
|
261
|
+
})
|
|
207
262
|
: turnCost;
|
|
208
263
|
const savings = opusCost - turnCost;
|
|
209
264
|
const msgTools = toolsByMessage.get(msgId);
|
|
@@ -215,6 +270,7 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
215
270
|
const turnData = {
|
|
216
271
|
turn: turnNum,
|
|
217
272
|
contextPct,
|
|
273
|
+
modelContextSize: modelCtxSize,
|
|
218
274
|
input: inputTokens,
|
|
219
275
|
cacheRead: cacheReadTokens,
|
|
220
276
|
cacheCreate: cacheCreationTokens,
|
|
@@ -267,7 +323,9 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
267
323
|
const currentContextTokens = lastTurn
|
|
268
324
|
? lastTurn.input + lastTurn.cacheRead + lastTurn.cacheCreate
|
|
269
325
|
: 0;
|
|
270
|
-
const modelContextSize =
|
|
326
|
+
const modelContextSize = lastTurn
|
|
327
|
+
? lastTurn.modelContextSize
|
|
328
|
+
: getModelCtxSize(model);
|
|
271
329
|
return {
|
|
272
330
|
sessionName,
|
|
273
331
|
model,
|
|
@@ -369,7 +427,20 @@ function resolveJsonlPath(sessionName, createdAfterMs) {
|
|
|
369
427
|
if (fs.existsSync(activeSessionsPath)) {
|
|
370
428
|
try {
|
|
371
429
|
const sessions = JSON.parse(fs.readFileSync(activeSessionsPath, 'utf-8'));
|
|
372
|
-
|
|
430
|
+
// Skip stale entries whose PID is dead (prevents cross-binding after restart)
|
|
431
|
+
const match = sessions.find((s) => {
|
|
432
|
+
if (s.sessionName !== sessionName)
|
|
433
|
+
return false;
|
|
434
|
+
if (s.pid && s.pid > 1) {
|
|
435
|
+
try {
|
|
436
|
+
process.kill(s.pid, 0);
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return true;
|
|
443
|
+
});
|
|
373
444
|
if (match?.projectPath) {
|
|
374
445
|
if (isStableSessionId(match.sessionId)) {
|
|
375
446
|
// Prefer exact sessionId lookup, but if that file does not exist yet
|
|
@@ -1100,8 +1171,8 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1100
1171
|
try {
|
|
1101
1172
|
const ctxPct = Math.min(data.currentContextPct, 100);
|
|
1102
1173
|
const ctxColor = ctxPct < 50 ? 'green' : ctxPct < 80 ? 'yellow' : 'red';
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1174
|
+
const tokensLabel = fmtK(data.currentContextTokens);
|
|
1175
|
+
const maxLabel = fmtK(data.modelContextSize);
|
|
1105
1176
|
// Visual progress bar (fills available width)
|
|
1106
1177
|
const contextInnerWidth = Math.max(10, contextBox.width - 2);
|
|
1107
1178
|
// Extend bar slightly closer to mascot while keeping a small visual gap.
|
|
@@ -1121,7 +1192,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1121
1192
|
const hitColor = data.cacheHitRate >= 80 ? 'green' : data.cacheHitRate >= 50 ? 'yellow' : 'red';
|
|
1122
1193
|
const cappedMax = Math.min(data.maxContextPct, 100);
|
|
1123
1194
|
contextBox.setContent(` ${bar}\n` +
|
|
1124
|
-
` {${ctxColor}-fg}${ctxPct.toFixed(0)}%{/${ctxColor}-fg} ${
|
|
1195
|
+
` {${ctxColor}-fg}${ctxPct.toFixed(0)}%{/${ctxColor}-fg} ${tokensLabel}/${maxLabel}` +
|
|
1125
1196
|
` {white-fg}Input{/white-fg} $${breakdown.input.toFixed(2)}` +
|
|
1126
1197
|
` {green-fg}Read{/green-fg} $${breakdown.read.toFixed(2)}` +
|
|
1127
1198
|
` {yellow-fg}Write{/yellow-fg} $${breakdown.write.toFixed(2)}` +
|
|
@@ -1766,7 +1837,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1766
1837
|
const tier = cachedProfile.organization?.rate_limit_tier ?? 'unknown';
|
|
1767
1838
|
const status = cachedProfile.organization?.subscription_status ?? 'unknown';
|
|
1768
1839
|
const extraUsage = cachedProfile.organization?.has_extra_usage_enabled
|
|
1769
|
-
? ' {green-fg}extra usage on{/green-fg}'
|
|
1840
|
+
? ' {green-fg}extra usage on{/green-fg} {gray-fg}(legacy >200k only){/gray-fg}'
|
|
1770
1841
|
: '';
|
|
1771
1842
|
line2 =
|
|
1772
1843
|
` {bold}Plan:{/bold} ${plan} {bold}Tier:{/bold} ${tier} {bold}Sub:{/bold} ${status}${extraUsage}`;
|
|
@@ -2146,10 +2217,14 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
|
|
|
2146
2217
|
if (!sessionName)
|
|
2147
2218
|
process.exit(0);
|
|
2148
2219
|
}
|
|
2149
|
-
|
|
2220
|
+
// Use launch timestamp as lower bound so initial resolution never picks up
|
|
2221
|
+
// stale JSONL files from previous sessions (the lazy resolution already does
|
|
2222
|
+
// this correctly — this ensures the INITIAL resolve matches).
|
|
2223
|
+
const launchTs = Date.now();
|
|
2224
|
+
const jsonlPath = resolveJsonlPath(sessionName, launchTs);
|
|
2150
2225
|
if (!jsonlPath) {
|
|
2151
2226
|
// JSONL may not exist yet (session just started) — launch with lazy resolution
|
|
2152
2227
|
console.log(chalk_1.default.gray(`Waiting for JSONL for "${sessionName}"...`));
|
|
2153
2228
|
}
|
|
2154
|
-
await launchDashboard(sessionName, jsonlPath || null, refreshMs, null,
|
|
2229
|
+
await launchDashboard(sessionName, jsonlPath || null, refreshMs, null, launchTs);
|
|
2155
2230
|
});
|
package/dist/commands/gemini.js
CHANGED
|
@@ -59,6 +59,39 @@ const proxy_url_1 = require("../utils/proxy-url");
|
|
|
59
59
|
let cliSessionName = null;
|
|
60
60
|
let cliSessionId = null;
|
|
61
61
|
const isWindows = process.platform === 'win32';
|
|
62
|
+
const PULSE_LOADED_TEXT = ' 🧠 ekkOS_Pulse Loaded!';
|
|
63
|
+
const PULSE_SHINE_FRAME_MS = 50;
|
|
64
|
+
const PULSE_SHINE_SWEEPS = 2;
|
|
65
|
+
function sleep(ms) {
|
|
66
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
67
|
+
}
|
|
68
|
+
function renderPulseShineFrame(text, shineIndex) {
|
|
69
|
+
const chars = Array.from(text);
|
|
70
|
+
return chars.map((ch, i) => {
|
|
71
|
+
const dist = Math.abs(i - shineIndex);
|
|
72
|
+
if (dist === 0)
|
|
73
|
+
return chalk_1.default.whiteBright.bold(ch);
|
|
74
|
+
if (dist === 1)
|
|
75
|
+
return chalk_1.default.yellowBright.bold(ch);
|
|
76
|
+
if (dist === 2)
|
|
77
|
+
return chalk_1.default.yellowBright(ch);
|
|
78
|
+
return chalk_1.default.yellow(ch);
|
|
79
|
+
}).join('');
|
|
80
|
+
}
|
|
81
|
+
async function showPulseLoadedBanner() {
|
|
82
|
+
if (!process.stdout.isTTY) {
|
|
83
|
+
console.log(chalk_1.default.cyan(PULSE_LOADED_TEXT));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const charCount = Array.from(PULSE_LOADED_TEXT).length;
|
|
87
|
+
for (let sweep = 0; sweep < PULSE_SHINE_SWEEPS; sweep++) {
|
|
88
|
+
for (let shineIndex = -3; shineIndex < charCount + 3; shineIndex++) {
|
|
89
|
+
process.stdout.write(`\r${renderPulseShineFrame(PULSE_LOADED_TEXT, shineIndex)}`);
|
|
90
|
+
await sleep(PULSE_SHINE_FRAME_MS);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
process.stdout.write(`\r${chalk_1.default.yellow(PULSE_LOADED_TEXT)}\n`);
|
|
94
|
+
}
|
|
62
95
|
/**
|
|
63
96
|
* Resolve Gemini CLI binary path.
|
|
64
97
|
* Checks common locations then falls back to PATH lookup.
|
|
@@ -118,13 +151,6 @@ function buildGeminiEnv(options) {
|
|
|
118
151
|
const proxyUrl = (0, proxy_url_1.buildGeminiProxyUrl)(userId, cliSessionName, process.cwd(), cliSessionId);
|
|
119
152
|
env.GOOGLE_GEMINI_BASE_URL = proxyUrl;
|
|
120
153
|
env.GOOGLE_VERTEX_BASE_URL = proxyUrl;
|
|
121
|
-
// User must bring their own Gemini API key — ekkOS just proxies the traffic
|
|
122
|
-
// for IPC compression and pattern injection (same model as Claude).
|
|
123
|
-
if (!env.GEMINI_API_KEY && !env.GOOGLE_API_KEY) {
|
|
124
|
-
console.warn(chalk_1.default.yellow(' ⚠ GEMINI_API_KEY not set.'));
|
|
125
|
-
console.warn(chalk_1.default.gray(' Get one at: https://aistudio.google.com/apikey'));
|
|
126
|
-
console.warn(chalk_1.default.gray(' Then: export GEMINI_API_KEY=your_key'));
|
|
127
|
-
}
|
|
128
154
|
if (options.verbose) {
|
|
129
155
|
// Redact userId from log
|
|
130
156
|
const safeUrl = proxyUrl.replace(/\/proxy\/[^/]+\//, '/proxy/[user]/');
|
|
@@ -151,11 +177,22 @@ async function gemini(options = {}) {
|
|
|
151
177
|
const sessionName = cliSessionName || 'gemini-session';
|
|
152
178
|
(0, state_1.registerActiveSession)(sessionId, sessionName, process.cwd());
|
|
153
179
|
if (!options.noProxy) {
|
|
154
|
-
|
|
180
|
+
await showPulseLoadedBanner();
|
|
155
181
|
}
|
|
156
182
|
console.log('');
|
|
183
|
+
// Extract any trailing arguments meant for the inner CLI
|
|
184
|
+
// We look for 'gemini' in process.argv and pass everything after it.
|
|
185
|
+
// If the user did `ekkos gemini -m gemini-3.1-pro-preview`, we want to pass `-m gemini-3.1-pro-preview`
|
|
186
|
+
let extraArgs = [];
|
|
187
|
+
const geminiIdx = process.argv.indexOf('gemini');
|
|
188
|
+
if (geminiIdx !== -1) {
|
|
189
|
+
extraArgs = process.argv.slice(geminiIdx + 1).filter(a => {
|
|
190
|
+
// Filter out ekkos wrapper options
|
|
191
|
+
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');
|
|
192
|
+
});
|
|
193
|
+
}
|
|
157
194
|
// Spawn Gemini CLI — stdio: inherit for full terminal passthrough
|
|
158
|
-
const child = (0, child_process_1.spawn)(geminiPath,
|
|
195
|
+
const child = (0, child_process_1.spawn)(geminiPath, extraArgs, {
|
|
159
196
|
stdio: 'inherit',
|
|
160
197
|
env,
|
|
161
198
|
cwd: process.cwd(),
|
package/dist/commands/init.js
CHANGED
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.init = init;
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const path_1 = require("path");
|
|
8
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
11
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
12
|
const ora_1 = __importDefault(require("ora"));
|
|
@@ -15,6 +17,7 @@ const settings_1 = require("../deploy/settings");
|
|
|
15
17
|
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
16
18
|
// import { deployHooks } from '../deploy/hooks';
|
|
17
19
|
const skills_1 = require("../deploy/skills");
|
|
20
|
+
const agents_1 = require("../deploy/agents");
|
|
18
21
|
const instructions_1 = require("../deploy/instructions");
|
|
19
22
|
const templates_1 = require("../utils/templates");
|
|
20
23
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -34,15 +37,20 @@ async function pollForApproval(code, expiresIn) {
|
|
|
34
37
|
const pollInterval = 3000; // 3 seconds
|
|
35
38
|
const maxAttempts = Math.floor((expiresIn * 1000) / pollInterval);
|
|
36
39
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (response.ok) {
|
|
42
|
-
const data = await response.json();
|
|
43
|
-
if (data.status !== 'pending') {
|
|
44
|
-
return data;
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(`${platform_1.PLATFORM_URL}/api/device-auth/codes/${code}/status`, { method: 'GET' });
|
|
42
|
+
if (response.status === 410) {
|
|
43
|
+
return { status: 'expired' };
|
|
45
44
|
}
|
|
45
|
+
if (response.ok) {
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
if (data.status !== 'pending') {
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Network error — silently retry instead of killing the flow
|
|
46
54
|
}
|
|
47
55
|
// Wait before next poll
|
|
48
56
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
@@ -77,17 +85,33 @@ async function deviceAuth() {
|
|
|
77
85
|
throw error;
|
|
78
86
|
}
|
|
79
87
|
console.log('');
|
|
80
|
-
console.log(chalk_1.default.white.bold(`Your code: ${chalk_1.default.cyan.bold(deviceCode.code)}`));
|
|
88
|
+
console.log(chalk_1.default.white.bold(` Your code: ${chalk_1.default.cyan.bold(deviceCode.code)}`));
|
|
89
|
+
// Auto-copy code to clipboard
|
|
90
|
+
try {
|
|
91
|
+
if (process.platform === 'darwin') {
|
|
92
|
+
(0, child_process_1.execSync)(`echo -n "${deviceCode.code}" | pbcopy`, { stdio: 'ignore' });
|
|
93
|
+
}
|
|
94
|
+
else if (process.platform === 'win32') {
|
|
95
|
+
(0, child_process_1.execSync)(`echo|set /p="${deviceCode.code}" | clip`, { stdio: 'ignore' });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
(0, child_process_1.execSync)(`echo -n "${deviceCode.code}" | xclip -selection clipboard`, { stdio: 'ignore' });
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk_1.default.green(' ✓ Copied to clipboard'));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Clipboard not available — no problem, code is displayed
|
|
104
|
+
}
|
|
81
105
|
console.log('');
|
|
82
106
|
// Open browser
|
|
83
107
|
const verificationUrl = deviceCode.verificationUrl || `${platform_1.PLATFORM_URL}/activate`;
|
|
84
|
-
console.log(chalk_1.default.gray(`Opening browser
|
|
108
|
+
console.log(chalk_1.default.gray(` Opening browser → ${verificationUrl}`));
|
|
85
109
|
try {
|
|
86
110
|
await (0, open_1.default)(verificationUrl);
|
|
87
111
|
}
|
|
88
112
|
catch {
|
|
89
|
-
console.log(chalk_1.default.yellow('Could not open browser automatically.'));
|
|
90
|
-
console.log(chalk_1.default.gray(`Please visit: ${verificationUrl}`));
|
|
113
|
+
console.log(chalk_1.default.yellow(' Could not open browser automatically.'));
|
|
114
|
+
console.log(chalk_1.default.gray(` Please visit: ${verificationUrl}`));
|
|
91
115
|
}
|
|
92
116
|
console.log('');
|
|
93
117
|
// Poll for approval
|
|
@@ -217,13 +241,13 @@ async function selectIDEs(autoSelect = false) {
|
|
|
217
241
|
}
|
|
218
242
|
console.log('');
|
|
219
243
|
}
|
|
220
|
-
// Auto-select
|
|
244
|
+
// Auto-select: configure ALL detected IDEs (no prompt)
|
|
221
245
|
if (autoSelect || detectedList.length === 1) {
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
console.log(chalk_1.default.green(`✓ Auto-detected: ${chalk_1.default.bold(
|
|
246
|
+
const toSetup = detectedList.length > 0 ? detectedList : [current ?? 'claude'];
|
|
247
|
+
const names = toSetup.map(id => id === 'claude' ? 'Claude Code' : id === 'cursor' ? 'Cursor' : 'Windsurf');
|
|
248
|
+
console.log(chalk_1.default.green(`✓ Auto-detected: ${chalk_1.default.bold(names.join(', '))}`));
|
|
225
249
|
console.log('');
|
|
226
|
-
return
|
|
250
|
+
return toSetup;
|
|
227
251
|
}
|
|
228
252
|
const ideChoices = [
|
|
229
253
|
{ name: 'Claude Code', value: 'claude', checked: detectedList.includes('claude') || current === 'claude' },
|
|
@@ -249,6 +273,7 @@ async function deployForClaude(apiKey, userId, options) {
|
|
|
249
273
|
mcp: false,
|
|
250
274
|
settings: false,
|
|
251
275
|
skills: { count: 0, skills: [] },
|
|
276
|
+
agents: { count: 0, agents: [] },
|
|
252
277
|
instructions: false
|
|
253
278
|
};
|
|
254
279
|
// MCP configuration
|
|
@@ -283,12 +308,21 @@ async function deployForClaude(apiKey, userId, options) {
|
|
|
283
308
|
spinner.fail('Skills deployment failed');
|
|
284
309
|
}
|
|
285
310
|
}
|
|
286
|
-
//
|
|
311
|
+
// Agents (prune, rewind, scout, trace)
|
|
312
|
+
spinner = (0, ora_1.default)('Deploying ekkOS agents...').start();
|
|
313
|
+
try {
|
|
314
|
+
result.agents = (0, agents_1.deployAgents)();
|
|
315
|
+
spinner.succeed(`Agents (${result.agents.agents.join(', ')})`);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
spinner.fail('Agent deployment failed');
|
|
319
|
+
}
|
|
320
|
+
// CLAUDE.md (merge — never overwrites existing user content)
|
|
287
321
|
spinner = (0, ora_1.default)('Deploying global instructions...').start();
|
|
288
322
|
try {
|
|
289
323
|
(0, instructions_1.deployInstructions)();
|
|
290
324
|
result.instructions = true;
|
|
291
|
-
spinner.succeed('Global instructions (CLAUDE.md)');
|
|
325
|
+
spinner.succeed('Global instructions (CLAUDE.md — merged)');
|
|
292
326
|
}
|
|
293
327
|
catch (error) {
|
|
294
328
|
spinner.fail('Global instructions failed');
|
|
@@ -461,6 +495,40 @@ async function init(options) {
|
|
|
461
495
|
catch {
|
|
462
496
|
verifySpinner.warn('Could not reach ekkOS API — check your network');
|
|
463
497
|
}
|
|
498
|
+
// Phase: Scaffold ekkos.yml if not present in the current directory or git root
|
|
499
|
+
const cwd = process.cwd();
|
|
500
|
+
let projectRoot = cwd;
|
|
501
|
+
try {
|
|
502
|
+
projectRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
503
|
+
}
|
|
504
|
+
catch { /* not a git repo — use cwd */ }
|
|
505
|
+
const ekkosYmlPath = (0, path_1.join)(projectRoot, 'ekkos.yml');
|
|
506
|
+
if (!(0, fs_1.existsSync)(ekkosYmlPath)) {
|
|
507
|
+
const projectName = (0, path_1.basename)(projectRoot);
|
|
508
|
+
const hasPackageJson = (0, fs_1.existsSync)((0, path_1.join)(projectRoot, 'package.json'));
|
|
509
|
+
const hasCargo = (0, fs_1.existsSync)((0, path_1.join)(projectRoot, 'Cargo.toml'));
|
|
510
|
+
const buildCmd = hasPackageJson ? 'npm run build' : hasCargo ? 'cargo build' : '';
|
|
511
|
+
const testCmd = hasPackageJson ? 'npm test' : hasCargo ? 'cargo test' : '';
|
|
512
|
+
const lintCmd = hasPackageJson ? 'npm run lint' : hasCargo ? 'cargo clippy' : '';
|
|
513
|
+
const ymlLines = [
|
|
514
|
+
'version: 1',
|
|
515
|
+
`project: "${projectName}"`,
|
|
516
|
+
'',
|
|
517
|
+
'ans:',
|
|
518
|
+
' tier: "R1"',
|
|
519
|
+
];
|
|
520
|
+
if (buildCmd)
|
|
521
|
+
ymlLines.push(` build: "${buildCmd}"`);
|
|
522
|
+
if (testCmd)
|
|
523
|
+
ymlLines.push(` test: "${testCmd}"`);
|
|
524
|
+
if (lintCmd)
|
|
525
|
+
ymlLines.push(` lint: "${lintCmd}"`);
|
|
526
|
+
ymlLines.push('');
|
|
527
|
+
(0, fs_1.writeFileSync)(ekkosYmlPath, ymlLines.join('\n'), 'utf-8');
|
|
528
|
+
console.log('');
|
|
529
|
+
console.log(chalk_1.default.green(` Created ${chalk_1.default.bold('ekkos.yml')} in ${projectRoot}`));
|
|
530
|
+
console.log(chalk_1.default.gray(` Customize: ${chalk_1.default.cyan('https://platform.ekkos.dev/dashboard/settings/project')}`));
|
|
531
|
+
}
|
|
464
532
|
// Summary with prominent next step
|
|
465
533
|
const ideNames = installedIDEs.map(id => id === 'claude' ? 'Claude Code' : id === 'cursor' ? 'Cursor' : 'Windsurf');
|
|
466
534
|
const mcpPaths = {
|
|
@@ -488,8 +556,11 @@ async function init(options) {
|
|
|
488
556
|
console.log(chalk_1.default.yellow.bold(' NEXT STEPS:'));
|
|
489
557
|
console.log('');
|
|
490
558
|
console.log(chalk_1.default.white(` 1. Restart ${ideNames.join(' / ')}`));
|
|
491
|
-
console.log(chalk_1.default.white(' 2. Run ') + chalk_1.default.cyan.bold('ekkos') + chalk_1.default.white(' to
|
|
559
|
+
console.log(chalk_1.default.white(' 2. Run ') + chalk_1.default.cyan.bold('ekkos scan') + chalk_1.default.white(' to map your project systems'));
|
|
560
|
+
console.log(chalk_1.default.white(' 3. Run ') + chalk_1.default.cyan.bold('ekkos') + chalk_1.default.white(' to start coding with memory'));
|
|
492
561
|
console.log('');
|
|
493
|
-
console.log(chalk_1.default.gray(` Dashboard:
|
|
562
|
+
console.log(chalk_1.default.gray(` Dashboard: https://platform.ekkos.dev/dashboard`));
|
|
563
|
+
console.log(chalk_1.default.gray(` Configure ANS: https://platform.ekkos.dev/dashboard/settings/project`));
|
|
564
|
+
console.log(chalk_1.default.gray(` Connect vitals: https://platform.ekkos.dev/dashboard/settings/vitals`));
|
|
494
565
|
console.log('');
|
|
495
566
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.watchLivingDocs = watchLivingDocs;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const living_docs_manager_js_1 = require("../local/living-docs-manager.js");
|
|
10
|
+
const state_1 = require("../utils/state");
|
|
11
|
+
const platform_js_1 = require("../utils/platform.js");
|
|
12
|
+
function printStartupSummary(options) {
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(chalk_1.default.cyan.bold(' ekkOS Living Docs Watch'));
|
|
15
|
+
console.log(chalk_1.default.gray(' ─────────────────────────'));
|
|
16
|
+
console.log(chalk_1.default.gray(` Path: ${options.targetPath}`));
|
|
17
|
+
console.log(chalk_1.default.gray(` Timezone: ${options.timeZone}`));
|
|
18
|
+
console.log(chalk_1.default.gray(` Registry seed: ${options.seedingEnabled ? 'enabled' : 'disabled'}`));
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(chalk_1.default.gray(' Watching local files and rewriting ekkOS_CONTEXT.md on change.'));
|
|
21
|
+
console.log(chalk_1.default.gray(' Press Ctrl+C to stop.'));
|
|
22
|
+
console.log('');
|
|
23
|
+
}
|
|
24
|
+
async function watchLivingDocs(options) {
|
|
25
|
+
const targetPath = (0, path_1.resolve)(options.path || process.cwd());
|
|
26
|
+
const timeZone = options.timeZone || process.env.EKKOS_USER_TIMEZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
27
|
+
const apiKey = options.noSeed ? null : (0, state_1.getAuthToken)();
|
|
28
|
+
const apiUrl = options.noSeed ? undefined : (process.env.EKKOS_API_URL || platform_js_1.MCP_API_URL);
|
|
29
|
+
const manager = new living_docs_manager_js_1.LocalLivingDocsManager({
|
|
30
|
+
targetPath,
|
|
31
|
+
apiUrl,
|
|
32
|
+
apiKey,
|
|
33
|
+
timeZone,
|
|
34
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
35
|
+
flushDebounceMs: options.debounceMs,
|
|
36
|
+
onLog: message => console.log(chalk_1.default.gray(` ${message}`)),
|
|
37
|
+
});
|
|
38
|
+
printStartupSummary({
|
|
39
|
+
targetPath,
|
|
40
|
+
timeZone,
|
|
41
|
+
seedingEnabled: !!(apiUrl && apiKey),
|
|
42
|
+
});
|
|
43
|
+
manager.start();
|
|
44
|
+
await new Promise((resolvePromise) => {
|
|
45
|
+
let stopped = false;
|
|
46
|
+
const stop = () => {
|
|
47
|
+
if (stopped)
|
|
48
|
+
return;
|
|
49
|
+
stopped = true;
|
|
50
|
+
manager.stop();
|
|
51
|
+
process.off('SIGINT', handleSigInt);
|
|
52
|
+
process.off('SIGTERM', handleSigTerm);
|
|
53
|
+
resolvePromise();
|
|
54
|
+
};
|
|
55
|
+
const handleSigInt = () => {
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(chalk_1.default.gray(' Stopping living docs watcher...'));
|
|
58
|
+
stop();
|
|
59
|
+
};
|
|
60
|
+
const handleSigTerm = () => {
|
|
61
|
+
stop();
|
|
62
|
+
};
|
|
63
|
+
process.on('SIGINT', handleSigInt);
|
|
64
|
+
process.on('SIGTERM', handleSigTerm);
|
|
65
|
+
});
|
|
66
|
+
}
|