agent-skillboard 0.2.8 → 0.2.10
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 +28 -0
- package/README.md +6 -0
- package/docs/install.md +15 -0
- package/package.json +2 -2
- package/src/advisor/action-core.mjs +1 -1
- package/src/agent-inventory-platforms.mjs +3 -3
- package/src/agent-inventory.mjs +3 -3
- package/src/agent-skill-import.mjs +1 -1
- package/src/brief-renderer.mjs +2 -2
- package/src/cli.mjs +3 -1
- package/src/control/variant-files.mjs +2 -2
- package/src/hook-plan.mjs +1 -1
- package/src/install-output-detector.mjs +2 -2
- package/src/lifecycle-cli.mjs +90 -8
- package/src/skill-paths.mjs +1 -1
- package/src/source-cache.mjs +1 -1
- package/src/source-profiles.mjs +4 -4
- package/src/source-verification.mjs +1 -1
- package/src/workspace.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.10 — 2026-07-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `sudo npm install -g agent-skillboard` now resolves `SUDO_USER` during
|
|
10
|
+
install-time setup so the SkillBoard guidance skill is written to the
|
|
11
|
+
invoking user's agent homes instead of `/root`.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Install docs and CLI help now describe both normal global npm installs and
|
|
16
|
+
sudo/system npm installs, including the distinction between agent-home setup
|
|
17
|
+
and npm's executable prefix.
|
|
18
|
+
|
|
19
|
+
## 0.2.9 — 2026-07-03
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Lowered the published runtime engine requirement from Node.js `>=20` to
|
|
24
|
+
`>=14.21` after validating CLI smoke on Node 14.21 and 16.20 plus the full
|
|
25
|
+
test suite on Node 18.19.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Replaced runtime use of `node:readline/promises` and
|
|
30
|
+
`String.prototype.replaceAll` so the CLI can load and run on older Node.js
|
|
31
|
+
versions without npm `EBADENGINE` warnings on common system Node installs.
|
|
32
|
+
|
|
5
33
|
## 0.2.8 — 2026-07-03
|
|
6
34
|
|
|
7
35
|
### Added
|
package/README.md
CHANGED
|
@@ -93,6 +93,12 @@ AI/automation/operator details:
|
|
|
93
93
|
npm install -g agent-skillboard
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
If your system npm requires elevated permissions, `sudo npm install -g
|
|
97
|
+
agent-skillboard` is also supported. In that flow, install-time setup resolves
|
|
98
|
+
`SUDO_USER` and writes the user-level guidance skill under the invoking user's
|
|
99
|
+
agent homes, while the `skillboard` binary still lands in the global prefix used
|
|
100
|
+
by that npm command.
|
|
101
|
+
|
|
96
102
|
The install-time setup writes a user-level `skillboard` guidance skill under
|
|
97
103
|
detected agent homes. For Codex, detection includes `CODEX_HOME/skills`,
|
|
98
104
|
`AGENTS_HOME/skills`, `~/.agents/skills`, and `~/.codex/skills`.
|
package/docs/install.md
CHANGED
|
@@ -19,12 +19,27 @@ Codex, Claude, OpenCode, and Hermes user skill roots. The setup is best-effort:
|
|
|
19
19
|
it never fails the package install, does not edit agent config files, and does
|
|
20
20
|
not create project policy files.
|
|
21
21
|
|
|
22
|
+
The published CLI supports Node.js 14.21 or newer. Node 12 and older are not
|
|
23
|
+
supported without a transpiled bundle because the source uses modern ESM and
|
|
24
|
+
syntax such as nullish coalescing.
|
|
25
|
+
|
|
22
26
|
AI/automation/operator details:
|
|
23
27
|
|
|
24
28
|
```bash
|
|
25
29
|
npm install -g agent-skillboard
|
|
26
30
|
```
|
|
27
31
|
|
|
32
|
+
If your system npm requires elevated permissions, this is also supported:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
sudo npm install -g agent-skillboard
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Under sudo, the postinstall setup resolves `SUDO_USER` and targets that user's
|
|
39
|
+
agent homes instead of writing guidance under `/root`. The executable prefix is
|
|
40
|
+
still decided by the npm command you run, so use the same npm/Node environment
|
|
41
|
+
you expect to provide `skillboard` on `PATH`.
|
|
42
|
+
|
|
28
43
|
The install-time setup writes a user-level SkillBoard guidance skill under
|
|
29
44
|
detected agent homes such as `CODEX_HOME`, `AGENTS_HOME`, `CLAUDE_HOME`,
|
|
30
45
|
`OPENCODE_HOME`, and `HERMES_HOME`. Codex detection also checks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-skillboard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Let AI agents pick and use allowed skills in each workflow.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"yaml": "^2.8.1"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
55
|
+
"node": ">=14.21"
|
|
56
56
|
},
|
|
57
57
|
"license": "MIT",
|
|
58
58
|
"devDependencies": {
|
|
@@ -70,5 +70,5 @@ function emptyApplication(blockedReason) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function shellQuote(value) {
|
|
73
|
-
return /^[A-Za-z0-9_./:=@-]+$/.test(value) ? value : `'${value.
|
|
73
|
+
return /^[A-Za-z0-9_./:=@-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
74
74
|
}
|
|
@@ -43,7 +43,7 @@ export function userHermesUnit(path, home) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function hermesProfileUnit(path, home) {
|
|
46
|
-
const profile = safeSegment(basename(dirname(path))).
|
|
46
|
+
const profile = safeSegment(basename(dirname(path))).replace(/\./g, "-");
|
|
47
47
|
return userSkillUnit(`hermes.profile.${profile}.skills`, path, home);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -55,7 +55,7 @@ export function customUserUnit(path, home) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function isHermesProfileSkillsPath(path) {
|
|
58
|
-
const normalized = path.
|
|
58
|
+
const normalized = path.replace(/\\/g, "/");
|
|
59
59
|
return /\/\.hermes\/profiles\/[^/]+\/skills$/u.test(normalized);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -72,7 +72,7 @@ export function displayPath(path, home) {
|
|
|
72
72
|
return "~";
|
|
73
73
|
}
|
|
74
74
|
if (!rel.startsWith("..") && !isAbsolute(rel)) {
|
|
75
|
-
return `~/${rel.
|
|
75
|
+
return `~/${rel.replace(/\\/g, "/")}`;
|
|
76
76
|
}
|
|
77
77
|
return resolvedPath;
|
|
78
78
|
}
|
package/src/agent-inventory.mjs
CHANGED
|
@@ -242,7 +242,7 @@ function localWorkflowTarget(unit) {
|
|
|
242
242
|
const profile = unit.id.slice("hermes.profile.".length, -".skills".length);
|
|
243
243
|
return { harness: "hermes", workflow: `hermes-${profile}-local-manual` };
|
|
244
244
|
}
|
|
245
|
-
const base = safeSegment(unit?.id ?? "local").
|
|
245
|
+
const base = safeSegment(unit?.id ?? "local").replace(/\./g, "-");
|
|
246
246
|
return { harness: "local", workflow: `${base}-local-manual` };
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -495,7 +495,7 @@ async function findSkillFiles(root, base, options, seen = new Set()) {
|
|
|
495
495
|
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
496
496
|
for (const entry of entries) {
|
|
497
497
|
const path = join(root, entry.name);
|
|
498
|
-
const rel = relative(base, path).
|
|
498
|
+
const rel = relative(base, path).replace(/\\/g, "/");
|
|
499
499
|
if (options.excludeSystem === true && (rel === ".system" || rel.startsWith(".system/"))) {
|
|
500
500
|
continue;
|
|
501
501
|
}
|
|
@@ -539,7 +539,7 @@ function skillIdFor(unit, root, file, frontmatter) {
|
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
function skillPath(root, file) {
|
|
542
|
-
return relative(root, dirname(file)).
|
|
542
|
+
return relative(root, dirname(file)).replace(/\\/g, "/");
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
function skillNode(skill, defaults = {}) {
|
|
@@ -266,7 +266,7 @@ function digest(content) {
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
function resolveRelativeTarget(root, value) {
|
|
269
|
-
const relativePath = String(value).
|
|
269
|
+
const relativePath = String(value).replace(/\\/g, "/");
|
|
270
270
|
if (relativePath.trim() === "" || relativePath.includes("\0")) {
|
|
271
271
|
throw new Error("skill path must be a non-empty relative path");
|
|
272
272
|
}
|
package/src/brief-renderer.mjs
CHANGED
|
@@ -565,8 +565,8 @@ function workflowSummary(workflow) {
|
|
|
565
565
|
}
|
|
566
566
|
|
|
567
567
|
function safeText(value, maxLength = 180) {
|
|
568
|
-
const compact = String(value).
|
|
569
|
-
const withoutTicks = compact.
|
|
568
|
+
const compact = String(value).replace(/\s+/g, " ").trim();
|
|
569
|
+
const withoutTicks = compact.replace(/`/g, "'");
|
|
570
570
|
return withoutTicks.length > maxLength ? `${withoutTicks.slice(0, maxLength - 3)}...` : withoutTicks;
|
|
571
571
|
}
|
|
572
572
|
|
package/src/cli.mjs
CHANGED
|
@@ -1097,7 +1097,7 @@ function formatList(values) {
|
|
|
1097
1097
|
}
|
|
1098
1098
|
|
|
1099
1099
|
function shellQuote(value) {
|
|
1100
|
-
return /^[A-Za-z0-9_./:=@-]+$/.test(value) ? value : `'${value.
|
|
1100
|
+
return /^[A-Za-z0-9_./:=@-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
1101
1101
|
}
|
|
1102
1102
|
|
|
1103
1103
|
function readCsv(value) {
|
|
@@ -1548,7 +1548,9 @@ function helpText() {
|
|
|
1548
1548
|
"",
|
|
1549
1549
|
"After global install:",
|
|
1550
1550
|
" npm install -g agent-skillboard",
|
|
1551
|
+
" sudo npm install -g agent-skillboard is also supported when system npm requires it.",
|
|
1551
1552
|
" The package postinstall auto-runs agent-layer guidance setup for detected supported agents.",
|
|
1553
|
+
" Under sudo, setup targets SUDO_USER's agent homes while npm still controls the binary prefix.",
|
|
1552
1554
|
" Run skillboard setup later after adding another supported agent or when install scripts were skipped.",
|
|
1553
1555
|
"",
|
|
1554
1556
|
"AI/automation operations:",
|
|
@@ -35,7 +35,7 @@ export function resolveVariantSnapshotFile({ configPath, snapshotPath }) {
|
|
|
35
35
|
throw new Error(`snapshot path must stay under ${SNAPSHOT_ROOT}`);
|
|
36
36
|
}
|
|
37
37
|
return {
|
|
38
|
-
storedPath: relative(configDir, absolutePath).
|
|
38
|
+
storedPath: relative(configDir, absolutePath).replace(/\\/g, "/"),
|
|
39
39
|
absolutePath,
|
|
40
40
|
rootPath: root
|
|
41
41
|
};
|
|
@@ -213,7 +213,7 @@ function normalizeStoredSnapshotPath(value) {
|
|
|
213
213
|
if (value.includes("\0")) {
|
|
214
214
|
throw new Error("snapshot path must not contain null bytes");
|
|
215
215
|
}
|
|
216
|
-
const normalized = value.
|
|
216
|
+
const normalized = value.replace(/\\/g, "/");
|
|
217
217
|
if (normalized.startsWith("/") || normalized.startsWith("//") || /^[A-Za-z]:\//.test(normalized)) {
|
|
218
218
|
throw new Error("snapshot path must be relative to the config directory");
|
|
219
219
|
}
|
package/src/hook-plan.mjs
CHANGED
|
@@ -315,9 +315,9 @@ function normalizePath(value, root) {
|
|
|
315
315
|
|
|
316
316
|
function displayPath(path, root) {
|
|
317
317
|
if (!isAbsolute(path)) {
|
|
318
|
-
return path.
|
|
318
|
+
return path.replace(/\\/g, "/");
|
|
319
319
|
}
|
|
320
|
-
const rel = relative(root, path).
|
|
320
|
+
const rel = relative(root, path).replace(/\\/g, "/");
|
|
321
321
|
return rel.startsWith("..") ? path : rel;
|
|
322
322
|
}
|
|
323
323
|
|
package/src/lifecycle-cli.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
import { createInterface } from "node:readline
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { promisify } from "node:util";
|
|
4
6
|
import { setupAgentSkillTargets, supportedAgentNames } from "./agent-skill-roots.mjs";
|
|
5
7
|
import { initProject } from "./init.mjs";
|
|
6
8
|
import { uninstallProject } from "./uninstall.mjs";
|
|
7
9
|
|
|
8
10
|
const AGENT_INTEGRATION_START = "<!-- BEGIN SKILLBOARD AGENT INTEGRATION -->";
|
|
9
11
|
const AGENT_INTEGRATION_END = "<!-- END SKILLBOARD AGENT INTEGRATION -->";
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
10
13
|
|
|
11
14
|
export async function runInitCommand(options, stdout, runtime = defaultRuntime()) {
|
|
12
15
|
const root = resolve(options.get("dir") ?? ".");
|
|
@@ -180,13 +183,19 @@ async function promptForSetup(runtime) {
|
|
|
180
183
|
output: runtime.stdout
|
|
181
184
|
});
|
|
182
185
|
try {
|
|
183
|
-
const answer = await
|
|
186
|
+
const answer = await question(rl, "Install SkillBoard agent integration now? [y/N] ");
|
|
184
187
|
return /^(y|yes)$/i.test(answer.trim());
|
|
185
188
|
} finally {
|
|
186
189
|
rl.close();
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
192
|
|
|
193
|
+
function question(rl, prompt) {
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
rl.question(prompt, resolve);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
190
199
|
function formatList(values) {
|
|
191
200
|
return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
|
|
192
201
|
}
|
|
@@ -212,7 +221,7 @@ function defaultRuntime() {
|
|
|
212
221
|
|
|
213
222
|
async function agentSetupTargets(options, runtime) {
|
|
214
223
|
const env = runtime.env ?? process.env;
|
|
215
|
-
const home = env
|
|
224
|
+
const home = await resolveSetupHome(env, runtime);
|
|
216
225
|
const requested = readCsv(options.get("agent"));
|
|
217
226
|
const supported = new Set(supportedAgentNames());
|
|
218
227
|
const names = requested.length === 0 ? supportedAgentNames() : requested;
|
|
@@ -226,6 +235,79 @@ async function agentSetupTargets(options, runtime) {
|
|
|
226
235
|
return targets;
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
async function resolveSetupHome(env, runtime) {
|
|
239
|
+
const explicit = nonEmpty(env.SKILLBOARD_SETUP_HOME);
|
|
240
|
+
if (explicit !== null) {
|
|
241
|
+
return explicit;
|
|
242
|
+
}
|
|
243
|
+
if (shouldUseSudoUserHome(env)) {
|
|
244
|
+
const sudoHome = nonEmpty(env.SUDO_HOME)
|
|
245
|
+
?? await passwdHome(env.SUDO_USER, runtime.passwdPath ?? "/etc/passwd")
|
|
246
|
+
?? await getentHome(env.SUDO_USER, env)
|
|
247
|
+
?? await conventionalUserHome(env.SUDO_USER);
|
|
248
|
+
if (sudoHome !== null) {
|
|
249
|
+
return sudoHome;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return env.HOME ?? env.USERPROFILE;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function shouldUseSudoUserHome(env) {
|
|
256
|
+
const sudoUser = nonEmpty(env.SUDO_USER);
|
|
257
|
+
return process.platform !== "win32"
|
|
258
|
+
&& sudoUser !== null
|
|
259
|
+
&& sudoUser !== "root"
|
|
260
|
+
&& (
|
|
261
|
+
env.SUDO_UID !== undefined
|
|
262
|
+
|| env.SUDO_GID !== undefined
|
|
263
|
+
|| env.USER === "root"
|
|
264
|
+
|| env.LOGNAME === "root"
|
|
265
|
+
|| env.HOME === "/root"
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function passwdHome(user, passwdPath) {
|
|
270
|
+
const text = await readFile(passwdPath, "utf8").catch(() => "");
|
|
271
|
+
for (const line of text.split("\n")) {
|
|
272
|
+
if (line.startsWith(`${user}:`)) {
|
|
273
|
+
return nonEmpty(line.split(":")[5]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function getentHome(user, env) {
|
|
280
|
+
try {
|
|
281
|
+
const { stdout } = await execFileAsync("getent", ["passwd", user], {
|
|
282
|
+
env,
|
|
283
|
+
timeout: 1000
|
|
284
|
+
});
|
|
285
|
+
return nonEmpty(stdout.trim().split(":")[5]);
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function conventionalUserHome(user) {
|
|
292
|
+
const candidates = process.platform === "darwin"
|
|
293
|
+
? [`/Users/${user}`]
|
|
294
|
+
: [`/home/${user}`];
|
|
295
|
+
for (const candidate of candidates) {
|
|
296
|
+
if (await exists(candidate)) {
|
|
297
|
+
return candidate;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function exists(path) {
|
|
304
|
+
return access(path).then(() => true, () => false);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function nonEmpty(value) {
|
|
308
|
+
return value === undefined || value.trim() === "" ? null : value;
|
|
309
|
+
}
|
|
310
|
+
|
|
229
311
|
async function installAgentIntegration(targets) {
|
|
230
312
|
const created = [];
|
|
231
313
|
const updated = [];
|
|
@@ -299,7 +381,7 @@ ${AGENT_INTEGRATION_END}
|
|
|
299
381
|
|
|
300
382
|
function commandPrefix(runtime) {
|
|
301
383
|
const entrypoint = runtime.entrypointPath ?? "";
|
|
302
|
-
const normalized = entrypoint.
|
|
384
|
+
const normalized = entrypoint.replace(/\\/g, "/");
|
|
303
385
|
if (normalized.includes("/_npx/")) {
|
|
304
386
|
const packageSpec = runtime.packageSpec ?? "agent-skillboard";
|
|
305
387
|
return `npx --yes --package ${shellQuote(packageSpec)} skillboard`;
|
|
@@ -314,7 +396,7 @@ function isSourceTreeEntrypoint(entrypoint) {
|
|
|
314
396
|
if (entrypoint === "") {
|
|
315
397
|
return false;
|
|
316
398
|
}
|
|
317
|
-
const normalized = entrypoint.
|
|
399
|
+
const normalized = entrypoint.replace(/\\/g, "/");
|
|
318
400
|
return (normalized === "bin/skillboard.mjs" || normalized.endsWith("/bin/skillboard.mjs"))
|
|
319
401
|
&& !normalized.includes("/node_modules/")
|
|
320
402
|
&& !normalized.includes("/_npx/")
|
|
@@ -323,7 +405,7 @@ function isSourceTreeEntrypoint(entrypoint) {
|
|
|
323
405
|
|
|
324
406
|
function sourceTreeEntrypoint(entrypoint, cwd) {
|
|
325
407
|
const absoluteEntrypoint = isAbsolute(entrypoint) ? entrypoint : resolve(cwd, entrypoint);
|
|
326
|
-
const relativeEntrypoint = relative(cwd, absoluteEntrypoint).
|
|
408
|
+
const relativeEntrypoint = relative(cwd, absoluteEntrypoint).replace(/\\/g, "/");
|
|
327
409
|
if (!relativeEntrypoint.startsWith("../") && relativeEntrypoint !== ".." && !isAbsolute(relativeEntrypoint)) {
|
|
328
410
|
return shellQuote(relativeEntrypoint);
|
|
329
411
|
}
|
|
@@ -334,5 +416,5 @@ function shellQuote(value) {
|
|
|
334
416
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
|
|
335
417
|
return value;
|
|
336
418
|
}
|
|
337
|
-
return `'${value.
|
|
419
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
338
420
|
}
|
package/src/skill-paths.mjs
CHANGED
|
@@ -5,7 +5,7 @@ export function normalizeSkillPath(value, label = "skill path") {
|
|
|
5
5
|
if (value.includes("\0")) {
|
|
6
6
|
throw new Error(`${label} must not contain null bytes`);
|
|
7
7
|
}
|
|
8
|
-
const normalized = value.
|
|
8
|
+
const normalized = value.replace(/\\/g, "/");
|
|
9
9
|
if (normalized.startsWith("/") || normalized.startsWith("//") || /^[A-Za-z]:\//.test(normalized)) {
|
|
10
10
|
throw new Error(`${label} must be relative to the skills root`);
|
|
11
11
|
}
|
package/src/source-cache.mjs
CHANGED
|
@@ -164,7 +164,7 @@ function resolveUnderRoot(root, path) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
function relativePath(root, path) {
|
|
167
|
-
const rel = relative(root, path).
|
|
167
|
+
const rel = relative(root, path).replace(/\\/g, "/");
|
|
168
168
|
return rel.startsWith("..") ? path : rel;
|
|
169
169
|
}
|
|
170
170
|
|
package/src/source-profiles.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export async function importSource(options) {
|
|
|
14
14
|
const matchedFiles = files
|
|
15
15
|
.map((file) => ({
|
|
16
16
|
file,
|
|
17
|
-
relativeFile: relative(sourceRoot, file).
|
|
17
|
+
relativeFile: relative(sourceRoot, file).replace(/\\/g, "/")
|
|
18
18
|
}))
|
|
19
19
|
.filter((entry) => matchesAnyProfilePattern(entry.relativeFile, profile.skillPaths))
|
|
20
20
|
.sort((left, right) => left.relativeFile.localeCompare(right.relativeFile));
|
|
@@ -238,9 +238,9 @@ function matchingPathRule(file, profile) {
|
|
|
238
238
|
function patternToRegExp(pattern) {
|
|
239
239
|
const escaped = pattern
|
|
240
240
|
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
241
|
-
.
|
|
242
|
-
.
|
|
243
|
-
.
|
|
241
|
+
.split("**").join("\0")
|
|
242
|
+
.split("*").join("[^/]*")
|
|
243
|
+
.split("\0").join(".*");
|
|
244
244
|
return new RegExp(`^${escaped}$`);
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -165,7 +165,7 @@ export async function sourceDigest(path) {
|
|
|
165
165
|
|
|
166
166
|
async function addPathDigest(hash, root, path) {
|
|
167
167
|
const stats = await lstat(path);
|
|
168
|
-
const rel = relative(root, path).
|
|
168
|
+
const rel = relative(root, path).replace(/\\/g, "/") || ".";
|
|
169
169
|
if (stats.isSymbolicLink()) {
|
|
170
170
|
hash.update(`symlink\0${rel}\0${await readlink(path)}\n`);
|
|
171
171
|
return;
|
package/src/workspace.mjs
CHANGED
|
@@ -51,7 +51,7 @@ async function discoverInstalledSkills(skillsRoot, declaredSkills, options = {})
|
|
|
51
51
|
const skillFiles = await findSkillFiles(skillsRoot);
|
|
52
52
|
for (const file of skillFiles) {
|
|
53
53
|
const frontmatter = parseSkillFrontmatter(await readFile(file, "utf8"));
|
|
54
|
-
const path = relative(skillsRoot, file).
|
|
54
|
+
const path = relative(skillsRoot, file).replace(/\\/g, "/").replace(/\/SKILL\.md$/, "");
|
|
55
55
|
const declared = declaredSkills.find((skill) => skill.path === path);
|
|
56
56
|
appendInstalledSkill(installed, installedKeys, {
|
|
57
57
|
id: declared?.id ?? frontmatter.name ?? path,
|