@khorsheed/dsh-capability-catalog 0.1.95

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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +286 -0
  4. package/README.zh.md +138 -0
  5. package/cordis.patch.yml +8 -0
  6. package/lib/client.js +10100 -0
  7. package/lib/index.js +4022 -0
  8. package/lib/invariant.js +25 -0
  9. package/lib/mcpConnector-C2TbkPBY.js +69 -0
  10. package/lib/typert.host.d.ts +3 -0
  11. package/lib/typert.host.js +1417 -0
  12. package/lib/typert.remote-client.d.ts +68 -0
  13. package/lib/typert.remote-client.js +1136 -0
  14. package/lib/types/capabilities.d.ts +103 -0
  15. package/lib/types/capabilities.js +116 -0
  16. package/lib/types/channels.d.ts +41 -0
  17. package/lib/types/channels.js +75 -0
  18. package/lib/types/client/AddMcpDialog.d.ts +13 -0
  19. package/lib/types/client/AddMcpDialog.js +79 -0
  20. package/lib/types/client/AddSkillModal.d.ts +17 -0
  21. package/lib/types/client/AddSkillModal.js +138 -0
  22. package/lib/types/client/BundleFileTree.d.ts +7 -0
  23. package/lib/types/client/BundleFileTree.js +74 -0
  24. package/lib/types/client/CapabilityCatalogCard.d.ts +57 -0
  25. package/lib/types/client/CapabilityCatalogCard.js +479 -0
  26. package/lib/types/client/CredentialField.d.ts +14 -0
  27. package/lib/types/client/CredentialField.js +9 -0
  28. package/lib/types/client/McpCards.d.ts +13 -0
  29. package/lib/types/client/McpCards.js +12 -0
  30. package/lib/types/client/McpServerManageModal.d.ts +18 -0
  31. package/lib/types/client/McpServerManageModal.js +63 -0
  32. package/lib/types/client/MetadataRow.d.ts +10 -0
  33. package/lib/types/client/MetadataRow.js +6 -0
  34. package/lib/types/client/ModalShell.d.ts +16 -0
  35. package/lib/types/client/ModalShell.js +27 -0
  36. package/lib/types/client/ModeChips.d.ts +38 -0
  37. package/lib/types/client/ModeChips.js +42 -0
  38. package/lib/types/client/SchemaView.d.ts +11 -0
  39. package/lib/types/client/SchemaView.js +143 -0
  40. package/lib/types/client/SkillCards.d.ts +28 -0
  41. package/lib/types/client/SkillCards.js +43 -0
  42. package/lib/types/client/SkillDetailModal.d.ts +69 -0
  43. package/lib/types/client/SkillDetailModal.js +167 -0
  44. package/lib/types/client/ToolCards.d.ts +28 -0
  45. package/lib/types/client/ToolCards.js +52 -0
  46. package/lib/types/client/ToolDetailModal.d.ts +12 -0
  47. package/lib/types/client/ToolDetailModal.js +29 -0
  48. package/lib/types/client/icons.d.ts +26 -0
  49. package/lib/types/client/icons.js +10 -0
  50. package/lib/types/client/index.d.ts +21 -0
  51. package/lib/types/client/index.js +196 -0
  52. package/lib/types/client/locales.d.ts +392 -0
  53. package/lib/types/client/locales.js +395 -0
  54. package/lib/types/client/mcp-model.d.ts +24 -0
  55. package/lib/types/client/mcp-model.js +87 -0
  56. package/lib/types/client/mode-model.d.ts +108 -0
  57. package/lib/types/client/mode-model.js +140 -0
  58. package/lib/types/client/preset-display.d.ts +21 -0
  59. package/lib/types/client/preset-display.js +20 -0
  60. package/lib/types/client/settle.d.ts +40 -0
  61. package/lib/types/client/settle.js +57 -0
  62. package/lib/types/client/slots.d.ts +86 -0
  63. package/lib/types/client/slots.js +1 -0
  64. package/lib/types/envHint.d.ts +22 -0
  65. package/lib/types/envHint.js +76 -0
  66. package/lib/types/import.d.ts +106 -0
  67. package/lib/types/import.js +635 -0
  68. package/lib/types/index.d.ts +216 -0
  69. package/lib/types/index.js +739 -0
  70. package/lib/types/invariant.d.ts +16 -0
  71. package/lib/types/invariant.js +23 -0
  72. package/lib/types/mcpConnector.d.ts +24 -0
  73. package/lib/types/mcpConnector.js +73 -0
  74. package/lib/types/mcpStore.d.ts +68 -0
  75. package/lib/types/mcpStore.js +235 -0
  76. package/lib/types/mcpTools.d.ts +45 -0
  77. package/lib/types/mcpTools.js +125 -0
  78. package/lib/types/mcps.d.ts +64 -0
  79. package/lib/types/mcps.js +157 -0
  80. package/lib/types/modes.d.ts +65 -0
  81. package/lib/types/modes.js +97 -0
  82. package/lib/types/namespace.d.ts +9 -0
  83. package/lib/types/namespace.js +8 -0
  84. package/lib/types/official-tools.d.ts +3 -0
  85. package/lib/types/official-tools.js +44 -0
  86. package/lib/types/preset-scope.d.ts +105 -0
  87. package/lib/types/preset-scope.js +117 -0
  88. package/lib/types/remote.d.ts +48 -0
  89. package/lib/types/remote.js +120 -0
  90. package/lib/types/scoped-delivery.d.ts +243 -0
  91. package/lib/types/scoped-delivery.js +686 -0
  92. package/lib/types/scoped-edits.d.ts +90 -0
  93. package/lib/types/scoped-edits.js +234 -0
  94. package/lib/types/settings.d.ts +28 -0
  95. package/lib/types/settings.js +28 -0
  96. package/lib/types/shellEnv.d.ts +65 -0
  97. package/lib/types/shellEnv.js +232 -0
  98. package/lib/types/skills.d.ts +103 -0
  99. package/lib/types/skills.js +303 -0
  100. package/lib/types/tool-origin.d.ts +29 -0
  101. package/lib/types/tool-origin.js +30 -0
  102. package/lib/types/types.d.ts +315 -0
  103. package/lib/types/types.js +8 -0
  104. package/lib/types/zip.d.ts +24 -0
  105. package/lib/types/zip.js +80 -0
  106. package/locale/en.json +6 -0
  107. package/locale/zh.json +6 -0
  108. package/package.json +119 -0
  109. package/scripts/gen-official-tools.mts +52 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Skill collection: project `ctx.skills.snapshot()` output into catalog rows,
