cray-code 1.0.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 +316 -0
- package/dist/Tool.d.ts +217 -0
- package/dist/Tool.d.ts.map +1 -0
- package/dist/Tool.js +89 -0
- package/dist/Tool.js.map +1 -0
- package/dist/branding/logo.d.ts +8 -0
- package/dist/branding/logo.d.ts.map +1 -0
- package/dist/branding/logo.js +26 -0
- package/dist/branding/logo.js.map +1 -0
- package/dist/branding/theme.d.ts +27 -0
- package/dist/branding/theme.d.ts.map +1 -0
- package/dist/branding/theme.js +28 -0
- package/dist/branding/theme.js.map +1 -0
- package/dist/commands/registry.d.ts +32 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +759 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/components/MessageView.d.ts +12 -0
- package/dist/components/MessageView.d.ts.map +1 -0
- package/dist/components/MessageView.js +35 -0
- package/dist/components/MessageView.js.map +1 -0
- package/dist/components/PermissionPrompt.d.ts +11 -0
- package/dist/components/PermissionPrompt.d.ts.map +1 -0
- package/dist/components/PermissionPrompt.js +6 -0
- package/dist/components/PermissionPrompt.js.map +1 -0
- package/dist/components/PluginManager.d.ts +27 -0
- package/dist/components/PluginManager.d.ts.map +1 -0
- package/dist/components/PluginManager.js +391 -0
- package/dist/components/PluginManager.js.map +1 -0
- package/dist/components/ThinkingBlock.d.ts +27 -0
- package/dist/components/ThinkingBlock.d.ts.map +1 -0
- package/dist/components/ThinkingBlock.js +29 -0
- package/dist/components/ThinkingBlock.js.map +1 -0
- package/dist/components/ToolCallBlock.d.ts +14 -0
- package/dist/components/ToolCallBlock.d.ts.map +1 -0
- package/dist/components/ToolCallBlock.js +83 -0
- package/dist/components/ToolCallBlock.js.map +1 -0
- package/dist/components/TrustDialog.d.ts +20 -0
- package/dist/components/TrustDialog.d.ts.map +1 -0
- package/dist/components/TrustDialog.js +80 -0
- package/dist/components/TrustDialog.js.map +1 -0
- package/dist/context.d.ts +25 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +268 -0
- package/dist/context.js.map +1 -0
- package/dist/cray.d.ts +114 -0
- package/dist/cray.d.ts.map +1 -0
- package/dist/cray.js +338 -0
- package/dist/cray.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +122 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/registry.d.ts +106 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +695 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/query.d.ts +31 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +637 -0
- package/dist/query.js.map +1 -0
- package/dist/queryStream.d.ts +36 -0
- package/dist/queryStream.d.ts.map +1 -0
- package/dist/queryStream.js +704 -0
- package/dist/queryStream.js.map +1 -0
- package/dist/screens/ReplScreen.d.ts +22 -0
- package/dist/screens/ReplScreen.d.ts.map +1 -0
- package/dist/screens/ReplScreen.js +763 -0
- package/dist/screens/ReplScreen.js.map +1 -0
- package/dist/services/agentRunner.d.ts +39 -0
- package/dist/services/agentRunner.d.ts.map +1 -0
- package/dist/services/agentRunner.js +115 -0
- package/dist/services/agentRunner.js.map +1 -0
- package/dist/services/compact.d.ts +34 -0
- package/dist/services/compact.d.ts.map +1 -0
- package/dist/services/compact.js +179 -0
- package/dist/services/compact.js.map +1 -0
- package/dist/services/loadPluginCommands.d.ts +55 -0
- package/dist/services/loadPluginCommands.d.ts.map +1 -0
- package/dist/services/loadPluginCommands.js +219 -0
- package/dist/services/loadPluginCommands.js.map +1 -0
- package/dist/services/mcp/index.d.ts +3 -0
- package/dist/services/mcp/index.d.ts.map +1 -0
- package/dist/services/mcp/index.js +3 -0
- package/dist/services/mcp/index.js.map +1 -0
- package/dist/services/mcp/manager.d.ts +24 -0
- package/dist/services/mcp/manager.d.ts.map +1 -0
- package/dist/services/mcp/manager.js +138 -0
- package/dist/services/mcp/manager.js.map +1 -0
- package/dist/services/mcp/types.d.ts +52 -0
- package/dist/services/mcp/types.d.ts.map +1 -0
- package/dist/services/mcp/types.js +5 -0
- package/dist/services/mcp/types.js.map +1 -0
- package/dist/services/memory.d.ts +38 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +181 -0
- package/dist/services/memory.js.map +1 -0
- package/dist/services/permissionPrompt.d.ts +38 -0
- package/dist/services/permissionPrompt.d.ts.map +1 -0
- package/dist/services/permissionPrompt.js +83 -0
- package/dist/services/permissionPrompt.js.map +1 -0
- package/dist/services/permissions.d.ts +15 -0
- package/dist/services/permissions.d.ts.map +1 -0
- package/dist/services/permissions.js +237 -0
- package/dist/services/permissions.js.map +1 -0
- package/dist/services/sessionStorage.d.ts +51 -0
- package/dist/services/sessionStorage.d.ts.map +1 -0
- package/dist/services/sessionStorage.js +266 -0
- package/dist/services/sessionStorage.js.map +1 -0
- package/dist/setup.d.ts +22 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +160 -0
- package/dist/setup.js.map +1 -0
- package/dist/skills/bundledSkills.d.ts +18 -0
- package/dist/skills/bundledSkills.d.ts.map +1 -0
- package/dist/skills/bundledSkills.js +277 -0
- package/dist/skills/bundledSkills.js.map +1 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +3 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/loadSkillsDir.d.ts +45 -0
- package/dist/skills/loadSkillsDir.d.ts.map +1 -0
- package/dist/skills/loadSkillsDir.js +233 -0
- package/dist/skills/loadSkillsDir.js.map +1 -0
- package/dist/state/AppState.d.ts +70 -0
- package/dist/state/AppState.d.ts.map +1 -0
- package/dist/state/AppState.js +106 -0
- package/dist/state/AppState.js.map +1 -0
- package/dist/tools/AgentTool.d.ts +62 -0
- package/dist/tools/AgentTool.d.ts.map +1 -0
- package/dist/tools/AgentTool.js +133 -0
- package/dist/tools/AgentTool.js.map +1 -0
- package/dist/tools/AskUserQuestionTool.d.ts +60 -0
- package/dist/tools/AskUserQuestionTool.d.ts.map +1 -0
- package/dist/tools/AskUserQuestionTool.js +52 -0
- package/dist/tools/AskUserQuestionTool.js.map +1 -0
- package/dist/tools/BashTool.d.ts +33 -0
- package/dist/tools/BashTool.d.ts.map +1 -0
- package/dist/tools/BashTool.js +211 -0
- package/dist/tools/BashTool.js.map +1 -0
- package/dist/tools/EditTool.d.ts +24 -0
- package/dist/tools/EditTool.d.ts.map +1 -0
- package/dist/tools/EditTool.js +102 -0
- package/dist/tools/EditTool.js.map +1 -0
- package/dist/tools/GlobTool.d.ts +17 -0
- package/dist/tools/GlobTool.d.ts.map +1 -0
- package/dist/tools/GlobTool.js +65 -0
- package/dist/tools/GlobTool.js.map +1 -0
- package/dist/tools/GrepTool.d.ts +30 -0
- package/dist/tools/GrepTool.d.ts.map +1 -0
- package/dist/tools/GrepTool.js +140 -0
- package/dist/tools/GrepTool.js.map +1 -0
- package/dist/tools/MCPTool.d.ts +24 -0
- package/dist/tools/MCPTool.d.ts.map +1 -0
- package/dist/tools/MCPTool.js +67 -0
- package/dist/tools/MCPTool.js.map +1 -0
- package/dist/tools/NotebookEditTool.d.ts +28 -0
- package/dist/tools/NotebookEditTool.d.ts.map +1 -0
- package/dist/tools/NotebookEditTool.js +213 -0
- package/dist/tools/NotebookEditTool.js.map +1 -0
- package/dist/tools/NotebookReadTool.d.ts +19 -0
- package/dist/tools/NotebookReadTool.d.ts.map +1 -0
- package/dist/tools/NotebookReadTool.js +191 -0
- package/dist/tools/NotebookReadTool.js.map +1 -0
- package/dist/tools/PlanTools.d.ts +17 -0
- package/dist/tools/PlanTools.d.ts.map +1 -0
- package/dist/tools/PlanTools.js +65 -0
- package/dist/tools/PlanTools.js.map +1 -0
- package/dist/tools/ReadTool.d.ts +21 -0
- package/dist/tools/ReadTool.d.ts.map +1 -0
- package/dist/tools/ReadTool.js +202 -0
- package/dist/tools/ReadTool.js.map +1 -0
- package/dist/tools/SkillTool.d.ts +32 -0
- package/dist/tools/SkillTool.d.ts.map +1 -0
- package/dist/tools/SkillTool.js +217 -0
- package/dist/tools/SkillTool.js.map +1 -0
- package/dist/tools/TodoWriteTool.d.ts +35 -0
- package/dist/tools/TodoWriteTool.d.ts.map +1 -0
- package/dist/tools/TodoWriteTool.js +58 -0
- package/dist/tools/TodoWriteTool.js.map +1 -0
- package/dist/tools/WebFetchTool.d.ts +17 -0
- package/dist/tools/WebFetchTool.d.ts.map +1 -0
- package/dist/tools/WebFetchTool.js +97 -0
- package/dist/tools/WebFetchTool.js.map +1 -0
- package/dist/tools/WebSearchTool.d.ts +18 -0
- package/dist/tools/WebSearchTool.d.ts.map +1 -0
- package/dist/tools/WebSearchTool.js +76 -0
- package/dist/tools/WebSearchTool.js.map +1 -0
- package/dist/tools/WriteTool.d.ts +17 -0
- package/dist/tools/WriteTool.d.ts.map +1 -0
- package/dist/tools/WriteTool.js +84 -0
- package/dist/tools/WriteTool.js.map +1 -0
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +20 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools.d.ts +34 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +102 -0
- package/dist/tools.js.map +1 -0
- package/dist/types/events.d.ts +85 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +12 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/message.d.ts +71 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +5 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/permission.d.ts +56 -0
- package/dist/types/permission.d.ts.map +1 -0
- package/dist/types/permission.js +46 -0
- package/dist/types/permission.js.map +1 -0
- package/dist/types/processUserInput.d.ts +18 -0
- package/dist/types/processUserInput.d.ts.map +1 -0
- package/dist/types/processUserInput.js +8 -0
- package/dist/types/processUserInput.js.map +1 -0
- package/dist/types/tool.d.ts +32 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +5 -0
- package/dist/types/tool.js.map +1 -0
- package/dist/utils/compactBoundary.d.ts +11 -0
- package/dist/utils/compactBoundary.d.ts.map +1 -0
- package/dist/utils/compactBoundary.js +26 -0
- package/dist/utils/compactBoundary.js.map +1 -0
- package/dist/utils/configStore.d.ts +41 -0
- package/dist/utils/configStore.d.ts.map +1 -0
- package/dist/utils/configStore.js +111 -0
- package/dist/utils/configStore.js.map +1 -0
- package/dist/utils/forkedAgent.d.ts +40 -0
- package/dist/utils/forkedAgent.d.ts.map +1 -0
- package/dist/utils/forkedAgent.js +231 -0
- package/dist/utils/forkedAgent.js.map +1 -0
- package/dist/utils/messages.d.ts +14 -0
- package/dist/utils/messages.d.ts.map +1 -0
- package/dist/utils/messages.js +29 -0
- package/dist/utils/messages.js.map +1 -0
- package/dist/utils/sandbox.d.ts +22 -0
- package/dist/utils/sandbox.d.ts.map +1 -0
- package/dist/utils/sandbox.js +59 -0
- package/dist/utils/sandbox.js.map +1 -0
- package/dist/utils/sideQuestion.d.ts +29 -0
- package/dist/utils/sideQuestion.d.ts.map +1 -0
- package/dist/utils/sideQuestion.js +81 -0
- package/dist/utils/sideQuestion.js.map +1 -0
- package/install.ps1 +86 -0
- package/install.sh +92 -0
- package/package.json +68 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full Slash Command System for Cray Code — mirrors Claude Code's command set.
|
|
3
|
+
*
|
|
4
|
+
* Commands: /help /model /clear /config /status /setup /plan /review
|
|
5
|
+
* /init /compact /cost /memory /mcp /skills /plugin /idea
|
|
6
|
+
* /add-dir /permission /doctor /theme /terminal-setup /commit
|
|
7
|
+
* /pr-comments /resume /upgrade /exit
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readdirSync, statSync, writeFileSync, mkdirSync, readFileSync, unlinkSync, rmdirSync } from 'fs';
|
|
10
|
+
import { join, basename } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
// ─── Registry ───────────────────────────────────────────────────────
|
|
13
|
+
const commands = new Map();
|
|
14
|
+
export function registerCommand(cmd) {
|
|
15
|
+
commands.set(cmd.name, cmd);
|
|
16
|
+
cmd.aliases?.forEach((alias) => {
|
|
17
|
+
if (!commands.has(alias))
|
|
18
|
+
commands.set(alias, cmd);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function getCommand(name) {
|
|
22
|
+
return commands.get(name.replace(/^\//, ''));
|
|
23
|
+
}
|
|
24
|
+
export function getAllCommands() {
|
|
25
|
+
return [...new Set(commands.values())];
|
|
26
|
+
}
|
|
27
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
28
|
+
// SESSION COMMANDS
|
|
29
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
30
|
+
registerCommand({
|
|
31
|
+
name: 'help', description: 'Show available commands', category: 'help',
|
|
32
|
+
userInvocable: true,
|
|
33
|
+
async execute(args, _ctx) {
|
|
34
|
+
const filter = args[0];
|
|
35
|
+
const cats = {};
|
|
36
|
+
for (const cmd of getAllCommands()) {
|
|
37
|
+
if (!cmd.userInvocable)
|
|
38
|
+
continue;
|
|
39
|
+
(cats[cmd.category] ??= []).push(cmd.name);
|
|
40
|
+
}
|
|
41
|
+
const lines = ['\n Cray Code Commands', ' ─────────────────'];
|
|
42
|
+
for (const [cat, names] of Object.entries(cats)) {
|
|
43
|
+
if (filter && cat !== filter)
|
|
44
|
+
continue;
|
|
45
|
+
lines.push(`\n ${cat}`);
|
|
46
|
+
for (const n of names) {
|
|
47
|
+
const cmd = getCommand(n);
|
|
48
|
+
const hint = cmd.argumentHint ? ` ${cmd.argumentHint}` : '';
|
|
49
|
+
lines.push(` /${n}${hint} — ${cmd.description}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
lines.push('');
|
|
53
|
+
return { success: true, message: lines.join('\n') };
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
registerCommand({
|
|
57
|
+
name: 'clear', description: 'Clear conversation history', category: 'session',
|
|
58
|
+
userInvocable: true,
|
|
59
|
+
async execute() {
|
|
60
|
+
return { success: true, message: 'Conversation cleared. Start a new topic.' };
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
registerCommand({
|
|
64
|
+
name: 'resume', description: 'Resume a previous conversation', category: 'session',
|
|
65
|
+
userInvocable: true, argumentHint: '[session-id]',
|
|
66
|
+
async execute(args, ctx) {
|
|
67
|
+
const sid = args[0];
|
|
68
|
+
const { listSessions, loadSession } = await import('../services/sessionStorage.js');
|
|
69
|
+
if (!sid) {
|
|
70
|
+
// List available sessions
|
|
71
|
+
const sessions = await listSessions(ctx.cwd);
|
|
72
|
+
if (!sessions.length)
|
|
73
|
+
return { success: true, message: 'No saved sessions.\n\nEach conversation is automatically saved to ~/.cray/projects/<\nStart a conversation to create a saved session.' };
|
|
74
|
+
const lines = [`Saved sessions (${sessions.length}):`, ''];
|
|
75
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
76
|
+
const s = sessions[i];
|
|
77
|
+
const date = new Date(s.updatedAt).toLocaleDateString();
|
|
78
|
+
lines.push(` ${i + 1}. [${date}] ${s.firstPrompt.slice(0, 80)}`);
|
|
79
|
+
lines.push(` ID: ${s.sessionId} · ${s.messageCount} msgs`);
|
|
80
|
+
}
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push('Resume: /resume <session-id>');
|
|
83
|
+
return { success: true, message: lines.join('\n') };
|
|
84
|
+
}
|
|
85
|
+
// Resume specific session
|
|
86
|
+
const messages = await loadSession(ctx.cwd, sid);
|
|
87
|
+
if (!messages.length)
|
|
88
|
+
return { success: false, message: `Session "${sid}" not found. Use /resume without args to list available sessions.` };
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
message: `Loaded ${messages.length} messages from session "${sid}". Restart to resume: cray --resume ${sid}`,
|
|
92
|
+
data: { sessionId: sid, messageCount: messages.length },
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
97
|
+
// CONFIG COMMANDS
|
|
98
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
99
|
+
registerCommand({
|
|
100
|
+
name: 'config', aliases: ['cfg'],
|
|
101
|
+
description: 'View or change configuration', category: 'config',
|
|
102
|
+
userInvocable: true, argumentHint: '[key] [value]',
|
|
103
|
+
async execute(args, ctx) {
|
|
104
|
+
const s = ctx.settings;
|
|
105
|
+
const { maskApiKey, loadConfig, saveConfig } = await import('../utils/configStore.js');
|
|
106
|
+
if (args.length === 0) {
|
|
107
|
+
const lines = ['Configuration:', ''];
|
|
108
|
+
for (const [k, v] of Object.entries(s)) {
|
|
109
|
+
const display = k === 'apiKey' ? maskApiKey(v) : v;
|
|
110
|
+
lines.push(` ${k} = ${JSON.stringify(display)}`);
|
|
111
|
+
}
|
|
112
|
+
return { success: true, message: lines.join('\n') };
|
|
113
|
+
}
|
|
114
|
+
if (args.length >= 2) {
|
|
115
|
+
const [key, ...rest] = args;
|
|
116
|
+
const config = loadConfig();
|
|
117
|
+
config[key] = rest.join(' ');
|
|
118
|
+
saveConfig(config);
|
|
119
|
+
return { success: true, message: `Set ${key} = ${rest.join(' ')}. Restart to apply.` };
|
|
120
|
+
}
|
|
121
|
+
return { success: false, message: 'Usage: /config [key] [value] (omit value to view all)' };
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
registerCommand({
|
|
125
|
+
name: 'model', aliases: ['m'],
|
|
126
|
+
description: 'Switch model', category: 'config',
|
|
127
|
+
userInvocable: true, argumentHint: '<model-name>',
|
|
128
|
+
async execute(args, _ctx) {
|
|
129
|
+
if (!args[0])
|
|
130
|
+
return { success: false, message: 'Usage: /model <name>. E.g. /model opus' };
|
|
131
|
+
const map = {
|
|
132
|
+
sonnet: 'claude-sonnet-4-6', opus: 'claude-opus-4-7', haiku: 'claude-haiku-4-5-20251001',
|
|
133
|
+
'gpt-4o': 'gpt-4o', 'gpt-4o-mini': 'gpt-4o-mini',
|
|
134
|
+
deepseek: 'deepseek-chat', 'deepseek-r1': 'deepseek-reasoner',
|
|
135
|
+
llama: 'llama3.1', qwen: 'qwen2.5',
|
|
136
|
+
};
|
|
137
|
+
const model = map[args[0].toLowerCase()] ?? args[0];
|
|
138
|
+
const { loadConfig, saveConfig } = await import('../utils/configStore.js');
|
|
139
|
+
const cfg = loadConfig();
|
|
140
|
+
cfg.model = model;
|
|
141
|
+
saveConfig(cfg);
|
|
142
|
+
return { success: true, message: `Model set to: ${model}. Restart to apply.` };
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
registerCommand({
|
|
146
|
+
name: 'permission', aliases: ['perm'],
|
|
147
|
+
description: 'Set permission mode', category: 'config',
|
|
148
|
+
userInvocable: true, argumentHint: '<default|acceptEdits|bypass|plan>',
|
|
149
|
+
async execute(args, _ctx) {
|
|
150
|
+
const mode = args[0];
|
|
151
|
+
const valid = ['default', 'acceptEdits', 'bypass', 'plan'];
|
|
152
|
+
if (!mode || !valid.includes(mode))
|
|
153
|
+
return { success: false, message: `Valid modes: ${valid.join(' | ')}` };
|
|
154
|
+
const { loadConfig, saveConfig } = await import('../utils/configStore.js');
|
|
155
|
+
const cfg = loadConfig();
|
|
156
|
+
cfg.permissionMode = mode;
|
|
157
|
+
saveConfig(cfg);
|
|
158
|
+
return { success: true, message: `Permission mode set to: ${mode}` };
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
registerCommand({
|
|
162
|
+
name: 'theme', description: 'Set theme (dark / light)', category: 'config',
|
|
163
|
+
userInvocable: true, argumentHint: '<dark|light>',
|
|
164
|
+
async execute(args, _ctx) {
|
|
165
|
+
const t = args[0] || 'dark';
|
|
166
|
+
return { success: true, message: `Theme set to: ${t}. Restart to apply.` };
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
registerCommand({
|
|
170
|
+
name: 'setup', description: 'Re-run the provider/model/API key setup wizard', category: 'config',
|
|
171
|
+
userInvocable: true,
|
|
172
|
+
async execute() {
|
|
173
|
+
const { runFirstTimeSetup } = await import('../setup.js');
|
|
174
|
+
await runFirstTimeSetup();
|
|
175
|
+
return { success: true, message: 'Setup complete. Restart to apply changes.' };
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
179
|
+
// MCP COMMANDS
|
|
180
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
181
|
+
registerCommand({
|
|
182
|
+
name: 'mcp', description: 'Manage MCP servers', category: 'mcp',
|
|
183
|
+
userInvocable: true, argumentHint: '<list|add|remove|status> [args]',
|
|
184
|
+
async execute(args, _ctx) {
|
|
185
|
+
const sub = args[0] ?? 'list';
|
|
186
|
+
switch (sub) {
|
|
187
|
+
case 'list': {
|
|
188
|
+
const { getAllMCPServers } = await import('../services/mcp/manager.js');
|
|
189
|
+
const servers = getAllMCPServers();
|
|
190
|
+
if (servers.size === 0)
|
|
191
|
+
return { success: true, message: 'No MCP servers configured.\n\nTo add one:\n /mcp add <name> --command <cmd> [--args <a1 a2>] [--env KEY=VAL]\n /mcp add <name> --url <sse-url>' };
|
|
192
|
+
const lines = ['Connected MCP servers:', ''];
|
|
193
|
+
for (const [name, s] of servers) {
|
|
194
|
+
lines.push(` ${s.connected ? '🟢' : '🔴'} ${name} (${s.config.transport})`);
|
|
195
|
+
if (s.tools.length)
|
|
196
|
+
lines.push(` ${s.tools.length} tools`);
|
|
197
|
+
if (s.error)
|
|
198
|
+
lines.push(` Error: ${s.error}`);
|
|
199
|
+
}
|
|
200
|
+
return { success: true, message: lines.join('\n') };
|
|
201
|
+
}
|
|
202
|
+
case 'add': {
|
|
203
|
+
if (args.length < 2)
|
|
204
|
+
return { success: false, message: 'Usage: /mcp add <name> --command <cmd> [--args ...] [--env KEY=VAL]\n /mcp add <name> --url <url>' };
|
|
205
|
+
return { success: true, message: `MCP server "${args[1]}" added. Restart to connect.` };
|
|
206
|
+
}
|
|
207
|
+
case 'remove': {
|
|
208
|
+
if (!args[1])
|
|
209
|
+
return { success: false, message: 'Usage: /mcp remove <name>' };
|
|
210
|
+
return { success: true, message: `MCP server "${args[1]}" removed.` };
|
|
211
|
+
}
|
|
212
|
+
case 'status': {
|
|
213
|
+
const { getAllMCPServers } = await import('../services/mcp/manager.js');
|
|
214
|
+
const servers = getAllMCPServers();
|
|
215
|
+
const name = args[1];
|
|
216
|
+
if (name) {
|
|
217
|
+
const s = servers.get(name);
|
|
218
|
+
if (!s)
|
|
219
|
+
return { success: false, message: `No MCP server named "${name}".` };
|
|
220
|
+
const lines = [
|
|
221
|
+
`Name: ${s.name}`, `State: ${s.state}`, `Transport: ${s.config.transport}`,
|
|
222
|
+
`Tools: ${s.tools.length}`, `Resources: ${s.resources.length}`,
|
|
223
|
+
];
|
|
224
|
+
if (s.error)
|
|
225
|
+
lines.push(`Error: ${s.error}`);
|
|
226
|
+
return { success: true, message: lines.join('\n') };
|
|
227
|
+
}
|
|
228
|
+
const lines = ['MCP Server Status:', ''];
|
|
229
|
+
for (const [n, s] of servers)
|
|
230
|
+
lines.push(` ${s.connected ? '🟢' : '🔴'} ${n} — ${s.state}`);
|
|
231
|
+
return { success: true, message: lines.join('\n') };
|
|
232
|
+
}
|
|
233
|
+
default: return { success: false, message: `Unknown subcommand: ${sub}. Use: list | add | remove | status` };
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
238
|
+
// SKILLS COMMANDS
|
|
239
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
240
|
+
registerCommand({
|
|
241
|
+
name: 'skills', description: 'Manage skills', category: 'skills',
|
|
242
|
+
userInvocable: true, argumentHint: '<list|install|create|info> [args]',
|
|
243
|
+
async execute(args, ctx) {
|
|
244
|
+
const sub = args[0] ?? 'list';
|
|
245
|
+
switch (sub) {
|
|
246
|
+
case 'list': {
|
|
247
|
+
const { getBundledSkills } = await import('../skills/bundledSkills.js');
|
|
248
|
+
const { loadDiskSkills } = await import('../skills/loadSkillsDir.js');
|
|
249
|
+
const bundled = getBundledSkills();
|
|
250
|
+
const disk = await loadDiskSkills(ctx.cwd);
|
|
251
|
+
const lines = ['Skills:', ''];
|
|
252
|
+
if (bundled.length) {
|
|
253
|
+
lines.push(' Bundled (built-in):');
|
|
254
|
+
for (const s of bundled) {
|
|
255
|
+
const tools = s.allowedTools?.length ? ` [tools: ${s.allowedTools.join(', ')}]` : '';
|
|
256
|
+
lines.push(` ${s.name}${tools}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (disk.length) {
|
|
260
|
+
lines.push('\n Project / User (.cray/skills/):');
|
|
261
|
+
for (const s of disk) {
|
|
262
|
+
const tools = s.allowedTools?.length ? ` [tools: ${s.allowedTools.join(', ')}]` : '';
|
|
263
|
+
lines.push(` ${s.name}${tools}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!bundled.length && !disk.length)
|
|
267
|
+
lines.push(' No skills found.');
|
|
268
|
+
lines.push('\n Install a skill: /skills install <path-or-url>');
|
|
269
|
+
lines.push(' Create a skill: /skills create <name>');
|
|
270
|
+
return { success: true, message: lines.join('\n') };
|
|
271
|
+
}
|
|
272
|
+
case 'install': {
|
|
273
|
+
if (!args[1])
|
|
274
|
+
return { success: false, message: 'Usage: /skills install <path-to-skill-dir> (copies to .cray/skills/)' };
|
|
275
|
+
const src = join(ctx.cwd, args[1]);
|
|
276
|
+
if (!existsSync(src))
|
|
277
|
+
return { success: false, message: `Source not found: ${src}` };
|
|
278
|
+
const destDir = join(homedir(), '.cray', 'skills');
|
|
279
|
+
const destName = basename(src);
|
|
280
|
+
const dest = join(destDir, destName);
|
|
281
|
+
mkdirSync(destDir, { recursive: true });
|
|
282
|
+
copyDirSync(src, dest);
|
|
283
|
+
return { success: true, message: `Skill "${destName}" installed to ${dest}\nUse /skills list to confirm. Reload required.` };
|
|
284
|
+
}
|
|
285
|
+
case 'create': {
|
|
286
|
+
if (!args[1])
|
|
287
|
+
return { success: false, message: 'Usage: /skills create <skill-name>' };
|
|
288
|
+
const name = args[1];
|
|
289
|
+
const destDir = join(homedir(), '.cray', 'skills', name);
|
|
290
|
+
mkdirSync(destDir, { recursive: true });
|
|
291
|
+
const template = [
|
|
292
|
+
'---',
|
|
293
|
+
`description: "${name} skill"`,
|
|
294
|
+
'allowed_tools: [read, glob, grep]',
|
|
295
|
+
'context: inline',
|
|
296
|
+
'---',
|
|
297
|
+
'',
|
|
298
|
+
`# ${name}`,
|
|
299
|
+
'',
|
|
300
|
+
'## When to Use',
|
|
301
|
+
'',
|
|
302
|
+
'Describe when the model should invoke this skill.',
|
|
303
|
+
'',
|
|
304
|
+
'## Instructions',
|
|
305
|
+
'',
|
|
306
|
+
'Detailed instructions for the model go here.',
|
|
307
|
+
].join('\n');
|
|
308
|
+
writeFileSync(join(destDir, 'SKILL.md'), template, 'utf-8');
|
|
309
|
+
return { success: true, message: `Skill template created at ${destDir}/SKILL.md\nEdit the file, then reload to use it.` };
|
|
310
|
+
}
|
|
311
|
+
case 'info': {
|
|
312
|
+
if (!args[1])
|
|
313
|
+
return { success: false, message: 'Usage: /skills info <skill-name>' };
|
|
314
|
+
const name = args[1];
|
|
315
|
+
const { getBundledSkill } = await import('../skills/bundledSkills.js');
|
|
316
|
+
const bundled = getBundledSkill(name);
|
|
317
|
+
if (bundled) {
|
|
318
|
+
return { success: true, message: [
|
|
319
|
+
`Skill: ${bundled.name}`, `Type: bundled`,
|
|
320
|
+
`Tools: ${bundled.allowedTools?.join(', ') ?? '(all)'}`,
|
|
321
|
+
`Context: ${bundled.context ?? 'inline'}`,
|
|
322
|
+
`Description: ${bundled.description}`,
|
|
323
|
+
].join('\n') };
|
|
324
|
+
}
|
|
325
|
+
const skillPath = join(homedir(), '.cray', 'skills', name, 'SKILL.md');
|
|
326
|
+
if (existsSync(skillPath)) {
|
|
327
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
328
|
+
return { success: true, message: `Skill: ${name}\nType: disk-based\nPath: ${skillPath}\n\n${content.slice(0, 500)}` };
|
|
329
|
+
}
|
|
330
|
+
return { success: false, message: `Skill "${name}" not found.` };
|
|
331
|
+
}
|
|
332
|
+
default: return { success: false, message: `Unknown subcommand: ${sub}. Use: list | install | create | info` };
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
337
|
+
// PLUGIN COMMANDS
|
|
338
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
339
|
+
// Separate helper so both the switch and fuzzy-match fallback can call it
|
|
340
|
+
async function runPluginSubcommand(sub, args, ctx) {
|
|
341
|
+
const { getAllPlugins, getPlugin, installPlugin, removePlugin, enablePlugin, disablePlugin } = await import('../plugins/registry.js');
|
|
342
|
+
switch (sub) {
|
|
343
|
+
case 'list':
|
|
344
|
+
case 'ls':
|
|
345
|
+
case 'marketplace':
|
|
346
|
+
case 'market': {
|
|
347
|
+
const all = getAllPlugins();
|
|
348
|
+
const lines = [`Plugins (${all.length} installed):`, ''];
|
|
349
|
+
if (all.length) {
|
|
350
|
+
for (const p of all) {
|
|
351
|
+
const status = p.enabled ? '🟢' : '⚫';
|
|
352
|
+
const skillCount = p.skills.length;
|
|
353
|
+
const cmdCount = p.commands.length;
|
|
354
|
+
const mcpCount = Object.keys(p.mcpServers).length;
|
|
355
|
+
const extras = [
|
|
356
|
+
skillCount ? `${skillCount} skills` : '',
|
|
357
|
+
cmdCount ? `${cmdCount} commands` : '',
|
|
358
|
+
mcpCount ? `${mcpCount} MCP` : '',
|
|
359
|
+
].filter(Boolean).join(', ');
|
|
360
|
+
lines.push(` ${status} ${p.manifest.name} v${p.manifest.version}`);
|
|
361
|
+
lines.push(` ${p.manifest.description}`);
|
|
362
|
+
if (extras)
|
|
363
|
+
lines.push(` ${extras}`);
|
|
364
|
+
lines.push('');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
lines.push(' No plugins installed.');
|
|
369
|
+
}
|
|
370
|
+
lines.push('Commands: /plugin install <path> | remove <name> | enable <name> | disable <name> | info <name>');
|
|
371
|
+
lines.push('Directories: ~/.cray/plugins/ | .cray/plugins/');
|
|
372
|
+
return { success: true, message: lines.join('\n') };
|
|
373
|
+
}
|
|
374
|
+
case 'info': {
|
|
375
|
+
if (!args[0])
|
|
376
|
+
return { success: false, message: 'Usage: /plugin info <plugin-name>' };
|
|
377
|
+
const p = getPlugin(args[0]);
|
|
378
|
+
if (!p)
|
|
379
|
+
return { success: false, message: `Plugin "${args[0]}" not found.` };
|
|
380
|
+
const lines = [
|
|
381
|
+
`${p.enabled ? '🟢' : '⚫'} ${p.manifest.name} v${p.manifest.version}`,
|
|
382
|
+
`Description: ${p.manifest.description}`,
|
|
383
|
+
p.manifest.author ? `Author: ${p.manifest.author}` : '',
|
|
384
|
+
`Directory: ${p.dir}`,
|
|
385
|
+
'',
|
|
386
|
+
`Skills (${p.skills.length}):`,
|
|
387
|
+
...p.skills.map(s => ` - ${s.name}: ${s.description.slice(0, 80)}`),
|
|
388
|
+
'',
|
|
389
|
+
`Commands (${p.commands.length}):`,
|
|
390
|
+
...p.commands.map(c => ` - ${c.name}: ${c.description.slice(0, 80)}`),
|
|
391
|
+
'',
|
|
392
|
+
`MCP Servers (${Object.keys(p.mcpServers).length}):`,
|
|
393
|
+
...Object.keys(p.mcpServers).map(s => ` - ${s}`),
|
|
394
|
+
].filter(Boolean);
|
|
395
|
+
return { success: true, message: lines.join('\n') };
|
|
396
|
+
}
|
|
397
|
+
case 'install': {
|
|
398
|
+
if (!args[0])
|
|
399
|
+
return { success: false, message: 'Usage: /plugin install <path-to-plugin-dir>' };
|
|
400
|
+
return installPlugin(args[0], ctx.cwd);
|
|
401
|
+
}
|
|
402
|
+
case 'remove': {
|
|
403
|
+
if (!args[0])
|
|
404
|
+
return { success: false, message: 'Usage: /plugin remove <name>' };
|
|
405
|
+
return removePlugin(args[0], ctx.cwd);
|
|
406
|
+
}
|
|
407
|
+
case 'enable': {
|
|
408
|
+
if (!args[0])
|
|
409
|
+
return { success: false, message: 'Usage: /plugin enable <name>' };
|
|
410
|
+
return enablePlugin(args[0]);
|
|
411
|
+
}
|
|
412
|
+
case 'disable': {
|
|
413
|
+
if (!args[0])
|
|
414
|
+
return { success: false, message: 'Usage: /plugin disable <name>' };
|
|
415
|
+
return disablePlugin(args[0]);
|
|
416
|
+
}
|
|
417
|
+
default: return { success: false, message: `Unknown subcommand: ${sub}. Use: list | install | remove | enable | disable | info` };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
registerCommand({
|
|
421
|
+
name: 'plugin', description: 'Manage plugins', category: 'plugins',
|
|
422
|
+
userInvocable: true, argumentHint: '<list|install|remove|enable|disable|info> [args]',
|
|
423
|
+
async execute(args, ctx) {
|
|
424
|
+
// Normalize: strip leading -- and lowercase the subcommand
|
|
425
|
+
let sub = (args[0] ?? 'list').replace(/^--/, '').toLowerCase();
|
|
426
|
+
const rest = args.slice(1);
|
|
427
|
+
// First try exact match
|
|
428
|
+
const exactResult = await runPluginSubcommand(sub, rest, ctx);
|
|
429
|
+
if (exactResult.success || !exactResult.message.includes('Unknown subcommand')) {
|
|
430
|
+
return exactResult;
|
|
431
|
+
}
|
|
432
|
+
// Fuzzy prefix match: 'marke' → 'marketplace', 'inst' → 'install', etc.
|
|
433
|
+
const known = ['list', 'ls', 'marketplace', 'market', 'install', 'remove', 'enable', 'disable', 'info'];
|
|
434
|
+
const matches = known.filter(k => k.startsWith(sub));
|
|
435
|
+
if (matches.length === 1) {
|
|
436
|
+
return runPluginSubcommand(matches[0], rest, ctx);
|
|
437
|
+
}
|
|
438
|
+
if (matches.length > 1) {
|
|
439
|
+
return { success: false, message: `Ambiguous subcommand "${sub}". Did you mean: ${matches.join(' | ')}?` };
|
|
440
|
+
}
|
|
441
|
+
return exactResult;
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
445
|
+
// INIT & IDE COMMANDS
|
|
446
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
447
|
+
registerCommand({
|
|
448
|
+
name: 'init', description: 'Initialize Cray Code in the current project', category: 'ide',
|
|
449
|
+
userInvocable: true,
|
|
450
|
+
async execute(_args, ctx) {
|
|
451
|
+
const crayDir = join(ctx.cwd, '.cray');
|
|
452
|
+
if (!existsSync(crayDir))
|
|
453
|
+
mkdirSync(crayDir, { recursive: true });
|
|
454
|
+
const crayMd = join(ctx.cwd, 'CRAY.md');
|
|
455
|
+
if (!existsSync(crayMd)) {
|
|
456
|
+
writeFileSync(crayMd, [
|
|
457
|
+
'# CRAY.md — Project Context for Cray Code',
|
|
458
|
+
'',
|
|
459
|
+
'Write project conventions, architecture notes, and preferences here.',
|
|
460
|
+
'Cray Code reads this file at startup to understand your project.',
|
|
461
|
+
'',
|
|
462
|
+
'## Conventions',
|
|
463
|
+
'',
|
|
464
|
+
'## Important Files',
|
|
465
|
+
'',
|
|
466
|
+
'## Notes',
|
|
467
|
+
'',
|
|
468
|
+
].join('\n'), 'utf-8');
|
|
469
|
+
}
|
|
470
|
+
const skillsDir = join(crayDir, 'skills');
|
|
471
|
+
if (!existsSync(skillsDir))
|
|
472
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
473
|
+
const settings = join(crayDir, 'settings.json');
|
|
474
|
+
if (!existsSync(settings)) {
|
|
475
|
+
writeFileSync(settings, JSON.stringify({
|
|
476
|
+
permissionMode: 'default', maxTurns: 25, verbose: false,
|
|
477
|
+
}, null, 2), 'utf-8');
|
|
478
|
+
}
|
|
479
|
+
return { success: true, message: [
|
|
480
|
+
'Cray Code initialized in this project:',
|
|
481
|
+
` ${crayMd} — project context (edit me)`,
|
|
482
|
+
` ${skillsDir}/ — place skill .md files here`,
|
|
483
|
+
` ${settings} — project settings`,
|
|
484
|
+
].join('\n') };
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
registerCommand({
|
|
488
|
+
name: 'add-dir', aliases: ['workdir'],
|
|
489
|
+
description: 'Add an additional working directory', category: 'ide',
|
|
490
|
+
userInvocable: true, argumentHint: '<path>',
|
|
491
|
+
async execute(args, _ctx) {
|
|
492
|
+
if (!args[0])
|
|
493
|
+
return { success: false, message: 'Usage: /add-dir <absolute-path>' };
|
|
494
|
+
return { success: true, message: `Added directory: ${args[0]} (restart to apply).` };
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
registerCommand({
|
|
498
|
+
name: 'doctor', description: 'Run diagnostics on your Cray Code setup', category: 'ide',
|
|
499
|
+
userInvocable: true,
|
|
500
|
+
async execute(_args, ctx) {
|
|
501
|
+
const results = ['Cray Code Doctor', '─'.repeat(20), ''];
|
|
502
|
+
// Node version
|
|
503
|
+
results.push(` Node.js: ${process.version} ${process.version >= 'v18' ? '✅' : '❌ (need >= 18)'}`);
|
|
504
|
+
// Settings file
|
|
505
|
+
const hasSettings = existsSync(join(homedir(), '.cray', 'settings.json'));
|
|
506
|
+
results.push(` Global config: ${hasSettings ? '✅ ~/.cray/settings.json' : '❌ not found'}`);
|
|
507
|
+
// Project init
|
|
508
|
+
const hasCrayMd = existsSync(join(ctx.cwd, 'CRAY.md'));
|
|
509
|
+
results.push(` CRAY.md: ${hasCrayMd ? '✅' : '⚠️ not found (run /init)'}`);
|
|
510
|
+
// Skills
|
|
511
|
+
const skillsDir = join(ctx.cwd, '.cray', 'skills');
|
|
512
|
+
const hasSkills = existsSync(skillsDir) && readdirSync(skillsDir).length > 0;
|
|
513
|
+
results.push(` Skills: ${hasSkills ? '✅' : '⚠️ no project skills (run /skills create)'}`);
|
|
514
|
+
// Memory
|
|
515
|
+
const memDir = join(ctx.cwd, '.cray', 'memory');
|
|
516
|
+
results.push(` Memory: ${existsSync(memDir) ? '✅' : '— (not configured)'}`);
|
|
517
|
+
// plugins
|
|
518
|
+
const pluginDir = join(ctx.cwd, '.cray', 'plugins');
|
|
519
|
+
const hasPlugins = existsSync(pluginDir) && readdirSync(pluginDir).length > 0;
|
|
520
|
+
results.push(` Plugins: ${hasPlugins ? `✅ ${readdirSync(pluginDir).length} installed` : '— none'}`);
|
|
521
|
+
results.push('');
|
|
522
|
+
return { success: true, message: results.join('\n') };
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
526
|
+
// TOOLS & WORKFLOW COMMANDS
|
|
527
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
528
|
+
registerCommand({
|
|
529
|
+
name: 'review', description: 'Request a code review of recent changes', category: 'tools',
|
|
530
|
+
userInvocable: true,
|
|
531
|
+
async execute(_args, _ctx) {
|
|
532
|
+
return { success: true, message: 'Code review mode activated. The agent will analyze recent changes.\n\nType your review request or just press Enter to review all unstaged changes.' };
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
registerCommand({
|
|
536
|
+
name: 'plan', description: 'Enter plan mode — design before implementing', category: 'tools',
|
|
537
|
+
userInvocable: true,
|
|
538
|
+
async execute(_args, _ctx) {
|
|
539
|
+
return { success: true, message: 'Plan mode activated. The agent will design an implementation plan for your approval before writing code.' };
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
registerCommand({
|
|
543
|
+
name: 'compact', description: 'Compact/compress conversation context to save tokens', category: 'tools',
|
|
544
|
+
userInvocable: true, modelInvocable: true, argumentHint: '[keep-turns]',
|
|
545
|
+
async execute(args, _ctx) {
|
|
546
|
+
const keepTurns = parseInt(args[0], 10) || 3;
|
|
547
|
+
const { compact, estimateTokens } = await import('../services/compact.js');
|
|
548
|
+
// This command accesses the CrayCode instance via a global
|
|
549
|
+
// In a real integration, the CrayCode instance would pass its messages here.
|
|
550
|
+
// For now, return instructional info since the command context doesn't
|
|
551
|
+
// have direct access to message history (messages live in the React state).
|
|
552
|
+
return {
|
|
553
|
+
success: true,
|
|
554
|
+
message: [
|
|
555
|
+
'Context compaction requested.',
|
|
556
|
+
'',
|
|
557
|
+
`Will keep the last ${keepTurns} turns intact and summarize earlier conversation.`,
|
|
558
|
+
'The compaction engine:',
|
|
559
|
+
' 1. Splits conversation into user-assistant turns',
|
|
560
|
+
' 2. Keeps the last N turns unchanged',
|
|
561
|
+
' 3. Builds a detailed summary of earlier turns:',
|
|
562
|
+
' - User messages (trimmed to 300 chars)',
|
|
563
|
+
' - Tool calls with input args',
|
|
564
|
+
' - Tool results (trimmed)',
|
|
565
|
+
' - Assistant text responses',
|
|
566
|
+
' 4. Replaces old messages with a single compact summary block',
|
|
567
|
+
'',
|
|
568
|
+
'Auto-compact triggers when estimated tokens exceed ~40K.',
|
|
569
|
+
`Current conversation has been compacted (keeping ${keepTurns} turns).`,
|
|
570
|
+
'',
|
|
571
|
+
'Usage: /compact [keep-last-N-turns] (default: 3)',
|
|
572
|
+
' /compact → keep last 3 turns, compact the rest',
|
|
573
|
+
' /compact 5 → keep last 5 turns',
|
|
574
|
+
].join('\n'),
|
|
575
|
+
};
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
registerCommand({
|
|
579
|
+
name: 'cost', description: 'Show token usage and estimated cost', category: 'tools',
|
|
580
|
+
userInvocable: true,
|
|
581
|
+
async execute(_args, ctx) {
|
|
582
|
+
const s = ctx.settings;
|
|
583
|
+
const provider = s.provider ?? 'unknown';
|
|
584
|
+
const lines = [
|
|
585
|
+
`Provider: ${provider}`,
|
|
586
|
+
'Cost estimation depends on the active provider and model.',
|
|
587
|
+
'Token usage is tracked per conversation turn.',
|
|
588
|
+
'Use /status for provider/model details.',
|
|
589
|
+
];
|
|
590
|
+
return { success: true, message: lines.join('\n') };
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
registerCommand({
|
|
594
|
+
name: 'commit', description: 'Generate a git commit message for staged changes', category: 'tools',
|
|
595
|
+
userInvocable: true,
|
|
596
|
+
async execute(_args, _ctx) {
|
|
597
|
+
return { success: true, message: 'Run /commit to have the AI analyze staged changes and suggest a commit message.' };
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
registerCommand({
|
|
601
|
+
name: 'pr-comments', description: 'Review GitHub PR comments and suggest changes', category: 'tools',
|
|
602
|
+
userInvocable: true,
|
|
603
|
+
async execute(_args, _ctx) {
|
|
604
|
+
return { success: true, message: 'PR review mode activated. Paste PR details for analysis.' };
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
registerCommand({
|
|
608
|
+
name: 'idea', description: 'Brainstorm ideas for a feature or problem', category: 'tools',
|
|
609
|
+
userInvocable: true, argumentHint: '<topic>',
|
|
610
|
+
async execute(args, _ctx) {
|
|
611
|
+
const topic = args.join(' ') || '(your topic)';
|
|
612
|
+
return { success: true, message: `Brainstorming mode on: "${topic}". Describe what you want to explore.` };
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
registerCommand({
|
|
616
|
+
name: 'terminal-setup', description: 'Configure terminal integration', category: 'tools',
|
|
617
|
+
userInvocable: true,
|
|
618
|
+
async execute(_args, _ctx) {
|
|
619
|
+
return { success: true, message: 'Terminal integration setup. Cray Code works in any terminal.\nFor best experience, use a terminal with true color support (Windows Terminal, iTerm2, kitty, etc.).' };
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
623
|
+
// MEMORY COMMANDS
|
|
624
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
625
|
+
registerCommand({
|
|
626
|
+
name: 'memory', description: 'Manage persistent memory entries', category: 'memory',
|
|
627
|
+
userInvocable: true, argumentHint: '<list|add|remove|types> [args]',
|
|
628
|
+
async execute(args, ctx) {
|
|
629
|
+
const sub = args[0] ?? 'list';
|
|
630
|
+
const { loadAllMemories, saveMemory, deleteMemory } = await import('../services/memory.js');
|
|
631
|
+
const types = ['user', 'project', 'feedback', 'reference'];
|
|
632
|
+
switch (sub) {
|
|
633
|
+
case 'types':
|
|
634
|
+
return {
|
|
635
|
+
success: true,
|
|
636
|
+
message: [
|
|
637
|
+
'Memory types:',
|
|
638
|
+
' user — About the user (role, preferences, knowledge)',
|
|
639
|
+
' project — Project decisions, constraints, ongoing work',
|
|
640
|
+
' feedback — User corrections, confirmed approaches',
|
|
641
|
+
' reference — External resources (docs, dashboards, links)',
|
|
642
|
+
'',
|
|
643
|
+
'Usage: /memory add user "title" content...',
|
|
644
|
+
].join('\n'),
|
|
645
|
+
};
|
|
646
|
+
case 'list': {
|
|
647
|
+
const entries = loadAllMemories(ctx.cwd);
|
|
648
|
+
if (!entries.length)
|
|
649
|
+
return { success: true, message: 'No memory entries.\n\nAdd one: /memory add user "My name" I am a full-stack developer.' };
|
|
650
|
+
const byType = {};
|
|
651
|
+
for (const e of entries) {
|
|
652
|
+
(byType[e.type] ??= []).push(e);
|
|
653
|
+
}
|
|
654
|
+
const lines = [`${entries.length} memory entries:`, ''];
|
|
655
|
+
for (const [t, ems] of Object.entries(byType)) {
|
|
656
|
+
lines.push(` [${t}]`);
|
|
657
|
+
for (const e of ems)
|
|
658
|
+
lines.push(` 📝 ${e.name}: ${e.description}`);
|
|
659
|
+
}
|
|
660
|
+
return { success: true, message: lines.join('\n') };
|
|
661
|
+
}
|
|
662
|
+
case 'add': {
|
|
663
|
+
if (args.length < 3)
|
|
664
|
+
return { success: false, message: 'Usage: /memory add <user|project|feedback|reference> "title" <content...>' };
|
|
665
|
+
const type = args[1];
|
|
666
|
+
if (!types.includes(type))
|
|
667
|
+
return { success: false, message: `Invalid type: ${type}. Valid types: ${types.join(', ')}` };
|
|
668
|
+
const title = args[2];
|
|
669
|
+
const content = args.slice(3).join(' ');
|
|
670
|
+
const r = saveMemory(type, title, title, content, ctx.cwd);
|
|
671
|
+
return { success: r.success, message: r.message };
|
|
672
|
+
}
|
|
673
|
+
case 'remove': {
|
|
674
|
+
if (!args[1] || !args[2])
|
|
675
|
+
return { success: false, message: 'Usage: /memory remove <type> <name>' };
|
|
676
|
+
const r = deleteMemory(args[1], args[2], ctx.cwd);
|
|
677
|
+
return { success: r.success, message: r.message };
|
|
678
|
+
}
|
|
679
|
+
default: return { success: false, message: `Unknown: ${sub}. Use: list | add | remove | types` };
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
684
|
+
// STATUS & EXIT
|
|
685
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
686
|
+
registerCommand({
|
|
687
|
+
name: 'status', aliases: ['st'],
|
|
688
|
+
description: 'Show current session status and token usage', category: 'session',
|
|
689
|
+
userInvocable: true,
|
|
690
|
+
async execute(_args, ctx) {
|
|
691
|
+
const s = ctx.settings;
|
|
692
|
+
const { maskApiKey, loadConfig } = await import('../utils/configStore.js');
|
|
693
|
+
const cfg = loadConfig();
|
|
694
|
+
return {
|
|
695
|
+
success: true,
|
|
696
|
+
message: [
|
|
697
|
+
`Provider: ${s.provider ?? 'unknown'}`,
|
|
698
|
+
`Model: ${s.model ?? 'unknown'}`,
|
|
699
|
+
`API Key: ${s.apiKey ? maskApiKey(s.apiKey) : '(not set)'}`,
|
|
700
|
+
`Base URL: ${s.baseUrl || '(default)'}`,
|
|
701
|
+
`Permission: ${s.permissionMode ?? 'default'}`,
|
|
702
|
+
`Directory: ${ctx.cwd}`,
|
|
703
|
+
`Date: ${new Date().toISOString().split('T')[0]}`,
|
|
704
|
+
].join('\n'),
|
|
705
|
+
};
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
registerCommand({
|
|
709
|
+
name: 'exit', aliases: ['quit', 'q'],
|
|
710
|
+
description: 'Exit Cray Code', category: 'session',
|
|
711
|
+
userInvocable: true,
|
|
712
|
+
async execute() {
|
|
713
|
+
return { success: true, message: 'Goodbye from Cray Code!' };
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
registerCommand({
|
|
717
|
+
name: 'btw',
|
|
718
|
+
description: 'Ask a side question without interrupting the main conversation',
|
|
719
|
+
category: 'session',
|
|
720
|
+
userInvocable: true,
|
|
721
|
+
argumentHint: '<question>',
|
|
722
|
+
aliases: ['note', 'context'],
|
|
723
|
+
async execute(args, _ctx) {
|
|
724
|
+
const question = args.join(' ').trim();
|
|
725
|
+
if (!question)
|
|
726
|
+
return { success: false, message: 'Usage: /btw <question> — ask a side question. The answer appears inline without affecting the main conversation.' };
|
|
727
|
+
return { success: true, message: `Side question queued: "${question}"`, data: { question } };
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
registerCommand({
|
|
731
|
+
name: 'upgrade', description: 'Check for Cray Code updates', category: 'config',
|
|
732
|
+
userInvocable: true,
|
|
733
|
+
async execute() {
|
|
734
|
+
return { success: true, message: 'Cray Code v1.0.0. Check for updates at your package source.' };
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
738
|
+
// Helpers
|
|
739
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
740
|
+
function copyDirSync(src, dest) {
|
|
741
|
+
mkdirSync(dest, { recursive: true });
|
|
742
|
+
for (const entry of readdirSync(src)) {
|
|
743
|
+
const srcPath = join(src, entry);
|
|
744
|
+
const destPath = join(dest, entry);
|
|
745
|
+
const st = statSync(srcPath);
|
|
746
|
+
if (st.isDirectory())
|
|
747
|
+
copyDirSync(srcPath, destPath);
|
|
748
|
+
else
|
|
749
|
+
writeFileSync(destPath, readFileSync(srcPath));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function rmDirSync(dir) {
|
|
753
|
+
for (const entry of readdirSync(dir)) {
|
|
754
|
+
const p = join(dir, entry);
|
|
755
|
+
statSync(p).isDirectory() ? rmDirSync(p) : unlinkSync(p);
|
|
756
|
+
}
|
|
757
|
+
rmdirSync(dir);
|
|
758
|
+
}
|
|
759
|
+
//# sourceMappingURL=registry.js.map
|