@sheepbun/yips 0.1.1
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/dist/agent/commands/command-catalog.js +243 -0
- package/dist/agent/commands/commands.js +418 -0
- package/dist/agent/conductor.js +118 -0
- package/dist/agent/context/code-context.js +68 -0
- package/dist/agent/context/memory-store.js +159 -0
- package/dist/agent/context/session-store.js +211 -0
- package/dist/agent/protocol/tool-protocol.js +160 -0
- package/dist/agent/skills/skills.js +327 -0
- package/dist/agent/tools/tool-executor.js +415 -0
- package/dist/agent/tools/tool-safety.js +52 -0
- package/dist/app/index.js +35 -0
- package/dist/app/repl.js +105 -0
- package/dist/app/update-check.js +132 -0
- package/dist/app/version.js +51 -0
- package/dist/code-context.js +68 -0
- package/dist/colors.js +204 -0
- package/dist/command-catalog.js +242 -0
- package/dist/commands.js +350 -0
- package/dist/conductor.js +94 -0
- package/dist/config/config.js +335 -0
- package/dist/config/hooks.js +187 -0
- package/dist/config.js +335 -0
- package/dist/downloader-state.js +302 -0
- package/dist/downloader-ui.js +289 -0
- package/dist/gateway/adapters/discord.js +108 -0
- package/dist/gateway/adapters/formatting.js +96 -0
- package/dist/gateway/adapters/telegram.js +106 -0
- package/dist/gateway/adapters/types.js +2 -0
- package/dist/gateway/adapters/whatsapp.js +124 -0
- package/dist/gateway/auth-policy.js +66 -0
- package/dist/gateway/core.js +87 -0
- package/dist/gateway/headless-conductor.js +328 -0
- package/dist/gateway/message-router.js +23 -0
- package/dist/gateway/rate-limiter.js +48 -0
- package/dist/gateway/runtime/backend-policy.js +18 -0
- package/dist/gateway/runtime/discord-bot.js +104 -0
- package/dist/gateway/runtime/discord-main.js +69 -0
- package/dist/gateway/session-manager.js +77 -0
- package/dist/gateway/types.js +2 -0
- package/dist/hardware.js +92 -0
- package/dist/hooks.js +187 -0
- package/dist/index.js +34 -0
- package/dist/input-engine.js +250 -0
- package/dist/llama-client.js +227 -0
- package/dist/llama-server.js +620 -0
- package/dist/llm/llama-client.js +227 -0
- package/dist/llm/llama-server.js +620 -0
- package/dist/llm/token-counter.js +47 -0
- package/dist/memory-store.js +159 -0
- package/dist/messages.js +59 -0
- package/dist/model-downloader.js +382 -0
- package/dist/model-manager-state.js +118 -0
- package/dist/model-manager-ui.js +194 -0
- package/dist/model-manager.js +190 -0
- package/dist/models/hardware.js +92 -0
- package/dist/models/model-downloader.js +382 -0
- package/dist/models/model-manager.js +190 -0
- package/dist/prompt-box.js +78 -0
- package/dist/prompt-composer.js +498 -0
- package/dist/repl.js +105 -0
- package/dist/session-store.js +211 -0
- package/dist/spinner.js +76 -0
- package/dist/title-box.js +388 -0
- package/dist/token-counter.js +47 -0
- package/dist/tool-executor.js +415 -0
- package/dist/tool-protocol.js +121 -0
- package/dist/tool-safety.js +52 -0
- package/dist/tui/app.js +2553 -0
- package/dist/tui/startup.js +56 -0
- package/dist/tui-input-routing.js +53 -0
- package/dist/tui.js +51 -0
- package/dist/types/app-types.js +2 -0
- package/dist/types.js +2 -0
- package/dist/ui/colors.js +204 -0
- package/dist/ui/downloader/downloader-state.js +302 -0
- package/dist/ui/downloader/downloader-ui.js +289 -0
- package/dist/ui/input/input-engine.js +250 -0
- package/dist/ui/input/tui-input-routing.js +53 -0
- package/dist/ui/input/vt-session.js +168 -0
- package/dist/ui/messages.js +59 -0
- package/dist/ui/model-manager/model-manager-state.js +118 -0
- package/dist/ui/model-manager/model-manager-ui.js +194 -0
- package/dist/ui/prompt/prompt-box.js +78 -0
- package/dist/ui/prompt/prompt-composer.js +498 -0
- package/dist/ui/spinner.js +76 -0
- package/dist/ui/title-box.js +388 -0
- package/dist/ui/tui/app.js +6 -0
- package/dist/ui/tui/autocomplete.js +85 -0
- package/dist/ui/tui/constants.js +18 -0
- package/dist/ui/tui/history.js +29 -0
- package/dist/ui/tui/layout.js +341 -0
- package/dist/ui/tui/runtime-core.js +2584 -0
- package/dist/ui/tui/runtime-utils.js +53 -0
- package/dist/ui/tui/start-tui.js +54 -0
- package/dist/ui/tui/startup.js +56 -0
- package/dist/version.js +51 -0
- package/dist/vt-session.js +168 -0
- package/install.sh +457 -0
- package/package.json +128 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadCommandCatalog = loadCommandCatalog;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const GENERIC_DESCRIPTIONS = new Set(["Tool command", "Markdown skill", "Command"]);
|
|
7
|
+
const RESTORED_COMMAND_DEFAULTS = [
|
|
8
|
+
{ name: "backend", description: "Switch AI backends (llamacpp, claude)", kind: "builtin" },
|
|
9
|
+
{ name: "clear", description: "Clear context and start a new session", kind: "builtin" },
|
|
10
|
+
{ name: "dl", description: "Alias for /download", kind: "builtin" },
|
|
11
|
+
{ name: "download", description: "Open the interactive model downloader", kind: "builtin" },
|
|
12
|
+
{ name: "exit", description: "Exit Yips", kind: "builtin" },
|
|
13
|
+
{ name: "fetch", description: "Retrieve and display content from a URL", kind: "tool" },
|
|
14
|
+
{ name: "grab", description: "Read a file's content into context", kind: "tool" },
|
|
15
|
+
{ name: "help", description: "Show available commands and tips", kind: "skill" },
|
|
16
|
+
{ name: "memorize", description: "Save a fact to long-term memory", kind: "tool" },
|
|
17
|
+
{ name: "model", description: "Open the Model Manager or switch to a specific model", kind: "builtin" },
|
|
18
|
+
{ name: "new", description: "Start a new session", kind: "builtin" },
|
|
19
|
+
{ name: "nick", description: "Set a custom nickname for a model", kind: "builtin" },
|
|
20
|
+
{ name: "quit", description: "Exit Yips", kind: "builtin" },
|
|
21
|
+
{ name: "search", description: "Search the web (DuckDuckGo)", kind: "tool" },
|
|
22
|
+
{ name: "sessions", description: "Interactively select and load a session", kind: "builtin" },
|
|
23
|
+
{ name: "stream", description: "Toggle streaming responses", kind: "builtin" },
|
|
24
|
+
{ name: "tokens", description: "Show or set token counter mode and max", kind: "builtin" },
|
|
25
|
+
{ name: "update", description: "Check for newer Yips versions and upgrade guidance", kind: "builtin" },
|
|
26
|
+
{ name: "verbose", description: "Toggle verbose output", kind: "builtin" },
|
|
27
|
+
{ name: "vt", description: "Toggle the Virtual Terminal", kind: "tool" }
|
|
28
|
+
];
|
|
29
|
+
function normalizeCommandName(name) {
|
|
30
|
+
return name.trim().replace(/^\/+/, "").toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function isGenericDescription(description) {
|
|
33
|
+
const trimmed = description.trim();
|
|
34
|
+
if (trimmed.length === 0) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return GENERIC_DESCRIPTIONS.has(trimmed);
|
|
38
|
+
}
|
|
39
|
+
function normalizeDescription(description) {
|
|
40
|
+
const trimmed = description.trim();
|
|
41
|
+
return trimmed.length > 0 ? trimmed : "Command";
|
|
42
|
+
}
|
|
43
|
+
function sortedDirectoryEntries(path) {
|
|
44
|
+
return (0, node_fs_1.readdirSync)(path, { withFileTypes: true })
|
|
45
|
+
.filter((entry) => entry.isDirectory())
|
|
46
|
+
.map((entry) => entry.name)
|
|
47
|
+
.sort((left, right) => left.localeCompare(right));
|
|
48
|
+
}
|
|
49
|
+
function fileNames(path) {
|
|
50
|
+
return (0, node_fs_1.readdirSync)(path, { withFileTypes: true })
|
|
51
|
+
.filter((entry) => entry.isFile())
|
|
52
|
+
.map((entry) => entry.name)
|
|
53
|
+
.sort((left, right) => left.localeCompare(right));
|
|
54
|
+
}
|
|
55
|
+
function fileBasename(filename) {
|
|
56
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
57
|
+
return dotIndex >= 0 ? filename.slice(0, dotIndex) : filename;
|
|
58
|
+
}
|
|
59
|
+
function pickCommandFile(commandDir, commandDirName, extension) {
|
|
60
|
+
if (!(0, node_fs_1.existsSync)(commandDir)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const names = fileNames(commandDir);
|
|
64
|
+
const extensionLower = extension.toLowerCase();
|
|
65
|
+
const targetBase = commandDirName.toLowerCase();
|
|
66
|
+
const exact = names.find((name) => {
|
|
67
|
+
if (!name.toLowerCase().endsWith(extensionLower)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return fileBasename(name).toLowerCase() === targetBase;
|
|
71
|
+
});
|
|
72
|
+
if (exact) {
|
|
73
|
+
return (0, node_path_1.join)(commandDir, exact);
|
|
74
|
+
}
|
|
75
|
+
const fallback = names.find((name) => name.toLowerCase().endsWith(extensionLower));
|
|
76
|
+
return fallback ? (0, node_path_1.join)(commandDir, fallback) : null;
|
|
77
|
+
}
|
|
78
|
+
function toNonEmptyTrimmedLines(text) {
|
|
79
|
+
return text
|
|
80
|
+
.split(/\r?\n/u)
|
|
81
|
+
.map((line) => line.trim())
|
|
82
|
+
.filter((line) => line.length > 0);
|
|
83
|
+
}
|
|
84
|
+
function descriptionFromLines(lines) {
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
if (/^description\s*:/iu.test(line)) {
|
|
87
|
+
const extracted = line.replace(/^description\s*:\s*/iu, "").trim();
|
|
88
|
+
if (extracted.length > 0) {
|
|
89
|
+
return extracted;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const first = lines[0];
|
|
94
|
+
if (!first) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const split = first.split(" - ");
|
|
98
|
+
if (split.length > 1) {
|
|
99
|
+
const remainder = split.slice(1).join(" - ").trim();
|
|
100
|
+
if (remainder.length > 0) {
|
|
101
|
+
return remainder;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return first;
|
|
105
|
+
}
|
|
106
|
+
function extractPythonDescription(contents) {
|
|
107
|
+
const docstringMatch = contents.match(/^\s*(?:#.*\r?\n|\s)*(?:"""([\s\S]*?)"""|'''([\s\S]*?)''')/u);
|
|
108
|
+
const docstring = docstringMatch?.[1] ?? docstringMatch?.[2];
|
|
109
|
+
if (!docstring) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return descriptionFromLines(toNonEmptyTrimmedLines(docstring));
|
|
113
|
+
}
|
|
114
|
+
function extractMarkdownDescription(contents) {
|
|
115
|
+
const lines = toNonEmptyTrimmedLines(contents);
|
|
116
|
+
if (lines.length === 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const first = lines[0];
|
|
120
|
+
if (!first) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (!first.startsWith("#")) {
|
|
124
|
+
return first;
|
|
125
|
+
}
|
|
126
|
+
for (const line of lines.slice(1)) {
|
|
127
|
+
if (line.startsWith("#") || line.startsWith("!") || line.startsWith("```")) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
return line;
|
|
131
|
+
}
|
|
132
|
+
const heading = first.replace(/^#+\s*/u, "").trim();
|
|
133
|
+
return heading.length > 0 ? heading : null;
|
|
134
|
+
}
|
|
135
|
+
function readTextSafely(path) {
|
|
136
|
+
try {
|
|
137
|
+
return (0, node_fs_1.readFileSync)(path, "utf8");
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function discoverCommands(parentDir, parentKind) {
|
|
144
|
+
if (!(0, node_fs_1.existsSync)(parentDir)) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
const discovered = [];
|
|
148
|
+
for (const commandDirName of sortedDirectoryEntries(parentDir)) {
|
|
149
|
+
const commandName = normalizeCommandName(commandDirName);
|
|
150
|
+
if (commandName.length === 0) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const commandDir = (0, node_path_1.join)(parentDir, commandDirName);
|
|
154
|
+
const pythonPath = pickCommandFile(commandDir, commandDirName, ".py");
|
|
155
|
+
const markdownPath = pickCommandFile(commandDir, commandDirName, ".md");
|
|
156
|
+
let kind = parentKind;
|
|
157
|
+
let description = null;
|
|
158
|
+
let priority = 0;
|
|
159
|
+
if (pythonPath) {
|
|
160
|
+
const contents = readTextSafely(pythonPath);
|
|
161
|
+
if (contents) {
|
|
162
|
+
description = extractPythonDescription(contents);
|
|
163
|
+
}
|
|
164
|
+
kind = "tool";
|
|
165
|
+
priority = description ? 2 : 0;
|
|
166
|
+
}
|
|
167
|
+
else if (markdownPath) {
|
|
168
|
+
const contents = readTextSafely(markdownPath);
|
|
169
|
+
if (contents) {
|
|
170
|
+
description = extractMarkdownDescription(contents);
|
|
171
|
+
}
|
|
172
|
+
kind = "skill";
|
|
173
|
+
priority = description ? 2 : 0;
|
|
174
|
+
}
|
|
175
|
+
discovered.push({
|
|
176
|
+
name: commandName,
|
|
177
|
+
description: normalizeDescription(description ?? "Command"),
|
|
178
|
+
kind,
|
|
179
|
+
implemented: false,
|
|
180
|
+
priority
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return discovered;
|
|
184
|
+
}
|
|
185
|
+
function upsertCatalogEntry(catalog, candidate) {
|
|
186
|
+
const normalizedName = normalizeCommandName(candidate.name);
|
|
187
|
+
if (normalizedName.length === 0) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const normalizedCandidate = {
|
|
191
|
+
...candidate,
|
|
192
|
+
name: normalizedName,
|
|
193
|
+
description: normalizeDescription(candidate.description)
|
|
194
|
+
};
|
|
195
|
+
const existing = catalog.get(normalizedName);
|
|
196
|
+
if (!existing) {
|
|
197
|
+
catalog.set(normalizedName, normalizedCandidate);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const candidateGeneric = isGenericDescription(normalizedCandidate.description);
|
|
201
|
+
const existingGeneric = isGenericDescription(existing.description);
|
|
202
|
+
const shouldReplace = normalizedCandidate.priority > existing.priority ||
|
|
203
|
+
(normalizedCandidate.priority === existing.priority && !candidateGeneric && existingGeneric);
|
|
204
|
+
if (shouldReplace) {
|
|
205
|
+
catalog.set(normalizedName, {
|
|
206
|
+
...normalizedCandidate,
|
|
207
|
+
implemented: existing.implemented || normalizedCandidate.implemented
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
catalog.set(normalizedName, {
|
|
212
|
+
...existing,
|
|
213
|
+
implemented: existing.implemented || normalizedCandidate.implemented
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function loadCommandCatalog(options = {}) {
|
|
217
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
218
|
+
const catalog = new Map();
|
|
219
|
+
for (const descriptor of RESTORED_COMMAND_DEFAULTS) {
|
|
220
|
+
upsertCatalogEntry(catalog, {
|
|
221
|
+
...descriptor,
|
|
222
|
+
name: normalizeCommandName(descriptor.name),
|
|
223
|
+
implemented: false,
|
|
224
|
+
priority: 1
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const toolsDir = (0, node_path_1.join)(projectRoot, "commands", "tools");
|
|
228
|
+
const skillsDir = (0, node_path_1.join)(projectRoot, "commands", "skills");
|
|
229
|
+
for (const descriptor of discoverCommands(toolsDir, "tool")) {
|
|
230
|
+
upsertCatalogEntry(catalog, descriptor);
|
|
231
|
+
}
|
|
232
|
+
for (const descriptor of discoverCommands(skillsDir, "skill")) {
|
|
233
|
+
upsertCatalogEntry(catalog, descriptor);
|
|
234
|
+
}
|
|
235
|
+
return [...catalog.values()]
|
|
236
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
237
|
+
.map((descriptor) => ({
|
|
238
|
+
name: descriptor.name,
|
|
239
|
+
description: descriptor.description,
|
|
240
|
+
kind: descriptor.kind,
|
|
241
|
+
implemented: descriptor.implemented
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Slash command registry and dispatch system. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.CommandRegistry = void 0;
|
|
5
|
+
exports.parseCommand = parseCommand;
|
|
6
|
+
exports.createDefaultRegistry = createDefaultRegistry;
|
|
7
|
+
const command_catalog_1 = require("#agent/commands/command-catalog");
|
|
8
|
+
const update_check_1 = require("#app/update-check");
|
|
9
|
+
const config_1 = require("#config/config");
|
|
10
|
+
const memory_store_1 = require("#agent/context/memory-store");
|
|
11
|
+
const model_manager_1 = require("#models/model-manager");
|
|
12
|
+
const model_downloader_1 = require("#models/model-downloader");
|
|
13
|
+
const skills_1 = require("#agent/skills/skills");
|
|
14
|
+
function isGenericDescription(description) {
|
|
15
|
+
const trimmed = description.trim();
|
|
16
|
+
return trimmed.length === 0 || trimmed === "Command";
|
|
17
|
+
}
|
|
18
|
+
class CommandRegistry {
|
|
19
|
+
commands = new Map();
|
|
20
|
+
descriptors = new Map();
|
|
21
|
+
constructor(initialDescriptors = []) {
|
|
22
|
+
for (const descriptor of initialDescriptors) {
|
|
23
|
+
const name = descriptor.name.toLowerCase();
|
|
24
|
+
this.descriptors.set(name, { ...descriptor, name });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
register(name, handler, description, kind = "builtin") {
|
|
28
|
+
const normalizedName = name.toLowerCase();
|
|
29
|
+
const existing = this.descriptors.get(normalizedName);
|
|
30
|
+
const mergedDescription = existing && !isGenericDescription(existing.description) ? existing.description : description;
|
|
31
|
+
this.commands.set(normalizedName, {
|
|
32
|
+
name: normalizedName,
|
|
33
|
+
description: mergedDescription,
|
|
34
|
+
handler
|
|
35
|
+
});
|
|
36
|
+
this.descriptors.set(normalizedName, {
|
|
37
|
+
name: normalizedName,
|
|
38
|
+
description: mergedDescription,
|
|
39
|
+
kind: existing?.kind ?? kind,
|
|
40
|
+
implemented: true
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async dispatch(name, args, context) {
|
|
44
|
+
const command = this.commands.get(name.toLowerCase());
|
|
45
|
+
if (command) {
|
|
46
|
+
return await command.handler(args, context);
|
|
47
|
+
}
|
|
48
|
+
if (this.descriptors.has(name.toLowerCase())) {
|
|
49
|
+
return {
|
|
50
|
+
output: `Command /${name} is recognized but not implemented in this TypeScript rewrite yet. ` +
|
|
51
|
+
"Type /help to see implemented commands.",
|
|
52
|
+
action: "continue"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
output: `Unknown command: /${name}. Type /help for help.`,
|
|
57
|
+
action: "continue"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
getHelp() {
|
|
61
|
+
const commands = this.listCommands();
|
|
62
|
+
const implemented = commands.filter((command) => command.implemented);
|
|
63
|
+
const planned = commands.filter((command) => !command.implemented);
|
|
64
|
+
const lines = ["Available commands:"];
|
|
65
|
+
lines.push("Implemented:");
|
|
66
|
+
for (const cmd of implemented) {
|
|
67
|
+
lines.push(` /${cmd.name} - ${cmd.description}`);
|
|
68
|
+
}
|
|
69
|
+
if (planned.length > 0) {
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push("Recognized (not implemented in this rewrite yet):");
|
|
72
|
+
for (const cmd of planned) {
|
|
73
|
+
lines.push(` /${cmd.name} - ${cmd.description}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return lines.join("\n");
|
|
77
|
+
}
|
|
78
|
+
has(name) {
|
|
79
|
+
return this.descriptors.has(name.toLowerCase());
|
|
80
|
+
}
|
|
81
|
+
getNames() {
|
|
82
|
+
return this.listCommands().map((command) => command.name);
|
|
83
|
+
}
|
|
84
|
+
listCommands() {
|
|
85
|
+
return [...this.descriptors.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
86
|
+
}
|
|
87
|
+
getAutocompleteCommands() {
|
|
88
|
+
return this.getNames().map((name) => `/${name}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.CommandRegistry = CommandRegistry;
|
|
92
|
+
function splitCommandArgs(input) {
|
|
93
|
+
const matches = input.match(/"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+/gu);
|
|
94
|
+
if (!matches) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
return matches.map((token) => {
|
|
98
|
+
if ((token.startsWith('"') && token.endsWith('"')) ||
|
|
99
|
+
(token.startsWith("'") && token.endsWith("'"))) {
|
|
100
|
+
return token.slice(1, -1);
|
|
101
|
+
}
|
|
102
|
+
return token;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function parseTokenCountArg(input) {
|
|
106
|
+
const trimmed = input.trim().toLowerCase();
|
|
107
|
+
if (trimmed.length === 0) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const match = trimmed.match(/^(\d+)(k)?$/u);
|
|
111
|
+
if (!match) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const raw = Number(match[1]);
|
|
115
|
+
if (!Number.isInteger(raw) || raw <= 0) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const multiplier = match[2] === "k" ? 1000 : 1;
|
|
119
|
+
return raw * multiplier;
|
|
120
|
+
}
|
|
121
|
+
function parseCommand(input) {
|
|
122
|
+
const trimmed = input.trim();
|
|
123
|
+
if (!trimmed.startsWith("/"))
|
|
124
|
+
return null;
|
|
125
|
+
const spaceIndex = trimmed.indexOf(" ");
|
|
126
|
+
if (spaceIndex === -1) {
|
|
127
|
+
return { command: trimmed.slice(1).toLowerCase(), args: "" };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
command: trimmed.slice(1, spaceIndex).toLowerCase(),
|
|
131
|
+
args: trimmed.slice(spaceIndex + 1).trim()
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function createDefaultRegistry() {
|
|
135
|
+
const registry = new CommandRegistry((0, command_catalog_1.loadCommandCatalog)());
|
|
136
|
+
const downloadUsage = [
|
|
137
|
+
"Model downloader:",
|
|
138
|
+
" /download Open interactive downloader",
|
|
139
|
+
" /download <hf_url> Download directly from hf.co/huggingface URL",
|
|
140
|
+
" /dl ... Alias for /download"
|
|
141
|
+
].join("\n");
|
|
142
|
+
const handleDownload = async (args) => {
|
|
143
|
+
const trimmed = args.trim();
|
|
144
|
+
const tokens = trimmed.length > 0 ? trimmed.split(/\s+/u) : [];
|
|
145
|
+
try {
|
|
146
|
+
if (tokens.length === 0) {
|
|
147
|
+
return {
|
|
148
|
+
action: "continue",
|
|
149
|
+
uiAction: { type: "open-downloader" }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const inputArg = tokens.join(" ");
|
|
153
|
+
if (inputArg.toLowerCase() === "help") {
|
|
154
|
+
return { output: downloadUsage, action: "continue" };
|
|
155
|
+
}
|
|
156
|
+
if (!(0, model_downloader_1.isHfDownloadUrl)(inputArg)) {
|
|
157
|
+
return {
|
|
158
|
+
output: `Invalid /download argument. Only direct Hugging Face URLs are supported.\n\n${downloadUsage}`,
|
|
159
|
+
action: "continue"
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const parsed = (0, model_downloader_1.parseHfDownloadUrl)(inputArg);
|
|
163
|
+
const result = await (0, model_downloader_1.downloadModelFile)(parsed);
|
|
164
|
+
const modelsDir = (0, model_downloader_1.resolveDefaultModelsDir)();
|
|
165
|
+
return {
|
|
166
|
+
output: `Downloaded ${parsed.filename} from ${parsed.repoId}.\n` +
|
|
167
|
+
`Saved to: ${result.localPath}\n` +
|
|
168
|
+
`Models dir: ${modelsDir}\n` +
|
|
169
|
+
`Use with: /model ${parsed.repoId}/${parsed.filename}`,
|
|
170
|
+
action: "continue"
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
+
return {
|
|
176
|
+
output: `Download command failed: ${message}`,
|
|
177
|
+
action: "continue"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const memorizeUsage = [
|
|
182
|
+
"Memory commands:",
|
|
183
|
+
" /memorize <fact> Save a memory",
|
|
184
|
+
" /memorize list [limit] List recent memories (default 10)",
|
|
185
|
+
" /memorize read <memory_id> Read a saved memory",
|
|
186
|
+
" /memorize help Show this help"
|
|
187
|
+
].join("\n");
|
|
188
|
+
registry.register("help", () => ({
|
|
189
|
+
output: registry.getHelp(),
|
|
190
|
+
action: "continue"
|
|
191
|
+
}), "Show this help");
|
|
192
|
+
registry.register("exit", () => ({ output: "Goodbye.", action: "exit" }), "Exit Yips");
|
|
193
|
+
registry.register("quit", () => ({ output: "Goodbye.", action: "exit" }), "Exit Yips");
|
|
194
|
+
registry.register("restart", () => ({ output: "Restarting Yips.", action: "restart" }), "Restart Yips");
|
|
195
|
+
registry.register("clear", () => ({ action: "clear" }), "Clear the screen");
|
|
196
|
+
registry.register("new", () => ({ action: "clear" }), "Start a new conversation");
|
|
197
|
+
registry.register("model", async (args, context) => {
|
|
198
|
+
try {
|
|
199
|
+
const trimmed = args.trim();
|
|
200
|
+
if (trimmed.length === 0) {
|
|
201
|
+
return { action: "continue", uiAction: { type: "open-model-manager" } };
|
|
202
|
+
}
|
|
203
|
+
let selectedModel = trimmed;
|
|
204
|
+
const localModels = await (0, model_manager_1.listLocalModels)({ nicknames: context.config.nicknames });
|
|
205
|
+
const matched = (0, model_manager_1.findMatchingModel)(localModels, trimmed);
|
|
206
|
+
if (matched) {
|
|
207
|
+
selectedModel = matched.id;
|
|
208
|
+
}
|
|
209
|
+
context.config.backend = "llamacpp";
|
|
210
|
+
context.config.model = selectedModel;
|
|
211
|
+
await (0, config_1.saveConfig)(context.config);
|
|
212
|
+
const matchSuffix = matched ? ` (matched from '${trimmed}')` : " (free-form fallback)";
|
|
213
|
+
return { output: `Model set to: ${selectedModel}${matchSuffix}`, action: "continue" };
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
217
|
+
return {
|
|
218
|
+
output: `Model command failed: ${message}`,
|
|
219
|
+
action: "continue"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}, "View or set the current model");
|
|
223
|
+
registry.register("sessions", async () => {
|
|
224
|
+
return { action: "continue", uiAction: { type: "open-sessions" } };
|
|
225
|
+
}, "Interactively select and load a session");
|
|
226
|
+
registry.register("vt", async () => ({ action: "continue", uiAction: { type: "open-vt" } }), "Open Virtual Terminal");
|
|
227
|
+
registry.register("stream", (_args, context) => {
|
|
228
|
+
context.config.streaming = !context.config.streaming;
|
|
229
|
+
const state = context.config.streaming ? "enabled" : "disabled";
|
|
230
|
+
return { output: `Streaming ${state}.`, action: "continue" };
|
|
231
|
+
}, "Toggle streaming mode");
|
|
232
|
+
registry.register("verbose", (_args, context) => {
|
|
233
|
+
context.config.verbose = !context.config.verbose;
|
|
234
|
+
const state = context.config.verbose ? "enabled" : "disabled";
|
|
235
|
+
return { output: `Verbose mode ${state}.`, action: "continue" };
|
|
236
|
+
}, "Toggle verbose mode");
|
|
237
|
+
registry.register("update", async () => {
|
|
238
|
+
const currentVersion = await (0, update_check_1.getInstalledPackageVersion)();
|
|
239
|
+
const result = await (0, update_check_1.checkForUpdates)(currentVersion);
|
|
240
|
+
const guidance = [
|
|
241
|
+
"Update options:",
|
|
242
|
+
" npm global (canonical): npm install -g @sheepbun/yips@latest",
|
|
243
|
+
" npm global (legacy/unscoped): npm install -g yips@latest (may be unavailable)",
|
|
244
|
+
" local source: git pull --ff-only && ./install.sh",
|
|
245
|
+
" docs: https://yips.dev"
|
|
246
|
+
];
|
|
247
|
+
if (result.status === "update-available" && result.latestVersion) {
|
|
248
|
+
return {
|
|
249
|
+
output: [
|
|
250
|
+
`Update available: ${result.currentVersion} -> ${result.latestVersion}`,
|
|
251
|
+
...guidance
|
|
252
|
+
].join("\n"),
|
|
253
|
+
action: "continue"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (result.status === "up-to-date" && result.latestVersion) {
|
|
257
|
+
return {
|
|
258
|
+
output: [
|
|
259
|
+
`You are up to date (${result.currentVersion}). Latest: ${result.latestVersion}.`,
|
|
260
|
+
...guidance
|
|
261
|
+
].join("\n"),
|
|
262
|
+
action: "continue"
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const unknownReason = result.error ? ` (${result.error})` : "";
|
|
266
|
+
return {
|
|
267
|
+
output: [
|
|
268
|
+
`Could not verify latest version${unknownReason}`,
|
|
269
|
+
`Current version: ${result.currentVersion}`,
|
|
270
|
+
...guidance
|
|
271
|
+
].join("\n"),
|
|
272
|
+
action: "continue"
|
|
273
|
+
};
|
|
274
|
+
}, "Check for updates and show upgrade commands");
|
|
275
|
+
registry.register("tokens", async (args, context) => {
|
|
276
|
+
const trimmed = args.trim();
|
|
277
|
+
if (trimmed.length === 0) {
|
|
278
|
+
if (context.config.tokensMode === "auto") {
|
|
279
|
+
return { output: "Tokens mode: auto (dynamic).", action: "continue" };
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
output: `Tokens mode: manual (${context.config.tokensManualMax}).`,
|
|
283
|
+
action: "continue"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (trimmed.toLowerCase() === "auto") {
|
|
287
|
+
context.config.tokensMode = "auto";
|
|
288
|
+
await (0, config_1.saveConfig)(context.config);
|
|
289
|
+
return {
|
|
290
|
+
output: "Token limit mode set to auto.",
|
|
291
|
+
action: "continue"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const parsed = parseTokenCountArg(trimmed);
|
|
295
|
+
if (parsed === null) {
|
|
296
|
+
return {
|
|
297
|
+
output: "Usage: /tokens auto | /tokens <positive_number|numberk>",
|
|
298
|
+
action: "continue"
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
context.config.tokensMode = "manual";
|
|
302
|
+
context.config.tokensManualMax = parsed;
|
|
303
|
+
await (0, config_1.saveConfig)(context.config);
|
|
304
|
+
return {
|
|
305
|
+
output: `Token limit set to ${parsed} (manual).`,
|
|
306
|
+
action: "continue"
|
|
307
|
+
};
|
|
308
|
+
}, "Show or set token counter mode and max");
|
|
309
|
+
registry.register("download", (args) => handleDownload(args), "Open the model downloader");
|
|
310
|
+
registry.register("dl", (args) => handleDownload(args), "Alias for /download");
|
|
311
|
+
registry.register("search", async (args) => {
|
|
312
|
+
const query = args.trim();
|
|
313
|
+
if (query.length === 0) {
|
|
314
|
+
return { action: "continue", output: "Usage: /search <query>" };
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const output = await (0, skills_1.executeSearchSkill)({ query });
|
|
318
|
+
return { action: "continue", output };
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
322
|
+
return { action: "continue", output: `Search command failed: ${message}` };
|
|
323
|
+
}
|
|
324
|
+
}, "Search the web (DuckDuckGo)");
|
|
325
|
+
registry.register("fetch", async (args) => {
|
|
326
|
+
const url = args.trim();
|
|
327
|
+
if (url.length === 0) {
|
|
328
|
+
return { action: "continue", output: "Usage: /fetch <url>" };
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const output = await (0, skills_1.executeFetchSkill)({ url });
|
|
332
|
+
return { action: "continue", output };
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
336
|
+
return { action: "continue", output: `Fetch command failed: ${message}` };
|
|
337
|
+
}
|
|
338
|
+
}, "Retrieve and display content from a URL");
|
|
339
|
+
registry.register("memorize", async (args) => {
|
|
340
|
+
const trimmed = args.trim();
|
|
341
|
+
if (trimmed.length === 0 || trimmed.toLowerCase() === "help") {
|
|
342
|
+
return { output: memorizeUsage, action: "continue" };
|
|
343
|
+
}
|
|
344
|
+
const tokens = trimmed.split(/\s+/u);
|
|
345
|
+
const subcommand = (tokens[0] ?? "").toLowerCase();
|
|
346
|
+
try {
|
|
347
|
+
if (subcommand === "list") {
|
|
348
|
+
const limitRaw = tokens[1] ?? "10";
|
|
349
|
+
const limit = Number.parseInt(limitRaw, 10);
|
|
350
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
351
|
+
return { output: "Usage: /memorize list [positive_limit]", action: "continue" };
|
|
352
|
+
}
|
|
353
|
+
const memories = await (0, memory_store_1.listMemories)(limit);
|
|
354
|
+
if (memories.length === 0) {
|
|
355
|
+
return { output: "No saved memories yet.", action: "continue" };
|
|
356
|
+
}
|
|
357
|
+
const lines = ["Saved memories:"];
|
|
358
|
+
for (const memory of memories) {
|
|
359
|
+
lines.push(`- ${memory.id}: ${memory.preview}`);
|
|
360
|
+
}
|
|
361
|
+
return { output: lines.join("\n"), action: "continue" };
|
|
362
|
+
}
|
|
363
|
+
if (subcommand === "read") {
|
|
364
|
+
const memoryId = tokens.slice(1).join(" ").trim();
|
|
365
|
+
if (memoryId.length === 0) {
|
|
366
|
+
return { output: "Usage: /memorize read <memory_id>", action: "continue" };
|
|
367
|
+
}
|
|
368
|
+
const memory = await (0, memory_store_1.readMemory)(memoryId);
|
|
369
|
+
return {
|
|
370
|
+
output: [`Memory ${memory.id}:`, memory.content].join("\n\n"),
|
|
371
|
+
action: "continue"
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const saved = await (0, memory_store_1.saveMemory)(trimmed);
|
|
375
|
+
return {
|
|
376
|
+
output: `Saved memory: ${saved.id}`,
|
|
377
|
+
action: "continue"
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
382
|
+
return { output: `Memorize command failed: ${message}`, action: "continue" };
|
|
383
|
+
}
|
|
384
|
+
}, "Save, list, and read long-term memories");
|
|
385
|
+
registry.register("nick", async (args, context) => {
|
|
386
|
+
try {
|
|
387
|
+
const tokens = splitCommandArgs(args.trim());
|
|
388
|
+
if (tokens.length < 2) {
|
|
389
|
+
return {
|
|
390
|
+
output: "Usage: /nick <model_name_or_filename> <nickname>",
|
|
391
|
+
action: "continue"
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const [target, ...nicknameTokens] = tokens;
|
|
395
|
+
const nickname = nicknameTokens.join(" ").trim();
|
|
396
|
+
if (!target || nickname.length === 0) {
|
|
397
|
+
return {
|
|
398
|
+
output: "Usage: /nick <model_name_or_filename> <nickname>",
|
|
399
|
+
action: "continue"
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
context.config.nicknames[target] = nickname;
|
|
403
|
+
await (0, config_1.saveConfig)(context.config);
|
|
404
|
+
return {
|
|
405
|
+
output: `Nickname set: ${target} -> ${nickname}`,
|
|
406
|
+
action: "continue"
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
411
|
+
return {
|
|
412
|
+
output: `Nick command failed: ${message}`,
|
|
413
|
+
action: "continue"
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}, "Set a custom nickname for a model");
|
|
417
|
+
return registry;
|
|
418
|
+
}
|