@jini-ai/agentic 0.3.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/README.md +33 -6
- package/dist/core/a2ui/agent-to-renderer.d.ts +3 -1
- package/dist/core/a2ui/agent-to-renderer.js +1 -1
- package/dist/core/a2ui/catalog.d.ts +14 -5
- package/dist/core/a2ui/catalog.js +5 -5
- package/dist/core/a2ui/common-types.d.ts +11 -3
- package/dist/core/a2ui/common-types.js +5 -3
- package/dist/core/a2ui/index.d.ts +1 -1
- package/dist/core/a2ui/index.js +1 -1
- package/dist/core/a2ui/interpreter.d.ts +45 -10
- package/dist/core/a2ui/interpreter.js +38 -43
- package/dist/core/a2ui/json-pointer.d.ts +16 -4
- package/dist/core/a2ui/json-pointer.js +7 -7
- package/dist/core/a2ui/renderer-to-agent.d.ts +22 -8
- package/dist/core/a2ui/renderer-to-agent.js +5 -5
- package/dist/core/a2ui/resolve.d.ts +7 -2
- package/dist/core/a2ui/resolve.js +10 -9
- package/dist/core/a2ui/tree.d.ts +8 -2
- package/dist/core/a2ui/tree.js +2 -2
- package/dist/core/ag-ui.d.ts +17 -9
- package/dist/core/ag-ui.js +4 -4
- package/dist/core/annotated-actions-script.d.ts +13 -0
- package/dist/core/annotated-actions-script.js +137 -0
- package/dist/core/capability.d.ts +12 -3
- package/dist/core/capability.js +3 -3
- package/dist/core/dom/dom-page-driver.d.ts +8 -4
- package/dist/core/dom/dom-page-driver.js +19 -20
- package/dist/core/dom/index.d.ts +3 -3
- package/dist/core/dom/index.js +1 -1
- package/dist/core/dom/model-context.d.ts +18 -8
- package/dist/core/dom/model-context.js +12 -15
- package/dist/core/dom/native-model-context.d.ts +6 -0
- package/dist/core/dom/native-model-context.js +19 -0
- package/dist/core/element-handles.d.ts +6 -2
- package/dist/core/element-handles.js +3 -3
- package/dist/core/gen-ui/encoder.d.ts +21 -11
- package/dist/core/gen-ui/encoder.js +8 -7
- package/dist/core/gen-ui/events.d.ts +6 -5
- package/dist/core/gen-ui/events.js +6 -5
- package/dist/core/gen-ui/index.d.ts +1 -1
- package/dist/core/gen-ui/index.js +1 -1
- package/dist/core/guards.d.ts +17 -5
- package/dist/core/guards.js +6 -6
- package/dist/core/handle.d.ts +12 -5
- package/dist/core/handle.js +9 -8
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +3 -1
- package/dist/core/list-handles.d.ts +4 -1
- package/dist/core/list-handles.js +1 -1
- package/dist/core/mcp-ui-apps.d.ts +39 -9
- package/dist/core/mcp-ui-apps.js +10 -8
- package/dist/core/messages.d.ts +6 -0
- package/dist/core/messages.js +6 -0
- package/dist/core/page-capabilities.d.ts +0 -11
- package/dist/core/page-capabilities.js +13 -1
- package/dist/core/page-driver.d.ts +32 -11
- package/dist/core/page-executor.d.ts +8 -2
- package/dist/core/page-executor.js +31 -31
- package/dist/core/webmcp.d.ts +27 -8
- package/dist/core/webmcp.js +14 -14
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/skills/install/archive.d.ts +8 -0
- package/dist/skills/install/archive.js +47 -0
- package/dist/skills/install/fetch-adapter.d.ts +6 -0
- package/dist/skills/install/fetch-adapter.js +6 -0
- package/dist/skills/install/github.d.ts +21 -0
- package/dist/skills/install/github.js +92 -0
- package/dist/skills/install/index.d.ts +11 -0
- package/dist/skills/install/index.js +11 -0
- package/dist/skills/install/install-service.d.ts +44 -0
- package/dist/skills/install/install-service.js +121 -0
- package/dist/skills/install/layout.d.ts +13 -0
- package/dist/skills/install/layout.js +22 -0
- package/dist/skills/install/live-registration.d.ts +61 -0
- package/dist/skills/install/live-registration.js +64 -0
- package/dist/skills/install/node-filesystem.d.ts +4 -0
- package/dist/skills/install/node-filesystem.js +41 -0
- package/dist/skills/install/ports.d.ts +101 -0
- package/dist/skills/install/ports.js +2 -0
- package/dist/skills/install/state.d.ts +13 -0
- package/dist/skills/install/state.js +23 -0
- package/dist/skills/install/validation.d.ts +45 -0
- package/dist/skills/install/validation.js +104 -0
- package/package.json +20 -5
- package/dist/a2ui/agent-to-renderer.d.ts +0 -613
- package/dist/a2ui/agent-to-renderer.js +0 -191
- package/dist/a2ui/catalog.d.ts +0 -73
- package/dist/a2ui/catalog.js +0 -304
- package/dist/a2ui/common-types.d.ts +0 -401
- package/dist/a2ui/common-types.js +0 -130
- package/dist/a2ui/index.d.ts +0 -16
- package/dist/a2ui/index.js +0 -16
- package/dist/a2ui/interpreter.d.ts +0 -71
- package/dist/a2ui/interpreter.js +0 -337
- package/dist/a2ui/json-pointer.d.ts +0 -48
- package/dist/a2ui/json-pointer.js +0 -174
- package/dist/a2ui/renderer-to-agent.d.ts +0 -342
- package/dist/a2ui/renderer-to-agent.js +0 -111
- package/dist/a2ui/resolve.d.ts +0 -28
- package/dist/a2ui/resolve.js +0 -144
- package/dist/a2ui/tree.d.ts +0 -66
- package/dist/a2ui/tree.js +0 -74
- package/dist/ag-ui.d.ts +0 -82
- package/dist/ag-ui.js +0 -85
- package/dist/agui/encoder.d.ts +0 -50
- package/dist/agui/encoder.js +0 -97
- package/dist/agui/events.d.ts +0 -59
- package/dist/agui/events.js +0 -15
- package/dist/capability.d.ts +0 -81
- package/dist/capability.js +0 -79
- package/dist/dom/dom-page-driver.d.ts +0 -36
- package/dist/dom/dom-page-driver.js +0 -601
- package/dist/dom/index.d.ts +0 -17
- package/dist/dom/index.js +0 -17
- package/dist/dom/model-context.d.ts +0 -69
- package/dist/dom/model-context.js +0 -44
- package/dist/element-handles.d.ts +0 -120
- package/dist/element-handles.js +0 -39
- package/dist/gen-ui/encoder.d.ts +0 -50
- package/dist/gen-ui/encoder.js +0 -97
- package/dist/gen-ui/events.d.ts +0 -59
- package/dist/gen-ui/events.js +0 -15
- package/dist/gen-ui/index.d.ts +0 -18
- package/dist/gen-ui/index.js +0 -17
- package/dist/guards.d.ts +0 -96
- package/dist/guards.js +0 -186
- package/dist/handle.d.ts +0 -41
- package/dist/handle.js +0 -41
- package/dist/mcp-ui-apps.d.ts +0 -223
- package/dist/mcp-ui-apps.js +0 -252
- package/dist/mcp-ui.d.ts +0 -133
- package/dist/mcp-ui.js +0 -134
- package/dist/page-capabilities.d.ts +0 -14
- package/dist/page-capabilities.js +0 -129
- package/dist/page-driver.d.ts +0 -120
- package/dist/page-driver.js +0 -2
- package/dist/page-executor.d.ts +0 -94
- package/dist/page-executor.js +0 -320
- package/dist/webmcp.d.ts +0 -202
- package/dist/webmcp.js +0 -173
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Commit-pinned public GitHub import with one request/body deadline, byte caps and refused redirects. */
|
|
2
|
+
import { type SkillUploadFile } from "./validation.js";
|
|
3
|
+
import type { SkillFetchPort } from "./ports.js";
|
|
4
|
+
export interface GitHubSkillSource {
|
|
5
|
+
readonly githubUrl: string;
|
|
6
|
+
readonly commit: string;
|
|
7
|
+
}
|
|
8
|
+
/** Restricts every request to api.github.com, disallows redirects, pins one immutable commit.
|
|
9
|
+
* @complexity O(files + downloaded bytes), at most 256 file requests and 8 MiB decoded content.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fetchGitHubSkill({ githubUrl, fetch: fetchImpl }: {
|
|
12
|
+
githubUrl: string;
|
|
13
|
+
fetch: SkillFetchPort;
|
|
14
|
+
}, _optional?: Record<string, never>): Promise<{
|
|
15
|
+
files: SkillUploadFile[];
|
|
16
|
+
source: {
|
|
17
|
+
githubUrl: string;
|
|
18
|
+
commit: string;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Commit-pinned public GitHub import with one request/body deadline, byte caps and refused redirects. */
|
|
2
|
+
import { MAX_SKILL_BYTES, MAX_SKILL_FILES, MAX_SKILL_FILE_BYTES, decodeSkillBase64, SkillInputError } from "./validation.js";
|
|
3
|
+
/** Restricts every request to api.github.com, disallows redirects, pins one immutable commit.
|
|
4
|
+
* @complexity O(files + downloaded bytes), at most 256 file requests and 8 MiB decoded content.
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchGitHubSkill({ githubUrl, fetch: fetchImpl }, _optional = {}) {
|
|
7
|
+
let url;
|
|
8
|
+
try {
|
|
9
|
+
url = new URL(githubUrl);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new SkillInputError({ message: "Use an HTTPS GitHub repository URL, optionally ending in /tree/ref/skill-folder." });
|
|
13
|
+
}
|
|
14
|
+
let parts;
|
|
15
|
+
try {
|
|
16
|
+
parts = url.pathname.replace(/\/$/, "").split("/").slice(1).map(p => decodeURIComponent(p));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new SkillInputError({ message: "Use an HTTPS GitHub repository URL, optionally ending in /tree/ref/skill-folder." });
|
|
20
|
+
}
|
|
21
|
+
if (url.protocol !== "https:" || url.hostname !== "github.com" || url.port || url.username || url.password || url.search || url.hash || parts.length < 2 || (parts.length > 2 && (parts[2] !== "tree" || parts.length < 4)) || parts.some(p => !/^[a-zA-Z0-9_.-]+$/.test(p) || p === "." || p === ".."))
|
|
22
|
+
throw new SkillInputError({ message: "Use an HTTPS GitHub repository URL, optionally ending in /tree/ref/skill-folder." });
|
|
23
|
+
const owner = parts[0];
|
|
24
|
+
const repo = parts[1].replace(/\.git$/, "");
|
|
25
|
+
if (!repo)
|
|
26
|
+
throw new SkillInputError({ message: "Use an HTTPS GitHub repository URL, optionally ending in /tree/ref/skill-folder." });
|
|
27
|
+
const base = `https://api.github.com/repos/${owner}/${repo}`;
|
|
28
|
+
// One deadline for the whole fetch, including streaming response bodies.
|
|
29
|
+
const signal = AbortSignal.timeout(30_000);
|
|
30
|
+
async function json(endpoint) {
|
|
31
|
+
const response = await fetchImpl({ url: endpoint }, { redirect: "error", signal, headers: { Accept: "application/vnd.github+json" } });
|
|
32
|
+
if (!response.ok)
|
|
33
|
+
throw new SkillInputError({ message: `GitHub could not fetch this skill (HTTP ${response.status}). Use a public repository URL.` });
|
|
34
|
+
if (!response.body)
|
|
35
|
+
throw new SkillInputError({ message: "GitHub returned an empty response." });
|
|
36
|
+
const reader = response.body.getReader();
|
|
37
|
+
let bytes = 0;
|
|
38
|
+
const chunks = [];
|
|
39
|
+
try {
|
|
40
|
+
for (;;) {
|
|
41
|
+
const part = await reader.read();
|
|
42
|
+
if (part.done)
|
|
43
|
+
break;
|
|
44
|
+
bytes += part.value.length;
|
|
45
|
+
if (bytes > MAX_SKILL_BYTES * 2)
|
|
46
|
+
throw new SkillInputError({ message: "GitHub skill response exceeds its size limit." });
|
|
47
|
+
chunks.push(part.value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await reader.cancel();
|
|
52
|
+
}
|
|
53
|
+
const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
54
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
55
|
+
throw new SkillInputError({ message: "GitHub returned an invalid skill response." });
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
const commit = (await json(`${base}/commits/${encodeURIComponent(parts[3] ?? "HEAD")}`)).sha;
|
|
59
|
+
if (typeof commit !== "string" || !/^[a-f0-9]{40}$/.test(commit))
|
|
60
|
+
throw new SkillInputError({ message: "GitHub returned an invalid commit." });
|
|
61
|
+
const tree = await json(`${base}/git/trees/${commit}?recursive=1`);
|
|
62
|
+
if (tree.truncated || !Array.isArray(tree.tree))
|
|
63
|
+
throw new SkillInputError({ message: "GitHub repository tree is incomplete. Choose a smaller skill repository." });
|
|
64
|
+
const prefix = parts.length > 4 ? parts.slice(4).join("/") + "/" : "";
|
|
65
|
+
const files = [];
|
|
66
|
+
let total = 0;
|
|
67
|
+
let actualBytes = 0;
|
|
68
|
+
for (const row of tree.tree) {
|
|
69
|
+
if (typeof row.path !== "string" || !row.path.startsWith(prefix))
|
|
70
|
+
continue;
|
|
71
|
+
const relative = row.path.slice(prefix.length);
|
|
72
|
+
if (!/^(SKILL\.md|README\.md|LICENSE|(?:references|scripts|assets)\/.*)$/.test(relative) || row.type === "tree")
|
|
73
|
+
continue;
|
|
74
|
+
if (row.type !== "blob" || !["100644", "100755"].includes(String(row.mode)))
|
|
75
|
+
throw new SkillInputError({ message: "GitHub skills may contain only regular files." });
|
|
76
|
+
if (typeof row.size !== "number" || row.size < 0 || row.size > MAX_SKILL_FILE_BYTES || typeof row.sha !== "string" || !/^[a-f0-9]{40}$/.test(row.sha))
|
|
77
|
+
throw new SkillInputError({ message: "GitHub skill file exceeds its size limit or has invalid metadata." });
|
|
78
|
+
total += row.size;
|
|
79
|
+
if (total > MAX_SKILL_BYTES || files.length >= MAX_SKILL_FILES)
|
|
80
|
+
throw new SkillInputError({ message: "GitHub skill exceeds 8 MiB or 256 files." });
|
|
81
|
+
const blob = await json(`${base}/git/blobs/${row.sha}`);
|
|
82
|
+
if (blob.encoding !== "base64" || typeof blob.content !== "string")
|
|
83
|
+
throw new SkillInputError({ message: "GitHub returned an invalid skill file." });
|
|
84
|
+
const contentBase64 = blob.content.replace(/\s/g, "");
|
|
85
|
+
actualBytes += decodeSkillBase64({ value: contentBase64 }, { maxBytes: MAX_SKILL_FILE_BYTES }).length;
|
|
86
|
+
if (actualBytes > MAX_SKILL_BYTES)
|
|
87
|
+
throw new SkillInputError({ message: "GitHub skill exceeds 8 MiB or 256 files." });
|
|
88
|
+
files.push({ path: relative, contentBase64 });
|
|
89
|
+
}
|
|
90
|
+
return { files, source: { githubUrl, commit } };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./validation.js";
|
|
2
|
+
export * from "./ports.js";
|
|
3
|
+
export * from "./archive.js";
|
|
4
|
+
export * from "./github.js";
|
|
5
|
+
export * from "./state.js";
|
|
6
|
+
export * from "./install-service.js";
|
|
7
|
+
export * from "./layout.js";
|
|
8
|
+
export * from "./live-registration.js";
|
|
9
|
+
export * from "./fetch-adapter.js";
|
|
10
|
+
export * from "./node-filesystem.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./validation.js";
|
|
2
|
+
export * from "./ports.js";
|
|
3
|
+
export * from "./archive.js";
|
|
4
|
+
export * from "./github.js";
|
|
5
|
+
export * from "./state.js";
|
|
6
|
+
export * from "./install-service.js";
|
|
7
|
+
export * from "./layout.js";
|
|
8
|
+
export * from "./live-registration.js";
|
|
9
|
+
export * from "./fetch-adapter.js";
|
|
10
|
+
export * from "./node-filesystem.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type SkillUploadFile } from "./validation.js";
|
|
2
|
+
import { type GitHubSkillSource } from "./github.js";
|
|
3
|
+
import type { SkillInstallDeps } from "./ports.js";
|
|
4
|
+
export interface ManagedSkill {
|
|
5
|
+
readonly toolId: string;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly enabled: boolean;
|
|
9
|
+
readonly source: "uploaded" | GitHubSkillSource;
|
|
10
|
+
}
|
|
11
|
+
export type SkillInstallInput = {
|
|
12
|
+
readonly workspaceId: string;
|
|
13
|
+
} & ({
|
|
14
|
+
readonly githubUrl: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly files: readonly SkillUploadFile[];
|
|
17
|
+
} | {
|
|
18
|
+
readonly archiveBase64: string;
|
|
19
|
+
});
|
|
20
|
+
export interface SkillInstaller {
|
|
21
|
+
installSkill(required: SkillInstallInput): Promise<ManagedSkill>;
|
|
22
|
+
listManagedSkills(required: {
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
}): Promise<ManagedSkill[]>;
|
|
25
|
+
setSkillEnabled(required: {
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
toolId: string;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
uninstallSkill(required: {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
toolId: string;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Validate before writes; publish via rename, with instance-local queues keyed by resolved root.
|
|
36
|
+
* Cross-process exclusivity remains the host filesystem/layout adapter's responsibility.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createSkillInstaller(deps: SkillInstallDeps, optional?: {
|
|
39
|
+
onChanged?: (required: {
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
toolId: string;
|
|
42
|
+
}) => void | Promise<void>;
|
|
43
|
+
}): SkillInstaller;
|
|
44
|
+
//# sourceMappingURL=install-service.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { SkillInputError, validateSkillFiles } from "./validation.js";
|
|
3
|
+
import { readSkillArchive } from "./archive.js";
|
|
4
|
+
import { fetchGitHubSkill } from "./github.js";
|
|
5
|
+
import { readSkillState } from "./state.js";
|
|
6
|
+
/** Validate before writes; publish via rename, with instance-local queues keyed by resolved root.
|
|
7
|
+
* Cross-process exclusivity remains the host filesystem/layout adapter's responsibility.
|
|
8
|
+
*/
|
|
9
|
+
export function createSkillInstaller(deps, optional = {}) {
|
|
10
|
+
const queues = new Map();
|
|
11
|
+
const layoutFor = (workspaceId) => {
|
|
12
|
+
const layout = deps.layout.resolve({ workspaceId });
|
|
13
|
+
if (!path.isAbsolute(layout.workspaceRoot) || !path.isAbsolute(layout.stagingRoot))
|
|
14
|
+
throw new SkillInputError({ message: "Skill roots must be absolute." });
|
|
15
|
+
if (!layout.stateFileName || /[/\\\x00]/.test(layout.stateFileName) || [".", ".."].includes(layout.stateFileName) || ["SKILL.md", "README.md", "LICENSE", "references", "scripts", "assets"].includes(layout.stateFileName))
|
|
16
|
+
throw new SkillInputError({ message: "Invalid skill state file name." });
|
|
17
|
+
return layout;
|
|
18
|
+
};
|
|
19
|
+
async function locked(workspaceId, work) {
|
|
20
|
+
const key = layoutFor(workspaceId).workspaceRoot;
|
|
21
|
+
const result = (queues.get(key) ?? Promise.resolve()).catch(() => { }).then(work);
|
|
22
|
+
queues.set(key, result);
|
|
23
|
+
try {
|
|
24
|
+
return await result;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
if (queues.get(key) === result)
|
|
28
|
+
queues.delete(key);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function listManagedSkills(required) {
|
|
32
|
+
const layout = layoutFor(required.workspaceId);
|
|
33
|
+
const sources = await deps.toolSourceLoader.load(required, { includeDisabled: true });
|
|
34
|
+
return Promise.all(sources.map(async (skill) => {
|
|
35
|
+
const directory = containedDirectory(layout.workspaceRoot, skill.directory);
|
|
36
|
+
return { toolId: skill.id, name: skill.skillName, description: skill.description,
|
|
37
|
+
...await readSkillState({ directory, stateFileName: layout.stateFileName, filesystem: deps.filesystem }) };
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
async function managedDirectory(required) {
|
|
41
|
+
const skill = (await deps.toolSourceLoader.load({ workspaceId: required.workspaceId }, { includeDisabled: true })).find(source => source.id === required.toolId);
|
|
42
|
+
if (!skill)
|
|
43
|
+
throw new SkillInputError({ message: "Skill was not found." });
|
|
44
|
+
const directory = containedDirectory(layoutFor(required.workspaceId).workspaceRoot, skill.directory);
|
|
45
|
+
if ((await deps.filesystem.lstat({ path: directory })).kind !== "directory")
|
|
46
|
+
throw new SkillInputError({ message: "Skill directory must be a regular directory." });
|
|
47
|
+
return directory;
|
|
48
|
+
}
|
|
49
|
+
const changed = async (workspaceId, toolId) => { await optional.onChanged?.({ workspaceId, toolId }); };
|
|
50
|
+
return {
|
|
51
|
+
listManagedSkills,
|
|
52
|
+
async installSkill(input) {
|
|
53
|
+
let upload;
|
|
54
|
+
let source = "uploaded";
|
|
55
|
+
if ("githubUrl" in input)
|
|
56
|
+
({ files: upload, source } = await fetchGitHubSkill({ githubUrl: input.githubUrl, fetch: deps.fetch }));
|
|
57
|
+
else if ("archiveBase64" in input)
|
|
58
|
+
upload = await readSkillArchive({ base64: input.archiveBase64, archiveReader: deps.archiveReader });
|
|
59
|
+
else
|
|
60
|
+
upload = input.files;
|
|
61
|
+
const validated = validateSkillFiles({ files: upload, yamlReader: deps.yamlReader });
|
|
62
|
+
const toolId = deps.toolId({ name: validated.name });
|
|
63
|
+
const layout = layoutFor(input.workspaceId);
|
|
64
|
+
return locked(input.workspaceId, async () => {
|
|
65
|
+
if ((await listManagedSkills(input)).some(skill => skill.toolId === toolId))
|
|
66
|
+
throw new SkillInputError({ message: `Skill '${validated.name}' is already installed. Remove it before installing another version.` });
|
|
67
|
+
await deps.filesystem.mkdir({ path: layout.workspaceRoot });
|
|
68
|
+
await deps.filesystem.mkdir({ path: layout.stagingRoot });
|
|
69
|
+
const staging = await deps.filesystem.mkdtemp({ prefix: path.join(layout.stagingRoot, "skill-") });
|
|
70
|
+
try {
|
|
71
|
+
for (const [relative, bytes] of validated.files) {
|
|
72
|
+
const target = path.join(staging, relative);
|
|
73
|
+
await deps.filesystem.mkdir({ path: path.dirname(target) });
|
|
74
|
+
await deps.filesystem.writeFile({ path: target, bytes, exclusive: true, mode: 0o600 });
|
|
75
|
+
}
|
|
76
|
+
await deps.filesystem.writeFile({ path: path.join(staging, layout.stateFileName), bytes: Buffer.from(JSON.stringify({ enabled: true, source })), exclusive: true, mode: 0o600 });
|
|
77
|
+
await deps.filesystem.rename({ from: staging, to: path.join(layout.workspaceRoot, validated.name) });
|
|
78
|
+
await changed(input.workspaceId, toolId);
|
|
79
|
+
return { toolId, name: validated.name, description: validated.description, enabled: true, source };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await deps.filesystem.remove({ path: staging }, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
async setSkillEnabled(input) {
|
|
87
|
+
if (typeof input.enabled !== "boolean")
|
|
88
|
+
throw new SkillInputError({ message: "enabled must be a boolean." });
|
|
89
|
+
await locked(input.workspaceId, async () => {
|
|
90
|
+
const directory = await managedDirectory(input);
|
|
91
|
+
const layout = layoutFor(input.workspaceId);
|
|
92
|
+
const state = await readSkillState({ directory, stateFileName: layout.stateFileName, filesystem: deps.filesystem });
|
|
93
|
+
const id = deps.ids.next({});
|
|
94
|
+
if (!id || /[/\\\x00]/.test(id))
|
|
95
|
+
throw new SkillInputError({ message: "Invalid skill temporary id." });
|
|
96
|
+
const temporary = path.join(directory, `.skill-state-${id}.tmp`);
|
|
97
|
+
try {
|
|
98
|
+
// A rejected write can leave partial bytes; cleanup must cover writing as well as rename.
|
|
99
|
+
await deps.filesystem.writeFile({ path: temporary, bytes: Buffer.from(JSON.stringify({ ...state, enabled: input.enabled })), exclusive: true, mode: 0o600 });
|
|
100
|
+
await deps.filesystem.rename({ from: temporary, to: path.join(directory, layout.stateFileName) });
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await deps.filesystem.remove({ path: temporary }, { force: true });
|
|
104
|
+
}
|
|
105
|
+
await changed(input.workspaceId, input.toolId);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
async uninstallSkill(input) {
|
|
109
|
+
await locked(input.workspaceId, async () => {
|
|
110
|
+
await deps.filesystem.remove({ path: await managedDirectory(input) }, { recursive: true });
|
|
111
|
+
await changed(input.workspaceId, input.toolId);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function containedDirectory(root, directory) {
|
|
117
|
+
if (!directory || directory === "." || directory === ".." || /[/\\\x00]/.test(directory))
|
|
118
|
+
throw new SkillInputError({ message: "Skill directory must be one safe segment." });
|
|
119
|
+
return path.join(root, directory);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=install-service.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SkillLayoutPort } from './ports.js';
|
|
2
|
+
/** Every storage location is host-owned; this adapter has no product or cwd defaults.
|
|
3
|
+
* Layout resolves the same required root/stagingRoot/workspaceDirectory/stateFileName
|
|
4
|
+
* contract used by the installer. No site, environment or cwd defaults are allowed: separate
|
|
5
|
+
* workspace roots and staging roots keep installation writes inside the host's selected paths.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSkillLayout(required: {
|
|
8
|
+
root: string;
|
|
9
|
+
stagingRoot: string;
|
|
10
|
+
workspaceDirectory: string;
|
|
11
|
+
stateFileName: string;
|
|
12
|
+
}, _optional?: Record<string, never>): SkillLayoutPort;
|
|
13
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/** Every storage location is host-owned; this adapter has no product or cwd defaults.
|
|
3
|
+
* Layout resolves the same required root/stagingRoot/workspaceDirectory/stateFileName
|
|
4
|
+
* contract used by the installer. No site, environment or cwd defaults are allowed: separate
|
|
5
|
+
* workspace roots and staging roots keep installation writes inside the host's selected paths.
|
|
6
|
+
*/
|
|
7
|
+
export function createSkillLayout(required, _optional = {}) {
|
|
8
|
+
const { root, stagingRoot, workspaceDirectory, stateFileName } = required;
|
|
9
|
+
if (!path.isAbsolute(root) || !path.isAbsolute(stagingRoot))
|
|
10
|
+
throw new Error('Skill roots must be absolute paths.');
|
|
11
|
+
if (!/^[a-z0-9]+(?:[-.][a-z0-9]+)*$/.test(workspaceDirectory))
|
|
12
|
+
throw new Error('Invalid workspace directory segment.');
|
|
13
|
+
if (!stateFileName || /[/\\\x00]/.test(stateFileName) || ['.', '..', 'SKILL.md', 'README.md', 'LICENSE', 'references', 'scripts', 'assets'].includes(stateFileName))
|
|
14
|
+
throw new Error('Invalid skill state file name.');
|
|
15
|
+
return Object.freeze({ resolve({ workspaceId }) {
|
|
16
|
+
const normalized = workspaceId.toLowerCase();
|
|
17
|
+
if (normalized.length > 64 || !/^[a-z0-9]+(?:[-.][a-z0-9]+)*$/.test(normalized))
|
|
18
|
+
throw new Error(`resolve: '${workspaceId}' is not a valid workspace id`);
|
|
19
|
+
return { workspaceRoot: path.join(root, workspaceDirectory, normalized), stagingRoot: path.resolve(stagingRoot), stateFileName };
|
|
20
|
+
} });
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface SkillRegistration<Descriptor extends {
|
|
2
|
+
id: string;
|
|
3
|
+
} = {
|
|
4
|
+
id: string;
|
|
5
|
+
}, Context = unknown> {
|
|
6
|
+
readonly descriptor: Descriptor;
|
|
7
|
+
readonly policy: {
|
|
8
|
+
authorize(required: {
|
|
9
|
+
context: Context;
|
|
10
|
+
}): unknown;
|
|
11
|
+
};
|
|
12
|
+
readonly handler: (required: {
|
|
13
|
+
context: Context;
|
|
14
|
+
}) => unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface SkillRegistryPort<Descriptor extends {
|
|
17
|
+
id: string;
|
|
18
|
+
} = {
|
|
19
|
+
id: string;
|
|
20
|
+
}, Context = unknown> {
|
|
21
|
+
list(required: Record<string, never>): Descriptor[];
|
|
22
|
+
has(required: {
|
|
23
|
+
id: string;
|
|
24
|
+
}): boolean;
|
|
25
|
+
register(required: {
|
|
26
|
+
tool: SkillRegistration<Descriptor, Context>;
|
|
27
|
+
}): void;
|
|
28
|
+
}
|
|
29
|
+
export interface SkillsRefreshRegistry {
|
|
30
|
+
refreshInstalledSkills?(required: Record<string, never>): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
/** Keep append-only registry slots while dispatching current registrations or failing closed. */
|
|
33
|
+
export declare function createLiveSkillRegistration<Descriptor extends {
|
|
34
|
+
id: string;
|
|
35
|
+
}, Context>(required: {
|
|
36
|
+
registry: SkillRegistryPort<Descriptor, Context>;
|
|
37
|
+
inactiveError: (required: {
|
|
38
|
+
id: string;
|
|
39
|
+
}) => Error;
|
|
40
|
+
}, _optional?: Record<string, never>): ({ tools }: {
|
|
41
|
+
tools: readonly SkillRegistration<Descriptor, Context>[];
|
|
42
|
+
}) => boolean;
|
|
43
|
+
/** Prevent older loads overwriting newer registrations; reset after both success and failure. */
|
|
44
|
+
export declare function createSkillRefresher<T>(required: {
|
|
45
|
+
load: (required: Record<string, never>) => Promise<readonly T[]>;
|
|
46
|
+
replace: (required: {
|
|
47
|
+
tools: readonly T[];
|
|
48
|
+
}) => boolean;
|
|
49
|
+
}, _optional?: Record<string, never>): {
|
|
50
|
+
refreshInstalledSkills(required: Record<string, never>): Promise<boolean>;
|
|
51
|
+
};
|
|
52
|
+
export declare function createSkillRefreshMiddleware(required: {
|
|
53
|
+
registry: SkillsRefreshRegistry;
|
|
54
|
+
}, optional?: {
|
|
55
|
+
onChanged?: (required: Record<string, never>) => void;
|
|
56
|
+
}): ({ next }: {
|
|
57
|
+
next: (required: Record<string, never>, optional?: {
|
|
58
|
+
error?: unknown;
|
|
59
|
+
}) => void;
|
|
60
|
+
}) => Promise<void>;
|
|
61
|
+
//# sourceMappingURL=live-registration.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Keep append-only registry slots while dispatching current registrations or failing closed. */
|
|
2
|
+
export function createLiveSkillRegistration(required, _optional = {}) {
|
|
3
|
+
const { registry } = required;
|
|
4
|
+
const active = new Map();
|
|
5
|
+
const slots = new Map();
|
|
6
|
+
const list = registry.list.bind(registry), has = registry.has.bind(registry);
|
|
7
|
+
registry.list = input => list(input).filter(d => !slots.has(d.id) || active.has(d.id));
|
|
8
|
+
registry.has = input => slots.has(input.id) ? active.has(input.id) : has(input);
|
|
9
|
+
let fingerprint = '';
|
|
10
|
+
return ({ tools }) => {
|
|
11
|
+
const nextFingerprint = JSON.stringify(tools.map(t => t.descriptor));
|
|
12
|
+
const next = new Map(tools.map(t => [t.descriptor.id, t]));
|
|
13
|
+
if (next.size !== tools.length)
|
|
14
|
+
throw new Error('Duplicate skill registration ID.');
|
|
15
|
+
for (const tool of tools) {
|
|
16
|
+
const id = tool.descriptor.id, existing = slots.get(id);
|
|
17
|
+
if (existing)
|
|
18
|
+
Object.assign(existing.descriptor, tool.descriptor);
|
|
19
|
+
else {
|
|
20
|
+
const slot = {
|
|
21
|
+
descriptor: { ...tool.descriptor },
|
|
22
|
+
policy: { authorize: input => active.get(id)?.policy.authorize(input) ?? 'deny' },
|
|
23
|
+
handler: input => {
|
|
24
|
+
const current = active.get(id);
|
|
25
|
+
if (!current)
|
|
26
|
+
throw required.inactiveError({ id });
|
|
27
|
+
return current.handler(input);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
registry.register({ tool: slot });
|
|
31
|
+
slots.set(id, slot);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
active.clear();
|
|
35
|
+
for (const [id, tool] of next)
|
|
36
|
+
active.set(id, tool);
|
|
37
|
+
const changed = fingerprint !== nextFingerprint;
|
|
38
|
+
fingerprint = nextFingerprint;
|
|
39
|
+
return changed;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Prevent older loads overwriting newer registrations; reset after both success and failure. */
|
|
43
|
+
export function createSkillRefresher(required, _optional = {}) {
|
|
44
|
+
let pending;
|
|
45
|
+
return { refreshInstalledSkills: (_required) => {
|
|
46
|
+
if (!pending)
|
|
47
|
+
pending = required.load({}).then(tools => required.replace({ tools })).finally(() => { pending = undefined; });
|
|
48
|
+
return pending;
|
|
49
|
+
} };
|
|
50
|
+
}
|
|
51
|
+
export function createSkillRefreshMiddleware(required, optional = {}) {
|
|
52
|
+
return async ({ next }) => {
|
|
53
|
+
try {
|
|
54
|
+
if (await required.registry.refreshInstalledSkills?.({}))
|
|
55
|
+
optional.onChanged?.({});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
next({}, { error });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
next({});
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=live-registration.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FilesystemPort } from './ports.js';
|
|
2
|
+
/** Node adapter kept separate from the injected installation policy. */
|
|
3
|
+
export declare function createNodeSkillFilesystem(_required: Record<string, never>, _optional?: Record<string, never>): FilesystemPort;
|
|
4
|
+
//# sourceMappingURL=node-filesystem.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { lstat, mkdir, mkdtemp, open, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
/** Node adapter kept separate from the injected installation policy. */
|
|
4
|
+
export function createNodeSkillFilesystem(_required, _optional = {}) {
|
|
5
|
+
return {
|
|
6
|
+
mkdir: async ({ path }) => { await mkdir(path, { recursive: true }); },
|
|
7
|
+
mkdtemp: ({ prefix }) => mkdtemp(prefix),
|
|
8
|
+
lstat: async ({ path }) => {
|
|
9
|
+
const stat = await lstat(path);
|
|
10
|
+
return { kind: stat.isSymbolicLink() ? 'symlink' : stat.isFile() ? 'file' : stat.isDirectory() ? 'directory' : 'other', size: stat.size };
|
|
11
|
+
},
|
|
12
|
+
readFile: async ({ path, maxBytes }) => {
|
|
13
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
14
|
+
try {
|
|
15
|
+
const stat = await handle.stat();
|
|
16
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
17
|
+
throw new Error('Invalid skill installation record.');
|
|
18
|
+
const chunks = [];
|
|
19
|
+
let size = 0;
|
|
20
|
+
for (;;) {
|
|
21
|
+
const chunk = Buffer.alloc(Math.min(4096, maxBytes + 1 - size));
|
|
22
|
+
const result = await handle.read(chunk, 0, chunk.length, null);
|
|
23
|
+
if (!result.bytesRead)
|
|
24
|
+
break;
|
|
25
|
+
size += result.bytesRead;
|
|
26
|
+
if (size > maxBytes)
|
|
27
|
+
throw new Error('Invalid skill installation record.');
|
|
28
|
+
chunks.push(chunk.subarray(0, result.bytesRead));
|
|
29
|
+
}
|
|
30
|
+
return Buffer.concat(chunks);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
await handle.close();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
writeFile: ({ path, bytes, exclusive, mode }) => writeFile(path, bytes, { flag: exclusive ? 'wx' : 'w', mode }),
|
|
37
|
+
rename: ({ from, to }) => rename(from, to),
|
|
38
|
+
remove: ({ path }, options = {}) => rm(path, options),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=node-filesystem.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** The adapter uses failsafe YAML and rejects duplicate keys; the installer never parses YAML itself. */
|
|
2
|
+
export interface YamlReaderPort {
|
|
3
|
+
read(required: {
|
|
4
|
+
yaml: string;
|
|
5
|
+
}): unknown;
|
|
6
|
+
}
|
|
7
|
+
/** Layout is host-owned. Staging and workspace roots must share a rename-capable filesystem. */
|
|
8
|
+
export interface SkillLayoutPort {
|
|
9
|
+
resolve(required: {
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
}): {
|
|
12
|
+
workspaceRoot: string;
|
|
13
|
+
stagingRoot: string;
|
|
14
|
+
stateFileName: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface SkillFilesystemPort {
|
|
18
|
+
mkdir(required: {
|
|
19
|
+
path: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
mkdtemp(required: {
|
|
22
|
+
prefix: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
writeFile(required: {
|
|
25
|
+
path: string;
|
|
26
|
+
bytes: Uint8Array;
|
|
27
|
+
mode: number;
|
|
28
|
+
exclusive: true;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
readFile(required: {
|
|
31
|
+
path: string;
|
|
32
|
+
maxBytes: number;
|
|
33
|
+
}): Promise<Uint8Array>;
|
|
34
|
+
lstat(required: {
|
|
35
|
+
path: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
kind: "file" | "directory" | "symlink" | "other";
|
|
38
|
+
size: number;
|
|
39
|
+
}>;
|
|
40
|
+
rename(required: {
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
remove(required: {
|
|
45
|
+
path: string;
|
|
46
|
+
}, optional?: {
|
|
47
|
+
recursive?: boolean;
|
|
48
|
+
force?: boolean;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/** Compatibility name used by the original node adapter and copied contract tests. */
|
|
52
|
+
export type FilesystemPort = SkillFilesystemPort;
|
|
53
|
+
/** Adapters must expose lstat semantics: symlinks are never classified as regular files/directories. */
|
|
54
|
+
export interface SkillToolSource {
|
|
55
|
+
id: string;
|
|
56
|
+
skillName: string;
|
|
57
|
+
description: string;
|
|
58
|
+
directory: string;
|
|
59
|
+
}
|
|
60
|
+
export interface ToolSourceLoaderPort {
|
|
61
|
+
load(required: {
|
|
62
|
+
workspaceId: string;
|
|
63
|
+
}, optional?: {
|
|
64
|
+
includeDisabled?: boolean;
|
|
65
|
+
}): Promise<readonly SkillToolSource[]>;
|
|
66
|
+
}
|
|
67
|
+
export interface ArchiveEntry {
|
|
68
|
+
path: string;
|
|
69
|
+
kind: "file" | "directory" | "other";
|
|
70
|
+
size: number;
|
|
71
|
+
/** Closing/returning the iterator must release the entry stream, including early refusal. */
|
|
72
|
+
read(required: Record<string, never>): AsyncIterable<Uint8Array>;
|
|
73
|
+
}
|
|
74
|
+
/** ZIP adapter must use strict names, validate sizes, and expose lazy entries without extraction. */
|
|
75
|
+
export interface ArchiveReaderPort {
|
|
76
|
+
open(required: {
|
|
77
|
+
bytes: Uint8Array;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
entries: AsyncIterable<ArchiveEntry>;
|
|
80
|
+
close(required: Record<string, never>): void;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
/** HTTP transport follows the argument-object convention; hosts own credentials and networking. */
|
|
84
|
+
export type SkillFetchPort = (required: {
|
|
85
|
+
url: string;
|
|
86
|
+
}, optional?: RequestInit) => Promise<Response>;
|
|
87
|
+
export interface SkillInstallDeps {
|
|
88
|
+
layout: SkillLayoutPort;
|
|
89
|
+
filesystem: SkillFilesystemPort;
|
|
90
|
+
archiveReader: ArchiveReaderPort;
|
|
91
|
+
yamlReader: YamlReaderPort;
|
|
92
|
+
fetch: SkillFetchPort;
|
|
93
|
+
ids: {
|
|
94
|
+
next(required: Record<string, never>): string;
|
|
95
|
+
};
|
|
96
|
+
toolId: (required: {
|
|
97
|
+
name: string;
|
|
98
|
+
}) => string;
|
|
99
|
+
toolSourceLoader: ToolSourceLoaderPort;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GitHubSkillSource } from "./github.js";
|
|
2
|
+
import type { SkillFilesystemPort } from "./ports.js";
|
|
3
|
+
export interface SkillState {
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly source: "uploaded" | GitHubSkillSource;
|
|
6
|
+
}
|
|
7
|
+
/** Missing legacy records default to enabled; malformed records and symlinks fail closed. */
|
|
8
|
+
export declare function readSkillState(required: {
|
|
9
|
+
directory: string;
|
|
10
|
+
stateFileName: string;
|
|
11
|
+
filesystem: SkillFilesystemPort;
|
|
12
|
+
}, _optional?: Record<string, never>): Promise<SkillState>;
|
|
13
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/** Missing legacy records default to enabled; malformed records and symlinks fail closed. */
|
|
3
|
+
export async function readSkillState(required, _optional = {}) {
|
|
4
|
+
const statePath = path.join(required.directory, required.stateFileName);
|
|
5
|
+
try {
|
|
6
|
+
const info = await required.filesystem.lstat({ path: statePath });
|
|
7
|
+
if (info.kind !== "file" || info.size > 8192)
|
|
8
|
+
throw new Error("Invalid skill installation record.");
|
|
9
|
+
const bytes = await required.filesystem.readFile({ path: statePath, maxBytes: 8192 });
|
|
10
|
+
if (bytes.length > 8192)
|
|
11
|
+
throw new Error("Invalid skill installation record.");
|
|
12
|
+
const state = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
13
|
+
if (!state || typeof state.enabled !== "boolean" || (state.source !== "uploaded" && (!state.source || typeof state.source.githubUrl !== "string" || !/^[a-f0-9]{40}$/.test(state.source.commit))))
|
|
14
|
+
throw new Error("Invalid skill installation record.");
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === "ENOENT")
|
|
19
|
+
return { enabled: true, source: "uploaded" };
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=state.js.map
|