codemini-cli 0.5.10 → 0.5.11

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 (59) hide show
  1. package/OPERATIONS.md +242 -242
  2. package/README.md +588 -588
  3. package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-7HL7yft8.js → highlighted-body-OFNGDK62-CANOG7Xg.js} +1 -1
  4. package/codemini-web/dist/assets/{index-BK75hMb2.js → index-B71xykPM.js} +108 -108
  5. package/codemini-web/dist/assets/index-Dkq1DdDX.css +2 -0
  6. package/codemini-web/dist/assets/mermaid-GHXKKRXX-Z_w7M93P.js +1 -0
  7. package/codemini-web/dist/index.html +23 -23
  8. package/codemini-web/lib/approval-manager.js +32 -32
  9. package/codemini-web/lib/runtime-bridge.js +17 -11
  10. package/codemini-web/server.js +534 -205
  11. package/deployment.md +212 -212
  12. package/package.json +1 -1
  13. package/skills/brainstorm/SKILL.md +77 -77
  14. package/skills/codemini.skills.json +40 -40
  15. package/skills/grill-me/SKILL.md +30 -30
  16. package/skills/superpowers-lite/SKILL.md +82 -82
  17. package/src/cli.js +74 -74
  18. package/src/commands/chat.js +210 -210
  19. package/src/commands/run.js +313 -313
  20. package/src/commands/skill.js +438 -304
  21. package/src/commands/web.js +57 -57
  22. package/src/core/agent-loop.js +980 -980
  23. package/src/core/ast.js +309 -307
  24. package/src/core/chat-runtime.js +6261 -6253
  25. package/src/core/command-evaluator.js +72 -72
  26. package/src/core/command-loader.js +311 -311
  27. package/src/core/command-policy.js +301 -301
  28. package/src/core/command-risk.js +156 -156
  29. package/src/core/config-store.js +289 -289
  30. package/src/core/constants.js +18 -1
  31. package/src/core/context-compact.js +365 -365
  32. package/src/core/default-system-prompt.js +114 -107
  33. package/src/core/dream-audit.js +105 -105
  34. package/src/core/dream-consolidate.js +229 -229
  35. package/src/core/dream-evaluator.js +185 -185
  36. package/src/core/fff-adapter.js +383 -383
  37. package/src/core/memory-store.js +543 -543
  38. package/src/core/project-index.js +737 -548
  39. package/src/core/project-instructions.js +98 -98
  40. package/src/core/provider/anthropic.js +514 -514
  41. package/src/core/provider/openai-compatible.js +501 -501
  42. package/src/core/reflect-skill.js +178 -178
  43. package/src/core/reply-language.js +40 -40
  44. package/src/core/session-store.js +474 -474
  45. package/src/core/shell-profile.js +237 -237
  46. package/src/core/shell.js +323 -323
  47. package/src/core/soul.js +69 -69
  48. package/src/core/system-prompt-composer.js +52 -52
  49. package/src/core/tool-args.js +199 -154
  50. package/src/core/tool-output.js +184 -184
  51. package/src/core/tool-result-store.js +206 -206
  52. package/src/core/tools.js +3024 -2893
  53. package/src/core/version.js +11 -11
  54. package/src/tui/chat-app.js +5171 -5171
  55. package/src/tui/tool-activity/presenters/misc.js +30 -30
  56. package/src/tui/tool-activity/presenters/system.js +20 -20
  57. package/templates/project-requirements/report-shell.html +582 -582
  58. package/codemini-web/dist/assets/index-BSdIdn3L.css +0 -2
  59. package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +0 -1
