@useorgx/wizard 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -28,10 +28,25 @@ var ORGX_HOSTED_MCP_KEY = "orgx";
28
28
  var ORGX_LOCAL_MCP_KEY = "orgx-openclaw";
29
29
  var ORGX_HOSTED_MCP_URL = "https://mcp.useorgx.com/mcp";
30
30
  var ORGX_HOSTED_MCP_HEALTH_URL = "https://mcp.useorgx.com/health";
31
+ var ORGX_HOSTED_OAUTH_BASE_URL = "https://mcp.useorgx.com";
32
+ var ORGX_HOSTED_OAUTH_AUTHORIZE_URL = `${ORGX_HOSTED_OAUTH_BASE_URL}/authorize`;
33
+ var ORGX_HOSTED_OAUTH_TOKEN_URL = `${ORGX_HOSTED_OAUTH_BASE_URL}/token`;
34
+ var ORGX_HOSTED_OAUTH_REGISTER_URL = `${ORGX_HOSTED_OAUTH_BASE_URL}/register`;
31
35
  var ORGX_WIZARD_NPM_PACKAGE_NAME = "@useorgx/wizard";
32
36
  var NPM_REGISTRY_BASE_URL = "https://registry.npmjs.org";
33
37
  var DEFAULT_OPENCLAW_GATEWAY_PORT = 18789;
34
38
  var DEFAULT_ORGX_BASE_URL = process.env.ORGX_BASE_URL?.trim() || "https://useorgx.com";
39
+ var ORGX_WIZARD_OAUTH_SCOPES = [
40
+ "decisions:read",
41
+ "decisions:write",
42
+ "agents:read",
43
+ "agents:write",
44
+ "initiatives:read",
45
+ "initiatives:write",
46
+ "memory:read",
47
+ "offline_access"
48
+ ];
49
+ var ORGX_WIZARD_OAUTH_SCOPE = ORGX_WIZARD_OAUTH_SCOPES.join(" ");
35
50
  var HOME = homedir();
36
51
  var APPDATA = process.env.APPDATA?.trim();
37
52
  var LOCAL_APPDATA = process.env.LOCALAPPDATA?.trim();
@@ -59,11 +74,31 @@ var CLAUDE_DIR = join(HOME, ".claude");
59
74
  var CURSOR_DIR = join(HOME, ".cursor");
60
75
  var CODEX_DIR = join(HOME, ".codex");
61
76
  var OPENCLAW_DIR = join(HOME, ".openclaw");
77
+ var AGENTS_DIR = join(HOME, ".agents");
62
78
  var CLAUDE_SKILLS_DIR = join(CLAUDE_DIR, "skills");
63
79
  var CLAUDE_ORGX_SKILL_DIR = join(CLAUDE_SKILLS_DIR, "orgx");
64
80
  var CLAUDE_ORGX_SKILL_PATH = join(CLAUDE_ORGX_SKILL_DIR, "SKILL.md");
65
81
  var CURSOR_RULES_DIR = join(CURSOR_DIR, "rules");
66
82
  var CURSOR_ORGX_RULE_PATH = join(CURSOR_RULES_DIR, "orgx.md");
83
+ var CODEX_PLUGINS_DIR = join(CODEX_DIR, "plugins");
84
+ var CODEX_ORGX_PLUGIN_DIR = join(CODEX_PLUGINS_DIR, "orgx-codex-plugin");
85
+ var CODEX_MARKETPLACE_DIR = join(AGENTS_DIR, "plugins");
86
+ var CODEX_MARKETPLACE_PATH = join(CODEX_MARKETPLACE_DIR, "marketplace.json");
87
+ var CLAUDE_MANAGED_MARKETPLACE_DIR = join(
88
+ ORGX_WIZARD_CONFIG_HOME,
89
+ "plugins",
90
+ "claude-marketplace"
91
+ );
92
+ var CLAUDE_MANAGED_MARKETPLACE_MANIFEST_PATH = join(
93
+ CLAUDE_MANAGED_MARKETPLACE_DIR,
94
+ ".claude-plugin",
95
+ "marketplace.json"
96
+ );
97
+ var CLAUDE_MANAGED_PLUGIN_DIR = join(
98
+ CLAUDE_MANAGED_MARKETPLACE_DIR,
99
+ "plugins",
100
+ "orgx-claude-code-plugin"
101
+ );
67
102
  var CLAUDE_MCP_PATHS = uniquePaths([join(CLAUDE_DIR, "mcp.json")]);
68
103
  var CURSOR_MCP_PATHS = uniquePaths([join(CURSOR_DIR, "mcp.json")]);
69
104
  var CODEX_CONFIG_PATHS = uniquePaths([join(CODEX_DIR, "config.toml")]);
@@ -367,6 +402,19 @@ function buildStoredRecord(value) {
367
402
  verifiedAt: value.verifiedAt
368
403
  };
369
404
  }
405
+ function readWizardAuthMetadata(authPath = ORGX_WIZARD_AUTH_PATH) {
406
+ const stored = parseStoredWizardAuthRecord(readTextIfExists(authPath));
407
+ if (!stored) {
408
+ return null;
409
+ }
410
+ return {
411
+ baseUrl: stored.baseUrl,
412
+ keyPrefix: stored.keyPrefix,
413
+ source: "manual",
414
+ storage: stored.storage,
415
+ verifiedAt: stored.verifiedAt
416
+ };
417
+ }
370
418
  async function readWizardAuth(authPath = ORGX_WIZARD_AUTH_PATH, options = {}) {
371
419
  const stored = parseStoredWizardAuthRecord(readTextIfExists(authPath));
372
420
  if (!stored) {
@@ -466,7 +514,11 @@ function normalizeOrgxBaseUrl(raw) {
466
514
  }
467
515
  }
468
516
  function buildOrgxApiUrl(path, baseUrl) {
469
- const normalizedBase = normalizeOrgxBaseUrl(baseUrl).replace(/\/+$/, "");
517
+ const parsedBase = new URL(normalizeOrgxBaseUrl(baseUrl));
518
+ if (parsedBase.protocol === "https:" && normalizeHost(parsedBase.hostname) === "useorgx.com") {
519
+ parsedBase.hostname = "www.useorgx.com";
520
+ }
521
+ const normalizedBase = parsedBase.toString().replace(/\/+$/, "");
470
522
  const apiBase = normalizedBase.endsWith("/api") ? normalizedBase : `${normalizedBase}/api`;
471
523
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
472
524
  return `${apiBase}${normalizedPath}`;
@@ -484,6 +536,52 @@ function buildResolvedAuth(apiKey, baseUrl, source, extras = {}) {
484
536
  ...extras.verifiedAt ? { verifiedAt: extras.verifiedAt } : {}
485
537
  };
486
538
  }
539
+ function buildAuthHint(keyPrefix, baseUrl, source, extras = {}) {
540
+ return {
541
+ keyPrefix: keyPrefix.trim(),
542
+ baseUrl,
543
+ source,
544
+ ...extras.path ? { path: extras.path } : {},
545
+ ...extras.verifiedAt ? { verifiedAt: extras.verifiedAt } : {}
546
+ };
547
+ }
548
+ function peekResolvedOrgxAuth(options = {}) {
549
+ const envApiKey = process.env.ORGX_API_KEY?.trim();
550
+ const envBaseUrl = process.env.ORGX_BASE_URL?.trim();
551
+ if (envApiKey) {
552
+ return buildAuthHint(
553
+ extractKeyPrefix(envApiKey),
554
+ withResolvedBaseUrl(DEFAULT_ORGX_BASE_URL, envBaseUrl),
555
+ "environment"
556
+ );
557
+ }
558
+ const authPath = options.authPath ?? ORGX_WIZARD_AUTH_PATH;
559
+ const stored = readWizardAuthMetadata(authPath);
560
+ if (stored) {
561
+ return buildAuthHint(
562
+ stored.keyPrefix,
563
+ withResolvedBaseUrl(stored.baseUrl, envBaseUrl),
564
+ "wizard-store",
565
+ {
566
+ path: authPath,
567
+ verifiedAt: stored.verifiedAt
568
+ }
569
+ );
570
+ }
571
+ const openclawConfigPath = options.openclawConfigPath ?? OPENCLAW_CONFIG_PATH;
572
+ const openclawAuth = readOpenClawAuth(
573
+ openclawConfigPath ? readTextIfExists(openclawConfigPath) : null
574
+ );
575
+ if (openclawAuth.apiKey && openclawConfigPath) {
576
+ return buildAuthHint(
577
+ extractKeyPrefix(openclawAuth.apiKey),
578
+ withResolvedBaseUrl(openclawAuth.baseUrl || DEFAULT_ORGX_BASE_URL, envBaseUrl),
579
+ "openclaw-config-file",
580
+ { path: openclawConfigPath }
581
+ );
582
+ }
583
+ return null;
584
+ }
487
585
  async function resolveOrgxAuth(options = {}) {
488
586
  const envApiKey = process.env.ORGX_API_KEY?.trim();
489
587
  const envBaseUrl = process.env.ORGX_BASE_URL?.trim();
@@ -745,6 +843,295 @@ function openBrowser(url) {
745
843
  return { ok: true };
746
844
  }
747
845
 
846
+ // src/lib/wizard-state.ts
847
+ import { randomUUID } from "crypto";
848
+
849
+ // src/surfaces/names.ts
850
+ var SURFACE_NAMES = [
851
+ "claude",
852
+ "cursor",
853
+ "codex",
854
+ "openclaw",
855
+ "vscode",
856
+ "windsurf",
857
+ "zed",
858
+ "chatgpt"
859
+ ];
860
+ var AUTOMATED_SURFACE_NAMES = [
861
+ "claude",
862
+ "cursor",
863
+ "codex",
864
+ "openclaw",
865
+ "vscode",
866
+ "windsurf",
867
+ "zed"
868
+ ];
869
+ var MCP_SURFACE_NAMES = [
870
+ "claude",
871
+ "cursor",
872
+ "codex",
873
+ "vscode",
874
+ "windsurf",
875
+ "zed"
876
+ ];
877
+
878
+ // src/lib/wizard-state.ts
879
+ function isNonEmptyString2(value) {
880
+ return typeof value === "string" && value.trim().length > 0;
881
+ }
882
+ function isSurfaceName(value) {
883
+ return typeof value === "string" && SURFACE_NAMES.includes(value);
884
+ }
885
+ function parseContinuityDefaults(value) {
886
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
887
+ return void 0;
888
+ }
889
+ const record = value;
890
+ const parsed = {};
891
+ if (record.executionMode === "cloud" || record.executionMode === "local") {
892
+ parsed.executionMode = record.executionMode;
893
+ }
894
+ if (isSurfaceName(record.reviewSurface)) {
895
+ parsed.reviewSurface = record.reviewSurface;
896
+ }
897
+ if (isNonEmptyString2(record.workspaceId)) {
898
+ parsed.workspaceId = record.workspaceId.trim();
899
+ }
900
+ if (isNonEmptyString2(record.workspaceName)) {
901
+ parsed.workspaceName = record.workspaceName.trim();
902
+ }
903
+ return Object.keys(parsed).length > 0 ? parsed : void 0;
904
+ }
905
+ function parseDemoInitiative(value) {
906
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
907
+ return void 0;
908
+ }
909
+ const record = value;
910
+ if (!isNonEmptyString2(record.id) || !isNonEmptyString2(record.title) || !isNonEmptyString2(record.liveUrl) || !isNonEmptyString2(record.createdAt)) {
911
+ return void 0;
912
+ }
913
+ return {
914
+ createdAt: record.createdAt.trim(),
915
+ ...isNonEmptyString2(record.artifactId) ? { artifactId: record.artifactId.trim() } : {},
916
+ ...isNonEmptyString2(record.artifactName) ? { artifactName: record.artifactName.trim() } : {},
917
+ ...isNonEmptyString2(record.artifactType) ? { artifactType: record.artifactType.trim() } : {},
918
+ ...isNonEmptyString2(record.artifactUrl) ? { artifactUrl: record.artifactUrl.trim() } : {},
919
+ ...isNonEmptyString2(record.decisionId) ? { decisionId: record.decisionId.trim() } : {},
920
+ ...isNonEmptyString2(record.decisionStatus) ? { decisionStatus: record.decisionStatus.trim() } : {},
921
+ ...isNonEmptyString2(record.decisionTitle) ? { decisionTitle: record.decisionTitle.trim() } : {},
922
+ id: record.id.trim(),
923
+ liveUrl: record.liveUrl.trim(),
924
+ title: record.title.trim(),
925
+ ...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
926
+ ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
927
+ };
928
+ }
929
+ function parseOnboardingTask(value) {
930
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
931
+ return void 0;
932
+ }
933
+ const record = value;
934
+ if (!isNonEmptyString2(record.id) || !isNonEmptyString2(record.title) || !isNonEmptyString2(record.createdAt)) {
935
+ return void 0;
936
+ }
937
+ return {
938
+ createdAt: record.createdAt.trim(),
939
+ id: record.id.trim(),
940
+ ...isNonEmptyString2(record.initiativeId) ? { initiativeId: record.initiativeId.trim() } : {},
941
+ ...isNonEmptyString2(record.status) ? { status: record.status.trim() } : {},
942
+ title: record.title.trim(),
943
+ ...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
944
+ ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
945
+ };
946
+ }
947
+ function parseAgentRosterEntry(value) {
948
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
949
+ return void 0;
950
+ }
951
+ const record = value;
952
+ if (!isNonEmptyString2(record.agentType) || !isNonEmptyString2(record.domain) || !isNonEmptyString2(record.name)) {
953
+ return void 0;
954
+ }
955
+ return {
956
+ agentType: record.agentType.trim(),
957
+ domain: record.domain.trim(),
958
+ name: record.name.trim()
959
+ };
960
+ }
961
+ function parseAgentRoster(value) {
962
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
963
+ return void 0;
964
+ }
965
+ const record = value;
966
+ if (!isNonEmptyString2(record.configuredAt) || !Array.isArray(record.agents)) {
967
+ return void 0;
968
+ }
969
+ const agents = record.agents.map((entry) => parseAgentRosterEntry(entry)).filter((entry) => Boolean(entry));
970
+ if (agents.length === 0) {
971
+ return void 0;
972
+ }
973
+ return {
974
+ agents,
975
+ configuredAt: record.configuredAt.trim(),
976
+ ...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
977
+ ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
978
+ };
979
+ }
980
+ function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
981
+ return {
982
+ installationId: `wizard-${randomUUID()}`,
983
+ createdAt: now,
984
+ updatedAt: now
985
+ };
986
+ }
987
+ function sanitizeWizardStateRecord(record) {
988
+ const continuity = parseContinuityDefaults(record.continuity);
989
+ const agentRoster = parseAgentRoster(record.agentRoster);
990
+ const demoInitiative = parseDemoInitiative(record.demoInitiative);
991
+ const onboardingTask = parseOnboardingTask(record.onboardingTask);
992
+ return {
993
+ installationId: record.installationId.trim(),
994
+ createdAt: record.createdAt.trim(),
995
+ updatedAt: record.updatedAt.trim(),
996
+ ...continuity ? { continuity } : {},
997
+ ...agentRoster ? { agentRoster } : {},
998
+ ...demoInitiative ? { demoInitiative } : {},
999
+ ...onboardingTask ? { onboardingTask } : {}
1000
+ };
1001
+ }
1002
+ function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
1003
+ const parsed = parseJsonObject(readTextIfExists(statePath));
1004
+ if (!isNonEmptyString2(parsed.installationId) || !isNonEmptyString2(parsed.createdAt) || !isNonEmptyString2(parsed.updatedAt)) {
1005
+ return null;
1006
+ }
1007
+ const state = {
1008
+ installationId: parsed.installationId.trim(),
1009
+ createdAt: parsed.createdAt.trim(),
1010
+ updatedAt: parsed.updatedAt.trim()
1011
+ };
1012
+ const continuity = parseContinuityDefaults(parsed.continuity);
1013
+ if (continuity !== void 0) state.continuity = continuity;
1014
+ const agentRoster = parseAgentRoster(parsed.agentRoster);
1015
+ if (agentRoster !== void 0) state.agentRoster = agentRoster;
1016
+ const demoInitiative = parseDemoInitiative(parsed.demoInitiative);
1017
+ if (demoInitiative !== void 0) state.demoInitiative = demoInitiative;
1018
+ const onboardingTask = parseOnboardingTask(parsed.onboardingTask);
1019
+ if (onboardingTask !== void 0) state.onboardingTask = onboardingTask;
1020
+ return state;
1021
+ }
1022
+ function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
1023
+ const record = sanitizeWizardStateRecord(value);
1024
+ writeJsonFile(statePath, record, { mode: 384 });
1025
+ return record;
1026
+ }
1027
+ function updateWizardState(updater, statePath = ORGX_WIZARD_STATE_PATH) {
1028
+ const current = readWizardState(statePath) ?? createWizardState();
1029
+ const next = updater(current);
1030
+ const sanitized = sanitizeWizardStateRecord({
1031
+ ...next,
1032
+ installationId: next.installationId || current.installationId,
1033
+ createdAt: next.createdAt || current.createdAt,
1034
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1035
+ });
1036
+ writeJsonFile(statePath, sanitized, { mode: 384 });
1037
+ return sanitized;
1038
+ }
1039
+ function getOrCreateWizardInstallationId(statePath = ORGX_WIZARD_STATE_PATH) {
1040
+ const existing = readWizardState(statePath);
1041
+ if (existing) {
1042
+ return existing.installationId;
1043
+ }
1044
+ const record = createWizardState();
1045
+ writeWizardState(record, statePath);
1046
+ return record.installationId;
1047
+ }
1048
+
1049
+ // src/lib/agent-roster.ts
1050
+ var AGENT_ROSTER_PROFILES = [
1051
+ { agentType: "orchestrator-agent", defaultDomain: "orchestrator", name: "Xandy" },
1052
+ { agentType: "product-agent", defaultDomain: "product", name: "Pace" },
1053
+ { agentType: "engineering-agent", defaultDomain: "engineering", name: "Eli" },
1054
+ { agentType: "marketing-agent", defaultDomain: "marketing", name: "Mark" },
1055
+ { agentType: "sales-agent", defaultDomain: "sales", name: "Sage" },
1056
+ { agentType: "operations-agent", defaultDomain: "operations", name: "Orion" },
1057
+ { agentType: "design-agent", defaultDomain: "design", name: "Dana" }
1058
+ ];
1059
+ var AGENT_ROSTER_DOMAINS = AGENT_ROSTER_PROFILES.map((profile) => profile.defaultDomain);
1060
+ var DEFAULT_AGENT_ROSTER = AGENT_ROSTER_PROFILES.map(
1061
+ ({ agentType, defaultDomain, name }) => ({
1062
+ agentType,
1063
+ domain: defaultDomain,
1064
+ name
1065
+ })
1066
+ );
1067
+ function findAgentRosterProfile(name) {
1068
+ return AGENT_ROSTER_PROFILES.find((profile) => profile.name === name);
1069
+ }
1070
+ function listAgentRosterProfiles() {
1071
+ return AGENT_ROSTER_PROFILES.map((profile) => ({ ...profile }));
1072
+ }
1073
+ function buildAgentRosterEntries(assignments) {
1074
+ const seenNames = /* @__PURE__ */ new Set();
1075
+ return assignments.map((assignment) => {
1076
+ const name = assignment.name.trim();
1077
+ const domain = assignment.domain.trim();
1078
+ if (domain.length === 0) {
1079
+ throw new Error("OrgX agent roster domains must be non-empty.");
1080
+ }
1081
+ const profile = findAgentRosterProfile(name);
1082
+ if (!profile) {
1083
+ throw new Error(`Unknown OrgX agent '${assignment.name}'.`);
1084
+ }
1085
+ if (seenNames.has(profile.name)) {
1086
+ throw new Error(`OrgX agent '${profile.name}' can only be assigned once.`);
1087
+ }
1088
+ seenNames.add(profile.name);
1089
+ return {
1090
+ agentType: profile.agentType,
1091
+ domain,
1092
+ name: profile.name
1093
+ };
1094
+ });
1095
+ }
1096
+ function toAgentRosterRecord(workspace, agents) {
1097
+ return {
1098
+ agents: agents.map((agent) => ({ ...agent })),
1099
+ configuredAt: (/* @__PURE__ */ new Date()).toISOString(),
1100
+ workspaceId: workspace.id,
1101
+ workspaceName: workspace.name
1102
+ };
1103
+ }
1104
+ function configureAgentRoster(workspace, assignments, options = {}) {
1105
+ const agents = buildAgentRosterEntries(assignments);
1106
+ if (agents.length === 0) {
1107
+ throw new Error("Select at least one OrgX agent before saving the roster.");
1108
+ }
1109
+ const roster = toAgentRosterRecord(workspace, agents);
1110
+ updateWizardState(
1111
+ (current) => ({
1112
+ ...current,
1113
+ agentRoster: roster
1114
+ }),
1115
+ options.statePath
1116
+ );
1117
+ return roster;
1118
+ }
1119
+ function ensureDefaultAgentRoster(workspace, options = {}) {
1120
+ const existing = readWizardState(options.statePath)?.agentRoster;
1121
+ if (existing?.workspaceId === workspace.id && existing.agents.length > 0) {
1122
+ return existing;
1123
+ }
1124
+ const roster = toAgentRosterRecord(workspace, DEFAULT_AGENT_ROSTER);
1125
+ updateWizardState(
1126
+ (current) => ({
1127
+ ...current,
1128
+ agentRoster: roster
1129
+ }),
1130
+ options.statePath
1131
+ );
1132
+ return roster;
1133
+ }
1134
+
748
1135
  // src/lib/openclaw-health.ts
