cc4pm 1.8.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/.claude-plugin/README.md +17 -0
- package/.claude-plugin/plugin.json +25 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/README.zh-CN.md +134 -0
- package/contexts/dev.md +20 -0
- package/contexts/research.md +26 -0
- package/contexts/review.md +22 -0
- package/examples/CLAUDE.md +100 -0
- package/examples/statusline.json +19 -0
- package/examples/user-CLAUDE.md +109 -0
- package/install.sh +17 -0
- package/manifests/install-components.json +173 -0
- package/manifests/install-modules.json +335 -0
- package/manifests/install-profiles.json +75 -0
- package/package.json +117 -0
- package/schemas/ecc-install-config.schema.json +58 -0
- package/schemas/hooks.schema.json +197 -0
- package/schemas/install-components.schema.json +56 -0
- package/schemas/install-modules.schema.json +105 -0
- package/schemas/install-profiles.schema.json +45 -0
- package/schemas/install-state.schema.json +210 -0
- package/schemas/package-manager.schema.json +23 -0
- package/schemas/plugin.schema.json +58 -0
- package/scripts/ci/catalog.js +83 -0
- package/scripts/ci/validate-agents.js +81 -0
- package/scripts/ci/validate-commands.js +135 -0
- package/scripts/ci/validate-hooks.js +239 -0
- package/scripts/ci/validate-install-manifests.js +211 -0
- package/scripts/ci/validate-no-personal-paths.js +63 -0
- package/scripts/ci/validate-rules.js +81 -0
- package/scripts/ci/validate-skills.js +54 -0
- package/scripts/claw.js +468 -0
- package/scripts/doctor.js +110 -0
- package/scripts/ecc.js +194 -0
- package/scripts/hooks/auto-tmux-dev.js +88 -0
- package/scripts/hooks/check-console-log.js +71 -0
- package/scripts/hooks/check-hook-enabled.js +12 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +100 -0
- package/scripts/hooks/insaits-security-monitor.py +269 -0
- package/scripts/hooks/insaits-security-wrapper.js +88 -0
- package/scripts/hooks/post-bash-build-complete.js +27 -0
- package/scripts/hooks/post-bash-pr-created.js +36 -0
- package/scripts/hooks/post-edit-console-warn.js +54 -0
- package/scripts/hooks/post-edit-format.js +109 -0
- package/scripts/hooks/post-edit-typecheck.js +96 -0
- package/scripts/hooks/pre-bash-dev-server-block.js +187 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +28 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +33 -0
- package/scripts/hooks/pre-compact.js +48 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +168 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +120 -0
- package/scripts/hooks/session-end-marker.js +15 -0
- package/scripts/hooks/session-end.js +299 -0
- package/scripts/hooks/session-start.js +97 -0
- package/scripts/hooks/suggest-compact.js +80 -0
- package/scripts/install-apply.js +137 -0
- package/scripts/install-plan.js +254 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install/apply.js +23 -0
- package/scripts/lib/install/config.js +82 -0
- package/scripts/lib/install/request.js +113 -0
- package/scripts/lib/install/runtime.js +42 -0
- package/scripts/lib/install-executor.js +605 -0
- package/scripts/lib/install-lifecycle.js +763 -0
- package/scripts/lib/install-manifests.js +305 -0
- package/scripts/lib/install-state.js +120 -0
- package/scripts/lib/install-targets/antigravity-project.js +9 -0
- package/scripts/lib/install-targets/claude-home.js +10 -0
- package/scripts/lib/install-targets/codex-home.js +10 -0
- package/scripts/lib/install-targets/cursor-project.js +10 -0
- package/scripts/lib/install-targets/helpers.js +89 -0
- package/scripts/lib/install-targets/opencode-home.js +10 -0
- package/scripts/lib/install-targets/registry.js +64 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.d.ts +119 -0
- package/scripts/lib/package-manager.js +431 -0
- package/scripts/lib/project-detect.js +428 -0
- package/scripts/lib/resolve-formatter.js +185 -0
- package/scripts/lib/session-adapters/canonical-session.js +138 -0
- package/scripts/lib/session-adapters/claude-history.js +149 -0
- package/scripts/lib/session-adapters/dmux-tmux.js +80 -0
- package/scripts/lib/session-adapters/registry.js +111 -0
- package/scripts/lib/session-aliases.d.ts +136 -0
- package/scripts/lib/session-aliases.js +481 -0
- package/scripts/lib/session-manager.d.ts +131 -0
- package/scripts/lib/session-manager.js +464 -0
- package/scripts/lib/shell-split.js +86 -0
- package/scripts/lib/skill-improvement/amendify.js +89 -0
- package/scripts/lib/skill-improvement/evaluate.js +59 -0
- package/scripts/lib/skill-improvement/health.js +118 -0
- package/scripts/lib/skill-improvement/observations.js +108 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +491 -0
- package/scripts/lib/utils.d.ts +183 -0
- package/scripts/lib/utils.js +543 -0
- package/scripts/list-installed.js +90 -0
- package/scripts/orchestrate-codex-worker.sh +92 -0
- package/scripts/orchestrate-worktrees.js +108 -0
- package/scripts/orchestration-status.js +62 -0
- package/scripts/repair.js +97 -0
- package/scripts/session-inspect.js +150 -0
- package/scripts/setup-package-manager.js +204 -0
- package/scripts/skill-create-output.js +244 -0
- package/scripts/uninstall.js +96 -0
package/scripts/claw.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* NanoClaw v2 — Barebones Agent REPL for cc4pm
|
|
4
|
+
*
|
|
5
|
+
* Zero external dependencies. Session-aware REPL around `claude -p`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const { spawnSync } = require('child_process');
|
|
14
|
+
const readline = require('readline');
|
|
15
|
+
|
|
16
|
+
const SESSION_NAME_RE = /^[a-zA-Z0-9][-a-zA-Z0-9]*$/;
|
|
17
|
+
const DEFAULT_MODEL = process.env.CLAW_MODEL || 'sonnet';
|
|
18
|
+
const DEFAULT_COMPACT_KEEP_TURNS = 20;
|
|
19
|
+
|
|
20
|
+
function isValidSessionName(name) {
|
|
21
|
+
return typeof name === 'string' && name.length > 0 && SESSION_NAME_RE.test(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getClawDir() {
|
|
25
|
+
return path.join(os.homedir(), '.claude', 'claw');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSessionPath(name) {
|
|
29
|
+
return path.join(getClawDir(), `${name}.md`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listSessions(dir) {
|
|
33
|
+
const clawDir = dir || getClawDir();
|
|
34
|
+
if (!fs.existsSync(clawDir)) return [];
|
|
35
|
+
return fs.readdirSync(clawDir)
|
|
36
|
+
.filter(f => f.endsWith('.md'))
|
|
37
|
+
.map(f => f.replace(/\.md$/, ''));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function loadHistory(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
43
|
+
} catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function appendTurn(filePath, role, content, timestamp) {
|
|
49
|
+
const ts = timestamp || new Date().toISOString();
|
|
50
|
+
const entry = `### [${ts}] ${role}\n${content}\n---\n`;
|
|
51
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
52
|
+
fs.appendFileSync(filePath, entry, 'utf8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeSkillList(raw) {
|
|
56
|
+
if (!raw) return [];
|
|
57
|
+
if (Array.isArray(raw)) return raw.map(s => String(s).trim()).filter(Boolean);
|
|
58
|
+
return String(raw).split(',').map(s => s.trim()).filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function loadECCContext(skillList) {
|
|
62
|
+
const requested = normalizeSkillList(skillList !== undefined ? skillList : process.env.CLAW_SKILLS || '');
|
|
63
|
+
if (requested.length === 0) return '';
|
|
64
|
+
|
|
65
|
+
const chunks = [];
|
|
66
|
+
for (const name of requested) {
|
|
67
|
+
const skillPath = path.join(process.cwd(), 'skills', name, 'SKILL.md');
|
|
68
|
+
try {
|
|
69
|
+
chunks.push(fs.readFileSync(skillPath, 'utf8'));
|
|
70
|
+
} catch {
|
|
71
|
+
// Skip missing skills silently to keep REPL usable.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return chunks.join('\n\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildPrompt(systemPrompt, history, userMessage) {
|
|
79
|
+
const parts = [];
|
|
80
|
+
if (systemPrompt) parts.push(`=== SYSTEM CONTEXT ===\n${systemPrompt}\n`);
|
|
81
|
+
if (history) parts.push(`=== CONVERSATION HISTORY ===\n${history}\n`);
|
|
82
|
+
parts.push(`=== USER MESSAGE ===\n${userMessage}`);
|
|
83
|
+
return parts.join('\n');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function askClaude(systemPrompt, history, userMessage, model) {
|
|
87
|
+
const fullPrompt = buildPrompt(systemPrompt, history, userMessage);
|
|
88
|
+
const args = [];
|
|
89
|
+
if (model) {
|
|
90
|
+
args.push('--model', model);
|
|
91
|
+
}
|
|
92
|
+
args.push('-p', fullPrompt);
|
|
93
|
+
|
|
94
|
+
const result = spawnSync('claude', args, {
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
97
|
+
env: { ...process.env, CLAUDECODE: '' },
|
|
98
|
+
timeout: 300000,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (result.error) {
|
|
102
|
+
return `[Error: ${result.error.message}]`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (result.status !== 0 && result.stderr) {
|
|
106
|
+
return `[Error: claude exited with code ${result.status}: ${result.stderr.trim()}]`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (result.stdout || '').trim();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseTurns(history) {
|
|
113
|
+
const turns = [];
|
|
114
|
+
const regex = /### \[([^\]]+)\] ([^\n]+)\n([\s\S]*?)\n---\n/g;
|
|
115
|
+
let match;
|
|
116
|
+
while ((match = regex.exec(history)) !== null) {
|
|
117
|
+
turns.push({ timestamp: match[1], role: match[2], content: match[3] });
|
|
118
|
+
}
|
|
119
|
+
return turns;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function estimateTokenCount(text) {
|
|
123
|
+
return Math.ceil((text || '').length / 4);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getSessionMetrics(filePath) {
|
|
127
|
+
const history = loadHistory(filePath);
|
|
128
|
+
const turns = parseTurns(history);
|
|
129
|
+
const charCount = history.length;
|
|
130
|
+
const tokenEstimate = estimateTokenCount(history);
|
|
131
|
+
const userTurns = turns.filter(t => t.role === 'User').length;
|
|
132
|
+
const assistantTurns = turns.filter(t => t.role === 'Assistant').length;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
turns: turns.length,
|
|
136
|
+
userTurns,
|
|
137
|
+
assistantTurns,
|
|
138
|
+
charCount,
|
|
139
|
+
tokenEstimate,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function searchSessions(query, dir) {
|
|
144
|
+
const q = String(query || '').toLowerCase().trim();
|
|
145
|
+
if (!q) return [];
|
|
146
|
+
|
|
147
|
+
const sessionDir = dir || getClawDir();
|
|
148
|
+
const sessions = listSessions(sessionDir);
|
|
149
|
+
const results = [];
|
|
150
|
+
for (const name of sessions) {
|
|
151
|
+
const p = path.join(sessionDir, `${name}.md`);
|
|
152
|
+
const content = loadHistory(p);
|
|
153
|
+
if (!content) continue;
|
|
154
|
+
|
|
155
|
+
const idx = content.toLowerCase().indexOf(q);
|
|
156
|
+
if (idx >= 0) {
|
|
157
|
+
const start = Math.max(0, idx - 40);
|
|
158
|
+
const end = Math.min(content.length, idx + q.length + 40);
|
|
159
|
+
const snippet = content.slice(start, end).replace(/\n/g, ' ');
|
|
160
|
+
results.push({ session: name, snippet });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function compactSession(filePath, keepTurns = DEFAULT_COMPACT_KEEP_TURNS) {
|
|
167
|
+
const history = loadHistory(filePath);
|
|
168
|
+
if (!history) return false;
|
|
169
|
+
|
|
170
|
+
const turns = parseTurns(history);
|
|
171
|
+
if (turns.length <= keepTurns) return false;
|
|
172
|
+
|
|
173
|
+
const retained = turns.slice(-keepTurns);
|
|
174
|
+
const compactedHeader = `# NanoClaw Compaction\nCompacted at: ${new Date().toISOString()}\nRetained turns: ${keepTurns}/${turns.length}\n\n---\n`;
|
|
175
|
+
const compactedTurns = retained.map(t => `### [${t.timestamp}] ${t.role}\n${t.content}\n---\n`).join('');
|
|
176
|
+
fs.writeFileSync(filePath, compactedHeader + compactedTurns, 'utf8');
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function exportSession(filePath, format, outputPath) {
|
|
181
|
+
const history = loadHistory(filePath);
|
|
182
|
+
const sessionName = path.basename(filePath, '.md');
|
|
183
|
+
const fmt = String(format || 'md').toLowerCase();
|
|
184
|
+
|
|
185
|
+
if (!history) {
|
|
186
|
+
return { ok: false, message: 'No session history to export.' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const dir = path.dirname(filePath);
|
|
190
|
+
let out = outputPath;
|
|
191
|
+
if (!out) {
|
|
192
|
+
out = path.join(dir, `${sessionName}.export.${fmt === 'markdown' ? 'md' : fmt}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (fmt === 'md' || fmt === 'markdown') {
|
|
196
|
+
fs.writeFileSync(out, history, 'utf8');
|
|
197
|
+
return { ok: true, path: out };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (fmt === 'json') {
|
|
201
|
+
const turns = parseTurns(history);
|
|
202
|
+
fs.writeFileSync(out, JSON.stringify({ session: sessionName, turns }, null, 2), 'utf8');
|
|
203
|
+
return { ok: true, path: out };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (fmt === 'txt' || fmt === 'text') {
|
|
207
|
+
const turns = parseTurns(history);
|
|
208
|
+
const txt = turns.map(t => `[${t.timestamp}] ${t.role}:\n${t.content}\n`).join('\n');
|
|
209
|
+
fs.writeFileSync(out, txt, 'utf8');
|
|
210
|
+
return { ok: true, path: out };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { ok: false, message: `Unsupported export format: ${format}` };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function branchSession(currentSessionPath, newSessionName, targetDir = getClawDir()) {
|
|
217
|
+
if (!isValidSessionName(newSessionName)) {
|
|
218
|
+
return { ok: false, message: `Invalid branch session name: ${newSessionName}` };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const target = path.join(targetDir, `${newSessionName}.md`);
|
|
222
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
223
|
+
|
|
224
|
+
const content = loadHistory(currentSessionPath);
|
|
225
|
+
fs.writeFileSync(target, content, 'utf8');
|
|
226
|
+
return { ok: true, path: target, session: newSessionName };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function skillExists(skillName) {
|
|
230
|
+
const p = path.join(process.cwd(), 'skills', skillName, 'SKILL.md');
|
|
231
|
+
return fs.existsSync(p);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function handleClear(sessionPath) {
|
|
235
|
+
fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
|
|
236
|
+
fs.writeFileSync(sessionPath, '', 'utf8');
|
|
237
|
+
console.log('Session cleared.');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function handleHistory(sessionPath) {
|
|
241
|
+
const history = loadHistory(sessionPath);
|
|
242
|
+
if (!history) {
|
|
243
|
+
console.log('(no history)');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(history);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function handleSessions(dir) {
|
|
250
|
+
const sessions = listSessions(dir);
|
|
251
|
+
if (sessions.length === 0) {
|
|
252
|
+
console.log('(no sessions)');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log('Sessions:');
|
|
257
|
+
for (const s of sessions) {
|
|
258
|
+
console.log(` - ${s}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function handleHelp() {
|
|
263
|
+
console.log('NanoClaw REPL Commands:');
|
|
264
|
+
console.log(' /help Show this help');
|
|
265
|
+
console.log(' /clear Clear current session history');
|
|
266
|
+
console.log(' /history Print full conversation history');
|
|
267
|
+
console.log(' /sessions List saved sessions');
|
|
268
|
+
console.log(' /model [name] Show/set model');
|
|
269
|
+
console.log(' /load <skill-name> Load a skill into active context');
|
|
270
|
+
console.log(' /branch <session-name> Branch current session into a new session');
|
|
271
|
+
console.log(' /search <query> Search query across sessions');
|
|
272
|
+
console.log(' /compact Keep recent turns, compact older context');
|
|
273
|
+
console.log(' /export <md|json|txt> [path] Export current session');
|
|
274
|
+
console.log(' /metrics Show session metrics');
|
|
275
|
+
console.log(' exit Quit the REPL');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function main() {
|
|
279
|
+
const initialSessionName = process.env.CLAW_SESSION || 'default';
|
|
280
|
+
if (!isValidSessionName(initialSessionName)) {
|
|
281
|
+
console.error(`Error: Invalid session name "${initialSessionName}". Use alphanumeric characters and hyphens only.`);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fs.mkdirSync(getClawDir(), { recursive: true });
|
|
286
|
+
|
|
287
|
+
const state = {
|
|
288
|
+
sessionName: initialSessionName,
|
|
289
|
+
sessionPath: getSessionPath(initialSessionName),
|
|
290
|
+
model: DEFAULT_MODEL,
|
|
291
|
+
skills: normalizeSkillList(process.env.CLAW_SKILLS || ''),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
let eccContext = loadECCContext(state.skills);
|
|
295
|
+
|
|
296
|
+
const loadedCount = state.skills.filter(skillExists).length;
|
|
297
|
+
|
|
298
|
+
console.log(`NanoClaw v2 — Session: ${state.sessionName}`);
|
|
299
|
+
console.log(`Model: ${state.model}`);
|
|
300
|
+
if (loadedCount > 0) {
|
|
301
|
+
console.log(`Loaded ${loadedCount} skill(s) as context.`);
|
|
302
|
+
}
|
|
303
|
+
console.log('Type /help for commands, exit to quit.\n');
|
|
304
|
+
|
|
305
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
306
|
+
|
|
307
|
+
const prompt = () => {
|
|
308
|
+
rl.question('claw> ', (input) => {
|
|
309
|
+
const line = input.trim();
|
|
310
|
+
if (!line) return prompt();
|
|
311
|
+
|
|
312
|
+
if (line === 'exit') {
|
|
313
|
+
console.log('Goodbye.');
|
|
314
|
+
rl.close();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (line === '/help') {
|
|
319
|
+
handleHelp();
|
|
320
|
+
return prompt();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (line === '/clear') {
|
|
324
|
+
handleClear(state.sessionPath);
|
|
325
|
+
return prompt();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (line === '/history') {
|
|
329
|
+
handleHistory(state.sessionPath);
|
|
330
|
+
return prompt();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (line === '/sessions') {
|
|
334
|
+
handleSessions();
|
|
335
|
+
return prompt();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (line.startsWith('/model')) {
|
|
339
|
+
const model = line.replace('/model', '').trim();
|
|
340
|
+
if (!model) {
|
|
341
|
+
console.log(`Current model: ${state.model}`);
|
|
342
|
+
} else {
|
|
343
|
+
state.model = model;
|
|
344
|
+
console.log(`Model set to: ${state.model}`);
|
|
345
|
+
}
|
|
346
|
+
return prompt();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (line.startsWith('/load ')) {
|
|
350
|
+
const skill = line.replace('/load', '').trim();
|
|
351
|
+
if (!skill) {
|
|
352
|
+
console.log('Usage: /load <skill-name>');
|
|
353
|
+
return prompt();
|
|
354
|
+
}
|
|
355
|
+
if (!skillExists(skill)) {
|
|
356
|
+
console.log(`Skill not found: ${skill}`);
|
|
357
|
+
return prompt();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!state.skills.includes(skill)) {
|
|
361
|
+
state.skills.push(skill);
|
|
362
|
+
}
|
|
363
|
+
eccContext = loadECCContext(state.skills);
|
|
364
|
+
console.log(`Loaded skill: ${skill}`);
|
|
365
|
+
return prompt();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (line.startsWith('/branch ')) {
|
|
369
|
+
const target = line.replace('/branch', '').trim();
|
|
370
|
+
const result = branchSession(state.sessionPath, target);
|
|
371
|
+
if (!result.ok) {
|
|
372
|
+
console.log(result.message);
|
|
373
|
+
return prompt();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
state.sessionName = result.session;
|
|
377
|
+
state.sessionPath = result.path;
|
|
378
|
+
console.log(`Branched to session: ${state.sessionName}`);
|
|
379
|
+
return prompt();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (line.startsWith('/search ')) {
|
|
383
|
+
const query = line.replace('/search', '').trim();
|
|
384
|
+
const matches = searchSessions(query);
|
|
385
|
+
if (matches.length === 0) {
|
|
386
|
+
console.log('(no matches)');
|
|
387
|
+
return prompt();
|
|
388
|
+
}
|
|
389
|
+
console.log(`Found ${matches.length} match(es):`);
|
|
390
|
+
for (const match of matches) {
|
|
391
|
+
console.log(`- ${match.session}: ${match.snippet}`);
|
|
392
|
+
}
|
|
393
|
+
return prompt();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (line === '/compact') {
|
|
397
|
+
const changed = compactSession(state.sessionPath);
|
|
398
|
+
console.log(changed ? 'Session compacted.' : 'No compaction needed.');
|
|
399
|
+
return prompt();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (line.startsWith('/export ')) {
|
|
403
|
+
const parts = line.split(/\s+/).filter(Boolean);
|
|
404
|
+
const format = parts[1];
|
|
405
|
+
const outputPath = parts[2];
|
|
406
|
+
if (!format) {
|
|
407
|
+
console.log('Usage: /export <md|json|txt> [path]');
|
|
408
|
+
return prompt();
|
|
409
|
+
}
|
|
410
|
+
const result = exportSession(state.sessionPath, format, outputPath);
|
|
411
|
+
if (!result.ok) {
|
|
412
|
+
console.log(result.message);
|
|
413
|
+
} else {
|
|
414
|
+
console.log(`Exported: ${result.path}`);
|
|
415
|
+
}
|
|
416
|
+
return prompt();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (line === '/metrics') {
|
|
420
|
+
const m = getSessionMetrics(state.sessionPath);
|
|
421
|
+
console.log(`Session: ${state.sessionName}`);
|
|
422
|
+
console.log(`Model: ${state.model}`);
|
|
423
|
+
console.log(`Turns: ${m.turns} (user ${m.userTurns}, assistant ${m.assistantTurns})`);
|
|
424
|
+
console.log(`Chars: ${m.charCount}`);
|
|
425
|
+
console.log(`Estimated tokens: ${m.tokenEstimate}`);
|
|
426
|
+
return prompt();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Regular message
|
|
430
|
+
const history = loadHistory(state.sessionPath);
|
|
431
|
+
appendTurn(state.sessionPath, 'User', line);
|
|
432
|
+
const response = askClaude(eccContext, history, line, state.model);
|
|
433
|
+
console.log(`\n${response}\n`);
|
|
434
|
+
appendTurn(state.sessionPath, 'Assistant', response);
|
|
435
|
+
prompt();
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
prompt();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
module.exports = {
|
|
443
|
+
getClawDir,
|
|
444
|
+
getSessionPath,
|
|
445
|
+
listSessions,
|
|
446
|
+
loadHistory,
|
|
447
|
+
appendTurn,
|
|
448
|
+
loadECCContext,
|
|
449
|
+
buildPrompt,
|
|
450
|
+
askClaude,
|
|
451
|
+
isValidSessionName,
|
|
452
|
+
handleClear,
|
|
453
|
+
handleHistory,
|
|
454
|
+
handleSessions,
|
|
455
|
+
handleHelp,
|
|
456
|
+
parseTurns,
|
|
457
|
+
estimateTokenCount,
|
|
458
|
+
getSessionMetrics,
|
|
459
|
+
searchSessions,
|
|
460
|
+
compactSession,
|
|
461
|
+
exportSession,
|
|
462
|
+
branchSession,
|
|
463
|
+
main,
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
if (require.main === module) {
|
|
467
|
+
main();
|
|
468
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { buildDoctorReport } = require('./lib/install-lifecycle');
|
|
4
|
+
const { SUPPORTED_INSTALL_TARGETS } = require('./lib/install-manifests');
|
|
5
|
+
|
|
6
|
+
function showHelp(exitCode = 0) {
|
|
7
|
+
console.log(`
|
|
8
|
+
Usage: node scripts/doctor.js [--target <${SUPPORTED_INSTALL_TARGETS.join('|')}>] [--json]
|
|
9
|
+
|
|
10
|
+
Diagnose drift and missing managed files for cc4pm install-state in the current context.
|
|
11
|
+
`);
|
|
12
|
+
process.exit(exitCode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const args = argv.slice(2);
|
|
17
|
+
const parsed = {
|
|
18
|
+
targets: [],
|
|
19
|
+
json: false,
|
|
20
|
+
help: false,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
24
|
+
const arg = args[index];
|
|
25
|
+
|
|
26
|
+
if (arg === '--target') {
|
|
27
|
+
parsed.targets.push(args[index + 1] || null);
|
|
28
|
+
index += 1;
|
|
29
|
+
} else if (arg === '--json') {
|
|
30
|
+
parsed.json = true;
|
|
31
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
32
|
+
parsed.help = true;
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function statusLabel(status) {
|
|
42
|
+
if (status === 'ok') {
|
|
43
|
+
return 'OK';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (status === 'warning') {
|
|
47
|
+
return 'WARNING';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (status === 'error') {
|
|
51
|
+
return 'ERROR';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return status.toUpperCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printHuman(report) {
|
|
58
|
+
if (report.results.length === 0) {
|
|
59
|
+
console.log('No cc4pm install-state files found for the current home/project context.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('Doctor report:\n');
|
|
64
|
+
for (const result of report.results) {
|
|
65
|
+
console.log(`- ${result.adapter.id}`);
|
|
66
|
+
console.log(` Status: ${statusLabel(result.status)}`);
|
|
67
|
+
console.log(` Install-state: ${result.installStatePath}`);
|
|
68
|
+
|
|
69
|
+
if (result.issues.length === 0) {
|
|
70
|
+
console.log(' Issues: none');
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const issue of result.issues) {
|
|
75
|
+
console.log(` - [${issue.severity}] ${issue.code}: ${issue.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`\nSummary: checked=${report.summary.checkedCount}, ok=${report.summary.okCount}, warnings=${report.summary.warningCount}, errors=${report.summary.errorCount}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function main() {
|
|
83
|
+
try {
|
|
84
|
+
const options = parseArgs(process.argv);
|
|
85
|
+
if (options.help) {
|
|
86
|
+
showHelp(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const report = buildDoctorReport({
|
|
90
|
+
repoRoot: require('path').join(__dirname, '..'),
|
|
91
|
+
homeDir: process.env.HOME,
|
|
92
|
+
projectRoot: process.cwd(),
|
|
93
|
+
targets: options.targets,
|
|
94
|
+
});
|
|
95
|
+
const hasIssues = report.summary.errorCount > 0 || report.summary.warningCount > 0;
|
|
96
|
+
|
|
97
|
+
if (options.json) {
|
|
98
|
+
console.log(JSON.stringify(report, null, 2));
|
|
99
|
+
} else {
|
|
100
|
+
printHuman(report);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
process.exitCode = hasIssues ? 1 : 0;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`Error: ${error.message}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main();
|