@@ -1,311 +1,311 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import {
5
- getCommandsDir,
6
- getProjectCommandsDir,
7
- getProjectSkillsDir,
8
- getSkillsDir
9
- } from './paths.js';
10
- import { readSkillRegistry } from './skill-registry.js';
11
-
12
- const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
13
- const BUNDLED_SKILLS_DIR = path.resolve(MODULE_DIR, '..', '..', 'skills');
14
- const SKILL_CATALOG_FILE = 'codemini.skills.json';
15
- const FRONTMATTER_READ_BYTES = 16 * 1024;
16
-
17
- function parseArrayText(value) {
18
- const inner = value.slice(1, -1).trim();
19
- if (!inner) return [];
20
- return inner.split(',').map((item) => item.trim().replace(/^["']|["']$/g, ''));
21
- }
22
-
23
- function parseFrontmatter(raw) {
24
- if (!raw.startsWith('---\n')) {
25
- return { metadata: {}, content: raw };
26
- }
27
- const end = raw.indexOf('\n---\n', 4);
28
- if (end === -1) {
29
- return { metadata: {}, content: raw };
30
- }
31
-
32
- const metaRaw = raw.slice(4, end).trim();
33
- const content = raw.slice(end + 5).trim();
34
- const metadata = {};
35
-
36
- for (const line of metaRaw.split('\n')) {
37
- const idx = line.indexOf(':');
38
- if (idx <= 0) continue;
39
- const key = line.slice(0, idx).trim();
40
- const value = line.slice(idx + 1).trim();
41
- if (value.startsWith('[') && value.endsWith(']')) {
42
- metadata[key] = parseArrayText(value);
43
- } else {
44
- metadata[key] = value.replace(/^["']|["']$/g, '');
45
- }
46
- }
47
-
48
- return { metadata, content };
49
- }
50
-
51
- function readFrontmatterMetadata(filePath) {
52
- let fd;
53
- try {
54
- fd = fs.openSync(filePath, 'r');
55
- const buffer = Buffer.alloc(FRONTMATTER_READ_BYTES);
56
- const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
57
- const raw = buffer.subarray(0, bytesRead).toString('utf8');
58
- if (!raw.startsWith('---\n')) return {};
59
- const end = raw.indexOf('\n---\n', 4);
60
- if (end === -1) return {};
61
- return parseFrontmatter(raw.slice(0, end + 5)).metadata;
62
- } catch {
63
- return {};
64
- } finally {
65
- if (fd !== undefined) {
66
- try { fs.closeSync(fd); } catch {}
67
- }
68
- }
69
- }
70
-
71
- function readSkillCatalog(baseDir) {
72
- const catalogPath = path.join(baseDir, SKILL_CATALOG_FILE);
73
- try {
74
- const parsed = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
75
- return parsed && typeof parsed === 'object' && parsed.skills && typeof parsed.skills === 'object'
76
- ? parsed.skills
77
- : {};
78
- } catch {
79
- return {};
80
- }
81
- }
82
-
83
- function normalizeStringArray(value) {
84
- if (Array.isArray(value)) {
85
- return value.map((item) => String(item || '').trim()).filter(Boolean);
86
- }
87
- const single = String(value || '').trim();
88
- return single ? [single] : [];
89
- }
90
-
91
- function catalogMetadata(catalog, name) {
92
- const entry = catalog?.[name];
93
- if (!entry || typeof entry !== 'object') return {};
94
- return {
95
- ...(entry.description ? { description: String(entry.description) } : {}),
96
- ...(entry.mode ? { mode: String(entry.mode) } : {}),
97
- ...(entry.enabled !== undefined ? { enabled: entry.enabled !== false } : {}),
98
- ...(entry.priority !== undefined ? { priority: Number(entry.priority) } : {}),
99
- triggers: normalizeStringArray(entry.triggers)
100
- };
101
- }
102
-
103
- function commandWithContent(command, parsedContent) {
104
- if (parsedContent !== undefined) {
105
- return { ...command, content: parsedContent };
106
- }
107
-
108
- let cached;
109
- let loaded = false;
110
- return Object.defineProperty({ ...command }, 'content', {
111
- enumerable: true,
112
- configurable: true,
113
- get() {
114
- if (!loaded) {
115
- const raw = fs.readFileSync(command.path, 'utf8');
116
- cached = parseFrontmatter(raw).content;
117
- loaded = true;
118
- }
119
- return cached;
120
- }
121
- });
122
- }
123
-
124
- function safeEntries(dir) {
125
- try {
126
- return fs.readdirSync(dir);
127
- } catch {
128
- return [];
129
- }
130
- }
131
-
132
- function isSafeEntry(entry) {
133
- return entry !== '.' && entry !== '..' && !entry.includes('/') && !entry.includes('\\');
134
- }
135
-
136
- function setCommand(out, name, command) {
137
- const existing = out.get(name);
138
- if (existing?.source === 'bundled-skill') return;
139
- out.set(name, command);
140
- }
141
-
142
- function loadMarkdownCommandsFromDir(baseDir, source, out) {
143
- if (!fs.existsSync(baseDir)) return;
144
- for (const entry of safeEntries(baseDir)) {
145
- if (!isSafeEntry(entry)) continue;
146
- const full = path.join(baseDir, entry);
147
- const stat = fs.statSync(full);
148
-
149
- if (stat.isDirectory()) {
150
- const commandFile = path.join(full, `${entry}.md`);
151
- if (fs.existsSync(commandFile)) {
152
- const raw = fs.readFileSync(commandFile, 'utf8');
153
- const parsed = parseFrontmatter(raw);
154
- setCommand(out, entry, {
155
- name: entry,
156
- source,
157
- path: commandFile,
158
- metadata: parsed.metadata,
159
- content: parsed.content
160
- });
161
- }
162
- continue;
163
- }
164
-
165
- if (entry.endsWith('.md')) {
166
- const name = entry.replace(/\.md$/, '');
167
- const raw = fs.readFileSync(full, 'utf8');
168
- const parsed = parseFrontmatter(raw);
169
- setCommand(out, name, {
170
- name,
171
- source,
172
- path: full,
173
- metadata: parsed.metadata,
174
- content: parsed.content
175
- });
176
- }
177
- }
178
- }
179
-
180
- function loadLegacySkillsFromDir(baseDir, source, out) {
181
- if (!fs.existsSync(baseDir)) return;
182
- const catalog = readSkillCatalog(baseDir);
183
- for (const entry of safeEntries(baseDir)) {
184
- if (!isSafeEntry(entry)) continue;
185
- const full = path.join(baseDir, entry);
186
- const stat = fs.statSync(full);
187
- if (!stat.isDirectory()) continue;
188
- const catalogMeta = catalogMetadata(catalog, entry);
189
- const skillFile = path.join(full, 'SKILL.md');
190
- if (!fs.existsSync(skillFile)) continue;
191
- const frontmatter = readFrontmatterMetadata(skillFile);
192
- setCommand(out, entry, commandWithContent({
193
- name: entry,
194
- source: `${source}-skill`,
195
- path: skillFile,
196
- metadata: {
197
- ...frontmatter,
198
- ...catalogMeta,
199
- description: catalogMeta.description || frontmatter.description || 'Legacy skill',
200
- type: 'skill'
201
- }
202
- }));
203
- }
204
- }
205
-
206
- function loadBundledSkillsFromDir(baseDir, out) {
207
- if (!fs.existsSync(baseDir)) return;
208
- const catalog = readSkillCatalog(baseDir);
209
- for (const entry of safeEntries(baseDir)) {
210
- if (!isSafeEntry(entry)) continue;
211
- const full = path.join(baseDir, entry);
212
- const stat = fs.statSync(full);
213
- if (!stat.isDirectory()) continue;
214
- const catalogMeta = catalogMetadata(catalog, entry);
215
- const skillFile = path.join(full, 'SKILL.md');
216
- if (!fs.existsSync(skillFile)) continue;
217
- const frontmatter = readFrontmatterMetadata(skillFile);
218
- setCommand(out, entry, commandWithContent({
219
- name: entry,
220
- source: 'bundled-skill',
221
- path: skillFile,
222
- metadata: {
223
- ...frontmatter,
224
- ...catalogMeta,
225
- type: 'skill',
226
- version: frontmatter.version || '0.1.0',
227
- description: catalogMeta.description || frontmatter.description || 'Bundled skill'
228
- }
229
- }));
230
- }
231
- }
232
-
233
- function applySkillCatalogPatches(baseDir, out) {
234
- const catalog = readSkillCatalog(baseDir);
235
- for (const name of Object.keys(catalog)) {
236
- const existing = out.get(name);
237
- if (!existing || existing.metadata?.type !== 'skill') continue;
238
- const meta = catalogMetadata(catalog, name);
239
- existing.metadata = {
240
- ...existing.metadata,
241
- ...meta,
242
- description: meta.description || existing.metadata.description || ''
243
- };
244
- }
245
- }
246
-
247
- function loadInstalledSkillsFromRegistry(baseDir, registry, out) {
248
- if (!registry || !Array.isArray(registry.skills)) return;
249
- const catalog = readSkillCatalog(baseDir);
250
- for (const skill of registry.skills) {
251
- if (skill.enabled === false) continue;
252
- const name = skill.name;
253
- if (out.has(name)) continue;
254
- const catalogMeta = catalogMetadata(catalog, name);
255
- const entry = skill.entryFile || 'SKILL.md';
256
- const full = path.join(baseDir, name, entry);
257
- if (!fs.existsSync(full)) continue;
258
- const frontmatter = readFrontmatterMetadata(full);
259
- setCommand(out, name, commandWithContent({
260
- name,
261
- source: 'registry-skill',
262
- path: full,
263
- metadata: {
264
- ...frontmatter,
265
- ...catalogMeta,
266
- type: 'skill',
267
- version: skill.version || frontmatter.version || '0.0.0',
268
- description: catalogMeta.description || skill.description || frontmatter.description || 'Installed skill'
269
- }
270
- }));
271
- }
272
- }
273
-
274
- export function formatLocalDate(date = new Date()) {
275
- const value = date instanceof Date ? date : new Date(date);
276
- const year = value.getFullYear();
277
- const month = String(value.getMonth() + 1).padStart(2, '0');
278
- const day = String(value.getDate()).padStart(2, '0');
279
- return `${year}-${month}-${day}`;
280
- }
281
-
282
- function substituteVariables(text, args = []) {
283
- let out = text;
284
- args.forEach((arg, index) => {
285
- out = out.replaceAll(`{{${index + 1}}}`, arg);
286
- });
287
- out = out.replaceAll('{{args}}', args.join(' '));
288
- out = out.replaceAll('{{cwd}}', process.cwd());
289
- out = out.replaceAll('{{date}}', formatLocalDate());
290
- return out;
291
- }
292
-
293
- export async function loadCommandsAndSkills(cwd = process.cwd()) {
294
- const commands = new Map();
295
-
296
- loadBundledSkillsFromDir(BUNDLED_SKILLS_DIR, commands);
297
- applySkillCatalogPatches(getProjectSkillsDir(cwd), commands);
298
- loadMarkdownCommandsFromDir(getCommandsDir(), 'global', commands);
299
- loadMarkdownCommandsFromDir(getProjectCommandsDir(cwd), 'project', commands);
300
- loadLegacySkillsFromDir(getSkillsDir(), 'global', commands);
301
- loadLegacySkillsFromDir(getProjectSkillsDir(cwd), 'project', commands);
302
- applySkillCatalogPatches(getProjectSkillsDir(cwd), commands);
303
- const registry = await readSkillRegistry();
304
- loadInstalledSkillsFromRegistry(getSkillsDir(), registry, commands);
305
-
306
- return commands;
307
- }
308
-
309
- export function renderCommandPrompt(command, args) {
310
- return `[Executing ${command.metadata.type === 'skill' ? 'skill' : 'command'}: /${command.name}]\n\n${substituteVariables(command.content, args)}`;
311
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import {
5
+ getCommandsDir,
6
+ getProjectCommandsDir,
7
+ getProjectSkillsDir,
8
+ getSkillsDir
9
+ } from './paths.js';
10
+ import { readSkillRegistry } from './skill-registry.js';
11
+
12
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
13
+ const BUNDLED_SKILLS_DIR = path.resolve(MODULE_DIR, '..', '..', 'skills');
14
+ const SKILL_CATALOG_FILE = 'codemini.skills.json';
15
+ const FRONTMATTER_READ_BYTES = 16 * 1024;
16
+
17
+ function parseArrayText(value) {
18
+ const inner = value.slice(1, -1).trim();
19
+ if (!inner) return [];
20
+ return inner.split(',').map((item) => item.trim().replace(/^["']|["']$/g, ''));
21
+ }
22
+
23
+ function parseFrontmatter(raw) {
24
+ if (!raw.startsWith('---\n')) {
25
+ return { metadata: {}, content: raw };
26
+ }
27
+ const end = raw.indexOf('\n---\n', 4);
28
+ if (end === -1) {
29
+ return { metadata: {}, content: raw };
30
+ }
31
+
32
+ const metaRaw = raw.slice(4, end).trim();
33
+ const content = raw.slice(end + 5).trim();
34
+ const metadata = {};
35
+
36
+ for (const line of metaRaw.split('\n')) {
37
+ const idx = line.indexOf(':');
38
+ if (idx <= 0) continue;
39
+ const key = line.slice(0, idx).trim();
40
+ const value = line.slice(idx + 1).trim();
41
+ if (value.startsWith('[') && value.endsWith(']')) {
42
+ metadata[key] = parseArrayText(value);
43
+ } else {
44
+ metadata[key] = value.replace(/^["']|["']$/g, '');
45
+ }
46
+ }
47
+
48
+ return { metadata, content };
49
+ }
50
+
51
+ function readFrontmatterMetadata(filePath) {
52
+ let fd;
53
+ try {
54
+ fd = fs.openSync(filePath, 'r');
55
+ const buffer = Buffer.alloc(FRONTMATTER_READ_BYTES);
56
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
57
+ const raw = buffer.subarray(0, bytesRead).toString('utf8');
58
+ if (!raw.startsWith('---\n')) return {};
59
+ const end = raw.indexOf('\n---\n', 4);
60
+ if (end === -1) return {};
61
+ return parseFrontmatter(raw.slice(0, end + 5)).metadata;
62
+ } catch {
63
+ return {};
64
+ } finally {
65
+ if (fd !== undefined) {
66
+ try { fs.closeSync(fd); } catch {}
67
+ }
68
+ }
69
+ }
70
+
71
+ function readSkillCatalog(baseDir) {
72
+ const catalogPath = path.join(baseDir, SKILL_CATALOG_FILE);
73
+ try {
74
+ const parsed = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
75
+ return parsed && typeof parsed === 'object' && parsed.skills && typeof parsed.skills === 'object'
76
+ ? parsed.skills
77
+ : {};
78
+ } catch {
79
+ return {};
80
+ }
81
+ }
82
+
83
+ function normalizeStringArray(value) {
84
+ if (Array.isArray(value)) {
85
+ return value.map((item) => String(item || '').trim()).filter(Boolean);
86
+ }
87
+ const single = String(value || '').trim();
88
+ return single ? [single] : [];
89
+ }
90
+
91
+ function catalogMetadata(catalog, name) {
92
+ const entry = catalog?.[name];
93
+ if (!entry || typeof entry !== 'object') return {};
94
+ return {
95
+ ...(entry.description ? { description: String(entry.description) } : {}),
96
+ ...(entry.mode ? { mode: String(entry.mode) } : {}),
97
+ ...(entry.enabled !== undefined ? { enabled: entry.enabled !== false } : {}),
98
+ ...(entry.priority !== undefined ? { priority: Number(entry.priority) } : {}),
99
+ triggers: normalizeStringArray(entry.triggers)
100
+ };
101
+ }
102
+
103
+ function commandWithContent(command, parsedContent) {
104
+ if (parsedContent !== undefined) {
105
+ return { ...command, content: parsedContent };
106
+ }
107
+
108
+ let cached;
109
+ let loaded = false;
110
+ return Object.defineProperty({ ...command }, 'content', {
111
+ enumerable: true,
112
+ configurable: true,
113
+ get() {
114
+ if (!loaded) {
115
+ const raw = fs.readFileSync(command.path, 'utf8');
116
+ cached = parseFrontmatter(raw).content;
117
+ loaded = true;
118
+ }
119
+ return cached;
120
+ }
121
+ });
122
+ }
123
+
124
+ function safeEntries(dir) {
125
+ try {
126
+ return fs.readdirSync(dir);
127
+ } catch {
128
+ return [];
129
+ }
130
+ }
131
+
132
+ function isSafeEntry(entry) {
133
+ return entry !== '.' && entry !== '..' && !entry.includes('/') && !entry.includes('\\');
134
+ }
135
+
136
+ function setCommand(out, name, command) {
137
+ const existing = out.get(name);
138
+ if (existing?.source === 'bundled-skill') return;
139
+ out.set(name, command);
140
+ }
141
+
142
+ function loadMarkdownCommandsFromDir(baseDir, source, out) {
143
+ if (!fs.existsSync(baseDir)) return;
144
+ for (const entry of safeEntries(baseDir)) {
145
+ if (!isSafeEntry(entry)) continue;
146
+ const full = path.join(baseDir, entry);
147
+ const stat = fs.statSync(full);
148
+
149
+ if (stat.isDirectory()) {
150
+ const commandFile = path.join(full, `${entry}.md`);
151
+ if (fs.existsSync(commandFile)) {
152
+ const raw = fs.readFileSync(commandFile, 'utf8');
153
+ const parsed = parseFrontmatter(raw);
154
+ setCommand(out, entry, {
155
+ name: entry,
156
+ source,
157
+ path: commandFile,
158
+ metadata: parsed.metadata,
159
+ content: parsed.content
160
+ });
161
+ }
162
+ continue;
163
+ }
164
+
165
+ if (entry.endsWith('.md')) {
166
+ const name = entry.replace(/\.md$/, '');
167
+ const raw = fs.readFileSync(full, 'utf8');
168
+ const parsed = parseFrontmatter(raw);
169
+ setCommand(out, name, {
170
+ name,
171
+ source,
172
+ path: full,
173
+ metadata: parsed.metadata,
174
+ content: parsed.content
175
+ });
176
+ }
177
+ }
178
+ }
179
+
180
+ function loadLegacySkillsFromDir(baseDir, source, out) {
181
+ if (!fs.existsSync(baseDir)) return;
182
+ const catalog = readSkillCatalog(baseDir);
183
+ for (const entry of safeEntries(baseDir)) {
184
+ if (!isSafeEntry(entry)) continue;
185
+ const full = path.join(baseDir, entry);
186
+ const stat = fs.statSync(full);
187
+ if (!stat.isDirectory()) continue;
188
+ const catalogMeta = catalogMetadata(catalog, entry);
189
+ const skillFile = path.join(full, 'SKILL.md');
190
+ if (!fs.existsSync(skillFile)) continue;
191
+ const frontmatter = readFrontmatterMetadata(skillFile);
192
+ setCommand(out, entry, commandWithContent({
193
+ name: entry,
194
+ source: `${source}-skill`,
195
+ path: skillFile,
196
+ metadata: {
197
+ ...frontmatter,
198
+ ...catalogMeta,
199
+ description: catalogMeta.description || frontmatter.description || 'Legacy skill',
200
+ type: 'skill'
201
+ }
202
+ }));
203
+ }
204
+ }
205
+
206
+ function loadBundledSkillsFromDir(baseDir, out) {
207
+ if (!fs.existsSync(baseDir)) return;
208
+ const catalog = readSkillCatalog(baseDir);
209
+ for (const entry of safeEntries(baseDir)) {
210
+ if (!isSafeEntry(entry)) continue;
211
+ const full = path.join(baseDir, entry);
212
+ const stat = fs.statSync(full);
213
+ if (!stat.isDirectory()) continue;
214
+ const catalogMeta = catalogMetadata(catalog, entry);
215
+ const skillFile = path.join(full, 'SKILL.md');
216
+ if (!fs.existsSync(skillFile)) continue;
217
+ const frontmatter = readFrontmatterMetadata(skillFile);
218
+ setCommand(out, entry, commandWithContent({
219
+ name: entry,
220
+ source: 'bundled-skill',
221
+ path: skillFile,
222
+ metadata: {
223
+ ...frontmatter,
224
+ ...catalogMeta,
225
+ type: 'skill',
226
+ version: frontmatter.version || '0.1.0',
227
+ description: catalogMeta.description || frontmatter.description || 'Bundled skill'
228
+ }
229
+ }));
230
+ }
231
+ }
232
+
233
+ function applySkillCatalogPatches(baseDir, out) {
234
+ const catalog = readSkillCatalog(baseDir);
235
+ for (const name of Object.keys(catalog)) {
236
+ const existing = out.get(name);
237
+ if (!existing || existing.metadata?.type !== 'skill') continue;
238
+ const meta = catalogMetadata(catalog, name);
239
+ existing.metadata = {
240
+ ...existing.metadata,
241
+ ...meta,
242
+ description: meta.description || existing.metadata.description || ''
243
+ };
244
+ }
245
+ }
246
+
247
+ function loadInstalledSkillsFromRegistry(baseDir, registry, out) {
248
+ if (!registry || !Array.isArray(registry.skills)) return;
249
+ const catalog = readSkillCatalog(baseDir);
250
+ for (const skill of registry.skills) {
251
+ if (skill.enabled === false) continue;
252
+ const name = skill.name;
253
+ if (out.has(name)) continue;
254
+ const catalogMeta = catalogMetadata(catalog, name);
255
+ const entry = skill.entryFile || 'SKILL.md';
256
+ const full = path.join(baseDir, name, entry);
257
+ if (!fs.existsSync(full)) continue;
258
+ const frontmatter = readFrontmatterMetadata(full);
259
+ setCommand(out, name, commandWithContent({
260
+ name,
261
+ source: 'registry-skill',
262
+ path: full,
263
+ metadata: {
264
+ ...frontmatter,
265
+ ...catalogMeta,
266
+ type: 'skill',
267
+ version: skill.version || frontmatter.version || '0.0.0',
268
+ description: catalogMeta.description || skill.description || frontmatter.description || 'Installed skill'
269
+ }
270
+ }));
271
+ }
272
+ }
273
+
274
+ export function formatLocalDate(date = new Date()) {
275
+ const value = date instanceof Date ? date : new Date(date);
276
+ const year = value.getFullYear();
277
+ const month = String(value.getMonth() + 1).padStart(2, '0');
278
+ const day = String(value.getDate()).padStart(2, '0');
279
+ return `${year}-${month}-${day}`;
280
+ }
281
+
282
+ function substituteVariables(text, args = []) {
283
+ let out = text;
284
+ args.forEach((arg, index) => {
285
+ out = out.replaceAll(`{{${index + 1}}}`, arg);
286
+ });
287
+ out = out.replaceAll('{{args}}', args.join(' '));
288
+ out = out.replaceAll('{{cwd}}', process.cwd());
289
+ out = out.replaceAll('{{date}}', formatLocalDate());
290
+ return out;
291
+ }
292
+
293
+ export async function loadCommandsAndSkills(cwd = process.cwd()) {
294
+ const commands = new Map();
295
+
296
+ loadBundledSkillsFromDir(BUNDLED_SKILLS_DIR, commands);
297
+ applySkillCatalogPatches(getProjectSkillsDir(cwd), commands);
298
+ loadMarkdownCommandsFromDir(getCommandsDir(), 'global', commands);
299
+ loadMarkdownCommandsFromDir(getProjectCommandsDir(cwd), 'project', commands);
300
+ loadLegacySkillsFromDir(getSkillsDir(), 'global', commands);
301
+ loadLegacySkillsFromDir(getProjectSkillsDir(cwd), 'project', commands);
302
+ applySkillCatalogPatches(getProjectSkillsDir(cwd), commands);
303
+ const registry = await readSkillRegistry();
304
+ loadInstalledSkillsFromRegistry(getSkillsDir(), registry, commands);
305
+
306
+ return commands;
307
+ }
308
+
309
+ export function renderCommandPrompt(command, args) {
310
+ return `[Executing ${command.metadata.type === 'skill' ? 'skill' : 'command'}: /${command.name}]\n\n${substituteVariables(command.content, args)}`;
311
+ }