@ekkos/cli 1.3.7 → 1.3.9
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 +67 -11
- package/dist/commands/gemini.js +45 -18
- 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.d.ts +5 -0
- package/dist/commands/run.js +473 -15
- 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 +170 -87
- 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/dist/utils/proxy-url.d.ts +6 -1
- package/dist/utils/proxy-url.js +16 -2
- package/package.json +31 -17
- package/templates/CLAUDE.md +89 -99
- package/templates/agents/prune.md +83 -0
- package/templates/agents/rewind.md +84 -0
- package/templates/agents/scout.md +102 -0
- package/templates/agents/trace.md +99 -0
- package/templates/commands/continue.md +47 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack Detection — detect project language, framework, and package manager
|
|
3
|
+
*
|
|
4
|
+
* Pure functions with zero dependencies. Works on both local filesystem
|
|
5
|
+
* (via file list) and remote (via GitHub tree listing).
|
|
6
|
+
*
|
|
7
|
+
* Living Docs V4 — Phase 4: Language-Adapted Discovery
|
|
8
|
+
*/
|
|
9
|
+
export interface StackInfo {
|
|
10
|
+
language: string;
|
|
11
|
+
framework?: string;
|
|
12
|
+
packageManager?: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Detect the stack for a directory by checking for manifest files.
|
|
17
|
+
* Works on both local filesystem (check file existence) and remote (file list).
|
|
18
|
+
* @param files - list of relative file paths in the directory
|
|
19
|
+
* @param readFile - optional async function to read file contents for deeper detection
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectStack(files: string[], readFile?: (path: string) => Promise<string | null>): Promise<StackInfo>;
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stack Detection — detect project language, framework, and package manager
|
|
4
|
+
*
|
|
5
|
+
* Pure functions with zero dependencies. Works on both local filesystem
|
|
6
|
+
* (via file list) and remote (via GitHub tree listing).
|
|
7
|
+
*
|
|
8
|
+
* Living Docs V4 — Phase 4: Language-Adapted Discovery
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.detectStack = detectStack;
|
|
12
|
+
/**
|
|
13
|
+
* Detect the stack for a directory by checking for manifest files.
|
|
14
|
+
* Works on both local filesystem (check file existence) and remote (file list).
|
|
15
|
+
* @param files - list of relative file paths in the directory
|
|
16
|
+
* @param readFile - optional async function to read file contents for deeper detection
|
|
17
|
+
*/
|
|
18
|
+
async function detectStack(files, readFile) {
|
|
19
|
+
const fileSet = new Set(files.map(f => f.replace(/^\.\//, '')));
|
|
20
|
+
const hasFile = (name) => fileSet.has(name);
|
|
21
|
+
const hasGlob = (pattern) => {
|
|
22
|
+
// Simple suffix match for patterns like '*.csproj'
|
|
23
|
+
if (pattern.startsWith('*')) {
|
|
24
|
+
const suffix = pattern.slice(1);
|
|
25
|
+
return files.some(f => f.endsWith(suffix));
|
|
26
|
+
}
|
|
27
|
+
return fileSet.has(pattern);
|
|
28
|
+
};
|
|
29
|
+
// ── Package manager detection ──────────────────────────────────────────
|
|
30
|
+
const packageManager = detectPackageManager(fileSet);
|
|
31
|
+
// ── Language + framework detection (ordered by specificity) ────────────
|
|
32
|
+
// TypeScript / JavaScript
|
|
33
|
+
if (hasFile('package.json')) {
|
|
34
|
+
const info = await detectNodeStack(readFile);
|
|
35
|
+
return { ...info, packageManager: packageManager ?? info.packageManager };
|
|
36
|
+
}
|
|
37
|
+
// Rust
|
|
38
|
+
if (hasFile('Cargo.toml')) {
|
|
39
|
+
const info = await detectRustStack(readFile);
|
|
40
|
+
return { ...info, packageManager: packageManager ?? 'cargo' };
|
|
41
|
+
}
|
|
42
|
+
// Go
|
|
43
|
+
if (hasFile('go.mod')) {
|
|
44
|
+
const info = await detectGoStack(readFile);
|
|
45
|
+
return { ...info, packageManager: packageManager ?? 'go' };
|
|
46
|
+
}
|
|
47
|
+
// Python
|
|
48
|
+
if (hasFile('pyproject.toml') || hasFile('setup.py') || hasFile('requirements.txt') || hasFile('Pipfile') || hasFile('setup.cfg')) {
|
|
49
|
+
const info = await detectPythonStack(fileSet, readFile);
|
|
50
|
+
return { ...info, packageManager: packageManager ?? info.packageManager };
|
|
51
|
+
}
|
|
52
|
+
// Ruby
|
|
53
|
+
if (hasFile('Gemfile')) {
|
|
54
|
+
const info = await detectRubyStack(readFile);
|
|
55
|
+
return { ...info, packageManager: packageManager ?? 'bundle' };
|
|
56
|
+
}
|
|
57
|
+
// Java / Kotlin
|
|
58
|
+
if (hasFile('pom.xml') || hasFile('build.gradle') || hasFile('build.gradle.kts') || hasFile('settings.gradle') || hasFile('settings.gradle.kts')) {
|
|
59
|
+
const info = await detectJavaStack(fileSet, readFile);
|
|
60
|
+
return { ...info, packageManager: packageManager ?? info.packageManager };
|
|
61
|
+
}
|
|
62
|
+
// Dart / Flutter
|
|
63
|
+
if (hasFile('pubspec.yaml')) {
|
|
64
|
+
return { language: 'dart', framework: 'flutter', packageManager: 'pub' };
|
|
65
|
+
}
|
|
66
|
+
// Elixir / Phoenix
|
|
67
|
+
if (hasFile('mix.exs')) {
|
|
68
|
+
const info = await detectElixirStack(readFile);
|
|
69
|
+
return { ...info, packageManager: 'mix' };
|
|
70
|
+
}
|
|
71
|
+
// PHP / Laravel
|
|
72
|
+
if (hasFile('composer.json')) {
|
|
73
|
+
const info = await detectPhpStack(readFile);
|
|
74
|
+
return { ...info, packageManager: 'composer' };
|
|
75
|
+
}
|
|
76
|
+
// C# / .NET
|
|
77
|
+
if (hasGlob('*.csproj') || hasGlob('*.sln') || hasGlob('*.fsproj')) {
|
|
78
|
+
return { language: 'csharp', framework: 'dotnet', packageManager: 'nuget' };
|
|
79
|
+
}
|
|
80
|
+
// C/C++ — only if no other manifest was found
|
|
81
|
+
if (hasFile('Makefile') || hasFile('CMakeLists.txt') || hasFile('meson.build')) {
|
|
82
|
+
return { language: 'cpp', packageManager: packageManager ?? undefined };
|
|
83
|
+
}
|
|
84
|
+
return { language: 'unknown', packageManager: packageManager ?? undefined };
|
|
85
|
+
}
|
|
86
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
87
|
+
function detectPackageManager(fileSet) {
|
|
88
|
+
if (fileSet.has('pnpm-lock.yaml') || fileSet.has('pnpm-workspace.yaml'))
|
|
89
|
+
return 'pnpm';
|
|
90
|
+
if (fileSet.has('yarn.lock'))
|
|
91
|
+
return 'yarn';
|
|
92
|
+
if (fileSet.has('package-lock.json'))
|
|
93
|
+
return 'npm';
|
|
94
|
+
if (fileSet.has('bun.lockb') || fileSet.has('bun.lock'))
|
|
95
|
+
return 'bun';
|
|
96
|
+
if (fileSet.has('poetry.lock'))
|
|
97
|
+
return 'poetry';
|
|
98
|
+
if (fileSet.has('Pipfile.lock'))
|
|
99
|
+
return 'pipenv';
|
|
100
|
+
if (fileSet.has('uv.lock'))
|
|
101
|
+
return 'uv';
|
|
102
|
+
if (fileSet.has('Cargo.lock'))
|
|
103
|
+
return 'cargo';
|
|
104
|
+
if (fileSet.has('go.sum'))
|
|
105
|
+
return 'go';
|
|
106
|
+
if (fileSet.has('Gemfile.lock'))
|
|
107
|
+
return 'bundle';
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
async function detectNodeStack(readFile) {
|
|
111
|
+
const base = { language: 'typescript', packageManager: 'npm' };
|
|
112
|
+
if (!readFile)
|
|
113
|
+
return base;
|
|
114
|
+
const raw = await readFile('package.json');
|
|
115
|
+
if (!raw)
|
|
116
|
+
return base;
|
|
117
|
+
try {
|
|
118
|
+
const pkg = JSON.parse(raw);
|
|
119
|
+
const allDeps = {
|
|
120
|
+
...pkg.dependencies,
|
|
121
|
+
...pkg.devDependencies,
|
|
122
|
+
};
|
|
123
|
+
// Check if actually TypeScript
|
|
124
|
+
if (!allDeps['typescript'] && !allDeps['ts-node'] && !allDeps['tsx']) {
|
|
125
|
+
base.language = 'javascript';
|
|
126
|
+
}
|
|
127
|
+
// Framework detection — ordered by specificity (most specific first)
|
|
128
|
+
const frameworkMap = [
|
|
129
|
+
['next', 'next'],
|
|
130
|
+
['nuxt', 'nuxt'],
|
|
131
|
+
['remix', 'remix'],
|
|
132
|
+
['@sveltejs/kit', 'sveltekit'],
|
|
133
|
+
['svelte', 'svelte'],
|
|
134
|
+
['@angular/core', 'angular'],
|
|
135
|
+
['vue', 'vue'],
|
|
136
|
+
['react', 'react'],
|
|
137
|
+
['hono', 'hono'],
|
|
138
|
+
['fastify', 'fastify'],
|
|
139
|
+
['express', 'express'],
|
|
140
|
+
['koa', 'koa'],
|
|
141
|
+
['@nestjs/core', 'nest'],
|
|
142
|
+
['astro', 'astro'],
|
|
143
|
+
['gatsby', 'gatsby'],
|
|
144
|
+
['@electron/rebuild', 'electron'],
|
|
145
|
+
['electron', 'electron'],
|
|
146
|
+
];
|
|
147
|
+
for (const [dep, framework] of frameworkMap) {
|
|
148
|
+
if (allDeps[dep]) {
|
|
149
|
+
base.framework = framework;
|
|
150
|
+
base.version = allDeps[dep]?.replace(/^[\^~>=<]/, '');
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return base;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return base;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function detectRustStack(readFile) {
|
|
161
|
+
const base = { language: 'rust', packageManager: 'cargo' };
|
|
162
|
+
if (!readFile)
|
|
163
|
+
return base;
|
|
164
|
+
const raw = await readFile('Cargo.toml');
|
|
165
|
+
if (!raw)
|
|
166
|
+
return base;
|
|
167
|
+
const content = raw.toLowerCase();
|
|
168
|
+
const frameworkMap = [
|
|
169
|
+
['actix-web', 'actix'],
|
|
170
|
+
['axum', 'axum'],
|
|
171
|
+
['rocket', 'rocket'],
|
|
172
|
+
['warp', 'warp'],
|
|
173
|
+
['tide', 'tide'],
|
|
174
|
+
['tokio', 'tokio'],
|
|
175
|
+
['leptos', 'leptos'],
|
|
176
|
+
['yew', 'yew'],
|
|
177
|
+
['tauri', 'tauri'],
|
|
178
|
+
];
|
|
179
|
+
for (const [dep, framework] of frameworkMap) {
|
|
180
|
+
if (content.includes(dep)) {
|
|
181
|
+
base.framework = framework;
|
|
182
|
+
// Try to extract version: dep = "X.Y.Z" or dep = { version = "X.Y.Z" }
|
|
183
|
+
const versionMatch = raw.match(new RegExp(`${dep}\\s*=\\s*"([^"]+)"`, 'i'))
|
|
184
|
+
|| raw.match(new RegExp(`${dep}\\s*=\\s*\\{[^}]*version\\s*=\\s*"([^"]+)"`, 'i'));
|
|
185
|
+
if (versionMatch)
|
|
186
|
+
base.version = versionMatch[1];
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return base;
|
|
191
|
+
}
|
|
192
|
+
async function detectGoStack(readFile) {
|
|
193
|
+
const base = { language: 'go', packageManager: 'go' };
|
|
194
|
+
if (!readFile)
|
|
195
|
+
return base;
|
|
196
|
+
const raw = await readFile('go.mod');
|
|
197
|
+
if (!raw)
|
|
198
|
+
return base;
|
|
199
|
+
const frameworkMap = [
|
|
200
|
+
['github.com/gin-gonic/gin', 'gin'],
|
|
201
|
+
['github.com/labstack/echo', 'echo'],
|
|
202
|
+
['github.com/gofiber/fiber', 'fiber'],
|
|
203
|
+
['github.com/go-chi/chi', 'chi'],
|
|
204
|
+
['github.com/gorilla/mux', 'gorilla'],
|
|
205
|
+
['github.com/beego/beego', 'beego'],
|
|
206
|
+
['github.com/revel/revel', 'revel'],
|
|
207
|
+
['google.golang.org/grpc', 'grpc'],
|
|
208
|
+
['github.com/bufbuild/connect-go', 'connect'],
|
|
209
|
+
];
|
|
210
|
+
for (const [dep, framework] of frameworkMap) {
|
|
211
|
+
if (raw.includes(dep)) {
|
|
212
|
+
base.framework = framework;
|
|
213
|
+
// Try to extract version from go.mod require block
|
|
214
|
+
const versionMatch = raw.match(new RegExp(`${dep.replace(/\//g, '\\/')}\\s+v([\\d.]+)`));
|
|
215
|
+
if (versionMatch)
|
|
216
|
+
base.version = versionMatch[1];
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return base;
|
|
221
|
+
}
|
|
222
|
+
async function detectPythonStack(fileSet, readFile) {
|
|
223
|
+
const base = { language: 'python', packageManager: 'pip' };
|
|
224
|
+
// Refine package manager
|
|
225
|
+
if (fileSet.has('poetry.lock') || fileSet.has('pyproject.toml'))
|
|
226
|
+
base.packageManager = 'poetry';
|
|
227
|
+
if (fileSet.has('Pipfile') || fileSet.has('Pipfile.lock'))
|
|
228
|
+
base.packageManager = 'pipenv';
|
|
229
|
+
if (fileSet.has('uv.lock'))
|
|
230
|
+
base.packageManager = 'uv';
|
|
231
|
+
if (!readFile)
|
|
232
|
+
return base;
|
|
233
|
+
// Read all possible manifest files for framework detection
|
|
234
|
+
const contents = [];
|
|
235
|
+
for (const manifest of ['requirements.txt', 'pyproject.toml', 'Pipfile', 'setup.py', 'setup.cfg']) {
|
|
236
|
+
if (fileSet.has(manifest)) {
|
|
237
|
+
const raw = await readFile(manifest);
|
|
238
|
+
if (raw)
|
|
239
|
+
contents.push(raw.toLowerCase());
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const combined = contents.join('\n');
|
|
243
|
+
const frameworkMap = [
|
|
244
|
+
['django', 'django'],
|
|
245
|
+
['fastapi', 'fastapi'],
|
|
246
|
+
['flask', 'flask'],
|
|
247
|
+
['starlette', 'starlette'],
|
|
248
|
+
['tornado', 'tornado'],
|
|
249
|
+
['sanic', 'sanic'],
|
|
250
|
+
['aiohttp', 'aiohttp'],
|
|
251
|
+
['streamlit', 'streamlit'],
|
|
252
|
+
['gradio', 'gradio'],
|
|
253
|
+
['celery', 'celery'],
|
|
254
|
+
['scrapy', 'scrapy'],
|
|
255
|
+
];
|
|
256
|
+
for (const [dep, framework] of frameworkMap) {
|
|
257
|
+
if (combined.includes(dep)) {
|
|
258
|
+
base.framework = framework;
|
|
259
|
+
// Try to extract version from requirements.txt format: django==4.2.0
|
|
260
|
+
const versionMatch = combined.match(new RegExp(`${dep}[=~><]+([\\d.]+)`));
|
|
261
|
+
if (versionMatch)
|
|
262
|
+
base.version = versionMatch[1];
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return base;
|
|
267
|
+
}
|
|
268
|
+
async function detectRubyStack(readFile) {
|
|
269
|
+
const base = { language: 'ruby', packageManager: 'bundle' };
|
|
270
|
+
if (!readFile)
|
|
271
|
+
return base;
|
|
272
|
+
const raw = await readFile('Gemfile');
|
|
273
|
+
if (!raw)
|
|
274
|
+
return base;
|
|
275
|
+
const content = raw.toLowerCase();
|
|
276
|
+
const frameworkMap = [
|
|
277
|
+
['rails', 'rails'],
|
|
278
|
+
['sinatra', 'sinatra'],
|
|
279
|
+
['hanami', 'hanami'],
|
|
280
|
+
['grape', 'grape'],
|
|
281
|
+
['roda', 'roda'],
|
|
282
|
+
];
|
|
283
|
+
for (const [dep, framework] of frameworkMap) {
|
|
284
|
+
if (content.includes(`'${dep}'`) || content.includes(`"${dep}"`)) {
|
|
285
|
+
base.framework = framework;
|
|
286
|
+
// Try to extract version: gem 'rails', '~> 7.0'
|
|
287
|
+
const versionMatch = raw.match(new RegExp(`['"]${dep}['"]\\s*,\\s*['"][~>=<]*\\s*([\\d.]+)['"]`, 'i'));
|
|
288
|
+
if (versionMatch)
|
|
289
|
+
base.version = versionMatch[1];
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return base;
|
|
294
|
+
}
|
|
295
|
+
async function detectJavaStack(fileSet, readFile) {
|
|
296
|
+
const base = { language: 'java' };
|
|
297
|
+
// Package manager
|
|
298
|
+
if (fileSet.has('pom.xml')) {
|
|
299
|
+
base.packageManager = 'maven';
|
|
300
|
+
}
|
|
301
|
+
else if (fileSet.has('build.gradle') || fileSet.has('build.gradle.kts')) {
|
|
302
|
+
base.packageManager = 'gradle';
|
|
303
|
+
}
|
|
304
|
+
if (!readFile)
|
|
305
|
+
return base;
|
|
306
|
+
// Check for Kotlin
|
|
307
|
+
const gradleKts = fileSet.has('build.gradle.kts');
|
|
308
|
+
if (gradleKts) {
|
|
309
|
+
const raw = await readFile('build.gradle.kts');
|
|
310
|
+
if (raw && (raw.includes('kotlin') || raw.includes('org.jetbrains.kotlin'))) {
|
|
311
|
+
base.language = 'kotlin';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Framework detection from build files
|
|
315
|
+
const buildFiles = ['pom.xml', 'build.gradle', 'build.gradle.kts'];
|
|
316
|
+
for (const buildFile of buildFiles) {
|
|
317
|
+
if (!fileSet.has(buildFile))
|
|
318
|
+
continue;
|
|
319
|
+
const raw = await readFile(buildFile);
|
|
320
|
+
if (!raw)
|
|
321
|
+
continue;
|
|
322
|
+
const content = raw.toLowerCase();
|
|
323
|
+
if (content.includes('spring-boot') || content.includes('springframework')) {
|
|
324
|
+
base.framework = 'spring';
|
|
325
|
+
const versionMatch = raw.match(/spring-boot[^'"]*['"](\d+\.\d+\.\d+)['"]/i)
|
|
326
|
+
|| raw.match(/<spring-boot\.version>(\d+\.\d+\.\d+)<\/spring-boot\.version>/i);
|
|
327
|
+
if (versionMatch)
|
|
328
|
+
base.version = versionMatch[1];
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (content.includes('quarkus')) {
|
|
332
|
+
base.framework = 'quarkus';
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
if (content.includes('micronaut')) {
|
|
336
|
+
base.framework = 'micronaut';
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
if (content.includes('ktor')) {
|
|
340
|
+
base.framework = 'ktor';
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
if (content.includes('vertx') || content.includes('vert.x')) {
|
|
344
|
+
base.framework = 'vertx';
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return base;
|
|
349
|
+
}
|
|
350
|
+
async function detectElixirStack(readFile) {
|
|
351
|
+
const base = { language: 'elixir', packageManager: 'mix' };
|
|
352
|
+
if (!readFile)
|
|
353
|
+
return base;
|
|
354
|
+
const raw = await readFile('mix.exs');
|
|
355
|
+
if (!raw)
|
|
356
|
+
return base;
|
|
357
|
+
const content = raw.toLowerCase();
|
|
358
|
+
if (content.includes(':phoenix')) {
|
|
359
|
+
base.framework = 'phoenix';
|
|
360
|
+
const versionMatch = raw.match(/:phoenix\s*,\s*"~>\s*([\d.]+)"/);
|
|
361
|
+
if (versionMatch)
|
|
362
|
+
base.version = versionMatch[1];
|
|
363
|
+
}
|
|
364
|
+
else if (content.includes(':plug')) {
|
|
365
|
+
base.framework = 'plug';
|
|
366
|
+
}
|
|
367
|
+
else if (content.includes(':nerves')) {
|
|
368
|
+
base.framework = 'nerves';
|
|
369
|
+
}
|
|
370
|
+
return base;
|
|
371
|
+
}
|
|
372
|
+
async function detectPhpStack(readFile) {
|
|
373
|
+
const base = { language: 'php', packageManager: 'composer' };
|
|
374
|
+
if (!readFile)
|
|
375
|
+
return base;
|
|
376
|
+
const raw = await readFile('composer.json');
|
|
377
|
+
if (!raw)
|
|
378
|
+
return base;
|
|
379
|
+
try {
|
|
380
|
+
const composer = JSON.parse(raw);
|
|
381
|
+
const allDeps = {
|
|
382
|
+
...composer.require,
|
|
383
|
+
...composer['require-dev'],
|
|
384
|
+
};
|
|
385
|
+
const frameworkMap = [
|
|
386
|
+
['laravel/framework', 'laravel'],
|
|
387
|
+
['symfony/symfony', 'symfony'],
|
|
388
|
+
['symfony/framework-bundle', 'symfony'],
|
|
389
|
+
['slim/slim', 'slim'],
|
|
390
|
+
['cakephp/cakephp', 'cakephp'],
|
|
391
|
+
['codeigniter4/framework', 'codeigniter'],
|
|
392
|
+
['yiisoft/yii2', 'yii'],
|
|
393
|
+
];
|
|
394
|
+
for (const [dep, framework] of frameworkMap) {
|
|
395
|
+
if (allDeps[dep]) {
|
|
396
|
+
base.framework = framework;
|
|
397
|
+
base.version = allDeps[dep]?.replace(/^[\^~>=<*]/, '');
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return base;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
return base;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
* Used by: commands/run.ts, commands/test-claude.ts, commands/gemini.ts
|
|
4
4
|
*/
|
|
5
5
|
export declare const EKKOS_PROXY_URL: string;
|
|
6
|
+
export interface ClaudeProxyLaunchOptions {
|
|
7
|
+
claudeCodeVersion?: string;
|
|
8
|
+
claudeProfile?: string;
|
|
9
|
+
claudeContextWindow?: 'auto' | '200k' | '1m';
|
|
10
|
+
}
|
|
6
11
|
/**
|
|
7
12
|
* Build a fully-qualified proxy URL with user/session binding params.
|
|
8
13
|
* Format: https://proxy.ekkos.dev/proxy/{userId}/{sessionName}?project={base64(cwd)}&sid={sessionId}&tz={tz}
|
|
9
14
|
*
|
|
10
15
|
* Used by Claude (Anthropic SDK handles query params correctly).
|
|
11
16
|
*/
|
|
12
|
-
export declare function buildProxyUrl(userId: string, sessionName: string, projectPath: string, sessionId: string): string;
|
|
17
|
+
export declare function buildProxyUrl(userId: string, sessionName: string, projectPath: string, sessionId: string, options?: ClaudeProxyLaunchOptions): string;
|
|
13
18
|
/**
|
|
14
19
|
* Build a proxy URL without query params — metadata encoded in path segments.
|
|
15
20
|
* Format: https://proxy.ekkos.dev/gproxy/{userId}/{sessionName}/{sid}/{project64}
|
package/dist/utils/proxy-url.js
CHANGED
|
@@ -15,10 +15,24 @@ exports.EKKOS_PROXY_URL = process.env.EKKOS_PROXY_URL || 'https://proxy.ekkos.de
|
|
|
15
15
|
*
|
|
16
16
|
* Used by Claude (Anthropic SDK handles query params correctly).
|
|
17
17
|
*/
|
|
18
|
-
function buildProxyUrl(userId, sessionName, projectPath, sessionId) {
|
|
18
|
+
function buildProxyUrl(userId, sessionName, projectPath, sessionId, options) {
|
|
19
19
|
const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
|
|
20
20
|
const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
21
|
-
|
|
21
|
+
const params = new URLSearchParams({
|
|
22
|
+
project: projectPathEncoded,
|
|
23
|
+
sid: sessionId,
|
|
24
|
+
tz: userTz,
|
|
25
|
+
});
|
|
26
|
+
if (options?.claudeCodeVersion) {
|
|
27
|
+
params.set('ccv', options.claudeCodeVersion);
|
|
28
|
+
}
|
|
29
|
+
if (options?.claudeProfile) {
|
|
30
|
+
params.set('cp', options.claudeProfile);
|
|
31
|
+
}
|
|
32
|
+
if (options?.claudeContextWindow && options.claudeContextWindow !== 'auto') {
|
|
33
|
+
params.set('cw', options.claudeContextWindow);
|
|
34
|
+
}
|
|
35
|
+
return `${exports.EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(sessionName)}?${params.toString()}`;
|
|
22
36
|
}
|
|
23
37
|
/**
|
|
24
38
|
* Build a proxy URL without query params — metadata encoded in path segments.
|
package/package.json
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekkos/cli",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.9",
|
|
4
|
+
"description": "ekkOS memory CLI — persistent memory for AI coding assistants (Claude Code, Gemini, Cursor, Windsurf)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"bin": {
|
|
7
8
|
"ekkos": "dist/index.js",
|
|
8
|
-
"cli": "dist/index.js",
|
|
9
9
|
"ekkos-capture": "dist/cache/capture.js"
|
|
10
10
|
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/Ekkos-Technologies-Inc/ekkos.git",
|
|
14
|
+
"directory": "packages/ekkos-cli"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://ekkos.dev",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/Ekkos-Technologies-Inc/ekkos/issues"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"dev": "tsx src/index.ts",
|
|
23
|
+
"prepack": "node scripts/build-templates.js prepack",
|
|
24
|
+
"postpack": "node scripts/build-templates.js postpack",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
11
27
|
"keywords": [
|
|
12
28
|
"ekkos",
|
|
13
29
|
"ai",
|
|
@@ -18,11 +34,15 @@
|
|
|
18
34
|
"mcp",
|
|
19
35
|
"anthropic"
|
|
20
36
|
],
|
|
21
|
-
"author":
|
|
22
|
-
|
|
37
|
+
"author": {
|
|
38
|
+
"name": "ekkOS Technologies Inc.",
|
|
39
|
+
"url": "https://ekkos.dev"
|
|
40
|
+
},
|
|
41
|
+
"license": "UNLICENSED",
|
|
23
42
|
"dependencies": {
|
|
43
|
+
"@ekkos/agent": "^0.1.0",
|
|
44
|
+
"@ekkos/remote": "^0.14.1",
|
|
24
45
|
"@supabase/supabase-js": "^2.39.8",
|
|
25
|
-
"axios": "^1.7.0",
|
|
26
46
|
"blessed": "^0.1.81",
|
|
27
47
|
"blessed-contrib": "^4.11.0",
|
|
28
48
|
"ccusage": "^18.0.5",
|
|
@@ -32,10 +52,6 @@
|
|
|
32
52
|
"node-pty": "1.2.0-beta.7",
|
|
33
53
|
"open": "^10.0.0",
|
|
34
54
|
"ora": "^8.0.1",
|
|
35
|
-
"qrcode-terminal": "^0.12.0",
|
|
36
|
-
"socket.io-client": "^4.8.0",
|
|
37
|
-
"tweetnacl": "^1.0.3",
|
|
38
|
-
"ws": "^8.19.0",
|
|
39
55
|
"zod": "^3.23.0"
|
|
40
56
|
},
|
|
41
57
|
"devDependencies": {
|
|
@@ -50,10 +66,8 @@
|
|
|
50
66
|
"dist",
|
|
51
67
|
"!dist/cron",
|
|
52
68
|
"templates/CLAUDE.md",
|
|
53
|
-
"templates/skills"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
}
|
|
69
|
+
"templates/skills",
|
|
70
|
+
"templates/agents",
|
|
71
|
+
"templates/commands"
|
|
72
|
+
]
|
|
73
|
+
}
|