codemini-cli 0.6.3 → 0.6.4
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/codemini-web/dist/assets/{AboutDialog-jgqGjQgl.js → AboutDialog-MRopwNIL.js} +2 -2
- package/codemini-web/dist/assets/CodeWikiPanel-UpK5xGE3.js +1 -0
- package/codemini-web/dist/assets/ConfigDialog-CNl28wsj.js +1 -0
- package/codemini-web/dist/assets/GitDiffDialog-gSysUg2J.js +3 -0
- package/codemini-web/dist/assets/{MemoryDialog-BhxQgG0I.js → MemoryDialog-DFUmo3Kl.js} +3 -3
- package/codemini-web/dist/assets/MessageBubble-CGnnViv0.js +12 -0
- package/codemini-web/dist/assets/PatchDiff-B8rwvEg5.js +230 -0
- package/codemini-web/dist/assets/ProjectSelector-BF59M1zb.js +1 -0
- package/codemini-web/dist/assets/{SkillDialog-DxS43NpR.js → SkillDialog-CQTjbSiw.js} +4 -4
- package/codemini-web/dist/assets/SoulDialog-BLjUGqqB.js +1 -0
- package/codemini-web/dist/assets/chevron-right--85xg7qk.js +1 -0
- package/codemini-web/dist/assets/{chunk-BO2N2NFS-Budy_hfO.js → chunk-BO2N2NFS-6uELoidu.js} +6 -6
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-CQS1PAvD.js → highlighted-body-OFNGDK62-gb1UMBZ5.js} +1 -1
- package/codemini-web/dist/assets/index-1xqD0R5t.css +2 -0
- package/codemini-web/dist/assets/index-CDXQGwPs.js +65 -0
- package/codemini-web/dist/assets/{input-CNQgbKe6.js → input-Ca8O_061.js} +1 -1
- package/codemini-web/dist/assets/lib-BXWizt13.js +1 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-ROliF8Yd.js +1 -0
- package/codemini-web/dist/assets/{pencil-Ce_LFiEh.js → pencil-BhT11Ztp.js} +1 -1
- package/codemini-web/dist/assets/{refresh-cw-BKL-AZu5.js → refresh-cw-D7R5Lth6.js} +1 -1
- package/codemini-web/dist/assets/select-DBvcHBzs.js +1 -0
- package/codemini-web/dist/assets/{trash-2-KmAlCwXd.js → trash-2-BfNZcWfX.js} +1 -1
- package/codemini-web/dist/index.html +2 -2
- package/codemini-web/lib/runtime-bridge.js +325 -296
- package/codemini-web/server.js +310 -243
- package/package.json +1 -1
- package/src/core/agent-loop.js +188 -97
- package/src/core/chat-runtime.js +674 -571
- package/src/core/config-store.js +11 -3
- package/src/core/git-oplog-change-tracker.js +387 -0
- package/src/core/non-git-backup.js +116 -0
- package/src/core/paths.js +123 -123
- package/src/core/session-store.js +148 -99
- package/src/core/tools.js +499 -456
- package/src/tui/chat-app.js +196 -56
- package/codemini-web/dist/assets/CodeWikiPanel-EPuoerNv.js +0 -1
- package/codemini-web/dist/assets/ConfigDialog-B5IGZCc9.js +0 -1
- package/codemini-web/dist/assets/GitDiffDialog-Bb_Tw5ZK.js +0 -222
- package/codemini-web/dist/assets/MessageBubble-wUff4GP4.js +0 -6
- package/codemini-web/dist/assets/ProjectSelector-C0leTf6f.js +0 -1
- package/codemini-web/dist/assets/SoulDialog-XDTEGWvH.js +0 -1
- package/codemini-web/dist/assets/chevron-right-Dbzw7YzA.js +0 -1
- package/codemini-web/dist/assets/index-D0EGtNPr.js +0 -65
- package/codemini-web/dist/assets/index-wOUf3WkN.css +0 -2
- package/codemini-web/dist/assets/lib-BOngVP_M.js +0 -11
- package/codemini-web/dist/assets/lib-DrOTTm_N.js +0 -1
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-DrBu5KyC.js +0 -1
- package/codemini-web/dist/assets/select-BZXfigic.js +0 -1
package/codemini-web/server.js
CHANGED
|
@@ -8,11 +8,11 @@ import { loadConfig, saveConfig, setConfigValue, getConfigValue } from '../src/c
|
|
|
8
8
|
import { createChatRuntime } from '../src/core/chat-runtime.js';
|
|
9
9
|
import { createSession, loadSession, listSessions, resolveSession, deleteSession } from '../src/core/session-store.js';
|
|
10
10
|
import { buildDefaultSystemPrompt } from '../src/core/default-system-prompt.js';
|
|
11
|
-
import { RuntimeBridge } from './lib/runtime-bridge.js';
|
|
12
|
-
import { installSkillSource, listSkillEntries } from '../src/commands/skill.js';
|
|
13
|
-
import { computeFileSha256, readSkillRegistry, upsertSkillRegistryEntry, writeSkillRegistry } from '../src/core/skill-registry.js';
|
|
14
|
-
import { forgetMemory, listMemories, searchMemories } from '../src/core/memory-store.js';
|
|
15
|
-
import { getReplyLanguage } from '../src/core/reply-language.js';
|
|
11
|
+
import { RuntimeBridge } from './lib/runtime-bridge.js';
|
|
12
|
+
import { installSkillSource, listSkillEntries } from '../src/commands/skill.js';
|
|
13
|
+
import { computeFileSha256, readSkillRegistry, upsertSkillRegistryEntry, writeSkillRegistry } from '../src/core/skill-registry.js';
|
|
14
|
+
import { forgetMemory, listMemories, searchMemories } from '../src/core/memory-store.js';
|
|
15
|
+
import { getReplyLanguage } from '../src/core/reply-language.js';
|
|
16
16
|
import { getBaseConfigDir, getFileIndexPath, getProjectSkillsDir, getSkillsDir } from '../src/core/paths.js';
|
|
17
17
|
import { initializeProjectIndex } from '../src/core/project-index.js';
|
|
18
18
|
import { INDEX_SKIP_DIRS } from '../src/core/constants.js';
|
|
@@ -23,19 +23,19 @@ const GENERAL_PROJECT_DIR = (() => {
|
|
|
23
23
|
return path.join(base, 'workspace');
|
|
24
24
|
})();
|
|
25
25
|
|
|
26
|
-
const SKILL_CATALOG_FILE = 'codemini.skills.json';
|
|
27
|
-
const SKILL_MODES = new Set(['always', 'auto_attach', 'agent_requested', 'manual']);
|
|
28
|
-
const SKILL_SCOPES = new Set(['project', 'global']);
|
|
29
|
-
const MEMORY_SCOPES = new Set(['user', 'global', 'project']);
|
|
30
|
-
|
|
31
|
-
function normalizeSkillScope(scope) {
|
|
32
|
-
return SKILL_SCOPES.has(scope) ? scope : 'project';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalizeMemoryScope(scope) {
|
|
36
|
-
const value = String(scope || '').trim().toLowerCase();
|
|
37
|
-
return MEMORY_SCOPES.has(value) ? value : 'user';
|
|
38
|
-
}
|
|
26
|
+
const SKILL_CATALOG_FILE = 'codemini.skills.json';
|
|
27
|
+
const SKILL_MODES = new Set(['always', 'auto_attach', 'agent_requested', 'manual']);
|
|
28
|
+
const SKILL_SCOPES = new Set(['project', 'global']);
|
|
29
|
+
const MEMORY_SCOPES = new Set(['user', 'global', 'project']);
|
|
30
|
+
|
|
31
|
+
function normalizeSkillScope(scope) {
|
|
32
|
+
return SKILL_SCOPES.has(scope) ? scope : 'project';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeMemoryScope(scope) {
|
|
36
|
+
const value = String(scope || '').trim().toLowerCase();
|
|
37
|
+
return MEMORY_SCOPES.has(value) ? value : 'user';
|
|
38
|
+
}
|
|
39
39
|
|
|
40
40
|
function isSafeSkillName(name = '') {
|
|
41
41
|
return /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(name);
|
|
@@ -272,107 +272,107 @@ async function serveStatic(res, filePath) {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
function normalizeProjectPath(value) {
|
|
276
|
-
const raw = String(value || '').trim();
|
|
277
|
-
if (!raw) return '';
|
|
278
|
-
const win = raw.match(/^([A-Za-z]):[\\/](.*)$/);
|
|
275
|
+
function normalizeProjectPath(value) {
|
|
276
|
+
const raw = String(value || '').trim();
|
|
277
|
+
if (!raw) return '';
|
|
278
|
+
const win = raw.match(/^([A-Za-z]):[\\/](.*)$/);
|
|
279
279
|
if (win && process.platform !== 'win32') {
|
|
280
280
|
return path.join('/mnt', win[1].toLowerCase(), win[2].replace(/[\\/]+/g, '/'));
|
|
281
281
|
}
|
|
282
|
-
return path.resolve(raw);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function projectNameForDir(projectDir) {
|
|
286
|
-
if (isGeneralProjectDir(projectDir)) return '__codemini_general__';
|
|
287
|
-
return path.basename(path.resolve(projectDir || '')) || projectDir || '';
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function validProjectDir(value) {
|
|
291
|
-
const normalized = normalizeProjectPath(value);
|
|
292
|
-
if (!normalized) return '';
|
|
293
|
-
try {
|
|
294
|
-
const stat = await fs.stat(normalized);
|
|
295
|
-
return stat.isDirectory() ? normalized : '';
|
|
296
|
-
} catch {
|
|
297
|
-
return '';
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function resolveRequestProjectDir(value, fallbackDir) {
|
|
302
|
-
const resolved = await validProjectDir(value);
|
|
303
|
-
return resolved || fallbackDir;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async function parseProjectDirsParam(url, fallbackDir) {
|
|
307
|
-
const raw = url.searchParams.get('projects');
|
|
308
|
-
const parsed = raw ? tryParseJson(raw) : [];
|
|
309
|
-
const values = Array.isArray(parsed) ? parsed : [];
|
|
310
|
-
const seen = new Set();
|
|
311
|
-
const dirs = [];
|
|
312
|
-
for (const candidate of [fallbackDir, ...values]) {
|
|
313
|
-
const resolved = await validProjectDir(candidate);
|
|
314
|
-
if (!resolved || seen.has(resolved)) continue;
|
|
315
|
-
seen.add(resolved);
|
|
316
|
-
dirs.push(resolved);
|
|
317
|
-
}
|
|
318
|
-
if (dirs.length === 0 && fallbackDir) {
|
|
319
|
-
dirs.push(fallbackDir);
|
|
320
|
-
}
|
|
321
|
-
return dirs;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async function listSkillsForProjectDirs(projectDirs, fallbackDir) {
|
|
325
|
-
const dirs = projectDirs.length > 0 ? projectDirs : [fallbackDir];
|
|
326
|
-
const seen = new Set();
|
|
327
|
-
const results = [];
|
|
328
|
-
for (let index = 0; index < dirs.length; index += 1) {
|
|
329
|
-
const projectDir = dirs[index];
|
|
330
|
-
const entries = await listSkillEntries({ scope: 'all', cwd: projectDir });
|
|
331
|
-
for (const entry of entries) {
|
|
332
|
-
if (entry.scope !== 'project') {
|
|
333
|
-
const globalKey = `${entry.scope}:${entry.name}:${entry.path || ''}`;
|
|
334
|
-
if (seen.has(globalKey)) continue;
|
|
335
|
-
seen.add(globalKey);
|
|
336
|
-
results.push(entry);
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
const projectKey = `project:${projectDir}:${entry.name}:${entry.path || ''}`;
|
|
340
|
-
if (seen.has(projectKey)) continue;
|
|
341
|
-
seen.add(projectKey);
|
|
342
|
-
results.push({
|
|
343
|
-
...entry,
|
|
344
|
-
projectDir,
|
|
345
|
-
projectName: projectNameForDir(projectDir)
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return results.sort((a, b) => {
|
|
350
|
-
const left = `${a.scope}:${a.projectName || ''}:${a.name}`;
|
|
351
|
-
const right = `${b.scope}:${b.projectName || ''}:${b.name}`;
|
|
352
|
-
return left.localeCompare(right);
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
async function listMemoriesForProjectDirs({ scope, query, projectDirs, fallbackDir }) {
|
|
357
|
-
if (scope !== 'project') {
|
|
358
|
-
const items = query
|
|
359
|
-
? await searchMemories({ scope, query, workspaceRoot: fallbackDir })
|
|
360
|
-
: await listMemories({ scope, workspaceRoot: fallbackDir });
|
|
361
|
-
return items;
|
|
362
|
-
}
|
|
363
|
-
const dirs = projectDirs.length > 0 ? projectDirs : [fallbackDir];
|
|
364
|
-
const chunks = await Promise.all(dirs.map(async (projectDir) => {
|
|
365
|
-
const items = query
|
|
366
|
-
? await searchMemories({ scope, query, workspaceRoot: projectDir })
|
|
367
|
-
: await listMemories({ scope, workspaceRoot: projectDir });
|
|
368
|
-
return (items || []).map((item) => ({
|
|
369
|
-
...item,
|
|
370
|
-
projectDir,
|
|
371
|
-
projectName: projectNameForDir(projectDir)
|
|
372
|
-
}));
|
|
373
|
-
}));
|
|
374
|
-
return chunks.flat();
|
|
375
|
-
}
|
|
282
|
+
return path.resolve(raw);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function projectNameForDir(projectDir) {
|
|
286
|
+
if (isGeneralProjectDir(projectDir)) return '__codemini_general__';
|
|
287
|
+
return path.basename(path.resolve(projectDir || '')) || projectDir || '';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function validProjectDir(value) {
|
|
291
|
+
const normalized = normalizeProjectPath(value);
|
|
292
|
+
if (!normalized) return '';
|
|
293
|
+
try {
|
|
294
|
+
const stat = await fs.stat(normalized);
|
|
295
|
+
return stat.isDirectory() ? normalized : '';
|
|
296
|
+
} catch {
|
|
297
|
+
return '';
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function resolveRequestProjectDir(value, fallbackDir) {
|
|
302
|
+
const resolved = await validProjectDir(value);
|
|
303
|
+
return resolved || fallbackDir;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function parseProjectDirsParam(url, fallbackDir) {
|
|
307
|
+
const raw = url.searchParams.get('projects');
|
|
308
|
+
const parsed = raw ? tryParseJson(raw) : [];
|
|
309
|
+
const values = Array.isArray(parsed) ? parsed : [];
|
|
310
|
+
const seen = new Set();
|
|
311
|
+
const dirs = [];
|
|
312
|
+
for (const candidate of [fallbackDir, ...values]) {
|
|
313
|
+
const resolved = await validProjectDir(candidate);
|
|
314
|
+
if (!resolved || seen.has(resolved)) continue;
|
|
315
|
+
seen.add(resolved);
|
|
316
|
+
dirs.push(resolved);
|
|
317
|
+
}
|
|
318
|
+
if (dirs.length === 0 && fallbackDir) {
|
|
319
|
+
dirs.push(fallbackDir);
|
|
320
|
+
}
|
|
321
|
+
return dirs;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function listSkillsForProjectDirs(projectDirs, fallbackDir) {
|
|
325
|
+
const dirs = projectDirs.length > 0 ? projectDirs : [fallbackDir];
|
|
326
|
+
const seen = new Set();
|
|
327
|
+
const results = [];
|
|
328
|
+
for (let index = 0; index < dirs.length; index += 1) {
|
|
329
|
+
const projectDir = dirs[index];
|
|
330
|
+
const entries = await listSkillEntries({ scope: 'all', cwd: projectDir });
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
if (entry.scope !== 'project') {
|
|
333
|
+
const globalKey = `${entry.scope}:${entry.name}:${entry.path || ''}`;
|
|
334
|
+
if (seen.has(globalKey)) continue;
|
|
335
|
+
seen.add(globalKey);
|
|
336
|
+
results.push(entry);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const projectKey = `project:${projectDir}:${entry.name}:${entry.path || ''}`;
|
|
340
|
+
if (seen.has(projectKey)) continue;
|
|
341
|
+
seen.add(projectKey);
|
|
342
|
+
results.push({
|
|
343
|
+
...entry,
|
|
344
|
+
projectDir,
|
|
345
|
+
projectName: projectNameForDir(projectDir)
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return results.sort((a, b) => {
|
|
350
|
+
const left = `${a.scope}:${a.projectName || ''}:${a.name}`;
|
|
351
|
+
const right = `${b.scope}:${b.projectName || ''}:${b.name}`;
|
|
352
|
+
return left.localeCompare(right);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function listMemoriesForProjectDirs({ scope, query, projectDirs, fallbackDir }) {
|
|
357
|
+
if (scope !== 'project') {
|
|
358
|
+
const items = query
|
|
359
|
+
? await searchMemories({ scope, query, workspaceRoot: fallbackDir })
|
|
360
|
+
: await listMemories({ scope, workspaceRoot: fallbackDir });
|
|
361
|
+
return items;
|
|
362
|
+
}
|
|
363
|
+
const dirs = projectDirs.length > 0 ? projectDirs : [fallbackDir];
|
|
364
|
+
const chunks = await Promise.all(dirs.map(async (projectDir) => {
|
|
365
|
+
const items = query
|
|
366
|
+
? await searchMemories({ scope, query, workspaceRoot: projectDir })
|
|
367
|
+
: await listMemories({ scope, workspaceRoot: projectDir });
|
|
368
|
+
return (items || []).map((item) => ({
|
|
369
|
+
...item,
|
|
370
|
+
projectDir,
|
|
371
|
+
projectName: projectNameForDir(projectDir)
|
|
372
|
+
}));
|
|
373
|
+
}));
|
|
374
|
+
return chunks.flat();
|
|
375
|
+
}
|
|
376
376
|
|
|
377
377
|
async function resolveCodeWikiProjectDir(url, fallbackDir) {
|
|
378
378
|
const requested = normalizeProjectPath(url.searchParams.get('project') || '');
|
|
@@ -738,20 +738,34 @@ async function main() {
|
|
|
738
738
|
jsonResponse(res, { ok: true });
|
|
739
739
|
return;
|
|
740
740
|
}
|
|
741
|
-
if (req.method === 'POST' && url.pathname === '/api/execution-mode') {
|
|
742
|
-
const { mode } = await readBody(req);
|
|
743
|
-
if (!mode || !['normal', '
|
|
744
|
-
jsonResponse(res, { error: true, message: 'Invalid mode' }, 400);
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
741
|
+
if (req.method === 'POST' && url.pathname === '/api/execution-mode') {
|
|
742
|
+
const { mode } = await readBody(req);
|
|
743
|
+
if (!mode || !['normal', 'plan'].includes(mode)) {
|
|
744
|
+
jsonResponse(res, { error: true, message: 'Invalid mode' }, 400);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
747
|
if (bridge.isBusy()) {
|
|
748
748
|
jsonResponse(res, { error: true, message: 'Cannot switch execution mode while a request is running' }, 409);
|
|
749
749
|
return;
|
|
750
750
|
}
|
|
751
751
|
const ok = await bridge.setExecutionMode(mode);
|
|
752
|
-
jsonResponse(res, { ok });
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
752
|
+
jsonResponse(res, { ok });
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (req.method === 'POST' && url.pathname === '/api/approval-mode') {
|
|
756
|
+
const { mode } = await readBody(req);
|
|
757
|
+
if (!mode || !['review', 'auto', 'full_access'].includes(mode)) {
|
|
758
|
+
jsonResponse(res, { error: true, message: 'Invalid approval mode' }, 400);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (bridge.isBusy()) {
|
|
762
|
+
jsonResponse(res, { error: true, message: 'Cannot switch approval mode while a request is running' }, 409);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const ok = await bridge.setApprovalMode(mode);
|
|
766
|
+
jsonResponse(res, { ok });
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
755
769
|
if (req.method === 'POST' && url.pathname === '/api/approval') {
|
|
756
770
|
const { id, approved } = await readBody(req);
|
|
757
771
|
jsonResponse(res, { ok: bridge.handleApproval(id, !!approved) });
|
|
@@ -968,7 +982,11 @@ async function main() {
|
|
|
968
982
|
|
|
969
983
|
// ── Session management ──
|
|
970
984
|
if (req.method === 'GET' && url.pathname === '/api/sessions') {
|
|
971
|
-
const
|
|
985
|
+
const requestedLimit = Number(url.searchParams.get('limit') || 200);
|
|
986
|
+
const limit = Number.isFinite(requestedLimit)
|
|
987
|
+
? Math.max(1, Math.min(1000, Math.round(requestedLimit)))
|
|
988
|
+
: 200;
|
|
989
|
+
const sessions = await listSessions(limit);
|
|
972
990
|
const enriched = sessions.map(s => ({ ...s, isGeneral: isGeneralProjectDir(s.projectDir) }));
|
|
973
991
|
jsonResponse(res, enriched);
|
|
974
992
|
return;
|
|
@@ -1008,7 +1026,17 @@ async function main() {
|
|
|
1008
1026
|
});
|
|
1009
1027
|
await bridge.switchRuntime(newRuntime);
|
|
1010
1028
|
currentProjectDir = process.cwd();
|
|
1011
|
-
jsonResponse(res, {
|
|
1029
|
+
jsonResponse(res, {
|
|
1030
|
+
ok: true,
|
|
1031
|
+
sessionId,
|
|
1032
|
+
cwd: currentProjectDir,
|
|
1033
|
+
isGeneral: isGeneralProjectDir(currentProjectDir),
|
|
1034
|
+
state: { ...bridge.getState(), cwd: currentProjectDir, isGeneral: isGeneralProjectDir(currentProjectDir) },
|
|
1035
|
+
sessionData: {
|
|
1036
|
+
messages: bridge.getSessionMessages(),
|
|
1037
|
+
compact: bridge.getSessionCompactMeta()
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1012
1040
|
} catch (err) {
|
|
1013
1041
|
jsonResponse(res, { error: true, message: err.message }, 500);
|
|
1014
1042
|
}
|
|
@@ -1028,7 +1056,7 @@ async function main() {
|
|
|
1028
1056
|
let nextSessionId = bridge.getSessionId();
|
|
1029
1057
|
let cwd = currentProjectDir;
|
|
1030
1058
|
if (deletingCurrent) {
|
|
1031
|
-
const remaining = await listSessions(
|
|
1059
|
+
const remaining = await listSessions(1);
|
|
1032
1060
|
const next = remaining.find((session) => session.id !== sessionId);
|
|
1033
1061
|
const built = next
|
|
1034
1062
|
? await buildRuntimeForSession({ sessionId: next.id, model: bridge.getState().model })
|
|
@@ -1038,7 +1066,20 @@ async function main() {
|
|
|
1038
1066
|
nextSessionId = built.session.id;
|
|
1039
1067
|
cwd = currentProjectDir;
|
|
1040
1068
|
}
|
|
1041
|
-
jsonResponse(res, {
|
|
1069
|
+
jsonResponse(res, {
|
|
1070
|
+
ok: true,
|
|
1071
|
+
removed: result.removed,
|
|
1072
|
+
sessionId: nextSessionId,
|
|
1073
|
+
cwd,
|
|
1074
|
+
isGeneral: isGeneralProjectDir(currentProjectDir),
|
|
1075
|
+
...(deletingCurrent ? {
|
|
1076
|
+
state: { ...bridge.getState(), cwd: currentProjectDir, isGeneral: isGeneralProjectDir(currentProjectDir) },
|
|
1077
|
+
sessionData: {
|
|
1078
|
+
messages: bridge.getSessionMessages(),
|
|
1079
|
+
compact: bridge.getSessionCompactMeta()
|
|
1080
|
+
}
|
|
1081
|
+
} : {})
|
|
1082
|
+
});
|
|
1042
1083
|
} catch (err) {
|
|
1043
1084
|
jsonResponse(res, { error: true, message: err.message }, 500);
|
|
1044
1085
|
}
|
|
@@ -1068,7 +1109,7 @@ async function main() {
|
|
|
1068
1109
|
}
|
|
1069
1110
|
return;
|
|
1070
1111
|
}
|
|
1071
|
-
if (req.method === 'GET' && url.pathname === '/api/git-diff') {
|
|
1112
|
+
if (req.method === 'GET' && url.pathname === '/api/git-diff') {
|
|
1072
1113
|
try {
|
|
1073
1114
|
let patch;
|
|
1074
1115
|
try {
|
|
@@ -1105,9 +1146,35 @@ async function main() {
|
|
|
1105
1146
|
} catch {
|
|
1106
1147
|
jsonResponse(res, { patch: '', files: [] });
|
|
1107
1148
|
}
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
if (req.method === '
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (req.method === 'GET' && url.pathname === '/api/session-changes') {
|
|
1152
|
+
try {
|
|
1153
|
+
jsonResponse(res, { changes: await bridge.getChangeSets() });
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
jsonResponse(res, { error: true, message: err?.message || 'Failed to read session changes' }, 500);
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (req.method === 'GET' && url.pathname.startsWith('/api/session-changes/') && url.pathname.endsWith('/patch')) {
|
|
1160
|
+
const id = decodeURIComponent(url.pathname.slice('/api/session-changes/'.length, -'/patch'.length));
|
|
1161
|
+
try {
|
|
1162
|
+
jsonResponse(res, { id, patch: await bridge.getChangeSetPatch(id) });
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
jsonResponse(res, { error: true, message: err?.message || 'Failed to read change patch' }, 404);
|
|
1165
|
+
}
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (req.method === 'POST' && url.pathname.startsWith('/api/session-changes/') && url.pathname.endsWith('/undo')) {
|
|
1169
|
+
const id = decodeURIComponent(url.pathname.slice('/api/session-changes/'.length, -'/undo'.length));
|
|
1170
|
+
try {
|
|
1171
|
+
jsonResponse(res, await bridge.undoChangeSet(id));
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
jsonResponse(res, { error: true, message: err?.message || 'Failed to undo change' }, 409);
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (req.method === 'POST' && url.pathname === '/api/git-batch') {
|
|
1111
1178
|
const { dirs } = await readBody(req);
|
|
1112
1179
|
const result = {};
|
|
1113
1180
|
for (const dir of (Array.isArray(dirs) ? dirs : [])) {
|
|
@@ -1213,64 +1280,64 @@ async function main() {
|
|
|
1213
1280
|
}
|
|
1214
1281
|
return;
|
|
1215
1282
|
}
|
|
1216
|
-
if (req.method === 'GET' && url.pathname.startsWith('/api/config/get/')) {
|
|
1217
|
-
const key = url.pathname.slice('/api/config/get/'.length);
|
|
1218
|
-
const value = await getConfigValue(key);
|
|
1219
|
-
jsonResponse(res, { key, value });
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// ── Memory management ──
|
|
1224
|
-
if (req.method === 'GET' && url.pathname === '/api/memory') {
|
|
1225
|
-
const scope = normalizeMemoryScope(url.searchParams.get('scope'));
|
|
1226
|
-
const query = String(url.searchParams.get('q') || '').trim();
|
|
1227
|
-
try {
|
|
1228
|
-
const projectDirs = await parseProjectDirsParam(url, currentProjectDir);
|
|
1229
|
-
const items = await listMemoriesForProjectDirs({
|
|
1230
|
-
scope,
|
|
1231
|
-
query,
|
|
1232
|
-
projectDirs,
|
|
1233
|
-
fallbackDir: currentProjectDir
|
|
1234
|
-
});
|
|
1235
|
-
jsonResponse(res, { scope, query, items });
|
|
1236
|
-
} catch (err) {
|
|
1237
|
-
jsonResponse(res, { error: true, message: err.message }, 500);
|
|
1238
|
-
}
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
if (req.method === 'DELETE' && url.pathname.startsWith('/api/memory/')) {
|
|
1242
|
-
const id = decodeURIComponent(url.pathname.slice('/api/memory/'.length));
|
|
1243
|
-
const scope = normalizeMemoryScope(url.searchParams.get('scope'));
|
|
1244
|
-
if (!id) { jsonResponse(res, { error: true, message: 'Missing memory id' }, 400); return; }
|
|
1245
|
-
try {
|
|
1246
|
-
const workspaceRoot = scope === 'project'
|
|
1247
|
-
? await resolveRequestProjectDir(url.searchParams.get('projectDir'), currentProjectDir)
|
|
1248
|
-
: currentProjectDir;
|
|
1249
|
-
const result = await forgetMemory({ scope, id, workspaceRoot });
|
|
1250
|
-
jsonResponse(res, { ok: true, scope, ...result });
|
|
1251
|
-
} catch (err) {
|
|
1252
|
-
jsonResponse(res, { error: true, message: err.message }, 500);
|
|
1253
|
-
}
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// ── Skills management ──
|
|
1258
|
-
if (req.method === 'GET' && url.pathname === '/api/skills') {
|
|
1259
|
-
try {
|
|
1260
|
-
const projectDirs = await parseProjectDirsParam(url, currentProjectDir);
|
|
1261
|
-
const skills = await listSkillsForProjectDirs(projectDirs, currentProjectDir);
|
|
1262
|
-
jsonResponse(res, skills);
|
|
1263
|
-
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
if (req.method === 'GET' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/content')) {
|
|
1267
|
-
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/content'.length));
|
|
1268
|
-
try {
|
|
1269
|
-
const targetProjectDir = await resolveRequestProjectDir(url.searchParams.get('projectDir'), currentProjectDir);
|
|
1270
|
-
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1271
|
-
const skill = entries.find(s => s.name === name);
|
|
1272
|
-
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1273
|
-
const content = await fs.readFile(skill.path, 'utf8');
|
|
1283
|
+
if (req.method === 'GET' && url.pathname.startsWith('/api/config/get/')) {
|
|
1284
|
+
const key = url.pathname.slice('/api/config/get/'.length);
|
|
1285
|
+
const value = await getConfigValue(key);
|
|
1286
|
+
jsonResponse(res, { key, value });
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// ── Memory management ──
|
|
1291
|
+
if (req.method === 'GET' && url.pathname === '/api/memory') {
|
|
1292
|
+
const scope = normalizeMemoryScope(url.searchParams.get('scope'));
|
|
1293
|
+
const query = String(url.searchParams.get('q') || '').trim();
|
|
1294
|
+
try {
|
|
1295
|
+
const projectDirs = await parseProjectDirsParam(url, currentProjectDir);
|
|
1296
|
+
const items = await listMemoriesForProjectDirs({
|
|
1297
|
+
scope,
|
|
1298
|
+
query,
|
|
1299
|
+
projectDirs,
|
|
1300
|
+
fallbackDir: currentProjectDir
|
|
1301
|
+
});
|
|
1302
|
+
jsonResponse(res, { scope, query, items });
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
jsonResponse(res, { error: true, message: err.message }, 500);
|
|
1305
|
+
}
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (req.method === 'DELETE' && url.pathname.startsWith('/api/memory/')) {
|
|
1309
|
+
const id = decodeURIComponent(url.pathname.slice('/api/memory/'.length));
|
|
1310
|
+
const scope = normalizeMemoryScope(url.searchParams.get('scope'));
|
|
1311
|
+
if (!id) { jsonResponse(res, { error: true, message: 'Missing memory id' }, 400); return; }
|
|
1312
|
+
try {
|
|
1313
|
+
const workspaceRoot = scope === 'project'
|
|
1314
|
+
? await resolveRequestProjectDir(url.searchParams.get('projectDir'), currentProjectDir)
|
|
1315
|
+
: currentProjectDir;
|
|
1316
|
+
const result = await forgetMemory({ scope, id, workspaceRoot });
|
|
1317
|
+
jsonResponse(res, { ok: true, scope, ...result });
|
|
1318
|
+
} catch (err) {
|
|
1319
|
+
jsonResponse(res, { error: true, message: err.message }, 500);
|
|
1320
|
+
}
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// ── Skills management ──
|
|
1325
|
+
if (req.method === 'GET' && url.pathname === '/api/skills') {
|
|
1326
|
+
try {
|
|
1327
|
+
const projectDirs = await parseProjectDirsParam(url, currentProjectDir);
|
|
1328
|
+
const skills = await listSkillsForProjectDirs(projectDirs, currentProjectDir);
|
|
1329
|
+
jsonResponse(res, skills);
|
|
1330
|
+
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (req.method === 'GET' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/content')) {
|
|
1334
|
+
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/content'.length));
|
|
1335
|
+
try {
|
|
1336
|
+
const targetProjectDir = await resolveRequestProjectDir(url.searchParams.get('projectDir'), currentProjectDir);
|
|
1337
|
+
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1338
|
+
const skill = entries.find(s => s.name === name);
|
|
1339
|
+
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1340
|
+
const content = await fs.readFile(skill.path, 'utf8');
|
|
1274
1341
|
jsonResponse(res, { name: skill.name, content, scope: skill.scope });
|
|
1275
1342
|
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1276
1343
|
return;
|
|
@@ -1327,40 +1394,40 @@ async function main() {
|
|
|
1327
1394
|
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1328
1395
|
return;
|
|
1329
1396
|
}
|
|
1330
|
-
if (req.method === 'PUT' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/content')) {
|
|
1331
|
-
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/content'.length));
|
|
1332
|
-
const { content, projectDir } = await readBody(req);
|
|
1333
|
-
if (!content) { jsonResponse(res, { error: true, message: 'Missing content' }, 400); return; }
|
|
1334
|
-
try {
|
|
1335
|
-
const targetProjectDir = await resolveRequestProjectDir(projectDir, currentProjectDir);
|
|
1336
|
-
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1337
|
-
const skill = entries.find(s => s.name === name);
|
|
1338
|
-
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1339
|
-
if (skill.scope === 'builtin') { jsonResponse(res, { error: true, message: 'Cannot edit builtin skill' }, 403); return; }
|
|
1397
|
+
if (req.method === 'PUT' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/content')) {
|
|
1398
|
+
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/content'.length));
|
|
1399
|
+
const { content, projectDir } = await readBody(req);
|
|
1400
|
+
if (!content) { jsonResponse(res, { error: true, message: 'Missing content' }, 400); return; }
|
|
1401
|
+
try {
|
|
1402
|
+
const targetProjectDir = await resolveRequestProjectDir(projectDir, currentProjectDir);
|
|
1403
|
+
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1404
|
+
const skill = entries.find(s => s.name === name);
|
|
1405
|
+
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1406
|
+
if (skill.scope === 'builtin') { jsonResponse(res, { error: true, message: 'Cannot edit builtin skill' }, 403); return; }
|
|
1340
1407
|
await fs.writeFile(skill.path, content, 'utf8');
|
|
1341
1408
|
await bridge.reloadCommandsAndSkills();
|
|
1342
1409
|
jsonResponse(res, { ok: true });
|
|
1343
1410
|
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1344
1411
|
return;
|
|
1345
1412
|
}
|
|
1346
|
-
if (req.method === 'DELETE' && url.pathname.startsWith('/api/skills/')) {
|
|
1347
|
-
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length));
|
|
1348
|
-
try {
|
|
1349
|
-
const targetProjectDir = await resolveRequestProjectDir(url.searchParams.get('projectDir'), currentProjectDir);
|
|
1350
|
-
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1351
|
-
const skill = entries.find(s => s.name === name);
|
|
1352
|
-
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1353
|
-
if (skill.scope === 'builtin') { jsonResponse(res, { error: true, message: 'Cannot delete builtin skill' }, 403); return; }
|
|
1413
|
+
if (req.method === 'DELETE' && url.pathname.startsWith('/api/skills/')) {
|
|
1414
|
+
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length));
|
|
1415
|
+
try {
|
|
1416
|
+
const targetProjectDir = await resolveRequestProjectDir(url.searchParams.get('projectDir'), currentProjectDir);
|
|
1417
|
+
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1418
|
+
const skill = entries.find(s => s.name === name);
|
|
1419
|
+
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1420
|
+
if (skill.scope === 'builtin') { jsonResponse(res, { error: true, message: 'Cannot delete builtin skill' }, 403); return; }
|
|
1354
1421
|
const dir = path.dirname(skill.path);
|
|
1355
1422
|
await fs.rm(dir, { recursive: true, force: true });
|
|
1356
1423
|
const registry = await readSkillRegistry();
|
|
1357
1424
|
registry.skills = (registry.skills || []).filter(s => s.name !== name);
|
|
1358
1425
|
await writeSkillRegistry(undefined, registry);
|
|
1359
|
-
const catalog = await readProjectSkillCatalog(targetProjectDir);
|
|
1360
|
-
if (catalog.skills?.[name]) {
|
|
1361
|
-
delete catalog.skills[name];
|
|
1362
|
-
await writeProjectSkillCatalog(targetProjectDir, catalog);
|
|
1363
|
-
}
|
|
1426
|
+
const catalog = await readProjectSkillCatalog(targetProjectDir);
|
|
1427
|
+
if (catalog.skills?.[name]) {
|
|
1428
|
+
delete catalog.skills[name];
|
|
1429
|
+
await writeProjectSkillCatalog(targetProjectDir, catalog);
|
|
1430
|
+
}
|
|
1364
1431
|
await deleteSkillCatalogMetadata(getSkillsDir(), name);
|
|
1365
1432
|
const config = await loadConfig();
|
|
1366
1433
|
if (config.skills?.enabled) delete config.skills.enabled[name];
|
|
@@ -1370,13 +1437,13 @@ async function main() {
|
|
|
1370
1437
|
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1371
1438
|
return;
|
|
1372
1439
|
}
|
|
1373
|
-
if (req.method === 'PUT' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/metadata')) {
|
|
1374
|
-
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/metadata'.length));
|
|
1375
|
-
const body = await readBody(req);
|
|
1376
|
-
try {
|
|
1377
|
-
const targetProjectDir = await resolveRequestProjectDir(body?.projectDir, currentProjectDir);
|
|
1378
|
-
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1379
|
-
const skill = entries.find(s => s.name === name);
|
|
1440
|
+
if (req.method === 'PUT' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/metadata')) {
|
|
1441
|
+
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/metadata'.length));
|
|
1442
|
+
const body = await readBody(req);
|
|
1443
|
+
try {
|
|
1444
|
+
const targetProjectDir = await resolveRequestProjectDir(body?.projectDir, currentProjectDir);
|
|
1445
|
+
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1446
|
+
const skill = entries.find(s => s.name === name);
|
|
1380
1447
|
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1381
1448
|
if (skill.scope === 'builtin' && body?.scope && body.scope !== 'builtin') {
|
|
1382
1449
|
jsonResponse(res, { error: true, message: 'Cannot move builtin skill' }, 403);
|
|
@@ -1388,15 +1455,15 @@ async function main() {
|
|
|
1388
1455
|
let nextScope = skill.scope;
|
|
1389
1456
|
|
|
1390
1457
|
if (skill.scope !== 'builtin' && requestedScope !== skill.scope) {
|
|
1391
|
-
const sourceDir = path.dirname(skill.path);
|
|
1392
|
-
const targetBaseDir = skillBaseDirForScope(requestedScope, targetProjectDir);
|
|
1393
|
-
const targetDir = path.join(targetBaseDir, name);
|
|
1458
|
+
const sourceDir = path.dirname(skill.path);
|
|
1459
|
+
const targetBaseDir = skillBaseDirForScope(requestedScope, targetProjectDir);
|
|
1460
|
+
const targetDir = path.join(targetBaseDir, name);
|
|
1394
1461
|
await fs.rm(targetDir, { recursive: true, force: true });
|
|
1395
1462
|
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
1396
1463
|
await fs.cp(sourceDir, targetDir, { recursive: true, force: true });
|
|
1397
1464
|
await fs.rm(sourceDir, { recursive: true, force: true });
|
|
1398
|
-
if (requestedScope === 'global') {
|
|
1399
|
-
await deleteSkillCatalogMetadata(getProjectSkillsDir(targetProjectDir), name);
|
|
1465
|
+
if (requestedScope === 'global') {
|
|
1466
|
+
await deleteSkillCatalogMetadata(getProjectSkillsDir(targetProjectDir), name);
|
|
1400
1467
|
await upsertSkillRegistryEntry(undefined, {
|
|
1401
1468
|
name,
|
|
1402
1469
|
version: skill.version || '0.0.0',
|
|
@@ -1423,13 +1490,13 @@ async function main() {
|
|
|
1423
1490
|
...(metadataPatch.enabled !== undefined ? { enabled: metadataPatch.enabled } : {})
|
|
1424
1491
|
});
|
|
1425
1492
|
metadata = await upsertSkillCatalogMetadata(getSkillsDir(), name, body || {});
|
|
1426
|
-
} else if (nextScope === 'project') {
|
|
1427
|
-
metadata = await upsertProjectSkillMetadata(targetProjectDir, name, body || {});
|
|
1428
|
-
} else if (skill.scope !== 'builtin') {
|
|
1429
|
-
metadata = await upsertProjectSkillMetadata(targetProjectDir, name, body || {});
|
|
1430
|
-
} else {
|
|
1431
|
-
metadata = await upsertProjectSkillMetadata(targetProjectDir, name, body || {});
|
|
1432
|
-
}
|
|
1493
|
+
} else if (nextScope === 'project') {
|
|
1494
|
+
metadata = await upsertProjectSkillMetadata(targetProjectDir, name, body || {});
|
|
1495
|
+
} else if (skill.scope !== 'builtin') {
|
|
1496
|
+
metadata = await upsertProjectSkillMetadata(targetProjectDir, name, body || {});
|
|
1497
|
+
} else {
|
|
1498
|
+
metadata = await upsertProjectSkillMetadata(targetProjectDir, name, body || {});
|
|
1499
|
+
}
|
|
1433
1500
|
if (skill.scope !== 'builtin' && body?.enabled !== undefined) {
|
|
1434
1501
|
const config = await loadConfig();
|
|
1435
1502
|
config.skills = config.skills || {};
|
|
@@ -1445,16 +1512,16 @@ async function main() {
|
|
|
1445
1512
|
} catch (err) { jsonResponse(res, { error: true, message: err.message }, 500); }
|
|
1446
1513
|
return;
|
|
1447
1514
|
}
|
|
1448
|
-
if (req.method === 'POST' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/toggle')) {
|
|
1449
|
-
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/toggle'.length));
|
|
1450
|
-
const { enabled, projectDir } = await readBody(req);
|
|
1451
|
-
try {
|
|
1452
|
-
const targetProjectDir = await resolveRequestProjectDir(projectDir, currentProjectDir);
|
|
1453
|
-
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1454
|
-
const skill = entries.find(s => s.name === name);
|
|
1455
|
-
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1456
|
-
if (skill.scope === 'builtin') {
|
|
1457
|
-
const metadata = await upsertProjectSkillMetadata(targetProjectDir, name, { enabled });
|
|
1515
|
+
if (req.method === 'POST' && url.pathname.startsWith('/api/skills/') && url.pathname.endsWith('/toggle')) {
|
|
1516
|
+
const name = decodeURIComponent(url.pathname.slice('/api/skills/'.length, -'/toggle'.length));
|
|
1517
|
+
const { enabled, projectDir } = await readBody(req);
|
|
1518
|
+
try {
|
|
1519
|
+
const targetProjectDir = await resolveRequestProjectDir(projectDir, currentProjectDir);
|
|
1520
|
+
const entries = await listSkillEntries({ scope: 'all', cwd: targetProjectDir });
|
|
1521
|
+
const skill = entries.find(s => s.name === name);
|
|
1522
|
+
if (!skill) { jsonResponse(res, { error: true, message: 'Skill not found' }, 404); return; }
|
|
1523
|
+
if (skill.scope === 'builtin') {
|
|
1524
|
+
const metadata = await upsertProjectSkillMetadata(targetProjectDir, name, { enabled });
|
|
1458
1525
|
await bridge.reloadCommandsAndSkills();
|
|
1459
1526
|
jsonResponse(res, { ok: true, name, metadata });
|
|
1460
1527
|
return;
|