@kaitranntt/ccs 7.77.1-dev.12 → 7.77.1-dev.13

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 (41) hide show
  1. package/dist/web-server/shared-routes-collections.d.ts +13 -0
  2. package/dist/web-server/shared-routes-collections.d.ts.map +1 -0
  3. package/dist/web-server/shared-routes-collections.js +170 -0
  4. package/dist/web-server/shared-routes-collections.js.map +1 -0
  5. package/dist/web-server/shared-routes-content.d.ts +16 -0
  6. package/dist/web-server/shared-routes-content.d.ts.map +1 -0
  7. package/dist/web-server/shared-routes-content.js +152 -0
  8. package/dist/web-server/shared-routes-content.js.map +1 -0
  9. package/dist/web-server/shared-routes-markdown-walker.d.ts +12 -0
  10. package/dist/web-server/shared-routes-markdown-walker.d.ts.map +1 -0
  11. package/dist/web-server/shared-routes-markdown-walker.js +97 -0
  12. package/dist/web-server/shared-routes-markdown-walker.js.map +1 -0
  13. package/dist/web-server/shared-routes-markdown.d.ts +18 -0
  14. package/dist/web-server/shared-routes-markdown.d.ts.map +1 -0
  15. package/dist/web-server/shared-routes-markdown.js +167 -0
  16. package/dist/web-server/shared-routes-markdown.js.map +1 -0
  17. package/dist/web-server/shared-routes-path-guards.d.ts +16 -0
  18. package/dist/web-server/shared-routes-path-guards.d.ts.map +1 -0
  19. package/dist/web-server/shared-routes-path-guards.js +93 -0
  20. package/dist/web-server/shared-routes-path-guards.js.map +1 -0
  21. package/dist/web-server/shared-routes-plugin-registry-content.d.ts +12 -0
  22. package/dist/web-server/shared-routes-plugin-registry-content.d.ts.map +1 -0
  23. package/dist/web-server/shared-routes-plugin-registry-content.js +93 -0
  24. package/dist/web-server/shared-routes-plugin-registry-content.js.map +1 -0
  25. package/dist/web-server/shared-routes-plugins.d.ts +21 -0
  26. package/dist/web-server/shared-routes-plugins.d.ts.map +1 -0
  27. package/dist/web-server/shared-routes-plugins.js +197 -0
  28. package/dist/web-server/shared-routes-plugins.js.map +1 -0
  29. package/dist/web-server/shared-routes-symlink-status.d.ts +27 -0
  30. package/dist/web-server/shared-routes-symlink-status.d.ts.map +1 -0
  31. package/dist/web-server/shared-routes-symlink-status.js +135 -0
  32. package/dist/web-server/shared-routes-symlink-status.js.map +1 -0
  33. package/dist/web-server/shared-routes-types.d.ts +23 -0
  34. package/dist/web-server/shared-routes-types.d.ts.map +1 -0
  35. package/dist/web-server/shared-routes-types.js +15 -0
  36. package/dist/web-server/shared-routes-types.js.map +1 -0
  37. package/dist/web-server/shared-routes.d.ts +1 -0
  38. package/dist/web-server/shared-routes.d.ts.map +1 -1
  39. package/dist/web-server/shared-routes.js +30 -730
  40. package/dist/web-server/shared-routes.js.map +1 -1
  41. package/package.json +1 -1
@@ -2,6 +2,7 @@
2
2
  /**
3
3
  * Shared Data Routes (Phase 07)
4
4
  *
5
+ * Thin Express router — delegates all logic to focused sub-modules.
5
6
  * API routes for commands, skills, agents, and plugins from ~/.ccs/shared/
6
7
  */
7
8
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -32,50 +33,50 @@ exports.sharedRoutes = void 0;
32
33
  const express_1 = require("express");
33
34
  const fs = __importStar(require("fs"));
34
35
  const path = __importStar(require("path"));
35
- const yaml = __importStar(require("js-yaml"));
36
- const claude_config_path_1 = require("../utils/claude-config-path");
37
- const auth_middleware_1 = require("./middleware/auth-middleware");
38
36
  const config_loader_facade_1 = require("../config/config-loader-facade");
37
+ const auth_middleware_1 = require("./middleware/auth-middleware");
38
+ const shared_routes_types_1 = require("./shared-routes-types");
39
+ const shared_routes_path_guards_1 = require("./shared-routes-path-guards");
40
+ const shared_routes_collections_1 = require("./shared-routes-collections");
41
+ const shared_routes_content_1 = require("./shared-routes-content");
42
+ const shared_routes_symlink_status_1 = require("./shared-routes-symlink-status");
43
+ /** Strips extended fields so the summary endpoint stays wire-compatible. */
44
+ function symlinkStatusForSummary() {
45
+ const { valid, message } = (0, shared_routes_symlink_status_1.checkSymlinkStatus)();
46
+ return { valid, message };
47
+ }
39
48
  exports.sharedRoutes = (0, express_1.Router)();
