spur-board 0.6.0 → 0.6.1
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/dist/cli.mjs +124 -81
- package/dist/{client-Dw9ZovyI.mjs → client-Ln76BdDz.mjs} +1 -1
- package/dist/client.mjs +1 -1
- package/dist/{config-CeDZQlvy.mjs → config-B_6fA4Au.mjs} +2 -2
- package/dist/daemon.mjs +1 -1
- package/dist/mcp.mjs +3 -3
- package/dist/{src-Cy74ZQfR.mjs → src-BFgTh55w.mjs} +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { B as SLACK_BOT_SCOPES, R as configSchema, s as OPERATIONS, z as requiredSecretKeys } from "./src-
|
|
2
|
-
import { a as renderCliError, i as errorExitCode, n as requestTokenEnrollment, o as escapeTerminalControls, r as CliError, t as requestOperation } from "./client-
|
|
3
|
-
import { i as resolveDaemonAddress, n as loadClientConfig, r as loadEnrollmentConfig, t as configFile } from "./config-
|
|
1
|
+
import { B as SLACK_BOT_SCOPES, R as configSchema, s as OPERATIONS, z as requiredSecretKeys } from "./src-BFgTh55w.mjs";
|
|
2
|
+
import { a as renderCliError, i as errorExitCode, n as requestTokenEnrollment, o as escapeTerminalControls, r as CliError, t as requestOperation } from "./client-Ln76BdDz.mjs";
|
|
3
|
+
import { i as resolveDaemonAddress, n as loadClientConfig, r as loadEnrollmentConfig, t as configFile } from "./config-B_6fA4Au.mjs";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { chmodSync, closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir, hostname, userInfo } from "node:os";
|
|
7
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
8
8
|
import { Command, CommanderError } from "@commander-js/extra-typings";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import YAML from "yaml";
|
|
@@ -401,8 +401,6 @@ function renderUserUnit(paths) {
|
|
|
401
401
|
return [
|
|
402
402
|
"[Unit]",
|
|
403
403
|
"Description=spur-board daemon",
|
|
404
|
-
"After=network-online.target",
|
|
405
|
-
"Wants=network-online.target",
|
|
406
404
|
"",
|
|
407
405
|
"[Service]",
|
|
408
406
|
"Type=simple",
|
|
@@ -613,14 +611,48 @@ async function targetDirectory(path) {
|
|
|
613
611
|
throw error;
|
|
614
612
|
}
|
|
615
613
|
}
|
|
616
|
-
async function
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
614
|
+
async function resolvedParent(path) {
|
|
615
|
+
let entry;
|
|
616
|
+
try {
|
|
617
|
+
entry = await lstat(path);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
620
|
+
const ancestor = await resolvedParent(dirname(path));
|
|
621
|
+
const parent = join(ancestor.parent, basename(path));
|
|
622
|
+
return {
|
|
623
|
+
parent,
|
|
624
|
+
missing: [...ancestor.missing, parent]
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
throw error;
|
|
628
|
+
}
|
|
629
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) throw new Error(`Skill install parent must be a directory: ${path}`);
|
|
630
|
+
const canonical = await realpath(path);
|
|
631
|
+
if (!(await stat(canonical)).isDirectory()) throw new Error(`Skill install parent must be a directory: ${path}`);
|
|
632
|
+
return {
|
|
633
|
+
parent: canonical,
|
|
634
|
+
missing: []
|
|
635
|
+
};
|
|
621
636
|
}
|
|
622
|
-
async function
|
|
623
|
-
const
|
|
637
|
+
async function createMissingParents(paths) {
|
|
638
|
+
for (const path of paths) {
|
|
639
|
+
try {
|
|
640
|
+
await mkdir(path, { mode: 448 });
|
|
641
|
+
} catch (error) {
|
|
642
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
643
|
+
}
|
|
644
|
+
const entry = await lstat(path);
|
|
645
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || await realpath(path) !== path) throw new Error(`Skill install parent must be a directory, not a new symlink: ${path}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async function withInstallLock(home, env, action) {
|
|
649
|
+
const configured = env["XDG_STATE_HOME"];
|
|
650
|
+
const stateDir = join(configured && isAbsolute(configured) ? configured : join(home, ".local", "state"), "spur-board");
|
|
651
|
+
await mkdir(stateDir, {
|
|
652
|
+
recursive: true,
|
|
653
|
+
mode: 448
|
|
654
|
+
});
|
|
655
|
+
const fd = openSync(join(stateDir, "skills-install.lock"), constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW, 384);
|
|
624
656
|
try {
|
|
625
657
|
await new Promise((resolveLock, reject) => {
|
|
626
658
|
const child = spawn("flock", [
|
|
@@ -702,88 +734,96 @@ async function recoverSkill(parent, destination, name) {
|
|
|
702
734
|
await rm(owner);
|
|
703
735
|
}
|
|
704
736
|
}
|
|
705
|
-
async function installIntoHome(home, packageRoot, version, options) {
|
|
706
|
-
|
|
737
|
+
async function installIntoHome(home, packageRoot, version, options, env) {
|
|
738
|
+
const targets = await Promise.all(agents.map(async (agent) => {
|
|
739
|
+
const configured = agent === ".codex" ? env["CODEX_HOME"] : void 0;
|
|
740
|
+
return {
|
|
741
|
+
agent,
|
|
742
|
+
...await resolvedParent(join(configured ? resolve(configured) : join(home, agent), "skills"))
|
|
743
|
+
};
|
|
744
|
+
}));
|
|
745
|
+
for (const { parent } of targets) for (const skill of skills) await targetDirectory(join(parent, skill.name));
|
|
707
746
|
const installedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
747
|
+
for (const { parent, missing } of targets) {
|
|
748
|
+
await createMissingParents(missing);
|
|
749
|
+
if (await realpath(parent) !== parent) throw new Error(`Skill install parent changed after preflight: ${parent}`);
|
|
750
|
+
}
|
|
708
751
|
const results = [];
|
|
709
|
-
for (const agent of
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
});
|
|
746
|
-
|
|
752
|
+
for (const { agent, parent } of targets) for (const skill of skills) {
|
|
753
|
+
const destination = join(parent, skill.name);
|
|
754
|
+
await recoverSkill(parent, destination, skill.name);
|
|
755
|
+
const changed = !await currentVersion(destination, version);
|
|
756
|
+
if (changed) {
|
|
757
|
+
await targetDirectory(destination);
|
|
758
|
+
const stageId = randomUUID();
|
|
759
|
+
const staged = join(parent, `.${skill.name}-stage-${stageId}`);
|
|
760
|
+
const owner = `${staged}.owner`;
|
|
761
|
+
await writeFile(owner, `spur-board stage ${stageId}\n`, {
|
|
762
|
+
flag: "wx",
|
|
763
|
+
mode: 384
|
|
764
|
+
});
|
|
765
|
+
let stagedCreated = false;
|
|
766
|
+
try {
|
|
767
|
+
await mkdir(staged, { mode: 448 });
|
|
768
|
+
stagedCreated = true;
|
|
769
|
+
const source = join(packageRoot, "skills", skill.source);
|
|
770
|
+
if (options.copySkill) await options.copySkill(source, staged);
|
|
771
|
+
else await cp(source, staged, { recursive: true });
|
|
772
|
+
const stamp = {
|
|
773
|
+
version,
|
|
774
|
+
installedAt
|
|
775
|
+
};
|
|
776
|
+
await writeFile(join(staged, stampName), `${JSON.stringify(stamp, null, 2)}\n`);
|
|
777
|
+
if (await targetDirectory(destination)) {
|
|
778
|
+
const backupId = randomUUID();
|
|
779
|
+
const backup = join(parent, `.${skill.name}-backup-${backupId}`);
|
|
780
|
+
const backupOwner = `${backup}.owner`;
|
|
781
|
+
const original = await lstat(destination, { bigint: true });
|
|
782
|
+
await writeFile(backupOwner, `spur-board backup ${backupId} ${`${String(original.dev)}:${String(original.ino)}`}\n`, {
|
|
783
|
+
flag: "wx",
|
|
784
|
+
mode: 384
|
|
785
|
+
});
|
|
786
|
+
let backupContainsSkill = false;
|
|
787
|
+
try {
|
|
788
|
+
if (await targetDirectory(backup)) throw new Error(`Skill backup path already exists: ${backup}`);
|
|
789
|
+
await rename(destination, backup);
|
|
790
|
+
backupContainsSkill = true;
|
|
747
791
|
try {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
try {
|
|
752
|
-
await rename(staged, destination);
|
|
753
|
-
} catch (error) {
|
|
754
|
-
await rename(backup, destination);
|
|
755
|
-
backupContainsSkill = false;
|
|
756
|
-
throw error;
|
|
757
|
-
}
|
|
758
|
-
await rm(backup, { recursive: true });
|
|
792
|
+
await rename(staged, destination);
|
|
793
|
+
} catch (error) {
|
|
794
|
+
await rename(backup, destination);
|
|
759
795
|
backupContainsSkill = false;
|
|
760
|
-
|
|
761
|
-
if (!backupContainsSkill) await rm(backupOwner);
|
|
796
|
+
throw error;
|
|
762
797
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
798
|
+
await rm(backup, { recursive: true });
|
|
799
|
+
backupContainsSkill = false;
|
|
800
|
+
} finally {
|
|
801
|
+
if (!backupContainsSkill) await rm(backupOwner);
|
|
802
|
+
}
|
|
803
|
+
} else await rename(staged, destination);
|
|
804
|
+
} finally {
|
|
805
|
+
if (stagedCreated) await rm(staged, {
|
|
806
|
+
recursive: true,
|
|
807
|
+
force: true
|
|
808
|
+
});
|
|
809
|
+
await rm(owner);
|
|
771
810
|
}
|
|
772
|
-
results.push({
|
|
773
|
-
agent: agent === ".claude" ? "claude" : "codex",
|
|
774
|
-
skill: skill.name,
|
|
775
|
-
changed
|
|
776
|
-
});
|
|
777
811
|
}
|
|
812
|
+
results.push({
|
|
813
|
+
agent: agent === ".claude" ? "claude" : "codex",
|
|
814
|
+
skill: skill.name,
|
|
815
|
+
changed
|
|
816
|
+
});
|
|
778
817
|
}
|
|
779
818
|
return results;
|
|
780
819
|
}
|
|
781
820
|
async function installSkills(options = {}) {
|
|
782
821
|
const packageRoot = resolve(options.packageRoot ?? fileURLToPath(new URL("../", import.meta.url)));
|
|
783
822
|
const home = await realpath(resolve(options.home ?? homedir()));
|
|
823
|
+
const env = options.env ?? process.env;
|
|
784
824
|
const version = await packageVersion(packageRoot);
|
|
785
825
|
await Promise.all(skills.map((skill) => requireSource(join(packageRoot, "skills", skill.source))));
|
|
786
|
-
return withInstallLock(home, () => installIntoHome(home, packageRoot, version, options));
|
|
826
|
+
return withInstallLock(home, env, () => installIntoHome(home, packageRoot, version, options, env));
|
|
787
827
|
}
|
|
788
828
|
//#endregion
|
|
789
829
|
//#region src/cli.ts
|
|
@@ -968,7 +1008,10 @@ function registerSetupCommands(program, options, io, env) {
|
|
|
968
1008
|
if (checks.some((check) => check.blocking)) throw new CommanderError(1, "board.error", "Doctor found blocking failures.");
|
|
969
1009
|
});
|
|
970
1010
|
commandFor(program, "skills install").description("Install bundled skills for Claude Code and Codex.").action(async () => {
|
|
971
|
-
print(await runSetup(() => installSkills(
|
|
1011
|
+
print(await runSetup(() => installSkills({
|
|
1012
|
+
...options.setup,
|
|
1013
|
+
env
|
|
1014
|
+
})));
|
|
972
1015
|
});
|
|
973
1016
|
commandFor(program, "service install").description("Install and start the systemd user service.").action(async () => {
|
|
974
1017
|
print(await runSetup(() => installService(serviceOptions)));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as errorContextSchema, N as errorEnvelopeSchema, P as successEnvelopeSchema, j as ERRORS, l as renderJsonError } from "./src-
|
|
1
|
+
import { M as errorContextSchema, N as errorEnvelopeSchema, P as successEnvelopeSchema, j as ERRORS, l as renderJsonError } from "./src-BFgTh55w.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { request } from "node:http";
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as requestTokenEnrollment, t as requestOperation } from "./client-
|
|
1
|
+
import { n as requestTokenEnrollment, t as requestOperation } from "./client-Ln76BdDz.mjs";
|
|
2
2
|
export { requestOperation, requestTokenEnrollment };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as configSchema } from "./src-
|
|
2
|
-
import { r as CliError } from "./client-
|
|
1
|
+
import { R as configSchema } from "./src-BFgTh55w.mjs";
|
|
2
|
+
import { r as CliError } from "./client-Ln76BdDz.mjs";
|
|
3
3
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
package/dist/daemon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as taskKeySchema, B as SLACK_BOT_SCOPES, C as sessionFactsSchema, D as errorCodeOf, E as encodeActorId, L as DEFAULT_SLACK_COMMAND, O as parseActorId, P as successEnvelopeSchema, R as configSchema, S as prRefSchema, T as actorIdSchema, _ as EVENT_KINDS, a as buildCatalogSnapshot, b as SESSION_STATUSES, c as renderHttpProblem, d as SPACE_IDS, f as conversationRefSchema, g as userIdSchema, h as messageRefSchema, i as sourceCursorSchema, j as ERRORS, k as parseRef$1, m as interactionRefSchema, n as batchSchema, o as roleCanUseOperation, p as inboundEventSchema, r as entityRefSchema, s as OPERATIONS, t as AdapterError, v as eventPayloadSchemas, w as ticketFactsSchema, x as prFactsSchema, y as SESSION_HEALTH_STATES, z as requiredSecretKeys } from "./src-
|
|
1
|
+
import { A as taskKeySchema, B as SLACK_BOT_SCOPES, C as sessionFactsSchema, D as errorCodeOf, E as encodeActorId, L as DEFAULT_SLACK_COMMAND, O as parseActorId, P as successEnvelopeSchema, R as configSchema, S as prRefSchema, T as actorIdSchema, _ as EVENT_KINDS, a as buildCatalogSnapshot, b as SESSION_STATUSES, c as renderHttpProblem, d as SPACE_IDS, f as conversationRefSchema, g as userIdSchema, h as messageRefSchema, i as sourceCursorSchema, j as ERRORS, k as parseRef$1, m as interactionRefSchema, n as batchSchema, o as roleCanUseOperation, p as inboundEventSchema, r as entityRefSchema, s as OPERATIONS, t as AdapterError, v as eventPayloadSchemas, w as ticketFactsSchema, x as prFactsSchema, y as SESSION_HEALTH_STATES, z as requiredSecretKeys } from "./src-BFgTh55w.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { closeSync, existsSync, fstatSync, ftruncateSync, mkdirSync, openSync, readFileSync, readdirSync, writeSync } from "node:fs";
|
package/dist/mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { j as ERRORS, s as OPERATIONS, u as renderMcpError } from "./src-
|
|
2
|
-
import { r as CliError, t as requestOperation } from "./client-
|
|
3
|
-
import { n as loadClientConfig } from "./config-
|
|
1
|
+
import { j as ERRORS, s as OPERATIONS, u as renderMcpError } from "./src-BFgTh55w.mjs";
|
|
2
|
+
import { r as CliError, t as requestOperation } from "./client-Ln76BdDz.mjs";
|
|
3
|
+
import { n as loadClientConfig } from "./config-B_6fA4Au.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -528,7 +528,7 @@ const ERRORS = {
|
|
|
528
528
|
status: null,
|
|
529
529
|
exit: EXIT_CODES.notApplied,
|
|
530
530
|
message: "The daemon is not running or cannot be reached; nothing was applied.",
|
|
531
|
-
hint: "Ask in your own session; the operator can run `board service
|
|
531
|
+
hint: "Ask in your own session; the operator can check `board service status`, then run `board service restart`, or `board service install` if the service was never installed. Retry the same command with its existing idempotency key if it has one once the daemon is available.",
|
|
532
532
|
recovery: retry("Ask in your own session, then retry the same command with its existing idempotency key if it has one.", "After the daemon is available; wait briefly between retries.")
|
|
533
533
|
}),
|
|
534
534
|
config: spec("config", {
|