749
1136
  import { spawnSync as spawnSync2 } from "child_process";
750
1137
  function trimOutput(value) {
@@ -1714,35 +2101,6 @@ function inspectCodexConfigToml(input) {
1714
2101
  };
1715
2102
  }
1716
2103
 
1717
- // src/surfaces/names.ts
1718
- var SURFACE_NAMES = [
1719
- "claude",
1720
- "cursor",
1721
- "codex",
1722
- "openclaw",
1723
- "vscode",
1724
- "windsurf",
1725
- "zed",
1726
- "chatgpt"
1727
- ];
1728
- var AUTOMATED_SURFACE_NAMES = [
1729
- "claude",
1730
- "cursor",
1731
- "codex",
1732
- "openclaw",
1733
- "vscode",
1734
- "windsurf",
1735
- "zed"
1736
- ];
1737
- var MCP_SURFACE_NAMES = [
1738
- "claude",
1739
- "cursor",
1740
- "codex",
1741
- "vscode",
1742
- "windsurf",
1743
- "zed"
1744
- ];
1745
-
1746
2104
  // src/surfaces/detection.ts
1747
2105
  import { existsSync as existsSync2 } from "fs";
1748
2106
  import { dirname as dirname2 } from "path";
@@ -1823,6 +2181,9 @@ function detectSurface(name, exists = existsSync2) {
1823
2181
  }
1824
2182
 
1825
2183
  // src/surfaces/registry.ts
2184
+ var AUTH_SETUP_HINT = "orgx-wizard auth login";
2185
+ var AUTH_SET_KEY_HINT = "orgx-wizard auth set-key";
2186
+ var DOCTOR_HINT = "orgx-wizard doctor";
1826
2187
  function getSurfacePath(name) {
1827
2188
  const detection = detectSurface(name);
1828
2189
  const locator = getSurfaceLocator(name);
@@ -1951,27 +2312,92 @@ function automatedSurfaceStatus(name) {
1951
2312
  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."
1952
2313
  };
1953
2314
  }
1954
- function manualSurfaceStatus(name) {
1955
- const detection = detectSurface(name);
2315
+ function formatAuthHintSource(authHint) {
2316
+ switch (authHint.source) {
2317
+ case "environment":
2318
+ return "env ORGX_API_KEY";
2319
+ case "wizard-store":
2320
+ return "wizard auth store";
2321
+ case "openclaw-config-file":
2322
+ return "openclaw config";
2323
+ }
2324
+ }
2325
+ function buildChatgptManualStatus(detection, authHint) {
1956
2326
  const path = detection.existingPath ?? detection.preferredPath;
1957
- const shared = {
1958
- name,
2327
+ const details = [...detection.evidence];
2328
+ if (authHint) {
2329
+ details.push(
2330
+ `OrgX auth ready via ${formatAuthHintSource(authHint)} (${authHint.keyPrefix})`
2331
+ );
2332
+ details.push(`Hosted connector URL: ${ORGX_HOSTED_MCP_URL}`);
2333
+ details.push(
2334
+ "Manual step: in ChatGPT desktop developer mode, add a custom connector pointing at the hosted OrgX MCP URL."
2335
+ );
2336
+ details.push(
2337
+ "After connecting ChatGPT, run `orgx-wizard doctor` to verify hosted MCP tool access."
2338
+ );
2339
+ } else {
2340
+ details.push(
2341
+ "OrgX auth is not configured yet. Run `orgx-wizard auth login` or `orgx-wizard auth set-key` first."
2342
+ );
2343
+ details.push(
2344
+ "After auth is configured, add a custom ChatGPT developer connector pointing at the hosted OrgX MCP URL."
2345
+ );
2346
+ }
2347
+ let summary;
2348
+ if (!authHint) {
2349
+ 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.";
2350
+ } else {
2351
+ 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.";
2352
+ }
2353
+ return {
2354
+ name: "chatgpt",
1959
2355
  mode: "manual",
1960
2356
  detected: detection.detected,
1961
2357
  configured: false,
1962
- ...path ? { path } : {}
2358
+ ...path ? { path } : {},
2359
+ details,
2360
+ summary
2361
+ };
2362
+ }
2363
+ function buildChatgptAddResult(path, authHint, toolCheck) {
2364
+ if (!authHint) {
2365
+ return {
2366
+ name: "chatgpt",
2367
+ changed: false,
2368
+ ...path ? { path } : {},
2369
+ 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.`
2370
+ };
2371
+ }
2372
+ if (!toolCheck || !toolCheck.ok) {
2373
+ const failure = toolCheck?.error ?? "hosted MCP verification did not complete.";
2374
+ return {
2375
+ name: "chatgpt",
2376
+ changed: false,
2377
+ ...path ? { path } : {},
2378
+ 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.`
2379
+ };
2380
+ }
2381
+ return {
2382
+ name: "chatgpt",
2383
+ changed: false,
2384
+ ...path ? { path } : {},
2385
+ message: `OrgX auth and hosted MCP access are verified. Add ${ORGX_HOSTED_MCP_URL} as a custom connector in ChatGPT developer mode, then run \`${DOCTOR_HINT}\` to confirm \`${toolCheck.toolName}\`.`
2386
+ };
2387
+ }
2388
+ function buildChatgptRemoveResult(path) {
2389
+ return {
2390
+ name: "chatgpt",
2391
+ changed: false,
2392
+ ...path ? { path } : {},
2393
+ message: "Remove the custom OrgX connector from ChatGPT developer mode; the wizard does not manage ChatGPT config files directly."
1963
2394
  };
2395
+ }
2396
+ function manualSurfaceStatus(name) {
2397
+ const detection = detectSurface(name);
1964
2398
  switch (name) {
1965
2399
  case "chatgpt":
1966
- return {
1967
- ...shared,
1968
- details: [
1969
- ...detection.evidence,
1970
- "ChatGPT desktop support is not automated yet.",
1971
- "Manual follow-up: wire the OrgX surface once the app-side config contract is finalized."
1972
- ],
1973
- summary: detection.detected ? "ChatGPT support files detected; manual OrgX wiring still required." : "ChatGPT desktop support files were not found on disk."
1974
- };
2400
+ return buildChatgptManualStatus(detection, peekResolvedOrgxAuth());
1975
2401
  }
1976
2402
  }
1977
2403
  function getSurfaceStatus(name) {
@@ -2089,11 +2515,19 @@ async function addAutomatedSurface(name) {
2089
2515
  writeTextFile(path, next);
2090
2516
  return { name, changed: true, path, message: "OrgX cloud MCP is connected in Zed." };
2091
2517
  }
2518
+ case "chatgpt": {
2519
+ const authHint = peekResolvedOrgxAuth();
2520
+ if (!authHint) {
2521
+ return buildChatgptAddResult(path, null);
2522
+ }
2523
+ const toolCheck = await checkHostedMcpToolAccess();
2524
+ return buildChatgptAddResult(path, authHint, toolCheck);
2525
+ }
2092
2526
  default:
2093
2527
  return {
2094
2528
  name,
2095
2529
  changed: false,
2096
- message: `${name} is not automated yet.`
2530
+ message: `${name} is not supported by the wizard.`
2097
2531
  };
2098
2532
  }
2099
2533
  }
@@ -2184,11 +2618,13 @@ function removeAutomatedSurface(name) {
2184
2618
  message: "OrgX connection was removed from Zed."
2185
2619
  };
2186
2620
  }
2621
+ case "chatgpt":
2622
+ return buildChatgptRemoveResult(path);
2187
2623
  default:
2188
2624
  return {
2189
2625
  name,
2190
2626
  changed: false,
2191
- message: `${name} is not automated yet.`
2627
+ message: `${name} is not supported by the wizard.`
2192
2628
  };
2193
2629
  }
2194
2630
  }
@@ -2305,112 +2741,6 @@ function assessDoctorReport(report) {
2305
2741
  };
2306
2742
  }
2307
2743
 
2308
- // src/lib/wizard-state.ts
2309
- import { randomUUID } from "crypto";
2310
- function isNonEmptyString2(value) {
2311
- return typeof value === "string" && value.trim().length > 0;
2312
- }
2313
- function isSurfaceName(value) {
2314
- return typeof value === "string" && SURFACE_NAMES.includes(value);
2315
- }
2316
- function parseContinuityDefaults(value) {
2317
- if (!value || typeof value !== "object" || Array.isArray(value)) {
2318
- return void 0;
2319
- }
2320
- const record = value;
2321
- const parsed = {};
2322
- if (record.executionMode === "cloud" || record.executionMode === "local") {
2323
- parsed.executionMode = record.executionMode;
2324
- }
2325
- if (isSurfaceName(record.reviewSurface)) {
2326
- parsed.reviewSurface = record.reviewSurface;
2327
- }
2328
- if (isNonEmptyString2(record.workspaceId)) {
2329
- parsed.workspaceId = record.workspaceId.trim();
2330
- }
2331
- if (isNonEmptyString2(record.workspaceName)) {
2332
- parsed.workspaceName = record.workspaceName.trim();
2333
- }
2334
- return Object.keys(parsed).length > 0 ? parsed : void 0;
2335
- }
2336
- function parseDemoInitiative(value) {
2337
- if (!value || typeof value !== "object" || Array.isArray(value)) {
2338
- return void 0;
2339
- }
2340
- const record = value;
2341
- if (!isNonEmptyString2(record.id) || !isNonEmptyString2(record.title) || !isNonEmptyString2(record.liveUrl) || !isNonEmptyString2(record.createdAt)) {
2342
- return void 0;
2343
- }
2344
- return {
2345
- createdAt: record.createdAt.trim(),
2346
- id: record.id.trim(),
2347
- liveUrl: record.liveUrl.trim(),
2348
- title: record.title.trim(),
2349
- ...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
2350
- ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
2351
- };
2352
- }
2353
- function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
2354
- return {
2355
- installationId: `wizard-${randomUUID()}`,
2356
- createdAt: now,
2357
- updatedAt: now
2358
- };
2359
- }
2360
- function sanitizeWizardStateRecord(record) {
2361
- const continuity = parseContinuityDefaults(record.continuity);
2362
- const demoInitiative = parseDemoInitiative(record.demoInitiative);
2363
- return {
2364
- installationId: record.installationId.trim(),
2365
- createdAt: record.createdAt.trim(),
2366
- updatedAt: record.updatedAt.trim(),
2367
- ...continuity ? { continuity } : {},
2368
- ...demoInitiative ? { demoInitiative } : {}
2369
- };
2370
- }
2371
- function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
2372
- const parsed = parseJsonObject(readTextIfExists(statePath));
2373
- if (!isNonEmptyString2(parsed.installationId) || !isNonEmptyString2(parsed.createdAt) || !isNonEmptyString2(parsed.updatedAt)) {
2374
- return null;
2375
- }
2376
- const state = {
2377
- installationId: parsed.installationId.trim(),
2378
- createdAt: parsed.createdAt.trim(),
2379
- updatedAt: parsed.updatedAt.trim()
2380
- };
2381
- const continuity = parseContinuityDefaults(parsed.continuity);
2382
- if (continuity !== void 0) state.continuity = continuity;
2383
- const demoInitiative = parseDemoInitiative(parsed.demoInitiative);
2384
- if (demoInitiative !== void 0) state.demoInitiative = demoInitiative;
2385
- return state;
2386
- }
2387
- function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
2388
- const record = sanitizeWizardStateRecord(value);
2389
- writeJsonFile(statePath, record, { mode: 384 });
2390
- return record;
2391
- }
2392
- function updateWizardState(updater, statePath = ORGX_WIZARD_STATE_PATH) {
2393
- const current = readWizardState(statePath) ?? createWizardState();
2394
- const next = updater(current);
2395
- const sanitized = sanitizeWizardStateRecord({
2396
- ...next,
2397
- installationId: next.installationId || current.installationId,
2398
- createdAt: next.createdAt || current.createdAt,
2399
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2400
- });
2401
- writeJsonFile(statePath, sanitized, { mode: 384 });
2402
- return sanitized;
2403
- }
2404
- function getOrCreateWizardInstallationId(statePath = ORGX_WIZARD_STATE_PATH) {
2405
- const existing = readWizardState(statePath);
2406
- if (existing) {
2407
- return existing.installationId;
2408
- }
2409
- const record = createWizardState();
2410
- writeWizardState(record, statePath);
2411
- return record.installationId;
2412
- }
2413
-
2414
2744
  // src/lib/continuity.ts
