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