@hasna/skills 0.10.27 → 0.10.30
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/README.md +44 -1
- package/bin/index.js +1126 -921
- package/bin/mcp.js +360 -279
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +661 -471
- package/dist/lib/agent-adapters.d.ts +7 -1
- package/dist/lib/agent-sumi.d.ts +15 -0
- package/dist/lib/native-discovery-roots.d.ts +1 -1
- package/dist/lib/registry.d.ts +20 -3
- package/dist/lib/selection-resolver.d.ts +1 -0
- package/dist/lib/skill-context.d.ts +2 -0
- package/dist/sdk/index.js +653 -467
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6988,8 +6988,8 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6988
6988
|
});
|
|
6989
6989
|
|
|
6990
6990
|
// src/lib/registry.ts
|
|
6991
|
-
import { existsSync as
|
|
6992
|
-
import { join as
|
|
6991
|
+
import { existsSync as existsSync15, readFileSync as readFileSync18, readdirSync as readdirSync12 } from "fs";
|
|
6992
|
+
import { join as join31 } from "path";
|
|
6993
6993
|
|
|
6994
6994
|
// src/lib/config.ts
|
|
6995
6995
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
@@ -7252,17 +7252,17 @@ function unsetConfig(key, scope = "project") {
|
|
|
7252
7252
|
|
|
7253
7253
|
// src/lib/portable-skills.ts
|
|
7254
7254
|
import {
|
|
7255
|
-
existsSync as
|
|
7255
|
+
existsSync as existsSync14,
|
|
7256
7256
|
mkdirSync as mkdirSync9,
|
|
7257
7257
|
mkdtempSync as mkdtempSync2,
|
|
7258
7258
|
readdirSync as readdirSync11,
|
|
7259
|
-
realpathSync as
|
|
7259
|
+
realpathSync as realpathSync10,
|
|
7260
7260
|
renameSync as renameSync5,
|
|
7261
7261
|
rmSync as rmSync4,
|
|
7262
7262
|
statSync as statSync10,
|
|
7263
7263
|
writeFileSync as writeFileSync8
|
|
7264
7264
|
} from "fs";
|
|
7265
|
-
import { basename as basename11, dirname as dirname19, isAbsolute as isAbsolute17, join as
|
|
7265
|
+
import { basename as basename11, dirname as dirname19, isAbsolute as isAbsolute17, join as join30, normalize as normalize4, relative as relative6, resolve as resolve18, sep as sep8 } from "path";
|
|
7266
7266
|
|
|
7267
7267
|
// src/lib/registry-data/index.ts
|
|
7268
7268
|
var SKILLS = [];
|
|
@@ -8904,6 +8904,8 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
8904
8904
|
["codex", ".codex/skills"],
|
|
8905
8905
|
["codex", ".agents/skills"],
|
|
8906
8906
|
["gemini", ".gemini/skills"],
|
|
8907
|
+
["sumi", ".sumi/skills"],
|
|
8908
|
+
["sumi", ".sumi/skill"],
|
|
8907
8909
|
["codewith", ".codewith/skills"],
|
|
8908
8910
|
["opencode", ".config/opencode/skills"],
|
|
8909
8911
|
["opencode", ".opencode/skills"],
|
|
@@ -8918,12 +8920,12 @@ var NATIVE_SKILL_ROOTS = [
|
|
|
8918
8920
|
];
|
|
8919
8921
|
|
|
8920
8922
|
// src/lib/authoring-path.ts
|
|
8921
|
-
import { lstatSync as
|
|
8923
|
+
import { lstatSync as lstatSync23, realpathSync as realpathSync9 } from "fs";
|
|
8922
8924
|
import { homedir as homedir6 } from "os";
|
|
8923
|
-
import { basename as basename10, dirname as dirname18, join as
|
|
8925
|
+
import { basename as basename10, dirname as dirname18, join as join29, resolve as resolve17, sep as sep7 } from "path";
|
|
8924
8926
|
|
|
8925
8927
|
// src/lib/agent-adapters.ts
|
|
8926
|
-
var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes"];
|
|
8928
|
+
var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes", "sumi"];
|
|
8927
8929
|
function renderAgentHookCommand(command, agent, profileId, event) {
|
|
8928
8930
|
if (typeof profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) || profileId.includes(".."))
|
|
8929
8931
|
throw new Error("Invalid selection profile id");
|
|
@@ -8934,6 +8936,7 @@ var AGENT_ADAPTERS = {
|
|
|
8934
8936
|
codex: { root: ".codex/skills", config: ".codex/hooks.json", events: ["UserPromptSubmit", "SessionStart", "SubagentStart"], promptContext: true },
|
|
8935
8937
|
gemini: { root: ".gemini/skills", config: ".gemini/settings.json", events: ["BeforeAgent", "SessionStart"], promptContext: true },
|
|
8936
8938
|
opencode: { root: ".config/opencode/skills", config: ".config/opencode/opencode.json", events: ["chat.message"], promptContext: true },
|
|
8939
|
+
sumi: { root: ".hasna-internal/sumi/config/skills", config: ".hasna-internal/sumi/config/sumi.json", events: ["prompt", "context"], promptContext: true },
|
|
8937
8940
|
hermes: { root: ".hermes/skills", config: ".hermes/config.yaml", events: ["pre_llm_call", "pre_tool_call"], promptContext: true, promptFailureMode: "open", toolFailureMode: "supervised-child-errors-block", requiresNativeTrust: true },
|
|
8938
8941
|
cursor: { root: ".cursor/skills", config: ".cursor/hooks.json", events: ["beforeSubmitPrompt", "sessionStart"], promptContext: false }
|
|
8939
8942
|
};
|
|
@@ -9139,8 +9142,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9139
9142
|
|
|
9140
9143
|
// src/lib/agent-discovery.ts
|
|
9141
9144
|
import { createHash as createHash18 } from "crypto";
|
|
9142
|
-
import { lstatSync as
|
|
9143
|
-
import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as
|
|
9145
|
+
import { lstatSync as lstatSync22, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync8 } from "fs";
|
|
9146
|
+
import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as join28, resolve as resolve16, sep as sep6 } from "path";
|
|
9144
9147
|
import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
|
|
9145
9148
|
|
|
9146
9149
|
// node_modules/yaml/dist/index.js
|
|
@@ -9652,12 +9655,12 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
9652
9655
|
}
|
|
9653
9656
|
|
|
9654
9657
|
// src/lib/plugin-discovery.ts
|
|
9655
|
-
import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as
|
|
9656
|
-
import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as
|
|
9658
|
+
import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync19, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
|
|
9659
|
+
import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join27, resolve as resolve14 } from "path";
|
|
9657
9660
|
|
|
9658
9661
|
// src/lib/plugin-admission.ts
|
|
9659
9662
|
import { homedir as homedir5 } from "os";
|
|
9660
|
-
import { isAbsolute as isAbsolute13, join as
|
|
9663
|
+
import { isAbsolute as isAbsolute13, join as join26, resolve as resolve13 } from "path";
|
|
9661
9664
|
|
|
9662
9665
|
// src/lib/selection-cache.ts
|
|
9663
9666
|
import { createHash as createHash6, randomUUID } from "crypto";
|
|
@@ -10914,7 +10917,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
10914
10917
|
// package.json
|
|
10915
10918
|
var package_default = {
|
|
10916
10919
|
name: "@hasna/skills",
|
|
10917
|
-
version: "0.10.
|
|
10920
|
+
version: "0.10.30",
|
|
10918
10921
|
description: "Skills library for AI coding agents",
|
|
10919
10922
|
type: "module",
|
|
10920
10923
|
bin: {
|
|
@@ -12724,19 +12727,19 @@ async function prepareSkillDependencies(skillPath, env, timeoutMs = DEFAULT_PREP
|
|
|
12724
12727
|
}
|
|
12725
12728
|
|
|
12726
12729
|
// src/lib/skillinfo.ts
|
|
12727
|
-
import { existsSync as
|
|
12728
|
-
import { join as
|
|
12730
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
12731
|
+
import { join as join23 } from "path";
|
|
12729
12732
|
|
|
12730
12733
|
// src/lib/installer.ts
|
|
12731
|
-
import { existsSync as
|
|
12732
|
-
import { join as
|
|
12734
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15, rmSync as rmSync2 } from "fs";
|
|
12735
|
+
import { join as join21 } from "path";
|
|
12733
12736
|
import { homedir as homedir4 } from "os";
|
|
12734
12737
|
|
|
12735
12738
|
// src/lib/agent-sync.ts
|
|
12736
12739
|
import {
|
|
12737
12740
|
cpSync as cpSync2,
|
|
12738
|
-
existsSync as
|
|
12739
|
-
lstatSync as
|
|
12741
|
+
existsSync as existsSync10,
|
|
12742
|
+
lstatSync as lstatSync17,
|
|
12740
12743
|
mkdirSync as mkdirSync6,
|
|
12741
12744
|
mkdtempSync,
|
|
12742
12745
|
readFileSync as readFileSync13,
|
|
@@ -12747,7 +12750,7 @@ import {
|
|
|
12747
12750
|
writeFileSync as writeFileSync5
|
|
12748
12751
|
} from "fs";
|
|
12749
12752
|
import { homedir as homedir3 } from "os";
|
|
12750
|
-
import { basename as basename6, dirname as dirname12, join as
|
|
12753
|
+
import { basename as basename6, dirname as dirname12, join as join19 } from "path";
|
|
12751
12754
|
|
|
12752
12755
|
// src/lib/codex-local-mcp-controls.ts
|
|
12753
12756
|
function isCodexLocalStdioMcpServer(name, server) {
|
|
@@ -14050,12 +14053,14 @@ function disablesOneSkill(entry) {
|
|
|
14050
14053
|
return selector.length === 1 && typeof entry[selector[0]] === "string" && entry[selector[0]].length > 0;
|
|
14051
14054
|
}
|
|
14052
14055
|
function inference(target, version) {
|
|
14053
|
-
const keys = version === 3 ? [...INFERENCE, "service_tier", "plan_mode_reasoning_effort"] : INFERENCE;
|
|
14056
|
+
const keys = version === 3 ? [...INFERENCE, "service_tier", "plan_mode_reasoning_effort", "personality"] : INFERENCE;
|
|
14054
14057
|
for (const key of keys)
|
|
14055
14058
|
if (Object.hasOwn(target, key)) {
|
|
14056
14059
|
const value = target[key];
|
|
14057
14060
|
if (key === "model")
|
|
14058
14061
|
need4(typeof value === "string" && MODEL.test(value));
|
|
14062
|
+
else if (key === "personality")
|
|
14063
|
+
need4(typeof value === "string" && ["none", "friendly", "pragmatic"].includes(value));
|
|
14059
14064
|
else if (version === 3 && key !== "model_verbosity") {
|
|
14060
14065
|
need4(typeof value === "string" && value.length <= 128 && !/[\x00-\x1f\x7f]/.test(value));
|
|
14061
14066
|
if (key !== "service_tier")
|
|
@@ -14122,8 +14127,8 @@ function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
|
|
|
14122
14127
|
// src/lib/agent-integration.ts
|
|
14123
14128
|
import { createHash as createHash12, randomUUID as randomUUID2 } from "crypto";
|
|
14124
14129
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
14125
|
-
import { existsSync as
|
|
14126
|
-
import { dirname as dirname11, join as
|
|
14130
|
+
import { existsSync as existsSync9, lstatSync as lstatSync16, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants7, linkSync } from "fs";
|
|
14131
|
+
import { dirname as dirname11, join as join18, resolve as resolve11, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
|
|
14127
14132
|
import { homedir as homedir2 } from "os";
|
|
14128
14133
|
|
|
14129
14134
|
// src/lib/managed-policy.ts
|
|
@@ -14517,6 +14522,114 @@ function disableCodexBundledSkills(text2) {
|
|
|
14517
14522
|
return [...candidates][0];
|
|
14518
14523
|
}
|
|
14519
14524
|
|
|
14525
|
+
// src/lib/agent-sumi.ts
|
|
14526
|
+
import { existsSync as existsSync8, lstatSync as lstatSync15, realpathSync as realpathSync6 } from "fs";
|
|
14527
|
+
import { join as join17, resolve as resolve10 } from "path";
|
|
14528
|
+
function sumiConfigDirectory(home, env = process.env) {
|
|
14529
|
+
for (const key of ["SUMI_CONFIG", "SUMI_CONFIG_CONTENT"])
|
|
14530
|
+
if (env[key] !== undefined)
|
|
14531
|
+
throw new Error(`Sumi ${key} requires a dedicated discovery adapter; unset it before Skills integration`);
|
|
14532
|
+
const selected = (key) => env[key]?.trim() ? env[key] : undefined;
|
|
14533
|
+
const expand = (value) => resolve10(value === "~" ? home : value.startsWith("~/") ? join17(home, value.slice(2)) : value);
|
|
14534
|
+
const explicit = selected("SUMI_CONFIG_DIR");
|
|
14535
|
+
const xdg = selected("XDG_CONFIG_HOME");
|
|
14536
|
+
const custom = selected("SUMI_HOME");
|
|
14537
|
+
const canonical5 = explicit ? expand(explicit) : xdg ? join17(expand(xdg), "sumi") : join17(custom ? expand(custom) : join17(home, ".hasna-internal/sumi"), "config");
|
|
14538
|
+
if (explicit || xdg || custom)
|
|
14539
|
+
return canonical5;
|
|
14540
|
+
const legacy = join17(home, ".config/sumi");
|
|
14541
|
+
const a = lstatSync15(canonical5, { throwIfNoEntry: false }), b = lstatSync15(legacy, { throwIfNoEntry: false });
|
|
14542
|
+
if (a && b && realpathSync6(canonical5) !== realpathSync6(legacy))
|
|
14543
|
+
throw new Error("Sumi config roots conflict; complete native home adoption before Skills integration");
|
|
14544
|
+
const path = a ? canonical5 : b ? legacy : canonical5;
|
|
14545
|
+
if (!existsSync8(path))
|
|
14546
|
+
return path;
|
|
14547
|
+
const physical = realpathSync6(path);
|
|
14548
|
+
if ((a?.isSymbolicLink() || b?.isSymbolicLink()) && ![canonical5, legacy].includes(physical))
|
|
14549
|
+
throw new Error("Sumi config alias must resolve to its canonical or legacy root");
|
|
14550
|
+
return physical;
|
|
14551
|
+
}
|
|
14552
|
+
function sumiBridgeRoot(home) {
|
|
14553
|
+
return join17(sumiConfigDirectory(home), "skills");
|
|
14554
|
+
}
|
|
14555
|
+
function sumiConfigPath(home) {
|
|
14556
|
+
return join17(sumiConfigDirectory(home), "sumi.json");
|
|
14557
|
+
}
|
|
14558
|
+
function renderSumiPlugin(command, profile) {
|
|
14559
|
+
return `// Managed by @hasna/skills. Regenerate with skills hook install --agent sumi.
|
|
14560
|
+
import { spawn } from "node:child_process";
|
|
14561
|
+
const command = ${JSON.stringify(command)};
|
|
14562
|
+
const profile = ${JSON.stringify(profile)};
|
|
14563
|
+
function context(input) {
|
|
14564
|
+
return new Promise((resolve, reject) => {
|
|
14565
|
+
const child = spawn(command, ["hook", "user-prompt", "--agent", "sumi", "--selection-profile", profile], { cwd: input.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
14566
|
+
const chunks = []; let bytes = 0, failure;
|
|
14567
|
+
const timer = setTimeout(() => { failure = new Error("Skills prompt hook timed out"); child.kill("SIGKILL"); }, 15000);
|
|
14568
|
+
child.stdout.on("data", chunk => { bytes += chunk.length; if (bytes > 1048576) { failure = new Error("Skills prompt hook output exceeded its limit"); child.kill("SIGKILL"); } else chunks.push(chunk); });
|
|
14569
|
+
child.stderr.on("data", () => {});
|
|
14570
|
+
child.stdin.on("error", () => {});
|
|
14571
|
+
child.on("error", error => { clearTimeout(timer); reject(error); });
|
|
14572
|
+
child.on("close", code => {
|
|
14573
|
+
clearTimeout(timer);
|
|
14574
|
+
if (failure || code !== 0) return reject(failure ?? new Error("Skills prompt hook failed"));
|
|
14575
|
+
try {
|
|
14576
|
+
const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
14577
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) throw new Error("Invalid Skills hook response");
|
|
14578
|
+
if (result.decision === "block" || result.continue === false) throw new Error("Skills prompt hook refused");
|
|
14579
|
+
if (Object.keys(result).length && (!result.hookSpecificOutput || result.hookSpecificOutput.hookEventName !== input.hook_event_name)) throw new Error("Invalid Skills hook response");
|
|
14580
|
+
const text = result.hookSpecificOutput?.additionalContext ?? "";
|
|
14581
|
+
if (typeof text !== "string") throw new Error("Invalid Skills prompt context");
|
|
14582
|
+
resolve(text);
|
|
14583
|
+
} catch (error) { reject(error); }
|
|
14584
|
+
});
|
|
14585
|
+
child.stdin.end(JSON.stringify(input));
|
|
14586
|
+
});
|
|
14587
|
+
}
|
|
14588
|
+
export default {
|
|
14589
|
+
id: "local.hasna-skills-cli",
|
|
14590
|
+
async setup(ctx) {
|
|
14591
|
+
if (!ctx?.session?.hook || !ctx?.session?.get || !ctx?.skill?.transform || !ctx?.tool?.hook) throw new Error("Unsupported Sumi native Skills contract");
|
|
14592
|
+
const registrations = [];
|
|
14593
|
+
const sessions = new Set();
|
|
14594
|
+
async function input(sessionID, prompt, first) {
|
|
14595
|
+
// The Promise adapter unwraps single-data endpoint responses.
|
|
14596
|
+
const session = await ctx.session.get({ sessionID });
|
|
14597
|
+
if (!session || session.id !== sessionID || typeof session.location?.directory !== "string" || (session.parentID !== undefined && typeof session.parentID !== "string") || session.parentID === sessionID) throw new Error("Invalid Sumi session custody");
|
|
14598
|
+
return { hook_event_name: first ? session.parentID ? "SubagentStart" : "SessionStart" : "UserPromptSubmit", cwd: session.location.directory, session_id: sessionID, parent_session_id: session.parentID ?? null, prompt, restore: true };
|
|
14599
|
+
}
|
|
14600
|
+
registrations.push(await ctx.skill.transform(editor => {
|
|
14601
|
+
for (const skill of editor.list()) if (skill.id !== "skills-cli" || skill.name !== "skills-cli") editor.remove(skill.id);
|
|
14602
|
+
}));
|
|
14603
|
+
// Native config transforms may add skills after this transform. Guard
|
|
14604
|
+
// both direct tool calls and prompt attachments at the consumption edge.
|
|
14605
|
+
registrations.push(await ctx.tool.hook("execute.before", event => {
|
|
14606
|
+
if (event.tool === "skill" && event.input?.id !== "skills-cli") throw new Error("Native Sumi skill payload refused; use Skills CLI");
|
|
14607
|
+
}));
|
|
14608
|
+
registrations.push(await ctx.session.hook("prompt", async event => {
|
|
14609
|
+
if (typeof event.prompt?.text !== "string") throw new Error("Invalid Sumi prompt");
|
|
14610
|
+
if (event.prompt.skills !== undefined && (!Array.isArray(event.prompt.skills) || event.prompt.skills.some(skill => skill?.id !== "skills-cli"))) throw new Error("Native Sumi skill attachment refused; use Skills CLI");
|
|
14611
|
+
await context(await input(event.sessionID, event.prompt.text, !sessions.has(event.sessionID)));
|
|
14612
|
+
sessions.add(event.sessionID);
|
|
14613
|
+
}));
|
|
14614
|
+
registrations.push(await ctx.session.hook("context", async event => {
|
|
14615
|
+
const last = event.messages.findLast(message => message.role === "user");
|
|
14616
|
+
const prompt = (last?.content ?? []).filter(part => part.type === "text").map(part => part.text).join("\\n");
|
|
14617
|
+
// Recheck on every primary request, including resumed and synthetic
|
|
14618
|
+
// sessions. The CLI owns optional failures versus explicit refusals.
|
|
14619
|
+
const text = await context(await input(event.sessionID, prompt, !sessions.has(event.sessionID)));
|
|
14620
|
+
if (text) event.system.push({ type: "text", text });
|
|
14621
|
+
sessions.add(event.sessionID);
|
|
14622
|
+
}));
|
|
14623
|
+
return async () => { for (const registration of registrations) await registration.dispose(); };
|
|
14624
|
+
},
|
|
14625
|
+
};
|
|
14626
|
+
`;
|
|
14627
|
+
}
|
|
14628
|
+
var SUMI_SKILL_PERMISSIONS = [
|
|
14629
|
+
{ action: "skill", resource: "*", effect: "deny" },
|
|
14630
|
+
{ action: "skill", resource: "skills-cli", effect: "allow" }
|
|
14631
|
+
];
|
|
14632
|
+
|
|
14520
14633
|
// src/lib/agent-integration.ts
|
|
14521
14634
|
var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
|
|
14522
14635
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
@@ -14551,11 +14664,11 @@ function isSystemRootAlias(path) {
|
|
|
14551
14664
|
const target = `/private${path}`, link = `private${path}`;
|
|
14552
14665
|
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
|
|
14553
14666
|
try {
|
|
14554
|
-
const before =
|
|
14667
|
+
const before = lstatSync16(path, { bigint: true });
|
|
14555
14668
|
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
|
|
14556
14669
|
return false;
|
|
14557
14670
|
const directories = ["/", "/private", target].map((directory) => {
|
|
14558
|
-
const stat =
|
|
14671
|
+
const stat = lstatSync16(directory, { bigint: true });
|
|
14559
14672
|
if (!stat.isDirectory() || stat.uid !== 0n)
|
|
14560
14673
|
throw new Error("Untrusted system alias directory");
|
|
14561
14674
|
if (directory !== "/private/tmp") {
|
|
@@ -14572,23 +14685,23 @@ function isSystemRootAlias(path) {
|
|
|
14572
14685
|
}
|
|
14573
14686
|
return { directory, identity: identity4(stat) };
|
|
14574
14687
|
});
|
|
14575
|
-
return
|
|
14688
|
+
return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(lstatSync16(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync16(item.directory, { bigint: true })) === item.identity);
|
|
14576
14689
|
} catch {
|
|
14577
14690
|
return false;
|
|
14578
14691
|
}
|
|
14579
14692
|
}
|
|
14580
14693
|
function canonicalSystemPath(path) {
|
|
14581
|
-
const absolute =
|
|
14582
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
14694
|
+
const absolute = resolve11(path), root = `/${absolute.split(sep5)[1]}`;
|
|
14695
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync16(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14583
14696
|
return absolute;
|
|
14584
14697
|
if (!isSystemRootAlias(root))
|
|
14585
14698
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
14586
14699
|
return `/private${absolute}`;
|
|
14587
14700
|
}
|
|
14588
14701
|
function assertSafePath(path) {
|
|
14589
|
-
let cursor =
|
|
14702
|
+
let cursor = resolve11(path);
|
|
14590
14703
|
while (true) {
|
|
14591
|
-
if (
|
|
14704
|
+
if (lstatSync16(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
14592
14705
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
14593
14706
|
const parent = dirname11(cursor);
|
|
14594
14707
|
if (parent === cursor)
|
|
@@ -14598,16 +14711,16 @@ function assertSafePath(path) {
|
|
|
14598
14711
|
}
|
|
14599
14712
|
function readRootAlias(home, agent) {
|
|
14600
14713
|
assertSafePath(home);
|
|
14601
|
-
const realHome =
|
|
14602
|
-
const stat =
|
|
14714
|
+
const realHome = realpathSync7(home), alias = join18(realHome, `.${agent}`);
|
|
14715
|
+
const stat = lstatSync16(alias, { throwIfNoEntry: false, bigint: true });
|
|
14603
14716
|
if (!stat?.isSymbolicLink())
|
|
14604
14717
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
14605
|
-
const link = readlinkSync4(alias), targetPath =
|
|
14718
|
+
const link = readlinkSync4(alias), targetPath = resolve11(dirname11(alias), link);
|
|
14606
14719
|
assertSafePath(targetPath);
|
|
14607
|
-
const targetStat =
|
|
14720
|
+
const targetStat = lstatSync16(targetPath, { throwIfNoEntry: false });
|
|
14608
14721
|
if (!targetStat?.isDirectory())
|
|
14609
14722
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
14610
|
-
const target =
|
|
14723
|
+
const target = realpathSync7(targetPath), inside = relative5(realHome, target);
|
|
14611
14724
|
if (!inside || inside === ".." || inside.startsWith(`..${sep5}`) || isAbsolute11(inside))
|
|
14612
14725
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
14613
14726
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
@@ -14617,7 +14730,7 @@ function rootAliases(home, allowed = false) {
|
|
|
14617
14730
|
return [];
|
|
14618
14731
|
const result = [];
|
|
14619
14732
|
for (const agent of ["claude", "codex"])
|
|
14620
|
-
if (
|
|
14733
|
+
if (lstatSync16(join18(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14621
14734
|
result.push(readRootAlias(home, agent));
|
|
14622
14735
|
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
|
|
14623
14736
|
throw new Error("Agent root alias targets must not overlap");
|
|
@@ -14625,7 +14738,7 @@ function rootAliases(home, allowed = false) {
|
|
|
14625
14738
|
}
|
|
14626
14739
|
function canonicalAgentPath(path, aliases) {
|
|
14627
14740
|
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
|
|
14628
|
-
return binding ?
|
|
14741
|
+
return binding ? join18(binding.target, relative5(binding.alias, absolute)) : absolute;
|
|
14629
14742
|
}
|
|
14630
14743
|
function disabledCodexSkillPaths(config, aliases) {
|
|
14631
14744
|
const skills = config.skills;
|
|
@@ -14644,7 +14757,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
14644
14757
|
continue;
|
|
14645
14758
|
let document2;
|
|
14646
14759
|
try {
|
|
14647
|
-
document2 =
|
|
14760
|
+
document2 = realpathSync7(canonicalAgentPath(entry.path, aliases));
|
|
14648
14761
|
} catch {
|
|
14649
14762
|
continue;
|
|
14650
14763
|
}
|
|
@@ -14656,7 +14769,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
14656
14769
|
}
|
|
14657
14770
|
function recheckRootAliases(aliases) {
|
|
14658
14771
|
for (const binding of aliases) {
|
|
14659
|
-
if (!["claude", "codex"].includes(binding.agent) || binding.alias !==
|
|
14772
|
+
if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join18(binding.home, `.${binding.agent}`))
|
|
14660
14773
|
throw new Error("Invalid agent root alias binding");
|
|
14661
14774
|
const current = readRootAlias(binding.home, binding.agent);
|
|
14662
14775
|
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
@@ -14665,7 +14778,7 @@ function recheckRootAliases(aliases) {
|
|
|
14665
14778
|
}
|
|
14666
14779
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
14667
14780
|
assertSafePath(path);
|
|
14668
|
-
const before =
|
|
14781
|
+
const before = lstatSync16(path);
|
|
14669
14782
|
if (!before.isFile() || before.size > maximum)
|
|
14670
14783
|
throw new Error("Unsupported or oversized native skill file");
|
|
14671
14784
|
const descriptor = openSync8(path, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
|
|
@@ -14681,7 +14794,7 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
|
14681
14794
|
break;
|
|
14682
14795
|
length += count;
|
|
14683
14796
|
}
|
|
14684
|
-
const after = fstatSync8(descriptor), current =
|
|
14797
|
+
const after = fstatSync8(descriptor), current = lstatSync16(path);
|
|
14685
14798
|
if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after.size || current.mtimeMs !== after.mtimeMs || current.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
14686
14799
|
throw new Error("Native skill file changed while reading");
|
|
14687
14800
|
return bytes.subarray(0, length);
|
|
@@ -14693,14 +14806,14 @@ function treeHash(root) {
|
|
|
14693
14806
|
const hash2 = createHash12("sha256");
|
|
14694
14807
|
let bytes = 0, files = 0;
|
|
14695
14808
|
function visit2(path) {
|
|
14696
|
-
const stat =
|
|
14809
|
+
const stat = lstatSync16(path);
|
|
14697
14810
|
if (stat.isSymbolicLink())
|
|
14698
14811
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
14699
14812
|
const rel = relative5(root, path);
|
|
14700
14813
|
if (stat.isDirectory()) {
|
|
14701
14814
|
hash2.update(`d\x00${rel}\x00`);
|
|
14702
14815
|
for (const name of readdirSync8(path).sort())
|
|
14703
|
-
visit2(
|
|
14816
|
+
visit2(join18(path, name));
|
|
14704
14817
|
return;
|
|
14705
14818
|
}
|
|
14706
14819
|
if (!stat.isFile())
|
|
@@ -14722,10 +14835,10 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
14722
14835
|
const identities = new Set;
|
|
14723
14836
|
const selected = [];
|
|
14724
14837
|
for (const target of manifest.targets) {
|
|
14725
|
-
if (!isAbsolute11(target.projectRoot) ||
|
|
14838
|
+
if (!isAbsolute11(target.projectRoot) || resolve11(target.projectRoot) !== target.projectRoot)
|
|
14726
14839
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14727
|
-
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot,
|
|
14728
|
-
if (!
|
|
14840
|
+
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve11(projectRoot, target.path));
|
|
14841
|
+
if (!existsSync9(projectRoot) || !lstatSync16(projectRoot).isDirectory())
|
|
14729
14842
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14730
14843
|
assertSafePath(projectRoot);
|
|
14731
14844
|
if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
@@ -14734,8 +14847,8 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
14734
14847
|
if (identities.has(identity4))
|
|
14735
14848
|
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
14736
14849
|
identities.add(identity4);
|
|
14737
|
-
const expected = canonicalSystemPath(
|
|
14738
|
-
const matches = inventory.filter((entry2) => entry2.agent === target.agent &&
|
|
14850
|
+
const expected = canonicalSystemPath(resolve11(target.projectRoot, target.path));
|
|
14851
|
+
const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve11(entry2.path) === expected);
|
|
14739
14852
|
if (matches.length !== 1)
|
|
14740
14853
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
14741
14854
|
const entry = matches[0];
|
|
@@ -14764,9 +14877,9 @@ function projectAncestorDirectories(projects) {
|
|
|
14764
14877
|
}
|
|
14765
14878
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
14766
14879
|
home = canonicalSystemPath(home);
|
|
14767
|
-
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) ||
|
|
14880
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve11(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
14768
14881
|
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
14769
|
-
if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) ||
|
|
14882
|
+
if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve11(path) !== path))
|
|
14770
14883
|
throw new Error("Disabled vendor paths must be exact absolute paths");
|
|
14771
14884
|
const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
|
|
14772
14885
|
const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
|
|
@@ -14774,14 +14887,18 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14774
14887
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
14775
14888
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
14776
14889
|
const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS;
|
|
14777
|
-
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(
|
|
14778
|
-
const bridgePaths = Object.
|
|
14890
|
+
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join18(home, path), aliases)]);
|
|
14891
|
+
const bridgePaths = Object.entries(AGENT_ADAPTERS).filter(([agent]) => agent !== "sumi" || includesAgent("sumi")).map(([agent, adapter]) => canonicalAgentPath(join18(agent === "sumi" ? sumiBridgeRoot(home) : join18(home, adapter.root), CLI_BRIDGE_NAME), aliases));
|
|
14779
14892
|
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
14780
14893
|
for (const [agent, path] of rootDefinitions)
|
|
14781
|
-
roots.push([agent, canonicalAgentPath(
|
|
14894
|
+
roots.push([agent, canonicalAgentPath(join18(project, path), aliases)]);
|
|
14895
|
+
if (selectedAgents?.has("sumi"))
|
|
14896
|
+
for (const directory of [".claude", ".agents"])
|
|
14897
|
+
for (const name of ["skill", "skills"])
|
|
14898
|
+
roots.push(["sumi", canonicalAgentPath(join18(project, directory, name), aliases)]);
|
|
14782
14899
|
}
|
|
14783
14900
|
const installationInputs = options.codexInstallationInputRoots ?? [];
|
|
14784
|
-
if (installationInputs.some((input) =>
|
|
14901
|
+
if (installationInputs.some((input) => resolve11(input) !== input || realpathSync7(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep5) || input.startsWith(path + sep5)))))
|
|
14785
14902
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
14786
14903
|
const entries = [], seen = new Set;
|
|
14787
14904
|
const cacheScans = new Map, maxAliasProofEntries = 1e4;
|
|
@@ -14795,13 +14912,13 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14795
14912
|
}
|
|
14796
14913
|
let reviewedCacheAliasFound = false;
|
|
14797
14914
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
14798
|
-
const link = readlinkSync4(path), lexicalTarget =
|
|
14915
|
+
const link = readlinkSync4(path), lexicalTarget = resolve11(parent, link);
|
|
14799
14916
|
if (isAbsolute11(link) ? link !== lexicalTarget : dirname11(link) !== ".")
|
|
14800
14917
|
return false;
|
|
14801
14918
|
const target = canonicalSystemPath(lexicalTarget);
|
|
14802
14919
|
if (dirname11(target) !== parent)
|
|
14803
14920
|
return false;
|
|
14804
|
-
if (!
|
|
14921
|
+
if (!lstatSync16(target, { throwIfNoEntry: false })?.isDirectory())
|
|
14805
14922
|
return false;
|
|
14806
14923
|
const scan = cacheScans.get(target);
|
|
14807
14924
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
@@ -14824,37 +14941,40 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14824
14941
|
assertSafePath(path);
|
|
14825
14942
|
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep5}`)))
|
|
14826
14943
|
return scan;
|
|
14827
|
-
if (!
|
|
14944
|
+
if (!existsSync9(path))
|
|
14828
14945
|
return scan;
|
|
14829
|
-
const stat =
|
|
14830
|
-
if (stat.isFile())
|
|
14946
|
+
const stat = lstatSync16(path);
|
|
14947
|
+
if (stat.isFile()) {
|
|
14948
|
+
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
14949
|
+
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
14831
14950
|
return scan;
|
|
14951
|
+
}
|
|
14832
14952
|
if (!stat.isDirectory())
|
|
14833
14953
|
throw new Error(`Unsupported native discovery entry: ${path}`);
|
|
14834
|
-
if (
|
|
14954
|
+
if (existsSync9(join18(path, "SKILL.md"))) {
|
|
14835
14955
|
scan.hasSkills = true;
|
|
14836
14956
|
if (seen.has(path))
|
|
14837
14957
|
return scan;
|
|
14838
14958
|
seen.add(path);
|
|
14839
14959
|
let managed = false;
|
|
14840
|
-
const marker =
|
|
14841
|
-
if (
|
|
14960
|
+
const marker = join18(path, ".hasna-skills.json");
|
|
14961
|
+
if (existsSync9(marker)) {
|
|
14842
14962
|
try {
|
|
14843
14963
|
managed = JSON.parse(readFileSync12(marker, "utf8")).managedBy === "@hasna/skills";
|
|
14844
14964
|
} catch {}
|
|
14845
14965
|
}
|
|
14846
|
-
const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
|
|
14966
|
+
const bridge = (!vendor || agent === "sumi") && isOwnedCliBridge(path, bridgePaths);
|
|
14847
14967
|
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep5));
|
|
14848
|
-
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(
|
|
14968
|
+
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join18(home, ".codex", "skills", ".system"), aliases) + sep5) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve11(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
14849
14969
|
return scan;
|
|
14850
14970
|
}
|
|
14851
|
-
if (depth > (vendor ? 32 : 3))
|
|
14971
|
+
if (depth > (agent === "sumi" ? 32 : vendor ? 32 : 3))
|
|
14852
14972
|
return { ...scan, complete: false };
|
|
14853
14973
|
const children = [], directory = opendirSync3(path);
|
|
14854
14974
|
try {
|
|
14855
14975
|
let child;
|
|
14856
14976
|
while ((child = directory.readSync()) !== null) {
|
|
14857
|
-
admit(
|
|
14977
|
+
admit(join18(path, child.name));
|
|
14858
14978
|
children.push(child);
|
|
14859
14979
|
}
|
|
14860
14980
|
} finally {
|
|
@@ -14866,13 +14986,13 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14866
14986
|
for (const { name } of children) {
|
|
14867
14987
|
if (name === "node_modules")
|
|
14868
14988
|
continue;
|
|
14869
|
-
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && !(options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`)))
|
|
14989
|
+
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && agent !== "sumi" && !(options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`)))
|
|
14870
14990
|
continue;
|
|
14871
14991
|
const isVendor = vendor || name === ".system";
|
|
14872
14992
|
if (isVendor && !options.includeVendor)
|
|
14873
14993
|
continue;
|
|
14874
|
-
const child =
|
|
14875
|
-
if (isVendor &&
|
|
14994
|
+
const child = join18(path, name);
|
|
14995
|
+
if (isVendor && lstatSync16(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
14876
14996
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
14877
14997
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
|
|
14878
14998
|
scan.entries++;
|
|
@@ -14898,11 +15018,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14898
15018
|
scanRoot(agent, path);
|
|
14899
15019
|
if (options.includeVendor) {
|
|
14900
15020
|
for (const agent of ["codex", "claude"].filter(includesAgent))
|
|
14901
|
-
scanRoot(agent, canonicalAgentPath(
|
|
15021
|
+
scanRoot(agent, canonicalAgentPath(join18(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
14902
15022
|
if (includesAgent("claude"))
|
|
14903
|
-
scanRoot("claude", canonicalAgentPath(
|
|
15023
|
+
scanRoot("claude", canonicalAgentPath(join18(home, ".claude", "plugins", "synced"), aliases), true);
|
|
14904
15024
|
if (includesAgent("gemini"))
|
|
14905
|
-
scanRoot("gemini",
|
|
15025
|
+
scanRoot("gemini", join18(home, ".gemini", "extensions"), true);
|
|
14906
15026
|
}
|
|
14907
15027
|
const configured = options.configured ? INTEGRATION_AGENTS.filter(includesAgent).flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
14908
15028
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
@@ -14915,7 +15035,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14915
15035
|
}
|
|
14916
15036
|
function readOptional(path) {
|
|
14917
15037
|
assertSafePath(path);
|
|
14918
|
-
return
|
|
15038
|
+
return existsSync9(path) ? readFileSync12(path, "utf8") : null;
|
|
14919
15039
|
}
|
|
14920
15040
|
function jsonObject(text2, path) {
|
|
14921
15041
|
if (text2 === null)
|
|
@@ -15015,19 +15135,19 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
15015
15135
|
function disableCodexSkills(text2, skills, aliases, bridgePath) {
|
|
15016
15136
|
const bundled = disableCodexBundledSkills(text2);
|
|
15017
15137
|
let result = normalizeCodexInlinePathConfig(bundled);
|
|
15018
|
-
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [
|
|
15138
|
+
const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve11(bridgePath), resolve11(join18(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
|
|
15019
15139
|
const previousConfig = Bun.TOML.parse(bundled).skills?.config;
|
|
15020
15140
|
if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
|
|
15021
15141
|
throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
|
|
15022
15142
|
const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
|
|
15023
15143
|
const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
|
|
15024
15144
|
for (const skill of selections) {
|
|
15025
|
-
const path =
|
|
15145
|
+
const path = join18(skill.path, "SKILL.md");
|
|
15026
15146
|
let found = false;
|
|
15027
15147
|
result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
|
|
15028
15148
|
const parsed = Bun.TOML.parse(section);
|
|
15029
15149
|
const declared = parsed.skills?.config?.[0];
|
|
15030
|
-
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [
|
|
15150
|
+
if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve11(path), resolve11(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
|
|
15031
15151
|
return section;
|
|
15032
15152
|
found = true;
|
|
15033
15153
|
return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
|
|
@@ -15048,14 +15168,14 @@ enabled = false
|
|
|
15048
15168
|
return result;
|
|
15049
15169
|
}
|
|
15050
15170
|
function assertCodexInstallationInputs(home, projectDirs, inputs) {
|
|
15051
|
-
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
15171
|
+
const nativeRoots = [home, ...projectAncestorDirectories(projectDirs)].flatMap((directory) => ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => resolve11(directory, path)));
|
|
15052
15172
|
if (inputs.some((input) => nativeRoots.some((root) => root === input || root.startsWith(input + sep5) || input.startsWith(root + sep5))))
|
|
15053
15173
|
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
15054
15174
|
}
|
|
15055
15175
|
function planAgentIntegration(options) {
|
|
15056
15176
|
const home = options.home ?? homedir2(), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15057
15177
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
15058
|
-
const policyPath =
|
|
15178
|
+
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15059
15179
|
assertSafePath(policyPath);
|
|
15060
15180
|
const priorSnapshot = readManagedSkillPolicySnapshot(dataDir2), previousPolicy = priorSnapshot?.text ?? null, policy = priorSnapshot?.value ?? {};
|
|
15061
15181
|
if (policy.bridge !== undefined && (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge)))
|
|
@@ -15087,10 +15207,10 @@ function planAgentIntegration(options) {
|
|
|
15087
15207
|
}
|
|
15088
15208
|
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15089
15209
|
});
|
|
15090
|
-
const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(
|
|
15210
|
+
const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases)) ?? "") : {};
|
|
15091
15211
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
|
|
15092
15212
|
const observedNativeSources = [];
|
|
15093
|
-
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) =>
|
|
15213
|
+
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join18(entry.path, "SKILL.md")), canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), resolve11(options.projectDir ?? home), (path) => {
|
|
15094
15214
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
15095
15215
|
observedNativeSources.push({ path, sha256: sha3(bytes) });
|
|
15096
15216
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
@@ -15098,7 +15218,7 @@ function planAgentIntegration(options) {
|
|
|
15098
15218
|
const codexPluginSkills = codexPluginControls.skills;
|
|
15099
15219
|
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
15100
15220
|
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
15101
|
-
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root + sep5)) && codexDiscovery.sources.some((source) => source.path ===
|
|
15221
|
+
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root + sep5)) && codexDiscovery.sources.some((source) => source.path === join18(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
15102
15222
|
assertCodexInstallationInputs(home, [options.projectDir ?? home], codexPluginSourceInputs.map((input) => input.sourceRoot));
|
|
15103
15223
|
const changes = [];
|
|
15104
15224
|
for (const agent of [...new Set(options.agents)]) {
|
|
@@ -15106,19 +15226,19 @@ function planAgentIntegration(options) {
|
|
|
15106
15226
|
throw new Error(`Unsupported agent: ${agent}`);
|
|
15107
15227
|
const { command, profileId: profileId2 } = bindings.get(agent);
|
|
15108
15228
|
const adapter = AGENT_ADAPTERS[agent];
|
|
15109
|
-
const bridgePath = canonicalAgentPath(
|
|
15229
|
+
const bridgePath = canonicalAgentPath(join18(agent === "sumi" ? sumiBridgeRoot(home) : join18(home, adapter.root), CLI_BRIDGE_NAME), aliases);
|
|
15110
15230
|
assertSafePath(bridgePath);
|
|
15111
|
-
if (
|
|
15231
|
+
if (existsSync9(bridgePath) && !isOwnedCliBridge(bridgePath, [bridgePath]))
|
|
15112
15232
|
throw new Error(`Refusing to overwrite an unrecognized or modified Skills bridge: ${bridgePath}`);
|
|
15113
15233
|
for (const [name, after2] of Object.entries(CLI_BRIDGE_FILES)) {
|
|
15114
|
-
const path2 =
|
|
15234
|
+
const path2 = join18(bridgePath, name), before2 = readOptional(path2);
|
|
15115
15235
|
if (before2 !== after2)
|
|
15116
15236
|
changes.push({ path: path2, before: before2, after: after2 });
|
|
15117
15237
|
}
|
|
15118
|
-
const path = canonicalAgentPath(
|
|
15238
|
+
const path = canonicalAgentPath(agent === "sumi" ? sumiConfigPath(home) : join18(home, adapter.config), aliases);
|
|
15119
15239
|
const before = readOptional(path);
|
|
15120
15240
|
if (agent === "hermes") {
|
|
15121
|
-
const supervisorPath =
|
|
15241
|
+
const supervisorPath = join18(dataDir2, "agent-hooks", "hermes.js"), supervisorBefore = readOptional(supervisorPath), supervisorAfter = renderHermesSupervisor(command, profileId2);
|
|
15122
15242
|
const priorCommand = policy.bridge?.commands?.hermes, priorProfile = policy.bridge?.profiles?.hermes ?? policy.profileId;
|
|
15123
15243
|
const previous = policy.bridge?.supervisors?.hermes;
|
|
15124
15244
|
if (supervisorBefore !== null && supervisorBefore !== supervisorAfter && !(previous?.path === supervisorPath && typeof priorCommand === "string" && typeof priorProfile === "string" && supervisorBefore === renderHermesSupervisor(priorCommand, priorProfile)))
|
|
@@ -15129,19 +15249,30 @@ function planAgentIntegration(options) {
|
|
|
15129
15249
|
const after2 = configureHermesHooks(before, supervisor, previous);
|
|
15130
15250
|
if (before !== after2)
|
|
15131
15251
|
changes.push({ path, before, after: after2 });
|
|
15132
|
-
const optOut =
|
|
15252
|
+
const optOut = join18(home, ".hermes", HERMES_OPT_OUT), optOutBefore = readOptional(optOut);
|
|
15133
15253
|
if (optOutBefore === null)
|
|
15134
15254
|
changes.push({ path: optOut, before: null, after: `Managed by @hasna/skills: bundled native skill reseeding is disabled.
|
|
15135
15255
|
` });
|
|
15136
15256
|
continue;
|
|
15137
15257
|
}
|
|
15138
15258
|
const config = jsonObject(before, path);
|
|
15139
|
-
if (agent === "
|
|
15259
|
+
if (agent === "sumi") {
|
|
15260
|
+
if (config.permissions !== undefined && (!Array.isArray(config.permissions) || config.permissions.some((rule) => !rule || typeof rule.action !== "string" || typeof rule.resource !== "string" || !["allow", "deny", "ask"].includes(rule.effect))))
|
|
15261
|
+
throw new Error("Expected Sumi native permission rules");
|
|
15262
|
+
config.permissions = [...(config.permissions ?? []).filter((rule) => rule.action !== "skill"), ...SUMI_SKILL_PERMISSIONS];
|
|
15263
|
+
const pluginPath = join18(sumiConfigDirectory(home), "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
15264
|
+
const pluginAfter = renderSumiPlugin(command, profileId2);
|
|
15265
|
+
const priorCommand = policy.bridge?.commands?.sumi, priorProfile = policy.bridge?.profiles?.sumi ?? policy.profileId;
|
|
15266
|
+
if (pluginBefore !== null && pluginBefore !== pluginAfter && !(typeof priorCommand === "string" && typeof priorProfile === "string" && pluginBefore === renderSumiPlugin(priorCommand, priorProfile)))
|
|
15267
|
+
throw new Error("Refusing to overwrite a modified Sumi Skills plugin; preserve and review it first");
|
|
15268
|
+
if (pluginBefore !== pluginAfter)
|
|
15269
|
+
changes.push({ path: pluginPath, before: pluginBefore, after: pluginAfter });
|
|
15270
|
+
} else if (agent === "opencode") {
|
|
15140
15271
|
const permission = config.permission ?? {};
|
|
15141
15272
|
if (!permission || typeof permission !== "object" || Array.isArray(permission))
|
|
15142
15273
|
throw new Error("Expected OpenCode permission object");
|
|
15143
15274
|
config.permission = { ...permission, skill: { "*": "deny", [CLI_BRIDGE_NAME]: "allow" } };
|
|
15144
|
-
const pluginPath =
|
|
15275
|
+
const pluginPath = join18(home, ".config", "opencode", "plugins", "skills-cli.js"), pluginBefore = readOptional(pluginPath);
|
|
15145
15276
|
const pluginAfter = renderOpenCodePlugin(command, profileId2);
|
|
15146
15277
|
const priorCommand = policy.bridge?.commands?.opencode;
|
|
15147
15278
|
const ownedPrevious = typeof priorCommand === "string" && typeof policy.profileId === "string" && pluginBefore === renderOpenCodePlugin(priorCommand, policy.bridge?.profiles?.opencode ?? policy.profileId);
|
|
@@ -15158,13 +15289,13 @@ function planAgentIntegration(options) {
|
|
|
15158
15289
|
if (before !== after)
|
|
15159
15290
|
changes.push({ path, before, after });
|
|
15160
15291
|
if (agent === "codex") {
|
|
15161
|
-
const configPath = canonicalAgentPath(
|
|
15292
|
+
const configPath = canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
|
|
15162
15293
|
const next = disableReviewedCodexPluginNames(disableCodexSkills(previous ?? "", nativeSkills, aliases, bridgePath), codexPluginSkills);
|
|
15163
15294
|
if (next !== (previous ?? ""))
|
|
15164
15295
|
changes.push({ path: configPath, before: previous, after: next });
|
|
15165
15296
|
}
|
|
15166
15297
|
}
|
|
15167
|
-
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha3(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(
|
|
15298
|
+
const codexPluginSkillReview = options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha3(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join18(home, ".codex/config.toml"), aliases))?.before === null ? null : sha3(readOptional(canonicalAgentPath(join18(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview;
|
|
15168
15299
|
const discoveryAfter = discoveries.map((binding) => {
|
|
15169
15300
|
const rebound = rebindAgentDiscovery(binding, new Map(changes.map((change) => [change.path, change.after])));
|
|
15170
15301
|
if (binding.agent !== "codex")
|
|
@@ -15179,12 +15310,12 @@ function planAgentIntegration(options) {
|
|
|
15179
15310
|
version: CLI_BRIDGE_VERSION,
|
|
15180
15311
|
digest: CLI_BRIDGE_DIGEST,
|
|
15181
15312
|
agents: [...new Set([...priorAgents, ...options.agents])].sort(),
|
|
15182
|
-
home:
|
|
15313
|
+
home: resolve11(home),
|
|
15183
15314
|
includeVendor: true,
|
|
15184
|
-
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path:
|
|
15315
|
+
...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join18(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha3(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
|
|
15185
15316
|
commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
|
|
15186
15317
|
profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
|
|
15187
|
-
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(
|
|
15318
|
+
disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join18(home, ".codex", "skills", ".system"), aliases) + sep5)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
|
|
15188
15319
|
rootAliases: aliases,
|
|
15189
15320
|
...options.codexNativeCatalog || codexPluginSkills.length || codexInactivePlugins.length || codexPluginSourceInputs.length ? { codexPluginSkills, codexInactivePlugins, codexPluginSkillReview } : {},
|
|
15190
15321
|
discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
|
|
@@ -15207,7 +15338,7 @@ function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
|
15207
15338
|
const distance = relative5(canonical5(root), path);
|
|
15208
15339
|
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep5}`) && !isAbsolute11(distance);
|
|
15209
15340
|
};
|
|
15210
|
-
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(
|
|
15341
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(join18(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical5(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
15211
15342
|
}
|
|
15212
15343
|
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
15213
15344
|
try {
|
|
@@ -15224,8 +15355,8 @@ function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
|
15224
15355
|
}
|
|
15225
15356
|
}
|
|
15226
15357
|
function planClaudeHookEventsUpdate(options) {
|
|
15227
|
-
const home =
|
|
15228
|
-
const policyPath =
|
|
15358
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15359
|
+
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15229
15360
|
assertSafePath(policyPath);
|
|
15230
15361
|
const snapshot = readManagedSkillPolicySnapshot(dataDir2);
|
|
15231
15362
|
if (!snapshot)
|
|
@@ -15236,9 +15367,9 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15236
15367
|
}
|
|
15237
15368
|
const aliases = bridge.rootAliases ?? [];
|
|
15238
15369
|
recheckRootAliases(aliases);
|
|
15239
|
-
const path = canonicalAgentPath(
|
|
15370
|
+
const path = canonicalAgentPath(join18(home, ".claude", "settings.json"), aliases);
|
|
15240
15371
|
assertSafePath(path);
|
|
15241
|
-
const before =
|
|
15372
|
+
const before = lstatSync16(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
15242
15373
|
if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
|
|
15243
15374
|
throw new Error("Claude settings preimage changed");
|
|
15244
15375
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
@@ -15298,14 +15429,14 @@ function atomicWrite2(path, content) {
|
|
|
15298
15429
|
writeFileSync4(temporary, content, { mode: 384, flag: "wx" });
|
|
15299
15430
|
renameSync2(temporary, path);
|
|
15300
15431
|
} finally {
|
|
15301
|
-
if (
|
|
15432
|
+
if (existsSync9(temporary))
|
|
15302
15433
|
unlinkSync3(temporary);
|
|
15303
15434
|
}
|
|
15304
15435
|
}
|
|
15305
15436
|
function planAgentSettingsWitnessUpgrade(options) {
|
|
15306
15437
|
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
|
|
15307
15438
|
throw new Error("Invalid Codex witness target version");
|
|
15308
|
-
const home =
|
|
15439
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15309
15440
|
const snapshot = readManagedSkillPolicySnapshot(dataDir2);
|
|
15310
15441
|
if (!snapshot || sha3(snapshot.text) !== options.expectedPolicySha256)
|
|
15311
15442
|
throw new Error("Managed policy preimage changed");
|
|
@@ -15314,7 +15445,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15314
15445
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
15315
15446
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
15316
15447
|
throw new Error("Settings witness upgrade requires an existing reviewed native binding");
|
|
15317
|
-
const configPath = canonicalAgentPath(
|
|
15448
|
+
const configPath = canonicalAgentPath(join18(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
|
|
15318
15449
|
const settings = readOptional(configPath);
|
|
15319
15450
|
if (settings === null || sha3(settings) !== options.expectedSettingsSha256)
|
|
15320
15451
|
throw new Error("Native settings preimage changed");
|
|
@@ -15353,7 +15484,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15353
15484
|
const replacement = { ...binding, sources: rebound };
|
|
15354
15485
|
verifyAgentDiscovery(replacement);
|
|
15355
15486
|
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
15356
|
-
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path:
|
|
15487
|
+
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join18(dataDir2, "agent-policy.json"), before: snapshot.text, after }], nativeSkills: [], observedPolicy: { path: join18(dataDir2, "agent-policy.json"), before: snapshot.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
|
|
15357
15488
|
}
|
|
15358
15489
|
function applyAgentIntegration(plan) {
|
|
15359
15490
|
for (const source of plan.observedNativeSources ?? [])
|
|
@@ -15361,12 +15492,12 @@ function applyAgentIntegration(plan) {
|
|
|
15361
15492
|
throw new Error("Native identity source changed after planning");
|
|
15362
15493
|
if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
|
|
15363
15494
|
throw new Error("Native settings changed after witness planning");
|
|
15364
|
-
const policyPath =
|
|
15495
|
+
const policyPath = join18(plan.dataDir, "agent-policy.json");
|
|
15365
15496
|
assertSafePath(policyPath);
|
|
15366
|
-
const currentText = (path) =>
|
|
15497
|
+
const currentText = (path) => resolve11(path) === resolve11(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
15367
15498
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
15368
15499
|
for (const change of plan.changes)
|
|
15369
|
-
if (
|
|
15500
|
+
if (resolve11(change.path) === resolve11(policyPath))
|
|
15370
15501
|
parseManagedSkillPolicy(change.after);
|
|
15371
15502
|
const aliases = plan.rootAliases ?? [];
|
|
15372
15503
|
recheckRootAliases(aliases);
|
|
@@ -15395,7 +15526,7 @@ function applyAgentIntegration(plan) {
|
|
|
15395
15526
|
for (const change of plan.changes)
|
|
15396
15527
|
if (currentText(change.path) !== change.before)
|
|
15397
15528
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15398
|
-
const backupRoot =
|
|
15529
|
+
const backupRoot = join18(plan.dataDir, "migration", randomUUID2()), backups = [], written = [], createdDirectories = new Set;
|
|
15399
15530
|
if (!plan.changes.length) {
|
|
15400
15531
|
recheckRootAliases(aliases);
|
|
15401
15532
|
return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
@@ -15405,7 +15536,7 @@ function applyAgentIntegration(plan) {
|
|
|
15405
15536
|
chmodSync(backupRoot, 448);
|
|
15406
15537
|
for (const [index, change] of plan.changes.entries())
|
|
15407
15538
|
if (change.before !== null) {
|
|
15408
|
-
const backup =
|
|
15539
|
+
const backup = join18(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
|
|
15409
15540
|
writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
|
|
15410
15541
|
if (readFileSync12(backup, "utf8") !== change.before)
|
|
15411
15542
|
throw new Error("Native configuration preservation readback failed");
|
|
@@ -15416,7 +15547,7 @@ function applyAgentIntegration(plan) {
|
|
|
15416
15547
|
recheckRootAliases(aliases);
|
|
15417
15548
|
if (currentText(change.path) !== change.before)
|
|
15418
15549
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15419
|
-
for (let directory = dirname11(change.path);!
|
|
15550
|
+
for (let directory = dirname11(change.path);!existsSync9(directory); directory = dirname11(directory))
|
|
15420
15551
|
createdDirectories.add(directory);
|
|
15421
15552
|
const after = change.after;
|
|
15422
15553
|
atomicWrite2(change.path, after);
|
|
@@ -15436,7 +15567,7 @@ function applyAgentIntegration(plan) {
|
|
|
15436
15567
|
}
|
|
15437
15568
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
15438
15569
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
15439
|
-
atomicWrite2(
|
|
15570
|
+
atomicWrite2(join18(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha3(change.before), afterHash: sha3(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
15440
15571
|
`);
|
|
15441
15572
|
} catch (error) {
|
|
15442
15573
|
for (const change of written.reverse()) {
|
|
@@ -15490,7 +15621,7 @@ function writeArchiveJournal(path, value) {
|
|
|
15490
15621
|
} finally {
|
|
15491
15622
|
if (descriptor !== undefined)
|
|
15492
15623
|
closeSync8(descriptor);
|
|
15493
|
-
if (
|
|
15624
|
+
if (lstatSync16(temporary, { throwIfNoEntry: false }))
|
|
15494
15625
|
unlinkSync3(temporary);
|
|
15495
15626
|
}
|
|
15496
15627
|
}
|
|
@@ -15501,7 +15632,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15501
15632
|
recheckRootAliases(aliases);
|
|
15502
15633
|
for (const entry of inventory.filter((entry2) => entry2.bridge)) {
|
|
15503
15634
|
const adapter = AGENT_ADAPTERS[entry.agent];
|
|
15504
|
-
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(
|
|
15635
|
+
const expected = adapter && entry.bridgeHome ? canonicalAgentPath(join18(entry.agent === "sumi" ? sumiBridgeRoot(entry.bridgeHome) : join18(entry.bridgeHome, adapter.root), CLI_BRIDGE_NAME), aliases) : undefined;
|
|
15505
15636
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
15506
15637
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
15507
15638
|
}
|
|
@@ -15512,11 +15643,11 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15512
15643
|
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
15513
15644
|
if (!selected.length)
|
|
15514
15645
|
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
15515
|
-
const operationId = randomUUID2(), archiveRoot =
|
|
15646
|
+
const operationId = randomUUID2(), archiveRoot = join18(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join18(archiveRoot, "receipt.json");
|
|
15516
15647
|
const entries = selected.map((entry, index) => ({
|
|
15517
|
-
source: entry.vendor ?
|
|
15518
|
-
archive:
|
|
15519
|
-
hash: entry.vendor ? sha3(readNativeBytes(
|
|
15648
|
+
source: entry.vendor ? join18(entry.path, "SKILL.md") : entry.path,
|
|
15649
|
+
archive: join18(archiveRoot, `${index}-${sha3(entry.path).slice(0, 12)}`),
|
|
15650
|
+
hash: entry.vendor ? sha3(readNativeBytes(join18(entry.path, "SKILL.md"))) : entry.hash,
|
|
15520
15651
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
15521
15652
|
status: "planned"
|
|
15522
15653
|
}));
|
|
@@ -15524,7 +15655,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15524
15655
|
const moved = [];
|
|
15525
15656
|
const verifyArchive = (entry) => {
|
|
15526
15657
|
assertSafePath(entry.archive);
|
|
15527
|
-
const stat =
|
|
15658
|
+
const stat = lstatSync16(entry.archive);
|
|
15528
15659
|
if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
|
|
15529
15660
|
throw new Error("Archive type changed");
|
|
15530
15661
|
if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -15532,7 +15663,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15532
15663
|
};
|
|
15533
15664
|
assertSafePath(archiveRoot);
|
|
15534
15665
|
const created = [];
|
|
15535
|
-
for (let path = archiveRoot;!
|
|
15666
|
+
for (let path = archiveRoot;!existsSync9(path); path = dirname11(path))
|
|
15536
15667
|
created.push(path);
|
|
15537
15668
|
mkdirSync5(archiveRoot, { recursive: true, mode: 448 });
|
|
15538
15669
|
try {
|
|
@@ -15572,7 +15703,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15572
15703
|
recheckRootAliases(aliases);
|
|
15573
15704
|
assertSafePath(entry.source);
|
|
15574
15705
|
assertSafePath(entry.archive);
|
|
15575
|
-
if (
|
|
15706
|
+
if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
|
|
15576
15707
|
entry.status = "recovery-required";
|
|
15577
15708
|
entry.conflict = "source-occupied";
|
|
15578
15709
|
continue;
|
|
@@ -15589,7 +15720,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15589
15720
|
syncArchiveDirectory(dirname11(entry.source));
|
|
15590
15721
|
unlinkSync3(entry.archive);
|
|
15591
15722
|
} else {
|
|
15592
|
-
if (
|
|
15723
|
+
if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
|
|
15593
15724
|
entry.status = "recovery-required";
|
|
15594
15725
|
entry.conflict = "source-occupied";
|
|
15595
15726
|
continue;
|
|
@@ -15620,8 +15751,8 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
15620
15751
|
assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
15621
15752
|
}
|
|
15622
15753
|
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
15623
|
-
const home =
|
|
15624
|
-
assertSafePath(
|
|
15754
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15755
|
+
assertSafePath(join18(dataDir2, "agent-policy.json"));
|
|
15625
15756
|
const snapshot = readManagedSkillPolicySnapshot(dataDir2);
|
|
15626
15757
|
if (!snapshot)
|
|
15627
15758
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
@@ -15632,14 +15763,14 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15632
15763
|
if (!Array.isArray(aliases))
|
|
15633
15764
|
throw new Error("NATIVE_SKILL_DRIFT: invalid root alias binding");
|
|
15634
15765
|
recheckRootAliases(aliases);
|
|
15635
|
-
const expected = canonicalAgentPath(
|
|
15766
|
+
const expected = canonicalAgentPath(join18(agent === "sumi" ? sumiBridgeRoot(home) : join18(home, AGENT_ADAPTERS[agent].root), CLI_BRIDGE_NAME), aliases);
|
|
15636
15767
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
15637
15768
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
15638
15769
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
15639
|
-
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) ||
|
|
15770
|
+
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
15640
15771
|
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
15641
15772
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
15642
|
-
const configPath = canonicalAgentPath(
|
|
15773
|
+
const configPath = canonicalAgentPath(agent === "sumi" ? sumiConfigPath(home) : join18(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
|
|
15643
15774
|
const command = binding.commands?.[agent], profile = binding.profiles?.[agent];
|
|
15644
15775
|
if (typeof command !== "string" || typeof profile !== "string")
|
|
15645
15776
|
throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
|
|
@@ -15647,11 +15778,14 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15647
15778
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
15648
15779
|
if (agent === "hermes") {
|
|
15649
15780
|
const supervisor = binding.supervisors?.hermes;
|
|
15650
|
-
if (supervisor?.path !==
|
|
15781
|
+
if (supervisor?.path !== join18(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha3(renderHermesSupervisor(command, profile)))
|
|
15651
15782
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
15652
15783
|
assertHermesProtection(home, config, command, profile, supervisor);
|
|
15784
|
+
} else if (agent === "sumi") {
|
|
15785
|
+
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || readOptional(join18(sumiConfigDirectory(home), "plugins", "skills-cli.js")) !== renderSumiPlugin(command, profile))
|
|
15786
|
+
throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
|
|
15653
15787
|
} else if (agent === "opencode") {
|
|
15654
|
-
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(
|
|
15788
|
+
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join18(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
|
|
15655
15789
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
15656
15790
|
} else {
|
|
15657
15791
|
const required = {};
|
|
@@ -15668,21 +15802,21 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15668
15802
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
15669
15803
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
15670
15804
|
}
|
|
15671
|
-
const codexPath = canonicalAgentPath(
|
|
15805
|
+
const codexPath = canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases);
|
|
15672
15806
|
if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
|
|
15673
15807
|
throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
|
|
15674
15808
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
15675
15809
|
if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
|
|
15676
15810
|
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
15677
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path,
|
|
15811
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join18(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
15678
15812
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
15679
15813
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
15680
|
-
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(
|
|
15814
|
+
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024))))
|
|
15681
15815
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
15682
15816
|
const disabledPlugin = (entry) => {
|
|
15683
15817
|
if (agent !== "codex" || !entry.vendor)
|
|
15684
15818
|
return false;
|
|
15685
|
-
const document2 =
|
|
15819
|
+
const document2 = join18(entry.path, "SKILL.md"), cache = canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
15686
15820
|
return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
|
|
15687
15821
|
};
|
|
15688
15822
|
const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
|
|
@@ -15695,15 +15829,15 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15695
15829
|
return true;
|
|
15696
15830
|
if (!disabledPaths.has(entry.path))
|
|
15697
15831
|
return false;
|
|
15698
|
-
if (entry.path.startsWith(canonicalAgentPath(
|
|
15832
|
+
if (entry.path.startsWith(canonicalAgentPath(join18(home, ".codex", "plugins", "cache"), aliases) + sep5))
|
|
15699
15833
|
return true;
|
|
15700
|
-
const runtimeCache = canonicalAgentPath(
|
|
15834
|
+
const runtimeCache = canonicalAgentPath(join18(home, ".cache", "codex-runtimes"), aliases);
|
|
15701
15835
|
if (entry.path.startsWith(runtimeCache + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
|
|
15702
15836
|
return true;
|
|
15703
|
-
const bundledMarketplaces = canonicalAgentPath(
|
|
15837
|
+
const bundledMarketplaces = canonicalAgentPath(join18(home, ".codex", ".tmp", "bundled-marketplaces"), aliases);
|
|
15704
15838
|
if (entry.path.startsWith(bundledMarketplaces + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
|
|
15705
15839
|
return true;
|
|
15706
|
-
if (!entry.path.startsWith(canonicalAgentPath(
|
|
15840
|
+
if (!entry.path.startsWith(canonicalAgentPath(join18(home, ".codex", "skills", ".system"), aliases) + sep5))
|
|
15707
15841
|
return false;
|
|
15708
15842
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
15709
15843
|
};
|
|
@@ -15713,6 +15847,8 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15713
15847
|
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
15714
15848
|
try {
|
|
15715
15849
|
verifyAgentDiscovery(discovery, options.codexDiscoveryRecovery);
|
|
15850
|
+
if (agent === "sumi" && discovery.method === "reviewed")
|
|
15851
|
+
resolveAgentDiscovery({ home, agent, retainedReview: discovery, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15716
15852
|
if (discovery.method === "automatic") {
|
|
15717
15853
|
const current = resolveAgentDiscovery({ home, agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15718
15854
|
if (JSON.stringify(current) !== JSON.stringify(discovery))
|
|
@@ -15724,7 +15860,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
15724
15860
|
let installationInputRoots = [];
|
|
15725
15861
|
if (agent === "codex") {
|
|
15726
15862
|
try {
|
|
15727
|
-
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(
|
|
15863
|
+
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
15728
15864
|
} catch {
|
|
15729
15865
|
throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
|
|
15730
15866
|
}
|
|
@@ -15759,9 +15895,9 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
15759
15895
|
return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
|
|
15760
15896
|
}
|
|
15761
15897
|
function hasSkillsOwnershipMarker(dir) {
|
|
15762
|
-
const path =
|
|
15898
|
+
const path = join19(dir, SYNC_MARKER_FILE);
|
|
15763
15899
|
try {
|
|
15764
|
-
if (!
|
|
15900
|
+
if (!lstatSync17(path).isFile())
|
|
15765
15901
|
return false;
|
|
15766
15902
|
const marker = JSON.parse(readFileSync13(path, "utf8"));
|
|
15767
15903
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -15783,9 +15919,9 @@ function resolveSyncAgents(arg) {
|
|
|
15783
15919
|
function agentGlobalSkillsDir(agent, homeDir2 = homedir3()) {
|
|
15784
15920
|
switch (agent) {
|
|
15785
15921
|
case "opencode":
|
|
15786
|
-
return
|
|
15922
|
+
return join19(homeDir2, ".config", "opencode", "skills");
|
|
15787
15923
|
default:
|
|
15788
|
-
return
|
|
15924
|
+
return join19(homeDir2, `.${agent}`, "skills");
|
|
15789
15925
|
}
|
|
15790
15926
|
}
|
|
15791
15927
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -15844,8 +15980,8 @@ function resolveSyncCorpus(options = {}) {
|
|
|
15844
15980
|
function packageSourceRoots(source) {
|
|
15845
15981
|
const roots = [];
|
|
15846
15982
|
for (const sub of ["skills"]) {
|
|
15847
|
-
const candidate =
|
|
15848
|
-
if (
|
|
15983
|
+
const candidate = join19(source, sub);
|
|
15984
|
+
if (existsSync10(candidate) && isDirectory(candidate))
|
|
15849
15985
|
roots.push(candidate);
|
|
15850
15986
|
}
|
|
15851
15987
|
if (roots.length > 0)
|
|
@@ -15860,10 +15996,10 @@ function containsSkillDirectories(path) {
|
|
|
15860
15996
|
return false;
|
|
15861
15997
|
}
|
|
15862
15998
|
return entries.some((entry) => {
|
|
15863
|
-
const candidate =
|
|
15999
|
+
const candidate = join19(path, entry);
|
|
15864
16000
|
if (!isDirectory(candidate))
|
|
15865
16001
|
return false;
|
|
15866
|
-
return
|
|
16002
|
+
return existsSync10(join19(candidate, "SKILL.md")) || existsSync10(join19(candidate, "skill.json")) || existsSync10(join19(candidate, "package.json"));
|
|
15867
16003
|
});
|
|
15868
16004
|
}
|
|
15869
16005
|
function isDirectory(path) {
|
|
@@ -15903,7 +16039,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
15903
16039
|
actions.push({
|
|
15904
16040
|
skill: name,
|
|
15905
16041
|
agent,
|
|
15906
|
-
path:
|
|
16042
|
+
path: join19(agentGlobalSkillsDir(agent, homeDir2), name, "SKILL.md"),
|
|
15907
16043
|
action: "skip",
|
|
15908
16044
|
reason: "not found in this machine's corpus"
|
|
15909
16045
|
});
|
|
@@ -15933,7 +16069,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
15933
16069
|
}
|
|
15934
16070
|
function writeManagedAgentSkill(params) {
|
|
15935
16071
|
const homeDir2 = params.homeDir ?? homedir3();
|
|
15936
|
-
const dir =
|
|
16072
|
+
const dir = join19(agentGlobalSkillsDir(params.agent, homeDir2), params.skill);
|
|
15937
16073
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
15938
16074
|
skill: params.skill,
|
|
15939
16075
|
source: params.source,
|
|
@@ -15950,11 +16086,11 @@ function writeManagedAgentSkill(params) {
|
|
|
15950
16086
|
};
|
|
15951
16087
|
}
|
|
15952
16088
|
function writeManagedSkillDir(dir, skillMd, options) {
|
|
15953
|
-
const skillMdPath =
|
|
15954
|
-
const markerPath =
|
|
15955
|
-
const dirExists =
|
|
16089
|
+
const skillMdPath = join19(dir, "SKILL.md");
|
|
16090
|
+
const markerPath = join19(dir, SYNC_MARKER_FILE);
|
|
16091
|
+
const dirExists = existsSync10(dir);
|
|
15956
16092
|
const managed = hasSkillsOwnershipMarker(dir);
|
|
15957
|
-
const hasSkillMd =
|
|
16093
|
+
const hasSkillMd = existsSync10(skillMdPath);
|
|
15958
16094
|
if (dirExists && !managed && !hasSkillMd) {
|
|
15959
16095
|
return {
|
|
15960
16096
|
action: "skip",
|
|
@@ -15966,7 +16102,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
15966
16102
|
return {
|
|
15967
16103
|
action: "skip",
|
|
15968
16104
|
path: skillMdPath,
|
|
15969
|
-
reason:
|
|
16105
|
+
reason: existsSync10(markerPath) ? "an unmanaged SKILL.md already exists here (invalid or foreign ownership marker); pass --force to overwrite" : "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
|
|
15970
16106
|
};
|
|
15971
16107
|
}
|
|
15972
16108
|
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
@@ -15989,11 +16125,11 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
15989
16125
|
return { action, path: skillMdPath };
|
|
15990
16126
|
const parentDir = dirname12(dir);
|
|
15991
16127
|
mkdirSync6(parentDir, { recursive: true });
|
|
15992
|
-
const transactionDir = mkdtempSync(
|
|
15993
|
-
const candidateDir =
|
|
15994
|
-
const backupDir =
|
|
15995
|
-
const candidateSkillMdPath =
|
|
15996
|
-
const candidateMarkerPath =
|
|
16128
|
+
const transactionDir = mkdtempSync(join19(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
|
|
16129
|
+
const candidateDir = join19(transactionDir, "candidate");
|
|
16130
|
+
const backupDir = join19(transactionDir, "backup");
|
|
16131
|
+
const candidateSkillMdPath = join19(candidateDir, "SKILL.md");
|
|
16132
|
+
const candidateMarkerPath = join19(candidateDir, SYNC_MARKER_FILE);
|
|
15997
16133
|
const marker = {
|
|
15998
16134
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
15999
16135
|
skill: options.skill,
|
|
@@ -16020,9 +16156,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16020
16156
|
}
|
|
16021
16157
|
renameDirectory(candidateDir, dir);
|
|
16022
16158
|
} catch (error) {
|
|
16023
|
-
if (originalMoved &&
|
|
16159
|
+
if (originalMoved && existsSync10(backupDir)) {
|
|
16024
16160
|
try {
|
|
16025
|
-
if (
|
|
16161
|
+
if (existsSync10(dir))
|
|
16026
16162
|
rmSync(dir, { recursive: true, force: true });
|
|
16027
16163
|
renameDirectory(backupDir, dir);
|
|
16028
16164
|
originalMoved = false;
|
|
@@ -16042,7 +16178,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16042
16178
|
return { action, path: skillMdPath };
|
|
16043
16179
|
}
|
|
16044
16180
|
function removeManagedAgentSkill(skill, agent, homeDir2 = homedir3()) {
|
|
16045
|
-
const dir =
|
|
16181
|
+
const dir = join19(agentGlobalSkillsDir(agent, homeDir2), skill);
|
|
16046
16182
|
if (!hasSkillsOwnershipMarker(dir))
|
|
16047
16183
|
return false;
|
|
16048
16184
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -16050,8 +16186,8 @@ function removeManagedAgentSkill(skill, agent, homeDir2 = homedir3()) {
|
|
|
16050
16186
|
}
|
|
16051
16187
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
16052
16188
|
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
16053
|
-
const skillMdPath =
|
|
16054
|
-
if (
|
|
16189
|
+
const skillMdPath = join19(skillPath, "SKILL.md");
|
|
16190
|
+
if (existsSync10(skillMdPath))
|
|
16055
16191
|
return readFileSync13(skillMdPath, "utf-8");
|
|
16056
16192
|
}
|
|
16057
16193
|
return pointerSkillMd(name, description);
|
|
@@ -16092,8 +16228,8 @@ function resolveSkillAlias(name) {
|
|
|
16092
16228
|
}
|
|
16093
16229
|
|
|
16094
16230
|
// src/lib/project-state.ts
|
|
16095
|
-
import { existsSync as
|
|
16096
|
-
import { join as
|
|
16231
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
16232
|
+
import { join as join20 } from "path";
|
|
16097
16233
|
var VALID_PIN_SOURCES = [
|
|
16098
16234
|
"official",
|
|
16099
16235
|
"custom",
|
|
@@ -16108,14 +16244,14 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
16108
16244
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
16109
16245
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
16110
16246
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
16111
|
-
return
|
|
16247
|
+
return join20(targetDir, SKILLS_PROJECT_DIR);
|
|
16112
16248
|
}
|
|
16113
16249
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
16114
|
-
return
|
|
16250
|
+
return join20(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
16115
16251
|
}
|
|
16116
16252
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
16117
16253
|
const path = getProjectConfigPath(targetDir);
|
|
16118
|
-
if (!
|
|
16254
|
+
if (!existsSync11(path))
|
|
16119
16255
|
return null;
|
|
16120
16256
|
try {
|
|
16121
16257
|
return normalizeProjectConfig(JSON.parse(readFileSync14(path, "utf-8")));
|
|
@@ -16240,19 +16376,19 @@ function getSkillPath(name) {
|
|
|
16240
16376
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
16241
16377
|
if (extensionPath)
|
|
16242
16378
|
return extensionPath;
|
|
16243
|
-
return
|
|
16379
|
+
return join21(getPortableSkillsRoot(), skillName);
|
|
16244
16380
|
}
|
|
16245
16381
|
function getCanonicalSkillName(name) {
|
|
16246
16382
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
16247
16383
|
}
|
|
16248
16384
|
function skillExists(name) {
|
|
16249
|
-
return
|
|
16385
|
+
return existsSync12(getSkillPath(name));
|
|
16250
16386
|
}
|
|
16251
16387
|
function installSkill(name, options = {}) {
|
|
16252
16388
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
16253
16389
|
const canonicalName = getCanonicalSkillName(name);
|
|
16254
16390
|
const skillName = normalizeSkillName(canonicalName);
|
|
16255
|
-
if (!
|
|
16391
|
+
if (!existsSync12(getSkillPath(name))) {
|
|
16256
16392
|
const knownOfficial = Boolean(getSkill(name));
|
|
16257
16393
|
return {
|
|
16258
16394
|
skill: canonicalName,
|
|
@@ -16279,7 +16415,7 @@ function installSkill(name, options = {}) {
|
|
|
16279
16415
|
}
|
|
16280
16416
|
function installSkillSource(name, _options = {}) {
|
|
16281
16417
|
const canonicalName = getCanonicalSkillName(name);
|
|
16282
|
-
if (!
|
|
16418
|
+
if (!existsSync12(getSkillPath(name))) {
|
|
16283
16419
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
|
|
16284
16420
|
}
|
|
16285
16421
|
return {
|
|
@@ -16300,11 +16436,11 @@ function installSkillManifest(manifest, _options = {}) {
|
|
|
16300
16436
|
}
|
|
16301
16437
|
function createLocalSkillManifest(name, generateSkillMd) {
|
|
16302
16438
|
const sourcePath = getSkillPath(name);
|
|
16303
|
-
if (!
|
|
16439
|
+
if (!existsSync12(sourcePath))
|
|
16304
16440
|
return null;
|
|
16305
16441
|
let skillMd = "";
|
|
16306
|
-
const skillMdPath =
|
|
16307
|
-
if (
|
|
16442
|
+
const skillMdPath = join21(sourcePath, "SKILL.md");
|
|
16443
|
+
if (existsSync12(skillMdPath)) {
|
|
16308
16444
|
skillMd = readFileSync15(skillMdPath, "utf-8");
|
|
16309
16445
|
} else if (generateSkillMd) {
|
|
16310
16446
|
skillMd = generateSkillMd(name) ?? "";
|
|
@@ -16377,21 +16513,21 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
16377
16513
|
const base = projectDir || process.cwd();
|
|
16378
16514
|
switch (agent) {
|
|
16379
16515
|
case "pi":
|
|
16380
|
-
return scope === "project" ?
|
|
16516
|
+
return scope === "project" ? join21(base, ".pi", "skills") : join21(homedir4(), ".pi", "agent", "skills");
|
|
16381
16517
|
case "opencode":
|
|
16382
|
-
return scope === "project" ?
|
|
16518
|
+
return scope === "project" ? join21(base, ".opencode", "skills") : join21(homedir4(), ".config", "opencode", "skills");
|
|
16383
16519
|
default:
|
|
16384
|
-
return scope === "project" ?
|
|
16520
|
+
return scope === "project" ? join21(base, `.${agent}`, "skills") : join21(homedir4(), `.${agent}`, "skills");
|
|
16385
16521
|
}
|
|
16386
16522
|
}
|
|
16387
16523
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
16388
16524
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
16389
|
-
return
|
|
16525
|
+
return join21(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
16390
16526
|
}
|
|
16391
16527
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
16392
16528
|
assertNativeExportAllowed();
|
|
16393
16529
|
const canonicalName = getCanonicalSkillName(name);
|
|
16394
|
-
if (!
|
|
16530
|
+
if (!existsSync12(getSkillPath(name))) {
|
|
16395
16531
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
|
|
16396
16532
|
}
|
|
16397
16533
|
const scope = options.scope ?? "global";
|
|
@@ -16422,8 +16558,8 @@ function removeSkillForAgent(name, options) {
|
|
|
16422
16558
|
}
|
|
16423
16559
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
16424
16560
|
const sourcePath = getSkillPath(name);
|
|
16425
|
-
const skillMdPath =
|
|
16426
|
-
if (
|
|
16561
|
+
const skillMdPath = join21(sourcePath, "SKILL.md");
|
|
16562
|
+
if (existsSync12(skillMdPath))
|
|
16427
16563
|
return readFileSync15(skillMdPath, "utf-8");
|
|
16428
16564
|
if (generateSkillMd)
|
|
16429
16565
|
return generateSkillMd(name);
|
|
@@ -16442,7 +16578,7 @@ function warnMissingDependencies(name, targetDir) {
|
|
|
16442
16578
|
}
|
|
16443
16579
|
function generateMinimalSkillMd(name) {
|
|
16444
16580
|
const sourcePath = getSkillPath(name);
|
|
16445
|
-
if (!
|
|
16581
|
+
if (!existsSync12(sourcePath))
|
|
16446
16582
|
return null;
|
|
16447
16583
|
const canonicalName = getCanonicalSkillName(name);
|
|
16448
16584
|
const meta = getSkill(canonicalName);
|
|
@@ -16457,7 +16593,7 @@ function generateMinimalSkillMd(name) {
|
|
|
16457
16593
|
"---",
|
|
16458
16594
|
""
|
|
16459
16595
|
].filter(Boolean);
|
|
16460
|
-
const fallbackDoc = readFileIfExists(
|
|
16596
|
+
const fallbackDoc = readFileIfExists(join21(sourcePath, "README.md")) || readFileIfExists(join21(sourcePath, "CLAUDE.md"));
|
|
16461
16597
|
if (fallbackDoc)
|
|
16462
16598
|
return `${frontmatter.join(`
|
|
16463
16599
|
`)}${fallbackDoc.trim()}
|
|
@@ -16476,8 +16612,8 @@ skills run ${canonicalName}
|
|
|
16476
16612
|
`;
|
|
16477
16613
|
}
|
|
16478
16614
|
function readBundledSkillVersion(name) {
|
|
16479
|
-
const pkgPath =
|
|
16480
|
-
if (!
|
|
16615
|
+
const pkgPath = join21(getSkillPath(name), "package.json");
|
|
16616
|
+
if (!existsSync12(pkgPath))
|
|
16481
16617
|
return "unknown";
|
|
16482
16618
|
try {
|
|
16483
16619
|
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
@@ -16487,7 +16623,7 @@ function readBundledSkillVersion(name) {
|
|
|
16487
16623
|
}
|
|
16488
16624
|
}
|
|
16489
16625
|
function readFileIfExists(path) {
|
|
16490
|
-
return
|
|
16626
|
+
return existsSync12(path) ? readFileSync15(path, "utf-8") : null;
|
|
16491
16627
|
}
|
|
16492
16628
|
function loadProjectConfigCompat(targetDir) {
|
|
16493
16629
|
return loadProjectConfig(targetDir);
|
|
@@ -16531,8 +16667,8 @@ function selectSkillDoc(docs, file) {
|
|
|
16531
16667
|
function isInstructionSkillDir(skillPath, meta) {
|
|
16532
16668
|
if (meta?.kind === "instruction")
|
|
16533
16669
|
return true;
|
|
16534
|
-
const skillMdPath =
|
|
16535
|
-
if (!
|
|
16670
|
+
const skillMdPath = join23(skillPath, "SKILL.md");
|
|
16671
|
+
if (!existsSync13(skillMdPath))
|
|
16536
16672
|
return false;
|
|
16537
16673
|
try {
|
|
16538
16674
|
return parseSkillFrontmatter(readFileSync16(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
@@ -16558,12 +16694,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
|
|
|
16558
16694
|
];
|
|
16559
16695
|
function getSkillDocs(name) {
|
|
16560
16696
|
const skillPath = getSkillPath(name);
|
|
16561
|
-
if (!
|
|
16697
|
+
if (!existsSync13(skillPath))
|
|
16562
16698
|
return null;
|
|
16563
16699
|
return {
|
|
16564
|
-
skillMd: readIfExists(
|
|
16565
|
-
readme: readIfExists(
|
|
16566
|
-
claudeMd: readIfExists(
|
|
16700
|
+
skillMd: readIfExists(join23(skillPath, "SKILL.md")),
|
|
16701
|
+
readme: readIfExists(join23(skillPath, "README.md")),
|
|
16702
|
+
claudeMd: readIfExists(join23(skillPath, "CLAUDE.md"))
|
|
16567
16703
|
};
|
|
16568
16704
|
}
|
|
16569
16705
|
function getSkillBestDoc(name, file) {
|
|
@@ -16575,18 +16711,18 @@ function getSkillBestDoc(name, file) {
|
|
|
16575
16711
|
}
|
|
16576
16712
|
function getSkillRequirements(name) {
|
|
16577
16713
|
const skillPath = getSkillPath(name);
|
|
16578
|
-
if (!
|
|
16714
|
+
if (!existsSync13(skillPath))
|
|
16579
16715
|
return null;
|
|
16580
16716
|
const texts = [];
|
|
16581
16717
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
16582
|
-
const content = readIfExists(
|
|
16718
|
+
const content = readIfExists(join23(skillPath, file));
|
|
16583
16719
|
if (content)
|
|
16584
16720
|
texts.push(content);
|
|
16585
16721
|
}
|
|
16586
16722
|
const meta = getSkill(name);
|
|
16587
16723
|
let dependencies = {};
|
|
16588
16724
|
try {
|
|
16589
|
-
dependencies = JSON.parse(readFileSync16(
|
|
16725
|
+
dependencies = JSON.parse(readFileSync16(join23(skillPath, "package.json"), "utf8")).dependencies || {};
|
|
16590
16726
|
} catch {}
|
|
16591
16727
|
return getSkillRequirementsFromContent(name, texts, dependencies, meta);
|
|
16592
16728
|
}
|
|
@@ -16639,7 +16775,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
16639
16775
|
const meta = getSkill(name);
|
|
16640
16776
|
const canonicalName = meta?.name ?? name;
|
|
16641
16777
|
const skillPath = getSkillPath(canonicalName);
|
|
16642
|
-
if (!
|
|
16778
|
+
if (!existsSync13(skillPath)) {
|
|
16643
16779
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
16644
16780
|
}
|
|
16645
16781
|
if (isInstructionSkillDir(skillPath, meta)) {
|
|
@@ -16648,8 +16784,8 @@ async function runSkill(name, args, options = {}) {
|
|
|
16648
16784
|
error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
|
|
16649
16785
|
};
|
|
16650
16786
|
}
|
|
16651
|
-
const pkgPath =
|
|
16652
|
-
if (!
|
|
16787
|
+
const pkgPath = join23(skillPath, "package.json");
|
|
16788
|
+
if (!existsSync13(pkgPath)) {
|
|
16653
16789
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
16654
16790
|
}
|
|
16655
16791
|
let entryPoint2;
|
|
@@ -16668,12 +16804,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
16668
16804
|
} catch {
|
|
16669
16805
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
16670
16806
|
}
|
|
16671
|
-
const entryPath =
|
|
16672
|
-
if (!
|
|
16807
|
+
const entryPath = join23(skillPath, entryPoint2);
|
|
16808
|
+
if (!existsSync13(entryPath)) {
|
|
16673
16809
|
return { exitCode: 1, error: `Entry point '${entryPoint2}' not found in skill '${name}'` };
|
|
16674
16810
|
}
|
|
16675
16811
|
const env = { ...process.env, ...options.env };
|
|
16676
|
-
if (!
|
|
16812
|
+
if (!existsSync13(join23(skillPath, "node_modules")) || hasDependencyPreparationMarker(skillPath)) {
|
|
16677
16813
|
const failure = await prepareSkillDependencies(skillPath, env, options.preparationTimeoutMs);
|
|
16678
16814
|
if (failure)
|
|
16679
16815
|
return failure;
|
|
@@ -16742,7 +16878,7 @@ function generateSkillMd(name) {
|
|
|
16742
16878
|
if (!meta)
|
|
16743
16879
|
return null;
|
|
16744
16880
|
const skillPath = getSkillPath(name);
|
|
16745
|
-
if (!
|
|
16881
|
+
if (!existsSync13(skillPath))
|
|
16746
16882
|
return null;
|
|
16747
16883
|
const frontmatter = [
|
|
16748
16884
|
"---",
|
|
@@ -16751,11 +16887,11 @@ function generateSkillMd(name) {
|
|
|
16751
16887
|
"---"
|
|
16752
16888
|
].join(`
|
|
16753
16889
|
`);
|
|
16754
|
-
const readme = readIfExists(
|
|
16755
|
-
const claudeMd = readIfExists(
|
|
16890
|
+
const readme = readIfExists(join23(skillPath, "README.md"));
|
|
16891
|
+
const claudeMd = readIfExists(join23(skillPath, "CLAUDE.md"));
|
|
16756
16892
|
let cliCommand = null;
|
|
16757
|
-
const pkgPath =
|
|
16758
|
-
if (
|
|
16893
|
+
const pkgPath = join23(skillPath, "package.json");
|
|
16894
|
+
if (existsSync13(pkgPath)) {
|
|
16759
16895
|
try {
|
|
16760
16896
|
const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
16761
16897
|
if (pkg.bin) {
|
|
@@ -16832,7 +16968,7 @@ function extractEnvVars(text2) {
|
|
|
16832
16968
|
}
|
|
16833
16969
|
function readIfExists(path) {
|
|
16834
16970
|
try {
|
|
16835
|
-
if (
|
|
16971
|
+
if (existsSync13(path)) {
|
|
16836
16972
|
return readFileSync16(path, "utf-8");
|
|
16837
16973
|
}
|
|
16838
16974
|
} catch {}
|
|
@@ -16899,6 +17035,8 @@ async function syncSelectionProfile(profileId, options = {}) {
|
|
|
16899
17035
|
async function resolveSelectionContext(profileId, options = {}) {
|
|
16900
17036
|
const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
|
|
16901
17037
|
const session = sessionState?.receipt ?? null;
|
|
17038
|
+
if (options.nativeParentSessionId !== undefined && session && (session.parent?.sessionId ?? null) !== options.nativeParentSessionId)
|
|
17039
|
+
throw new SkillSelectionError("SESSION_PARENT_MISMATCH", "The native session id is already bound to different parent custody.");
|
|
16902
17040
|
const parentState = !session && options.parentSessionId ? readSkillSessionSnapshotIfExists(options.parentSessionId, options) : null;
|
|
16903
17041
|
if (!session && options.parentSessionId && !parentState) {
|
|
16904
17042
|
throw new SkillSelectionError("SESSION_PARENT_NOT_FOUND", "A child Skills session requires an existing parent receipt so its inherited pin can be bound exactly.");
|
|
@@ -17158,15 +17296,15 @@ function buildPluginProjection(entries) {
|
|
|
17158
17296
|
|
|
17159
17297
|
// src/lib/plugin-projection-store.ts
|
|
17160
17298
|
import { createHash as createHash14, randomUUID as randomUUID3 } from "crypto";
|
|
17161
|
-
import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as
|
|
17162
|
-
import { dirname as dirname13, isAbsolute as isAbsolute12, join as
|
|
17299
|
+
import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as lstatSync18, mkdirSync as mkdirSync8, openSync as openSync9, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
|
|
17300
|
+
import { dirname as dirname13, isAbsolute as isAbsolute12, join as join25, parse as parse5, resolve as resolve12 } from "path";
|
|
17163
17301
|
var READ_FLAGS = constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK;
|
|
17164
17302
|
var WRITE_FLAGS = constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW | constants8.O_NONBLOCK;
|
|
17165
17303
|
var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
|
|
17166
17304
|
var missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
17167
17305
|
function stat(path) {
|
|
17168
17306
|
try {
|
|
17169
|
-
return
|
|
17307
|
+
return lstatSync18(path, { bigint: true });
|
|
17170
17308
|
} catch (error) {
|
|
17171
17309
|
if (missing(error))
|
|
17172
17310
|
return null;
|
|
@@ -17174,7 +17312,7 @@ function stat(path) {
|
|
|
17174
17312
|
}
|
|
17175
17313
|
}
|
|
17176
17314
|
function canonical5(path) {
|
|
17177
|
-
pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) &&
|
|
17315
|
+
pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) && resolve12(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
|
|
17178
17316
|
}
|
|
17179
17317
|
function owner(stats, system = false) {
|
|
17180
17318
|
const current = uid();
|
|
@@ -17189,7 +17327,7 @@ function ancestors(path, create = false) {
|
|
|
17189
17327
|
let current = filesystemRoot;
|
|
17190
17328
|
for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
|
|
17191
17329
|
if (segment)
|
|
17192
|
-
current =
|
|
17330
|
+
current = join25(current, segment);
|
|
17193
17331
|
let currentStat = stat(current);
|
|
17194
17332
|
if (!currentStat && create) {
|
|
17195
17333
|
try {
|
|
@@ -17304,7 +17442,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
17304
17442
|
pluginNeed(++visited <= PLUGIN_PROJECTION_LIMITS.files * 50, "Plugin tree membership exceeds its limit");
|
|
17305
17443
|
const path = relative6 ? `${relative6}/${name}` : name;
|
|
17306
17444
|
pluginNeed(new TextEncoder().encode(path).byteLength <= 100 && !/[\\:\x00-\x1f\x7f]/u.test(path), "Plugin entry path exceeds its limit or is unsafe");
|
|
17307
|
-
const absolute =
|
|
17445
|
+
const absolute = join25(directory, name), entryStat = stat(absolute);
|
|
17308
17446
|
pluginNeed(entryStat, "Plugin tree member disappeared");
|
|
17309
17447
|
if (relative6 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
|
|
17310
17448
|
const bytes2 = readBytes(absolute, 32, "native-pin");
|
|
@@ -17321,7 +17459,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
17321
17459
|
directories.push({ path: absolute, stat: entryStat, names: pins });
|
|
17322
17460
|
for (const pin of pins) {
|
|
17323
17461
|
pluginNeed(/^[1-9][0-9]{0,9}$/.test(pin) && Number(pin) <= 2147483647, "Unrecognized native plugin pin name");
|
|
17324
|
-
const pinPath =
|
|
17462
|
+
const pinPath = join25(absolute, pin), pinStat = stat(pinPath);
|
|
17325
17463
|
pluginNeed(pinStat, "Native plugin pin disappeared");
|
|
17326
17464
|
const bytes2 = readBytes(pinPath, 4096, "native-pin");
|
|
17327
17465
|
let value;
|
|
@@ -17419,7 +17557,7 @@ function materializePluginTree(root, entries) {
|
|
|
17419
17557
|
mkdirSync8(staging, { mode: 448 });
|
|
17420
17558
|
staged = stat(staging);
|
|
17421
17559
|
for (const entry of entries) {
|
|
17422
|
-
const path =
|
|
17560
|
+
const path = join25(staging, entry.path);
|
|
17423
17561
|
ancestors(path, true);
|
|
17424
17562
|
writeExclusive(path, entry.bytes, entry.mode);
|
|
17425
17563
|
}
|
|
@@ -17546,7 +17684,7 @@ function describeEntries(entries) {
|
|
|
17546
17684
|
|
|
17547
17685
|
// src/lib/plugin-admission.ts
|
|
17548
17686
|
function pluginAdmissionRoot() {
|
|
17549
|
-
return
|
|
17687
|
+
return join26(homedir5(), ".hasna", "skills", "plugin-admission");
|
|
17550
17688
|
}
|
|
17551
17689
|
function validatePluginTarget(value) {
|
|
17552
17690
|
pluginKeys(value, ["schemaVersion", "pluginId", "registrations", "native", "resolver"]);
|
|
@@ -17557,7 +17695,7 @@ function validatePluginTarget(value) {
|
|
|
17557
17695
|
for (const registration of value.registrations) {
|
|
17558
17696
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
17559
17697
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
17560
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute13(registration.projectPath) &&
|
|
17698
|
+
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute13(registration.projectPath) && resolve13(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
|
|
17561
17699
|
const key = JSON.stringify([registration.scope, registration.projectPath]);
|
|
17562
17700
|
pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
|
|
17563
17701
|
scopes.add(key);
|
|
@@ -17565,7 +17703,7 @@ function validatePluginTarget(value) {
|
|
|
17565
17703
|
for (const key of ["native", "resolver"]) {
|
|
17566
17704
|
pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
17567
17705
|
pluginText(value[key].executable);
|
|
17568
|
-
pluginNeed(isAbsolute13(value[key].executable) &&
|
|
17706
|
+
pluginNeed(isAbsolute13(value[key].executable) && resolve13(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
|
|
17569
17707
|
pluginDigest(value[key].digest);
|
|
17570
17708
|
}
|
|
17571
17709
|
pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
|
|
@@ -17717,17 +17855,17 @@ function pluginResolverCommand(binding) {
|
|
|
17717
17855
|
}
|
|
17718
17856
|
function root(options) {
|
|
17719
17857
|
const path = options.storeRoot ?? pluginAdmissionRoot();
|
|
17720
|
-
pluginNeed(isAbsolute13(path) &&
|
|
17858
|
+
pluginNeed(isAbsolute13(path) && resolve13(path) === path, "Invalid plugin admission store root");
|
|
17721
17859
|
return path;
|
|
17722
17860
|
}
|
|
17723
17861
|
function pluginBindingPath(storeRoot, id) {
|
|
17724
17862
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
17725
|
-
return
|
|
17863
|
+
return join26(storeRoot, "bindings", `${id}.json`);
|
|
17726
17864
|
}
|
|
17727
17865
|
function pluginReceiptPath(storeRoot, id, digest) {
|
|
17728
17866
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
17729
17867
|
pluginDigest(digest);
|
|
17730
|
-
return
|
|
17868
|
+
return join26(storeRoot, "receipts", id, `${digest.slice(7)}.json`);
|
|
17731
17869
|
}
|
|
17732
17870
|
function readPluginBinding(storeRoot, id) {
|
|
17733
17871
|
const value = readPluginJson(pluginBindingPath(storeRoot, id));
|
|
@@ -17747,7 +17885,7 @@ function readPluginAdmissionReceipt(storeRoot, id, digest) {
|
|
|
17747
17885
|
pluginNeed(receipt.plan.bindingId === id && receipt.plan.planDigest === digest, "Plugin receipt identity differs from its filename");
|
|
17748
17886
|
const binding = readPluginBinding(storeRoot, id);
|
|
17749
17887
|
pluginNeed(canonicalJson(binding) === canonicalJson(receipt.plan.binding) && receipt.plan.sourceCommand === pluginResolverCommand(binding), "Plugin receipt binding changed");
|
|
17750
|
-
pluginNeed(receipt.materializedPath ===
|
|
17888
|
+
pluginNeed(receipt.materializedPath === join26(storeRoot, "objects", id, digest.slice(7)), "Plugin receipt points outside its immutable materialization");
|
|
17751
17889
|
pluginText(receipt.admittedAt, 64);
|
|
17752
17890
|
pluginNeed(Number.isFinite(Date.parse(receipt.admittedAt)), "Invalid plugin admission time");
|
|
17753
17891
|
return receipt;
|
|
@@ -17888,7 +18026,7 @@ async function admitPlugin(spec, profileId, target, approvedDigest, approvedEvid
|
|
|
17888
18026
|
const { plan, projection } = await candidate(spec, profileId, target, options);
|
|
17889
18027
|
pluginNeed(plan.planDigest === approvedDigest, "The plugin plan changed since review; obtain and review a new plan");
|
|
17890
18028
|
pluginNeed(plan.evidenceDigest === approvedEvidenceDigest, "The plugin routing evidence changed since review; obtain and review a new plan");
|
|
17891
|
-
const store = root(options), materializedPath =
|
|
18029
|
+
const store = root(options), materializedPath = join26(store, "objects", plan.bindingId, plan.planDigest.slice(7));
|
|
17892
18030
|
materializePluginTree(materializedPath, projection.files);
|
|
17893
18031
|
writePluginJsonImmutable(pluginBindingPath(store, plan.bindingId), plan.binding);
|
|
17894
18032
|
const path = pluginReceiptPath(store, plan.bindingId, plan.planDigest), existing = readPluginJson(path);
|
|
@@ -17922,21 +18060,21 @@ function validateManagedPluginWitnesses(value) {
|
|
|
17922
18060
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
17923
18061
|
seen.add(item.bindingId);
|
|
17924
18062
|
pluginText(item.storeRoot);
|
|
17925
|
-
pluginNeed(isAbsolute14(item.storeRoot) &&
|
|
18063
|
+
pluginNeed(isAbsolute14(item.storeRoot) && resolve14(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
17926
18064
|
}
|
|
17927
18065
|
}
|
|
17928
18066
|
function safe3(path) {
|
|
17929
18067
|
pluginText(path);
|
|
17930
|
-
pluginNeed(isAbsolute14(path) &&
|
|
18068
|
+
pluginNeed(isAbsolute14(path) && resolve14(path) === path, "Invalid plugin registry path");
|
|
17931
18069
|
for (let at = path;; at = dirname14(at)) {
|
|
17932
|
-
pluginNeed(!
|
|
18070
|
+
pluginNeed(!lstatSync19(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
17933
18071
|
if (at === dirname14(at))
|
|
17934
18072
|
break;
|
|
17935
18073
|
}
|
|
17936
18074
|
}
|
|
17937
18075
|
function readRegistry(path) {
|
|
17938
18076
|
safe3(path);
|
|
17939
|
-
const initial =
|
|
18077
|
+
const initial = lstatSync19(path);
|
|
17940
18078
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
17941
18079
|
const fd = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
17942
18080
|
try {
|
|
@@ -17950,7 +18088,7 @@ function readRegistry(path) {
|
|
|
17950
18088
|
break;
|
|
17951
18089
|
length += count;
|
|
17952
18090
|
}
|
|
17953
|
-
const after = fstatSync10(fd), current =
|
|
18091
|
+
const after = fstatSync10(fd), current = lstatSync19(path);
|
|
17954
18092
|
safe3(path);
|
|
17955
18093
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
17956
18094
|
let value;
|
|
@@ -17980,7 +18118,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
17980
18118
|
seen.add(target.pluginId);
|
|
17981
18119
|
const rows = plugins[target.pluginId];
|
|
17982
18120
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
17983
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot =
|
|
18121
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join27(dirname14(path), "cache", market, name);
|
|
17984
18122
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
17985
18123
|
plugins[target.pluginId] = rows.map((row) => {
|
|
17986
18124
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -17993,7 +18131,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
17993
18131
|
pluginText(row.sourceProducerPath);
|
|
17994
18132
|
pluginText(row.lastUpdated, 64);
|
|
17995
18133
|
pluginNeed(Number.isFinite(Date.parse(row.lastUpdated)), "Invalid managed plugin update timestamp");
|
|
17996
|
-
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath ===
|
|
18134
|
+
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join27(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
|
|
17997
18135
|
const approval = (producer) => {
|
|
17998
18136
|
pluginText(producer);
|
|
17999
18137
|
const digest = `sha256:${basename7(producer)}`;
|
|
@@ -18023,7 +18161,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18023
18161
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
|
|
18024
18162
|
});
|
|
18025
18163
|
safe3(cacheRoot);
|
|
18026
|
-
const before =
|
|
18164
|
+
const before = lstatSync19(cacheRoot);
|
|
18027
18165
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
18028
18166
|
const directory = opendirSync5(cacheRoot), names = [];
|
|
18029
18167
|
try {
|
|
@@ -18036,17 +18174,17 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18036
18174
|
directory.closeSync();
|
|
18037
18175
|
}
|
|
18038
18176
|
for (const version of names.sort()) {
|
|
18039
|
-
const files = pluginFileWitnesses(snapshotPluginTree(
|
|
18177
|
+
const files = pluginFileWitnesses(snapshotPluginTree(join27(cacheRoot, version), { nativeRuntime: `claude-${target.native.version}` }));
|
|
18040
18178
|
verifiedBytes += files.reduce((total, file) => total + file.size, 0);
|
|
18041
18179
|
pluginNeed(verifiedBytes <= 256 * 1024 * 1024, "Managed plugin verification exceeds its aggregate byte budget");
|
|
18042
18180
|
const serialized = JSON.stringify(files);
|
|
18043
|
-
const expected = currentFiles.get(
|
|
18181
|
+
const expected = currentFiles.get(join27(cacheRoot, version));
|
|
18044
18182
|
pluginNeed(expected === undefined || serialized === expected, "Current native cache differs from its exact registered producer receipt");
|
|
18045
18183
|
pluginNeed([...approvals.values()].some((receipt) => nativeVersionMatches(version, receipt) && JSON.stringify(receipt.plan.files) === serialized), "A retained native plugin cache version differs from every approved projection");
|
|
18046
18184
|
}
|
|
18047
18185
|
for (const row of rows)
|
|
18048
18186
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
18049
|
-
const after =
|
|
18187
|
+
const after = lstatSync19(cacheRoot);
|
|
18050
18188
|
safe3(cacheRoot);
|
|
18051
18189
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
18052
18190
|
}
|
|
@@ -18059,21 +18197,21 @@ function captureManagedPluginRegistry(path, managedPlugins) {
|
|
|
18059
18197
|
|
|
18060
18198
|
// src/lib/claude-marketplace-registry.ts
|
|
18061
18199
|
import { createHash as createHash15 } from "crypto";
|
|
18062
|
-
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as
|
|
18063
|
-
import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as
|
|
18200
|
+
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, readSync as readSync9 } from "fs";
|
|
18201
|
+
import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve15 } from "path";
|
|
18064
18202
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
18065
18203
|
function need5(value, reason) {
|
|
18066
18204
|
if (!value)
|
|
18067
18205
|
throw new Error(`Claude marketplace registry ${reason}`);
|
|
18068
18206
|
}
|
|
18069
18207
|
function absolutePath3(value) {
|
|
18070
|
-
need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) &&
|
|
18208
|
+
need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve15(value) === value, "requires a normalized absolute path");
|
|
18071
18209
|
}
|
|
18072
18210
|
var sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
18073
18211
|
function parents2(path) {
|
|
18074
18212
|
const result = [];
|
|
18075
18213
|
for (let at = dirname15(path);; at = dirname15(at)) {
|
|
18076
|
-
const stat2 =
|
|
18214
|
+
const stat2 = lstatSync20(at, { bigint: true });
|
|
18077
18215
|
need5(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
18078
18216
|
result.push([at, stat2]);
|
|
18079
18217
|
if (at === dirname15(at))
|
|
@@ -18084,7 +18222,7 @@ function readRegistry2(path, budget) {
|
|
|
18084
18222
|
absolutePath3(path);
|
|
18085
18223
|
need5(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
18086
18224
|
need5(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
18087
|
-
const ancestors2 = parents2(path), initial =
|
|
18225
|
+
const ancestors2 = parents2(path), initial = lstatSync20(path, { bigint: true });
|
|
18088
18226
|
need5(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
18089
18227
|
need5(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
18090
18228
|
const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
@@ -18099,10 +18237,10 @@ function readRegistry2(path, budget) {
|
|
|
18099
18237
|
break;
|
|
18100
18238
|
length += count;
|
|
18101
18239
|
}
|
|
18102
|
-
const after = fstatSync11(fd, { bigint: true }), current =
|
|
18240
|
+
const after = fstatSync11(fd, { bigint: true }), current = lstatSync20(path, { bigint: true });
|
|
18103
18241
|
need5(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
18104
18242
|
for (const [ancestor, before] of ancestors2) {
|
|
18105
|
-
const now =
|
|
18243
|
+
const now = lstatSync20(ancestor, { bigint: true });
|
|
18106
18244
|
need5(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
18107
18245
|
}
|
|
18108
18246
|
budget.remaining -= length;
|
|
@@ -18242,7 +18380,7 @@ function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 10
|
|
|
18242
18380
|
import { createHash as createHash17 } from "crypto";
|
|
18243
18381
|
|
|
18244
18382
|
// src/lib/codex-hook-trust-files.ts
|
|
18245
|
-
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as
|
|
18383
|
+
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
18246
18384
|
import { createHash as createHash16 } from "crypto";
|
|
18247
18385
|
import { dirname as dirname16 } from "path";
|
|
18248
18386
|
function need6(value, code) {
|
|
@@ -18252,7 +18390,7 @@ function need6(value, code) {
|
|
|
18252
18390
|
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
18253
18391
|
function safeParents(file) {
|
|
18254
18392
|
for (let p = dirname16(file);; p = dirname16(p)) {
|
|
18255
|
-
const s =
|
|
18393
|
+
const s = lstatSync21(p);
|
|
18256
18394
|
need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
18257
18395
|
need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
18258
18396
|
if (dirname16(p) === p)
|
|
@@ -18262,7 +18400,7 @@ function safeParents(file) {
|
|
|
18262
18400
|
function snapshot(file, privateFile = false, options = {}) {
|
|
18263
18401
|
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
18264
18402
|
safeParents(file);
|
|
18265
|
-
const initial =
|
|
18403
|
+
const initial = lstatSync21(file, { bigint: true });
|
|
18266
18404
|
need6(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
18267
18405
|
need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
18268
18406
|
const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
@@ -18277,7 +18415,7 @@ function snapshot(file, privateFile = false, options = {}) {
|
|
|
18277
18415
|
break;
|
|
18278
18416
|
length += n2;
|
|
18279
18417
|
}
|
|
18280
|
-
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before,
|
|
18418
|
+
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync21(file, { bigint: true })), "INPUT_CHANGED");
|
|
18281
18419
|
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
18282
18420
|
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
|
|
18283
18421
|
} finally {
|
|
@@ -18329,7 +18467,7 @@ function safe4(path) {
|
|
|
18329
18467
|
if (!isAbsolute16(path) || path.includes("\x00"))
|
|
18330
18468
|
throw new Error("Expected an absolute native discovery path");
|
|
18331
18469
|
for (let cursor = path;; cursor = dirname17(cursor)) {
|
|
18332
|
-
if (
|
|
18470
|
+
if (lstatSync22(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
18333
18471
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
18334
18472
|
if (dirname17(cursor) === cursor)
|
|
18335
18473
|
break;
|
|
@@ -18339,7 +18477,7 @@ function read2(path, changes) {
|
|
|
18339
18477
|
safe4(path);
|
|
18340
18478
|
if (changes?.has(path))
|
|
18341
18479
|
return changes.get(path);
|
|
18342
|
-
const stat2 =
|
|
18480
|
+
const stat2 = lstatSync22(path, { throwIfNoEntry: false });
|
|
18343
18481
|
if (!stat2)
|
|
18344
18482
|
return null;
|
|
18345
18483
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
@@ -18450,12 +18588,12 @@ function codexInstallationRoots(binding) {
|
|
|
18450
18588
|
if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version !== "codex-cli 0.160.0" || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
18451
18589
|
throw new Error("Invalid native installation input proof");
|
|
18452
18590
|
const cache = dirname17(dirname17(proof.plugins[0].pluginParent));
|
|
18453
|
-
const config =
|
|
18591
|
+
const config = join28(dirname17(dirname17(cache)), "config.toml"), home = dirname17(dirname17(dirname17(cache)));
|
|
18454
18592
|
const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
|
|
18455
|
-
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) =>
|
|
18593
|
+
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join28(home, path));
|
|
18456
18594
|
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep6) || input.sourceRoot.startsWith(root2 + sep6)) || binding.sources.some((source) => basename9(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep6)))))
|
|
18457
18595
|
throw new Error("Native installation input overlaps configuration or loading roots");
|
|
18458
|
-
if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path ===
|
|
18596
|
+
if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join28(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
|
|
18459
18597
|
throw new Error("Missing native installation input source witness");
|
|
18460
18598
|
return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
|
|
18461
18599
|
const text2 = read2(path);
|
|
@@ -18497,19 +18635,19 @@ function captureDiscoveryPathSources(paths) {
|
|
|
18497
18635
|
const budget = discoveryByteBudget();
|
|
18498
18636
|
return paths.map((path) => ({ path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget) }));
|
|
18499
18637
|
}
|
|
18500
|
-
function assertProjectDiscovery(agent, directories, home, canonical6 =
|
|
18638
|
+
function assertProjectDiscovery(agent, directories, home, canonical6 = resolve16, reviewed) {
|
|
18501
18639
|
const homeConfigPath = canonical6(agentDiscoveryConfigPath(home, agent));
|
|
18502
18640
|
for (const directory of directories) {
|
|
18503
|
-
const isHome =
|
|
18641
|
+
const isHome = resolve16(directory) === resolve16(home);
|
|
18504
18642
|
if (agent === "hermes") {
|
|
18505
18643
|
assertHermesEnvironment(home);
|
|
18506
18644
|
continue;
|
|
18507
18645
|
}
|
|
18508
|
-
if (isHome && agent !== "claude")
|
|
18646
|
+
if (isHome && agent !== "claude" && agent !== "sumi")
|
|
18509
18647
|
continue;
|
|
18510
|
-
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : [".cursor/hooks.json"];
|
|
18648
|
+
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : agent === "sumi" ? ["sumi.json", "sumi.jsonc", ".sumi/sumi.json", ".sumi/sumi.jsonc"] : [".cursor/hooks.json"];
|
|
18511
18649
|
for (const suffix of paths) {
|
|
18512
|
-
const path = canonical6(
|
|
18650
|
+
const path = canonical6(join28(directory, suffix)), raw = read2(path);
|
|
18513
18651
|
if (raw === null)
|
|
18514
18652
|
continue;
|
|
18515
18653
|
const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
|
|
@@ -18518,7 +18656,7 @@ function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15
|
|
|
18518
18656
|
if (suffix.endsWith(".jsonc"))
|
|
18519
18657
|
throw new Error(`NATIVE_SKILL_DRIFT: project JSONC discovery configuration requires review: ${path}`);
|
|
18520
18658
|
const config = parseConfig(raw, path, suffix.endsWith(".toml"));
|
|
18521
|
-
const keys = agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces", "skillOverrides"] : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions"] : agent === "opencode" ? ["plugin", "skills"] : ["hooks"];
|
|
18659
|
+
const keys = agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces", "skillOverrides"] : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? ["plugin", "plugins", "skills", "permissions"] : ["hooks"];
|
|
18522
18660
|
let admittedPlugins = false;
|
|
18523
18661
|
if (agent === "claude" && reviewed?.agent === agent && reviewed.method === "reviewed" && config.enabledPlugins && typeof config.enabledPlugins === "object" && !Array.isArray(config.enabledPlugins)) {
|
|
18524
18662
|
const exact = reviewed.sources.find((source) => source.path === path && source.format === undefined && source.fields === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
@@ -18531,19 +18669,28 @@ function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15
|
|
|
18531
18669
|
if (keys.some((key) => config[key] !== undefined && !(key === "enabledPlugins" && admittedPlugins)) || config.disableAllHooks === true || config.disableBundledSkills === false || config.hooksConfig?.enabled === false || config.permission?.skill !== undefined || config.permissions?.deny?.some((rule) => typeof rule === "string" && /^Skill(?:\(|$)/.test(rule)))
|
|
18532
18670
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
|
|
18533
18671
|
}
|
|
18672
|
+
if (agent === "sumi")
|
|
18673
|
+
for (const name of ["plugin", "plugins"]) {
|
|
18674
|
+
const path = canonical6(join28(directory, ".sumi", name));
|
|
18675
|
+
safe4(path);
|
|
18676
|
+
if (lstatSync22(path, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(path).length)
|
|
18677
|
+
throw new Error(`NATIVE_SKILL_DRIFT: project Sumi plugins require a dedicated discovery review: ${path}`);
|
|
18678
|
+
}
|
|
18534
18679
|
if (agent === "claude") {
|
|
18535
|
-
const commands = canonical6(
|
|
18680
|
+
const commands = canonical6(join28(directory, ".claude/commands"));
|
|
18536
18681
|
safe4(commands);
|
|
18537
|
-
if (
|
|
18682
|
+
if (lstatSync22(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
|
|
18538
18683
|
throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
|
|
18539
18684
|
}
|
|
18540
18685
|
}
|
|
18541
18686
|
}
|
|
18542
18687
|
function agentDiscoveryConfigPath(home, agent) {
|
|
18543
|
-
|
|
18688
|
+
if (agent === "sumi")
|
|
18689
|
+
return sumiConfigPath(home);
|
|
18690
|
+
return join28(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
|
|
18544
18691
|
}
|
|
18545
18692
|
function resolveAgentDiscovery(options) {
|
|
18546
|
-
const canonical6 = options.canonical ??
|
|
18693
|
+
const canonical6 = options.canonical ?? resolve16, home = resolve16(options.home), agent = options.agent;
|
|
18547
18694
|
const sources = [], roots = new Set, builtinNames = [];
|
|
18548
18695
|
const witness = (path, format, fields2) => {
|
|
18549
18696
|
const source = { path: canonical6(path), sha256: null, ...format ? { format, fields: fields2 } : {} };
|
|
@@ -18554,13 +18701,49 @@ function resolveAgentDiscovery(options) {
|
|
|
18554
18701
|
if (agent === "hermes")
|
|
18555
18702
|
assertHermesEnvironment(home);
|
|
18556
18703
|
const configPath = agentDiscoveryConfigPath(home, agent);
|
|
18557
|
-
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : ["version"]);
|
|
18704
|
+
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? ["skills", "plugin", "plugins", "permissions"] : ["version"]);
|
|
18558
18705
|
const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
|
|
18559
18706
|
const unresolved = (detail) => {
|
|
18560
18707
|
throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
|
|
18561
18708
|
};
|
|
18709
|
+
if (agent === "sumi") {
|
|
18710
|
+
const directory = sumiConfigDirectory(home);
|
|
18711
|
+
if (witness(join28(directory, "sumi.jsonc")) !== null)
|
|
18712
|
+
unresolved("JSONC configuration requires a supported format adapter");
|
|
18713
|
+
for (const root2 of [directory, join28(home, ".claude"), join28(home, ".agents")])
|
|
18714
|
+
for (const name of ["skill", "skills"])
|
|
18715
|
+
roots.add(join28(root2, name));
|
|
18716
|
+
if (config.skills !== undefined && (!Array.isArray(config.skills) || config.skills.some((path) => typeof path !== "string")))
|
|
18717
|
+
unresolved("skills must be a string array");
|
|
18718
|
+
for (const value of config.skills ?? []) {
|
|
18719
|
+
if (typeof value !== "string" || /[\0$]/.test(value) || !value || !(isAbsolute16(value) || value.startsWith("~/")))
|
|
18720
|
+
unresolved("remote or relative skill sources need a dedicated discovery adapter");
|
|
18721
|
+
roots.add(value.startsWith("~/") ? join28(home, value.slice(2)) : value);
|
|
18722
|
+
}
|
|
18723
|
+
for (const key of ["plugin", "plugins"])
|
|
18724
|
+
if (config[key] !== undefined && !Array.isArray(config[key]))
|
|
18725
|
+
unresolved(`${key} must be an array`);
|
|
18726
|
+
const plugins = [join28(directory, "plugin"), join28(directory, "plugins")];
|
|
18727
|
+
for (const path of plugins) {
|
|
18728
|
+
safe4(path);
|
|
18729
|
+
if (lstatSync22(path, { throwIfNoEntry: false })?.isDirectory())
|
|
18730
|
+
for (const name of readdirSync10(path).sort()) {
|
|
18731
|
+
if (name === "skills-cli.js" && path === plugins[1])
|
|
18732
|
+
continue;
|
|
18733
|
+
const target = join28(path, name);
|
|
18734
|
+
if (!lstatSync22(target).isFile())
|
|
18735
|
+
unresolved("local plugin packages need a dedicated discovery adapter");
|
|
18736
|
+
witness(target);
|
|
18737
|
+
}
|
|
18738
|
+
}
|
|
18739
|
+
for (const root2 of roots) {
|
|
18740
|
+
safe4(root2);
|
|
18741
|
+
if (lstatSync22(root2, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(root2).some((name) => name.endsWith(".md")))
|
|
18742
|
+
unresolved("flat markdown skills require a dedicated native migration adapter");
|
|
18743
|
+
}
|
|
18744
|
+
}
|
|
18562
18745
|
if (agent === "hermes") {
|
|
18563
|
-
const profile = witness(
|
|
18746
|
+
const profile = witness(join28(home, ".hermes/active_profile"));
|
|
18564
18747
|
if (profile?.trim() && profile.trim() !== "default")
|
|
18565
18748
|
unresolved("a non-default profile is active");
|
|
18566
18749
|
const declared = config.skills?.external_dirs ?? [], paths = typeof declared === "string" ? [declared] : declared;
|
|
@@ -18574,7 +18757,7 @@ function resolveAgentDiscovery(options) {
|
|
|
18574
18757
|
continue;
|
|
18575
18758
|
if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
|
|
18576
18759
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
18577
|
-
const path = normalized === "~" ? home : normalized.startsWith("~/") ?
|
|
18760
|
+
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join28(home, normalized.slice(2)) : resolve16(home, ".hermes", normalized);
|
|
18578
18761
|
safe4(path);
|
|
18579
18762
|
roots.add(path);
|
|
18580
18763
|
}
|
|
@@ -18583,8 +18766,8 @@ function resolveAgentDiscovery(options) {
|
|
|
18583
18766
|
const executable = Bun.which("gemini");
|
|
18584
18767
|
if (executable) {
|
|
18585
18768
|
let packageRoot;
|
|
18586
|
-
for (let directory = dirname17(
|
|
18587
|
-
const path =
|
|
18769
|
+
for (let directory = dirname17(realpathSync8(executable)), depth = 0;depth < 8; directory = dirname17(directory), depth++) {
|
|
18770
|
+
const path = join28(directory, "package.json"), raw = read2(path);
|
|
18588
18771
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
18589
18772
|
packageRoot = directory;
|
|
18590
18773
|
witness(path);
|
|
@@ -18595,12 +18778,12 @@ function resolveAgentDiscovery(options) {
|
|
|
18595
18778
|
}
|
|
18596
18779
|
if (!packageRoot)
|
|
18597
18780
|
unresolved("installed Gemini package layout is not recognized");
|
|
18598
|
-
const builtinRoot =
|
|
18781
|
+
const builtinRoot = join28(packageRoot, "bundle/builtin");
|
|
18599
18782
|
safe4(builtinRoot);
|
|
18600
|
-
if (!
|
|
18783
|
+
if (!lstatSync22(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
18601
18784
|
unresolved("installed Gemini builtin root is missing");
|
|
18602
18785
|
for (const name of readdirSync10(builtinRoot).sort()) {
|
|
18603
|
-
const text2 = witness(
|
|
18786
|
+
const text2 = witness(join28(builtinRoot, name, "SKILL.md"));
|
|
18604
18787
|
const match = text2?.match(/^---\r?\n[\s\S]*?^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
|
|
18605
18788
|
if (!match?.[1] || builtinNames.includes(match[1]))
|
|
18606
18789
|
unresolved("installed Gemini builtin name is missing or ambiguous");
|
|
@@ -18642,39 +18825,39 @@ function resolveAgentDiscovery(options) {
|
|
|
18642
18825
|
function plugin(root2) {
|
|
18643
18826
|
root2 = canonical6(root2);
|
|
18644
18827
|
safe4(root2);
|
|
18645
|
-
const manifestPath =
|
|
18828
|
+
const manifestPath = join28(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
18646
18829
|
const raw = witness(manifestPath);
|
|
18647
18830
|
if (raw === null)
|
|
18648
18831
|
unresolved("plugin manifest missing");
|
|
18649
18832
|
const manifest = parseConfig(raw, manifestPath);
|
|
18650
|
-
const hooks = witness(
|
|
18833
|
+
const hooks = witness(join28(root2, "hooks/hooks.json"));
|
|
18651
18834
|
if (manifest.hooks !== undefined || hooks !== null)
|
|
18652
18835
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
18653
|
-
const commands =
|
|
18836
|
+
const commands = join28(root2, "commands");
|
|
18654
18837
|
safe4(commands);
|
|
18655
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
18838
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync22(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length))
|
|
18656
18839
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
18657
|
-
const rootSkill = witness(
|
|
18840
|
+
const rootSkill = witness(join28(root2, "SKILL.md"));
|
|
18658
18841
|
if (rootSkill !== null)
|
|
18659
18842
|
roots.add(root2);
|
|
18660
18843
|
const declared = manifest.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
18661
18844
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
18662
18845
|
unresolved("unsupported plugin skill paths");
|
|
18663
18846
|
if (agent === "claude")
|
|
18664
|
-
roots.add(
|
|
18847
|
+
roots.add(join28(root2, "skills"));
|
|
18665
18848
|
for (const value of declared) {
|
|
18666
|
-
const path =
|
|
18849
|
+
const path = resolve16(root2, value);
|
|
18667
18850
|
if (path !== root2 && !path.startsWith(root2 + sep6))
|
|
18668
18851
|
unresolved("plugin skill path escapes its root");
|
|
18669
18852
|
roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname17(path) : path);
|
|
18670
18853
|
}
|
|
18671
18854
|
}
|
|
18672
18855
|
if (agent === "claude") {
|
|
18673
|
-
const registrations = witness(
|
|
18674
|
-
const installed = registrations === null ? {} : parseConfig(registrations,
|
|
18675
|
-
const commands = canonical6(
|
|
18856
|
+
const registrations = witness(join28(home, ".claude/plugins/installed_plugins.json"));
|
|
18857
|
+
const installed = registrations === null ? {} : parseConfig(registrations, join28(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
18858
|
+
const commands = canonical6(join28(home, ".claude/commands"));
|
|
18676
18859
|
safe4(commands);
|
|
18677
|
-
if (
|
|
18860
|
+
if (lstatSync22(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
|
|
18678
18861
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
18679
18862
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
18680
18863
|
if (enabled === false)
|
|
@@ -18696,16 +18879,16 @@ function resolveAgentDiscovery(options) {
|
|
|
18696
18879
|
const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
|
|
18697
18880
|
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute16(market.source))
|
|
18698
18881
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
18699
|
-
const catalogText = witness(
|
|
18882
|
+
const catalogText = witness(join28(market.source, ".agents/plugins/marketplace.json"));
|
|
18700
18883
|
if (catalogText === null)
|
|
18701
18884
|
unresolved("marketplace catalog missing");
|
|
18702
|
-
const catalog = parseConfig(catalogText,
|
|
18885
|
+
const catalog = parseConfig(catalogText, join28(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
18703
18886
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
18704
18887
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
18705
|
-
plugin(
|
|
18888
|
+
plugin(resolve16(market.source, matches[0].source.path));
|
|
18706
18889
|
}
|
|
18707
18890
|
} else if (agent === "opencode") {
|
|
18708
|
-
if (witness(
|
|
18891
|
+
if (witness(join28(home, ".config/opencode/opencode.jsonc")) !== null || witness(join28(home, ".config/opencode/config.json")) !== null)
|
|
18709
18892
|
unresolved("additional config layers require review");
|
|
18710
18893
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
18711
18894
|
unresolved("external plugin or remote skill source");
|
|
@@ -18714,14 +18897,17 @@ function resolveAgentDiscovery(options) {
|
|
|
18714
18897
|
unresolved("relative or malformed added skill root");
|
|
18715
18898
|
roots.add(path);
|
|
18716
18899
|
}
|
|
18717
|
-
const localPlugins =
|
|
18900
|
+
const localPlugins = join28(home, ".config/opencode/plugins");
|
|
18718
18901
|
safe4(localPlugins);
|
|
18719
|
-
if (
|
|
18902
|
+
if (lstatSync22(localPlugins, { throwIfNoEntry: false }) && readdirSync10(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
18720
18903
|
unresolved("additional local plugin hooks require review");
|
|
18904
|
+
} else if (agent === "sumi") {
|
|
18905
|
+
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical6(configPath) && source.path !== canonical6(join28(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
|
|
18906
|
+
unresolved("external or foreign local plugins require review");
|
|
18721
18907
|
} else if (agent === "hermes") {
|
|
18722
|
-
for (const directory of [
|
|
18908
|
+
for (const directory of [join28(home, ".hermes/plugins"), join28(home, ".hermes/hermes-agent")]) {
|
|
18723
18909
|
safe4(directory);
|
|
18724
|
-
if (
|
|
18910
|
+
if (lstatSync22(directory, { throwIfNoEntry: false }))
|
|
18725
18911
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
18726
18912
|
}
|
|
18727
18913
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -18729,30 +18915,30 @@ function resolveAgentDiscovery(options) {
|
|
|
18729
18915
|
} else if (agent === "gemini") {
|
|
18730
18916
|
if (Object.keys(config.extensions ?? {}).length || config.skills?.paths?.length)
|
|
18731
18917
|
unresolved("custom extension or skill paths");
|
|
18732
|
-
const directory =
|
|
18918
|
+
const directory = join28(home, ".gemini/extensions");
|
|
18733
18919
|
safe4(directory);
|
|
18734
|
-
if (
|
|
18920
|
+
if (lstatSync22(directory, { throwIfNoEntry: false }))
|
|
18735
18921
|
for (const name of readdirSync10(directory)) {
|
|
18736
|
-
const root2 =
|
|
18922
|
+
const root2 = join28(directory, name);
|
|
18737
18923
|
safe4(root2);
|
|
18738
|
-
if (!
|
|
18924
|
+
if (!lstatSync22(root2)?.isDirectory())
|
|
18739
18925
|
continue;
|
|
18740
|
-
const raw = witness(
|
|
18926
|
+
const raw = witness(join28(root2, "gemini-extension.json"));
|
|
18741
18927
|
if (raw === null)
|
|
18742
18928
|
unresolved("extension registration missing");
|
|
18743
|
-
const manifest = parseConfig(raw,
|
|
18744
|
-
if (manifest.hooks !== undefined || witness(
|
|
18929
|
+
const manifest = parseConfig(raw, join28(root2, "gemini-extension.json"));
|
|
18930
|
+
if (manifest.hooks !== undefined || witness(join28(root2, "hooks/hooks.json")) !== null || witness(join28(root2, ".gemini-extension-install.json")) !== null)
|
|
18745
18931
|
unresolved("extension hooks or linked registration require review");
|
|
18746
|
-
roots.add(
|
|
18932
|
+
roots.add(join28(root2, "skills"));
|
|
18747
18933
|
}
|
|
18748
18934
|
}
|
|
18749
|
-
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([
|
|
18935
|
+
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join28(home, ".hermes/plugins"), join28(home, ".hermes/hermes-agent")].map((path) => canonical6(path))) } : {} };
|
|
18750
18936
|
}
|
|
18751
18937
|
|
|
18752
18938
|
// src/lib/authoring-path.ts
|
|
18753
18939
|
var NATIVE_DISCOVERY_ROOTS = NATIVE_SKILL_ROOTS.map(([, path]) => path.split("/"));
|
|
18754
18940
|
function pathParts(path) {
|
|
18755
|
-
return
|
|
18941
|
+
return resolve17(path).split(sep7).filter(Boolean);
|
|
18756
18942
|
}
|
|
18757
18943
|
function nativeRootMatch(path) {
|
|
18758
18944
|
const parts = pathParts(path);
|
|
@@ -18766,24 +18952,24 @@ function nativeRootMatch(path) {
|
|
|
18766
18952
|
return;
|
|
18767
18953
|
}
|
|
18768
18954
|
function physicalPath(path) {
|
|
18769
|
-
let cursor =
|
|
18955
|
+
let cursor = resolve17(path);
|
|
18770
18956
|
const suffix = [];
|
|
18771
|
-
while (!
|
|
18957
|
+
while (!lstatSync23(cursor, { throwIfNoEntry: false })) {
|
|
18772
18958
|
suffix.unshift(basename10(cursor));
|
|
18773
18959
|
const parent = dirname18(cursor);
|
|
18774
18960
|
if (parent === cursor)
|
|
18775
|
-
return
|
|
18961
|
+
return resolve17(path);
|
|
18776
18962
|
cursor = parent;
|
|
18777
18963
|
}
|
|
18778
|
-
return
|
|
18964
|
+
return resolve17(realpathSync9(cursor), ...suffix);
|
|
18779
18965
|
}
|
|
18780
18966
|
function contains(root2, path) {
|
|
18781
18967
|
return path === root2 || path.startsWith(root2 + sep7);
|
|
18782
18968
|
}
|
|
18783
18969
|
function configuredRoots(home, explicitHome) {
|
|
18784
|
-
const homeData =
|
|
18970
|
+
const homeData = join29(home, ".hasna", "skills");
|
|
18785
18971
|
const homePolicy = readManagedSkillPolicySnapshot(homeData);
|
|
18786
|
-
const configuredPolicy = !explicitHome &&
|
|
18972
|
+
const configuredPolicy = !explicitHome && resolve17(getDataDirReadOnly()) !== resolve17(homeData) ? readManagedSkillPolicySnapshot(getDataDirReadOnly()) : null;
|
|
18787
18973
|
const policy = homePolicy ?? configuredPolicy;
|
|
18788
18974
|
const bridge = policy?.value.bridge;
|
|
18789
18975
|
if (bridge && (typeof bridge.home !== "string" || physicalPath(bridge.home) !== physicalPath(home))) {
|
|
@@ -18812,8 +18998,8 @@ function configuredRoots(home, explicitHome) {
|
|
|
18812
18998
|
admitBinding(binding);
|
|
18813
18999
|
continue;
|
|
18814
19000
|
}
|
|
18815
|
-
const nativeHome =
|
|
18816
|
-
if (!
|
|
19001
|
+
const nativeHome = join29(home, agent === "opencode" ? ".config/opencode" : `.${agent}`);
|
|
19002
|
+
if (!lstatSync23(nativeHome, { throwIfNoEntry: false }))
|
|
18817
19003
|
continue;
|
|
18818
19004
|
roots.push(...resolveAgentDiscovery({ home, agent, canonical: physicalPath }).roots);
|
|
18819
19005
|
}
|
|
@@ -18822,22 +19008,22 @@ function configuredRoots(home, explicitHome) {
|
|
|
18822
19008
|
function assertPortableAuthoringPath(path, options = {}) {
|
|
18823
19009
|
if (typeof path !== "string" || !path || path.includes("\x00"))
|
|
18824
19010
|
throw new Error("Invalid portable authoring path");
|
|
18825
|
-
const lexical =
|
|
19011
|
+
const lexical = resolve17(path);
|
|
18826
19012
|
const physical = physicalPath(lexical);
|
|
18827
19013
|
const candidates = [lexical, physical];
|
|
18828
19014
|
let native = candidates.map(nativeRootMatch).find(Boolean);
|
|
18829
|
-
const home =
|
|
18830
|
-
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) =>
|
|
18831
|
-
defaults.push(
|
|
19015
|
+
const home = resolve17(options.homeDir ?? homedir6());
|
|
19016
|
+
const defaults = NATIVE_DISCOVERY_ROOTS.map((parts) => join29(home, ...parts));
|
|
19017
|
+
defaults.push(join29(home, ".codex/plugins/cache"), join29(home, ".claude/plugins/cache"), join29(home, ".claude/plugins/synced"), join29(home, ".gemini/extensions"));
|
|
18832
19018
|
for (const root2 of defaults) {
|
|
18833
|
-
if (candidates.some((candidate2) => contains(
|
|
19019
|
+
if (candidates.some((candidate2) => contains(resolve17(root2), candidate2) || contains(physicalPath(root2), candidate2)))
|
|
18834
19020
|
native ??= root2;
|
|
18835
19021
|
}
|
|
18836
19022
|
if (native) {
|
|
18837
19023
|
throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
|
|
18838
19024
|
}
|
|
18839
19025
|
for (const root2 of configuredRoots(home, options.homeDir !== undefined)) {
|
|
18840
|
-
if (candidates.some((candidate2) => contains(
|
|
19026
|
+
if (candidates.some((candidate2) => contains(resolve17(root2), candidate2) || contains(physicalPath(root2), candidate2))) {
|
|
18841
19027
|
throw new Error(`Refusing portable Skills authoring below configured native discovery root: ${lexical}`);
|
|
18842
19028
|
}
|
|
18843
19029
|
}
|
|
@@ -18849,15 +19035,15 @@ function getPortableSkillsRoot(options = {}) {
|
|
|
18849
19035
|
function resolvePortableSkillsRoot(options, migrate) {
|
|
18850
19036
|
if (options.rootDir)
|
|
18851
19037
|
return options.rootDir;
|
|
18852
|
-
const appDir = options.homeDir ?
|
|
18853
|
-
const cache =
|
|
19038
|
+
const appDir = options.homeDir ? join30(options.homeDir, ".hasna", "skills") : migrate ? getDataDir() : getDataDirReadOnly();
|
|
19039
|
+
const cache = join30(appDir, SKILLS_CACHE_DIRNAME);
|
|
18854
19040
|
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
18855
19041
|
return cache;
|
|
18856
|
-
const installed =
|
|
19042
|
+
const installed = join30(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
18857
19043
|
return installed;
|
|
18858
19044
|
}
|
|
18859
19045
|
function getPortableSkillPath(name, options = {}) {
|
|
18860
|
-
return
|
|
19046
|
+
return join30(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
18861
19047
|
}
|
|
18862
19048
|
function findPortableSkill(name, options = {}) {
|
|
18863
19049
|
let normalized;
|
|
@@ -18867,7 +19053,7 @@ function findPortableSkill(name, options = {}) {
|
|
|
18867
19053
|
return null;
|
|
18868
19054
|
}
|
|
18869
19055
|
const path = getPortableSkillPath(normalized, options);
|
|
18870
|
-
if (!
|
|
19056
|
+
if (!existsSync14(path) || !statSync10(path).isDirectory())
|
|
18871
19057
|
return null;
|
|
18872
19058
|
try {
|
|
18873
19059
|
return summarizePortableSkill(path, normalized);
|
|
@@ -18883,7 +19069,7 @@ function listPortableSkills(options = {}) {
|
|
|
18883
19069
|
for (const entry of readdirSync11(root2).sort()) {
|
|
18884
19070
|
if (entry.startsWith("."))
|
|
18885
19071
|
continue;
|
|
18886
|
-
const path =
|
|
19072
|
+
const path = join30(root2, entry);
|
|
18887
19073
|
if (!safeIsDirectory(path))
|
|
18888
19074
|
continue;
|
|
18889
19075
|
try {
|
|
@@ -18917,11 +19103,11 @@ function isOfficialSkillName(name) {
|
|
|
18917
19103
|
function scaffoldPortableSkill(name, options = {}) {
|
|
18918
19104
|
const skillName = normalizeNewPortableSkillName(name);
|
|
18919
19105
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
18920
|
-
assertPortableAuthoringPath(
|
|
19106
|
+
assertPortableAuthoringPath(join30(readOnlyRoot, skillName), options);
|
|
18921
19107
|
const root2 = getPortableSkillsRoot(options);
|
|
18922
|
-
const skillPath =
|
|
19108
|
+
const skillPath = join30(root2, skillName);
|
|
18923
19109
|
assertPortableAuthoringPath(skillPath, options);
|
|
18924
|
-
if (
|
|
19110
|
+
if (existsSync14(skillPath)) {
|
|
18925
19111
|
if (!options.overwrite)
|
|
18926
19112
|
throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
|
|
18927
19113
|
rmSync4(skillPath, { recursive: true, force: true });
|
|
@@ -18939,7 +19125,7 @@ function scaffoldPortableSkill(name, options = {}) {
|
|
|
18939
19125
|
}
|
|
18940
19126
|
function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
18941
19127
|
const absoluteSource = normalize4(sourceDir);
|
|
18942
|
-
if (!
|
|
19128
|
+
if (!existsSync14(absoluteSource) || !statSync10(absoluteSource).isDirectory()) {
|
|
18943
19129
|
throw new Error(`Import directory not found: ${sourceDir}`);
|
|
18944
19130
|
}
|
|
18945
19131
|
const continueOnError = options.continueOnError ?? true;
|
|
@@ -18953,7 +19139,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
18953
19139
|
const skipped = [];
|
|
18954
19140
|
const entries = readdirSync11(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
18955
19141
|
for (const entryName of entries) {
|
|
18956
|
-
const childPath =
|
|
19142
|
+
const childPath = join30(absoluteSource, entryName);
|
|
18957
19143
|
if (!safeIsDirectory(childPath))
|
|
18958
19144
|
continue;
|
|
18959
19145
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -18982,19 +19168,19 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
18982
19168
|
};
|
|
18983
19169
|
}
|
|
18984
19170
|
function isSkillCandidate(dir) {
|
|
18985
|
-
return
|
|
19171
|
+
return existsSync14(join30(dir, "SKILL.md")) || existsSync14(join30(dir, "skill.json")) || existsSync14(join30(dir, "package.json"));
|
|
18986
19172
|
}
|
|
18987
19173
|
function physicalImportPath(path) {
|
|
18988
|
-
let ancestor =
|
|
19174
|
+
let ancestor = resolve18(path);
|
|
18989
19175
|
const suffix = [];
|
|
18990
|
-
while (!
|
|
19176
|
+
while (!existsSync14(ancestor)) {
|
|
18991
19177
|
suffix.unshift(basename11(ancestor));
|
|
18992
19178
|
ancestor = dirname19(ancestor);
|
|
18993
19179
|
}
|
|
18994
|
-
return
|
|
19180
|
+
return join30(realpathSync10(ancestor), ...suffix);
|
|
18995
19181
|
}
|
|
18996
19182
|
function assertDisjointImportPaths(source, destination) {
|
|
18997
|
-
const sourcePath =
|
|
19183
|
+
const sourcePath = realpathSync10(source);
|
|
18998
19184
|
const destinationPath = physicalImportPath(destination);
|
|
18999
19185
|
const contains2 = (parent, child) => {
|
|
19000
19186
|
const path = relative6(parent, child);
|
|
@@ -19006,7 +19192,7 @@ function assertDisjointImportPaths(source, destination) {
|
|
|
19006
19192
|
}
|
|
19007
19193
|
function portPortableSkill(sourcePath, options = {}) {
|
|
19008
19194
|
const absoluteSource = normalize4(sourcePath);
|
|
19009
|
-
if (!
|
|
19195
|
+
if (!existsSync14(absoluteSource) || !statSync10(absoluteSource).isDirectory()) {
|
|
19010
19196
|
throw new Error(`Skill source directory not found: ${sourcePath}`);
|
|
19011
19197
|
}
|
|
19012
19198
|
const inferred = readPortableSkillManifestForImport(absoluteSource);
|
|
@@ -19017,14 +19203,14 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
19017
19203
|
const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename11(absoluteSource)}')` : ""} matches a bundled official skill.`;
|
|
19018
19204
|
throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
|
|
19019
19205
|
}
|
|
19020
|
-
const readOnlyDestination =
|
|
19206
|
+
const readOnlyDestination = join30(resolvePortableSkillsRoot(options, false), skillName);
|
|
19021
19207
|
assertPortableAuthoringPath(readOnlyDestination, options);
|
|
19022
19208
|
assertDisjointImportPaths(absoluteSource, readOnlyDestination);
|
|
19023
19209
|
const root2 = getPortableSkillsRoot(options);
|
|
19024
|
-
const destination =
|
|
19210
|
+
const destination = join30(root2, skillName);
|
|
19025
19211
|
assertPortableAuthoringPath(destination, options);
|
|
19026
19212
|
assertDisjointImportPaths(absoluteSource, destination);
|
|
19027
|
-
if (
|
|
19213
|
+
if (existsSync14(destination)) {
|
|
19028
19214
|
if (!options.overwrite)
|
|
19029
19215
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
19030
19216
|
rmSync4(destination, { recursive: true, force: true });
|
|
@@ -19066,13 +19252,13 @@ function buildCorpusManifest(input, name) {
|
|
|
19066
19252
|
function writeCorpusSkill(input, options = {}) {
|
|
19067
19253
|
const name = normalizePortableSkillName(input.name);
|
|
19068
19254
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
19069
|
-
assertPortableAuthoringPath(
|
|
19255
|
+
assertPortableAuthoringPath(join30(readOnlyRoot, name), options);
|
|
19070
19256
|
const root2 = getPortableSkillsRoot(options);
|
|
19071
|
-
const skillPath =
|
|
19257
|
+
const skillPath = join30(root2, name);
|
|
19072
19258
|
assertPortableAuthoringPath(skillPath, options);
|
|
19073
|
-
const created = !
|
|
19259
|
+
const created = !existsSync14(skillPath);
|
|
19074
19260
|
mkdirSync9(skillPath, { recursive: true });
|
|
19075
|
-
writeFileSync8(
|
|
19261
|
+
writeFileSync8(join30(skillPath, "SKILL.md"), input.skillMd);
|
|
19076
19262
|
const manifest = buildCorpusManifest(input, name);
|
|
19077
19263
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
19078
19264
|
return { name, path: skillPath, manifest, created };
|
|
@@ -19080,22 +19266,22 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
19080
19266
|
function installCorpusSkillAtomically(input, options = {}) {
|
|
19081
19267
|
const name = normalizePortableSkillName(input.name);
|
|
19082
19268
|
const readOnlyRoot = resolvePortableSkillsRoot(options, false);
|
|
19083
|
-
assertPortableAuthoringPath(
|
|
19269
|
+
assertPortableAuthoringPath(join30(readOnlyRoot, name), options);
|
|
19084
19270
|
const root2 = getPortableSkillsRoot(options);
|
|
19085
|
-
const target =
|
|
19271
|
+
const target = join30(root2, name);
|
|
19086
19272
|
assertPortableAuthoringPath(target, options);
|
|
19087
|
-
const created = !
|
|
19273
|
+
const created = !existsSync14(target);
|
|
19088
19274
|
mkdirSync9(root2, { recursive: true });
|
|
19089
|
-
const staging = mkdtempSync2(
|
|
19275
|
+
const staging = mkdtempSync2(join30(root2, `.pull-${name}-`));
|
|
19090
19276
|
let moved = false;
|
|
19091
19277
|
let backup = null;
|
|
19092
19278
|
try {
|
|
19093
|
-
writeFileSync8(
|
|
19279
|
+
writeFileSync8(join30(staging, "SKILL.md"), input.skillMd);
|
|
19094
19280
|
const manifest = buildCorpusManifest(input, name);
|
|
19095
19281
|
writeSkillJsonWithHash(staging, manifest);
|
|
19096
|
-
if (
|
|
19097
|
-
backup = mkdtempSync2(
|
|
19098
|
-
renameSync5(target,
|
|
19282
|
+
if (existsSync14(target)) {
|
|
19283
|
+
backup = mkdtempSync2(join30(root2, `.pull-backup-${name}-`));
|
|
19284
|
+
renameSync5(target, join30(backup, name));
|
|
19099
19285
|
moved = true;
|
|
19100
19286
|
}
|
|
19101
19287
|
renameSync5(staging, target);
|
|
@@ -19104,9 +19290,9 @@ function installCorpusSkillAtomically(input, options = {}) {
|
|
|
19104
19290
|
return { name, path: target, manifest, created };
|
|
19105
19291
|
} catch (error) {
|
|
19106
19292
|
rmSync4(staging, { recursive: true, force: true });
|
|
19107
|
-
if (moved && backup &&
|
|
19293
|
+
if (moved && backup && existsSync14(join30(backup, name))) {
|
|
19108
19294
|
try {
|
|
19109
|
-
renameSync5(
|
|
19295
|
+
renameSync5(join30(backup, name), target);
|
|
19110
19296
|
} catch {}
|
|
19111
19297
|
}
|
|
19112
19298
|
throw error;
|
|
@@ -19118,10 +19304,10 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19118
19304
|
const issues = [...base.issues];
|
|
19119
19305
|
const warnings = [...base.warnings];
|
|
19120
19306
|
let manifest;
|
|
19121
|
-
if (
|
|
19122
|
-
const skillJsonPath =
|
|
19123
|
-
const skillMdPath =
|
|
19124
|
-
if (!
|
|
19307
|
+
if (existsSync14(skillPath)) {
|
|
19308
|
+
const skillJsonPath = join30(skillPath, "skill.json");
|
|
19309
|
+
const skillMdPath = join30(skillPath, "SKILL.md");
|
|
19310
|
+
if (!existsSync14(skillJsonPath) && !existsSync14(skillMdPath)) {
|
|
19125
19311
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
19126
19312
|
}
|
|
19127
19313
|
try {
|
|
@@ -19140,7 +19326,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19140
19326
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
19141
19327
|
}
|
|
19142
19328
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
19143
|
-
strict:
|
|
19329
|
+
strict: existsSync14(join30(skillPath, "skill.json")),
|
|
19144
19330
|
skillPath
|
|
19145
19331
|
});
|
|
19146
19332
|
for (const issue of contractIssues)
|
|
@@ -19180,8 +19366,8 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19180
19366
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
19181
19367
|
continue;
|
|
19182
19368
|
}
|
|
19183
|
-
const entryPath =
|
|
19184
|
-
if (!
|
|
19369
|
+
const entryPath = join30(skillPath, command.entry);
|
|
19370
|
+
if (!existsSync14(entryPath))
|
|
19185
19371
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
19186
19372
|
else if (statSync10(entryPath).isDirectory())
|
|
19187
19373
|
add3(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
|
|
@@ -19191,7 +19377,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
19191
19377
|
} catch (error) {
|
|
19192
19378
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
19193
19379
|
}
|
|
19194
|
-
if (manifest?.kind !== "instruction" && !
|
|
19380
|
+
if (manifest?.kind !== "instruction" && !existsSync14(join30(skillPath, "AGENTS.md"))) {
|
|
19195
19381
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
19196
19382
|
}
|
|
19197
19383
|
}
|
|
@@ -19227,13 +19413,13 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
19227
19413
|
if (!isSafeRelativePath2(command.entry)) {
|
|
19228
19414
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
19229
19415
|
}
|
|
19230
|
-
const entryPath =
|
|
19231
|
-
if (!
|
|
19416
|
+
const entryPath = join30(skill.path, command.entry);
|
|
19417
|
+
if (!existsSync14(entryPath)) {
|
|
19232
19418
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
19233
19419
|
}
|
|
19234
|
-
const pkgPath =
|
|
19235
|
-
const nodeModules =
|
|
19236
|
-
if (
|
|
19420
|
+
const pkgPath = join30(skill.path, "package.json");
|
|
19421
|
+
const nodeModules = join30(skill.path, "node_modules");
|
|
19422
|
+
if (existsSync14(pkgPath) && !existsSync14(nodeModules) && hasPackageDependencies(pkgPath)) {
|
|
19237
19423
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
19238
19424
|
cwd: skill.path,
|
|
19239
19425
|
stdout: "pipe",
|
|
@@ -19481,7 +19667,7 @@ function parseSkillMdFrontmatter(content) {
|
|
|
19481
19667
|
return Object.keys(result).length > 0 ? result : null;
|
|
19482
19668
|
}
|
|
19483
19669
|
function discoverSkillsInDir(dir, source = "custom") {
|
|
19484
|
-
if (!
|
|
19670
|
+
if (!existsSync15(dir))
|
|
19485
19671
|
return [];
|
|
19486
19672
|
const result = [];
|
|
19487
19673
|
try {
|
|
@@ -19489,8 +19675,8 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
19489
19675
|
for (const entry of entries) {
|
|
19490
19676
|
if (!entry.isDirectory())
|
|
19491
19677
|
continue;
|
|
19492
|
-
const skillMdPath =
|
|
19493
|
-
if (!
|
|
19678
|
+
const skillMdPath = join31(dir, entry.name, "SKILL.md");
|
|
19679
|
+
if (!existsSync15(skillMdPath))
|
|
19494
19680
|
continue;
|
|
19495
19681
|
let content;
|
|
19496
19682
|
try {
|
|
@@ -19509,7 +19695,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
19509
19695
|
category: fm.category || "Development Tools",
|
|
19510
19696
|
tags: fm.tags || [],
|
|
19511
19697
|
...fm.kind ? { kind: fm.kind } : {},
|
|
19512
|
-
...isHostedMetadataSkillDir(
|
|
19698
|
+
...isHostedMetadataSkillDir(join31(dir, entry.name)) ? { serverOwned: true } : {},
|
|
19513
19699
|
source
|
|
19514
19700
|
});
|
|
19515
19701
|
}
|
|
@@ -19518,16 +19704,16 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
19518
19704
|
}
|
|
19519
19705
|
function findExtensionSkillPath(name) {
|
|
19520
19706
|
const config = loadConfig();
|
|
19521
|
-
if (!config.extensionsDir || !
|
|
19707
|
+
if (!config.extensionsDir || !existsSync15(config.extensionsDir))
|
|
19522
19708
|
return null;
|
|
19523
19709
|
try {
|
|
19524
19710
|
const entries = readdirSync12(config.extensionsDir, { withFileTypes: true });
|
|
19525
19711
|
for (const entry of entries) {
|
|
19526
19712
|
if (!entry.isDirectory())
|
|
19527
19713
|
continue;
|
|
19528
|
-
const skillDir =
|
|
19529
|
-
const skillMdPath =
|
|
19530
|
-
if (!
|
|
19714
|
+
const skillDir = join31(config.extensionsDir, entry.name);
|
|
19715
|
+
const skillMdPath = join31(skillDir, "SKILL.md");
|
|
19716
|
+
if (!existsSync15(skillMdPath))
|
|
19531
19717
|
continue;
|
|
19532
19718
|
let content;
|
|
19533
19719
|
try {
|
|
@@ -19574,21 +19760,21 @@ function clearRegistryCache() {
|
|
|
19574
19760
|
registryCacheTime = 0;
|
|
19575
19761
|
registryCacheKey = null;
|
|
19576
19762
|
}
|
|
19577
|
-
function getSkillsByCategory(category) {
|
|
19578
|
-
return loadRegistry().filter((s) => s.category === category);
|
|
19763
|
+
function getSkillsByCategory(category, catalog) {
|
|
19764
|
+
return (catalog ?? loadRegistry()).filter((s) => s.category === category);
|
|
19579
19765
|
}
|
|
19580
19766
|
function getSkill(name) {
|
|
19581
19767
|
const registry = loadRegistry();
|
|
19582
19768
|
const slug = normalizeSkillSlug(name);
|
|
19583
19769
|
return registry.find((s) => s.name === slug) ?? registry.find((s) => s.name === resolveSkillAlias(slug));
|
|
19584
19770
|
}
|
|
19585
|
-
function getSkillsByTag(tag) {
|
|
19771
|
+
function getSkillsByTag(tag, catalog) {
|
|
19586
19772
|
const needle = tag.toLowerCase();
|
|
19587
|
-
return loadRegistry().filter((s) => s.tags.some((t2) => t2.toLowerCase().includes(needle)));
|
|
19773
|
+
return (catalog ?? loadRegistry()).filter((s) => s.tags.some((t2) => t2.toLowerCase().includes(needle)));
|
|
19588
19774
|
}
|
|
19589
|
-
function getAllTags() {
|
|
19775
|
+
function getAllTags(catalog) {
|
|
19590
19776
|
const tagSet = new Set;
|
|
19591
|
-
for (const skill of loadRegistry()) {
|
|
19777
|
+
for (const skill of catalog ?? loadRegistry()) {
|
|
19592
19778
|
for (const tag of skill.tags)
|
|
19593
19779
|
tagSet.add(tag.toLowerCase());
|
|
19594
19780
|
}
|
|
@@ -19596,20 +19782,20 @@ function getAllTags() {
|
|
|
19596
19782
|
}
|
|
19597
19783
|
// src/lib/run-state.ts
|
|
19598
19784
|
import { createHash as createHash19, randomBytes } from "crypto";
|
|
19599
|
-
import { existsSync as
|
|
19600
|
-
import { extname, join as
|
|
19785
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync19, readdirSync as readdirSync13, statSync as statSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
19786
|
+
import { extname, join as join32, relative as relative7 } from "path";
|
|
19601
19787
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
19602
19788
|
const now = new Date;
|
|
19603
19789
|
const id = createRunId(now);
|
|
19604
19790
|
const day = now.toISOString().slice(0, 10);
|
|
19605
19791
|
const skillName = normalizeSkillName(params.skill);
|
|
19606
19792
|
const root2 = getProjectStateDir(targetDir);
|
|
19607
|
-
const runDir =
|
|
19608
|
-
const logsDir =
|
|
19609
|
-
const exportDir =
|
|
19793
|
+
const runDir = join32(root2, "runs", day, id);
|
|
19794
|
+
const logsDir = join32(runDir, "logs");
|
|
19795
|
+
const exportDir = join32(root2, "exports", skillName, id);
|
|
19610
19796
|
mkdirSync10(logsDir, { recursive: true });
|
|
19611
19797
|
mkdirSync10(exportDir, { recursive: true });
|
|
19612
|
-
mkdirSync10(
|
|
19798
|
+
mkdirSync10(join32(root2, "tmp"), { recursive: true });
|
|
19613
19799
|
const record3 = {
|
|
19614
19800
|
id,
|
|
19615
19801
|
skill: skillName,
|
|
@@ -19661,27 +19847,27 @@ function updateSkillRun(context, patch) {
|
|
|
19661
19847
|
return context.record;
|
|
19662
19848
|
}
|
|
19663
19849
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
19664
|
-
writeFileSync9(
|
|
19665
|
-
writeFileSync9(
|
|
19850
|
+
writeFileSync9(join32(context.logsDir, "stdout.log"), stdout);
|
|
19851
|
+
writeFileSync9(join32(context.logsDir, "stderr.log"), stderr);
|
|
19666
19852
|
}
|
|
19667
19853
|
function appendRunEvent(context, event, data = {}) {
|
|
19668
19854
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
19669
19855
|
`;
|
|
19670
|
-
const path =
|
|
19671
|
-
const previous =
|
|
19856
|
+
const path = join32(context.runDir, "events.ndjson");
|
|
19857
|
+
const previous = existsSync16(path) ? readFileSync19(path, "utf-8") : "";
|
|
19672
19858
|
writeFileSync9(path, previous + line);
|
|
19673
19859
|
}
|
|
19674
19860
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
19675
|
-
const runsRoot =
|
|
19676
|
-
if (!
|
|
19861
|
+
const runsRoot = join32(getProjectStateDir(targetDir), "runs");
|
|
19862
|
+
if (!existsSync16(runsRoot))
|
|
19677
19863
|
return [];
|
|
19678
19864
|
const records = [];
|
|
19679
19865
|
for (const day of readdirSync13(runsRoot).sort().reverse()) {
|
|
19680
|
-
const dayDir =
|
|
19866
|
+
const dayDir = join32(runsRoot, day);
|
|
19681
19867
|
if (!statSync11(dayDir).isDirectory())
|
|
19682
19868
|
continue;
|
|
19683
19869
|
for (const runId of readdirSync13(dayDir).sort().reverse()) {
|
|
19684
|
-
const record3 = readRunRecord(
|
|
19870
|
+
const record3 = readRunRecord(join32(dayDir, runId));
|
|
19685
19871
|
if (record3)
|
|
19686
19872
|
records.push(record3);
|
|
19687
19873
|
if (records.length >= limit)
|
|
@@ -19691,29 +19877,29 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
19691
19877
|
return records;
|
|
19692
19878
|
}
|
|
19693
19879
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
19694
|
-
const runsRoot =
|
|
19695
|
-
if (!
|
|
19880
|
+
const runsRoot = join32(getProjectStateDir(targetDir), "runs");
|
|
19881
|
+
if (!existsSync16(runsRoot))
|
|
19696
19882
|
return null;
|
|
19697
19883
|
for (const day of readdirSync13(runsRoot)) {
|
|
19698
|
-
const record3 = readRunRecord(
|
|
19884
|
+
const record3 = readRunRecord(join32(runsRoot, day, runId));
|
|
19699
19885
|
if (record3)
|
|
19700
19886
|
return record3;
|
|
19701
19887
|
}
|
|
19702
19888
|
return null;
|
|
19703
19889
|
}
|
|
19704
19890
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
19705
|
-
return
|
|
19891
|
+
return join32(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
19706
19892
|
}
|
|
19707
19893
|
function writeRunRecord(context) {
|
|
19708
|
-
writeFileSync9(
|
|
19894
|
+
writeFileSync9(join32(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
19709
19895
|
`);
|
|
19710
19896
|
}
|
|
19711
19897
|
function writeArtifactsManifest(context, artifacts) {
|
|
19712
|
-
writeFileSync9(
|
|
19898
|
+
writeFileSync9(join32(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
19713
19899
|
`);
|
|
19714
19900
|
}
|
|
19715
19901
|
function collectRunArtifacts(context) {
|
|
19716
|
-
if (!
|
|
19902
|
+
if (!existsSync16(context.exportDir))
|
|
19717
19903
|
return [];
|
|
19718
19904
|
const artifacts = [];
|
|
19719
19905
|
for (const path of walkFiles(context.exportDir)) {
|
|
@@ -19729,8 +19915,8 @@ function collectRunArtifacts(context) {
|
|
|
19729
19915
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
19730
19916
|
}
|
|
19731
19917
|
function readRunRecord(runDir) {
|
|
19732
|
-
const path =
|
|
19733
|
-
if (!
|
|
19918
|
+
const path = join32(runDir, "run.json");
|
|
19919
|
+
if (!existsSync16(path))
|
|
19734
19920
|
return null;
|
|
19735
19921
|
try {
|
|
19736
19922
|
return JSON.parse(readFileSync19(path, "utf-8"));
|
|
@@ -19741,7 +19927,7 @@ function readRunRecord(runDir) {
|
|
|
19741
19927
|
function walkFiles(dir) {
|
|
19742
19928
|
const files = [];
|
|
19743
19929
|
for (const entry of readdirSync13(dir)) {
|
|
19744
|
-
const full =
|
|
19930
|
+
const full = join32(dir, entry);
|
|
19745
19931
|
if (statSync11(full).isDirectory())
|
|
19746
19932
|
files.push(...walkFiles(full));
|
|
19747
19933
|
else
|
|
@@ -40146,10 +40332,10 @@ async function readQuoteUnavailableCode(response) {
|
|
|
40146
40332
|
let timer;
|
|
40147
40333
|
let deadlineExceeded = false;
|
|
40148
40334
|
const expired = Symbol("quote body deadline");
|
|
40149
|
-
const deadline = new Promise((
|
|
40335
|
+
const deadline = new Promise((resolve19) => {
|
|
40150
40336
|
timer = setTimeout(() => {
|
|
40151
40337
|
deadlineExceeded = true;
|
|
40152
|
-
|
|
40338
|
+
resolve19(expired);
|
|
40153
40339
|
reader.cancel().catch(() => {});
|
|
40154
40340
|
}, 1500);
|
|
40155
40341
|
});
|
|
@@ -41248,14 +41434,14 @@ async function createRemoteSkillsClient(env = process.env) {
|
|
|
41248
41434
|
return connection ? new RemoteSkillsClient(connection.apiKey, connection.apiOrigin) : null;
|
|
41249
41435
|
}
|
|
41250
41436
|
// src/lib/scheduler.ts
|
|
41251
|
-
import { existsSync as
|
|
41252
|
-
import { join as
|
|
41437
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11 } from "fs";
|
|
41438
|
+
import { join as join33 } from "path";
|
|
41253
41439
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
41254
|
-
return
|
|
41440
|
+
return join33(targetDir, ".skills", "schedules.json");
|
|
41255
41441
|
}
|
|
41256
41442
|
function loadSchedules(targetDir = process.cwd()) {
|
|
41257
41443
|
const path = getSchedulesPath(targetDir);
|
|
41258
|
-
if (
|
|
41444
|
+
if (existsSync17(path)) {
|
|
41259
41445
|
try {
|
|
41260
41446
|
return JSON.parse(readFileSync20(path, "utf-8"));
|
|
41261
41447
|
} catch {}
|
|
@@ -41264,8 +41450,8 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
41264
41450
|
}
|
|
41265
41451
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
41266
41452
|
const path = getSchedulesPath(targetDir);
|
|
41267
|
-
const dir =
|
|
41268
|
-
if (!
|
|
41453
|
+
const dir = join33(targetDir, ".skills");
|
|
41454
|
+
if (!existsSync17(dir))
|
|
41269
41455
|
mkdirSync11(dir, { recursive: true });
|
|
41270
41456
|
writeFileSync10(path, JSON.stringify(data, null, 2));
|
|
41271
41457
|
}
|
|
@@ -41480,8 +41666,8 @@ function revisionIdOfRecord(record8) {
|
|
|
41480
41666
|
return revisionIdOf(record8);
|
|
41481
41667
|
}
|
|
41482
41668
|
// src/lib/pull.ts
|
|
41483
|
-
import { existsSync as
|
|
41484
|
-
import { dirname as dirname20, join as
|
|
41669
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync21, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "fs";
|
|
41670
|
+
import { dirname as dirname20, join as join34 } from "path";
|
|
41485
41671
|
|
|
41486
41672
|
// src/lib/skill-bundles.ts
|
|
41487
41673
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -41575,7 +41761,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
41575
41761
|
return reconcileTombstone(slug2, corpusOptions);
|
|
41576
41762
|
}
|
|
41577
41763
|
if (bundleResponse.status === 404) {
|
|
41578
|
-
const marker = readPullMarker(
|
|
41764
|
+
const marker = readPullMarker(join34(getPortableSkillsRoot(corpusOptions), slug2));
|
|
41579
41765
|
if (isPublishedInstallMarker(marker)) {
|
|
41580
41766
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
41581
41767
|
}
|
|
@@ -41614,7 +41800,7 @@ async function pullOne(client2, rawName, corpusOptions, verify) {
|
|
|
41614
41800
|
return { name: slug2, success: false, error: `Skill '${slug2}' was not found on the configured Skills instance.` };
|
|
41615
41801
|
}
|
|
41616
41802
|
if (!meta3?.revisionId) {
|
|
41617
|
-
const marker = readPullMarker(
|
|
41803
|
+
const marker = readPullMarker(join34(getPortableSkillsRoot(corpusOptions), slug2));
|
|
41618
41804
|
if (isPublishedInstallMarker(marker)) {
|
|
41619
41805
|
return { name: slug2, success: true, purged: true, removed: false };
|
|
41620
41806
|
}
|
|
@@ -41671,7 +41857,7 @@ function provenRevision(meta3, slug2, bundle2) {
|
|
|
41671
41857
|
return declared;
|
|
41672
41858
|
}
|
|
41673
41859
|
function reconcileTombstone(slug2, corpusOptions) {
|
|
41674
|
-
const target =
|
|
41860
|
+
const target = join34(getPortableSkillsRoot(corpusOptions), slug2);
|
|
41675
41861
|
if (!hasSkillsOwnershipMarker(target)) {
|
|
41676
41862
|
return { name: slug2, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
41677
41863
|
}
|
|
@@ -41680,7 +41866,7 @@ function reconcileTombstone(slug2, corpusOptions) {
|
|
|
41680
41866
|
}
|
|
41681
41867
|
function readPullMarker(dir) {
|
|
41682
41868
|
try {
|
|
41683
|
-
return JSON.parse(readFileSync21(
|
|
41869
|
+
return JSON.parse(readFileSync21(join34(dir, PULL_MARKER_FILE), "utf-8"));
|
|
41684
41870
|
} catch {
|
|
41685
41871
|
return null;
|
|
41686
41872
|
}
|
|
@@ -41815,14 +42001,14 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
41815
42001
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
41816
42002
|
const root2 = getPortableSkillsRoot(options);
|
|
41817
42003
|
mkdirSync12(root2, { recursive: true });
|
|
41818
|
-
const target =
|
|
41819
|
-
const created = !
|
|
41820
|
-
const staging = mkdtempSync3(
|
|
42004
|
+
const target = join34(root2, name);
|
|
42005
|
+
const created = !existsSync18(target);
|
|
42006
|
+
const staging = mkdtempSync3(join34(root2, `.pull-${name}-`));
|
|
41821
42007
|
let moved = false;
|
|
41822
42008
|
let backup = null;
|
|
41823
42009
|
try {
|
|
41824
42010
|
for (const entry of entries) {
|
|
41825
|
-
const destination =
|
|
42011
|
+
const destination = join34(staging, entry.path);
|
|
41826
42012
|
mkdirSync12(dirname20(destination), { recursive: true });
|
|
41827
42013
|
writeFileSync11(destination, entry.bytes, { mode: entry.mode });
|
|
41828
42014
|
}
|
|
@@ -41834,9 +42020,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
41834
42020
|
...marker.signature ? { signature: marker.signature } : {},
|
|
41835
42021
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
41836
42022
|
});
|
|
41837
|
-
if (
|
|
41838
|
-
backup = mkdtempSync3(
|
|
41839
|
-
renameSync6(target,
|
|
42023
|
+
if (existsSync18(target)) {
|
|
42024
|
+
backup = mkdtempSync3(join34(root2, `.pull-backup-${name}-`));
|
|
42025
|
+
renameSync6(target, join34(backup, name));
|
|
41840
42026
|
moved = true;
|
|
41841
42027
|
}
|
|
41842
42028
|
renameSync6(staging, target);
|
|
@@ -41844,9 +42030,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
41844
42030
|
rmSync5(backup, { recursive: true, force: true });
|
|
41845
42031
|
} catch (error61) {
|
|
41846
42032
|
rmSync5(staging, { recursive: true, force: true });
|
|
41847
|
-
if (moved && backup &&
|
|
42033
|
+
if (moved && backup && existsSync18(join34(backup, name))) {
|
|
41848
42034
|
try {
|
|
41849
|
-
renameSync6(
|
|
42035
|
+
renameSync6(join34(backup, name), target);
|
|
41850
42036
|
} catch {}
|
|
41851
42037
|
}
|
|
41852
42038
|
throw error61;
|
|
@@ -41865,7 +42051,7 @@ function writePullMarker(dir, record8) {
|
|
|
41865
42051
|
...record8.revisionId ? { revisionId: record8.revisionId } : {},
|
|
41866
42052
|
syncedAt: new Date().toISOString()
|
|
41867
42053
|
};
|
|
41868
|
-
writeFileSync11(
|
|
42054
|
+
writeFileSync11(join34(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
41869
42055
|
`);
|
|
41870
42056
|
}
|
|
41871
42057
|
async function safeMeta(client2, slug2) {
|
|
@@ -42307,12 +42493,12 @@ class RemotePrivatePublicationsClient {
|
|
|
42307
42493
|
previous = view;
|
|
42308
42494
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
42309
42495
|
return view;
|
|
42310
|
-
await new Promise((
|
|
42496
|
+
await new Promise((resolve19) => {
|
|
42311
42497
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
42312
42498
|
function done() {
|
|
42313
42499
|
clearTimeout(timer);
|
|
42314
42500
|
options.signal?.removeEventListener("abort", done);
|
|
42315
|
-
|
|
42501
|
+
resolve19();
|
|
42316
42502
|
}
|
|
42317
42503
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
42318
42504
|
});
|
|
@@ -43868,16 +44054,16 @@ function clone3(value) {
|
|
|
43868
44054
|
return JSON.parse(JSON.stringify(value));
|
|
43869
44055
|
}
|
|
43870
44056
|
// src/lib/feedback.ts
|
|
43871
|
-
import { appendFileSync, existsSync as
|
|
43872
|
-
import { dirname as dirname22, join as
|
|
44057
|
+
import { appendFileSync, existsSync as existsSync19, mkdirSync as mkdirSync14 } from "fs";
|
|
44058
|
+
import { dirname as dirname22, join as join35 } from "path";
|
|
43873
44059
|
import { Database } from "bun:sqlite";
|
|
43874
44060
|
function getFeedbackDbPath() {
|
|
43875
|
-
return
|
|
44061
|
+
return join35(getDataDir(), "skills.db");
|
|
43876
44062
|
}
|
|
43877
44063
|
function getFeedbackDb() {
|
|
43878
44064
|
const dbPath = getFeedbackDbPath();
|
|
43879
44065
|
const dir = dirname22(dbPath);
|
|
43880
|
-
if (!
|
|
44066
|
+
if (!existsSync19(dir))
|
|
43881
44067
|
mkdirSync14(dir, { recursive: true });
|
|
43882
44068
|
const db = new Database(dbPath);
|
|
43883
44069
|
db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -43904,9 +44090,9 @@ function saveFeedback(input2) {
|
|
|
43904
44090
|
throw new Error("Feedback message is required");
|
|
43905
44091
|
const category = input2.category ?? "general";
|
|
43906
44092
|
if (isApiMode()) {
|
|
43907
|
-
const path =
|
|
44093
|
+
const path = join35(getDataDir(), "feedback.jsonl");
|
|
43908
44094
|
const dir = dirname22(path);
|
|
43909
|
-
if (!
|
|
44095
|
+
if (!existsSync19(dir))
|
|
43910
44096
|
mkdirSync14(dir, { recursive: true });
|
|
43911
44097
|
appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
43912
44098
|
`);
|
|
@@ -43930,14 +44116,14 @@ function isApiMode(env = process.env) {
|
|
|
43930
44116
|
// src/lib/native-storage.ts
|
|
43931
44117
|
import { createHash as createHash23, createHmac as createHmac2 } from "crypto";
|
|
43932
44118
|
import {
|
|
43933
|
-
existsSync as
|
|
44119
|
+
existsSync as existsSync20,
|
|
43934
44120
|
mkdirSync as mkdirSync15,
|
|
43935
44121
|
readFileSync as readFileSync23,
|
|
43936
44122
|
readdirSync as readdirSync14,
|
|
43937
44123
|
statSync as statSync12,
|
|
43938
44124
|
writeFileSync as writeFileSync13
|
|
43939
44125
|
} from "fs";
|
|
43940
|
-
import { dirname as dirname23, join as
|
|
44126
|
+
import { dirname as dirname23, join as join36, normalize as normalize5, relative as relative9, sep as sep9 } from "path";
|
|
43941
44127
|
var SKILLS_STORAGE_TABLES = [
|
|
43942
44128
|
"skills_sync_records",
|
|
43943
44129
|
"skills_sync_cursors"
|
|
@@ -44074,7 +44260,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
44074
44260
|
local: {
|
|
44075
44261
|
dataDir: getDataDir(),
|
|
44076
44262
|
projectStateDir: getProjectStateDir(targetDir),
|
|
44077
|
-
feedbackDbPath:
|
|
44263
|
+
feedbackDbPath: join36(getDataDir(), "skills.db")
|
|
44078
44264
|
},
|
|
44079
44265
|
remote: {
|
|
44080
44266
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -44097,7 +44283,7 @@ function getStorageStatus(options = {}) {
|
|
|
44097
44283
|
function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
44098
44284
|
const projectStateDir = getProjectStateDir(targetDir);
|
|
44099
44285
|
const files = [];
|
|
44100
|
-
if (
|
|
44286
|
+
if (existsSync20(projectStateDir)) {
|
|
44101
44287
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
44102
44288
|
const bytes = readFileSync23(filePath);
|
|
44103
44289
|
const relativePath = toPosix(relative9(targetDir, filePath));
|
|
@@ -44124,7 +44310,7 @@ function importSkillsLocalSnapshot(snapshot2, targetDir = process.cwd(), options
|
|
|
44124
44310
|
continue;
|
|
44125
44311
|
}
|
|
44126
44312
|
const absolutePath4 = resolveSnapshotPath(targetDir, file2.path);
|
|
44127
|
-
if (
|
|
44313
|
+
if (existsSync20(absolutePath4) && !options.overwrite) {
|
|
44128
44314
|
skipped += 1;
|
|
44129
44315
|
continue;
|
|
44130
44316
|
}
|
|
@@ -44431,7 +44617,7 @@ function parsePositiveInteger(value) {
|
|
|
44431
44617
|
function walkFiles2(dir) {
|
|
44432
44618
|
const files = [];
|
|
44433
44619
|
for (const entry of readdirSync14(dir)) {
|
|
44434
|
-
const full =
|
|
44620
|
+
const full = join36(dir, entry);
|
|
44435
44621
|
const stats = statSync12(full);
|
|
44436
44622
|
if (stats.isDirectory())
|
|
44437
44623
|
files.push(...walkFiles2(full));
|
|
@@ -44448,7 +44634,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
|
44448
44634
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
44449
44635
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
44450
44636
|
}
|
|
44451
|
-
return
|
|
44637
|
+
return join36(targetDir, normalizedPath);
|
|
44452
44638
|
}
|
|
44453
44639
|
function normalizeS3Prefix(prefix) {
|
|
44454
44640
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -44521,12 +44707,12 @@ import {
|
|
|
44521
44707
|
statSync as statSync14,
|
|
44522
44708
|
writeFileSync as writeFileSync14
|
|
44523
44709
|
} from "fs";
|
|
44524
|
-
import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative10, resolve as
|
|
44710
|
+
import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative10, resolve as resolve19, sep as sep11 } from "path";
|
|
44525
44711
|
|
|
44526
44712
|
// src/lib/portable-snapshot-filter.ts
|
|
44527
44713
|
import { readdirSync as readdirSync15, statSync as statSync13 } from "fs";
|
|
44528
44714
|
import { homedir as homedir7 } from "os";
|
|
44529
|
-
import { join as
|
|
44715
|
+
import { join as join37, sep as sep10 } from "path";
|
|
44530
44716
|
var SYNC_HOMES = [
|
|
44531
44717
|
{ name: "skills", subClass: "skills", agent: null },
|
|
44532
44718
|
{ name: "custom", subClass: "custom", agent: null },
|
|
@@ -44619,16 +44805,16 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
44619
44805
|
function homePathFor(definition, homesRoot) {
|
|
44620
44806
|
const home = homesRoot ?? homedir7();
|
|
44621
44807
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
44622
|
-
return
|
|
44808
|
+
return join37(skillsDataRootForHome(home), definition.name);
|
|
44623
44809
|
}
|
|
44624
44810
|
if (definition.agent === "opencode") {
|
|
44625
|
-
return
|
|
44811
|
+
return join37(home, ".config", "opencode", "skills");
|
|
44626
44812
|
}
|
|
44627
|
-
return
|
|
44813
|
+
return join37(home, `.${definition.agent}`, "skills");
|
|
44628
44814
|
}
|
|
44629
44815
|
function destinationFor(definition, stationId, relativePath) {
|
|
44630
|
-
const category = definition.subClass === "agent-homes" ?
|
|
44631
|
-
return
|
|
44816
|
+
const category = definition.subClass === "agent-homes" ? join37("agent-homes", definition.agent ?? "") : definition.name;
|
|
44817
|
+
return join37("resources", stationId, "skills", category, ...relativePath.split(sep10));
|
|
44632
44818
|
}
|
|
44633
44819
|
function walkEntries(absoluteRoot) {
|
|
44634
44820
|
let entries;
|
|
@@ -44639,7 +44825,7 @@ function walkEntries(absoluteRoot) {
|
|
|
44639
44825
|
}
|
|
44640
44826
|
const output2 = [];
|
|
44641
44827
|
for (const entry of entries) {
|
|
44642
|
-
const childFull =
|
|
44828
|
+
const childFull = join37(absoluteRoot, entry.name);
|
|
44643
44829
|
if (entry.isSymbolicLink()) {
|
|
44644
44830
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
44645
44831
|
continue;
|
|
@@ -44650,7 +44836,7 @@ function walkEntries(absoluteRoot) {
|
|
|
44650
44836
|
}
|
|
44651
44837
|
const nested = walkEntries(childFull);
|
|
44652
44838
|
for (const item of nested) {
|
|
44653
|
-
output2.push({ ...item, relativePath:
|
|
44839
|
+
output2.push({ ...item, relativePath: join37(entry.name, item.relativePath) });
|
|
44654
44840
|
}
|
|
44655
44841
|
continue;
|
|
44656
44842
|
}
|
|
@@ -44759,7 +44945,7 @@ function humanHomes(scanned) {
|
|
|
44759
44945
|
}));
|
|
44760
44946
|
}
|
|
44761
44947
|
function writeStationSnapshot(options) {
|
|
44762
|
-
const repoRoot =
|
|
44948
|
+
const repoRoot = resolve19(options.repoRoot ?? process.cwd());
|
|
44763
44949
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
44764
44950
|
const manifestFiles = plans.map((plan) => ({
|
|
44765
44951
|
relativePath: plan.source.relativePath,
|
|
@@ -44785,7 +44971,7 @@ function writeStationSnapshot(options) {
|
|
|
44785
44971
|
const conflicts = [];
|
|
44786
44972
|
const untouched = [];
|
|
44787
44973
|
for (const plan of plans) {
|
|
44788
|
-
const destination =
|
|
44974
|
+
const destination = resolve19(repoRoot, plan.destination);
|
|
44789
44975
|
const destinationRelative = relative10(repoRoot, destination);
|
|
44790
44976
|
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep11) || isAbsolute19(destinationRelative)) {
|
|
44791
44977
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
@@ -44808,7 +44994,7 @@ function writeStationSnapshot(options) {
|
|
|
44808
44994
|
}
|
|
44809
44995
|
let written = 0;
|
|
44810
44996
|
for (const plan of untouched) {
|
|
44811
|
-
const destination =
|
|
44997
|
+
const destination = resolve19(repoRoot, plan.destination);
|
|
44812
44998
|
mkdirSync16(dirname24(destination), { recursive: true });
|
|
44813
44999
|
copyFileSync(plan.source.fullPath, destination);
|
|
44814
45000
|
written += 1;
|
|
@@ -44827,7 +45013,7 @@ function writeStationSnapshot(options) {
|
|
|
44827
45013
|
},
|
|
44828
45014
|
files: manifestFiles
|
|
44829
45015
|
};
|
|
44830
|
-
const manifestPath =
|
|
45016
|
+
const manifestPath = resolve19(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
44831
45017
|
mkdirSync16(dirname24(manifestPath), { recursive: true });
|
|
44832
45018
|
writeFileSync14(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
44833
45019
|
`);
|
|
@@ -44848,7 +45034,7 @@ import {
|
|
|
44848
45034
|
statSync as statSync15,
|
|
44849
45035
|
writeFileSync as writeFileSync15
|
|
44850
45036
|
} from "fs";
|
|
44851
|
-
import { dirname as dirname25, join as
|
|
45037
|
+
import { dirname as dirname25, join as join38, resolve as resolve20, sep as sep12 } from "path";
|
|
44852
45038
|
var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
|
|
44853
45039
|
var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
|
|
44854
45040
|
var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
@@ -44856,11 +45042,11 @@ function fail(code2, message, detail = []) {
|
|
|
44856
45042
|
throw new StationSnapshotError(code2, message, detail);
|
|
44857
45043
|
}
|
|
44858
45044
|
function snapshotRootFor(repoRoot, stationId) {
|
|
44859
|
-
return
|
|
45045
|
+
return join38(repoRoot, "resources", stationId, "skills");
|
|
44860
45046
|
}
|
|
44861
45047
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
44862
45048
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
44863
|
-
const manifestPath =
|
|
45049
|
+
const manifestPath = join38(snapshotRoot, "sync-manifest.json");
|
|
44864
45050
|
let manifest;
|
|
44865
45051
|
try {
|
|
44866
45052
|
manifest = JSON.parse(readFileSync26(manifestPath, "utf8"));
|
|
@@ -44887,7 +45073,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
44887
45073
|
const hashMismatches = [];
|
|
44888
45074
|
const skippedByRule = [];
|
|
44889
45075
|
for (const agent of SYNC_AGENTS) {
|
|
44890
|
-
const agentRoot =
|
|
45076
|
+
const agentRoot = join38(snapshotRoot, "agent-homes", agent);
|
|
44891
45077
|
let identEntries;
|
|
44892
45078
|
try {
|
|
44893
45079
|
identEntries = readdirSync16(agentRoot, { withFileTypes: true });
|
|
@@ -44898,7 +45084,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
44898
45084
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
44899
45085
|
continue;
|
|
44900
45086
|
}
|
|
44901
|
-
const identRoot =
|
|
45087
|
+
const identRoot = join38(agentRoot, identEntry.name);
|
|
44902
45088
|
const entries = walkEntries(identRoot);
|
|
44903
45089
|
for (const entry of entries) {
|
|
44904
45090
|
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep12)];
|
|
@@ -45049,8 +45235,8 @@ function skillSha256(skill) {
|
|
|
45049
45235
|
`)).digest("hex");
|
|
45050
45236
|
}
|
|
45051
45237
|
function writeStationHydration(options) {
|
|
45052
|
-
const repoRoot =
|
|
45053
|
-
const cacheRoot =
|
|
45238
|
+
const repoRoot = resolve20(options.repoRoot ?? process.cwd());
|
|
45239
|
+
const cacheRoot = resolve20(options.cacheRoot ?? resolveCorpusRoot());
|
|
45054
45240
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
45055
45241
|
const resultSkills = plan.winners.map((skill) => ({
|
|
45056
45242
|
ident: skill.ident,
|
|
@@ -45083,7 +45269,7 @@ function writeStationHydration(options) {
|
|
|
45083
45269
|
const toWrite = [];
|
|
45084
45270
|
for (const skill of plan.winners) {
|
|
45085
45271
|
for (const file2 of skill.files) {
|
|
45086
|
-
const destination =
|
|
45272
|
+
const destination = join38(cacheRoot, skill.ident, file2.withinIdent);
|
|
45087
45273
|
const digest3 = sha256File(file2.winner.fullPath);
|
|
45088
45274
|
let existingDigest = null;
|
|
45089
45275
|
try {
|
|
@@ -45125,7 +45311,7 @@ function writeStationHydration(options) {
|
|
|
45125
45311
|
},
|
|
45126
45312
|
skills: resultSkills
|
|
45127
45313
|
};
|
|
45128
|
-
const hydrationManifestPath =
|
|
45314
|
+
const hydrationManifestPath = join38(dirname25(cacheRoot), `hydration-${options.stationId}.json`);
|
|
45129
45315
|
mkdirSync17(dirname25(hydrationManifestPath), { recursive: true });
|
|
45130
45316
|
writeFileSync15(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
45131
45317
|
`);
|
|
@@ -45137,23 +45323,23 @@ function writeStationHydration(options) {
|
|
|
45137
45323
|
};
|
|
45138
45324
|
}
|
|
45139
45325
|
// src/lib/private-publication-recovery.ts
|
|
45140
|
-
import { constants as constants12, closeSync as closeSync13, fsyncSync as fsyncSync5, fstatSync as fstatSync13, lstatSync as
|
|
45141
|
-
import { dirname as dirname26, isAbsolute as isAbsolute20, join as
|
|
45326
|
+
import { constants as constants12, closeSync as closeSync13, fsyncSync as fsyncSync5, fstatSync as fstatSync13, lstatSync as lstatSync24, mkdirSync as mkdirSync18, openSync as openSync13, readSync as readSync11, realpathSync as realpathSync11, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "fs";
|
|
45327
|
+
import { dirname as dirname26, isAbsolute as isAbsolute20, join as join39, resolve as resolve21 } from "path";
|
|
45142
45328
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
45143
45329
|
var fail2 = () => {
|
|
45144
45330
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
|
|
45145
45331
|
};
|
|
45146
45332
|
function safeDirectory(directory2) {
|
|
45147
|
-
if (!isAbsolute20(directory2) || directory2 !==
|
|
45333
|
+
if (!isAbsolute20(directory2) || directory2 !== resolve21(directory2) || realpathSync11(directory2) !== directory2)
|
|
45148
45334
|
return fail2();
|
|
45149
45335
|
for (let path = directory2;; path = dirname26(path)) {
|
|
45150
|
-
const stat2 =
|
|
45336
|
+
const stat2 = lstatSync24(path);
|
|
45151
45337
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
45152
45338
|
return fail2();
|
|
45153
45339
|
if (path === dirname26(path))
|
|
45154
45340
|
break;
|
|
45155
45341
|
}
|
|
45156
|
-
const own2 =
|
|
45342
|
+
const own2 = lstatSync24(directory2);
|
|
45157
45343
|
if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
|
|
45158
45344
|
return fail2();
|
|
45159
45345
|
return { dev: own2.dev, ino: own2.ino };
|
|
@@ -45164,7 +45350,7 @@ function unchangedDirectory(directory2, identity4) {
|
|
|
45164
45350
|
return fail2();
|
|
45165
45351
|
}
|
|
45166
45352
|
function readOwned(directory2, name, max) {
|
|
45167
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
45353
|
+
const identity4 = safeDirectory(directory2), file2 = join39(directory2, name);
|
|
45168
45354
|
const fd = openSync13(file2, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
45169
45355
|
try {
|
|
45170
45356
|
const stat2 = fstatSync13(fd);
|
|
@@ -45188,7 +45374,7 @@ function readOwned(directory2, name, max) {
|
|
|
45188
45374
|
}
|
|
45189
45375
|
}
|
|
45190
45376
|
function writeOwned(directory2, name, bytes) {
|
|
45191
|
-
const fd = openSync13(
|
|
45377
|
+
const fd = openSync13(join39(directory2, name), constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW, 384);
|
|
45192
45378
|
try {
|
|
45193
45379
|
writeFileSync16(fd, bytes);
|
|
45194
45380
|
fsyncSync5(fd);
|
|
@@ -45201,7 +45387,7 @@ function save(directory2, value) {
|
|
|
45201
45387
|
writeOwned(directory2, temporary, JSON.stringify(value) + `
|
|
45202
45388
|
`);
|
|
45203
45389
|
unchangedDirectory(directory2, identity4);
|
|
45204
|
-
renameSync7(
|
|
45390
|
+
renameSync7(join39(directory2, temporary), join39(directory2, "receipt.json"));
|
|
45205
45391
|
const fd = openSync13(directory2, constants12.O_RDONLY | constants12.O_NOFOLLOW);
|
|
45206
45392
|
try {
|
|
45207
45393
|
fsyncSync5(fd);
|
|
@@ -45214,7 +45400,7 @@ function bind(client2, receipt) {
|
|
|
45214
45400
|
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
45215
45401
|
}
|
|
45216
45402
|
async function locked(directory2, action) {
|
|
45217
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
45403
|
+
const identity4 = safeDirectory(directory2), file2 = join39(directory2, "operation.lock");
|
|
45218
45404
|
let fd;
|
|
45219
45405
|
try {
|
|
45220
45406
|
fd = openSync13(file2, constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW, 384);
|
|
@@ -45230,7 +45416,7 @@ async function locked(directory2, action) {
|
|
|
45230
45416
|
} finally {
|
|
45231
45417
|
closeSync13(fd);
|
|
45232
45418
|
unchangedDirectory(directory2, identity4);
|
|
45233
|
-
const now =
|
|
45419
|
+
const now = lstatSync24(file2);
|
|
45234
45420
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
45235
45421
|
fail2();
|
|
45236
45422
|
unlinkSync5(file2);
|
|
@@ -45282,7 +45468,7 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
|
|
|
45282
45468
|
phase: "prepared",
|
|
45283
45469
|
intent: null
|
|
45284
45470
|
};
|
|
45285
|
-
if (!isAbsolute20(recoveryDirectory) ||
|
|
45471
|
+
if (!isAbsolute20(recoveryDirectory) || resolve21(recoveryDirectory) !== recoveryDirectory || realpathSync11(dirname26(recoveryDirectory)) !== dirname26(recoveryDirectory))
|
|
45286
45472
|
return fail2();
|
|
45287
45473
|
mkdirSync18(recoveryDirectory, { mode: 448 });
|
|
45288
45474
|
safeDirectory(recoveryDirectory);
|
|
@@ -45374,7 +45560,7 @@ async function inspectLocked(client2, directory2, cancel) {
|
|
|
45374
45560
|
}
|
|
45375
45561
|
// src/lib/skill-context.ts
|
|
45376
45562
|
import { createHash as createHash26 } from "crypto";
|
|
45377
|
-
import { resolve as
|
|
45563
|
+
import { resolve as resolve22 } from "path";
|
|
45378
45564
|
function words(value) {
|
|
45379
45565
|
return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
45380
45566
|
}
|
|
@@ -45396,7 +45582,11 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
45396
45582
|
}
|
|
45397
45583
|
const profileId = input2.profileId ?? "default";
|
|
45398
45584
|
const sessionId = input2.sessionId ? input2.agentId ? `${input2.sessionId}:${input2.agentId}` : input2.sessionId : undefined;
|
|
45399
|
-
|
|
45585
|
+
if (input2.parentSessionId !== undefined) {
|
|
45586
|
+
if (input2.agentId !== undefined || !sessionId || input2.parentSessionId === sessionId || input2.parentSessionId !== null && (typeof input2.parentSessionId !== "string" || !input2.parentSessionId.trim()))
|
|
45587
|
+
throw new SkillSelectionError("INVALID_SESSION", "Native session custody requires its own session id and a distinct parent, without a legacy agent id.");
|
|
45588
|
+
}
|
|
45589
|
+
const resolverOptions = { ...options, projectDir: input2.cwd ?? options.projectDir, sessionId, nativeParentSessionId: input2.parentSessionId, parentSessionId: input2.parentSessionId ?? (input2.agentId ? input2.sessionId : undefined) };
|
|
45400
45590
|
const resolved = await resolveSelectionContext(profileId, resolverOptions);
|
|
45401
45591
|
const profile = resolved.receipt.profile;
|
|
45402
45592
|
const explicit = new Set((input2.skills ?? []).map((spec) => selectionKey(exactProfileSelection(spec, profile))));
|
|
@@ -45409,7 +45599,7 @@ async function buildSkillContext(input2, options = {}) {
|
|
|
45409
45599
|
const promptWords = words(prompt);
|
|
45410
45600
|
const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
|
|
45411
45601
|
const loaded = new Set(resolved.session?.loaded ?? []);
|
|
45412
|
-
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd,
|
|
45602
|
+
const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve22(input2.cwd)] : []];
|
|
45413
45603
|
const candidates = profile.selections.map((selection) => {
|
|
45414
45604
|
const key = selectionKey(selection);
|
|
45415
45605
|
if (input2.restore && loaded.has(key))
|