2415
2745
  var REVIEW_SURFACE_ORDER = [
2416
2746
  "claude",
@@ -2489,6 +2819,14 @@ function persistContinuityDefaults(seed = {}, statePath) {
2489
2819
  // src/lib/initiatives.ts
2490
2820
  var FOUNDER_DEMO_INITIATIVE_TITLE = "Founder Demo Initiative";
2491
2821
  var FOUNDER_DEMO_INITIATIVE_SUMMARY = "Starter initiative created by @useorgx/wizard to validate workspace routing, live views, and the default OrgX skill pack.";
2822
+ var FOUNDER_DEMO_DECISION_TITLE = "Approve founder demo workspace";
2823
+ var FOUNDER_DEMO_DECISION_SUMMARY = "Initial decision created by @useorgx/wizard so the founder preset leaves the workspace with a resolved approval trail.";
2824
+ var FOUNDER_DEMO_DECISION_RESOLUTION = "Approved automatically by @useorgx/wizard after the founder preset finished creating the live demo workspace.";
2825
+ var FOUNDER_DEMO_ARTIFACT_NAME = "Founder Demo Live View";
2826
+ var FOUNDER_DEMO_ARTIFACT_TYPE = "document";
2827
+ var FOUNDER_DEMO_ARTIFACT_DESCRIPTION = "Live founder demo generated by @useorgx/wizard after workspace bootstrap completed.";
2828
+ var ONBOARDING_TASK_TITLE = "Complete OrgX onboarding";
2829
+ var ONBOARDING_TASK_SUMMARY = "Starter onboarding task created by @useorgx/wizard so the first workspace has a clear follow-up after setup.";
2492
2830
  function parseResponseBody3(text2) {
2493
2831
  if (!text2) {
2494
2832
  return null;
@@ -2508,11 +2846,15 @@ function formatHttpError2(status, body) {
2508
2846
  }
2509
2847
  return `HTTP ${status}`;
2510
2848
  }
2511
- function parseInitiative(payload) {
2849
+ function extractEntity(payload) {
2512
2850
  const entity = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
2513
2851
  if (!isRecord(entity)) {
2514
- throw new Error("OrgX returned an unexpected initiative payload.");
2852
+ throw new Error("OrgX returned an unexpected entity payload.");
2515
2853
  }
2854
+ return entity;
2855
+ }
2856
+ function parseInitiative(payload) {
2857
+ const entity = extractEntity(payload);
2516
2858
  const id = typeof entity.id === "string" ? entity.id.trim() : "";
2517
2859
  const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2518
2860
  if (!id || !title) {
@@ -2524,9 +2866,60 @@ function parseInitiative(payload) {
2524
2866
  ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
2525
2867
  };
2526
2868
  }
2527
- function toDemoInitiativeRecord(initiative, liveUrl, workspace) {
2869
+ function parseDecision(payload) {
2870
+ const entity = extractEntity(payload);
2871
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2872
+ const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2873
+ if (!id || !title) {
2874
+ throw new Error("OrgX returned an incomplete decision payload.");
2875
+ }
2876
+ return {
2877
+ id,
2878
+ title,
2879
+ ...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
2880
+ ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
2881
+ };
2882
+ }
2883
+ function parseArtifact(payload) {
2884
+ const entity = extractEntity(payload);
2885
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2886
+ const name = typeof entity.name === "string" ? entity.name.trim() : typeof entity.title === "string" ? entity.title.trim() : "";
2887
+ const type = typeof entity.artifact_type === "string" ? entity.artifact_type.trim() : typeof entity.type === "string" ? entity.type.trim() : "";
2888
+ const url = typeof entity.external_url === "string" ? entity.external_url.trim() : typeof entity.url === "string" ? entity.url.trim() : "";
2889
+ if (!id || !name) {
2890
+ throw new Error("OrgX returned an incomplete artifact payload.");
2891
+ }
2892
+ return {
2893
+ id,
2894
+ name,
2895
+ ...type ? { type } : {},
2896
+ ...url ? { url } : {}
2897
+ };
2898
+ }
2899
+ function parseTask(payload) {
2900
+ const entity = extractEntity(payload);
2901
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2902
+ const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2903
+ if (!id || !title) {
2904
+ throw new Error("OrgX returned an incomplete task payload.");
2905
+ }
2906
+ return {
2907
+ id,
2908
+ title,
2909
+ ...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
2910
+ ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
2911
+ };
2912
+ }
2913
+ function toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace) {
2528
2914
  return {
2529
2915
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2916
+ artifactId: artifact.id,
2917
+ artifactName: artifact.name,
2918
+ ...artifact.type ? { artifactType: artifact.type } : {},
2919
+ ...artifact.url ? { artifactUrl: artifact.url } : {},
2920
+ decisionId: decision.id,
2921
+ ...decision.status ? { decisionStatus: decision.status } : {},
2922
+ decisionTitle: decision.title,
2530
2923
  id: initiative.id,
2531
2924
  liveUrl,
2532
2925
  title: initiative.title,
@@ -2534,6 +2927,17 @@ function toDemoInitiativeRecord(initiative, liveUrl, workspace) {
2534
2927
  workspaceName: workspace.name
2535
2928
  };
2536
2929
  }
2930
+ function toOnboardingTaskRecord(task, workspace, initiativeId) {
2931
+ return {
2932
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2933
+ id: task.id,
2934
+ ...initiativeId ? { initiativeId } : {},
2935
+ ...task.status ? { status: task.status } : {},
2936
+ title: task.title,
2937
+ workspaceId: workspace.id,
2938
+ workspaceName: workspace.name
2939
+ };
2940
+ }
2537
2941
  async function requireOrgxAuth2(options = {}) {
2538
2942
  const auth = await resolveOrgxAuth(options);
2539
2943
  if (!auth) {
@@ -2543,11 +2947,7 @@ async function requireOrgxAuth2(options = {}) {
2543
2947
  }
2544
2948
  return auth;
2545
2949
  }
2546
- async function createInitiative(input, options = {}) {
2547
- const title = input.title.trim();
2548
- if (!title) {
2549
- throw new Error("Initiative title is required.");
2550
- }
2950
+ async function createEntity(type, body, parse2, options = {}) {
2551
2951
  const auth = await requireOrgxAuth2(options);
2552
2952
  const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
2553
2953
  method: "POST",
@@ -2556,56 +2956,183 @@ async function createInitiative(input, options = {}) {
2556
2956
  "Content-Type": "application/json"
2557
2957
  },
2558
2958
  body: JSON.stringify({
2559
- type: "initiative",
2560
- title,
2561
- status: "active",
2562
- ...input.summary?.trim() ? { summary: input.summary.trim() } : {},
2563
- ...input.workspaceId?.trim() ? { workspace_id: input.workspaceId.trim() } : {}
2959
+ type,
2960
+ ...body
2564
2961
  }),
2565
2962
  signal: AbortSignal.timeout(7e3)
2566
2963
  });
2567
- const body = parseResponseBody3(await response.text());
2964
+ const responseBody = parseResponseBody3(await response.text());
2568
2965
  if (!response.ok) {
2569
- throw new Error(`Failed to create initiative. ${formatHttpError2(response.status, body)}`);
2966
+ throw new Error(`Failed to create ${type}. ${formatHttpError2(response.status, responseBody)}`);
2570
2967
  }
2571
- return parseInitiative(body);
2968
+ return parse2(responseBody);
2572
2969
  }
2573
- async function ensureFounderDemoInitiative(workspace, options = {}) {
2574
- const existingRecord = readWizardState(options.statePath)?.demoInitiative;
2575
- if (existingRecord?.workspaceId === workspace.id) {
2576
- return {
2577
- created: false,
2578
- initiative: {
2579
- id: existingRecord.id,
2580
- title: existingRecord.title
2581
- },
2582
- liveUrl: existingRecord.liveUrl
2583
- };
2584
- }
2970
+ async function updateEntity(type, id, body, parse2, options = {}) {
2585
2971
  const auth = await requireOrgxAuth2(options);
2586
- const initiative = await createInitiative(
2972
+ const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
2973
+ method: "PATCH",
2974
+ headers: {
2975
+ Authorization: `Bearer ${auth.apiKey}`,
2976
+ "Content-Type": "application/json"
2977
+ },
2978
+ body: JSON.stringify({
2979
+ type,
2980
+ id,
2981
+ ...body
2982
+ }),
2983
+ signal: AbortSignal.timeout(7e3)
2984
+ });
2985
+ const responseBody = parseResponseBody3(await response.text());
2986
+ if (!response.ok) {
2987
+ throw new Error(`Failed to update ${type}. ${formatHttpError2(response.status, responseBody)}`);
2988
+ }
2989
+ return parse2(responseBody);
2990
+ }
2991
+ function buildLiveUrl(baseUrl, initiativeId) {
2992
+ const parsed = new URL(normalizeOrgxBaseUrl(baseUrl));
2993
+ if (parsed.protocol === "https:" && parsed.hostname === "useorgx.com") {
2994
+ parsed.hostname = "www.useorgx.com";
2995
+ }
2996
+ parsed.pathname = `/live/${initiativeId}`;
2997
+ parsed.search = "";
2998
+ parsed.hash = "";
2999
+ return parsed.toString();
3000
+ }
3001
+ async function createFounderDemoDecision(initiative, workspaceId, options = {}) {
3002
+ return createEntity(
3003
+ "decision",
2587
3004
  {
2588
- title: FOUNDER_DEMO_INITIATIVE_TITLE,
2589
- summary: FOUNDER_DEMO_INITIATIVE_SUMMARY,
2590
- workspaceId: workspace.id
3005
+ initiative_id: initiative.id,
3006
+ summary: FOUNDER_DEMO_DECISION_SUMMARY,
3007
+ title: FOUNDER_DEMO_DECISION_TITLE,
3008
+ workspace_id: workspaceId
2591
3009
  },
3010
+ parseDecision,
2592
3011
  options
2593
3012
  );
2594
- const liveUrl = `${auth.baseUrl.replace(/\/+$/, "")}/live/${initiative.id}`;
2595
- const record = toDemoInitiativeRecord(initiative, liveUrl, workspace);
2596
- updateWizardState(
2597
- (current) => ({
2598
- ...current,
2599
- demoInitiative: record
2600
- }),
2601
- options.statePath
3013
+ }
3014
+ async function approveFounderDemoDecision(decisionId, options = {}) {
3015
+ return updateEntity(
3016
+ "decision",
3017
+ decisionId,
3018
+ {
3019
+ resolution_summary: FOUNDER_DEMO_DECISION_RESOLUTION,
3020
+ status: "approved"
3021
+ },
3022
+ parseDecision,
3023
+ options
3024
+ );
3025
+ }
3026
+ async function createFounderDemoArtifact(initiative, liveUrl, workspaceId, options = {}) {
3027
+ return createEntity(
3028
+ "artifact",
3029
+ {
3030
+ artifact_type: FOUNDER_DEMO_ARTIFACT_TYPE,
3031
+ description: FOUNDER_DEMO_ARTIFACT_DESCRIPTION,
3032
+ entity_id: initiative.id,
3033
+ entity_type: "initiative",
3034
+ external_url: liveUrl,
3035
+ initiative_id: initiative.id,
3036
+ name: FOUNDER_DEMO_ARTIFACT_NAME,
3037
+ workspace_id: workspaceId
3038
+ },
3039
+ parseArtifact,
3040
+ options
3041
+ );
3042
+ }
3043
+ async function createInitiative(input, options = {}) {
3044
+ const title = input.title.trim();
3045
+ if (!title) {
3046
+ throw new Error("Initiative title is required.");
3047
+ }
3048
+ return createEntity(
3049
+ "initiative",
3050
+ {
3051
+ title,
3052
+ status: "active",
3053
+ ...input.summary?.trim() ? { summary: input.summary.trim() } : {},
3054
+ ...input.workspaceId?.trim() ? { workspace_id: input.workspaceId.trim() } : {}
3055
+ },
3056
+ parseInitiative,
3057
+ options
3058
+ );
3059
+ }
3060
+ async function ensureFounderDemoInitiative(workspace, options = {}) {
3061
+ const existingRecord = readWizardState(options.statePath)?.demoInitiative;
3062
+ const matchingRecord = existingRecord?.workspaceId === workspace.id ? existingRecord : void 0;
3063
+ const auth = await requireOrgxAuth2(options);
3064
+ const initiative = matchingRecord ? {
3065
+ id: matchingRecord.id,
3066
+ title: matchingRecord.title
3067
+ } : await createInitiative(
3068
+ {
3069
+ title: FOUNDER_DEMO_INITIATIVE_TITLE,
3070
+ summary: FOUNDER_DEMO_INITIATIVE_SUMMARY,
3071
+ workspaceId: workspace.id
3072
+ },
3073
+ options
3074
+ );
3075
+ const liveUrl = matchingRecord?.liveUrl || buildLiveUrl(auth.baseUrl, initiative.id);
3076
+ const decision = matchingRecord?.decisionId ? matchingRecord.decisionStatus === "approved" && matchingRecord.decisionTitle ? {
3077
+ id: matchingRecord.decisionId,
3078
+ status: matchingRecord.decisionStatus,
3079
+ title: matchingRecord.decisionTitle
3080
+ } : await approveFounderDemoDecision(matchingRecord.decisionId, options) : await approveFounderDemoDecision(
3081
+ (await createFounderDemoDecision(initiative, workspace.id, options)).id,
3082
+ options
3083
+ );
3084
+ const artifact = matchingRecord?.artifactId && matchingRecord.artifactName ? {
3085
+ id: matchingRecord.artifactId,
3086
+ name: matchingRecord.artifactName,
3087
+ ...matchingRecord.artifactType ? { type: matchingRecord.artifactType } : {},
3088
+ ...matchingRecord.artifactUrl ? { url: matchingRecord.artifactUrl } : {}
3089
+ } : await createFounderDemoArtifact(initiative, liveUrl, workspace.id, options);
3090
+ const record = toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace);
3091
+ updateWizardState(
3092
+ (current) => ({
3093
+ ...current,
3094
+ demoInitiative: record
3095
+ }),
3096
+ options.statePath
2602
3097
  );
2603
3098
  return {
2604
- created: true,
3099
+ artifact,
3100
+ created: !matchingRecord,
3101
+ decision,
2605
3102
  initiative,
2606
3103
  liveUrl
2607
3104
  };
2608
3105
  }
3106
+ async function ensureOnboardingTask(workspace, options = {}) {
3107
+ const existingRecord = readWizardState(options.statePath)?.onboardingTask;
3108
+ if (existingRecord?.workspaceId === workspace.id) {
3109
+ return {
3110
+ id: existingRecord.id,
3111
+ title: existingRecord.title,
3112
+ ...existingRecord.status ? { status: existingRecord.status } : {}
3113
+ };
3114
+ }
3115
+ const task = await createEntity(
3116
+ "task",
3117
+ {
3118
+ status: "todo",
3119
+ summary: ONBOARDING_TASK_SUMMARY,
3120
+ title: ONBOARDING_TASK_TITLE,
3121
+ workspace_id: workspace.id,
3122
+ ...options.initiativeId ? { initiative_id: options.initiativeId } : {}
3123
+ },
3124
+ parseTask,
3125
+ options
3126
+ );
3127
+ updateWizardState(
3128
+ (current) => ({
3129
+ ...current,
3130
+ onboardingTask: toOnboardingTaskRecord(task, workspace, options.initiativeId)
3131
+ }),
3132
+ options.statePath
3133
+ );
3134
+ return task;
3135
+ }
2609
3136
 
2610
3137
  // src/lib/skills.ts
2611
3138
  import { join as join2 } from "path";
@@ -2615,6 +3142,7 @@ var DEFAULT_ORGX_SKILL_PACKS = [
2615
3142
  "bulk-create",
2616
3143
  "nightly-recap"
2617
3144
  ];
3145
+ var PLUGIN_MANAGED_SKILL_TARGETS = ["claude", "codex"];
2618
3146
  var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
2619
3147
  var ORGX_SKILLS_OWNER = "useorgx";
2620
3148
  var ORGX_SKILLS_REPO = "skills";
@@ -2627,6 +3155,8 @@ var CURSOR_RULES_CONTENT = `# OrgX Rules
2627
3155
  - Preserve \`_context\` when widget or app-rendering flows depend on client, conversation, session, or user metadata. Do not strip or rename it.
2628
3156
  - Prefer \`list_entities\` for pending approvals and state inspection before reaching for legacy aliases.
2629
3157
  - Keep hosted OrgX MCP calls distinct from local OpenClaw/plugin surfaces before mutating config or entity state.
3158
+ - Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
3159
+ - Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supplemental, not proof.
2630
3160
  `;
2631
3161
  var CLAUDE_ORGX_SKILL_CONTENT = `---
2632
3162
  name: orgx
@@ -2652,6 +3182,8 @@ This skill is for the hosted OrgX tool surface, not the local OpenClaw plugin. K
2652
3182
  - Treat \`scaffold_initiative continue_on_error\` as best-effort scaffolding. Use it only when partial creation is acceptable and inspect failures afterward.
2653
3183
  - Preserve \`_context\` when widget or app rendering depends on client, conversation, session, or user metadata.
2654
3184
  - Prefer \`list_entities\` over older aliases when you need pending decisions, blockers, or entity state.
3185
+ - Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
3186
+ - Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supporting context only.
2655
3187
 
2656
3188
  ## Suggested Skill Packs
2657
3189
 
@@ -2742,85 +3274,972 @@ function writeManagedFile(path, content, label, sourceUrl) {
2742
3274
  writeTextFile(path, content);
2743
3275
  }
2744
3276
  return {
2745
- label,
2746
- path,
2747
- changed,
2748
- ...sourceUrl ? { sourceUrl } : {}
3277
+ label,
3278
+ path,
3279
+ changed,
3280
+ ...sourceUrl ? { sourceUrl } : {}
3281
+ };
3282
+ }
3283
+ function resolveSkillPackNames(requested = []) {
3284
+ if (requested.length === 0 || requested.includes("all")) {
3285
+ return [...DEFAULT_ORGX_SKILL_PACKS];
3286
+ }
3287
+ const seen = /* @__PURE__ */ new Set();
3288
+ const normalized = [];
3289
+ for (const name of requested) {
3290
+ const next = name.trim();
3291
+ if (!next || seen.has(next)) {
3292
+ continue;
3293
+ }
3294
+ seen.add(next);
3295
+ normalized.push(next);
3296
+ }
3297
+ return normalized;
3298
+ }
3299
+ function resolvePluginManagedSkillTargets(requested = []) {
3300
+ const seen = /* @__PURE__ */ new Set();
3301
+ const targets = [];
3302
+ for (const rawTarget of requested) {
3303
+ const target = rawTarget.trim().toLowerCase();
3304
+ if (!PLUGIN_MANAGED_SKILL_TARGETS.includes(target)) {
3305
+ continue;
3306
+ }
3307
+ if (seen.has(target)) {
3308
+ continue;
3309
+ }
3310
+ seen.add(target);
3311
+ targets.push(target);
3312
+ }
3313
+ return targets;
3314
+ }
3315
+ function planOrgxSkillsInstall(pluginTargets = []) {
3316
+ const pluginManagedTargets = resolvePluginManagedSkillTargets(pluginTargets);
3317
+ const managesClaudeSkills = pluginManagedTargets.includes("claude");
3318
+ const notes = [];
3319
+ if (managesClaudeSkills) {
3320
+ notes.push(
3321
+ "Claude Code plugin is installed, so the wizard skipped overlapping standalone Claude OrgX skill files and Claude skill-pack sync."
3322
+ );
3323
+ }
3324
+ if (pluginManagedTargets.includes("codex")) {
3325
+ notes.push(
3326
+ "Codex companion plugin bundles its own OrgX skills, so `wizard skills add` only manages standalone editor assets outside Codex."
3327
+ );
3328
+ }
3329
+ return {
3330
+ installClaudeSkillBootstrap: !managesClaudeSkills,
3331
+ installClaudeSkillPacks: !managesClaudeSkills,
3332
+ notes,
3333
+ pluginManagedTargets
3334
+ };
3335
+ }
3336
+ async function fetchAvailablePackNames(fetchImpl, ref) {
3337
+ const entries = await fetchDirectoryEntries("", fetchImpl, ref);
3338
+ return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
3339
+ }
3340
+ async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref) {
3341
+ const rootPath = skillName;
3342
+ const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
3343
+ const writes = [];
3344
+ for (const file of files) {
3345
+ const content = await fetchRemoteText(file.sourceUrl, fetchImpl);
3346
+ const relativePath = file.path.slice(`${rootPath}/`.length);
3347
+ writes.push(
3348
+ writeManagedFile(
3349
+ join2(claudeSkillsDir, skillName, relativePath),
3350
+ content,
3351
+ `${skillName}/${relativePath}`,
3352
+ file.sourceUrl
3353
+ )
3354
+ );
3355
+ }
3356
+ return {
3357
+ name: skillName,
3358
+ files: writes
3359
+ };
3360
+ }
3361
+ async function installOrgxSkills(options = {}) {
3362
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
3363
+ if (!fetchImpl) {
3364
+ throw new Error("Global fetch is unavailable in this runtime.");
3365
+ }
3366
+ const ref = options.ref ?? ORGX_SKILLS_REF;
3367
+ const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
3368
+ const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
3369
+ const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
3370
+ const plan = planOrgxSkillsInstall(options.pluginTargets ?? []);
3371
+ const requestedNames = options.skillNames ?? [];
3372
+ let skillNames;
3373
+ if (requestedNames.length === 0 || requestedNames.includes("all")) {
3374
+ try {
3375
+ skillNames = await fetchAvailablePackNames(fetchImpl, ref);
3376
+ } catch {
3377
+ skillNames = [...DEFAULT_ORGX_SKILL_PACKS];
3378
+ }
3379
+ } else {
3380
+ skillNames = resolveSkillPackNames(requestedNames);
3381
+ }
3382
+ const writes = [
3383
+ writeManagedFile(cursorRulePath, CURSOR_RULES_CONTENT, "cursor-rules")
3384
+ ];
3385
+ if (plan.installClaudeSkillBootstrap) {
3386
+ writes.push(
3387
+ writeManagedFile(claudeOrgxSkillPath, CLAUDE_ORGX_SKILL_CONTENT, "claude-orgx-skill")
3388
+ );
3389
+ }
3390
+ const packs = [];
3391
+ if (plan.installClaudeSkillPacks) {
3392
+ for (const skillName of skillNames) {
3393
+ packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref));
3394
+ }
3395
+ }
3396
+ return {
3397
+ notes: plan.notes,
3398
+ writes,
3399
+ packs
3400
+ };
3401
+ }
3402
+
3403
+ // src/lib/plugins.ts
3404
+ import { spawn } from "child_process";
3405
+ import {
3406
+ existsSync as existsSync3,
3407
+ mkdirSync as mkdirSync2,
3408
+ mkdtempSync,
3409
+ readFileSync as readFileSync2,
3410
+ readdirSync,
3411
+ rmSync,
3412
+ statSync as statSync2,
3413
+ writeFileSync as writeFileSync2
3414
+ } from "fs";
3415
+ import { tmpdir } from "os";
3416
+ import { dirname as dirname3, join as join3, relative } from "path";
3417
+ var DEFAULT_ORGX_PLUGIN_TARGETS = ["claude", "codex", "openclaw"];
3418
+ var ORGX_PLUGIN_GITHUB_OWNER = "useorgx";
3419
+ var ORGX_PLUGIN_GITHUB_REF = "main";
3420
+ var ORGX_CLAUDE_PLUGIN_NAME = "orgx-claude-code-plugin";
3421
+ var ORGX_CLAUDE_MARKETPLACE_NAME = "orgx-local";
3422
+ var ORGX_CODEX_PLUGIN_NAME = "orgx-codex-plugin";
3423
+ var ORGX_OPENCLAW_PLUGIN_ID = "orgx";
3424
+ var ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME = "@useorgx/openclaw-plugin";
3425
+ var CLAUDE_PLUGIN_SYNC_SPEC = {
3426
+ owner: ORGX_PLUGIN_GITHUB_OWNER,
3427
+ repo: ORGX_CLAUDE_PLUGIN_NAME,
3428
+ ref: ORGX_PLUGIN_GITHUB_REF,
3429
+ include: [
3430
+ { localPath: ".claude-plugin", remotePath: ".claude-plugin" },
3431
+ { localPath: "agents", remotePath: "agents" },
3432
+ { localPath: "commands", remotePath: "commands" },
3433
+ { localPath: "hooks", remotePath: "hooks" },
3434
+ { localPath: "lib", remotePath: "lib" },
3435
+ { localPath: "scripts", remotePath: "scripts" },
3436
+ { localPath: "skills", remotePath: "skills" }
3437
+ ]
3438
+ };
3439
+ var CODEX_PLUGIN_SYNC_SPEC = {
3440
+ owner: ORGX_PLUGIN_GITHUB_OWNER,
3441
+ repo: ORGX_CODEX_PLUGIN_NAME,
3442
+ ref: ORGX_PLUGIN_GITHUB_REF,
3443
+ include: [
3444
+ { localPath: ".codex-plugin", remotePath: ".codex-plugin" },
3445
+ { localPath: ".mcp.json", remotePath: ".mcp.json" },
3446
+ { localPath: "assets", remotePath: "assets" },
3447
+ { localPath: "skills", remotePath: "skills" }
3448
+ ]
3449
+ };
3450
+ function defaultPluginPaths() {
3451
+ return {
3452
+ claudeMarketplaceDir: CLAUDE_MANAGED_MARKETPLACE_DIR,
3453
+ claudeMarketplaceManifestPath: CLAUDE_MANAGED_MARKETPLACE_MANIFEST_PATH,
3454
+ claudePluginDir: CLAUDE_MANAGED_PLUGIN_DIR,
3455
+ codexMarketplacePath: CODEX_MARKETPLACE_PATH,
3456
+ codexPluginDir: CODEX_ORGX_PLUGIN_DIR
3457
+ };
3458
+ }
3459
+ function resolvePluginPaths(paths = {}) {
3460
+ return {
3461
+ ...defaultPluginPaths(),
3462
+ ...paths
3463
+ };
3464
+ }
3465
+ function isRepoDirectoryEntry(value) {
3466
+ if (!value || typeof value !== "object") return false;
3467
+ const entry = value;
3468
+ const hasType = entry.type === "file" || entry.type === "dir";
3469
+ return typeof entry.name === "string" && typeof entry.path === "string" && hasType;
3470
+ }
3471
+ function encodeRepoPath2(value) {
3472
+ return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3473
+ }
3474
+ function isLikelyRepoFilePath(path) {
3475
+ const basename = path.split("/").pop() ?? path;
3476
+ return basename.includes(".") && !/^\.[^./]+$/.test(basename);
3477
+ }
3478
+ function buildContentsUrl2(spec, path) {
3479
+ const encodedPath = encodeRepoPath2(path);
3480
+ return `https://api.github.com/repos/${spec.owner}/${spec.repo}/contents/${encodedPath}?ref=${encodeURIComponent(spec.ref)}`;
3481
+ }
3482
+ function resolveGitHubError2(spec, status, path) {
3483
+ const repoName = `${spec.owner}/${spec.repo}`;
3484
+ if (status === 404) {
3485
+ return `Could not find '${path}' in ${repoName}.`;
3486
+ }
3487
+ return `GitHub returned HTTP ${status} while loading '${path}' from ${repoName}.`;
3488
+ }
3489
+ async function fetchDirectoryEntries2(spec, path, fetchImpl) {
3490
+ const response = await fetchImpl(buildContentsUrl2(spec, path), {
3491
+ headers: {
3492
+ Accept: "application/vnd.github+json"
3493
+ },
3494
+ signal: AbortSignal.timeout(1e4)
3495
+ });
3496
+ if (!response.ok) {
3497
+ throw new Error(resolveGitHubError2(spec, response.status, path));
3498
+ }
3499
+ const data = await response.json();
3500
+ if (!Array.isArray(data)) {
3501
+ throw new Error(`Expected '${path}' to be a directory in ${spec.owner}/${spec.repo}.`);
3502
+ }
3503
+ return data.filter(isRepoDirectoryEntry).sort((left, right) => left.path.localeCompare(right.path));
3504
+ }
3505
+ async function fetchRepoFile(spec, path, fetchImpl) {
3506
+ const response = await fetchImpl(buildContentsUrl2(spec, path), {
3507
+ headers: {
3508
+ Accept: "application/vnd.github+json"
3509
+ },
3510
+ signal: AbortSignal.timeout(1e4)
3511
+ });
3512
+ if (!response.ok) {
3513
+ throw new Error(resolveGitHubError2(spec, response.status, path));
3514
+ }
3515
+ const data = await response.json();
3516
+ if (!isRepoDirectoryEntry(data) || data.type !== "file" || typeof data.download_url !== "string") {
3517
+ throw new Error(`Expected '${path}' to be a file in ${spec.owner}/${spec.repo}.`);
3518
+ }
3519
+ return data;
3520
+ }
3521
+ async function listRemoteRepoFiles(spec, path, localPath, fetchImpl) {
3522
+ const files = [];
3523
+ if (isLikelyRepoFilePath(path) && !path.endsWith("/")) {
3524
+ const file = await fetchRepoFile(spec, path, fetchImpl);
3525
+ files.push({
3526
+ localPath,
3527
+ path: file.path,
3528
+ sourceUrl: file.download_url ?? ""
3529
+ });
3530
+ return files;
3531
+ }
3532
+ const entries = await fetchDirectoryEntries2(spec, path, fetchImpl);
3533
+ for (const entry of entries) {
3534
+ if (entry.type === "dir") {
3535
+ files.push(
3536
+ ...await listRemoteRepoFiles(
3537
+ spec,
3538
+ entry.path,
3539
+ join3(localPath, entry.name),
3540
+ fetchImpl
3541
+ )
3542
+ );
3543
+ continue;
3544
+ }
3545
+ if (!entry.download_url) {
3546
+ throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
3547
+ }
3548
+ files.push({
3549
+ localPath: join3(localPath, entry.name),
3550
+ path: entry.path,
3551
+ sourceUrl: entry.download_url
3552
+ });
3553
+ }
3554
+ return files;
3555
+ }
3556
+ async function collectRemoteRepoFiles(spec, fetchImpl) {
3557
+ const files = [];
3558
+ for (const entry of spec.include) {
3559
+ const isFile = isLikelyRepoFilePath(entry.remotePath) && !entry.remotePath.endsWith("/");
3560
+ if (isFile) {
3561
+ const file = await fetchRepoFile(spec, entry.remotePath, fetchImpl);
3562
+ files.push({
3563
+ localPath: entry.localPath,
3564
+ path: file.path,
3565
+ sourceUrl: file.download_url ?? ""
3566
+ });
3567
+ continue;
3568
+ }
3569
+ files.push(...await listRemoteRepoFiles(spec, entry.remotePath, entry.localPath, fetchImpl));
3570
+ }
3571
+ return files.sort((left, right) => left.localPath.localeCompare(right.localPath));
3572
+ }
3573
+ async function fetchRemoteBytes(sourceUrl, fetchImpl) {
3574
+ const response = await fetchImpl(sourceUrl, {
3575
+ headers: {
3576
+ Accept: "application/octet-stream"
3577
+ },
3578
+ signal: AbortSignal.timeout(1e4)
3579
+ });
3580
+ if (!response.ok) {
3581
+ throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
3582
+ }
3583
+ return Buffer.from(await response.arrayBuffer());
3584
+ }
3585
+ function readBytesIfExists(path) {
3586
+ if (!existsSync3(path)) return null;
3587
+ try {
3588
+ if (!statSync2(path).isFile()) {
3589
+ return null;
3590
+ }
3591
+ return readFileSync2(path);
3592
+ } catch (error) {
3593
+ const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
3594
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
3595
+ return null;
3596
+ }
3597
+ throw error;
3598
+ }
3599
+ }
3600
+ function writeBytesIfChanged(path, bytes) {
3601
+ const existing = readBytesIfExists(path);
3602
+ if (existing && Buffer.compare(existing, bytes) === 0) {
3603
+ return false;
3604
+ }
3605
+ mkdirSync2(dirname3(path), { recursive: true });
3606
+ writeFileSync2(path, bytes);
3607
+ return true;
3608
+ }
3609
+ function removePathIfExists(path) {
3610
+ if (!existsSync3(path)) return false;
3611
+ rmSync(path, { force: true, recursive: true });
3612
+ return true;
3613
+ }
3614
+ function listRelativeFiles(root, base = root) {
3615
+ if (!existsSync3(root)) return [];
3616
+ if (!statSync2(root).isDirectory()) {
3617
+ return [];
3618
+ }
3619
+ const files = [];
3620
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
3621
+ const nextPath = join3(root, entry.name);
3622
+ if (entry.isDirectory()) {
3623
+ files.push(...listRelativeFiles(nextPath, base));
3624
+ continue;
3625
+ }
3626
+ if (entry.isFile()) {
3627
+ files.push(relative(base, nextPath));
3628
+ }
3629
+ }
3630
+ return files.sort();
3631
+ }
3632
+ function pruneEmptyDirectories(root, current = root) {
3633
+ if (!existsSync3(current) || !statSync2(current).isDirectory()) {
3634
+ return false;
3635
+ }
3636
+ let changed = false;
3637
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
3638
+ if (!entry.isDirectory()) continue;
3639
+ changed = pruneEmptyDirectories(root, join3(current, entry.name)) || changed;
3640
+ }
3641
+ if (current !== root && readdirSync(current).length === 0) {
3642
+ rmSync(current, { force: true, recursive: true });
3643
+ return true;
3644
+ }
3645
+ return changed;
3646
+ }
3647
+ async function syncManagedRepoTree(spec, destinationRoot, fetchImpl) {
3648
+ const remoteFiles = await collectRemoteRepoFiles(spec, fetchImpl);
3649
+ let changed = false;
3650
+ const expected = new Set(remoteFiles.map((file) => file.localPath));
3651
+ if (existsSync3(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
3652
+ rmSync(destinationRoot, { force: true, recursive: true });
3653
+ changed = true;
3654
+ }
3655
+ for (const file of listRelativeFiles(destinationRoot)) {
3656
+ if (expected.has(file)) continue;
3657
+ rmSync(join3(destinationRoot, file), { force: true });
3658
+ changed = true;
3659
+ }
3660
+ changed = pruneEmptyDirectories(destinationRoot) || changed;
3661
+ for (const file of remoteFiles) {
3662
+ const bytes = await fetchRemoteBytes(file.sourceUrl, fetchImpl);
3663
+ if (writeBytesIfChanged(join3(destinationRoot, file.localPath), bytes)) {
3664
+ changed = true;
3665
+ }
3666
+ }
3667
+ return { changed, fileCount: remoteFiles.length };
3668
+ }
3669
+ function serializeJson(value) {
3670
+ return `${JSON.stringify(value, null, 2)}
3671
+ `;
3672
+ }
3673
+ function writeJsonIfChanged(path, value) {
3674
+ const next = serializeJson(value);
3675
+ const existing = readTextIfExists(path);
3676
+ if (existing === next) {
3677
+ return false;
3678
+ }
3679
+ mkdirSync2(dirname3(path), { recursive: true });
3680
+ writeFileSync2(path, next, "utf8");
3681
+ return true;
3682
+ }
3683
+ function buildClaudeMarketplaceManifest() {
3684
+ return {
3685
+ $schema: "https://anthropic.com/claude-code/marketplace.schema.json",
3686
+ name: ORGX_CLAUDE_MARKETPLACE_NAME,
3687
+ description: "Local OrgX plugins",
3688
+ owner: {
3689
+ name: "OrgX Team"
3690
+ },
3691
+ plugins: [
3692
+ {
3693
+ name: ORGX_CLAUDE_PLUGIN_NAME,
3694
+ description: "OrgX MCP tools and runtime telemetry hooks for Claude Code.",
3695
+ source: `./plugins/${ORGX_CLAUDE_PLUGIN_NAME}`
3696
+ }
3697
+ ]
3698
+ };
3699
+ }
3700
+ function buildCodexMarketplaceEntry() {
3701
+ return {
3702
+ name: ORGX_CODEX_PLUGIN_NAME,
3703
+ source: {
3704
+ source: "local",
3705
+ path: `./.codex/plugins/${ORGX_CODEX_PLUGIN_NAME}`
3706
+ },
3707
+ policy: {
3708
+ installation: "AVAILABLE",
3709
+ authentication: "ON_INSTALL"
3710
+ },
3711
+ category: "Productivity"
3712
+ };
3713
+ }
3714
+ function readMarketplacePlugins(path) {
3715
+ const document = parseJsonObject(readTextIfExists(path));
3716
+ const plugins = Array.isArray(document.plugins) ? document.plugins.filter((value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)) : [];
3717
+ return { document, plugins };
3718
+ }
3719
+ function upsertCodexMarketplaceEntry(path) {
3720
+ const { document, plugins } = readMarketplacePlugins(path);
3721
+ const nextEntry = buildCodexMarketplaceEntry();
3722
+ let replaced = false;
3723
+ const nextPlugins = plugins.map((plugin) => {
3724
+ if (plugin.name !== ORGX_CODEX_PLUGIN_NAME) {
3725
+ return plugin;
3726
+ }
3727
+ replaced = true;
3728
+ return nextEntry;
3729
+ });
3730
+ if (!replaced) {
3731
+ nextPlugins.push(nextEntry);
3732
+ }
3733
+ const nextDocument = {
3734
+ ...document,
3735
+ ...document.name ? {} : { name: ORGX_CLAUDE_MARKETPLACE_NAME },
3736
+ ...readObject(document.interface).displayName ? {} : { interface: { displayName: "OrgX Local" } },
3737
+ plugins: nextPlugins
3738
+ };
3739
+ return writeJsonIfChanged(path, nextDocument);
3740
+ }
3741
+ function removeCodexMarketplaceEntry(path) {
3742
+ if (!existsSync3(path)) {
3743
+ return false;
3744
+ }
3745
+ const { document, plugins } = readMarketplacePlugins(path);
3746
+ const nextPlugins = plugins.filter((plugin) => plugin.name !== ORGX_CODEX_PLUGIN_NAME);
3747
+ if (nextPlugins.length === plugins.length) {
3748
+ return false;
3749
+ }
3750
+ if (nextPlugins.length === 0) {
3751
+ rmSync(path, { force: true });
3752
+ return true;
3753
+ }
3754
+ return writeJsonIfChanged(path, {
3755
+ ...document,
3756
+ plugins: nextPlugins
3757
+ });
3758
+ }
3759
+ function codexMarketplaceHasOrgxEntry(path) {
3760
+ const { plugins } = readMarketplacePlugins(path);
3761
+ return plugins.some((plugin) => plugin.name === ORGX_CODEX_PLUGIN_NAME);
3762
+ }
3763
+ function extractClaudePluginNames(payload) {
3764
+ try {
3765
+ const parsed = JSON.parse(payload);
3766
+ if (!Array.isArray(parsed)) return [];
3767
+ return parsed.flatMap((entry) => {
3768
+ if (typeof entry === "string") return [entry];
3769
+ if (!entry || typeof entry !== "object") return [];
3770
+ if (typeof entry.name === "string") return [entry.name];
3771
+ if (typeof entry.id === "string") return [entry.id];
3772
+ return [];
3773
+ });
3774
+ } catch {
3775
+ return [];
3776
+ }
3777
+ }
3778
+ function extractOpenclawPluginIds(payload) {
3779
+ try {
3780
+ const parsed = JSON.parse(payload);
3781
+ if (Array.isArray(parsed)) {
3782
+ return parsed.flatMap((entry) => {
3783
+ if (!entry || typeof entry !== "object") return [];
3784
+ if (typeof entry.id === "string") return [entry.id];
3785
+ if (typeof entry.name === "string") return [entry.name];
3786
+ return [];
3787
+ });
3788
+ }
3789
+ const root = readObject(parsed);
3790
+ const plugins = Array.isArray(root.plugins) ? root.plugins : [];
3791
+ return plugins.flatMap((entry) => {
3792
+ if (!entry || typeof entry !== "object") return [];
3793
+ const item = entry;
3794
+ if (typeof item.id === "string") return [item.id];
3795
+ if (typeof item.name === "string") return [item.name];
3796
+ return [];
3797
+ });
3798
+ } catch {
3799
+ return [];
3800
+ }
3801
+ }
3802
+ function formatCommandFailure(command, args, result) {
3803
+ const detail = [result.stderr.trim(), result.stdout.trim()].find((value) => value.length > 0);
3804
+ if (result.errorCode === "ENOENT") {
3805
+ return `Command '${command}' is not available on PATH.`;
3806
+ }
3807
+ return `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
3808
+ }
3809
+ async function defaultCommandRunner(command, args) {
3810
+ return await new Promise((resolve) => {
3811
+ const child = spawn(command, [...args], {
3812
+ env: process.env,
3813
+ stdio: ["ignore", "pipe", "pipe"]
3814
+ });
3815
+ let stdout = "";
3816
+ let stderr = "";
3817
+ child.stdout.on("data", (chunk) => {
3818
+ stdout += chunk.toString();
3819
+ });
3820
+ child.stderr.on("data", (chunk) => {
3821
+ stderr += chunk.toString();
3822
+ });
3823
+ child.on("error", (error) => {
3824
+ const errorCode = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
3825
+ resolve({
3826
+ exitCode: -1,
3827
+ stdout,
3828
+ stderr,
3829
+ ...errorCode ? { errorCode } : {}
3830
+ });
3831
+ });
3832
+ child.on("close", (code) => {
3833
+ resolve({
3834
+ exitCode: code ?? -1,
3835
+ stdout,
3836
+ stderr
3837
+ });
3838
+ });
3839
+ });
3840
+ }
3841
+ function resolveCommandRunner(runner) {
3842
+ return runner ?? defaultCommandRunner;
3843
+ }
3844
+ async function commandExists(command, runner) {
3845
+ const result = await runner(command, ["--help"]);
3846
+ return result.errorCode !== "ENOENT" && result.exitCode !== 127;
3847
+ }
3848
+ async function getClaudeInstallState(runner) {
3849
+ const available = detectSurface("claude").detected || await commandExists("claude", runner);
3850
+ if (!available) {
3851
+ return { available: false, installed: false };
3852
+ }
3853
+ const result = await runner("claude", ["plugin", "list", "--json"]);
3854
+ if (result.exitCode !== 0) {
3855
+ return { available: true, installed: false };
3856
+ }
3857
+ return {
3858
+ available: true,
3859
+ installed: extractClaudePluginNames(result.stdout).includes(ORGX_CLAUDE_PLUGIN_NAME)
3860
+ };
3861
+ }
3862
+ async function getOpenclawInstallState(runner) {
3863
+ const available = detectSurface("openclaw").detected || await commandExists("openclaw", runner);
3864
+ if (!available) {
3865
+ return { available: false, installed: false };
3866
+ }
3867
+ const result = await runner("openclaw", ["plugins", "list", "--json"]);
3868
+ if (result.exitCode !== 0) {
3869
+ return { available: true, installed: false };
3870
+ }
3871
+ return {
3872
+ available: true,
3873
+ installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
3874
+ };
3875
+ }
3876
+ async function buildClaudeStatus(paths, runner) {
3877
+ const state = await getClaudeInstallState(runner);
3878
+ if (state.installed) {
3879
+ return {
3880
+ target: "claude",
3881
+ available: true,
3882
+ installed: true,
3883
+ message: "Claude Code plugin is installed."
3884
+ };
3885
+ }
3886
+ if (!state.available) {
3887
+ return {
3888
+ target: "claude",
3889
+ available: false,
3890
+ installed: false,
3891
+ message: "Claude Code was not detected."
3892
+ };
3893
+ }
3894
+ const marketplaceExists = existsSync3(paths.claudeMarketplaceManifestPath);
3895
+ return {
3896
+ target: "claude",
3897
+ available: true,
3898
+ installed: false,
3899
+ message: marketplaceExists ? "Claude marketplace wrapper exists, but the plugin is not installed." : "Claude Code is available and ready for plugin install."
3900
+ };
3901
+ }
3902
+ function buildCodexStatus(paths, runner) {
3903
+ return (async () => {
3904
+ const available = detectSurface("codex").detected || await commandExists("codex", runner);
3905
+ const pluginExists = existsSync3(paths.codexPluginDir);
3906
+ const marketplaceExists = codexMarketplaceHasOrgxEntry(paths.codexMarketplacePath);
3907
+ const installed = pluginExists && marketplaceExists;
3908
+ if (installed) {
3909
+ return {
3910
+ target: "codex",
3911
+ available: true,
3912
+ installed: true,
3913
+ message: "Codex plugin files and marketplace entry are present."
3914
+ };
3915
+ }
3916
+ if (!available) {
3917
+ return {
3918
+ target: "codex",
3919
+ available: false,
3920
+ installed: false,
3921
+ message: "Codex was not detected."
3922
+ };
3923
+ }
3924
+ if (pluginExists && !marketplaceExists) {
3925
+ return {
3926
+ target: "codex",
3927
+ available: true,
3928
+ installed: false,
3929
+ message: "Codex plugin files exist, but the marketplace entry is missing."
3930
+ };
3931
+ }
3932
+ if (!pluginExists && marketplaceExists) {
3933
+ return {
3934
+ target: "codex",
3935
+ available: true,
3936
+ installed: false,
3937
+ message: "Codex marketplace entry exists, but plugin files are missing."
3938
+ };
3939
+ }
3940
+ return {
3941
+ target: "codex",
3942
+ available: true,
3943
+ installed: false,
3944
+ message: "Codex is available and ready for plugin install."
3945
+ };
3946
+ })();
3947
+ }
3948
+ async function buildOpenclawStatus(runner) {
3949
+ const state = await getOpenclawInstallState(runner);
3950
+ if (state.installed) {
3951
+ return {
3952
+ target: "openclaw",
3953
+ available: true,
3954
+ installed: true,
3955
+ message: "OpenClaw plugin is installed."
3956
+ };
3957
+ }
3958
+ return {
3959
+ target: "openclaw",
3960
+ available: state.available,
3961
+ installed: false,
3962
+ message: state.available ? "OpenClaw is available and ready for plugin install." : "OpenClaw was not detected."
3963
+ };
3964
+ }
3965
+ async function resolveOpenclawTarball(fetchImpl) {
3966
+ const response = await fetchImpl(
3967
+ `${NPM_REGISTRY_BASE_URL}/${encodeURIComponent(ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME)}`,
3968
+ {
3969
+ headers: {
3970
+ Accept: "application/json"
3971
+ },
3972
+ signal: AbortSignal.timeout(1e4)
3973
+ }
3974
+ );
3975
+ if (!response.ok) {
3976
+ throw new Error(
3977
+ `npm registry returned HTTP ${response.status} while resolving ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`
3978
+ );
3979
+ }
3980
+ const data = await response.json();
3981
+ const latestVersion = data["dist-tags"]?.latest;
3982
+ const tarballUrl = latestVersion ? data.versions?.[latestVersion]?.dist?.tarball : void 0;
3983
+ if (!latestVersion || !tarballUrl) {
3984
+ throw new Error(`Could not resolve a tarball URL for ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`);
3985
+ }
3986
+ return {
3987
+ tarballUrl,
3988
+ version: latestVersion
3989
+ };
3990
+ }
3991
+ async function installClaudePlugin(paths, fetchImpl, runner) {
3992
+ const state = await getClaudeInstallState(runner);
3993
+ if (!state.available) {
3994
+ return {
3995
+ target: "claude",
3996
+ changed: false,
3997
+ message: "Claude Code is not available on this machine."
3998
+ };
3999
+ }
4000
+ const syncResult = await syncManagedRepoTree(CLAUDE_PLUGIN_SYNC_SPEC, paths.claudePluginDir, fetchImpl);
4001
+ const manifestChanged = writeJsonIfChanged(
4002
+ paths.claudeMarketplaceManifestPath,
4003
+ buildClaudeMarketplaceManifest()
4004
+ );
4005
+ const marketplaceAdd = await runner("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir]);
4006
+ if (marketplaceAdd.exitCode !== 0) {
4007
+ throw new Error(formatCommandFailure("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir], marketplaceAdd));
4008
+ }
4009
+ let installedChanged = false;
4010
+ if (!state.installed) {
4011
+ const install = await runner("claude", [
4012
+ "plugin",
4013
+ "install",
4014
+ `${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
4015
+ "--scope",
4016
+ "user"
4017
+ ]);
4018
+ if (install.exitCode !== 0) {
4019
+ throw new Error(
4020
+ formatCommandFailure(
4021
+ "claude",
4022
+ [
4023
+ "plugin",
4024
+ "install",
4025
+ `${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
4026
+ "--scope",
4027
+ "user"
4028
+ ],
4029
+ install
4030
+ )
4031
+ );
4032
+ }
4033
+ installedChanged = true;
4034
+ }
4035
+ const changed = syncResult.changed || manifestChanged || installedChanged;
4036
+ return {
4037
+ target: "claude",
4038
+ changed,
4039
+ message: changed ? `Synced ${syncResult.fileCount} Claude plugin files and ensured the plugin is installed.` : "Claude Code plugin is already installed and up to date."
4040
+ };
4041
+ }
4042
+ async function installCodexPlugin(paths, fetchImpl, runner) {
4043
+ const available = detectSurface("codex").detected || await commandExists("codex", runner);
4044
+ if (!available) {
4045
+ return {
4046
+ target: "codex",
4047
+ changed: false,
4048
+ message: "Codex is not available on this machine."
4049
+ };
4050
+ }
4051
+ const syncResult = await syncManagedRepoTree(CODEX_PLUGIN_SYNC_SPEC, paths.codexPluginDir, fetchImpl);
4052
+ const marketplaceChanged = upsertCodexMarketplaceEntry(paths.codexMarketplacePath);
4053
+ const changed = syncResult.changed || marketplaceChanged;
4054
+ return {
4055
+ target: "codex",
4056
+ changed,
4057
+ 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."
4058
+ };
4059
+ }
4060
+ async function installOpenclawPlugin(fetchImpl, runner) {
4061
+ const state = await getOpenclawInstallState(runner);
4062
+ if (!state.available) {
4063
+ return {
4064
+ target: "openclaw",
4065
+ changed: false,
4066
+ message: "OpenClaw is not available on this machine."
4067
+ };
4068
+ }
4069
+ if (state.installed) {
4070
+ return {
4071
+ target: "openclaw",
4072
+ changed: false,
4073
+ message: "OpenClaw plugin is already installed."
4074
+ };
4075
+ }
4076
+ const { tarballUrl, version } = await resolveOpenclawTarball(fetchImpl);
4077
+ const tarballBytes = await fetchRemoteBytes(tarballUrl, fetchImpl);
4078
+ const tempRoot = mkdtempSync(join3(tmpdir(), "orgx-wizard-openclaw-"));
4079
+ const archivePath = join3(tempRoot, `orgx-openclaw-plugin-${version}.tgz`);
4080
+ try {
4081
+ writeFileSync2(archivePath, tarballBytes);
4082
+ const install = await runner("openclaw", ["plugins", "install", archivePath]);
4083
+ if (install.exitCode !== 0) {
4084
+ throw new Error(
4085
+ formatCommandFailure("openclaw", ["plugins", "install", archivePath], install)
4086
+ );
4087
+ }
4088
+ } finally {
4089
+ rmSync(tempRoot, { force: true, recursive: true });
4090
+ }
4091
+ return {
4092
+ target: "openclaw",
4093
+ changed: true,
4094
+ message: `Installed OpenClaw plugin ${ORGX_OPENCLAW_PLUGIN_ID} from ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}@${version}.`
4095
+ };
4096
+ }
4097
+ async function uninstallClaudePlugin(paths, runner) {
4098
+ const state = await getClaudeInstallState(runner);
4099
+ let changed = false;
4100
+ if (state.available && state.installed) {
4101
+ const uninstall = await runner("claude", [
4102
+ "plugin",
4103
+ "uninstall",
4104
+ ORGX_CLAUDE_PLUGIN_NAME,
4105
+ "--scope",
4106
+ "user"
4107
+ ]);
4108
+ if (uninstall.exitCode !== 0) {
4109
+ throw new Error(
4110
+ formatCommandFailure(
4111
+ "claude",
4112
+ ["plugin", "uninstall", ORGX_CLAUDE_PLUGIN_NAME, "--scope", "user"],
4113
+ uninstall
4114
+ )
4115
+ );
4116
+ }
4117
+ changed = true;
4118
+ }
4119
+ if (state.available) {
4120
+ const removeMarketplace = await runner("claude", ["plugin", "marketplace", "remove", ORGX_CLAUDE_MARKETPLACE_NAME]);
4121
+ if (removeMarketplace.exitCode === 0) {
4122
+ changed = true;
4123
+ }
4124
+ }
4125
+ changed = removePathIfExists(paths.claudeMarketplaceDir) || changed;
4126
+ return {
4127
+ target: "claude",
4128
+ changed,
4129
+ message: changed ? "Removed the managed Claude Code plugin and marketplace wrapper." : "Claude Code plugin was not installed."
4130
+ };
4131
+ }
4132
+ async function uninstallCodexPlugin(paths) {
4133
+ const removedPluginDir = removePathIfExists(paths.codexPluginDir);
4134
+ const removedMarketplaceEntry = removeCodexMarketplaceEntry(paths.codexMarketplacePath);
4135
+ const changed = removedPluginDir || removedMarketplaceEntry;
4136
+ return {
4137
+ target: "codex",
4138
+ changed,
4139
+ message: changed ? "Removed the managed Codex plugin files and marketplace entry." : "Codex plugin was not installed."
4140
+ };
4141
+ }
4142
+ async function uninstallOpenclawPlugin(runner) {
4143
+ const state = await getOpenclawInstallState(runner);
4144
+ if (!state.available || !state.installed) {
4145
+ return {
4146
+ target: "openclaw",
4147
+ changed: false,
4148
+ message: "OpenClaw plugin was not installed."
4149
+ };
4150
+ }
4151
+ const uninstall = await runner("openclaw", ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"]);
4152
+ if (uninstall.exitCode !== 0) {
4153
+ throw new Error(
4154
+ formatCommandFailure(
4155
+ "openclaw",
4156
+ ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"],
4157
+ uninstall
4158
+ )
4159
+ );
4160
+ }
4161
+ return {
4162
+ target: "openclaw",
4163
+ changed: true,
4164
+ message: "Removed the managed OpenClaw plugin."
2749
4165
  };
2750
4166
  }
2751
- function resolveSkillPackNames(requested = []) {
4167
+ function resolvePluginTargets(requested = []) {
2752
4168
  if (requested.length === 0 || requested.includes("all")) {
2753
- return [...DEFAULT_ORGX_SKILL_PACKS];
4169
+ return [...DEFAULT_ORGX_PLUGIN_TARGETS];
2754
4170
  }
2755
4171
  const seen = /* @__PURE__ */ new Set();
2756
4172
  const normalized = [];
2757
- for (const name of requested) {
2758
- const next = name.trim();
2759
- if (!next || seen.has(next)) {
2760
- continue;
4173
+ for (const rawTarget of requested) {
4174
+ const target = rawTarget.trim().toLowerCase();
4175
+ if (target === "" || target === "all") continue;
4176
+ if (!DEFAULT_ORGX_PLUGIN_TARGETS.includes(target)) {
4177
+ throw new Error(
4178
+ `Unknown plugin target '${rawTarget}'. Supported targets: ${DEFAULT_ORGX_PLUGIN_TARGETS.join(", ")}.`
4179
+ );
2761
4180
  }
2762
- seen.add(next);
2763
- normalized.push(next);
4181
+ const nextTarget = target;
4182
+ if (seen.has(nextTarget)) continue;
4183
+ seen.add(nextTarget);
4184
+ normalized.push(nextTarget);
2764
4185
  }
2765
4186
  return normalized;
2766
4187
  }
2767
- async function fetchAvailablePackNames(fetchImpl, ref) {
2768
- const entries = await fetchDirectoryEntries("", fetchImpl, ref);
2769
- return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
2770
- }
2771
- async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref) {
2772
- const rootPath = skillName;
2773
- const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
2774
- const writes = [];
2775
- for (const file of files) {
2776
- const content = await fetchRemoteText(file.sourceUrl, fetchImpl);
2777
- const relativePath = file.path.slice(`${rootPath}/`.length);
2778
- writes.push(
2779
- writeManagedFile(
2780
- join2(claudeSkillsDir, skillName, relativePath),
2781
- content,
2782
- `${skillName}/${relativePath}`,
2783
- file.sourceUrl
2784
- )
2785
- );
2786
- }
2787
- return {
2788
- name: skillName,
2789
- files: writes
2790
- };
2791
- }
2792
- async function installOrgxSkills(options = {}) {
4188
+ async function listOrgxPluginStatuses(options = {}) {
4189
+ const paths = resolvePluginPaths(options.paths);
4190
+ const runner = resolveCommandRunner(options.commandRunner);
4191
+ return await Promise.all([
4192
+ buildClaudeStatus(paths, runner),
4193
+ buildCodexStatus(paths, runner),
4194
+ buildOpenclawStatus(runner)
4195
+ ]);
4196
+ }
4197
+ async function installOrgxPlugins(options = {}) {
4198
+ const targets = resolvePluginTargets(options.targets ?? []);
2793
4199
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
2794
4200
  if (!fetchImpl) {
2795
4201
  throw new Error("Global fetch is unavailable in this runtime.");
2796
4202
  }
2797
- const ref = options.ref ?? ORGX_SKILLS_REF;
2798
- const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
2799
- const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
2800
- const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
2801
- const requestedNames = options.skillNames ?? [];
2802
- let skillNames;
2803
- if (requestedNames.length === 0 || requestedNames.includes("all")) {
2804
- try {
2805
- skillNames = await fetchAvailablePackNames(fetchImpl, ref);
2806
- } catch {
2807
- skillNames = [...DEFAULT_ORGX_SKILL_PACKS];
4203
+ const paths = resolvePluginPaths(options.paths);
4204
+ const runner = resolveCommandRunner(options.commandRunner);
4205
+ const results = [];
4206
+ for (const target of targets) {
4207
+ switch (target) {
4208
+ case "claude":
4209
+ results.push(await installClaudePlugin(paths, fetchImpl, runner));
4210
+ break;
4211
+ case "codex":
4212
+ results.push(await installCodexPlugin(paths, fetchImpl, runner));
4213
+ break;
4214
+ case "openclaw":
4215
+ results.push(await installOpenclawPlugin(fetchImpl, runner));
4216
+ break;
2808
4217
  }
2809
- } else {
2810
- skillNames = resolveSkillPackNames(requestedNames);
2811
4218
  }
2812
- const writes = [
2813
- writeManagedFile(cursorRulePath, CURSOR_RULES_CONTENT, "cursor-rules"),
2814
- writeManagedFile(claudeOrgxSkillPath, CLAUDE_ORGX_SKILL_CONTENT, "claude-orgx-skill")
2815
- ];
2816
- const packs = [];
2817
- for (const skillName of skillNames) {
2818
- packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref));
4219
+ return { results };
4220
+ }
4221
+ async function uninstallOrgxPlugins(options = {}) {
4222
+ const targets = resolvePluginTargets(options.targets ?? []);
4223
+ const paths = resolvePluginPaths(options.paths);
4224
+ const runner = resolveCommandRunner(options.commandRunner);
4225
+ const results = [];
4226
+ for (const target of targets) {
4227
+ switch (target) {
4228
+ case "claude":
4229
+ results.push(await uninstallClaudePlugin(paths, runner));
4230
+ break;
4231
+ case "codex":
4232
+ results.push(await uninstallCodexPlugin(paths));
4233
+ break;
4234
+ case "openclaw":
4235
+ results.push(await uninstallOpenclawPlugin(runner));
4236
+ break;
4237
+ }
2819
4238
  }
2820
- return {
2821
- writes,
2822
- packs
2823
- };
4239
+ return { results };
4240
+ }
4241
+ function countPluginReportChanges(report) {
4242
+ return report.results.filter((result) => result.changed).length;
2824
4243
  }
2825
4244
 
2826
4245
  // src/lib/setup-workspace.ts
@@ -3010,7 +4429,9 @@ async function runWorkspaceSetup(client, prompts, options) {
3010
4429
  // src/lib/founder-preset.ts
3011
4430
  async function runFounderPreset(prompts, options) {
3012
4431
  const surfaceResults = await setupDetectedSurfaces();
4432
+ const pluginTargets = options.pluginTargets ?? (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
3013
4433
  const skillReport = await installOrgxSkills({
4434
+ pluginTargets,
3014
4435
  skillNames: [...DEFAULT_ORGX_SKILL_PACKS]
3015
4436
  });
3016
4437
  let workspaceSetup;
@@ -3058,6 +4479,179 @@ function summarizeMutationResults(results) {
3058
4479
  }));
3059
4480
  }
3060
4481
 
4482
+ // src/lib/setup-verification.ts
4483
+ var HOSTED_MCP_OUTAGE_TITLE = "Hosted OrgX MCP is unreachable.";
4484
+ function getSetupVerificationHeadline(summary) {
4485
+ switch (summary.status) {
4486
+ case "degraded":
4487
+ return "Hosted OrgX MCP is down; local setup can still continue.";
4488
+ case "error":
4489
+ return "Issues detected";
4490
+ case "warning":
4491
+ return "Warnings detected";
4492
+ case "ok":
4493
+ return "All systems ready.";
4494
+ }
4495
+ }
4496
+ function summarizeSetupVerification(assessment) {
4497
+ const errors = assessment.issues.filter((issue) => issue.level === "error");
4498
+ if (errors.length === 0) {
4499
+ return {
4500
+ hostedMcpDegraded: false,
4501
+ status: assessment.issues.length > 0 ? "warning" : "ok"
4502
+ };
4503
+ }
4504
+ const hostedMcpDegraded = errors.every((issue) => issue.title === HOSTED_MCP_OUTAGE_TITLE);
4505
+ return {
4506
+ hostedMcpDegraded,
4507
+ status: hostedMcpDegraded ? "degraded" : "error"
4508
+ };
4509
+ }
4510
+
4511
+ // src/lib/telemetry-events.ts
4512
+ function withBaseProperties(properties, base) {
4513
+ return {
4514
+ ...base,
4515
+ ...properties
4516
+ };
4517
+ }
4518
+ function buildWorkspaceSetupTelemetryProperties(result, base = {}) {
4519
+ return withBaseProperties(
4520
+ {
4521
+ created: result.created === true,
4522
+ default_changed: result.defaultChanged === true,
4523
+ has_workspace: Boolean(result.workspace),
4524
+ status: result.status
4525
+ },
4526
+ base
4527
+ );
4528
+ }
4529
+ function buildOnboardingTaskTelemetryProperties(input, base = {}) {
4530
+ return withBaseProperties(
4531
+ {
4532
+ created: input.created,
4533
+ has_initiative: input.hasInitiative,
4534
+ task_status: input.task.status ?? "unknown"
4535
+ },
4536
+ base
4537
+ );
4538
+ }
4539
+ function buildAgentRosterTelemetryProperties(input, base = {}) {
4540
+ return withBaseProperties(
4541
+ {
4542
+ agent_count: input.roster.agents.length,
4543
+ domain_count: new Set(input.roster.agents.map((agent) => agent.domain)).size,
4544
+ strategy: input.strategy
4545
+ },
4546
+ base
4547
+ );
4548
+ }
4549
+ function buildFounderDemoTelemetryProperties(result, base = {}) {
4550
+ return withBaseProperties(
4551
+ {
4552
+ created: result.created,
4553
+ decision_status: result.decision.status ?? "unknown",
4554
+ has_artifact_url: Boolean(result.artifact.url)
4555
+ },
4556
+ base
4557
+ );
4558
+ }
4559
+ function buildDoctorTelemetryProperties(report, assessment, verification, base = {}) {
4560
+ const errorCount = assessment.issues.filter((issue) => issue.level === "error").length;
4561
+ const warningCount = assessment.issues.filter((issue) => issue.level === "warning").length;
4562
+ return withBaseProperties(
4563
+ {
4564
+ auth_configured: report.auth.configured,
4565
+ auth_ok: report.auth.ok,
4566
+ configured_surface_count: report.surfaces.filter((surface) => surface.configured).length,
4567
+ detected_surface_count: report.surfaces.filter((surface) => surface.detected).length,
4568
+ error_count: errorCount,
4569
+ hosted_mcp_degraded: verification.hostedMcpDegraded,
4570
+ hosted_mcp_ok: report.hostedMcp.ok,
4571
+ hosted_mcp_tool_ok: report.hostedMcpTool.ok,
4572
+ issue_count: assessment.issues.length,
4573
+ openclaw_available: !report.openclaw.skipped,
4574
+ openclaw_ok: report.openclaw.ok,
4575
+ status: verification.status,
4576
+ warning_count: warningCount,
4577
+ workspace_configured: report.workspace.configured,
4578
+ workspace_ok: report.workspace.ok
4579
+ },
4580
+ base
4581
+ );
4582
+ }
4583
+
4584
+ // src/lib/telemetry.ts
4585
+ var POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com";
4586
+ var WIZARD_LIB = "@useorgx/wizard";
4587
+ function isTruthyEnv(value) {
4588
+ if (!value) return false;
4589
+ switch (value.trim().toLowerCase()) {
4590
+ case "1":
4591
+ case "true":
4592
+ case "yes":
4593
+ case "y":
4594
+ case "on":
4595
+ return true;
4596
+ default:
4597
+ return false;
4598
+ }
4599
+ }
4600
+ function isWizardTelemetryDisabled() {
4601
+ const explicitEnable = isTruthyEnv(process.env.ORGX_TELEMETRY_ENABLED);
4602
+ if (!explicitEnable) return true;
4603
+ return isTruthyEnv(process.env.ORGX_TELEMETRY_DISABLED) || isTruthyEnv(process.env.OPENCLAW_TELEMETRY_DISABLED) || isTruthyEnv(process.env.POSTHOG_DISABLED);
4604
+ }
4605
+ function resolvePosthogApiKey() {
4606
+ const value = process.env.ORGX_POSTHOG_API_KEY ?? process.env.POSTHOG_API_KEY ?? process.env.ORGX_POSTHOG_KEY ?? process.env.POSTHOG_KEY ?? "";
4607
+ const trimmed = value.trim();
4608
+ return trimmed || null;
4609
+ }
4610
+ function resolvePosthogHost() {
4611
+ const value = process.env.ORGX_POSTHOG_HOST ?? process.env.POSTHOG_HOST ?? process.env.ORGX_POSTHOG_API_HOST ?? process.env.POSTHOG_API_HOST ?? "";
4612
+ const trimmed = value.trim();
4613
+ return trimmed || POSTHOG_DEFAULT_HOST;
4614
+ }
4615
+ function toPosthogBatchUrl(host) {
4616
+ try {
4617
+ return new URL("/batch/", host).toString();
4618
+ } catch {
4619
+ return `${POSTHOG_DEFAULT_HOST}/batch/`;
4620
+ }
4621
+ }
4622
+ async function trackWizardTelemetry(event, properties = {}, options = {}) {
4623
+ if (isWizardTelemetryDisabled()) return false;
4624
+ const apiKey = resolvePosthogApiKey();
4625
+ if (!apiKey) return false;
4626
+ const installationId = getOrCreateWizardInstallationId(options.statePath);
4627
+ const timestamp = options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
4628
+ const response = await fetch(toPosthogBatchUrl(resolvePosthogHost()), {
4629
+ method: "POST",
4630
+ headers: {
4631
+ "Content-Type": "application/json"
4632
+ },
4633
+ body: JSON.stringify({
4634
+ api_key: apiKey,
4635
+ batch: [
4636
+ {
4637
+ type: "capture",
4638
+ event,
4639
+ distinct_id: installationId,
4640
+ properties: {
4641
+ $lib: WIZARD_LIB,
4642
+ source: WIZARD_LIB,
4643
+ wizard_installation_id: installationId,
4644
+ ...properties
4645
+ },
4646
+ timestamp
4647
+ }
4648
+ ],
4649
+ sent_at: timestamp
4650
+ })
4651
+ }).catch(() => null);
4652
+ return response?.ok === true;
4653
+ }
4654
+
3061
4655
  // src/spinner.ts
3062
4656
  import ora from "ora";
3063
4657
  import pc2 from "picocolors";
@@ -3095,12 +4689,34 @@ function formatAuthSource(source) {
3095
4689
  return "not configured";
3096
4690
  }
3097
4691
  }
4692
+ function formatPluginTargetLabel(target) {
4693
+ switch (target) {
4694
+ case "claude":
4695
+ return "Claude Code";
4696
+ case "codex":
4697
+ return "Codex";
4698
+ case "openclaw":
4699
+ return "OpenClaw";
4700
+ }
4701
+ }
3098
4702
  function printSurfaceTable(statuses) {
3099
4703
  for (const status of statuses) {
3100
4704
  const icon = status.configured ? ICON.ok : status.detected ? ICON.warn : ICON.skip;
3101
4705
  const state = status.configured ? pc3.green("configured") : status.detected ? pc3.yellow("detected") : pc3.dim("not found");
3102
4706
  const mode = pc3.dim(status.mode === "automated" ? "auto " : "manual");
3103
4707
  console.log(` ${icon} ${pc3.bold(status.name.padEnd(10))} ${mode} ${state}`);
4708
+ if (status.mode === "manual") {
4709
+ console.log(` ${pc3.dim(status.summary)}`);
4710
+ }
4711
+ }
4712
+ }
4713
+ function printPluginStatusReport(statuses) {
4714
+ for (const status of statuses) {
4715
+ const icon = status.installed ? ICON.ok : status.available ? ICON.warn : ICON.skip;
4716
+ const state = status.installed ? pc3.green("installed") : status.available ? pc3.yellow("available") : pc3.dim("not found");
4717
+ console.log(
4718
+ ` ${icon} ${pc3.bold(formatPluginTargetLabel(status.target).padEnd(12))} ${state} ${pc3.dim(status.message)}`
4719
+ );
3104
4720
  }
3105
4721
  }
3106
4722
  function printMutationResults(results) {
@@ -3110,6 +4726,15 @@ function printMutationResults(results) {
3110
4726
  console.log(` ${icon} ${pc3.bold(result.name.padEnd(10))} ${state} ${pc3.dim(result.message)}`);
3111
4727
  }
3112
4728
  }
4729
+ function printPluginMutationReport(report) {
4730
+ for (const result of report.results) {
4731
+ const icon = result.changed ? ICON.ok : ICON.skip;
4732
+ const state = result.changed ? pc3.green("updated ") : pc3.dim("unchanged");
4733
+ console.log(
4734
+ ` ${icon} ${pc3.bold(formatPluginTargetLabel(result.target).padEnd(12))} ${state} ${pc3.dim(result.message)}`
4735
+ );
4736
+ }
4737
+ }
3113
4738
  function printSkillInstallReport(report) {
3114
4739
  for (const write of report.writes) {
3115
4740
  const icon = write.changed ? ICON.ok : ICON.skip;
@@ -3122,6 +4747,23 @@ function printSkillInstallReport(report) {
3122
4747
  const state = changedCount > 0 ? pc3.green(`${changedCount} updated `) : pc3.dim("unchanged");
3123
4748
  console.log(` ${icon} ${pc3.bold(pack.name.padEnd(10))} ${state} ${pc3.dim(`${pack.files.length} files`)}`);
3124
4749
  }
4750
+ for (const note of report.notes) {
4751
+ console.log(` ${ICON.skip} ${pc3.dim(note)}`);
4752
+ }
4753
+ }
4754
+ function countSkillReportChanges(report) {
4755
+ const writeChanges = report.writes.filter((write) => write.changed).length;
4756
+ const packChanges = report.packs.reduce(
4757
+ (count, pack) => count + pack.files.filter((file) => file.changed).length,
4758
+ 0
4759
+ );
4760
+ return writeChanges + packChanges;
4761
+ }
4762
+ async function safeTrackWizardTelemetry(event, properties = {}) {
4763
+ try {
4764
+ await trackWizardTelemetry(event, properties);
4765
+ } catch {
4766
+ }
3125
4767
  }
3126
4768
  function printWorkspace(workspace) {
3127
4769
  console.log(
@@ -3170,6 +4812,12 @@ function printFounderPresetResult(result) {
3170
4812
  ` ${result.demoInitiative.created ? pc3.green("created") : pc3.yellow("unchanged")} ${result.demoInitiative.initiative.title}`
3171
4813
  );
3172
4814
  console.log(` live: ${result.demoInitiative.liveUrl}`);
4815
+ console.log(
4816
+ ` decision: ${result.demoInitiative.decision.title} ${pc3.dim(`(${result.demoInitiative.decision.status ?? "pending"})`)}`
4817
+ );
4818
+ console.log(
4819
+ ` artifact: ${result.demoInitiative.artifact.name}${result.demoInitiative.artifact.url ? ` ${pc3.dim(result.demoInitiative.artifact.url)}` : ""}`
4820
+ );
3173
4821
  }
3174
4822
  }
3175
4823
  function normalizePromptResult(value) {
@@ -3187,6 +4835,64 @@ async function textPrompt(input) {
3187
4835
  };
3188
4836
  return normalizePromptResult(await clack.text(promptInput));
3189
4837
  }
4838
+ async function multiselectPrompt(input) {
4839
+ const promptInput = {
4840
+ message: input.message,
4841
+ options: input.options,
4842
+ ...input.required !== void 0 ? { required: input.required } : {}
4843
+ };
4844
+ return normalizePromptResult(await clack.multiselect(promptInput));
4845
+ }
4846
+ function printAgentRoster(roster) {
4847
+ console.log(` ${ICON.ok} ${pc3.green("agent roster")} ${pc3.bold(`${roster.agents.length} agents`)}`);
4848
+ for (const agent of roster.agents) {
4849
+ console.log(
4850
+ ` ${pc3.dim(agent.domain.padEnd(12))} ${pc3.bold(agent.name)} ${pc3.dim(agent.agentType)}`
4851
+ );
4852
+ }
4853
+ }
4854
+ async function promptForGuidedAgentRoster(workspace) {
4855
+ const remainingAgents = listAgentRosterProfiles();
4856
+ const assignments = [];
4857
+ for (const domain of AGENT_ROSTER_DOMAINS) {
4858
+ if (remainingAgents.length === 0) {
4859
+ break;
4860
+ }
4861
+ const defaultAgent = remainingAgents.find((agent) => agent.defaultDomain === domain);
4862
+ const choice = await selectPrompt({
4863
+ initialValue: defaultAgent?.name ?? "skip",
4864
+ message: `Assign an OrgX agent to the ${domain} domain`,
4865
+ options: [
4866
+ ...remainingAgents.map((agent) => ({
4867
+ value: agent.name,
4868
+ label: agent.name,
4869
+ hint: `${agent.agentType} \xB7 default ${agent.defaultDomain}`
4870
+ })),
4871
+ { value: "skip", label: `Leave ${domain} unassigned`, hint: "optional" }
4872
+ ]
4873
+ });
4874
+ if (clack.isCancel(choice)) {
4875
+ clack.cancel("Setup cancelled.");
4876
+ return "cancelled";
4877
+ }
4878
+ if (choice === "skip") {
4879
+ continue;
4880
+ }
4881
+ const index = remainingAgents.findIndex((agent) => agent.name === choice);
4882
+ if (index === -1) {
4883
+ continue;
4884
+ }
4885
+ const [selectedAgent] = remainingAgents.splice(index, 1);
4886
+ if (!selectedAgent) {
4887
+ continue;
4888
+ }
4889
+ assignments.push({ domain, name: selectedAgent.name });
4890
+ }
4891
+ if (assignments.length === 0) {
4892
+ return null;
4893
+ }
4894
+ return configureAgentRoster(workspace, assignments);
4895
+ }
3190
4896
  async function verifyAndPersistAuth(apiKey, options) {
3191
4897
  const trimmedKey = apiKey.trim();
3192
4898
  if (!trimmedKey.toLowerCase().startsWith("oxk_")) {
@@ -3207,6 +4913,10 @@ async function verifyAndPersistAuth(apiKey, options) {
3207
4913
  baseUrl,
3208
4914
  verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
3209
4915
  });
4916
+ await safeTrackWizardTelemetry("auth_completed", {
4917
+ auth_source: options.telemetrySource ?? "manual_api_key",
4918
+ has_base_url: Boolean(baseUrl)
4919
+ });
3210
4920
  const openclawResults = detectSurface("openclaw").detected ? await addSurface("openclaw") : [];
3211
4921
  return { openclawResults, stored, verification };
3212
4922
  }
@@ -3228,6 +4938,168 @@ function parseTimeoutSeconds(value) {
3228
4938
  }
3229
4939
  return parsed;
3230
4940
  }
4941
+ async function maybeConfigureOptionalWorkspaceAddOns(input) {
4942
+ if (!input.interactive || !input.workspace) {
4943
+ return "skipped";
4944
+ }
4945
+ const existingState = readWizardState();
4946
+ const hasOnboardingTask = existingState?.onboardingTask?.workspaceId === input.workspace.id;
4947
+ if (!hasOnboardingTask) {
4948
+ const onboardingChoice = await selectPrompt({
4949
+ message: `Create a starter onboarding task for ${input.workspace.name}?`,
4950
+ options: [
4951
+ { value: "yes", label: "Create onboarding task", hint: "recommended" },
4952
+ { value: "no", label: "Skip task creation" }
4953
+ ]
4954
+ });
4955
+ if (clack.isCancel(onboardingChoice)) {
4956
+ clack.cancel("Setup cancelled.");
4957
+ return "cancelled";
4958
+ }
4959
+ if (onboardingChoice === "yes") {
4960
+ const task = await ensureOnboardingTask(input.workspace, {
4961
+ ...input.initiativeId ? { initiativeId: input.initiativeId } : {}
4962
+ });
4963
+ console.log(` ${ICON.ok} ${pc3.green("onboarding")} ${pc3.bold(task.title)}`);
4964
+ await safeTrackWizardTelemetry(
4965
+ "onboarding_task_created",
4966
+ buildOnboardingTaskTelemetryProperties(
4967
+ {
4968
+ created: true,
4969
+ hasInitiative: Boolean(input.initiativeId),
4970
+ task
4971
+ },
4972
+ {
4973
+ command: input.telemetry?.command ?? "setup",
4974
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {}
4975
+ }
4976
+ )
4977
+ );
4978
+ }
4979
+ }
4980
+ const refreshedState = readWizardState();
4981
+ const hasAgentRoster = refreshedState?.agentRoster?.workspaceId === input.workspace.id;
4982
+ if (!hasAgentRoster) {
4983
+ const rosterChoice = await selectPrompt({
4984
+ message: `Set up an OrgX agent roster for ${input.workspace.name}?`,
4985
+ options: [
4986
+ { value: "guided", label: "Choose agents and domains", hint: "recommended" },
4987
+ { value: "default", label: "Install the full default roster" },
4988
+ { value: "no", label: "Skip roster setup" }
4989
+ ]
4990
+ });
4991
+ if (clack.isCancel(rosterChoice)) {
4992
+ clack.cancel("Setup cancelled.");
4993
+ return "cancelled";
4994
+ }
4995
+ if (rosterChoice === "guided") {
4996
+ const roster = await promptForGuidedAgentRoster(input.workspace);
4997
+ if (roster === "cancelled") {
4998
+ return "cancelled";
4999
+ }
5000
+ if (roster) {
5001
+ printAgentRoster(roster);
5002
+ await safeTrackWizardTelemetry(
5003
+ "agent_roster_configured",
5004
+ buildAgentRosterTelemetryProperties(
5005
+ {
5006
+ roster,
5007
+ strategy: "guided"
5008
+ },
5009
+ {
5010
+ command: input.telemetry?.command ?? "setup",
5011
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {}
5012
+ }
5013
+ )
5014
+ );
5015
+ } else {
5016
+ console.log(` ${ICON.skip} ${pc3.dim("agent roster skipped")} ${pc3.dim("(no agents selected)")}`);
5017
+ }
5018
+ } else if (rosterChoice === "default") {
5019
+ const roster = ensureDefaultAgentRoster(input.workspace);
5020
+ printAgentRoster(roster);
5021
+ await safeTrackWizardTelemetry(
5022
+ "agent_roster_configured",
5023
+ buildAgentRosterTelemetryProperties(
5024
+ {
5025
+ roster,
5026
+ strategy: "default"
5027
+ },
5028
+ {
5029
+ command: input.telemetry?.command ?? "setup",
5030
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {}
5031
+ }
5032
+ )
5033
+ );
5034
+ }
5035
+ }
5036
+ return "configured";
5037
+ }
5038
+ async function promptOptionalCompanionPluginTargets(input) {
5039
+ if (!input.interactive) {
5040
+ return [];
5041
+ }
5042
+ const statuses = await listOrgxPluginStatuses();
5043
+ const installable = statuses.filter((status) => status.available && !status.installed);
5044
+ if (installable.length === 0) {
5045
+ return [];
5046
+ }
5047
+ const selection = await multiselectPrompt({
5048
+ message: "Install companion OrgX plugins into detected tools?",
5049
+ options: installable.map((status) => ({
5050
+ value: status.target,
5051
+ label: `Install ${formatPluginTargetLabel(status.target)}`,
5052
+ hint: status.message
5053
+ })),
5054
+ required: false
5055
+ });
5056
+ if (clack.isCancel(selection)) {
5057
+ clack.cancel("Setup cancelled.");
5058
+ return "cancelled";
5059
+ }
5060
+ return selection;
5061
+ }
5062
+ function printPluginSkillOwnershipNote(targets) {
5063
+ if (!targets.some((target) => target === "claude" || target === "codex")) {
5064
+ return;
5065
+ }
5066
+ console.log(
5067
+ ` ${ICON.skip} ${pc3.dim(
5068
+ "Claude Code and Codex companion plugins carry their own OrgX skills. Use 'wizard skills add' for Cursor rules or standalone Claude setup when those plugins are not in play."
5069
+ )}`
5070
+ );
5071
+ }
5072
+ async function installSelectedCompanionPlugins(input) {
5073
+ if (input.targets.length === 0) {
5074
+ return "skipped";
5075
+ }
5076
+ const spinner = createOrgxSpinner("Installing OrgX companion plugins");
5077
+ spinner.start();
5078
+ const report = await installOrgxPlugins({ targets: input.targets });
5079
+ spinner.succeed("OrgX companion plugins processed");
5080
+ printPluginMutationReport(report);
5081
+ printPluginSkillOwnershipNote(input.targets);
5082
+ await safeTrackWizardTelemetry("plugins_installed", {
5083
+ changed_count: countPluginReportChanges(report),
5084
+ command: input.telemetry?.command ?? "setup",
5085
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {},
5086
+ requested_target_count: input.targets.length,
5087
+ target_count: report.results.length
5088
+ });
5089
+ return countPluginReportChanges(report) > 0 ? "configured" : "skipped";
5090
+ }
5091
+ async function maybeInstallOptionalCompanionPlugins(input) {
5092
+ const selection = await promptOptionalCompanionPluginTargets({
5093
+ interactive: input.interactive
5094
+ });
5095
+ if (selection === "cancelled") {
5096
+ return "cancelled";
5097
+ }
5098
+ return await installSelectedCompanionPlugins({
5099
+ targets: selection,
5100
+ ...input.telemetry ? { telemetry: input.telemetry } : {}
5101
+ });
5102
+ }
3231
5103
  function printAuthStatus(status) {
3232
5104
  if (!status.configured) {
3233
5105
  console.log(` ${ICON.warn} ${pc3.yellow("no account")} run ${pc3.cyan(`${getCmd()} auth login`)} to connect`);
@@ -3242,6 +5114,7 @@ function printAuthStatus(status) {
3242
5114
  }
3243
5115
  }
3244
5116
  function printDoctorReport(report, assessment) {
5117
+ const verification = summarizeSetupVerification(assessment);
3245
5118
  console.log(pc3.dim(" surfaces"));
3246
5119
  printSurfaceTable(report.surfaces);
3247
5120
  console.log("");
@@ -3290,11 +5163,12 @@ function printDoctorReport(report, assessment) {
3290
5163
  console.log(` ${pc3.dim("\u2192")} ${pc3.dim(`OrgX is active across ${configuredCount} editor${configuredCount !== 1 ? "s" : ""}`)}`);
3291
5164
  }
3292
5165
  } else {
3293
- const hasError = assessment.issues.some((i) => i.level === "error");
3294
- const headline = hasError ? `${ICON.err} ${pc3.red("Issues detected")}` : `${ICON.warn} ${pc3.yellow("Warnings detected")}`;
5166
+ const headlineText = getSetupVerificationHeadline(verification);
5167
+ const headline = verification.status === "error" ? `${ICON.err} ${pc3.red(headlineText)}` : `${ICON.warn} ${pc3.yellow(headlineText)}`;
3295
5168
  console.log(` ${headline}`);
3296
5169
  for (const issue of assessment.issues) {
3297
- const label = issue.level === "error" ? pc3.red("error") : pc3.yellow("warn ");
5170
+ const degradedHostedMcpIssue = verification.hostedMcpDegraded && issue.title === HOSTED_MCP_OUTAGE_TITLE;
5171
+ const label = degradedHostedMcpIssue ? pc3.yellow("degr ") : issue.level === "error" ? pc3.red("error") : pc3.yellow("warn ");
3298
5172
  console.log(`
3299
5173
  ${label} ${issue.title}`);
3300
5174
  console.log(` ${pc3.dim("\u2192")} ${issue.suggestion}`);
@@ -3304,16 +5178,28 @@ function printDoctorReport(report, assessment) {
3304
5178
  async function main() {
3305
5179
  const program = new Command();
3306
5180
  program.name("orgx-wizard").description("One-line CLI onboarding for OrgX surfaces.").showHelpAfterError();
3307
- const pkgVersion = true ? "0.1.6" : void 0;
5181
+ const pkgVersion = true ? "0.1.8" : void 0;
3308
5182
  program.version(pkgVersion ?? "unknown", "-V, --version");
3309
5183
  program.hook("preAction", () => {
3310
5184
  console.log(renderBanner(pkgVersion));
3311
5185
  });
3312
5186
  program.command("setup").description("Patch all detected automated OrgX surfaces.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
5187
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
5188
+ await safeTrackWizardTelemetry("wizard_started", {
5189
+ command: "setup",
5190
+ interactive,
5191
+ preset: options.preset ?? "standard"
5192
+ });
3313
5193
  if (options.preset) {
3314
5194
  if (options.preset !== "founder") {
3315
5195
  throw new Error(`Unknown setup preset '${options.preset}'. Supported presets: founder.`);
3316
5196
  }
5197
+ const founderPluginTargets = await promptOptionalCompanionPluginTargets({
5198
+ interactive
5199
+ });
5200
+ if (founderPluginTargets === "cancelled") {
5201
+ return;
5202
+ }
3317
5203
  const spinner2 = createOrgxSpinner("Running founder setup preset");
3318
5204
  spinner2.start();
3319
5205
  const presetResult = await runFounderPreset(
@@ -3324,7 +5210,8 @@ async function main() {
3324
5210
  text: textPrompt
3325
5211
  },
3326
5212
  {
3327
- interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
5213
+ interactive,
5214
+ pluginTargets: founderPluginTargets
3328
5215
  }
3329
5216
  );
3330
5217
  if ("status" in presetResult) {
@@ -3337,11 +5224,62 @@ async function main() {
3337
5224
  }
3338
5225
  spinner2.succeed("Founder setup preset complete");
3339
5226
  printFounderPresetResult(presetResult);
5227
+ await safeTrackWizardTelemetry("mcp_injected", {
5228
+ changed_count: presetResult.surfaceResults.filter((result) => result.changed).length,
5229
+ preset: "founder",
5230
+ surface_count: presetResult.surfaceResults.length
5231
+ });
5232
+ await safeTrackWizardTelemetry("skills_installed", {
5233
+ changed_count: countSkillReportChanges(presetResult.skillReport),
5234
+ pack_count: presetResult.skillReport.packs.length,
5235
+ preset: "founder",
5236
+ write_count: presetResult.skillReport.writes.length
5237
+ });
5238
+ if (presetResult.workspaceSetup) {
5239
+ await safeTrackWizardTelemetry(
5240
+ "workspace_bootstrapped",
5241
+ buildWorkspaceSetupTelemetryProperties(presetResult.workspaceSetup, {
5242
+ command: "setup",
5243
+ interactive,
5244
+ preset: "founder"
5245
+ })
5246
+ );
5247
+ }
5248
+ if (presetResult.demoInitiative) {
5249
+ await safeTrackWizardTelemetry(
5250
+ "founder_demo_ready",
5251
+ buildFounderDemoTelemetryProperties(presetResult.demoInitiative, {
5252
+ command: "setup",
5253
+ preset: "founder"
5254
+ })
5255
+ );
5256
+ }
5257
+ const addOnResult = await maybeConfigureOptionalWorkspaceAddOns({
5258
+ interactive,
5259
+ telemetry: { command: "setup", preset: "founder" },
5260
+ workspace: presetResult.workspace,
5261
+ ...presetResult.demoInitiative ? { initiativeId: presetResult.demoInitiative.initiative.id } : {}
5262
+ });
5263
+ if (addOnResult === "cancelled") {
5264
+ return;
5265
+ }
5266
+ await installSelectedCompanionPlugins({
5267
+ telemetry: { command: "setup", preset: "founder" },
5268
+ targets: founderPluginTargets
5269
+ });
3340
5270
  console.log("");
3341
5271
  const doctor2 = await runDoctor();
3342
5272
  const assessment2 = assessDoctorReport(doctor2);
5273
+ const verification2 = summarizeSetupVerification(assessment2);
5274
+ await safeTrackWizardTelemetry(
5275
+ "setup_verified",
5276
+ buildDoctorTelemetryProperties(doctor2, assessment2, verification2, {
5277
+ command: "setup",
5278
+ preset: "founder"
5279
+ })
5280
+ );
3343
5281
  printDoctorReport(doctor2, assessment2);
3344
- if (!assessment2.ok) {
5282
+ if (verification2.status === "error") {
3345
5283
  process.exitCode = 1;
3346
5284
  }
3347
5285
  return;
@@ -3351,10 +5289,14 @@ async function main() {
3351
5289
  const results = await setupDetectedSurfaces();
3352
5290
  spinner.succeed("Detected surfaces configured");
3353
5291
  printMutationResults(results);
5292
+ await safeTrackWizardTelemetry("mcp_injected", {
5293
+ changed_count: results.filter((result) => result.changed).length,
5294
+ preset: "standard",
5295
+ surface_count: results.length
5296
+ });
3354
5297
  let resolvedAuth = await resolveOrgxAuth();
3355
5298
  if (!resolvedAuth) {
3356
5299
  console.log("");
3357
- const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
3358
5300
  if (interactive) {
3359
5301
  const choice = await selectPrompt({
3360
5302
  message: "Connect your OrgX account to enable workspace and AI tool access",
@@ -3368,7 +5310,7 @@ async function main() {
3368
5310
  return;
3369
5311
  }
3370
5312
  if (choice === "login") {
3371
- const loginOk = await runBrowserLogin();
5313
+ const loginOk = await runBrowserLogin({ telemetrySource: "browser_pairing" });
3372
5314
  if (!loginOk) {
3373
5315
  console.log(`
3374
5316
  ${pc3.dim("\u2192")} ${pc3.cyan(`${getCmd()} auth login`)} ${pc3.dim("to try again")}`);
@@ -3404,25 +5346,60 @@ async function main() {
3404
5346
  text: textPrompt
3405
5347
  },
3406
5348
  {
3407
- interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
5349
+ interactive
3408
5350
  }
3409
5351
  );
3410
5352
  if (workspaceSetup.status === "cancelled") {
3411
5353
  return;
3412
5354
  }
5355
+ await safeTrackWizardTelemetry(
5356
+ "workspace_bootstrapped",
5357
+ buildWorkspaceSetupTelemetryProperties(workspaceSetup, {
5358
+ command: "setup",
5359
+ interactive,
5360
+ preset: "standard"
5361
+ })
5362
+ );
5363
+ const resolvedWorkspace = workspaceSetup.workspace ?? await getCurrentWorkspace().catch(() => null);
5364
+ if (resolvedWorkspace) {
5365
+ persistContinuityDefaults({ workspace: resolvedWorkspace });
5366
+ }
3413
5367
  if (workspaceSetup.workspace) {
3414
5368
  console.log(` ${ICON.ok} ${pc3.green("workspace ")} ${pc3.bold(workspaceSetup.workspace.name)}`);
3415
5369
  }
5370
+ const addOnResult = await maybeConfigureOptionalWorkspaceAddOns({
5371
+ interactive,
5372
+ telemetry: { command: "setup", preset: "standard" },
5373
+ workspace: resolvedWorkspace
5374
+ });
5375
+ if (addOnResult === "cancelled") {
5376
+ return;
5377
+ }
5378
+ const pluginInstallResult = await maybeInstallOptionalCompanionPlugins({
5379
+ interactive,
5380
+ telemetry: { command: "setup", preset: "standard" }
5381
+ });
5382
+ if (pluginInstallResult === "cancelled") {
5383
+ return;
5384
+ }
3416
5385
  }
3417
5386
  const doctor = await runDoctor();
3418
5387
  const assessment = assessDoctorReport(doctor);
5388
+ const verification = summarizeSetupVerification(assessment);
5389
+ await safeTrackWizardTelemetry(
5390
+ "setup_verified",
5391
+ buildDoctorTelemetryProperties(doctor, assessment, verification, {
5392
+ command: "setup",
5393
+ preset: "standard"
5394
+ })
5395
+ );
3419
5396
  const configuredCount = doctor.surfaces.filter((s) => s.configured).length;
3420
5397
  console.log("");
3421
5398
  if (assessment.issues.length === 0) {
3422
5399
  console.log(` ${ICON.ok} ${pc3.green("You're all set.")} ${pc3.dim(`OrgX is active across ${configuredCount} editor${configuredCount !== 1 ? "s" : ""}`)}`);
3423
5400
  } else {
3424
5401
  printDoctorReport(doctor, assessment);
3425
- if (!assessment.ok) {
5402
+ if (verification.status === "error") {
3426
5403
  process.exitCode = 1;
3427
5404
  }
3428
5405
  }
@@ -3460,7 +5437,8 @@ async function main() {
3460
5437
  });
3461
5438
  spinner.text = "Verifying API key...";
3462
5439
  const result = await verifyAndPersistAuth(ready.key, {
3463
- ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5440
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {},
5441
+ ...opts.telemetrySource ? { telemetrySource: opts.telemetrySource } : {}
3464
5442
  });
3465
5443
  if (!("stored" in result)) {
3466
5444
  spinner.fail("Pairing completed but key was rejected");
@@ -3509,7 +5487,10 @@ async function main() {
3509
5487
  if (options.apiKey) {
3510
5488
  const spinner = createOrgxSpinner("Verifying OrgX API key");
3511
5489
  spinner.start();
3512
- const result = await verifyAndPersistAuth(options.apiKey, options);
5490
+ const result = await verifyAndPersistAuth(options.apiKey, {
5491
+ ...options,
5492
+ telemetrySource: "manual_api_key"
5493
+ });
3513
5494
  if (!("stored" in result)) {
3514
5495
  spinner.fail("OrgX API key verification failed");
3515
5496
  printAuthStatus(result.verification);
@@ -3528,13 +5509,17 @@ async function main() {
3528
5509
  ...options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {},
3529
5510
  ...options.deviceName !== void 0 ? { deviceName: options.deviceName } : {},
3530
5511
  ...options.open !== void 0 ? { open: options.open } : {},
5512
+ telemetrySource: "browser_pairing",
3531
5513
  timeout: options.timeout
3532
5514
  });
3533
5515
  });
3534
5516
  auth.command("set-key").description("Verify and persist a per-user OrgX API key for the wizard.").argument("<apiKey>", "per-user OrgX API key (oxk_...)").option("--base-url <url>", "OrgX base URL").action(async (apiKey, options) => {
3535
5517
  const spinner = createOrgxSpinner("Verifying OrgX API key");
3536
5518
  spinner.start();
3537
- const result = await verifyAndPersistAuth(apiKey, options);
5519
+ const result = await verifyAndPersistAuth(apiKey, {
5520
+ ...options,
5521
+ telemetrySource: "manual_api_key"
5522
+ });
3538
5523
  if (!("stored" in result)) {
3539
5524
  spinner.fail("OrgX API key verification failed");
3540
5525
  printAuthStatus(result.verification);
@@ -3583,6 +5568,41 @@ async function main() {
3583
5568
  const results = removeMcpSurface(names);
3584
5569
  printMutationResults(results);
3585
5570
  });
5571
+ const plugins = program.command("plugins").description("Install or remove companion OrgX plugins for Claude Code, Codex, and OpenClaw.");
5572
+ plugins.command("list").description("Show companion plugin availability and install status.").action(async () => {
5573
+ const spinner = createOrgxSpinner("Checking companion plugin status");
5574
+ spinner.start();
5575
+ const statuses = await listOrgxPluginStatuses();
5576
+ spinner.stop();
5577
+ printPluginStatusReport(statuses);
5578
+ });
5579
+ plugins.command("add").description("Install OrgX companion plugins into Claude Code, Codex, or OpenClaw.").argument("[targets...]", "claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
5580
+ const spinner = createOrgxSpinner("Installing OrgX companion plugins");
5581
+ spinner.start();
5582
+ const report = await installOrgxPlugins({ targets });
5583
+ spinner.succeed("OrgX companion plugins processed");
5584
+ printPluginMutationReport(report);
5585
+ printPluginSkillOwnershipNote(report.results.map((result) => result.target));
5586
+ await safeTrackWizardTelemetry("plugins_installed", {
5587
+ changed_count: countPluginReportChanges(report),
5588
+ command: "plugins:add",
5589
+ requested_target_count: targets.length,
5590
+ target_count: report.results.length
5591
+ });
5592
+ });
5593
+ plugins.command("remove").description("Uninstall managed OrgX companion plugins from Claude Code, Codex, or OpenClaw.").argument("[targets...]", "claude, codex, openclaw, or all", ["all"]).action(async (targets) => {
5594
+ const spinner = createOrgxSpinner("Removing OrgX companion plugins");
5595
+ spinner.start();
5596
+ const report = await uninstallOrgxPlugins({ targets });
5597
+ spinner.succeed("OrgX companion plugins removed");
5598
+ printPluginMutationReport(report);
5599
+ await safeTrackWizardTelemetry("plugins_removed", {
5600
+ changed_count: countPluginReportChanges(report),
5601
+ command: "plugins:remove",
5602
+ requested_target_count: targets.length,
5603
+ target_count: report.results.length
5604
+ });
5605
+ });
3586
5606
  const workspace = program.command("workspace").description("Inspect and create OrgX workspaces.");
3587
5607
  workspace.command("current").description("Show the current OrgX workspace.").action(async () => {
3588
5608
  const spinner = createOrgxSpinner("Loading current OrgX workspace");
@@ -3612,6 +5632,9 @@ async function main() {
3612
5632
  }
3613
5633
  );
3614
5634
  spinner.succeed("OrgX workspace created");
5635
+ if (created.isDefault) {
5636
+ persistContinuityDefaults({ workspace: created });
5637
+ }
3615
5638
  printWorkspace(created);
3616
5639
  if (!created.isDefault) {
3617
5640
  console.log("");
@@ -3625,6 +5648,7 @@ async function main() {
3625
5648
  spinner.succeed(
3626
5649
  result.changed ? "Default OrgX workspace updated" : "OrgX workspace already set as default"
3627
5650
  );
5651
+ persistContinuityDefaults({ workspace: result.workspace });
3628
5652
  printWorkspace(result.workspace);
3629
5653
  });
3630
5654
  program.command("doctor").description("Verify local OrgX surface config and optional remote setup status.").action(async () => {
@@ -3633,18 +5657,37 @@ async function main() {
3633
5657
  const report = await runDoctor();
3634
5658
  spinner.stop();
3635
5659
  const assessment = assessDoctorReport(report);
5660
+ const verification = summarizeSetupVerification(assessment);
5661
+ await safeTrackWizardTelemetry(
5662
+ "doctor_ran",
5663
+ buildDoctorTelemetryProperties(report, assessment, verification, {
5664
+ command: "doctor"
5665
+ })
5666
+ );
3636
5667
  printDoctorReport(report, assessment);
3637
- if (!assessment.ok) {
5668
+ if (verification.status === "error") {
3638
5669
  process.exitCode = 1;
3639
5670
  }
3640
5671
  });
3641
5672
  const skills = program.command("skills").description("Install OrgX rules and Claude skill packs.");
3642
- skills.command("add").description("Write Cursor and Claude OrgX rules and install OrgX Claude skill packs.").argument("[packs...]", "skill pack names or 'all'", ["all"]).action(async (packs) => {
5673
+ skills.command("add").description("Write standalone OrgX editor rules and Claude skill packs, skipping surfaces already owned by companion plugins.").argument("[packs...]", "skill pack names or 'all'", ["all"]).action(async (packs) => {
5674
+ const pluginTargets = (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
3643
5675
  const spinner = createOrgxSpinner("Installing OrgX rules and skills");
3644
5676
  spinner.start();
3645
- const report = await installOrgxSkills({ skillNames: packs });
5677
+ const report = await installOrgxSkills({
5678
+ pluginTargets,
5679
+ skillNames: packs
5680
+ });
3646
5681
  spinner.succeed("OrgX rules and skills installed");
3647
5682
  printSkillInstallReport(report);
5683
+ await safeTrackWizardTelemetry("skills_installed", {
5684
+ changed_count: countSkillReportChanges(report),
5685
+ command: "skills:add",
5686
+ pack_count: report.packs.length,
5687
+ plugin_managed_target_count: pluginTargets.length,
5688
+ requested_pack_count: packs.length,
5689
+ write_count: report.writes.length
5690
+ });
3648
5691
  });
3649
5692
  await program.parseAsync(process.argv);
3650
5693
  }