apphx 0.0.0-stage → 1.1.0
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/LICENSE +21 -0
- package/README.md +227 -2
- package/dist/apple-types.js +45 -0
- package/dist/apple-types.js.map +1 -0
- package/dist/apple.js +900 -0
- package/dist/apple.js.map +1 -0
- package/dist/cli.js +414 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +80 -0
- package/dist/config.js.map +1 -0
- package/dist/dependencies.js +157 -0
- package/dist/dependencies.js.map +1 -0
- package/dist/export-options.js +49 -0
- package/dist/export-options.js.map +1 -0
- package/dist/flutter.js +171 -0
- package/dist/flutter.js.map +1 -0
- package/dist/inbox.js +128 -0
- package/dist/inbox.js.map +1 -0
- package/dist/integrations.js +435 -0
- package/dist/integrations.js.map +1 -0
- package/dist/preflight.js +43 -0
- package/dist/preflight.js.map +1 -0
- package/dist/process.js +260 -0
- package/dist/process.js.map +1 -0
- package/dist/remote.js +143 -0
- package/dist/remote.js.map +1 -0
- package/dist/requirements.js +37 -0
- package/dist/requirements.js.map +1 -0
- package/dist/runner.js +722 -0
- package/dist/runner.js.map +1 -0
- package/dist/scheduler.js +83 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/schemas.js +232 -0
- package/dist/schemas.js.map +1 -0
- package/dist/store.js +361 -0
- package/dist/store.js.map +1 -0
- package/examples/factory.json +28 -0
- package/examples/luggage.json +39 -0
- package/examples/remote.json +6 -0
- package/package.json +30 -3
- package/skills/apphx/LICENSE +21 -0
- package/skills/apphx/SKILL.md +103 -0
- package/skills/apphx/resources/requirement.json +39 -0
- package/skills/apphx/resources/research-result.json +21 -0
- package/skills/apphx/resources/review-result.json +7 -0
package/dist/inbox.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { toError } from "./schemas.js";
|
|
2
|
+
import { validateAppleSpec } from "./apple.js";
|
|
3
|
+
import { MAX_INPUT_BYTES, parseRequirement, readInputFile } from "./requirements.js";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { closeSync, fsyncSync, linkSync, mkdirSync, openSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { setImmediate } from "node:timers/promises";
|
|
8
|
+
// src/inbox.ts
|
|
9
|
+
function canonical(value) {
|
|
10
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
11
|
+
if (value !== null && typeof value === "object") {
|
|
12
|
+
const object = value;
|
|
13
|
+
return `{${Object.keys(object).filter((key) => object[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonical(object[key])}`).join(",")}}`;
|
|
14
|
+
}
|
|
15
|
+
return JSON.stringify(value) ?? "null";
|
|
16
|
+
}
|
|
17
|
+
function requestValue({ spec, baseJobId }) {
|
|
18
|
+
return {
|
|
19
|
+
...spec,
|
|
20
|
+
...baseJobId === void 0 ? {} : { baseJobId }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function submitRequirement(config, value) {
|
|
24
|
+
if (!config.inbox) throw new Error("No inbox is configured; set inbox.directory before submitting a requirement");
|
|
25
|
+
const request = parseRequirement(value);
|
|
26
|
+
if (config.allowSubmit) validateAppleSpec(request.spec);
|
|
27
|
+
const serialized = canonical(requestValue(request));
|
|
28
|
+
const content = `${serialized}\n`;
|
|
29
|
+
if (Buffer.byteLength(content) > 1048576) throw new Error(`Requirement exceeds ${MAX_INPUT_BYTES} bytes; reduce its size`);
|
|
30
|
+
const directory = resolve(config.inbox.directory);
|
|
31
|
+
mkdirSync(directory, {
|
|
32
|
+
recursive: true,
|
|
33
|
+
mode: 448
|
|
34
|
+
});
|
|
35
|
+
const version = request.spec.apple?.version ?? "1.0.0";
|
|
36
|
+
const versionKey = createHash("sha256").update(version).digest("hex");
|
|
37
|
+
const path = join(directory, `${request.spec.id}-${versionKey}.json`);
|
|
38
|
+
const temporary = join(directory, `.requirement-${randomUUID()}.tmp`);
|
|
39
|
+
const fd = openSync(temporary, "wx", 384);
|
|
40
|
+
try {
|
|
41
|
+
try {
|
|
42
|
+
writeFileSync(fd, content);
|
|
43
|
+
fsyncSync(fd);
|
|
44
|
+
} finally {
|
|
45
|
+
closeSync(fd);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
linkSync(temporary, path);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (toError(error).code !== "EEXIST") throw error;
|
|
51
|
+
if (canonical(requestValue(parseRequirement(JSON.parse(readInputFile(path).toString("utf8"))))) !== serialized) throw new Error(`Requirement ${request.spec.id} ${version} already exists at ${path} with different content; use a new product version`);
|
|
52
|
+
}
|
|
53
|
+
return { path };
|
|
54
|
+
} finally {
|
|
55
|
+
unlinkSync(temporary);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function scanInbox(store, config, options = {}) {
|
|
59
|
+
const observed = options.observe === true;
|
|
60
|
+
if (!config.inbox) return {
|
|
61
|
+
entries: [],
|
|
62
|
+
error: null,
|
|
63
|
+
observed
|
|
64
|
+
};
|
|
65
|
+
const directory = resolve(config.inbox.directory);
|
|
66
|
+
let names;
|
|
67
|
+
try {
|
|
68
|
+
names = readdirSync(directory).filter((name) => name.endsWith(".json")).sort();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
entries: [],
|
|
72
|
+
error: `Cannot read inbox ${directory}: ${toError(error).message}`,
|
|
73
|
+
observed
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const previous = new Map(store.inboxEntries().map((entry) => [entry.source, entry]));
|
|
77
|
+
const entries = [];
|
|
78
|
+
const pending = [];
|
|
79
|
+
for (const name of names) {
|
|
80
|
+
await setImmediate();
|
|
81
|
+
if (options.signal?.aborted) break;
|
|
82
|
+
const source = join(directory, name);
|
|
83
|
+
let digest = null;
|
|
84
|
+
try {
|
|
85
|
+
const bytes = readInputFile(source);
|
|
86
|
+
digest = createHash("sha256").update(bytes).digest("hex");
|
|
87
|
+
const request = parseRequirement(JSON.parse(bytes.toString("utf8")));
|
|
88
|
+
if (config.allowSubmit) validateAppleSpec(request.spec);
|
|
89
|
+
const old = previous.get(source);
|
|
90
|
+
if (!observed && old?.status === "accepted" && old.digest === digest && old.jobId && store.get(old.jobId).requestSpec !== null) {
|
|
91
|
+
store.checkIngest(request.spec, request.baseJobId);
|
|
92
|
+
entries.push(old);
|
|
93
|
+
} else if (observed) {
|
|
94
|
+
const existing = store.checkIngest(request.spec, request.baseJobId, pending);
|
|
95
|
+
entries.push({
|
|
96
|
+
source,
|
|
97
|
+
digest,
|
|
98
|
+
status: "accepted",
|
|
99
|
+
jobId: existing?.id ?? null,
|
|
100
|
+
error: null,
|
|
101
|
+
observedAt: Date.now()
|
|
102
|
+
});
|
|
103
|
+
pending.push(request);
|
|
104
|
+
} else entries.push(store.ingest(source, digest, request.spec, config, request.baseJobId));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const message = `Cannot accept requirement ${source}: ${toError(error).message}`;
|
|
107
|
+
const old = previous.get(source);
|
|
108
|
+
if (observed) entries.push({
|
|
109
|
+
source,
|
|
110
|
+
digest,
|
|
111
|
+
status: "error",
|
|
112
|
+
jobId: null,
|
|
113
|
+
error: message,
|
|
114
|
+
observedAt: Date.now()
|
|
115
|
+
});
|
|
116
|
+
else if (old?.status === "error" && old.digest === digest && old.error === message) entries.push(old);
|
|
117
|
+
else entries.push(store.inboxError(source, digest, message));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
entries,
|
|
122
|
+
error: null,
|
|
123
|
+
observed
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export { scanInbox, submitRequirement };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.js","names":["yieldTurn"],"sources":["../src/inbox.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { closeSync, fsyncSync, linkSync, mkdirSync, openSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { setImmediate as yieldTurn } from 'node:timers/promises';\nimport { validateAppleSpec } from './apple.js';\nimport { toError, type Config, type InboxEntry } from './schemas.js';\nimport { MAX_INPUT_BYTES, parseRequirement, readInputFile, type Requirement } from './requirements.js';\nimport type { Store } from './store.js';\n\nexport type InboxScan = { entries: InboxEntry[]; error: string | null; observed: boolean };\n\n\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;\n if (value !== null && typeof value === 'object') {\n const object = value as Record<string, unknown>;\n return `{${Object.keys(object).filter(key => object[key] !== undefined).sort().map(key => `${JSON.stringify(key)}:${canonical(object[key])}`).join(',')}}`;\n }\n return JSON.stringify(value) ?? 'null';\n}\n\nfunction requestValue({ spec, baseJobId }: Requirement): unknown {\n return { ...spec, ...(baseJobId === undefined ? {} : { baseJobId }) };\n}\n\n\nexport function submitRequirement(config: Config, value: unknown): { path: string } {\n if (!config.inbox) throw new Error('No inbox is configured; set inbox.directory before submitting a requirement');\n const request = parseRequirement(value);\n if (config.allowSubmit) validateAppleSpec(request.spec);\n const serialized = canonical(requestValue(request));\n const content = `${serialized}\\n`;\n if (Buffer.byteLength(content) > MAX_INPUT_BYTES) throw new Error(`Requirement exceeds ${MAX_INPUT_BYTES} bytes; reduce its size`);\n const directory = resolve(config.inbox.directory);\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n const version = request.spec.apple?.version ?? '1.0.0';\n const versionKey = createHash('sha256').update(version).digest('hex');\n const path = join(directory, `${request.spec.id}-${versionKey}.json`);\n const temporary = join(directory, `.requirement-${randomUUID()}.tmp`);\n const fd = openSync(temporary, 'wx', 0o600);\n try {\n try {\n writeFileSync(fd, content);\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n try {\n // A hard link publishes complete bytes atomically without replacing another writer's request.\n linkSync(temporary, path);\n } catch (error) {\n if (toError(error).code !== 'EEXIST') throw error;\n const existing = parseRequirement(JSON.parse(readInputFile(path).toString('utf8')) as unknown);\n if (canonical(requestValue(existing)) !== serialized) {\n throw new Error(`Requirement ${request.spec.id} ${version} already exists at ${path} with different content; use a new product version`);\n }\n }\n return { path };\n } finally {\n unlinkSync(temporary);\n }\n}\n\nexport async function scanInbox(store: Store, config: Config, options: { observe?: boolean; signal?: AbortSignal } = {}): Promise<InboxScan> {\n const observed = options.observe === true;\n if (!config.inbox) return { entries: [], error: null, observed };\n const directory = resolve(config.inbox.directory);\n let names: string[];\n try {\n names = readdirSync(directory).filter(name => name.endsWith('.json')).sort();\n } catch (error) {\n return { entries: [], error: `Cannot read inbox ${directory}: ${toError(error).message}`, observed };\n }\n const previous = new Map(store.inboxEntries().map(entry => [entry.source, entry]));\n const entries: InboxEntry[] = [];\n const pending: Requirement[] = [];\n for (const name of names) {\n await yieldTurn();\n if (options.signal?.aborted) break;\n const source = join(directory, name);\n let digest: string | null = null;\n try {\n const bytes = readInputFile(source);\n digest = createHash('sha256').update(bytes).digest('hex');\n const request = parseRequirement(JSON.parse(bytes.toString('utf8')) as unknown);\n if (config.allowSubmit) validateAppleSpec(request.spec);\n const old = previous.get(source);\n if (!observed && old?.status === 'accepted' && old.digest === digest && old.jobId && store.get(old.jobId).requestSpec !== null) {\n store.checkIngest(request.spec, request.baseJobId);\n entries.push(old);\n } else if (observed) {\n const existing = store.checkIngest(request.spec, request.baseJobId, pending);\n entries.push({ source, digest, status: 'accepted', jobId: existing?.id ?? null, error: null, observedAt: Date.now() });\n pending.push(request);\n } else {\n entries.push(store.ingest(source, digest, request.spec, config, request.baseJobId));\n }\n } catch (error) {\n const message = `Cannot accept requirement ${source}: ${toError(error).message}`;\n const old = previous.get(source);\n if (observed) {\n entries.push({ source, digest, status: 'error', jobId: null, error: message, observedAt: Date.now() });\n } else if (old?.status === 'error' && old.digest === digest && old.error === message) {\n entries.push(old);\n } else {\n entries.push(store.inboxError(source, digest, message));\n }\n }\n }\n return { entries, error: null, observed };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, parse, resolve, sep } from "node:path";
|
|
5
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
// src/integrations.ts
|
|
8
|
+
const skill = "apphx";
|
|
9
|
+
const marker = ".apphx-install.json";
|
|
10
|
+
const owner = "apphx";
|
|
11
|
+
const bundledDirectory = fileURLToPath(new URL("../skills/apphx/", import.meta.url));
|
|
12
|
+
const packageFile = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
13
|
+
const hosts = Object.freeze([
|
|
14
|
+
{
|
|
15
|
+
id: "codex",
|
|
16
|
+
name: "Codex",
|
|
17
|
+
invocation: "$apphx",
|
|
18
|
+
projectDirectory: ".agents/skills",
|
|
19
|
+
globalDirectory: "~/.agents/skills",
|
|
20
|
+
source: "https://developers.openai.com/codex/skills",
|
|
21
|
+
instructions: "Use $apphx in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "claude",
|
|
25
|
+
name: "Claude Code",
|
|
26
|
+
invocation: "/apphx",
|
|
27
|
+
projectDirectory: ".claude/skills",
|
|
28
|
+
globalDirectory: "$CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills)",
|
|
29
|
+
source: "https://code.claude.com/docs/en/skills",
|
|
30
|
+
instructions: "Use /apphx in a local Claude Code session. Start a new session if the skill is not listed. Cloud sessions do not inherit local user skills."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "cursor",
|
|
34
|
+
name: "Cursor",
|
|
35
|
+
invocation: "/apphx",
|
|
36
|
+
projectDirectory: ".cursor/skills",
|
|
37
|
+
globalDirectory: "~/.cursor/skills",
|
|
38
|
+
source: "https://cursor.com/docs/skills",
|
|
39
|
+
instructions: "In Agent chat, type / and select apphx. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "pi",
|
|
43
|
+
name: "Pi",
|
|
44
|
+
invocation: "/skill:apphx",
|
|
45
|
+
projectDirectory: ".pi/skills",
|
|
46
|
+
globalDirectory: "$PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills)",
|
|
47
|
+
source: "https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md",
|
|
48
|
+
instructions: "Grant project trust before using project skills, then /reload and /skill:apphx. Stock Pi has no native subagents: use a separate human-started reviewer session, not a spawned coding-agent CLI."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "gemini",
|
|
52
|
+
name: "Gemini CLI",
|
|
53
|
+
invocation: "Use the apphx skill",
|
|
54
|
+
projectDirectory: ".gemini/skills",
|
|
55
|
+
globalDirectory: "$GEMINI_CLI_HOME/.gemini/skills (default ~/.gemini/skills)",
|
|
56
|
+
source: "https://geminicli.com/docs/cli/skills/",
|
|
57
|
+
instructions: "Run /skills reload and /skills list, then ask: Use the apphx skill. Gemini calls activate_skill; approve its consent prompt. This is not a /apphx command."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "opencode",
|
|
61
|
+
name: "OpenCode",
|
|
62
|
+
invocation: "Use the apphx skill",
|
|
63
|
+
projectDirectory: ".opencode/skills",
|
|
64
|
+
globalDirectory: "$XDG_CONFIG_HOME/opencode/skills (default ~/.config/opencode/skills)",
|
|
65
|
+
source: "https://opencode.ai/docs/skills/",
|
|
66
|
+
instructions: "Ask OpenCode to use apphx; the agent loads it with its native skill({ name: \"apphx\" }) tool. Skill permissions must permit loading. Restart if necessary; no custom slash command is installed."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "deepseek",
|
|
70
|
+
name: "DeepSeek Harness",
|
|
71
|
+
invocation: "Use the apphx skill",
|
|
72
|
+
projectDirectory: ".dsh/skills (nearest Git root, otherwise selected project)",
|
|
73
|
+
globalDirectory: "$DSH_HOME/skills (default ~/.dsh/skills)",
|
|
74
|
+
source: "https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/skills",
|
|
75
|
+
instructions: "Ask DeepSeek Harness to use apphx; its native skill({ name: \"apphx\" }) tool loads it. Requires the shipped filesystem skill provider and skill tool, with default roots enabled. Custom provider configurations are not modified."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "hermes",
|
|
79
|
+
name: "Hermes Agent",
|
|
80
|
+
invocation: "/apphx",
|
|
81
|
+
projectDirectory: ".hermes/skills",
|
|
82
|
+
globalDirectory: "$HERMES_HOME/skills (default ~/.hermes/skills)",
|
|
83
|
+
source: "https://hermes-agent.nousresearch.com/docs/user-guide/features/skills",
|
|
84
|
+
instructions: "Use /apphx. Project installation requires a Git root and a separate owner-approved hermes skills trust decision; this installer never grants trust. For non-Git projects or profile-wide cron use --global in the same HERMES_HOME as Hermes. Confirm discovery with hermes skills list; cron must attach the skill and an absolute workdir."
|
|
85
|
+
}
|
|
86
|
+
].map((host) => Object.freeze(host)));
|
|
87
|
+
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
88
|
+
const safeRelative = (path) => /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(path) && path.split("/").every((part) => part !== "." && part !== ".." && part.length > 0) && path !== marker;
|
|
89
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
90
|
+
const manifestSchema = z.strictObject({
|
|
91
|
+
format: z.literal(1),
|
|
92
|
+
owner: z.literal(owner),
|
|
93
|
+
skill: z.literal(skill),
|
|
94
|
+
host: z.string(),
|
|
95
|
+
version: z.string().regex(/^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/),
|
|
96
|
+
files: z.record(z.string().refine(safeRelative), digestSchema),
|
|
97
|
+
metadataDigest: digestSchema
|
|
98
|
+
});
|
|
99
|
+
function assertPath(path) {
|
|
100
|
+
const absolute = resolve(path);
|
|
101
|
+
let current = parse(absolute).root;
|
|
102
|
+
const parts = absolute.slice(current.length).split(sep).filter(Boolean);
|
|
103
|
+
for (let i = 0; i < parts.length; i++) {
|
|
104
|
+
current = join(current, parts[i]);
|
|
105
|
+
const info = lstatSync(current, { throwIfNoEntry: false });
|
|
106
|
+
if (!info) return;
|
|
107
|
+
if (info.isSymbolicLink()) throw new Error(`Refusing symlink path: ${current}. Select its real directory explicitly.`);
|
|
108
|
+
if (i < parts.length - 1 && !info.isDirectory()) throw new Error(`Not a directory: ${current}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function readRegular(path, packageSource = false) {
|
|
112
|
+
assertPath(path);
|
|
113
|
+
const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
114
|
+
try {
|
|
115
|
+
const info = fstatSync(descriptor);
|
|
116
|
+
if (!info.isFile() || !packageSource && info.nlink !== 1) throw new Error(`Refusing non-regular or hard-linked installation file: ${path}`);
|
|
117
|
+
return readFileSync(descriptor);
|
|
118
|
+
} finally {
|
|
119
|
+
closeSync(descriptor);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function readTree(root, packageSource = false) {
|
|
123
|
+
assertPath(root);
|
|
124
|
+
if (!lstatSync(root).isDirectory()) throw new Error(`Not a skill directory: ${root}`);
|
|
125
|
+
const tree = {
|
|
126
|
+
files: Object.create(null),
|
|
127
|
+
directories: []
|
|
128
|
+
};
|
|
129
|
+
function visit(directory, prefix) {
|
|
130
|
+
for (const name of readdirSync(directory).sort()) {
|
|
131
|
+
const path = join(directory, name);
|
|
132
|
+
const relative = prefix ? `${prefix}/${name}` : name;
|
|
133
|
+
const info = lstatSync(path);
|
|
134
|
+
if (info.isSymbolicLink()) throw new Error(`Refusing symlink installation entry: ${path}`);
|
|
135
|
+
if (info.isDirectory()) {
|
|
136
|
+
tree.directories.push(relative);
|
|
137
|
+
visit(path, relative);
|
|
138
|
+
} else tree.files[relative] = readRegular(path, packageSource);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
visit(root, "");
|
|
142
|
+
return tree;
|
|
143
|
+
}
|
|
144
|
+
function fileDigests(tree) {
|
|
145
|
+
return Object.fromEntries(Object.entries(tree.files).filter(([path]) => path !== marker).map(([path, bytes]) => [path, digest(bytes)]));
|
|
146
|
+
}
|
|
147
|
+
function metadataDigest(value) {
|
|
148
|
+
return digest(JSON.stringify([
|
|
149
|
+
value.format,
|
|
150
|
+
value.owner,
|
|
151
|
+
value.skill,
|
|
152
|
+
value.host,
|
|
153
|
+
value.version,
|
|
154
|
+
Object.entries(value.files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
155
|
+
]));
|
|
156
|
+
}
|
|
157
|
+
function directoriesFor(files) {
|
|
158
|
+
const directories = /* @__PURE__ */ new Set();
|
|
159
|
+
for (const file of Object.keys(files)) {
|
|
160
|
+
let parent = file.slice(0, file.lastIndexOf("/"));
|
|
161
|
+
if (!file.includes("/")) continue;
|
|
162
|
+
while (parent) {
|
|
163
|
+
directories.add(parent);
|
|
164
|
+
parent = parent.includes("/") ? parent.slice(0, parent.lastIndexOf("/")) : "";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return [...directories].sort();
|
|
168
|
+
}
|
|
169
|
+
function readOwned(path, host) {
|
|
170
|
+
const tree = readTree(path);
|
|
171
|
+
if (!tree.files[marker]) throw new Error(`Foreign skill directory has no ${marker}: ${path}`);
|
|
172
|
+
let manifest;
|
|
173
|
+
try {
|
|
174
|
+
manifest = manifestSchema.parse(JSON.parse(tree.files[marker].toString("utf8")));
|
|
175
|
+
} catch {
|
|
176
|
+
throw new Error(`Invalid ownership metadata: ${path}`);
|
|
177
|
+
}
|
|
178
|
+
if (manifest.host !== host || manifest.metadataDigest !== metadataDigest(manifest)) throw new Error(`Foreign or edited ownership metadata: ${path}`);
|
|
179
|
+
if (!manifest.files["SKILL.md"] || JSON.stringify(Object.keys(tree.files).filter((file) => file !== marker).sort()) !== JSON.stringify(Object.keys(manifest.files).sort())) throw new Error(`Added or missing files in owned skill: ${path}`);
|
|
180
|
+
for (const [file, expected] of Object.entries(manifest.files)) if (digest(tree.files[file]) !== expected) throw new Error(`User-edited skill file: ${join(path, file)}`);
|
|
181
|
+
if (JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(manifest.files))) throw new Error(`Unowned directories in skill: ${path}`);
|
|
182
|
+
return manifest;
|
|
183
|
+
}
|
|
184
|
+
function selectedHost(id) {
|
|
185
|
+
const host = hosts.find((value) => value.id === id);
|
|
186
|
+
if (!host) throw new Error(`Unsupported host ${JSON.stringify(id)}. Supported hosts: ${hosts.map((value) => value.id).join(", ")}`);
|
|
187
|
+
return host;
|
|
188
|
+
}
|
|
189
|
+
function environmentDirectory(name, fallback) {
|
|
190
|
+
const configured = process.env[name];
|
|
191
|
+
if (!configured) return fallback;
|
|
192
|
+
const expanded = configured === "~" ? homedir() : configured.startsWith("~/") ? join(homedir(), configured.slice(2)) : configured;
|
|
193
|
+
if (!isAbsolute(expanded)) throw new Error(`${name} must be an absolute directory for installation`);
|
|
194
|
+
return resolve(expanded);
|
|
195
|
+
}
|
|
196
|
+
function location(id, options) {
|
|
197
|
+
const host = selectedHost(id);
|
|
198
|
+
if (options.global && options.project !== void 0) throw new Error("Choose either project or global installation, not both");
|
|
199
|
+
if (options.global) {
|
|
200
|
+
let directory;
|
|
201
|
+
switch (id) {
|
|
202
|
+
case "claude":
|
|
203
|
+
directory = join(environmentDirectory("CLAUDE_CONFIG_DIR", join(homedir(), ".claude")), "skills");
|
|
204
|
+
break;
|
|
205
|
+
case "gemini":
|
|
206
|
+
directory = join(environmentDirectory("GEMINI_CLI_HOME", homedir()), ".gemini", "skills");
|
|
207
|
+
break;
|
|
208
|
+
case "pi":
|
|
209
|
+
directory = join(environmentDirectory("PI_CODING_AGENT_DIR", join(homedir(), ".pi", "agent")), "skills");
|
|
210
|
+
break;
|
|
211
|
+
case "opencode":
|
|
212
|
+
directory = join(environmentDirectory("XDG_CONFIG_HOME", join(homedir(), ".config")), "opencode", "skills");
|
|
213
|
+
break;
|
|
214
|
+
case "deepseek":
|
|
215
|
+
directory = join(environmentDirectory("DSH_HOME", join(homedir(), ".dsh")), "skills");
|
|
216
|
+
break;
|
|
217
|
+
case "hermes":
|
|
218
|
+
directory = join(environmentDirectory("HERMES_HOME", join(homedir(), ".hermes")), "skills");
|
|
219
|
+
break;
|
|
220
|
+
default: directory = join(homedir(), host.projectDirectory);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
host,
|
|
224
|
+
scope: "global",
|
|
225
|
+
path: join(directory, skill)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (options.project !== void 0 && !options.project.trim()) throw new Error("Project directory must not be empty");
|
|
229
|
+
let project = resolve(options.project ?? process.cwd());
|
|
230
|
+
assertPath(project);
|
|
231
|
+
if (!lstatSync(project, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`Project directory does not exist: ${project}`);
|
|
232
|
+
if (id === "deepseek" || id === "hermes") {
|
|
233
|
+
let found = false;
|
|
234
|
+
for (let candidate = project;; candidate = dirname(candidate)) {
|
|
235
|
+
if (lstatSync(join(candidate, ".git"), { throwIfNoEntry: false })) {
|
|
236
|
+
project = candidate;
|
|
237
|
+
found = true;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (dirname(candidate) === candidate) break;
|
|
241
|
+
}
|
|
242
|
+
if (id === "hermes" && !found) throw new Error("Hermes project skills require a Git root; use --global for a profile-level installation instead");
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
host,
|
|
246
|
+
scope: "project",
|
|
247
|
+
path: join(project, id === "deepseek" ? ".dsh/skills" : host.projectDirectory, skill)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function details(target) {
|
|
251
|
+
return {
|
|
252
|
+
host: target.host.id,
|
|
253
|
+
name: target.host.name,
|
|
254
|
+
scope: target.scope,
|
|
255
|
+
path: target.path,
|
|
256
|
+
invocation: target.host.invocation,
|
|
257
|
+
instructions: target.host.instructions,
|
|
258
|
+
source: target.host.source
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function bundle(host) {
|
|
262
|
+
const tree = readTree(bundledDirectory, true);
|
|
263
|
+
if (!tree.files["SKILL.md"] || tree.files[marker]) throw new Error("Invalid bundled apphx skill");
|
|
264
|
+
const files = fileDigests(tree);
|
|
265
|
+
if (!Object.keys(files).every(safeRelative) || JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(files))) throw new Error("Invalid bundled skill resource paths");
|
|
266
|
+
const version = JSON.parse(readRegular(packageFile, true).toString("utf8")).version;
|
|
267
|
+
const value = {
|
|
268
|
+
format: 1,
|
|
269
|
+
owner,
|
|
270
|
+
skill,
|
|
271
|
+
host,
|
|
272
|
+
version,
|
|
273
|
+
files
|
|
274
|
+
};
|
|
275
|
+
const manifest = manifestSchema.parse({
|
|
276
|
+
...value,
|
|
277
|
+
metadataDigest: metadataDigest(value)
|
|
278
|
+
});
|
|
279
|
+
tree.files[marker] = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
|
|
280
|
+
return {
|
|
281
|
+
tree,
|
|
282
|
+
manifest
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function inspectIntegration(host, options = {}) {
|
|
286
|
+
const target = location(host, options);
|
|
287
|
+
const result = details(target);
|
|
288
|
+
try {
|
|
289
|
+
assertPath(target.path);
|
|
290
|
+
if (!lstatSync(target.path, { throwIfNoEntry: false })) return {
|
|
291
|
+
...result,
|
|
292
|
+
installed: false,
|
|
293
|
+
status: "missing"
|
|
294
|
+
};
|
|
295
|
+
const manifest = readOwned(target.path, host);
|
|
296
|
+
const current = bundle(host).manifest.metadataDigest === manifest.metadataDigest;
|
|
297
|
+
return {
|
|
298
|
+
...result,
|
|
299
|
+
installed: true,
|
|
300
|
+
status: "installed",
|
|
301
|
+
version: manifest.version,
|
|
302
|
+
current
|
|
303
|
+
};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
...result,
|
|
307
|
+
installed: false,
|
|
308
|
+
status: "conflict",
|
|
309
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function withLock(target, action) {
|
|
314
|
+
const directory = dirname(dirname(target.path));
|
|
315
|
+
assertPath(directory);
|
|
316
|
+
mkdirSync(directory, { recursive: true });
|
|
317
|
+
assertPath(directory);
|
|
318
|
+
const lock = join(directory, ".apphx-install.lock");
|
|
319
|
+
let descriptor;
|
|
320
|
+
try {
|
|
321
|
+
descriptor = openSync(lock, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 384);
|
|
322
|
+
} catch {
|
|
323
|
+
throw new Error(`Installation is locked at ${lock}; if an earlier installer stopped, inspect it before removing its lock`);
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
return action();
|
|
327
|
+
} finally {
|
|
328
|
+
closeSync(descriptor);
|
|
329
|
+
unlinkSync(lock);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function applyIntegration(host, options, operation) {
|
|
333
|
+
const target = location(host, options);
|
|
334
|
+
const next = bundle(host);
|
|
335
|
+
assertPath(target.path);
|
|
336
|
+
if (operation === "update" && !lstatSync(target.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);
|
|
337
|
+
return withLock(target, () => {
|
|
338
|
+
assertPath(target.path);
|
|
339
|
+
const previous = lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : void 0;
|
|
340
|
+
if (operation === "update" && !previous) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);
|
|
341
|
+
const result = {
|
|
342
|
+
...details(target),
|
|
343
|
+
installed: true,
|
|
344
|
+
status: "installed",
|
|
345
|
+
version: next.manifest.version,
|
|
346
|
+
current: true
|
|
347
|
+
};
|
|
348
|
+
if (previous?.metadataDigest === next.manifest.metadataDigest) return {
|
|
349
|
+
...result,
|
|
350
|
+
changed: false
|
|
351
|
+
};
|
|
352
|
+
if (operation === "install" && previous) throw new Error(`An older or different workflow is installed at ${target.path}; use apphx update ${host} with the same scope`);
|
|
353
|
+
const parent = dirname(target.path);
|
|
354
|
+
mkdirSync(parent, { recursive: true });
|
|
355
|
+
const staging = mkdtempSync(join(dirname(parent), ".apphx-stage-"));
|
|
356
|
+
const replacement = join(staging, skill);
|
|
357
|
+
const backup = join(staging, "previous");
|
|
358
|
+
let moved = false;
|
|
359
|
+
try {
|
|
360
|
+
mkdirSync(replacement);
|
|
361
|
+
for (const [file, bytes] of Object.entries(next.tree.files)) {
|
|
362
|
+
const path = join(replacement, file);
|
|
363
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
364
|
+
writeFileSync(path, bytes, {
|
|
365
|
+
flag: "wx",
|
|
366
|
+
mode: 420
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
assertPath(target.path);
|
|
370
|
+
if (previous) {
|
|
371
|
+
if (readOwned(target.path, host).metadataDigest !== previous.metadataDigest) throw new Error("Installation changed during upgrade");
|
|
372
|
+
renameSync(target.path, backup);
|
|
373
|
+
moved = true;
|
|
374
|
+
} else if (lstatSync(target.path, { throwIfNoEntry: false })) throw new Error("Skill appeared during installation; refusing overwrite");
|
|
375
|
+
try {
|
|
376
|
+
renameSync(replacement, target.path);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
if (moved) {
|
|
379
|
+
renameSync(backup, target.path);
|
|
380
|
+
moved = false;
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
if (moved) {
|
|
385
|
+
readOwned(backup, host);
|
|
386
|
+
rmSync(backup, { recursive: true });
|
|
387
|
+
moved = false;
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
...result,
|
|
391
|
+
changed: true
|
|
392
|
+
};
|
|
393
|
+
} finally {
|
|
394
|
+
if (!moved) rmSync(staging, {
|
|
395
|
+
recursive: true,
|
|
396
|
+
force: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
function installIntegration(host, options = {}) {
|
|
402
|
+
return applyIntegration(host, options, "install");
|
|
403
|
+
}
|
|
404
|
+
function updateIntegration(host, options = {}) {
|
|
405
|
+
return applyIntegration(host, options, "update");
|
|
406
|
+
}
|
|
407
|
+
function uninstallIntegration(host, options = {}) {
|
|
408
|
+
const target = location(host, options);
|
|
409
|
+
assertPath(target.path);
|
|
410
|
+
const result = {
|
|
411
|
+
...details(target),
|
|
412
|
+
installed: false,
|
|
413
|
+
status: "missing"
|
|
414
|
+
};
|
|
415
|
+
if (!lstatSync(target.path, { throwIfNoEntry: false })) return {
|
|
416
|
+
...result,
|
|
417
|
+
changed: false
|
|
418
|
+
};
|
|
419
|
+
return withLock(target, () => {
|
|
420
|
+
assertPath(target.path);
|
|
421
|
+
if (!lstatSync(target.path, { throwIfNoEntry: false })) return {
|
|
422
|
+
...result,
|
|
423
|
+
changed: false
|
|
424
|
+
};
|
|
425
|
+
readOwned(target.path, host);
|
|
426
|
+
rmSync(target.path, { recursive: true });
|
|
427
|
+
return {
|
|
428
|
+
...result,
|
|
429
|
+
changed: true
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
export { hosts, inspectIntegration, installIntegration, uninstallIntegration, updateIntegration };
|
|
434
|
+
|
|
435
|
+
//# sourceMappingURL=integrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrations.js","names":[],"sources":["../src/integrations.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { closeSync, constants, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { z } from 'zod';\n\nconst skill = 'apphx';\nconst marker = '.apphx-install.json';\nconst owner = 'apphx';\nconst bundledDirectory = fileURLToPath(new URL('../skills/apphx/', import.meta.url));\nconst packageFile = fileURLToPath(new URL('../package.json', import.meta.url));\n\nexport const hosts = Object.freeze([\n { id: 'codex', name: 'Codex', invocation: '$apphx', projectDirectory: '.agents/skills', globalDirectory: '~/.agents/skills', source: 'https://developers.openai.com/codex/skills', instructions: 'Use $apphx in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed.' },\n { id: 'claude', name: 'Claude Code', invocation: '/apphx', projectDirectory: '.claude/skills', globalDirectory: '$CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills)', source: 'https://code.claude.com/docs/en/skills', instructions: 'Use /apphx in a local Claude Code session. Start a new session if the skill is not listed. Cloud sessions do not inherit local user skills.' },\n { id: 'cursor', name: 'Cursor', invocation: '/apphx', projectDirectory: '.cursor/skills', globalDirectory: '~/.cursor/skills', source: 'https://cursor.com/docs/skills', instructions: 'In Agent chat, type / and select apphx. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync.' },\n { id: 'pi', name: 'Pi', invocation: '/skill:apphx', projectDirectory: '.pi/skills', globalDirectory: '$PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills)', source: 'https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md', instructions: 'Grant project trust before using project skills, then /reload and /skill:apphx. Stock Pi has no native subagents: use a separate human-started reviewer session, not a spawned coding-agent CLI.' },\n { id: 'gemini', name: 'Gemini CLI', invocation: 'Use the apphx skill', projectDirectory: '.gemini/skills', globalDirectory: '$GEMINI_CLI_HOME/.gemini/skills (default ~/.gemini/skills)', source: 'https://geminicli.com/docs/cli/skills/', instructions: 'Run /skills reload and /skills list, then ask: Use the apphx skill. Gemini calls activate_skill; approve its consent prompt. This is not a /apphx command.' },\n { id: 'opencode', name: 'OpenCode', invocation: 'Use the apphx skill', projectDirectory: '.opencode/skills', globalDirectory: '$XDG_CONFIG_HOME/opencode/skills (default ~/.config/opencode/skills)', source: 'https://opencode.ai/docs/skills/', instructions: 'Ask OpenCode to use apphx; the agent loads it with its native skill({ name: \"apphx\" }) tool. Skill permissions must permit loading. Restart if necessary; no custom slash command is installed.' },\n { id: 'deepseek', name: 'DeepSeek Harness', invocation: 'Use the apphx skill', projectDirectory: '.dsh/skills (nearest Git root, otherwise selected project)', globalDirectory: '$DSH_HOME/skills (default ~/.dsh/skills)', source: 'https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/skills', instructions: 'Ask DeepSeek Harness to use apphx; its native skill({ name: \"apphx\" }) tool loads it. Requires the shipped filesystem skill provider and skill tool, with default roots enabled. Custom provider configurations are not modified.' },\n { id: 'hermes', name: 'Hermes Agent', invocation: '/apphx', projectDirectory: '.hermes/skills', globalDirectory: '$HERMES_HOME/skills (default ~/.hermes/skills)', source: 'https://hermes-agent.nousresearch.com/docs/user-guide/features/skills', instructions: 'Use /apphx. Project installation requires a Git root and a separate owner-approved hermes skills trust decision; this installer never grants trust. For non-Git projects or profile-wide cron use --global in the same HERMES_HOME as Hermes. Confirm discovery with hermes skills list; cron must attach the skill and an absolute workdir.' },\n].map(host => Object.freeze(host)));\n\nexport type IntegrationOptions = { project?: string; global?: boolean };\ntype Host = (typeof hosts)[number];\ntype Location = { host: Host; scope: 'project' | 'global'; path: string };\nexport type IntegrationInspection = {\n host: string; name: string; scope: 'project' | 'global'; path: string;\n invocation: string; instructions: string; source: string;\n installed: boolean; status: 'missing' | 'installed' | 'conflict';\n version?: string; current?: boolean; reason?: string;\n};\n\ntype Tree = { files: Record<string, Buffer>; directories: string[] };\nconst digest = (bytes: string | Buffer) => createHash('sha256').update(bytes).digest('hex');\nconst safeRelative = (path: string) => /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(path) && path.split('/').every(part => part !== '.' && part !== '..' && part.length > 0) && path !== marker;\nconst digestSchema = z.string().regex(/^[a-f0-9]{64}$/);\nconst manifestSchema = z.strictObject({\n format: z.literal(1), owner: z.literal(owner), skill: z.literal(skill), host: z.string(),\n version: z.string().regex(/^\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?(?:\\+[\\w.-]+)?$/),\n files: z.record(z.string().refine(safeRelative), digestSchema), metadataDigest: digestSchema,\n});\ntype Manifest = z.infer<typeof manifestSchema>;\n\n// Never follow a symlink, including an existing ancestor of a not-yet-created target.\nfunction assertPath(path: string): void {\n const absolute = resolve(path);\n let current = parse(absolute).root;\n const parts = absolute.slice(current.length).split(sep).filter(Boolean);\n for (let i = 0; i < parts.length; i++) {\n current = join(current, parts[i]!);\n const info = lstatSync(current, { throwIfNoEntry: false });\n if (!info) return;\n if (info.isSymbolicLink()) throw new Error(`Refusing symlink path: ${current}. Select its real directory explicitly.`);\n if (i < parts.length - 1 && !info.isDirectory()) throw new Error(`Not a directory: ${current}`);\n }\n}\n\nfunction readRegular(path: string, packageSource = false): Buffer {\n assertPath(path);\n const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);\n try {\n const info = fstatSync(descriptor);\n if (!info.isFile() || (!packageSource && info.nlink !== 1)) throw new Error(`Refusing non-regular or hard-linked installation file: ${path}`);\n return readFileSync(descriptor);\n } finally { closeSync(descriptor); }\n}\n\nfunction readTree(root: string, packageSource = false): Tree {\n assertPath(root);\n if (!lstatSync(root).isDirectory()) throw new Error(`Not a skill directory: ${root}`);\n const tree: Tree = { files: Object.create(null), directories: [] };\n function visit(directory: string, prefix: string): void {\n for (const name of readdirSync(directory).sort()) {\n const path = join(directory, name);\n const relative = prefix ? `${prefix}/${name}` : name;\n const info = lstatSync(path);\n if (info.isSymbolicLink()) throw new Error(`Refusing symlink installation entry: ${path}`);\n if (info.isDirectory()) { tree.directories.push(relative); visit(path, relative); }\n else tree.files[relative] = readRegular(path, packageSource);\n }\n }\n visit(root, '');\n return tree;\n}\n\nfunction fileDigests(tree: Tree): Record<string, string> {\n return Object.fromEntries(Object.entries(tree.files).filter(([path]) => path !== marker).map(([path, bytes]) => [path, digest(bytes)]));\n}\n\nfunction metadataDigest(value: Omit<Manifest, 'metadataDigest'>): string {\n return digest(JSON.stringify([value.format, value.owner, value.skill, value.host, value.version, Object.entries(value.files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)]));\n}\n\nfunction directoriesFor(files: Record<string, unknown>): string[] {\n const directories = new Set<string>();\n for (const file of Object.keys(files)) {\n let parent = file.slice(0, file.lastIndexOf('/'));\n if (!file.includes('/')) continue;\n while (parent) { directories.add(parent); parent = parent.includes('/') ? parent.slice(0, parent.lastIndexOf('/')) : ''; }\n }\n return [...directories].sort();\n}\n\nfunction readOwned(path: string, host: string): Manifest {\n const tree = readTree(path);\n if (!tree.files[marker]) throw new Error(`Foreign skill directory has no ${marker}: ${path}`);\n let manifest: Manifest;\n try { manifest = manifestSchema.parse(JSON.parse(tree.files[marker].toString('utf8'))); }\n catch { throw new Error(`Invalid ownership metadata: ${path}`); }\n if (manifest.host !== host || manifest.metadataDigest !== metadataDigest(manifest)) throw new Error(`Foreign or edited ownership metadata: ${path}`);\n if (!manifest.files['SKILL.md'] || JSON.stringify(Object.keys(tree.files).filter(file => file !== marker).sort()) !== JSON.stringify(Object.keys(manifest.files).sort())) throw new Error(`Added or missing files in owned skill: ${path}`);\n for (const [file, expected] of Object.entries(manifest.files)) {\n if (digest(tree.files[file]!) !== expected) throw new Error(`User-edited skill file: ${join(path, file)}`);\n }\n if (JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(manifest.files))) throw new Error(`Unowned directories in skill: ${path}`);\n return manifest;\n}\n\nfunction selectedHost(id: string): Host {\n const host = hosts.find(value => value.id === id);\n if (!host) throw new Error(`Unsupported host ${JSON.stringify(id)}. Supported hosts: ${hosts.map(value => value.id).join(', ')}`);\n return host;\n}\n\nfunction environmentDirectory(name: string, fallback: string): string {\n const configured = process.env[name];\n if (!configured) return fallback;\n const expanded = configured === '~' ? homedir() : configured.startsWith('~/') ? join(homedir(), configured.slice(2)) : configured;\n if (!isAbsolute(expanded)) throw new Error(`${name} must be an absolute directory for installation`);\n return resolve(expanded);\n}\n\nfunction location(id: string, options: IntegrationOptions): Location {\n const host = selectedHost(id);\n if (options.global && options.project !== undefined) throw new Error('Choose either project or global installation, not both');\n if (options.global) {\n let directory: string;\n switch (id) {\n case 'claude': directory = join(environmentDirectory('CLAUDE_CONFIG_DIR', join(homedir(), '.claude')), 'skills'); break;\n case 'gemini': directory = join(environmentDirectory('GEMINI_CLI_HOME', homedir()), '.gemini', 'skills'); break;\n case 'pi': directory = join(environmentDirectory('PI_CODING_AGENT_DIR', join(homedir(), '.pi', 'agent')), 'skills'); break;\n case 'opencode': directory = join(environmentDirectory('XDG_CONFIG_HOME', join(homedir(), '.config')), 'opencode', 'skills'); break;\n case 'deepseek': directory = join(environmentDirectory('DSH_HOME', join(homedir(), '.dsh')), 'skills'); break;\n case 'hermes': directory = join(environmentDirectory('HERMES_HOME', join(homedir(), '.hermes')), 'skills'); break;\n default: directory = join(homedir(), host.projectDirectory);\n }\n return { host, scope: 'global', path: join(directory, skill) };\n }\n if (options.project !== undefined && !options.project.trim()) throw new Error('Project directory must not be empty');\n let project = resolve(options.project ?? process.cwd());\n assertPath(project);\n if (!lstatSync(project, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`Project directory does not exist: ${project}`);\n if (id === 'deepseek' || id === 'hermes') {\n let found = false;\n for (let candidate = project; ; candidate = dirname(candidate)) {\n if (lstatSync(join(candidate, '.git'), { throwIfNoEntry: false })) { project = candidate; found = true; break; }\n if (dirname(candidate) === candidate) break;\n }\n if (id === 'hermes' && !found) throw new Error('Hermes project skills require a Git root; use --global for a profile-level installation instead');\n }\n return { host, scope: 'project', path: join(project, id === 'deepseek' ? '.dsh/skills' : host.projectDirectory, skill) };\n}\n\nfunction details(target: Location) {\n return { host: target.host.id, name: target.host.name, scope: target.scope, path: target.path, invocation: target.host.invocation, instructions: target.host.instructions, source: target.host.source };\n}\n\nfunction bundle(host: string): { tree: Tree; manifest: Manifest } {\n // Package stores may hard-link read-only resources; owned installation targets may not.\n const tree = readTree(bundledDirectory, true);\n if (!tree.files['SKILL.md'] || tree.files[marker]) throw new Error('Invalid bundled apphx skill');\n const files = fileDigests(tree);\n if (!Object.keys(files).every(safeRelative) || JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(files))) throw new Error('Invalid bundled skill resource paths');\n const version = JSON.parse(readRegular(packageFile, true).toString('utf8')).version;\n const value: Omit<Manifest, 'metadataDigest'> = { format: 1, owner, skill, host, version, files };\n const manifest = manifestSchema.parse({ ...value, metadataDigest: metadataDigest(value) });\n tree.files[marker] = Buffer.from(`${JSON.stringify(manifest, null, 2)}\\n`);\n return { tree, manifest };\n}\n\nexport function inspectIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection {\n const target = location(host, options);\n const result = details(target);\n try {\n assertPath(target.path);\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, installed: false, status: 'missing' };\n const manifest = readOwned(target.path, host);\n const current = bundle(host).manifest.metadataDigest === manifest.metadataDigest;\n return { ...result, installed: true, status: 'installed', version: manifest.version, current };\n } catch (error) {\n return { ...result, installed: false, status: 'conflict', reason: error instanceof Error ? error.message : String(error) };\n }\n}\n\nfunction withLock<T>(target: Location, action: () => T): T {\n const directory = dirname(dirname(target.path));\n assertPath(directory);\n mkdirSync(directory, { recursive: true });\n assertPath(directory);\n const lock = join(directory, '.apphx-install.lock');\n let descriptor: number;\n try { descriptor = openSync(lock, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600); }\n catch { throw new Error(`Installation is locked at ${lock}; if an earlier installer stopped, inspect it before removing its lock`); }\n try { return action(); }\n finally { closeSync(descriptor); unlinkSync(lock); }\n}\n\nfunction applyIntegration(host: string, options: IntegrationOptions, operation: 'install' | 'update'): IntegrationInspection & { changed: boolean } {\n const target = location(host, options);\n const next = bundle(host);\n assertPath(target.path);\n if (operation === 'update' && !lstatSync(target.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);\n return withLock(target, () => {\n assertPath(target.path);\n const previous = lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : undefined;\n if (operation === 'update' && !previous) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);\n const result = { ...details(target), installed: true, status: 'installed' as const, version: next.manifest.version, current: true };\n if (previous?.metadataDigest === next.manifest.metadataDigest) return { ...result, changed: false };\n if (operation === 'install' && previous) throw new Error(`An older or different workflow is installed at ${target.path}; use apphx update ${host} with the same scope`);\n const parent = dirname(target.path);\n mkdirSync(parent, { recursive: true });\n const staging = mkdtempSync(join(dirname(parent), '.apphx-stage-'));\n const replacement = join(staging, skill);\n const backup = join(staging, 'previous');\n let moved = false;\n try {\n mkdirSync(replacement);\n for (const [file, bytes] of Object.entries(next.tree.files)) {\n const path = join(replacement, file);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, bytes, { flag: 'wx', mode: 0o644 });\n }\n assertPath(target.path);\n if (previous) {\n if (readOwned(target.path, host).metadataDigest !== previous.metadataDigest) throw new Error('Installation changed during upgrade');\n renameSync(target.path, backup);\n moved = true;\n } else if (lstatSync(target.path, { throwIfNoEntry: false })) throw new Error('Skill appeared during installation; refusing overwrite');\n try { renameSync(replacement, target.path); }\n catch (error) { if (moved) { renameSync(backup, target.path); moved = false; } throw error; }\n if (moved) { readOwned(backup, host); rmSync(backup, { recursive: true }); moved = false; }\n return { ...result, changed: true };\n } finally {\n // Keep a displaced install intact if rollback or cleanup was interrupted.\n if (!moved) rmSync(staging, { recursive: true, force: true });\n }\n });\n}\n\nexport function installIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n return applyIntegration(host, options, 'install');\n}\n\nexport function updateIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n return applyIntegration(host, options, 'update');\n}\n\nexport function uninstallIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n const target = location(host, options);\n assertPath(target.path);\n const result = { ...details(target), installed: false, status: 'missing' as const };\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, changed: false };\n return withLock(target, () => {\n assertPath(target.path);\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, changed: false };\n readOwned(target.path, host);\n rmSync(target.path, { recursive: true });\n return { ...result, changed: true };\n });\n}\n"],"mappings":";;;;;;;AAOA,MAAM,QAAQ;AACd,MAAM,SAAS;AACf,MAAM,QAAQ;AACd,MAAM,mBAAmB,cAAc,IAAI,IAAI,oBAAoB,YAAY,GAAG,CAAC;AACnF,MAAM,cAAc,cAAc,IAAI,IAAI,mBAAmB,YAAY,GAAG,CAAC;AAE7E,MAAa,QAAQ,OAAO,OAAO;CACjC;EAAE,IAAI;EAAS,MAAM;EAAS,YAAY;EAAU,kBAAkB;EAAkB,iBAAiB;EAAoB,QAAQ;EAA8C,cAAc;CAA8G;CAC/S;EAAE,IAAI;EAAU,MAAM;EAAe,YAAY;EAAU,kBAAkB;EAAkB,iBAAiB;EAAwD,QAAQ;EAA0C,cAAc;CAA8I;CACtX;EAAE,IAAI;EAAU,MAAM;EAAU,YAAY;EAAU,kBAAkB;EAAkB,iBAAiB;EAAoB,QAAQ;EAAkC,cAAc;CAA4I;CACnU;EAAE,IAAI;EAAM,MAAM;EAAM,YAAY;EAAgB,kBAAkB;EAAc,iBAAiB;EAA4D,QAAQ;EAAsF,cAAc;CAAmM;CAChd;EAAE,IAAI;EAAU,MAAM;EAAc,YAAY;EAAuB,kBAAkB;EAAkB,iBAAiB;EAA8D,QAAQ;EAA0C,cAAc;CAA6J;CACvZ;EAAE,IAAI;EAAY,MAAM;EAAY,YAAY;EAAuB,kBAAkB;EAAoB,iBAAiB;EAAwE,QAAQ;EAAoC,cAAc;CAAkM;CAClc;EAAE,IAAI;EAAY,MAAM;EAAoB,YAAY;EAAuB,kBAAkB;EAA8D,iBAAiB;EAA4C,QAAQ;EAAsF,cAAc;CAAoO;CAC5iB;EAAE,IAAI;EAAU,MAAM;EAAgB,YAAY;EAAU,kBAAkB;EAAkB,iBAAiB;EAAkD,QAAQ;EAAyE,cAAc;CAA+U;AACnlB,CAAC,CAAC,KAAI,SAAQ,OAAO,OAAO,IAAI,CAAC,CAAC;AAalC,MAAM,UAAU,UAA2B,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AAC1F,MAAM,gBAAgB,SAAiB,gCAAgC,KAAK,IAAI,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC,OAAM,SAAQ,SAAS,OAAO,SAAS,QAAQ,KAAK,SAAS,CAAC,KAAK,SAAS;AACjL,MAAM,eAAe,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;AACtD,MAAM,iBAAiB,EAAE,aAAa;CACpC,QAAQ,EAAE,QAAQ,CAAC;CAAG,OAAO,EAAE,QAAQ,KAAK;CAAG,OAAO,EAAE,QAAQ,KAAK;CAAG,MAAM,EAAE,OAAO;CACvF,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,4CAA4C;CACtE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,YAAY,GAAG,YAAY;CAAG,gBAAgB;AAClF,CAAC;AAID,SAAS,WAAW,MAAoB;CACtC,MAAM,WAAW,QAAQ,IAAI;CAC7B,IAAI,UAAU,MAAM,QAAQ,CAAC,CAAC;CAC9B,MAAM,QAAQ,SAAS,MAAM,QAAQ,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;CACtE,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,UAAU,KAAK,SAAS,MAAM,EAAG;EACjC,MAAM,OAAO,UAAU,SAAS,EAAE,gBAAgB,MAAM,CAAC;EACzD,IAAI,CAAC,MAAM;EACX,IAAI,KAAK,eAAe,GAAG,MAAM,IAAI,MAAM,0BAA0B,QAAQ,wCAAwC;EACrH,IAAI,IAAI,MAAM,SAAS,KAAK,CAAC,KAAK,YAAY,GAAG,MAAM,IAAI,MAAM,oBAAoB,SAAS;CAChG;AACF;AAEA,SAAS,YAAY,MAAc,gBAAgB,OAAe;CAChE,WAAW,IAAI;CACf,MAAM,aAAa,SAAS,MAAM,UAAU,WAAW,UAAU,aAAa,UAAU,UAAU;CAClG,IAAI;EACF,MAAM,OAAO,UAAU,UAAU;EACjC,IAAI,CAAC,KAAK,OAAO,KAAM,CAAC,iBAAiB,KAAK,UAAU,GAAI,MAAM,IAAI,MAAM,0DAA0D,MAAM;EAC5I,OAAO,aAAa,UAAU;CAChC,UAAU;EAAE,UAAU,UAAU;CAAG;AACrC;AAEA,SAAS,SAAS,MAAc,gBAAgB,OAAa;CAC3D,WAAW,IAAI;CACf,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,YAAY,GAAG,MAAM,IAAI,MAAM,0BAA0B,MAAM;CACpF,MAAM,OAAa;EAAE,OAAO,OAAO,OAAO,IAAI;EAAG,aAAa,CAAC;CAAE;CACjE,SAAS,MAAM,WAAmB,QAAsB;EACtD,KAAK,MAAM,QAAQ,YAAY,SAAS,CAAC,CAAC,KAAK,GAAG;GAChD,MAAM,OAAO,KAAK,WAAW,IAAI;GACjC,MAAM,WAAW,SAAS,GAAG,OAAO,GAAG,SAAS;GAChD,MAAM,OAAO,UAAU,IAAI;GAC3B,IAAI,KAAK,eAAe,GAAG,MAAM,IAAI,MAAM,wCAAwC,MAAM;GACzF,IAAI,KAAK,YAAY,GAAG;IAAE,KAAK,YAAY,KAAK,QAAQ;IAAG,MAAM,MAAM,QAAQ;GAAG,OAC7E,KAAK,MAAM,YAAY,YAAY,MAAM,aAAa;EAC7D;CACF;CACA,MAAM,MAAM,EAAE;CACd,OAAO;AACT;AAEA,SAAS,YAAY,MAAoC;CACvD,OAAO,OAAO,YAAY,OAAO,QAAQ,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,UAAU,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC;AACxI;AAEA,SAAS,eAAe,OAAiD;CACvE,OAAO,OAAO,KAAK,UAAU;EAAC,MAAM;EAAQ,MAAM;EAAO,MAAM;EAAO,MAAM;EAAM,MAAM;EAAS,OAAO,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC;CAAC,CAAC,CAAC;AAC/K;AAEA,SAAS,eAAe,OAA0C;CAChE,MAAM,8BAAc,IAAI,IAAY;CACpC,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,GAAG;EACrC,IAAI,SAAS,KAAK,MAAM,GAAG,KAAK,YAAY,GAAG,CAAC;EAChD,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG;EACzB,OAAO,QAAQ;GAAE,YAAY,IAAI,MAAM;GAAG,SAAS,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,OAAO,YAAY,GAAG,CAAC,IAAI;EAAI;CAC3H;CACA,OAAO,CAAC,GAAG,WAAW,CAAC,CAAC,KAAK;AAC/B;AAEA,SAAS,UAAU,MAAc,MAAwB;CACvD,MAAM,OAAO,SAAS,IAAI;CAC1B,IAAI,CAAC,KAAK,MAAM,SAAS,MAAM,IAAI,MAAM,kCAAkC,OAAO,IAAI,MAAM;CAC5F,IAAI;CACJ,IAAI;EAAE,WAAW,eAAe,MAAM,KAAK,MAAM,KAAK,MAAM,OAAO,CAAC,SAAS,MAAM,CAAC,CAAC;CAAG,QAClF;EAAE,MAAM,IAAI,MAAM,+BAA+B,MAAM;CAAG;CAChE,IAAI,SAAS,SAAS,QAAQ,SAAS,mBAAmB,eAAe,QAAQ,GAAG,MAAM,IAAI,MAAM,yCAAyC,MAAM;CACnJ,IAAI,CAAC,SAAS,MAAM,eAAe,KAAK,UAAU,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,QAAO,SAAQ,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,UAAU,OAAO,KAAK,SAAS,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,0CAA0C,MAAM;CAC1O,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,SAAS,KAAK,GAC1D,IAAI,OAAO,KAAK,MAAM,KAAM,MAAM,UAAU,MAAM,IAAI,MAAM,2BAA2B,KAAK,MAAM,IAAI,GAAG;CAE3G,IAAI,KAAK,UAAU,KAAK,YAAY,KAAK,CAAC,MAAM,KAAK,UAAU,eAAe,SAAS,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,iCAAiC,MAAM;CACvJ,OAAO;AACT;AAEA,SAAS,aAAa,IAAkB;CACtC,MAAM,OAAO,MAAM,MAAK,UAAS,MAAM,OAAO,EAAE;CAChD,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,oBAAoB,KAAK,UAAU,EAAE,EAAE,qBAAqB,MAAM,KAAI,UAAS,MAAM,EAAE,CAAC,CAAC,KAAK,IAAI,GAAG;CAChI,OAAO;AACT;AAEA,SAAS,qBAAqB,MAAc,UAA0B;CACpE,MAAM,aAAa,QAAQ,IAAI;CAC/B,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,WAAW,eAAe,MAAM,QAAQ,IAAI,WAAW,WAAW,IAAI,IAAI,KAAK,QAAQ,GAAG,WAAW,MAAM,CAAC,CAAC,IAAI;CACvH,IAAI,CAAC,WAAW,QAAQ,GAAG,MAAM,IAAI,MAAM,GAAG,KAAK,gDAAgD;CACnG,OAAO,QAAQ,QAAQ;AACzB;AAEA,SAAS,SAAS,IAAY,SAAuC;CACnE,MAAM,OAAO,aAAa,EAAE;CAC5B,IAAI,QAAQ,UAAU,QAAQ,YAAY,KAAA,GAAW,MAAM,IAAI,MAAM,wDAAwD;CAC7H,IAAI,QAAQ,QAAQ;EAClB,IAAI;EACJ,QAAQ,IAAR;GACE,KAAK;IAAU,YAAY,KAAK,qBAAqB,qBAAqB,KAAK,QAAQ,GAAG,SAAS,CAAC,GAAG,QAAQ;IAAG;GAClH,KAAK;IAAU,YAAY,KAAK,qBAAqB,mBAAmB,QAAQ,CAAC,GAAG,WAAW,QAAQ;IAAG;GAC1G,KAAK;IAAM,YAAY,KAAK,qBAAqB,uBAAuB,KAAK,QAAQ,GAAG,OAAO,OAAO,CAAC,GAAG,QAAQ;IAAG;GACrH,KAAK;IAAY,YAAY,KAAK,qBAAqB,mBAAmB,KAAK,QAAQ,GAAG,SAAS,CAAC,GAAG,YAAY,QAAQ;IAAG;GAC9H,KAAK;IAAY,YAAY,KAAK,qBAAqB,YAAY,KAAK,QAAQ,GAAG,MAAM,CAAC,GAAG,QAAQ;IAAG;GACxG,KAAK;IAAU,YAAY,KAAK,qBAAqB,eAAe,KAAK,QAAQ,GAAG,SAAS,CAAC,GAAG,QAAQ;IAAG;GAC5G,SAAS,YAAY,KAAK,QAAQ,GAAG,KAAK,gBAAgB;EAC5D;EACA,OAAO;GAAE;GAAM,OAAO;GAAU,MAAM,KAAK,WAAW,KAAK;EAAE;CAC/D;CACA,IAAI,QAAQ,YAAY,KAAA,KAAa,CAAC,QAAQ,QAAQ,KAAK,GAAG,MAAM,IAAI,MAAM,qCAAqC;CACnH,IAAI,UAAU,QAAQ,QAAQ,WAAW,QAAQ,IAAI,CAAC;CACtD,WAAW,OAAO;CAClB,IAAI,CAAC,UAAU,SAAS,EAAE,gBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,IAAI,MAAM,qCAAqC,SAAS;CACjI,IAAI,OAAO,cAAc,OAAO,UAAU;EACxC,IAAI,QAAQ;EACZ,KAAK,IAAI,YAAY,UAAW,YAAY,QAAQ,SAAS,GAAG;GAC9D,IAAI,UAAU,KAAK,WAAW,MAAM,GAAG,EAAE,gBAAgB,MAAM,CAAC,GAAG;IAAE,UAAU;IAAW,QAAQ;IAAM;GAAO;GAC/G,IAAI,QAAQ,SAAS,MAAM,WAAW;EACxC;EACA,IAAI,OAAO,YAAY,CAAC,OAAO,MAAM,IAAI,MAAM,iGAAiG;CAClJ;CACA,OAAO;EAAE;EAAM,OAAO;EAAW,MAAM,KAAK,SAAS,OAAO,aAAa,gBAAgB,KAAK,kBAAkB,KAAK;CAAE;AACzH;AAEA,SAAS,QAAQ,QAAkB;CACjC,OAAO;EAAE,MAAM,OAAO,KAAK;EAAI,MAAM,OAAO,KAAK;EAAM,OAAO,OAAO;EAAO,MAAM,OAAO;EAAM,YAAY,OAAO,KAAK;EAAY,cAAc,OAAO,KAAK;EAAc,QAAQ,OAAO,KAAK;CAAO;AACxM;AAEA,SAAS,OAAO,MAAkD;CAEhE,MAAM,OAAO,SAAS,kBAAkB,IAAI;CAC5C,IAAI,CAAC,KAAK,MAAM,eAAe,KAAK,MAAM,SAAS,MAAM,IAAI,MAAM,6BAA6B;CAChG,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,YAAY,KAAK,KAAK,UAAU,KAAK,YAAY,KAAK,CAAC,MAAM,KAAK,UAAU,eAAe,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,sCAAsC;CACxL,MAAM,UAAU,KAAK,MAAM,YAAY,aAAa,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC;CAC5E,MAAM,QAA0C;EAAE,QAAQ;EAAG;EAAO;EAAO;EAAM;EAAS;CAAM;CAChG,MAAM,WAAW,eAAe,MAAM;EAAE,GAAG;EAAO,gBAAgB,eAAe,KAAK;CAAE,CAAC;CACzF,KAAK,MAAM,UAAU,OAAO,KAAK,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,GAAG;CACzE,OAAO;EAAE;EAAM;CAAS;AAC1B;AAEA,SAAgB,mBAAmB,MAAc,UAA8B,CAAC,GAA0B;CACxG,MAAM,SAAS,SAAS,MAAM,OAAO;CACrC,MAAM,SAAS,QAAQ,MAAM;CAC7B,IAAI;EACF,WAAW,OAAO,IAAI;EACtB,IAAI,CAAC,UAAU,OAAO,MAAM,EAAE,gBAAgB,MAAM,CAAC,GAAG,OAAO;GAAE,GAAG;GAAQ,WAAW;GAAO,QAAQ;EAAU;EAChH,MAAM,WAAW,UAAU,OAAO,MAAM,IAAI;EAC5C,MAAM,UAAU,OAAO,IAAI,CAAC,CAAC,SAAS,mBAAmB,SAAS;EAClE,OAAO;GAAE,GAAG;GAAQ,WAAW;GAAM,QAAQ;GAAa,SAAS,SAAS;GAAS;EAAQ;CAC/F,SAAS,OAAO;EACd,OAAO;GAAE,GAAG;GAAQ,WAAW;GAAO,QAAQ;GAAY,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAAE;CAC3H;AACF;AAEA,SAAS,SAAY,QAAkB,QAAoB;CACzD,MAAM,YAAY,QAAQ,QAAQ,OAAO,IAAI,CAAC;CAC9C,WAAW,SAAS;CACpB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;CACxC,WAAW,SAAS;CACpB,MAAM,OAAO,KAAK,WAAW,qBAAqB;CAClD,IAAI;CACJ,IAAI;EAAE,aAAa,SAAS,MAAM,UAAU,UAAU,UAAU,SAAS,UAAU,WAAW,UAAU,YAAY,GAAK;CAAG,QACtH;EAAE,MAAM,IAAI,MAAM,6BAA6B,KAAK,uEAAuE;CAAG;CACpI,IAAI;EAAE,OAAO,OAAO;CAAG,UACf;EAAE,UAAU,UAAU;EAAG,WAAW,IAAI;CAAG;AACrD;AAEA,SAAS,iBAAiB,MAAc,SAA6B,WAA+E;CAClJ,MAAM,SAAS,SAAS,MAAM,OAAO;CACrC,MAAM,OAAO,OAAO,IAAI;CACxB,WAAW,OAAO,IAAI;CACtB,IAAI,cAAc,YAAY,CAAC,UAAU,OAAO,MAAM,EAAE,gBAAgB,MAAM,CAAC,GAAG,MAAM,IAAI,MAAM,sBAAsB,OAAO,KAAK,sBAAsB,KAAK,2BAA2B;CAC1L,OAAO,SAAS,cAAc;EAC5B,WAAW,OAAO,IAAI;EACtB,MAAM,WAAW,UAAU,OAAO,MAAM,EAAE,gBAAgB,MAAM,CAAC,IAAI,UAAU,OAAO,MAAM,IAAI,IAAI,KAAA;EACpG,IAAI,cAAc,YAAY,CAAC,UAAU,MAAM,IAAI,MAAM,sBAAsB,OAAO,KAAK,sBAAsB,KAAK,2BAA2B;EACjJ,MAAM,SAAS;GAAE,GAAG,QAAQ,MAAM;GAAG,WAAW;GAAM,QAAQ;GAAsB,SAAS,KAAK,SAAS;GAAS,SAAS;EAAK;EAClI,IAAI,UAAU,mBAAmB,KAAK,SAAS,gBAAgB,OAAO;GAAE,GAAG;GAAQ,SAAS;EAAM;EAClG,IAAI,cAAc,aAAa,UAAU,MAAM,IAAI,MAAM,kDAAkD,OAAO,KAAK,qBAAqB,KAAK,qBAAqB;EACtK,MAAM,SAAS,QAAQ,OAAO,IAAI;EAClC,UAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;EACrC,MAAM,UAAU,YAAY,KAAK,QAAQ,MAAM,GAAG,eAAe,CAAC;EAClE,MAAM,cAAc,KAAK,SAAS,KAAK;EACvC,MAAM,SAAS,KAAK,SAAS,UAAU;EACvC,IAAI,QAAQ;EACZ,IAAI;GACF,UAAU,WAAW;GACrB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,KAAK,KAAK,GAAG;IAC3D,MAAM,OAAO,KAAK,aAAa,IAAI;IACnC,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;IAC5C,cAAc,MAAM,OAAO;KAAE,MAAM;KAAM,MAAM;IAAM,CAAC;GACxD;GACA,WAAW,OAAO,IAAI;GACtB,IAAI,UAAU;IACZ,IAAI,UAAU,OAAO,MAAM,IAAI,CAAC,CAAC,mBAAmB,SAAS,gBAAgB,MAAM,IAAI,MAAM,qCAAqC;IAClI,WAAW,OAAO,MAAM,MAAM;IAC9B,QAAQ;GACV,OAAO,IAAI,UAAU,OAAO,MAAM,EAAE,gBAAgB,MAAM,CAAC,GAAG,MAAM,IAAI,MAAM,wDAAwD;GACtI,IAAI;IAAE,WAAW,aAAa,OAAO,IAAI;GAAG,SACrC,OAAO;IAAE,IAAI,OAAO;KAAE,WAAW,QAAQ,OAAO,IAAI;KAAG,QAAQ;IAAO;IAAE,MAAM;GAAO;GAC5F,IAAI,OAAO;IAAE,UAAU,QAAQ,IAAI;IAAG,OAAO,QAAQ,EAAE,WAAW,KAAK,CAAC;IAAG,QAAQ;GAAO;GAC1F,OAAO;IAAE,GAAG;IAAQ,SAAS;GAAK;EACpC,UAAU;GAER,IAAI,CAAC,OAAO,OAAO,SAAS;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;EAC9D;CACF,CAAC;AACH;AAEA,SAAgB,mBAAmB,MAAc,UAA8B,CAAC,GAAiD;CAC/H,OAAO,iBAAiB,MAAM,SAAS,SAAS;AAClD;AAEA,SAAgB,kBAAkB,MAAc,UAA8B,CAAC,GAAiD;CAC9H,OAAO,iBAAiB,MAAM,SAAS,QAAQ;AACjD;AAEA,SAAgB,qBAAqB,MAAc,UAA8B,CAAC,GAAiD;CACjI,MAAM,SAAS,SAAS,MAAM,OAAO;CACrC,WAAW,OAAO,IAAI;CACtB,MAAM,SAAS;EAAE,GAAG,QAAQ,MAAM;EAAG,WAAW;EAAO,QAAQ;CAAmB;CAClF,IAAI,CAAC,UAAU,OAAO,MAAM,EAAE,gBAAgB,MAAM,CAAC,GAAG,OAAO;EAAE,GAAG;EAAQ,SAAS;CAAM;CAC3F,OAAO,SAAS,cAAc;EAC5B,WAAW,OAAO,IAAI;EACtB,IAAI,CAAC,UAAU,OAAO,MAAM,EAAE,gBAAgB,MAAM,CAAC,GAAG,OAAO;GAAE,GAAG;GAAQ,SAAS;EAAM;EAC3F,UAAU,OAAO,MAAM,IAAI;EAC3B,OAAO,OAAO,MAAM,EAAE,WAAW,KAAK,CAAC;EACvC,OAAO;GAAE,GAAG;GAAQ,SAAS;EAAK;CACpC,CAAC;AACH"}
|