@xelth/eck-snapshot 5.9.0 → 6.6.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.
- package/README.md +321 -190
- package/index.js +1 -1
- package/package.json +15 -2
- package/scripts/mcp-eck-core.js +143 -13
- package/setup.json +119 -81
- package/src/cli/cli.js +256 -385
- package/src/cli/commands/createSnapshot.js +391 -175
- package/src/cli/commands/recon.js +308 -0
- package/src/cli/commands/setupMcp.js +280 -19
- package/src/cli/commands/trainTokens.js +42 -32
- package/src/cli/commands/updateSnapshot.js +136 -43
- package/src/core/depthConfig.js +54 -0
- package/src/core/skeletonizer.js +280 -21
- package/src/templates/architect-prompt.template.md +34 -0
- package/src/templates/multiAgent.md +68 -15
- package/src/templates/opencode/coder.template.md +53 -17
- package/src/templates/opencode/junior-architect.template.md +54 -15
- package/src/templates/skeleton-instruction.md +1 -1
- package/src/templates/update-prompt.template.md +2 -0
- package/src/utils/aiHeader.js +57 -27
- package/src/utils/claudeMdGenerator.js +182 -88
- package/src/utils/fileUtils.js +217 -149
- 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,297 @@ 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';
|
|
9
|
+
import { ensureSnapshotsInGitignore } from '../utils/fileUtils.js';
|
|
5
10
|
|
|
6
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
12
|
const __dirname = path.dirname(__filename);
|
|
8
13
|
|
|
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) {
|
|
14
|
+
async function getGlobalConfig() {
|
|
15
|
+
const configPath = path.join(os.homedir(), '.eck', 'cli-config.json');
|
|
36
16
|
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
|
-
};
|
|
17
|
+
const data = await fs.readFile(configPath, 'utf-8');
|
|
18
|
+
return JSON.parse(data);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
const newConfig = { instanceId: crypto.randomUUID(), telemetryEnabled: true };
|
|
21
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true }).catch(() => {});
|
|
22
|
+
await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2)).catch(() => {});
|
|
23
|
+
return newConfig;
|
|
64
24
|
}
|
|
65
25
|
}
|
|
66
26
|
|
|
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
|
-
});
|
|
27
|
+
async function setGlobalConfig(updates) {
|
|
28
|
+
const configPath = path.join(os.homedir(), '.eck', 'cli-config.json');
|
|
29
|
+
const current = await getGlobalConfig();
|
|
30
|
+
const next = { ...current, ...updates };
|
|
31
|
+
await fs.writeFile(configPath, JSON.stringify(next, null, 2)).catch(() => {});
|
|
32
|
+
return next;
|
|
98
33
|
}
|
|
99
34
|
|
|
35
|
+
// Import core logic (we bypass the CLI wrapper entirely)
|
|
36
|
+
import { createRepoSnapshot } from './commands/createSnapshot.js';
|
|
37
|
+
import { updateSnapshot, updateSnapshotJson } from './commands/updateSnapshot.js';
|
|
38
|
+
import { setupMcp } from './commands/setupMcp.js';
|
|
39
|
+
import { detectProject } from './commands/detectProject.js';
|
|
40
|
+
import { runDoctor } from './commands/doctor.js';
|
|
41
|
+
import { runReconTool } from './commands/recon.js';
|
|
42
|
+
import { runTokenTools } from './commands/trainTokens.js';
|
|
43
|
+
|
|
44
|
+
// Legacy command shims: translate old positional commands to JSON payloads
|
|
45
|
+
// so internal callers (mcp-eck-core.js) keep working after the JSON migration.
|
|
46
|
+
const LEGACY_COMMANDS = {
|
|
47
|
+
'update-auto': (args) => {
|
|
48
|
+
const baseIdx = args.indexOf('--base');
|
|
49
|
+
const base = baseIdx !== -1 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
50
|
+
return { name: 'eck_update_auto', arguments: { fail: args.includes('--fail') || args.includes('-f'), base } };
|
|
51
|
+
},
|
|
52
|
+
'snapshot': () => ({ name: 'eck_snapshot', arguments: {} }),
|
|
53
|
+
'update': (args) => {
|
|
54
|
+
const baseIdx = args.indexOf('--base');
|
|
55
|
+
const base = baseIdx !== -1 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
56
|
+
return { name: 'eck_update', arguments: { fail: args.includes('--fail') || args.includes('-f'), base } };
|
|
57
|
+
},
|
|
58
|
+
'setup-mcp': (args) => ({ name: 'eck_setup_mcp', arguments: { opencode: args.includes('--opencode'), both: args.includes('--both') } }),
|
|
59
|
+
'detect': () => ({ name: 'eck_detect', arguments: {} }),
|
|
60
|
+
'doctor': () => ({ name: 'eck_doctor', arguments: {} }),
|
|
61
|
+
'scout': (args) => ({ name: 'eck_scout', arguments: { depth: args[0] !== undefined ? parseInt(args[0], 10) : 0 } }),
|
|
62
|
+
'fetch': (args) => ({ name: 'eck_fetch', arguments: { patterns: args } }),
|
|
63
|
+
'link': (args) => ({ name: 'eck_snapshot', arguments: { isLinkedProject: true, linkDepth: args[0] !== undefined ? parseInt(args[0], 10) : 0 } }),
|
|
64
|
+
'profile': (args) => args[0] ? ({ name: 'eck_snapshot', arguments: { profile: args.join(',') } }) : ({ name: 'eck_snapshot', arguments: { profile: true } }),
|
|
65
|
+
'booklm': () => ({ name: 'eck_snapshot', arguments: { notebooklm: 'scout' } }),
|
|
66
|
+
'notelm': () => ({ name: 'eck_snapshot', arguments: { notebooklm: 'architect' } }),
|
|
67
|
+
'notebook': (args) => {
|
|
68
|
+
if (args[0] === 'link') return { name: 'eck_snapshot', arguments: { notebooklm: 'link', linkDepth: args[1] !== undefined ? parseInt(args[1], 10) : 0 } };
|
|
69
|
+
if (args[0] === 'scout') return { name: 'eck_snapshot', arguments: { notebooklm: 'scout', linkDepth: args[1] !== undefined ? parseInt(args[1], 10) : 0 } };
|
|
70
|
+
return { name: 'eck_snapshot', arguments: { notebooklm: 'hybrid' } };
|
|
71
|
+
},
|
|
72
|
+
'telemetry': (args) => ({ name: 'eck_telemetry', arguments: { action: args[0] } }),
|
|
73
|
+
};
|
|
74
|
+
|
|
100
75
|
export function run() {
|
|
101
|
-
//
|
|
102
|
-
|
|
76
|
+
// Intercept legacy positional commands before commander parses them
|
|
77
|
+
const rawArgs = process.argv.slice(2);
|
|
78
|
+
const firstArg = rawArgs[0];
|
|
79
|
+
if (firstArg && LEGACY_COMMANDS[firstArg]) {
|
|
80
|
+
const payload = LEGACY_COMMANDS[firstArg](rawArgs.slice(1));
|
|
81
|
+
// Replace argv so commander sees the JSON payload
|
|
82
|
+
process.argv = [process.argv[0], process.argv[1], JSON.stringify(payload)];
|
|
83
|
+
}
|
|
103
84
|
|
|
104
85
|
const program = new Command();
|
|
105
|
-
|
|
106
86
|
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
87
|
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
88
|
+
const helpGuide = `
|
|
89
|
+
eck-snapshot (v${pkg.version}) - AI-Native Repository Context Tool.
|
|
90
|
+
===================================================================
|
|
91
|
+
|
|
92
|
+
[AI AGENTS: PURE JSON/MCP INTERFACE ACTIVE]
|
|
93
|
+
This CLI is designed to be operated by AI agents using JSON payloads.
|
|
94
|
+
- eck_snapshot : { profile, skeleton, jas, link, linkDepth }
|
|
95
|
+
- eck_update : Delta snapshot
|
|
96
|
+
- eck_scout : { depth: 0-9 }
|
|
97
|
+
- eck_fetch : { patterns: [] }
|
|
98
|
+
- eck_setup_mcp : Configure MCP servers
|
|
99
|
+
- eck_detect : Detect project type
|
|
100
|
+
- eck_doctor : Health check
|
|
101
|
+
|
|
102
|
+
[HUMAN COMMANDS: SHORTHANDS]
|
|
103
|
+
Ranked by frequency of use:
|
|
104
|
+
|
|
105
|
+
1. eck-snapshot snapshot Full project snapshot
|
|
106
|
+
2. eck-snapshot update Delta update (changed files only)
|
|
107
|
+
--base <snapshot.md> : Compare against an old snapshot file
|
|
108
|
+
3. eck-snapshot profile [name] Snapshot filtered by profile (from .eck/profiles.json)
|
|
109
|
+
No arg = list available profiles
|
|
110
|
+
Example: eck-snapshot profile backend
|
|
111
|
+
Multiple: eck-snapshot profile backend,api
|
|
112
|
+
4. eck-snapshot scout [0-9] Scout external repo. Depths:
|
|
113
|
+
0: Tree only (default)
|
|
114
|
+
1-4: Truncated (10, 30, 60, 100 lines)
|
|
115
|
+
5: Skeleton (Signatures only)
|
|
116
|
+
6: Skeleton + docs
|
|
117
|
+
7-9: Full content (500, 1000, unlimited)
|
|
118
|
+
5. eck-snapshot fetch <glob> Fetch specific files (e.g., "src/**/*.js")
|
|
119
|
+
6. eck-snapshot link [0-9] Linked companion snapshot (same depths)
|
|
120
|
+
7. eck-snapshot booklm Export for NotebookLM (Scout - fetch generator)
|
|
121
|
+
8. eck-snapshot notelm Export for NotebookLM (Architect - experimental)
|
|
122
|
+
9. eck-snapshot notebook Export for NotebookLM (Primary Project)
|
|
123
|
+
eck-snapshot notebook link 5 Export Linked Project for NotebookLM (chunked)
|
|
124
|
+
eck-snapshot notebook scout 5 Export Scouted Project for NotebookLM (chunked)
|
|
125
|
+
10. eck-snapshot setup-mcp Configure AI agents (Claude Code, OpenCode)
|
|
126
|
+
10. eck-snapshot detect Detect project type and active filters
|
|
127
|
+
11. eck-snapshot doctor Check project health and stubs
|
|
128
|
+
|
|
129
|
+
[FEEDBACK]
|
|
130
|
+
eck-snapshot -e "message" Send feedback/ideas to developers (read by AI)
|
|
131
|
+
eck-snapshot -E "message" Send urgent bug report
|
|
132
|
+
|
|
133
|
+
[DATENSCHUTZ / PRIVACY]
|
|
134
|
+
We respect your privacy. By default, eck-snapshot collects anonymous usage counts
|
|
135
|
+
and crash logs to improve the tool. NO source code or sensitive data is ever sent.
|
|
136
|
+
To completely disable background telemetry:
|
|
137
|
+
eck-snapshot telemetry disable
|
|
138
|
+
To re-enable it:
|
|
139
|
+
eck-snapshot telemetry enable
|
|
158
140
|
`;
|
|
159
141
|
|
|
160
142
|
program
|
|
161
143
|
.name('eck-snapshot')
|
|
162
|
-
.description('A lightweight, platform-independent CLI for creating project snapshots.')
|
|
163
144
|
.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
|
-
|
|
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);
|
|
145
|
+
.addHelpText('before', helpGuide)
|
|
146
|
+
.argument('[payload]', 'JSON string representing the MCP tool call')
|
|
147
|
+
.option('-e, --feedback <message>', 'Send feedback or report an issue to developers')
|
|
148
|
+
.option('-E, --urgent-feedback <message>', 'Send urgent feedback to developers')
|
|
149
|
+
.action(async (payloadStr, options) => {
|
|
150
|
+
const globalConfig = await getGlobalConfig();
|
|
151
|
+
|
|
152
|
+
// --- Handle Feedback Flags ---
|
|
153
|
+
if (options.feedback || options.urgentFeedback) {
|
|
154
|
+
const msg = options.feedback || options.urgentFeedback;
|
|
155
|
+
const type = options.urgentFeedback ? 'URGENT' : 'NORMAL';
|
|
156
|
+
const queuePath = path.join(process.cwd(), '.eck', 'telemetry_queue.json');
|
|
157
|
+
|
|
158
|
+
let queue = { instanceId: globalConfig.instanceId, feedback: [], usage: {}, errors: [] };
|
|
159
|
+
try {
|
|
160
|
+
const existing = JSON.parse(await fs.readFile(queuePath, 'utf-8'));
|
|
161
|
+
queue = { ...queue, ...existing };
|
|
162
|
+
} catch(e) { /* no existing queue */ }
|
|
163
|
+
|
|
164
|
+
queue.feedback.push({ type, message: msg, date: new Date().toISOString() });
|
|
165
|
+
|
|
166
|
+
await fs.mkdir(path.dirname(queuePath), { recursive: true }).catch(() => {});
|
|
167
|
+
await ensureSnapshotsInGitignore(process.cwd()).catch(() => {});
|
|
168
|
+
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
|
|
169
|
+
|
|
170
|
+
console.log(chalk.green('Feedback saved locally. It will be sent to developers during the next telemetry sync.'));
|
|
171
|
+
console.log(chalk.gray('(Note: Messages are processed by AI for developers)'));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
302
174
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
175
|
+
// Default behavior for human users: empty call = full snapshot
|
|
176
|
+
if (!payloadStr) {
|
|
177
|
+
console.log(chalk.cyan('🚀 No arguments provided. Defaulting to full repository snapshot...'));
|
|
178
|
+
console.log(chalk.gray('💡 Run `eck-snapshot -h` to see all available JSON tools.\n'));
|
|
179
|
+
payloadStr = '{"name": "eck_snapshot", "arguments": {}}';
|
|
180
|
+
}
|
|
309
181
|
|
|
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) => {
|
|
182
|
+
let payload;
|
|
317
183
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.
|
|
184
|
+
payload = JSON.parse(payloadStr.trim());
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.error(chalk.red('❌ Error: Input must be a valid JSON string.'));
|
|
187
|
+
console.log(chalk.yellow(`Example: eck-snapshot '{"name": "eck_snapshot"}'`));
|
|
322
188
|
process.exit(1);
|
|
323
189
|
}
|
|
324
|
-
});
|
|
325
190
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
191
|
+
const toolName = payload.name;
|
|
192
|
+
const args = payload.arguments || {};
|
|
193
|
+
|
|
194
|
+
// --- Handle Telemetry Config Command ---
|
|
195
|
+
if (toolName === 'eck_telemetry') {
|
|
196
|
+
if (args.action === 'disable') {
|
|
197
|
+
await setGlobalConfig({ telemetryEnabled: false });
|
|
198
|
+
console.log(chalk.yellow('Background telemetry has been disabled.'));
|
|
199
|
+
} else if (args.action === 'enable') {
|
|
200
|
+
await setGlobalConfig({ telemetryEnabled: true });
|
|
201
|
+
console.log(chalk.green('Background telemetry has been enabled. Thank you for supporting the project!'));
|
|
202
|
+
} else {
|
|
203
|
+
console.log(chalk.blue(`Telemetry is currently: ${globalConfig.telemetryEnabled ? 'ENABLED' : 'DISABLED'}`));
|
|
204
|
+
console.log(chalk.gray(`Instance ID: ${globalConfig.instanceId}`));
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
340
207
|
}
|
|
341
|
-
});
|
|
342
208
|
|
|
209
|
+
const cwd = process.cwd();
|
|
210
|
+
const queuePath = path.join(cwd, '.eck', 'telemetry_queue.json');
|
|
343
211
|
|
|
212
|
+
try {
|
|
213
|
+
// --- Track Usage Locally (Guarded by Privacy Opt-in) ---
|
|
214
|
+
if (globalConfig.telemetryEnabled) {
|
|
215
|
+
try {
|
|
216
|
+
let queue = { instanceId: globalConfig.instanceId, feedback: [], usage: {}, errors: [] };
|
|
217
|
+
try {
|
|
218
|
+
const existing = JSON.parse(await fs.readFile(queuePath, 'utf-8'));
|
|
219
|
+
queue = { ...queue, ...existing };
|
|
220
|
+
} catch(e) { /* no existing queue */ }
|
|
221
|
+
queue.usage[toolName] = (queue.usage[toolName] || 0) + 1;
|
|
222
|
+
await fs.mkdir(path.dirname(queuePath), { recursive: true }).catch(() => {});
|
|
223
|
+
await ensureSnapshotsInGitignore(cwd).catch(() => {});
|
|
224
|
+
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
|
|
225
|
+
} catch(e) { /* ignore tracking errors */ }
|
|
226
|
+
}
|
|
344
227
|
|
|
228
|
+
switch (toolName) {
|
|
229
|
+
case 'eck_snapshot':
|
|
230
|
+
await createRepoSnapshot(cwd, args);
|
|
231
|
+
break;
|
|
232
|
+
case 'eck_update':
|
|
233
|
+
await updateSnapshot(cwd, args);
|
|
234
|
+
break;
|
|
235
|
+
case 'eck_update_auto':
|
|
236
|
+
await updateSnapshotJson(cwd, args);
|
|
237
|
+
break;
|
|
238
|
+
case 'eck_setup_mcp':
|
|
239
|
+
await setupMcp(args);
|
|
240
|
+
break;
|
|
241
|
+
case 'eck_detect':
|
|
242
|
+
await detectProject(cwd, args);
|
|
243
|
+
break;
|
|
244
|
+
case 'eck_doctor':
|
|
245
|
+
await runDoctor(cwd);
|
|
246
|
+
break;
|
|
247
|
+
case 'eck_scout':
|
|
248
|
+
case 'eck_fetch':
|
|
249
|
+
await runReconTool(payload);
|
|
250
|
+
break;
|
|
251
|
+
case 'eck_train_tokens':
|
|
252
|
+
case 'eck_token_stats':
|
|
253
|
+
await runTokenTools(payload);
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
console.log(chalk.red(`❌ Unknown tool: "${toolName}"`));
|
|
257
|
+
console.log(chalk.yellow('Run `eck-snapshot -h` to see available JSON tools.'));
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
// --- Track Errors Locally (Guarded by Privacy Opt-in) ---
|
|
262
|
+
if (globalConfig.telemetryEnabled) {
|
|
263
|
+
try {
|
|
264
|
+
let queue = { instanceId: globalConfig.instanceId, feedback: [], usage: {}, errors: [] };
|
|
265
|
+
try {
|
|
266
|
+
const existing = JSON.parse(await fs.readFile(queuePath, 'utf-8'));
|
|
267
|
+
queue = { ...queue, ...existing };
|
|
268
|
+
} catch(e) { /* no existing queue */ }
|
|
269
|
+
queue.errors.push({ tool: toolName, error: err.message, date: new Date().toISOString() });
|
|
270
|
+
await fs.mkdir(path.dirname(queuePath), { recursive: true }).catch(() => {});
|
|
271
|
+
await ensureSnapshotsInGitignore(cwd).catch(() => {});
|
|
272
|
+
await fs.writeFile(queuePath, JSON.stringify(queue, null, 2));
|
|
273
|
+
} catch(e) { /* ignore tracking errors */ }
|
|
274
|
+
}
|
|
345
275
|
|
|
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);
|
|
276
|
+
console.error(chalk.red(`❌ Execution failed for ${toolName}:`), err.message);
|
|
358
277
|
process.exit(1);
|
|
359
278
|
}
|
|
360
279
|
});
|
|
361
280
|
|
|
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
|
-
});
|
|
281
|
+
// Start version check in background (non-blocking)
|
|
282
|
+
checkForUpdates(pkg.version);
|
|
425
283
|
|
|
426
284
|
program.parse(process.argv);
|
|
427
285
|
}
|
|
286
|
+
|
|
287
|
+
function checkForUpdates(currentVersion) {
|
|
288
|
+
import('execa').then(({ execa }) => {
|
|
289
|
+
execa('npm', ['view', '@xelth/eck-snapshot', 'version'], { timeout: 5000 })
|
|
290
|
+
.then(({ stdout }) => {
|
|
291
|
+
const latest = stdout.trim();
|
|
292
|
+
if (latest && latest !== currentVersion) {
|
|
293
|
+
console.error(`\n${chalk.yellow(`⬆ Update available: ${currentVersion} → ${latest}`)}`);
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
.catch(() => {});
|
|
297
|
+
});
|
|
298
|
+
}
|