showdar-skills 0.12.1 → 0.13.0
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/CHANGELOG.md +15 -0
- package/README.md +28 -0
- package/bin/showdar.js +39 -10
- package/package.json +1 -1
- package/src/project.js +74 -20
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.13.0]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `showdar add profile <profile>`: additive union of built-in profile primitives without replacing an existing base profile or other installed skills.
|
|
14
|
+
- `showdar add workflow <name>`: install built-in workflow skills and any missing stage primitives; `showdar add feature` / `showdar add showdar-feature` remain equivalent aliases.
|
|
15
|
+
- `showdar add workflow ./local.json`: convenient spelling for existing project-scoped custom workflow installation.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Additive installation preserves existing extension metadata and supports projects initialized with `--ai all`.
|
|
20
|
+
- `showdar init --profile ...` retains its explicit replace semantics; existing standalone `showdar add-workflow` remains supported.
|
|
21
|
+
- Added installer regression coverage for existing-skill preservation, aliases, workflow stage closure, custom workflows, foreign-path safety and re-init replacement.
|
|
22
|
+
|
|
23
|
+
|
|
9
24
|
## [0.12.1]
|
|
10
25
|
|
|
11
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -40,6 +40,32 @@ Claude Code as supported installation targets. Choose `backend`, `qa`, or
|
|
|
40
40
|
`product` when that gives discovery a more precise context; use `full` when
|
|
41
41
|
you want all capabilities available.
|
|
42
42
|
|
|
43
|
+
## Additive installation
|
|
44
|
+
|
|
45
|
+
`showdar init --profile <name>` **replaces** the Showdar-owned skill selection.
|
|
46
|
+
`showdar add` always **preserves** existing skills and the selected base profile:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
showdar init --profile developer --ai opencode
|
|
50
|
+
showdar add git # same as showdar add showdar-git
|
|
51
|
+
showdar add profile insurance # union, no deletions
|
|
52
|
+
showdar add workflow feature # workflow + missing primitive stages
|
|
53
|
+
showdar add workflow ./acme-release.json # standalone custom workflow, project scope
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Both `showdar add feature` and `showdar add showdar-feature` are aliases for
|
|
57
|
+
`showdar add workflow feature`, including automatic installation of missing
|
|
58
|
+
primitive stage skills. All built-in profiles are additive via `add profile`;
|
|
59
|
+
`init` remains the explicit replace operation. Existing workflow packs and
|
|
60
|
+
custom workflow definitions are preserved when adding skills. The runtime
|
|
61
|
+
router is included in the Showdar CLI and is automatically referenced by
|
|
62
|
+
updated native guidance; it does not require an additional skill.
|
|
63
|
+
|
|
64
|
+
Built-in workflow dependency installation does **not** execute the workflow,
|
|
65
|
+
authorize source mutation, commit, merge or push. A custom workflow JSON file
|
|
66
|
+
uses the existing `add-workflow` validation/manifest semantics; remote files
|
|
67
|
+
and arbitrary executable plugins are unsupported.
|
|
68
|
+
|
|
43
69
|
## Runtime routing and task branches
|
|
44
70
|
|
|
45
71
|
```bash
|
|
@@ -845,6 +871,8 @@ Product behavior notes:
|
|
|
845
871
|
```bash
|
|
846
872
|
showdar init [--scope <project|global>] --ai <target> --profile <profile>
|
|
847
873
|
showdar add <skill> [--ai <target>] [--scope <project|global>]
|
|
874
|
+
showdar add profile <profile> [--ai <target>] [--scope <project|global>]
|
|
875
|
+
showdar add workflow <builtin-name|local-json-path> [--ai <target>] [--scope <project|global>]
|
|
848
876
|
showdar add-pack <local-path>
|
|
849
877
|
showdar remove-pack <name>
|
|
850
878
|
showdar add-workflow <local-path>
|
package/bin/showdar.js
CHANGED
|
@@ -7,8 +7,8 @@ import { routeRequest, formatRoute } from '../src/runtime-route.js';
|
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { readFile } from 'node:fs/promises';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { AI_TARGETS, PRIMITIVE_COUNT, PROFILE_ALIASES, PROFILES, SKILLS, TOTAL_COUNT, WORKFLOW_COUNT, canonicalProfile, isDeprecatedProfile, resolveProfile } from '../src/catalog.js';
|
|
11
|
-
import { addPack, addSkill, addWorkflow, globalManifestPath, initGlobal, initProject, inspectGlobal, inspectProject, listExtensions, listExtensionsDetailed, removeGlobal, removePack, removeProject, validatePackSource, createPack, inspectPack, doctor, updatePack, readProjectOverrides } from '../src/project.js';
|
|
10
|
+
import { AI_TARGETS, PRIMITIVE_COUNT, PROFILE_ALIASES, PROFILES, SKILLS, TOTAL_COUNT, WORKFLOW_COUNT, canonicalProfile, isDeprecatedProfile, resolveProfile, getWorkflow, normalizeSkillName } from '../src/catalog.js';
|
|
11
|
+
import { addPack, addSkill, addSkills, addWorkflow, globalManifestPath, initGlobal, initProject, inspectGlobal, inspectProject, listExtensions, listExtensionsDetailed, removeGlobal, removePack, removeProject, validatePackSource, createPack, inspectPack, doctor, updatePack, readProjectOverrides } from '../src/project.js';
|
|
12
12
|
import { formatPlanPreviewHuman, projectPackUpdatePreview } from '../src/pack-plan.js';
|
|
13
13
|
import { assessCheckpointAgainstCandidate } from '../src/pack-inspect.js';
|
|
14
14
|
import { validateRepository } from '../src/validate.js';
|
|
@@ -58,10 +58,10 @@ function printHelp(version, command = null) {
|
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
if (command === 'add') {
|
|
61
|
-
console.log(`Showdar Skills ${version}\n\nUsage:\n showdar add <skill> [--ai <
|
|
61
|
+
console.log(`Showdar Skills ${version}\n\nUsage:\n showdar add <skill> [--ai <target>] [--scope <project|global>]\n showdar add profile <profile> [--ai <target>] [--scope <project|global>]\n showdar add workflow <builtin-name|local-json-path> [--ai <target>] [--scope <project|global>]\n\nExamples:\n showdar add git\n showdar add showdar-git\n showdar add profile insurance\n showdar add workflow feature\n showdar add workflow ./workflows/acme-release.json\n\nAdd preserves installed skills; init replaces the managed set. Built-in workflows add required stages.`);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <universal|codex|opencode|cursor|claude|all>] [--pack <local-path>]\n showdar add <skill> [--ai <
|
|
64
|
+
console.log(`Showdar Skills ${version}\n\nUsage:\n showdar init ${scopeUsage} [--profile <name>] [--ai <universal|codex|opencode|cursor|claude|all>] [--pack <local-path>]\n showdar add <skill> [--ai <target>] [--scope <project|global>]\n showdar add profile <profile> [--ai <target>] [--scope <project|global>]\n showdar add workflow <builtin-name|local-json-path> [--ai <target>] [--scope <project|global>]\n showdar route (--stdin | --prompt <text>) [--json]\n showdar git-start --type <type> --name <task> [--base <branch>] [--dry-run] [--json]\n showdar guard --mutation <read-only|local-write> [--json]\n showdar add-pack <local-path>\n showdar remove-pack <name>\n showdar add-workflow <local-path>\n showdar status ${scopeUsage}\n showdar doctor ${scopeUsage}\n showdar validate\n showdar list [--extensions]\n showdar remove ${scopeUsage}\n showdar create-pack <path> [--vendor <vendor>] [--description <text>] [--with-workflow <id>] [--with-profile <name>]\n showdar validate-pack <local-path> [--json]\n showdar inspect-pack <local-path> [--json] [--checkpoint <file>]\n showdar doctor ${scopeUsage} [--extensions] [--json]\n showdar update-pack <local-path> [--dry-run] [--json]\n showdar validate\n showdar list [--extensions] [--json]\n showdar remove ${scopeUsage}\n\nExtension packs accept local directories/workspace paths only; tarball, URL, Git, and registry sources are rejected.\n\nDefaults: scope project, profile full, AI target universal.\nProfiles: ${Object.keys(PROFILES).join(', ')}\nDeprecated aliases: ${Object.entries(PROFILE_ALIASES).map(([alias, target]) => `${alias} -> ${target}`).join(', ')}\nAI targets: ${AI_TARGETS.join(', ')}`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
async function main() {
|
|
@@ -242,20 +242,49 @@ async function main() {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
if (command === 'add') {
|
|
245
|
-
const positional = args.filter((
|
|
246
|
-
const
|
|
247
|
-
if (!
|
|
245
|
+
const positional = args.filter((arg, index) => index > 0 && !arg.startsWith('--') && args[index - 1] !== '--ai' && args[index - 1] !== '--scope');
|
|
246
|
+
const kind = positional[0];
|
|
247
|
+
if (!kind) throw new Error('Usage: showdar add <skill> | profile <profile> | workflow <builtin-name|local-json-path>');
|
|
248
248
|
const hasAiFlag = args.includes('--ai');
|
|
249
249
|
const hasScopeFlag = args.includes('--scope');
|
|
250
|
-
const
|
|
250
|
+
const options = {
|
|
251
251
|
cwd: projectRoot,
|
|
252
|
-
skill: skillArg,
|
|
253
252
|
ai: hasAiFlag ? valueAfter(args, '--ai', 'universal') : null,
|
|
254
253
|
scope: hasScopeFlag ? scope : null,
|
|
255
254
|
home: homedir(),
|
|
256
255
|
packageRoot,
|
|
257
256
|
packageVersion: version,
|
|
258
|
-
}
|
|
257
|
+
};
|
|
258
|
+
if (kind === 'profile') {
|
|
259
|
+
if (positional.length !== 2) throw new Error('Usage: showdar add profile <profile> [--ai <target>] [--scope <project|global>]');
|
|
260
|
+
const profileName = positional[1];
|
|
261
|
+
const selected = resolveProfile(profileName);
|
|
262
|
+
const result = await addSkills({ ...options, skills: selected });
|
|
263
|
+
if (isDeprecatedProfile(profileName)) console.warn(`Warning: profile "${profileName}" is deprecated; use "${canonicalProfile(profileName)}".`);
|
|
264
|
+
console.log(`Showdar profile added.\nAdded profile: ${canonicalProfile(profileName)}\nNew skills: ${result.added}\nAlready installed: ${result.alreadyInstalled}\nTotal requested: ${result.skills.length}\nExisting profile preserved: ${result.profile ?? '(none)'}\nScope: ${result.scope}\nAI: ${result.ai}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const installBuiltinWorkflow = async workflow => {
|
|
268
|
+
const result = await addSkills({ ...options, skills: [workflow.id, ...workflow.stages] });
|
|
269
|
+
console.log(`Showdar workflow added.\nWorkflow: ${workflow.id}\nNew skills: ${result.added}\nAlready installed: ${result.alreadyInstalled}\nRequired stage skills: ${workflow.stages.join(', ')}\nScope: ${result.scope}\nAI: ${result.ai}`);
|
|
270
|
+
};
|
|
271
|
+
if (kind === 'workflow') {
|
|
272
|
+
if (positional.length !== 2) throw new Error('Usage: showdar add workflow <builtin-name|local-json-path>');
|
|
273
|
+
const name = positional[1];
|
|
274
|
+
const builtin = getWorkflow(name.startsWith('showdar-') ? name : `showdar-${name}`);
|
|
275
|
+
if (builtin) return installBuiltinWorkflow(builtin);
|
|
276
|
+
if (!name.endsWith('.json')) throw new Error(`Unknown built-in workflow "${name}". For custom workflows provide a local JSON file.`);
|
|
277
|
+
if (hasAiFlag || (hasScopeFlag && scope !== 'project')) {
|
|
278
|
+
throw new Error('Custom workflow files support project scope only and do not accept --ai.');
|
|
279
|
+
}
|
|
280
|
+
const result = await addWorkflow({ cwd: projectRoot, source: name });
|
|
281
|
+
console.log(`Showdar custom workflow added.\nWorkflow: ${result.workflow}\nPath: ${result.path}`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (positional.length !== 1) throw new Error('Usage: showdar add <skill> [--ai <target>] [--scope <project|global>]');
|
|
285
|
+
const workflow = getWorkflow(normalizeSkillName(kind));
|
|
286
|
+
if (workflow) return installBuiltinWorkflow(workflow);
|
|
287
|
+
const result = await addSkill({ ...options, skill: kind });
|
|
259
288
|
console.log(`Showdar skill ${result.added ? 'added' : 'already installed'}.\nSkill: ${result.skill}\nScope: ${result.scope}\nAI: ${result.ai}\nPath: ${result.destination}`);
|
|
260
289
|
return;
|
|
261
290
|
}
|
package/package.json
CHANGED
package/src/project.js
CHANGED
|
@@ -584,7 +584,7 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
584
584
|
|
|
585
585
|
const effectiveAi = ai ?? existingManifest?.ai ?? 'universal';
|
|
586
586
|
const effectiveScope = scope ?? existingManifest?.scope ?? 'project';
|
|
587
|
-
if (!NATIVE_TARGETS.includes(effectiveAi)) throw new Error(`Unknown AI target "${effectiveAi}".`);
|
|
587
|
+
if (effectiveAi !== 'all' && !NATIVE_TARGETS.includes(effectiveAi)) throw new Error(`Unknown AI target "${effectiveAi}".`);
|
|
588
588
|
if (effectiveScope !== 'project' && effectiveScope !== 'global') throw new Error(`Unknown scope "${effectiveScope}".`);
|
|
589
589
|
|
|
590
590
|
if (existingManifest?.skills?.includes(skillId)) {
|
|
@@ -633,10 +633,7 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
633
633
|
return { skill: skillId, root: '', destination: '', added: false, scope: effectiveScope, ai: effectiveAi, profile: existingManifest.profile };
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
const
|
|
637
|
-
? globalSkillRootFor(effectiveAi, { homeRoot: home })
|
|
638
|
-
: skillRootFor(effectiveAi, cwd);
|
|
639
|
-
const destination = path.join(root, skillId);
|
|
636
|
+
const targets = resolveTargets(effectiveAi);
|
|
640
637
|
const managedRoots = effectiveScope === 'global'
|
|
641
638
|
? [...new Set([
|
|
642
639
|
...NATIVE_TARGETS.map((t) => globalSkillRootFor(t, { homeRoot: home })),
|
|
@@ -644,30 +641,38 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
644
641
|
])]
|
|
645
642
|
: [];
|
|
646
643
|
const manifestPath = effectiveScope === 'global' ? globalManifestPath(home) : path.join(cwd, PROJECT_MANIFEST);
|
|
647
|
-
|
|
648
644
|
const source = path.join(packageRoot, 'skills', skillId);
|
|
649
645
|
if (!(await exists(path.join(source, 'SKILL.md')))) throw new Error(`Packaged skill source missing: ${skillId}`);
|
|
650
646
|
|
|
651
647
|
await mkdir(baseRoot, { recursive: true });
|
|
652
648
|
const priorOwned = ownedPathSet(existingManifest);
|
|
653
|
-
const relative = manifestPathFor(baseRoot, destination);
|
|
654
|
-
const existed = await exists(destination);
|
|
655
|
-
const alreadyTracked = priorOwned.has(relative);
|
|
656
|
-
|
|
657
649
|
const files = [];
|
|
658
|
-
|
|
650
|
+
const destinations = [...new Set(targets.map(target => effectiveScope === 'global'
|
|
651
|
+
? globalSkillRootFor(target, { homeRoot: home })
|
|
652
|
+
: skillRootFor(target, cwd)))].map(root => path.join(root, skillId));
|
|
653
|
+
const alreadyInstalled = destinations.every(destination =>
|
|
654
|
+
priorOwned.has(manifestPathFor(baseRoot, destination)));
|
|
655
|
+
|
|
656
|
+
// Inspect every target before copying anything. Never overwrite foreign skills.
|
|
657
|
+
for (const destination of destinations) {
|
|
658
|
+
await assertSafeManagedPath(baseRoot, destination, managedRoots);
|
|
659
|
+
const relative = manifestPathFor(baseRoot, destination);
|
|
660
|
+
if ((await exists(destination)) && !priorOwned.has(relative)) {
|
|
661
|
+
throw new Error(`Refusing to overwrite existing non-Showdar-managed skill or command: ${destination}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
for (const destination of destinations) {
|
|
665
|
+
await copyOwned({ baseRoot, source, destination, priorOwned, newFiles: files, managedRoots });
|
|
666
|
+
}
|
|
659
667
|
|
|
660
|
-
const targets = [effectiveAi];
|
|
661
668
|
const commandHarnesses = [];
|
|
662
669
|
for (const target of targets) {
|
|
663
670
|
const adapter = ADAPTERS[target];
|
|
664
671
|
if (adapter?.commands?.destination) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if (globalRoot) commandHarnesses.push({ target, root: globalRoot });
|
|
670
|
-
}
|
|
672
|
+
const root = effectiveScope === 'project'
|
|
673
|
+
? commandRootFor(target, cwd)
|
|
674
|
+
: globalCommandRootForTarget(target, { homeRoot: home });
|
|
675
|
+
if (root) commandHarnesses.push({ target, root });
|
|
671
676
|
}
|
|
672
677
|
}
|
|
673
678
|
|
|
@@ -684,10 +689,11 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
684
689
|
const merged = new Map((existingManifest?.files ?? []).map((e) => [e.path, e]));
|
|
685
690
|
for (const f of files) merged.set(f.path, f);
|
|
686
691
|
|
|
687
|
-
const instructionFile = existingManifest?.instructions ?? (effectiveScope === 'project' ? instructionSurfaceFor(effectiveAi, cwd) : null);
|
|
692
|
+
const instructionFile = existingManifest?.instructions ?? (effectiveScope === 'project' ? instructionSurfaceFor(effectiveAi === 'all' ? 'universal' : effectiveAi, cwd) : null);
|
|
688
693
|
const commandHarness = [...new Set([...(existingManifest?.commandHarness ?? []), ...commandHarnesses.map((c) => c.target)])];
|
|
689
694
|
|
|
690
695
|
const manifest = {
|
|
696
|
+
...(existingManifest ?? {}),
|
|
691
697
|
version: existingManifest?.version ?? 2,
|
|
692
698
|
scope: effectiveScope,
|
|
693
699
|
packageVersion,
|
|
@@ -713,7 +719,55 @@ export async function addSkill({ cwd, skill, ai = null, scope = null, home = hom
|
|
|
713
719
|
}
|
|
714
720
|
}
|
|
715
721
|
|
|
716
|
-
return { skill: skillId, root, destination, added: !
|
|
722
|
+
return { skill: skillId, root: path.dirname(destinations[0]), destination: destinations[0], added: !alreadyInstalled, scope: effectiveScope, ai: effectiveAi, profile: manifest.profile };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Additive profile/workflow member install. Existing project profile and any
|
|
728
|
+
* extra extension metadata are preserved. Requested members are deduplicated.
|
|
729
|
+
* Each member uses the standard single-skill installer and remains rerunnable.
|
|
730
|
+
*/
|
|
731
|
+
export async function addSkills({ cwd, skills, ai = null, scope = null, home = homedir(), packageRoot, packageVersion = '0.2.0' }) {
|
|
732
|
+
if (!Array.isArray(skills) || !skills.length) throw new Error('At least one skill is required.');
|
|
733
|
+
const skillIds = [...new Set(skills.map(normalizeSkillName))];
|
|
734
|
+
const baseRoot = scope === 'global' ? home : cwd;
|
|
735
|
+
const manifestPath = scope === 'global' ? globalManifestPath(home) : path.join(cwd, PROJECT_MANIFEST);
|
|
736
|
+
const existing = await readManifest(manifestPath, baseRoot);
|
|
737
|
+
const effectiveAi = ai ?? existing?.ai ?? 'universal';
|
|
738
|
+
const targets = resolveTargets(effectiveAi);
|
|
739
|
+
const owned = ownedPathSet(existing);
|
|
740
|
+
const managedRoots = scope === 'global'
|
|
741
|
+
? [...new Set(NATIVE_TARGETS.map(target => globalSkillRootFor(target, { homeRoot: home })))]
|
|
742
|
+
: [];
|
|
743
|
+
// Preflight packaged inputs and all requested target paths before writing.
|
|
744
|
+
for (const skillId of skillIds) {
|
|
745
|
+
const source = path.join(packageRoot, 'skills', skillId, 'SKILL.md');
|
|
746
|
+
if (!(await exists(source))) throw new Error(`Packaged skill source missing: ${skillId}`);
|
|
747
|
+
for (const target of targets) {
|
|
748
|
+
const skillRoot = scope === 'global'
|
|
749
|
+
? globalSkillRootFor(target, { homeRoot: home })
|
|
750
|
+
: skillRootFor(target, cwd);
|
|
751
|
+
const destination = path.join(skillRoot, skillId);
|
|
752
|
+
await assertSafeManagedPath(baseRoot, destination, managedRoots);
|
|
753
|
+
const relative = manifestPathFor(baseRoot, destination);
|
|
754
|
+
if ((await exists(destination)) && !owned.has(relative)) {
|
|
755
|
+
throw new Error(`Refusing to overwrite existing non-Showdar-managed skill or command: ${destination}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const installed = [];
|
|
760
|
+
for (const skill of skillIds) {
|
|
761
|
+
installed.push(await addSkill({ cwd, skill, ai, scope, home, packageRoot, packageVersion }));
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
skills: skillIds,
|
|
765
|
+
added: installed.filter(entry => entry.added).length,
|
|
766
|
+
alreadyInstalled: installed.filter(entry => !entry.added).length,
|
|
767
|
+
profile: installed.at(-1)?.profile ?? existing?.profile ?? null,
|
|
768
|
+
ai: installed.at(-1)?.ai ?? effectiveAi,
|
|
769
|
+
scope: installed.at(-1)?.scope ?? scope ?? existing?.scope ?? 'project',
|
|
770
|
+
};
|
|
717
771
|
}
|
|
718
772
|
|
|
719
773
|
export async function removeProject(projectRoot) {
|