@xelth/eck-snapshot 5.9.0 → 6.4.0
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.
Potentially problematic release.
This version of @xelth/eck-snapshot might be problematic. Click here for more details.
- package/README.md +267 -190
- package/package.json +15 -2
- package/scripts/mcp-eck-core.js +61 -13
- package/setup.json +114 -80
- package/src/cli/cli.js +235 -385
- package/src/cli/commands/createSnapshot.js +336 -122
- package/src/cli/commands/recon.js +244 -0
- package/src/cli/commands/setupMcp.js +278 -19
- package/src/cli/commands/trainTokens.js +42 -32
- package/src/cli/commands/updateSnapshot.js +128 -76
- package/src/core/depthConfig.js +54 -0
- package/src/core/skeletonizer.js +71 -18
- package/src/templates/architect-prompt.template.md +34 -0
- package/src/templates/multiAgent.md +18 -10
- package/src/templates/opencode/coder.template.md +44 -17
- package/src/templates/opencode/junior-architect.template.md +45 -15
- package/src/templates/skeleton-instruction.md +1 -1
- package/src/utils/aiHeader.js +57 -27
- package/src/utils/claudeMdGenerator.js +136 -78
- package/src/utils/fileUtils.js +1011 -1016
- package/src/utils/gitUtils.js +12 -8
- package/src/utils/opencodeAgentsGenerator.js +8 -2
- package/src/utils/projectDetector.js +66 -21
- package/src/utils/tokenEstimator.js +11 -7
- package/src/cli/commands/consilium.js +0 -86
- package/src/cli/commands/detectProfiles.js +0 -98
- package/src/cli/commands/envSync.js +0 -319
- package/src/cli/commands/generateProfileGuide.js +0 -144
- package/src/cli/commands/pruneSnapshot.js +0 -106
- package/src/cli/commands/restoreSnapshot.js +0 -173
- package/src/cli/commands/setupGemini.js +0 -149
- package/src/cli/commands/setupGemini.test.js +0 -115
- package/src/cli/commands/showFile.js +0 -39
- package/src/services/claudeCliService.js +0 -626
- package/src/services/claudeCliService.test.js +0 -267
package/src/cli/cli.js
CHANGED
|
@@ -2,426 +2,276 @@ import { Command } from 'commander';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import crypto from 'crypto';
|
|
5
9
|
|
|
6
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
11
|
const __dirname = path.dirname(__filename);
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { restoreSnapshot } from './commands/restoreSnapshot.js';
|
|
12
|
-
import { pruneSnapshot } from './commands/pruneSnapshot.js';
|
|
13
|
-
import { generateConsilium } from './commands/consilium.js';
|
|
14
|
-
import { detectProject, testFileParsing } from './commands/detectProject.js';
|
|
15
|
-
import { trainTokens, showTokenStats } from './commands/trainTokens.js';
|
|
16
|
-
import { executePrompt, executePromptWithSession } from '../services/claudeCliService.js';
|
|
17
|
-
import { detectProfiles } from './commands/detectProfiles.js';
|
|
18
|
-
import { generateProfileGuide } from './commands/generateProfileGuide.js';
|
|
19
|
-
import { setupGemini } from './commands/setupGemini.js';
|
|
20
|
-
import { pushTelemetry } from '../utils/telemetry.js';
|
|
21
|
-
|
|
22
|
-
import { showFile } from './commands/showFile.js';
|
|
23
|
-
import { runDoctor } from './commands/doctor.js';
|
|
24
|
-
import { setupMcp } from './commands/setupMcp.js';
|
|
25
|
-
import { envPush, envPull } from './commands/envSync.js';
|
|
26
|
-
import inquirer from 'inquirer';
|
|
27
|
-
import ora from 'ora';
|
|
28
|
-
import { execa } from 'execa';
|
|
29
|
-
import chalk from 'chalk';
|
|
30
|
-
import { createRequire } from 'module';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Check code boundaries in a file
|
|
34
|
-
*/
|
|
35
|
-
async function checkCodeBoundaries(filePath, agentId) {
|
|
13
|
+
async function getGlobalConfig() {
|
|
14
|
+
const configPath = path.join(os.homedir(), '.eck', 'cli-config.json');
|
|
36
15
|
try {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
boundaries.push({
|
|
45
|
-
owner: match[1],
|
|
46
|
-
startIndex: match.index,
|
|
47
|
-
endIndex: match.index + match[0].length,
|
|
48
|
-
content: match[2]
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
file: filePath,
|
|
54
|
-
hasBoundaries: boundaries.length > 0,
|
|
55
|
-
boundaries: boundaries,
|
|
56
|
-
canModify: boundaries.every(b => b.owner === agentId || b.owner === 'SHARED')
|
|
57
|
-
};
|
|
58
|
-
} catch (error) {
|
|
59
|
-
return {
|
|
60
|
-
file: filePath,
|
|
61
|
-
error: error.message,
|
|
62
|
-
canModify: true // If can't read, assume can modify (new file)
|
|
63
|
-
};
|
|
16
|
+
const data = await fs.readFile(configPath, 'utf-8');
|
|
17
|
+
return JSON.parse(data);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
const newConfig = { instanceId: crypto.randomUUID(), telemetryEnabled: true };
|
|
20
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true }).catch(() => {});
|
|
21
|
+
await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2)).catch(() => {});
|
|
22
|
+
return newConfig;
|
|
64
23
|
}
|
|
65
24
|
}
|
|
66
25
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Fire and forget — result captured via closure
|
|
75
|
-
let updateMessage = null;
|
|
76
|
-
|
|
77
|
-
const check = execa('npm', ['view', '@xelth/eck-snapshot', 'version'], { timeout: 5000 })
|
|
78
|
-
.then(({ stdout }) => {
|
|
79
|
-
const latest = stdout.trim();
|
|
80
|
-
if (latest && latest !== currentVersion) {
|
|
81
|
-
const current = currentVersion.split('.').map(Number);
|
|
82
|
-
const remote = latest.split('.').map(Number);
|
|
83
|
-
const isNewer = remote[0] > current[0] ||
|
|
84
|
-
(remote[0] === current[0] && remote[1] > current[1]) ||
|
|
85
|
-
(remote[0] === current[0] && remote[1] === current[1] && remote[2] > current[2]);
|
|
86
|
-
if (isNewer) {
|
|
87
|
-
updateMessage = `\n${chalk.yellow(`⬆ Update available: ${currentVersion} → ${latest}`)} run: ${chalk.cyan('npm i -g @xelth/eck-snapshot')}`;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
.catch(() => { /* network error, skip silently */ });
|
|
92
|
-
|
|
93
|
-
process.on('exit', () => {
|
|
94
|
-
if (updateMessage) {
|
|
95
|
-
console.error(updateMessage);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
26
|
+
async function setGlobalConfig(updates) {
|
|
27
|
+
const configPath = path.join(os.homedir(), '.eck', 'cli-config.json');
|
|
28
|
+
const current = await getGlobalConfig();
|
|
29
|
+
const next = { ...current, ...updates };
|
|
30
|
+
await fs.writeFile(configPath, JSON.stringify(next, null, 2)).catch(() => {});
|
|
31
|
+
return next;
|
|
98
32
|
}
|
|
99
33
|
|
|
34
|
+
// Import core logic (we bypass the CLI wrapper entirely)
|
|
35
|
+
import { createRepoSnapshot } from './commands/createSnapshot.js';
|
|
36
|
+
import { updateSnapshot, updateSnapshotJson } from './commands/updateSnapshot.js';
|
|
37
|
+
import { setupMcp } from './commands/setupMcp.js';
|
|
38
|
+
import { detectProject } from './commands/detectProject.js';
|
|
39
|
+
import { runDoctor } from './commands/doctor.js';
|
|
40
|
+
import { runReconTool } from './commands/recon.js';
|
|
41
|
+
import { runTokenTools } from './commands/trainTokens.js';
|
|
42
|
+
|
|
43
|
+
// Legacy command shims: translate old positional commands to JSON payloads
|
|
44
|
+
// so internal callers (mcp-eck-core.js) keep working after the JSON migration.
|
|
45
|
+
const LEGACY_COMMANDS = {
|
|
46
|
+
'update-auto': (args) => ({ name: 'eck_update_auto', arguments: { fail: args.includes('--fail') || args.includes('-f') } }),
|
|
47
|
+
'snapshot': () => ({ name: 'eck_snapshot', arguments: {} }),
|
|
48
|
+
'update': (args) => ({ name: 'eck_update', arguments: { fail: args.includes('--fail') || args.includes('-f') } }),
|
|
49
|
+
'setup-mcp': (args) => ({ name: 'eck_setup_mcp', arguments: { opencode: args.includes('--opencode'), both: args.includes('--both') } }),
|
|
50
|
+
'detect': () => ({ name: 'eck_detect', arguments: {} }),
|
|
51
|
+
'doctor': () => ({ name: 'eck_doctor', arguments: {} }),
|
|
52
|
+
'scout': (args) => ({ name: 'eck_scout', arguments: { depth: args[0] ? parseInt(args[0], 10) : 0 } }),
|
|
53
|
+
'fetch': (args) => ({ name: 'eck_fetch', arguments: { patterns: args } }),
|
|
54
|
+
'link': (args) => ({ name: 'eck_snapshot', arguments: { isLinkedProject: true, linkDepth: args[0] ? parseInt(args[0], 10) : 0 } }),
|
|
55
|
+
'profile': (args) => args[0] ? ({ name: 'eck_snapshot', arguments: { profile: args.join(',') } }) : ({ name: 'eck_snapshot', arguments: { profile: true } }),
|
|
56
|
+
'booklm': () => ({ name: 'eck_snapshot', arguments: { notebooklm: 'scout' } }),
|
|
57
|
+
'notelm': () => ({ name: 'eck_snapshot', arguments: { notebooklm: 'architect' } }),
|
|
58
|
+
'telemetry': (args) => ({ name: 'eck_telemetry', arguments: { action: args[0] } }),
|
|
59
|
+
};
|
|
60
|
+
|
|
100
61
|
export function run() {
|
|
101
|
-
//
|
|
102
|
-
|
|
62
|
+
// Intercept legacy positional commands before commander parses them
|
|
63
|
+
const rawArgs = process.argv.slice(2);
|
|
64
|
+
const firstArg = rawArgs[0];
|
|
65
|
+
if (firstArg && LEGACY_COMMANDS[firstArg]) {
|
|
66
|
+
const payload = LEGACY_COMMANDS[firstArg](rawArgs.slice(1));
|
|
67
|
+
// Replace argv so commander sees the JSON payload
|
|
68
|
+
process.argv = [process.argv[0], process.argv[1], JSON.stringify(payload)];
|
|
69
|
+
}
|
|
103
70
|
|
|
104
71
|
const program = new Command();
|
|
105
|
-
|
|
106
72
|
const pkg = createRequire(import.meta.url)('../../package.json');
|
|
107
|
-
const helpGuide = `eck-snapshot (v${pkg.version}) - AI-Native Repository Context Tool.
|
|
108
|
-
|
|
109
|
-
--- 🚀 Core Workflow: Optimized for Web LLMs (Gemini/ChatGPT) ---
|
|
110
|
-
|
|
111
|
-
1. Initial Context (Maximum Compression)
|
|
112
|
-
Create a lightweight map of your entire project. Bodies of functions are hidden.
|
|
113
|
-
This fits huge monoliths into the context window.
|
|
114
|
-
|
|
115
|
-
$ eck-snapshot --skeleton
|
|
116
|
-
-> Generates: .eck/snapshots/<name>_sk.md (Upload this to AI)
|
|
117
|
-
|
|
118
|
-
2. Lazy Loading (On-Demand Details)
|
|
119
|
-
If the AI needs to see the implementation of specific files, it will ask you.
|
|
120
|
-
You can display multiple files at once to copy-paste back to the chat.
|
|
121
|
-
|
|
122
|
-
$ eck-snapshot show src/auth.js src/utils/hash.js
|
|
123
|
-
|
|
124
|
-
3. Working & Updating
|
|
125
|
-
As you apply changes, the AI loses context. Instead of re-sending the full repo,
|
|
126
|
-
send only what changed since the last snapshot.
|
|
127
|
-
|
|
128
|
-
$ eck-snapshot update
|
|
129
|
-
-> Generates: .eck/snapshots/update_<timestamp>.md (Contains changed files + git diff)
|
|
130
|
-
|
|
131
|
-
--- 🛠️ Managing Context Profiles ---
|
|
132
|
-
|
|
133
|
-
Option A: Auto-Detection (Best for start)
|
|
134
|
-
Uses AI to scan folders and suggest profiles (backend, frontend, etc).
|
|
135
|
-
$ eck-snapshot profile-detect
|
|
136
|
-
|
|
137
|
-
Option B: Manual Guide (Best for large repos)
|
|
138
|
-
If the project is too big for auto-detection, this generates a prompt text file
|
|
139
|
-
that you can paste into a powerful Web LLM (like Gemini 1.5 Pro) to design profiles manually.
|
|
140
|
-
|
|
141
|
-
1. Run: $ eck-snapshot generate-profile-guide
|
|
142
|
-
2. Open: .eck/profile_generation_guide.md
|
|
143
|
-
3. Copy: Paste the content into your AI chat.
|
|
144
|
-
4. Save: Take the JSON response and save it to .eck/profiles.json
|
|
145
|
-
|
|
146
|
-
Option C: Using Profiles
|
|
147
|
-
$ eck-snapshot --profile (List all profiles)
|
|
148
|
-
$ eck-snapshot --profile backend (Use profile)
|
|
149
|
-
$ eck-snapshot --profile "frontend,-**/*.test.js" (Ad-hoc filtering)
|
|
150
|
-
|
|
151
|
-
--- 🧩 Auxiliary Commands ---
|
|
152
73
|
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
74
|
+
const helpGuide = `
|
|
75
|
+
eck-snapshot (v${pkg.version}) - AI-Native Repository Context Tool.
|
|
76
|
+
===================================================================
|
|
77
|
+
|
|
78
|
+
[AI AGENTS: PURE JSON/MCP INTERFACE ACTIVE]
|
|
79
|
+
This CLI is designed to be operated by AI agents using JSON payloads.
|
|
80
|
+
- eck_snapshot : { profile, skeleton, jas, link, linkDepth }
|
|
81
|
+
- eck_update : Delta snapshot
|
|
82
|
+
- eck_scout : { depth: 0-9 }
|
|
83
|
+
- eck_fetch : { patterns: [] }
|
|
84
|
+
- eck_setup_mcp : Configure MCP servers
|
|
85
|
+
- eck_detect : Detect project type
|
|
86
|
+
- eck_doctor : Health check
|
|
87
|
+
|
|
88
|
+
[HUMAN COMMANDS: SHORTHANDS]
|
|
89
|
+
Ranked by frequency of use:
|
|
90
|
+
|
|
91
|
+
1. eck-snapshot snapshot Full project snapshot
|
|
92
|
+
2. eck-snapshot update Delta update (changed files only)
|
|
93
|
+
3. eck-snapshot profile [name] Snapshot filtered by profile (from .eck/profiles.json)
|
|
94
|
+
No arg = list available profiles
|
|
95
|
+
Example: eck-snapshot profile backend
|
|
96
|
+
Multiple: eck-snapshot profile backend,api
|
|
97
|
+
4. eck-snapshot scout [0-9] Scout external repo. Depths:
|
|
98
|
+
0: Tree only (default)
|
|
99
|
+
1-4: Truncated (10, 30, 60, 100 lines)
|
|
100
|
+
5: Skeleton (Signatures only)
|
|
101
|
+
6: Skeleton + docs
|
|
102
|
+
7-9: Full content (500, 1000, unlimited)
|
|
103
|
+
5. eck-snapshot fetch <glob> Fetch specific files (e.g., "src/**/*.js")
|
|
104
|
+
6. eck-snapshot link [0-9] Linked companion snapshot (same depths)
|
|
105
|
+
7. eck-snapshot booklm Export for NotebookLM (Scout - fetch generator)
|
|
106
|
+
8. eck-snapshot notelm Export for NotebookLM (Architect - experimental)
|
|
107
|
+
9. eck-snapshot setup-mcp Configure AI agents (Claude Code, OpenCode)
|
|
108
|
+
10. eck-snapshot detect Detect project type and active filters
|
|
109
|
+
11. eck-snapshot doctor Check project health and stubs
|
|
110
|
+
|
|
111
|
+
[FEEDBACK]
|
|
112
|
+
eck-snapshot -e "message" Send feedback/ideas to developers (read by AI)
|
|
113
|
+
eck-snapshot -E "message" Send urgent bug report
|
|
114
|
+
|
|
115
|
+
[DATENSCHUTZ / PRIVACY]
|
|
116
|
+
We respect your privacy. By default, eck-snapshot collects anonymous usage counts
|
|
117
|
+
and crash logs to improve the tool. NO source code or sensitive data is ever sent.
|
|
118
|
+
To completely disable background telemetry:
|
|
119
|
+
eck-snapshot telemetry disable
|
|
120
|
+
To re-enable it:
|
|
121
|
+
eck-snapshot telemetry enable
|
|
158
122
|
`;
|
|
159
123
|
|
|
160
124
|
program
|
|
161
125
|
.name('eck-snapshot')
|
|
162
|
-
.description('A lightweight, platform-independent CLI for creating project snapshots.')
|
|
163
126
|
.version(pkg.version)
|
|
164
|
-
.addHelpText('before', helpGuide)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
.addHelpText('after', `
|
|
193
|
-
Quick --profile Examples:
|
|
194
|
-
--profile List all available profiles
|
|
195
|
-
--profile backend Use the 'backend' profile
|
|
196
|
-
--profile "backend,-**/tests/**" Use backend, exclude tests
|
|
197
|
-
--profile "src/**/*.js,-**/*.test.js" Ad-hoc: all JS, exclude tests
|
|
198
|
-
|
|
199
|
-
See "Managing Context Profiles" section above for profile setup.
|
|
200
|
-
`);
|
|
201
|
-
|
|
202
|
-
// Update snapshot command
|
|
203
|
-
program
|
|
204
|
-
.command('update')
|
|
205
|
-
.description('Create a delta snapshot of changed files since the last full snapshot')
|
|
206
|
-
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
207
|
-
.option('--config <path>', 'Configuration file path')
|
|
208
|
-
.action(updateSnapshot);
|
|
209
|
-
|
|
210
|
-
// Auto/Silent Update command for Agents
|
|
211
|
-
program
|
|
212
|
-
.command('update-auto')
|
|
213
|
-
.description('Silent update for AI agents (JSON output)')
|
|
214
|
-
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
215
|
-
.action(updateSnapshotJson);
|
|
216
|
-
|
|
217
|
-
// Restore command
|
|
218
|
-
program
|
|
219
|
-
.command('restore')
|
|
220
|
-
.description('Restore files from a snapshot')
|
|
221
|
-
.argument('<snapshot_file>', 'Snapshot file path')
|
|
222
|
-
.argument('[target_directory]', 'Target directory', process.cwd())
|
|
223
|
-
.option('-f, --force', 'Skip confirmation')
|
|
224
|
-
.option('-v, --verbose', 'Show detailed progress')
|
|
225
|
-
.option('--dry-run', 'Preview without writing')
|
|
226
|
-
.option('--include <patterns...>', 'Include patterns')
|
|
227
|
-
.option('--exclude <patterns...>', 'Exclude patterns')
|
|
228
|
-
.option('--concurrency <number>', 'Concurrent operations', (val) => parseInt(val), 10)
|
|
229
|
-
.action(restoreSnapshot);
|
|
230
|
-
|
|
231
|
-
// Prune command
|
|
232
|
-
program
|
|
233
|
-
.command('prune')
|
|
234
|
-
.description('Intelligently reduce snapshot size using AI file ranking')
|
|
235
|
-
.argument('<snapshot_file>', 'Path to the snapshot file to prune')
|
|
236
|
-
.option('--target-size <size>', 'Target size (e.g., 500KB, 1MB)', '500KB')
|
|
237
|
-
.action(pruneSnapshot);
|
|
238
|
-
|
|
239
|
-
// Consilium command
|
|
240
|
-
program
|
|
241
|
-
.command('consilium')
|
|
242
|
-
.description('Generate a consilium request for complex decisions')
|
|
243
|
-
.option('--type <type>', 'Decision type', 'technical_decision')
|
|
244
|
-
.option('--title <title>', 'Decision title')
|
|
245
|
-
.option('--description <desc>', 'Detailed description')
|
|
246
|
-
.option('--complexity <num>', 'Complexity score (1-10)', (val) => parseInt(val), 7)
|
|
247
|
-
.option('--constraints <list>', 'Comma-separated constraints')
|
|
248
|
-
.option('--snapshot <file>', 'Include snapshot file')
|
|
249
|
-
.option('--agent <id>', 'Requesting agent ID')
|
|
250
|
-
.option('-o, --output <file>', 'Output file', 'consilium_request.json')
|
|
251
|
-
.action(generateConsilium);
|
|
252
|
-
|
|
253
|
-
// Check boundaries command
|
|
254
|
-
program
|
|
255
|
-
.command('check-boundaries')
|
|
256
|
-
.description('Check agent boundaries in a file')
|
|
257
|
-
.argument('<file>', 'File to check')
|
|
258
|
-
.option('--agent <id>', 'Your agent ID')
|
|
259
|
-
.action(async (file, options) => {
|
|
260
|
-
const result = await checkCodeBoundaries(file, options.agent || 'UNKNOWN');
|
|
261
|
-
console.log(JSON.stringify(result, null, 2));
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Project detection command
|
|
265
|
-
program
|
|
266
|
-
.command('detect')
|
|
267
|
-
.description('Detect and display project type and configuration')
|
|
268
|
-
.argument('[projectPath]', 'Path to the project', process.cwd())
|
|
269
|
-
.option('-v, --verbose', 'Show detailed detection results')
|
|
270
|
-
.action(detectProject);
|
|
271
|
-
|
|
272
|
-
// Android parsing test command
|
|
273
|
-
program
|
|
274
|
-
.command('test-android')
|
|
275
|
-
.description('Test Android file parsing capabilities')
|
|
276
|
-
.argument('<filePath>', 'Path to Android source file (.kt or .java)')
|
|
277
|
-
.option('--show-content', 'Show content preview of parsed segments')
|
|
278
|
-
.action(testFileParsing);
|
|
279
|
-
|
|
280
|
-
// Token training command
|
|
281
|
-
program
|
|
282
|
-
.command('train-tokens')
|
|
283
|
-
.description('Train token estimation with actual results')
|
|
284
|
-
.argument('<projectType>', 'Project type (android, nodejs, python, etc.)')
|
|
285
|
-
.argument('<fileSizeBytes>', 'File size in bytes')
|
|
286
|
-
.argument('<estimatedTokens>', 'Estimated token count')
|
|
287
|
-
.argument('<actualTokens>', 'Actual token count from LLM')
|
|
288
|
-
.action(trainTokens);
|
|
289
|
-
|
|
290
|
-
// Token statistics command
|
|
291
|
-
program
|
|
292
|
-
.command('token-stats')
|
|
293
|
-
.description('Show token estimation statistics and accuracy')
|
|
294
|
-
.action(showTokenStats);
|
|
295
|
-
|
|
296
|
-
// Profile detection command
|
|
297
|
-
program
|
|
298
|
-
.command('profile-detect')
|
|
299
|
-
.description('Use AI to scan the directory tree and auto-generate local context profiles (saves to .eck/profiles.json)')
|
|
300
|
-
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
301
|
-
.action(detectProfiles);
|
|
127
|
+
.addHelpText('before', helpGuide)
|
|
128
|
+
.argument('[payload]', 'JSON string representing the MCP tool call')
|
|
129
|
+
.option('-e, --feedback <message>', 'Send feedback or report an issue to developers')
|
|
130
|
+
.option('-E, --urgent-feedback <message>', 'Send urgent feedback to developers')
|
|
131
|
+
.action(async (payloadStr, options) => {
|
|
132
|
+
const globalConfig = await getGlobalConfig();
|
|
133
|
+
|
|
134
|
+
// --- Handle Feedback Flags ---
|
|
135
|
+
if (options.feedback || options.urgentFeedback) {
|
|
136
|
+
const msg = options.feedback || options.urgentFeedback;
|
|
137
|
+
const type = options.urgentFeedback ? 'URGENT' : 'NORMAL';
|
|
138
|
+
const queuePath = path.join(process.cwd(), '.eck', 'telemetry_queue.json');
|
|
139
|
+
|
|
140
|
+
let queue = { instanceId: globalConfig.instanceId, feedback: [], usage: {}, errors: [] };
|
|
141
|
+
try {
|
|
142
|
+
const existing = JSON.parse(await fs.readFile(queuePath, 'utf-8'));
|
|
143
|
+
queue = { ...queue, ...existing };
|
|
144
|
+
} catch(e) { /* no existing queue */ }
|
|
145
|
+
|
|
146
|
+
queue.feedback.push({ type, message: msg, date: new Date().toISOString() });
|
|
147
|
+
|
|
148
|
+
await fs.mkdir(path.dirname(queuePath), { recursive: true }).catch(() => {});
|
|
149
|
+
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
|
|
150
|
+
|
|
151
|
+
console.log(chalk.green('Feedback saved locally. It will be sent to developers during the next telemetry sync.'));
|
|
152
|
+
console.log(chalk.gray('(Note: Messages are processed by AI for developers)'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
302
155
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
156
|
+
// Default behavior for human users: empty call = full snapshot
|
|
157
|
+
if (!payloadStr) {
|
|
158
|
+
console.log(chalk.cyan('🚀 No arguments provided. Defaulting to full repository snapshot...'));
|
|
159
|
+
console.log(chalk.gray('💡 Run `eck-snapshot -h` to see all available JSON tools.\n'));
|
|
160
|
+
payloadStr = '{"name": "eck_snapshot", "arguments": {}}';
|
|
161
|
+
}
|
|
309
162
|
|
|
310
|
-
|
|
311
|
-
program
|
|
312
|
-
.command('ask-claude')
|
|
313
|
-
.description('Execute a prompt using claude-code CLI and return JSON response')
|
|
314
|
-
.argument('<prompt>', 'Prompt to send to Claude')
|
|
315
|
-
.option('-c, --continue', 'Continue the most recent conversation')
|
|
316
|
-
.action(async (prompt, options) => {
|
|
163
|
+
let payload;
|
|
317
164
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.
|
|
165
|
+
payload = JSON.parse(payloadStr.trim());
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error(chalk.red('❌ Error: Input must be a valid JSON string.'));
|
|
168
|
+
console.log(chalk.yellow(`Example: eck-snapshot '{"name": "eck_snapshot"}'`));
|
|
322
169
|
process.exit(1);
|
|
323
170
|
}
|
|
324
|
-
});
|
|
325
171
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
172
|
+
const toolName = payload.name;
|
|
173
|
+
const args = payload.arguments || {};
|
|
174
|
+
|
|
175
|
+
// --- Handle Telemetry Config Command ---
|
|
176
|
+
if (toolName === 'eck_telemetry') {
|
|
177
|
+
if (args.action === 'disable') {
|
|
178
|
+
await setGlobalConfig({ telemetryEnabled: false });
|
|
179
|
+
console.log(chalk.yellow('Background telemetry has been disabled.'));
|
|
180
|
+
} else if (args.action === 'enable') {
|
|
181
|
+
await setGlobalConfig({ telemetryEnabled: true });
|
|
182
|
+
console.log(chalk.green('Background telemetry has been enabled. Thank you for supporting the project!'));
|
|
183
|
+
} else {
|
|
184
|
+
console.log(chalk.blue(`Telemetry is currently: ${globalConfig.telemetryEnabled ? 'ENABLED' : 'DISABLED'}`));
|
|
185
|
+
console.log(chalk.gray(`Instance ID: ${globalConfig.instanceId}`));
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
340
188
|
}
|
|
341
|
-
});
|
|
342
189
|
|
|
190
|
+
const cwd = process.cwd();
|
|
191
|
+
const queuePath = path.join(cwd, '.eck', 'telemetry_queue.json');
|
|
343
192
|
|
|
193
|
+
try {
|
|
194
|
+
// --- Track Usage Locally (Guarded by Privacy Opt-in) ---
|
|
195
|
+
if (globalConfig.telemetryEnabled) {
|
|
196
|
+
try {
|
|
197
|
+
let queue = { instanceId: globalConfig.instanceId, feedback: [], usage: {}, errors: [] };
|
|
198
|
+
try {
|
|
199
|
+
const existing = JSON.parse(await fs.readFile(queuePath, 'utf-8'));
|
|
200
|
+
queue = { ...queue, ...existing };
|
|
201
|
+
} catch(e) { /* no existing queue */ }
|
|
202
|
+
queue.usage[toolName] = (queue.usage[toolName] || 0) + 1;
|
|
203
|
+
await fs.mkdir(path.dirname(queuePath), { recursive: true }).catch(() => {});
|
|
204
|
+
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
|
|
205
|
+
} catch(e) { /* ignore tracking errors */ }
|
|
206
|
+
}
|
|
344
207
|
|
|
208
|
+
switch (toolName) {
|
|
209
|
+
case 'eck_snapshot':
|
|
210
|
+
await createRepoSnapshot(cwd, args);
|
|
211
|
+
break;
|
|
212
|
+
case 'eck_update':
|
|
213
|
+
await updateSnapshot(cwd, args);
|
|
214
|
+
break;
|
|
215
|
+
case 'eck_update_auto':
|
|
216
|
+
await updateSnapshotJson(cwd, args);
|
|
217
|
+
break;
|
|
218
|
+
case 'eck_setup_mcp':
|
|
219
|
+
await setupMcp(args);
|
|
220
|
+
break;
|
|
221
|
+
case 'eck_detect':
|
|
222
|
+
await detectProject(cwd, args);
|
|
223
|
+
break;
|
|
224
|
+
case 'eck_doctor':
|
|
225
|
+
await runDoctor(cwd);
|
|
226
|
+
break;
|
|
227
|
+
case 'eck_scout':
|
|
228
|
+
case 'eck_fetch':
|
|
229
|
+
await runReconTool(payload);
|
|
230
|
+
break;
|
|
231
|
+
case 'eck_train_tokens':
|
|
232
|
+
case 'eck_token_stats':
|
|
233
|
+
await runTokenTools(payload);
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
console.log(chalk.red(`❌ Unknown tool: "${toolName}"`));
|
|
237
|
+
console.log(chalk.yellow('Run `eck-snapshot -h` to see available JSON tools.'));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
} catch (err) {
|
|
241
|
+
// --- Track Errors Locally (Guarded by Privacy Opt-in) ---
|
|
242
|
+
if (globalConfig.telemetryEnabled) {
|
|
243
|
+
try {
|
|
244
|
+
let queue = { instanceId: globalConfig.instanceId, feedback: [], usage: {}, errors: [] };
|
|
245
|
+
try {
|
|
246
|
+
const existing = JSON.parse(await fs.readFile(queuePath, 'utf-8'));
|
|
247
|
+
queue = { ...queue, ...existing };
|
|
248
|
+
} catch(e) { /* no existing queue */ }
|
|
249
|
+
queue.errors.push({ tool: toolName, error: err.message, date: new Date().toISOString() });
|
|
250
|
+
await fs.mkdir(path.dirname(queuePath), { recursive: true }).catch(() => {});
|
|
251
|
+
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
|
|
252
|
+
} catch(e) { /* ignore tracking errors */ }
|
|
253
|
+
}
|
|
345
254
|
|
|
346
|
-
|
|
347
|
-
.command('generate-ai-prompt')
|
|
348
|
-
.description('Generate a specific AI prompt from a template.')
|
|
349
|
-
.option('--role <role>', 'The role for which to generate a prompt', 'architect')
|
|
350
|
-
.action(async (options) => {
|
|
351
|
-
try {
|
|
352
|
-
const templatePath = path.join(__dirname, '..', 'templates', `${options.role}-prompt.template.md`);
|
|
353
|
-
const template = await fs.readFile(templatePath, 'utf-8');
|
|
354
|
-
// In the future, we can inject dynamic data here from setup.json
|
|
355
|
-
console.log(template);
|
|
356
|
-
} catch (error) {
|
|
357
|
-
console.error(`Failed to generate prompt for role '${options.role}':`, error.message);
|
|
255
|
+
console.error(chalk.red(`❌ Execution failed for ${toolName}:`), err.message);
|
|
358
256
|
process.exit(1);
|
|
359
257
|
}
|
|
360
258
|
});
|
|
361
259
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
.command('setup-gemini')
|
|
365
|
-
.description('Generate claude.toml configuration for gemini-cli integration with dynamic paths')
|
|
366
|
-
.option('-v, --verbose', 'Show detailed output and error information')
|
|
367
|
-
.action(setupGemini);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
// Show file command (for skeleton mode lazy loading)
|
|
371
|
-
program
|
|
372
|
-
.command('show')
|
|
373
|
-
.description('Output the full content of specific file(s) (for AI lazy loading)')
|
|
374
|
-
.argument('<filePaths...>', 'Space-separated paths to files')
|
|
375
|
-
.action(showFile);
|
|
376
|
-
|
|
377
|
-
// Doctor command (health check for manifests)
|
|
378
|
-
program
|
|
379
|
-
.command('doctor')
|
|
380
|
-
.description('Check project health and detect unfinished manifest stubs')
|
|
381
|
-
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
382
|
-
.action(runDoctor);
|
|
383
|
-
|
|
384
|
-
// Setup MCP servers command (replaces setup-claude-mcp with unified approach)
|
|
385
|
-
program
|
|
386
|
-
.command('setup-mcp')
|
|
387
|
-
.description('Setup/restore MCP servers (eck-core + glm-zai) for Claude Code and/or OpenCode')
|
|
388
|
-
.option('--opencode', 'Setup for OpenCode only')
|
|
389
|
-
.option('--both', 'Setup for both Claude Code and OpenCode')
|
|
390
|
-
.option('-v, --verbose', 'Show detailed output')
|
|
391
|
-
.action(setupMcp);
|
|
392
|
-
|
|
393
|
-
// Environment sync commands (encrypted .eck/ transfer between machines)
|
|
394
|
-
const envCmd = program.command('env').description('Encrypted environment sync');
|
|
395
|
-
envCmd
|
|
396
|
-
.command('push')
|
|
397
|
-
.description('Pack and encrypt .eck/ config files into .eck-sync.enc')
|
|
398
|
-
.option('-v, --verbose', 'Show detailed output')
|
|
399
|
-
.action(envPush);
|
|
400
|
-
envCmd
|
|
401
|
-
.command('pull')
|
|
402
|
-
.description('Decrypt and restore .eck/ config files from .eck-sync.enc')
|
|
403
|
-
.option('-f, --force', 'Overwrite existing .eck/ files without prompting')
|
|
404
|
-
.option('-v, --verbose', 'Show detailed output')
|
|
405
|
-
.action(envPull);
|
|
406
|
-
|
|
407
|
-
// Telemetry commands
|
|
408
|
-
const telemetryCmd = program.command('telemetry').description('Manage Telemetry Hub synchronization');
|
|
409
|
-
telemetryCmd
|
|
410
|
-
.command('push')
|
|
411
|
-
.description('Manually push the latest AnswerToSA.md report to xelth.com/T/report')
|
|
412
|
-
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
413
|
-
.action(async (repoPath) => {
|
|
414
|
-
console.log(chalk.blue('Pushing agent telemetry...'));
|
|
415
|
-
await pushTelemetry(repoPath, false);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
telemetryCmd
|
|
419
|
-
.command('sync-weights')
|
|
420
|
-
.description('Fetch the latest global token estimation weights from Telemetry Hub')
|
|
421
|
-
.action(async () => {
|
|
422
|
-
const { syncTokenWeights } = await import('../utils/tokenEstimator.js');
|
|
423
|
-
await syncTokenWeights();
|
|
424
|
-
});
|
|
260
|
+
// Start version check in background (non-blocking)
|
|
261
|
+
checkForUpdates(pkg.version);
|
|
425
262
|
|
|
426
263
|
program.parse(process.argv);
|
|
427
264
|
}
|
|
265
|
+
|
|
266
|
+
function checkForUpdates(currentVersion) {
|
|
267
|
+
import('execa').then(({ execa }) => {
|
|
268
|
+
execa('npm', ['view', '@xelth/eck-snapshot', 'version'], { timeout: 5000 })
|
|
269
|
+
.then(({ stdout }) => {
|
|
270
|
+
const latest = stdout.trim();
|
|
271
|
+
if (latest && latest !== currentVersion) {
|
|
272
|
+
console.error(`\n${chalk.yellow(`⬆ Update available: ${currentVersion} → ${latest}`)}`);
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
.catch(() => {});
|
|
276
|
+
});
|
|
277
|
+
}
|