apphx 2.0.1 → 2.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/README.md +10 -8
- package/dist/cli.js +41 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/integrations.js +154 -95
- package/dist/integrations.js.map +1 -1
- package/dist/notify.js +267 -0
- package/dist/notify.js.map +1 -0
- package/dist/runner.js +119 -20
- package/dist/runner.js.map +1 -1
- package/dist/schemas.js +57 -2
- package/dist/schemas.js.map +1 -1
- package/dist/vendor/obfuscator/dist/config.js +119 -6
- package/dist/vendor/obfuscator/dist/config.js.map +1 -1
- package/dist/vendor/obfuscator/dist/dfa.js +528 -0
- package/dist/vendor/obfuscator/dist/dfa.js.map +1 -0
- package/dist/vendor/obfuscator/dist/flutter.js +3266 -0
- package/dist/vendor/obfuscator/dist/flutter.js.map +1 -0
- package/dist/vendor/obfuscator/dist/image-utils.js +335 -0
- package/dist/vendor/obfuscator/dist/image-utils.js.map +1 -0
- package/dist/vendor/obfuscator/dist/junk.js +1501 -0
- package/dist/vendor/obfuscator/dist/junk.js.map +1 -0
- package/dist/vendor/obfuscator/dist/lexicon-data.js +972 -0
- package/dist/vendor/obfuscator/dist/lexicon-data.js.map +1 -0
- package/dist/vendor/obfuscator/dist/obfuscate.js +51 -2
- package/dist/vendor/obfuscator/dist/obfuscate.js.map +1 -1
- package/dist/vendor/obfuscator/dist/objc-symbols.js +1819 -0
- package/dist/vendor/obfuscator/dist/objc-symbols.js.map +1 -0
- package/dist/vendor/obfuscator/dist/project-identity.js +852 -0
- package/dist/vendor/obfuscator/dist/project-identity.js.map +1 -0
- package/dist/vendor/obfuscator/dist/string-encrypt.js +330 -0
- package/dist/vendor/obfuscator/dist/string-encrypt.js.map +1 -0
- package/examples/android-java.factory.json +40 -5
- package/examples/expo.factory.json +68 -5
- package/examples/flutter.factory.json +40 -4
- package/examples/luggage.json +1 -0
- package/examples/multi-role-agents.md +129 -0
- package/examples/mygo.factory.json +54 -4
- package/examples/queue-loop-demo.md +146 -0
- package/examples/react-native.factory.json +58 -5
- package/examples/roles.example.json +31 -0
- package/examples/swift.factory.json +60 -5
- package/examples/uni-app.factory.json +58 -5
- package/package.json +1 -1
- package/skills/apphx/SKILL.md +39 -4
- package/skills/apphx/resources/requirement.json +1 -0
- package/skills/apphx-obfuscator/LICENSE +21 -0
- package/skills/apphx-obfuscator/SKILL.md +72 -0
package/dist/integrations.js
CHANGED
|
@@ -5,10 +5,13 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
// src/integrations.ts
|
|
8
|
-
const
|
|
8
|
+
const skills = ["apphx", "apphx-obfuscator"];
|
|
9
9
|
const marker = ".apphx-install.json";
|
|
10
10
|
const owner = "apphx";
|
|
11
|
-
const
|
|
11
|
+
const bundledDirectories = {
|
|
12
|
+
apphx: fileURLToPath(new URL("../skills/apphx/", import.meta.url)),
|
|
13
|
+
"apphx-obfuscator": fileURLToPath(new URL("../skills/apphx-obfuscator/", import.meta.url))
|
|
14
|
+
};
|
|
12
15
|
const packageFile = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
13
16
|
const hosts = Object.freeze([
|
|
14
17
|
{
|
|
@@ -18,7 +21,9 @@ const hosts = Object.freeze([
|
|
|
18
21
|
projectDirectory: ".agents/skills",
|
|
19
22
|
globalDirectory: "~/.agents/skills",
|
|
20
23
|
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."
|
|
24
|
+
instructions: "Use $apphx in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed.",
|
|
25
|
+
delegation: "Native subagents/agents exist: run design and review roles as parallel or serial fresh contexts. Verify against your installed host version.",
|
|
26
|
+
obfuscatorInvocation: "$apphx-obfuscator"
|
|
22
27
|
},
|
|
23
28
|
{
|
|
24
29
|
id: "claude",
|
|
@@ -27,7 +32,9 @@ const hosts = Object.freeze([
|
|
|
27
32
|
projectDirectory: ".claude/skills",
|
|
28
33
|
globalDirectory: "$CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills)",
|
|
29
34
|
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."
|
|
35
|
+
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.",
|
|
36
|
+
delegation: "Native subagents exist: run design and review roles as parallel or serial fresh contexts. Verify against your installed host version.",
|
|
37
|
+
obfuscatorInvocation: "/apphx-obfuscator"
|
|
31
38
|
},
|
|
32
39
|
{
|
|
33
40
|
id: "cursor",
|
|
@@ -36,7 +43,9 @@ const hosts = Object.freeze([
|
|
|
36
43
|
projectDirectory: ".cursor/skills",
|
|
37
44
|
globalDirectory: "~/.cursor/skills",
|
|
38
45
|
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."
|
|
46
|
+
instructions: "In Agent chat, type / and select apphx. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync.",
|
|
47
|
+
delegation: "Multi-agent support varies by version: fall back to separate fresh sessions per role when subagents are unavailable.",
|
|
48
|
+
obfuscatorInvocation: "/apphx-obfuscator"
|
|
40
49
|
},
|
|
41
50
|
{
|
|
42
51
|
id: "pi",
|
|
@@ -45,7 +54,9 @@ const hosts = Object.freeze([
|
|
|
45
54
|
projectDirectory: ".pi/skills",
|
|
46
55
|
globalDirectory: "$PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills)",
|
|
47
56
|
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."
|
|
57
|
+
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.",
|
|
58
|
+
delegation: "Stock Pi has no native subagents: run each role in its own human-started fresh session.",
|
|
59
|
+
obfuscatorInvocation: "/skill:apphx-obfuscator"
|
|
49
60
|
},
|
|
50
61
|
{
|
|
51
62
|
id: "gemini",
|
|
@@ -54,7 +65,9 @@ const hosts = Object.freeze([
|
|
|
54
65
|
projectDirectory: ".gemini/skills",
|
|
55
66
|
globalDirectory: "$GEMINI_CLI_HOME/.gemini/skills (default ~/.gemini/skills)",
|
|
56
67
|
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."
|
|
68
|
+
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.",
|
|
69
|
+
delegation: "No native subagent delegation documented: run roles sequentially in fresh sessions.",
|
|
70
|
+
obfuscatorInvocation: "Use the apphx-obfuscator skill"
|
|
58
71
|
},
|
|
59
72
|
{
|
|
60
73
|
id: "opencode",
|
|
@@ -63,7 +76,9 @@ const hosts = Object.freeze([
|
|
|
63
76
|
projectDirectory: ".opencode/skills",
|
|
64
77
|
globalDirectory: "$XDG_CONFIG_HOME/opencode/skills (default ~/.config/opencode/skills)",
|
|
65
78
|
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."
|
|
79
|
+
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.",
|
|
80
|
+
delegation: "Subagent support varies by version: fall back to separate fresh sessions per role when subagents are unavailable.",
|
|
81
|
+
obfuscatorInvocation: "Use the apphx-obfuscator skill"
|
|
67
82
|
},
|
|
68
83
|
{
|
|
69
84
|
id: "deepseek",
|
|
@@ -72,7 +87,9 @@ const hosts = Object.freeze([
|
|
|
72
87
|
projectDirectory: ".dsh/skills (nearest Git root, otherwise selected project)",
|
|
73
88
|
globalDirectory: "$DSH_HOME/skills (default ~/.dsh/skills)",
|
|
74
89
|
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."
|
|
90
|
+
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.",
|
|
91
|
+
delegation: "The harness supports delegated agents; verify your provider exposes them, else use fresh sessions per role.",
|
|
92
|
+
obfuscatorInvocation: "Use the apphx-obfuscator skill"
|
|
76
93
|
},
|
|
77
94
|
{
|
|
78
95
|
id: "hermes",
|
|
@@ -81,7 +98,9 @@ const hosts = Object.freeze([
|
|
|
81
98
|
projectDirectory: ".hermes/skills",
|
|
82
99
|
globalDirectory: "$HERMES_HOME/skills (default ~/.hermes/skills)",
|
|
83
100
|
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."
|
|
101
|
+
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.",
|
|
102
|
+
delegation: "Native subagents and cron exist: run design and review roles as parallel or serial fresh contexts; the scheduler handles unattended wakes. Verify against your installed host version.",
|
|
103
|
+
obfuscatorInvocation: "/apphx-obfuscator"
|
|
85
104
|
},
|
|
86
105
|
{
|
|
87
106
|
id: "qoder",
|
|
@@ -90,16 +109,20 @@ const hosts = Object.freeze([
|
|
|
90
109
|
projectDirectory: ".qoder/skills",
|
|
91
110
|
globalDirectory: "~/.qoder/skills",
|
|
92
111
|
source: "https://docs.qoder.com/cli/Skills",
|
|
93
|
-
instructions: "Use /apphx manually or let the model auto-trigger the skill. Run /skills reload in a running session to refresh discovery. User-level skills override project-level skills with the same name."
|
|
112
|
+
instructions: "Use /apphx manually or let the model auto-trigger the skill. Run /skills reload in a running session to refresh discovery. User-level skills override project-level skills with the same name.",
|
|
113
|
+
delegation: "No native subagent delegation documented: run roles sequentially in fresh sessions.",
|
|
114
|
+
obfuscatorInvocation: "/apphx-obfuscator"
|
|
94
115
|
},
|
|
95
116
|
{
|
|
96
117
|
id: "oh-my-pi",
|
|
97
118
|
name: "Oh My Pi",
|
|
98
119
|
invocation: "/skill:apphx",
|
|
99
120
|
projectDirectory: ".omp/skills",
|
|
100
|
-
globalDirectory: "~/.omp/agent/skills",
|
|
121
|
+
globalDirectory: "~/.omp/profiles/<name>/agent/skills when OMP_PROFILE/PI_PROFILE selects a profile, else ~/.omp/agent/skills",
|
|
101
122
|
source: "https://github.com/can1357/oh-my-pi/blob/main/docs/skills.md",
|
|
102
|
-
instructions: "Use /skill:apphx. Skills are discovered one level under skills/ with SKILL.md; description frontmatter is required by the native .omp provider. Restart the session if discovery has not refreshed."
|
|
123
|
+
instructions: "Use /skill:apphx. Skills are discovered one level under skills/ with SKILL.md; description frontmatter is required by the native .omp provider. Restart the session if discovery has not refreshed.",
|
|
124
|
+
delegation: "IDE-wired agent with extension tools; run roles in separate fresh sessions when subagents are unavailable.",
|
|
125
|
+
obfuscatorInvocation: "/skill:apphx-obfuscator"
|
|
103
126
|
},
|
|
104
127
|
{
|
|
105
128
|
id: "dimagent",
|
|
@@ -108,7 +131,9 @@ const hosts = Object.freeze([
|
|
|
108
131
|
projectDirectory: ".dimcode/skills",
|
|
109
132
|
globalDirectory: "~/.dimcode/v2/skills",
|
|
110
133
|
source: "https://github.com/daguanren21/apple-app-factory",
|
|
111
|
-
instructions: "Use /skill:apphx or describe the requirement to trigger the apphx skill. Restart the session after installation so the skill is discovered."
|
|
134
|
+
instructions: "Use /skill:apphx or describe the requirement to trigger the apphx skill. Restart the session after installation so the skill is discovered.",
|
|
135
|
+
delegation: "Native subagent delegation exists: run design and review roles as parallel or serial fresh contexts. Verify against your installed host version.",
|
|
136
|
+
obfuscatorInvocation: "/skill:apphx-obfuscator"
|
|
112
137
|
}
|
|
113
138
|
].map((host) => Object.freeze(host)));
|
|
114
139
|
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
@@ -117,7 +142,7 @@ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
|
117
142
|
const manifestSchema = z.strictObject({
|
|
118
143
|
format: z.literal(1),
|
|
119
144
|
owner: z.literal(owner),
|
|
120
|
-
skill: z.
|
|
145
|
+
skill: z.enum(["apphx", "apphx-obfuscator"]),
|
|
121
146
|
host: z.string(),
|
|
122
147
|
version: z.string().regex(/^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/),
|
|
123
148
|
files: z.record(z.string().refine(safeRelative), digestSchema),
|
|
@@ -220,7 +245,7 @@ function environmentDirectory(name, fallback) {
|
|
|
220
245
|
if (!isAbsolute(expanded)) throw new Error(`${name} must be an absolute directory for installation`);
|
|
221
246
|
return resolve(expanded);
|
|
222
247
|
}
|
|
223
|
-
function location(id, options) {
|
|
248
|
+
function location(id, options, name = "apphx") {
|
|
224
249
|
const host = selectedHost(id);
|
|
225
250
|
if (options.global && options.project !== void 0) throw new Error("Choose either project or global installation, not both");
|
|
226
251
|
if (options.global) {
|
|
@@ -235,6 +260,24 @@ function location(id, options) {
|
|
|
235
260
|
case "pi":
|
|
236
261
|
directory = join(environmentDirectory("PI_CODING_AGENT_DIR", join(homedir(), ".pi", "agent")), "skills");
|
|
237
262
|
break;
|
|
263
|
+
case "oh-my-pi": {
|
|
264
|
+
const omp = process.env.OMP_PROFILE?.trim();
|
|
265
|
+
const pi = process.env.PI_PROFILE?.trim();
|
|
266
|
+
const selected = new Set([omp, pi].filter((value) => Boolean(value)));
|
|
267
|
+
if (selected.size > 1) throw new Error(`OMP_PROFILE (${JSON.stringify(omp)}) and PI_PROFILE (${JSON.stringify(pi)}) select different profiles; unset one before a global install`);
|
|
268
|
+
const profile = [...selected][0];
|
|
269
|
+
if (profile) {
|
|
270
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(profile)) throw new Error(`Oh My Pi profile ${JSON.stringify(profile)} is not a usable profile name; use only letters, digits, '.', '_' or '-', or unset OMP_PROFILE/PI_PROFILE`);
|
|
271
|
+
directory = join(homedir(), ".omp", "profiles", profile, "agent", "skills");
|
|
272
|
+
} else directory = join(homedir(), ".omp", "agent", "skills");
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case "qoder":
|
|
276
|
+
directory = join(homedir(), ".qoder", "skills");
|
|
277
|
+
break;
|
|
278
|
+
case "dimagent":
|
|
279
|
+
directory = join(homedir(), ".dimcode", "v2", "skills");
|
|
280
|
+
break;
|
|
238
281
|
case "opencode":
|
|
239
282
|
directory = join(environmentDirectory("XDG_CONFIG_HOME", join(homedir(), ".config")), "opencode", "skills");
|
|
240
283
|
break;
|
|
@@ -249,7 +292,8 @@ function location(id, options) {
|
|
|
249
292
|
return {
|
|
250
293
|
host,
|
|
251
294
|
scope: "global",
|
|
252
|
-
|
|
295
|
+
skill: name,
|
|
296
|
+
path: join(directory, name)
|
|
253
297
|
};
|
|
254
298
|
}
|
|
255
299
|
if (options.project !== void 0 && !options.project.trim()) throw new Error("Project directory must not be empty");
|
|
@@ -271,30 +315,32 @@ function location(id, options) {
|
|
|
271
315
|
return {
|
|
272
316
|
host,
|
|
273
317
|
scope: "project",
|
|
274
|
-
|
|
318
|
+
skill: name,
|
|
319
|
+
path: join(project, id === "deepseek" ? ".dsh/skills" : host.projectDirectory, name)
|
|
275
320
|
};
|
|
276
321
|
}
|
|
277
322
|
function details(target) {
|
|
278
323
|
return {
|
|
279
324
|
host: target.host.id,
|
|
280
325
|
name: target.host.name,
|
|
326
|
+
skill: target.skill,
|
|
281
327
|
scope: target.scope,
|
|
282
328
|
path: target.path,
|
|
283
|
-
invocation: target.host.invocation,
|
|
329
|
+
invocation: target.skill === "apphx" ? target.host.invocation : target.host.obfuscatorInvocation,
|
|
284
330
|
instructions: target.host.instructions,
|
|
285
331
|
source: target.host.source
|
|
286
332
|
};
|
|
287
333
|
}
|
|
288
|
-
function bundle(host) {
|
|
289
|
-
const tree = readTree(
|
|
290
|
-
if (!tree.files["SKILL.md"] || tree.files[marker]) throw new Error(
|
|
334
|
+
function bundle(host, name) {
|
|
335
|
+
const tree = readTree(bundledDirectories[name], true);
|
|
336
|
+
if (!tree.files["SKILL.md"] || tree.files[marker]) throw new Error(`Invalid bundled ${name} skill`);
|
|
291
337
|
const files = fileDigests(tree);
|
|
292
338
|
if (!Object.keys(files).every(safeRelative) || JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(files))) throw new Error("Invalid bundled skill resource paths");
|
|
293
339
|
const version = JSON.parse(readRegular(packageFile, true).toString("utf8")).version;
|
|
294
340
|
const value = {
|
|
295
341
|
format: 1,
|
|
296
342
|
owner,
|
|
297
|
-
skill,
|
|
343
|
+
skill: name,
|
|
298
344
|
host,
|
|
299
345
|
version,
|
|
300
346
|
files
|
|
@@ -309,8 +355,8 @@ function bundle(host) {
|
|
|
309
355
|
manifest
|
|
310
356
|
};
|
|
311
357
|
}
|
|
312
|
-
function inspectIntegration(host, options = {}) {
|
|
313
|
-
const target = location(host, options);
|
|
358
|
+
function inspectIntegration(host, options = {}, name = "apphx") {
|
|
359
|
+
const target = location(host, options, name);
|
|
314
360
|
const result = details(target);
|
|
315
361
|
try {
|
|
316
362
|
assertPath(target.path);
|
|
@@ -320,7 +366,7 @@ function inspectIntegration(host, options = {}) {
|
|
|
320
366
|
status: "missing"
|
|
321
367
|
};
|
|
322
368
|
const manifest = readOwned(target.path, host);
|
|
323
|
-
const current = bundle(host).manifest.metadataDigest === manifest.metadataDigest;
|
|
369
|
+
const current = bundle(host, name).manifest.metadataDigest === manifest.metadataDigest;
|
|
324
370
|
return {
|
|
325
371
|
...result,
|
|
326
372
|
installed: true,
|
|
@@ -356,73 +402,83 @@ function withLock(target, action) {
|
|
|
356
402
|
unlinkSync(lock);
|
|
357
403
|
}
|
|
358
404
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const next = bundle(host);
|
|
405
|
+
/** Stage and swap one owned skill directory; `previous` is the validated installed manifest or undefined. */
|
|
406
|
+
function replaceSkill(target, host, next, previous) {
|
|
362
407
|
assertPath(target.path);
|
|
363
|
-
|
|
364
|
-
|
|
408
|
+
const parent = dirname(target.path);
|
|
409
|
+
mkdirSync(parent, { recursive: true });
|
|
410
|
+
const staging = mkdtempSync(join(dirname(parent), ".apphx-stage-"));
|
|
411
|
+
const replacement = join(staging, target.skill);
|
|
412
|
+
const backup = join(staging, "previous");
|
|
413
|
+
let moved = false;
|
|
414
|
+
try {
|
|
415
|
+
mkdirSync(replacement);
|
|
416
|
+
for (const [file, bytes] of Object.entries(next.tree.files)) {
|
|
417
|
+
const path = join(replacement, file);
|
|
418
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
419
|
+
writeFileSync(path, bytes, {
|
|
420
|
+
flag: "wx",
|
|
421
|
+
mode: 420
|
|
422
|
+
});
|
|
423
|
+
}
|
|
365
424
|
assertPath(target.path);
|
|
366
|
-
|
|
367
|
-
|
|
425
|
+
if (previous) {
|
|
426
|
+
if (readOwned(target.path, host).metadataDigest !== previous.metadataDigest) throw new Error("Installation changed during upgrade");
|
|
427
|
+
renameSync(target.path, backup);
|
|
428
|
+
moved = true;
|
|
429
|
+
} else if (lstatSync(target.path, { throwIfNoEntry: false })) throw new Error("Skill appeared during installation; refusing overwrite");
|
|
430
|
+
try {
|
|
431
|
+
renameSync(replacement, target.path);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (moved) {
|
|
434
|
+
renameSync(backup, target.path);
|
|
435
|
+
moved = false;
|
|
436
|
+
}
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
if (moved) {
|
|
440
|
+
readOwned(backup, host);
|
|
441
|
+
rmSync(backup, { recursive: true });
|
|
442
|
+
moved = false;
|
|
443
|
+
}
|
|
444
|
+
} finally {
|
|
445
|
+
if (!moved) rmSync(staging, {
|
|
446
|
+
recursive: true,
|
|
447
|
+
force: true
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function applyIntegration(host, options, operation) {
|
|
452
|
+
const targets = skills.map((name) => location(host, options, name));
|
|
453
|
+
const bundles = Object.fromEntries(skills.map((name) => [name, bundle(host, name)]));
|
|
454
|
+
const primary = targets[0];
|
|
455
|
+
for (const target of targets) assertPath(target.path);
|
|
456
|
+
if (operation === "update" && !lstatSync(primary.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${primary.path}; run apphx install ${host} first with the same scope`);
|
|
457
|
+
return withLock(primary, () => {
|
|
458
|
+
for (const target of targets) assertPath(target.path);
|
|
459
|
+
const previous = targets.map((target) => lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : void 0);
|
|
460
|
+
if (operation === "update" && !previous[0]) throw new Error(`No installation at ${primary.path}; run apphx install ${host} first with the same scope`);
|
|
368
461
|
const result = {
|
|
369
|
-
...details(
|
|
462
|
+
...details(primary),
|
|
370
463
|
installed: true,
|
|
371
464
|
status: "installed",
|
|
372
|
-
version:
|
|
465
|
+
version: bundles.apphx.manifest.version,
|
|
373
466
|
current: true
|
|
374
467
|
};
|
|
375
|
-
if (previous?.metadataDigest
|
|
468
|
+
if (!targets.some((target, index) => previous[index]?.metadataDigest !== bundles[target.skill].manifest.metadataDigest)) return {
|
|
376
469
|
...result,
|
|
377
470
|
changed: false
|
|
378
471
|
};
|
|
379
|
-
if (operation === "install" && previous) throw new Error(`An older or different workflow is installed at ${
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const backup = join(staging, "previous");
|
|
385
|
-
let moved = false;
|
|
386
|
-
try {
|
|
387
|
-
mkdirSync(replacement);
|
|
388
|
-
for (const [file, bytes] of Object.entries(next.tree.files)) {
|
|
389
|
-
const path = join(replacement, file);
|
|
390
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
391
|
-
writeFileSync(path, bytes, {
|
|
392
|
-
flag: "wx",
|
|
393
|
-
mode: 420
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
assertPath(target.path);
|
|
397
|
-
if (previous) {
|
|
398
|
-
if (readOwned(target.path, host).metadataDigest !== previous.metadataDigest) throw new Error("Installation changed during upgrade");
|
|
399
|
-
renameSync(target.path, backup);
|
|
400
|
-
moved = true;
|
|
401
|
-
} else if (lstatSync(target.path, { throwIfNoEntry: false })) throw new Error("Skill appeared during installation; refusing overwrite");
|
|
402
|
-
try {
|
|
403
|
-
renameSync(replacement, target.path);
|
|
404
|
-
} catch (error) {
|
|
405
|
-
if (moved) {
|
|
406
|
-
renameSync(backup, target.path);
|
|
407
|
-
moved = false;
|
|
408
|
-
}
|
|
409
|
-
throw error;
|
|
410
|
-
}
|
|
411
|
-
if (moved) {
|
|
412
|
-
readOwned(backup, host);
|
|
413
|
-
rmSync(backup, { recursive: true });
|
|
414
|
-
moved = false;
|
|
415
|
-
}
|
|
416
|
-
return {
|
|
417
|
-
...result,
|
|
418
|
-
changed: true
|
|
419
|
-
};
|
|
420
|
-
} finally {
|
|
421
|
-
if (!moved) rmSync(staging, {
|
|
422
|
-
recursive: true,
|
|
423
|
-
force: true
|
|
424
|
-
});
|
|
472
|
+
if (operation === "install" && previous[0]) throw new Error(`An older or different workflow is installed at ${primary.path}; use apphx update ${host} with the same scope`);
|
|
473
|
+
for (let index = 0; index < targets.length; index++) {
|
|
474
|
+
const target = targets[index];
|
|
475
|
+
if (previous[index]?.metadataDigest === bundles[target.skill].manifest.metadataDigest) continue;
|
|
476
|
+
replaceSkill(target, host, bundles[target.skill], previous[index]);
|
|
425
477
|
}
|
|
478
|
+
return {
|
|
479
|
+
...result,
|
|
480
|
+
changed: true
|
|
481
|
+
};
|
|
426
482
|
});
|
|
427
483
|
}
|
|
428
484
|
function installIntegration(host, options = {}) {
|
|
@@ -432,28 +488,31 @@ function updateIntegration(host, options = {}) {
|
|
|
432
488
|
return applyIntegration(host, options, "update");
|
|
433
489
|
}
|
|
434
490
|
function uninstallIntegration(host, options = {}) {
|
|
435
|
-
const
|
|
436
|
-
|
|
491
|
+
const targets = skills.map((name) => location(host, options, name));
|
|
492
|
+
const primary = targets[0];
|
|
493
|
+
for (const target of targets) assertPath(target.path);
|
|
437
494
|
const result = {
|
|
438
|
-
...details(
|
|
495
|
+
...details(primary),
|
|
439
496
|
installed: false,
|
|
440
497
|
status: "missing"
|
|
441
498
|
};
|
|
442
|
-
if (!lstatSync(target.path, { throwIfNoEntry: false })) return {
|
|
499
|
+
if (!targets.some((target) => lstatSync(target.path, { throwIfNoEntry: false }))) return {
|
|
443
500
|
...result,
|
|
444
501
|
changed: false
|
|
445
502
|
};
|
|
446
|
-
return withLock(
|
|
447
|
-
assertPath(target.path);
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
503
|
+
return withLock(primary, () => {
|
|
504
|
+
for (const target of targets) assertPath(target.path);
|
|
505
|
+
for (const target of targets) if (lstatSync(target.path, { throwIfNoEntry: false })) readOwned(target.path, host);
|
|
506
|
+
let changed = false;
|
|
507
|
+
for (const target of targets) {
|
|
508
|
+
if (!lstatSync(target.path, { throwIfNoEntry: false })) continue;
|
|
509
|
+
readOwned(target.path, host);
|
|
510
|
+
rmSync(target.path, { recursive: true });
|
|
511
|
+
changed = true;
|
|
512
|
+
}
|
|
454
513
|
return {
|
|
455
514
|
...result,
|
|
456
|
-
changed
|
|
515
|
+
changed
|
|
457
516
|
};
|
|
458
517
|
});
|
|
459
518
|
}
|
package/dist/integrations.js.map
CHANGED
|
@@ -1 +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 { id: 'qoder', name: 'Qoder', invocation: '/apphx', projectDirectory: '.qoder/skills', globalDirectory: '~/.qoder/skills', source: 'https://docs.qoder.com/cli/Skills', instructions: 'Use /apphx manually or let the model auto-trigger the skill. Run /skills reload in a running session to refresh discovery. User-level skills override project-level skills with the same name.' },\n { id: 'oh-my-pi', name: 'Oh My Pi', invocation: '/skill:apphx', projectDirectory: '.omp/skills', globalDirectory: '~/.omp/agent/skills', source: 'https://github.com/can1357/oh-my-pi/blob/main/docs/skills.md', instructions: 'Use /skill:apphx. Skills are discovered one level under skills/ with SKILL.md; description frontmatter is required by the native .omp provider. Restart the session if discovery has not refreshed.' },\n { id: 'dimagent', name: 'DimAgent', invocation: '/skill:apphx', projectDirectory: '.dimcode/skills', globalDirectory: '~/.dimcode/v2/skills', source: 'https://github.com/daguanren21/apple-app-factory', instructions: 'Use /skill:apphx or describe the requirement to trigger the apphx skill. Restart the session after installation so the skill is discovered.' },\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":"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"}
|
|
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 skills = ['apphx', 'apphx-obfuscator'] as const;\ntype SkillName = (typeof skills)[number];\nconst marker = '.apphx-install.json';\nconst owner = 'apphx';\nconst bundledDirectories: Record<SkillName, string> = {\n apphx: fileURLToPath(new URL('../skills/apphx/', import.meta.url)),\n 'apphx-obfuscator': fileURLToPath(new URL('../skills/apphx-obfuscator/', import.meta.url)),\n};\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.' , delegation: 'Native subagents/agents exist: run design and review roles as parallel or serial fresh contexts. Verify against your installed host version.' , obfuscatorInvocation: '$apphx-obfuscator' },\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.' , delegation: 'Native subagents exist: run design and review roles as parallel or serial fresh contexts. Verify against your installed host version.' , obfuscatorInvocation: '/apphx-obfuscator' },\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.' , delegation: 'Multi-agent support varies by version: fall back to separate fresh sessions per role when subagents are unavailable.' , obfuscatorInvocation: '/apphx-obfuscator' },\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.' , delegation: 'Stock Pi has no native subagents: run each role in its own human-started fresh session.' , obfuscatorInvocation: '/skill:apphx-obfuscator' },\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.' , delegation: 'No native subagent delegation documented: run roles sequentially in fresh sessions.' , obfuscatorInvocation: 'Use the apphx-obfuscator skill' },\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.' , delegation: 'Subagent support varies by version: fall back to separate fresh sessions per role when subagents are unavailable.' , obfuscatorInvocation: 'Use the apphx-obfuscator skill' },\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.' , delegation: 'The harness supports delegated agents; verify your provider exposes them, else use fresh sessions per role.' , obfuscatorInvocation: 'Use the apphx-obfuscator skill' },\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.' , delegation: 'Native subagents and cron exist: run design and review roles as parallel or serial fresh contexts; the scheduler handles unattended wakes. Verify against your installed host version.' , obfuscatorInvocation: '/apphx-obfuscator' },\n { id: 'qoder', name: 'Qoder', invocation: '/apphx', projectDirectory: '.qoder/skills', globalDirectory: '~/.qoder/skills', source: 'https://docs.qoder.com/cli/Skills', instructions: 'Use /apphx manually or let the model auto-trigger the skill. Run /skills reload in a running session to refresh discovery. User-level skills override project-level skills with the same name.' , delegation: 'No native subagent delegation documented: run roles sequentially in fresh sessions.' , obfuscatorInvocation: '/apphx-obfuscator' },\n { id: 'oh-my-pi', name: 'Oh My Pi', invocation: '/skill:apphx', projectDirectory: '.omp/skills', globalDirectory: '~/.omp/profiles/<name>/agent/skills when OMP_PROFILE/PI_PROFILE selects a profile, else ~/.omp/agent/skills', source: 'https://github.com/can1357/oh-my-pi/blob/main/docs/skills.md', instructions: 'Use /skill:apphx. Skills are discovered one level under skills/ with SKILL.md; description frontmatter is required by the native .omp provider. Restart the session if discovery has not refreshed.' , delegation: 'IDE-wired agent with extension tools; run roles in separate fresh sessions when subagents are unavailable.' , obfuscatorInvocation: '/skill:apphx-obfuscator' },\n { id: 'dimagent', name: 'DimAgent', invocation: '/skill:apphx', projectDirectory: '.dimcode/skills', globalDirectory: '~/.dimcode/v2/skills', source: 'https://github.com/daguanren21/apple-app-factory', instructions: 'Use /skill:apphx or describe the requirement to trigger the apphx skill. Restart the session after installation so the skill is discovered.' , delegation: 'Native subagent delegation exists: run design and review roles as parallel or serial fresh contexts. Verify against your installed host version.' , obfuscatorInvocation: '/skill:apphx-obfuscator' },\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; skill: SkillName };\nexport type IntegrationInspection = {\n host: string; name: string; skill: SkillName; 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.enum(['apphx', 'apphx-obfuscator']), 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, name: SkillName = 'apphx'): 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 'oh-my-pi': {\n // Oh My Pi resolves its global skill root from the selected profile and\n // its agent directory; install where the host reads or reject loudly.\n const omp = process.env.OMP_PROFILE?.trim();\n const pi = process.env.PI_PROFILE?.trim();\n const selected = new Set([omp, pi].filter((value): value is string => Boolean(value)));\n if (selected.size > 1) throw new Error(`OMP_PROFILE (${JSON.stringify(omp)}) and PI_PROFILE (${JSON.stringify(pi)}) select different profiles; unset one before a global install`);\n const profile = [...selected][0];\n if (profile) {\n if (!/^[a-zA-Z0-9._-]+$/.test(profile)) throw new Error(`Oh My Pi profile ${JSON.stringify(profile)} is not a usable profile name; use only letters, digits, '.', '_' or '-', or unset OMP_PROFILE/PI_PROFILE`);\n directory = join(homedir(), '.omp', 'profiles', profile, 'agent', 'skills');\n } else {\n directory = join(homedir(), '.omp', 'agent', 'skills');\n }\n break;\n }\n case 'qoder': directory = join(homedir(), '.qoder', 'skills'); break;\n case 'dimagent': directory = join(homedir(), '.dimcode', 'v2', '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', skill: name, path: join(directory, name) };\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', skill: name, path: join(project, id === 'deepseek' ? '.dsh/skills' : host.projectDirectory, name) };\n}\n\nfunction details(target: Location) {\n return { host: target.host.id, name: target.host.name, skill: target.skill, scope: target.scope, path: target.path, invocation: target.skill === 'apphx' ? target.host.invocation : target.host.obfuscatorInvocation, instructions: target.host.instructions, source: target.host.source };\n}\n\nfunction bundle(host: string, name: SkillName): { tree: Tree; manifest: Manifest } {\n // Package stores may hard-link read-only resources; owned installation targets may not.\n const tree = readTree(bundledDirectories[name], true);\n if (!tree.files['SKILL.md'] || tree.files[marker]) throw new Error(`Invalid bundled ${name} 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: name, 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 = {}, name: SkillName = 'apphx'): IntegrationInspection {\n const target = location(host, options, name);\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, name).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\n/** Stage and swap one owned skill directory; `previous` is the validated installed manifest or undefined. */\nfunction replaceSkill(target: Location, host: string, next: { tree: Tree; manifest: Manifest }, previous: Manifest | undefined): void {\n assertPath(target.path);\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, target.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 } 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\nfunction applyIntegration(host: string, options: IntegrationOptions, operation: 'install' | 'update'): IntegrationInspection & { changed: boolean } {\n // Both bundled skills install together so every host gains the /$ apphx workflow and the\n // /$ apphx-obfuscator trigger. The apphx skill is the primary result; its absence gates update.\n const targets = skills.map(name => location(host, options, name));\n const bundles = Object.fromEntries(skills.map(name => [name, bundle(host, name)])) as Record<SkillName, { tree: Tree; manifest: Manifest }>;\n const primary = targets[0]!;\n for (const target of targets) assertPath(target.path);\n if (operation === 'update' && !lstatSync(primary.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${primary.path}; run apphx install ${host} first with the same scope`);\n return withLock(primary, () => {\n for (const target of targets) assertPath(target.path);\n // Validate every existing owned dir up front so foreign content is never adopted or displaced.\n const previous = targets.map(target => lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : undefined);\n if (operation === 'update' && !previous[0]) throw new Error(`No installation at ${primary.path}; run apphx install ${host} first with the same scope`);\n const result = { ...details(primary), installed: true, status: 'installed' as const, version: bundles.apphx.manifest.version, current: true };\n const changedAny = targets.some((target, index) => previous[index]?.metadataDigest !== bundles[target.skill].manifest.metadataDigest);\n if (!changedAny) return { ...result, changed: false };\n if (operation === 'install' && previous[0]) throw new Error(`An older or different workflow is installed at ${primary.path}; use apphx update ${host} with the same scope`);\n for (let index = 0; index < targets.length; index++) {\n const target = targets[index]!;\n if (previous[index]?.metadataDigest === bundles[target.skill].manifest.metadataDigest) continue;\n replaceSkill(target, host, bundles[target.skill], previous[index]);\n }\n return { ...result, changed: true };\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 targets = skills.map(name => location(host, options, name));\n const primary = targets[0]!;\n for (const target of targets) assertPath(target.path);\n const result = { ...details(primary), installed: false, status: 'missing' as const };\n if (!targets.some(target => lstatSync(target.path, { throwIfNoEntry: false }))) return { ...result, changed: false };\n return withLock(primary, () => {\n for (const target of targets) assertPath(target.path);\n // Validate ownership before deleting anything so a foreign sibling never breaks uninstall halfway.\n for (const target of targets) {\n if (lstatSync(target.path, { throwIfNoEntry: false })) readOwned(target.path, host);\n }\n let changed = false;\n for (const target of targets) {\n if (!lstatSync(target.path, { throwIfNoEntry: false })) continue;\n readOwned(target.path, host);\n rmSync(target.path, { recursive: true });\n changed = true;\n }\n return { ...result, changed };\n });\n}\n"],"mappings":"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"}
|