@useorgx/wizard 0.1.7 → 0.1.9

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
- var DEFAULT_ORGX_BASE_URL = process.env.ORGX_BASE_URL?.trim() || "https://www.useorgx.com";
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,296 @@ 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.workstreamId) ? { workstreamId: record.workstreamId.trim() } : {},
944
+ ...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
945
+ ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
946
+ };
947
+ }
948
+ function parseAgentRosterEntry(value) {
949
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
950
+ return void 0;
951
+ }
952
+ const record = value;
953
+ if (!isNonEmptyString2(record.agentType) || !isNonEmptyString2(record.domain) || !isNonEmptyString2(record.name)) {
954
+ return void 0;
955
+ }
956
+ return {
957
+ agentType: record.agentType.trim(),
958
+ domain: record.domain.trim(),
959
+ name: record.name.trim()
960
+ };
961
+ }
962
+ function parseAgentRoster(value) {
963
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
964
+ return void 0;
965
+ }
966
+ const record = value;
967
+ if (!isNonEmptyString2(record.configuredAt) || !Array.isArray(record.agents)) {
968
+ return void 0;
969
+ }
970
+ const agents = record.agents.map((entry) => parseAgentRosterEntry(entry)).filter((entry) => Boolean(entry));
971
+ if (agents.length === 0) {
972
+ return void 0;
973
+ }
974
+ return {
975
+ agents,
976
+ configuredAt: record.configuredAt.trim(),
977
+ ...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
978
+ ...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
979
+ };
980
+ }
981
+ function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
982
+ return {
983
+ installationId: `wizard-${randomUUID()}`,
984
+ createdAt: now,
985
+ updatedAt: now
986
+ };
987
+ }
988
+ function sanitizeWizardStateRecord(record) {
989
+ const continuity = parseContinuityDefaults(record.continuity);
990
+ const agentRoster = parseAgentRoster(record.agentRoster);
991
+ const demoInitiative = parseDemoInitiative(record.demoInitiative);
992
+ const onboardingTask = parseOnboardingTask(record.onboardingTask);
993
+ return {
994
+ installationId: record.installationId.trim(),
995
+ createdAt: record.createdAt.trim(),
996
+ updatedAt: record.updatedAt.trim(),
997
+ ...continuity ? { continuity } : {},
998
+ ...agentRoster ? { agentRoster } : {},
999
+ ...demoInitiative ? { demoInitiative } : {},
1000
+ ...onboardingTask ? { onboardingTask } : {}
1001
+ };
1002
+ }
1003
+ function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
1004
+ const parsed = parseJsonObject(readTextIfExists(statePath));
1005
+ if (!isNonEmptyString2(parsed.installationId) || !isNonEmptyString2(parsed.createdAt) || !isNonEmptyString2(parsed.updatedAt)) {
1006
+ return null;
1007
+ }
1008
+ const state = {
1009
+ installationId: parsed.installationId.trim(),
1010
+ createdAt: parsed.createdAt.trim(),
1011
+ updatedAt: parsed.updatedAt.trim()
1012
+ };
1013
+ const continuity = parseContinuityDefaults(parsed.continuity);
1014
+ if (continuity !== void 0) state.continuity = continuity;
1015
+ const agentRoster = parseAgentRoster(parsed.agentRoster);
1016
+ if (agentRoster !== void 0) state.agentRoster = agentRoster;
1017
+ const demoInitiative = parseDemoInitiative(parsed.demoInitiative);
1018
+ if (demoInitiative !== void 0) state.demoInitiative = demoInitiative;
1019
+ const onboardingTask = parseOnboardingTask(parsed.onboardingTask);
1020
+ if (onboardingTask !== void 0) state.onboardingTask = onboardingTask;
1021
+ return state;
1022
+ }
1023
+ function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
1024
+ const record = sanitizeWizardStateRecord(value);
1025
+ writeJsonFile(statePath, record, { mode: 384 });
1026
+ return record;
1027
+ }
1028
+ function updateWizardState(updater, statePath = ORGX_WIZARD_STATE_PATH) {
1029
+ const current = readWizardState(statePath) ?? createWizardState();
1030
+ const next = updater(current);
1031
+ const sanitized = sanitizeWizardStateRecord({
1032
+ ...next,
1033
+ installationId: next.installationId || current.installationId,
1034
+ createdAt: next.createdAt || current.createdAt,
1035
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1036
+ });
1037
+ writeJsonFile(statePath, sanitized, { mode: 384 });
1038
+ return sanitized;
1039
+ }
1040
+ function getOrCreateWizardInstallationId(statePath = ORGX_WIZARD_STATE_PATH) {
1041
+ const existing = readWizardState(statePath);
1042
+ if (existing) {
1043
+ return existing.installationId;
1044
+ }
1045
+ const record = createWizardState();
1046
+ writeWizardState(record, statePath);
1047
+ return record.installationId;
1048
+ }
1049
+
1050
+ // src/lib/agent-roster.ts
1051
+ var AGENT_ROSTER_PROFILES = [
1052
+ { agentType: "orchestrator-agent", defaultDomain: "orchestrator", name: "Xandy" },
1053
+ { agentType: "product-agent", defaultDomain: "product", name: "Pace" },
1054
+ { agentType: "engineering-agent", defaultDomain: "engineering", name: "Eli" },
1055
+ { agentType: "marketing-agent", defaultDomain: "marketing", name: "Mark" },
1056
+ { agentType: "sales-agent", defaultDomain: "sales", name: "Sage" },
1057
+ { agentType: "operations-agent", defaultDomain: "operations", name: "Orion" },
1058
+ { agentType: "design-agent", defaultDomain: "design", name: "Dana" }
1059
+ ];
1060
+ var AGENT_ROSTER_DOMAINS = AGENT_ROSTER_PROFILES.map((profile) => profile.defaultDomain);
1061
+ var DEFAULT_AGENT_ROSTER = AGENT_ROSTER_PROFILES.map(
1062
+ ({ agentType, defaultDomain, name }) => ({
1063
+ agentType,
1064
+ domain: defaultDomain,
1065
+ name
1066
+ })
1067
+ );
1068
+ function findAgentRosterProfile(name) {
1069
+ return AGENT_ROSTER_PROFILES.find((profile) => profile.name === name);
1070
+ }
1071
+ function listAgentRosterProfiles() {
1072
+ return AGENT_ROSTER_PROFILES.map((profile) => ({ ...profile }));
1073
+ }
1074
+ function buildAgentRosterEntries(assignments) {
1075
+ const seenNames = /* @__PURE__ */ new Set();
1076
+ return assignments.map((assignment) => {
1077
+ const name = assignment.name.trim();
1078
+ const domain = assignment.domain.trim();
1079
+ if (domain.length === 0) {
1080
+ throw new Error("OrgX agent roster domains must be non-empty.");
1081
+ }
1082
+ const profile = findAgentRosterProfile(name);
1083
+ if (!profile) {
1084
+ throw new Error(`Unknown OrgX agent '${assignment.name}'.`);
1085
+ }
1086
+ if (seenNames.has(profile.name)) {
1087
+ throw new Error(`OrgX agent '${profile.name}' can only be assigned once.`);
1088
+ }
1089
+ seenNames.add(profile.name);
1090
+ return {
1091
+ agentType: profile.agentType,
1092
+ domain,
1093
+ name: profile.name
1094
+ };
1095
+ });
1096
+ }
1097
+ function toAgentRosterRecord(workspace, agents) {
1098
+ return {
1099
+ agents: agents.map((agent) => ({ ...agent })),
1100
+ configuredAt: (/* @__PURE__ */ new Date()).toISOString(),
1101
+ workspaceId: workspace.id,
1102
+ workspaceName: workspace.name
1103
+ };
1104
+ }
1105
+ function configureAgentRoster(workspace, assignments, options = {}) {
1106
+ const agents = buildAgentRosterEntries(assignments);
1107
+ if (agents.length === 0) {
1108
+ throw new Error("Select at least one OrgX agent before saving the roster.");
1109
+ }
1110
+ const roster = toAgentRosterRecord(workspace, agents);
1111
+ updateWizardState(
1112
+ (current) => ({
1113
+ ...current,
1114
+ agentRoster: roster
1115
+ }),
1116
+ options.statePath
1117
+ );
1118
+ return roster;
1119
+ }
1120
+ function ensureDefaultAgentRoster(workspace, options = {}) {
1121
+ const existing = readWizardState(options.statePath)?.agentRoster;
1122
+ if (existing?.workspaceId === workspace.id && existing.agents.length > 0) {
1123
+ return existing;
1124
+ }
1125
+ const roster = toAgentRosterRecord(workspace, DEFAULT_AGENT_ROSTER);
1126
+ updateWizardState(
1127
+ (current) => ({
1128
+ ...current,
1129
+ agentRoster: roster
1130
+ }),
1131
+ options.statePath
1132
+ );
1133
+ return roster;
1134
+ }
1135
+
748
1136
  // src/lib/openclaw-health.ts
749
1137
  import { spawnSync as spawnSync2 } from "child_process";
750
1138
  function trimOutput(value) {
@@ -852,39 +1240,6 @@ async function checkHostedMcpHealth() {
852
1240
 
853
1241
  // src/lib/hosted-mcp-tool-check.ts
854
1242
  var DEFAULT_TOOL_NAME = "get_setup_status";
855
- function buildHeaders(apiKey, sessionId) {
856
- const headers = {
857
- Authorization: `Bearer ${apiKey}`,
858
- "Content-Type": "application/json",
859
- Accept: "application/json, text/event-stream"
860
- };
861
- if (sessionId) {
862
- headers["Mcp-Session-Id"] = sessionId;
863
- }
864
- return headers;
865
- }
866
- async function readJsonSafe(response) {
867
- const text2 = await response.text();
868
- if (!text2) return null;
869
- try {
870
- return JSON.parse(text2);
871
- } catch {
872
- return text2;
873
- }
874
- }
875
- function readJsonRpcError(payload) {
876
- if (!payload || typeof payload !== "object") {
877
- return void 0;
878
- }
879
- const maybeError = payload.error;
880
- if (!maybeError || typeof maybeError !== "object") {
881
- return void 0;
882
- }
883
- return typeof maybeError.message === "string" ? maybeError.message : "Unknown MCP JSON-RPC error.";
884
- }
885
- function readSessionId(response) {
886
- return response.headers.get("mcp-session-id") ?? response.headers.get("Mcp-Session-Id") ?? void 0;
887
- }
888
1243
  async function checkHostedMcpToolAccess(options = {}, toolName = DEFAULT_TOOL_NAME) {
889
1244
  const auth = await resolveOrgxAuth(options);
890
1245
  if (!auth) {
@@ -899,157 +1254,20 @@ async function checkHostedMcpToolAccess(options = {}, toolName = DEFAULT_TOOL_NA
899
1254
  details: []
900
1255
  };
901
1256
  }
902
- try {
903
- const initializeResponse = await fetch(ORGX_HOSTED_MCP_URL, {
904
- method: "POST",
905
- headers: buildHeaders(auth.apiKey),
906
- body: JSON.stringify({
907
- jsonrpc: "2.0",
908
- id: 1,
909
- method: "initialize",
910
- params: {
911
- protocolVersion: "2024-11-05",
912
- clientInfo: {
913
- name: "orgx-wizard",
914
- version: "0.1.0"
915
- },
916
- capabilities: {}
917
- }
918
- }),
919
- signal: AbortSignal.timeout(1e4)
920
- });
921
- const initializePayload = await readJsonSafe(initializeResponse);
922
- const initializeError = readJsonRpcError(initializePayload);
923
- const sessionId = readSessionId(initializeResponse);
924
- if (!initializeResponse.ok) {
925
- return {
926
- configured: true,
927
- ok: false,
928
- skipped: false,
929
- source: auth.source,
930
- toolName,
931
- url: ORGX_HOSTED_MCP_URL,
932
- baseUrl: auth.baseUrl,
933
- initializeStatus: initializeResponse.status,
934
- error: `MCP initialize failed with HTTP ${initializeResponse.status}.`,
935
- details: [`auth source: ${auth.source}`],
936
- response: initializePayload
937
- };
938
- }
939
- if (initializeError) {
940
- return {
941
- configured: true,
942
- ok: false,
943
- skipped: false,
944
- source: auth.source,
945
- toolName,
946
- url: ORGX_HOSTED_MCP_URL,
947
- baseUrl: auth.baseUrl,
948
- initializeStatus: initializeResponse.status,
949
- error: `MCP initialize returned JSON-RPC error: ${initializeError}`,
950
- details: [`auth source: ${auth.source}`],
951
- response: initializePayload
952
- };
953
- }
954
- if (!sessionId) {
955
- return {
956
- configured: true,
957
- ok: false,
958
- skipped: false,
959
- source: auth.source,
960
- toolName,
961
- url: ORGX_HOSTED_MCP_URL,
962
- baseUrl: auth.baseUrl,
963
- initializeStatus: initializeResponse.status,
964
- error: "MCP initialize succeeded but no Mcp-Session-Id header was returned.",
965
- details: [`auth source: ${auth.source}`],
966
- response: initializePayload
967
- };
968
- }
969
- const callResponse = await fetch(ORGX_HOSTED_MCP_URL, {
970
- method: "POST",
971
- headers: buildHeaders(auth.apiKey, sessionId),
972
- body: JSON.stringify({
973
- jsonrpc: "2.0",
974
- id: 2,
975
- method: "tools/call",
976
- params: {
977
- name: toolName,
978
- arguments: {}
979
- }
980
- }),
981
- signal: AbortSignal.timeout(1e4)
982
- });
983
- const callPayload = await readJsonSafe(callResponse);
984
- const callError = readJsonRpcError(callPayload);
985
- if (!callResponse.ok) {
986
- return {
987
- configured: true,
988
- ok: false,
989
- skipped: false,
990
- source: auth.source,
991
- toolName,
992
- url: ORGX_HOSTED_MCP_URL,
993
- baseUrl: auth.baseUrl,
994
- initializeStatus: initializeResponse.status,
995
- callStatus: callResponse.status,
996
- error: `MCP tool call failed with HTTP ${callResponse.status}.`,
997
- details: [
998
- `auth source: ${auth.source}`,
999
- `session established: ${sessionId}`
1000
- ],
1001
- response: callPayload
1002
- };
1003
- }
1004
- if (callError) {
1005
- return {
1006
- configured: true,
1007
- ok: false,
1008
- skipped: false,
1009
- source: auth.source,
1010
- toolName,
1011
- url: ORGX_HOSTED_MCP_URL,
1012
- baseUrl: auth.baseUrl,
1013
- initializeStatus: initializeResponse.status,
1014
- callStatus: callResponse.status,
1015
- error: `MCP tool call returned JSON-RPC error: ${callError}`,
1016
- details: [
1017
- `auth source: ${auth.source}`,
1018
- `session established: ${sessionId}`
1019
- ],
1020
- response: callPayload
1021
- };
1022
- }
1023
- return {
1024
- configured: true,
1025
- ok: true,
1026
- skipped: false,
1027
- source: auth.source,
1028
- toolName,
1029
- url: ORGX_HOSTED_MCP_URL,
1030
- baseUrl: auth.baseUrl,
1031
- initializeStatus: initializeResponse.status,
1032
- callStatus: callResponse.status,
1033
- details: [
1034
- `auth source: ${auth.source}`,
1035
- `session established: ${sessionId}`,
1036
- `tool call: ${toolName}`
1037
- ],
1038
- response: callPayload
1039
- };
1040
- } catch (error) {
1041
- return {
1042
- configured: true,
1043
- ok: false,
1044
- skipped: false,
1045
- source: auth.source,
1046
- toolName,
1047
- url: ORGX_HOSTED_MCP_URL,
1048
- baseUrl: auth.baseUrl,
1049
- error: error instanceof Error ? error.message : String(error),
1050
- details: [`auth source: ${auth.source}`]
1051
- };
1052
- }
1257
+ return {
1258
+ configured: true,
1259
+ ok: false,
1260
+ skipped: true,
1261
+ source: auth.source,
1262
+ toolName,
1263
+ url: ORGX_HOSTED_MCP_URL,
1264
+ baseUrl: auth.baseUrl,
1265
+ error: "Hosted OrgX MCP uses OAuth; API-key tool verification is skipped.",
1266
+ details: [
1267
+ `auth source: ${auth.source}`,
1268
+ "Hosted OrgX MCP connectors complete OAuth during client setup, so the wizard cannot preflight tools with an oxk_ API key."
1269
+ ]
1270
+ };
1053
1271
  }
1054
1272
 
1055
1273
  // src/lib/npm-registry-health.ts
@@ -1246,7 +1464,7 @@ async function getCurrentWorkspace(options = {}) {
1246
1464
  }
1247
1465
  return workspace;
1248
1466
  }
1249
- if (response.status === 404) {
1467
+ if (response.status === 401 || response.status === 404) {
1250
1468
  const workspaces = await listWorkspaces(options);
1251
1469
  return workspaces[0] ?? null;
1252
1470
  }
@@ -1714,35 +1932,6 @@ function inspectCodexConfigToml(input) {
1714
1932
  };
1715
1933
  }
1716
1934
 
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
1935
  // src/surfaces/detection.ts
1747
1936
  import { existsSync as existsSync2 } from "fs";
1748
1937
  import { dirname as dirname2 } from "path";
@@ -1823,6 +2012,9 @@ function detectSurface(name, exists = existsSync2) {
1823
2012
  }
1824
2013
 
1825
2014
  // src/surfaces/registry.ts
2015
+ var AUTH_SETUP_HINT = "orgx-wizard auth login";
2016
+ var AUTH_SET_KEY_HINT = "orgx-wizard auth set-key";
2017
+ var DOCTOR_HINT = "orgx-wizard doctor";
1826
2018
  function getSurfacePath(name) {
1827
2019
  const detection = detectSurface(name);
1828
2020
  const locator = getSurfaceLocator(name);
@@ -1951,27 +2143,95 @@ function automatedSurfaceStatus(name) {
1951
2143
  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
2144
  };
1953
2145
  }
1954
- function manualSurfaceStatus(name) {
1955
- const detection = detectSurface(name);
2146
+ function formatAuthHintSource(authHint) {
2147
+ switch (authHint.source) {
2148
+ case "environment":
2149
+ return "env ORGX_API_KEY";
2150
+ case "wizard-store":
2151
+ return "wizard auth store";
2152
+ case "openclaw-config-file":
2153
+ return "openclaw config";
2154
+ }
2155
+ }
2156
+ function buildChatgptManualStatus(detection, authHint) {
1956
2157
  const path = detection.existingPath ?? detection.preferredPath;
1957
- const shared = {
1958
- name,
2158
+ const details = [...detection.evidence];
2159
+ if (authHint) {
2160
+ details.push(
2161
+ `OrgX auth ready via ${formatAuthHintSource(authHint)} (${authHint.keyPrefix})`
2162
+ );
2163
+ details.push(`Hosted connector URL: ${ORGX_HOSTED_MCP_URL}`);
2164
+ details.push(
2165
+ "Manual step: in ChatGPT desktop developer mode, add a custom connector pointing at the hosted OrgX MCP URL."
2166
+ );
2167
+ details.push(
2168
+ "ChatGPT completes OAuth during connector setup; the wizard cannot preflight hosted MCP tools with an oxk_ API key."
2169
+ );
2170
+ details.push(
2171
+ "After connecting ChatGPT, run `orgx-wizard doctor` to verify hosted MCP health from this machine."
2172
+ );
2173
+ } else {
2174
+ details.push(
2175
+ "OrgX auth is not configured yet. Run `orgx-wizard auth login` or `orgx-wizard auth set-key` first."
2176
+ );
2177
+ details.push(
2178
+ "After auth is configured, add a custom ChatGPT developer connector pointing at the hosted OrgX MCP URL."
2179
+ );
2180
+ }
2181
+ let summary;
2182
+ if (!authHint) {
2183
+ 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.";
2184
+ } else {
2185
+ 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.";
2186
+ }
2187
+ return {
2188
+ name: "chatgpt",
1959
2189
  mode: "manual",
1960
2190
  detected: detection.detected,
1961
2191
  configured: false,
1962
- ...path ? { path } : {}
2192
+ ...path ? { path } : {},
2193
+ details,
2194
+ summary
2195
+ };
2196
+ }
2197
+ function buildChatgptAddResult(path, authHint, toolCheck) {
2198
+ if (!authHint) {
2199
+ return {
2200
+ name: "chatgpt",
2201
+ changed: false,
2202
+ ...path ? { path } : {},
2203
+ 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.`
2204
+ };
2205
+ }
2206
+ if (toolCheck && !toolCheck.ok && !toolCheck.skipped) {
2207
+ const failure = toolCheck?.error ?? "hosted MCP verification did not complete.";
2208
+ return {
2209
+ name: "chatgpt",
2210
+ changed: false,
2211
+ ...path ? { path } : {},
2212
+ 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.`
2213
+ };
2214
+ }
2215
+ return {
2216
+ name: "chatgpt",
2217
+ changed: false,
2218
+ ...path ? { path } : {},
2219
+ message: `OrgX auth is ready via ${formatAuthHintSource(authHint)}. Add ${ORGX_HOSTED_MCP_URL} as a custom connector in ChatGPT developer mode; ChatGPT will complete the OAuth flow there. Then run \`${DOCTOR_HINT}\` to verify hosted MCP health from this machine.`
2220
+ };
2221
+ }
2222
+ function buildChatgptRemoveResult(path) {
2223
+ return {
2224
+ name: "chatgpt",
2225
+ changed: false,
2226
+ ...path ? { path } : {},
2227
+ message: "Remove the custom OrgX connector from ChatGPT developer mode; the wizard does not manage ChatGPT config files directly."
1963
2228
  };
2229
+ }
2230
+ function manualSurfaceStatus(name) {
2231
+ const detection = detectSurface(name);
1964
2232
  switch (name) {
1965
2233
  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
- };
2234
+ return buildChatgptManualStatus(detection, peekResolvedOrgxAuth());
1975
2235
  }
1976
2236
  }
1977
2237
  function getSurfaceStatus(name) {
@@ -2089,11 +2349,18 @@ async function addAutomatedSurface(name) {
2089
2349
  writeTextFile(path, next);
2090
2350
  return { name, changed: true, path, message: "OrgX cloud MCP is connected in Zed." };
2091
2351
  }
2352
+ case "chatgpt": {
2353
+ const authHint = peekResolvedOrgxAuth();
2354
+ if (!authHint) {
2355
+ return buildChatgptAddResult(path, null);
2356
+ }
2357
+ return buildChatgptAddResult(path, authHint);
2358
+ }
2092
2359
  default:
2093
2360
  return {
2094
2361
  name,
2095
2362
  changed: false,
2096
- message: `${name} is not automated yet.`
2363
+ message: `${name} is not supported by the wizard.`
2097
2364
  };
2098
2365
  }
2099
2366
  }
@@ -2184,11 +2451,13 @@ function removeAutomatedSurface(name) {
2184
2451
  message: "OrgX connection was removed from Zed."
2185
2452
  };
2186
2453
  }
