@useorgx/wizard 0.1.25 → 0.1.26
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/dist/cli.js +2332 -2282
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2125,6 +2125,21 @@ async function checkWorkspaceConnectivity(options = {}) {
|
|
|
2125
2125
|
}
|
|
2126
2126
|
}
|
|
2127
2127
|
|
|
2128
|
+
// src/lib/plugins.ts
|
|
2129
|
+
import { spawn } from "child_process";
|
|
2130
|
+
import {
|
|
2131
|
+
existsSync as existsSync4,
|
|
2132
|
+
mkdirSync as mkdirSync2,
|
|
2133
|
+
mkdtempSync,
|
|
2134
|
+
readFileSync as readFileSync2,
|
|
2135
|
+
readdirSync as readdirSync2,
|
|
2136
|
+
rmSync,
|
|
2137
|
+
statSync as statSync2,
|
|
2138
|
+
writeFileSync as writeFileSync2
|
|
2139
|
+
} from "fs";
|
|
2140
|
+
import { tmpdir } from "os";
|
|
2141
|
+
import { dirname as dirname3, join as join3, relative } from "path";
|
|
2142
|
+
|
|
2128
2143
|
// src/surfaces/mcp-config.ts
|
|
2129
2144
|
import * as TOML from "@iarna/toml";
|
|
2130
2145
|
function cloneConfigCollection(config, key) {
|
|
@@ -2254,10 +2269,16 @@ function inspectCursorMcpConfig(input) {
|
|
|
2254
2269
|
const local = readObject(servers[ORGX_LOCAL_MCP_KEY]);
|
|
2255
2270
|
const localConfigured = typeof local.url === "string";
|
|
2256
2271
|
const localUrl = localConfigured ? String(local.url) : "missing";
|
|
2272
|
+
const hostedConfigured = matchHttpEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL);
|
|
2257
2273
|
return {
|
|
2274
|
+
// Back-compat: "configured" on Cursor historically meant the OpenClaw bridge line only.
|
|
2258
2275
|
configured: localConfigured,
|
|
2276
|
+
hostedConfigured,
|
|
2259
2277
|
localConfigured,
|
|
2260
|
-
details: [
|
|
2278
|
+
details: [
|
|
2279
|
+
`hosted orgx: ${hostedConfigured ? "ok" : "missing"}`,
|
|
2280
|
+
`local orgx-openclaw: ${localUrl}`
|
|
2281
|
+
]
|
|
2261
2282
|
};
|
|
2262
2283
|
}
|
|
2263
2284
|
function patchVscodeMcpConfig(input) {
|
|
@@ -2527,2571 +2548,2600 @@ function detectSurface(name, exists = existsSync2) {
|
|
|
2527
2548
|
return detection;
|
|
2528
2549
|
}
|
|
2529
2550
|
|
|
2530
|
-
// src/
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2551
|
+
// src/lib/skills.ts
|
|
2552
|
+
import { createHash as createHash2 } from "crypto";
|
|
2553
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
2554
|
+
import { basename, join as join2 } from "path";
|
|
2555
|
+
var DEFAULT_ORGX_SKILL_PACKS = [
|
|
2556
|
+
"morning-briefing",
|
|
2557
|
+
"initiative-kickoff",
|
|
2558
|
+
"bulk-create",
|
|
2559
|
+
"nightly-recap"
|
|
2560
|
+
];
|
|
2561
|
+
var PLUGIN_MANAGED_SKILL_TARGETS = ["claude", "codex"];
|
|
2562
|
+
var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
|
|
2563
|
+
var ORGX_SKILLS_OWNER = "useorgx";
|
|
2564
|
+
var ORGX_SKILLS_REPO = "skills";
|
|
2565
|
+
var ORGX_SKILLS_REF = "main";
|
|
2566
|
+
var SKILL_EXTENSION_SCOPES = ["user", "workspace", "project"];
|
|
2567
|
+
var COMPOSED_SKILL_MARKER = "ORGX SKILL COMPOSED v1";
|
|
2568
|
+
var EXTENSION_FRONTMATTER_DELIMITER = "---";
|
|
2569
|
+
var CURSOR_RULES_CONTENT = `# OrgX Rules
|
|
2570
|
+
|
|
2571
|
+
- Prefer \`workspace_id\` on OrgX tool calls. \`command_center_id\` is a deprecated alias and should only be used for backwards compatibility. If both are present, they must match.
|
|
2572
|
+
- Treat \`entity_action\` with \`force: true\` as an escape hatch. It bypasses hierarchy and proof checks, so only use it when the user explicitly wants that behavior and say why.
|
|
2573
|
+
- Treat \`scaffold_initiative continue_on_error\` as best-effort creation. Use it only when partial scaffolds are acceptable, and follow up on any failed children.
|
|
2574
|
+
- Preserve \`_context\` when widget or app-rendering flows depend on client, conversation, session, or user metadata. Do not strip or rename it.
|
|
2575
|
+
- Prefer \`list_entities\` for pending approvals and state inspection before reaching for legacy aliases.
|
|
2576
|
+
- Keep hosted OrgX MCP calls distinct from local OpenClaw/plugin surfaces before mutating config or entity state.
|
|
2577
|
+
- Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
|
|
2578
|
+
- Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supplemental, not proof.
|
|
2579
|
+
`;
|
|
2580
|
+
var CLAUDE_ORGX_SKILL_CONTENT = `---
|
|
2581
|
+
name: orgx
|
|
2582
|
+
description: Use when working against the hosted OrgX MCP server for workspace-scoped planning, entity mutations, progress reporting, and decision handling.
|
|
2583
|
+
version: 1.0.0
|
|
2584
|
+
user-invocable: true
|
|
2585
|
+
tags:
|
|
2586
|
+
- orchestration
|
|
2587
|
+
- productivity
|
|
2588
|
+
- workspace
|
|
2589
|
+
---
|
|
2590
|
+
|
|
2591
|
+
# OrgX Hosted MCP
|
|
2592
|
+
|
|
2593
|
+
Use this skill when Claude is connected to the hosted OrgX MCP server at \`https://mcp.useorgx.com/mcp\`.
|
|
2594
|
+
|
|
2595
|
+
This skill is for the hosted OrgX tool surface, not the local OpenClaw plugin. Keep those surfaces distinct before mutating config or entity state.
|
|
2596
|
+
|
|
2597
|
+
## Operating Rules
|
|
2598
|
+
|
|
2599
|
+
- Prefer \`workspace_id\` on OrgX tools. \`command_center_id\` is a deprecated alias kept for compatibility.
|
|
2600
|
+
- Treat \`entity_action\` with \`force: true\` as a deliberate override only. It bypasses hierarchy and proof-chain checks.
|
|
2601
|
+
- Treat \`scaffold_initiative continue_on_error\` as best-effort scaffolding. Use it only when partial creation is acceptable and inspect failures afterward.
|
|
2602
|
+
- Preserve \`_context\` when widget or app rendering depends on client, conversation, session, or user metadata.
|
|
2603
|
+
- Prefer \`list_entities\` over older aliases when you need pending decisions, blockers, or entity state.
|
|
2604
|
+
- Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
|
|
2605
|
+
- Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supporting context only.
|
|
2606
|
+
|
|
2607
|
+
## Suggested Skill Packs
|
|
2608
|
+
|
|
2609
|
+
Install and use these packs alongside this base skill:
|
|
2610
|
+
|
|
2611
|
+
- \`morning-briefing\`
|
|
2612
|
+
- \`initiative-kickoff\`
|
|
2613
|
+
- \`bulk-create\`
|
|
2614
|
+
- \`nightly-recap\`
|
|
2615
|
+
|
|
2616
|
+
## Suggested Workflow
|
|
2617
|
+
|
|
2618
|
+
1. Inspect state with \`mcp__orgx__list_entities\`, \`mcp__orgx__get_initiative_pulse\`, or the relevant workspace-scoped read tool.
|
|
2619
|
+
2. Mutate state with the narrowest tool that matches the task.
|
|
2620
|
+
3. When you need a hard override, explain why before using \`force: true\`.
|
|
2621
|
+
4. When you scaffold, decide up front whether \`continue_on_error\` is acceptable.
|
|
2622
|
+
5. Carry \`_context\` through any widget-producing flows so the UI can render and resume correctly.
|
|
2623
|
+
`;
|
|
2624
|
+
function sha256(value) {
|
|
2625
|
+
return createHash2("sha256").update(value).digest("hex");
|
|
2542
2626
|
}
|
|
2543
|
-
function
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
detected: detection.detected,
|
|
2550
|
-
configured: inspection.configured
|
|
2551
|
-
};
|
|
2552
|
-
if (path) {
|
|
2553
|
-
result.path = path;
|
|
2554
|
-
}
|
|
2555
|
-
if (detection.detected) {
|
|
2556
|
-
result.url = buildLocalMcpUrl(raw);
|
|
2627
|
+
function normalizeSkillId(value) {
|
|
2628
|
+
const normalized = value.trim().toLowerCase();
|
|
2629
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
|
|
2630
|
+
throw new Error(
|
|
2631
|
+
"Skill id must start with a letter or number and only contain letters, numbers, '.', '_' or '-'."
|
|
2632
|
+
);
|
|
2557
2633
|
}
|
|
2558
|
-
return
|
|
2634
|
+
return normalized;
|
|
2559
2635
|
}
|
|
2560
|
-
function
|
|
2561
|
-
const
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
if (name === "claude") {
|
|
2565
|
-
const inspection2 = inspectClaudeMcpConfig(path ? readTextIfExists(path) : null);
|
|
2566
|
-
const configured = inspection2.hostedConfigured === true && (openclaw.detected ? inspection2.localConfigured === true : true);
|
|
2567
|
-
return {
|
|
2568
|
-
name,
|
|
2569
|
-
mode: "automated",
|
|
2570
|
-
detected: detection.detected,
|
|
2571
|
-
configured,
|
|
2572
|
-
...path ? { path } : {},
|
|
2573
|
-
details: [
|
|
2574
|
-
...inspection2.details,
|
|
2575
|
-
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; hosted OrgX is the only available route."],
|
|
2576
|
-
...detection.evidence
|
|
2577
|
-
],
|
|
2578
|
-
summary: configured ? openclaw.detected ? "OrgX is connected in Claude with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Claude." : detection.detected ? openclaw.detected ? `Claude is missing either hosted OrgX or the local OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Claude is missing the hosted OrgX MCP entry." : "Claude config directory was not detected on disk."
|
|
2579
|
-
};
|
|
2636
|
+
function normalizeSkillExtensionScope(value) {
|
|
2637
|
+
const normalized = (value ?? "user").trim().toLowerCase();
|
|
2638
|
+
if (!SKILL_EXTENSION_SCOPES.includes(normalized)) {
|
|
2639
|
+
throw new Error("Skill extension scope must be one of: user, workspace, project.");
|
|
2580
2640
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2641
|
+
return normalized;
|
|
2642
|
+
}
|
|
2643
|
+
function defaultExtensionTitle(skillId, scope) {
|
|
2644
|
+
const prefix = scope === "user" ? "Personal" : scope === "workspace" ? "Workspace" : "Project";
|
|
2645
|
+
return `${prefix} ${skillId} behavior`;
|
|
2646
|
+
}
|
|
2647
|
+
function extensionFilePath(skillId, scope, extensionsDir = ORGX_SKILL_EXTENSIONS_DIR) {
|
|
2648
|
+
return join2(extensionsDir, `${scope}.${skillId}.md`);
|
|
2649
|
+
}
|
|
2650
|
+
function extensionTemplate(input) {
|
|
2651
|
+
const body = input.content?.trim() ? input.content.trim() : [
|
|
2652
|
+
`# ${input.title}`,
|
|
2653
|
+
"",
|
|
2654
|
+
"- Add your custom behavior here.",
|
|
2655
|
+
"- These instructions are appended after the OrgX-managed core skill when the wizard syncs skills."
|
|
2656
|
+
].join("\n");
|
|
2657
|
+
return [
|
|
2658
|
+
EXTENSION_FRONTMATTER_DELIMITER,
|
|
2659
|
+
`skill: ${input.skillId}`,
|
|
2660
|
+
`scope: ${input.scope}`,
|
|
2661
|
+
"enabled: true",
|
|
2662
|
+
`title: ${input.title}`,
|
|
2663
|
+
EXTENSION_FRONTMATTER_DELIMITER,
|
|
2664
|
+
"",
|
|
2665
|
+
body,
|
|
2666
|
+
""
|
|
2667
|
+
].join("\n");
|
|
2668
|
+
}
|
|
2669
|
+
function parseFrontmatter(content) {
|
|
2670
|
+
if (!content.startsWith(`${EXTENSION_FRONTMATTER_DELIMITER}
|
|
2671
|
+
`)) {
|
|
2672
|
+
return { body: content, data: {} };
|
|
2597
2673
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
mode: "automated",
|
|
2604
|
-
detected: detection.detected,
|
|
2605
|
-
configured,
|
|
2606
|
-
...path ? { path } : {},
|
|
2607
|
-
details: [
|
|
2608
|
-
...inspection2.details,
|
|
2609
|
-
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; hosted OrgX is the only available route."],
|
|
2610
|
-
...detection.evidence
|
|
2611
|
-
],
|
|
2612
|
-
summary: configured ? openclaw.detected ? "OrgX is connected in Codex with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Codex." : detection.detected ? openclaw.detected ? `Codex is missing either hosted OrgX or the local OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Codex is missing the hosted OrgX MCP entry." : "Codex config directory was not detected on disk."
|
|
2613
|
-
};
|
|
2674
|
+
const closeIndex = content.indexOf(`
|
|
2675
|
+
${EXTENSION_FRONTMATTER_DELIMITER}
|
|
2676
|
+
`, 4);
|
|
2677
|
+
if (closeIndex === -1) {
|
|
2678
|
+
return { body: content, data: {} };
|
|
2614
2679
|
}
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2680
|
+
const raw = content.slice(4, closeIndex);
|
|
2681
|
+
const body = content.slice(closeIndex + 5);
|
|
2682
|
+
const data = {};
|
|
2683
|
+
for (const line of raw.split("\n")) {
|
|
2684
|
+
const index = line.indexOf(":");
|
|
2685
|
+
if (index === -1) {
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
const key = line.slice(0, index).trim();
|
|
2689
|
+
const value = line.slice(index + 1).trim();
|
|
2690
|
+
if (key) {
|
|
2691
|
+
data[key] = value;
|
|
2692
|
+
}
|
|
2626
2693
|
}
|
|
2627
|
-
|
|
2628
|
-
const inspection2 = inspectWindsurfMcpConfig(path ? readTextIfExists(path) : null);
|
|
2629
|
-
return {
|
|
2630
|
-
name,
|
|
2631
|
-
mode: "automated",
|
|
2632
|
-
detected: detection.detected,
|
|
2633
|
-
configured: inspection2.hostedConfigured === true,
|
|
2634
|
-
...path ? { path } : {},
|
|
2635
|
-
details: [...inspection2.details, ...detection.evidence],
|
|
2636
|
-
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in Windsurf." : detection.detected ? "Windsurf is missing the hosted OrgX MCP entry." : "Windsurf config directory was not detected on disk."
|
|
2637
|
-
};
|
|
2638
|
-
}
|
|
2639
|
-
if (name === "zed") {
|
|
2640
|
-
const inspection2 = inspectZedMcpConfig(path ? readTextIfExists(path) : null);
|
|
2641
|
-
return {
|
|
2642
|
-
name,
|
|
2643
|
-
mode: "automated",
|
|
2644
|
-
detected: detection.detected,
|
|
2645
|
-
configured: inspection2.hostedConfigured === true,
|
|
2646
|
-
...path ? { path } : {},
|
|
2647
|
-
details: [...inspection2.details, ...detection.evidence],
|
|
2648
|
-
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in Zed." : detection.detected ? "Zed is missing the hosted OrgX MCP entry." : "Zed config directory was not detected on disk."
|
|
2649
|
-
};
|
|
2650
|
-
}
|
|
2651
|
-
const inspection = inspectOpenClawConfig(path ? readTextIfExists(path) : null);
|
|
2652
|
-
return {
|
|
2653
|
-
name,
|
|
2654
|
-
mode: "automated",
|
|
2655
|
-
detected: detection.detected,
|
|
2656
|
-
configured: inspection.configured,
|
|
2657
|
-
...path ? { path } : {},
|
|
2658
|
-
details: [...inspection.details, ...detection.evidence],
|
|
2659
|
-
summary: inspection.configured ? `OpenClaw plugin entry ${inspection.pluginKey ?? "missing"} is configured.` : inspection.installed ? "OpenClaw is installed but OrgX defaults have not been written." : detection.detected ? "OpenClaw support files exist but no OrgX plugin entry was found." : "OpenClaw is not installed or no support files were found."
|
|
2660
|
-
};
|
|
2694
|
+
return { body, data };
|
|
2661
2695
|
}
|
|
2662
|
-
function
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2696
|
+
function serializeFrontmatter(data, body) {
|
|
2697
|
+
return [
|
|
2698
|
+
EXTENSION_FRONTMATTER_DELIMITER,
|
|
2699
|
+
...Object.entries(data).map(([key, value]) => `${key}: ${value}`),
|
|
2700
|
+
EXTENSION_FRONTMATTER_DELIMITER,
|
|
2701
|
+
"",
|
|
2702
|
+
body.trimEnd(),
|
|
2703
|
+
""
|
|
2704
|
+
].join("\n");
|
|
2671
2705
|
}
|
|
2672
|
-
function
|
|
2673
|
-
const
|
|
2674
|
-
const
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
details.push(
|
|
2684
|
-
"ChatGPT completes OAuth during connector setup; the wizard cannot preflight hosted MCP tools with an oxk_ API key."
|
|
2685
|
-
);
|
|
2686
|
-
details.push(
|
|
2687
|
-
"After connecting ChatGPT, run `orgx-wizard doctor` to verify hosted MCP health from this machine."
|
|
2688
|
-
);
|
|
2689
|
-
} else {
|
|
2690
|
-
details.push(
|
|
2691
|
-
"OrgX auth is not configured yet. Run `orgx-wizard auth login` or `orgx-wizard auth set-key` first."
|
|
2692
|
-
);
|
|
2693
|
-
details.push(
|
|
2694
|
-
"After auth is configured, add a custom ChatGPT developer connector pointing at the hosted OrgX MCP URL."
|
|
2695
|
-
);
|
|
2696
|
-
}
|
|
2697
|
-
let summary;
|
|
2698
|
-
if (!authHint) {
|
|
2699
|
-
summary = detection.detected ? "ChatGPT is installed, but OrgX auth is not configured yet." : "ChatGPT support files were not found; configure OrgX auth first, then add the hosted connector manually.";
|
|
2700
|
-
} else {
|
|
2701
|
-
summary = detection.detected ? "ChatGPT is installed; finish the manual developer connector setup." : "OrgX auth is ready, but ChatGPT desktop support files were not found on disk.";
|
|
2702
|
-
}
|
|
2706
|
+
function parseSkillExtension(path, content) {
|
|
2707
|
+
const fallbackId = basename(path, ".md");
|
|
2708
|
+
const fallbackParts = fallbackId.split(".");
|
|
2709
|
+
const fallbackScope = normalizeSkillExtensionScope(
|
|
2710
|
+
SKILL_EXTENSION_SCOPES.includes(fallbackParts[0]) ? fallbackParts.shift() : "user"
|
|
2711
|
+
);
|
|
2712
|
+
const fallbackSkillId = normalizeSkillId(fallbackParts.join(".") || fallbackId);
|
|
2713
|
+
const parsed = parseFrontmatter(content);
|
|
2714
|
+
const skillId = normalizeSkillId(parsed.data.skill || fallbackSkillId);
|
|
2715
|
+
const scope = normalizeSkillExtensionScope(parsed.data.scope || fallbackScope);
|
|
2716
|
+
const title = parsed.data.title?.trim() || defaultExtensionTitle(skillId, scope);
|
|
2703
2717
|
return {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2718
|
+
content: parsed.body.trim(),
|
|
2719
|
+
enabled: parsed.data.enabled?.trim().toLowerCase() !== "false",
|
|
2720
|
+
id: `${scope}.${skillId}`,
|
|
2721
|
+
path,
|
|
2722
|
+
scope,
|
|
2723
|
+
skillId,
|
|
2724
|
+
title
|
|
2711
2725
|
};
|
|
2712
2726
|
}
|
|
2713
|
-
function
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
changed: false,
|
|
2718
|
-
...path ? { path } : {},
|
|
2719
|
-
message: `Run \`${AUTH_SETUP_HINT}\` or \`${AUTH_SET_KEY_HINT}\` first, then add ${ORGX_HOSTED_MCP_URL} as a custom connector in ChatGPT developer mode.`
|
|
2720
|
-
};
|
|
2727
|
+
function listSkillExtensions(options = {}) {
|
|
2728
|
+
const extensionsDir = options.extensionsDir ?? ORGX_SKILL_EXTENSIONS_DIR;
|
|
2729
|
+
if (!existsSync3(extensionsDir)) {
|
|
2730
|
+
return [];
|
|
2721
2731
|
}
|
|
2722
|
-
|
|
2723
|
-
const
|
|
2732
|
+
return readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => {
|
|
2733
|
+
const path = join2(extensionsDir, entry.name);
|
|
2734
|
+
const content = readTextIfExists(path);
|
|
2735
|
+
return content === null ? null : parseSkillExtension(path, content);
|
|
2736
|
+
}).filter((entry) => Boolean(entry)).sort((left, right) => left.id.localeCompare(right.id));
|
|
2737
|
+
}
|
|
2738
|
+
function addSkillExtension(options) {
|
|
2739
|
+
const skillId = normalizeSkillId(options.skillId);
|
|
2740
|
+
const scope = normalizeSkillExtensionScope(options.scope);
|
|
2741
|
+
const path = extensionFilePath(skillId, scope, options.extensionsDir);
|
|
2742
|
+
const title = options.title?.trim() || defaultExtensionTitle(skillId, scope);
|
|
2743
|
+
const existing = readTextIfExists(path);
|
|
2744
|
+
if (existing !== null && options.overwrite !== true) {
|
|
2745
|
+
const extension2 = parseSkillExtension(path, existing);
|
|
2746
|
+
if (!extension2) {
|
|
2747
|
+
throw new Error(`Could not parse existing skill extension at ${path}.`);
|
|
2748
|
+
}
|
|
2724
2749
|
return {
|
|
2725
|
-
name: "chatgpt",
|
|
2726
2750
|
changed: false,
|
|
2727
|
-
|
|
2728
|
-
|
|
2751
|
+
created: false,
|
|
2752
|
+
extension: extension2,
|
|
2753
|
+
path
|
|
2729
2754
|
};
|
|
2730
2755
|
}
|
|
2756
|
+
const nextContent = extensionTemplate({
|
|
2757
|
+
scope,
|
|
2758
|
+
skillId,
|
|
2759
|
+
title,
|
|
2760
|
+
...options.content !== void 0 ? { content: options.content } : {}
|
|
2761
|
+
});
|
|
2762
|
+
writeTextFile(path, nextContent);
|
|
2763
|
+
const extension = parseSkillExtension(path, nextContent);
|
|
2764
|
+
if (!extension) {
|
|
2765
|
+
throw new Error(`Could not parse newly written skill extension at ${path}.`);
|
|
2766
|
+
}
|
|
2731
2767
|
return {
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2768
|
+
changed: existing !== nextContent,
|
|
2769
|
+
created: existing === null,
|
|
2770
|
+
extension,
|
|
2771
|
+
path
|
|
2736
2772
|
};
|
|
2737
2773
|
}
|
|
2738
|
-
function
|
|
2774
|
+
function setSkillExtensionEnabled(input) {
|
|
2775
|
+
const skillId = normalizeSkillId(input.skillId);
|
|
2776
|
+
const scope = normalizeSkillExtensionScope(input.scope);
|
|
2777
|
+
const path = extensionFilePath(skillId, scope, input.extensionsDir);
|
|
2778
|
+
const existing = readTextIfExists(path);
|
|
2779
|
+
if (existing === null) {
|
|
2780
|
+
if (!input.enabled) {
|
|
2781
|
+
throw new Error(`No ${scope} extension exists for ${skillId}.`);
|
|
2782
|
+
}
|
|
2783
|
+
return addSkillExtension({
|
|
2784
|
+
scope,
|
|
2785
|
+
skillId,
|
|
2786
|
+
...input.extensionsDir !== void 0 ? { extensionsDir: input.extensionsDir } : {}
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
const parsed = parseFrontmatter(existing);
|
|
2790
|
+
const data = {
|
|
2791
|
+
skill: parsed.data.skill || skillId,
|
|
2792
|
+
scope: parsed.data.scope || scope,
|
|
2793
|
+
enabled: input.enabled ? "true" : "false",
|
|
2794
|
+
title: parsed.data.title || defaultExtensionTitle(skillId, scope)
|
|
2795
|
+
};
|
|
2796
|
+
const nextContent = serializeFrontmatter(data, parsed.body);
|
|
2797
|
+
if (nextContent !== existing) {
|
|
2798
|
+
writeTextFile(path, nextContent);
|
|
2799
|
+
}
|
|
2800
|
+
const extension = parseSkillExtension(path, nextContent);
|
|
2801
|
+
if (!extension) {
|
|
2802
|
+
throw new Error(`Could not parse skill extension at ${path}.`);
|
|
2803
|
+
}
|
|
2739
2804
|
return {
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2805
|
+
changed: nextContent !== existing,
|
|
2806
|
+
created: false,
|
|
2807
|
+
extension,
|
|
2808
|
+
path
|
|
2744
2809
|
};
|
|
2745
2810
|
}
|
|
2746
|
-
function
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
return buildChatgptManualStatus(detection, peekResolvedOrgxAuth());
|
|
2811
|
+
function composeSkillContent(skillId, coreContent, extensions) {
|
|
2812
|
+
const enabled = extensions.filter((extension) => extension.enabled && extension.skillId === skillId);
|
|
2813
|
+
if (enabled.length === 0) {
|
|
2814
|
+
return coreContent;
|
|
2751
2815
|
}
|
|
2816
|
+
const extensionBlocks = enabled.map((extension) => [
|
|
2817
|
+
`<!-- extension: ${extension.id} -->`,
|
|
2818
|
+
extension.content.trim()
|
|
2819
|
+
].join("\n"));
|
|
2820
|
+
return [
|
|
2821
|
+
`<!-- ${COMPOSED_SKILL_MARKER}`,
|
|
2822
|
+
`skill: ${skillId}`,
|
|
2823
|
+
`core-sha256: ${sha256(coreContent)}`,
|
|
2824
|
+
"generated: true",
|
|
2825
|
+
`edit-with: orgx-wizard skills extensions edit ${skillId}`,
|
|
2826
|
+
"-->",
|
|
2827
|
+
`<!-- ORGX CORE BEGIN ${skillId} -->`,
|
|
2828
|
+
coreContent.trimEnd(),
|
|
2829
|
+
`<!-- ORGX CORE END ${skillId} -->`,
|
|
2830
|
+
"",
|
|
2831
|
+
"<!-- ORGX USER EXTENSIONS BEGIN -->",
|
|
2832
|
+
...extensionBlocks,
|
|
2833
|
+
"<!-- ORGX USER EXTENSIONS END -->",
|
|
2834
|
+
""
|
|
2835
|
+
].join("\n");
|
|
2752
2836
|
}
|
|
2753
|
-
function
|
|
2754
|
-
|
|
2755
|
-
return automatedSurfaceStatus(
|
|
2756
|
-
name
|
|
2757
|
-
);
|
|
2758
|
-
}
|
|
2759
|
-
return manualSurfaceStatus(name);
|
|
2837
|
+
function encodeRepoPath(value) {
|
|
2838
|
+
return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
|
|
2760
2839
|
}
|
|
2761
|
-
function
|
|
2762
|
-
|
|
2840
|
+
function buildContentsUrl(path, ref) {
|
|
2841
|
+
const encodedPath = encodeRepoPath(path);
|
|
2842
|
+
return `https://api.github.com/repos/${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`;
|
|
2763
2843
|
}
|
|
2764
|
-
function
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
return [...allTargets];
|
|
2844
|
+
function resolveGitHubError(status, path) {
|
|
2845
|
+
if (status === 404) {
|
|
2846
|
+
return `OrgX skill pack '${path}' was not found in ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`;
|
|
2768
2847
|
}
|
|
2769
|
-
return
|
|
2770
|
-
}
|
|
2771
|
-
function resolveSurfaceSelection(selection) {
|
|
2772
|
-
return resolveTargets(selection, SURFACE_NAMES);
|
|
2848
|
+
return `GitHub returned HTTP ${status} while loading '${path}' from ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`;
|
|
2773
2849
|
}
|
|
2774
|
-
function
|
|
2775
|
-
return
|
|
2850
|
+
async function readResponseText(response) {
|
|
2851
|
+
return response.text();
|
|
2776
2852
|
}
|
|
2777
|
-
async function
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2853
|
+
async function fetchDirectoryEntries(path, fetchImpl, ref) {
|
|
2854
|
+
const response = await fetchImpl(buildContentsUrl(path, ref), {
|
|
2855
|
+
headers: {
|
|
2856
|
+
Accept: "application/vnd.github+json"
|
|
2857
|
+
},
|
|
2858
|
+
signal: AbortSignal.timeout(1e4)
|
|
2859
|
+
});
|
|
2860
|
+
if (!response.ok) {
|
|
2861
|
+
throw new Error(resolveGitHubError(response.status, path));
|
|
2786
2862
|
}
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
changed: false,
|
|
2804
|
-
path,
|
|
2805
|
-
message: "Cursor needs OpenClaw installed before OrgX can be connected locally."
|
|
2806
|
-
};
|
|
2807
|
-
}
|
|
2808
|
-
const previous = readTextIfExists(path);
|
|
2809
|
-
const next = patchCursorMcpConfig(previous, openclaw.url);
|
|
2810
|
-
writeTextFile(path, next);
|
|
2811
|
-
return {
|
|
2812
|
-
name,
|
|
2813
|
-
changed: previous !== next,
|
|
2814
|
-
path,
|
|
2815
|
-
message: "OrgX is connected in Cursor through the local OpenClaw bridge."
|
|
2816
|
-
};
|
|
2817
|
-
}
|
|
2818
|
-
case "codex": {
|
|
2819
|
-
const previous = readTextIfExists(path);
|
|
2820
|
-
const next = patchCodexConfigToml(previous, openclaw.url);
|
|
2821
|
-
writeTextFile(path, next);
|
|
2822
|
-
return {
|
|
2823
|
-
name,
|
|
2824
|
-
changed: previous !== next,
|
|
2825
|
-
path,
|
|
2826
|
-
message: openclaw.detected ? "OrgX is connected in Codex with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Codex."
|
|
2827
|
-
};
|
|
2828
|
-
}
|
|
2829
|
-
case "openclaw": {
|
|
2830
|
-
const previous = readTextIfExists(path);
|
|
2831
|
-
const auth = await resolveOrgxAuth();
|
|
2832
|
-
const next = patchOpenClawConfig(previous, auth ?? {});
|
|
2833
|
-
writeTextFile(path, next);
|
|
2834
|
-
return {
|
|
2835
|
-
name,
|
|
2836
|
-
changed: previous !== next,
|
|
2837
|
-
path,
|
|
2838
|
-
message: auth ? "OpenClaw is configured as the local OrgX bridge with the resolved OrgX auth." : "OpenClaw is configured as the local OrgX bridge."
|
|
2839
|
-
};
|
|
2840
|
-
}
|
|
2841
|
-
case "vscode": {
|
|
2842
|
-
const previous = readTextIfExists(path);
|
|
2843
|
-
if (inspectVscodeMcpConfig(previous).hostedConfigured) {
|
|
2844
|
-
return { name, changed: false, path, message: "OrgX cloud MCP is connected in VS Code." };
|
|
2845
|
-
}
|
|
2846
|
-
const next = patchVscodeMcpConfig(previous);
|
|
2847
|
-
writeTextFile(path, next);
|
|
2848
|
-
return { name, changed: true, path, message: "OrgX cloud MCP is connected in VS Code." };
|
|
2849
|
-
}
|
|
2850
|
-
case "windsurf": {
|
|
2851
|
-
const previous = readTextIfExists(path);
|
|
2852
|
-
if (inspectWindsurfMcpConfig(previous).hostedConfigured) {
|
|
2853
|
-
return { name, changed: false, path, message: "OrgX cloud MCP is connected in Windsurf." };
|
|
2854
|
-
}
|
|
2855
|
-
const next = patchWindsurfMcpConfig(previous);
|
|
2856
|
-
writeTextFile(path, next);
|
|
2857
|
-
return { name, changed: true, path, message: "OrgX cloud MCP is connected in Windsurf." };
|
|
2858
|
-
}
|
|
2859
|
-
case "zed": {
|
|
2860
|
-
const previous = readTextIfExists(path);
|
|
2861
|
-
if (inspectZedMcpConfig(previous).hostedConfigured) {
|
|
2862
|
-
return { name, changed: false, path, message: "OrgX cloud MCP is connected in Zed." };
|
|
2863
|
-
}
|
|
2864
|
-
const next = patchZedMcpConfig(previous);
|
|
2865
|
-
writeTextFile(path, next);
|
|
2866
|
-
return { name, changed: true, path, message: "OrgX cloud MCP is connected in Zed." };
|
|
2863
|
+
const data = await response.json();
|
|
2864
|
+
if (!Array.isArray(data)) {
|
|
2865
|
+
throw new Error(`Expected '${path}' to be a directory in ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`);
|
|
2866
|
+
}
|
|
2867
|
+
return data.filter((entry) => {
|
|
2868
|
+
if (!entry || typeof entry !== "object") return false;
|
|
2869
|
+
return entry.type === "file" || entry.type === "dir";
|
|
2870
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
2871
|
+
}
|
|
2872
|
+
async function listRemoteSkillFiles(path, fetchImpl, ref) {
|
|
2873
|
+
const entries = await fetchDirectoryEntries(path, fetchImpl, ref);
|
|
2874
|
+
const files = [];
|
|
2875
|
+
for (const entry of entries) {
|
|
2876
|
+
if (entry.type === "dir") {
|
|
2877
|
+
files.push(...await listRemoteSkillFiles(entry.path, fetchImpl, ref));
|
|
2878
|
+
continue;
|
|
2867
2879
|
}
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
if (!authHint) {
|
|
2871
|
-
return buildChatgptAddResult(path, null);
|
|
2872
|
-
}
|
|
2873
|
-
return buildChatgptAddResult(path, authHint);
|
|
2880
|
+
if (!entry.download_url) {
|
|
2881
|
+
throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
|
|
2874
2882
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
message: `${name} is not supported by the wizard.`
|
|
2880
|
-
};
|
|
2883
|
+
files.push({
|
|
2884
|
+
path: entry.path,
|
|
2885
|
+
sourceUrl: entry.download_url
|
|
2886
|
+
});
|
|
2881
2887
|
}
|
|
2888
|
+
return files;
|
|
2882
2889
|
}
|
|
2883
|
-
function
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2890
|
+
async function fetchRemoteText(sourceUrl, fetchImpl) {
|
|
2891
|
+
const response = await fetchImpl(sourceUrl, {
|
|
2892
|
+
headers: {
|
|
2893
|
+
Accept: "text/plain"
|
|
2894
|
+
},
|
|
2895
|
+
signal: AbortSignal.timeout(1e4)
|
|
2896
|
+
});
|
|
2897
|
+
if (!response.ok) {
|
|
2898
|
+
throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
|
|
2899
|
+
}
|
|
2900
|
+
return readResponseText(response);
|
|
2901
|
+
}
|
|
2902
|
+
function getTrackedSkillFile(records, path) {
|
|
2903
|
+
return records.find((record) => record.path === path);
|
|
2904
|
+
}
|
|
2905
|
+
function writeManagedFile(path, content, label, sourceUrl, tracking) {
|
|
2906
|
+
const existing = readTextIfExists(path);
|
|
2907
|
+
const tracked = tracking ? getTrackedSkillFile(tracking.records, path) : void 0;
|
|
2908
|
+
if (tracking && existing !== null && tracked && sha256(existing) !== tracked.contentSha256 && tracking.force !== true) {
|
|
2886
2909
|
return {
|
|
2887
|
-
|
|
2910
|
+
label,
|
|
2911
|
+
path,
|
|
2888
2912
|
changed: false,
|
|
2889
|
-
|
|
2913
|
+
skipped: true,
|
|
2914
|
+
reason: "manual edits detected; move them into a skill extension or rerun with --force",
|
|
2915
|
+
...sourceUrl ? { sourceUrl } : {}
|
|
2890
2916
|
};
|
|
2891
2917
|
}
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
path,
|
|
2923
|
-
message: "OrgX connection was removed from Codex."
|
|
2924
|
-
};
|
|
2918
|
+
const changed = existing !== content;
|
|
2919
|
+
if (changed) {
|
|
2920
|
+
writeTextFile(path, content);
|
|
2921
|
+
}
|
|
2922
|
+
if (tracking) {
|
|
2923
|
+
tracking.stagedRecords.set(path, {
|
|
2924
|
+
contentSha256: sha256(content),
|
|
2925
|
+
...tracking.coreContent ? { coreSha256: sha256(tracking.coreContent) } : {},
|
|
2926
|
+
path,
|
|
2927
|
+
skillId: tracking.skillId,
|
|
2928
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
return {
|
|
2932
|
+
label,
|
|
2933
|
+
path,
|
|
2934
|
+
changed,
|
|
2935
|
+
...sourceUrl ? { sourceUrl } : {}
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
function resolveSkillPackNames(requested = []) {
|
|
2939
|
+
if (requested.length === 0 || requested.includes("all")) {
|
|
2940
|
+
return [...DEFAULT_ORGX_SKILL_PACKS];
|
|
2941
|
+
}
|
|
2942
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2943
|
+
const normalized = [];
|
|
2944
|
+
for (const name of requested) {
|
|
2945
|
+
const next = name.trim();
|
|
2946
|
+
if (!next || seen.has(next)) {
|
|
2947
|
+
continue;
|
|
2925
2948
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2949
|
+
seen.add(next);
|
|
2950
|
+
normalized.push(next);
|
|
2951
|
+
}
|
|
2952
|
+
return normalized;
|
|
2953
|
+
}
|
|
2954
|
+
function resolvePluginManagedSkillTargets(requested = []) {
|
|
2955
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2956
|
+
const targets = [];
|
|
2957
|
+
for (const rawTarget of requested) {
|
|
2958
|
+
const target = rawTarget.trim().toLowerCase();
|
|
2959
|
+
if (!PLUGIN_MANAGED_SKILL_TARGETS.includes(target)) {
|
|
2960
|
+
continue;
|
|
2936
2961
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
const next = removeVscodeMcpConfig(previous);
|
|
2940
|
-
writeTextFile(path, next);
|
|
2941
|
-
return {
|
|
2942
|
-
name,
|
|
2943
|
-
changed: previous !== next,
|
|
2944
|
-
path,
|
|
2945
|
-
message: "OrgX connection was removed from VS Code."
|
|
2946
|
-
};
|
|
2962
|
+
if (seen.has(target)) {
|
|
2963
|
+
continue;
|
|
2947
2964
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
const next = removeWindsurfMcpConfig(previous);
|
|
2951
|
-
writeTextFile(path, next);
|
|
2952
|
-
return {
|
|
2953
|
-
name,
|
|
2954
|
-
changed: previous !== next,
|
|
2955
|
-
path,
|
|
2956
|
-
message: "OrgX connection was removed from Windsurf."
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
case "zed": {
|
|
2960
|
-
const previous = readTextIfExists(path);
|
|
2961
|
-
const next = removeZedMcpConfig(previous);
|
|
2962
|
-
writeTextFile(path, next);
|
|
2963
|
-
return {
|
|
2964
|
-
name,
|
|
2965
|
-
changed: previous !== next,
|
|
2966
|
-
path,
|
|
2967
|
-
message: "OrgX connection was removed from Zed."
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
case "chatgpt":
|
|
2971
|
-
return buildChatgptRemoveResult(path);
|
|
2972
|
-
default:
|
|
2973
|
-
return {
|
|
2974
|
-
name,
|
|
2975
|
-
changed: false,
|
|
2976
|
-
message: `${name} is not supported by the wizard.`
|
|
2977
|
-
};
|
|
2978
|
-
}
|
|
2979
|
-
}
|
|
2980
|
-
async function addSurface(selection) {
|
|
2981
|
-
const targets = resolveSurfaceSelection(selection);
|
|
2982
|
-
const results = [];
|
|
2983
|
-
for (const surface of targets) {
|
|
2984
|
-
results.push(await addAutomatedSurface(surface));
|
|
2965
|
+
seen.add(target);
|
|
2966
|
+
targets.push(target);
|
|
2985
2967
|
}
|
|
2986
|
-
return
|
|
2987
|
-
}
|
|
2988
|
-
function removeSurface(selection) {
|
|
2989
|
-
const targets = resolveSurfaceSelection(selection);
|
|
2990
|
-
return targets.map((surface) => removeAutomatedSurface(surface));
|
|
2968
|
+
return targets;
|
|
2991
2969
|
}
|
|
2992
|
-
|
|
2993
|
-
const
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2996
|
-
|
|
2970
|
+
function planOrgxSkillsInstall(pluginTargets = []) {
|
|
2971
|
+
const pluginManagedTargets = resolvePluginManagedSkillTargets(pluginTargets);
|
|
2972
|
+
const managesClaudeSkills = pluginManagedTargets.includes("claude");
|
|
2973
|
+
const notes = [];
|
|
2974
|
+
if (managesClaudeSkills) {
|
|
2975
|
+
notes.push(
|
|
2976
|
+
"Claude Code plugin is installed, so the wizard skipped overlapping standalone Claude OrgX skill files and Claude skill-pack sync."
|
|
2977
|
+
);
|
|
2997
2978
|
}
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
return targets.map((surface) => removeAutomatedSurface(surface));
|
|
3003
|
-
}
|
|
3004
|
-
async function setupDetectedSurfaces() {
|
|
3005
|
-
const targets = AUTOMATED_SURFACE_NAMES.filter((name) => getSurfaceStatus(name).detected);
|
|
3006
|
-
targets.sort((left, right) => {
|
|
3007
|
-
if (left === "openclaw") return -1;
|
|
3008
|
-
if (right === "openclaw") return 1;
|
|
3009
|
-
return 0;
|
|
3010
|
-
});
|
|
3011
|
-
const results = [];
|
|
3012
|
-
for (const name of targets) {
|
|
3013
|
-
results.push(await addAutomatedSurface(name));
|
|
2979
|
+
if (pluginManagedTargets.includes("codex")) {
|
|
2980
|
+
notes.push(
|
|
2981
|
+
"Codex companion plugin bundles its own OrgX skills, so `wizard skills add` only manages standalone editor assets outside Codex."
|
|
2982
|
+
);
|
|
3014
2983
|
}
|
|
3015
|
-
return results;
|
|
3016
|
-
}
|
|
3017
|
-
function skippedOpenClawHealth() {
|
|
3018
2984
|
return {
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
details: []
|
|
2985
|
+
installClaudeSkillBootstrap: !managesClaudeSkills,
|
|
2986
|
+
installClaudeSkillPacks: !managesClaudeSkills,
|
|
2987
|
+
notes,
|
|
2988
|
+
pluginManagedTargets
|
|
3024
2989
|
};
|
|
3025
2990
|
}
|
|
3026
|
-
async function
|
|
3027
|
-
const
|
|
3028
|
-
|
|
3029
|
-
const auth = await checkOrgxAuth();
|
|
3030
|
-
const hostedMcp = await checkHostedMcpHealth();
|
|
3031
|
-
const hostedMcpTool = await checkHostedMcpToolAccess();
|
|
3032
|
-
const npmRegistry = await checkNpmRegistryHealth();
|
|
3033
|
-
const workspace = await checkWorkspaceConnectivity();
|
|
3034
|
-
const openclaw = openclawSurface?.detected ? await checkOpenClawHealth(openclawSurface.path) : skippedOpenClawHealth();
|
|
3035
|
-
return { surfaces, auth, hostedMcp, hostedMcpTool, npmRegistry, workspace, openclaw };
|
|
2991
|
+
async function fetchAvailablePackNames(fetchImpl, ref) {
|
|
2992
|
+
const entries = await fetchDirectoryEntries("", fetchImpl, ref);
|
|
2993
|
+
return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
|
|
3036
2994
|
}
|
|
3037
|
-
function
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
if (report.workspace.configured && !report.workspace.ok) {
|
|
3061
|
-
issues.push({
|
|
3062
|
-
level: "error",
|
|
3063
|
-
title: "Current workspace lookup failed.",
|
|
3064
|
-
suggestion: "Verify the resolved OrgX base URL and that the authenticated user still has access to a workspace."
|
|
3065
|
-
});
|
|
3066
|
-
}
|
|
3067
|
-
if (!report.npmRegistry.reachable) {
|
|
3068
|
-
issues.push({
|
|
3069
|
-
level: "warning",
|
|
3070
|
-
title: "npm registry is unreachable.",
|
|
3071
|
-
suggestion: "Check outbound network access to https://registry.npmjs.org before relying on npm-based installation flows."
|
|
3072
|
-
});
|
|
3073
|
-
} else if (!report.npmRegistry.published) {
|
|
3074
|
-
issues.push({
|
|
3075
|
-
level: "warning",
|
|
3076
|
-
title: `${report.npmRegistry.packageName} is not published on npm yet.`,
|
|
3077
|
-
suggestion: "This is expected before launch. Publish the package in the release workstream before asking users to install from npm."
|
|
3078
|
-
});
|
|
3079
|
-
}
|
|
3080
|
-
if (!report.openclaw.skipped && !report.openclaw.ok) {
|
|
3081
|
-
issues.push({
|
|
3082
|
-
level: "warning",
|
|
3083
|
-
title: "OpenClaw is installed but its local gateway health check failed.",
|
|
3084
|
-
suggestion: "Run `openclaw doctor` or `openclaw doctor --repair` before relying on local bridge clients like Cursor."
|
|
3085
|
-
});
|
|
2995
|
+
async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, tracking) {
|
|
2996
|
+
const rootPath = skillName;
|
|
2997
|
+
const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
|
|
2998
|
+
const writes = [];
|
|
2999
|
+
for (const file of files) {
|
|
3000
|
+
const coreContent = await fetchRemoteText(file.sourceUrl, fetchImpl);
|
|
3001
|
+
const relativePath = file.path.slice(`${rootPath}/`.length);
|
|
3002
|
+
const content = relativePath === "SKILL.md" ? composeSkillContent(skillName, coreContent, tracking.extensions) : coreContent;
|
|
3003
|
+
writes.push(
|
|
3004
|
+
writeManagedFile(
|
|
3005
|
+
join2(claudeSkillsDir, skillName, relativePath),
|
|
3006
|
+
content,
|
|
3007
|
+
`${skillName}/${relativePath}`,
|
|
3008
|
+
file.sourceUrl,
|
|
3009
|
+
{
|
|
3010
|
+
coreContent,
|
|
3011
|
+
records: tracking.records,
|
|
3012
|
+
skillId: skillName,
|
|
3013
|
+
stagedRecords: tracking.stagedRecords,
|
|
3014
|
+
...tracking.force !== void 0 ? { force: tracking.force } : {}
|
|
3015
|
+
}
|
|
3016
|
+
)
|
|
3017
|
+
);
|
|
3086
3018
|
}
|
|
3087
3019
|
return {
|
|
3088
|
-
|
|
3089
|
-
|
|
3020
|
+
name: skillName,
|
|
3021
|
+
files: writes
|
|
3090
3022
|
};
|
|
3091
3023
|
}
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
"cursor",
|
|
3097
|
-
"codex",
|
|
3098
|
-
"vscode",
|
|
3099
|
-
"windsurf",
|
|
3100
|
-
"zed",
|
|
3101
|
-
"chatgpt"
|
|
3102
|
-
];
|
|
3103
|
-
function selectReviewSurface(statuses) {
|
|
3104
|
-
for (const name of REVIEW_SURFACE_ORDER) {
|
|
3105
|
-
const configured = statuses.find((status) => status.name === name && status.configured);
|
|
3106
|
-
if (configured) {
|
|
3107
|
-
return configured.name;
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
for (const name of REVIEW_SURFACE_ORDER) {
|
|
3111
|
-
const detected = statuses.find((status) => status.name === name && status.detected);
|
|
3112
|
-
if (detected) {
|
|
3113
|
-
return detected.name;
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
return void 0;
|
|
3117
|
-
}
|
|
3118
|
-
function inferExecutionMode(explicitMode, statuses) {
|
|
3119
|
-
if (explicitMode) {
|
|
3120
|
-
return explicitMode;
|
|
3121
|
-
}
|
|
3122
|
-
const openclaw = statuses.find((status) => status.name === "openclaw");
|
|
3123
|
-
if (openclaw?.configured || openclaw?.detected) {
|
|
3124
|
-
return "local";
|
|
3024
|
+
async function installOrgxSkills(options = {}) {
|
|
3025
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
3026
|
+
if (!fetchImpl) {
|
|
3027
|
+
throw new Error("Global fetch is unavailable in this runtime.");
|
|
3125
3028
|
}
|
|
3126
|
-
const
|
|
3127
|
-
|
|
3029
|
+
const ref = options.ref ?? ORGX_SKILLS_REF;
|
|
3030
|
+
const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
|
|
3031
|
+
const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
|
|
3032
|
+
const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
|
|
3033
|
+
const statePath = options.statePath ?? ORGX_WIZARD_STATE_PATH;
|
|
3034
|
+
const extensions = listSkillExtensions(
|
|
3035
|
+
options.skillExtensionsDir !== void 0 ? { extensionsDir: options.skillExtensionsDir } : {}
|
|
3128
3036
|
);
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
const
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3135
|
-
if (
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
|
-
continuity.workspaceId = seed.workspace.id;
|
|
3037
|
+
const enabledExtensions = extensions.filter((extension) => extension.enabled);
|
|
3038
|
+
const trackedRecords = readWizardState(statePath)?.skillFiles?.files ?? [];
|
|
3039
|
+
const stagedRecords = /* @__PURE__ */ new Map();
|
|
3040
|
+
const plan = planOrgxSkillsInstall(options.pluginTargets ?? []);
|
|
3041
|
+
const requestedNames = options.skillNames ?? [];
|
|
3042
|
+
let skillNames;
|
|
3043
|
+
if (requestedNames.length === 0 || requestedNames.includes("all")) {
|
|
3044
|
+
try {
|
|
3045
|
+
skillNames = await fetchAvailablePackNames(fetchImpl, ref);
|
|
3046
|
+
} catch {
|
|
3047
|
+
skillNames = [...DEFAULT_ORGX_SKILL_PACKS];
|
|
3048
|
+
}
|
|
3049
|
+
} else {
|
|
3050
|
+
skillNames = resolveSkillPackNames(requestedNames);
|
|
3144
3051
|
}
|
|
3145
|
-
const
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
}
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
var FOUNDER_DEMO_ARTIFACT_DESCRIPTION = "Live founder demo generated by @useorgx/wizard after workspace bootstrap completed.";
|
|
3177
|
-
var ONBOARDING_TASK_TITLE = "Complete OrgX onboarding";
|
|
3178
|
-
var ONBOARDING_TASK_SUMMARY = "Starter onboarding task created by @useorgx/wizard so the first workspace has a clear follow-up after setup.";
|
|
3179
|
-
var ONBOARDING_WORKSTREAM_TITLE = "OrgX onboarding";
|
|
3180
|
-
var ONBOARDING_WORKSTREAM_SUMMARY = "Starter onboarding workstream created by @useorgx/wizard so the first workspace has a home for setup follow-up tasks.";
|
|
3181
|
-
var FIRST_VALUE_INITIATIVE_TITLE = "Make OrgX useful on this machine";
|
|
3182
|
-
var FIRST_VALUE_INITIATIVE_SUMMARY = "First initiative created by @useorgx/wizard so setup ends with a live workspace, an onboarding workstream, and a clean handoff into the user's configured AI tools.";
|
|
3183
|
-
function parseResponseBody3(text2) {
|
|
3184
|
-
if (!text2) {
|
|
3185
|
-
return null;
|
|
3186
|
-
}
|
|
3187
|
-
try {
|
|
3188
|
-
return JSON.parse(text2);
|
|
3189
|
-
} catch {
|
|
3190
|
-
return text2;
|
|
3052
|
+
const writes = [
|
|
3053
|
+
writeManagedFile(
|
|
3054
|
+
cursorRulePath,
|
|
3055
|
+
composeSkillContent("cursor-rules", CURSOR_RULES_CONTENT, extensions),
|
|
3056
|
+
"cursor-rules",
|
|
3057
|
+
void 0,
|
|
3058
|
+
{
|
|
3059
|
+
coreContent: CURSOR_RULES_CONTENT,
|
|
3060
|
+
records: trackedRecords,
|
|
3061
|
+
skillId: "cursor-rules",
|
|
3062
|
+
stagedRecords,
|
|
3063
|
+
...options.force !== void 0 ? { force: options.force } : {}
|
|
3064
|
+
}
|
|
3065
|
+
)
|
|
3066
|
+
];
|
|
3067
|
+
if (plan.installClaudeSkillBootstrap) {
|
|
3068
|
+
writes.push(
|
|
3069
|
+
writeManagedFile(
|
|
3070
|
+
claudeOrgxSkillPath,
|
|
3071
|
+
composeSkillContent("orgx", CLAUDE_ORGX_SKILL_CONTENT, extensions),
|
|
3072
|
+
"claude-orgx-skill",
|
|
3073
|
+
void 0,
|
|
3074
|
+
{
|
|
3075
|
+
coreContent: CLAUDE_ORGX_SKILL_CONTENT,
|
|
3076
|
+
records: trackedRecords,
|
|
3077
|
+
skillId: "orgx",
|
|
3078
|
+
stagedRecords,
|
|
3079
|
+
...options.force !== void 0 ? { force: options.force } : {}
|
|
3080
|
+
}
|
|
3081
|
+
)
|
|
3082
|
+
);
|
|
3191
3083
|
}
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3084
|
+
const packs = [];
|
|
3085
|
+
if (plan.installClaudeSkillPacks) {
|
|
3086
|
+
for (const skillName of skillNames) {
|
|
3087
|
+
packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, {
|
|
3088
|
+
extensions,
|
|
3089
|
+
records: trackedRecords,
|
|
3090
|
+
stagedRecords,
|
|
3091
|
+
...options.force !== void 0 ? { force: options.force } : {}
|
|
3092
|
+
}));
|
|
3093
|
+
}
|
|
3196
3094
|
}
|
|
3197
|
-
if (
|
|
3198
|
-
|
|
3095
|
+
if (stagedRecords.size > 0) {
|
|
3096
|
+
updateWizardState((current) => {
|
|
3097
|
+
const previousFiles = current.skillFiles?.files ?? [];
|
|
3098
|
+
const nextFiles = /* @__PURE__ */ new Map();
|
|
3099
|
+
for (const record of previousFiles) {
|
|
3100
|
+
nextFiles.set(record.path, record);
|
|
3101
|
+
}
|
|
3102
|
+
for (const record of stagedRecords.values()) {
|
|
3103
|
+
nextFiles.set(record.path, record);
|
|
3104
|
+
}
|
|
3105
|
+
return {
|
|
3106
|
+
...current,
|
|
3107
|
+
skillFiles: {
|
|
3108
|
+
files: [...nextFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
3109
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3110
|
+
}
|
|
3111
|
+
};
|
|
3112
|
+
}, statePath);
|
|
3199
3113
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
throw new Error("OrgX returned an unexpected entity payload.");
|
|
3114
|
+
const notes = [...plan.notes];
|
|
3115
|
+
if (enabledExtensions.length > 0) {
|
|
3116
|
+
notes.push(
|
|
3117
|
+
`Applied ${enabledExtensions.length} enabled OrgX skill extension${enabledExtensions.length === 1 ? "" : "s"} while syncing skills/rules.`
|
|
3118
|
+
);
|
|
3206
3119
|
}
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
function parseInitiative(payload) {
|
|
3210
|
-
const entity = extractEntity(payload);
|
|
3211
|
-
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
3212
|
-
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
3213
|
-
if (!id || !title) {
|
|
3214
|
-
throw new Error("OrgX returned an incomplete initiative payload.");
|
|
3120
|
+
if (writes.some((write) => write.skipped) || packs.some((pack) => pack.files.some((file) => file.skipped))) {
|
|
3121
|
+
notes.push("Some generated skill files were skipped because local manual edits were detected.");
|
|
3215
3122
|
}
|
|
3216
3123
|
return {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3124
|
+
extensions,
|
|
3125
|
+
notes,
|
|
3126
|
+
writes,
|
|
3127
|
+
packs
|
|
3220
3128
|
};
|
|
3221
3129
|
}
|
|
3222
|
-
function
|
|
3223
|
-
const
|
|
3224
|
-
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
3225
|
-
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
3226
|
-
if (!id || !title) {
|
|
3227
|
-
throw new Error("OrgX returned an incomplete decision payload.");
|
|
3228
|
-
}
|
|
3130
|
+
function getSkillStatus(options = {}) {
|
|
3131
|
+
const state = readWizardState(options.statePath ?? ORGX_WIZARD_STATE_PATH);
|
|
3229
3132
|
return {
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3133
|
+
extensions: listSkillExtensions(
|
|
3134
|
+
options.extensionsDir !== void 0 ? { extensionsDir: options.extensionsDir } : {}
|
|
3135
|
+
),
|
|
3136
|
+
trackedFiles: state?.skillFiles?.files ?? []
|
|
3234
3137
|
};
|
|
3235
3138
|
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3139
|
+
|
|
3140
|
+
// src/lib/plugins.ts
|
|
3141
|
+
var DEFAULT_ORGX_PLUGIN_TARGETS = ["cursor", "claude", "codex", "openclaw"];
|
|
3142
|
+
var ORGX_PLUGIN_GITHUB_OWNER = "useorgx";
|
|
3143
|
+
var ORGX_PLUGIN_GITHUB_REF = "main";
|
|
3144
|
+
var ORGX_CLAUDE_PLUGIN_NAME = "orgx-claude-code-plugin";
|
|
3145
|
+
var ORGX_CLAUDE_MARKETPLACE_NAME = "orgx-local";
|
|
3146
|
+
var ORGX_CODEX_PLUGIN_NAME = "orgx-codex-plugin";
|
|
3147
|
+
var ORGX_CURSOR_PLUGIN_NAME = "cursor-plugin";
|
|
3148
|
+
var ORGX_OPENCLAW_PLUGIN_ID = "orgx";
|
|
3149
|
+
var ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME = "@useorgx/openclaw-plugin";
|
|
3150
|
+
var CLAUDE_PLUGIN_SYNC_SPEC = {
|
|
3151
|
+
owner: ORGX_PLUGIN_GITHUB_OWNER,
|
|
3152
|
+
repo: ORGX_CLAUDE_PLUGIN_NAME,
|
|
3153
|
+
ref: ORGX_PLUGIN_GITHUB_REF,
|
|
3154
|
+
include: [
|
|
3155
|
+
{ localPath: ".claude-plugin", remotePath: ".claude-plugin" },
|
|
3156
|
+
{ localPath: "agents", remotePath: "agents" },
|
|
3157
|
+
{ localPath: "commands", remotePath: "commands" },
|
|
3158
|
+
{ localPath: "hooks", remotePath: "hooks" },
|
|
3159
|
+
{ localPath: "lib", remotePath: "lib" },
|
|
3160
|
+
{ localPath: "scripts", remotePath: "scripts" },
|
|
3161
|
+
{ localPath: "skills", remotePath: "skills" }
|
|
3162
|
+
]
|
|
3163
|
+
};
|
|
3164
|
+
var CODEX_PLUGIN_SYNC_SPEC = {
|
|
3165
|
+
owner: ORGX_PLUGIN_GITHUB_OWNER,
|
|
3166
|
+
repo: ORGX_CODEX_PLUGIN_NAME,
|
|
3167
|
+
ref: ORGX_PLUGIN_GITHUB_REF,
|
|
3168
|
+
include: [
|
|
3169
|
+
{ localPath: ".codex-plugin", remotePath: ".codex-plugin" },
|
|
3170
|
+
{ localPath: ".mcp.json", remotePath: ".mcp.json" },
|
|
3171
|
+
{ localPath: "assets", remotePath: "assets" },
|
|
3172
|
+
{ localPath: "skills", remotePath: "skills" }
|
|
3173
|
+
]
|
|
3174
|
+
};
|
|
3175
|
+
var CURSOR_PLUGIN_SYNC_SPEC = {
|
|
3176
|
+
owner: ORGX_PLUGIN_GITHUB_OWNER,
|
|
3177
|
+
repo: ORGX_CURSOR_PLUGIN_NAME,
|
|
3178
|
+
ref: ORGX_PLUGIN_GITHUB_REF,
|
|
3179
|
+
include: [
|
|
3180
|
+
{ localPath: ".cursor-plugin", remotePath: ".cursor-plugin" },
|
|
3181
|
+
{ localPath: ".mcp.json", remotePath: ".mcp.json" },
|
|
3182
|
+
{ localPath: "agents", remotePath: "agents" },
|
|
3183
|
+
{ localPath: "assets", remotePath: "assets" },
|
|
3184
|
+
{ localPath: "commands", remotePath: "commands" },
|
|
3185
|
+
{ localPath: "hooks", remotePath: "hooks" },
|
|
3186
|
+
{ localPath: "rules", remotePath: "rules" },
|
|
3187
|
+
{ localPath: "scripts", remotePath: "scripts" },
|
|
3188
|
+
{ localPath: "skills", remotePath: "skills" }
|
|
3189
|
+
]
|
|
3190
|
+
};
|
|
3191
|
+
function defaultPluginPaths() {
|
|
3245
3192
|
return {
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3193
|
+
claudeMarketplaceDir: CLAUDE_MANAGED_MARKETPLACE_DIR,
|
|
3194
|
+
claudeMarketplaceManifestPath: CLAUDE_MANAGED_MARKETPLACE_MANIFEST_PATH,
|
|
3195
|
+
claudePluginDir: CLAUDE_MANAGED_PLUGIN_DIR,
|
|
3196
|
+
codexMarketplacePath: CODEX_MARKETPLACE_PATH,
|
|
3197
|
+
codexPluginDir: CODEX_ORGX_PLUGIN_DIR,
|
|
3198
|
+
cursorPluginDir: CURSOR_ORGX_PLUGIN_DIR,
|
|
3199
|
+
cursorRulePath: CURSOR_ORGX_RULE_PATH
|
|
3250
3200
|
};
|
|
3251
3201
|
}
|
|
3252
|
-
function
|
|
3253
|
-
const entity = extractEntity(payload);
|
|
3254
|
-
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
3255
|
-
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
3256
|
-
if (!id || !title) {
|
|
3257
|
-
throw new Error("OrgX returned an incomplete task payload.");
|
|
3258
|
-
}
|
|
3202
|
+
function resolvePluginPaths(paths = {}) {
|
|
3259
3203
|
return {
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
|
|
3263
|
-
...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
|
|
3204
|
+
...defaultPluginPaths(),
|
|
3205
|
+
...paths
|
|
3264
3206
|
};
|
|
3265
3207
|
}
|
|
3266
|
-
function
|
|
3267
|
-
|
|
3268
|
-
const
|
|
3269
|
-
const
|
|
3270
|
-
|
|
3271
|
-
throw new Error("OrgX returned an incomplete workstream payload.");
|
|
3272
|
-
}
|
|
3273
|
-
return { id, title };
|
|
3208
|
+
function isRepoDirectoryEntry(value) {
|
|
3209
|
+
if (!value || typeof value !== "object") return false;
|
|
3210
|
+
const entry = value;
|
|
3211
|
+
const hasType = entry.type === "file" || entry.type === "dir";
|
|
3212
|
+
return typeof entry.name === "string" && typeof entry.path === "string" && hasType;
|
|
3274
3213
|
}
|
|
3275
|
-
function
|
|
3276
|
-
return
|
|
3277
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3278
|
-
artifactId: artifact.id,
|
|
3279
|
-
artifactName: artifact.name,
|
|
3280
|
-
...artifact.type ? { artifactType: artifact.type } : {},
|
|
3281
|
-
...artifact.url ? { artifactUrl: artifact.url } : {},
|
|
3282
|
-
decisionId: decision.id,
|
|
3283
|
-
...decision.status ? { decisionStatus: decision.status } : {},
|
|
3284
|
-
decisionTitle: decision.title,
|
|
3285
|
-
id: initiative.id,
|
|
3286
|
-
liveUrl,
|
|
3287
|
-
title: initiative.title,
|
|
3288
|
-
workspaceId: workspace.id,
|
|
3289
|
-
workspaceName: workspace.name
|
|
3290
|
-
};
|
|
3214
|
+
function encodeRepoPath2(value) {
|
|
3215
|
+
return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
|
|
3291
3216
|
}
|
|
3292
|
-
function
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
id: task.id,
|
|
3296
|
-
...options.initiativeId ? { initiativeId: options.initiativeId } : {},
|
|
3297
|
-
...task.status ? { status: task.status } : {},
|
|
3298
|
-
title: task.title,
|
|
3299
|
-
...options.workstreamId ? { workstreamId: options.workstreamId } : {},
|
|
3300
|
-
workspaceId: workspace.id,
|
|
3301
|
-
workspaceName: workspace.name
|
|
3302
|
-
};
|
|
3217
|
+
function isLikelyRepoFilePath(path) {
|
|
3218
|
+
const basename3 = path.split("/").pop() ?? path;
|
|
3219
|
+
return basename3.includes(".") && !/^\.[^./]+$/.test(basename3);
|
|
3303
3220
|
}
|
|
3304
|
-
function
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
id: initiative.id,
|
|
3308
|
-
liveUrl,
|
|
3309
|
-
...initiative.summary ? { summary: initiative.summary } : {},
|
|
3310
|
-
title: initiative.title,
|
|
3311
|
-
workspaceId: workspace.id,
|
|
3312
|
-
workspaceName: workspace.name
|
|
3313
|
-
};
|
|
3221
|
+
function buildContentsUrl2(spec, path) {
|
|
3222
|
+
const encodedPath = encodeRepoPath2(path);
|
|
3223
|
+
return `https://api.github.com/repos/${spec.owner}/${spec.repo}/contents/${encodedPath}?ref=${encodeURIComponent(spec.ref)}`;
|
|
3314
3224
|
}
|
|
3315
|
-
|
|
3316
|
-
const
|
|
3317
|
-
if (
|
|
3318
|
-
|
|
3319
|
-
"No OrgX API key configured. Run `wizard auth login` or `wizard auth set-key <oxk_...>` first."
|
|
3320
|
-
);
|
|
3225
|
+
function resolveGitHubError2(spec, status, path) {
|
|
3226
|
+
const repoName = `${spec.owner}/${spec.repo}`;
|
|
3227
|
+
if (status === 404) {
|
|
3228
|
+
return `Could not find '${path}' in ${repoName}.`;
|
|
3321
3229
|
}
|
|
3322
|
-
return
|
|
3230
|
+
return `GitHub returned HTTP ${status} while loading '${path}' from ${repoName}.`;
|
|
3323
3231
|
}
|
|
3324
|
-
async function
|
|
3325
|
-
const
|
|
3326
|
-
const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
|
|
3327
|
-
method: "POST",
|
|
3232
|
+
async function fetchDirectoryEntries2(spec, path, fetchImpl) {
|
|
3233
|
+
const response = await fetchImpl(buildContentsUrl2(spec, path), {
|
|
3328
3234
|
headers: {
|
|
3329
|
-
|
|
3330
|
-
"Content-Type": "application/json"
|
|
3235
|
+
Accept: "application/vnd.github+json"
|
|
3331
3236
|
},
|
|
3332
|
-
|
|
3333
|
-
type,
|
|
3334
|
-
...body
|
|
3335
|
-
}),
|
|
3336
|
-
signal: AbortSignal.timeout(7e3)
|
|
3237
|
+
signal: AbortSignal.timeout(1e4)
|
|
3337
3238
|
});
|
|
3338
|
-
const responseBody = parseResponseBody3(await response.text());
|
|
3339
3239
|
if (!response.ok) {
|
|
3340
|
-
throw new Error(
|
|
3240
|
+
throw new Error(resolveGitHubError2(spec, response.status, path));
|
|
3341
3241
|
}
|
|
3342
|
-
|
|
3242
|
+
const data = await response.json();
|
|
3243
|
+
if (!Array.isArray(data)) {
|
|
3244
|
+
throw new Error(`Expected '${path}' to be a directory in ${spec.owner}/${spec.repo}.`);
|
|
3245
|
+
}
|
|
3246
|
+
return data.filter(isRepoDirectoryEntry).sort((left, right) => left.path.localeCompare(right.path));
|
|
3343
3247
|
}
|
|
3344
|
-
async function
|
|
3345
|
-
const
|
|
3346
|
-
const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
|
|
3347
|
-
method: "PATCH",
|
|
3248
|
+
async function fetchRepoFile(spec, path, fetchImpl) {
|
|
3249
|
+
const response = await fetchImpl(buildContentsUrl2(spec, path), {
|
|
3348
3250
|
headers: {
|
|
3349
|
-
|
|
3350
|
-
"Content-Type": "application/json"
|
|
3251
|
+
Accept: "application/vnd.github+json"
|
|
3351
3252
|
},
|
|
3352
|
-
|
|
3353
|
-
type,
|
|
3354
|
-
id,
|
|
3355
|
-
...body
|
|
3356
|
-
}),
|
|
3357
|
-
signal: AbortSignal.timeout(7e3)
|
|
3253
|
+
signal: AbortSignal.timeout(1e4)
|
|
3358
3254
|
});
|
|
3359
|
-
const responseBody = parseResponseBody3(await response.text());
|
|
3360
3255
|
if (!response.ok) {
|
|
3361
|
-
throw new Error(
|
|
3256
|
+
throw new Error(resolveGitHubError2(spec, response.status, path));
|
|
3362
3257
|
}
|
|
3363
|
-
|
|
3258
|
+
const data = await response.json();
|
|
3259
|
+
if (!isRepoDirectoryEntry(data) || data.type !== "file" || typeof data.download_url !== "string") {
|
|
3260
|
+
throw new Error(`Expected '${path}' to be a file in ${spec.owner}/${spec.repo}.`);
|
|
3261
|
+
}
|
|
3262
|
+
return data;
|
|
3364
3263
|
}
|
|
3365
|
-
function
|
|
3366
|
-
const
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3264
|
+
async function listRemoteRepoFiles(spec, path, localPath, fetchImpl) {
|
|
3265
|
+
const files = [];
|
|
3266
|
+
if (isLikelyRepoFilePath(path) && !path.endsWith("/")) {
|
|
3267
|
+
const file = await fetchRepoFile(spec, path, fetchImpl);
|
|
3268
|
+
files.push({
|
|
3269
|
+
localPath,
|
|
3270
|
+
path: file.path,
|
|
3271
|
+
sourceUrl: file.download_url ?? ""
|
|
3272
|
+
});
|
|
3273
|
+
return files;
|
|
3274
|
+
}
|
|
3275
|
+
const entries = await fetchDirectoryEntries2(spec, path, fetchImpl);
|
|
3276
|
+
for (const entry of entries) {
|
|
3277
|
+
if (entry.type === "dir") {
|
|
3278
|
+
files.push(
|
|
3279
|
+
...await listRemoteRepoFiles(
|
|
3280
|
+
spec,
|
|
3281
|
+
entry.path,
|
|
3282
|
+
join3(localPath, entry.name),
|
|
3283
|
+
fetchImpl
|
|
3284
|
+
)
|
|
3285
|
+
);
|
|
3286
|
+
continue;
|
|
3287
|
+
}
|
|
3288
|
+
if (!entry.download_url) {
|
|
3289
|
+
throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
|
|
3290
|
+
}
|
|
3291
|
+
files.push({
|
|
3292
|
+
localPath: join3(localPath, entry.name),
|
|
3293
|
+
path: entry.path,
|
|
3294
|
+
sourceUrl: entry.download_url
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
return files;
|
|
3371
3298
|
}
|
|
3372
|
-
async function
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3299
|
+
async function collectRemoteRepoFiles(spec, fetchImpl) {
|
|
3300
|
+
const files = [];
|
|
3301
|
+
for (const entry of spec.include) {
|
|
3302
|
+
const isFile = isLikelyRepoFilePath(entry.remotePath) && !entry.remotePath.endsWith("/");
|
|
3303
|
+
if (isFile) {
|
|
3304
|
+
const file = await fetchRepoFile(spec, entry.remotePath, fetchImpl);
|
|
3305
|
+
files.push({
|
|
3306
|
+
localPath: entry.localPath,
|
|
3307
|
+
path: file.path,
|
|
3308
|
+
sourceUrl: file.download_url ?? ""
|
|
3309
|
+
});
|
|
3310
|
+
continue;
|
|
3311
|
+
}
|
|
3312
|
+
files.push(...await listRemoteRepoFiles(spec, entry.remotePath, entry.localPath, fetchImpl));
|
|
3313
|
+
}
|
|
3314
|
+
return files.sort((left, right) => left.localPath.localeCompare(right.localPath));
|
|
3384
3315
|
}
|
|
3385
|
-
async function
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
{
|
|
3390
|
-
resolution_summary: FOUNDER_DEMO_DECISION_RESOLUTION,
|
|
3391
|
-
status: "approved"
|
|
3316
|
+
async function fetchRemoteBytes(sourceUrl, fetchImpl) {
|
|
3317
|
+
const response = await fetchImpl(sourceUrl, {
|
|
3318
|
+
headers: {
|
|
3319
|
+
Accept: "application/octet-stream"
|
|
3392
3320
|
},
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
)
|
|
3321
|
+
signal: AbortSignal.timeout(1e4)
|
|
3322
|
+
});
|
|
3323
|
+
if (!response.ok) {
|
|
3324
|
+
throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
|
|
3325
|
+
}
|
|
3326
|
+
return Buffer.from(await response.arrayBuffer());
|
|
3396
3327
|
}
|
|
3397
|
-
|
|
3398
|
-
return
|
|
3399
|
-
|
|
3400
|
-
{
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
options
|
|
3412
|
-
);
|
|
3328
|
+
function readBytesIfExists(path) {
|
|
3329
|
+
if (!existsSync4(path)) return null;
|
|
3330
|
+
try {
|
|
3331
|
+
if (!statSync2(path).isFile()) {
|
|
3332
|
+
return null;
|
|
3333
|
+
}
|
|
3334
|
+
return readFileSync2(path);
|
|
3335
|
+
} catch (error) {
|
|
3336
|
+
const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
3337
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
throw error;
|
|
3341
|
+
}
|
|
3413
3342
|
}
|
|
3414
|
-
|
|
3415
|
-
const
|
|
3416
|
-
if (
|
|
3417
|
-
|
|
3343
|
+
function writeBytesIfChanged(path, bytes) {
|
|
3344
|
+
const existing = readBytesIfExists(path);
|
|
3345
|
+
if (existing && Buffer.compare(existing, bytes) === 0) {
|
|
3346
|
+
return false;
|
|
3418
3347
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
title,
|
|
3423
|
-
status: "active",
|
|
3424
|
-
...input.summary?.trim() ? { summary: input.summary.trim() } : {},
|
|
3425
|
-
...input.workspaceId?.trim() ? { workspace_id: input.workspaceId.trim() } : {}
|
|
3426
|
-
},
|
|
3427
|
-
parseInitiative,
|
|
3428
|
-
options
|
|
3429
|
-
);
|
|
3348
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
3349
|
+
writeFileSync2(path, bytes);
|
|
3350
|
+
return true;
|
|
3430
3351
|
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
const initiative = matchingRecord ? {
|
|
3436
|
-
id: matchingRecord.id,
|
|
3437
|
-
title: matchingRecord.title
|
|
3438
|
-
} : await createInitiative(
|
|
3439
|
-
{
|
|
3440
|
-
title: FOUNDER_DEMO_INITIATIVE_TITLE,
|
|
3441
|
-
summary: FOUNDER_DEMO_INITIATIVE_SUMMARY,
|
|
3442
|
-
workspaceId: workspace.id
|
|
3443
|
-
},
|
|
3444
|
-
options
|
|
3445
|
-
);
|
|
3446
|
-
const liveUrl = matchingRecord?.liveUrl || buildLiveUrl(auth.baseUrl, initiative.id);
|
|
3447
|
-
const decision = matchingRecord?.decisionId ? matchingRecord.decisionStatus === "approved" && matchingRecord.decisionTitle ? {
|
|
3448
|
-
id: matchingRecord.decisionId,
|
|
3449
|
-
status: matchingRecord.decisionStatus,
|
|
3450
|
-
title: matchingRecord.decisionTitle
|
|
3451
|
-
} : await approveFounderDemoDecision(matchingRecord.decisionId, options) : await approveFounderDemoDecision(
|
|
3452
|
-
(await createFounderDemoDecision(initiative, workspace.id, options)).id,
|
|
3453
|
-
options
|
|
3454
|
-
);
|
|
3455
|
-
const artifact = matchingRecord?.artifactId && matchingRecord.artifactName ? {
|
|
3456
|
-
id: matchingRecord.artifactId,
|
|
3457
|
-
name: matchingRecord.artifactName,
|
|
3458
|
-
...matchingRecord.artifactType ? { type: matchingRecord.artifactType } : {},
|
|
3459
|
-
...matchingRecord.artifactUrl ? { url: matchingRecord.artifactUrl } : {}
|
|
3460
|
-
} : await createFounderDemoArtifact(initiative, liveUrl, workspace.id, options);
|
|
3461
|
-
const record = toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace);
|
|
3462
|
-
updateWizardState(
|
|
3463
|
-
(current) => ({
|
|
3464
|
-
...current,
|
|
3465
|
-
demoInitiative: record
|
|
3466
|
-
}),
|
|
3467
|
-
options.statePath
|
|
3468
|
-
);
|
|
3469
|
-
return {
|
|
3470
|
-
artifact,
|
|
3471
|
-
created: !matchingRecord,
|
|
3472
|
-
decision,
|
|
3473
|
-
initiative,
|
|
3474
|
-
liveUrl
|
|
3475
|
-
};
|
|
3476
|
-
}
|
|
3477
|
-
async function ensureFirstValueInitiative(workspace, options = {}) {
|
|
3478
|
-
const existingRecord = readWizardState(options.statePath)?.firstValueInitiative;
|
|
3479
|
-
const matchingRecord = existingRecord?.workspaceId === workspace.id ? existingRecord : void 0;
|
|
3480
|
-
const auth = await requireOrgxAuth2(options);
|
|
3481
|
-
if (matchingRecord) {
|
|
3482
|
-
return {
|
|
3483
|
-
created: false,
|
|
3484
|
-
initiative: {
|
|
3485
|
-
id: matchingRecord.id,
|
|
3486
|
-
title: matchingRecord.title,
|
|
3487
|
-
...matchingRecord.summary ? { summary: matchingRecord.summary } : {}
|
|
3488
|
-
},
|
|
3489
|
-
liveUrl: matchingRecord.liveUrl || buildLiveUrl(auth.baseUrl, matchingRecord.id)
|
|
3490
|
-
};
|
|
3491
|
-
}
|
|
3492
|
-
const initiative = await createInitiative(
|
|
3493
|
-
{
|
|
3494
|
-
title: options.title?.trim() || FIRST_VALUE_INITIATIVE_TITLE,
|
|
3495
|
-
summary: options.summary?.trim() || FIRST_VALUE_INITIATIVE_SUMMARY,
|
|
3496
|
-
workspaceId: workspace.id
|
|
3497
|
-
},
|
|
3498
|
-
options
|
|
3499
|
-
);
|
|
3500
|
-
const liveUrl = buildLiveUrl(auth.baseUrl, initiative.id);
|
|
3501
|
-
updateWizardState(
|
|
3502
|
-
(current) => ({
|
|
3503
|
-
...current,
|
|
3504
|
-
firstValueInitiative: toFirstValueInitiativeRecord(initiative, liveUrl, workspace)
|
|
3505
|
-
}),
|
|
3506
|
-
options.statePath
|
|
3507
|
-
);
|
|
3508
|
-
return {
|
|
3509
|
-
created: true,
|
|
3510
|
-
initiative,
|
|
3511
|
-
liveUrl
|
|
3512
|
-
};
|
|
3352
|
+
function removePathIfExists(path) {
|
|
3353
|
+
if (!existsSync4(path)) return false;
|
|
3354
|
+
rmSync(path, { force: true, recursive: true });
|
|
3355
|
+
return true;
|
|
3513
3356
|
}
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
if (
|
|
3517
|
-
return
|
|
3518
|
-
id: existingRecord.id,
|
|
3519
|
-
title: existingRecord.title,
|
|
3520
|
-
...existingRecord.status ? { status: existingRecord.status } : {}
|
|
3521
|
-
};
|
|
3357
|
+
function listRelativeFiles(root, base = root) {
|
|
3358
|
+
if (!existsSync4(root)) return [];
|
|
3359
|
+
if (!statSync2(root).isDirectory()) {
|
|
3360
|
+
return [];
|
|
3522
3361
|
}
|
|
3523
|
-
const
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3362
|
+
const files = [];
|
|
3363
|
+
for (const entry of readdirSync2(root, { withFileTypes: true })) {
|
|
3364
|
+
const nextPath = join3(root, entry.name);
|
|
3365
|
+
if (entry.isDirectory()) {
|
|
3366
|
+
files.push(...listRelativeFiles(nextPath, base));
|
|
3367
|
+
continue;
|
|
3368
|
+
}
|
|
3369
|
+
if (entry.isFile()) {
|
|
3370
|
+
files.push(relative(base, nextPath));
|
|
3371
|
+
}
|
|
3528
3372
|
}
|
|
3529
|
-
|
|
3530
|
-
"workstream",
|
|
3531
|
-
{
|
|
3532
|
-
initiative_id: initiativeId,
|
|
3533
|
-
status: "active",
|
|
3534
|
-
summary: ONBOARDING_WORKSTREAM_SUMMARY,
|
|
3535
|
-
title: ONBOARDING_WORKSTREAM_TITLE,
|
|
3536
|
-
workspace_id: workspace.id
|
|
3537
|
-
},
|
|
3538
|
-
parseWorkstream,
|
|
3539
|
-
options
|
|
3540
|
-
);
|
|
3541
|
-
const task = await createEntity(
|
|
3542
|
-
"task",
|
|
3543
|
-
{
|
|
3544
|
-
initiative_id: initiativeId,
|
|
3545
|
-
status: "todo",
|
|
3546
|
-
summary: ONBOARDING_TASK_SUMMARY,
|
|
3547
|
-
title: ONBOARDING_TASK_TITLE,
|
|
3548
|
-
workstream_id: workstream.id,
|
|
3549
|
-
workspace_id: workspace.id
|
|
3550
|
-
},
|
|
3551
|
-
parseTask,
|
|
3552
|
-
options
|
|
3553
|
-
);
|
|
3554
|
-
updateWizardState(
|
|
3555
|
-
(current) => ({
|
|
3556
|
-
...current,
|
|
3557
|
-
onboardingTask: toOnboardingTaskRecord(task, workspace, {
|
|
3558
|
-
initiativeId,
|
|
3559
|
-
workstreamId: workstream.id
|
|
3560
|
-
})
|
|
3561
|
-
}),
|
|
3562
|
-
options.statePath
|
|
3563
|
-
);
|
|
3564
|
-
return task;
|
|
3565
|
-
}
|
|
3566
|
-
|
|
3567
|
-
// src/lib/skills.ts
|
|
3568
|
-
import { createHash as createHash2 } from "crypto";
|
|
3569
|
-
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
3570
|
-
import { basename, join as join2 } from "path";
|
|
3571
|
-
var DEFAULT_ORGX_SKILL_PACKS = [
|
|
3572
|
-
"morning-briefing",
|
|
3573
|
-
"initiative-kickoff",
|
|
3574
|
-
"bulk-create",
|
|
3575
|
-
"nightly-recap"
|
|
3576
|
-
];
|
|
3577
|
-
var PLUGIN_MANAGED_SKILL_TARGETS = ["claude", "codex"];
|
|
3578
|
-
var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
|
|
3579
|
-
var ORGX_SKILLS_OWNER = "useorgx";
|
|
3580
|
-
var ORGX_SKILLS_REPO = "skills";
|
|
3581
|
-
var ORGX_SKILLS_REF = "main";
|
|
3582
|
-
var SKILL_EXTENSION_SCOPES = ["user", "workspace", "project"];
|
|
3583
|
-
var COMPOSED_SKILL_MARKER = "ORGX SKILL COMPOSED v1";
|
|
3584
|
-
var EXTENSION_FRONTMATTER_DELIMITER = "---";
|
|
3585
|
-
var CURSOR_RULES_CONTENT = `# OrgX Rules
|
|
3586
|
-
|
|
3587
|
-
- Prefer \`workspace_id\` on OrgX tool calls. \`command_center_id\` is a deprecated alias and should only be used for backwards compatibility. If both are present, they must match.
|
|
3588
|
-
- Treat \`entity_action\` with \`force: true\` as an escape hatch. It bypasses hierarchy and proof checks, so only use it when the user explicitly wants that behavior and say why.
|
|
3589
|
-
- Treat \`scaffold_initiative continue_on_error\` as best-effort creation. Use it only when partial scaffolds are acceptable, and follow up on any failed children.
|
|
3590
|
-
- Preserve \`_context\` when widget or app-rendering flows depend on client, conversation, session, or user metadata. Do not strip or rename it.
|
|
3591
|
-
- Prefer \`list_entities\` for pending approvals and state inspection before reaching for legacy aliases.
|
|
3592
|
-
- Keep hosted OrgX MCP calls distinct from local OpenClaw/plugin surfaces before mutating config or entity state.
|
|
3593
|
-
- Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
|
|
3594
|
-
- Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supplemental, not proof.
|
|
3595
|
-
`;
|
|
3596
|
-
var CLAUDE_ORGX_SKILL_CONTENT = `---
|
|
3597
|
-
name: orgx
|
|
3598
|
-
description: Use when working against the hosted OrgX MCP server for workspace-scoped planning, entity mutations, progress reporting, and decision handling.
|
|
3599
|
-
version: 1.0.0
|
|
3600
|
-
user-invocable: true
|
|
3601
|
-
tags:
|
|
3602
|
-
- orchestration
|
|
3603
|
-
- productivity
|
|
3604
|
-
- workspace
|
|
3605
|
-
---
|
|
3606
|
-
|
|
3607
|
-
# OrgX Hosted MCP
|
|
3608
|
-
|
|
3609
|
-
Use this skill when Claude is connected to the hosted OrgX MCP server at \`https://mcp.useorgx.com/mcp\`.
|
|
3610
|
-
|
|
3611
|
-
This skill is for the hosted OrgX tool surface, not the local OpenClaw plugin. Keep those surfaces distinct before mutating config or entity state.
|
|
3612
|
-
|
|
3613
|
-
## Operating Rules
|
|
3614
|
-
|
|
3615
|
-
- Prefer \`workspace_id\` on OrgX tools. \`command_center_id\` is a deprecated alias kept for compatibility.
|
|
3616
|
-
- Treat \`entity_action\` with \`force: true\` as a deliberate override only. It bypasses hierarchy and proof-chain checks.
|
|
3617
|
-
- Treat \`scaffold_initiative continue_on_error\` as best-effort scaffolding. Use it only when partial creation is acceptable and inspect failures afterward.
|
|
3618
|
-
- Preserve \`_context\` when widget or app rendering depends on client, conversation, session, or user metadata.
|
|
3619
|
-
- Prefer \`list_entities\` over older aliases when you need pending decisions, blockers, or entity state.
|
|
3620
|
-
- Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
|
|
3621
|
-
- Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supporting context only.
|
|
3622
|
-
|
|
3623
|
-
## Suggested Skill Packs
|
|
3624
|
-
|
|
3625
|
-
Install and use these packs alongside this base skill:
|
|
3626
|
-
|
|
3627
|
-
- \`morning-briefing\`
|
|
3628
|
-
- \`initiative-kickoff\`
|
|
3629
|
-
- \`bulk-create\`
|
|
3630
|
-
- \`nightly-recap\`
|
|
3631
|
-
|
|
3632
|
-
## Suggested Workflow
|
|
3633
|
-
|
|
3634
|
-
1. Inspect state with \`mcp__orgx__list_entities\`, \`mcp__orgx__get_initiative_pulse\`, or the relevant workspace-scoped read tool.
|
|
3635
|
-
2. Mutate state with the narrowest tool that matches the task.
|
|
3636
|
-
3. When you need a hard override, explain why before using \`force: true\`.
|
|
3637
|
-
4. When you scaffold, decide up front whether \`continue_on_error\` is acceptable.
|
|
3638
|
-
5. Carry \`_context\` through any widget-producing flows so the UI can render and resume correctly.
|
|
3639
|
-
`;
|
|
3640
|
-
function sha256(value) {
|
|
3641
|
-
return createHash2("sha256").update(value).digest("hex");
|
|
3373
|
+
return files.sort();
|
|
3642
3374
|
}
|
|
3643
|
-
function
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
throw new Error(
|
|
3647
|
-
"Skill id must start with a letter or number and only contain letters, numbers, '.', '_' or '-'."
|
|
3648
|
-
);
|
|
3375
|
+
function pruneEmptyDirectories(root, current = root) {
|
|
3376
|
+
if (!existsSync4(current) || !statSync2(current).isDirectory()) {
|
|
3377
|
+
return false;
|
|
3649
3378
|
}
|
|
3650
|
-
|
|
3651
|
-
}
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
if (!SKILL_EXTENSION_SCOPES.includes(normalized)) {
|
|
3655
|
-
throw new Error("Skill extension scope must be one of: user, workspace, project.");
|
|
3379
|
+
let changed = false;
|
|
3380
|
+
for (const entry of readdirSync2(current, { withFileTypes: true })) {
|
|
3381
|
+
if (!entry.isDirectory()) continue;
|
|
3382
|
+
changed = pruneEmptyDirectories(root, join3(current, entry.name)) || changed;
|
|
3656
3383
|
}
|
|
3657
|
-
|
|
3658
|
-
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
return
|
|
3662
|
-
}
|
|
3663
|
-
function extensionFilePath(skillId, scope, extensionsDir = ORGX_SKILL_EXTENSIONS_DIR) {
|
|
3664
|
-
return join2(extensionsDir, `${scope}.${skillId}.md`);
|
|
3665
|
-
}
|
|
3666
|
-
function extensionTemplate(input) {
|
|
3667
|
-
const body = input.content?.trim() ? input.content.trim() : [
|
|
3668
|
-
`# ${input.title}`,
|
|
3669
|
-
"",
|
|
3670
|
-
"- Add your custom behavior here.",
|
|
3671
|
-
"- These instructions are appended after the OrgX-managed core skill when the wizard syncs skills."
|
|
3672
|
-
].join("\n");
|
|
3673
|
-
return [
|
|
3674
|
-
EXTENSION_FRONTMATTER_DELIMITER,
|
|
3675
|
-
`skill: ${input.skillId}`,
|
|
3676
|
-
`scope: ${input.scope}`,
|
|
3677
|
-
"enabled: true",
|
|
3678
|
-
`title: ${input.title}`,
|
|
3679
|
-
EXTENSION_FRONTMATTER_DELIMITER,
|
|
3680
|
-
"",
|
|
3681
|
-
body,
|
|
3682
|
-
""
|
|
3683
|
-
].join("\n");
|
|
3384
|
+
if (current !== root && readdirSync2(current).length === 0) {
|
|
3385
|
+
rmSync(current, { force: true, recursive: true });
|
|
3386
|
+
return true;
|
|
3387
|
+
}
|
|
3388
|
+
return changed;
|
|
3684
3389
|
}
|
|
3685
|
-
function
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3390
|
+
async function syncManagedRepoTree(spec, destinationRoot, fetchImpl) {
|
|
3391
|
+
const remoteFiles = await collectRemoteRepoFiles(spec, fetchImpl);
|
|
3392
|
+
let changed = false;
|
|
3393
|
+
const expected = new Set(remoteFiles.map((file) => file.localPath));
|
|
3394
|
+
if (existsSync4(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
|
|
3395
|
+
rmSync(destinationRoot, { force: true, recursive: true });
|
|
3396
|
+
changed = true;
|
|
3689
3397
|
}
|
|
3690
|
-
const
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
return { body: content, data: {} };
|
|
3398
|
+
for (const file of listRelativeFiles(destinationRoot)) {
|
|
3399
|
+
if (expected.has(file)) continue;
|
|
3400
|
+
rmSync(join3(destinationRoot, file), { force: true });
|
|
3401
|
+
changed = true;
|
|
3695
3402
|
}
|
|
3696
|
-
|
|
3697
|
-
const
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
if (index === -1) {
|
|
3702
|
-
continue;
|
|
3703
|
-
}
|
|
3704
|
-
const key = line.slice(0, index).trim();
|
|
3705
|
-
const value = line.slice(index + 1).trim();
|
|
3706
|
-
if (key) {
|
|
3707
|
-
data[key] = value;
|
|
3403
|
+
changed = pruneEmptyDirectories(destinationRoot) || changed;
|
|
3404
|
+
for (const file of remoteFiles) {
|
|
3405
|
+
const bytes = await fetchRemoteBytes(file.sourceUrl, fetchImpl);
|
|
3406
|
+
if (writeBytesIfChanged(join3(destinationRoot, file.localPath), bytes)) {
|
|
3407
|
+
changed = true;
|
|
3708
3408
|
}
|
|
3709
3409
|
}
|
|
3710
|
-
return {
|
|
3711
|
-
}
|
|
3712
|
-
function serializeFrontmatter(data, body) {
|
|
3713
|
-
return [
|
|
3714
|
-
EXTENSION_FRONTMATTER_DELIMITER,
|
|
3715
|
-
...Object.entries(data).map(([key, value]) => `${key}: ${value}`),
|
|
3716
|
-
EXTENSION_FRONTMATTER_DELIMITER,
|
|
3717
|
-
"",
|
|
3718
|
-
body.trimEnd(),
|
|
3719
|
-
""
|
|
3720
|
-
].join("\n");
|
|
3721
|
-
}
|
|
3722
|
-
function parseSkillExtension(path, content) {
|
|
3723
|
-
const fallbackId = basename(path, ".md");
|
|
3724
|
-
const fallbackParts = fallbackId.split(".");
|
|
3725
|
-
const fallbackScope = normalizeSkillExtensionScope(
|
|
3726
|
-
SKILL_EXTENSION_SCOPES.includes(fallbackParts[0]) ? fallbackParts.shift() : "user"
|
|
3727
|
-
);
|
|
3728
|
-
const fallbackSkillId = normalizeSkillId(fallbackParts.join(".") || fallbackId);
|
|
3729
|
-
const parsed = parseFrontmatter(content);
|
|
3730
|
-
const skillId = normalizeSkillId(parsed.data.skill || fallbackSkillId);
|
|
3731
|
-
const scope = normalizeSkillExtensionScope(parsed.data.scope || fallbackScope);
|
|
3732
|
-
const title = parsed.data.title?.trim() || defaultExtensionTitle(skillId, scope);
|
|
3733
|
-
return {
|
|
3734
|
-
content: parsed.body.trim(),
|
|
3735
|
-
enabled: parsed.data.enabled?.trim().toLowerCase() !== "false",
|
|
3736
|
-
id: `${scope}.${skillId}`,
|
|
3737
|
-
path,
|
|
3738
|
-
scope,
|
|
3739
|
-
skillId,
|
|
3740
|
-
title
|
|
3741
|
-
};
|
|
3410
|
+
return { changed, fileCount: remoteFiles.length };
|
|
3742
3411
|
}
|
|
3743
|
-
function
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
return [];
|
|
3747
|
-
}
|
|
3748
|
-
return readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => {
|
|
3749
|
-
const path = join2(extensionsDir, entry.name);
|
|
3750
|
-
const content = readTextIfExists(path);
|
|
3751
|
-
return content === null ? null : parseSkillExtension(path, content);
|
|
3752
|
-
}).filter((entry) => Boolean(entry)).sort((left, right) => left.id.localeCompare(right.id));
|
|
3412
|
+
function serializeJson(value) {
|
|
3413
|
+
return `${JSON.stringify(value, null, 2)}
|
|
3414
|
+
`;
|
|
3753
3415
|
}
|
|
3754
|
-
function
|
|
3755
|
-
const
|
|
3756
|
-
const scope = normalizeSkillExtensionScope(options.scope);
|
|
3757
|
-
const path = extensionFilePath(skillId, scope, options.extensionsDir);
|
|
3758
|
-
const title = options.title?.trim() || defaultExtensionTitle(skillId, scope);
|
|
3416
|
+
function writeJsonIfChanged(path, value) {
|
|
3417
|
+
const next = serializeJson(value);
|
|
3759
3418
|
const existing = readTextIfExists(path);
|
|
3760
|
-
if (existing
|
|
3761
|
-
|
|
3762
|
-
if (!extension2) {
|
|
3763
|
-
throw new Error(`Could not parse existing skill extension at ${path}.`);
|
|
3764
|
-
}
|
|
3765
|
-
return {
|
|
3766
|
-
changed: false,
|
|
3767
|
-
created: false,
|
|
3768
|
-
extension: extension2,
|
|
3769
|
-
path
|
|
3770
|
-
};
|
|
3771
|
-
}
|
|
3772
|
-
const nextContent = extensionTemplate({
|
|
3773
|
-
scope,
|
|
3774
|
-
skillId,
|
|
3775
|
-
title,
|
|
3776
|
-
...options.content !== void 0 ? { content: options.content } : {}
|
|
3777
|
-
});
|
|
3778
|
-
writeTextFile(path, nextContent);
|
|
3779
|
-
const extension = parseSkillExtension(path, nextContent);
|
|
3780
|
-
if (!extension) {
|
|
3781
|
-
throw new Error(`Could not parse newly written skill extension at ${path}.`);
|
|
3419
|
+
if (existing === next) {
|
|
3420
|
+
return false;
|
|
3782
3421
|
}
|
|
3422
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
3423
|
+
writeFileSync2(path, next, "utf8");
|
|
3424
|
+
return true;
|
|
3425
|
+
}
|
|
3426
|
+
function buildClaudeMarketplaceManifest() {
|
|
3783
3427
|
return {
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3428
|
+
$schema: "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3429
|
+
name: ORGX_CLAUDE_MARKETPLACE_NAME,
|
|
3430
|
+
description: "Local OrgX plugins",
|
|
3431
|
+
owner: {
|
|
3432
|
+
name: "OrgX Team"
|
|
3433
|
+
},
|
|
3434
|
+
plugins: [
|
|
3435
|
+
{
|
|
3436
|
+
name: ORGX_CLAUDE_PLUGIN_NAME,
|
|
3437
|
+
description: "OrgX MCP tools and runtime telemetry hooks for Claude Code.",
|
|
3438
|
+
source: `./plugins/${ORGX_CLAUDE_PLUGIN_NAME}`
|
|
3439
|
+
}
|
|
3440
|
+
]
|
|
3788
3441
|
};
|
|
3789
3442
|
}
|
|
3790
|
-
function
|
|
3791
|
-
const skillId = normalizeSkillId(input.skillId);
|
|
3792
|
-
const scope = normalizeSkillExtensionScope(input.scope);
|
|
3793
|
-
const path = extensionFilePath(skillId, scope, input.extensionsDir);
|
|
3794
|
-
const existing = readTextIfExists(path);
|
|
3795
|
-
if (existing === null) {
|
|
3796
|
-
if (!input.enabled) {
|
|
3797
|
-
throw new Error(`No ${scope} extension exists for ${skillId}.`);
|
|
3798
|
-
}
|
|
3799
|
-
return addSkillExtension({
|
|
3800
|
-
scope,
|
|
3801
|
-
skillId,
|
|
3802
|
-
...input.extensionsDir !== void 0 ? { extensionsDir: input.extensionsDir } : {}
|
|
3803
|
-
});
|
|
3804
|
-
}
|
|
3805
|
-
const parsed = parseFrontmatter(existing);
|
|
3806
|
-
const data = {
|
|
3807
|
-
skill: parsed.data.skill || skillId,
|
|
3808
|
-
scope: parsed.data.scope || scope,
|
|
3809
|
-
enabled: input.enabled ? "true" : "false",
|
|
3810
|
-
title: parsed.data.title || defaultExtensionTitle(skillId, scope)
|
|
3811
|
-
};
|
|
3812
|
-
const nextContent = serializeFrontmatter(data, parsed.body);
|
|
3813
|
-
if (nextContent !== existing) {
|
|
3814
|
-
writeTextFile(path, nextContent);
|
|
3815
|
-
}
|
|
3816
|
-
const extension = parseSkillExtension(path, nextContent);
|
|
3817
|
-
if (!extension) {
|
|
3818
|
-
throw new Error(`Could not parse skill extension at ${path}.`);
|
|
3819
|
-
}
|
|
3443
|
+
function buildCodexMarketplaceEntry() {
|
|
3820
3444
|
return {
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3445
|
+
name: ORGX_CODEX_PLUGIN_NAME,
|
|
3446
|
+
source: {
|
|
3447
|
+
source: "local",
|
|
3448
|
+
path: `./.codex/plugins/${ORGX_CODEX_PLUGIN_NAME}`
|
|
3449
|
+
},
|
|
3450
|
+
policy: {
|
|
3451
|
+
installation: "AVAILABLE",
|
|
3452
|
+
authentication: "ON_INSTALL"
|
|
3453
|
+
},
|
|
3454
|
+
category: "Productivity"
|
|
3825
3455
|
};
|
|
3826
3456
|
}
|
|
3827
|
-
function
|
|
3828
|
-
const
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
}
|
|
3832
|
-
const extensionBlocks = enabled.map((extension) => [
|
|
3833
|
-
`<!-- extension: ${extension.id} -->`,
|
|
3834
|
-
extension.content.trim()
|
|
3835
|
-
].join("\n"));
|
|
3836
|
-
return [
|
|
3837
|
-
`<!-- ${COMPOSED_SKILL_MARKER}`,
|
|
3838
|
-
`skill: ${skillId}`,
|
|
3839
|
-
`core-sha256: ${sha256(coreContent)}`,
|
|
3840
|
-
"generated: true",
|
|
3841
|
-
`edit-with: orgx-wizard skills extensions edit ${skillId}`,
|
|
3842
|
-
"-->",
|
|
3843
|
-
`<!-- ORGX CORE BEGIN ${skillId} -->`,
|
|
3844
|
-
coreContent.trimEnd(),
|
|
3845
|
-
`<!-- ORGX CORE END ${skillId} -->`,
|
|
3846
|
-
"",
|
|
3847
|
-
"<!-- ORGX USER EXTENSIONS BEGIN -->",
|
|
3848
|
-
...extensionBlocks,
|
|
3849
|
-
"<!-- ORGX USER EXTENSIONS END -->",
|
|
3850
|
-
""
|
|
3851
|
-
].join("\n");
|
|
3852
|
-
}
|
|
3853
|
-
function encodeRepoPath(value) {
|
|
3854
|
-
return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
|
|
3457
|
+
function readMarketplacePlugins(path) {
|
|
3458
|
+
const document = parseJsonObject(readTextIfExists(path));
|
|
3459
|
+
const plugins = Array.isArray(document.plugins) ? document.plugins.filter((value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)) : [];
|
|
3460
|
+
return { document, plugins };
|
|
3855
3461
|
}
|
|
3856
|
-
function
|
|
3857
|
-
const
|
|
3858
|
-
|
|
3462
|
+
function upsertCodexMarketplaceEntry(path) {
|
|
3463
|
+
const { document, plugins } = readMarketplacePlugins(path);
|
|
3464
|
+
const nextEntry = buildCodexMarketplaceEntry();
|
|
3465
|
+
let replaced = false;
|
|
3466
|
+
const nextPlugins = plugins.map((plugin) => {
|
|
3467
|
+
if (plugin.name !== ORGX_CODEX_PLUGIN_NAME) {
|
|
3468
|
+
return plugin;
|
|
3469
|
+
}
|
|
3470
|
+
replaced = true;
|
|
3471
|
+
return nextEntry;
|
|
3472
|
+
});
|
|
3473
|
+
if (!replaced) {
|
|
3474
|
+
nextPlugins.push(nextEntry);
|
|
3475
|
+
}
|
|
3476
|
+
const nextDocument = {
|
|
3477
|
+
...document,
|
|
3478
|
+
...document.name ? {} : { name: ORGX_CLAUDE_MARKETPLACE_NAME },
|
|
3479
|
+
...readObject(document.interface).displayName ? {} : { interface: { displayName: "OrgX Local" } },
|
|
3480
|
+
plugins: nextPlugins
|
|
3481
|
+
};
|
|
3482
|
+
return writeJsonIfChanged(path, nextDocument);
|
|
3859
3483
|
}
|
|
3860
|
-
function
|
|
3861
|
-
if (
|
|
3862
|
-
return
|
|
3484
|
+
function removeCodexMarketplaceEntry(path) {
|
|
3485
|
+
if (!existsSync4(path)) {
|
|
3486
|
+
return false;
|
|
3863
3487
|
}
|
|
3864
|
-
|
|
3488
|
+
const { document, plugins } = readMarketplacePlugins(path);
|
|
3489
|
+
const nextPlugins = plugins.filter((plugin) => plugin.name !== ORGX_CODEX_PLUGIN_NAME);
|
|
3490
|
+
if (nextPlugins.length === plugins.length) {
|
|
3491
|
+
return false;
|
|
3492
|
+
}
|
|
3493
|
+
if (nextPlugins.length === 0) {
|
|
3494
|
+
rmSync(path, { force: true });
|
|
3495
|
+
return true;
|
|
3496
|
+
}
|
|
3497
|
+
return writeJsonIfChanged(path, {
|
|
3498
|
+
...document,
|
|
3499
|
+
plugins: nextPlugins
|
|
3500
|
+
});
|
|
3865
3501
|
}
|
|
3866
|
-
|
|
3867
|
-
|
|
3502
|
+
function codexMarketplaceHasOrgxEntry(path) {
|
|
3503
|
+
const { plugins } = readMarketplacePlugins(path);
|
|
3504
|
+
return plugins.some((plugin) => plugin.name === ORGX_CODEX_PLUGIN_NAME);
|
|
3868
3505
|
}
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3506
|
+
function extractClaudePluginNames(payload) {
|
|
3507
|
+
try {
|
|
3508
|
+
const parsed = JSON.parse(payload);
|
|
3509
|
+
if (!Array.isArray(parsed)) return [];
|
|
3510
|
+
return parsed.flatMap((entry) => {
|
|
3511
|
+
if (typeof entry === "string") return [entry];
|
|
3512
|
+
if (!entry || typeof entry !== "object") return [];
|
|
3513
|
+
if (typeof entry.name === "string") return [entry.name];
|
|
3514
|
+
if (typeof entry.id === "string") return [entry.id];
|
|
3515
|
+
return [];
|
|
3516
|
+
});
|
|
3517
|
+
} catch {
|
|
3518
|
+
return [];
|
|
3882
3519
|
}
|
|
3883
|
-
return data.filter((entry) => {
|
|
3884
|
-
if (!entry || typeof entry !== "object") return false;
|
|
3885
|
-
return entry.type === "file" || entry.type === "dir";
|
|
3886
|
-
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
3887
3520
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3521
|
+
function extractOpenclawPluginIds(payload) {
|
|
3522
|
+
try {
|
|
3523
|
+
const parsed = JSON.parse(payload);
|
|
3524
|
+
if (Array.isArray(parsed)) {
|
|
3525
|
+
return parsed.flatMap((entry) => {
|
|
3526
|
+
if (!entry || typeof entry !== "object") return [];
|
|
3527
|
+
if (typeof entry.id === "string") return [entry.id];
|
|
3528
|
+
if (typeof entry.name === "string") return [entry.name];
|
|
3529
|
+
return [];
|
|
3530
|
+
});
|
|
3898
3531
|
}
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3532
|
+
const root = readObject(parsed);
|
|
3533
|
+
const plugins = Array.isArray(root.plugins) ? root.plugins : [];
|
|
3534
|
+
return plugins.flatMap((entry) => {
|
|
3535
|
+
if (!entry || typeof entry !== "object") return [];
|
|
3536
|
+
const item = entry;
|
|
3537
|
+
if (typeof item.id === "string") return [item.id];
|
|
3538
|
+
if (typeof item.name === "string") return [item.name];
|
|
3539
|
+
return [];
|
|
3902
3540
|
});
|
|
3541
|
+
} catch {
|
|
3542
|
+
return [];
|
|
3903
3543
|
}
|
|
3904
|
-
return files;
|
|
3905
3544
|
}
|
|
3906
|
-
|
|
3907
|
-
const
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
},
|
|
3911
|
-
signal: AbortSignal.timeout(1e4)
|
|
3912
|
-
});
|
|
3913
|
-
if (!response.ok) {
|
|
3914
|
-
throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
|
|
3545
|
+
function formatCommandFailure(command, args, result) {
|
|
3546
|
+
const detail = [result.stderr.trim(), result.stdout.trim()].find((value) => value.length > 0);
|
|
3547
|
+
if (result.errorCode === "ENOENT") {
|
|
3548
|
+
return `Command '${command}' is not available on PATH.`;
|
|
3915
3549
|
}
|
|
3916
|
-
return
|
|
3550
|
+
return `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
|
|
3917
3551
|
}
|
|
3918
|
-
function
|
|
3919
|
-
return
|
|
3552
|
+
async function defaultCommandRunner(command, args) {
|
|
3553
|
+
return await new Promise((resolve2) => {
|
|
3554
|
+
const child = spawn(command, [...args], {
|
|
3555
|
+
env: process.env,
|
|
3556
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3557
|
+
});
|
|
3558
|
+
let stdout = "";
|
|
3559
|
+
let stderr = "";
|
|
3560
|
+
child.stdout.on("data", (chunk) => {
|
|
3561
|
+
stdout += chunk.toString();
|
|
3562
|
+
});
|
|
3563
|
+
child.stderr.on("data", (chunk) => {
|
|
3564
|
+
stderr += chunk.toString();
|
|
3565
|
+
});
|
|
3566
|
+
child.on("error", (error) => {
|
|
3567
|
+
const errorCode = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
3568
|
+
resolve2({
|
|
3569
|
+
exitCode: -1,
|
|
3570
|
+
stdout,
|
|
3571
|
+
stderr,
|
|
3572
|
+
...errorCode ? { errorCode } : {}
|
|
3573
|
+
});
|
|
3574
|
+
});
|
|
3575
|
+
child.on("close", (code) => {
|
|
3576
|
+
resolve2({
|
|
3577
|
+
exitCode: code ?? -1,
|
|
3578
|
+
stdout,
|
|
3579
|
+
stderr
|
|
3580
|
+
});
|
|
3581
|
+
});
|
|
3582
|
+
});
|
|
3920
3583
|
}
|
|
3921
|
-
function
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
};
|
|
3933
|
-
}
|
|
3934
|
-
const changed = existing !== content;
|
|
3935
|
-
if (changed) {
|
|
3936
|
-
writeTextFile(path, content);
|
|
3584
|
+
function resolveCommandRunner(runner) {
|
|
3585
|
+
return runner ?? defaultCommandRunner;
|
|
3586
|
+
}
|
|
3587
|
+
async function commandExists(command, runner) {
|
|
3588
|
+
const result = await runner(command, ["--help"]);
|
|
3589
|
+
return result.errorCode !== "ENOENT" && result.exitCode !== 127;
|
|
3590
|
+
}
|
|
3591
|
+
async function getClaudeInstallState(runner) {
|
|
3592
|
+
const available = detectSurface("claude").detected || await commandExists("claude", runner);
|
|
3593
|
+
if (!available) {
|
|
3594
|
+
return { available: false, installed: false };
|
|
3937
3595
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
...tracking.coreContent ? { coreSha256: sha256(tracking.coreContent) } : {},
|
|
3942
|
-
path,
|
|
3943
|
-
skillId: tracking.skillId,
|
|
3944
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3945
|
-
});
|
|
3596
|
+
const result = await runner("claude", ["plugin", "list", "--json"]);
|
|
3597
|
+
if (result.exitCode !== 0) {
|
|
3598
|
+
return { available: true, installed: false };
|
|
3946
3599
|
}
|
|
3947
3600
|
return {
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
changed,
|
|
3951
|
-
...sourceUrl ? { sourceUrl } : {}
|
|
3601
|
+
available: true,
|
|
3602
|
+
installed: extractClaudePluginNames(result.stdout).includes(ORGX_CLAUDE_PLUGIN_NAME)
|
|
3952
3603
|
};
|
|
3953
3604
|
}
|
|
3954
|
-
function
|
|
3955
|
-
|
|
3956
|
-
|
|
3605
|
+
async function getOpenclawInstallState(runner) {
|
|
3606
|
+
const available = detectSurface("openclaw").detected || await commandExists("openclaw", runner);
|
|
3607
|
+
if (!available) {
|
|
3608
|
+
return { available: false, installed: false };
|
|
3957
3609
|
}
|
|
3958
|
-
const
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
const next = name.trim();
|
|
3962
|
-
if (!next || seen.has(next)) {
|
|
3963
|
-
continue;
|
|
3964
|
-
}
|
|
3965
|
-
seen.add(next);
|
|
3966
|
-
normalized.push(next);
|
|
3610
|
+
const result = await runner("openclaw", ["plugins", "list", "--json"]);
|
|
3611
|
+
if (result.exitCode !== 0) {
|
|
3612
|
+
return { available: true, installed: false };
|
|
3967
3613
|
}
|
|
3968
|
-
return
|
|
3614
|
+
return {
|
|
3615
|
+
available: true,
|
|
3616
|
+
installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
|
|
3617
|
+
};
|
|
3969
3618
|
}
|
|
3970
|
-
function
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3619
|
+
function cursorPluginManifestPath(paths) {
|
|
3620
|
+
return join3(paths.cursorPluginDir, ".cursor-plugin", "plugin.json");
|
|
3621
|
+
}
|
|
3622
|
+
function cursorPluginMcpPath(paths) {
|
|
3623
|
+
return join3(paths.cursorPluginDir, ".mcp.json");
|
|
3624
|
+
}
|
|
3625
|
+
function isCursorPluginInstalled(paths) {
|
|
3626
|
+
return existsSync4(cursorPluginManifestPath(paths)) && existsSync4(cursorPluginMcpPath(paths));
|
|
3627
|
+
}
|
|
3628
|
+
function getCursorToolingState(options = {}) {
|
|
3629
|
+
const paths = resolvePluginPaths(options.paths);
|
|
3630
|
+
const mcpFile = options.mcpPath ?? CURSOR_MCP_PATH ?? join3(CURSOR_DIR, "mcp.json");
|
|
3631
|
+
const mcpText = readTextIfExists(mcpFile);
|
|
3632
|
+
const inspection = inspectCursorMcpConfig(mcpText);
|
|
3633
|
+
return {
|
|
3634
|
+
pluginBundleInstalled: isCursorPluginInstalled(paths),
|
|
3635
|
+
mcpLocalOpenclaw: inspection.localConfigured === true,
|
|
3636
|
+
mcpHostedOrgx: inspection.hostedConfigured === true
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
function isCursorPluginAvailable(paths) {
|
|
3640
|
+
return detectSurface("cursor").detected || existsSync4(paths.cursorPluginDir) || existsSync4(dirname3(paths.cursorPluginDir)) || readTextIfExists(paths.cursorRulePath) !== null;
|
|
3641
|
+
}
|
|
3642
|
+
function buildCursorStatus(paths) {
|
|
3643
|
+
const existingRules = readTextIfExists(paths.cursorRulePath);
|
|
3644
|
+
const available = isCursorPluginAvailable(paths);
|
|
3645
|
+
const installed = isCursorPluginInstalled(paths);
|
|
3646
|
+
if (installed) {
|
|
3647
|
+
return {
|
|
3648
|
+
target: "cursor",
|
|
3649
|
+
available: true,
|
|
3650
|
+
installed: true,
|
|
3651
|
+
message: "Cursor OrgX plugin bundle is installed."
|
|
3652
|
+
};
|
|
3983
3653
|
}
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
notes.push(
|
|
3992
|
-
"Claude Code plugin is installed, so the wizard skipped overlapping standalone Claude OrgX skill files and Claude skill-pack sync."
|
|
3993
|
-
);
|
|
3654
|
+
if (existingRules === CURSOR_RULES_CONTENT) {
|
|
3655
|
+
return {
|
|
3656
|
+
target: "cursor",
|
|
3657
|
+
available: true,
|
|
3658
|
+
installed: false,
|
|
3659
|
+
message: "Legacy Cursor OrgX rules are installed; run plugins add cursor to install the full plugin bundle."
|
|
3660
|
+
};
|
|
3994
3661
|
}
|
|
3995
|
-
if (
|
|
3996
|
-
|
|
3997
|
-
"
|
|
3998
|
-
|
|
3662
|
+
if (existingRules !== null) {
|
|
3663
|
+
return {
|
|
3664
|
+
target: "cursor",
|
|
3665
|
+
available: true,
|
|
3666
|
+
installed: false,
|
|
3667
|
+
message: "Cursor rules file exists, but the OrgX plugin bundle is missing."
|
|
3668
|
+
};
|
|
3999
3669
|
}
|
|
4000
3670
|
return {
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
3671
|
+
target: "cursor",
|
|
3672
|
+
available,
|
|
3673
|
+
installed: false,
|
|
3674
|
+
message: available ? "Cursor is available and ready for OrgX plugin install." : "Cursor was not detected."
|
|
4005
3675
|
};
|
|
4006
3676
|
}
|
|
4007
|
-
async function
|
|
4008
|
-
const
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
file.sourceUrl,
|
|
4025
|
-
{
|
|
4026
|
-
coreContent,
|
|
4027
|
-
records: tracking.records,
|
|
4028
|
-
skillId: skillName,
|
|
4029
|
-
stagedRecords: tracking.stagedRecords,
|
|
4030
|
-
...tracking.force !== void 0 ? { force: tracking.force } : {}
|
|
4031
|
-
}
|
|
4032
|
-
)
|
|
4033
|
-
);
|
|
3677
|
+
async function buildClaudeStatus(paths, runner) {
|
|
3678
|
+
const state = await getClaudeInstallState(runner);
|
|
3679
|
+
if (state.installed) {
|
|
3680
|
+
return {
|
|
3681
|
+
target: "claude",
|
|
3682
|
+
available: true,
|
|
3683
|
+
installed: true,
|
|
3684
|
+
message: "Claude Code plugin is installed."
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
if (!state.available) {
|
|
3688
|
+
return {
|
|
3689
|
+
target: "claude",
|
|
3690
|
+
available: false,
|
|
3691
|
+
installed: false,
|
|
3692
|
+
message: "Claude Code was not detected."
|
|
3693
|
+
};
|
|
4034
3694
|
}
|
|
3695
|
+
const marketplaceExists = existsSync4(paths.claudeMarketplaceManifestPath);
|
|
4035
3696
|
return {
|
|
4036
|
-
|
|
4037
|
-
|
|
3697
|
+
target: "claude",
|
|
3698
|
+
available: true,
|
|
3699
|
+
installed: false,
|
|
3700
|
+
message: marketplaceExists ? "Claude marketplace wrapper exists, but the plugin is not installed." : "Claude Code is available and ready for plugin install."
|
|
4038
3701
|
};
|
|
4039
3702
|
}
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
const enabledExtensions = extensions.filter((extension) => extension.enabled);
|
|
4054
|
-
const trackedRecords = readWizardState(statePath)?.skillFiles?.files ?? [];
|
|
4055
|
-
const stagedRecords = /* @__PURE__ */ new Map();
|
|
4056
|
-
const plan = planOrgxSkillsInstall(options.pluginTargets ?? []);
|
|
4057
|
-
const requestedNames = options.skillNames ?? [];
|
|
4058
|
-
let skillNames;
|
|
4059
|
-
if (requestedNames.length === 0 || requestedNames.includes("all")) {
|
|
4060
|
-
try {
|
|
4061
|
-
skillNames = await fetchAvailablePackNames(fetchImpl, ref);
|
|
4062
|
-
} catch {
|
|
4063
|
-
skillNames = [...DEFAULT_ORGX_SKILL_PACKS];
|
|
3703
|
+
function buildCodexStatus(paths, runner) {
|
|
3704
|
+
return (async () => {
|
|
3705
|
+
const available = detectSurface("codex").detected || await commandExists("codex", runner);
|
|
3706
|
+
const pluginExists = existsSync4(paths.codexPluginDir);
|
|
3707
|
+
const marketplaceExists = codexMarketplaceHasOrgxEntry(paths.codexMarketplacePath);
|
|
3708
|
+
const installed = pluginExists && marketplaceExists;
|
|
3709
|
+
if (installed) {
|
|
3710
|
+
return {
|
|
3711
|
+
target: "codex",
|
|
3712
|
+
available: true,
|
|
3713
|
+
installed: true,
|
|
3714
|
+
message: "Codex plugin files and marketplace entry are present."
|
|
3715
|
+
};
|
|
4064
3716
|
}
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
"cursor-rules",
|
|
4073
|
-
void 0,
|
|
4074
|
-
{
|
|
4075
|
-
coreContent: CURSOR_RULES_CONTENT,
|
|
4076
|
-
records: trackedRecords,
|
|
4077
|
-
skillId: "cursor-rules",
|
|
4078
|
-
stagedRecords,
|
|
4079
|
-
...options.force !== void 0 ? { force: options.force } : {}
|
|
4080
|
-
}
|
|
4081
|
-
)
|
|
4082
|
-
];
|
|
4083
|
-
if (plan.installClaudeSkillBootstrap) {
|
|
4084
|
-
writes.push(
|
|
4085
|
-
writeManagedFile(
|
|
4086
|
-
claudeOrgxSkillPath,
|
|
4087
|
-
composeSkillContent("orgx", CLAUDE_ORGX_SKILL_CONTENT, extensions),
|
|
4088
|
-
"claude-orgx-skill",
|
|
4089
|
-
void 0,
|
|
4090
|
-
{
|
|
4091
|
-
coreContent: CLAUDE_ORGX_SKILL_CONTENT,
|
|
4092
|
-
records: trackedRecords,
|
|
4093
|
-
skillId: "orgx",
|
|
4094
|
-
stagedRecords,
|
|
4095
|
-
...options.force !== void 0 ? { force: options.force } : {}
|
|
4096
|
-
}
|
|
4097
|
-
)
|
|
4098
|
-
);
|
|
4099
|
-
}
|
|
4100
|
-
const packs = [];
|
|
4101
|
-
if (plan.installClaudeSkillPacks) {
|
|
4102
|
-
for (const skillName of skillNames) {
|
|
4103
|
-
packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, {
|
|
4104
|
-
extensions,
|
|
4105
|
-
records: trackedRecords,
|
|
4106
|
-
stagedRecords,
|
|
4107
|
-
...options.force !== void 0 ? { force: options.force } : {}
|
|
4108
|
-
}));
|
|
3717
|
+
if (!available) {
|
|
3718
|
+
return {
|
|
3719
|
+
target: "codex",
|
|
3720
|
+
available: false,
|
|
3721
|
+
installed: false,
|
|
3722
|
+
message: "Codex was not detected."
|
|
3723
|
+
};
|
|
4109
3724
|
}
|
|
4110
|
-
|
|
4111
|
-
if (stagedRecords.size > 0) {
|
|
4112
|
-
updateWizardState((current) => {
|
|
4113
|
-
const previousFiles = current.skillFiles?.files ?? [];
|
|
4114
|
-
const nextFiles = /* @__PURE__ */ new Map();
|
|
4115
|
-
for (const record of previousFiles) {
|
|
4116
|
-
nextFiles.set(record.path, record);
|
|
4117
|
-
}
|
|
4118
|
-
for (const record of stagedRecords.values()) {
|
|
4119
|
-
nextFiles.set(record.path, record);
|
|
4120
|
-
}
|
|
3725
|
+
if (pluginExists && !marketplaceExists) {
|
|
4121
3726
|
return {
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
}
|
|
3727
|
+
target: "codex",
|
|
3728
|
+
available: true,
|
|
3729
|
+
installed: false,
|
|
3730
|
+
message: "Codex plugin files exist, but the marketplace entry is missing."
|
|
4127
3731
|
};
|
|
4128
|
-
}
|
|
3732
|
+
}
|
|
3733
|
+
if (!pluginExists && marketplaceExists) {
|
|
3734
|
+
return {
|
|
3735
|
+
target: "codex",
|
|
3736
|
+
available: true,
|
|
3737
|
+
installed: false,
|
|
3738
|
+
message: "Codex marketplace entry exists, but plugin files are missing."
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
return {
|
|
3742
|
+
target: "codex",
|
|
3743
|
+
available: true,
|
|
3744
|
+
installed: false,
|
|
3745
|
+
message: "Codex is available and ready for plugin install."
|
|
3746
|
+
};
|
|
3747
|
+
})();
|
|
3748
|
+
}
|
|
3749
|
+
async function buildOpenclawStatus(runner) {
|
|
3750
|
+
const state = await getOpenclawInstallState(runner);
|
|
3751
|
+
if (state.installed) {
|
|
3752
|
+
return {
|
|
3753
|
+
target: "openclaw",
|
|
3754
|
+
available: true,
|
|
3755
|
+
installed: true,
|
|
3756
|
+
message: "OpenClaw plugin is installed."
|
|
3757
|
+
};
|
|
4129
3758
|
}
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
3759
|
+
return {
|
|
3760
|
+
target: "openclaw",
|
|
3761
|
+
available: state.available,
|
|
3762
|
+
installed: false,
|
|
3763
|
+
message: state.available ? "OpenClaw is available and ready for plugin install." : "OpenClaw was not detected."
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
async function resolveOpenclawTarball(fetchImpl) {
|
|
3767
|
+
const response = await fetchImpl(
|
|
3768
|
+
`${NPM_REGISTRY_BASE_URL}/${encodeURIComponent(ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME)}`,
|
|
3769
|
+
{
|
|
3770
|
+
headers: {
|
|
3771
|
+
Accept: "application/json"
|
|
3772
|
+
},
|
|
3773
|
+
signal: AbortSignal.timeout(1e4)
|
|
3774
|
+
}
|
|
3775
|
+
);
|
|
3776
|
+
if (!response.ok) {
|
|
3777
|
+
throw new Error(
|
|
3778
|
+
`npm registry returned HTTP ${response.status} while resolving ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`
|
|
4134
3779
|
);
|
|
4135
3780
|
}
|
|
4136
|
-
|
|
4137
|
-
|
|
3781
|
+
const data = await response.json();
|
|
3782
|
+
const latestVersion = data["dist-tags"]?.latest;
|
|
3783
|
+
const tarballUrl = latestVersion ? data.versions?.[latestVersion]?.dist?.tarball : void 0;
|
|
3784
|
+
if (!latestVersion || !tarballUrl) {
|
|
3785
|
+
throw new Error(`Could not resolve a tarball URL for ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`);
|
|
4138
3786
|
}
|
|
4139
3787
|
return {
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
writes,
|
|
4143
|
-
packs
|
|
3788
|
+
tarballUrl,
|
|
3789
|
+
version: latestVersion
|
|
4144
3790
|
};
|
|
4145
3791
|
}
|
|
4146
|
-
function
|
|
4147
|
-
|
|
3792
|
+
async function installCursorPlugin(paths, fetchImpl) {
|
|
3793
|
+
if (!isCursorPluginAvailable(paths)) {
|
|
3794
|
+
return {
|
|
3795
|
+
target: "cursor",
|
|
3796
|
+
changed: false,
|
|
3797
|
+
message: "Cursor is not available on this machine."
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
const syncResult = await syncManagedRepoTree(CURSOR_PLUGIN_SYNC_SPEC, paths.cursorPluginDir, fetchImpl);
|
|
4148
3801
|
return {
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
trackedFiles: state?.skillFiles?.files ?? []
|
|
3802
|
+
target: "cursor",
|
|
3803
|
+
changed: syncResult.changed,
|
|
3804
|
+
message: syncResult.changed ? `Synced ${syncResult.fileCount} Cursor plugin files into ${paths.cursorPluginDir}. Reload Cursor to finish loading the plugin.` : "Cursor plugin bundle is already installed and up to date."
|
|
4153
3805
|
};
|
|
4154
3806
|
}
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
{ localPath: "assets", remotePath: "assets" },
|
|
4201
|
-
{ localPath: "skills", remotePath: "skills" }
|
|
4202
|
-
]
|
|
4203
|
-
};
|
|
4204
|
-
var CURSOR_PLUGIN_SYNC_SPEC = {
|
|
4205
|
-
owner: ORGX_PLUGIN_GITHUB_OWNER,
|
|
4206
|
-
repo: ORGX_CURSOR_PLUGIN_NAME,
|
|
4207
|
-
ref: ORGX_PLUGIN_GITHUB_REF,
|
|
4208
|
-
include: [
|
|
4209
|
-
{ localPath: ".cursor-plugin", remotePath: ".cursor-plugin" },
|
|
4210
|
-
{ localPath: ".mcp.json", remotePath: ".mcp.json" },
|
|
4211
|
-
{ localPath: "agents", remotePath: "agents" },
|
|
4212
|
-
{ localPath: "assets", remotePath: "assets" },
|
|
4213
|
-
{ localPath: "commands", remotePath: "commands" },
|
|
4214
|
-
{ localPath: "hooks", remotePath: "hooks" },
|
|
4215
|
-
{ localPath: "rules", remotePath: "rules" },
|
|
4216
|
-
{ localPath: "scripts", remotePath: "scripts" },
|
|
4217
|
-
{ localPath: "skills", remotePath: "skills" }
|
|
4218
|
-
]
|
|
4219
|
-
};
|
|
4220
|
-
function defaultPluginPaths() {
|
|
3807
|
+
async function installClaudePlugin(paths, fetchImpl, runner) {
|
|
3808
|
+
const state = await getClaudeInstallState(runner);
|
|
3809
|
+
if (!state.available) {
|
|
3810
|
+
return {
|
|
3811
|
+
target: "claude",
|
|
3812
|
+
changed: false,
|
|
3813
|
+
message: "Claude Code is not available on this machine."
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
const syncResult = await syncManagedRepoTree(CLAUDE_PLUGIN_SYNC_SPEC, paths.claudePluginDir, fetchImpl);
|
|
3817
|
+
const manifestChanged = writeJsonIfChanged(
|
|
3818
|
+
paths.claudeMarketplaceManifestPath,
|
|
3819
|
+
buildClaudeMarketplaceManifest()
|
|
3820
|
+
);
|
|
3821
|
+
const marketplaceAdd = await runner("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir]);
|
|
3822
|
+
if (marketplaceAdd.exitCode !== 0) {
|
|
3823
|
+
throw new Error(formatCommandFailure("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir], marketplaceAdd));
|
|
3824
|
+
}
|
|
3825
|
+
let installedChanged = false;
|
|
3826
|
+
if (!state.installed) {
|
|
3827
|
+
const install = await runner("claude", [
|
|
3828
|
+
"plugin",
|
|
3829
|
+
"install",
|
|
3830
|
+
`${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
|
|
3831
|
+
"--scope",
|
|
3832
|
+
"user"
|
|
3833
|
+
]);
|
|
3834
|
+
if (install.exitCode !== 0) {
|
|
3835
|
+
throw new Error(
|
|
3836
|
+
formatCommandFailure(
|
|
3837
|
+
"claude",
|
|
3838
|
+
[
|
|
3839
|
+
"plugin",
|
|
3840
|
+
"install",
|
|
3841
|
+
`${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
|
|
3842
|
+
"--scope",
|
|
3843
|
+
"user"
|
|
3844
|
+
],
|
|
3845
|
+
install
|
|
3846
|
+
)
|
|
3847
|
+
);
|
|
3848
|
+
}
|
|
3849
|
+
installedChanged = true;
|
|
3850
|
+
}
|
|
3851
|
+
const changed = syncResult.changed || manifestChanged || installedChanged;
|
|
4221
3852
|
return {
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
codexMarketplacePath: CODEX_MARKETPLACE_PATH,
|
|
4226
|
-
codexPluginDir: CODEX_ORGX_PLUGIN_DIR,
|
|
4227
|
-
cursorPluginDir: CURSOR_ORGX_PLUGIN_DIR,
|
|
4228
|
-
cursorRulePath: CURSOR_ORGX_RULE_PATH
|
|
3853
|
+
target: "claude",
|
|
3854
|
+
changed,
|
|
3855
|
+
message: changed ? `Synced ${syncResult.fileCount} Claude plugin files and ensured the plugin is installed.` : "Claude Code plugin is already installed and up to date."
|
|
4229
3856
|
};
|
|
4230
3857
|
}
|
|
4231
|
-
function
|
|
3858
|
+
async function installCodexPlugin(paths, fetchImpl, runner) {
|
|
3859
|
+
const available = detectSurface("codex").detected || await commandExists("codex", runner);
|
|
3860
|
+
if (!available) {
|
|
3861
|
+
return {
|
|
3862
|
+
target: "codex",
|
|
3863
|
+
changed: false,
|
|
3864
|
+
message: "Codex is not available on this machine."
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
const syncResult = await syncManagedRepoTree(CODEX_PLUGIN_SYNC_SPEC, paths.codexPluginDir, fetchImpl);
|
|
3868
|
+
const marketplaceChanged = upsertCodexMarketplaceEntry(paths.codexMarketplacePath);
|
|
3869
|
+
const changed = syncResult.changed || marketplaceChanged;
|
|
4232
3870
|
return {
|
|
4233
|
-
|
|
4234
|
-
|
|
3871
|
+
target: "codex",
|
|
3872
|
+
changed,
|
|
3873
|
+
message: changed ? `Synced ${syncResult.fileCount} Codex plugin files and updated the marketplace entry.` : "Codex plugin files and marketplace entry already match the managed OrgX plugin."
|
|
4235
3874
|
};
|
|
4236
3875
|
}
|
|
4237
|
-
function
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
}
|
|
4246
|
-
function isLikelyRepoFilePath(path) {
|
|
4247
|
-
const basename3 = path.split("/").pop() ?? path;
|
|
4248
|
-
return basename3.includes(".") && !/^\.[^./]+$/.test(basename3);
|
|
4249
|
-
}
|
|
4250
|
-
function buildContentsUrl2(spec, path) {
|
|
4251
|
-
const encodedPath = encodeRepoPath2(path);
|
|
4252
|
-
return `https://api.github.com/repos/${spec.owner}/${spec.repo}/contents/${encodedPath}?ref=${encodeURIComponent(spec.ref)}`;
|
|
4253
|
-
}
|
|
4254
|
-
function resolveGitHubError2(spec, status, path) {
|
|
4255
|
-
const repoName = `${spec.owner}/${spec.repo}`;
|
|
4256
|
-
if (status === 404) {
|
|
4257
|
-
return `Could not find '${path}' in ${repoName}.`;
|
|
3876
|
+
async function installOpenclawPlugin(fetchImpl, runner) {
|
|
3877
|
+
const state = await getOpenclawInstallState(runner);
|
|
3878
|
+
if (!state.available) {
|
|
3879
|
+
return {
|
|
3880
|
+
target: "openclaw",
|
|
3881
|
+
changed: false,
|
|
3882
|
+
message: "OpenClaw is not available on this machine."
|
|
3883
|
+
};
|
|
4258
3884
|
}
|
|
4259
|
-
|
|
3885
|
+
if (state.installed) {
|
|
3886
|
+
return {
|
|
3887
|
+
target: "openclaw",
|
|
3888
|
+
changed: false,
|
|
3889
|
+
message: "OpenClaw plugin is already installed."
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
const { tarballUrl, version } = await resolveOpenclawTarball(fetchImpl);
|
|
3893
|
+
const tarballBytes = await fetchRemoteBytes(tarballUrl, fetchImpl);
|
|
3894
|
+
const tempRoot = mkdtempSync(join3(tmpdir(), "orgx-wizard-openclaw-"));
|
|
3895
|
+
const archivePath = join3(tempRoot, `orgx-openclaw-plugin-${version}.tgz`);
|
|
3896
|
+
try {
|
|
3897
|
+
writeFileSync2(archivePath, tarballBytes);
|
|
3898
|
+
const install = await runner("openclaw", ["plugins", "install", archivePath]);
|
|
3899
|
+
if (install.exitCode !== 0) {
|
|
3900
|
+
throw new Error(
|
|
3901
|
+
formatCommandFailure("openclaw", ["plugins", "install", archivePath], install)
|
|
3902
|
+
);
|
|
3903
|
+
}
|
|
3904
|
+
} finally {
|
|
3905
|
+
rmSync(tempRoot, { force: true, recursive: true });
|
|
3906
|
+
}
|
|
3907
|
+
return {
|
|
3908
|
+
target: "openclaw",
|
|
3909
|
+
changed: true,
|
|
3910
|
+
message: `Installed OpenClaw plugin ${ORGX_OPENCLAW_PLUGIN_ID} from ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}@${version}.`
|
|
3911
|
+
};
|
|
4260
3912
|
}
|
|
4261
|
-
|
|
4262
|
-
const
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
3913
|
+
function uninstallCursorPlugin(paths) {
|
|
3914
|
+
const existingRules = readTextIfExists(paths.cursorRulePath);
|
|
3915
|
+
const removedPluginDir = removePathIfExists(paths.cursorPluginDir);
|
|
3916
|
+
const removedLegacyRules = existingRules === CURSOR_RULES_CONTENT ? deleteFileIfExists(paths.cursorRulePath) : false;
|
|
3917
|
+
const changed = removedPluginDir || removedLegacyRules;
|
|
3918
|
+
return {
|
|
3919
|
+
target: "cursor",
|
|
3920
|
+
changed,
|
|
3921
|
+
message: changed ? existingRules !== null && existingRules !== CURSOR_RULES_CONTENT ? "Removed the managed Cursor OrgX plugin bundle. Existing Cursor rules differ from managed OrgX rules, so they were left in place." : "Removed the managed Cursor OrgX plugin bundle and legacy managed Cursor rules." : existingRules !== null && existingRules !== CURSOR_RULES_CONTENT ? "Cursor plugin was not installed. Existing Cursor rules differ from managed OrgX rules, so they were left in place." : "Cursor plugin was not installed."
|
|
3922
|
+
};
|
|
3923
|
+
}
|
|
3924
|
+
async function uninstallClaudePlugin(paths, runner) {
|
|
3925
|
+
const state = await getClaudeInstallState(runner);
|
|
3926
|
+
let changed = false;
|
|
3927
|
+
if (state.available && state.installed) {
|
|
3928
|
+
const uninstall = await runner("claude", [
|
|
3929
|
+
"plugin",
|
|
3930
|
+
"uninstall",
|
|
3931
|
+
ORGX_CLAUDE_PLUGIN_NAME,
|
|
3932
|
+
"--scope",
|
|
3933
|
+
"user"
|
|
3934
|
+
]);
|
|
3935
|
+
if (uninstall.exitCode !== 0) {
|
|
3936
|
+
throw new Error(
|
|
3937
|
+
formatCommandFailure(
|
|
3938
|
+
"claude",
|
|
3939
|
+
["plugin", "uninstall", ORGX_CLAUDE_PLUGIN_NAME, "--scope", "user"],
|
|
3940
|
+
uninstall
|
|
3941
|
+
)
|
|
3942
|
+
);
|
|
3943
|
+
}
|
|
3944
|
+
changed = true;
|
|
4270
3945
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
3946
|
+
if (state.available) {
|
|
3947
|
+
const removeMarketplace = await runner("claude", ["plugin", "marketplace", "remove", ORGX_CLAUDE_MARKETPLACE_NAME]);
|
|
3948
|
+
if (removeMarketplace.exitCode === 0) {
|
|
3949
|
+
changed = true;
|
|
3950
|
+
}
|
|
4274
3951
|
}
|
|
4275
|
-
|
|
3952
|
+
changed = removePathIfExists(paths.claudeMarketplaceDir) || changed;
|
|
3953
|
+
return {
|
|
3954
|
+
target: "claude",
|
|
3955
|
+
changed,
|
|
3956
|
+
message: changed ? "Removed the managed Claude Code plugin and marketplace wrapper." : "Claude Code plugin was not installed."
|
|
3957
|
+
};
|
|
4276
3958
|
}
|
|
4277
|
-
async function
|
|
4278
|
-
const
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
3959
|
+
async function uninstallCodexPlugin(paths) {
|
|
3960
|
+
const removedPluginDir = removePathIfExists(paths.codexPluginDir);
|
|
3961
|
+
const removedMarketplaceEntry = removeCodexMarketplaceEntry(paths.codexMarketplacePath);
|
|
3962
|
+
const changed = removedPluginDir || removedMarketplaceEntry;
|
|
3963
|
+
return {
|
|
3964
|
+
target: "codex",
|
|
3965
|
+
changed,
|
|
3966
|
+
message: changed ? "Removed the managed Codex plugin files and marketplace entry." : "Codex plugin was not installed."
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
async function uninstallOpenclawPlugin(runner) {
|
|
3970
|
+
const state = await getOpenclawInstallState(runner);
|
|
3971
|
+
if (!state.available || !state.installed) {
|
|
3972
|
+
return {
|
|
3973
|
+
target: "openclaw",
|
|
3974
|
+
changed: false,
|
|
3975
|
+
message: "OpenClaw plugin was not installed."
|
|
3976
|
+
};
|
|
4286
3977
|
}
|
|
4287
|
-
const
|
|
4288
|
-
if (
|
|
4289
|
-
throw new Error(
|
|
3978
|
+
const uninstall = await runner("openclaw", ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"]);
|
|
3979
|
+
if (uninstall.exitCode !== 0) {
|
|
3980
|
+
throw new Error(
|
|
3981
|
+
formatCommandFailure(
|
|
3982
|
+
"openclaw",
|
|
3983
|
+
["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"],
|
|
3984
|
+
uninstall
|
|
3985
|
+
)
|
|
3986
|
+
);
|
|
4290
3987
|
}
|
|
4291
|
-
return
|
|
3988
|
+
return {
|
|
3989
|
+
target: "openclaw",
|
|
3990
|
+
changed: true,
|
|
3991
|
+
message: "Removed the managed OpenClaw plugin."
|
|
3992
|
+
};
|
|
4292
3993
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
const file = await fetchRepoFile(spec, path, fetchImpl);
|
|
4297
|
-
files.push({
|
|
4298
|
-
localPath,
|
|
4299
|
-
path: file.path,
|
|
4300
|
-
sourceUrl: file.download_url ?? ""
|
|
4301
|
-
});
|
|
4302
|
-
return files;
|
|
3994
|
+
function resolvePluginTargets(requested = []) {
|
|
3995
|
+
if (requested.length === 0 || requested.includes("all")) {
|
|
3996
|
+
return [...DEFAULT_ORGX_PLUGIN_TARGETS];
|
|
4303
3997
|
}
|
|
4304
|
-
const
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
fetchImpl
|
|
4313
|
-
)
|
|
3998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3999
|
+
const normalized = [];
|
|
4000
|
+
for (const rawTarget of requested) {
|
|
4001
|
+
const target = rawTarget.trim().toLowerCase();
|
|
4002
|
+
if (target === "" || target === "all") continue;
|
|
4003
|
+
if (!DEFAULT_ORGX_PLUGIN_TARGETS.includes(target)) {
|
|
4004
|
+
throw new Error(
|
|
4005
|
+
`Unknown plugin target '${rawTarget}'. Supported targets: ${DEFAULT_ORGX_PLUGIN_TARGETS.join(", ")}.`
|
|
4314
4006
|
);
|
|
4315
|
-
continue;
|
|
4316
4007
|
}
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
localPath: join3(localPath, entry.name),
|
|
4322
|
-
path: entry.path,
|
|
4323
|
-
sourceUrl: entry.download_url
|
|
4324
|
-
});
|
|
4008
|
+
const nextTarget = target;
|
|
4009
|
+
if (seen.has(nextTarget)) continue;
|
|
4010
|
+
seen.add(nextTarget);
|
|
4011
|
+
normalized.push(nextTarget);
|
|
4325
4012
|
}
|
|
4326
|
-
return
|
|
4013
|
+
return normalized;
|
|
4327
4014
|
}
|
|
4328
|
-
async function
|
|
4329
|
-
const
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
sourceUrl: file.download_url ?? ""
|
|
4338
|
-
});
|
|
4339
|
-
continue;
|
|
4340
|
-
}
|
|
4341
|
-
files.push(...await listRemoteRepoFiles(spec, entry.remotePath, entry.localPath, fetchImpl));
|
|
4342
|
-
}
|
|
4343
|
-
return files.sort((left, right) => left.localPath.localeCompare(right.localPath));
|
|
4015
|
+
async function listOrgxPluginStatuses(options = {}) {
|
|
4016
|
+
const paths = resolvePluginPaths(options.paths);
|
|
4017
|
+
const runner = resolveCommandRunner(options.commandRunner);
|
|
4018
|
+
return await Promise.all([
|
|
4019
|
+
buildCursorStatus(paths),
|
|
4020
|
+
buildClaudeStatus(paths, runner),
|
|
4021
|
+
buildCodexStatus(paths, runner),
|
|
4022
|
+
buildOpenclawStatus(runner)
|
|
4023
|
+
]);
|
|
4344
4024
|
}
|
|
4345
|
-
async function
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
signal: AbortSignal.timeout(1e4)
|
|
4351
|
-
});
|
|
4352
|
-
if (!response.ok) {
|
|
4353
|
-
throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
|
|
4025
|
+
async function installOrgxPlugins(options = {}) {
|
|
4026
|
+
const targets = resolvePluginTargets(options.targets ?? []);
|
|
4027
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
4028
|
+
if (!fetchImpl) {
|
|
4029
|
+
throw new Error("Global fetch is unavailable in this runtime.");
|
|
4354
4030
|
}
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4031
|
+
const paths = resolvePluginPaths(options.paths);
|
|
4032
|
+
const runner = resolveCommandRunner(options.commandRunner);
|
|
4033
|
+
const results = [];
|
|
4034
|
+
for (const target of targets) {
|
|
4035
|
+
switch (target) {
|
|
4036
|
+
case "cursor":
|
|
4037
|
+
results.push(await installCursorPlugin(paths, fetchImpl));
|
|
4038
|
+
break;
|
|
4039
|
+
case "claude":
|
|
4040
|
+
results.push(await installClaudePlugin(paths, fetchImpl, runner));
|
|
4041
|
+
break;
|
|
4042
|
+
case "codex":
|
|
4043
|
+
results.push(await installCodexPlugin(paths, fetchImpl, runner));
|
|
4044
|
+
break;
|
|
4045
|
+
case "openclaw":
|
|
4046
|
+
results.push(await installOpenclawPlugin(fetchImpl, runner));
|
|
4047
|
+
break;
|
|
4368
4048
|
}
|
|
4369
|
-
throw error;
|
|
4370
4049
|
}
|
|
4050
|
+
return { results };
|
|
4371
4051
|
}
|
|
4372
|
-
function
|
|
4373
|
-
const
|
|
4374
|
-
|
|
4375
|
-
|
|
4052
|
+
async function uninstallOrgxPlugins(options = {}) {
|
|
4053
|
+
const targets = resolvePluginTargets(options.targets ?? []);
|
|
4054
|
+
const paths = resolvePluginPaths(options.paths);
|
|
4055
|
+
const runner = resolveCommandRunner(options.commandRunner);
|
|
4056
|
+
const results = [];
|
|
4057
|
+
for (const target of targets) {
|
|
4058
|
+
switch (target) {
|
|
4059
|
+
case "cursor":
|
|
4060
|
+
results.push(uninstallCursorPlugin(paths));
|
|
4061
|
+
break;
|
|
4062
|
+
case "claude":
|
|
4063
|
+
results.push(await uninstallClaudePlugin(paths, runner));
|
|
4064
|
+
break;
|
|
4065
|
+
case "codex":
|
|
4066
|
+
results.push(await uninstallCodexPlugin(paths));
|
|
4067
|
+
break;
|
|
4068
|
+
case "openclaw":
|
|
4069
|
+
results.push(await uninstallOpenclawPlugin(runner));
|
|
4070
|
+
break;
|
|
4071
|
+
}
|
|
4376
4072
|
}
|
|
4377
|
-
|
|
4378
|
-
writeFileSync2(path, bytes);
|
|
4379
|
-
return true;
|
|
4073
|
+
return { results };
|
|
4380
4074
|
}
|
|
4381
|
-
function
|
|
4382
|
-
|
|
4383
|
-
rmSync(path, { force: true, recursive: true });
|
|
4384
|
-
return true;
|
|
4075
|
+
function countPluginReportChanges(report) {
|
|
4076
|
+
return report.results.filter((result) => result.changed).length;
|
|
4385
4077
|
}
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4078
|
+
|
|
4079
|
+
// src/surfaces/registry.ts
|
|
4080
|
+
var AUTH_SETUP_HINT = "orgx-wizard auth login";
|
|
4081
|
+
var AUTH_SET_KEY_HINT = "orgx-wizard auth set-key";
|
|
4082
|
+
var DOCTOR_HINT = "orgx-wizard doctor";
|
|
4083
|
+
function getSurfacePath(name) {
|
|
4084
|
+
const detection = detectSurface(name);
|
|
4085
|
+
const locator = getSurfaceLocator(name);
|
|
4086
|
+
const existingPath = detection.existingPath;
|
|
4087
|
+
if (existingPath && locator.configPaths.includes(existingPath)) {
|
|
4088
|
+
return existingPath;
|
|
4390
4089
|
}
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4090
|
+
return locator.configPaths[0] ?? detection.preferredPath ?? existingPath;
|
|
4091
|
+
}
|
|
4092
|
+
function getOpenClawDependencyState() {
|
|
4093
|
+
const detection = detectSurface("openclaw");
|
|
4094
|
+
const path = detection.existingPath ?? detection.preferredPath;
|
|
4095
|
+
const raw = path ? readTextIfExists(path) : null;
|
|
4096
|
+
const inspection = inspectOpenClawConfig(raw);
|
|
4097
|
+
const result = {
|
|
4098
|
+
detected: detection.detected,
|
|
4099
|
+
configured: inspection.configured
|
|
4100
|
+
};
|
|
4101
|
+
if (path) {
|
|
4102
|
+
result.path = path;
|
|
4401
4103
|
}
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
function pruneEmptyDirectories(root, current = root) {
|
|
4405
|
-
if (!existsSync4(current) || !statSync2(current).isDirectory()) {
|
|
4406
|
-
return false;
|
|
4104
|
+
if (detection.detected) {
|
|
4105
|
+
result.url = buildLocalMcpUrl(raw);
|
|
4407
4106
|
}
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4107
|
+
return result;
|
|
4108
|
+
}
|
|
4109
|
+
function automatedSurfaceStatus(name) {
|
|
4110
|
+
const detection = detectSurface(name);
|
|
4111
|
+
const path = detection.existingPath ?? detection.preferredPath;
|
|
4112
|
+
const openclaw = getOpenClawDependencyState();
|
|
4113
|
+
if (name === "claude") {
|
|
4114
|
+
const inspection2 = inspectClaudeMcpConfig(path ? readTextIfExists(path) : null);
|
|
4115
|
+
const configured = inspection2.hostedConfigured === true && (openclaw.detected ? inspection2.localConfigured === true : true);
|
|
4116
|
+
return {
|
|
4117
|
+
name,
|
|
4118
|
+
mode: "automated",
|
|
4119
|
+
detected: detection.detected,
|
|
4120
|
+
configured,
|
|
4121
|
+
...path ? { path } : {},
|
|
4122
|
+
details: [
|
|
4123
|
+
...inspection2.details,
|
|
4124
|
+
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; hosted OrgX is the only available route."],
|
|
4125
|
+
...detection.evidence
|
|
4126
|
+
],
|
|
4127
|
+
summary: configured ? openclaw.detected ? "OrgX is connected in Claude with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Claude." : detection.detected ? openclaw.detected ? `Claude is missing either hosted OrgX or the local OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Claude is missing the hosted OrgX MCP entry." : "Claude config directory was not detected on disk."
|
|
4128
|
+
};
|
|
4412
4129
|
}
|
|
4413
|
-
if (
|
|
4414
|
-
|
|
4415
|
-
|
|
4130
|
+
if (name === "cursor") {
|
|
4131
|
+
const inspection2 = inspectCursorMcpConfig(path ? readTextIfExists(path) : null);
|
|
4132
|
+
const tooling = getCursorToolingState();
|
|
4133
|
+
const configured = openclaw.detected && inspection2.localConfigured === true;
|
|
4134
|
+
const mcpWired = tooling.mcpLocalOpenclaw || tooling.mcpHostedOrgx;
|
|
4135
|
+
const summary = (() => {
|
|
4136
|
+
if (!detection.detected) {
|
|
4137
|
+
return "Cursor config directory was not detected on disk.";
|
|
4138
|
+
}
|
|
4139
|
+
if (configured) {
|
|
4140
|
+
return tooling.pluginBundleInstalled ? "OrgX is connected in Cursor through the local OpenClaw bridge; companion plugin bundle is installed." : "OrgX is connected in Cursor through the local OpenClaw bridge; companion plugin bundle (skills, hooks, rules) is not installed.";
|
|
4141
|
+
}
|
|
4142
|
+
if (mcpWired && !tooling.pluginBundleInstalled) {
|
|
4143
|
+
if (tooling.mcpHostedOrgx && !openclaw.detected) {
|
|
4144
|
+
return "OrgX hosted MCP is present in Cursor mcp.json; install OpenClaw for the local bridge, or add the companion plugin bundle for the full pack.";
|
|
4145
|
+
}
|
|
4146
|
+
return "OrgX MCP is present in Cursor mcp.json, but the companion plugin bundle (skills, hooks, rules) is not installed.";
|
|
4147
|
+
}
|
|
4148
|
+
if (detection.detected) {
|
|
4149
|
+
return openclaw.detected ? `Cursor is missing the local OrgX OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Cursor is ready to wire, but OpenClaw must be installed first.";
|
|
4150
|
+
}
|
|
4151
|
+
return "Cursor config directory was not detected on disk.";
|
|
4152
|
+
})();
|
|
4153
|
+
return {
|
|
4154
|
+
name,
|
|
4155
|
+
mode: "automated",
|
|
4156
|
+
detected: detection.detected,
|
|
4157
|
+
configured,
|
|
4158
|
+
...path ? { path } : {},
|
|
4159
|
+
cursorIntegration: tooling,
|
|
4160
|
+
details: [
|
|
4161
|
+
`companion plugin bundle: ${tooling.pluginBundleInstalled ? "installed" : "not installed"}`,
|
|
4162
|
+
...inspection2.details,
|
|
4163
|
+
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; Cursor cannot use the local OrgX bridge yet."],
|
|
4164
|
+
...detection.evidence
|
|
4165
|
+
],
|
|
4166
|
+
summary
|
|
4167
|
+
};
|
|
4416
4168
|
}
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4169
|
+
if (name === "codex") {
|
|
4170
|
+
const inspection2 = inspectCodexConfigToml(path ? readTextIfExists(path) : null);
|
|
4171
|
+
const configured = inspection2.hostedConfigured === true && (openclaw.detected ? inspection2.localConfigured === true : true);
|
|
4172
|
+
return {
|
|
4173
|
+
name,
|
|
4174
|
+
mode: "automated",
|
|
4175
|
+
detected: detection.detected,
|
|
4176
|
+
configured,
|
|
4177
|
+
...path ? { path } : {},
|
|
4178
|
+
details: [
|
|
4179
|
+
...inspection2.details,
|
|
4180
|
+
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; hosted OrgX is the only available route."],
|
|
4181
|
+
...detection.evidence
|
|
4182
|
+
],
|
|
4183
|
+
summary: configured ? openclaw.detected ? "OrgX is connected in Codex with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Codex." : detection.detected ? openclaw.detected ? `Codex is missing either hosted OrgX or the local OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Codex is missing the hosted OrgX MCP entry." : "Codex config directory was not detected on disk."
|
|
4184
|
+
};
|
|
4426
4185
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4186
|
+
if (name === "vscode") {
|
|
4187
|
+
const inspection2 = inspectVscodeMcpConfig(path ? readTextIfExists(path) : null);
|
|
4188
|
+
return {
|
|
4189
|
+
name,
|
|
4190
|
+
mode: "automated",
|
|
4191
|
+
detected: detection.detected,
|
|
4192
|
+
configured: inspection2.hostedConfigured === true,
|
|
4193
|
+
...path ? { path } : {},
|
|
4194
|
+
details: [...inspection2.details, ...detection.evidence],
|
|
4195
|
+
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in VS Code." : detection.detected ? "VS Code is missing the hosted OrgX MCP entry." : "VS Code config directory was not detected on disk."
|
|
4196
|
+
};
|
|
4431
4197
|
}
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4198
|
+
if (name === "windsurf") {
|
|
4199
|
+
const inspection2 = inspectWindsurfMcpConfig(path ? readTextIfExists(path) : null);
|
|
4200
|
+
return {
|
|
4201
|
+
name,
|
|
4202
|
+
mode: "automated",
|
|
4203
|
+
detected: detection.detected,
|
|
4204
|
+
configured: inspection2.hostedConfigured === true,
|
|
4205
|
+
...path ? { path } : {},
|
|
4206
|
+
details: [...inspection2.details, ...detection.evidence],
|
|
4207
|
+
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in Windsurf." : detection.detected ? "Windsurf is missing the hosted OrgX MCP entry." : "Windsurf config directory was not detected on disk."
|
|
4208
|
+
};
|
|
4438
4209
|
}
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4210
|
+
if (name === "zed") {
|
|
4211
|
+
const inspection2 = inspectZedMcpConfig(path ? readTextIfExists(path) : null);
|
|
4212
|
+
return {
|
|
4213
|
+
name,
|
|
4214
|
+
mode: "automated",
|
|
4215
|
+
detected: detection.detected,
|
|
4216
|
+
configured: inspection2.hostedConfigured === true,
|
|
4217
|
+
...path ? { path } : {},
|
|
4218
|
+
details: [...inspection2.details, ...detection.evidence],
|
|
4219
|
+
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in Zed." : detection.detected ? "Zed is missing the hosted OrgX MCP entry." : "Zed config directory was not detected on disk."
|
|
4220
|
+
};
|
|
4450
4221
|
}
|
|
4451
|
-
|
|
4452
|
-
writeFileSync2(path, next, "utf8");
|
|
4453
|
-
return true;
|
|
4454
|
-
}
|
|
4455
|
-
function buildClaudeMarketplaceManifest() {
|
|
4222
|
+
const inspection = inspectOpenClawConfig(path ? readTextIfExists(path) : null);
|
|
4456
4223
|
return {
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
{
|
|
4465
|
-
name: ORGX_CLAUDE_PLUGIN_NAME,
|
|
4466
|
-
description: "OrgX MCP tools and runtime telemetry hooks for Claude Code.",
|
|
4467
|
-
source: `./plugins/${ORGX_CLAUDE_PLUGIN_NAME}`
|
|
4468
|
-
}
|
|
4469
|
-
]
|
|
4224
|
+
name,
|
|
4225
|
+
mode: "automated",
|
|
4226
|
+
detected: detection.detected,
|
|
4227
|
+
configured: inspection.configured,
|
|
4228
|
+
...path ? { path } : {},
|
|
4229
|
+
details: [...inspection.details, ...detection.evidence],
|
|
4230
|
+
summary: inspection.configured ? `OpenClaw plugin entry ${inspection.pluginKey ?? "missing"} is configured.` : inspection.installed ? "OpenClaw is installed but OrgX defaults have not been written." : detection.detected ? "OpenClaw support files exist but no OrgX plugin entry was found." : "OpenClaw is not installed or no support files were found."
|
|
4470
4231
|
};
|
|
4471
4232
|
}
|
|
4472
|
-
function
|
|
4233
|
+
function formatAuthHintSource(authHint) {
|
|
4234
|
+
switch (authHint.source) {
|
|
4235
|
+
case "environment":
|
|
4236
|
+
return "env ORGX_API_KEY";
|
|
4237
|
+
case "wizard-store":
|
|
4238
|
+
return "wizard auth store";
|
|
4239
|
+
case "openclaw-config-file":
|
|
4240
|
+
return "openclaw config";
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
function buildChatgptManualStatus(detection, authHint) {
|
|
4244
|
+
const path = detection.existingPath ?? detection.preferredPath;
|
|
4245
|
+
const details = [...detection.evidence];
|
|
4246
|
+
if (authHint) {
|
|
4247
|
+
details.push(
|
|
4248
|
+
`OrgX auth ready via ${formatAuthHintSource(authHint)} (${authHint.keyPrefix})`
|
|
4249
|
+
);
|
|
4250
|
+
details.push(`Hosted connector URL: ${ORGX_HOSTED_MCP_URL}`);
|
|
4251
|
+
details.push(
|
|
4252
|
+
"Manual step: in ChatGPT desktop developer mode, add a custom connector pointing at the hosted OrgX MCP URL."
|
|
4253
|
+
);
|
|
4254
|
+
details.push(
|
|
4255
|
+
"ChatGPT completes OAuth during connector setup; the wizard cannot preflight hosted MCP tools with an oxk_ API key."
|
|
4256
|
+
);
|
|
4257
|
+
details.push(
|
|
4258
|
+
"After connecting ChatGPT, run `orgx-wizard doctor` to verify hosted MCP health from this machine."
|
|
4259
|
+
);
|
|
4260
|
+
} else {
|
|
4261
|
+
details.push(
|
|
4262
|
+
"OrgX auth is not configured yet. Run `orgx-wizard auth login` or `orgx-wizard auth set-key` first."
|
|
4263
|
+
);
|
|
4264
|
+
details.push(
|
|
4265
|
+
"After auth is configured, add a custom ChatGPT developer connector pointing at the hosted OrgX MCP URL."
|
|
4266
|
+
);
|
|
4267
|
+
}
|
|
4268
|
+
let summary;
|
|
4269
|
+
if (!authHint) {
|
|
4270
|
+
summary = detection.detected ? "ChatGPT is installed, but OrgX auth is not configured yet." : "ChatGPT support files were not found; configure OrgX auth first, then add the hosted connector manually.";
|
|
4271
|
+
} else {
|
|
4272
|
+
summary = detection.detected ? "ChatGPT is installed; finish the manual developer connector setup." : "OrgX auth is ready, but ChatGPT desktop support files were not found on disk.";
|
|
4273
|
+
}
|
|
4473
4274
|
return {
|
|
4474
|
-
name:
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
},
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
authentication: "ON_INSTALL"
|
|
4482
|
-
},
|
|
4483
|
-
category: "Productivity"
|
|
4275
|
+
name: "chatgpt",
|
|
4276
|
+
mode: "manual",
|
|
4277
|
+
detected: detection.detected,
|
|
4278
|
+
configured: false,
|
|
4279
|
+
...path ? { path } : {},
|
|
4280
|
+
details,
|
|
4281
|
+
summary
|
|
4484
4282
|
};
|
|
4485
4283
|
}
|
|
4486
|
-
function
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
nextPlugins.push(nextEntry);
|
|
4284
|
+
function buildChatgptAddResult(path, authHint, toolCheck) {
|
|
4285
|
+
if (!authHint) {
|
|
4286
|
+
return {
|
|
4287
|
+
name: "chatgpt",
|
|
4288
|
+
changed: false,
|
|
4289
|
+
...path ? { path } : {},
|
|
4290
|
+
message: `Run \`${AUTH_SETUP_HINT}\` or \`${AUTH_SET_KEY_HINT}\` first, then add ${ORGX_HOSTED_MCP_URL} as a custom connector in ChatGPT developer mode.`
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
if (toolCheck && !toolCheck.ok && !toolCheck.skipped) {
|
|
4294
|
+
const failure = toolCheck?.error ?? "hosted MCP verification did not complete.";
|
|
4295
|
+
return {
|
|
4296
|
+
name: "chatgpt",
|
|
4297
|
+
changed: false,
|
|
4298
|
+
...path ? { path } : {},
|
|
4299
|
+
message: `OrgX auth is configured via ${formatAuthHintSource(authHint)}, but hosted MCP verification failed (${failure}). Fix that first, then add ${ORGX_HOSTED_MCP_URL} in ChatGPT developer mode.`
|
|
4300
|
+
};
|
|
4504
4301
|
}
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
...
|
|
4509
|
-
|
|
4302
|
+
return {
|
|
4303
|
+
name: "chatgpt",
|
|
4304
|
+
changed: false,
|
|
4305
|
+
...path ? { path } : {},
|
|
4306
|
+
message: `OrgX auth is ready via ${formatAuthHintSource(authHint)}. Add ${ORGX_HOSTED_MCP_URL} as a custom connector in ChatGPT developer mode; ChatGPT will complete the OAuth flow there. Then run \`${DOCTOR_HINT}\` to verify hosted MCP health from this machine.`
|
|
4510
4307
|
};
|
|
4511
|
-
return writeJsonIfChanged(path, nextDocument);
|
|
4512
4308
|
}
|
|
4513
|
-
function
|
|
4514
|
-
|
|
4515
|
-
|
|
4309
|
+
function buildChatgptRemoveResult(path) {
|
|
4310
|
+
return {
|
|
4311
|
+
name: "chatgpt",
|
|
4312
|
+
changed: false,
|
|
4313
|
+
...path ? { path } : {},
|
|
4314
|
+
message: "Remove the custom OrgX connector from ChatGPT developer mode; the wizard does not manage ChatGPT config files directly."
|
|
4315
|
+
};
|
|
4316
|
+
}
|
|
4317
|
+
function manualSurfaceStatus(name) {
|
|
4318
|
+
const detection = detectSurface(name);
|
|
4319
|
+
switch (name) {
|
|
4320
|
+
case "chatgpt":
|
|
4321
|
+
return buildChatgptManualStatus(detection, peekResolvedOrgxAuth());
|
|
4516
4322
|
}
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
if (
|
|
4520
|
-
return
|
|
4323
|
+
}
|
|
4324
|
+
function getSurfaceStatus(name) {
|
|
4325
|
+
if (AUTOMATED_SURFACE_NAMES.includes(name)) {
|
|
4326
|
+
return automatedSurfaceStatus(
|
|
4327
|
+
name
|
|
4328
|
+
);
|
|
4521
4329
|
}
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4330
|
+
return manualSurfaceStatus(name);
|
|
4331
|
+
}
|
|
4332
|
+
function listSurfaceStatuses() {
|
|
4333
|
+
return SURFACE_NAMES.map(getSurfaceStatus);
|
|
4334
|
+
}
|
|
4335
|
+
function resolveTargets(selection, allTargets) {
|
|
4336
|
+
const values = Array.isArray(selection) ? selection : [selection];
|
|
4337
|
+
if (values.includes("all")) {
|
|
4338
|
+
return [...allTargets];
|
|
4525
4339
|
}
|
|
4526
|
-
return
|
|
4527
|
-
...document,
|
|
4528
|
-
plugins: nextPlugins
|
|
4529
|
-
});
|
|
4340
|
+
return [...new Set(values)];
|
|
4530
4341
|
}
|
|
4531
|
-
function
|
|
4532
|
-
|
|
4533
|
-
return plugins.some((plugin) => plugin.name === ORGX_CODEX_PLUGIN_NAME);
|
|
4342
|
+
function resolveSurfaceSelection(selection) {
|
|
4343
|
+
return resolveTargets(selection, SURFACE_NAMES);
|
|
4534
4344
|
}
|
|
4535
|
-
function
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4345
|
+
function resolveMcpSurfaceSelection(selection) {
|
|
4346
|
+
return resolveTargets(selection, MCP_SURFACE_NAMES);
|
|
4347
|
+
}
|
|
4348
|
+
async function addAutomatedSurface(name) {
|
|
4349
|
+
const path = getSurfacePath(name);
|
|
4350
|
+
const openclaw = getOpenClawDependencyState();
|
|
4351
|
+
if (!path) {
|
|
4352
|
+
return {
|
|
4353
|
+
name,
|
|
4354
|
+
changed: false,
|
|
4355
|
+
message: `${name} has no writable config path on this platform.`
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4358
|
+
switch (name) {
|
|
4359
|
+
case "claude": {
|
|
4360
|
+
const previous = readTextIfExists(path);
|
|
4361
|
+
const next = patchClaudeMcpConfig(previous, openclaw.url);
|
|
4362
|
+
writeTextFile(path, next);
|
|
4363
|
+
return {
|
|
4364
|
+
name,
|
|
4365
|
+
changed: previous !== next,
|
|
4366
|
+
path,
|
|
4367
|
+
message: openclaw.detected ? "OrgX is connected in Claude with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Claude."
|
|
4368
|
+
};
|
|
4369
|
+
}
|
|
4370
|
+
case "cursor": {
|
|
4371
|
+
if (!openclaw.detected || !openclaw.url) {
|
|
4372
|
+
return {
|
|
4373
|
+
name,
|
|
4374
|
+
changed: false,
|
|
4375
|
+
path,
|
|
4376
|
+
message: "Cursor needs OpenClaw installed before OrgX can be connected locally."
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
const previous = readTextIfExists(path);
|
|
4380
|
+
const next = patchCursorMcpConfig(previous, openclaw.url);
|
|
4381
|
+
writeTextFile(path, next);
|
|
4382
|
+
return {
|
|
4383
|
+
name,
|
|
4384
|
+
changed: previous !== next,
|
|
4385
|
+
path,
|
|
4386
|
+
message: "OrgX is connected in Cursor through the local OpenClaw bridge."
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4389
|
+
case "codex": {
|
|
4390
|
+
const previous = readTextIfExists(path);
|
|
4391
|
+
const next = patchCodexConfigToml(previous, openclaw.url);
|
|
4392
|
+
writeTextFile(path, next);
|
|
4393
|
+
return {
|
|
4394
|
+
name,
|
|
4395
|
+
changed: previous !== next,
|
|
4396
|
+
path,
|
|
4397
|
+
message: openclaw.detected ? "OrgX is connected in Codex with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Codex."
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
case "openclaw": {
|
|
4401
|
+
const previous = readTextIfExists(path);
|
|
4402
|
+
const auth = await resolveOrgxAuth();
|
|
4403
|
+
const next = patchOpenClawConfig(previous, auth ?? {});
|
|
4404
|
+
writeTextFile(path, next);
|
|
4405
|
+
return {
|
|
4406
|
+
name,
|
|
4407
|
+
changed: previous !== next,
|
|
4408
|
+
path,
|
|
4409
|
+
message: auth ? "OpenClaw is configured as the local OrgX bridge with the resolved OrgX auth." : "OpenClaw is configured as the local OrgX bridge."
|
|
4410
|
+
};
|
|
4411
|
+
}
|
|
4412
|
+
case "vscode": {
|
|
4413
|
+
const previous = readTextIfExists(path);
|
|
4414
|
+
if (inspectVscodeMcpConfig(previous).hostedConfigured) {
|
|
4415
|
+
return { name, changed: false, path, message: "OrgX cloud MCP is connected in VS Code." };
|
|
4416
|
+
}
|
|
4417
|
+
const next = patchVscodeMcpConfig(previous);
|
|
4418
|
+
writeTextFile(path, next);
|
|
4419
|
+
return { name, changed: true, path, message: "OrgX cloud MCP is connected in VS Code." };
|
|
4420
|
+
}
|
|
4421
|
+
case "windsurf": {
|
|
4422
|
+
const previous = readTextIfExists(path);
|
|
4423
|
+
if (inspectWindsurfMcpConfig(previous).hostedConfigured) {
|
|
4424
|
+
return { name, changed: false, path, message: "OrgX cloud MCP is connected in Windsurf." };
|
|
4425
|
+
}
|
|
4426
|
+
const next = patchWindsurfMcpConfig(previous);
|
|
4427
|
+
writeTextFile(path, next);
|
|
4428
|
+
return { name, changed: true, path, message: "OrgX cloud MCP is connected in Windsurf." };
|
|
4429
|
+
}
|
|
4430
|
+
case "zed": {
|
|
4431
|
+
const previous = readTextIfExists(path);
|
|
4432
|
+
if (inspectZedMcpConfig(previous).hostedConfigured) {
|
|
4433
|
+
return { name, changed: false, path, message: "OrgX cloud MCP is connected in Zed." };
|
|
4434
|
+
}
|
|
4435
|
+
const next = patchZedMcpConfig(previous);
|
|
4436
|
+
writeTextFile(path, next);
|
|
4437
|
+
return { name, changed: true, path, message: "OrgX cloud MCP is connected in Zed." };
|
|
4438
|
+
}
|
|
4439
|
+
case "chatgpt": {
|
|
4440
|
+
const authHint = peekResolvedOrgxAuth();
|
|
4441
|
+
if (!authHint) {
|
|
4442
|
+
return buildChatgptAddResult(path, null);
|
|
4443
|
+
}
|
|
4444
|
+
return buildChatgptAddResult(path, authHint);
|
|
4445
|
+
}
|
|
4446
|
+
default:
|
|
4447
|
+
return {
|
|
4448
|
+
name,
|
|
4449
|
+
changed: false,
|
|
4450
|
+
message: `${name} is not supported by the wizard.`
|
|
4451
|
+
};
|
|
4548
4452
|
}
|
|
4549
4453
|
}
|
|
4550
|
-
function
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4454
|
+
function removeAutomatedSurface(name) {
|
|
4455
|
+
const path = getSurfacePath(name);
|
|
4456
|
+
if (!path) {
|
|
4457
|
+
return {
|
|
4458
|
+
name,
|
|
4459
|
+
changed: false,
|
|
4460
|
+
message: `${name} has no writable config path on this platform.`
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
switch (name) {
|
|
4464
|
+
case "claude": {
|
|
4465
|
+
const previous = readTextIfExists(path);
|
|
4466
|
+
const next = removeClaudeMcpConfig(previous);
|
|
4467
|
+
writeTextFile(path, next);
|
|
4468
|
+
return {
|
|
4469
|
+
name,
|
|
4470
|
+
changed: previous !== next,
|
|
4471
|
+
path,
|
|
4472
|
+
message: "OrgX connection was removed from Claude."
|
|
4473
|
+
};
|
|
4474
|
+
}
|
|
4475
|
+
case "cursor": {
|
|
4476
|
+
const previous = readTextIfExists(path);
|
|
4477
|
+
const next = removeCursorMcpConfig(previous);
|
|
4478
|
+
writeTextFile(path, next);
|
|
4479
|
+
return {
|
|
4480
|
+
name,
|
|
4481
|
+
changed: previous !== next,
|
|
4482
|
+
path,
|
|
4483
|
+
message: "OrgX connection was removed from Cursor."
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
case "codex": {
|
|
4487
|
+
const previous = readTextIfExists(path);
|
|
4488
|
+
const next = removeCodexConfigToml(previous);
|
|
4489
|
+
writeTextFile(path, next);
|
|
4490
|
+
return {
|
|
4491
|
+
name,
|
|
4492
|
+
changed: previous !== next,
|
|
4493
|
+
path,
|
|
4494
|
+
message: "OrgX connection was removed from Codex."
|
|
4495
|
+
};
|
|
4496
|
+
}
|
|
4497
|
+
case "openclaw": {
|
|
4498
|
+
const previous = readTextIfExists(path);
|
|
4499
|
+
const next = removeOpenClawConfig(previous);
|
|
4500
|
+
writeTextFile(path, next);
|
|
4501
|
+
return {
|
|
4502
|
+
name,
|
|
4503
|
+
changed: previous !== next,
|
|
4504
|
+
path,
|
|
4505
|
+
message: "OpenClaw OrgX defaults were removed."
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
4508
|
+
case "vscode": {
|
|
4509
|
+
const previous = readTextIfExists(path);
|
|
4510
|
+
const next = removeVscodeMcpConfig(previous);
|
|
4511
|
+
writeTextFile(path, next);
|
|
4512
|
+
return {
|
|
4513
|
+
name,
|
|
4514
|
+
changed: previous !== next,
|
|
4515
|
+
path,
|
|
4516
|
+
message: "OrgX connection was removed from VS Code."
|
|
4517
|
+
};
|
|
4560
4518
|
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4519
|
+
case "windsurf": {
|
|
4520
|
+
const previous = readTextIfExists(path);
|
|
4521
|
+
const next = removeWindsurfMcpConfig(previous);
|
|
4522
|
+
writeTextFile(path, next);
|
|
4523
|
+
return {
|
|
4524
|
+
name,
|
|
4525
|
+
changed: previous !== next,
|
|
4526
|
+
path,
|
|
4527
|
+
message: "OrgX connection was removed from Windsurf."
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4530
|
+
case "zed": {
|
|
4531
|
+
const previous = readTextIfExists(path);
|
|
4532
|
+
const next = removeZedMcpConfig(previous);
|
|
4533
|
+
writeTextFile(path, next);
|
|
4534
|
+
return {
|
|
4535
|
+
name,
|
|
4536
|
+
changed: previous !== next,
|
|
4537
|
+
path,
|
|
4538
|
+
message: "OrgX connection was removed from Zed."
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
case "chatgpt":
|
|
4542
|
+
return buildChatgptRemoveResult(path);
|
|
4543
|
+
default:
|
|
4544
|
+
return {
|
|
4545
|
+
name,
|
|
4546
|
+
changed: false,
|
|
4547
|
+
message: `${name} is not supported by the wizard.`
|
|
4548
|
+
};
|
|
4572
4549
|
}
|
|
4573
4550
|
}
|
|
4574
|
-
function
|
|
4575
|
-
const
|
|
4576
|
-
|
|
4577
|
-
|
|
4551
|
+
async function addSurface(selection) {
|
|
4552
|
+
const targets = resolveSurfaceSelection(selection);
|
|
4553
|
+
const results = [];
|
|
4554
|
+
for (const surface of targets) {
|
|
4555
|
+
results.push(await addAutomatedSurface(surface));
|
|
4578
4556
|
}
|
|
4579
|
-
return
|
|
4557
|
+
return results;
|
|
4580
4558
|
}
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
env: process.env,
|
|
4585
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4586
|
-
});
|
|
4587
|
-
let stdout = "";
|
|
4588
|
-
let stderr = "";
|
|
4589
|
-
child.stdout.on("data", (chunk) => {
|
|
4590
|
-
stdout += chunk.toString();
|
|
4591
|
-
});
|
|
4592
|
-
child.stderr.on("data", (chunk) => {
|
|
4593
|
-
stderr += chunk.toString();
|
|
4594
|
-
});
|
|
4595
|
-
child.on("error", (error) => {
|
|
4596
|
-
const errorCode = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
4597
|
-
resolve2({
|
|
4598
|
-
exitCode: -1,
|
|
4599
|
-
stdout,
|
|
4600
|
-
stderr,
|
|
4601
|
-
...errorCode ? { errorCode } : {}
|
|
4602
|
-
});
|
|
4603
|
-
});
|
|
4604
|
-
child.on("close", (code) => {
|
|
4605
|
-
resolve2({
|
|
4606
|
-
exitCode: code ?? -1,
|
|
4607
|
-
stdout,
|
|
4608
|
-
stderr
|
|
4609
|
-
});
|
|
4610
|
-
});
|
|
4611
|
-
});
|
|
4559
|
+
function removeSurface(selection) {
|
|
4560
|
+
const targets = resolveSurfaceSelection(selection);
|
|
4561
|
+
return targets.map((surface) => removeAutomatedSurface(surface));
|
|
4612
4562
|
}
|
|
4613
|
-
function
|
|
4614
|
-
|
|
4563
|
+
async function addMcpSurface(selection) {
|
|
4564
|
+
const targets = resolveMcpSurfaceSelection(selection);
|
|
4565
|
+
const results = [];
|
|
4566
|
+
for (const surface of targets) {
|
|
4567
|
+
results.push(await addAutomatedSurface(surface));
|
|
4568
|
+
}
|
|
4569
|
+
return results;
|
|
4615
4570
|
}
|
|
4616
|
-
|
|
4617
|
-
const
|
|
4618
|
-
return
|
|
4571
|
+
function removeMcpSurface(selection) {
|
|
4572
|
+
const targets = resolveMcpSurfaceSelection(selection);
|
|
4573
|
+
return targets.map((surface) => removeAutomatedSurface(surface));
|
|
4619
4574
|
}
|
|
4620
|
-
async function
|
|
4621
|
-
const
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4575
|
+
async function setupDetectedSurfaces() {
|
|
4576
|
+
const targets = AUTOMATED_SURFACE_NAMES.filter((name) => getSurfaceStatus(name).detected);
|
|
4577
|
+
targets.sort((left, right) => {
|
|
4578
|
+
if (left === "openclaw") return -1;
|
|
4579
|
+
if (right === "openclaw") return 1;
|
|
4580
|
+
return 0;
|
|
4581
|
+
});
|
|
4582
|
+
const results = [];
|
|
4583
|
+
for (const name of targets) {
|
|
4584
|
+
results.push(await addAutomatedSurface(name));
|
|
4628
4585
|
}
|
|
4629
|
-
return
|
|
4630
|
-
available: true,
|
|
4631
|
-
installed: extractClaudePluginNames(result.stdout).includes(ORGX_CLAUDE_PLUGIN_NAME)
|
|
4632
|
-
};
|
|
4586
|
+
return results;
|
|
4633
4587
|
}
|
|
4634
|
-
|
|
4635
|
-
const available = detectSurface("openclaw").detected || await commandExists("openclaw", runner);
|
|
4636
|
-
if (!available) {
|
|
4637
|
-
return { available: false, installed: false };
|
|
4638
|
-
}
|
|
4639
|
-
const result = await runner("openclaw", ["plugins", "list", "--json"]);
|
|
4640
|
-
if (result.exitCode !== 0) {
|
|
4641
|
-
return { available: true, installed: false };
|
|
4642
|
-
}
|
|
4588
|
+
function skippedOpenClawHealth() {
|
|
4643
4589
|
return {
|
|
4644
|
-
|
|
4645
|
-
|
|
4590
|
+
ok: false,
|
|
4591
|
+
skipped: true,
|
|
4592
|
+
method: "none",
|
|
4593
|
+
error: "OpenClaw support files were not detected; skipping gateway health check.",
|
|
4594
|
+
details: []
|
|
4646
4595
|
};
|
|
4647
4596
|
}
|
|
4648
|
-
function
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
return detectSurface("cursor").detected || existsSync4(paths.cursorPluginDir) || existsSync4(dirname3(paths.cursorPluginDir)) || readTextIfExists(paths.cursorRulePath) !== null;
|
|
4597
|
+
async function runDoctor() {
|
|
4598
|
+
const surfaces = listSurfaceStatuses();
|
|
4599
|
+
const openclawSurface = surfaces.find((surface) => surface.name === "openclaw");
|
|
4600
|
+
const auth = await checkOrgxAuth();
|
|
4601
|
+
const hostedMcp = await checkHostedMcpHealth();
|
|
4602
|
+
const hostedMcpTool = await checkHostedMcpToolAccess();
|
|
4603
|
+
const npmRegistry = await checkNpmRegistryHealth();
|
|
4604
|
+
const workspace = await checkWorkspaceConnectivity();
|
|
4605
|
+
const openclaw = openclawSurface?.detected ? await checkOpenClawHealth(openclawSurface.path) : skippedOpenClawHealth();
|
|
4606
|
+
return { surfaces, auth, hostedMcp, hostedMcpTool, npmRegistry, workspace, openclaw };
|
|
4659
4607
|
}
|
|
4660
|
-
function
|
|
4661
|
-
const
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
installed: true,
|
|
4669
|
-
message: "Cursor OrgX plugin bundle is installed."
|
|
4670
|
-
};
|
|
4608
|
+
function assessDoctorReport(report) {
|
|
4609
|
+
const issues = [];
|
|
4610
|
+
if (!report.hostedMcp.ok) {
|
|
4611
|
+
issues.push({
|
|
4612
|
+
level: "error",
|
|
4613
|
+
title: "Hosted OrgX MCP is unreachable.",
|
|
4614
|
+
suggestion: "Retry later or verify https://mcp.useorgx.com/health. Hosted-only clients will stay broken until that endpoint recovers."
|
|
4615
|
+
});
|
|
4671
4616
|
}
|
|
4672
|
-
if (
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
};
|
|
4617
|
+
if (report.hostedMcp.ok && report.hostedMcpTool.configured && !report.hostedMcpTool.ok && !report.hostedMcpTool.skipped) {
|
|
4618
|
+
issues.push({
|
|
4619
|
+
level: "error",
|
|
4620
|
+
title: `Hosted OrgX MCP tool verification failed (${report.hostedMcpTool.toolName}).`,
|
|
4621
|
+
suggestion: "Re-run `wizard auth login` to refresh the per-user key, then retry `wizard doctor` to confirm the hosted MCP can initialize and execute tools."
|
|
4622
|
+
});
|
|
4679
4623
|
}
|
|
4680
|
-
if (
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
};
|
|
4624
|
+
if (report.auth.configured && !report.auth.ok && !report.auth.skipped) {
|
|
4625
|
+
issues.push({
|
|
4626
|
+
level: "error",
|
|
4627
|
+
title: "OrgX user auth could not be verified.",
|
|
4628
|
+
suggestion: "Run `wizard auth set-key <oxk_...>` again or correct the resolved ORGX_API_KEY / base URL before retrying."
|
|
4629
|
+
});
|
|
4687
4630
|
}
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
}
|
|
4695
|
-
async function buildClaudeStatus(paths, runner) {
|
|
4696
|
-
const state = await getClaudeInstallState(runner);
|
|
4697
|
-
if (state.installed) {
|
|
4698
|
-
return {
|
|
4699
|
-
target: "claude",
|
|
4700
|
-
available: true,
|
|
4701
|
-
installed: true,
|
|
4702
|
-
message: "Claude Code plugin is installed."
|
|
4703
|
-
};
|
|
4631
|
+
if (report.workspace.configured && !report.workspace.ok) {
|
|
4632
|
+
issues.push({
|
|
4633
|
+
level: "error",
|
|
4634
|
+
title: "Current workspace lookup failed.",
|
|
4635
|
+
suggestion: "Verify the resolved OrgX base URL and that the authenticated user still has access to a workspace."
|
|
4636
|
+
});
|
|
4704
4637
|
}
|
|
4705
|
-
if (!
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4638
|
+
if (!report.npmRegistry.reachable) {
|
|
4639
|
+
issues.push({
|
|
4640
|
+
level: "warning",
|
|
4641
|
+
title: "npm registry is unreachable.",
|
|
4642
|
+
suggestion: "Check outbound network access to https://registry.npmjs.org before relying on npm-based installation flows."
|
|
4643
|
+
});
|
|
4644
|
+
} else if (!report.npmRegistry.published) {
|
|
4645
|
+
issues.push({
|
|
4646
|
+
level: "warning",
|
|
4647
|
+
title: `${report.npmRegistry.packageName} is not published on npm yet.`,
|
|
4648
|
+
suggestion: "This is expected before launch. Publish the package in the release workstream before asking users to install from npm."
|
|
4649
|
+
});
|
|
4650
|
+
}
|
|
4651
|
+
if (!report.openclaw.skipped && !report.openclaw.ok) {
|
|
4652
|
+
issues.push({
|
|
4653
|
+
level: "warning",
|
|
4654
|
+
title: "OpenClaw is installed but its local gateway health check failed.",
|
|
4655
|
+
suggestion: "Run `openclaw doctor` or `openclaw doctor --repair` before relying on local bridge clients like Cursor."
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
const cursor = report.surfaces.find((surface) => surface.name === "cursor");
|
|
4659
|
+
const ci = cursor?.cursorIntegration;
|
|
4660
|
+
if (cursor?.detected && ci && (ci.mcpLocalOpenclaw || ci.mcpHostedOrgx) && !ci.pluginBundleInstalled) {
|
|
4661
|
+
issues.push({
|
|
4662
|
+
level: "warning",
|
|
4663
|
+
title: "Cursor has OrgX MCP wiring but not the full companion plugin bundle.",
|
|
4664
|
+
suggestion: "Run `orgx-wizard plugins add cursor` to install the managed pack (skills, rules, hooks, commands), then reload Cursor. Skip this if you intentionally use MCP-only mode."
|
|
4665
|
+
});
|
|
4712
4666
|
}
|
|
4713
|
-
const marketplaceExists = existsSync4(paths.claudeMarketplaceManifestPath);
|
|
4714
4667
|
return {
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
installed: false,
|
|
4718
|
-
message: marketplaceExists ? "Claude marketplace wrapper exists, but the plugin is not installed." : "Claude Code is available and ready for plugin install."
|
|
4668
|
+
ok: !issues.some((issue) => issue.level === "error"),
|
|
4669
|
+
issues
|
|
4719
4670
|
};
|
|
4720
4671
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
if (
|
|
4736
|
-
return
|
|
4737
|
-
target: "codex",
|
|
4738
|
-
available: false,
|
|
4739
|
-
installed: false,
|
|
4740
|
-
message: "Codex was not detected."
|
|
4741
|
-
};
|
|
4742
|
-
}
|
|
4743
|
-
if (pluginExists && !marketplaceExists) {
|
|
4744
|
-
return {
|
|
4745
|
-
target: "codex",
|
|
4746
|
-
available: true,
|
|
4747
|
-
installed: false,
|
|
4748
|
-
message: "Codex plugin files exist, but the marketplace entry is missing."
|
|
4749
|
-
};
|
|
4672
|
+
|
|
4673
|
+
// src/lib/continuity.ts
|
|
4674
|
+
var REVIEW_SURFACE_ORDER = [
|
|
4675
|
+
"claude",
|
|
4676
|
+
"cursor",
|
|
4677
|
+
"codex",
|
|
4678
|
+
"vscode",
|
|
4679
|
+
"windsurf",
|
|
4680
|
+
"zed",
|
|
4681
|
+
"chatgpt"
|
|
4682
|
+
];
|
|
4683
|
+
function selectReviewSurface(statuses) {
|
|
4684
|
+
for (const name of REVIEW_SURFACE_ORDER) {
|
|
4685
|
+
const configured = statuses.find((status) => status.name === name && status.configured);
|
|
4686
|
+
if (configured) {
|
|
4687
|
+
return configured.name;
|
|
4750
4688
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
message: "Codex marketplace entry exists, but plugin files are missing."
|
|
4757
|
-
};
|
|
4689
|
+
}
|
|
4690
|
+
for (const name of REVIEW_SURFACE_ORDER) {
|
|
4691
|
+
const detected = statuses.find((status) => status.name === name && status.detected);
|
|
4692
|
+
if (detected) {
|
|
4693
|
+
return detected.name;
|
|
4758
4694
|
}
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
available: true,
|
|
4762
|
-
installed: false,
|
|
4763
|
-
message: "Codex is available and ready for plugin install."
|
|
4764
|
-
};
|
|
4765
|
-
})();
|
|
4695
|
+
}
|
|
4696
|
+
return void 0;
|
|
4766
4697
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4698
|
+
function inferExecutionMode(explicitMode, statuses) {
|
|
4699
|
+
if (explicitMode) {
|
|
4700
|
+
return explicitMode;
|
|
4701
|
+
}
|
|
4702
|
+
const openclaw = statuses.find((status) => status.name === "openclaw");
|
|
4703
|
+
if (openclaw?.configured || openclaw?.detected) {
|
|
4704
|
+
return "local";
|
|
4705
|
+
}
|
|
4706
|
+
const hostedSurface = statuses.find(
|
|
4707
|
+
(status) => status.name !== "openclaw" && (status.configured || status.detected)
|
|
4708
|
+
);
|
|
4709
|
+
return hostedSurface ? "cloud" : void 0;
|
|
4710
|
+
}
|
|
4711
|
+
function inferContinuityDefaults(seed = {}) {
|
|
4712
|
+
const statuses = seed.statuses ?? listSurfaceStatuses();
|
|
4713
|
+
const continuity = {};
|
|
4714
|
+
const reviewSurface = selectReviewSurface(statuses);
|
|
4715
|
+
if (reviewSurface) {
|
|
4716
|
+
continuity.reviewSurface = reviewSurface;
|
|
4717
|
+
}
|
|
4718
|
+
const executionMode = inferExecutionMode(seed.executionMode, statuses);
|
|
4719
|
+
if (executionMode) {
|
|
4720
|
+
continuity.executionMode = executionMode;
|
|
4721
|
+
}
|
|
4722
|
+
if (seed.workspace?.id) {
|
|
4723
|
+
continuity.workspaceId = seed.workspace.id;
|
|
4776
4724
|
}
|
|
4725
|
+
const workspaceName = seed.workspace?.name ?? seed.workspaceName ?? void 0;
|
|
4726
|
+
if (workspaceName?.trim()) {
|
|
4727
|
+
continuity.workspaceName = workspaceName.trim();
|
|
4728
|
+
}
|
|
4729
|
+
return continuity;
|
|
4730
|
+
}
|
|
4731
|
+
function mergeContinuityDefaults(current, next) {
|
|
4777
4732
|
return {
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
installed: false,
|
|
4781
|
-
message: state.available ? "OpenClaw is available and ready for plugin install." : "OpenClaw was not detected."
|
|
4733
|
+
...current ?? {},
|
|
4734
|
+
...next
|
|
4782
4735
|
};
|
|
4783
4736
|
}
|
|
4784
|
-
|
|
4785
|
-
const
|
|
4786
|
-
|
|
4787
|
-
{
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
}
|
|
4737
|
+
function persistContinuityDefaults(seed = {}, statePath) {
|
|
4738
|
+
const next = inferContinuityDefaults(seed);
|
|
4739
|
+
return updateWizardState(
|
|
4740
|
+
(current) => ({
|
|
4741
|
+
...current,
|
|
4742
|
+
continuity: mergeContinuityDefaults(current.continuity, next)
|
|
4743
|
+
}),
|
|
4744
|
+
statePath
|
|
4793
4745
|
);
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
// src/lib/initiatives.ts
|
|
4749
|
+
var FOUNDER_DEMO_INITIATIVE_TITLE = "Founder Demo Initiative";
|
|
4750
|
+
var FOUNDER_DEMO_INITIATIVE_SUMMARY = "Starter initiative created by @useorgx/wizard to validate workspace routing, live views, and the default OrgX skill pack.";
|
|
4751
|
+
var FOUNDER_DEMO_DECISION_TITLE = "Approve founder demo workspace";
|
|
4752
|
+
var FOUNDER_DEMO_DECISION_SUMMARY = "Initial decision created by @useorgx/wizard so the founder preset leaves the workspace with a resolved approval trail.";
|
|
4753
|
+
var FOUNDER_DEMO_DECISION_RESOLUTION = "Approved automatically by @useorgx/wizard after the founder preset finished creating the live demo workspace.";
|
|
4754
|
+
var FOUNDER_DEMO_ARTIFACT_NAME = "Founder Demo Live View";
|
|
4755
|
+
var FOUNDER_DEMO_ARTIFACT_TYPE = "document";
|
|
4756
|
+
var FOUNDER_DEMO_ARTIFACT_DESCRIPTION = "Live founder demo generated by @useorgx/wizard after workspace bootstrap completed.";
|
|
4757
|
+
var ONBOARDING_TASK_TITLE = "Complete OrgX onboarding";
|
|
4758
|
+
var ONBOARDING_TASK_SUMMARY = "Starter onboarding task created by @useorgx/wizard so the first workspace has a clear follow-up after setup.";
|
|
4759
|
+
var ONBOARDING_WORKSTREAM_TITLE = "OrgX onboarding";
|
|
4760
|
+
var ONBOARDING_WORKSTREAM_SUMMARY = "Starter onboarding workstream created by @useorgx/wizard so the first workspace has a home for setup follow-up tasks.";
|
|
4761
|
+
var FIRST_VALUE_INITIATIVE_TITLE = "Make OrgX useful on this machine";
|
|
4762
|
+
var FIRST_VALUE_INITIATIVE_SUMMARY = "First initiative created by @useorgx/wizard so setup ends with a live workspace, an onboarding workstream, and a clean handoff into the user's configured AI tools.";
|
|
4763
|
+
function parseResponseBody3(text2) {
|
|
4764
|
+
if (!text2) {
|
|
4765
|
+
return null;
|
|
4798
4766
|
}
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
throw new Error(`Could not resolve a tarball URL for ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`);
|
|
4767
|
+
try {
|
|
4768
|
+
return JSON.parse(text2);
|
|
4769
|
+
} catch {
|
|
4770
|
+
return text2;
|
|
4804
4771
|
}
|
|
4805
|
-
return {
|
|
4806
|
-
tarballUrl,
|
|
4807
|
-
version: latestVersion
|
|
4808
|
-
};
|
|
4809
4772
|
}
|
|
4810
|
-
|
|
4811
|
-
if (
|
|
4812
|
-
return {
|
|
4813
|
-
target: "cursor",
|
|
4814
|
-
changed: false,
|
|
4815
|
-
message: "Cursor is not available on this machine."
|
|
4816
|
-
};
|
|
4773
|
+
function formatHttpError2(status, body) {
|
|
4774
|
+
if (typeof body === "string" && body.trim().length > 0) {
|
|
4775
|
+
return `HTTP ${status}: ${body}`;
|
|
4817
4776
|
}
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
message: syncResult.changed ? `Synced ${syncResult.fileCount} Cursor plugin files into ${paths.cursorPluginDir}. Reload Cursor to finish loading the plugin.` : "Cursor plugin bundle is already installed and up to date."
|
|
4823
|
-
};
|
|
4777
|
+
if (isRecord(body) && typeof body.error === "string" && body.error.trim().length > 0) {
|
|
4778
|
+
return `HTTP ${status}: ${body.error}`;
|
|
4779
|
+
}
|
|
4780
|
+
return `HTTP ${status}`;
|
|
4824
4781
|
}
|
|
4825
|
-
|
|
4826
|
-
const
|
|
4827
|
-
if (!
|
|
4828
|
-
|
|
4829
|
-
target: "claude",
|
|
4830
|
-
changed: false,
|
|
4831
|
-
message: "Claude Code is not available on this machine."
|
|
4832
|
-
};
|
|
4782
|
+
function extractEntity(payload) {
|
|
4783
|
+
const entity = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
|
|
4784
|
+
if (!isRecord(entity)) {
|
|
4785
|
+
throw new Error("OrgX returned an unexpected entity payload.");
|
|
4833
4786
|
}
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
);
|
|
4839
|
-
const
|
|
4840
|
-
if (
|
|
4841
|
-
throw new Error(
|
|
4787
|
+
return entity;
|
|
4788
|
+
}
|
|
4789
|
+
function parseInitiative(payload) {
|
|
4790
|
+
const entity = extractEntity(payload);
|
|
4791
|
+
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
4792
|
+
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
4793
|
+
if (!id || !title) {
|
|
4794
|
+
throw new Error("OrgX returned an incomplete initiative payload.");
|
|
4842
4795
|
}
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
"claude",
|
|
4856
|
-
[
|
|
4857
|
-
"plugin",
|
|
4858
|
-
"install",
|
|
4859
|
-
`${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
|
|
4860
|
-
"--scope",
|
|
4861
|
-
"user"
|
|
4862
|
-
],
|
|
4863
|
-
install
|
|
4864
|
-
)
|
|
4865
|
-
);
|
|
4866
|
-
}
|
|
4867
|
-
installedChanged = true;
|
|
4796
|
+
return {
|
|
4797
|
+
id,
|
|
4798
|
+
title,
|
|
4799
|
+
...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
|
|
4800
|
+
};
|
|
4801
|
+
}
|
|
4802
|
+
function parseDecision(payload) {
|
|
4803
|
+
const entity = extractEntity(payload);
|
|
4804
|
+
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
4805
|
+
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
4806
|
+
if (!id || !title) {
|
|
4807
|
+
throw new Error("OrgX returned an incomplete decision payload.");
|
|
4868
4808
|
}
|
|
4869
|
-
const changed = syncResult.changed || manifestChanged || installedChanged;
|
|
4870
4809
|
return {
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4810
|
+
id,
|
|
4811
|
+
title,
|
|
4812
|
+
...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
|
|
4813
|
+
...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
|
|
4874
4814
|
};
|
|
4875
4815
|
}
|
|
4876
|
-
|
|
4877
|
-
const
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4816
|
+
function parseArtifact(payload) {
|
|
4817
|
+
const entity = extractEntity(payload);
|
|
4818
|
+
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
4819
|
+
const name = typeof entity.name === "string" ? entity.name.trim() : typeof entity.title === "string" ? entity.title.trim() : "";
|
|
4820
|
+
const type = typeof entity.artifact_type === "string" ? entity.artifact_type.trim() : typeof entity.type === "string" ? entity.type.trim() : "";
|
|
4821
|
+
const url = typeof entity.external_url === "string" ? entity.external_url.trim() : typeof entity.url === "string" ? entity.url.trim() : "";
|
|
4822
|
+
if (!id || !name) {
|
|
4823
|
+
throw new Error("OrgX returned an incomplete artifact payload.");
|
|
4884
4824
|
}
|
|
4885
|
-
const syncResult = await syncManagedRepoTree(CODEX_PLUGIN_SYNC_SPEC, paths.codexPluginDir, fetchImpl);
|
|
4886
|
-
const marketplaceChanged = upsertCodexMarketplaceEntry(paths.codexMarketplacePath);
|
|
4887
|
-
const changed = syncResult.changed || marketplaceChanged;
|
|
4888
4825
|
return {
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4826
|
+
id,
|
|
4827
|
+
name,
|
|
4828
|
+
...type ? { type } : {},
|
|
4829
|
+
...url ? { url } : {}
|
|
4892
4830
|
};
|
|
4893
4831
|
}
|
|
4894
|
-
|
|
4895
|
-
const
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
message: "OpenClaw is not available on this machine."
|
|
4901
|
-
};
|
|
4902
|
-
}
|
|
4903
|
-
if (state.installed) {
|
|
4904
|
-
return {
|
|
4905
|
-
target: "openclaw",
|
|
4906
|
-
changed: false,
|
|
4907
|
-
message: "OpenClaw plugin is already installed."
|
|
4908
|
-
};
|
|
4832
|
+
function parseTask(payload) {
|
|
4833
|
+
const entity = extractEntity(payload);
|
|
4834
|
+
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
4835
|
+
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
4836
|
+
if (!id || !title) {
|
|
4837
|
+
throw new Error("OrgX returned an incomplete task payload.");
|
|
4909
4838
|
}
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
rmSync(tempRoot, { force: true, recursive: true });
|
|
4839
|
+
return {
|
|
4840
|
+
id,
|
|
4841
|
+
title,
|
|
4842
|
+
...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
|
|
4843
|
+
...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4846
|
+
function parseWorkstream(payload) {
|
|
4847
|
+
const entity = extractEntity(payload);
|
|
4848
|
+
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
4849
|
+
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
4850
|
+
if (!id || !title) {
|
|
4851
|
+
throw new Error("OrgX returned an incomplete workstream payload.");
|
|
4924
4852
|
}
|
|
4853
|
+
return { id, title };
|
|
4854
|
+
}
|
|
4855
|
+
function toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace) {
|
|
4925
4856
|
return {
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4857
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4858
|
+
artifactId: artifact.id,
|
|
4859
|
+
artifactName: artifact.name,
|
|
4860
|
+
...artifact.type ? { artifactType: artifact.type } : {},
|
|
4861
|
+
...artifact.url ? { artifactUrl: artifact.url } : {},
|
|
4862
|
+
decisionId: decision.id,
|
|
4863
|
+
...decision.status ? { decisionStatus: decision.status } : {},
|
|
4864
|
+
decisionTitle: decision.title,
|
|
4865
|
+
id: initiative.id,
|
|
4866
|
+
liveUrl,
|
|
4867
|
+
title: initiative.title,
|
|
4868
|
+
workspaceId: workspace.id,
|
|
4869
|
+
workspaceName: workspace.name
|
|
4929
4870
|
};
|
|
4930
4871
|
}
|
|
4931
|
-
function
|
|
4932
|
-
const existingRules = readTextIfExists(paths.cursorRulePath);
|
|
4933
|
-
const removedPluginDir = removePathIfExists(paths.cursorPluginDir);
|
|
4934
|
-
const removedLegacyRules = existingRules === CURSOR_RULES_CONTENT ? deleteFileIfExists(paths.cursorRulePath) : false;
|
|
4935
|
-
const changed = removedPluginDir || removedLegacyRules;
|
|
4872
|
+
function toOnboardingTaskRecord(task, workspace, options = {}) {
|
|
4936
4873
|
return {
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4874
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4875
|
+
id: task.id,
|
|
4876
|
+
...options.initiativeId ? { initiativeId: options.initiativeId } : {},
|
|
4877
|
+
...task.status ? { status: task.status } : {},
|
|
4878
|
+
title: task.title,
|
|
4879
|
+
...options.workstreamId ? { workstreamId: options.workstreamId } : {},
|
|
4880
|
+
workspaceId: workspace.id,
|
|
4881
|
+
workspaceName: workspace.name
|
|
4940
4882
|
};
|
|
4941
4883
|
}
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
)
|
|
4960
|
-
);
|
|
4961
|
-
}
|
|
4962
|
-
changed = true;
|
|
4884
|
+
function toFirstValueInitiativeRecord(initiative, liveUrl, workspace) {
|
|
4885
|
+
return {
|
|
4886
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4887
|
+
id: initiative.id,
|
|
4888
|
+
liveUrl,
|
|
4889
|
+
...initiative.summary ? { summary: initiative.summary } : {},
|
|
4890
|
+
title: initiative.title,
|
|
4891
|
+
workspaceId: workspace.id,
|
|
4892
|
+
workspaceName: workspace.name
|
|
4893
|
+
};
|
|
4894
|
+
}
|
|
4895
|
+
async function requireOrgxAuth2(options = {}) {
|
|
4896
|
+
const auth = await resolveOrgxAuth(options);
|
|
4897
|
+
if (!auth) {
|
|
4898
|
+
throw new Error(
|
|
4899
|
+
"No OrgX API key configured. Run `wizard auth login` or `wizard auth set-key <oxk_...>` first."
|
|
4900
|
+
);
|
|
4963
4901
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4902
|
+
return auth;
|
|
4903
|
+
}
|
|
4904
|
+
async function createEntity(type, body, parse2, options = {}) {
|
|
4905
|
+
const auth = await requireOrgxAuth2(options);
|
|
4906
|
+
const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
|
|
4907
|
+
method: "POST",
|
|
4908
|
+
headers: {
|
|
4909
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
4910
|
+
"Content-Type": "application/json"
|
|
4911
|
+
},
|
|
4912
|
+
body: JSON.stringify({
|
|
4913
|
+
type,
|
|
4914
|
+
...body
|
|
4915
|
+
}),
|
|
4916
|
+
signal: AbortSignal.timeout(7e3)
|
|
4917
|
+
});
|
|
4918
|
+
const responseBody = parseResponseBody3(await response.text());
|
|
4919
|
+
if (!response.ok) {
|
|
4920
|
+
throw new Error(`Failed to create ${type}. ${formatHttpError2(response.status, responseBody)}`);
|
|
4921
|
+
}
|
|
4922
|
+
return parse2(responseBody);
|
|
4923
|
+
}
|
|
4924
|
+
async function updateEntity(type, id, body, parse2, options = {}) {
|
|
4925
|
+
const auth = await requireOrgxAuth2(options);
|
|
4926
|
+
const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
|
|
4927
|
+
method: "PATCH",
|
|
4928
|
+
headers: {
|
|
4929
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
4930
|
+
"Content-Type": "application/json"
|
|
4931
|
+
},
|
|
4932
|
+
body: JSON.stringify({
|
|
4933
|
+
type,
|
|
4934
|
+
id,
|
|
4935
|
+
...body
|
|
4936
|
+
}),
|
|
4937
|
+
signal: AbortSignal.timeout(7e3)
|
|
4938
|
+
});
|
|
4939
|
+
const responseBody = parseResponseBody3(await response.text());
|
|
4940
|
+
if (!response.ok) {
|
|
4941
|
+
throw new Error(`Failed to update ${type}. ${formatHttpError2(response.status, responseBody)}`);
|
|
4942
|
+
}
|
|
4943
|
+
return parse2(responseBody);
|
|
4944
|
+
}
|
|
4945
|
+
function buildLiveUrl(baseUrl, initiativeId) {
|
|
4946
|
+
const parsed = new URL(normalizeOrgxBaseUrl(baseUrl));
|
|
4947
|
+
parsed.pathname = `/live/${initiativeId}`;
|
|
4948
|
+
parsed.search = "";
|
|
4949
|
+
parsed.hash = "";
|
|
4950
|
+
return parsed.toString();
|
|
4951
|
+
}
|
|
4952
|
+
async function createFounderDemoDecision(initiative, workspaceId, options = {}) {
|
|
4953
|
+
return createEntity(
|
|
4954
|
+
"decision",
|
|
4955
|
+
{
|
|
4956
|
+
initiative_id: initiative.id,
|
|
4957
|
+
summary: FOUNDER_DEMO_DECISION_SUMMARY,
|
|
4958
|
+
title: FOUNDER_DEMO_DECISION_TITLE,
|
|
4959
|
+
workspace_id: workspaceId
|
|
4960
|
+
},
|
|
4961
|
+
parseDecision,
|
|
4962
|
+
options
|
|
4963
|
+
);
|
|
4964
|
+
}
|
|
4965
|
+
async function approveFounderDemoDecision(decisionId, options = {}) {
|
|
4966
|
+
return updateEntity(
|
|
4967
|
+
"decision",
|
|
4968
|
+
decisionId,
|
|
4969
|
+
{
|
|
4970
|
+
resolution_summary: FOUNDER_DEMO_DECISION_RESOLUTION,
|
|
4971
|
+
status: "approved"
|
|
4972
|
+
},
|
|
4973
|
+
parseDecision,
|
|
4974
|
+
options
|
|
4975
|
+
);
|
|
4976
|
+
}
|
|
4977
|
+
async function createFounderDemoArtifact(initiative, liveUrl, workspaceId, options = {}) {
|
|
4978
|
+
return createEntity(
|
|
4979
|
+
"artifact",
|
|
4980
|
+
{
|
|
4981
|
+
artifact_type: FOUNDER_DEMO_ARTIFACT_TYPE,
|
|
4982
|
+
description: FOUNDER_DEMO_ARTIFACT_DESCRIPTION,
|
|
4983
|
+
entity_id: initiative.id,
|
|
4984
|
+
entity_type: "initiative",
|
|
4985
|
+
external_url: liveUrl,
|
|
4986
|
+
initiative_id: initiative.id,
|
|
4987
|
+
name: FOUNDER_DEMO_ARTIFACT_NAME,
|
|
4988
|
+
workspace_id: workspaceId
|
|
4989
|
+
},
|
|
4990
|
+
parseArtifact,
|
|
4991
|
+
options
|
|
4992
|
+
);
|
|
4993
|
+
}
|
|
4994
|
+
async function createInitiative(input, options = {}) {
|
|
4995
|
+
const title = input.title.trim();
|
|
4996
|
+
if (!title) {
|
|
4997
|
+
throw new Error("Initiative title is required.");
|
|
4969
4998
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4999
|
+
return createEntity(
|
|
5000
|
+
"initiative",
|
|
5001
|
+
{
|
|
5002
|
+
title,
|
|
5003
|
+
status: "active",
|
|
5004
|
+
...input.summary?.trim() ? { summary: input.summary.trim() } : {},
|
|
5005
|
+
...input.workspaceId?.trim() ? { workspace_id: input.workspaceId.trim() } : {}
|
|
5006
|
+
},
|
|
5007
|
+
parseInitiative,
|
|
5008
|
+
options
|
|
5009
|
+
);
|
|
4976
5010
|
}
|
|
4977
|
-
async function
|
|
4978
|
-
const
|
|
4979
|
-
const
|
|
4980
|
-
const
|
|
5011
|
+
async function ensureFounderDemoInitiative(workspace, options = {}) {
|
|
5012
|
+
const existingRecord = readWizardState(options.statePath)?.demoInitiative;
|
|
5013
|
+
const matchingRecord = existingRecord?.workspaceId === workspace.id ? existingRecord : void 0;
|
|
5014
|
+
const auth = await requireOrgxAuth2(options);
|
|
5015
|
+
const initiative = matchingRecord ? {
|
|
5016
|
+
id: matchingRecord.id,
|
|
5017
|
+
title: matchingRecord.title
|
|
5018
|
+
} : await createInitiative(
|
|
5019
|
+
{
|
|
5020
|
+
title: FOUNDER_DEMO_INITIATIVE_TITLE,
|
|
5021
|
+
summary: FOUNDER_DEMO_INITIATIVE_SUMMARY,
|
|
5022
|
+
workspaceId: workspace.id
|
|
5023
|
+
},
|
|
5024
|
+
options
|
|
5025
|
+
);
|
|
5026
|
+
const liveUrl = matchingRecord?.liveUrl || buildLiveUrl(auth.baseUrl, initiative.id);
|
|
5027
|
+
const decision = matchingRecord?.decisionId ? matchingRecord.decisionStatus === "approved" && matchingRecord.decisionTitle ? {
|
|
5028
|
+
id: matchingRecord.decisionId,
|
|
5029
|
+
status: matchingRecord.decisionStatus,
|
|
5030
|
+
title: matchingRecord.decisionTitle
|
|
5031
|
+
} : await approveFounderDemoDecision(matchingRecord.decisionId, options) : await approveFounderDemoDecision(
|
|
5032
|
+
(await createFounderDemoDecision(initiative, workspace.id, options)).id,
|
|
5033
|
+
options
|
|
5034
|
+
);
|
|
5035
|
+
const artifact = matchingRecord?.artifactId && matchingRecord.artifactName ? {
|
|
5036
|
+
id: matchingRecord.artifactId,
|
|
5037
|
+
name: matchingRecord.artifactName,
|
|
5038
|
+
...matchingRecord.artifactType ? { type: matchingRecord.artifactType } : {},
|
|
5039
|
+
...matchingRecord.artifactUrl ? { url: matchingRecord.artifactUrl } : {}
|
|
5040
|
+
} : await createFounderDemoArtifact(initiative, liveUrl, workspace.id, options);
|
|
5041
|
+
const record = toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace);
|
|
5042
|
+
updateWizardState(
|
|
5043
|
+
(current) => ({
|
|
5044
|
+
...current,
|
|
5045
|
+
demoInitiative: record
|
|
5046
|
+
}),
|
|
5047
|
+
options.statePath
|
|
5048
|
+
);
|
|
4981
5049
|
return {
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
5050
|
+
artifact,
|
|
5051
|
+
created: !matchingRecord,
|
|
5052
|
+
decision,
|
|
5053
|
+
initiative,
|
|
5054
|
+
liveUrl
|
|
4985
5055
|
};
|
|
4986
5056
|
}
|
|
4987
|
-
async function
|
|
4988
|
-
const
|
|
4989
|
-
|
|
5057
|
+
async function ensureFirstValueInitiative(workspace, options = {}) {
|
|
5058
|
+
const existingRecord = readWizardState(options.statePath)?.firstValueInitiative;
|
|
5059
|
+
const matchingRecord = existingRecord?.workspaceId === workspace.id ? existingRecord : void 0;
|
|
5060
|
+
const auth = await requireOrgxAuth2(options);
|
|
5061
|
+
if (matchingRecord) {
|
|
4990
5062
|
return {
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
5063
|
+
created: false,
|
|
5064
|
+
initiative: {
|
|
5065
|
+
id: matchingRecord.id,
|
|
5066
|
+
title: matchingRecord.title,
|
|
5067
|
+
...matchingRecord.summary ? { summary: matchingRecord.summary } : {}
|
|
5068
|
+
},
|
|
5069
|
+
liveUrl: matchingRecord.liveUrl || buildLiveUrl(auth.baseUrl, matchingRecord.id)
|
|
4994
5070
|
};
|
|
4995
5071
|
}
|
|
4996
|
-
const
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5072
|
+
const initiative = await createInitiative(
|
|
5073
|
+
{
|
|
5074
|
+
title: options.title?.trim() || FIRST_VALUE_INITIATIVE_TITLE,
|
|
5075
|
+
summary: options.summary?.trim() || FIRST_VALUE_INITIATIVE_SUMMARY,
|
|
5076
|
+
workspaceId: workspace.id
|
|
5077
|
+
},
|
|
5078
|
+
options
|
|
5079
|
+
);
|
|
5080
|
+
const liveUrl = buildLiveUrl(auth.baseUrl, initiative.id);
|
|
5081
|
+
updateWizardState(
|
|
5082
|
+
(current) => ({
|
|
5083
|
+
...current,
|
|
5084
|
+
firstValueInitiative: toFirstValueInitiativeRecord(initiative, liveUrl, workspace)
|
|
5085
|
+
}),
|
|
5086
|
+
options.statePath
|
|
5087
|
+
);
|
|
5006
5088
|
return {
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5089
|
+
created: true,
|
|
5090
|
+
initiative,
|
|
5091
|
+
liveUrl
|
|
5010
5092
|
};
|
|
5011
5093
|
}
|
|
5012
|
-
function
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
if (target === "" || target === "all") continue;
|
|
5021
|
-
if (!DEFAULT_ORGX_PLUGIN_TARGETS.includes(target)) {
|
|
5022
|
-
throw new Error(
|
|
5023
|
-
`Unknown plugin target '${rawTarget}'. Supported targets: ${DEFAULT_ORGX_PLUGIN_TARGETS.join(", ")}.`
|
|
5024
|
-
);
|
|
5025
|
-
}
|
|
5026
|
-
const nextTarget = target;
|
|
5027
|
-
if (seen.has(nextTarget)) continue;
|
|
5028
|
-
seen.add(nextTarget);
|
|
5029
|
-
normalized.push(nextTarget);
|
|
5030
|
-
}
|
|
5031
|
-
return normalized;
|
|
5032
|
-
}
|
|
5033
|
-
async function listOrgxPluginStatuses(options = {}) {
|
|
5034
|
-
const paths = resolvePluginPaths(options.paths);
|
|
5035
|
-
const runner = resolveCommandRunner(options.commandRunner);
|
|
5036
|
-
return await Promise.all([
|
|
5037
|
-
buildCursorStatus(paths),
|
|
5038
|
-
buildClaudeStatus(paths, runner),
|
|
5039
|
-
buildCodexStatus(paths, runner),
|
|
5040
|
-
buildOpenclawStatus(runner)
|
|
5041
|
-
]);
|
|
5042
|
-
}
|
|
5043
|
-
async function installOrgxPlugins(options = {}) {
|
|
5044
|
-
const targets = resolvePluginTargets(options.targets ?? []);
|
|
5045
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
5046
|
-
if (!fetchImpl) {
|
|
5047
|
-
throw new Error("Global fetch is unavailable in this runtime.");
|
|
5048
|
-
}
|
|
5049
|
-
const paths = resolvePluginPaths(options.paths);
|
|
5050
|
-
const runner = resolveCommandRunner(options.commandRunner);
|
|
5051
|
-
const results = [];
|
|
5052
|
-
for (const target of targets) {
|
|
5053
|
-
switch (target) {
|
|
5054
|
-
case "cursor":
|
|
5055
|
-
results.push(await installCursorPlugin(paths, fetchImpl));
|
|
5056
|
-
break;
|
|
5057
|
-
case "claude":
|
|
5058
|
-
results.push(await installClaudePlugin(paths, fetchImpl, runner));
|
|
5059
|
-
break;
|
|
5060
|
-
case "codex":
|
|
5061
|
-
results.push(await installCodexPlugin(paths, fetchImpl, runner));
|
|
5062
|
-
break;
|
|
5063
|
-
case "openclaw":
|
|
5064
|
-
results.push(await installOpenclawPlugin(fetchImpl, runner));
|
|
5065
|
-
break;
|
|
5066
|
-
}
|
|
5094
|
+
async function ensureOnboardingTask(workspace, options = {}) {
|
|
5095
|
+
const existingRecord = readWizardState(options.statePath)?.onboardingTask;
|
|
5096
|
+
if (existingRecord?.workspaceId === workspace.id) {
|
|
5097
|
+
return {
|
|
5098
|
+
id: existingRecord.id,
|
|
5099
|
+
title: existingRecord.title,
|
|
5100
|
+
...existingRecord.status ? { status: existingRecord.status } : {}
|
|
5101
|
+
};
|
|
5067
5102
|
}
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
const runner = resolveCommandRunner(options.commandRunner);
|
|
5074
|
-
const results = [];
|
|
5075
|
-
for (const target of targets) {
|
|
5076
|
-
switch (target) {
|
|
5077
|
-
case "cursor":
|
|
5078
|
-
results.push(uninstallCursorPlugin(paths));
|
|
5079
|
-
break;
|
|
5080
|
-
case "claude":
|
|
5081
|
-
results.push(await uninstallClaudePlugin(paths, runner));
|
|
5082
|
-
break;
|
|
5083
|
-
case "codex":
|
|
5084
|
-
results.push(await uninstallCodexPlugin(paths));
|
|
5085
|
-
break;
|
|
5086
|
-
case "openclaw":
|
|
5087
|
-
results.push(await uninstallOpenclawPlugin(runner));
|
|
5088
|
-
break;
|
|
5089
|
-
}
|
|
5103
|
+
const initiativeId = options.initiativeId?.trim();
|
|
5104
|
+
if (!initiativeId) {
|
|
5105
|
+
throw new Error(
|
|
5106
|
+
"Starter onboarding task requires an initiative context. Re-run setup with the founder preset or create an initiative before requesting onboarding tasks."
|
|
5107
|
+
);
|
|
5090
5108
|
}
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5109
|
+
const workstream = await createEntity(
|
|
5110
|
+
"workstream",
|
|
5111
|
+
{
|
|
5112
|
+
initiative_id: initiativeId,
|
|
5113
|
+
status: "active",
|
|
5114
|
+
summary: ONBOARDING_WORKSTREAM_SUMMARY,
|
|
5115
|
+
title: ONBOARDING_WORKSTREAM_TITLE,
|
|
5116
|
+
workspace_id: workspace.id
|
|
5117
|
+
},
|
|
5118
|
+
parseWorkstream,
|
|
5119
|
+
options
|
|
5120
|
+
);
|
|
5121
|
+
const task = await createEntity(
|
|
5122
|
+
"task",
|
|
5123
|
+
{
|
|
5124
|
+
initiative_id: initiativeId,
|
|
5125
|
+
status: "todo",
|
|
5126
|
+
summary: ONBOARDING_TASK_SUMMARY,
|
|
5127
|
+
title: ONBOARDING_TASK_TITLE,
|
|
5128
|
+
workstream_id: workstream.id,
|
|
5129
|
+
workspace_id: workspace.id
|
|
5130
|
+
},
|
|
5131
|
+
parseTask,
|
|
5132
|
+
options
|
|
5133
|
+
);
|
|
5134
|
+
updateWizardState(
|
|
5135
|
+
(current) => ({
|
|
5136
|
+
...current,
|
|
5137
|
+
onboardingTask: toOnboardingTaskRecord(task, workspace, {
|
|
5138
|
+
initiativeId,
|
|
5139
|
+
workstreamId: workstream.id
|
|
5140
|
+
})
|
|
5141
|
+
}),
|
|
5142
|
+
options.statePath
|
|
5143
|
+
);
|
|
5144
|
+
return task;
|
|
5095
5145
|
}
|
|
5096
5146
|
|
|
5097
5147
|
// src/lib/setup-workspace.ts
|
|
@@ -8079,7 +8129,7 @@ function printDoctorReport(report, assessment) {
|
|
|
8079
8129
|
async function main() {
|
|
8080
8130
|
const program = new Command();
|
|
8081
8131
|
program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
|
|
8082
|
-
const pkgVersion = true ? "0.1.
|
|
8132
|
+
const pkgVersion = true ? "0.1.26" : void 0;
|
|
8083
8133
|
program.version(pkgVersion ?? "unknown", "-V, --version");
|
|
8084
8134
|
program.hook("preAction", () => {
|
|
8085
8135
|
console.log(renderBanner(pkgVersion));
|