3
+ * decode the frontmatter `metadata.credentials` declaration (the plugin's own
4
+ * convention), and load per-skill detail on demand via `ctx.skills.get()`.
5
+ *
6
+ * The registry already performs root discovery, de-duplication, and rank
7
+ * resolution; this module only projects the winning summaries onto wire rows
8
+ * and reads credentials state through the optional `ctx.credentials` service.
9
+ * @module @khorsheed/dsh-capability-catalog/skills
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { CapabilityCatalogSnapshot, CatalogCredentialDecl, CatalogSkillDetail, CatalogSkillFileRead, CatalogSkillRow } from './types.ts';
13
+ /** The slice of the credentials service this package reads (optional; ref space). */
14
+ export interface CredentialsSlice {
15
+ describe: (ref: unknown) => Promise<{
16
+ readonly configured?: boolean;
17
+ readonly source?: string;
18
+ readonly writable?: boolean;
19
+ } | undefined>;
20
+ set: (ref: unknown, value: string) => Promise<void>;
21
+ }
22
+ /** POSIX identifier — the minimal shape a credential reference name must match
23
+ * (the env keys the catalog handles are a strict subset of this). */
24
+ export declare const CREDENTIAL_REF_NAME: RegExp;
25
+ /** Skill `source` buckets that resolve to a local file the catalog may delete.
26
+ * Built-in / plugin-provided skills (`runtime`, `bundled`, official `skill-badge`)
27
+ * have no catalog-owned file and are never deletable. */
28
+ export declare const DELETABLE_SOURCES: ReadonlySet<string>;
29
+ /** Minimal SkillSummary shape (invocation-neutral). */
30
+ interface SkillSummaryLike {
31
+ readonly name: string;
32
+ readonly description: string;
33
+ readonly whenToUse?: string;
34
+ readonly invocation: {
35
+ readonly modelInvocable: boolean;
36
+ readonly userInvocable: boolean;
37
+ };
38
+ readonly source: string;
39
+ readonly provider: string;
40
+ readonly path?: string;
41
+ readonly resourceBase?: {
42
+ readonly kind: string;
43
+ readonly path?: string;
44
+ };
45
+ }
46
+ /** Minimal loaded SkillDefinition (adds content + metadata). */
47
+ export interface SkillDefinitionLike extends SkillSummaryLike {
48
+ readonly content: string;
49
+ readonly metadata?: Readonly<Record<string, unknown>>;
50
+ }
51
+ /** The slice of the skills service this package consumes (optional). */
52
+ export interface RegistrySlice {
53
+ snapshot: (options?: {
54
+ cwd?: string;
55
+ scope?: unknown;
56
+ signal?: AbortSignal;
57
+ }) => Promise<{
58
+ readonly skills: readonly SkillSummaryLike[];
59
+ readonly complete: boolean;
60
+ }>;
61
+ get: (name: string, options?: {
62
+ cwd?: string;
63
+ scope?: unknown;
64
+ signal?: AbortSignal;
65
+ }) => Promise<SkillDefinitionLike | undefined>;
66
+ }
67
+ /** Decode `metadata.credentials` as an array of { key, label? }. */
68
+ export declare function decodeCredentialDecls(metadata: Readonly<Record<string, unknown>> | undefined): readonly CatalogCredentialDecl[];
69
+ /** Scan a skill body for env-var references (`$X`, `process.env.X`, `env['X']`,
70
+ * `{{env:X}}`) and surface them as generic credential decls — envs are not
71
+ * assumed to be API keys; they may be tokens, ids, or any secret. */
72
+ export declare function decodeEnvDecls(content: string): readonly CatalogCredentialDecl[];
73
+ /** Merge metadata.credentials and env-derived decls, deduped by key. */
74
+ export declare function mergeCredentialDecls(metadataDecls: readonly CatalogCredentialDecl[], envDecls: readonly CatalogCredentialDecl[]): readonly CatalogCredentialDecl[];
75
+ /** Project one summary onto a wire skill row. */
76
+ export declare function skillRowFrom(summary: SkillSummaryLike, updatedAt?: number, bodySha?: string): CatalogSkillRow;
77
+ /** Resolve the optional services on a context. */
78
+ export declare function resolveServices(ctx: Context): {
79
+ registry: RegistrySlice | undefined;
80
+ credentials: CredentialsSlice | undefined;
81
+ };
82
+ /**
83
+ * Collect the skills portion of the catalog across the given view scopes.
84
+ *
85
+ * `bodySha` costs one registry load per skill, so it is opt-in: the browser
86
+ * card wants a listing, and the capability fingerprint wants the bodies.
87
+ */
88
+ export declare function collectSkills(registry: RegistrySlice, workdir: string | undefined, scopes?: readonly unknown[], collectOptions?: {
89
+ bodySha?: boolean;
90
+ }): Promise<CapabilityCatalogSnapshot>;
91
+ /** Read one skill-bundle file's text content, on demand. */
92
+ export declare function readSkillFileContent(registry: RegistrySlice, name: string, filePath: string, workdir: string | undefined, scope?: unknown, fallback?: SkillDefinitionLike): Promise<CatalogSkillFileRead | undefined>;
93
+ /** Delete a file skill's directory. Returns an error for built-in / plugin-provided
94
+ * skills (`runtime`, `bundled`, official `skill-badge`) that have no catalog-owned
95
+ * file, and for any skill whose bundle directory cannot be resolved. */
96
+ export declare function deleteSkillDir(def: SkillDefinitionLike): Promise<{
97
+ ok: boolean;
98
+ error?: string;
99
+ }>;
100
+ /** Load one skill's full detail (content + metadata + credentials state). */
101
+ export declare function loadSkillDetail(ctx: Context, registry: RegistrySlice, name: string, workdir: string | undefined, scope?: unknown, fallback?: SkillDefinitionLike): Promise<CatalogSkillDetail | undefined>;
102
+ export {};
103
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Skill collection: project `ctx.skills.snapshot()` output into catalog rows,
3
+ * decode the frontmatter `metadata.credentials` declaration (the plugin's own
4
+ * convention), and load per-skill detail on demand via `ctx.skills.get()`.
5
+ *
6
+ * The registry already performs root discovery, de-duplication, and rank
7
+ * resolution; this module only projects the winning summaries onto wire rows
8
+ * and reads credentials state through the optional `ctx.credentials` service.
9
+ * @module @khorsheed/dsh-capability-catalog/skills
10
+ */
11
+ import { readFile, readdir, rm, stat } from 'node:fs/promises';
12
+ import { join, relative } from 'node:path';
13
+ import { hashSkillBody } from './capabilities.js';
14
+ /** POSIX identifier — the minimal shape a credential reference name must match
15
+ * (the env keys the catalog handles are a strict subset of this). */
16
+ export const CREDENTIAL_REF_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
17
+ /** Skill `source` buckets that resolve to a local file the catalog may delete.
18
+ * Built-in / plugin-provided skills (`runtime`, `bundled`, official `skill-badge`)
19
+ * have no catalog-owned file and are never deletable. */
20
+ export const DELETABLE_SOURCES = new Set([
21
+ 'user-dsh', 'user-agents', 'project-dsh', 'project-agents', 'custom',
22
+ ]);
23
+ /** Decode `metadata.credentials` as an array of { key, label? }. */
24
+ export function decodeCredentialDecls(metadata) {
25
+ const raw = metadata?.credentials;
26
+ if (!Array.isArray(raw))
27
+ return [];
28
+ const out = [];
29
+ for (const item of raw) {
30
+ if (typeof item !== 'object' || item === null)
31
+ continue;
32
+ const key = item.key;
33
+ if (typeof key !== 'string' || key.length === 0)
34
+ continue;
35
+ const label = item.label;
36
+ out.push({ key, ...(typeof label === 'string' ? { label } : {}) });
37
+ }
38
+ return out;
39
+ }
40
+ /** Scan a skill body for env-var references (`$X`, `process.env.X`, `env['X']`,
41
+ * `{{env:X}}`) and surface them as generic credential decls — envs are not
42
+ * assumed to be API keys; they may be tokens, ids, or any secret. */
43
+ export function decodeEnvDecls(content) {
44
+ const seen = new Set();
45
+ const out = [];
46
+ const add = (raw) => {
47
+ if (raw === undefined)
48
+ return;
49
+ const key = raw.replace(/^\$\{?/, '').replace(/\}?$/, '').trim();
50
+ if (!/^[A-Z][A-Z0-9_]*$/.test(key) || seen.has(key))
51
+ return;
52
+ seen.add(key);
53
+ out.push({ key });
54
+ };
55
+ const patterns = [
56
+ /\$\{?([A-Z][A-Z0-9_]*)\}?/g,
57
+ /process\.env\.([A-Z][A-Z0-9_]*)/g,
58
+ /env\[\s*['"]?([A-Z][A-Z0-9_]+)['"]?\s*\]/g,
59
+ /\{\{\s*env:([A-Z][A-Z0-9_]*)\s*\}\}/g,
60
+ ];
61
+ for (const p of patterns) {
62
+ let m;
63
+ while ((m = p.exec(content)) !== null)
64
+ add(m[1]);
65
+ }
66
+ return out;
67
+ }
68
+ /** Merge metadata.credentials and env-derived decls, deduped by key. */
69
+ export function mergeCredentialDecls(metadataDecls, envDecls) {
70
+ const byKey = new Map();
71
+ for (const decl of [...metadataDecls, ...envDecls]) {
72
+ const existing = byKey.get(decl.key);
73
+ byKey.set(decl.key, existing !== undefined && existing.label !== undefined ? existing : decl);
74
+ }
75
+ return [...byKey.values()];
76
+ }
77
+ /** Project one summary onto a wire skill row. */
78
+ export function skillRowFrom(summary, updatedAt, bodySha) {
79
+ return {
80
+ name: summary.name,
81
+ description: summary.description,
82
+ source: summary.source,
83
+ provider: summary.provider,
84
+ modelInvocable: summary.invocation.modelInvocable,
85
+ userInvocable: summary.invocation.userInvocable,
86
+ ...summary.whenToUse !== undefined ? { whenToUse: summary.whenToUse } : {},
87
+ ...updatedAt !== undefined ? { updatedAt } : {},
88
+ ...bodySha !== undefined ? { bodySha } : {},
89
+ };
90
+ }
91
+ /** Last-modified time (epoch ms) of a skill's body: the SKILL.md in its bundle
92
+ * directory for a directory skill, else the directory itself. Remote
93
+ * (`url`/`opaque`) skills have no local file and yield `undefined`. */
94
+ async function skillUpdatedAt(summary) {
95
+ const base = summary.resourceBase;
96
+ if (base?.kind !== 'directory' || base.path === undefined)
97
+ return undefined;
98
+ try {
99
+ const skillMd = await stat(join(base.path, 'SKILL.md')).catch(() => undefined);
100
+ return (skillMd ?? await stat(base.path)).mtimeMs;
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
106
+ /** Resolve the optional services on a context. */
107
+ export function resolveServices(ctx) {
108
+ const registry = ctx.get?.('skills');
109
+ const credentials = ctx.get?.('credentials');
110
+ return { registry, credentials };
111
+ }
112
+ /**
113
+ * sha256 of one skill's body, loaded through the registry so the digest is
114
+ * taken over what the MODEL would be handed rather than over whichever file
115
+ * happens to sit in the bundle directory. A skill the registry declines to
116
+ * load (remote, opaque, or raced away) contributes no digest — the
117
+ * fingerprint records `null` for it rather than guessing.
118
+ */
119
+ async function skillBodySha(registry, name, options) {
120
+ try {
121
+ const definition = await registry.get(name, options);
122
+ if (typeof definition?.content !== 'string')
123
+ return undefined;
124
+ return hashSkillBody(definition.content);
125
+ }
126
+ catch {
127
+ return undefined;
128
+ }
129
+ }
130
+ /**
131
+ * Collect the skills portion of the catalog across the given view scopes.
132
+ *
133
+ * `bodySha` costs one registry load per skill, so it is opt-in: the browser
134
+ * card wants a listing, and the capability fingerprint wants the bodies.
135
+ */
136
+ export async function collectSkills(registry, workdir, scopes = [undefined], collectOptions = {}) {
137
+ const byName = new Map();
138
+ for (const scope of scopes) {
139
+ const base = workdir === undefined ? {} : { cwd: workdir };
140
+ const options = scope === undefined ? base : { ...base, scope };
141
+ let snapshot;
142
+ try {
143
+ snapshot = await registry.snapshot(options);
144
+ }
145
+ catch {
146
+ // A single scope (e.g. one agent preset) failing must not blank the whole
147
+ // catalog; skip it and keep the rest.
148
+ continue;
149
+ }
150
+ for (const summary of snapshot.skills) {
151
+ if (byName.has(summary.name))
152
+ continue;
153
+ const bodySha = collectOptions.bodySha === true
154
+ ? await skillBodySha(registry, summary.name, options)
155
+ : undefined;
156
+ const row = skillRowFrom(summary, await skillUpdatedAt(summary), bodySha);
157
+ byName.set(row.name, row);
158
+ }
159
+ }
160
+ const skills = [...byName.values()];
161
+ return {
162
+ skills,
163
+ tools: [],
164
+ mcpServers: [],
165
+ channels: [{ channel: 'skill', count: skills.length }],
166
+ };
167
+ }
168
+ /** Directory names never listed as skill-bundle resources. */
169
+ const SKIP_SKILL_DIRS = new Set(['.git', 'node_modules']);
170
+ /** Recursively collect a directory's file paths relative to `root`. */
171
+ async function collectSkillFiles(dir, root, out, depth) {
172
+ if (depth > 16 || out.length > 1000)
173
+ return;
174
+ let entries;
175
+ try {
176
+ entries = await readdir(dir, { withFileTypes: true });
177
+ }
178
+ catch {
179
+ return;
180
+ }
181
+ for (const entry of entries) {
182
+ const full = join(dir, entry.name);
183
+ if (entry.isDirectory()) {
184
+ if (SKIP_SKILL_DIRS.has(entry.name))
185
+ continue;
186
+ await collectSkillFiles(full, root, out, depth + 1);
187
+ }
188
+ else if (entry.isFile()) {
189
+ out.push(relative(root, full));
190
+ }
191
+ }
192
+ }
193
+ /** Reject unsafe bundle-relative paths (absolute, drive-letter, or `..`). */
194
+ function sanitizeBundlePath(path) {
195
+ if (path === '' || path.startsWith('/') || /^[a-zA-Z]:/.test(path))
196
+ return undefined;
197
+ const safe = [];
198
+ for (const segment of path.split('/')) {
199
+ if (segment === '' || segment === '.')
200
+ continue;
201
+ if (segment === '..')
202
+ return undefined;
203
+ safe.push(segment);
204
+ }
205
+ return safe.length === 0 ? undefined : safe.join('/');
206
+ }
207
+ /** Read one skill-bundle file's text content, on demand. */
208
+ export async function readSkillFileContent(registry, name, filePath, workdir, scope = undefined, fallback) {
209
+ const base = workdir === undefined ? {} : { cwd: workdir };
210
+ const lookup = scope === undefined ? base : { ...base, scope };
211
+ const def = (await registry.get(name, lookup)) ?? fallback;
212
+ if (def === undefined)
213
+ return undefined;
214
+ const resourceBase = def.resourceBase;
215
+ const bundleDir = resourceBase?.kind === 'directory' ? resourceBase.path : undefined;
216
+ if (bundleDir === undefined)
217
+ return undefined;
218
+ const safe = sanitizeBundlePath(filePath);
219
+ if (safe === undefined)
220
+ return undefined;
221
+ const full = join(bundleDir, safe);
222
+ if (!full.startsWith(bundleDir))
223
+ return undefined;
224
+ try {
225
+ const buf = await readFile(full);
226
+ return { content: buf.toString('utf8') };
227
+ }
228
+ catch {
229
+ return undefined;
230
+ }
231
+ }
232
+ /** Delete a file skill's directory. Returns an error for built-in / plugin-provided
233
+ * skills (`runtime`, `bundled`, official `skill-badge`) that have no catalog-owned
234
+ * file, and for any skill whose bundle directory cannot be resolved. */
235
+ export async function deleteSkillDir(def) {
236
+ if (!DELETABLE_SOURCES.has(def.source))
237
+ return { ok: false, error: 'not a deletable file skill' };
238
+ const dir = def.resourceBase?.kind === 'directory' ? def.resourceBase.path : undefined;
239
+ if (dir === undefined)
240
+ return { ok: false, error: 'skill has no bundle directory' };
241
+ try {
242
+ await stat(dir);
243
+ await rm(dir, { recursive: true, force: true });
244
+ return { ok: true };
245
+ }
246
+ catch (error) {
247
+ return { ok: false, error: String(error) };
248
+ }
249
+ }
250
+ /** Load one skill's full detail (content + metadata + credentials state). */
251
+ export async function loadSkillDetail(ctx, registry, name, workdir, scope = undefined, fallback) {
252
+ const base = workdir === undefined ? {} : { cwd: workdir };
253
+ const lookup = scope === undefined ? base : { ...base, scope };
254
+ // The registry read is scope-limited on purpose; a managed skill scoped to
255
+ // another preset falls back to the plugin's own copy so the catalog can still
256
+ // show and edit it.
257
+ const def = (await registry.get(name, lookup)) ?? fallback;
258
+ if (def === undefined)
259
+ return undefined;
260
+ // metadata.credentials are the user-configurable secrets; env-var references
261
+ // detected in the body are the agent's own runtime inputs (DSH_HOME etc.) and
262
+ // must NOT be offered as configurable credential forms — keep them apart.
263
+ const metadataDecls = decodeCredentialDecls(def.metadata);
264
+ const environmentRefs = decodeEnvDecls(def.content).map((decl) => decl.key);
265
+ const { credentials } = resolveServices(ctx);
266
+ let credentialStates = [];
267
+ if (metadataDecls.length > 0 && credentials !== undefined) {
268
+ credentialStates = await Promise.all(metadataDecls.map(async (decl) => {
269
+ const configured = CREDENTIAL_REF_NAME.test(decl.key)
270
+ ? (await credentials.describe(decl.key).catch(() => undefined))?.configured === true
271
+ : false;
272
+ return {
273
+ key: decl.key,
274
+ ...decl.label !== undefined ? { label: decl.label } : {},
275
+ configured,
276
+ };
277
+ }));
278
+ }
279
+ // Bundle-relative file list for a directory skill (SKILL.md + scripts/assets).
280
+ const resourceBase = def.resourceBase;
281
+ const bundleDir = resourceBase?.kind === 'directory' ? resourceBase.path : undefined;
282
+ let files = [];
283
+ if (bundleDir !== undefined) {
284
+ const collected = [];
285
+ await collectSkillFiles(bundleDir, bundleDir, collected, 0);
286
+ files = collected.sort();
287
+ }
288
+ return {
289
+ name: def.name,
290
+ description: def.description,
291
+ source: def.source,
292
+ provider: def.provider,
293
+ modelInvocable: def.invocation.modelInvocable,
294
+ userInvocable: def.invocation.userInvocable,
295
+ ...def.path !== undefined ? { path: def.path } : {},
296
+ ...def.whenToUse !== undefined ? { whenToUse: def.whenToUse } : {},
297
+ content: def.content,
298
+ ...def.metadata !== undefined ? { metadataText: JSON.stringify(def.metadata) } : {},
299
+ ...credentialStates.length > 0 ? { credentials: credentialStates } : {},
300
+ ...environmentRefs.length > 0 ? { environmentRefs } : {},
301
+ ...files.length > 0 ? { files } : {},
302
+ };
303
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Community tool-origin convention: a host-side tag a plugin attaches to a tool
3
+ * definition BEFORE `ctx.tools.register`, so the catalog can attribute the tool
4
+ * to `plugin` (and any channel) instead of relying on heuristics.
5
+ *
6
+ * The harness `ToolSchema` carries no source/owner field and the system-prompt
7
+ * assembly projects tools to `{ name, description, parameters }`, so the tag must
8
+ * live on the retained `ToolDefinition` and be read via `ctx.tools.get(name)`
9
+ * (the registry keeps the definition by reference, so the symbol survives).
10
+ * The key is `Symbol.for('dsh.tool.origin')` (global symbol registry) — a plugin
11
+ * may tag without importing anything, using `def[Symbol.for('dsh.tool.origin')]`.
12
+ * `setToolOrigin` is the convenience form exported for the catalog's own plugins.
13
+ * @module @khorsheed/dsh-capability-catalog/tool-origin
14
+ */
15
+ /** The global origin-tag key. `Symbol.for` returns the same symbol everywhere. */
16
+ export declare const TOOL_ORIGIN: unique symbol;
17
+ /** The origin a tool author declares: which channel it belongs to (often 'plugin'). */
18
+ export interface ToolOrigin {
19
+ readonly channel: 'plugin' | 'builtin' | 'mcp';
20
+ /** The declaring package/module, e.g. '@khorsheed/dsh-my-plugin'. */
21
+ readonly owner?: string;
22
+ }
23
+ /** Convenience: attach an origin tag to a tool definition (before `register`). */
24
+ export declare function setToolOrigin<T extends object>(definition: T, origin: ToolOrigin): T;
25
+ /** Read the origin tag off a tool definition (host-side; never on the model wire). */
26
+ export declare function toolOrigin(definition: object): ToolOrigin | undefined;
27
+ /** Whether an origin's `channel` is one the catalog can honor (plugin/builtin/mcp). */
28
+ export declare function isValidOrigin(origin: ToolOrigin): boolean;
29
+ //# sourceMappingURL=tool-origin.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Community tool-origin convention: a host-side tag a plugin attaches to a tool
3
+ * definition BEFORE `ctx.tools.register`, so the catalog can attribute the tool
4
+ * to `plugin` (and any channel) instead of relying on heuristics.
5
+ *
6
+ * The harness `ToolSchema` carries no source/owner field and the system-prompt
7
+ * assembly projects tools to `{ name, description, parameters }`, so the tag must
8
+ * live on the retained `ToolDefinition` and be read via `ctx.tools.get(name)`
9
+ * (the registry keeps the definition by reference, so the symbol survives).
10
+ * The key is `Symbol.for('dsh.tool.origin')` (global symbol registry) — a plugin
11
+ * may tag without importing anything, using `def[Symbol.for('dsh.tool.origin')]`.
12
+ * `setToolOrigin` is the convenience form exported for the catalog's own plugins.
13
+ * @module @khorsheed/dsh-capability-catalog/tool-origin
14
+ */
15
+ /** The global origin-tag key. `Symbol.for` returns the same symbol everywhere. */
16
+ export const TOOL_ORIGIN = Symbol.for('dsh.tool.origin');
17
+ /** Convenience: attach an origin tag to a tool definition (before `register`). */
18
+ export function setToolOrigin(definition, origin) {
19
+ ;
20
+ definition[TOOL_ORIGIN] = origin;
21
+ return definition;
22
+ }
23
+ /** Read the origin tag off a tool definition (host-side; never on the model wire). */
24
+ export function toolOrigin(definition) {
25
+ return definition[TOOL_ORIGIN];
26
+ }
27
+ /** Whether an origin's `channel` is one the catalog can honor (plugin/builtin/mcp). */
28
+ export function isValidOrigin(origin) {
29
+ return origin?.channel === 'plugin' || origin?.channel === 'builtin' || origin?.channel === 'mcp';
30
+ }