@kavienw/deepseek-cli 0.1.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 +470 -0
- package/dist/agent.js +864 -0
- package/dist/agent.js.map +1 -0
- package/dist/attachments.js +54 -0
- package/dist/attachments.js.map +1 -0
- package/dist/btw.js +52 -0
- package/dist/btw.js.map +1 -0
- package/dist/client.js +88 -0
- package/dist/client.js.map +1 -0
- package/dist/commands.js +922 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.js +98 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks.js +90 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.js +408 -0
- package/dist/mcp.js.map +1 -0
- package/dist/permissions.js +30 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugins.js +341 -0
- package/dist/plugins.js.map +1 -0
- package/dist/project.js +114 -0
- package/dist/project.js.map +1 -0
- package/dist/session.js +57 -0
- package/dist/session.js.map +1 -0
- package/dist/skills.js +147 -0
- package/dist/skills.js.map +1 -0
- package/dist/todo.js +67 -0
- package/dist/todo.js.map +1 -0
- package/dist/tools/bash.js +61 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/diff.js +26 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/edit.js +73 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/glob.js +47 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.js +133 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/index.js +60 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.js +62 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/setThinking.js +46 -0
- package/dist/tools/setThinking.js.map +1 -0
- package/dist/tools/task.js +40 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/todo.js +73 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/webFetch.js +64 -0
- package/dist/tools/webFetch.js.map +1 -0
- package/dist/tools/webSearch.js +200 -0
- package/dist/tools/webSearch.js.map +1 -0
- package/dist/tools/write.js +46 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/ui/render.js +248 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/repl.js +429 -0
- package/dist/ui/repl.js.map +1 -0
- package/package.json +48 -0
package/dist/commands.js
ADDED
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { select } from "@inquirer/prompts";
|
|
4
|
+
import { MODELS, findModel, saveConfig, configPath, normalizeThinkingMode, } from "./config.js";
|
|
5
|
+
import { findProjectRoot, loadProjectMemory, projectMemoryTarget } from "./project.js";
|
|
6
|
+
import { addNote, clearNotes, listNotes, removeNote } from "./btw.js";
|
|
7
|
+
import { createSkillTemplate, findSkillCommand, globalSkillDir, loadSkillCommands, projectSkillDir, renderSkillPrompt, } from "./skills.js";
|
|
8
|
+
import { installPlugin, listPlugins, marketplaceSource, pluginSkillCount, pluginsDir, removePlugin, resolveInstallSource, scaffoldPlugin, searchMarketplace, setPluginEnabled, updatePlugin, } from "./plugins.js";
|
|
9
|
+
import { ALL_TOOLS, clearExternalTools, registerExternalTools } from "./tools/index.js";
|
|
10
|
+
import { ui, chalk } from "./ui/render.js";
|
|
11
|
+
import { saveSession, loadSession, deleteSession } from "./session.js";
|
|
12
|
+
import { createHooksTemplate, hooksPath, loadHooks } from "./hooks.js";
|
|
13
|
+
import { claudeMcpPath, createMcpTemplate, globalMcpPath, loadMcpTools, mcpStatus, projectMcpPath } from "./mcp.js";
|
|
14
|
+
export const COMMANDS = [
|
|
15
|
+
{ name: "help", aliases: ["?"], usage: "/help", description: "show this help" },
|
|
16
|
+
{ name: "model", usage: "/model", description: "choose a model with arrow keys" },
|
|
17
|
+
{ name: "models", usage: "/models", description: "fetch and list available DeepSeek models" },
|
|
18
|
+
{ name: "skills", usage: "/skills", description: "list custom skill commands" },
|
|
19
|
+
{ name: "skill-new", usage: "/skill-new <name>", description: "create a project skill template" },
|
|
20
|
+
{ name: "plugin", aliases: ["plugins"], usage: "/plugin <list|search|install|new|remove|...>", description: "install and manage plugins" },
|
|
21
|
+
{ name: "mcp", usage: "/mcp <list|init|reload>", description: "manage MCP stdio servers and tools" },
|
|
22
|
+
{ name: "hooks", usage: "/hooks [init|list]", description: "manage tool-use shell hooks" },
|
|
23
|
+
{ name: "init", usage: "/init", description: "create DEEPSEEK.md project instructions" },
|
|
24
|
+
{ name: "memory", usage: "/memory", description: "show loaded project instruction files" },
|
|
25
|
+
{ name: "tools", usage: "/tools", description: "list available tools" },
|
|
26
|
+
{ name: "todos", aliases: ["todo"], usage: "/todos", description: "show the current task list" },
|
|
27
|
+
{ name: "thinking", usage: "/thinking [on|off|collapsed|full]", description: "toggle/set the reasoning trace display (bare = on/off)" },
|
|
28
|
+
{ name: "config", usage: "/config", description: "show current configuration" },
|
|
29
|
+
{ name: "btw", usage: "/btw [note | list | done <n> | clear]", description: "jot down an out-of-scope idea to revisit later" },
|
|
30
|
+
{ name: "review", usage: "/review [ref]", description: "review local git changes (or a diff against <ref>)" },
|
|
31
|
+
{ name: "task", usage: "/task <prompt>", description: "run an isolated subagent-style task" },
|
|
32
|
+
{ name: "doctor", usage: "/doctor", description: "check environment and configuration health" },
|
|
33
|
+
{ name: "usage", usage: "/usage", description: "show token usage for this session" },
|
|
34
|
+
{ name: "compress", usage: "/compress", description: "compress conversation context into a durable summary" },
|
|
35
|
+
{ name: "save", usage: "/save", description: "save current session to disk" },
|
|
36
|
+
{ name: "resume", usage: "/resume", description: "restore saved session for this project" },
|
|
37
|
+
{ name: "rewind", aliases: ["undo"], usage: "/rewind [n]", description: "rewind conversation and undo file edits to a checkpoint" },
|
|
38
|
+
{ name: "clear", usage: "/clear", description: "clear conversation history and saved session" },
|
|
39
|
+
{
|
|
40
|
+
name: "exit",
|
|
41
|
+
aliases: ["quit", "q"],
|
|
42
|
+
usage: "/exit",
|
|
43
|
+
description: "quit (auto-saves session; exit/quit/q also work)",
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
const BARE_COMMANDS = new Set(["exit", "quit", "q"]);
|
|
47
|
+
function commandNames(command) {
|
|
48
|
+
return [command.name, ...(command.aliases ?? [])];
|
|
49
|
+
}
|
|
50
|
+
function scoreMatch(query, candidate) {
|
|
51
|
+
const q = query.toLowerCase();
|
|
52
|
+
const c = candidate.toLowerCase();
|
|
53
|
+
if (!q)
|
|
54
|
+
return 1;
|
|
55
|
+
if (c === q)
|
|
56
|
+
return 100;
|
|
57
|
+
if (c.startsWith(q))
|
|
58
|
+
return 90 - (c.length - q.length);
|
|
59
|
+
if (c.includes(q))
|
|
60
|
+
return 70 - c.indexOf(q);
|
|
61
|
+
let qi = 0;
|
|
62
|
+
for (const char of c) {
|
|
63
|
+
if (char === q[qi])
|
|
64
|
+
qi++;
|
|
65
|
+
if (qi === q.length)
|
|
66
|
+
return 50 - (c.length - q.length);
|
|
67
|
+
}
|
|
68
|
+
const distance = editDistance(q, c);
|
|
69
|
+
const maxDistance = Math.max(1, Math.floor(Math.max(q.length, c.length) * 0.4));
|
|
70
|
+
if (distance <= maxDistance)
|
|
71
|
+
return 45 - distance * 5;
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
function preferModel(a, b) {
|
|
75
|
+
const score = (id) => {
|
|
76
|
+
const normalized = id.toLowerCase();
|
|
77
|
+
if (normalized.includes("v4") && normalized.includes("pro"))
|
|
78
|
+
return 100;
|
|
79
|
+
if (normalized.includes("pro"))
|
|
80
|
+
return 90;
|
|
81
|
+
if (normalized.includes("v4") && normalized.includes("flash"))
|
|
82
|
+
return 80;
|
|
83
|
+
if (normalized.includes("flash"))
|
|
84
|
+
return 70;
|
|
85
|
+
if (normalized.includes("deepseek"))
|
|
86
|
+
return 50;
|
|
87
|
+
return 0;
|
|
88
|
+
};
|
|
89
|
+
return score(b) - score(a) || a.localeCompare(b);
|
|
90
|
+
}
|
|
91
|
+
function labelForModel(id) {
|
|
92
|
+
const known = findModel(id);
|
|
93
|
+
if (known)
|
|
94
|
+
return known.label;
|
|
95
|
+
return id;
|
|
96
|
+
}
|
|
97
|
+
function descriptionForModel(id) {
|
|
98
|
+
const known = findModel(id);
|
|
99
|
+
if (known)
|
|
100
|
+
return known.description;
|
|
101
|
+
return id.includes("deepseek") ? "Available from DeepSeek API" : "Available model";
|
|
102
|
+
}
|
|
103
|
+
async function listAvailableModelIds(ctx) {
|
|
104
|
+
try {
|
|
105
|
+
const apiModels = await ctx.agent.listModels();
|
|
106
|
+
const ids = apiModels
|
|
107
|
+
.map((model) => model.id)
|
|
108
|
+
.filter((id) => id.toLowerCase().includes("deepseek"))
|
|
109
|
+
.sort(preferModel);
|
|
110
|
+
if (ids.length > 0)
|
|
111
|
+
return { ids, source: "DeepSeek API" };
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
ui.warn(`Could not fetch models from API: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
return { ids: MODELS.map((model) => model.id).sort(preferModel), source: "local fallback" };
|
|
117
|
+
}
|
|
118
|
+
function editDistance(a, b) {
|
|
119
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
120
|
+
const curr = Array.from({ length: b.length + 1 }, () => 0);
|
|
121
|
+
for (let i = 1; i <= a.length; i++) {
|
|
122
|
+
curr[0] = i;
|
|
123
|
+
for (let j = 1; j <= b.length; j++) {
|
|
124
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
125
|
+
}
|
|
126
|
+
for (let j = 0; j <= b.length; j++)
|
|
127
|
+
prev[j] = curr[j];
|
|
128
|
+
}
|
|
129
|
+
return prev[b.length];
|
|
130
|
+
}
|
|
131
|
+
function allCommands(cwd) {
|
|
132
|
+
const skills = cwd
|
|
133
|
+
? loadSkillCommands(cwd).map((skill) => ({
|
|
134
|
+
name: skill.name,
|
|
135
|
+
usage: skill.usage,
|
|
136
|
+
description: `${skill.description} (${skill.scope} skill)`,
|
|
137
|
+
}))
|
|
138
|
+
: [];
|
|
139
|
+
return [...COMMANDS, ...skills];
|
|
140
|
+
}
|
|
141
|
+
export function suggestCommands(query, options = {}) {
|
|
142
|
+
const { limit = 6, cwd } = options;
|
|
143
|
+
const normalized = query.trim().replace(/^\//, "");
|
|
144
|
+
return allCommands(cwd).map((command) => ({
|
|
145
|
+
command,
|
|
146
|
+
score: Math.max(...commandNames(command).map((name) => scoreMatch(normalized, name))),
|
|
147
|
+
}))
|
|
148
|
+
.filter((match) => match.score > 0)
|
|
149
|
+
.sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name))
|
|
150
|
+
.slice(0, limit)
|
|
151
|
+
.map((match) => match.command);
|
|
152
|
+
}
|
|
153
|
+
export function suggestModels(query, limit = 6) {
|
|
154
|
+
const normalized = query.trim();
|
|
155
|
+
const aliases = {
|
|
156
|
+
pro: "deepseek-v4-pro",
|
|
157
|
+
flash: "deepseek-v4-flash",
|
|
158
|
+
v4pro: "deepseek-v4-pro",
|
|
159
|
+
v4flash: "deepseek-v4-flash",
|
|
160
|
+
};
|
|
161
|
+
const candidates = [...MODELS.map((model) => model.id), ...Object.keys(aliases)];
|
|
162
|
+
return candidates
|
|
163
|
+
.map((candidate) => ({ candidate, score: scoreMatch(normalized, candidate) }))
|
|
164
|
+
.filter((match) => match.score > 0)
|
|
165
|
+
.sort((a, b) => b.score - a.score || a.candidate.localeCompare(b.candidate))
|
|
166
|
+
.map((match) => aliases[match.candidate] ?? match.candidate)
|
|
167
|
+
.filter((value, index, all) => all.indexOf(value) === index)
|
|
168
|
+
.slice(0, limit);
|
|
169
|
+
}
|
|
170
|
+
function parseCommand(input) {
|
|
171
|
+
const trimmed = input.trim();
|
|
172
|
+
if (trimmed.startsWith("/"))
|
|
173
|
+
return trimmed.slice(1);
|
|
174
|
+
if (BARE_COMMANDS.has(trimmed.toLowerCase()))
|
|
175
|
+
return trimmed;
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
/** Returns true if the input was handled as a slash command. */
|
|
179
|
+
export function isCommand(input) {
|
|
180
|
+
return parseCommand(input) !== null;
|
|
181
|
+
}
|
|
182
|
+
export async function runCommand(input, ctx) {
|
|
183
|
+
const commandText = parseCommand(input);
|
|
184
|
+
if (commandText === null)
|
|
185
|
+
return {};
|
|
186
|
+
if (!commandText.trim()) {
|
|
187
|
+
printHelp();
|
|
188
|
+
return {};
|
|
189
|
+
}
|
|
190
|
+
const [cmd, ...rest] = commandText.split(/\s+/);
|
|
191
|
+
switch (cmd.toLowerCase()) {
|
|
192
|
+
case "help":
|
|
193
|
+
case "?":
|
|
194
|
+
printHelp();
|
|
195
|
+
return {};
|
|
196
|
+
case "exit":
|
|
197
|
+
case "quit":
|
|
198
|
+
case "q":
|
|
199
|
+
return { exit: true };
|
|
200
|
+
case "clear":
|
|
201
|
+
ctx.agent.reset();
|
|
202
|
+
deleteSession(ctx.agent.getCwd());
|
|
203
|
+
console.clear();
|
|
204
|
+
ui.success("Conversation cleared.");
|
|
205
|
+
return {};
|
|
206
|
+
case "save":
|
|
207
|
+
saveSession(ctx.agent.getCwd(), ctx.agent.getSession());
|
|
208
|
+
ui.success(`Session saved (${ctx.agent.messageCount()} messages).`);
|
|
209
|
+
return {};
|
|
210
|
+
case "rewind":
|
|
211
|
+
case "undo":
|
|
212
|
+
await handleRewind(cmd.toLowerCase() === "undo", rest.join(" ").trim(), ctx);
|
|
213
|
+
return {};
|
|
214
|
+
case "resume": {
|
|
215
|
+
const data = loadSession(ctx.agent.getCwd());
|
|
216
|
+
if (!data || (data.messages.length <= 1 && !data.contextSummary)) {
|
|
217
|
+
ui.warn("No saved session found for this project.");
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
ctx.agent.restoreSession(data);
|
|
221
|
+
ui.success(`Session restored (${ctx.agent.messageCount()} messages${ctx.agent.getContextSummary() ? ", compressed context active" : ""}).`);
|
|
222
|
+
return {};
|
|
223
|
+
}
|
|
224
|
+
case "btw":
|
|
225
|
+
handleBtw(rest.join(" ").trim(), ctx);
|
|
226
|
+
return {};
|
|
227
|
+
case "review":
|
|
228
|
+
await handleReview(rest.join(" ").trim(), ctx);
|
|
229
|
+
return {};
|
|
230
|
+
case "doctor":
|
|
231
|
+
handleDoctor(ctx);
|
|
232
|
+
return {};
|
|
233
|
+
case "task":
|
|
234
|
+
await handleTask(rest.join(" ").trim(), ctx);
|
|
235
|
+
return {};
|
|
236
|
+
case "usage":
|
|
237
|
+
if (ctx.agent.totalUsage.totalTokens > 0) {
|
|
238
|
+
ui.usage(ctx.agent.totalUsage);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
ui.info("No token usage recorded yet.");
|
|
242
|
+
}
|
|
243
|
+
return {};
|
|
244
|
+
case "compress":
|
|
245
|
+
await handleCompress(ctx);
|
|
246
|
+
return {};
|
|
247
|
+
case "model":
|
|
248
|
+
await handleModel(ctx);
|
|
249
|
+
return {};
|
|
250
|
+
case "models":
|
|
251
|
+
await printModels(ctx);
|
|
252
|
+
return {};
|
|
253
|
+
case "skills":
|
|
254
|
+
printSkills(ctx);
|
|
255
|
+
return {};
|
|
256
|
+
case "skill-new":
|
|
257
|
+
handleSkillNew(rest.join(" ").trim(), ctx);
|
|
258
|
+
return {};
|
|
259
|
+
case "plugin":
|
|
260
|
+
case "plugins":
|
|
261
|
+
await handlePlugin(rest);
|
|
262
|
+
return {};
|
|
263
|
+
case "mcp":
|
|
264
|
+
await handleMcp(rest, ctx);
|
|
265
|
+
return {};
|
|
266
|
+
case "hooks":
|
|
267
|
+
handleHooks(rest[0] ?? "list", ctx);
|
|
268
|
+
return {};
|
|
269
|
+
case "init":
|
|
270
|
+
await handleInit(ctx);
|
|
271
|
+
return {};
|
|
272
|
+
case "memory":
|
|
273
|
+
printMemory(ctx);
|
|
274
|
+
return {};
|
|
275
|
+
case "tools":
|
|
276
|
+
printTools();
|
|
277
|
+
return {};
|
|
278
|
+
case "todos":
|
|
279
|
+
case "todo":
|
|
280
|
+
printTodos(ctx);
|
|
281
|
+
return {};
|
|
282
|
+
case "thinking":
|
|
283
|
+
handleThinking(rest.join(" ").trim(), ctx);
|
|
284
|
+
return {};
|
|
285
|
+
case "config":
|
|
286
|
+
printConfig(ctx.config);
|
|
287
|
+
return {};
|
|
288
|
+
default:
|
|
289
|
+
if (await runSkillCommand(cmd, rest.join(" ").trim(), ctx))
|
|
290
|
+
return {};
|
|
291
|
+
printUnknownCommand(cmd, ctx);
|
|
292
|
+
return {};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function runSkillCommand(cmd, arg, ctx) {
|
|
296
|
+
const skill = findSkillCommand(ctx.agent.getCwd(), cmd);
|
|
297
|
+
if (!skill)
|
|
298
|
+
return false;
|
|
299
|
+
const prompt = renderSkillPrompt(skill, arg, ctx.agent.getCwd());
|
|
300
|
+
await ctx.agent.run(prompt);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
async function handleRewind(isUndo, arg, ctx) {
|
|
304
|
+
const list = ctx.agent.listCheckpoints();
|
|
305
|
+
if (list.length === 0) {
|
|
306
|
+
ui.info("No checkpoints yet — nothing to rewind.");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
let targetIndex;
|
|
310
|
+
if (isUndo) {
|
|
311
|
+
targetIndex = list.length - 1; // undo the most recent turn
|
|
312
|
+
}
|
|
313
|
+
else if (arg) {
|
|
314
|
+
const n = Number(arg);
|
|
315
|
+
if (!Number.isInteger(n) || n < 1 || n > list.length) {
|
|
316
|
+
ui.warn(`Usage: /rewind <1-${list.length}> (or /rewind to choose)`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
targetIndex = n - 1;
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
targetIndex = await select({
|
|
323
|
+
message: "Rewind to which point? This restores the conversation AND undoes file edits made since.",
|
|
324
|
+
choices: list
|
|
325
|
+
.slice()
|
|
326
|
+
.reverse()
|
|
327
|
+
.map((c) => ({
|
|
328
|
+
name: `#${c.index + 1} ${c.label}${c.files ? chalk.dim(` · ${c.files} file(s)`) : ""} ${chalk.dim(c.time.slice(11, 19))}`,
|
|
329
|
+
value: c.index,
|
|
330
|
+
})),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
const result = ctx.agent.rewindTo(targetIndex);
|
|
334
|
+
if (!result.ok) {
|
|
335
|
+
ui.warn("Could not rewind to that checkpoint.");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
ui.success(`Rewound to checkpoint #${targetIndex + 1}: restored ${result.restoredFiles} file(s); ${result.messages} message(s) remain.`);
|
|
339
|
+
try {
|
|
340
|
+
saveSession(ctx.agent.getCwd(), ctx.agent.getSession());
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// best-effort persistence
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function handleCompress(ctx) {
|
|
347
|
+
try {
|
|
348
|
+
const result = await ctx.agent.compressContext("manual");
|
|
349
|
+
if (!result.compressed) {
|
|
350
|
+
ui.info("No conversation history to compress yet.");
|
|
351
|
+
if (result.summary) {
|
|
352
|
+
ui.info("A compressed summary is already active.");
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
saveSession(ctx.agent.getCwd(), ctx.agent.getSession());
|
|
357
|
+
ui.success(`Context compressed: ${result.messagesBefore} messages, ~${result.estimatedTokensBefore.toLocaleString()} tokens -> ${result.summaryChars.toLocaleString()} chars summary.`);
|
|
358
|
+
ui.info("The summary is now pinned into the next requests and saved with this project session.");
|
|
359
|
+
ui.divider();
|
|
360
|
+
console.log(chalk.bold("Compressed context summary"));
|
|
361
|
+
console.log(chalk.dim(result.summary));
|
|
362
|
+
ui.divider();
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
ui.error(`Could not compress context: ${err.message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function printUnknownCommand(cmd, ctx) {
|
|
369
|
+
ui.warn(`Unknown command: /${cmd}.`);
|
|
370
|
+
const suggestions = suggestCommands(cmd, { limit: 4, cwd: ctx.agent.getCwd() });
|
|
371
|
+
if (suggestions.length === 0) {
|
|
372
|
+
ui.info("Type /help for the command list.");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
ui.info("Did you mean:");
|
|
376
|
+
for (const command of suggestions) {
|
|
377
|
+
console.log(` ${chalk.cyan(command.usage.padEnd(14))} ${chalk.dim(command.description)}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function buildInitPrompt(root, target, exists) {
|
|
381
|
+
return [
|
|
382
|
+
`${exists ? "Review and update" : "Create"} the project instruction file at ${target}.`,
|
|
383
|
+
`Project root: ${root}`,
|
|
384
|
+
"",
|
|
385
|
+
"Investigate the project FIRST using your tools, then write the file. Steps:",
|
|
386
|
+
"1. Use glob to list top-level files and key directories; identify the project type.",
|
|
387
|
+
"2. Read the most informative files only — package.json / pyproject.toml / go.mod / pom.xml / build.gradle / Cargo.toml, README.*, primary config and entry-point files. Use grep to locate scripts, frameworks, and conventions.",
|
|
388
|
+
"3. Determine: what the project does, its tech stack and language(s), how the code is organized, the EXACT build/run/test/lint commands, and important conventions or caveats.",
|
|
389
|
+
`4. Write ${target} with write_file using concise Markdown and these sections:`,
|
|
390
|
+
" # <Project name> — one to three sentences on what it does",
|
|
391
|
+
" ## Tech stack",
|
|
392
|
+
" ## Project structure (key directories/files and their roles)",
|
|
393
|
+
" ## Commands (exact build/run/test/lint commands taken from the project config)",
|
|
394
|
+
" ## Conventions & notes (patterns to follow, gotchas, things to avoid)",
|
|
395
|
+
"",
|
|
396
|
+
"Rules: base everything on files you actually read — do NOT invent commands or features. Omit anything you cannot verify. Keep it under ~150 lines. End with a one-line summary of what you captured.",
|
|
397
|
+
].join("\n");
|
|
398
|
+
}
|
|
399
|
+
async function handleInit(ctx) {
|
|
400
|
+
const root = findProjectRoot(ctx.agent.getCwd());
|
|
401
|
+
const target = projectMemoryTarget(ctx.agent.getCwd());
|
|
402
|
+
const exists = fs.existsSync(target);
|
|
403
|
+
if (exists) {
|
|
404
|
+
ui.warn(`${target} already exists — the agent will review the project and update it.`);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
ui.info(`Analyzing the project to generate ${target} …`);
|
|
408
|
+
}
|
|
409
|
+
ui.info("You'll be asked to approve the file write.");
|
|
410
|
+
await ctx.agent.run(buildInitPrompt(root, target, exists));
|
|
411
|
+
// Pull the freshly written DEEPSEEK.md into the active system prompt.
|
|
412
|
+
ctx.agent.reloadProjectContext();
|
|
413
|
+
if (fs.existsSync(target)) {
|
|
414
|
+
ui.success("Project context reloaded — DEEPSEEK.md is now part of this session.");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function printMemory(ctx) {
|
|
418
|
+
const memory = loadProjectMemory(ctx.agent.getCwd());
|
|
419
|
+
console.log();
|
|
420
|
+
console.log(` ${chalk.dim("project root:")} ${memory.root}`);
|
|
421
|
+
if (memory.files.length === 0) {
|
|
422
|
+
console.log(` ${chalk.yellow("no project instruction files found")}`);
|
|
423
|
+
console.log(` ${chalk.dim("run /init to create DEEPSEEK.md")}`);
|
|
424
|
+
console.log();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
console.log(` ${chalk.dim("loaded memory:")}`);
|
|
428
|
+
for (const file of memory.files) {
|
|
429
|
+
console.log(` - ${file}`);
|
|
430
|
+
}
|
|
431
|
+
console.log();
|
|
432
|
+
}
|
|
433
|
+
function printSkills(ctx) {
|
|
434
|
+
const skills = loadSkillCommands(ctx.agent.getCwd());
|
|
435
|
+
console.log();
|
|
436
|
+
console.log(` ${chalk.dim("project skills:")} ${projectSkillDir(ctx.agent.getCwd())}`);
|
|
437
|
+
console.log(` ${chalk.dim("global skills: ")} ${globalSkillDir()}`);
|
|
438
|
+
if (skills.length === 0) {
|
|
439
|
+
console.log(` ${chalk.yellow("no custom skills found")}`);
|
|
440
|
+
console.log(` ${chalk.dim("run /skill-new <name> to create one")}`);
|
|
441
|
+
console.log();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
for (const skill of skills) {
|
|
445
|
+
const origin = skill.plugin ? `plugin:${skill.plugin}` : skill.scope;
|
|
446
|
+
console.log(` ${chalk.cyan(skill.usage.padEnd(16))} ${chalk.dim(skill.description)}`);
|
|
447
|
+
console.log(` ${chalk.dim(` ${origin}: ${skill.source}`)}`);
|
|
448
|
+
}
|
|
449
|
+
console.log();
|
|
450
|
+
}
|
|
451
|
+
function handleSkillNew(name, ctx) {
|
|
452
|
+
if (!name) {
|
|
453
|
+
ui.warn("Usage: /skill-new <name>");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const result = createSkillTemplate(ctx.agent.getCwd(), name);
|
|
458
|
+
if (result.created) {
|
|
459
|
+
ui.success(`Created skill template: ${result.path}`);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
ui.warn(`Skill already exists: ${result.path}`);
|
|
463
|
+
}
|
|
464
|
+
ui.info("Edit the file, then type / to see the new command.");
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
ui.error(`Could not create skill: ${err.message}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async function handleMcp(args, ctx) {
|
|
471
|
+
const action = (args[0] ?? "list").toLowerCase();
|
|
472
|
+
if (action === "init" || action === "add") {
|
|
473
|
+
const scope = args[1] === "global" ? "global" : "project";
|
|
474
|
+
const result = createMcpTemplate(ctx.agent.getCwd(), scope);
|
|
475
|
+
if (result.created)
|
|
476
|
+
ui.success(`Created MCP config: ${result.path}`);
|
|
477
|
+
else
|
|
478
|
+
ui.warn(`MCP config already exists: ${result.path}`);
|
|
479
|
+
ui.info("Edit the config and run /mcp reload. Tool names will appear as mcp__server__tool.");
|
|
480
|
+
ui.info("DeepSeek CLI also reads Claude-compatible .mcp.json files with mcpServers.");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (action === "reload") {
|
|
484
|
+
clearExternalTools();
|
|
485
|
+
const loaded = await loadMcpTools(ctx.agent.getCwd());
|
|
486
|
+
registerExternalTools(loaded.tools);
|
|
487
|
+
ui.success(`Loaded ${loaded.infos.length} MCP tool${loaded.infos.length === 1 ? "" : "s"}.`);
|
|
488
|
+
for (const error of loaded.errors)
|
|
489
|
+
ui.warn(`MCP: ${error}`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const status = mcpStatus();
|
|
493
|
+
console.log();
|
|
494
|
+
console.log(` ${chalk.dim("project config:")} ${projectMcpPath(ctx.agent.getCwd())}`);
|
|
495
|
+
console.log(` ${chalk.dim("claude compat:")} ${claudeMcpPath(ctx.agent.getCwd())}`);
|
|
496
|
+
console.log(` ${chalk.dim("global config: ")} ${globalMcpPath()}`);
|
|
497
|
+
if (status.infos.length === 0) {
|
|
498
|
+
console.log(` ${chalk.yellow("no MCP tools loaded")}`);
|
|
499
|
+
console.log(` ${chalk.dim("run /mcp init, edit the config, then /mcp reload")}`);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
for (const info of status.infos) {
|
|
503
|
+
console.log(` ${chalk.cyan(info.toolName.padEnd(30))} ${chalk.dim(`${info.serverName}.${info.remoteName}`)}`);
|
|
504
|
+
if (info.description)
|
|
505
|
+
console.log(` ${chalk.dim(info.description)}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
for (const error of status.errors)
|
|
509
|
+
console.log(` ${chalk.red("error:")} ${error}`);
|
|
510
|
+
console.log();
|
|
511
|
+
}
|
|
512
|
+
function handleHooks(action, ctx) {
|
|
513
|
+
const normalized = action.toLowerCase();
|
|
514
|
+
if (normalized === "init") {
|
|
515
|
+
const result = createHooksTemplate(ctx.agent.getCwd());
|
|
516
|
+
if (result.created)
|
|
517
|
+
ui.success(`Created hooks config: ${result.path}`);
|
|
518
|
+
else
|
|
519
|
+
ui.warn(`Hooks config already exists: ${result.path}`);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const config = loadHooks(ctx.agent.getCwd());
|
|
523
|
+
console.log();
|
|
524
|
+
console.log(` ${chalk.dim("hooks:")} ${hooksPath(ctx.agent.getCwd())}`);
|
|
525
|
+
const pre = config.preToolUse ?? [];
|
|
526
|
+
const post = config.postToolUse ?? [];
|
|
527
|
+
if (pre.length === 0 && post.length === 0) {
|
|
528
|
+
console.log(` ${chalk.yellow("no hooks configured")}`);
|
|
529
|
+
console.log(` ${chalk.dim("run /hooks init to create .deepseek/hooks.json")}`);
|
|
530
|
+
console.log();
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
for (const [label, hooks] of [["preToolUse", pre], ["postToolUse", post]]) {
|
|
534
|
+
console.log(` ${chalk.bold(label)}`);
|
|
535
|
+
for (const hook of hooks)
|
|
536
|
+
console.log(` - ${chalk.cyan(hook.command)} ${chalk.dim(hook.continueOnError ? "(continue on error)" : "")}`);
|
|
537
|
+
}
|
|
538
|
+
console.log();
|
|
539
|
+
}
|
|
540
|
+
async function handlePlugin(args) {
|
|
541
|
+
const action = (args[0] ?? "list").toLowerCase();
|
|
542
|
+
const rest = args.slice(1);
|
|
543
|
+
switch (action) {
|
|
544
|
+
case "list":
|
|
545
|
+
case "ls":
|
|
546
|
+
printPlugins();
|
|
547
|
+
return;
|
|
548
|
+
case "search":
|
|
549
|
+
case "find":
|
|
550
|
+
await handlePluginSearch(rest.join(" ").trim());
|
|
551
|
+
return;
|
|
552
|
+
case "new":
|
|
553
|
+
case "create":
|
|
554
|
+
case "scaffold":
|
|
555
|
+
handlePluginNew(rest[0]);
|
|
556
|
+
return;
|
|
557
|
+
case "install":
|
|
558
|
+
case "add":
|
|
559
|
+
await handlePluginInstall(rest.join(" ").trim());
|
|
560
|
+
return;
|
|
561
|
+
case "remove":
|
|
562
|
+
case "uninstall":
|
|
563
|
+
case "rm":
|
|
564
|
+
handlePluginRemove(rest[0]);
|
|
565
|
+
return;
|
|
566
|
+
case "enable":
|
|
567
|
+
handlePluginToggle(rest[0], true);
|
|
568
|
+
return;
|
|
569
|
+
case "disable":
|
|
570
|
+
handlePluginToggle(rest[0], false);
|
|
571
|
+
return;
|
|
572
|
+
case "update":
|
|
573
|
+
case "upgrade":
|
|
574
|
+
handlePluginUpdate(rest[0]);
|
|
575
|
+
return;
|
|
576
|
+
default:
|
|
577
|
+
ui.warn(`Unknown /plugin action: ${action}`);
|
|
578
|
+
ui.info("Usage: /plugin <list | search [query] | new <name> | install <name|src> | remove <name> | enable <name> | disable <name> | update <name>>");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function handleBtw(arg, ctx) {
|
|
582
|
+
const cwd = ctx.agent.getCwd();
|
|
583
|
+
const trimmed = arg.trim();
|
|
584
|
+
// Bare /btw or /btw list → show the backlog.
|
|
585
|
+
if (!trimmed || trimmed === "list" || trimmed === "ls") {
|
|
586
|
+
const notes = listNotes(cwd);
|
|
587
|
+
if (notes.length === 0) {
|
|
588
|
+
ui.info("No 'by the way' notes yet. Use /btw <idea> to jot one down.");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
console.log();
|
|
592
|
+
console.log(` ${chalk.dim("by-the-way backlog:")}`);
|
|
593
|
+
notes.forEach((note, i) => {
|
|
594
|
+
const when = note.createdAt.slice(0, 10);
|
|
595
|
+
console.log(` ${chalk.cyan(String(i + 1).padStart(2))}. ${note.text} ${chalk.dim(`(${when})`)}`);
|
|
596
|
+
});
|
|
597
|
+
console.log(` ${chalk.dim("resolve with /btw done <n>, or /btw clear")}`);
|
|
598
|
+
console.log();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (trimmed === "clear") {
|
|
602
|
+
const count = clearNotes(cwd);
|
|
603
|
+
ui.success(`Cleared ${count} note${count === 1 ? "" : "s"}.`);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const doneMatch = trimmed.match(/^(done|rm|remove)\s+(\d+)$/i);
|
|
607
|
+
if (doneMatch) {
|
|
608
|
+
const removed = removeNote(cwd, Number(doneMatch[2]));
|
|
609
|
+
if (removed)
|
|
610
|
+
ui.success(`Done: ${removed.text}`);
|
|
611
|
+
else
|
|
612
|
+
ui.warn(`No note #${doneMatch[2]}. Use /btw list to see them.`);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
// Otherwise treat the whole argument as a new note.
|
|
616
|
+
addNote(cwd, trimmed);
|
|
617
|
+
const pending = listNotes(cwd).length;
|
|
618
|
+
ui.success(`Noted for later (${pending} pending). Use /btw to review.`);
|
|
619
|
+
}
|
|
620
|
+
async function handleReview(ref, ctx) {
|
|
621
|
+
const cwd = ctx.agent.getCwd();
|
|
622
|
+
const target = ref || "HEAD";
|
|
623
|
+
let diff = "";
|
|
624
|
+
try {
|
|
625
|
+
diff = execSync(`git diff ${target}`, {
|
|
626
|
+
cwd,
|
|
627
|
+
encoding: "utf8",
|
|
628
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
629
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
630
|
+
});
|
|
631
|
+
if (!diff.trim() && target === "HEAD") {
|
|
632
|
+
// Fall back to staged changes if the working tree is clean.
|
|
633
|
+
diff = execSync("git diff --cached", { cwd, encoding: "utf8", maxBuffer: 10 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
ui.error("Not a git repository, or git is unavailable.");
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (!diff.trim()) {
|
|
641
|
+
ui.info("No changes to review.");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const MAX = 60_000;
|
|
645
|
+
const clipped = diff.length > MAX ? diff.slice(0, MAX) + "\n…[diff truncated]" : diff;
|
|
646
|
+
const prompt = [
|
|
647
|
+
`Review the following git diff (\`git diff ${target}\`).`,
|
|
648
|
+
"Focus on bugs, security issues, edge cases, and maintainability. Be concise and actionable;",
|
|
649
|
+
"group findings by severity and reference file:line where possible. If it looks good, say so.",
|
|
650
|
+
"",
|
|
651
|
+
"```diff",
|
|
652
|
+
clipped,
|
|
653
|
+
"```",
|
|
654
|
+
].join("\n");
|
|
655
|
+
await ctx.agent.run(prompt);
|
|
656
|
+
}
|
|
657
|
+
async function handleTask(prompt, ctx) {
|
|
658
|
+
if (!prompt) {
|
|
659
|
+
ui.warn("Usage: /task <prompt>");
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
ui.info("Running isolated task context; main conversation history will be preserved.");
|
|
663
|
+
await ctx.agent.runIsolated([
|
|
664
|
+
"Run this as a focused subagent task. Work independently, use tools as needed, and report concise findings/results.",
|
|
665
|
+
"Do not assume access to the parent conversation beyond project memory, compressed context, and this prompt.",
|
|
666
|
+
"",
|
|
667
|
+
prompt,
|
|
668
|
+
].join("\n"));
|
|
669
|
+
}
|
|
670
|
+
function checkCommand(command) {
|
|
671
|
+
try {
|
|
672
|
+
return execSync(command, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0];
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function handleDoctor(ctx) {
|
|
679
|
+
const ok = (b) => (b ? chalk.green("✓") : chalk.red("✗"));
|
|
680
|
+
const warn = chalk.yellow("!");
|
|
681
|
+
console.log();
|
|
682
|
+
console.log(chalk.bold(" Environment"));
|
|
683
|
+
console.log(` ${chalk.green("✓")} node ${process.version}`);
|
|
684
|
+
const git = checkCommand("git --version");
|
|
685
|
+
console.log(` ${git ? chalk.green("✓") : chalk.red("✗")} git ${git ? chalk.dim(git.replace("git version ", "")) : chalk.dim("not found")}`);
|
|
686
|
+
const rg = checkCommand("rg --version");
|
|
687
|
+
console.log(` ${rg ? chalk.green("✓") : warn} ripgrep ${rg ? chalk.dim(rg.replace("ripgrep ", "")) : chalk.dim("not found (falls back to JS search)")}`);
|
|
688
|
+
console.log();
|
|
689
|
+
console.log(chalk.bold(" Configuration"));
|
|
690
|
+
console.log(` ${ok(Boolean(ctx.config.apiKey))} API key ${ctx.config.apiKey ? chalk.dim(ctx.config.apiKeyFromEnv ? "(from env)" : "(stored)") : chalk.red("missing — set DEEPSEEK_API_KEY")}`);
|
|
691
|
+
console.log(` ${chalk.green("✓")} model ${chalk.dim(ctx.config.model)}`);
|
|
692
|
+
console.log(` ${chalk.green("✓")} baseURL ${chalk.dim(ctx.config.baseURL)}`);
|
|
693
|
+
console.log(` ${chalk.green("✓")} thinking ${chalk.dim(ctx.config.thinkingMode ?? "off")}`);
|
|
694
|
+
console.log(` ${chalk.green("✓")} config ${chalk.dim(configPath())}`);
|
|
695
|
+
console.log();
|
|
696
|
+
console.log(chalk.bold(" Project"));
|
|
697
|
+
const memory = loadProjectMemory(ctx.agent.getCwd());
|
|
698
|
+
console.log(` ${memory.files.length > 0 ? chalk.green("✓") : warn} project memory ${memory.files.length > 0 ? chalk.dim(`${memory.files.length} file(s)`) : chalk.dim("none — run /init")}`);
|
|
699
|
+
const plugins = listPlugins();
|
|
700
|
+
console.log(` ${chalk.green("✓")} plugins ${chalk.dim(`${plugins.length} installed`)}`);
|
|
701
|
+
const skills = loadSkillCommands(ctx.agent.getCwd());
|
|
702
|
+
console.log(` ${chalk.green("✓")} skills ${chalk.dim(`${skills.length} available`)}`);
|
|
703
|
+
console.log(` ${marketplaceSource() ? chalk.green("✓") : warn} marketplace ${marketplaceSource() ? chalk.dim(marketplaceSource() ?? "") : chalk.dim("not configured (DEEPSEEK_PLUGIN_REGISTRY)")}`);
|
|
704
|
+
console.log();
|
|
705
|
+
}
|
|
706
|
+
function handlePluginNew(name) {
|
|
707
|
+
if (!name) {
|
|
708
|
+
ui.warn("Usage: /plugin new <name>");
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
const result = scaffoldPlugin(name);
|
|
713
|
+
ui.success(`Created plugin '${result.name}' at ${result.dir}.`);
|
|
714
|
+
ui.info(`Sample skill: ${result.skillFile} — invoke it with /${result.name}`);
|
|
715
|
+
ui.info("Edit plugin.json and add more skill .md files; type / to see new commands.");
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
ui.error(`Could not create plugin: ${err.message}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function handlePluginSearch(query) {
|
|
722
|
+
const src = marketplaceSource();
|
|
723
|
+
if (!src) {
|
|
724
|
+
ui.warn("No plugin marketplace configured.");
|
|
725
|
+
ui.info("Point DEEPSEEK_PLUGIN_REGISTRY at an index.json (URL or local path), e.g.:");
|
|
726
|
+
ui.info(" export DEEPSEEK_PLUGIN_REGISTRY=https://example.com/plugins/index.json");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
ui.info(`Searching marketplace (${src}) …`);
|
|
730
|
+
let results;
|
|
731
|
+
try {
|
|
732
|
+
results = await searchMarketplace(query);
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
ui.error(err.message);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (results.length === 0) {
|
|
739
|
+
ui.warn(query ? `No plugins match "${query}".` : "The marketplace is empty.");
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
console.log();
|
|
743
|
+
for (const entry of results) {
|
|
744
|
+
const ver = entry.version ? chalk.dim(` v${entry.version}`) : "";
|
|
745
|
+
console.log(` ${chalk.bold(entry.name)}${ver}`);
|
|
746
|
+
if (entry.description)
|
|
747
|
+
console.log(` ${chalk.dim(entry.description)}`);
|
|
748
|
+
console.log(` ${chalk.dim(`install: /plugin install ${entry.name}`)}`);
|
|
749
|
+
}
|
|
750
|
+
console.log();
|
|
751
|
+
}
|
|
752
|
+
async function handlePluginInstall(arg) {
|
|
753
|
+
if (!arg) {
|
|
754
|
+
ui.warn("Usage: /plugin install <name | git-url | local-path>");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
let resolved;
|
|
758
|
+
try {
|
|
759
|
+
resolved = await resolveInstallSource(arg);
|
|
760
|
+
}
|
|
761
|
+
catch (err) {
|
|
762
|
+
ui.error(err.message);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
ui.info(`Installing plugin from ${resolved.source}${resolved.via === "marketplace" ? " (marketplace)" : ""} …`);
|
|
766
|
+
try {
|
|
767
|
+
const { plugin, skillCount } = installPlugin(resolved.source);
|
|
768
|
+
ui.success(`Installed ${plugin.name}@${plugin.version} (${skillCount} skill${skillCount === 1 ? "" : "s"}).`);
|
|
769
|
+
if (skillCount > 0)
|
|
770
|
+
ui.info("New skills are now available — type / to see them.");
|
|
771
|
+
}
|
|
772
|
+
catch (err) {
|
|
773
|
+
ui.error(`Install failed: ${err.message}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function printPlugins() {
|
|
777
|
+
const plugins = listPlugins();
|
|
778
|
+
console.log();
|
|
779
|
+
console.log(` ${chalk.dim("plugins dir:")} ${pluginsDir()}`);
|
|
780
|
+
if (plugins.length === 0) {
|
|
781
|
+
console.log(` ${chalk.yellow("no plugins installed")}`);
|
|
782
|
+
console.log(` ${chalk.dim("install one with /plugin install <git-url | local-path>")}`);
|
|
783
|
+
console.log();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
for (const plugin of plugins) {
|
|
787
|
+
const state = plugin.enabled ? chalk.green("●") : chalk.dim("○ (disabled)");
|
|
788
|
+
const count = pluginSkillCount(plugin.name);
|
|
789
|
+
console.log(` ${state} ${chalk.bold(plugin.name)} ${chalk.dim(`v${plugin.version}`)} ${chalk.dim(`· ${count} skill${count === 1 ? "" : "s"}`)}`);
|
|
790
|
+
console.log(` ${chalk.dim(plugin.description)}`);
|
|
791
|
+
console.log(` ${chalk.dim(`source: ${plugin.source}`)}`);
|
|
792
|
+
}
|
|
793
|
+
console.log();
|
|
794
|
+
}
|
|
795
|
+
function handlePluginRemove(name) {
|
|
796
|
+
if (!name) {
|
|
797
|
+
ui.warn("Usage: /plugin remove <name>");
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (removePlugin(name))
|
|
801
|
+
ui.success(`Removed plugin '${name}'.`);
|
|
802
|
+
else
|
|
803
|
+
ui.warn(`Plugin '${name}' is not installed.`);
|
|
804
|
+
}
|
|
805
|
+
function handlePluginToggle(name, enabled) {
|
|
806
|
+
if (!name) {
|
|
807
|
+
ui.warn(`Usage: /plugin ${enabled ? "enable" : "disable"} <name>`);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (setPluginEnabled(name, enabled))
|
|
811
|
+
ui.success(`Plugin '${name}' ${enabled ? "enabled" : "disabled"}.`);
|
|
812
|
+
else
|
|
813
|
+
ui.warn(`Plugin '${name}' is not installed.`);
|
|
814
|
+
}
|
|
815
|
+
function handlePluginUpdate(name) {
|
|
816
|
+
if (!name) {
|
|
817
|
+
ui.warn("Usage: /plugin update <name>");
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const result = updatePlugin(name);
|
|
821
|
+
if (result.updated)
|
|
822
|
+
ui.success(result.message);
|
|
823
|
+
else
|
|
824
|
+
ui.warn(result.message);
|
|
825
|
+
}
|
|
826
|
+
async function handleModel(ctx) {
|
|
827
|
+
const { ids, source } = await listAvailableModelIds(ctx);
|
|
828
|
+
const target = await select({
|
|
829
|
+
message: `Select a model (${source})`,
|
|
830
|
+
default: ids.includes(ctx.config.model) ? ctx.config.model : ids[0],
|
|
831
|
+
choices: ids.map((id) => ({
|
|
832
|
+
name: `${labelForModel(id)}${id === ctx.config.model ? chalk.dim(" (current)") : ""}`,
|
|
833
|
+
value: id,
|
|
834
|
+
description: descriptionForModel(id),
|
|
835
|
+
})),
|
|
836
|
+
});
|
|
837
|
+
ctx.config.model = target;
|
|
838
|
+
ctx.agent.setModel(target);
|
|
839
|
+
ui.success(`Model set to ${chalk.green(target)}.`);
|
|
840
|
+
try {
|
|
841
|
+
saveConfig(ctx.config);
|
|
842
|
+
}
|
|
843
|
+
catch (err) {
|
|
844
|
+
ui.warn(`Could not save config: ${err.message}`);
|
|
845
|
+
ui.info("Model changed for this session only.");
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function handleThinking(arg, ctx) {
|
|
849
|
+
const current = ctx.config.thinkingMode ?? "off";
|
|
850
|
+
let next;
|
|
851
|
+
if (arg.trim() === "") {
|
|
852
|
+
// bare /thinking toggles on/off (collapsed reachable via /thinking collapsed)
|
|
853
|
+
next = current === "off" ? "full" : "off";
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
const parsed = normalizeThinkingMode(arg);
|
|
857
|
+
if (!parsed) {
|
|
858
|
+
ui.warn("Usage: /thinking [on | off | collapsed | full]");
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
next = parsed;
|
|
862
|
+
}
|
|
863
|
+
ctx.config.thinkingMode = next;
|
|
864
|
+
ctx.agent.setThinkingMode(next);
|
|
865
|
+
const label = next === "off" ? "off (hidden)" : next === "collapsed" ? "collapsed (first lines)" : "full";
|
|
866
|
+
ui.success(`Thinking display: ${chalk.green(label)}.`);
|
|
867
|
+
try {
|
|
868
|
+
saveConfig(ctx.config);
|
|
869
|
+
}
|
|
870
|
+
catch (err) {
|
|
871
|
+
ui.warn(`Could not save config: ${err.message}`);
|
|
872
|
+
ui.info("Preference applied for this session only.");
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
function printHelp() {
|
|
876
|
+
console.log();
|
|
877
|
+
ui.info("Commands:");
|
|
878
|
+
for (const command of allCommands()) {
|
|
879
|
+
console.log(` ${chalk.cyan(command.usage.padEnd(14))} ${chalk.dim(command.description)}`);
|
|
880
|
+
}
|
|
881
|
+
console.log();
|
|
882
|
+
ui.info("Tips:");
|
|
883
|
+
ui.info(" - End a line with \\ to continue typing on the next line.");
|
|
884
|
+
ui.info(" - The agent reads files, runs commands, and edits code for you.");
|
|
885
|
+
console.log();
|
|
886
|
+
}
|
|
887
|
+
async function printModels(ctx) {
|
|
888
|
+
const { ids, source } = await listAvailableModelIds(ctx);
|
|
889
|
+
console.log();
|
|
890
|
+
console.log(` ${chalk.dim("source:")} ${source}`);
|
|
891
|
+
for (const id of ids) {
|
|
892
|
+
const marker = id === ctx.config.model ? chalk.green(" ●") : " ";
|
|
893
|
+
console.log(`${marker} ${chalk.bold(id)}`);
|
|
894
|
+
console.log(` ${chalk.dim(descriptionForModel(id))}`);
|
|
895
|
+
}
|
|
896
|
+
console.log();
|
|
897
|
+
}
|
|
898
|
+
function printTodos(ctx) {
|
|
899
|
+
console.log();
|
|
900
|
+
console.log(chalk.bold(" Todos"));
|
|
901
|
+
console.log(chalk.dim(ctx.agent.getTodos()).replace(/^/gm, " "));
|
|
902
|
+
console.log();
|
|
903
|
+
}
|
|
904
|
+
function printTools() {
|
|
905
|
+
console.log();
|
|
906
|
+
for (const t of ALL_TOOLS) {
|
|
907
|
+
const flag = t.needsApproval ? chalk.yellow(" [needs approval]") : "";
|
|
908
|
+
console.log(` ${chalk.cyan(t.name)}${flag}`);
|
|
909
|
+
console.log(` ${chalk.dim(t.description.split(". ")[0])}`);
|
|
910
|
+
}
|
|
911
|
+
console.log();
|
|
912
|
+
}
|
|
913
|
+
function printConfig(config) {
|
|
914
|
+
console.log();
|
|
915
|
+
console.log(` ${chalk.dim("model: ")} ${config.model}`);
|
|
916
|
+
console.log(` ${chalk.dim("baseURL: ")} ${config.baseURL}`);
|
|
917
|
+
console.log(` ${chalk.dim("api key: ")} ${config.apiKey ? chalk.green("set") + (config.apiKeyFromEnv ? chalk.dim(" (from env)") : "") : chalk.red("missing")}`);
|
|
918
|
+
console.log(` ${chalk.dim("thinking:")} ${(config.thinkingMode ?? "off") === "off" ? chalk.yellow("off") : chalk.green(config.thinkingMode)}`);
|
|
919
|
+
console.log(` ${chalk.dim("config: ")} ${configPath()}`);
|
|
920
|
+
console.log();
|
|
921
|
+
}
|
|
922
|
+
//# sourceMappingURL=commands.js.map
|