@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.
Files changed (3) hide show
  1. package/dist/cli.js +2332 -2282
  2. package/dist/cli.js.map +1 -1
  3. 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: [`local orgx-openclaw: ${localUrl}`]
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/surfaces/registry.ts
2531
- var AUTH_SETUP_HINT = "orgx-wizard auth login";
2532
- var AUTH_SET_KEY_HINT = "orgx-wizard auth set-key";
2533
- var DOCTOR_HINT = "orgx-wizard doctor";
2534
- function getSurfacePath(name) {
2535
- const detection = detectSurface(name);
2536
- const locator = getSurfaceLocator(name);
2537
- const existingPath = detection.existingPath;
2538
- if (existingPath && locator.configPaths.includes(existingPath)) {
2539
- return existingPath;
2540
- }
2541
- return locator.configPaths[0] ?? detection.preferredPath ?? existingPath;
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 getOpenClawDependencyState() {
2544
- const detection = detectSurface("openclaw");
2545
- const path = detection.existingPath ?? detection.preferredPath;
2546
- const raw = path ? readTextIfExists(path) : null;
2547
- const inspection = inspectOpenClawConfig(raw);
2548
- const result = {
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 result;
2634
+ return normalized;
2559
2635
  }
2560
- function automatedSurfaceStatus(name) {
2561
- const detection = detectSurface(name);
2562
- const path = detection.existingPath ?? detection.preferredPath;
2563
- const openclaw = getOpenClawDependencyState();
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
- if (name === "cursor") {
2582
- const inspection2 = inspectCursorMcpConfig(path ? readTextIfExists(path) : null);
2583
- const configured = openclaw.detected && inspection2.localConfigured === true;
2584
- return {
2585
- name,
2586
- mode: "automated",
2587
- detected: detection.detected,
2588
- configured,
2589
- ...path ? { path } : {},
2590
- details: [
2591
- ...inspection2.details,
2592
- ...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; Cursor cannot use the local OrgX bridge yet."],
2593
- ...detection.evidence
2594
- ],
2595
- summary: configured ? "OrgX is connected in Cursor through the local OpenClaw bridge." : detection.detected ? openclaw.detected ? `Cursor is missing the local OrgX OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Cursor is ready to wire, but OpenClaw must be installed first." : "Cursor config directory was not detected on disk."
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
- if (name === "codex") {
2599
- const inspection2 = inspectCodexConfigToml(path ? readTextIfExists(path) : null);
2600
- const configured = inspection2.hostedConfigured === true && (openclaw.detected ? inspection2.localConfigured === true : true);
2601
- return {
2602
- name,
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
- if (name === "vscode") {
2616
- const inspection2 = inspectVscodeMcpConfig(path ? readTextIfExists(path) : null);
2617
- return {
2618
- name,
2619
- mode: "automated",
2620
- detected: detection.detected,
2621
- configured: inspection2.hostedConfigured === true,
2622
- ...path ? { path } : {},
2623
- details: [...inspection2.details, ...detection.evidence],
2624
- 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."
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
- if (name === "windsurf") {
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 formatAuthHintSource(authHint) {
2663
- switch (authHint.source) {
2664
- case "environment":
2665
- return "env ORGX_API_KEY";
2666
- case "wizard-store":
2667
- return "wizard auth store";
2668
- case "openclaw-config-file":
2669
- return "openclaw config";
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 buildChatgptManualStatus(detection, authHint) {
2673
- const path = detection.existingPath ?? detection.preferredPath;
2674
- const details = [...detection.evidence];
2675
- if (authHint) {
2676
- details.push(
2677
- `OrgX auth ready via ${formatAuthHintSource(authHint)} (${authHint.keyPrefix})`
2678
- );
2679
- details.push(`Hosted connector URL: ${ORGX_HOSTED_MCP_URL}`);
2680
- details.push(
2681
- "Manual step: in ChatGPT desktop developer mode, add a custom connector pointing at the hosted OrgX MCP URL."
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
- name: "chatgpt",
2705
- mode: "manual",
2706
- detected: detection.detected,
2707
- configured: false,
2708
- ...path ? { path } : {},
2709
- details,
2710
- summary
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 buildChatgptAddResult(path, authHint, toolCheck) {
2714
- if (!authHint) {
2715
- return {
2716
- name: "chatgpt",
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
- if (toolCheck && !toolCheck.ok && !toolCheck.skipped) {
2723
- const failure = toolCheck?.error ?? "hosted MCP verification did not complete.";
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
- ...path ? { path } : {},
2728
- 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.`
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
- name: "chatgpt",
2733
- changed: false,
2734
- ...path ? { path } : {},
2735
- 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.`
2768
+ changed: existing !== nextContent,
2769
+ created: existing === null,
2770
+ extension,
2771
+ path
2736
2772
  };
2737
2773
  }
2738
- function buildChatgptRemoveResult(path) {
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
- name: "chatgpt",
2741
- changed: false,
2742
- ...path ? { path } : {},
2743
- message: "Remove the custom OrgX connector from ChatGPT developer mode; the wizard does not manage ChatGPT config files directly."
2805
+ changed: nextContent !== existing,
2806
+ created: false,
2807
+ extension,
2808
+ path
2744
2809
  };
2745
2810
  }
2746
- function manualSurfaceStatus(name) {
2747
- const detection = detectSurface(name);
2748
- switch (name) {
2749
- case "chatgpt":
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 getSurfaceStatus(name) {
2754
- if (AUTOMATED_SURFACE_NAMES.includes(name)) {
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 listSurfaceStatuses() {
2762
- return SURFACE_NAMES.map(getSurfaceStatus);
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 resolveTargets(selection, allTargets) {
2765
- const values = Array.isArray(selection) ? selection : [selection];
2766
- if (values.includes("all")) {
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 [...new Set(values)];
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 resolveMcpSurfaceSelection(selection) {
2775
- return resolveTargets(selection, MCP_SURFACE_NAMES);
2850
+ async function readResponseText(response) {
2851
+ return response.text();
2776
2852
  }
2777
- async function addAutomatedSurface(name) {
2778
- const path = getSurfacePath(name);
2779
- const openclaw = getOpenClawDependencyState();
2780
- if (!path) {
2781
- return {
2782
- name,
2783
- changed: false,
2784
- message: `${name} has no writable config path on this platform.`
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
- switch (name) {
2788
- case "claude": {
2789
- const previous = readTextIfExists(path);
2790
- const next = patchClaudeMcpConfig(previous, openclaw.url);
2791
- writeTextFile(path, next);
2792
- return {
2793
- name,
2794
- changed: previous !== next,
2795
- path,
2796
- message: openclaw.detected ? "OrgX is connected in Claude with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Claude."
2797
- };
2798
- }
2799
- case "cursor": {
2800
- if (!openclaw.detected || !openclaw.url) {
2801
- return {
2802
- name,
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
- case "chatgpt": {
2869
- const authHint = peekResolvedOrgxAuth();
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
- default:
2876
- return {
2877
- name,
2878
- changed: false,
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 removeAutomatedSurface(name) {
2884
- const path = getSurfacePath(name);
2885
- if (!path) {
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
- name,
2910
+ label,
2911
+ path,
2888
2912
  changed: false,
2889
- message: `${name} has no writable config path on this platform.`
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
- switch (name) {
2893
- case "claude": {
2894
- const previous = readTextIfExists(path);
2895
- const next = removeClaudeMcpConfig(previous);
2896
- writeTextFile(path, next);
2897
- return {
2898
- name,
2899
- changed: previous !== next,
2900
- path,
2901
- message: "OrgX connection was removed from Claude."
2902
- };
2903
- }
2904
- case "cursor": {
2905
- const previous = readTextIfExists(path);
2906
- const next = removeCursorMcpConfig(previous);
2907
- writeTextFile(path, next);
2908
- return {
2909
- name,
2910
- changed: previous !== next,
2911
- path,
2912
- message: "OrgX connection was removed from Cursor."
2913
- };
2914
- }
2915
- case "codex": {
2916
- const previous = readTextIfExists(path);
2917
- const next = removeCodexConfigToml(previous);
2918
- writeTextFile(path, next);
2919
- return {
2920
- name,
2921
- changed: previous !== next,
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
- case "openclaw": {
2927
- const previous = readTextIfExists(path);
2928
- const next = removeOpenClawConfig(previous);
2929
- writeTextFile(path, next);
2930
- return {
2931
- name,
2932
- changed: previous !== next,
2933
- path,
2934
- message: "OpenClaw OrgX defaults were removed."
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
- case "vscode": {
2938
- const previous = readTextIfExists(path);
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
- case "windsurf": {
2949
- const previous = readTextIfExists(path);
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 results;
2987
- }
2988
- function removeSurface(selection) {
2989
- const targets = resolveSurfaceSelection(selection);
2990
- return targets.map((surface) => removeAutomatedSurface(surface));
2968
+ return targets;
2991
2969
  }
2992
- async function addMcpSurface(selection) {
2993
- const targets = resolveMcpSurfaceSelection(selection);
2994
- const results = [];
2995
- for (const surface of targets) {
2996
- results.push(await addAutomatedSurface(surface));
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
- return results;
2999
- }
3000
- function removeMcpSurface(selection) {
3001
- const targets = resolveMcpSurfaceSelection(selection);
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
- ok: false,
3020
- skipped: true,
3021
- method: "none",
3022
- error: "OpenClaw support files were not detected; skipping gateway health check.",
3023
- details: []
2985
+ installClaudeSkillBootstrap: !managesClaudeSkills,
2986
+ installClaudeSkillPacks: !managesClaudeSkills,
2987
+ notes,
2988
+ pluginManagedTargets
3024
2989
  };
3025
2990
  }
3026
- async function runDoctor() {
3027
- const surfaces = listSurfaceStatuses();
3028
- const openclawSurface = surfaces.find((surface) => surface.name === "openclaw");
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 assessDoctorReport(report) {
3038
- const issues = [];
3039
- if (!report.hostedMcp.ok) {
3040
- issues.push({
3041
- level: "error",
3042
- title: "Hosted OrgX MCP is unreachable.",
3043
- suggestion: "Retry later or verify https://mcp.useorgx.com/health. Hosted-only clients will stay broken until that endpoint recovers."
3044
- });
3045
- }
3046
- if (report.hostedMcp.ok && report.hostedMcpTool.configured && !report.hostedMcpTool.ok && !report.hostedMcpTool.skipped) {
3047
- issues.push({
3048
- level: "error",
3049
- title: `Hosted OrgX MCP tool verification failed (${report.hostedMcpTool.toolName}).`,
3050
- 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."
3051
- });
3052
- }
3053
- if (report.auth.configured && !report.auth.ok && !report.auth.skipped) {
3054
- issues.push({
3055
- level: "error",
3056
- title: "OrgX user auth could not be verified.",
3057
- suggestion: "Run `wizard auth set-key <oxk_...>` again or correct the resolved ORGX_API_KEY / base URL before retrying."
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
- ok: !issues.some((issue) => issue.level === "error"),
3089
- issues
3020
+ name: skillName,
3021
+ files: writes
3090
3022
  };
3091
3023
  }
3092
-
3093
- // src/lib/continuity.ts
3094
- var REVIEW_SURFACE_ORDER = [
3095
- "claude",
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 hostedSurface = statuses.find(
3127
- (status) => status.name !== "openclaw" && (status.configured || status.detected)
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
- return hostedSurface ? "cloud" : void 0;
3130
- }
3131
- function inferContinuityDefaults(seed = {}) {
3132
- const statuses = seed.statuses ?? listSurfaceStatuses();
3133
- const continuity = {};
3134
- const reviewSurface = selectReviewSurface(statuses);
3135
- if (reviewSurface) {
3136
- continuity.reviewSurface = reviewSurface;
3137
- }
3138
- const executionMode = inferExecutionMode(seed.executionMode, statuses);
3139
- if (executionMode) {
3140
- continuity.executionMode = executionMode;
3141
- }
3142
- if (seed.workspace?.id) {
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 workspaceName = seed.workspace?.name ?? seed.workspaceName ?? void 0;
3146
- if (workspaceName?.trim()) {
3147
- continuity.workspaceName = workspaceName.trim();
3148
- }
3149
- return continuity;
3150
- }
3151
- function mergeContinuityDefaults(current, next) {
3152
- return {
3153
- ...current ?? {},
3154
- ...next
3155
- };
3156
- }
3157
- function persistContinuityDefaults(seed = {}, statePath) {
3158
- const next = inferContinuityDefaults(seed);
3159
- return updateWizardState(
3160
- (current) => ({
3161
- ...current,
3162
- continuity: mergeContinuityDefaults(current.continuity, next)
3163
- }),
3164
- statePath
3165
- );
3166
- }
3167
-
3168
- // src/lib/initiatives.ts
3169
- var FOUNDER_DEMO_INITIATIVE_TITLE = "Founder Demo Initiative";
3170
- var FOUNDER_DEMO_INITIATIVE_SUMMARY = "Starter initiative created by @useorgx/wizard to validate workspace routing, live views, and the default OrgX skill pack.";
3171
- var FOUNDER_DEMO_DECISION_TITLE = "Approve founder demo workspace";
3172
- var FOUNDER_DEMO_DECISION_SUMMARY = "Initial decision created by @useorgx/wizard so the founder preset leaves the workspace with a resolved approval trail.";
3173
- var FOUNDER_DEMO_DECISION_RESOLUTION = "Approved automatically by @useorgx/wizard after the founder preset finished creating the live demo workspace.";
3174
- var FOUNDER_DEMO_ARTIFACT_NAME = "Founder Demo Live View";
3175
- var FOUNDER_DEMO_ARTIFACT_TYPE = "document";
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
- function formatHttpError2(status, body) {
3194
- if (typeof body === "string" && body.trim().length > 0) {
3195
- return `HTTP ${status}: ${body}`;
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 (isRecord(body) && typeof body.error === "string" && body.error.trim().length > 0) {
3198
- return `HTTP ${status}: ${body.error}`;
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
- return `HTTP ${status}`;
3201
- }
3202
- function extractEntity(payload) {
3203
- const entity = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
3204
- if (!isRecord(entity)) {
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
- return entity;
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
- id,
3218
- title,
3219
- ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
3124
+ extensions,
3125
+ notes,
3126
+ writes,
3127
+ packs
3220
3128
  };
3221
3129
  }
3222
- function parseDecision(payload) {
3223
- const entity = extractEntity(payload);
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
- id,
3231
- title,
3232
- ...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
3233
- ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
3133
+ extensions: listSkillExtensions(
3134
+ options.extensionsDir !== void 0 ? { extensionsDir: options.extensionsDir } : {}
3135
+ ),
3136
+ trackedFiles: state?.skillFiles?.files ?? []
3234
3137
  };
3235
3138
  }
3236
- function parseArtifact(payload) {
3237
- const entity = extractEntity(payload);
3238
- const id = typeof entity.id === "string" ? entity.id.trim() : "";
3239
- const name = typeof entity.name === "string" ? entity.name.trim() : typeof entity.title === "string" ? entity.title.trim() : "";
3240
- const type = typeof entity.artifact_type === "string" ? entity.artifact_type.trim() : typeof entity.type === "string" ? entity.type.trim() : "";
3241
- const url = typeof entity.external_url === "string" ? entity.external_url.trim() : typeof entity.url === "string" ? entity.url.trim() : "";
3242
- if (!id || !name) {
3243
- throw new Error("OrgX returned an incomplete artifact payload.");
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
- id,
3247
- name,
3248
- ...type ? { type } : {},
3249
- ...url ? { url } : {}
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 parseTask(payload) {
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
- id,
3261
- title,
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 parseWorkstream(payload) {
3267
- const entity = extractEntity(payload);
3268
- const id = typeof entity.id === "string" ? entity.id.trim() : "";
3269
- const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
3270
- if (!id || !title) {
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 toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace) {
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 toOnboardingTaskRecord(task, workspace, options = {}) {
3293
- return {
3294
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
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 toFirstValueInitiativeRecord(initiative, liveUrl, workspace) {
3305
- return {
3306
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
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
- async function requireOrgxAuth2(options = {}) {
3316
- const auth = await resolveOrgxAuth(options);
3317
- if (!auth) {
3318
- throw new Error(
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 auth;
3230
+ return `GitHub returned HTTP ${status} while loading '${path}' from ${repoName}.`;
3323
3231
  }
3324
- async function createEntity(type, body, parse2, options = {}) {
3325
- const auth = await requireOrgxAuth2(options);
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
- Authorization: `Bearer ${auth.apiKey}`,
3330
- "Content-Type": "application/json"
3235
+ Accept: "application/vnd.github+json"
3331
3236
  },
3332
- body: JSON.stringify({
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(`Failed to create ${type}. ${formatHttpError2(response.status, responseBody)}`);
3240
+ throw new Error(resolveGitHubError2(spec, response.status, path));
3341
3241
  }
3342
- return parse2(responseBody);
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 updateEntity(type, id, body, parse2, options = {}) {
3345
- const auth = await requireOrgxAuth2(options);
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
- Authorization: `Bearer ${auth.apiKey}`,
3350
- "Content-Type": "application/json"
3251
+ Accept: "application/vnd.github+json"
3351
3252
  },
3352
- body: JSON.stringify({
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(`Failed to update ${type}. ${formatHttpError2(response.status, responseBody)}`);
3256
+ throw new Error(resolveGitHubError2(spec, response.status, path));
3362
3257
  }
3363
- return parse2(responseBody);
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 buildLiveUrl(baseUrl, initiativeId) {
3366
- const parsed = new URL(normalizeOrgxBaseUrl(baseUrl));
3367
- parsed.pathname = `/live/${initiativeId}`;
3368
- parsed.search = "";
3369
- parsed.hash = "";
3370
- return parsed.toString();
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 createFounderDemoDecision(initiative, workspaceId, options = {}) {
3373
- return createEntity(
3374
- "decision",
3375
- {
3376
- initiative_id: initiative.id,
3377
- summary: FOUNDER_DEMO_DECISION_SUMMARY,
3378
- title: FOUNDER_DEMO_DECISION_TITLE,
3379
- workspace_id: workspaceId
3380
- },
3381
- parseDecision,
3382
- options
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 approveFounderDemoDecision(decisionId, options = {}) {
3386
- return updateEntity(
3387
- "decision",
3388
- decisionId,
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
- parseDecision,
3394
- options
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
- async function createFounderDemoArtifact(initiative, liveUrl, workspaceId, options = {}) {
3398
- return createEntity(
3399
- "artifact",
3400
- {
3401
- artifact_type: FOUNDER_DEMO_ARTIFACT_TYPE,
3402
- description: FOUNDER_DEMO_ARTIFACT_DESCRIPTION,
3403
- entity_id: initiative.id,
3404
- entity_type: "initiative",
3405
- external_url: liveUrl,
3406
- initiative_id: initiative.id,
3407
- name: FOUNDER_DEMO_ARTIFACT_NAME,
3408
- workspace_id: workspaceId
3409
- },
3410
- parseArtifact,
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
- async function createInitiative(input, options = {}) {
3415
- const title = input.title.trim();
3416
- if (!title) {
3417
- throw new Error("Initiative title is required.");
3343
+ function writeBytesIfChanged(path, bytes) {
3344
+ const existing = readBytesIfExists(path);
3345
+ if (existing && Buffer.compare(existing, bytes) === 0) {
3346
+ return false;
3418
3347
  }
3419
- return createEntity(
3420
- "initiative",
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
- async function ensureFounderDemoInitiative(workspace, options = {}) {
3432
- const existingRecord = readWizardState(options.statePath)?.demoInitiative;
3433
- const matchingRecord = existingRecord?.workspaceId === workspace.id ? existingRecord : void 0;
3434
- const auth = await requireOrgxAuth2(options);
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
- async function ensureOnboardingTask(workspace, options = {}) {
3515
- const existingRecord = readWizardState(options.statePath)?.onboardingTask;
3516
- if (existingRecord?.workspaceId === workspace.id) {
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 initiativeId = options.initiativeId?.trim();
3524
- if (!initiativeId) {
3525
- throw new Error(
3526
- "Starter onboarding task requires an initiative context. Re-run setup with the founder preset or create an initiative before requesting onboarding tasks."
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
- const workstream = await createEntity(
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 normalizeSkillId(value) {
3644
- const normalized = value.trim().toLowerCase();
3645
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
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
- return normalized;
3651
- }
3652
- function normalizeSkillExtensionScope(value) {
3653
- const normalized = (value ?? "user").trim().toLowerCase();
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
- return normalized;
3658
- }
3659
- function defaultExtensionTitle(skillId, scope) {
3660
- const prefix = scope === "user" ? "Personal" : scope === "workspace" ? "Workspace" : "Project";
3661
- return `${prefix} ${skillId} behavior`;
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 parseFrontmatter(content) {
3686
- if (!content.startsWith(`${EXTENSION_FRONTMATTER_DELIMITER}
3687
- `)) {
3688
- return { body: content, data: {} };
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 closeIndex = content.indexOf(`
3691
- ${EXTENSION_FRONTMATTER_DELIMITER}
3692
- `, 4);
3693
- if (closeIndex === -1) {
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
- const raw = content.slice(4, closeIndex);
3697
- const body = content.slice(closeIndex + 5);
3698
- const data = {};
3699
- for (const line of raw.split("\n")) {
3700
- const index = line.indexOf(":");
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 { body, data };
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 listSkillExtensions(options = {}) {
3744
- const extensionsDir = options.extensionsDir ?? ORGX_SKILL_EXTENSIONS_DIR;
3745
- if (!existsSync3(extensionsDir)) {
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 addSkillExtension(options) {
3755
- const skillId = normalizeSkillId(options.skillId);
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 !== null && options.overwrite !== true) {
3761
- const extension2 = parseSkillExtension(path, existing);
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
- changed: existing !== nextContent,
3785
- created: existing === null,
3786
- extension,
3787
- path
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 setSkillExtensionEnabled(input) {
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
- changed: nextContent !== existing,
3822
- created: false,
3823
- extension,
3824
- path
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 composeSkillContent(skillId, coreContent, extensions) {
3828
- const enabled = extensions.filter((extension) => extension.enabled && extension.skillId === skillId);
3829
- if (enabled.length === 0) {
3830
- return coreContent;
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 buildContentsUrl(path, ref) {
3857
- const encodedPath = encodeRepoPath(path);
3858
- return `https://api.github.com/repos/${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`;
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 resolveGitHubError(status, path) {
3861
- if (status === 404) {
3862
- return `OrgX skill pack '${path}' was not found in ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`;
3484
+ function removeCodexMarketplaceEntry(path) {
3485
+ if (!existsSync4(path)) {
3486
+ return false;
3863
3487
  }
3864
- return `GitHub returned HTTP ${status} while loading '${path}' from ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`;
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
- async function readResponseText(response) {
3867
- return response.text();
3502
+ function codexMarketplaceHasOrgxEntry(path) {
3503
+ const { plugins } = readMarketplacePlugins(path);
3504
+ return plugins.some((plugin) => plugin.name === ORGX_CODEX_PLUGIN_NAME);
3868
3505
  }
3869
- async function fetchDirectoryEntries(path, fetchImpl, ref) {
3870
- const response = await fetchImpl(buildContentsUrl(path, ref), {
3871
- headers: {
3872
- Accept: "application/vnd.github+json"
3873
- },
3874
- signal: AbortSignal.timeout(1e4)
3875
- });
3876
- if (!response.ok) {
3877
- throw new Error(resolveGitHubError(response.status, path));
3878
- }
3879
- const data = await response.json();
3880
- if (!Array.isArray(data)) {
3881
- throw new Error(`Expected '${path}' to be a directory in ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`);
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
- async function listRemoteSkillFiles(path, fetchImpl, ref) {
3889
- const entries = await fetchDirectoryEntries(path, fetchImpl, ref);
3890
- const files = [];
3891
- for (const entry of entries) {
3892
- if (entry.type === "dir") {
3893
- files.push(...await listRemoteSkillFiles(entry.path, fetchImpl, ref));
3894
- continue;
3895
- }
3896
- if (!entry.download_url) {
3897
- throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
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
- files.push({
3900
- path: entry.path,
3901
- sourceUrl: entry.download_url
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
- async function fetchRemoteText(sourceUrl, fetchImpl) {
3907
- const response = await fetchImpl(sourceUrl, {
3908
- headers: {
3909
- Accept: "text/plain"
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 readResponseText(response);
3550
+ return `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
3917
3551
  }
3918
- function getTrackedSkillFile(records, path) {
3919
- return records.find((record) => record.path === path);
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 writeManagedFile(path, content, label, sourceUrl, tracking) {
3922
- const existing = readTextIfExists(path);
3923
- const tracked = tracking ? getTrackedSkillFile(tracking.records, path) : void 0;
3924
- if (tracking && existing !== null && tracked && sha256(existing) !== tracked.contentSha256 && tracking.force !== true) {
3925
- return {
3926
- label,
3927
- path,
3928
- changed: false,
3929
- skipped: true,
3930
- reason: "manual edits detected; move them into a skill extension or rerun with --force",
3931
- ...sourceUrl ? { sourceUrl } : {}
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
- if (tracking) {
3939
- tracking.stagedRecords.set(path, {
3940
- contentSha256: sha256(content),
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
- label,
3949
- path,
3950
- changed,
3951
- ...sourceUrl ? { sourceUrl } : {}
3601
+ available: true,
3602
+ installed: extractClaudePluginNames(result.stdout).includes(ORGX_CLAUDE_PLUGIN_NAME)
3952
3603
  };
3953
3604
  }
3954
- function resolveSkillPackNames(requested = []) {
3955
- if (requested.length === 0 || requested.includes("all")) {
3956
- return [...DEFAULT_ORGX_SKILL_PACKS];
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 seen = /* @__PURE__ */ new Set();
3959
- const normalized = [];
3960
- for (const name of requested) {
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 normalized;
3614
+ return {
3615
+ available: true,
3616
+ installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
3617
+ };
3969
3618
  }
3970
- function resolvePluginManagedSkillTargets(requested = []) {
3971
- const seen = /* @__PURE__ */ new Set();
3972
- const targets = [];
3973
- for (const rawTarget of requested) {
3974
- const target = rawTarget.trim().toLowerCase();
3975
- if (!PLUGIN_MANAGED_SKILL_TARGETS.includes(target)) {
3976
- continue;
3977
- }
3978
- if (seen.has(target)) {
3979
- continue;
3980
- }
3981
- seen.add(target);
3982
- targets.push(target);
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
- return targets;
3985
- }
3986
- function planOrgxSkillsInstall(pluginTargets = []) {
3987
- const pluginManagedTargets = resolvePluginManagedSkillTargets(pluginTargets);
3988
- const managesClaudeSkills = pluginManagedTargets.includes("claude");
3989
- const notes = [];
3990
- if (managesClaudeSkills) {
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 (pluginManagedTargets.includes("codex")) {
3996
- notes.push(
3997
- "Codex companion plugin bundles its own OrgX skills, so `wizard skills add` only manages standalone editor assets outside Codex."
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
- installClaudeSkillBootstrap: !managesClaudeSkills,
4002
- installClaudeSkillPacks: !managesClaudeSkills,
4003
- notes,
4004
- pluginManagedTargets
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 fetchAvailablePackNames(fetchImpl, ref) {
4008
- const entries = await fetchDirectoryEntries("", fetchImpl, ref);
4009
- return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
4010
- }
4011
- async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref, tracking) {
4012
- const rootPath = skillName;
4013
- const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
4014
- const writes = [];
4015
- for (const file of files) {
4016
- const coreContent = await fetchRemoteText(file.sourceUrl, fetchImpl);
4017
- const relativePath = file.path.slice(`${rootPath}/`.length);
4018
- const content = relativePath === "SKILL.md" ? composeSkillContent(skillName, coreContent, tracking.extensions) : coreContent;
4019
- writes.push(
4020
- writeManagedFile(
4021
- join2(claudeSkillsDir, skillName, relativePath),
4022
- content,
4023
- `${skillName}/${relativePath}`,
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
- name: skillName,
4037
- files: writes
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
- async function installOrgxSkills(options = {}) {
4041
- const fetchImpl = options.fetchImpl ?? globalThis.fetch;
4042
- if (!fetchImpl) {
4043
- throw new Error("Global fetch is unavailable in this runtime.");
4044
- }
4045
- const ref = options.ref ?? ORGX_SKILLS_REF;
4046
- const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
4047
- const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
4048
- const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
4049
- const statePath = options.statePath ?? ORGX_WIZARD_STATE_PATH;
4050
- const extensions = listSkillExtensions(
4051
- options.skillExtensionsDir !== void 0 ? { extensionsDir: options.skillExtensionsDir } : {}
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
- } else {
4066
- skillNames = resolveSkillPackNames(requestedNames);
4067
- }
4068
- const writes = [
4069
- writeManagedFile(
4070
- cursorRulePath,
4071
- composeSkillContent("cursor-rules", CURSOR_RULES_CONTENT, extensions),
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
- ...current,
4123
- skillFiles: {
4124
- files: [...nextFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
4125
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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
- }, statePath);
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
- const notes = [...plan.notes];
4131
- if (enabledExtensions.length > 0) {
4132
- notes.push(
4133
- `Applied ${enabledExtensions.length} enabled OrgX skill extension${enabledExtensions.length === 1 ? "" : "s"} while syncing skills/rules.`
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
- if (writes.some((write) => write.skipped) || packs.some((pack) => pack.files.some((file) => file.skipped))) {
4137
- notes.push("Some generated skill files were skipped because local manual edits were detected.");
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
- extensions,
4141
- notes,
4142
- writes,
4143
- packs
3788
+ tarballUrl,
3789
+ version: latestVersion
4144
3790
  };
4145
3791
  }
4146
- function getSkillStatus(options = {}) {
4147
- const state = readWizardState(options.statePath ?? ORGX_WIZARD_STATE_PATH);
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
- extensions: listSkillExtensions(
4150
- options.extensionsDir !== void 0 ? { extensionsDir: options.extensionsDir } : {}
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
- // src/lib/plugins.ts
4157
- import { spawn } from "child_process";
4158
- import {
4159
- existsSync as existsSync4,
4160
- mkdirSync as mkdirSync2,
4161
- mkdtempSync,
4162
- readFileSync as readFileSync2,
4163
- readdirSync as readdirSync2,
4164
- rmSync,
4165
- statSync as statSync2,
4166
- writeFileSync as writeFileSync2
4167
- } from "fs";
4168
- import { tmpdir } from "os";
4169
- import { dirname as dirname3, join as join3, relative } from "path";
4170
- var DEFAULT_ORGX_PLUGIN_TARGETS = ["cursor", "claude", "codex", "openclaw"];
4171
- var ORGX_PLUGIN_GITHUB_OWNER = "useorgx";
4172
- var ORGX_PLUGIN_GITHUB_REF = "main";
4173
- var ORGX_CLAUDE_PLUGIN_NAME = "orgx-claude-code-plugin";
4174
- var ORGX_CLAUDE_MARKETPLACE_NAME = "orgx-local";
4175
- var ORGX_CODEX_PLUGIN_NAME = "orgx-codex-plugin";
4176
- var ORGX_CURSOR_PLUGIN_NAME = "cursor-plugin";
4177
- var ORGX_OPENCLAW_PLUGIN_ID = "orgx";
4178
- var ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME = "@useorgx/openclaw-plugin";
4179
- var CLAUDE_PLUGIN_SYNC_SPEC = {
4180
- owner: ORGX_PLUGIN_GITHUB_OWNER,
4181
- repo: ORGX_CLAUDE_PLUGIN_NAME,
4182
- ref: ORGX_PLUGIN_GITHUB_REF,
4183
- include: [
4184
- { localPath: ".claude-plugin", remotePath: ".claude-plugin" },
4185
- { localPath: "agents", remotePath: "agents" },
4186
- { localPath: "commands", remotePath: "commands" },
4187
- { localPath: "hooks", remotePath: "hooks" },
4188
- { localPath: "lib", remotePath: "lib" },
4189
- { localPath: "scripts", remotePath: "scripts" },
4190
- { localPath: "skills", remotePath: "skills" }
4191
- ]
4192
- };
4193
- var CODEX_PLUGIN_SYNC_SPEC = {
4194
- owner: ORGX_PLUGIN_GITHUB_OWNER,
4195
- repo: ORGX_CODEX_PLUGIN_NAME,
4196
- ref: ORGX_PLUGIN_GITHUB_REF,
4197
- include: [
4198
- { localPath: ".codex-plugin", remotePath: ".codex-plugin" },
4199
- { localPath: ".mcp.json", remotePath: ".mcp.json" },
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
- claudeMarketplaceDir: CLAUDE_MANAGED_MARKETPLACE_DIR,
4223
- claudeMarketplaceManifestPath: CLAUDE_MANAGED_MARKETPLACE_MANIFEST_PATH,
4224
- claudePluginDir: CLAUDE_MANAGED_PLUGIN_DIR,
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 resolvePluginPaths(paths = {}) {
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
- ...defaultPluginPaths(),
4234
- ...paths
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 isRepoDirectoryEntry(value) {
4238
- if (!value || typeof value !== "object") return false;
4239
- const entry = value;
4240
- const hasType = entry.type === "file" || entry.type === "dir";
4241
- return typeof entry.name === "string" && typeof entry.path === "string" && hasType;
4242
- }
4243
- function encodeRepoPath2(value) {
4244
- return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
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
- return `GitHub returned HTTP ${status} while loading '${path}' from ${repoName}.`;
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
- async function fetchDirectoryEntries2(spec, path, fetchImpl) {
4262
- const response = await fetchImpl(buildContentsUrl2(spec, path), {
4263
- headers: {
4264
- Accept: "application/vnd.github+json"
4265
- },
4266
- signal: AbortSignal.timeout(1e4)
4267
- });
4268
- if (!response.ok) {
4269
- throw new Error(resolveGitHubError2(spec, response.status, path));
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
- const data = await response.json();
4272
- if (!Array.isArray(data)) {
4273
- throw new Error(`Expected '${path}' to be a directory in ${spec.owner}/${spec.repo}.`);
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
- return data.filter(isRepoDirectoryEntry).sort((left, right) => left.path.localeCompare(right.path));
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 fetchRepoFile(spec, path, fetchImpl) {
4278
- const response = await fetchImpl(buildContentsUrl2(spec, path), {
4279
- headers: {
4280
- Accept: "application/vnd.github+json"
4281
- },
4282
- signal: AbortSignal.timeout(1e4)
4283
- });
4284
- if (!response.ok) {
4285
- throw new Error(resolveGitHubError2(spec, response.status, path));
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 data = await response.json();
4288
- if (!isRepoDirectoryEntry(data) || data.type !== "file" || typeof data.download_url !== "string") {
4289
- throw new Error(`Expected '${path}' to be a file in ${spec.owner}/${spec.repo}.`);
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 data;
3988
+ return {
3989
+ target: "openclaw",
3990
+ changed: true,
3991
+ message: "Removed the managed OpenClaw plugin."
3992
+ };
4292
3993
  }
4293
- async function listRemoteRepoFiles(spec, path, localPath, fetchImpl) {
4294
- const files = [];
4295
- if (isLikelyRepoFilePath(path) && !path.endsWith("/")) {
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 entries = await fetchDirectoryEntries2(spec, path, fetchImpl);
4305
- for (const entry of entries) {
4306
- if (entry.type === "dir") {
4307
- files.push(
4308
- ...await listRemoteRepoFiles(
4309
- spec,
4310
- entry.path,
4311
- join3(localPath, entry.name),
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
- if (!entry.download_url) {
4318
- throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
4319
- }
4320
- files.push({
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 files;
4013
+ return normalized;
4327
4014
  }
4328
- async function collectRemoteRepoFiles(spec, fetchImpl) {
4329
- const files = [];
4330
- for (const entry of spec.include) {
4331
- const isFile = isLikelyRepoFilePath(entry.remotePath) && !entry.remotePath.endsWith("/");
4332
- if (isFile) {
4333
- const file = await fetchRepoFile(spec, entry.remotePath, fetchImpl);
4334
- files.push({
4335
- localPath: entry.localPath,
4336
- path: file.path,
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 fetchRemoteBytes(sourceUrl, fetchImpl) {
4346
- const response = await fetchImpl(sourceUrl, {
4347
- headers: {
4348
- Accept: "application/octet-stream"
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
- return Buffer.from(await response.arrayBuffer());
4356
- }
4357
- function readBytesIfExists(path) {
4358
- if (!existsSync4(path)) return null;
4359
- try {
4360
- if (!statSync2(path).isFile()) {
4361
- return null;
4362
- }
4363
- return readFileSync2(path);
4364
- } catch (error) {
4365
- const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
4366
- if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
4367
- return null;
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 writeBytesIfChanged(path, bytes) {
4373
- const existing = readBytesIfExists(path);
4374
- if (existing && Buffer.compare(existing, bytes) === 0) {
4375
- return false;
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
- mkdirSync2(dirname3(path), { recursive: true });
4378
- writeFileSync2(path, bytes);
4379
- return true;
4073
+ return { results };
4380
4074
  }
4381
- function removePathIfExists(path) {
4382
- if (!existsSync4(path)) return false;
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
- function listRelativeFiles(root, base = root) {
4387
- if (!existsSync4(root)) return [];
4388
- if (!statSync2(root).isDirectory()) {
4389
- return [];
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
- const files = [];
4392
- for (const entry of readdirSync2(root, { withFileTypes: true })) {
4393
- const nextPath = join3(root, entry.name);
4394
- if (entry.isDirectory()) {
4395
- files.push(...listRelativeFiles(nextPath, base));
4396
- continue;
4397
- }
4398
- if (entry.isFile()) {
4399
- files.push(relative(base, nextPath));
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
- return files.sort();
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
- let changed = false;
4409
- for (const entry of readdirSync2(current, { withFileTypes: true })) {
4410
- if (!entry.isDirectory()) continue;
4411
- changed = pruneEmptyDirectories(root, join3(current, entry.name)) || changed;
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 (current !== root && readdirSync2(current).length === 0) {
4414
- rmSync(current, { force: true, recursive: true });
4415
- return true;
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
- return changed;
4418
- }
4419
- async function syncManagedRepoTree(spec, destinationRoot, fetchImpl) {
4420
- const remoteFiles = await collectRemoteRepoFiles(spec, fetchImpl);
4421
- let changed = false;
4422
- const expected = new Set(remoteFiles.map((file) => file.localPath));
4423
- if (existsSync4(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
4424
- rmSync(destinationRoot, { force: true, recursive: true });
4425
- changed = true;
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
- for (const file of listRelativeFiles(destinationRoot)) {
4428
- if (expected.has(file)) continue;
4429
- rmSync(join3(destinationRoot, file), { force: true });
4430
- changed = true;
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
- changed = pruneEmptyDirectories(destinationRoot) || changed;
4433
- for (const file of remoteFiles) {
4434
- const bytes = await fetchRemoteBytes(file.sourceUrl, fetchImpl);
4435
- if (writeBytesIfChanged(join3(destinationRoot, file.localPath), bytes)) {
4436
- changed = true;
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
- return { changed, fileCount: remoteFiles.length };
4440
- }
4441
- function serializeJson(value) {
4442
- return `${JSON.stringify(value, null, 2)}
4443
- `;
4444
- }
4445
- function writeJsonIfChanged(path, value) {
4446
- const next = serializeJson(value);
4447
- const existing = readTextIfExists(path);
4448
- if (existing === next) {
4449
- return false;
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
- mkdirSync2(dirname3(path), { recursive: true });
4452
- writeFileSync2(path, next, "utf8");
4453
- return true;
4454
- }
4455
- function buildClaudeMarketplaceManifest() {
4222
+ const inspection = inspectOpenClawConfig(path ? readTextIfExists(path) : null);
4456
4223
  return {
4457
- $schema: "https://anthropic.com/claude-code/marketplace.schema.json",
4458
- name: ORGX_CLAUDE_MARKETPLACE_NAME,
4459
- description: "Local OrgX plugins",
4460
- owner: {
4461
- name: "OrgX Team"
4462
- },
4463
- plugins: [
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 buildCodexMarketplaceEntry() {
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: ORGX_CODEX_PLUGIN_NAME,
4475
- source: {
4476
- source: "local",
4477
- path: `./.codex/plugins/${ORGX_CODEX_PLUGIN_NAME}`
4478
- },
4479
- policy: {
4480
- installation: "AVAILABLE",
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 readMarketplacePlugins(path) {
4487
- const document = parseJsonObject(readTextIfExists(path));
4488
- const plugins = Array.isArray(document.plugins) ? document.plugins.filter((value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)) : [];
4489
- return { document, plugins };
4490
- }
4491
- function upsertCodexMarketplaceEntry(path) {
4492
- const { document, plugins } = readMarketplacePlugins(path);
4493
- const nextEntry = buildCodexMarketplaceEntry();
4494
- let replaced = false;
4495
- const nextPlugins = plugins.map((plugin) => {
4496
- if (plugin.name !== ORGX_CODEX_PLUGIN_NAME) {
4497
- return plugin;
4498
- }
4499
- replaced = true;
4500
- return nextEntry;
4501
- });
4502
- if (!replaced) {
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
- const nextDocument = {
4506
- ...document,
4507
- ...document.name ? {} : { name: ORGX_CLAUDE_MARKETPLACE_NAME },
4508
- ...readObject(document.interface).displayName ? {} : { interface: { displayName: "OrgX Local" } },
4509
- plugins: nextPlugins
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 removeCodexMarketplaceEntry(path) {
4514
- if (!existsSync4(path)) {
4515
- return false;
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
- const { document, plugins } = readMarketplacePlugins(path);
4518
- const nextPlugins = plugins.filter((plugin) => plugin.name !== ORGX_CODEX_PLUGIN_NAME);
4519
- if (nextPlugins.length === plugins.length) {
4520
- return false;
4323
+ }
4324
+ function getSurfaceStatus(name) {
4325
+ if (AUTOMATED_SURFACE_NAMES.includes(name)) {
4326
+ return automatedSurfaceStatus(
4327
+ name
4328
+ );
4521
4329
  }
4522
- if (nextPlugins.length === 0) {
4523
- rmSync(path, { force: true });
4524
- return true;
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 writeJsonIfChanged(path, {
4527
- ...document,
4528
- plugins: nextPlugins
4529
- });
4340
+ return [...new Set(values)];
4530
4341
  }
4531
- function codexMarketplaceHasOrgxEntry(path) {
4532
- const { plugins } = readMarketplacePlugins(path);
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 extractClaudePluginNames(payload) {
4536
- try {
4537
- const parsed = JSON.parse(payload);
4538
- if (!Array.isArray(parsed)) return [];
4539
- return parsed.flatMap((entry) => {
4540
- if (typeof entry === "string") return [entry];
4541
- if (!entry || typeof entry !== "object") return [];
4542
- if (typeof entry.name === "string") return [entry.name];
4543
- if (typeof entry.id === "string") return [entry.id];
4544
- return [];
4545
- });
4546
- } catch {
4547
- return [];
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 extractOpenclawPluginIds(payload) {
4551
- try {
4552
- const parsed = JSON.parse(payload);
4553
- if (Array.isArray(parsed)) {
4554
- return parsed.flatMap((entry) => {
4555
- if (!entry || typeof entry !== "object") return [];
4556
- if (typeof entry.id === "string") return [entry.id];
4557
- if (typeof entry.name === "string") return [entry.name];
4558
- return [];
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
- const root = readObject(parsed);
4562
- const plugins = Array.isArray(root.plugins) ? root.plugins : [];
4563
- return plugins.flatMap((entry) => {
4564
- if (!entry || typeof entry !== "object") return [];
4565
- const item = entry;
4566
- if (typeof item.id === "string") return [item.id];
4567
- if (typeof item.name === "string") return [item.name];
4568
- return [];
4569
- });
4570
- } catch {
4571
- return [];
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 formatCommandFailure(command, args, result) {
4575
- const detail = [result.stderr.trim(), result.stdout.trim()].find((value) => value.length > 0);
4576
- if (result.errorCode === "ENOENT") {
4577
- return `Command '${command}' is not available on PATH.`;
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 `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
4557
+ return results;
4580
4558
  }
4581
- async function defaultCommandRunner(command, args) {
4582
- return await new Promise((resolve2) => {
4583
- const child = spawn(command, [...args], {
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 resolveCommandRunner(runner) {
4614
- return runner ?? defaultCommandRunner;
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
- async function commandExists(command, runner) {
4617
- const result = await runner(command, ["--help"]);
4618
- return result.errorCode !== "ENOENT" && result.exitCode !== 127;
4571
+ function removeMcpSurface(selection) {
4572
+ const targets = resolveMcpSurfaceSelection(selection);
4573
+ return targets.map((surface) => removeAutomatedSurface(surface));
4619
4574
  }
4620
- async function getClaudeInstallState(runner) {
4621
- const available = detectSurface("claude").detected || await commandExists("claude", runner);
4622
- if (!available) {
4623
- return { available: false, installed: false };
4624
- }
4625
- const result = await runner("claude", ["plugin", "list", "--json"]);
4626
- if (result.exitCode !== 0) {
4627
- return { available: true, installed: false };
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
- async function getOpenclawInstallState(runner) {
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
- available: true,
4645
- installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
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 cursorPluginManifestPath(paths) {
4649
- return join3(paths.cursorPluginDir, ".cursor-plugin", "plugin.json");
4650
- }
4651
- function cursorPluginMcpPath(paths) {
4652
- return join3(paths.cursorPluginDir, ".mcp.json");
4653
- }
4654
- function isCursorPluginInstalled(paths) {
4655
- return existsSync4(cursorPluginManifestPath(paths)) && existsSync4(cursorPluginMcpPath(paths));
4656
- }
4657
- function isCursorPluginAvailable(paths) {
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 buildCursorStatus(paths) {
4661
- const existingRules = readTextIfExists(paths.cursorRulePath);
4662
- const available = isCursorPluginAvailable(paths);
4663
- const installed = isCursorPluginInstalled(paths);
4664
- if (installed) {
4665
- return {
4666
- target: "cursor",
4667
- available: true,
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 (existingRules === CURSOR_RULES_CONTENT) {
4673
- return {
4674
- target: "cursor",
4675
- available: true,
4676
- installed: false,
4677
- message: "Legacy Cursor OrgX rules are installed; run plugins add cursor to install the full plugin bundle."
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 (existingRules !== null) {
4681
- return {
4682
- target: "cursor",
4683
- available: true,
4684
- installed: false,
4685
- message: "Cursor rules file exists, but the OrgX plugin bundle is missing."
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
- return {
4689
- target: "cursor",
4690
- available,
4691
- installed: false,
4692
- message: available ? "Cursor is available and ready for OrgX plugin install." : "Cursor was not detected."
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 (!state.available) {
4706
- return {
4707
- target: "claude",
4708
- available: false,
4709
- installed: false,
4710
- message: "Claude Code was not detected."
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
- target: "claude",
4716
- available: true,
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
- function buildCodexStatus(paths, runner) {
4722
- return (async () => {
4723
- const available = detectSurface("codex").detected || await commandExists("codex", runner);
4724
- const pluginExists = existsSync4(paths.codexPluginDir);
4725
- const marketplaceExists = codexMarketplaceHasOrgxEntry(paths.codexMarketplacePath);
4726
- const installed = pluginExists && marketplaceExists;
4727
- if (installed) {
4728
- return {
4729
- target: "codex",
4730
- available: true,
4731
- installed: true,
4732
- message: "Codex plugin files and marketplace entry are present."
4733
- };
4734
- }
4735
- if (!available) {
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
- if (!pluginExists && marketplaceExists) {
4752
- return {
4753
- target: "codex",
4754
- available: true,
4755
- installed: false,
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
- return {
4760
- target: "codex",
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
- async function buildOpenclawStatus(runner) {
4768
- const state = await getOpenclawInstallState(runner);
4769
- if (state.installed) {
4770
- return {
4771
- target: "openclaw",
4772
- available: true,
4773
- installed: true,
4774
- message: "OpenClaw plugin is installed."
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
- target: "openclaw",
4779
- available: state.available,
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
- async function resolveOpenclawTarball(fetchImpl) {
4785
- const response = await fetchImpl(
4786
- `${NPM_REGISTRY_BASE_URL}/${encodeURIComponent(ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME)}`,
4787
- {
4788
- headers: {
4789
- Accept: "application/json"
4790
- },
4791
- signal: AbortSignal.timeout(1e4)
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
- if (!response.ok) {
4795
- throw new Error(
4796
- `npm registry returned HTTP ${response.status} while resolving ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`
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
- const data = await response.json();
4800
- const latestVersion = data["dist-tags"]?.latest;
4801
- const tarballUrl = latestVersion ? data.versions?.[latestVersion]?.dist?.tarball : void 0;
4802
- if (!latestVersion || !tarballUrl) {
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
- async function installCursorPlugin(paths, fetchImpl) {
4811
- if (!isCursorPluginAvailable(paths)) {
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
- const syncResult = await syncManagedRepoTree(CURSOR_PLUGIN_SYNC_SPEC, paths.cursorPluginDir, fetchImpl);
4819
- return {
4820
- target: "cursor",
4821
- changed: syncResult.changed,
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
- async function installClaudePlugin(paths, fetchImpl, runner) {
4826
- const state = await getClaudeInstallState(runner);
4827
- if (!state.available) {
4828
- return {
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
- const syncResult = await syncManagedRepoTree(CLAUDE_PLUGIN_SYNC_SPEC, paths.claudePluginDir, fetchImpl);
4835
- const manifestChanged = writeJsonIfChanged(
4836
- paths.claudeMarketplaceManifestPath,
4837
- buildClaudeMarketplaceManifest()
4838
- );
4839
- const marketplaceAdd = await runner("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir]);
4840
- if (marketplaceAdd.exitCode !== 0) {
4841
- throw new Error(formatCommandFailure("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir], marketplaceAdd));
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
- let installedChanged = false;
4844
- if (!state.installed) {
4845
- const install = await runner("claude", [
4846
- "plugin",
4847
- "install",
4848
- `${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
4849
- "--scope",
4850
- "user"
4851
- ]);
4852
- if (install.exitCode !== 0) {
4853
- throw new Error(
4854
- formatCommandFailure(
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
- target: "claude",
4872
- changed,
4873
- message: changed ? `Synced ${syncResult.fileCount} Claude plugin files and ensured the plugin is installed.` : "Claude Code plugin is already installed and up to date."
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
- async function installCodexPlugin(paths, fetchImpl, runner) {
4877
- const available = detectSurface("codex").detected || await commandExists("codex", runner);
4878
- if (!available) {
4879
- return {
4880
- target: "codex",
4881
- changed: false,
4882
- message: "Codex is not available on this machine."
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
- target: "codex",
4890
- changed,
4891
- 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."
4826
+ id,
4827
+ name,
4828
+ ...type ? { type } : {},
4829
+ ...url ? { url } : {}
4892
4830
  };
4893
4831
  }
4894
- async function installOpenclawPlugin(fetchImpl, runner) {
4895
- const state = await getOpenclawInstallState(runner);
4896
- if (!state.available) {
4897
- return {
4898
- target: "openclaw",
4899
- changed: false,
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
- const { tarballUrl, version } = await resolveOpenclawTarball(fetchImpl);
4911
- const tarballBytes = await fetchRemoteBytes(tarballUrl, fetchImpl);
4912
- const tempRoot = mkdtempSync(join3(tmpdir(), "orgx-wizard-openclaw-"));
4913
- const archivePath = join3(tempRoot, `orgx-openclaw-plugin-${version}.tgz`);
4914
- try {
4915
- writeFileSync2(archivePath, tarballBytes);
4916
- const install = await runner("openclaw", ["plugins", "install", archivePath]);
4917
- if (install.exitCode !== 0) {
4918
- throw new Error(
4919
- formatCommandFailure("openclaw", ["plugins", "install", archivePath], install)
4920
- );
4921
- }
4922
- } finally {
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
- target: "openclaw",
4927
- changed: true,
4928
- message: `Installed OpenClaw plugin ${ORGX_OPENCLAW_PLUGIN_ID} from ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}@${version}.`
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 uninstallCursorPlugin(paths) {
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
- target: "cursor",
4938
- changed,
4939
- 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."
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
- async function uninstallClaudePlugin(paths, runner) {
4943
- const state = await getClaudeInstallState(runner);
4944
- let changed = false;
4945
- if (state.available && state.installed) {
4946
- const uninstall = await runner("claude", [
4947
- "plugin",
4948
- "uninstall",
4949
- ORGX_CLAUDE_PLUGIN_NAME,
4950
- "--scope",
4951
- "user"
4952
- ]);
4953
- if (uninstall.exitCode !== 0) {
4954
- throw new Error(
4955
- formatCommandFailure(
4956
- "claude",
4957
- ["plugin", "uninstall", ORGX_CLAUDE_PLUGIN_NAME, "--scope", "user"],
4958
- uninstall
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
- if (state.available) {
4965
- const removeMarketplace = await runner("claude", ["plugin", "marketplace", "remove", ORGX_CLAUDE_MARKETPLACE_NAME]);
4966
- if (removeMarketplace.exitCode === 0) {
4967
- changed = true;
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
- changed = removePathIfExists(paths.claudeMarketplaceDir) || changed;
4971
- return {
4972
- target: "claude",
4973
- changed,
4974
- message: changed ? "Removed the managed Claude Code plugin and marketplace wrapper." : "Claude Code plugin was not installed."
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 uninstallCodexPlugin(paths) {
4978
- const removedPluginDir = removePathIfExists(paths.codexPluginDir);
4979
- const removedMarketplaceEntry = removeCodexMarketplaceEntry(paths.codexMarketplacePath);
4980
- const changed = removedPluginDir || removedMarketplaceEntry;
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
- target: "codex",
4983
- changed,
4984
- message: changed ? "Removed the managed Codex plugin files and marketplace entry." : "Codex plugin was not installed."
5050
+ artifact,
5051
+ created: !matchingRecord,
5052
+ decision,
5053
+ initiative,
5054
+ liveUrl
4985
5055
  };
4986
5056
  }
4987
- async function uninstallOpenclawPlugin(runner) {
4988
- const state = await getOpenclawInstallState(runner);
4989
- if (!state.available || !state.installed) {
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
- target: "openclaw",
4992
- changed: false,
4993
- message: "OpenClaw plugin was not installed."
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 uninstall = await runner("openclaw", ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"]);
4997
- if (uninstall.exitCode !== 0) {
4998
- throw new Error(
4999
- formatCommandFailure(
5000
- "openclaw",
5001
- ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"],
5002
- uninstall
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
- target: "openclaw",
5008
- changed: true,
5009
- message: "Removed the managed OpenClaw plugin."
5089
+ created: true,
5090
+ initiative,
5091
+ liveUrl
5010
5092
  };
5011
5093
  }
5012
- function resolvePluginTargets(requested = []) {
5013
- if (requested.length === 0 || requested.includes("all")) {
5014
- return [...DEFAULT_ORGX_PLUGIN_TARGETS];
5015
- }
5016
- const seen = /* @__PURE__ */ new Set();
5017
- const normalized = [];
5018
- for (const rawTarget of requested) {
5019
- const target = rawTarget.trim().toLowerCase();
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
- return { results };
5069
- }
5070
- async function uninstallOrgxPlugins(options = {}) {
5071
- const targets = resolvePluginTargets(options.targets ?? []);
5072
- const paths = resolvePluginPaths(options.paths);
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
- return { results };
5092
- }
5093
- function countPluginReportChanges(report) {
5094
- return report.results.filter((result) => result.changed).length;
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.25" : void 0;
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));