40
49
  exports.sharedRoutes.use((req, res, next) => {
41
50
  if ((0, auth_middleware_1.requireLocalAccessWhenAuthDisabled)(req, res, 'Shared-content endpoints require localhost access when dashboard auth is disabled.')) {
42
51
  next();
43
52
  }
44
53
  });
45
- const MAX_DIRECTORY_TRAVERSAL_DEPTH = 10;
46
- const MAX_DESCRIPTION_LENGTH = 140;
47
- const MAX_MARKDOWN_FILE_BYTES = 1024 * 1024; // 1 MiB
48
- const MAX_CONTENT_FILE_BYTES = 2 * 1024 * 1024; // 2 MiB
49
- const SHARED_ITEMS_CACHE_TTL_MS = 1000;
50
- const PLUGIN_REGISTRY_PATH_PREFIX = 'plugin-registry:';
51
- const PLUGIN_INFRASTRUCTURE_DIRECTORIES = new Set(['cache', 'data', 'marketplaces']);
52
- const sharedItemsCache = new Map();
53
54
  /**
54
55
  * GET /api/shared/commands
55
56
  */
56
57
  exports.sharedRoutes.get('/commands', (_req, res) => {
57
- const items = getSharedItems('commands');
58
+ const items = (0, shared_routes_collections_1.getSharedItems)('commands');
58
59
  res.json({ items });
59
60
  });
60
61
  /**
61
62
  * GET /api/shared/skills
62
63
  */
63
64
  exports.sharedRoutes.get('/skills', (_req, res) => {
64
- const items = getSharedItems('skills');
65
+ const items = (0, shared_routes_collections_1.getSharedItems)('skills');
65
66
  res.json({ items });
66
67
  });
67
68
  /**
68
69
  * GET /api/shared/agents
69
70
  */
70
71
  exports.sharedRoutes.get('/agents', (_req, res) => {
71
- const items = getSharedItems('agents');
72
+ const items = (0, shared_routes_collections_1.getSharedItems)('agents');
72
73
  res.json({ items });
73
74
  });
74
75
  /**
75
76
  * GET /api/shared/plugins
76
77
  */
77
78
  exports.sharedRoutes.get('/plugins', (_req, res) => {
78
- const items = getSharedItems('plugins');
79
+ const items = (0, shared_routes_collections_1.getSharedItems)('plugins');
79
80
  res.json({ items });
80
81
  });
81
82
  /**
@@ -84,7 +85,7 @@ exports.sharedRoutes.get('/plugins', (_req, res) => {
84
85
  exports.sharedRoutes.get('/content', (req, res) => {
85
86
  const typeParam = req.query.type;
86
87
  const itemPathParam = req.query.path;
87
- if (!isSharedContentType(typeParam)) {
88
+ if (!(0, shared_routes_types_1.isSharedContentType)(typeParam)) {
88
89
  res.status(400).json({ error: 'Invalid or missing type parameter' });
89
90
  return;
90
91
  }
@@ -98,11 +99,11 @@ exports.sharedRoutes.get('/content', (req, res) => {
98
99
  res.status(404).json({ error: 'Shared directory not found' });
99
100
  return;
100
101
  }
101
- const sharedDirRoot = safeRealPath(sharedDir) ?? path.resolve(sharedDir);
102
+ const sharedDirRoot = (0, shared_routes_path_guards_1.safeRealPath)(sharedDir) ?? path.resolve(sharedDir);
102
103
  const allowedRoots = typeParam === 'settings'
103
- ? resolveSettingsAllowedRoots(ccsDir, sharedDirRoot)
104
- : resolveAllowedRoots(typeParam, ccsDir, sharedDirRoot);
105
- const contentResult = getSharedItemContent(typeParam, itemPathParam, allowedRoots, sharedDirRoot);
104
+ ? (0, shared_routes_path_guards_1.resolveSettingsAllowedRoots)(ccsDir, sharedDirRoot)
105
+ : (0, shared_routes_path_guards_1.resolveAllowedRoots)(typeParam, ccsDir, sharedDirRoot);
106
+ const contentResult = (0, shared_routes_content_1.getSharedItemContent)(typeParam, itemPathParam, allowedRoots, sharedDirRoot);
106
107
  if (!contentResult) {
107
108
  res.status(404).json({ error: 'Shared content not found' });
108
109
  return;
@@ -113,20 +114,20 @@ exports.sharedRoutes.get('/content', (req, res) => {
113
114
  * GET /api/shared/summary
114
115
  */
