showdar-skills 0.2.0 → 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/README.md +96 -117
- package/bin/showdar.js +10 -0
- package/package.json +1 -1
- package/src/project.js +140 -22
package/README.md
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
# Showdar Skills
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/showdar-skills)
|
|
3
4
|
[](https://nodejs.org/)
|
|
4
5
|
[](./LICENSE)
|
|
5
6
|
[](#skill-catalog)
|
|
6
7
|
|
|
7
8
|
Production-grade software engineering skills for coding agents. Showdar covers
|
|
8
|
-
requirements
|
|
9
|
-
operations, release readiness, and Git
|
|
9
|
+
the full lifecycle—from requirements and planning through implementation, QA,
|
|
10
|
+
security, operations, release readiness, and Git—with lightweight intent
|
|
10
11
|
routing and progressive knowledge loading.
|
|
11
12
|
|
|
12
|
-
> npm publication: prepared for the first release; `showdar-skills@0.2.0` is
|
|
13
|
-
> not published yet.
|
|
14
|
-
|
|
15
13
|
## Quick start
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
skills explicitly:
|
|
15
|
+
Install the CLI, then install a role-oriented skill profile into your project:
|
|
19
16
|
|
|
20
17
|
```bash
|
|
21
18
|
npm install -g showdar-skills
|
|
@@ -24,7 +21,7 @@ showdar init --ai codex --profile developer
|
|
|
24
21
|
showdar doctor
|
|
25
22
|
```
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
To install from source instead:
|
|
28
25
|
|
|
29
26
|
```bash
|
|
30
27
|
git clone https://github.com/caongocquy/showdar-skills.git
|
|
@@ -32,17 +29,20 @@ cd showdar-skills
|
|
|
32
29
|
npm install -g .
|
|
33
30
|
```
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
Showdar works with Codex, OpenCode, Claude Code, and universal agent skill
|
|
33
|
+
directories. Choose `backend`, `qa`, or `product` when that gives discovery a
|
|
34
|
+
more precise context; use `full` when you want all capabilities available.
|
|
38
35
|
|
|
39
36
|
## Why Showdar?
|
|
40
37
|
|
|
41
|
-
- 15 focused skills
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
38
|
+
- **15 focused skills** instead of one oversized agent prompt.
|
|
39
|
+
- **Lifecycle coverage** from product rules to implementation, verification,
|
|
40
|
+
security, operations, release readiness, and Git completion.
|
|
41
|
+
- **Intent-based discovery** that selects the workflow matching the request.
|
|
42
|
+
- **Progressive knowledge loading** for deeper references, data, scripts, and
|
|
43
|
+
examples only when the selected task needs them.
|
|
44
|
+
- **Safe boundaries** around security findings, production operations, release
|
|
45
|
+
readiness, and destructive Git actions.
|
|
46
46
|
|
|
47
47
|
## How it works
|
|
48
48
|
|
|
@@ -66,34 +66,43 @@ SKILL.md
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The 15 skills are not eagerly loaded as full prompts. Lightweight descriptions
|
|
69
|
-
help the agent choose
|
|
69
|
+
help the agent choose one skill; that skill then loads its workflow and deeper
|
|
70
70
|
knowledge progressively.
|
|
71
71
|
|
|
72
|
-
## Supported agents
|
|
72
|
+
## Supported agents
|
|
73
73
|
|
|
74
|
-
|
|
|
74
|
+
| Target | Project destination | Global destination |
|
|
75
75
|
| --- | --- | --- |
|
|
76
|
-
| Codex | `.agents/skills/` | `~/.agents/skills/` |
|
|
77
|
-
|
|
|
78
|
-
| OpenCode | `.opencode/
|
|
76
|
+
| Codex / Universal | `.agents/skills/` | `~/.agents/skills/` |
|
|
77
|
+
| OpenCode skills | `.opencode/skills/` | `~/.config/opencode/skills/` |
|
|
78
|
+
| OpenCode commands | `.opencode/commands/showdar/` | `~/.config/opencode/commands/showdar/` |
|
|
79
79
|
| Claude Code | `.claude/skills/` | `~/.claude/skills/` |
|
|
80
80
|
|
|
81
|
-
Codex and Universal intentionally share `.agents/skills
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
Codex and Universal intentionally share `.agents/skills/`. OpenCode receives
|
|
82
|
+
both skills and native `/showdar/...` command files.
|
|
83
|
+
|
|
84
|
+
## Project and global installation
|
|
85
|
+
|
|
86
|
+
Global CLI installation and global skill installation are separate decisions.
|
|
87
|
+
The CLI is installed once; `showdar init` controls where its managed skills go.
|
|
84
88
|
|
|
85
|
-
### Project
|
|
89
|
+
### Project scope
|
|
86
90
|
|
|
87
91
|
Project scope is the default and writes to the current project:
|
|
88
92
|
|
|
89
93
|
```bash
|
|
90
94
|
cd my-project
|
|
91
|
-
showdar init --
|
|
95
|
+
showdar init --ai codex --profile developer
|
|
92
96
|
showdar status
|
|
93
97
|
showdar doctor
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
This creates `.agents/skills/` for Codex/Universal, or the corresponding native
|
|
101
|
+
target directories. Project ownership is recorded in `.showdar.json`.
|
|
102
|
+
|
|
103
|
+
### Global scope
|
|
104
|
+
|
|
105
|
+
Global scope installs user-level skills and does not require a Git repository:
|
|
97
106
|
|
|
98
107
|
```bash
|
|
99
108
|
showdar init --scope global --ai codex --profile developer
|
|
@@ -101,24 +110,22 @@ showdar status --scope global
|
|
|
101
110
|
showdar doctor --scope global
|
|
102
111
|
```
|
|
103
112
|
|
|
104
|
-
Global
|
|
105
|
-
|
|
106
|
-
Only Showdar-owned paths are refreshed or removed.
|
|
113
|
+
Global ownership is recorded in `~/.showdar/global.json`. Only Showdar-owned
|
|
114
|
+
paths are refreshed or removed.
|
|
107
115
|
|
|
108
116
|
## Profiles
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
eagerly load all skill bodies.
|
|
118
|
+
Role-specific profiles improve routing precision. `full` exposes every skill,
|
|
119
|
+
but still does not eagerly load every skill body.
|
|
113
120
|
|
|
114
|
-
| Profile | Skills |
|
|
115
|
-
| --- | ---: |
|
|
116
|
-
| `minimal` | 8 |
|
|
117
|
-
| `developer` | 12 |
|
|
118
|
-
| `backend` | 14 |
|
|
119
|
-
| `qa` | 9 |
|
|
120
|
-
| `product` | 6 |
|
|
121
|
-
| `full` | 15 |
|
|
121
|
+
| Profile | Skills | Best for |
|
|
122
|
+
| --- | ---: | --- |
|
|
123
|
+
| `minimal` | 8 | Focused everyday assistance |
|
|
124
|
+
| `developer` | 12 | General application development |
|
|
125
|
+
| `backend` | 14 | APIs, services, and runtime operations |
|
|
126
|
+
| `qa` | 9 | Testing and quality workflows |
|
|
127
|
+
| `product` | 6 | Product, requirements, and design work |
|
|
128
|
+
| `full` | 15 | All capabilities |
|
|
122
129
|
|
|
123
130
|
Legacy aliases remain compatible:
|
|
124
131
|
|
|
@@ -131,7 +138,7 @@ New manifests store the canonical `developer` profile.
|
|
|
131
138
|
|
|
132
139
|
## Skill catalog
|
|
133
140
|
|
|
134
|
-
All 15 entries
|
|
141
|
+
All 15 entries are first-class Showdar skills.
|
|
135
142
|
|
|
136
143
|
### Analysis and planning
|
|
137
144
|
|
|
@@ -173,47 +180,36 @@ All 15 entries remain first-class Showdar skills.
|
|
|
173
180
|
| `showdar-recover` | Interrupted or partial engineering work must be reconstructed from repository evidence before continuing. |
|
|
174
181
|
| `showdar-git` | Performing local Git inspection, staging, commits, branch integration, conflicts, cleanup, or explicitly requested remote Git actions. |
|
|
175
182
|
|
|
176
|
-
##
|
|
183
|
+
## A typical software workflow
|
|
177
184
|
|
|
178
185
|
```text
|
|
179
186
|
Requirements
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Plan
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
|
195
|
-
v
|
|
196
|
-
Review / Security
|
|
197
|
-
|
|
|
198
|
-
v
|
|
199
|
-
Ship readiness
|
|
200
|
-
|
|
|
201
|
-
+--> Ops
|
|
202
|
-
|
|
|
203
|
-
v
|
|
187
|
+
|
|
|
188
|
+
v
|
|
189
|
+
Plan -----> Design
|
|
190
|
+
|
|
|
191
|
+
v
|
|
192
|
+
Build ----> Debug / Test / Quality
|
|
193
|
+
|
|
|
194
|
+
v
|
|
195
|
+
Review ---> Security
|
|
196
|
+
|
|
|
197
|
+
v
|
|
198
|
+
Ship readiness -----> Ops
|
|
199
|
+
|
|
|
200
|
+
v
|
|
204
201
|
Git completion
|
|
205
202
|
```
|
|
206
203
|
|
|
207
|
-
This is a
|
|
208
|
-
|
|
204
|
+
This is a mental model, not a mandatory pipeline. Choose the skill that matches
|
|
205
|
+
the current intent.
|
|
209
206
|
|
|
210
207
|
## Usage examples
|
|
211
208
|
|
|
212
|
-
Codex discovers installed
|
|
209
|
+
Codex discovers installed skills from natural requests or explicit names:
|
|
213
210
|
|
|
214
211
|
```text
|
|
215
212
|
$showdar-requirements review this ticket for missing rules
|
|
216
|
-
$showdar-plan plan the implementation
|
|
217
213
|
$showdar-debug find the root cause of this crash
|
|
218
214
|
$showdar-quality create regression scenarios
|
|
219
215
|
$showdar-security threat model this auth flow
|
|
@@ -221,7 +217,7 @@ $showdar-ops inspect the deployment setup
|
|
|
221
217
|
$showdar-git commit only the current task changes
|
|
222
218
|
```
|
|
223
219
|
|
|
224
|
-
OpenCode
|
|
220
|
+
OpenCode exposes native commands after initialization with `--ai opencode` or
|
|
225
221
|
`--ai all`:
|
|
226
222
|
|
|
227
223
|
```text
|
|
@@ -234,16 +230,13 @@ OpenCode uses native commands when initialized with `--ai opencode` or
|
|
|
234
230
|
|
|
235
231
|
## Safety boundaries
|
|
236
232
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
exposes secret values.
|
|
245
|
-
- `showdar-requirements` records assumptions and open decisions instead of
|
|
246
|
-
inventing stakeholder or business choices.
|
|
233
|
+
| Skill | Boundary |
|
|
234
|
+
| --- | --- |
|
|
235
|
+
| `showdar-ship` | Verifies readiness; it does not deploy or create CI/CD by default. |
|
|
236
|
+
| `showdar-ops` | Handles operational work; remote or production mutation requires explicit intent, target, and authorization. |
|
|
237
|
+
| `showdar-git` | Does not imply push, force-push, or destructive cleanup. |
|
|
238
|
+
| `showdar-security` | Performs defensive, evidence-based analysis and never exposes secret values. |
|
|
239
|
+
| `showdar-requirements` | Records assumptions and open decisions instead of inventing business decisions. |
|
|
247
240
|
|
|
248
241
|
## CLI reference
|
|
249
242
|
|
|
@@ -256,50 +249,36 @@ showdar validate
|
|
|
256
249
|
showdar remove [--scope <project|global>]
|
|
257
250
|
```
|
|
258
251
|
|
|
259
|
-
|
|
260
|
-
`--scope` defaults to `project`;
|
|
261
|
-
and the `mobile`/`web` aliases.
|
|
262
|
-
for
|
|
252
|
+
Main flags are `--ai`, `--profile`, and `--scope`. `--ai` accepts `codex`,
|
|
253
|
+
`opencode`, `claude`, `universal`, or `all`. `--scope` defaults to `project`;
|
|
254
|
+
`--profile` accepts the six canonical profiles and the `mobile`/`web` aliases.
|
|
255
|
+
Run `showdar --help` or a command's `--help` for current options.
|
|
263
256
|
|
|
264
|
-
`showdar validate` validates the installed Showdar package
|
|
265
|
-
|
|
266
|
-
|
|
257
|
+
`showdar validate` validates the installed Showdar package. `showdar doctor`
|
|
258
|
+
checks managed files against ownership hashes, while `showdar remove` removes
|
|
259
|
+
only those managed paths and preserves unrelated files.
|
|
267
260
|
|
|
268
|
-
##
|
|
261
|
+
## Updating and refreshing
|
|
269
262
|
|
|
270
|
-
There is no separate `showdar update` command
|
|
271
|
-
`showdar init` with the same scope, target, and profile:
|
|
263
|
+
There is no separate `showdar update` command:
|
|
272
264
|
|
|
273
265
|
```bash
|
|
274
|
-
#
|
|
266
|
+
# Upgrade the CLI from npm
|
|
275
267
|
npm install -g showdar-skills@latest
|
|
276
268
|
|
|
277
|
-
#
|
|
278
|
-
npm install -g .
|
|
279
|
-
|
|
269
|
+
# Refresh Showdar-owned skills in the selected scope
|
|
280
270
|
showdar init --scope project --ai codex --profile developer
|
|
281
271
|
```
|
|
282
272
|
|
|
283
|
-
|
|
273
|
+
For source development, reinstall from the checkout with `npm install -g .`.
|
|
274
|
+
Re-running `showdar init` is idempotent and refreshes managed files. Use
|
|
284
275
|
`showdar remove` for project scope or `showdar remove --scope global` for the
|
|
285
276
|
user installation.
|
|
286
277
|
|
|
287
|
-
## Maintainer release
|
|
288
|
-
|
|
289
|
-
The first release is manual because the package does not exist on npm yet:
|
|
290
|
-
|
|
291
|
-
```bash
|
|
292
|
-
npm pack
|
|
293
|
-
npm publish ./showdar-skills-0.2.0.tgz
|
|
294
|
-
```
|
|
278
|
+
## Maintainer release guide
|
|
295
279
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
workflow runs only for `v*` tags, verifies the tag/package version, uses OIDC
|
|
299
|
-
with provenance, and contains no npm token. It skips an already-published
|
|
300
|
-
version and fails on registry errors other than a real E404. Trusted
|
|
301
|
-
Publishing is not configured yet. The repository's `RELEASING.md` contains
|
|
302
|
-
the maintainer runbook.
|
|
280
|
+
Release and Trusted Publishing instructions live in the
|
|
281
|
+
[maintainer release guide](https://github.com/caongocquy/showdar-skills/blob/main/RELEASING.md).
|
|
303
282
|
|
|
304
283
|
## Development
|
|
305
284
|
|
|
@@ -312,11 +291,11 @@ npm run eval
|
|
|
312
291
|
npm pack --dry-run
|
|
313
292
|
```
|
|
314
293
|
|
|
315
|
-
Showdar
|
|
316
|
-
|
|
317
|
-
|
|
294
|
+
Showdar is dependency-light and uses Node.js built-ins for its CLI, validator,
|
|
295
|
+
search engine, installer, and tests. Supporting knowledge remains in each
|
|
296
|
+
skill's `data/`, `references/`, `scripts/`, `stacks/`, and `examples/`
|
|
318
297
|
directories so the selected workflow can load it progressively.
|
|
319
298
|
|
|
320
299
|
## License
|
|
321
300
|
|
|
322
|
-
MIT
|
|
301
|
+
[MIT](./LICENSE)
|
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() } = {}) {
|