agent-skillboard 0.2.9 → 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 CHANGED
@@ -2,6 +2,20 @@
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
+
5
19
  ## 0.2.9 — 2026-07-03
6
20
 
7
21
  ### Changed
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
@@ -29,6 +29,17 @@ 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skillboard",
3
- "version": "0.2.9",
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",
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 = [];