115
116
  exports.sharedRoutes.get('/summary', (_req, res) => {
116
- const commands = getSharedItems('commands').length;
117
- const skills = getSharedItems('skills').length;
118
- const agents = getSharedItems('agents').length;
119
- const plugins = getSharedItems('plugins').length;
117
+ const commands = (0, shared_routes_collections_1.getSharedItems)('commands').length;
118
+ const skills = (0, shared_routes_collections_1.getSharedItems)('skills').length;
119
+ const agents = (0, shared_routes_collections_1.getSharedItems)('agents').length;
120
+ const plugins = (0, shared_routes_collections_1.getSharedItems)('plugins').length;
120
121
  const ccsDir = (0, config_loader_facade_1.getCcsDir)();
121
122
  const settingsPath = path.join(ccsDir, 'shared', 'settings.json');
122
- const sharedDirRoot = safeRealPath(path.join(ccsDir, 'shared'));
123
- const resolvedSettingsPath = safeRealPath(settingsPath);
123
+ const sharedDirRoot = (0, shared_routes_path_guards_1.safeRealPath)(path.join(ccsDir, 'shared'));
124
+ const resolvedSettingsPath = (0, shared_routes_path_guards_1.safeRealPath)(settingsPath);
124
125
  const settingsAllowedRoots = sharedDirRoot
125
- ? resolveSettingsAllowedRoots(ccsDir, sharedDirRoot)
126
+ ? (0, shared_routes_path_guards_1.resolveSettingsAllowedRoots)(ccsDir, sharedDirRoot)
126
127
  : new Set();
127
128
  const hasSettings = Boolean(sharedDirRoot) &&
128
129
  Boolean(resolvedSettingsPath) &&
129
- isPathWithinAny(resolvedSettingsPath, settingsAllowedRoots);
130
+ (0, shared_routes_path_guards_1.isPathWithinAny)(resolvedSettingsPath, settingsAllowedRoots);
130
131
  res.json({
131
132
  commands,
132
133
  skills,
@@ -134,708 +135,7 @@ exports.sharedRoutes.get('/summary', (_req, res) => {
134
135
  plugins,
135
136
  settings: hasSettings,
136
137
  total: commands + skills + agents + plugins + (hasSettings ? 1 : 0),
137
- symlinkStatus: checkSymlinkStatus(),
138
+ symlinkStatus: symlinkStatusForSummary(),
138
139
  });
139
140
  });
140
- function isSharedCollectionType(value) {
141
- return value === 'commands' || value === 'skills' || value === 'agents' || value === 'plugins';
142
- }
143
- function isSharedContentType(value) {
144
- return isSharedCollectionType(value) || value === 'settings';
145
- }
146
- function resolveAllowedRoots(type, ccsDir, sharedDirRoot) {
147
- if (type === 'commands' || type === 'plugins') {
148
- return new Set([sharedDirRoot]);
149
- }
150
- return new Set([
151
- sharedDirRoot,
152
- ...[
153
- path.join((0, claude_config_path_1.getClaudeConfigDir)(), type),
154
- path.join(ccsDir, '.claude', type),
155
- path.join(ccsDir, 'shared', type),
156
- ]
157
- .map((dirPath) => safeRealPath(dirPath))
158
- .filter((dirPath) => typeof dirPath === 'string'),
159
- ]);
160
- }
161
- function resolveSettingsAllowedRoots(ccsDir, sharedDirRoot) {
162
- const claudeConfigDir = (0, claude_config_path_1.getClaudeConfigDir)();
163
- return new Set([
164
- sharedDirRoot,
165
- safeRealPath(path.join(claudeConfigDir, 'settings.json')),
166
- safeRealPath(path.join(ccsDir, '.claude', 'settings.json')),
167
- ].filter((dirPath) => typeof dirPath === 'string'));
168
- }
169
- function getSharedItems(type) {
170
- const ccsDir = (0, config_loader_facade_1.getCcsDir)();
171
- const sharedDir = path.join(ccsDir, 'shared', type);
172
- const now = Date.now();
173
- if (!fs.existsSync(sharedDir)) {
174
- sharedItemsCache.delete(type);
175
- return [];
176
- }
177
- const cached = sharedItemsCache.get(type);
178
- if (cached && cached.sharedDir === sharedDir && cached.expiresAt > now) {
179
- return cached.items;
180
- }
181
- const items = [];
182
- const sharedDirRoot = safeRealPath(sharedDir) ?? path.resolve(sharedDir);
183
- const allowedRoots = resolveAllowedRoots(type, ccsDir, sharedDirRoot);
184
- if (type === 'commands') {
185
- const commandItems = getCommandItems(sharedDir, allowedRoots);
186
- sharedItemsCache.set(type, {
187
- items: commandItems,
188
- sharedDir,
189
- expiresAt: now + SHARED_ITEMS_CACHE_TTL_MS,
190
- });
191
- return commandItems;
192
- }
193
- if (type === 'plugins') {
194
- const pluginItems = getPluginItems(sharedDir, allowedRoots);
195
- sharedItemsCache.set(type, {
196
- items: pluginItems,
197
- sharedDir,
198
- expiresAt: now + SHARED_ITEMS_CACHE_TTL_MS,
199
- });
200
- return pluginItems;
201
- }
202
- try {
203
- const entries = fs.readdirSync(sharedDir, { withFileTypes: true });
204
- for (const entry of entries) {
205
- try {
206
- const entryPath = path.join(sharedDir, entry.name);
207
- const resolvedEntryPath = safeRealPath(entryPath);
208
- if (!resolvedEntryPath || !isPathWithinAny(resolvedEntryPath, allowedRoots)) {
209
- continue;
210
- }
211
- const stats = fs.statSync(resolvedEntryPath);
212
- const item = getSkillOrAgentItem(type, entry, entryPath, resolvedEntryPath, allowedRoots, stats);
213
- if (!item) {
214
- continue;
215
- }
216
- items.push(item);
217
- }
218
- catch {
219
- // Fail soft per entry so one bad item does not hide valid results.
220
- }
221
- }
222
- }
223
- catch {
224
- // Directory read failed
225
- }
226
- const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));
227
- sharedItemsCache.set(type, {
228
- items: sortedItems,
229
- sharedDir,
230
- expiresAt: now + SHARED_ITEMS_CACHE_TTL_MS,
231
- });
232
- return sortedItems;
233
- }
234
- function getPluginItems(sharedDir, allowedRoots) {
235
- const registryItems = getPluginRegistryItems(sharedDir, allowedRoots);
236
- const legacyDirectoryItems = getLegacyPluginDirectoryItems(sharedDir, allowedRoots);
237
- const itemsByPath = new Map();
238
- for (const item of [...registryItems, ...legacyDirectoryItems]) {
239
- itemsByPath.set(item.path, item);
240
- }
241
- return [...itemsByPath.values()].sort((a, b) => a.name.localeCompare(b.name));
242
- }
243
- function getPluginRegistryItems(sharedDir, allowedRoots) {
244
- const registry = readPluginInstalledRegistry(sharedDir, allowedRoots);
245
- if (!registry) {
246
- return [];
247
- }
248
- return Object.entries(registry.plugins).map(([pluginName, metadata]) => ({
249
- name: pluginName,
250
- description: describeInstalledPlugin(pluginName, metadata),
251
- path: encodePluginRegistryPath(pluginName),
252
- type: 'plugin',
253
- }));
254
- }
255
- function getLegacyPluginDirectoryItems(sharedDir, allowedRoots) {
256
- let entries = [];
257
- try {
258
- entries = fs.readdirSync(sharedDir, { withFileTypes: true });
259
- }
260
- catch {
261
- return [];
262
- }
263
- const items = [];
264
- for (const entry of entries) {
265
- if (!entry.isDirectory() || PLUGIN_INFRASTRUCTURE_DIRECTORIES.has(entry.name)) {
266
- continue;
267
- }
268
- const entryPath = path.join(sharedDir, entry.name);
269
- const resolvedEntryPath = safeRealPath(entryPath);
270
- if (!resolvedEntryPath || !isPathWithinAny(resolvedEntryPath, allowedRoots)) {
271
- continue;
272
- }
273
- const description = readFirstMarkdownDescription([
274
- path.join(resolvedEntryPath, 'README.md'),
275
- path.join(resolvedEntryPath, 'readme.md'),
276
- path.join(resolvedEntryPath, 'PLUGIN.md'),
277
- ], allowedRoots);
278
- if (!description) {
279
- continue;
280
- }
281
- items.push({
282
- name: entry.name,
283
- description,
284
- path: entryPath,
285
- type: 'plugin',
286
- });
287
- }
288
- return items;
289
- }
290
- function getCommandItems(sharedDir, allowedRoots) {
291
- const markdownFiles = collectMarkdownFiles(sharedDir, allowedRoots);
292
- const items = [];
293
- for (const markdownFile of markdownFiles) {
294
- const description = readMarkdownDescription(markdownFile.resolvedPath, allowedRoots);
295
- if (!description) {
296
- continue;
297
- }
298
- const relativePath = path.relative(sharedDir, markdownFile.displayPath);
299
- const normalizedName = relativePath.split(path.sep).join('/').replace(/\.md$/i, '');
300
- if (!normalizedName) {
301
- continue;
302
- }
303
- items.push({
304
- name: normalizedName,
305
- description,
306
- path: markdownFile.displayPath,
307
- type: 'command',
308
- });
309
- }
310
- return items.sort((a, b) => a.name.localeCompare(b.name));
311
- }
312
- function getSkillOrAgentItem(type, entry, entryPath, resolvedEntryPath, allowedRoots, stats) {
313
- if (type === 'skills') {
314
- if (!stats.isDirectory()) {
315
- return null;
316
- }
317
- const description = readMarkdownDescription(path.join(resolvedEntryPath, 'SKILL.md'), allowedRoots);
318
- if (!description) {
319
- return null;
320
- }
321
- return {
322
- name: entry.name,
323
- description,
324
- path: entryPath,
325
- type: 'skill',
326
- };
327
- }
328
- if (stats.isDirectory()) {
329
- const description = readFirstMarkdownDescription([
330
- path.join(resolvedEntryPath, 'prompt.md'),
331
- path.join(resolvedEntryPath, 'AGENT.md'),
332
- path.join(resolvedEntryPath, 'agent.md'),
333
- ], allowedRoots);
334
- if (!description) {
335
- return null;
336
- }
337
- return {
338
- name: entry.name,
339
- description,
340
- path: entryPath,
341
- type: 'agent',
342
- };
343
- }
344
- if (!stats.isFile() || !entry.name.toLowerCase().endsWith('.md')) {
345
- return null;
346
- }
347
- const description = readMarkdownDescription(resolvedEntryPath, allowedRoots);
348
- if (!description) {
349
- return null;
350
- }
351
- return {
352
- name: entry.name.replace(/\.md$/i, ''),
353
- description,
354
- path: entryPath,
355
- type: 'agent',
356
- };
357
- }
358
- function collectMarkdownFiles(sharedDir, allowedRoots) {
359
- const directoriesToVisit = [
360
- { path: sharedDir, depth: 0 },
361
- ];
362
- const visitedDirectories = new Set();
363
- const markdownFiles = [];
364
- while (directoriesToVisit.length > 0) {
365
- const current = directoriesToVisit.pop();
366
- if (!current) {
367
- continue;
368
- }
369
- const currentDir = current.path;
370
- const resolvedCurrentDir = safeRealPath(currentDir);
371
- if (!resolvedCurrentDir || !isPathWithinAny(resolvedCurrentDir, allowedRoots)) {
372
- continue;
373
- }
374
- const normalizedDirPath = normalizeForPathComparison(resolvedCurrentDir);
375
- if (visitedDirectories.has(normalizedDirPath)) {
376
- continue;
377
- }
378
- visitedDirectories.add(normalizedDirPath);
379
- let entries = [];
380
- try {
381
- entries = fs.readdirSync(currentDir, { withFileTypes: true });
382
- }
383
- catch {
384
- continue;
385
- }
386
- for (const entry of entries) {
387
- const entryPath = path.join(currentDir, entry.name);
388
- const resolvedEntryPath = safeRealPath(entryPath);
389
- if (!resolvedEntryPath || !isPathWithinAny(resolvedEntryPath, allowedRoots)) {
390
- continue;
391
- }
392
- let stats;
393
- try {
394
- stats = fs.statSync(resolvedEntryPath);
395
- }
396
- catch {
397
- continue;
398
- }
399
- if (stats.isDirectory()) {
400
- if (current.depth < MAX_DIRECTORY_TRAVERSAL_DEPTH) {
401
- directoriesToVisit.push({ path: entryPath, depth: current.depth + 1 });
402
- }
403
- continue;
404
- }
405
- if (stats.isFile() && entry.name.toLowerCase().endsWith('.md')) {
406
- markdownFiles.push({
407
- displayPath: entryPath,
408
- resolvedPath: resolvedEntryPath,
409
- });
410
- }
411
- }
412
- }
413
- return markdownFiles;
414
- }
415
- function extractDescription(content) {
416
- const frontmatterDescription = extractFrontmatterDescription(content);
417
- if (frontmatterDescription) {
418
- return trimDescription(frontmatterDescription);
419
- }
420
- // Extract first non-empty, non-heading line from the markdown body.
421
- const lines = stripFrontmatter(content).split('\n');
422
- for (const line of lines) {
423
- const trimmed = line.trim();
424
- if (isDescriptionBodyLine(trimmed)) {
425
- return trimDescription(trimmed);
426
- }
427
- }
428
- return 'No description';
429
- }
430
- function isDescriptionBodyLine(line) {
431
- if (!line) {
432
- return false;
433
- }
434
- if (line === '---' || line === '...') {
435
- return false;
436
- }
437
- return !line.startsWith('#') && !line.startsWith('<!--');
438
- }
439
- function extractFrontmatterDescription(content) {
440
- const frontmatterMatch = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\s*\r?\n|$)/);
441
- if (!frontmatterMatch) {
442
- return null;
443
- }
444
- try {
445
- const parsed = yaml.load(frontmatterMatch[1]);
446
- const description = parsed?.description;
447
- if (typeof description !== 'string') {
448
- return null;
449
- }
450
- const trimmed = description.trim();
451
- return trimmed.length > 0 ? trimmed : null;
452
- }
453
- catch {
454
- return null;
455
- }
456
- }
457
- function stripFrontmatter(content) {
458
- return content.replace(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/, '');
459
- }
460
- function trimDescription(description) {
461
- if (description.length <= MAX_DESCRIPTION_LENGTH) {
462
- return description;
463
- }
464
- return `${description.slice(0, MAX_DESCRIPTION_LENGTH - 3).trimEnd()}...`;
465
- }
466
- function readFirstMarkdownDescription(markdownPaths, allowedRoots) {
467
- for (const markdownPath of markdownPaths) {
468
- const description = readMarkdownDescription(markdownPath, allowedRoots);
469
- if (description) {
470
- return description;
471
- }
472
- }
473
- return null;
474
- }
475
- function readMarkdownDescription(markdownPath, allowedRoots) {
476
- try {
477
- const resolvedMarkdownPath = safeRealPath(markdownPath);
478
- if (!resolvedMarkdownPath || !isPathWithinAny(resolvedMarkdownPath, allowedRoots)) {
479
- return null;
480
- }
481
- const stats = fs.statSync(resolvedMarkdownPath);
482
- if (!stats.isFile()) {
483
- return null;
484
- }
485
- if (stats.size > MAX_MARKDOWN_FILE_BYTES) {
486
- return null;
487
- }
488
- const content = fs.readFileSync(resolvedMarkdownPath, 'utf8');
489
- return extractDescription(content);
490
- }
491
- catch {
492
- return null;
493
- }
494
- }
495
- function readMarkdownContent(markdownPath, allowedRoots) {
496
- try {
497
- const resolvedMarkdownPath = safeRealPath(markdownPath);
498
- if (!resolvedMarkdownPath || !isPathWithinAny(resolvedMarkdownPath, allowedRoots)) {
499
- return null;
500
- }
501
- const stats = fs.statSync(resolvedMarkdownPath);
502
- if (!stats.isFile()) {
503
- return null;
504
- }
505
- if (stats.size > MAX_CONTENT_FILE_BYTES) {
506
- return null;
507
- }
508
- return fs.readFileSync(resolvedMarkdownPath, 'utf8');
509
- }
510
- catch {
511
- return null;
512
- }
513
- }
514
- function encodePluginRegistryPath(pluginName) {
515
- return `${PLUGIN_REGISTRY_PATH_PREFIX}${encodeURIComponent(pluginName)}`;
516
- }
517
- function decodePluginRegistryPath(itemPath) {
518
- if (!itemPath.startsWith(PLUGIN_REGISTRY_PATH_PREFIX)) {
519
- return null;
520
- }
521
- try {
522
- const encodedName = itemPath.slice(PLUGIN_REGISTRY_PATH_PREFIX.length);
523
- const pluginName = decodeURIComponent(encodedName);
524
- return pluginName.length > 0 ? pluginName : null;
525
- }
526
- catch {
527
- return null;
528
- }
529
- }
530
- function readJsonObject(filePath, allowedRoots) {
531
- const resolvedPath = safeRealPath(filePath);
532
- if (!resolvedPath || !isPathWithinAny(resolvedPath, allowedRoots)) {
533
- return null;
534
- }
535
- try {
536
- const stats = fs.statSync(resolvedPath);
537
- if (!stats.isFile() || stats.size > MAX_CONTENT_FILE_BYTES) {
538
- return null;
539
- }
540
- const parsed = JSON.parse(fs.readFileSync(resolvedPath, 'utf8'));
541
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
542
- return null;
543
- }
544
- return parsed;
545
- }
546
- catch {
547
- return null;
548
- }
549
- }
550
- function readPluginInstalledRegistry(sharedDir, allowedRoots) {
551
- const parsed = readJsonObject(path.join(sharedDir, 'installed_plugins.json'), allowedRoots);
552
- if (!parsed ||
553
- !parsed.plugins ||
554
- typeof parsed.plugins !== 'object' ||
555
- Array.isArray(parsed.plugins)) {
556
- return null;
557
- }
558
- return {
559
- version: typeof parsed.version === 'number' ? parsed.version : undefined,
560
- plugins: parsed.plugins,
561
- };
562
- }
563
- function describeInstalledPlugin(pluginName, metadata) {
564
- const recordCount = Array.isArray(metadata) ? metadata.length : 1;
565
- const marketplace = extractMarketplaceFromPluginName(pluginName);
566
- const recordLabel = `${recordCount} ${recordCount === 1 ? 'record' : 'records'}`;
567
- return marketplace
568
- ? `Installed from ${marketplace}; ${recordLabel} in shared registry`
569
- : `Installed plugin; ${recordLabel} in shared registry`;
570
- }
571
- function extractMarketplaceFromPluginName(pluginName) {
572
- const atIndex = pluginName.lastIndexOf('@');
573
- if (atIndex <= 0 || atIndex === pluginName.length - 1) {
574
- return null;
575
- }
576
- return pluginName.slice(atIndex + 1);
577
- }
578
- function resolveReadableMarkdownPath(markdownPaths, allowedRoots) {
579
- for (const markdownPath of markdownPaths) {
580
- const resolvedMarkdownPath = safeRealPath(markdownPath);
581
- if (!resolvedMarkdownPath || !isPathWithinAny(resolvedMarkdownPath, allowedRoots)) {
582
- continue;
583
- }
584
- try {
585
- const stats = fs.statSync(resolvedMarkdownPath);
586
- if (!stats.isFile() || stats.size > MAX_CONTENT_FILE_BYTES) {
587
- continue;
588
- }
589
- return resolvedMarkdownPath;
590
- }
591
- catch {
592
- continue;
593
- }
594
- }
595
- return null;
596
- }
597
- function getSharedItemContent(type, itemPath, allowedRoots, sharedDir) {
598
- if (type === 'settings') {
599
- return getSharedSettingsContent(itemPath, allowedRoots);
600
- }
601
- if (type === 'plugins') {
602
- const pluginRegistryContent = getPluginRegistryContent(itemPath, sharedDir, allowedRoots);
603
- if (pluginRegistryContent) {
604
- return pluginRegistryContent;
605
- }
606
- }
607
- const resolvedItemPath = safeRealPath(itemPath);
608
- if (!resolvedItemPath || !isPathWithinAny(resolvedItemPath, allowedRoots)) {
609
- return null;
610
- }
611
- let itemStats;
612
- try {
613
- itemStats = fs.statSync(resolvedItemPath);
614
- }
615
- catch {
616
- return null;
617
- }
618
- let markdownPath = null;
619
- if (type === 'commands') {
620
- if (!itemStats.isFile() || !itemPath.toLowerCase().endsWith('.md')) {
621
- return null;
622
- }
623
- markdownPath = resolvedItemPath;
624
- }
625
- else if (type === 'skills') {
626
- if (!itemStats.isDirectory()) {
627
- return null;
628
- }
629
- markdownPath = resolveReadableMarkdownPath([path.join(resolvedItemPath, 'SKILL.md')], allowedRoots);
630
- }
631
- else if (type === 'plugins') {
632
- if (!itemStats.isDirectory() || isPluginInfrastructurePath(resolvedItemPath, sharedDir)) {
633
- return null;
634
- }
635
- markdownPath = resolveReadableMarkdownPath([
636
- path.join(resolvedItemPath, 'README.md'),
637
- path.join(resolvedItemPath, 'readme.md'),
638
- path.join(resolvedItemPath, 'PLUGIN.md'),
639
- ], allowedRoots);
640
- if (!markdownPath) {
641
- return null;
642
- }
643
- }
644
- else {
645
- if (itemStats.isDirectory()) {
646
- markdownPath = resolveReadableMarkdownPath([
647
- path.join(resolvedItemPath, 'prompt.md'),
648
- path.join(resolvedItemPath, 'AGENT.md'),
649
- path.join(resolvedItemPath, 'agent.md'),
650
- ], allowedRoots);
651
- }
652
- else if (itemStats.isFile() && itemPath.toLowerCase().endsWith('.md')) {
653
- markdownPath = resolvedItemPath;
654
- }
655
- }
656
- if (!markdownPath) {
657
- return null;
658
- }
659
- const content = readMarkdownContent(markdownPath, allowedRoots);
660
- if (!content) {
661
- return null;
662
- }
663
- return {
664
- content,
665
- contentPath: markdownPath,
666
- };
667
- }
668
- function isPluginInfrastructurePath(candidatePath, sharedDir) {
669
- for (const directoryName of PLUGIN_INFRASTRUCTURE_DIRECTORIES) {
670
- const resolvedInfrastructurePath = safeRealPath(path.join(sharedDir, directoryName));
671
- if (resolvedInfrastructurePath && isPathWithin(candidatePath, resolvedInfrastructurePath)) {
672
- return true;
673
- }
674
- }
675
- return false;
676
- }
677
- function getPluginRegistryContent(itemPath, sharedDir, allowedRoots) {
678
- const pluginName = decodePluginRegistryPath(itemPath);
679
- if (!pluginName) {
680
- return null;
681
- }
682
- const registryPath = path.join(sharedDir, 'installed_plugins.json');
683
- const resolvedRegistryPath = safeRealPath(registryPath);
684
- if (!resolvedRegistryPath || !isPathWithinAny(resolvedRegistryPath, allowedRoots)) {
685
- return null;
686
- }
687
- const registry = readPluginInstalledRegistry(sharedDir, allowedRoots);
688
- if (!registry || !Object.prototype.hasOwnProperty.call(registry.plugins, pluginName)) {
689
- return null;
690
- }
691
- const metadata = registry.plugins[pluginName];
692
- const marketplace = extractMarketplaceFromPluginName(pluginName);
693
- const marketplaceEntry = marketplace
694
- ? readJsonObject(path.join(sharedDir, 'known_marketplaces.json'), allowedRoots)?.[marketplace]
695
- : null;
696
- const blocklistEntry = findPluginBlocklistEntry(sharedDir, allowedRoots, pluginName);
697
- const lines = [
698
- `# ${pluginName}`,
699
- '',
700
- `Registry: \`${resolvedRegistryPath}\``,
701
- '',
702
- '## Installed Plugin',
703
- '',
704
- `- Source: shared \`installed_plugins.json\` registry`,
705
- `- Registry version: ${registry.version ?? 'unknown'}`,
706
- `- Marketplace: ${marketplace ?? 'not recorded in plugin name'}`,
707
- `- Install records: ${Array.isArray(metadata) ? metadata.length : 1}`,
708
- ];
709
- if (marketplaceEntry) {
710
- lines.push('', '## Marketplace Registry Entry', '', '```json', stringifyJson(marketplaceEntry), '```');
711
- }
712
- if (blocklistEntry) {
713
- lines.push('', '## Blocklist Notice', '', '```json', stringifyJson(blocklistEntry), '```');
714
- }
715
- lines.push('', '## Plugin Registry Entry', '', '```json', stringifyJson(metadata), '```');
716
- return {
717
- content: lines.join('\n'),
718
- contentPath: resolvedRegistryPath,
719
- };
720
- }
721
- function findPluginBlocklistEntry(sharedDir, allowedRoots, pluginName) {
722
- const blocklist = readJsonObject(path.join(sharedDir, 'blocklist.json'), allowedRoots);
723
- const plugins = blocklist?.plugins;
724
- if (!Array.isArray(plugins)) {
725
- return null;
726
- }
727
- return (plugins.find((entry) => entry &&
728
- typeof entry === 'object' &&
729
- 'plugin' in entry &&
730
- entry.plugin === pluginName) ?? null);
731
- }
732
- function stringifyJson(value) {
733
- return JSON.stringify(value, null, 2) ?? 'null';
734
- }
735
- function safeRealPath(targetPath) {
736
- try {
737
- return fs.realpathSync(targetPath);
738
- }
739
- catch {
740
- return null;
741
- }
742
- }
743
- function getSharedSettingsContent(itemPath, allowedRoots) {
744
- if (path.basename(itemPath) !== 'settings.json') {
745
- return null;
746
- }
747
- const sharedRoot = Array.from(allowedRoots)[0];
748
- if (!sharedRoot) {
749
- return null;
750
- }
751
- const settingsPath = path.join(sharedRoot, 'settings.json');
752
- const resolvedSettingsPath = safeRealPath(settingsPath);
753
- if (!resolvedSettingsPath || !isPathWithinAny(resolvedSettingsPath, allowedRoots)) {
754
- return null;
755
- }
756
- let stats;
757
- try {
758
- stats = fs.statSync(resolvedSettingsPath);
759
- }
760
- catch {
761
- return null;
762
- }
763
- if (!stats.isFile() || stats.size > MAX_CONTENT_FILE_BYTES) {
764
- return null;
765
- }
766
- try {
767
- const rawContent = fs.readFileSync(resolvedSettingsPath, 'utf8');
768
- const parsed = JSON.parse(rawContent);
769
- return {
770
- content: JSON.stringify(parsed, null, 2),
771
- contentPath: resolvedSettingsPath,
772
- };
773
- }
774
- catch {
775
- const content = readMarkdownContent(resolvedSettingsPath, allowedRoots);
776
- return content
777
- ? {
778
- content,
779
- contentPath: resolvedSettingsPath,
780
- }
781
- : null;
782
- }
783
- }
784
- function isPathWithin(candidatePath, basePath) {
785
- const normalizedCandidate = normalizeForPathComparison(candidatePath);
786
- const normalizedBase = normalizeForPathComparison(basePath);
787
- const relative = path.relative(normalizedBase, normalizedCandidate);
788
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
789
- }
790
- function isPathWithinAny(candidatePath, basePaths) {
791
- for (const basePath of basePaths) {
792
- if (isPathWithin(candidatePath, basePath)) {
793
- return true;
794
- }
795
- }
796
- return false;
797
- }
798
- function normalizeForPathComparison(targetPath) {
799
- const normalized = path.resolve(targetPath);
800
- return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
801
- }
802
- function checkSymlinkStatus() {
803
- const ccsDir = (0, config_loader_facade_1.getCcsDir)();
804
- const sharedDir = path.join(ccsDir, 'shared');
805
- if (!fs.existsSync(sharedDir)) {
806
- return { valid: false, message: 'Shared directory not found' };
807
- }
808
- // Check all three symlinks: commands, skills, agents
809
- const linkTypes = ['commands', 'skills', 'agents'];
810
- let validLinks = 0;
811
- for (const linkType of linkTypes) {
812
- const linkPath = path.join(sharedDir, linkType);
813
- try {
814
- if (fs.existsSync(linkPath)) {
815
- const stats = fs.lstatSync(linkPath);
816
- if (stats.isSymbolicLink()) {
817
- const target = fs.readlinkSync(linkPath);
818
- // Check if it points to Claude config dir.
819
- const expectedTarget = path.join((0, claude_config_path_1.getClaudeConfigDir)(), linkType);
820
- if (path.resolve(path.dirname(linkPath), target) === path.resolve(expectedTarget)) {
821
- validLinks++;
822
- }
823
- }
824
- }
825
- }
826
- catch {
827
- // Not a symlink or read error
828
- }
829
- }
830
- if (validLinks === linkTypes.length) {
831
- return { valid: true, message: 'Symlinks active' };
832
- }
833
- else if (validLinks > 0) {
834
- return {
835
- valid: false,
836
- message: `Symlinks partially configured (${validLinks}/${linkTypes.length})`,
837
- };
838
- }
839
- return { valid: false, message: 'Symlinks not configured' };
840
- }
841
141
  //# sourceMappingURL=shared-routes.js.map