@sheepbun/yips 0.1.1 → 0.1.47

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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/bin/yips.js +15 -0
  4. package/package.json +21 -128
  5. package/postinstall.js +52 -0
  6. package/dist/agent/commands/command-catalog.js +0 -243
  7. package/dist/agent/commands/commands.js +0 -418
  8. package/dist/agent/conductor.js +0 -118
  9. package/dist/agent/context/code-context.js +0 -68
  10. package/dist/agent/context/memory-store.js +0 -159
  11. package/dist/agent/context/session-store.js +0 -211
  12. package/dist/agent/protocol/tool-protocol.js +0 -160
  13. package/dist/agent/skills/skills.js +0 -327
  14. package/dist/agent/tools/tool-executor.js +0 -415
  15. package/dist/agent/tools/tool-safety.js +0 -52
  16. package/dist/app/index.js +0 -35
  17. package/dist/app/repl.js +0 -105
  18. package/dist/app/update-check.js +0 -132
  19. package/dist/app/version.js +0 -51
  20. package/dist/code-context.js +0 -68
  21. package/dist/colors.js +0 -204
  22. package/dist/command-catalog.js +0 -242
  23. package/dist/commands.js +0 -350
  24. package/dist/conductor.js +0 -94
  25. package/dist/config/config.js +0 -335
  26. package/dist/config/hooks.js +0 -187
  27. package/dist/config.js +0 -335
  28. package/dist/downloader-state.js +0 -302
  29. package/dist/downloader-ui.js +0 -289
  30. package/dist/gateway/adapters/discord.js +0 -108
  31. package/dist/gateway/adapters/formatting.js +0 -96
  32. package/dist/gateway/adapters/telegram.js +0 -106
  33. package/dist/gateway/adapters/types.js +0 -2
  34. package/dist/gateway/adapters/whatsapp.js +0 -124
  35. package/dist/gateway/auth-policy.js +0 -66
  36. package/dist/gateway/core.js +0 -87
  37. package/dist/gateway/headless-conductor.js +0 -328
  38. package/dist/gateway/message-router.js +0 -23
  39. package/dist/gateway/rate-limiter.js +0 -48
  40. package/dist/gateway/runtime/backend-policy.js +0 -18
  41. package/dist/gateway/runtime/discord-bot.js +0 -104
  42. package/dist/gateway/runtime/discord-main.js +0 -69
  43. package/dist/gateway/session-manager.js +0 -77
  44. package/dist/gateway/types.js +0 -2
  45. package/dist/hardware.js +0 -92
  46. package/dist/hooks.js +0 -187
  47. package/dist/index.js +0 -34
  48. package/dist/input-engine.js +0 -250
  49. package/dist/llama-client.js +0 -227
  50. package/dist/llama-server.js +0 -620
  51. package/dist/llm/llama-client.js +0 -227
  52. package/dist/llm/llama-server.js +0 -620
  53. package/dist/llm/token-counter.js +0 -47
  54. package/dist/memory-store.js +0 -159
  55. package/dist/messages.js +0 -59
  56. package/dist/model-downloader.js +0 -382
  57. package/dist/model-manager-state.js +0 -118
  58. package/dist/model-manager-ui.js +0 -194
  59. package/dist/model-manager.js +0 -190
  60. package/dist/models/hardware.js +0 -92
  61. package/dist/models/model-downloader.js +0 -382
  62. package/dist/models/model-manager.js +0 -190
  63. package/dist/prompt-box.js +0 -78
  64. package/dist/prompt-composer.js +0 -498
  65. package/dist/repl.js +0 -105
  66. package/dist/session-store.js +0 -211
  67. package/dist/spinner.js +0 -76
  68. package/dist/title-box.js +0 -388
  69. package/dist/token-counter.js +0 -47
  70. package/dist/tool-executor.js +0 -415
  71. package/dist/tool-protocol.js +0 -121
  72. package/dist/tool-safety.js +0 -52
  73. package/dist/tui/app.js +0 -2553
  74. package/dist/tui/startup.js +0 -56
  75. package/dist/tui-input-routing.js +0 -53
  76. package/dist/tui.js +0 -51
  77. package/dist/types/app-types.js +0 -2
  78. package/dist/types.js +0 -2
  79. package/dist/ui/colors.js +0 -204
  80. package/dist/ui/downloader/downloader-state.js +0 -302
  81. package/dist/ui/downloader/downloader-ui.js +0 -289
  82. package/dist/ui/input/input-engine.js +0 -250
  83. package/dist/ui/input/tui-input-routing.js +0 -53
  84. package/dist/ui/input/vt-session.js +0 -168
  85. package/dist/ui/messages.js +0 -59
  86. package/dist/ui/model-manager/model-manager-state.js +0 -118
  87. package/dist/ui/model-manager/model-manager-ui.js +0 -194
  88. package/dist/ui/prompt/prompt-box.js +0 -78
  89. package/dist/ui/prompt/prompt-composer.js +0 -498
  90. package/dist/ui/spinner.js +0 -76
  91. package/dist/ui/title-box.js +0 -388
  92. package/dist/ui/tui/app.js +0 -6
  93. package/dist/ui/tui/autocomplete.js +0 -85
  94. package/dist/ui/tui/constants.js +0 -18
  95. package/dist/ui/tui/history.js +0 -29
  96. package/dist/ui/tui/layout.js +0 -341
  97. package/dist/ui/tui/runtime-core.js +0 -2584
  98. package/dist/ui/tui/runtime-utils.js +0 -53
  99. package/dist/ui/tui/start-tui.js +0 -54
  100. package/dist/ui/tui/startup.js +0 -56
  101. package/dist/version.js +0 -51
  102. package/dist/vt-session.js +0 -168
  103. package/install.sh +0 -457
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Katherine North
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Automatic Git Versioning
2
+
3
+ - **Format**: `vmajor.minor.patch` where each commit adds `+0.0.1`, patch rolls into minor after `v0.0.99 -> v0.1.00`, minor rolls into major after `v0.99.99 -> v1.0.00`.
4
+ - **Example**: 121 commits → `v0.1.21`
5
+ - **Usage**: Run `python version.py` to see the current version
6
+
7
+ The version is recalculated from the commit count so it always reflects the repository history.
8
+
9
+ ## 🚀 Quick Start
10
+
11
+ Yips is designed to be zero-config. The included startup script handles dependency checks, virtual environments, and configuration automatically.
12
+
13
+ ### 1. Clone the repository
14
+ ```bash
15
+ git clone https://github.com/sheepbun/yips-cli.git
16
+ cd Yips
17
+ ```
18
+
19
+ ### 2. Run the startup script
20
+ ```bash
21
+ chmod +x startup.sh
22
+ ./startup.sh
23
+ ```
24
+
25
+ The script will:
26
+ * Verify Python 3 installation.
27
+ * Create a virtual environment (`.venv`) if missing.
28
+ * Install/Update all dependencies from `requirements.txt`.
29
+ * Clone/update and build `llama.cpp` automatically, enabling CUDA when an NVIDIA CUDA environment is detected.
30
+ * Initialize your `.yips_config.json` on the first run.
31
+ * Launch the Yips CLI.
32
+
33
+ ## 🛠 Backends
34
+
35
+ Yips supports multiple backends for maximum flexibility:
36
+ 1. **llama.cpp** (Local, High Performance) - *Recommended*
37
+ 2. **LM Studio** (Local, GUI-based)
38
+ 3. **Claude CLI** (Cloud-based fallback)
39
+
40
+ Use the `/backend` command within Yips to switch between them.
41
+
42
+ ## 🧠 Model Management
43
+
44
+ Yips features a hardware-aware Model Manager. Run:
45
+ ```bash
46
+ /model
47
+ ```
48
+ within the CLI to manage your local models, switch between backends, or jump to the interactive downloader.
49
+
50
+ ## Developer documentation
51
+
52
+ For persistence layout, how to add slash-command plugins, model tool tags, and setup/backend operations, see [docs/DEVELOPER.md](docs/DEVELOPER.md).
package/bin/yips.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require('child_process');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const isWin = os.platform() === 'win32';
7
+ const ext = isWin ? '.exe' : '';
8
+ const binPath = path.join(__dirname, `yips${ext}`);
9
+
10
+ const result = spawnSync(binPath, process.argv.slice(2), { stdio: 'inherit' });
11
+ if (result.error) {
12
+ console.error(`Failed to execute Yips binary at ${binPath}:`, result.error.message);
13
+ process.exit(1);
14
+ }
15
+ process.exit(result.status || 0);
package/package.json CHANGED
@@ -1,128 +1,21 @@
1
- {
2
- "name": "@sheepbun/yips",
3
- "version": "0.1.1",
4
- "description": "Local-first AI code editor and self-hosted gateway (TypeScript rewrite).",
5
- "license": "UNLICENSED",
6
- "homepage": "https://yips.dev",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/sheepbun/yips.git"
10
- },
11
- "bugs": {
12
- "url": "https://github.com/sheepbun/yips/issues"
13
- },
14
- "main": "dist/app/index.js",
15
- "bin": {
16
- "yips": "dist/app/index.js"
17
- },
18
- "files": [
19
- "dist/**",
20
- "install.sh"
21
- ],
22
- "imports": {
23
- "#app/*": {
24
- "development": "./src/app/*.ts",
25
- "default": "./dist/app/*.js"
26
- },
27
- "#agent/conductor": {
28
- "development": "./src/agent/conductor.ts",
29
- "default": "./dist/agent/conductor.js"
30
- },
31
- "#agent/commands/*": {
32
- "development": "./src/agent/commands/*.ts",
33
- "default": "./dist/agent/commands/*.js"
34
- },
35
- "#agent/context/*": {
36
- "development": "./src/agent/context/*.ts",
37
- "default": "./dist/agent/context/*.js"
38
- },
39
- "#agent/protocol/*": {
40
- "development": "./src/agent/protocol/*.ts",
41
- "default": "./dist/agent/protocol/*.js"
42
- },
43
- "#agent/tools/*": {
44
- "development": "./src/agent/tools/*.ts",
45
- "default": "./dist/agent/tools/*.js"
46
- },
47
- "#agent/skills/*": {
48
- "development": "./src/agent/skills/*.ts",
49
- "default": "./dist/agent/skills/*.js"
50
- },
51
- "#config/*": {
52
- "development": "./src/config/*.ts",
53
- "default": "./dist/config/*.js"
54
- },
55
- "#gateway/*": {
56
- "development": "./src/gateway/*.ts",
57
- "default": "./dist/gateway/*.js"
58
- },
59
- "#llm/*": {
60
- "development": "./src/llm/*.ts",
61
- "default": "./dist/llm/*.js"
62
- },
63
- "#models/*": {
64
- "development": "./src/models/*.ts",
65
- "default": "./dist/models/*.js"
66
- },
67
- "#types/*": {
68
- "development": "./src/types/*.ts",
69
- "default": "./dist/types/*.js"
70
- },
71
- "#ui/*": {
72
- "development": "./src/ui/*.ts",
73
- "default": "./dist/ui/*.js"
74
- },
75
- "#ui/prompt/*": {
76
- "development": "./src/ui/prompt/*.ts",
77
- "default": "./dist/ui/prompt/*.js"
78
- },
79
- "#ui/input/*": {
80
- "development": "./src/ui/input/*.ts",
81
- "default": "./dist/ui/input/*.js"
82
- },
83
- "#ui/downloader/*": {
84
- "development": "./src/ui/downloader/*.ts",
85
- "default": "./dist/ui/downloader/*.js"
86
- },
87
- "#ui/model-manager/*": {
88
- "development": "./src/ui/model-manager/*.ts",
89
- "default": "./dist/ui/model-manager/*.js"
90
- },
91
- "#ui/tui/*": {
92
- "development": "./src/ui/tui/*.ts",
93
- "default": "./dist/ui/tui/*.js"
94
- }
95
- },
96
- "engines": {
97
- "node": ">=20.0.0"
98
- },
99
- "scripts": {
100
- "build": "tsc -p tsconfig.build.json",
101
- "dev": "tsx --conditions=development src/app/index.ts",
102
- "gateway:discord": "tsx --conditions=development src/gateway/runtime/discord-main.ts",
103
- "start": "node dist/app/index.js",
104
- "typecheck": "tsc --noEmit -p tsconfig.json",
105
- "test": "vitest run",
106
- "test:watch": "vitest",
107
- "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
108
- "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\" \".github/**/*.yml\" \"*.json\"",
109
- "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\" \".github/**/*.yml\" \"*.json\""
110
- },
111
- "devDependencies": {
112
- "@types/node": "^22.13.4",
113
- "@types/react": "^18.3.26",
114
- "@typescript-eslint/eslint-plugin": "^7.18.0",
115
- "@typescript-eslint/parser": "^7.18.0",
116
- "eslint": "^8.57.1",
117
- "prettier": "^3.5.3",
118
- "tsx": "^4.19.3",
119
- "typescript": "^5.8.2",
120
- "vitest": "^3.0.7"
121
- },
122
- "dependencies": {
123
- "discord.js": "^14.22.1",
124
- "ink": "^4.4.1",
125
- "node-pty": "^1.1.0",
126
- "react": "^18.3.1"
127
- }
128
- }
1
+ {
2
+ "name": "@sheepbun/yips",
3
+ "version": "0.1.47",
4
+ "description": "Yips Personal Desktop Agent",
5
+ "scripts": {
6
+ "postinstall": "node postinstall.js"
7
+ },
8
+ "bin": {
9
+ "yips": "bin/yips.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/sheepbun/yips.git"
14
+ },
15
+ "author": "Sheepbun",
16
+ "license": "MIT",
17
+ "files": [
18
+ "postinstall.js",
19
+ "bin/yips.js"
20
+ ]
21
+ }
package/postinstall.js ADDED
@@ -0,0 +1,52 @@
1
+ const fs = require('fs');
2
+ const https = require('https');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const isWin = os.platform() === 'win32';
7
+ const ext = isWin ? '.exe' : '';
8
+ const binaryName = isWin ? 'yips-core-windows.exe' : 'yips-core-linux';
9
+ const initialUrl = `https://github.com/sheepbun/yips/releases/latest/download/${binaryName}`;
10
+ const binDir = path.join(__dirname, 'bin');
11
+
12
+ if (!fs.existsSync(binDir)) {
13
+ fs.mkdirSync(binDir);
14
+ }
15
+
16
+ const dest = path.join(binDir, `yips${ext}`);
17
+
18
+ console.log(`Downloading Yips binary from ${initialUrl}...`);
19
+
20
+ function download(url) {
21
+ https.get(url, (res) => {
22
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307 || res.statusCode === 308) {
23
+ // Follow redirect
24
+ if (res.headers.location) {
25
+ download(res.headers.location);
26
+ } else {
27
+ console.error(`Redirect without location header (status ${res.statusCode})`);
28
+ process.exit(1);
29
+ }
30
+ } else if (res.statusCode === 200) {
31
+ const file = fs.createWriteStream(dest);
32
+ res.pipe(file);
33
+ file.on('finish', () => {
34
+ file.close();
35
+ if (!isWin) {
36
+ fs.chmodSync(dest, 0o755);
37
+ }
38
+ console.log('Downloaded Yips binary successfully.');
39
+ });
40
+ } else {
41
+ console.error(`Failed to download: HTTP ${res.statusCode}`);
42
+ process.exit(1);
43
+ }
44
+ }).on('error', handleError);
45
+ }
46
+
47
+ download(initialUrl);
48
+
49
+ function handleError(err) {
50
+ console.error('Error downloading Yips:', err.message);
51
+ process.exit(1);
52
+ }
@@ -1,243 +0,0 @@
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
- }