skillspub 0.1.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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/catalog.js +352 -0
- package/dist/cli.js +1562 -0
- package/dist/core.js +31 -0
- package/dist/explain.js +307 -0
- package/dist/harnesses/claude.js +95 -0
- package/dist/harnesses/grok.js +746 -0
- package/dist/harnesses/pi.js +1380 -0
- package/dist/harnesses/registry.js +30 -0
- package/dist/harnesses/target.js +5 -0
- package/dist/harnesses/types.js +1 -0
- package/dist/inventory.js +1437 -0
- package/dist/npx-skills.js +273 -0
- package/dist/reconcile.js +1014 -0
- package/dist/shared.js +1411 -0
- package/dist/source-verification.js +297 -0
- package/dist/targets/shared.js +13 -0
- package/dist/tui.js +2373 -0
- package/dist/view.js +321 -0
- package/package.json +51 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { explainVisibility, explainVisibilityFromInventory } from "./explain.js";
|
|
4
|
+
import { hashDirectory, scanGlobalInventory, scanProjectInventory, } from "./inventory.js";
|
|
5
|
+
import { normalizeNpxSkillsName, npxSkillsProvenanceLabel, readNpxSkillsLock, sameNpxSkillsSource, } from "./npx-skills.js";
|
|
6
|
+
import { attachUpdateAvailability, projectRows } from "./view.js";
|
|
7
|
+
export function sourceMirrorState(truth) {
|
|
8
|
+
if (/mirror-diverged|diverged mirror/i.test(truth.drift))
|
|
9
|
+
return 'diverged';
|
|
10
|
+
if (/mirror-sync/i.test(truth.drift))
|
|
11
|
+
return 'mirror-sync required';
|
|
12
|
+
return truth.relationships.some((relationship) => /\smirror\//i.test(relationship))
|
|
13
|
+
? 'current'
|
|
14
|
+
: 'none';
|
|
15
|
+
}
|
|
16
|
+
function sourceRelationshipActual(relationship) {
|
|
17
|
+
if (relationship.info.presence === 'deadlink')
|
|
18
|
+
return 'broken';
|
|
19
|
+
return relationship.info.underOff ? 'off' : 'on';
|
|
20
|
+
}
|
|
21
|
+
export function relationshipStatusText(info) {
|
|
22
|
+
return `${info.presence === 'deadlink' ? 'BROKEN' : info.underOff ? 'OFF' : 'ON'} ${info.form}`;
|
|
23
|
+
}
|
|
24
|
+
export function sourceRelationships(row) {
|
|
25
|
+
return (row?.observedRelationships ?? row?.relationships ?? [])
|
|
26
|
+
.filter(({ target }) => target === 'shared');
|
|
27
|
+
}
|
|
28
|
+
export function sourceInventoryRows(snapshot) {
|
|
29
|
+
return snapshot.rows.filter((row) => sourceRelationships(row).length > 0);
|
|
30
|
+
}
|
|
31
|
+
export function sourceDesiredTruth(home, row, scope, projectPath) {
|
|
32
|
+
if (!row)
|
|
33
|
+
return { desired: 'not applicable', drift: 'not applicable' };
|
|
34
|
+
const relationships = sourceRelationships(row);
|
|
35
|
+
let state = {};
|
|
36
|
+
try {
|
|
37
|
+
const file = scope === 'global'
|
|
38
|
+
? path.join(home.configDir, 'state.json')
|
|
39
|
+
: path.join(projectPath, '.skillspub', 'state.json');
|
|
40
|
+
if (fs.existsSync(file))
|
|
41
|
+
state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { desired: 'unknown', drift: 'unknown' };
|
|
45
|
+
}
|
|
46
|
+
const baseIntent = state.baseIntent && typeof state.baseIntent === 'object' && !Array.isArray(state.baseIntent)
|
|
47
|
+
? state.baseIntent
|
|
48
|
+
: {};
|
|
49
|
+
const claims = state.claims && typeof state.claims === 'object' && !Array.isArray(state.claims)
|
|
50
|
+
? state.claims
|
|
51
|
+
: {};
|
|
52
|
+
let observedDrift = false;
|
|
53
|
+
const desired = relationships.map((relationship) => {
|
|
54
|
+
const actual = relationship.info.underOff ? 'OFF' : 'ON';
|
|
55
|
+
if (relationship.info.presence === 'deadlink' || relationship.info.diverged)
|
|
56
|
+
observedDrift = true;
|
|
57
|
+
if (relationship.readOnly)
|
|
58
|
+
return `read-only ${actual}`;
|
|
59
|
+
const slotId = `${relationship.targetId}\0${relationship.slot}`;
|
|
60
|
+
let activation = actual;
|
|
61
|
+
if (Array.isArray(claims[slotId]) && claims[slotId].length > 0)
|
|
62
|
+
activation = 'ON';
|
|
63
|
+
else if (baseIntent[slotId] === 'off')
|
|
64
|
+
activation = 'OFF';
|
|
65
|
+
else if (baseIntent[slotId] === 'on')
|
|
66
|
+
activation = 'ON';
|
|
67
|
+
if (activation !== actual)
|
|
68
|
+
observedDrift = true;
|
|
69
|
+
return activation;
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
desired: [...new Set(desired)].join('/') || 'unknown',
|
|
73
|
+
drift: observedDrift ? 'observed' : 'none observed',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function sourceVisibility(home, row, projectPath) {
|
|
77
|
+
if (!row?.realPath)
|
|
78
|
+
return 'unknown';
|
|
79
|
+
try {
|
|
80
|
+
const visibility = explainVisibility(home, `skill:${row.id}`, { projectPath });
|
|
81
|
+
return [...new Set(visibility.harnesses.map(({ effectiveVisibility }) => effectiveVisibility))].join('/');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return 'unknown';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Effective visibility derived from the same inventory evidence — no second scan. */
|
|
88
|
+
function sourceVisibilityFromInventory(home, report, row, projectPath) {
|
|
89
|
+
if (!row?.realPath)
|
|
90
|
+
return 'unknown';
|
|
91
|
+
try {
|
|
92
|
+
const visibility = explainVisibilityFromInventory(home, report, `skill:${row.id}`, { projectPath });
|
|
93
|
+
return [...new Set(visibility.harnesses.map(({ effectiveVisibility }) => effectiveVisibility))].join('/');
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return 'unknown';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function deriveSourceVerification(home, rows, plan, scope, projectPath, visibilityOf) {
|
|
100
|
+
const slot = plan.operation === 'shared.remove'
|
|
101
|
+
? plan.source.slot
|
|
102
|
+
: plan.candidate?.normalizedSlot ?? plan.slots[0] ?? 'unknown';
|
|
103
|
+
const row = rows.find((candidate) => sourceRelationships(candidate).some((relationship) => relationship.targetId === plan.targetId && relationship.slot === slot));
|
|
104
|
+
const relationships = row?.observedRelationships ?? row?.relationships ?? [];
|
|
105
|
+
const removalLockRemains = plan.operation === 'shared.remove' &&
|
|
106
|
+
readNpxSkillsLock(plan.target.lockFile).some(({ slot: lockSlot }) => lockSlot === slot);
|
|
107
|
+
const removalDependenciesRemain = plan.operation === 'shared.remove'
|
|
108
|
+
? plan.dependencies.filter(({ path: dependencyPath }) => fs.lstatSync(dependencyPath, { throwIfNoEntry: false }))
|
|
109
|
+
: [];
|
|
110
|
+
const removalSourceRemains = plan.operation === 'shared.remove' && Boolean(row);
|
|
111
|
+
const desiredTruth = plan.operation === 'shared.remove'
|
|
112
|
+
? {
|
|
113
|
+
desired: 'removed',
|
|
114
|
+
drift: removalSourceRemains || removalLockRemains || removalDependenciesRemain.length > 0
|
|
115
|
+
? 'observed'
|
|
116
|
+
: 'none',
|
|
117
|
+
}
|
|
118
|
+
: sourceDesiredTruth(home, row, scope, projectPath);
|
|
119
|
+
const actual = relationships.map((relationship) => `${relationship.targetId}/${relationship.slot}=${sourceRelationshipActual(relationship)}/${relationship.info.form}`);
|
|
120
|
+
const driftEvidence = relationships.flatMap(({ targetId, slot: relationshipSlot, info }) => {
|
|
121
|
+
if (info.presence === 'deadlink')
|
|
122
|
+
return [`${targetId}/${relationshipSlot}: broken`];
|
|
123
|
+
if (info.diverged)
|
|
124
|
+
return [`${targetId}/${relationshipSlot}: diverged mirror`];
|
|
125
|
+
if (info.mirrored && row?.realPath) {
|
|
126
|
+
try {
|
|
127
|
+
if (hashDirectory(info.path) !== hashDirectory(row.realPath))
|
|
128
|
+
return [`${targetId}/${relationshipSlot}: mirror-sync required`];
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return [`${targetId}/${relationshipSlot}: mirror truth unreadable`];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
});
|
|
136
|
+
if (plan.operation === 'shared.remove') {
|
|
137
|
+
if (removalSourceRemains)
|
|
138
|
+
driftEvidence.push('Shared source remains');
|
|
139
|
+
if (removalLockRemains)
|
|
140
|
+
driftEvidence.push('Vercel skills lock entry remains');
|
|
141
|
+
for (const dependency of removalDependenciesRemain)
|
|
142
|
+
driftEvidence.push(`dependent Relationship remains: ${dependency.targetId}/${dependency.slot}`);
|
|
143
|
+
}
|
|
144
|
+
else if (desiredTruth.drift === 'observed')
|
|
145
|
+
driftEvidence.push('Actual differs from Desired');
|
|
146
|
+
const mirrorDrift = driftEvidence.filter((item) => item.includes('mirror')).length;
|
|
147
|
+
return {
|
|
148
|
+
resource: row?.realPath ?? 'missing',
|
|
149
|
+
provenance: row?.sourceLabel ?? (plan.operation === 'shared.remove'
|
|
150
|
+
? plan.source.provenance
|
|
151
|
+
: 'Source unknown'),
|
|
152
|
+
slot,
|
|
153
|
+
relationships: relationships.map(({ targetId, slot: relationshipSlot, info }) => `${targetId}/${relationshipSlot} ${info.form}/${info.underOff ? 'off' : 'on'} ${info.path}`),
|
|
154
|
+
actual: actual.join(', ') || `${plan.targetId}/${slot}=missing`,
|
|
155
|
+
desired: desiredTruth.desired,
|
|
156
|
+
drift: mirrorDrift > 0
|
|
157
|
+
? `mirror-sync required (${mirrorDrift}); ${driftEvidence.join(', ')}`
|
|
158
|
+
: driftEvidence.join(', ') || 'none observed',
|
|
159
|
+
updateAvailability: row?.updateAvailability?.status ?? 'unknown',
|
|
160
|
+
effectiveVisibility: visibilityOf(row),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Current scope-local truth for a remote Catalog candidate, matched by its
|
|
165
|
+
* normalized Slot against the latest snapshot. Non-writable (inherited)
|
|
166
|
+
* relationships never count toward installed matching: an installation in one
|
|
167
|
+
* isolated scope must not appear installed in the other.
|
|
168
|
+
*/
|
|
169
|
+
export function catalogCandidateTruth(home, snapshot, candidate, scope, projectPath) {
|
|
170
|
+
if (!candidate)
|
|
171
|
+
return undefined;
|
|
172
|
+
const slot = normalizeNpxSkillsName(candidate.name);
|
|
173
|
+
const match = sourceInventoryRows(snapshot)
|
|
174
|
+
.map((row) => ({
|
|
175
|
+
row,
|
|
176
|
+
relationships: sourceRelationships(row).filter((relationship) => !relationship.readOnly && relationship.slot === slot),
|
|
177
|
+
}))
|
|
178
|
+
.find(({ relationships }) => relationships.length > 0);
|
|
179
|
+
if (!match)
|
|
180
|
+
return {
|
|
181
|
+
state: 'not-installed',
|
|
182
|
+
actual: 'not installed',
|
|
183
|
+
desired: 'not applicable',
|
|
184
|
+
drift: 'not applicable',
|
|
185
|
+
updateAvailability: 'unknown',
|
|
186
|
+
relationships: 0,
|
|
187
|
+
effectiveVisibility: 'not applicable',
|
|
188
|
+
};
|
|
189
|
+
const { row, relationships } = match;
|
|
190
|
+
// Desired/Drift stay 'not applicable' for foreign Slots: ownership is not ours to derive.
|
|
191
|
+
const provenanceKnown = npxSkillsProvenanceLabel(row.provenance) !== 'Source unknown';
|
|
192
|
+
if (!provenanceKnown)
|
|
193
|
+
return {
|
|
194
|
+
state: 'occupied-unknown',
|
|
195
|
+
actual: 'Slot occupied — Source unknown; no ownership assumed',
|
|
196
|
+
desired: 'not applicable',
|
|
197
|
+
drift: 'not applicable',
|
|
198
|
+
updateAvailability: 'unknown',
|
|
199
|
+
relationships: sourceRelationships(row).length,
|
|
200
|
+
effectiveVisibility: 'unknown',
|
|
201
|
+
};
|
|
202
|
+
if (!sameNpxSkillsSource(candidate.source, candidate.name, row.provenance))
|
|
203
|
+
return {
|
|
204
|
+
state: 'replace',
|
|
205
|
+
actual: `occupied by ${row.sourceLabel} — Replace required`,
|
|
206
|
+
desired: 'not applicable',
|
|
207
|
+
drift: 'not applicable',
|
|
208
|
+
updateAvailability: row.updateAvailability?.status ?? 'unknown',
|
|
209
|
+
relationships: sourceRelationships(row).length,
|
|
210
|
+
effectiveVisibility: 'unknown',
|
|
211
|
+
};
|
|
212
|
+
const desiredTruth = sourceDesiredTruth(home, row, scope, projectPath);
|
|
213
|
+
return {
|
|
214
|
+
state: 'installed',
|
|
215
|
+
actual: relationships.map(({ info }) => relationshipStatusText(info)).join(', '),
|
|
216
|
+
desired: desiredTruth.desired,
|
|
217
|
+
drift: desiredTruth.drift,
|
|
218
|
+
updateAvailability: row.updateAvailability?.status ?? 'unknown',
|
|
219
|
+
relationships: sourceRelationships(row).length,
|
|
220
|
+
effectiveVisibility: sourceVisibility(home, row, scope === 'project' ? projectPath : undefined),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function deriveUpdateVerification(sourceRows, plan, result, visibilityOf) {
|
|
224
|
+
const rows = result.items.map((item) => ({
|
|
225
|
+
item,
|
|
226
|
+
row: sourceRows.find((candidate) => sourceRelationships(candidate).some(({ targetId, slot }) => targetId === plan.targetId && slot === item.slot)),
|
|
227
|
+
}));
|
|
228
|
+
const relationshipRows = rows.flatMap(({ row }) => (row?.observedRelationships ?? row?.relationships ?? []).map((relationship) => ({ row, relationship })));
|
|
229
|
+
const relationships = relationshipRows.map(({ relationship }) => relationship);
|
|
230
|
+
const visibility = rows.map(({ item, row }) => `${item.name}=${visibilityOf(row)}`);
|
|
231
|
+
const actual = relationships.map((relationship) => `${relationship.targetId}/${relationship.slot}=${sourceRelationshipActual(relationship)}/${relationship.info.form}`);
|
|
232
|
+
const observedDrift = relationshipRows.flatMap(({ row, relationship: { targetId, slot, info } }) => {
|
|
233
|
+
if (info.presence === 'deadlink')
|
|
234
|
+
return [`${targetId}/${slot}: broken`];
|
|
235
|
+
if (info.diverged)
|
|
236
|
+
return [`${targetId}/${slot}: mirror-diverged`];
|
|
237
|
+
if (info.mirrored && row?.realPath) {
|
|
238
|
+
try {
|
|
239
|
+
if (hashDirectory(info.path) !== hashDirectory(row.realPath))
|
|
240
|
+
return [`${targetId}/${slot}: mirror-sync`];
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return [`${targetId}/${slot}: mirror truth unreadable`];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return [];
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
resource: rows.map(({ row }) => row?.realPath ?? 'missing').join(', '),
|
|
250
|
+
provenance: rows.map(({ item, row }) => `${item.name}=${row?.sourceLabel ?? 'Source unknown'}`).join(', '),
|
|
251
|
+
slot: result.items.map(({ slot }) => slot).join(', '),
|
|
252
|
+
relationships: relationships.map(({ targetId, slot, info }) => `${targetId}/${slot} ${info.form}/${info.underOff ? 'off' : 'on'} ${info.path}`),
|
|
253
|
+
actual: actual.join(', ') || result.actual,
|
|
254
|
+
desired: plan.items.map(({ name, desired }) => `${name}=${desired}`).join(', '),
|
|
255
|
+
drift: [...new Set([...result.drift, ...observedDrift])].join(', ') || 'none',
|
|
256
|
+
updateAvailability: rows.map(({ item, row }) => `${item.name}=${row?.updateAvailability?.status ?? 'unknown'}${row?.updateAvailability?.checkedAt ? ` @ ${row.updateAvailability.checkedAt}` : ''}`).join(', '),
|
|
257
|
+
effectiveVisibility: visibility.join(', '),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Verify a Source mutation (add, replace, update, remove) through exactly one
|
|
262
|
+
* fresh post-mutation inventory scan per invocation. Actual state, Desired
|
|
263
|
+
* state, Drift, Update availability, provenance, Relationships, and Effective
|
|
264
|
+
* visibility are all derived from that same inventory evidence. Fail-soft:
|
|
265
|
+
* unavailable evidence is reported as explicit unknown values, never as
|
|
266
|
+
* success.
|
|
267
|
+
*/
|
|
268
|
+
export function verifySourceMutation(home, plan, result, projectPath) {
|
|
269
|
+
const scope = plan.scope?.kind ?? 'global';
|
|
270
|
+
const exactProjectPath = projectPath ?? plan.scope?.path ?? process.cwd();
|
|
271
|
+
try {
|
|
272
|
+
const report = scope === 'project'
|
|
273
|
+
? scanProjectInventory(home, exactProjectPath, undefined, { persist: false })
|
|
274
|
+
: scanGlobalInventory(home, undefined, { persist: false });
|
|
275
|
+
const rows = attachUpdateAvailability(projectRows(report), home, report)
|
|
276
|
+
.filter((row) => sourceRelationships(row).length > 0);
|
|
277
|
+
const visibilityOf = (row) => sourceVisibilityFromInventory(home, report, row, scope === 'project' ? exactProjectPath : undefined);
|
|
278
|
+
return 'items' in plan
|
|
279
|
+
? deriveUpdateVerification(rows, plan, result, visibilityOf)
|
|
280
|
+
: deriveSourceVerification(home, rows, plan, scope, exactProjectPath, visibilityOf);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return {
|
|
284
|
+
resource: 'unknown',
|
|
285
|
+
provenance: 'Source unknown',
|
|
286
|
+
slot: 'items' in plan
|
|
287
|
+
? plan.items.map(({ slot }) => slot).join(', ')
|
|
288
|
+
: plan.slots.join(', '),
|
|
289
|
+
relationships: [],
|
|
290
|
+
actual: result.actual,
|
|
291
|
+
desired: 'unknown',
|
|
292
|
+
drift: result.drift.join(', ') || 'rescan unavailable',
|
|
293
|
+
updateAvailability: 'unknown',
|
|
294
|
+
effectiveVisibility: 'unknown',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function sharedTargetDefinition() {
|
|
4
|
+
const home = os.homedir();
|
|
5
|
+
return {
|
|
6
|
+
key: 'shared',
|
|
7
|
+
kind: 'shared',
|
|
8
|
+
discoveryRoot: path.join(home, '.agents', 'skills'),
|
|
9
|
+
parkingRoot: path.join(home, '.agents', '.skillspub-off', 'skills'),
|
|
10
|
+
projectPath: '.agents/skills',
|
|
11
|
+
lockFile: path.join(home, '.agents', '.skill-lock.json'),
|
|
12
|
+
};
|
|
13
|
+
}
|