showdar-skills 0.2.1 → 0.2.2
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/bin/showdar.js +10 -0
- package/package.json +1 -1
- package/src/project.js +140 -22
package/bin/showdar.js
CHANGED
|
@@ -49,6 +49,10 @@ async function main() {
|
|
|
49
49
|
const version = await packageVersion();
|
|
50
50
|
|
|
51
51
|
if (command === 'help' || command === '--help' || command === '-h') return printHelp(version);
|
|
52
|
+
if (command === '--version' || command === '-V') {
|
|
53
|
+
console.log(version);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
52
56
|
if (args.includes('--help') || args.includes('-h')) return printHelp(version, command);
|
|
53
57
|
|
|
54
58
|
const scope = ['init', 'status', 'doctor', 'remove'].includes(command) ? scopeAfter(args) : null;
|
|
@@ -83,6 +87,10 @@ async function main() {
|
|
|
83
87
|
? await initGlobal({ homeRoot: homedir(), packageRoot, profile, ai, skillIds, commandNames, packageVersion: version })
|
|
84
88
|
: await initProject({ projectRoot, packageRoot, profile, ai, skillIds, commandNames, packageVersion: version });
|
|
85
89
|
console.log(`Showdar Skills installed.\nScope: ${scope}\nProfile: ${profile}\nAI: ${ai}\nTargets: ${result.targets.join(', ')}\nSkills: ${result.skills}\nOpenCode commands: ${result.commands}`);
|
|
90
|
+
if (scope === 'project') {
|
|
91
|
+
console.log(`Requested: ${result.requestedSkills}\nInstalled in project: ${result.installedSkills}\nSatisfied by global: ${result.satisfiedByGlobal}\nSkipped duplicate copies: ${result.skippedDuplicates}`);
|
|
92
|
+
}
|
|
93
|
+
for (const warning of result.warnings ?? []) console.log(`warning: ${warning}`);
|
|
86
94
|
if (scope === 'global') console.log(`Manifest: ${globalManifestPath()}`);
|
|
87
95
|
if (result.targets.includes('codex')) console.log('Codex: invoke skills directly with $showdar-<name> or let native skill discovery route by description.');
|
|
88
96
|
if (result.targets.includes('opencode')) console.log('OpenCode: use native skill discovery or /showdar/<command>.');
|
|
@@ -97,7 +105,9 @@ async function main() {
|
|
|
97
105
|
return;
|
|
98
106
|
}
|
|
99
107
|
console.log(`Showdar Skills\nScope: ${result.scope}\nProfile: ${result.profile}\nAI: ${result.ai}\nTargets: ${result.targets.join(', ')}\nSkills: ${result.skills}\nCommands: ${result.commands}\nHealth: ${result.healthy ? 'OK' : 'BROKEN'}`);
|
|
108
|
+
if (scope === 'project') console.log(`Requested: ${result.requestedSkills}\nInstalled in project: ${result.installedSkills}\nSatisfied by global: ${result.satisfiedByGlobal}`);
|
|
100
109
|
for (const issue of result.issues) console.log(`- ${issue}`);
|
|
110
|
+
for (const warning of result.warnings ?? []) console.log(`warning: ${warning}`);
|
|
101
111
|
if (command === 'doctor' && !result.healthy) process.exitCode = 1;
|
|
102
112
|
return;
|
|
103
113
|
}
|
package/package.json
CHANGED
package/src/project.js
CHANGED
|
@@ -100,6 +100,15 @@ function ownedPathSet(manifest) {
|
|
|
100
100
|
return new Set((manifest?.files ?? []).map((entry) => entry.path));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function uniqueRoots(targets, resolveRoot) {
|
|
104
|
+
const roots = new Map();
|
|
105
|
+
for (const target of targets) {
|
|
106
|
+
const root = resolveRoot(target);
|
|
107
|
+
if (!roots.has(path.resolve(root))) roots.set(path.resolve(root), { root, target });
|
|
108
|
+
}
|
|
109
|
+
return [...roots.values()];
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
function safeOwnedPath(baseRoot, relative, allowedRoots = []) {
|
|
104
113
|
if (typeof relative !== 'string' || !relative) return null;
|
|
105
114
|
const roots = [baseRoot, ...allowedRoots].map((root) => path.resolve(root));
|
|
@@ -124,20 +133,45 @@ async function copyOwned({ baseRoot, source, destination, priorOwned, newFiles }
|
|
|
124
133
|
newFiles.push({ path: relative, hash: await hashTree(destination) });
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
async function initInstallation({ baseRoot, manifestPath, agentsRoot, packageRoot, profile, ai, skillIds, commandNames = [], packageVersion = '0.2.0', scope, skillRootForTarget, commandRoot, managedRoots = [] }) {
|
|
136
|
+
async function initInstallation({ baseRoot, manifestPath, agentsRoot, packageRoot, profile, ai, skillIds, commandNames = [], packageVersion = '0.2.0', scope, skillRootForTarget, commandRoot, managedRoots = [], homeRoot = homedir(), globalSkillRootForTarget = null }) {
|
|
128
137
|
await mkdir(baseRoot, { recursive: true });
|
|
129
138
|
const targets = resolveTargets(ai);
|
|
130
139
|
const prior = await readManifest(manifestPath);
|
|
131
140
|
const priorOwned = ownedPathSet(prior);
|
|
132
141
|
const desiredPaths = new Set();
|
|
133
142
|
|
|
134
|
-
const skillRoots =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
const skillRoots = uniqueRoots(targets, skillRootForTarget);
|
|
144
|
+
const globalManifest = scope === 'project' && globalSkillRootForTarget
|
|
145
|
+
? await readManifest(globalManifestPath(homeRoot))
|
|
146
|
+
: null;
|
|
147
|
+
const globalOwned = ownedPathSet(globalManifest);
|
|
148
|
+
const globalSatisfaction = [];
|
|
149
|
+
const installedSkillIds = new Set();
|
|
150
|
+
const globallySatisfiedSkillIds = new Set();
|
|
151
|
+
let skippedDuplicates = 0;
|
|
152
|
+
const skillDestinations = [];
|
|
153
|
+
|
|
154
|
+
for (const skillId of skillIds) {
|
|
155
|
+
if (!(await exists(path.join(packageRoot, 'skills', skillId, 'SKILL.md')))) throw new Error(`Skill asset not found: ${skillId}`);
|
|
138
156
|
}
|
|
139
|
-
|
|
140
|
-
|
|
157
|
+
|
|
158
|
+
for (const { root, target } of skillRoots) {
|
|
159
|
+
for (const skillId of skillIds) {
|
|
160
|
+
const destination = path.join(root, skillId);
|
|
161
|
+
const relative = manifestPathFor(baseRoot, destination);
|
|
162
|
+
const destinationExists = await exists(destination);
|
|
163
|
+
const globalPath = globalSkillRootForTarget ? path.join(globalSkillRootForTarget(target), skillId) : null;
|
|
164
|
+
const globalRelative = globalPath ? manifestPathFor(homeRoot, globalPath) : null;
|
|
165
|
+
const globalAvailable = Boolean(globalPath && globalOwned.has(globalRelative) && await exists(globalPath));
|
|
166
|
+
if (scope === 'project' && globalAvailable && !destinationExists) {
|
|
167
|
+
skippedDuplicates += 1;
|
|
168
|
+
globallySatisfiedSkillIds.add(skillId);
|
|
169
|
+
globalSatisfaction.push({ skill: skillId, target, path: globalRelative });
|
|
170
|
+
} else {
|
|
171
|
+
desiredPaths.add(relative);
|
|
172
|
+
skillDestinations.push({ destination, skillId });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
141
175
|
}
|
|
142
176
|
if (targets.includes('opencode')) {
|
|
143
177
|
for (const name of commandNames) desiredPaths.add(manifestPathFor(baseRoot, path.join(commandRoot(), `${name}.md`)));
|
|
@@ -149,12 +183,10 @@ async function initInstallation({ baseRoot, manifestPath, agentsRoot, packageRoo
|
|
|
149
183
|
}
|
|
150
184
|
|
|
151
185
|
const files = [];
|
|
152
|
-
for (const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
await copyOwned({ baseRoot, source, destination: path.join(root, skillId), priorOwned, newFiles: files });
|
|
157
|
-
}
|
|
186
|
+
for (const { destination, skillId } of skillDestinations) {
|
|
187
|
+
const source = path.join(packageRoot, 'skills', skillId);
|
|
188
|
+
await copyOwned({ baseRoot, source, destination, priorOwned, newFiles: files });
|
|
189
|
+
installedSkillIds.add(skillId);
|
|
158
190
|
}
|
|
159
191
|
|
|
160
192
|
if (targets.includes('opencode')) {
|
|
@@ -174,16 +206,27 @@ async function initInstallation({ baseRoot, manifestPath, agentsRoot, packageRoo
|
|
|
174
206
|
ai,
|
|
175
207
|
targets,
|
|
176
208
|
skills: [...skillIds],
|
|
209
|
+
satisfiedByGlobal: globalSatisfaction,
|
|
177
210
|
commands: targets.includes('opencode') ? [...commandNames] : [],
|
|
178
211
|
files,
|
|
179
212
|
};
|
|
180
213
|
await mkdir(path.dirname(manifestPath), { recursive: true });
|
|
181
214
|
await writeJsonAtomic(manifestPath, manifest);
|
|
182
215
|
if (agentsRoot) await writeAgentsBlock(agentsRoot, skillIds);
|
|
183
|
-
|
|
216
|
+
const result = await inspectInstallation({
|
|
217
|
+
baseRoot, manifestPath, agentsRoot, scope, homeRoot,
|
|
218
|
+
globalSkillRootForTarget,
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
...result,
|
|
222
|
+
requestedSkills: skillIds.length,
|
|
223
|
+
installedSkills: installedSkillIds.size,
|
|
224
|
+
satisfiedByGlobal: globallySatisfiedSkillIds.size,
|
|
225
|
+
skippedDuplicates,
|
|
226
|
+
};
|
|
184
227
|
}
|
|
185
228
|
|
|
186
|
-
export async function initProject({ projectRoot, packageRoot, profile, ai, skillIds, commandNames = [], packageVersion = '0.2.0' }) {
|
|
229
|
+
export async function initProject({ projectRoot, homeRoot = homedir(), packageRoot, profile, ai, skillIds, commandNames = [], packageVersion = '0.2.0' }) {
|
|
187
230
|
return initInstallation({
|
|
188
231
|
baseRoot: projectRoot,
|
|
189
232
|
manifestPath: path.join(projectRoot, PROJECT_MANIFEST),
|
|
@@ -195,7 +238,9 @@ export async function initProject({ projectRoot, packageRoot, profile, ai, skill
|
|
|
195
238
|
commandNames,
|
|
196
239
|
packageVersion,
|
|
197
240
|
scope: 'project',
|
|
241
|
+
homeRoot,
|
|
198
242
|
skillRootForTarget: (target) => skillRootFor(target, projectRoot),
|
|
243
|
+
globalSkillRootForTarget: (target) => globalSkillRootFor(target, { homeRoot }),
|
|
199
244
|
commandRoot: () => opencodeCommandRoot(projectRoot),
|
|
200
245
|
});
|
|
201
246
|
}
|
|
@@ -219,17 +264,79 @@ export async function initGlobal({ homeRoot = homedir(), packageRoot, profile, a
|
|
|
219
264
|
});
|
|
220
265
|
}
|
|
221
266
|
|
|
222
|
-
async function inspectInstallation({ baseRoot, manifestPath, agentsRoot, scope, managedRoots = [] }) {
|
|
267
|
+
async function inspectInstallation({ baseRoot, manifestPath, agentsRoot, scope, managedRoots = [], homeRoot = homedir(), globalSkillRootForTarget = null }) {
|
|
223
268
|
let manifest;
|
|
224
269
|
try { manifest = await readManifest(manifestPath); }
|
|
225
|
-
catch (error) { return { installed: true, healthy: false, scope, profile: null, ai: null, targets: [], skills: 0, commands: 0, issues: [error.message] }; }
|
|
226
|
-
if (!manifest) return { installed: false, healthy: false, scope, profile: null, ai: null, targets: [], skills: 0, commands: 0, issues: ['Showdar is not installed.'] };
|
|
270
|
+
catch (error) { return { installed: true, healthy: false, scope, profile: null, ai: null, targets: [], skills: 0, requestedSkills: 0, installedSkills: 0, satisfiedByGlobal: 0, commands: 0, issues: [error.message], warnings: [] }; }
|
|
271
|
+
if (!manifest) return { installed: false, healthy: false, scope, profile: null, ai: null, targets: [], skills: 0, requestedSkills: 0, installedSkills: 0, satisfiedByGlobal: 0, commands: 0, issues: ['Showdar is not installed.'], warnings: [] };
|
|
227
272
|
|
|
228
273
|
const issues = [];
|
|
274
|
+
const warnings = [];
|
|
275
|
+
const projectOwned = ownedPathSet(manifest);
|
|
276
|
+
const targets = manifest.targets?.length ? manifest.targets : manifest.ai ? resolveTargets(manifest.ai) : [];
|
|
277
|
+
const skillIds = manifest.skills ?? [];
|
|
278
|
+
const globalManifest = scope === 'project' && globalSkillRootForTarget
|
|
279
|
+
? await readManifest(globalManifestPath(homeRoot))
|
|
280
|
+
: null;
|
|
281
|
+
const globalOwned = ownedPathSet(globalManifest);
|
|
282
|
+
const globalSatisfiedPaths = new Set();
|
|
283
|
+
const installedSkillIds = new Set();
|
|
284
|
+
const globallySatisfiedSkillIds = new Set();
|
|
285
|
+
const recordedGlobalSkills = new Set((manifest.satisfiedByGlobal ?? []).map((entry) => entry?.skill).filter(Boolean));
|
|
286
|
+
|
|
287
|
+
if (scope === 'project' && globalSkillRootForTarget) {
|
|
288
|
+
const projectRoots = uniqueRoots(targets, (target) => skillRootFor(target, baseRoot));
|
|
289
|
+
const globalRoots = uniqueRoots(targets, globalSkillRootForTarget);
|
|
290
|
+
const globalSkills = new Set();
|
|
291
|
+
for (const { root } of globalRoots) {
|
|
292
|
+
const prefix = `${manifestPathFor(homeRoot, root)}/`;
|
|
293
|
+
for (const entry of globalManifest?.files ?? []) {
|
|
294
|
+
if (!entry.path.startsWith(prefix)) continue;
|
|
295
|
+
const skillId = entry.path.slice(prefix.length).split('/')[0];
|
|
296
|
+
if (!skillId.startsWith('showdar-')) continue;
|
|
297
|
+
if (globalOwned.has(entry.path) && await exists(path.join(homeRoot, entry.path))) globalSkills.add(skillId);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const extra = [...globalSkills].filter((skillId) => !skillIds.includes(skillId)).sort();
|
|
301
|
+
if (extra.length) warnings.push(`Global Showdar installation exposes skills outside project profile "${manifest.profile ?? 'unknown'}": ${extra.join(', ')}. Project deduplication prevents duplicate copies but cannot hide globally installed skills. For strict project profile isolation: showdar remove --scope global`);
|
|
302
|
+
|
|
303
|
+
for (const { root, target } of projectRoots) {
|
|
304
|
+
for (const skillId of skillIds) {
|
|
305
|
+
const projectPath = path.join(root, skillId);
|
|
306
|
+
const projectRelative = manifestPathFor(baseRoot, projectPath);
|
|
307
|
+
const projectExists = await exists(projectPath);
|
|
308
|
+
const projectIsOwned = projectOwned.has(projectRelative);
|
|
309
|
+
const globalPath = path.join(globalSkillRootForTarget(target), skillId);
|
|
310
|
+
const globalRelative = manifestPathFor(homeRoot, globalPath);
|
|
311
|
+
const globalExists = await exists(globalPath);
|
|
312
|
+
const globalIsOwned = globalOwned.has(globalRelative);
|
|
313
|
+
|
|
314
|
+
if (projectExists && projectIsOwned) installedSkillIds.add(skillId);
|
|
315
|
+
if (projectExists && projectIsOwned && globalExists && globalIsOwned) {
|
|
316
|
+
warnings.push(`Duplicate Showdar skill discovery:\n ${skillId}\n project: ${projectPath}\n global: ${globalPath}\n To prefer project isolation: showdar remove --scope global`);
|
|
317
|
+
} else if (!projectExists && globalExists && globalIsOwned) {
|
|
318
|
+
globalSatisfiedPaths.add(projectRelative);
|
|
319
|
+
globallySatisfiedSkillIds.add(skillId);
|
|
320
|
+
} else if (!projectExists && !globalExists) {
|
|
321
|
+
issues.push(recordedGlobalSkills.has(skillId)
|
|
322
|
+
? `Globally satisfied skill is missing: ${skillId} (${globalPath})`
|
|
323
|
+
: `Missing requested skill: ${projectPath}`);
|
|
324
|
+
} else if (projectExists && !projectIsOwned) {
|
|
325
|
+
issues.push(`Project skill path is not Showdar-owned: ${projectPath}`);
|
|
326
|
+
} else if (globalExists && !globalIsOwned) {
|
|
327
|
+
warnings.push(`Global skill path is not Showdar-owned: ${globalPath}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
229
333
|
for (const entry of manifest.files ?? []) {
|
|
230
334
|
const target = safeOwnedPath(baseRoot, entry.path, managedRoots);
|
|
231
335
|
if (!target) { issues.push(`Invalid managed path: ${entry.path}`); continue; }
|
|
232
|
-
if (!(await exists(target))) {
|
|
336
|
+
if (!(await exists(target))) {
|
|
337
|
+
if (!globalSatisfiedPaths.has(entry.path)) issues.push(`Missing managed path: ${entry.path}`);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
233
340
|
const actual = await hashTree(target);
|
|
234
341
|
if (actual !== entry.hash) issues.push(`Managed path drift detected: ${entry.path}`);
|
|
235
342
|
}
|
|
@@ -250,9 +357,13 @@ async function inspectInstallation({ baseRoot, manifestPath, agentsRoot, scope,
|
|
|
250
357
|
profile: manifest.profile ?? null,
|
|
251
358
|
ai: manifest.ai ?? null,
|
|
252
359
|
targets: manifest.targets ?? [],
|
|
253
|
-
skills:
|
|
360
|
+
skills: skillIds.length,
|
|
361
|
+
requestedSkills: skillIds.length,
|
|
362
|
+
installedSkills: scope === 'project' && globalSkillRootForTarget ? installedSkillIds.size : skillIds.length,
|
|
363
|
+
satisfiedByGlobal: scope === 'project' && globalSkillRootForTarget ? globallySatisfiedSkillIds.size : 0,
|
|
254
364
|
commands: (manifest.commands ?? []).length,
|
|
255
365
|
issues,
|
|
366
|
+
warnings,
|
|
256
367
|
};
|
|
257
368
|
}
|
|
258
369
|
|
|
@@ -268,8 +379,15 @@ async function removeInstallation({ baseRoot, manifestPath, agentsRoot, managedR
|
|
|
268
379
|
if (agentsRoot) await removeAgentsBlock(agentsRoot);
|
|
269
380
|
}
|
|
270
381
|
|
|
271
|
-
export async function inspectProject(projectRoot) {
|
|
272
|
-
return inspectInstallation({
|
|
382
|
+
export async function inspectProject(projectRoot, { homeRoot = homedir() } = {}) {
|
|
383
|
+
return inspectInstallation({
|
|
384
|
+
baseRoot: projectRoot,
|
|
385
|
+
manifestPath: path.join(projectRoot, PROJECT_MANIFEST),
|
|
386
|
+
agentsRoot: projectRoot,
|
|
387
|
+
scope: 'project',
|
|
388
|
+
homeRoot,
|
|
389
|
+
globalSkillRootForTarget: (target) => globalSkillRootFor(target, { homeRoot }),
|
|
390
|
+
});
|
|
273
391
|
}
|
|
274
392
|
|
|
275
393
|
export async function inspectGlobal({ homeRoot = homedir() } = {}) {
|