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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skillboard",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Let AI agents pick and use allowed skills in each workflow.",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -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 (candidate.explicit || await exists(candidate.skillRoot)) {
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(home, ".agents", "skills"), "~/.agents/skills", false, false),
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:",
@@ -1,12 +1,15 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
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.HOME ?? env.USERPROFILE;
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 = [];