2454
+ case "chatgpt":
2455
+ return buildChatgptRemoveResult(path);
2187
2456
  default:
2188
2457
  return {
2189
2458
  name,
2190
2459
  changed: false,
2191
- message: `${name} is not automated yet.`
2460
+ message: `${name} is not supported by the wizard.`
2192
2461
  };
2193
2462
  }
2194
2463
  }
@@ -2305,171 +2574,65 @@ function assessDoctorReport(report) {
2305
2574
  };
2306
2575
  }
2307
2576
 
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;
2577
+ // src/lib/continuity.ts
2578
+ var REVIEW_SURFACE_ORDER = [
2579
+ "claude",
2580
+ "cursor",
2581
+ "codex",
2582
+ "vscode",
2583
+ "windsurf",
2584
+ "zed",
2585
+ "chatgpt"
2586
+ ];
2587
+ function selectReviewSurface(statuses) {
2588
+ for (const name of REVIEW_SURFACE_ORDER) {
2589
+ const configured = statuses.find((status) => status.name === name && status.configured);
2590
+ if (configured) {
2591
+ return configured.name;
2592
+ }
2324
2593
  }
2325
- if (isSurfaceName(record.reviewSurface)) {
2326
- parsed.reviewSurface = record.reviewSurface;
2594
+ for (const name of REVIEW_SURFACE_ORDER) {
2595
+ const detected = statuses.find((status) => status.name === name && status.detected);
2596
+ if (detected) {
2597
+ return detected.name;
2598
+ }
2327
2599
  }
2328
- if (isNonEmptyString2(record.workspaceId)) {
2329
- parsed.workspaceId = record.workspaceId.trim();
2600
+ return void 0;
2601
+ }
2602
+ function inferExecutionMode(explicitMode, statuses) {
2603
+ if (explicitMode) {
2604
+ return explicitMode;
2330
2605
  }
2331
- if (isNonEmptyString2(record.workspaceName)) {
2332
- parsed.workspaceName = record.workspaceName.trim();
2606
+ const openclaw = statuses.find((status) => status.name === "openclaw");
2607
+ if (openclaw?.configured || openclaw?.detected) {
2608
+ return "local";
2333
2609
  }
2334
- return Object.keys(parsed).length > 0 ? parsed : void 0;
2610
+ const hostedSurface = statuses.find(
2611
+ (status) => status.name !== "openclaw" && (status.configured || status.detected)
2612
+ );
2613
+ return hostedSurface ? "cloud" : void 0;
2335
2614
  }
2336
- function parseDemoInitiative(value) {
2337
- if (!value || typeof value !== "object" || Array.isArray(value)) {
2338
- return void 0;
2615
+ function inferContinuityDefaults(seed = {}) {
2616
+ const statuses = seed.statuses ?? listSurfaceStatuses();
2617
+ const continuity = {};
2618
+ const reviewSurface = selectReviewSurface(statuses);
2619
+ if (reviewSurface) {
2620
+ continuity.reviewSurface = reviewSurface;
2339
2621
  }
2340
- const record = value;
2341
- if (!isNonEmptyString2(record.id) || !isNonEmptyString2(record.title) || !isNonEmptyString2(record.liveUrl) || !isNonEmptyString2(record.createdAt)) {
2342
- return void 0;
2622
+ const executionMode = inferExecutionMode(seed.executionMode, statuses);
2623
+ if (executionMode) {
2624
+ continuity.executionMode = executionMode;
2625
+ }
2626
+ if (seed.workspace?.id) {
2627
+ continuity.workspaceId = seed.workspace.id;
2343
2628
  }
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
- // src/lib/continuity.ts
2415
- var REVIEW_SURFACE_ORDER = [
2416
- "claude",
2417
- "cursor",
2418
- "codex",
2419
- "vscode",
2420
- "windsurf",
2421
- "zed",
2422
- "chatgpt"
2423
- ];
2424
- function selectReviewSurface(statuses) {
2425
- for (const name of REVIEW_SURFACE_ORDER) {
2426
- const configured = statuses.find((status) => status.name === name && status.configured);
2427
- if (configured) {
2428
- return configured.name;
2429
- }
2430
- }
2431
- for (const name of REVIEW_SURFACE_ORDER) {
2432
- const detected = statuses.find((status) => status.name === name && status.detected);
2433
- if (detected) {
2434
- return detected.name;
2435
- }
2436
- }
2437
- return void 0;
2438
- }
2439
- function inferExecutionMode(explicitMode, statuses) {
2440
- if (explicitMode) {
2441
- return explicitMode;
2442
- }
2443
- const openclaw = statuses.find((status) => status.name === "openclaw");
2444
- if (openclaw?.configured || openclaw?.detected) {
2445
- return "local";
2446
- }
2447
- const hostedSurface = statuses.find(
2448
- (status) => status.name !== "openclaw" && (status.configured || status.detected)
2449
- );
2450
- return hostedSurface ? "cloud" : void 0;
2451
- }
2452
- function inferContinuityDefaults(seed = {}) {
2453
- const statuses = seed.statuses ?? listSurfaceStatuses();
2454
- const continuity = {};
2455
- const reviewSurface = selectReviewSurface(statuses);
2456
- if (reviewSurface) {
2457
- continuity.reviewSurface = reviewSurface;
2458
- }
2459
- const executionMode = inferExecutionMode(seed.executionMode, statuses);
2460
- if (executionMode) {
2461
- continuity.executionMode = executionMode;
2462
- }
2463
- if (seed.workspace?.id) {
2464
- continuity.workspaceId = seed.workspace.id;
2465
- }
2466
- const workspaceName = seed.workspace?.name ?? seed.workspaceName ?? void 0;
2467
- if (workspaceName?.trim()) {
2468
- continuity.workspaceName = workspaceName.trim();
2469
- }
2470
- return continuity;
2471
- }
2472
- function mergeContinuityDefaults(current, next) {
2629
+ const workspaceName = seed.workspace?.name ?? seed.workspaceName ?? void 0;
2630
+ if (workspaceName?.trim()) {
2631
+ continuity.workspaceName = workspaceName.trim();
2632
+ }
2633
+ return continuity;
2634
+ }
2635
+ function mergeContinuityDefaults(current, next) {
2473
2636
  return {
2474
2637
  ...current ?? {},
2475
2638
  ...next
@@ -2489,6 +2652,16 @@ function persistContinuityDefaults(seed = {}, statePath) {
2489
2652
  // src/lib/initiatives.ts
2490
2653
  var FOUNDER_DEMO_INITIATIVE_TITLE = "Founder Demo Initiative";
2491
2654
  var FOUNDER_DEMO_INITIATIVE_SUMMARY = "Starter initiative created by @useorgx/wizard to validate workspace routing, live views, and the default OrgX skill pack.";
2655
+ var FOUNDER_DEMO_DECISION_TITLE = "Approve founder demo workspace";
2656
+ var FOUNDER_DEMO_DECISION_SUMMARY = "Initial decision created by @useorgx/wizard so the founder preset leaves the workspace with a resolved approval trail.";
2657
+ var FOUNDER_DEMO_DECISION_RESOLUTION = "Approved automatically by @useorgx/wizard after the founder preset finished creating the live demo workspace.";
2658
+ var FOUNDER_DEMO_ARTIFACT_NAME = "Founder Demo Live View";
2659
+ var FOUNDER_DEMO_ARTIFACT_TYPE = "document";
2660
+ var FOUNDER_DEMO_ARTIFACT_DESCRIPTION = "Live founder demo generated by @useorgx/wizard after workspace bootstrap completed.";
2661
+ var ONBOARDING_TASK_TITLE = "Complete OrgX onboarding";
2662
+ var ONBOARDING_TASK_SUMMARY = "Starter onboarding task created by @useorgx/wizard so the first workspace has a clear follow-up after setup.";
2663
+ var ONBOARDING_WORKSTREAM_TITLE = "OrgX onboarding";
2664
+ var ONBOARDING_WORKSTREAM_SUMMARY = "Starter onboarding workstream created by @useorgx/wizard so the first workspace has a home for setup follow-up tasks.";
2492
2665
  function parseResponseBody3(text2) {
2493
2666
  if (!text2) {
2494
2667
  return null;
@@ -2508,11 +2681,15 @@ function formatHttpError2(status, body) {
2508
2681
  }
2509
2682
  return `HTTP ${status}`;
2510
2683
  }
2511
- function parseInitiative(payload) {
2684
+ function extractEntity(payload) {
2512
2685
  const entity = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
2513
2686
  if (!isRecord(entity)) {
2514
- throw new Error("OrgX returned an unexpected initiative payload.");
2687
+ throw new Error("OrgX returned an unexpected entity payload.");
2515
2688
  }
2689
+ return entity;
2690
+ }
2691
+ function parseInitiative(payload) {
2692
+ const entity = extractEntity(payload);
2516
2693
  const id = typeof entity.id === "string" ? entity.id.trim() : "";
2517
2694
  const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2518
2695
  if (!id || !title) {
@@ -2524,9 +2701,69 @@ function parseInitiative(payload) {
2524
2701
  ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
2525
2702
  };
2526
2703
  }
2527
- function toDemoInitiativeRecord(initiative, liveUrl, workspace) {
2704
+ function parseDecision(payload) {
2705
+ const entity = extractEntity(payload);
2706
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2707
+ const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2708
+ if (!id || !title) {
2709
+ throw new Error("OrgX returned an incomplete decision payload.");
2710
+ }
2711
+ return {
2712
+ id,
2713
+ title,
2714
+ ...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
2715
+ ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
2716
+ };
2717
+ }
2718
+ function parseArtifact(payload) {
2719
+ const entity = extractEntity(payload);
2720
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2721
+ const name = typeof entity.name === "string" ? entity.name.trim() : typeof entity.title === "string" ? entity.title.trim() : "";
2722
+ const type = typeof entity.artifact_type === "string" ? entity.artifact_type.trim() : typeof entity.type === "string" ? entity.type.trim() : "";
2723
+ const url = typeof entity.external_url === "string" ? entity.external_url.trim() : typeof entity.url === "string" ? entity.url.trim() : "";
2724
+ if (!id || !name) {
2725
+ throw new Error("OrgX returned an incomplete artifact payload.");
2726
+ }
2727
+ return {
2728
+ id,
2729
+ name,
2730
+ ...type ? { type } : {},
2731
+ ...url ? { url } : {}
2732
+ };
2733
+ }
2734
+ function parseTask(payload) {
2735
+ const entity = extractEntity(payload);
2736
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2737
+ const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2738
+ if (!id || !title) {
2739
+ throw new Error("OrgX returned an incomplete task payload.");
2740
+ }
2741
+ return {
2742
+ id,
2743
+ title,
2744
+ ...typeof entity.status === "string" && entity.status.trim().length > 0 ? { status: entity.status.trim() } : {},
2745
+ ...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
2746
+ };
2747
+ }
2748
+ function parseWorkstream(payload) {
2749
+ const entity = extractEntity(payload);
2750
+ const id = typeof entity.id === "string" ? entity.id.trim() : "";
2751
+ const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
2752
+ if (!id || !title) {
2753
+ throw new Error("OrgX returned an incomplete workstream payload.");
2754
+ }
2755
+ return { id, title };
2756
+ }
2757
+ function toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace) {
2528
2758
  return {
2529
2759
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2760
+ artifactId: artifact.id,
2761
+ artifactName: artifact.name,
2762
+ ...artifact.type ? { artifactType: artifact.type } : {},
2763
+ ...artifact.url ? { artifactUrl: artifact.url } : {},
2764
+ decisionId: decision.id,
2765
+ ...decision.status ? { decisionStatus: decision.status } : {},
2766
+ decisionTitle: decision.title,
2530
2767
  id: initiative.id,
2531
2768
  liveUrl,
2532
2769
  title: initiative.title,
@@ -2534,6 +2771,18 @@ function toDemoInitiativeRecord(initiative, liveUrl, workspace) {
2534
2771
  workspaceName: workspace.name
2535
2772
  };
2536
2773
  }
2774
+ function toOnboardingTaskRecord(task, workspace, options = {}) {
2775
+ return {
2776
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2777
+ id: task.id,
2778
+ ...options.initiativeId ? { initiativeId: options.initiativeId } : {},
2779
+ ...task.status ? { status: task.status } : {},
2780
+ title: task.title,
2781
+ ...options.workstreamId ? { workstreamId: options.workstreamId } : {},
2782
+ workspaceId: workspace.id,
2783
+ workspaceName: workspace.name
2784
+ };
2785
+ }
2537
2786
  async function requireOrgxAuth2(options = {}) {
2538
2787
  const auth = await resolveOrgxAuth(options);
2539
2788
  if (!auth) {
@@ -2543,11 +2792,7 @@ async function requireOrgxAuth2(options = {}) {
2543
2792
  }
2544
2793
  return auth;
2545
2794
  }
2546
- async function createInitiative(input, options = {}) {
2547
- const title = input.title.trim();
2548
- if (!title) {
2549
- throw new Error("Initiative title is required.");
2550
- }
2795
+ async function createEntity(type, body, parse2, options = {}) {
2551
2796
  const auth = await requireOrgxAuth2(options);
2552
2797
  const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
2553
2798
  method: "POST",
@@ -2556,34 +2801,115 @@ async function createInitiative(input, options = {}) {
2556
2801
  "Content-Type": "application/json"
2557
2802
  },
2558
2803
  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() } : {}
2804
+ type,
2805
+ ...body
2806
+ }),
2807
+ signal: AbortSignal.timeout(7e3)
2808
+ });
2809
+ const responseBody = parseResponseBody3(await response.text());
2810
+ if (!response.ok) {
2811
+ throw new Error(`Failed to create ${type}. ${formatHttpError2(response.status, responseBody)}`);
2812
+ }
2813
+ return parse2(responseBody);
2814
+ }
2815
+ async function updateEntity(type, id, body, parse2, options = {}) {
2816
+ const auth = await requireOrgxAuth2(options);
2817
+ const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
2818
+ method: "PATCH",
2819
+ headers: {
2820
+ Authorization: `Bearer ${auth.apiKey}`,
2821
+ "Content-Type": "application/json"
2822
+ },
2823
+ body: JSON.stringify({
2824
+ type,
2825
+ id,
2826
+ ...body
2564
2827
  }),
2565
2828
  signal: AbortSignal.timeout(7e3)
2566
2829
  });
2567
- const body = parseResponseBody3(await response.text());
2830
+ const responseBody = parseResponseBody3(await response.text());
2568
2831
  if (!response.ok) {
2569
- throw new Error(`Failed to create initiative. ${formatHttpError2(response.status, body)}`);
2832
+ throw new Error(`Failed to update ${type}. ${formatHttpError2(response.status, responseBody)}`);
2833
+ }
2834
+ return parse2(responseBody);
2835
+ }
2836
+ function buildLiveUrl(baseUrl, initiativeId) {
2837
+ const parsed = new URL(normalizeOrgxBaseUrl(baseUrl));
2838
+ if (parsed.protocol === "https:" && parsed.hostname === "useorgx.com") {
2839
+ parsed.hostname = "www.useorgx.com";
2840
+ }
2841
+ parsed.pathname = `/live/${initiativeId}`;
2842
+ parsed.search = "";
2843
+ parsed.hash = "";
2844
+ return parsed.toString();
2845
+ }
2846
+ async function createFounderDemoDecision(initiative, workspaceId, options = {}) {
2847
+ return createEntity(
2848
+ "decision",
2849
+ {
2850
+ initiative_id: initiative.id,
2851
+ summary: FOUNDER_DEMO_DECISION_SUMMARY,
2852
+ title: FOUNDER_DEMO_DECISION_TITLE,
2853
+ workspace_id: workspaceId
2854
+ },
2855
+ parseDecision,
2856
+ options
2857
+ );
2858
+ }
2859
+ async function approveFounderDemoDecision(decisionId, options = {}) {
2860
+ return updateEntity(
2861
+ "decision",
2862
+ decisionId,
2863
+ {
2864
+ resolution_summary: FOUNDER_DEMO_DECISION_RESOLUTION,
2865
+ status: "approved"
2866
+ },
2867
+ parseDecision,
2868
+ options
2869
+ );
2870
+ }
2871
+ async function createFounderDemoArtifact(initiative, liveUrl, workspaceId, options = {}) {
2872
+ return createEntity(
2873
+ "artifact",
2874
+ {
2875
+ artifact_type: FOUNDER_DEMO_ARTIFACT_TYPE,
2876
+ description: FOUNDER_DEMO_ARTIFACT_DESCRIPTION,
2877
+ entity_id: initiative.id,
2878
+ entity_type: "initiative",
2879
+ external_url: liveUrl,
2880
+ initiative_id: initiative.id,
2881
+ name: FOUNDER_DEMO_ARTIFACT_NAME,
2882
+ workspace_id: workspaceId
2883
+ },
2884
+ parseArtifact,
2885
+ options
2886
+ );
2887
+ }
2888
+ async function createInitiative(input, options = {}) {
2889
+ const title = input.title.trim();
2890
+ if (!title) {
2891
+ throw new Error("Initiative title is required.");
2570
2892
  }
2571
- return parseInitiative(body);
2893
+ return createEntity(
2894
+ "initiative",
2895
+ {
2896
+ title,
2897
+ status: "active",
2898
+ ...input.summary?.trim() ? { summary: input.summary.trim() } : {},
2899
+ ...input.workspaceId?.trim() ? { workspace_id: input.workspaceId.trim() } : {}
2900
+ },
2901
+ parseInitiative,
2902
+ options
2903
+ );
2572
2904
  }
2573
2905
  async function ensureFounderDemoInitiative(workspace, options = {}) {
2574
2906
  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
- }
2907
+ const matchingRecord = existingRecord?.workspaceId === workspace.id ? existingRecord : void 0;
2585
2908
  const auth = await requireOrgxAuth2(options);
2586
- const initiative = await createInitiative(
2909
+ const initiative = matchingRecord ? {
2910
+ id: matchingRecord.id,
2911
+ title: matchingRecord.title
2912
+ } : await createInitiative(
2587
2913
  {
2588
2914
  title: FOUNDER_DEMO_INITIATIVE_TITLE,
2589
2915
  summary: FOUNDER_DEMO_INITIATIVE_SUMMARY,
@@ -2591,8 +2917,22 @@ async function ensureFounderDemoInitiative(workspace, options = {}) {
2591
2917
  },
2592
2918
  options
2593
2919
  );
2594
- const liveUrl = `${auth.baseUrl.replace(/\/+$/, "")}/live/${initiative.id}`;
2595
- const record = toDemoInitiativeRecord(initiative, liveUrl, workspace);
2920
+ const liveUrl = matchingRecord?.liveUrl || buildLiveUrl(auth.baseUrl, initiative.id);
2921
+ const decision = matchingRecord?.decisionId ? matchingRecord.decisionStatus === "approved" && matchingRecord.decisionTitle ? {
2922
+ id: matchingRecord.decisionId,
2923
+ status: matchingRecord.decisionStatus,
2924
+ title: matchingRecord.decisionTitle
2925
+ } : await approveFounderDemoDecision(matchingRecord.decisionId, options) : await approveFounderDemoDecision(
2926
+ (await createFounderDemoDecision(initiative, workspace.id, options)).id,
2927
+ options
2928
+ );
2929
+ const artifact = matchingRecord?.artifactId && matchingRecord.artifactName ? {
2930
+ id: matchingRecord.artifactId,
2931
+ name: matchingRecord.artifactName,
2932
+ ...matchingRecord.artifactType ? { type: matchingRecord.artifactType } : {},
2933
+ ...matchingRecord.artifactUrl ? { url: matchingRecord.artifactUrl } : {}
2934
+ } : await createFounderDemoArtifact(initiative, liveUrl, workspace.id, options);
2935
+ const record = toDemoInitiativeRecord(artifact, decision, initiative, liveUrl, workspace);
2596
2936
  updateWizardState(
2597
2937
  (current) => ({
2598
2938
  ...current,
@@ -2601,11 +2941,65 @@ async function ensureFounderDemoInitiative(workspace, options = {}) {
2601
2941
  options.statePath
2602
2942
  );
2603
2943
  return {
2604
- created: true,
2944
+ artifact,
2945
+ created: !matchingRecord,
2946
+ decision,
2605
2947
  initiative,
2606
2948
  liveUrl
2607
2949
  };
2608
2950
  }
2951
+ async function ensureOnboardingTask(workspace, options = {}) {
2952
+ const existingRecord = readWizardState(options.statePath)?.onboardingTask;
2953
+ if (existingRecord?.workspaceId === workspace.id) {
2954
+ return {
2955
+ id: existingRecord.id,
2956
+ title: existingRecord.title,
2957
+ ...existingRecord.status ? { status: existingRecord.status } : {}
2958
+ };
2959
+ }
2960
+ const initiativeId = options.initiativeId?.trim();
2961
+ if (!initiativeId) {
2962
+ throw new Error(
2963
+ "Starter onboarding task requires an initiative context. Re-run setup with the founder preset or create an initiative before requesting onboarding tasks."
2964
+ );
2965
+ }
2966
+ const workstream = await createEntity(
2967
+ "workstream",
2968
+ {
2969
+ initiative_id: initiativeId,
2970
+ status: "active",
2971
+ summary: ONBOARDING_WORKSTREAM_SUMMARY,
2972
+ title: ONBOARDING_WORKSTREAM_TITLE,
2973
+ workspace_id: workspace.id
2974
+ },
2975
+ parseWorkstream,
2976
+ options
2977
+ );
2978
+ const task = await createEntity(
2979
+ "task",
2980
+ {
2981
+ initiative_id: initiativeId,
2982
+ status: "todo",
2983
+ summary: ONBOARDING_TASK_SUMMARY,
2984
+ title: ONBOARDING_TASK_TITLE,
2985
+ workstream_id: workstream.id,
2986
+ workspace_id: workspace.id
2987
+ },
2988
+ parseTask,
2989
+ options
2990
+ );
2991
+ updateWizardState(
2992
+ (current) => ({
2993
+ ...current,
2994
+ onboardingTask: toOnboardingTaskRecord(task, workspace, {
2995
+ initiativeId,
2996
+ workstreamId: workstream.id
2997
+ })
2998
+ }),
2999
+ options.statePath
3000
+ );
3001
+ return task;
3002
+ }
2609
3003
 
2610
3004
  // src/lib/skills.ts
2611
3005
  import { join as join2 } from "path";
@@ -2615,6 +3009,7 @@ var DEFAULT_ORGX_SKILL_PACKS = [
2615
3009
  "bulk-create",
2616
3010
  "nightly-recap"
2617
3011
  ];
3012
+ var PLUGIN_MANAGED_SKILL_TARGETS = ["claude", "codex"];
2618
3013
  var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
2619
3014
  var ORGX_SKILLS_OWNER = "useorgx";
2620
3015
  var ORGX_SKILLS_REPO = "skills";
@@ -2627,6 +3022,8 @@ var CURSOR_RULES_CONTENT = `# OrgX Rules
2627
3022
  - Preserve \`_context\` when widget or app-rendering flows depend on client, conversation, session, or user metadata. Do not strip or rename it.
2628
3023
  - Prefer \`list_entities\` for pending approvals and state inspection before reaching for legacy aliases.
2629
3024
  - Keep hosted OrgX MCP calls distinct from local OpenClaw/plugin surfaces before mutating config or entity state.
3025
+ - Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
3026
+ - Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supplemental, not proof.
2630
3027
  `;
2631
3028
  var CLAUDE_ORGX_SKILL_CONTENT = `---
2632
3029
  name: orgx
@@ -2652,6 +3049,8 @@ This skill is for the hosted OrgX tool surface, not the local OpenClaw plugin. K
2652
3049
  - Treat \`scaffold_initiative continue_on_error\` as best-effort scaffolding. Use it only when partial creation is acceptable and inspect failures afterward.
2653
3050
  - Preserve \`_context\` when widget or app rendering depends on client, conversation, session, or user metadata.
2654
3051
  - Prefer \`list_entities\` over older aliases when you need pending decisions, blockers, or entity state.
3052
+ - Artifact proof must use durable sources: GitHub PR/commit/blob permalinks, published/public URLs, or absolute file paths / \`file://...\`.
3053
+ - Never use OrgX wrapper pages like \`/live/...\`, \`/artifacts/...\`, or \`/console/...\` as proof links. \`preview_markdown\` is supporting context only.
2655
3054
 
2656
3055
  ## Suggested Skill Packs
2657
3056
 
@@ -2742,85 +3141,972 @@ function writeManagedFile(path, content, label, sourceUrl) {
2742
3141
  writeTextFile(path, content);
2743
3142
  }
2744
3143
  return {
2745
- label,
2746
- path,
2747
- changed,
2748
- ...sourceUrl ? { sourceUrl } : {}
3144
+ label,
3145
+ path,
3146
+ changed,
3147
+ ...sourceUrl ? { sourceUrl } : {}
3148
+ };
3149
+ }
3150
+ function resolveSkillPackNames(requested = []) {
3151
+ if (requested.length === 0 || requested.includes("all")) {
3152
+ return [...DEFAULT_ORGX_SKILL_PACKS];
3153
+ }
3154
+ const seen = /* @__PURE__ */ new Set();
3155
+ const normalized = [];
3156
+ for (const name of requested) {
3157
+ const next = name.trim();
3158
+ if (!next || seen.has(next)) {
3159
+ continue;
3160
+ }
3161
+ seen.add(next);
3162
+ normalized.push(next);
3163
+ }
3164
+ return normalized;
3165
+ }
3166
+ function resolvePluginManagedSkillTargets(requested = []) {
3167
+ const seen = /* @__PURE__ */ new Set();
3168
+ const targets = [];
3169
+ for (const rawTarget of requested) {
3170
+ const target = rawTarget.trim().toLowerCase();
3171
+ if (!PLUGIN_MANAGED_SKILL_TARGETS.includes(target)) {
3172
+ continue;
3173
+ }
3174
+ if (seen.has(target)) {
3175
+ continue;
3176
+ }
3177
+ seen.add(target);
3178
+ targets.push(target);
3179
+ }
3180
+ return targets;
3181
+ }
3182
+ function planOrgxSkillsInstall(pluginTargets = []) {
3183
+ const pluginManagedTargets = resolvePluginManagedSkillTargets(pluginTargets);
3184
+ const managesClaudeSkills = pluginManagedTargets.includes("claude");
3185
+ const notes = [];
3186
+ if (managesClaudeSkills) {
3187
+ notes.push(
3188
+ "Claude Code plugin is installed, so the wizard skipped overlapping standalone Claude OrgX skill files and Claude skill-pack sync."
3189
+ );
3190
+ }
3191
+ if (pluginManagedTargets.includes("codex")) {
3192
+ notes.push(
3193
+ "Codex companion plugin bundles its own OrgX skills, so `wizard skills add` only manages standalone editor assets outside Codex."
3194
+ );
3195
+ }
3196
+ return {
3197
+ installClaudeSkillBootstrap: !managesClaudeSkills,
3198
+ installClaudeSkillPacks: !managesClaudeSkills,
3199
+ notes,
3200
+ pluginManagedTargets
3201
+ };
3202
+ }
3203
+ async function fetchAvailablePackNames(fetchImpl, ref) {
3204
+ const entries = await fetchDirectoryEntries("", fetchImpl, ref);
3205
+ return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
3206
+ }
3207
+ async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref) {
3208
+ const rootPath = skillName;
3209
+ const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
3210
+ const writes = [];
3211
+ for (const file of files) {
3212
+ const content = await fetchRemoteText(file.sourceUrl, fetchImpl);
3213
+ const relativePath = file.path.slice(`${rootPath}/`.length);
3214
+ writes.push(
3215
+ writeManagedFile(
3216
+ join2(claudeSkillsDir, skillName, relativePath),
3217
+ content,
3218
+ `${skillName}/${relativePath}`,
3219
+ file.sourceUrl
3220
+ )
3221
+ );
3222
+ }
3223
+ return {
3224
+ name: skillName,
3225
+ files: writes
3226
+ };
3227
+ }
3228
+ async function installOrgxSkills(options = {}) {
3229
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
3230
+ if (!fetchImpl) {
3231
+ throw new Error("Global fetch is unavailable in this runtime.");
3232
+ }
3233
+ const ref = options.ref ?? ORGX_SKILLS_REF;
3234
+ const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
3235
+ const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
3236
+ const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
3237
+ const plan = planOrgxSkillsInstall(options.pluginTargets ?? []);
3238
+ const requestedNames = options.skillNames ?? [];
3239
+ let skillNames;
3240
+ if (requestedNames.length === 0 || requestedNames.includes("all")) {
3241
+ try {
3242
+ skillNames = await fetchAvailablePackNames(fetchImpl, ref);
3243
+ } catch {
3244
+ skillNames = [...DEFAULT_ORGX_SKILL_PACKS];
3245
+ }
3246
+ } else {
3247
+ skillNames = resolveSkillPackNames(requestedNames);
3248
+ }
3249
+ const writes = [
3250
+ writeManagedFile(cursorRulePath, CURSOR_RULES_CONTENT, "cursor-rules")
3251
+ ];
3252
+ if (plan.installClaudeSkillBootstrap) {
3253
+ writes.push(
3254
+ writeManagedFile(claudeOrgxSkillPath, CLAUDE_ORGX_SKILL_CONTENT, "claude-orgx-skill")
3255
+ );
3256
+ }
3257
+ const packs = [];
3258
+ if (plan.installClaudeSkillPacks) {
3259
+ for (const skillName of skillNames) {
3260
+ packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref));
3261
+ }
3262
+ }
3263
+ return {
3264
+ notes: plan.notes,
3265
+ writes,
3266
+ packs
3267
+ };
3268
+ }
3269
+
3270
+ // src/lib/plugins.ts
3271
+ import { spawn } from "child_process";
3272
+ import {
3273
+ existsSync as existsSync3,
3274
+ mkdirSync as mkdirSync2,
3275
+ mkdtempSync,
3276
+ readFileSync as readFileSync2,
3277
+ readdirSync,
3278
+ rmSync,
3279
+ statSync as statSync2,
3280
+ writeFileSync as writeFileSync2
3281
+ } from "fs";
3282
+ import { tmpdir } from "os";
3283
+ import { dirname as dirname3, join as join3, relative } from "path";
3284
+ var DEFAULT_ORGX_PLUGIN_TARGETS = ["claude", "codex", "openclaw"];
3285
+ var ORGX_PLUGIN_GITHUB_OWNER = "useorgx";
3286
+ var ORGX_PLUGIN_GITHUB_REF = "main";
3287
+ var ORGX_CLAUDE_PLUGIN_NAME = "orgx-claude-code-plugin";
3288
+ var ORGX_CLAUDE_MARKETPLACE_NAME = "orgx-local";
3289
+ var ORGX_CODEX_PLUGIN_NAME = "orgx-codex-plugin";
3290
+ var ORGX_OPENCLAW_PLUGIN_ID = "orgx";
3291
+ var ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME = "@useorgx/openclaw-plugin";
3292
+ var CLAUDE_PLUGIN_SYNC_SPEC = {
3293
+ owner: ORGX_PLUGIN_GITHUB_OWNER,
3294
+ repo: ORGX_CLAUDE_PLUGIN_NAME,
3295
+ ref: ORGX_PLUGIN_GITHUB_REF,
3296
+ include: [
3297
+ { localPath: ".claude-plugin", remotePath: ".claude-plugin" },
3298
+ { localPath: "agents", remotePath: "agents" },
3299
+ { localPath: "commands", remotePath: "commands" },
3300
+ { localPath: "hooks", remotePath: "hooks" },
3301
+ { localPath: "lib", remotePath: "lib" },
3302
+ { localPath: "scripts", remotePath: "scripts" },
3303
+ { localPath: "skills", remotePath: "skills" }
3304
+ ]
3305
+ };
3306
+ var CODEX_PLUGIN_SYNC_SPEC = {
3307
+ owner: ORGX_PLUGIN_GITHUB_OWNER,
3308
+ repo: ORGX_CODEX_PLUGIN_NAME,
3309
+ ref: ORGX_PLUGIN_GITHUB_REF,
3310
+ include: [
3311
+ { localPath: ".codex-plugin", remotePath: ".codex-plugin" },
3312
+ { localPath: ".mcp.json", remotePath: ".mcp.json" },
3313
+ { localPath: "assets", remotePath: "assets" },
3314
+ { localPath: "skills", remotePath: "skills" }
3315
+ ]
3316
+ };
3317
+ function defaultPluginPaths() {
3318
+ return {
3319
+ claudeMarketplaceDir: CLAUDE_MANAGED_MARKETPLACE_DIR,
3320
+ claudeMarketplaceManifestPath: CLAUDE_MANAGED_MARKETPLACE_MANIFEST_PATH,
3321
+ claudePluginDir: CLAUDE_MANAGED_PLUGIN_DIR,
3322
+ codexMarketplacePath: CODEX_MARKETPLACE_PATH,
3323
+ codexPluginDir: CODEX_ORGX_PLUGIN_DIR
3324
+ };
3325
+ }
3326
+ function resolvePluginPaths(paths = {}) {
3327
+ return {
3328
+ ...defaultPluginPaths(),
3329
+ ...paths
3330
+ };
3331
+ }
3332
+ function isRepoDirectoryEntry(value) {
3333
+ if (!value || typeof value !== "object") return false;
3334
+ const entry = value;
3335
+ const hasType = entry.type === "file" || entry.type === "dir";
3336
+ return typeof entry.name === "string" && typeof entry.path === "string" && hasType;
3337
+ }
3338
+ function encodeRepoPath2(value) {
3339
+ return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
3340
+ }
3341
+ function isLikelyRepoFilePath(path) {
3342
+ const basename = path.split("/").pop() ?? path;
3343
+ return basename.includes(".") && !/^\.[^./]+$/.test(basename);
3344
+ }
3345
+ function buildContentsUrl2(spec, path) {
3346
+ const encodedPath = encodeRepoPath2(path);
3347
+ return `https://api.github.com/repos/${spec.owner}/${spec.repo}/contents/${encodedPath}?ref=${encodeURIComponent(spec.ref)}`;
3348
+ }
3349
+ function resolveGitHubError2(spec, status, path) {
3350
+ const repoName = `${spec.owner}/${spec.repo}`;
3351
+ if (status === 404) {
3352
+ return `Could not find '${path}' in ${repoName}.`;
3353
+ }
3354
+ return `GitHub returned HTTP ${status} while loading '${path}' from ${repoName}.`;
3355
+ }
3356
+ async function fetchDirectoryEntries2(spec, path, fetchImpl) {
3357
+ const response = await fetchImpl(buildContentsUrl2(spec, path), {
3358
+ headers: {
3359
+ Accept: "application/vnd.github+json"
3360
+ },
3361
+ signal: AbortSignal.timeout(1e4)
3362
+ });
3363
+ if (!response.ok) {
3364
+ throw new Error(resolveGitHubError2(spec, response.status, path));
3365
+ }
3366
+ const data = await response.json();
3367
+ if (!Array.isArray(data)) {
3368
+ throw new Error(`Expected '${path}' to be a directory in ${spec.owner}/${spec.repo}.`);
3369
+ }
3370
+ return data.filter(isRepoDirectoryEntry).sort((left, right) => left.path.localeCompare(right.path));
3371
+ }
3372
+ async function fetchRepoFile(spec, path, fetchImpl) {
3373
+ const response = await fetchImpl(buildContentsUrl2(spec, path), {
3374
+ headers: {
3375
+ Accept: "application/vnd.github+json"
3376
+ },
3377
+ signal: AbortSignal.timeout(1e4)
3378
+ });
3379
+ if (!response.ok) {
3380
+ throw new Error(resolveGitHubError2(spec, response.status, path));
3381
+ }
3382
+ const data = await response.json();
3383
+ if (!isRepoDirectoryEntry(data) || data.type !== "file" || typeof data.download_url !== "string") {
3384
+ throw new Error(`Expected '${path}' to be a file in ${spec.owner}/${spec.repo}.`);
3385
+ }
3386
+ return data;
3387
+ }
3388
+ async function listRemoteRepoFiles(spec, path, localPath, fetchImpl) {
3389
+ const files = [];
3390
+ if (isLikelyRepoFilePath(path) && !path.endsWith("/")) {
3391
+ const file = await fetchRepoFile(spec, path, fetchImpl);
3392
+ files.push({
3393
+ localPath,
3394
+ path: file.path,
3395
+ sourceUrl: file.download_url ?? ""
3396
+ });
3397
+ return files;
3398
+ }
3399
+ const entries = await fetchDirectoryEntries2(spec, path, fetchImpl);
3400
+ for (const entry of entries) {
3401
+ if (entry.type === "dir") {
3402
+ files.push(
3403
+ ...await listRemoteRepoFiles(
3404
+ spec,
3405
+ entry.path,
3406
+ join3(localPath, entry.name),
3407
+ fetchImpl
3408
+ )
3409
+ );
3410
+ continue;
3411
+ }
3412
+ if (!entry.download_url) {
3413
+ throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
3414
+ }
3415
+ files.push({
3416
+ localPath: join3(localPath, entry.name),
3417
+ path: entry.path,
3418
+ sourceUrl: entry.download_url
3419
+ });
3420
+ }
3421
+ return files;
3422
+ }
3423
+ async function collectRemoteRepoFiles(spec, fetchImpl) {
3424
+ const files = [];
3425
+ for (const entry of spec.include) {
3426
+ const isFile = isLikelyRepoFilePath(entry.remotePath) && !entry.remotePath.endsWith("/");
3427
+ if (isFile) {
3428
+ const file = await fetchRepoFile(spec, entry.remotePath, fetchImpl);
3429
+ files.push({
3430
+ localPath: entry.localPath,
3431
+ path: file.path,
3432
+ sourceUrl: file.download_url ?? ""
3433
+ });
3434
+ continue;
3435
+ }
3436
+ files.push(...await listRemoteRepoFiles(spec, entry.remotePath, entry.localPath, fetchImpl));
3437
+ }
3438
+ return files.sort((left, right) => left.localPath.localeCompare(right.localPath));
3439
+ }
3440
+ async function fetchRemoteBytes(sourceUrl, fetchImpl) {
3441
+ const response = await fetchImpl(sourceUrl, {
3442
+ headers: {
3443
+ Accept: "application/octet-stream"
3444
+ },
3445
+ signal: AbortSignal.timeout(1e4)
3446
+ });
3447
+ if (!response.ok) {
3448
+ throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
3449
+ }
3450
+ return Buffer.from(await response.arrayBuffer());
3451
+ }
3452
+ function readBytesIfExists(path) {
3453
+ if (!existsSync3(path)) return null;
3454
+ try {
3455
+ if (!statSync2(path).isFile()) {
3456
+ return null;
3457
+ }
3458
+ return readFileSync2(path);
3459
+ } catch (error) {
3460
+ const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
3461
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
3462
+ return null;
3463
+ }
3464
+ throw error;
3465
+ }
3466
+ }
3467
+ function writeBytesIfChanged(path, bytes) {
3468
+ const existing = readBytesIfExists(path);
3469
+ if (existing && Buffer.compare(existing, bytes) === 0) {
3470
+ return false;
3471
+ }
3472
+ mkdirSync2(dirname3(path), { recursive: true });
3473
+ writeFileSync2(path, bytes);
3474
+ return true;
3475
+ }
3476
+ function removePathIfExists(path) {
3477
+ if (!existsSync3(path)) return false;
3478
+ rmSync(path, { force: true, recursive: true });
3479
+ return true;
3480
+ }
3481
+ function listRelativeFiles(root, base = root) {
3482
+ if (!existsSync3(root)) return [];
3483
+ if (!statSync2(root).isDirectory()) {
3484
+ return [];
3485
+ }
3486
+ const files = [];
3487
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
3488
+ const nextPath = join3(root, entry.name);
3489
+ if (entry.isDirectory()) {
3490
+ files.push(...listRelativeFiles(nextPath, base));
3491
+ continue;
3492
+ }
3493
+ if (entry.isFile()) {
3494
+ files.push(relative(base, nextPath));
3495
+ }
3496
+ }
3497
+ return files.sort();
3498
+ }
3499
+ function pruneEmptyDirectories(root, current = root) {
3500
+ if (!existsSync3(current) || !statSync2(current).isDirectory()) {
3501
+ return false;
3502
+ }
3503
+ let changed = false;
3504
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
3505
+ if (!entry.isDirectory()) continue;
3506
+ changed = pruneEmptyDirectories(root, join3(current, entry.name)) || changed;
3507
+ }
3508
+ if (current !== root && readdirSync(current).length === 0) {
3509
+ rmSync(current, { force: true, recursive: true });
3510
+ return true;
3511
+ }
3512
+ return changed;
3513
+ }
3514
+ async function syncManagedRepoTree(spec, destinationRoot, fetchImpl) {
3515
+ const remoteFiles = await collectRemoteRepoFiles(spec, fetchImpl);
3516
+ let changed = false;
3517
+ const expected = new Set(remoteFiles.map((file) => file.localPath));
3518
+ if (existsSync3(destinationRoot) && !statSync2(destinationRoot).isDirectory()) {
3519
+ rmSync(destinationRoot, { force: true, recursive: true });
3520
+ changed = true;
3521
+ }
3522
+ for (const file of listRelativeFiles(destinationRoot)) {
3523
+ if (expected.has(file)) continue;
3524
+ rmSync(join3(destinationRoot, file), { force: true });
3525
+ changed = true;
3526
+ }
3527
+ changed = pruneEmptyDirectories(destinationRoot) || changed;
3528
+ for (const file of remoteFiles) {
3529
+ const bytes = await fetchRemoteBytes(file.sourceUrl, fetchImpl);
3530
+ if (writeBytesIfChanged(join3(destinationRoot, file.localPath), bytes)) {
3531
+ changed = true;
3532
+ }
3533
+ }
3534
+ return { changed, fileCount: remoteFiles.length };
3535
+ }
3536
+ function serializeJson(value) {
3537
+ return `${JSON.stringify(value, null, 2)}
3538
+ `;
3539
+ }
3540
+ function writeJsonIfChanged(path, value) {
3541
+ const next = serializeJson(value);
3542
+ const existing = readTextIfExists(path);
3543
+ if (existing === next) {
3544
+ return false;
3545
+ }
3546
+ mkdirSync2(dirname3(path), { recursive: true });
3547
+ writeFileSync2(path, next, "utf8");
3548
+ return true;
3549
+ }
3550
+ function buildClaudeMarketplaceManifest() {
3551
+ return {
3552
+ $schema: "https://anthropic.com/claude-code/marketplace.schema.json",
3553
+ name: ORGX_CLAUDE_MARKETPLACE_NAME,
3554
+ description: "Local OrgX plugins",
3555
+ owner: {
3556
+ name: "OrgX Team"
3557
+ },
3558
+ plugins: [
3559
+ {
3560
+ name: ORGX_CLAUDE_PLUGIN_NAME,
3561
+ description: "OrgX MCP tools and runtime telemetry hooks for Claude Code.",
3562
+ source: `./plugins/${ORGX_CLAUDE_PLUGIN_NAME}`
3563
+ }
3564
+ ]
3565
+ };
3566
+ }
3567
+ function buildCodexMarketplaceEntry() {
3568
+ return {
3569
+ name: ORGX_CODEX_PLUGIN_NAME,
3570
+ source: {
3571
+ source: "local",
3572
+ path: `./.codex/plugins/${ORGX_CODEX_PLUGIN_NAME}`
3573
+ },
3574
+ policy: {
3575
+ installation: "AVAILABLE",
3576
+ authentication: "ON_INSTALL"
3577
+ },
3578
+ category: "Productivity"
3579
+ };
3580
+ }
3581
+ function readMarketplacePlugins(path) {
3582
+ const document = parseJsonObject(readTextIfExists(path));
3583
+ const plugins = Array.isArray(document.plugins) ? document.plugins.filter((value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)) : [];
3584
+ return { document, plugins };
3585
+ }
3586
+ function upsertCodexMarketplaceEntry(path) {
3587
+ const { document, plugins } = readMarketplacePlugins(path);
3588
+ const nextEntry = buildCodexMarketplaceEntry();
3589
+ let replaced = false;
3590
+ const nextPlugins = plugins.map((plugin) => {
3591
+ if (plugin.name !== ORGX_CODEX_PLUGIN_NAME) {
3592
+ return plugin;
3593
+ }
3594
+ replaced = true;
3595
+ return nextEntry;
3596
+ });
3597
+ if (!replaced) {
3598
+ nextPlugins.push(nextEntry);
3599
+ }
3600
+ const nextDocument = {
3601
+ ...document,
3602
+ ...document.name ? {} : { name: ORGX_CLAUDE_MARKETPLACE_NAME },
3603
+ ...readObject(document.interface).displayName ? {} : { interface: { displayName: "OrgX Local" } },
3604
+ plugins: nextPlugins
3605
+ };
3606
+ return writeJsonIfChanged(path, nextDocument);
3607
+ }
3608
+ function removeCodexMarketplaceEntry(path) {
3609
+ if (!existsSync3(path)) {
3610
+ return false;
3611
+ }
3612
+ const { document, plugins } = readMarketplacePlugins(path);
3613
+ const nextPlugins = plugins.filter((plugin) => plugin.name !== ORGX_CODEX_PLUGIN_NAME);
3614
+ if (nextPlugins.length === plugins.length) {
3615
+ return false;
3616
+ }
3617
+ if (nextPlugins.length === 0) {
3618
+ rmSync(path, { force: true });
3619
+ return true;
3620
+ }
3621
+ return writeJsonIfChanged(path, {
3622
+ ...document,
3623
+ plugins: nextPlugins
3624
+ });
3625
+ }
3626
+ function codexMarketplaceHasOrgxEntry(path) {
3627
+ const { plugins } = readMarketplacePlugins(path);
3628
+ return plugins.some((plugin) => plugin.name === ORGX_CODEX_PLUGIN_NAME);
3629
+ }
3630
+ function extractClaudePluginNames(payload) {
3631
+ try {
3632
+ const parsed = JSON.parse(payload);
3633
+ if (!Array.isArray(parsed)) return [];
3634
+ return parsed.flatMap((entry) => {
3635
+ if (typeof entry === "string") return [entry];
3636
+ if (!entry || typeof entry !== "object") return [];
3637
+ if (typeof entry.name === "string") return [entry.name];
3638
+ if (typeof entry.id === "string") return [entry.id];
3639
+ return [];
3640
+ });
3641
+ } catch {
3642
+ return [];
3643
+ }
3644
+ }
3645
+ function extractOpenclawPluginIds(payload) {
3646
+ try {
3647
+ const parsed = JSON.parse(payload);
3648
+ if (Array.isArray(parsed)) {
3649
+ return parsed.flatMap((entry) => {
3650
+ if (!entry || typeof entry !== "object") return [];
3651
+ if (typeof entry.id === "string") return [entry.id];
3652
+ if (typeof entry.name === "string") return [entry.name];
3653
+ return [];
3654
+ });
3655
+ }
3656
+ const root = readObject(parsed);
3657
+ const plugins = Array.isArray(root.plugins) ? root.plugins : [];
3658
+ return plugins.flatMap((entry) => {
3659
+ if (!entry || typeof entry !== "object") return [];
3660
+ const item = entry;
3661
+ if (typeof item.id === "string") return [item.id];
3662
+ if (typeof item.name === "string") return [item.name];
3663
+ return [];
3664
+ });
3665
+ } catch {
3666
+ return [];
3667
+ }
3668
+ }
3669
+ function formatCommandFailure(command, args, result) {
3670
+ const detail = [result.stderr.trim(), result.stdout.trim()].find((value) => value.length > 0);
3671
+ if (result.errorCode === "ENOENT") {
3672
+ return `Command '${command}' is not available on PATH.`;
3673
+ }
3674
+ return `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
3675
+ }
3676
+ async function defaultCommandRunner(command, args) {
3677
+ return await new Promise((resolve) => {
3678
+ const child = spawn(command, [...args], {
3679
+ env: process.env,
3680
+ stdio: ["ignore", "pipe", "pipe"]
3681
+ });
3682
+ let stdout = "";
3683
+ let stderr = "";
3684
+ child.stdout.on("data", (chunk) => {
3685
+ stdout += chunk.toString();
3686
+ });
3687
+ child.stderr.on("data", (chunk) => {
3688
+ stderr += chunk.toString();
3689
+ });
3690
+ child.on("error", (error) => {
3691
+ const errorCode = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
3692
+ resolve({
3693
+ exitCode: -1,
3694
+ stdout,
3695
+ stderr,
3696
+ ...errorCode ? { errorCode } : {}
3697
+ });
3698
+ });
3699
+ child.on("close", (code) => {
3700
+ resolve({
3701
+ exitCode: code ?? -1,
3702
+ stdout,
3703
+ stderr
3704
+ });
3705
+ });
3706
+ });
3707
+ }
3708
+ function resolveCommandRunner(runner) {
3709
+ return runner ?? defaultCommandRunner;
3710
+ }
3711
+ async function commandExists(command, runner) {
3712
+ const result = await runner(command, ["--help"]);
3713
+ return result.errorCode !== "ENOENT" && result.exitCode !== 127;
3714
+ }
3715
+ async function getClaudeInstallState(runner) {
3716
+ const available = detectSurface("claude").detected || await commandExists("claude", runner);
3717
+ if (!available) {
3718
+ return { available: false, installed: false };
3719
+ }
3720
+ const result = await runner("claude", ["plugin", "list", "--json"]);
3721
+ if (result.exitCode !== 0) {
3722
+ return { available: true, installed: false };
3723
+ }
3724
+ return {
3725
+ available: true,
3726
+ installed: extractClaudePluginNames(result.stdout).includes(ORGX_CLAUDE_PLUGIN_NAME)
3727
+ };
3728
+ }
3729
+ async function getOpenclawInstallState(runner) {
3730
+ const available = detectSurface("openclaw").detected || await commandExists("openclaw", runner);
3731
+ if (!available) {
3732
+ return { available: false, installed: false };
3733
+ }
3734
+ const result = await runner("openclaw", ["plugins", "list", "--json"]);
3735
+ if (result.exitCode !== 0) {
3736
+ return { available: true, installed: false };
3737
+ }
3738
+ return {
3739
+ available: true,
3740
+ installed: extractOpenclawPluginIds(result.stdout).includes(ORGX_OPENCLAW_PLUGIN_ID)
3741
+ };
3742
+ }
3743
+ async function buildClaudeStatus(paths, runner) {
3744
+ const state = await getClaudeInstallState(runner);
3745
+ if (state.installed) {
3746
+ return {
3747
+ target: "claude",
3748
+ available: true,
3749
+ installed: true,
3750
+ message: "Claude Code plugin is installed."
3751
+ };
3752
+ }
3753
+ if (!state.available) {
3754
+ return {
3755
+ target: "claude",
3756
+ available: false,
3757
+ installed: false,
3758
+ message: "Claude Code was not detected."
3759
+ };
3760
+ }
3761
+ const marketplaceExists = existsSync3(paths.claudeMarketplaceManifestPath);
3762
+ return {
3763
+ target: "claude",
3764
+ available: true,
3765
+ installed: false,
3766
+ message: marketplaceExists ? "Claude marketplace wrapper exists, but the plugin is not installed." : "Claude Code is available and ready for plugin install."
3767
+ };
3768
+ }
3769
+ function buildCodexStatus(paths, runner) {
3770
+ return (async () => {
3771
+ const available = detectSurface("codex").detected || await commandExists("codex", runner);
3772
+ const pluginExists = existsSync3(paths.codexPluginDir);
3773
+ const marketplaceExists = codexMarketplaceHasOrgxEntry(paths.codexMarketplacePath);
3774
+ const installed = pluginExists && marketplaceExists;
3775
+ if (installed) {
3776
+ return {
3777
+ target: "codex",
3778
+ available: true,
3779
+ installed: true,
3780
+ message: "Codex plugin files and marketplace entry are present."
3781
+ };
3782
+ }
3783
+ if (!available) {
3784
+ return {
3785
+ target: "codex",
3786
+ available: false,
3787
+ installed: false,
3788
+ message: "Codex was not detected."
3789
+ };
3790
+ }
3791
+ if (pluginExists && !marketplaceExists) {
3792
+ return {
3793
+ target: "codex",
3794
+ available: true,
3795
+ installed: false,
3796
+ message: "Codex plugin files exist, but the marketplace entry is missing."
3797
+ };
3798
+ }
3799
+ if (!pluginExists && marketplaceExists) {
3800
+ return {
3801
+ target: "codex",
3802
+ available: true,
3803
+ installed: false,
3804
+ message: "Codex marketplace entry exists, but plugin files are missing."
3805
+ };
3806
+ }
3807
+ return {
3808
+ target: "codex",
3809
+ available: true,
3810
+ installed: false,
3811
+ message: "Codex is available and ready for plugin install."
3812
+ };
3813
+ })();
3814
+ }
3815
+ async function buildOpenclawStatus(runner) {
3816
+ const state = await getOpenclawInstallState(runner);
3817
+ if (state.installed) {
3818
+ return {
3819
+ target: "openclaw",
3820
+ available: true,
3821
+ installed: true,
3822
+ message: "OpenClaw plugin is installed."
3823
+ };
3824
+ }
3825
+ return {
3826
+ target: "openclaw",
3827
+ available: state.available,
3828
+ installed: false,
3829
+ message: state.available ? "OpenClaw is available and ready for plugin install." : "OpenClaw was not detected."
3830
+ };
3831
+ }
3832
+ async function resolveOpenclawTarball(fetchImpl) {
3833
+ const response = await fetchImpl(
3834
+ `${NPM_REGISTRY_BASE_URL}/${encodeURIComponent(ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME)}`,
3835
+ {
3836
+ headers: {
3837
+ Accept: "application/json"
3838
+ },
3839
+ signal: AbortSignal.timeout(1e4)
3840
+ }
3841
+ );
3842
+ if (!response.ok) {
3843
+ throw new Error(
3844
+ `npm registry returned HTTP ${response.status} while resolving ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`
3845
+ );
3846
+ }
3847
+ const data = await response.json();
3848
+ const latestVersion = data["dist-tags"]?.latest;
3849
+ const tarballUrl = latestVersion ? data.versions?.[latestVersion]?.dist?.tarball : void 0;
3850
+ if (!latestVersion || !tarballUrl) {
3851
+ throw new Error(`Could not resolve a tarball URL for ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}.`);
3852
+ }
3853
+ return {
3854
+ tarballUrl,
3855
+ version: latestVersion
3856
+ };
3857
+ }
3858
+ async function installClaudePlugin(paths, fetchImpl, runner) {
3859
+ const state = await getClaudeInstallState(runner);
3860
+ if (!state.available) {
3861
+ return {
3862
+ target: "claude",
3863
+ changed: false,
3864
+ message: "Claude Code is not available on this machine."
3865
+ };
3866
+ }
3867
+ const syncResult = await syncManagedRepoTree(CLAUDE_PLUGIN_SYNC_SPEC, paths.claudePluginDir, fetchImpl);
3868
+ const manifestChanged = writeJsonIfChanged(
3869
+ paths.claudeMarketplaceManifestPath,
3870
+ buildClaudeMarketplaceManifest()
3871
+ );
3872
+ const marketplaceAdd = await runner("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir]);
3873
+ if (marketplaceAdd.exitCode !== 0) {
3874
+ throw new Error(formatCommandFailure("claude", ["plugin", "marketplace", "add", paths.claudeMarketplaceDir], marketplaceAdd));
3875
+ }
3876
+ let installedChanged = false;
3877
+ if (!state.installed) {
3878
+ const install = await runner("claude", [
3879
+ "plugin",
3880
+ "install",
3881
+ `${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
3882
+ "--scope",
3883
+ "user"
3884
+ ]);
3885
+ if (install.exitCode !== 0) {
3886
+ throw new Error(
3887
+ formatCommandFailure(
3888
+ "claude",
3889
+ [
3890
+ "plugin",
3891
+ "install",
3892
+ `${ORGX_CLAUDE_PLUGIN_NAME}@${ORGX_CLAUDE_MARKETPLACE_NAME}`,
3893
+ "--scope",
3894
+ "user"
3895
+ ],
3896
+ install
3897
+ )
3898
+ );
3899
+ }
3900
+ installedChanged = true;
3901
+ }
3902
+ const changed = syncResult.changed || manifestChanged || installedChanged;
3903
+ return {
3904
+ target: "claude",
3905
+ changed,
3906
+ message: changed ? `Synced ${syncResult.fileCount} Claude plugin files and ensured the plugin is installed.` : "Claude Code plugin is already installed and up to date."
3907
+ };
3908
+ }
3909
+ async function installCodexPlugin(paths, fetchImpl, runner) {
3910
+ const available = detectSurface("codex").detected || await commandExists("codex", runner);
3911
+ if (!available) {
3912
+ return {
3913
+ target: "codex",
3914
+ changed: false,
3915
+ message: "Codex is not available on this machine."
3916
+ };
3917
+ }
3918
+ const syncResult = await syncManagedRepoTree(CODEX_PLUGIN_SYNC_SPEC, paths.codexPluginDir, fetchImpl);
3919
+ const marketplaceChanged = upsertCodexMarketplaceEntry(paths.codexMarketplacePath);
3920
+ const changed = syncResult.changed || marketplaceChanged;
3921
+ return {
3922
+ target: "codex",
3923
+ changed,
3924
+ 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."
3925
+ };
3926
+ }
3927
+ async function installOpenclawPlugin(fetchImpl, runner) {
3928
+ const state = await getOpenclawInstallState(runner);
3929
+ if (!state.available) {
3930
+ return {
3931
+ target: "openclaw",
3932
+ changed: false,
3933
+ message: "OpenClaw is not available on this machine."
3934
+ };
3935
+ }
3936
+ if (state.installed) {
3937
+ return {
3938
+ target: "openclaw",
3939
+ changed: false,
3940
+ message: "OpenClaw plugin is already installed."
3941
+ };
3942
+ }
3943
+ const { tarballUrl, version } = await resolveOpenclawTarball(fetchImpl);
3944
+ const tarballBytes = await fetchRemoteBytes(tarballUrl, fetchImpl);
3945
+ const tempRoot = mkdtempSync(join3(tmpdir(), "orgx-wizard-openclaw-"));
3946
+ const archivePath = join3(tempRoot, `orgx-openclaw-plugin-${version}.tgz`);
3947
+ try {
3948
+ writeFileSync2(archivePath, tarballBytes);
3949
+ const install = await runner("openclaw", ["plugins", "install", archivePath]);
3950
+ if (install.exitCode !== 0) {
3951
+ throw new Error(
3952
+ formatCommandFailure("openclaw", ["plugins", "install", archivePath], install)
3953
+ );
3954
+ }
3955
+ } finally {
3956
+ rmSync(tempRoot, { force: true, recursive: true });
3957
+ }
3958
+ return {
3959
+ target: "openclaw",
3960
+ changed: true,
3961
+ message: `Installed OpenClaw plugin ${ORGX_OPENCLAW_PLUGIN_ID} from ${ORGX_OPENCLAW_PLUGIN_PACKAGE_NAME}@${version}.`
3962
+ };
3963
+ }
3964
+ async function uninstallClaudePlugin(paths, runner) {
3965
+ const state = await getClaudeInstallState(runner);
3966
+ let changed = false;
3967
+ if (state.available && state.installed) {
3968
+ const uninstall = await runner("claude", [
3969
+ "plugin",
3970
+ "uninstall",
3971
+ ORGX_CLAUDE_PLUGIN_NAME,
3972
+ "--scope",
3973
+ "user"
3974
+ ]);
3975
+ if (uninstall.exitCode !== 0) {
3976
+ throw new Error(
3977
+ formatCommandFailure(
3978
+ "claude",
3979
+ ["plugin", "uninstall", ORGX_CLAUDE_PLUGIN_NAME, "--scope", "user"],
3980
+ uninstall
3981
+ )
3982
+ );
3983
+ }
3984
+ changed = true;
3985
+ }
3986
+ if (state.available) {
3987
+ const removeMarketplace = await runner("claude", ["plugin", "marketplace", "remove", ORGX_CLAUDE_MARKETPLACE_NAME]);
3988
+ if (removeMarketplace.exitCode === 0) {
3989
+ changed = true;
3990
+ }
3991
+ }
3992
+ changed = removePathIfExists(paths.claudeMarketplaceDir) || changed;
3993
+ return {
3994
+ target: "claude",
3995
+ changed,
3996
+ message: changed ? "Removed the managed Claude Code plugin and marketplace wrapper." : "Claude Code plugin was not installed."
3997
+ };
3998
+ }
3999
+ async function uninstallCodexPlugin(paths) {
4000
+ const removedPluginDir = removePathIfExists(paths.codexPluginDir);
4001
+ const removedMarketplaceEntry = removeCodexMarketplaceEntry(paths.codexMarketplacePath);
4002
+ const changed = removedPluginDir || removedMarketplaceEntry;
4003
+ return {
4004
+ target: "codex",
4005
+ changed,
4006
+ message: changed ? "Removed the managed Codex plugin files and marketplace entry." : "Codex plugin was not installed."
4007
+ };
4008
+ }
4009
+ async function uninstallOpenclawPlugin(runner) {
4010
+ const state = await getOpenclawInstallState(runner);
4011
+ if (!state.available || !state.installed) {
4012
+ return {
4013
+ target: "openclaw",
4014
+ changed: false,
4015
+ message: "OpenClaw plugin was not installed."
4016
+ };
4017
+ }
4018
+ const uninstall = await runner("openclaw", ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"]);
4019
+ if (uninstall.exitCode !== 0) {
4020
+ throw new Error(
4021
+ formatCommandFailure(
4022
+ "openclaw",
4023
+ ["plugins", "uninstall", ORGX_OPENCLAW_PLUGIN_ID, "--force"],
4024
+ uninstall
4025
+ )
4026
+ );
4027
+ }
4028
+ return {
4029
+ target: "openclaw",
4030
+ changed: true,
4031
+ message: "Removed the managed OpenClaw plugin."
2749
4032
  };
2750
4033
  }
2751
- function resolveSkillPackNames(requested = []) {
4034
+ function resolvePluginTargets(requested = []) {
2752
4035
  if (requested.length === 0 || requested.includes("all")) {
2753
- return [...DEFAULT_ORGX_SKILL_PACKS];
4036
+ return [...DEFAULT_ORGX_PLUGIN_TARGETS];
2754
4037
  }
2755
4038
  const seen = /* @__PURE__ */ new Set();
2756
4039
  const normalized = [];
2757
- for (const name of requested) {
2758
- const next = name.trim();
2759
- if (!next || seen.has(next)) {
2760
- continue;
4040
+ for (const rawTarget of requested) {
4041
+ const target = rawTarget.trim().toLowerCase();
4042
+ if (target === "" || target === "all") continue;
4043
+ if (!DEFAULT_ORGX_PLUGIN_TARGETS.includes(target)) {
4044
+ throw new Error(
4045
+ `Unknown plugin target '${rawTarget}'. Supported targets: ${DEFAULT_ORGX_PLUGIN_TARGETS.join(", ")}.`
4046
+ );
2761
4047
  }
2762
- seen.add(next);
2763
- normalized.push(next);
4048
+ const nextTarget = target;
4049
+ if (seen.has(nextTarget)) continue;
4050
+ seen.add(nextTarget);
4051
+ normalized.push(nextTarget);
2764
4052
  }
2765
4053
  return normalized;
2766
4054
  }
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 = {}) {
4055
+ async function listOrgxPluginStatuses(options = {}) {
4056
+ const paths = resolvePluginPaths(options.paths);
4057
+ const runner = resolveCommandRunner(options.commandRunner);
4058
+ return await Promise.all([
4059
+ buildClaudeStatus(paths, runner),
4060
+ buildCodexStatus(paths, runner),
4061
+ buildOpenclawStatus(runner)
4062
+ ]);
4063
+ }
4064
+ async function installOrgxPlugins(options = {}) {
4065
+ const targets = resolvePluginTargets(options.targets ?? []);
2793
4066
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
2794
4067
  if (!fetchImpl) {
2795
4068
  throw new Error("Global fetch is unavailable in this runtime.");
2796
4069
  }
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];
4070
+ const paths = resolvePluginPaths(options.paths);
4071
+ const runner = resolveCommandRunner(options.commandRunner);
4072
+ const results = [];
4073
+ for (const target of targets) {
4074
+ switch (target) {
4075
+ case "claude":
4076
+ results.push(await installClaudePlugin(paths, fetchImpl, runner));
4077
+ break;
4078
+ case "codex":
4079
+ results.push(await installCodexPlugin(paths, fetchImpl, runner));
4080
+ break;
4081
+ case "openclaw":
4082
+ results.push(await installOpenclawPlugin(fetchImpl, runner));
4083
+ break;
2808
4084
  }
2809
- } else {
2810
- skillNames = resolveSkillPackNames(requestedNames);
2811
4085
  }
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));
4086
+ return { results };
4087
+ }
4088
+ async function uninstallOrgxPlugins(options = {}) {
4089
+ const targets = resolvePluginTargets(options.targets ?? []);
4090
+ const paths = resolvePluginPaths(options.paths);
4091
+ const runner = resolveCommandRunner(options.commandRunner);
4092
+ const results = [];
4093
+ for (const target of targets) {
4094
+ switch (target) {
4095
+ case "claude":
4096
+ results.push(await uninstallClaudePlugin(paths, runner));
4097
+ break;
4098
+ case "codex":
4099
+ results.push(await uninstallCodexPlugin(paths));
4100
+ break;
4101
+ case "openclaw":
4102
+ results.push(await uninstallOpenclawPlugin(runner));
4103
+ break;
4104
+ }
2819
4105
  }
2820
- return {
2821
- writes,
2822
- packs
2823
- };
4106
+ return { results };
4107
+ }
4108
+ function countPluginReportChanges(report) {
4109
+ return report.results.filter((result) => result.changed).length;
2824
4110
  }
2825
4111
 
2826
4112
  // src/lib/setup-workspace.ts
@@ -3010,7 +4296,9 @@ async function runWorkspaceSetup(client, prompts, options) {
3010
4296
  // src/lib/founder-preset.ts
3011
4297
  async function runFounderPreset(prompts, options) {
3012
4298
  const surfaceResults = await setupDetectedSurfaces();
4299
+ const pluginTargets = options.pluginTargets ?? (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
3013
4300
  const skillReport = await installOrgxSkills({
4301
+ pluginTargets,
3014
4302
  skillNames: [...DEFAULT_ORGX_SKILL_PACKS]
3015
4303
  });
3016
4304
  let workspaceSetup;
@@ -3058,6 +4346,179 @@ function summarizeMutationResults(results) {
3058
4346
  }));
3059
4347
  }
3060
4348
 
4349
+ // src/lib/setup-verification.ts
4350
+ var HOSTED_MCP_OUTAGE_TITLE = "Hosted OrgX MCP is unreachable.";
4351
+ function getSetupVerificationHeadline(summary) {
4352
+ switch (summary.status) {
4353
+ case "degraded":
4354
+ return "Hosted OrgX MCP is down; local setup can still continue.";
4355
+ case "error":
4356
+ return "Issues detected";
4357
+ case "warning":
4358
+ return "Warnings detected";
4359
+ case "ok":
4360
+ return "All systems ready.";
4361
+ }
4362
+ }
4363
+ function summarizeSetupVerification(assessment) {
4364
+ const errors = assessment.issues.filter((issue) => issue.level === "error");
4365
+ if (errors.length === 0) {
4366
+ return {
4367
+ hostedMcpDegraded: false,
4368
+ status: assessment.issues.length > 0 ? "warning" : "ok"
4369
+ };
4370
+ }
4371
+ const hostedMcpDegraded = errors.every((issue) => issue.title === HOSTED_MCP_OUTAGE_TITLE);
4372
+ return {
4373
+ hostedMcpDegraded,
4374
+ status: hostedMcpDegraded ? "degraded" : "error"
4375
+ };
4376
+ }
4377
+
4378
+ // src/lib/telemetry-events.ts
4379
+ function withBaseProperties(properties, base) {
4380
+ return {
4381
+ ...base,
4382
+ ...properties
4383
+ };
4384
+ }
4385
+ function buildWorkspaceSetupTelemetryProperties(result, base = {}) {
4386
+ return withBaseProperties(
4387
+ {
4388
+ created: result.created === true,
4389
+ default_changed: result.defaultChanged === true,
4390
+ has_workspace: Boolean(result.workspace),
4391
+ status: result.status
4392
+ },
4393
+ base
4394
+ );
4395
+ }
4396
+ function buildOnboardingTaskTelemetryProperties(input, base = {}) {
4397
+ return withBaseProperties(
4398
+ {
4399
+ created: input.created,
4400
+ has_initiative: input.hasInitiative,
4401
+ task_status: input.task.status ?? "unknown"
4402
+ },
4403
+ base
4404
+ );
4405
+ }
4406
+ function buildAgentRosterTelemetryProperties(input, base = {}) {
4407
+ return withBaseProperties(
4408
+ {
4409
+ agent_count: input.roster.agents.length,
4410
+ domain_count: new Set(input.roster.agents.map((agent) => agent.domain)).size,
4411
+ strategy: input.strategy
4412
+ },
4413
+ base
4414
+ );
4415
+ }
4416
+ function buildFounderDemoTelemetryProperties(result, base = {}) {
4417
+ return withBaseProperties(
4418
+ {
4419
+ created: result.created,
4420
+ decision_status: result.decision.status ?? "unknown",
4421
+ has_artifact_url: Boolean(result.artifact.url)
4422
+ },
4423
+ base
4424
+ );
4425
+ }
4426
+ function buildDoctorTelemetryProperties(report, assessment, verification, base = {}) {
4427
+ const errorCount = assessment.issues.filter((issue) => issue.level === "error").length;
4428
+ const warningCount = assessment.issues.filter((issue) => issue.level === "warning").length;
4429
+ return withBaseProperties(
4430
+ {
4431
+ auth_configured: report.auth.configured,
4432
+ auth_ok: report.auth.ok,
4433
+ configured_surface_count: report.surfaces.filter((surface) => surface.configured).length,
4434
+ detected_surface_count: report.surfaces.filter((surface) => surface.detected).length,
4435
+ error_count: errorCount,
4436
+ hosted_mcp_degraded: verification.hostedMcpDegraded,
4437
+ hosted_mcp_ok: report.hostedMcp.ok,
4438
+ hosted_mcp_tool_ok: report.hostedMcpTool.ok,
4439
+ issue_count: assessment.issues.length,
4440
+ openclaw_available: !report.openclaw.skipped,
4441
+ openclaw_ok: report.openclaw.ok,
4442
+ status: verification.status,
4443
+ warning_count: warningCount,
4444
+ workspace_configured: report.workspace.configured,
4445
+ workspace_ok: report.workspace.ok
4446
+ },
4447
+ base
4448
+ );
4449
+ }
4450
+
4451
+ // src/lib/telemetry.ts
4452
+ var POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com";
4453
+ var WIZARD_LIB = "@useorgx/wizard";
4454
+ function isTruthyEnv(value) {
4455
+ if (!value) return false;
4456
+ switch (value.trim().toLowerCase()) {
4457
+ case "1":
4458
+ case "true":
4459
+ case "yes":
4460
+ case "y":
4461
+ case "on":
4462
+ return true;
4463
+ default:
4464
+ return false;
4465
+ }
4466
+ }
4467
+ function isWizardTelemetryDisabled() {
4468
+ const explicitEnable = isTruthyEnv(process.env.ORGX_TELEMETRY_ENABLED);
4469
+ if (!explicitEnable) return true;
4470
+ return isTruthyEnv(process.env.ORGX_TELEMETRY_DISABLED) || isTruthyEnv(process.env.OPENCLAW_TELEMETRY_DISABLED) || isTruthyEnv(process.env.POSTHOG_DISABLED);
4471
+ }
4472
+ function resolvePosthogApiKey() {
4473
+ const value = process.env.ORGX_POSTHOG_API_KEY ?? process.env.POSTHOG_API_KEY ?? process.env.ORGX_POSTHOG_KEY ?? process.env.POSTHOG_KEY ?? "";
4474
+ const trimmed = value.trim();
4475
+ return trimmed || null;
4476
+ }
4477
+ function resolvePosthogHost() {
4478
+ const value = process.env.ORGX_POSTHOG_HOST ?? process.env.POSTHOG_HOST ?? process.env.ORGX_POSTHOG_API_HOST ?? process.env.POSTHOG_API_HOST ?? "";
4479
+ const trimmed = value.trim();
4480
+ return trimmed || POSTHOG_DEFAULT_HOST;
4481
+ }
4482
+ function toPosthogBatchUrl(host) {
4483
+ try {
4484
+ return new URL("/batch/", host).toString();
4485
+ } catch {
4486
+ return `${POSTHOG_DEFAULT_HOST}/batch/`;
4487
+ }
4488
+ }
4489
+ async function trackWizardTelemetry(event, properties = {}, options = {}) {
4490
+ if (isWizardTelemetryDisabled()) return false;
4491
+ const apiKey = resolvePosthogApiKey();
4492
+ if (!apiKey) return false;
4493
+ const installationId = getOrCreateWizardInstallationId(options.statePath);
4494
+ const timestamp = options.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
4495
+ const response = await fetch(toPosthogBatchUrl(resolvePosthogHost()), {
4496
+ method: "POST",
4497
+ headers: {
4498
+ "Content-Type": "application/json"
4499
+ },
4500
+ body: JSON.stringify({
4501
+ api_key: apiKey,
4502
+ batch: [
4503
+ {
4504
+ type: "capture",
4505
+ event,
4506
+ distinct_id: installationId,
4507
+ properties: {
4508
+ $lib: WIZARD_LIB,
4509
+ source: WIZARD_LIB,
4510
+ wizard_installation_id: installationId,
4511
+ ...properties
4512
+ },
4513
+ timestamp
4514
+ }
4515
+ ],
4516
+ sent_at: timestamp
4517
+ })
4518
+ }).catch(() => null);
4519
+ return response?.ok === true;
4520
+ }
4521
+
3061
4522
  // src/spinner.ts
3062
4523
  import ora from "ora";
3063
4524
  import pc2 from "picocolors";
@@ -3095,12 +4556,34 @@ function formatAuthSource(source) {
3095
4556
  return "not configured";
3096
4557
  }
3097
4558
  }
4559
+ function formatPluginTargetLabel(target) {
4560
+ switch (target) {
4561
+ case "claude":
4562
+ return "Claude Code";
4563
+ case "codex":
4564
+ return "Codex";
4565
+ case "openclaw":
4566
+ return "OpenClaw";
4567
+ }
4568
+ }
3098
4569
  function printSurfaceTable(statuses) {
3099
4570
  for (const status of statuses) {
3100
4571
  const icon = status.configured ? ICON.ok : status.detected ? ICON.warn : ICON.skip;
3101
4572
  const state = status.configured ? pc3.green("configured") : status.detected ? pc3.yellow("detected") : pc3.dim("not found");
3102
4573
  const mode = pc3.dim(status.mode === "automated" ? "auto " : "manual");
3103
4574
  console.log(` ${icon} ${pc3.bold(status.name.padEnd(10))} ${mode} ${state}`);
4575
+ if (status.mode === "manual") {
4576
+ console.log(` ${pc3.dim(status.summary)}`);
4577
+ }
4578
+ }
4579
+ }
4580
+ function printPluginStatusReport(statuses) {
4581
+ for (const status of statuses) {
4582
+ const icon = status.installed ? ICON.ok : status.available ? ICON.warn : ICON.skip;
4583
+ const state = status.installed ? pc3.green("installed") : status.available ? pc3.yellow("available") : pc3.dim("not found");
4584
+ console.log(
4585
+ ` ${icon} ${pc3.bold(formatPluginTargetLabel(status.target).padEnd(12))} ${state} ${pc3.dim(status.message)}`
4586
+ );
3104
4587
  }
3105
4588
  }
3106
4589
  function printMutationResults(results) {
@@ -3110,6 +4593,15 @@ function printMutationResults(results) {
3110
4593
  console.log(` ${icon} ${pc3.bold(result.name.padEnd(10))} ${state} ${pc3.dim(result.message)}`);
3111
4594
  }
3112
4595
  }
4596
+ function printPluginMutationReport(report) {
4597
+ for (const result of report.results) {
4598
+ const icon = result.changed ? ICON.ok : ICON.skip;
4599
+ const state = result.changed ? pc3.green("updated ") : pc3.dim("unchanged");
4600
+ console.log(
4601
+ ` ${icon} ${pc3.bold(formatPluginTargetLabel(result.target).padEnd(12))} ${state} ${pc3.dim(result.message)}`
4602
+ );
4603
+ }
4604
+ }
3113
4605
  function printSkillInstallReport(report) {
3114
4606
  for (const write of report.writes) {
3115
4607
  const icon = write.changed ? ICON.ok : ICON.skip;
@@ -3122,6 +4614,23 @@ function printSkillInstallReport(report) {
3122
4614
  const state = changedCount > 0 ? pc3.green(`${changedCount} updated `) : pc3.dim("unchanged");
3123
4615
  console.log(` ${icon} ${pc3.bold(pack.name.padEnd(10))} ${state} ${pc3.dim(`${pack.files.length} files`)}`);
3124
4616
  }
4617
+ for (const note of report.notes) {
4618
+ console.log(` ${ICON.skip} ${pc3.dim(note)}`);
4619
+ }
4620
+ }
4621
+ function countSkillReportChanges(report) {
4622
+ const writeChanges = report.writes.filter((write) => write.changed).length;
4623
+ const packChanges = report.packs.reduce(
4624
+ (count, pack) => count + pack.files.filter((file) => file.changed).length,
4625
+ 0
4626
+ );
4627
+ return writeChanges + packChanges;
4628
+ }
4629
+ async function safeTrackWizardTelemetry(event, properties = {}) {
4630
+ try {
4631
+ await trackWizardTelemetry(event, properties);
4632
+ } catch {
4633
+ }
3125
4634
  }
3126
4635
  function printWorkspace(workspace) {
3127
4636
  console.log(
@@ -3170,6 +4679,12 @@ function printFounderPresetResult(result) {
3170
4679
  ` ${result.demoInitiative.created ? pc3.green("created") : pc3.yellow("unchanged")} ${result.demoInitiative.initiative.title}`
3171
4680
  );
3172
4681
  console.log(` live: ${result.demoInitiative.liveUrl}`);
4682
+ console.log(
4683
+ ` decision: ${result.demoInitiative.decision.title} ${pc3.dim(`(${result.demoInitiative.decision.status ?? "pending"})`)}`
4684
+ );
4685
+ console.log(
4686
+ ` artifact: ${result.demoInitiative.artifact.name}${result.demoInitiative.artifact.url ? ` ${pc3.dim(result.demoInitiative.artifact.url)}` : ""}`
4687
+ );
3173
4688
  }
3174
4689
  }
3175
4690
  function normalizePromptResult(value) {
@@ -3187,6 +4702,64 @@ async function textPrompt(input) {
3187
4702
  };
3188
4703
  return normalizePromptResult(await clack.text(promptInput));
3189
4704
  }
4705
+ async function multiselectPrompt(input) {
4706
+ const promptInput = {
4707
+ message: input.message,
4708
+ options: input.options,
4709
+ ...input.required !== void 0 ? { required: input.required } : {}
4710
+ };
4711
+ return normalizePromptResult(await clack.multiselect(promptInput));
4712
+ }
4713
+ function printAgentRoster(roster) {
4714
+ console.log(` ${ICON.ok} ${pc3.green("agent roster")} ${pc3.bold(`${roster.agents.length} agents`)}`);
4715
+ for (const agent of roster.agents) {
4716
+ console.log(
4717
+ ` ${pc3.dim(agent.domain.padEnd(12))} ${pc3.bold(agent.name)} ${pc3.dim(agent.agentType)}`
4718
+ );
4719
+ }
4720
+ }
4721
+ async function promptForGuidedAgentRoster(workspace) {
4722
+ const remainingAgents = listAgentRosterProfiles();
4723
+ const assignments = [];
4724
+ for (const domain of AGENT_ROSTER_DOMAINS) {
4725
+ if (remainingAgents.length === 0) {
4726
+ break;
4727
+ }
4728
+ const defaultAgent = remainingAgents.find((agent) => agent.defaultDomain === domain);
4729
+ const choice = await selectPrompt({
4730
+ initialValue: defaultAgent?.name ?? "skip",
4731
+ message: `Assign an OrgX agent to the ${domain} domain`,
4732
+ options: [
4733
+ ...remainingAgents.map((agent) => ({
4734
+ value: agent.name,
4735
+ label: agent.name,
4736
+ hint: `${agent.agentType} \xB7 default ${agent.defaultDomain}`
4737
+ })),
4738
+ { value: "skip", label: `Leave ${domain} unassigned`, hint: "optional" }
4739
+ ]
4740
+ });
4741
+ if (clack.isCancel(choice)) {
4742
+ clack.cancel("Setup cancelled.");
4743
+ return "cancelled";
4744
+ }
4745
+ if (choice === "skip") {
4746
+ continue;
4747
+ }
4748
+ const index = remainingAgents.findIndex((agent) => agent.name === choice);
4749
+ if (index === -1) {
4750
+ continue;
4751
+ }
4752
+ const [selectedAgent] = remainingAgents.splice(index, 1);
4753
+ if (!selectedAgent) {
4754
+ continue;
4755
+ }
4756
+ assignments.push({ domain, name: selectedAgent.name });
4757
+ }
4758
+ if (assignments.length === 0) {
4759
+ return null;
4760
+ }
4761
+ return configureAgentRoster(workspace, assignments);
4762
+ }
3190
4763
  async function verifyAndPersistAuth(apiKey, options) {
3191
4764
  const trimmedKey = apiKey.trim();
3192
4765
  if (!trimmedKey.toLowerCase().startsWith("oxk_")) {
@@ -3207,6 +4780,10 @@ async function verifyAndPersistAuth(apiKey, options) {
3207
4780
  baseUrl,
3208
4781
  verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
3209
4782
  });
4783
+ await safeTrackWizardTelemetry("auth_completed", {
4784
+ auth_source: options.telemetrySource ?? "manual_api_key",
4785
+ has_base_url: Boolean(baseUrl)
4786
+ });
3210
4787
  const openclawResults = detectSurface("openclaw").detected ? await addSurface("openclaw") : [];
3211
4788
  return { openclawResults, stored, verification };
3212
4789
  }
@@ -3228,6 +4805,174 @@ function parseTimeoutSeconds(value) {
3228
4805
  }
3229
4806
  return parsed;
3230
4807
  }
4808
+ async function maybeConfigureOptionalWorkspaceAddOns(input) {
4809
+ if (!input.interactive || !input.workspace) {
4810
+ return "skipped";
4811
+ }
4812
+ const existingState = readWizardState();
4813
+ const effectiveInitiativeId = input.initiativeId?.trim() || (existingState?.demoInitiative?.workspaceId === input.workspace.id ? existingState.demoInitiative.id : void 0);
4814
+ const hasOnboardingTask = existingState?.onboardingTask?.workspaceId === input.workspace.id;
4815
+ if (!hasOnboardingTask && effectiveInitiativeId) {
4816
+ const onboardingChoice = await selectPrompt({
4817
+ message: `Create a starter onboarding task for ${input.workspace.name}?`,
4818
+ options: [
4819
+ { value: "yes", label: "Create onboarding task", hint: "recommended" },
4820
+ { value: "no", label: "Skip task creation" }
4821
+ ]
4822
+ });
4823
+ if (clack.isCancel(onboardingChoice)) {
4824
+ clack.cancel("Setup cancelled.");
4825
+ return "cancelled";
4826
+ }
4827
+ if (onboardingChoice === "yes") {
4828
+ try {
4829
+ const task = await ensureOnboardingTask(input.workspace, {
4830
+ initiativeId: effectiveInitiativeId
4831
+ });
4832
+ console.log(` ${ICON.ok} ${pc3.green("onboarding")} ${pc3.bold(task.title)}`);
4833
+ await safeTrackWizardTelemetry(
4834
+ "onboarding_task_created",
4835
+ buildOnboardingTaskTelemetryProperties(
4836
+ {
4837
+ created: true,
4838
+ hasInitiative: true,
4839
+ task
4840
+ },
4841
+ {
4842
+ command: input.telemetry?.command ?? "setup",
4843
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {}
4844
+ }
4845
+ )
4846
+ );
4847
+ } catch (error) {
4848
+ const message = error instanceof Error ? error.message : String(error);
4849
+ console.log(` ${ICON.warn} ${pc3.yellow("onboarding")} ${pc3.dim(message)}`);
4850
+ }
4851
+ }
4852
+ }
4853
+ const refreshedState = readWizardState();
4854
+ const hasAgentRoster = refreshedState?.agentRoster?.workspaceId === input.workspace.id;
4855
+ if (!hasAgentRoster) {
4856
+ const rosterChoice = await selectPrompt({
4857
+ message: `Set up an OrgX agent roster for ${input.workspace.name}?`,
4858
+ options: [
4859
+ { value: "guided", label: "Choose agents and domains", hint: "recommended" },
4860
+ { value: "default", label: "Install the full default roster" },
4861
+ { value: "no", label: "Skip roster setup" }
4862
+ ]
4863
+ });
4864
+ if (clack.isCancel(rosterChoice)) {
4865
+ clack.cancel("Setup cancelled.");
4866
+ return "cancelled";
4867
+ }
4868
+ if (rosterChoice === "guided") {
4869
+ const roster = await promptForGuidedAgentRoster(input.workspace);
4870
+ if (roster === "cancelled") {
4871
+ return "cancelled";
4872
+ }
4873
+ if (roster) {
4874
+ printAgentRoster(roster);
4875
+ await safeTrackWizardTelemetry(
4876
+ "agent_roster_configured",
4877
+ buildAgentRosterTelemetryProperties(
4878
+ {
4879
+ roster,
4880
+ strategy: "guided"
4881
+ },
4882
+ {
4883
+ command: input.telemetry?.command ?? "setup",
4884
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {}
4885
+ }
4886
+ )
4887
+ );
4888
+ } else {
4889
+ console.log(` ${ICON.skip} ${pc3.dim("agent roster skipped")} ${pc3.dim("(no agents selected)")}`);
4890
+ }
4891
+ } else if (rosterChoice === "default") {
4892
+ const roster = ensureDefaultAgentRoster(input.workspace);
4893
+ printAgentRoster(roster);
4894
+ await safeTrackWizardTelemetry(
4895
+ "agent_roster_configured",
4896
+ buildAgentRosterTelemetryProperties(
4897
+ {
4898
+ roster,
4899
+ strategy: "default"
4900
+ },
4901
+ {
4902
+ command: input.telemetry?.command ?? "setup",
4903
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {}
4904
+ }
4905
+ )
4906
+ );
4907
+ }
4908
+ }
4909
+ return "configured";
4910
+ }
4911
+ async function promptOptionalCompanionPluginTargets(input) {
4912
+ if (!input.interactive) {
4913
+ return [];
4914
+ }
4915
+ const statuses = await listOrgxPluginStatuses();
4916
+ const installable = statuses.filter((status) => status.available && !status.installed);
4917
+ if (installable.length === 0) {
4918
+ return [];
4919
+ }
4920
+ const selection = await multiselectPrompt({
4921
+ message: "Install companion OrgX plugins into detected tools?",
4922
+ options: installable.map((status) => ({
4923
+ value: status.target,
4924
+ label: `Install ${formatPluginTargetLabel(status.target)}`,
4925
+ hint: status.message
4926
+ })),
4927
+ required: false
4928
+ });
4929
+ if (clack.isCancel(selection)) {
4930
+ clack.cancel("Setup cancelled.");
4931
+ return "cancelled";
4932
+ }
4933
+ return selection;
4934
+ }
4935
+ function printPluginSkillOwnershipNote(targets) {
4936
+ if (!targets.some((target) => target === "claude" || target === "codex")) {
4937
+ return;
4938
+ }
4939
+ console.log(
4940
+ ` ${ICON.skip} ${pc3.dim(
4941
+ "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."
4942
+ )}`
4943
+ );
4944
+ }
4945
+ async function installSelectedCompanionPlugins(input) {
4946
+ if (input.targets.length === 0) {
4947
+ return "skipped";
4948
+ }
4949
+ const spinner = createOrgxSpinner("Installing OrgX companion plugins");
4950
+ spinner.start();
4951
+ const report = await installOrgxPlugins({ targets: input.targets });
4952
+ spinner.succeed("OrgX companion plugins processed");
4953
+ printPluginMutationReport(report);
4954
+ printPluginSkillOwnershipNote(input.targets);
4955
+ await safeTrackWizardTelemetry("plugins_installed", {
4956
+ changed_count: countPluginReportChanges(report),
4957
+ command: input.telemetry?.command ?? "setup",
4958
+ ...input.telemetry?.preset ? { preset: input.telemetry.preset } : {},
4959
+ requested_target_count: input.targets.length,
4960
+ target_count: report.results.length
4961
+ });
4962
+ return countPluginReportChanges(report) > 0 ? "configured" : "skipped";
4963
+ }
4964
+ async function maybeInstallOptionalCompanionPlugins(input) {
4965
+ const selection = await promptOptionalCompanionPluginTargets({
4966
+ interactive: input.interactive
4967
+ });
4968
+ if (selection === "cancelled") {
4969
+ return "cancelled";
4970
+ }
4971
+ return await installSelectedCompanionPlugins({
4972
+ targets: selection,
4973
+ ...input.telemetry ? { telemetry: input.telemetry } : {}
4974
+ });
4975
+ }
3231
4976
  function printAuthStatus(status) {
3232
4977
  if (!status.configured) {
3233
4978
  console.log(` ${ICON.warn} ${pc3.yellow("no account")} run ${pc3.cyan(`${getCmd()} auth login`)} to connect`);
@@ -3242,6 +4987,7 @@ function printAuthStatus(status) {
3242
4987
  }
3243
4988
  }
3244
4989
  function printDoctorReport(report, assessment) {
4990
+ const verification = summarizeSetupVerification(assessment);
3245
4991
  console.log(pc3.dim(" surfaces"));
3246
4992
  printSurfaceTable(report.surfaces);
3247
4993
  console.log("");
@@ -3290,11 +5036,12 @@ function printDoctorReport(report, assessment) {
3290
5036
  console.log(` ${pc3.dim("\u2192")} ${pc3.dim(`OrgX is active across ${configuredCount} editor${configuredCount !== 1 ? "s" : ""}`)}`);
3291
5037
  }
3292
5038
  } 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")}`;
5039
+ const headlineText = getSetupVerificationHeadline(verification);
5040
+ const headline = verification.status === "error" ? `${ICON.err} ${pc3.red(headlineText)}` : `${ICON.warn} ${pc3.yellow(headlineText)}`;
3295
5041
  console.log(` ${headline}`);
3296
5042
  for (const issue of assessment.issues) {
3297
- const label = issue.level === "error" ? pc3.red("error") : pc3.yellow("warn ");
5043
+ const degradedHostedMcpIssue = verification.hostedMcpDegraded && issue.title === HOSTED_MCP_OUTAGE_TITLE;
5044
+ const label = degradedHostedMcpIssue ? pc3.yellow("degr ") : issue.level === "error" ? pc3.red("error") : pc3.yellow("warn ");
3298
5045
  console.log(`
3299
5046
  ${label} ${issue.title}`);
3300
5047
  console.log(` ${pc3.dim("\u2192")} ${issue.suggestion}`);
@@ -3304,16 +5051,28 @@ function printDoctorReport(report, assessment) {
3304
5051
  async function main() {
3305
5052
  const program = new Command();
3306
5053
  program.name("orgx-wizard").description("One-line CLI onboarding for OrgX surfaces.").showHelpAfterError();
3307
- const pkgVersion = true ? "0.1.7" : void 0;
5054
+ const pkgVersion = true ? "0.1.9" : void 0;
3308
5055
  program.version(pkgVersion ?? "unknown", "-V, --version");
3309
5056
  program.hook("preAction", () => {
3310
5057
  console.log(renderBanner(pkgVersion));
3311
5058
  });
3312
5059
  program.command("setup").description("Patch all detected automated OrgX surfaces.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
5060
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
5061
+ await safeTrackWizardTelemetry("wizard_started", {
5062
+ command: "setup",
5063
+ interactive,
5064
+ preset: options.preset ?? "standard"
5065
+ });
3313
5066
  if (options.preset) {
3314
5067
  if (options.preset !== "founder") {
3315
5068
  throw new Error(`Unknown setup preset '${options.preset}'. Supported presets: founder.`);
3316
5069
  }
5070
+ const founderPluginTargets = await promptOptionalCompanionPluginTargets({
5071
+ interactive
5072
+ });
5073
+ if (founderPluginTargets === "cancelled") {
5074
+ return;
5075
+ }
3317
5076
  const spinner2 = createOrgxSpinner("Running founder setup preset");
3318
5077
  spinner2.start();
3319
5078
  const presetResult = await runFounderPreset(
@@ -3324,7 +5083,8 @@ async function main() {
3324
5083
  text: textPrompt
3325
5084
  },
3326
5085
  {
3327
- interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
5086
+ interactive,
5087
+ pluginTargets: founderPluginTargets
3328
5088
  }
3329
5089
  );
3330
5090
  if ("status" in presetResult) {
@@ -3337,11 +5097,62 @@ async function main() {
3337
5097
  }
3338
5098
  spinner2.succeed("Founder setup preset complete");
3339
5099
  printFounderPresetResult(presetResult);
5100
+ await safeTrackWizardTelemetry("mcp_injected", {
5101
+ changed_count: presetResult.surfaceResults.filter((result) => result.changed).length,
5102
+ preset: "founder",
5103
+ surface_count: presetResult.surfaceResults.length
5104
+ });
5105
+ await safeTrackWizardTelemetry("skills_installed", {
5106
+ changed_count: countSkillReportChanges(presetResult.skillReport),
5107
+ pack_count: presetResult.skillReport.packs.length,
5108
+ preset: "founder",
5109
+ write_count: presetResult.skillReport.writes.length
5110
+ });
5111
+ if (presetResult.workspaceSetup) {
5112
+ await safeTrackWizardTelemetry(
5113
+ "workspace_bootstrapped",
5114
+ buildWorkspaceSetupTelemetryProperties(presetResult.workspaceSetup, {
5115
+ command: "setup",
5116
+ interactive,
5117
+ preset: "founder"
5118
+ })
5119
+ );
5120
+ }
5121
+ if (presetResult.demoInitiative) {
5122
+ await safeTrackWizardTelemetry(
5123
+ "founder_demo_ready",
5124
+ buildFounderDemoTelemetryProperties(presetResult.demoInitiative, {
5125
+ command: "setup",
5126
+ preset: "founder"
5127
+ })
5128
+ );
5129
+ }
5130
+ const addOnResult = await maybeConfigureOptionalWorkspaceAddOns({
5131
+ interactive,
5132
+ telemetry: { command: "setup", preset: "founder" },
5133
+ workspace: presetResult.workspace,
5134
+ ...presetResult.demoInitiative ? { initiativeId: presetResult.demoInitiative.initiative.id } : {}
5135
+ });
5136
+ if (addOnResult === "cancelled") {
5137
+ return;
5138
+ }
5139
+ await installSelectedCompanionPlugins({
5140
+ telemetry: { command: "setup", preset: "founder" },
5141
+ targets: founderPluginTargets
5142
+ });
3340
5143
  console.log("");
3341
5144
  const doctor2 = await runDoctor();
3342
5145
  const assessment2 = assessDoctorReport(doctor2);
5146
+ const verification2 = summarizeSetupVerification(assessment2);
5147
+ await safeTrackWizardTelemetry(
5148
+ "setup_verified",
5149
+ buildDoctorTelemetryProperties(doctor2, assessment2, verification2, {
5150
+ command: "setup",
5151
+ preset: "founder"
5152
+ })
5153
+ );
3343
5154
  printDoctorReport(doctor2, assessment2);
3344
- if (!assessment2.ok) {
5155
+ if (verification2.status === "error") {
3345
5156
  process.exitCode = 1;
3346
5157
  }
3347
5158
  return;
@@ -3351,10 +5162,14 @@ async function main() {
3351
5162
  const results = await setupDetectedSurfaces();
3352
5163
  spinner.succeed("Detected surfaces configured");
3353
5164
  printMutationResults(results);
5165
+ await safeTrackWizardTelemetry("mcp_injected", {
5166
+ changed_count: results.filter((result) => result.changed).length,
5167
+ preset: "standard",
5168
+ surface_count: results.length
5169
+ });
3354
5170
  let resolvedAuth = await resolveOrgxAuth();
3355
5171
  if (!resolvedAuth) {
3356
5172
  console.log("");
3357
- const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
3358
5173
  if (interactive) {
3359
5174
  const choice = await selectPrompt({
3360
5175
  message: "Connect your OrgX account to enable workspace and AI tool access",
@@ -3368,7 +5183,7 @@ async function main() {
3368
5183
  return;
3369
5184
  }
3370
5185
  if (choice === "login") {
3371
- const loginOk = await runBrowserLogin();
5186
+ const loginOk = await runBrowserLogin({ telemetrySource: "browser_pairing" });
3372
5187
  if (!loginOk) {
3373
5188
  console.log(`
3374
5189
  ${pc3.dim("\u2192")} ${pc3.cyan(`${getCmd()} auth login`)} ${pc3.dim("to try again")}`);
@@ -3404,25 +5219,60 @@ async function main() {
3404
5219
  text: textPrompt
3405
5220
  },
3406
5221
  {
3407
- interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
5222
+ interactive
3408
5223
  }
3409
5224
  );
3410
5225
  if (workspaceSetup.status === "cancelled") {
3411
5226
  return;
3412
5227
  }
5228
+ await safeTrackWizardTelemetry(
5229
+ "workspace_bootstrapped",
5230
+ buildWorkspaceSetupTelemetryProperties(workspaceSetup, {
5231
+ command: "setup",
5232
+ interactive,
5233
+ preset: "standard"
5234
+ })
5235
+ );
5236
+ const resolvedWorkspace = workspaceSetup.workspace ?? await getCurrentWorkspace().catch(() => null);
5237
+ if (resolvedWorkspace) {
5238
+ persistContinuityDefaults({ workspace: resolvedWorkspace });
5239
+ }
3413
5240
  if (workspaceSetup.workspace) {
3414
5241
  console.log(` ${ICON.ok} ${pc3.green("workspace ")} ${pc3.bold(workspaceSetup.workspace.name)}`);
3415
5242
  }
5243
+ const addOnResult = await maybeConfigureOptionalWorkspaceAddOns({
5244
+ interactive,
5245
+ telemetry: { command: "setup", preset: "standard" },
5246
+ workspace: resolvedWorkspace
5247
+ });
5248
+ if (addOnResult === "cancelled") {
5249
+ return;
5250
+ }
5251
+ const pluginInstallResult = await maybeInstallOptionalCompanionPlugins({
5252
+ interactive,
5253
+ telemetry: { command: "setup", preset: "standard" }
5254
+ });
5255
+ if (pluginInstallResult === "cancelled") {
5256
+ return;
5257
+ }
3416
5258
  }
3417
5259
  const doctor = await runDoctor();
3418
5260
  const assessment = assessDoctorReport(doctor);
5261
+ const verification = summarizeSetupVerification(assessment);
5262
+ await safeTrackWizardTelemetry(
5263
+ "setup_verified",
5264
+ buildDoctorTelemetryProperties(doctor, assessment, verification, {
5265
+ command: "setup",
5266
+ preset: "standard"
5267
+ })
5268
+ );
3419
5269
  const configuredCount = doctor.surfaces.filter((s) => s.configured).length;
3420
5270
  console.log("");
3421
5271
  if (assessment.issues.length === 0) {
3422
5272
  console.log(` ${ICON.ok} ${pc3.green("You're all set.")} ${pc3.dim(`OrgX is active across ${configuredCount} editor${configuredCount !== 1 ? "s" : ""}`)}`);
3423
5273
  } else {
3424
5274
  printDoctorReport(doctor, assessment);
3425
- if (!assessment.ok) {
5275
+ if (verification.status === "error") {
3426
5276
  process.exitCode = 1;
3427
5277
  }
3428
5278
  }
@@ -3460,7 +5310,8 @@ async function main() {
3460
5310
  });
3461
5311
  spinner.text = "Verifying API key...";
3462
5312
  const result = await verifyAndPersistAuth(ready.key, {
3463
- ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
5313
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {},
5314
+ ...opts.telemetrySource ? { telemetrySource: opts.telemetrySource } : {}
3464
5315
  });
3465
5316
  if (!("stored" in result)) {
3466
5317
  spinner.fail("Pairing completed but key was rejected");
@@ -3509,7 +5360,10 @@ async function main() {
3509
5360
  if (options.apiKey) {
3510
5361
  const spinner = createOrgxSpinner("Verifying OrgX API key");
3511
5362
  spinner.start();
3512
- const result = await verifyAndPersistAuth(options.apiKey, options);
5363
+ const result = await verifyAndPersistAuth(options.apiKey, {
5364
+ ...options,
5365
+ telemetrySource: "manual_api_key"
5366
+ });
3513
5367
  if (!("stored" in result)) {
3514
5368
  spinner.fail("OrgX API key verification failed");
3515
5369
  printAuthStatus(result.verification);
@@ -3528,13 +5382,17 @@ async function main() {
3528
5382
  ...options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {},
3529
5383
  ...options.deviceName !== void 0 ? { deviceName: options.deviceName } : {},
3530
5384
  ...options.open !== void 0 ? { open: options.open } : {},
5385
+ telemetrySource: "browser_pairing",
3531
5386
  timeout: options.timeout
3532
5387
  });
3533
5388
  });
3534
5389
  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
5390
  const spinner = createOrgxSpinner("Verifying OrgX API key");
3536
5391
  spinner.start();
3537
- const result = await verifyAndPersistAuth(apiKey, options);
5392
+ const result = await verifyAndPersistAuth(apiKey, {
5393
+ ...options,
5394
+ telemetrySource: "manual_api_key"
5395
+ });
3538
5396
  if (!("stored" in result)) {
3539
5397
  spinner.fail("OrgX API key verification failed");
3540
5398
  printAuthStatus(result.verification);
@@ -3583,6 +5441,41 @@ async function main() {
3583
5441
  const results = removeMcpSurface(names);
3584
5442
  printMutationResults(results);
3585
5443
  });
5444
+ const plugins = program.command("plugins").description("Install or remove companion OrgX plugins for Claude Code, Codex, and OpenClaw.");
5445
+ plugins.command("list").description("Show companion plugin availability and install status.").action(async () => {
5446
+ const spinner = createOrgxSpinner("Checking companion plugin status");
5447
+ spinner.start();
5448
+ const statuses = await listOrgxPluginStatuses();
5449
+ spinner.stop();
5450
+ printPluginStatusReport(statuses);
5451
+ });
5452
+ 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) => {
5453
+ const spinner = createOrgxSpinner("Installing OrgX companion plugins");
5454
+ spinner.start();
5455
+ const report = await installOrgxPlugins({ targets });
5456
+ spinner.succeed("OrgX companion plugins processed");
5457
+ printPluginMutationReport(report);
5458
+ printPluginSkillOwnershipNote(report.results.map((result) => result.target));
5459
+ await safeTrackWizardTelemetry("plugins_installed", {
5460
+ changed_count: countPluginReportChanges(report),
5461
+ command: "plugins:add",
5462
+ requested_target_count: targets.length,
5463
+ target_count: report.results.length
5464
+ });
5465
+ });
5466
+ 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) => {
5467
+ const spinner = createOrgxSpinner("Removing OrgX companion plugins");
5468
+ spinner.start();
5469
+ const report = await uninstallOrgxPlugins({ targets });
5470
+ spinner.succeed("OrgX companion plugins removed");
5471
+ printPluginMutationReport(report);
5472
+ await safeTrackWizardTelemetry("plugins_removed", {
5473
+ changed_count: countPluginReportChanges(report),
5474
+ command: "plugins:remove",
5475
+ requested_target_count: targets.length,
5476
+ target_count: report.results.length
5477
+ });
5478
+ });
3586
5479
  const workspace = program.command("workspace").description("Inspect and create OrgX workspaces.");
3587
5480
  workspace.command("current").description("Show the current OrgX workspace.").action(async () => {
3588
5481
  const spinner = createOrgxSpinner("Loading current OrgX workspace");
@@ -3612,6 +5505,9 @@ async function main() {
3612
5505
  }
3613
5506
  );
3614
5507
  spinner.succeed("OrgX workspace created");
5508
+ if (created.isDefault) {
5509
+ persistContinuityDefaults({ workspace: created });
5510
+ }
3615
5511
  printWorkspace(created);
3616
5512
  if (!created.isDefault) {
3617
5513
  console.log("");
@@ -3625,6 +5521,7 @@ async function main() {
3625
5521
  spinner.succeed(
3626
5522
  result.changed ? "Default OrgX workspace updated" : "OrgX workspace already set as default"
3627
5523
  );
5524
+ persistContinuityDefaults({ workspace: result.workspace });
3628
5525
  printWorkspace(result.workspace);
3629
5526
  });
3630
5527
  program.command("doctor").description("Verify local OrgX surface config and optional remote setup status.").action(async () => {
@@ -3633,18 +5530,37 @@ async function main() {
3633
5530
  const report = await runDoctor();
3634
5531
  spinner.stop();
3635
5532
  const assessment = assessDoctorReport(report);
5533
+ const verification = summarizeSetupVerification(assessment);
5534
+ await safeTrackWizardTelemetry(
5535
+ "doctor_ran",
5536
+ buildDoctorTelemetryProperties(report, assessment, verification, {
5537
+ command: "doctor"
5538
+ })
5539
+ );
3636
5540
  printDoctorReport(report, assessment);
3637
- if (!assessment.ok) {
5541
+ if (verification.status === "error") {
3638
5542
  process.exitCode = 1;
3639
5543
  }
3640
5544
  });
3641
5545
  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) => {
5546
+ 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) => {
5547
+ const pluginTargets = (await listOrgxPluginStatuses()).filter((status) => status.installed).map((status) => status.target);
3643
5548
  const spinner = createOrgxSpinner("Installing OrgX rules and skills");
3644
5549
  spinner.start();
3645
- const report = await installOrgxSkills({ skillNames: packs });
5550
+ const report = await installOrgxSkills({
5551
+ pluginTargets,
5552
+ skillNames: packs
5553
+ });
3646
5554
  spinner.succeed("OrgX rules and skills installed");
3647
5555
  printSkillInstallReport(report);
5556
+ await safeTrackWizardTelemetry("skills_installed", {
5557
+ changed_count: countSkillReportChanges(report),
5558
+ command: "skills:add",
5559
+ pack_count: report.packs.length,
5560
+ plugin_managed_target_count: pluginTargets.length,
5561
+ requested_pack_count: packs.length,
5562
+ write_count: report.writes.length
5563
+ });
3648
5564
  });
3649
5565
  await program.parseAsync(process.argv);
3650
5566
  }