agent-skillboard 0.2.9 → 0.2.11
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 +22 -0
- package/README.md +8 -0
- package/docs/install.md +14 -0
- package/package.json +1 -1
- package/src/agent-skill-roots.mjs +9 -4
- package/src/cli.mjs +2 -0
- package/src/lifecycle-cli.mjs +78 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.11 — 2026-07-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Codex setup now creates and installs into `~/.agents/skills` when the user
|
|
10
|
+
already has a `~/.agents` home, even if `~/.agents/skills` did not exist yet
|
|
11
|
+
and `~/.codex/skills` was also detected.
|
|
12
|
+
|
|
13
|
+
## 0.2.10 — 2026-07-03
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- `sudo npm install -g agent-skillboard` now resolves `SUDO_USER` during
|
|
18
|
+
install-time setup so the SkillBoard guidance skill is written to the
|
|
19
|
+
invoking user's agent homes instead of `/root`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Install docs and CLI help now describe both normal global npm installs and
|
|
24
|
+
sudo/system npm installs, including the distinction between agent-home setup
|
|
25
|
+
and npm's executable prefix.
|
|
26
|
+
|
|
5
27
|
## 0.2.9 — 2026-07-03
|
|
6
28
|
|
|
7
29
|
### Changed
|
package/README.md
CHANGED
|
@@ -93,9 +93,17 @@ 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`.
|
|
105
|
+
If `~/.agents` already exists, setup creates `~/.agents/skills` because that is
|
|
106
|
+
the shared Codex-visible skill tree in LazyCodex-style environments.
|
|
99
107
|
It does not create `skillboard.config.yaml`,
|
|
100
108
|
`.skillboard/`, `AGENTS.md`, or `CLAUDE.md` in projects.
|
|
101
109
|
No separate setup command is required after a normal global install.
|
package/docs/install.md
CHANGED
|
@@ -29,10 +29,24 @@ AI/automation/operator details:
|
|
|
29
29
|
npm install -g agent-skillboard
|
|
30
30
|
```
|
|
31
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
|
+
|
|
32
43
|
The install-time setup writes a user-level SkillBoard guidance skill under
|
|
33
44
|
detected agent homes such as `CODEX_HOME`, `AGENTS_HOME`, `CLAUDE_HOME`,
|
|
34
45
|
`OPENCODE_HOME`, and `HERMES_HOME`. Codex detection also checks
|
|
35
46
|
`~/.agents/skills` and `~/.codex/skills`.
|
|
47
|
+
If `~/.agents` exists but `~/.agents/skills` does not, setup creates the
|
|
48
|
+
`skills` directory and installs the guidance skill there so Codex profiles that
|
|
49
|
+
read the shared agent skill tree can see SkillBoard after restart.
|
|
36
50
|
No separate setup command is required after a normal global install.
|
|
37
51
|
|
|
38
52
|
Run `skillboard setup` later when you add another supported agent, enable a new
|
package/package.json
CHANGED
|
@@ -12,7 +12,11 @@ export async function detectedAgentSkillRoots(agent, home = homedir(), env = pro
|
|
|
12
12
|
const candidates = await agentSkillRootCandidates(agent, home, env);
|
|
13
13
|
const detected = [];
|
|
14
14
|
for (const candidate of candidates) {
|
|
15
|
-
if (
|
|
15
|
+
if (
|
|
16
|
+
candidate.explicit
|
|
17
|
+
|| await exists(candidate.skillRoot)
|
|
18
|
+
|| candidate.detectWhenExists !== undefined && await exists(candidate.detectWhenExists)
|
|
19
|
+
) {
|
|
16
20
|
detected.push(candidate);
|
|
17
21
|
}
|
|
18
22
|
}
|
|
@@ -41,10 +45,11 @@ export async function agentSkillRootCandidates(agent, home = homedir(), env = pr
|
|
|
41
45
|
const normalized = normalizeAgent(agent);
|
|
42
46
|
const xdgConfig = env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
43
47
|
if (normalized === "codex") {
|
|
48
|
+
const agentsHome = join(home, ".agents");
|
|
44
49
|
return uniqueCandidates([
|
|
45
50
|
env.CODEX_HOME === undefined ? null : candidate(join(env.CODEX_HOME, "skills"), "CODEX_HOME/skills", true, false),
|
|
46
51
|
env.AGENTS_HOME === undefined ? null : candidate(join(env.AGENTS_HOME, "skills"), "AGENTS_HOME/skills", true, false),
|
|
47
|
-
candidate(join(
|
|
52
|
+
candidate(join(agentsHome, "skills"), "~/.agents/skills", false, false, agentsHome),
|
|
48
53
|
candidate(join(home, ".codex", "skills"), "~/.codex/skills", false, true)
|
|
49
54
|
]);
|
|
50
55
|
}
|
|
@@ -79,8 +84,8 @@ function normalizeAgent(agent) {
|
|
|
79
84
|
return agent;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
function candidate(skillRoot, source, explicit, fallback) {
|
|
83
|
-
return { skillRoot, source, explicit, fallback };
|
|
87
|
+
function candidate(skillRoot, source, explicit, fallback, detectWhenExists) {
|
|
88
|
+
return { skillRoot, source, explicit, fallback, detectWhenExists };
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
function fallbackCandidate(candidates) {
|
package/src/cli.mjs
CHANGED
|
@@ -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:",
|
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
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") ?? ".");
|
|
@@ -218,7 +221,7 @@ function defaultRuntime() {
|
|
|
218
221
|
|
|
219
222
|
async function agentSetupTargets(options, runtime) {
|
|
220
223
|
const env = runtime.env ?? process.env;
|
|
221
|
-
const home = env
|
|
224
|
+
const home = await resolveSetupHome(env, runtime);
|
|
222
225
|
const requested = readCsv(options.get("agent"));
|
|
223
226
|
const supported = new Set(supportedAgentNames());
|
|
224
227
|
const names = requested.length === 0 ? supportedAgentNames() : requested;
|
|
@@ -232,6 +235,79 @@ async function agentSetupTargets(options, runtime) {
|
|
|
232
235
|
return targets;
|
|
233
236
|
}
|
|
234
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
|
+
|
|
235
311
|
async function installAgentIntegration(targets) {
|
|
236
312
|
const created = [];
|
|
237
313
|
const updated = [];
|