@useorgx/wizard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/dist/cli.js +3799 -0
- package/dist/cli.js.map +1 -0
- package/package.json +52 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3799 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import * as clack from "@clack/prompts";
|
|
5
|
+
import { hostname } from "os";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import pc3 from "picocolors";
|
|
8
|
+
|
|
9
|
+
// src/banner.ts
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
function renderBanner() {
|
|
12
|
+
const lines = [
|
|
13
|
+
" ____ ____ _______ __",
|
|
14
|
+
" / __ \\/ __ \\ / ____/ |/ /",
|
|
15
|
+
"/ / / / /_/ // / __ | / ",
|
|
16
|
+
"/ /_/ / _, _// /_/ // | ",
|
|
17
|
+
"\\____/_/ |_| \\____//_/|_| "
|
|
18
|
+
];
|
|
19
|
+
return lines.map((line) => pc.cyan(line)).join("\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/constants.ts
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
import { join } from "path";
|
|
25
|
+
var ORGX_HOSTED_MCP_KEY = "orgx";
|
|
26
|
+
var ORGX_LOCAL_MCP_KEY = "orgx-openclaw";
|
|
27
|
+
var ORGX_HOSTED_MCP_URL = "https://mcp.useorgx.com/mcp";
|
|
28
|
+
var ORGX_HOSTED_MCP_HEALTH_URL = "https://mcp.useorgx.com/health";
|
|
29
|
+
var ORGX_WIZARD_NPM_PACKAGE_NAME = "@useorgx/wizard";
|
|
30
|
+
var NPM_REGISTRY_BASE_URL = "https://registry.npmjs.org";
|
|
31
|
+
var DEFAULT_OPENCLAW_GATEWAY_PORT = 18789;
|
|
32
|
+
var DEFAULT_ORGX_BASE_URL = process.env.ORGX_BASE_URL?.trim() || "https://useorgx.com";
|
|
33
|
+
var HOME = homedir();
|
|
34
|
+
var APPDATA = process.env.APPDATA?.trim();
|
|
35
|
+
var LOCAL_APPDATA = process.env.LOCALAPPDATA?.trim();
|
|
36
|
+
var XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME?.trim() || join(HOME, ".config");
|
|
37
|
+
var ORGX_WIZARD_CONFIG_HOME = process.env.ORGX_WIZARD_CONFIG_HOME?.trim() || join(XDG_CONFIG_HOME, "useorgx", "wizard");
|
|
38
|
+
var ORGX_WIZARD_AUTH_PATH = join(ORGX_WIZARD_CONFIG_HOME, "auth.json");
|
|
39
|
+
var ORGX_WIZARD_STATE_PATH = join(ORGX_WIZARD_CONFIG_HOME, "state.json");
|
|
40
|
+
var ORGX_WIZARD_KEYTAR_SERVICE = "@useorgx/wizard";
|
|
41
|
+
var ORGX_WIZARD_KEYTAR_ACCOUNT = "orgx-api-key";
|
|
42
|
+
function normalizeOrgxApiBaseUrl(baseUrl) {
|
|
43
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
44
|
+
if (/\/api(\/|$)/.test(normalized)) {
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
return `${normalized}/api`;
|
|
48
|
+
}
|
|
49
|
+
var ORGX_API_BASE_URL = normalizeOrgxApiBaseUrl(DEFAULT_ORGX_BASE_URL);
|
|
50
|
+
var ORGX_SETUP_STATUS_URL = `${ORGX_API_BASE_URL}/setup/status`;
|
|
51
|
+
var ORGX_AUTH_VERIFY_URL = `${ORGX_API_BASE_URL}/auth/verify`;
|
|
52
|
+
var ORGX_CLIENT_SYNC_URL = `${ORGX_API_BASE_URL}/client/sync`;
|
|
53
|
+
function uniquePaths(paths) {
|
|
54
|
+
return [...new Set(paths.filter((path) => Boolean(path)))];
|
|
55
|
+
}
|
|
56
|
+
var CLAUDE_DIR = join(HOME, ".claude");
|
|
57
|
+
var CURSOR_DIR = join(HOME, ".cursor");
|
|
58
|
+
var CODEX_DIR = join(HOME, ".codex");
|
|
59
|
+
var OPENCLAW_DIR = join(HOME, ".openclaw");
|
|
60
|
+
var CLAUDE_SKILLS_DIR = join(CLAUDE_DIR, "skills");
|
|
61
|
+
var CLAUDE_ORGX_SKILL_DIR = join(CLAUDE_SKILLS_DIR, "orgx");
|
|
62
|
+
var CLAUDE_ORGX_SKILL_PATH = join(CLAUDE_ORGX_SKILL_DIR, "SKILL.md");
|
|
63
|
+
var CURSOR_RULES_DIR = join(CURSOR_DIR, "rules");
|
|
64
|
+
var CURSOR_ORGX_RULE_PATH = join(CURSOR_RULES_DIR, "orgx.md");
|
|
65
|
+
var CLAUDE_MCP_PATHS = uniquePaths([join(CLAUDE_DIR, "mcp.json")]);
|
|
66
|
+
var CURSOR_MCP_PATHS = uniquePaths([join(CURSOR_DIR, "mcp.json")]);
|
|
67
|
+
var CODEX_CONFIG_PATHS = uniquePaths([join(CODEX_DIR, "config.toml")]);
|
|
68
|
+
var OPENCLAW_CONFIG_PATHS = uniquePaths([
|
|
69
|
+
join(OPENCLAW_DIR, "openclaw.json")
|
|
70
|
+
]);
|
|
71
|
+
var CLAUDE_MCP_PATH = CLAUDE_MCP_PATHS[0];
|
|
72
|
+
var CURSOR_MCP_PATH = CURSOR_MCP_PATHS[0];
|
|
73
|
+
var CODEX_CONFIG_PATH = CODEX_CONFIG_PATHS[0];
|
|
74
|
+
var OPENCLAW_CONFIG_PATH = OPENCLAW_CONFIG_PATHS[0];
|
|
75
|
+
var CLAUDE_INSTALL_PATHS = uniquePaths([CLAUDE_DIR]);
|
|
76
|
+
var CURSOR_INSTALL_PATHS = uniquePaths([CURSOR_DIR]);
|
|
77
|
+
var CODEX_INSTALL_PATHS = uniquePaths([CODEX_DIR]);
|
|
78
|
+
var OPENCLAW_INSTALL_PATHS = uniquePaths([OPENCLAW_DIR]);
|
|
79
|
+
var VSCODE_MCP_PATHS = uniquePaths([
|
|
80
|
+
join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
81
|
+
join(XDG_CONFIG_HOME, "Code", "User", "mcp.json"),
|
|
82
|
+
APPDATA ? join(APPDATA, "Code", "User", "mcp.json") : void 0
|
|
83
|
+
]);
|
|
84
|
+
var VSCODE_INSTALL_PATHS = uniquePaths([
|
|
85
|
+
join(HOME, "Library", "Application Support", "Code", "User"),
|
|
86
|
+
join(XDG_CONFIG_HOME, "Code", "User"),
|
|
87
|
+
APPDATA ? join(APPDATA, "Code", "User") : void 0,
|
|
88
|
+
LOCAL_APPDATA ? join(LOCAL_APPDATA, "Programs", "Microsoft VS Code") : void 0
|
|
89
|
+
]);
|
|
90
|
+
var WINDSURF_MCP_PATHS = uniquePaths([
|
|
91
|
+
join(HOME, ".codeium", "windsurf", "mcp_config.json")
|
|
92
|
+
]);
|
|
93
|
+
var WINDSURF_INSTALL_PATHS = uniquePaths([
|
|
94
|
+
join(HOME, ".codeium", "windsurf"),
|
|
95
|
+
LOCAL_APPDATA ? join(LOCAL_APPDATA, "Programs", "Windsurf") : void 0
|
|
96
|
+
]);
|
|
97
|
+
var ZED_SETTINGS_PATHS = uniquePaths([
|
|
98
|
+
join(HOME, "Library", "Application Support", "Zed", "settings.json"),
|
|
99
|
+
join(XDG_CONFIG_HOME, "zed", "settings.json"),
|
|
100
|
+
APPDATA ? join(APPDATA, "Zed", "settings.json") : void 0
|
|
101
|
+
]);
|
|
102
|
+
var ZED_INSTALL_PATHS = uniquePaths([
|
|
103
|
+
join(HOME, "Library", "Application Support", "Zed"),
|
|
104
|
+
join(XDG_CONFIG_HOME, "zed"),
|
|
105
|
+
APPDATA ? join(APPDATA, "Zed") : void 0,
|
|
106
|
+
LOCAL_APPDATA ? join(LOCAL_APPDATA, "Programs", "Zed") : void 0
|
|
107
|
+
]);
|
|
108
|
+
var CHATGPT_APP_PATHS = uniquePaths([
|
|
109
|
+
join(HOME, "Library", "Application Support", "OpenAI"),
|
|
110
|
+
join(HOME, "Library", "Application Support", "com.openai.chat"),
|
|
111
|
+
join(XDG_CONFIG_HOME, "OpenAI"),
|
|
112
|
+
join(XDG_CONFIG_HOME, "openai"),
|
|
113
|
+
APPDATA ? join(APPDATA, "OpenAI") : void 0,
|
|
114
|
+
LOCAL_APPDATA ? join(LOCAL_APPDATA, "OpenAI") : void 0
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
// src/lib/browser-auth.ts
|
|
118
|
+
import { spawnSync } from "child_process";
|
|
119
|
+
|
|
120
|
+
// src/lib/fs.ts
|
|
121
|
+
import {
|
|
122
|
+
chmodSync,
|
|
123
|
+
existsSync,
|
|
124
|
+
mkdirSync,
|
|
125
|
+
readFileSync,
|
|
126
|
+
statSync,
|
|
127
|
+
unlinkSync,
|
|
128
|
+
writeFileSync
|
|
129
|
+
} from "fs";
|
|
130
|
+
import { dirname } from "path";
|
|
131
|
+
function readTextIfExists(path) {
|
|
132
|
+
if (!existsSync(path)) return null;
|
|
133
|
+
try {
|
|
134
|
+
if (!statSync(path).isFile()) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return readFileSync(path, "utf8");
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
140
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function writeTextFile(path, content, options = {}) {
|
|
147
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
148
|
+
writeFileSync(path, content, {
|
|
149
|
+
encoding: "utf8",
|
|
150
|
+
...options.mode === void 0 ? {} : { mode: options.mode }
|
|
151
|
+
});
|
|
152
|
+
if (options.mode !== void 0) {
|
|
153
|
+
chmodSync(path, options.mode);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function writeJsonFile(path, value, options = {}) {
|
|
157
|
+
writeTextFile(path, `${JSON.stringify(value, null, 2)}
|
|
158
|
+
`, options);
|
|
159
|
+
}
|
|
160
|
+
function deleteFileIfExists(path) {
|
|
161
|
+
if (!existsSync(path)) return false;
|
|
162
|
+
try {
|
|
163
|
+
if (!statSync(path).isFile()) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
unlinkSync(path);
|
|
167
|
+
return true;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const code = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
170
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function isRecord(value) {
|
|
177
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
178
|
+
}
|
|
179
|
+
function parseJsonObject(input) {
|
|
180
|
+
if (!input) return {};
|
|
181
|
+
try {
|
|
182
|
+
const parsed = JSON.parse(input);
|
|
183
|
+
return isRecord(parsed) ? parsed : {};
|
|
184
|
+
} catch {
|
|
185
|
+
return {};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function readObject(value, fallback = {}) {
|
|
189
|
+
return isRecord(value) ? value : fallback;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/surfaces/openclaw-config.ts
|
|
193
|
+
function cleanupEmptyObjects(value) {
|
|
194
|
+
for (const [key, child] of Object.entries(value)) {
|
|
195
|
+
if (!isRecord(child)) continue;
|
|
196
|
+
cleanupEmptyObjects(child);
|
|
197
|
+
if (Object.keys(child).length === 0) {
|
|
198
|
+
delete value[key];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function readOpenClawGatewayPort(raw) {
|
|
203
|
+
const isValidPort = (value) => Number.isFinite(value) && value >= 1 && value <= 65535;
|
|
204
|
+
const root = isRecord(raw) ? raw : {};
|
|
205
|
+
const gateway = readObject(root.gateway);
|
|
206
|
+
const port = gateway.port;
|
|
207
|
+
if (typeof port === "number" && isValidPort(port)) {
|
|
208
|
+
return Math.floor(port);
|
|
209
|
+
}
|
|
210
|
+
if (typeof port === "string") {
|
|
211
|
+
const parsed = Number.parseInt(port, 10);
|
|
212
|
+
if (isValidPort(parsed)) {
|
|
213
|
+
return parsed;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return DEFAULT_OPENCLAW_GATEWAY_PORT;
|
|
217
|
+
}
|
|
218
|
+
function buildLocalMcpUrl(raw) {
|
|
219
|
+
return `http://127.0.0.1:${readOpenClawGatewayPort(raw)}/orgx/mcp`;
|
|
220
|
+
}
|
|
221
|
+
function resolvePluginKey(entries) {
|
|
222
|
+
if (isRecord(entries["openclaw-plugin"])) {
|
|
223
|
+
return "openclaw-plugin";
|
|
224
|
+
}
|
|
225
|
+
if (isRecord(entries.orgx)) {
|
|
226
|
+
return "orgx";
|
|
227
|
+
}
|
|
228
|
+
return "orgx";
|
|
229
|
+
}
|
|
230
|
+
function readOpenClawAuth(input) {
|
|
231
|
+
const current = parseJsonObject(input);
|
|
232
|
+
const plugins = readObject(current.plugins);
|
|
233
|
+
const entries = readObject(plugins.entries);
|
|
234
|
+
const pluginKey = isRecord(entries["openclaw-plugin"]) ? "openclaw-plugin" : isRecord(entries.orgx) ? "orgx" : null;
|
|
235
|
+
const entry = pluginKey ? readObject(entries[pluginKey]) : {};
|
|
236
|
+
const config = readObject(entry.config);
|
|
237
|
+
return {
|
|
238
|
+
apiKey: typeof config.apiKey === "string" ? config.apiKey.trim() : "",
|
|
239
|
+
baseUrl: typeof config.baseUrl === "string" && config.baseUrl.trim().length > 0 ? config.baseUrl.trim() : DEFAULT_ORGX_BASE_URL,
|
|
240
|
+
pluginKey,
|
|
241
|
+
enabled: entry.enabled !== false
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function patchOpenClawConfig(input, auth = {}) {
|
|
245
|
+
const current = parseJsonObject(input);
|
|
246
|
+
const plugins = readObject(current.plugins);
|
|
247
|
+
const entries = readObject(plugins.entries);
|
|
248
|
+
const pluginKey = resolvePluginKey(entries);
|
|
249
|
+
const entry = readObject(entries[pluginKey]);
|
|
250
|
+
const config = readObject(entry.config);
|
|
251
|
+
const apiKey = typeof auth.apiKey === "string" && auth.apiKey.trim().length > 0 ? auth.apiKey.trim() : typeof config.apiKey === "string" && config.apiKey.trim().length > 0 ? config.apiKey.trim() : void 0;
|
|
252
|
+
const baseUrl = typeof auth.baseUrl === "string" && auth.baseUrl.trim().length > 0 ? auth.baseUrl.trim() : typeof config.baseUrl === "string" && config.baseUrl.trim().length > 0 ? config.baseUrl.trim() : DEFAULT_ORGX_BASE_URL;
|
|
253
|
+
entries[pluginKey] = {
|
|
254
|
+
...entry,
|
|
255
|
+
enabled: true,
|
|
256
|
+
config: {
|
|
257
|
+
...config,
|
|
258
|
+
...apiKey ? { apiKey } : {},
|
|
259
|
+
baseUrl,
|
|
260
|
+
dashboardEnabled: typeof config.dashboardEnabled === "boolean" ? config.dashboardEnabled : true,
|
|
261
|
+
autoInstallAgentSuiteOnConnect: typeof config.autoInstallAgentSuiteOnConnect === "boolean" ? config.autoInstallAgentSuiteOnConnect : true,
|
|
262
|
+
autoConfigureMcpClientsOnConnect: typeof config.autoConfigureMcpClientsOnConnect === "boolean" ? config.autoConfigureMcpClientsOnConnect : true
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
plugins.entries = entries;
|
|
266
|
+
current.plugins = plugins;
|
|
267
|
+
return `${JSON.stringify(current, null, 2)}
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
function removeOpenClawConfig(input) {
|
|
271
|
+
const current = parseJsonObject(input);
|
|
272
|
+
const plugins = readObject(current.plugins);
|
|
273
|
+
const entries = readObject(plugins.entries);
|
|
274
|
+
for (const key of ["openclaw-plugin", "orgx"]) {
|
|
275
|
+
const entry = readObject(entries[key]);
|
|
276
|
+
if (!Object.keys(entry).length) continue;
|
|
277
|
+
const config = readObject(entry.config);
|
|
278
|
+
delete config.baseUrl;
|
|
279
|
+
delete config.dashboardEnabled;
|
|
280
|
+
delete config.autoInstallAgentSuiteOnConnect;
|
|
281
|
+
delete config.autoConfigureMcpClientsOnConnect;
|
|
282
|
+
if (Object.keys(config).length > 0) {
|
|
283
|
+
entry.config = config;
|
|
284
|
+
} else {
|
|
285
|
+
delete entry.config;
|
|
286
|
+
}
|
|
287
|
+
entries[key] = entry;
|
|
288
|
+
}
|
|
289
|
+
plugins.entries = entries;
|
|
290
|
+
current.plugins = plugins;
|
|
291
|
+
cleanupEmptyObjects(current);
|
|
292
|
+
return `${JSON.stringify(current, null, 2)}
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
function inspectOpenClawConfig(input) {
|
|
296
|
+
const current = parseJsonObject(input);
|
|
297
|
+
const plugins = readObject(current.plugins);
|
|
298
|
+
const entries = readObject(plugins.entries);
|
|
299
|
+
const installs = readObject(plugins.installs);
|
|
300
|
+
const pluginKey = isRecord(entries["openclaw-plugin"]) ? "openclaw-plugin" : isRecord(entries.orgx) ? "orgx" : null;
|
|
301
|
+
const entry = pluginKey ? readObject(entries[pluginKey]) : {};
|
|
302
|
+
const config = readObject(entry.config);
|
|
303
|
+
const installed = pluginKey !== null || isRecord(installs["openclaw-plugin"]) || isRecord(installs.orgx);
|
|
304
|
+
const configured = installed && entry.enabled === true && (typeof config.baseUrl === "string" || typeof config.autoInstallAgentSuiteOnConnect === "boolean" || typeof config.autoConfigureMcpClientsOnConnect === "boolean");
|
|
305
|
+
return {
|
|
306
|
+
configured,
|
|
307
|
+
installed,
|
|
308
|
+
pluginKey,
|
|
309
|
+
details: [
|
|
310
|
+
`plugin key: ${pluginKey ?? "missing"}`,
|
|
311
|
+
`enabled: ${entry.enabled === true ? "yes" : "no"}`,
|
|
312
|
+
`gateway port: ${readOpenClawGatewayPort(current)}`
|
|
313
|
+
]
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/lib/auth-store.ts
|
|
318
|
+
function isNonEmptyString(value) {
|
|
319
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
320
|
+
}
|
|
321
|
+
function extractKeyPrefix(apiKey) {
|
|
322
|
+
return apiKey.trim().slice(0, 12);
|
|
323
|
+
}
|
|
324
|
+
function parseStoredWizardAuthRecord(raw) {
|
|
325
|
+
const parsed = parseJsonObject(raw);
|
|
326
|
+
if (!isNonEmptyString(parsed.keyPrefix) || !isNonEmptyString(parsed.baseUrl) || !isNonEmptyString(parsed.verifiedAt)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const storage = parsed.storage === "keytar" ? "keytar" : "file";
|
|
330
|
+
return {
|
|
331
|
+
baseUrl: parsed.baseUrl.trim(),
|
|
332
|
+
...isNonEmptyString(parsed.apiKey) ? { apiKey: parsed.apiKey.trim() } : {},
|
|
333
|
+
keyPrefix: parsed.keyPrefix.trim(),
|
|
334
|
+
storage,
|
|
335
|
+
verifiedAt: parsed.verifiedAt.trim()
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function loadDefaultSecretStore() {
|
|
339
|
+
if (process.env.ORGX_WIZARD_DISABLE_KEYTAR === "1") {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const imported = await import("keytar");
|
|
344
|
+
const candidate = "default" in imported && imported.default ? imported.default : imported;
|
|
345
|
+
if (typeof candidate.getPassword === "function" && typeof candidate.setPassword === "function" && typeof candidate.deletePassword === "function") {
|
|
346
|
+
return candidate;
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
async function resolveSecretStore(options) {
|
|
354
|
+
if (options.secretStore !== void 0) {
|
|
355
|
+
return options.secretStore;
|
|
356
|
+
}
|
|
357
|
+
return loadDefaultSecretStore();
|
|
358
|
+
}
|
|
359
|
+
function buildStoredRecord(value) {
|
|
360
|
+
return {
|
|
361
|
+
...value.storage === "file" ? { apiKey: value.apiKey } : {},
|
|
362
|
+
baseUrl: value.baseUrl,
|
|
363
|
+
keyPrefix: value.keyPrefix,
|
|
364
|
+
storage: value.storage,
|
|
365
|
+
verifiedAt: value.verifiedAt
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function readWizardAuth(authPath = ORGX_WIZARD_AUTH_PATH, options = {}) {
|
|
369
|
+
const stored = parseStoredWizardAuthRecord(readTextIfExists(authPath));
|
|
370
|
+
if (!stored) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const apiKeyFromFile = stored.storage === "file" && isNonEmptyString(stored.apiKey) ? stored.apiKey.trim() : null;
|
|
374
|
+
if (apiKeyFromFile) {
|
|
375
|
+
return {
|
|
376
|
+
apiKey: apiKeyFromFile,
|
|
377
|
+
baseUrl: stored.baseUrl,
|
|
378
|
+
keyPrefix: stored.keyPrefix,
|
|
379
|
+
source: "manual",
|
|
380
|
+
storage: "file",
|
|
381
|
+
verifiedAt: stored.verifiedAt
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (stored.storage !== "keytar") {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const secretStore = await resolveSecretStore(options);
|
|
388
|
+
if (!secretStore) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
const apiKey = await secretStore.getPassword(
|
|
392
|
+
ORGX_WIZARD_KEYTAR_SERVICE,
|
|
393
|
+
ORGX_WIZARD_KEYTAR_ACCOUNT
|
|
394
|
+
);
|
|
395
|
+
if (!isNonEmptyString(apiKey)) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
apiKey: apiKey.trim(),
|
|
400
|
+
baseUrl: stored.baseUrl,
|
|
401
|
+
keyPrefix: stored.keyPrefix,
|
|
402
|
+
source: "manual",
|
|
403
|
+
storage: "keytar",
|
|
404
|
+
verifiedAt: stored.verifiedAt
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
async function writeWizardAuth(value, authPath = ORGX_WIZARD_AUTH_PATH, options = {}) {
|
|
408
|
+
const secretStore = options.preferSecureStorage === false ? null : await resolveSecretStore(options);
|
|
409
|
+
const record = {
|
|
410
|
+
apiKey: value.apiKey.trim(),
|
|
411
|
+
keyPrefix: value.keyPrefix?.trim() || extractKeyPrefix(value.apiKey),
|
|
412
|
+
baseUrl: value.baseUrl.trim(),
|
|
413
|
+
source: "manual",
|
|
414
|
+
storage: secretStore ? "keytar" : "file",
|
|
415
|
+
verifiedAt: value.verifiedAt.trim()
|
|
416
|
+
};
|
|
417
|
+
if (secretStore) {
|
|
418
|
+
await secretStore.setPassword(
|
|
419
|
+
ORGX_WIZARD_KEYTAR_SERVICE,
|
|
420
|
+
ORGX_WIZARD_KEYTAR_ACCOUNT,
|
|
421
|
+
record.apiKey
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
writeJsonFile(authPath, buildStoredRecord(record), { mode: 384 });
|
|
425
|
+
return record;
|
|
426
|
+
}
|
|
427
|
+
async function clearWizardAuth(authPath = ORGX_WIZARD_AUTH_PATH, options = {}) {
|
|
428
|
+
const secretStore = await resolveSecretStore(options);
|
|
429
|
+
const secretRemoved = secretStore ? await secretStore.deletePassword(
|
|
430
|
+
ORGX_WIZARD_KEYTAR_SERVICE,
|
|
431
|
+
ORGX_WIZARD_KEYTAR_ACCOUNT
|
|
432
|
+
) : false;
|
|
433
|
+
const fileRemoved = deleteFileIfExists(authPath);
|
|
434
|
+
return secretRemoved || fileRemoved;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/lib/auth.ts
|
|
438
|
+
function normalizeHost(value) {
|
|
439
|
+
return value.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
440
|
+
}
|
|
441
|
+
function isLoopbackHostname(hostname2) {
|
|
442
|
+
const normalized = normalizeHost(hostname2);
|
|
443
|
+
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
444
|
+
}
|
|
445
|
+
function normalizeOrgxBaseUrl(raw) {
|
|
446
|
+
const candidate = raw?.trim() || DEFAULT_ORGX_BASE_URL;
|
|
447
|
+
try {
|
|
448
|
+
const parsed = new URL(candidate);
|
|
449
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
450
|
+
return DEFAULT_ORGX_BASE_URL;
|
|
451
|
+
}
|
|
452
|
+
if (parsed.username || parsed.password) {
|
|
453
|
+
return DEFAULT_ORGX_BASE_URL;
|
|
454
|
+
}
|
|
455
|
+
if (parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) {
|
|
456
|
+
return DEFAULT_ORGX_BASE_URL;
|
|
457
|
+
}
|
|
458
|
+
parsed.search = "";
|
|
459
|
+
parsed.hash = "";
|
|
460
|
+
parsed.pathname = parsed.pathname.replace(/\/api\/?$/, "").replace(/\/+$/, "");
|
|
461
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
462
|
+
} catch {
|
|
463
|
+
return DEFAULT_ORGX_BASE_URL;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function buildOrgxApiUrl(path, baseUrl) {
|
|
467
|
+
const normalizedBase = normalizeOrgxBaseUrl(baseUrl).replace(/\/+$/, "");
|
|
468
|
+
const apiBase = normalizedBase.endsWith("/api") ? normalizedBase : `${normalizedBase}/api`;
|
|
469
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
470
|
+
return `${apiBase}${normalizedPath}`;
|
|
471
|
+
}
|
|
472
|
+
function withResolvedBaseUrl(baseUrl, envBaseUrl) {
|
|
473
|
+
return normalizeOrgxBaseUrl(envBaseUrl?.trim() || baseUrl);
|
|
474
|
+
}
|
|
475
|
+
function buildResolvedAuth(apiKey, baseUrl, source, extras = {}) {
|
|
476
|
+
return {
|
|
477
|
+
apiKey: apiKey.trim(),
|
|
478
|
+
keyPrefix: extractKeyPrefix(apiKey),
|
|
479
|
+
baseUrl,
|
|
480
|
+
source,
|
|
481
|
+
...extras.path ? { path: extras.path } : {},
|
|
482
|
+
...extras.verifiedAt ? { verifiedAt: extras.verifiedAt } : {}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async function resolveOrgxAuth(options = {}) {
|
|
486
|
+
const envApiKey = process.env.ORGX_API_KEY?.trim();
|
|
487
|
+
const envBaseUrl = process.env.ORGX_BASE_URL?.trim();
|
|
488
|
+
if (envApiKey) {
|
|
489
|
+
return buildResolvedAuth(
|
|
490
|
+
envApiKey,
|
|
491
|
+
withResolvedBaseUrl(DEFAULT_ORGX_BASE_URL, envBaseUrl),
|
|
492
|
+
"environment"
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
const authPath = options.authPath ?? ORGX_WIZARD_AUTH_PATH;
|
|
496
|
+
const stored = await readWizardAuth(authPath);
|
|
497
|
+
if (stored) {
|
|
498
|
+
return buildResolvedAuth(
|
|
499
|
+
stored.apiKey,
|
|
500
|
+
withResolvedBaseUrl(stored.baseUrl, envBaseUrl),
|
|
501
|
+
"wizard-store",
|
|
502
|
+
{
|
|
503
|
+
path: authPath,
|
|
504
|
+
verifiedAt: stored.verifiedAt
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
const openclawConfigPath = options.openclawConfigPath ?? OPENCLAW_CONFIG_PATH;
|
|
509
|
+
const openclawAuth = readOpenClawAuth(
|
|
510
|
+
openclawConfigPath ? readTextIfExists(openclawConfigPath) : null
|
|
511
|
+
);
|
|
512
|
+
if (openclawAuth.apiKey && openclawConfigPath) {
|
|
513
|
+
return buildResolvedAuth(
|
|
514
|
+
openclawAuth.apiKey,
|
|
515
|
+
withResolvedBaseUrl(openclawAuth.baseUrl || DEFAULT_ORGX_BASE_URL, envBaseUrl),
|
|
516
|
+
"openclaw-config-file",
|
|
517
|
+
{ path: openclawConfigPath }
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
async function parseResponseBody(response) {
|
|
523
|
+
const text2 = await response.text();
|
|
524
|
+
if (!text2) return null;
|
|
525
|
+
try {
|
|
526
|
+
return JSON.parse(text2);
|
|
527
|
+
} catch {
|
|
528
|
+
return text2;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async function verifyOrgxAuth(auth) {
|
|
532
|
+
const url = buildOrgxApiUrl("/client/sync", auth.baseUrl);
|
|
533
|
+
try {
|
|
534
|
+
const response = await fetch(url, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: {
|
|
537
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
538
|
+
"Content-Type": "application/json"
|
|
539
|
+
},
|
|
540
|
+
body: "{}",
|
|
541
|
+
signal: AbortSignal.timeout(7e3)
|
|
542
|
+
});
|
|
543
|
+
const data = await parseResponseBody(response);
|
|
544
|
+
return {
|
|
545
|
+
configured: true,
|
|
546
|
+
ok: response.ok,
|
|
547
|
+
skipped: false,
|
|
548
|
+
source: auth.source,
|
|
549
|
+
keyPrefix: auth.keyPrefix,
|
|
550
|
+
baseUrl: auth.baseUrl,
|
|
551
|
+
...auth.path ? { path: auth.path } : {},
|
|
552
|
+
...auth.verifiedAt ? { verifiedAt: auth.verifiedAt } : {},
|
|
553
|
+
status: response.status,
|
|
554
|
+
url,
|
|
555
|
+
data,
|
|
556
|
+
...response.ok ? {} : { error: `HTTP ${response.status}` }
|
|
557
|
+
};
|
|
558
|
+
} catch (error) {
|
|
559
|
+
return {
|
|
560
|
+
configured: true,
|
|
561
|
+
ok: false,
|
|
562
|
+
skipped: false,
|
|
563
|
+
source: auth.source,
|
|
564
|
+
keyPrefix: auth.keyPrefix,
|
|
565
|
+
baseUrl: auth.baseUrl,
|
|
566
|
+
...auth.path ? { path: auth.path } : {},
|
|
567
|
+
...auth.verifiedAt ? { verifiedAt: auth.verifiedAt } : {},
|
|
568
|
+
url,
|
|
569
|
+
error: error instanceof Error ? error.message : String(error)
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async function checkOrgxAuth(options = {}) {
|
|
574
|
+
const auth = await resolveOrgxAuth(options);
|
|
575
|
+
if (!auth) {
|
|
576
|
+
return {
|
|
577
|
+
configured: false,
|
|
578
|
+
ok: false,
|
|
579
|
+
skipped: true,
|
|
580
|
+
source: "none",
|
|
581
|
+
error: "No OrgX API key found in ORGX_API_KEY, the wizard auth store, or OpenClaw config.",
|
|
582
|
+
url: buildOrgxApiUrl("/client/sync", DEFAULT_ORGX_BASE_URL)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
return verifyOrgxAuth(auth);
|
|
586
|
+
}
|
|
587
|
+
function describeSetupStatusScope() {
|
|
588
|
+
return "The setup-status endpoint is still service-key-only and uses ORGX_SERVICE_KEY.";
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/lib/browser-auth.ts
|
|
592
|
+
function asString(value) {
|
|
593
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
594
|
+
}
|
|
595
|
+
function parseJsonBody(text2) {
|
|
596
|
+
if (!text2) return null;
|
|
597
|
+
try {
|
|
598
|
+
return JSON.parse(text2);
|
|
599
|
+
} catch {
|
|
600
|
+
return text2;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async function fetchJson({
|
|
604
|
+
url,
|
|
605
|
+
method,
|
|
606
|
+
body,
|
|
607
|
+
fetchImpl = fetch
|
|
608
|
+
}) {
|
|
609
|
+
const response = await fetchImpl(url, {
|
|
610
|
+
method,
|
|
611
|
+
headers: {
|
|
612
|
+
Accept: "application/json",
|
|
613
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
614
|
+
},
|
|
615
|
+
...body ? { body } : {},
|
|
616
|
+
signal: AbortSignal.timeout(7e3)
|
|
617
|
+
});
|
|
618
|
+
const payload = parseJsonBody(await response.text());
|
|
619
|
+
if (!response.ok) {
|
|
620
|
+
const errorMessage = isRecord(payload) ? asString(payload.error) ?? asString(payload.message) : typeof payload === "string" ? payload : null;
|
|
621
|
+
throw new Error(errorMessage ?? `HTTP ${response.status}`);
|
|
622
|
+
}
|
|
623
|
+
if (isRecord(payload) && payload.ok === true && "data" in payload) {
|
|
624
|
+
return payload.data;
|
|
625
|
+
}
|
|
626
|
+
return payload;
|
|
627
|
+
}
|
|
628
|
+
function parsePairingStartResult(value) {
|
|
629
|
+
if (!isRecord(value)) {
|
|
630
|
+
throw new Error("OrgX pairing start returned an invalid response.");
|
|
631
|
+
}
|
|
632
|
+
const pairingId = asString(value.pairingId);
|
|
633
|
+
const pollToken = asString(value.pollToken);
|
|
634
|
+
const connectUrl = asString(value.connectUrl);
|
|
635
|
+
const expiresAt = asString(value.expiresAt);
|
|
636
|
+
const pollIntervalMs = typeof value.pollIntervalMs === "number" ? value.pollIntervalMs : Number.NaN;
|
|
637
|
+
if (!pairingId || !pollToken || !connectUrl || !expiresAt || !Number.isFinite(pollIntervalMs)) {
|
|
638
|
+
throw new Error("OrgX pairing start returned an incomplete response.");
|
|
639
|
+
}
|
|
640
|
+
return { pairingId, pollToken, connectUrl, expiresAt, pollIntervalMs };
|
|
641
|
+
}
|
|
642
|
+
function parsePairingPollResult(value) {
|
|
643
|
+
if (!isRecord(value)) {
|
|
644
|
+
throw new Error("OrgX pairing poll returned an invalid response.");
|
|
645
|
+
}
|
|
646
|
+
const pairingId = asString(value.pairingId);
|
|
647
|
+
const status = asString(value.status);
|
|
648
|
+
const expiresAt = asString(value.expiresAt);
|
|
649
|
+
if (!pairingId || !status || !expiresAt) {
|
|
650
|
+
throw new Error("OrgX pairing poll returned an incomplete response.");
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
pairingId,
|
|
654
|
+
status,
|
|
655
|
+
expiresAt,
|
|
656
|
+
workspaceName: asString(value.workspaceName),
|
|
657
|
+
keyPrefix: asString(value.keyPrefix),
|
|
658
|
+
executionMode: value.executionMode === "cloud" || value.executionMode === "local" ? value.executionMode : null,
|
|
659
|
+
errorCode: asString(value.errorCode),
|
|
660
|
+
errorMessage: asString(value.errorMessage),
|
|
661
|
+
...asString(value.key) ? { key: asString(value.key) } : {}
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function sleep(ms) {
|
|
665
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
666
|
+
}
|
|
667
|
+
async function startBrowserPairing(options, fetchImpl) {
|
|
668
|
+
const data = await fetchJson({
|
|
669
|
+
url: buildOrgxApiUrl("/plugin/openclaw/pairings", options.baseUrl),
|
|
670
|
+
method: "POST",
|
|
671
|
+
body: JSON.stringify({
|
|
672
|
+
installationId: options.installationId,
|
|
673
|
+
...options.deviceName ? { deviceName: options.deviceName } : {},
|
|
674
|
+
...options.platform ? { platform: options.platform } : {}
|
|
675
|
+
}),
|
|
676
|
+
fetchImpl
|
|
677
|
+
});
|
|
678
|
+
return parsePairingStartResult(data);
|
|
679
|
+
}
|
|
680
|
+
async function pollBrowserPairing(options, fetchImpl) {
|
|
681
|
+
const baseUrl = normalizeOrgxBaseUrl(options.baseUrl);
|
|
682
|
+
const url = new URL(buildOrgxApiUrl(`/plugin/openclaw/pairings/${options.pairingId}`, baseUrl));
|
|
683
|
+
url.searchParams.set("pollToken", options.pollToken);
|
|
684
|
+
const data = await fetchJson({
|
|
685
|
+
url: url.toString(),
|
|
686
|
+
method: "GET",
|
|
687
|
+
fetchImpl
|
|
688
|
+
});
|
|
689
|
+
return parsePairingPollResult(data);
|
|
690
|
+
}
|
|
691
|
+
async function waitForBrowserPairing(options, fetchImpl) {
|
|
692
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
693
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
694
|
+
const deadline = Date.now() + timeoutMs;
|
|
695
|
+
while (Date.now() <= deadline) {
|
|
696
|
+
const result = await pollBrowserPairing(
|
|
697
|
+
{
|
|
698
|
+
baseUrl: options.baseUrl,
|
|
699
|
+
pairingId: options.pairingId,
|
|
700
|
+
pollToken: options.pollToken
|
|
701
|
+
},
|
|
702
|
+
fetchImpl
|
|
703
|
+
);
|
|
704
|
+
if (result.status === "ready") {
|
|
705
|
+
if (!result.key) {
|
|
706
|
+
throw new Error("OrgX pairing completed without returning an API key.");
|
|
707
|
+
}
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
if (result.status === "failed") {
|
|
711
|
+
throw new Error(result.errorMessage ?? "OrgX pairing failed.");
|
|
712
|
+
}
|
|
713
|
+
if (result.status === "expired") {
|
|
714
|
+
throw new Error("OrgX pairing expired before it was approved.");
|
|
715
|
+
}
|
|
716
|
+
if (result.status === "cancelled") {
|
|
717
|
+
throw new Error("OrgX pairing was cancelled in the browser.");
|
|
718
|
+
}
|
|
719
|
+
if (result.status === "consumed") {
|
|
720
|
+
throw new Error("OrgX pairing was already consumed by another client.");
|
|
721
|
+
}
|
|
722
|
+
await sleep(Math.min(5e3, Math.max(350, pollIntervalMs)));
|
|
723
|
+
}
|
|
724
|
+
throw new Error(
|
|
725
|
+
`Timed out waiting for OrgX pairing after ${Math.round(timeoutMs / 1e3)} seconds.`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
async function acknowledgeBrowserPairing(options, fetchImpl) {
|
|
729
|
+
await fetchJson({
|
|
730
|
+
url: buildOrgxApiUrl(`/plugin/openclaw/pairings/${options.pairingId}/ack`, options.baseUrl),
|
|
731
|
+
method: "POST",
|
|
732
|
+
body: JSON.stringify({ pollToken: options.pollToken }),
|
|
733
|
+
fetchImpl
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
function openBrowser(url) {
|
|
737
|
+
const platform = process.platform;
|
|
738
|
+
const result = platform === "darwin" ? spawnSync("open", [url], { stdio: "ignore" }) : platform === "win32" ? spawnSync("cmd", ["/c", "start", "", url], { stdio: "ignore" }) : spawnSync("xdg-open", [url], { stdio: "ignore" });
|
|
739
|
+
if (result.error) {
|
|
740
|
+
return { ok: false, error: result.error.message };
|
|
741
|
+
}
|
|
742
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
743
|
+
return { ok: false, error: `Browser open command exited with code ${result.status}.` };
|
|
744
|
+
}
|
|
745
|
+
return { ok: true };
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/lib/setup-status.ts
|
|
749
|
+
async function fetchSetupStatus(apiKey = process.env.ORGX_SERVICE_KEY?.trim()) {
|
|
750
|
+
if (!apiKey) {
|
|
751
|
+
return {
|
|
752
|
+
ok: false,
|
|
753
|
+
skipped: true,
|
|
754
|
+
error: "ORGX_SERVICE_KEY is not set; /api/setup/status is a service-level check and was skipped.",
|
|
755
|
+
url: ORGX_SETUP_STATUS_URL
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const response = await fetch(ORGX_SETUP_STATUS_URL, {
|
|
760
|
+
headers: {
|
|
761
|
+
Authorization: `Bearer ${apiKey}`
|
|
762
|
+
},
|
|
763
|
+
signal: AbortSignal.timeout(7e3)
|
|
764
|
+
});
|
|
765
|
+
const text2 = await response.text();
|
|
766
|
+
let data = text2;
|
|
767
|
+
try {
|
|
768
|
+
data = text2.length > 0 ? JSON.parse(text2) : null;
|
|
769
|
+
} catch {
|
|
770
|
+
data = text2;
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
ok: response.ok,
|
|
774
|
+
skipped: false,
|
|
775
|
+
status: response.status,
|
|
776
|
+
data,
|
|
777
|
+
url: ORGX_SETUP_STATUS_URL,
|
|
778
|
+
...response.ok ? {} : { error: `HTTP ${response.status}` }
|
|
779
|
+
};
|
|
780
|
+
} catch (error) {
|
|
781
|
+
return {
|
|
782
|
+
ok: false,
|
|
783
|
+
skipped: false,
|
|
784
|
+
error: error instanceof Error ? error.message : String(error),
|
|
785
|
+
url: ORGX_SETUP_STATUS_URL
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/lib/openclaw-health.ts
|
|
791
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
792
|
+
function trimOutput(value) {
|
|
793
|
+
const next = value?.trim();
|
|
794
|
+
return next ? next : void 0;
|
|
795
|
+
}
|
|
796
|
+
async function checkOpenClawHealth(configPath) {
|
|
797
|
+
const rawConfig = configPath ? readTextIfExists(configPath) : null;
|
|
798
|
+
const port = readOpenClawGatewayPort(rawConfig);
|
|
799
|
+
const url = `http://127.0.0.1:${port}/orgx/live`;
|
|
800
|
+
const cli = spawnSync2("openclaw", ["gateway", "status"], {
|
|
801
|
+
encoding: "utf8",
|
|
802
|
+
timeout: 5e3
|
|
803
|
+
});
|
|
804
|
+
if (!cli.error) {
|
|
805
|
+
const stdout = trimOutput(cli.stdout);
|
|
806
|
+
const stderr = trimOutput(cli.stderr);
|
|
807
|
+
const details = [`gateway port: ${port}`];
|
|
808
|
+
if (stdout) details.push(`cli: ${stdout}`);
|
|
809
|
+
if (stderr) details.push(`cli stderr: ${stderr}`);
|
|
810
|
+
if (cli.status === 0) {
|
|
811
|
+
return {
|
|
812
|
+
ok: true,
|
|
813
|
+
skipped: false,
|
|
814
|
+
method: "cli",
|
|
815
|
+
url,
|
|
816
|
+
details
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
const response = await fetch(url, {
|
|
822
|
+
redirect: "manual",
|
|
823
|
+
signal: AbortSignal.timeout(5e3)
|
|
824
|
+
});
|
|
825
|
+
if (response.ok || response.status >= 300 && response.status < 400) {
|
|
826
|
+
return {
|
|
827
|
+
ok: true,
|
|
828
|
+
skipped: false,
|
|
829
|
+
method: "http",
|
|
830
|
+
url,
|
|
831
|
+
details: [`gateway port: ${port}`, `http status: ${response.status}`]
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
ok: false,
|
|
836
|
+
skipped: false,
|
|
837
|
+
method: "http",
|
|
838
|
+
url,
|
|
839
|
+
error: `HTTP ${response.status}`,
|
|
840
|
+
details: [`gateway port: ${port}`, `http status: ${response.status}`]
|
|
841
|
+
};
|
|
842
|
+
} catch (error) {
|
|
843
|
+
const cliError = cli.error instanceof Error ? cli.error.message : void 0;
|
|
844
|
+
return {
|
|
845
|
+
ok: false,
|
|
846
|
+
skipped: false,
|
|
847
|
+
method: cliError ? "none" : "http",
|
|
848
|
+
url,
|
|
849
|
+
error: error instanceof Error ? error.message : String(error),
|
|
850
|
+
details: [
|
|
851
|
+
`gateway port: ${port}`,
|
|
852
|
+
...cliError ? [`openclaw cli unavailable: ${cliError}`] : []
|
|
853
|
+
]
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/lib/hosted-mcp-health.ts
|
|
859
|
+
function trimBody(value) {
|
|
860
|
+
const next = value.trim();
|
|
861
|
+
if (!next) return void 0;
|
|
862
|
+
return next.length > 120 ? `${next.slice(0, 117)}...` : next;
|
|
863
|
+
}
|
|
864
|
+
async function checkHostedMcpHealth() {
|
|
865
|
+
try {
|
|
866
|
+
const response = await fetch(ORGX_HOSTED_MCP_HEALTH_URL, {
|
|
867
|
+
method: "GET",
|
|
868
|
+
signal: AbortSignal.timeout(5e3)
|
|
869
|
+
});
|
|
870
|
+
const text2 = await response.text();
|
|
871
|
+
const body = trimBody(text2);
|
|
872
|
+
return {
|
|
873
|
+
ok: response.ok,
|
|
874
|
+
skipped: false,
|
|
875
|
+
status: response.status,
|
|
876
|
+
url: ORGX_HOSTED_MCP_HEALTH_URL,
|
|
877
|
+
...body ? { body } : {},
|
|
878
|
+
...response.ok ? {} : { error: `HTTP ${response.status}` },
|
|
879
|
+
details: [
|
|
880
|
+
`http status: ${response.status}`,
|
|
881
|
+
...body ? [`body: ${body}`] : []
|
|
882
|
+
]
|
|
883
|
+
};
|
|
884
|
+
} catch (error) {
|
|
885
|
+
return {
|
|
886
|
+
ok: false,
|
|
887
|
+
skipped: false,
|
|
888
|
+
url: ORGX_HOSTED_MCP_HEALTH_URL,
|
|
889
|
+
error: error instanceof Error ? error.message : String(error),
|
|
890
|
+
details: []
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/lib/hosted-mcp-tool-check.ts
|
|
896
|
+
var DEFAULT_TOOL_NAME = "get_setup_status";
|
|
897
|
+
function buildHeaders(apiKey, sessionId) {
|
|
898
|
+
const headers = {
|
|
899
|
+
Authorization: `Bearer ${apiKey}`,
|
|
900
|
+
"Content-Type": "application/json",
|
|
901
|
+
Accept: "application/json, text/event-stream"
|
|
902
|
+
};
|
|
903
|
+
if (sessionId) {
|
|
904
|
+
headers["Mcp-Session-Id"] = sessionId;
|
|
905
|
+
}
|
|
906
|
+
return headers;
|
|
907
|
+
}
|
|
908
|
+
async function readJsonSafe(response) {
|
|
909
|
+
const text2 = await response.text();
|
|
910
|
+
if (!text2) return null;
|
|
911
|
+
try {
|
|
912
|
+
return JSON.parse(text2);
|
|
913
|
+
} catch {
|
|
914
|
+
return text2;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function readJsonRpcError(payload) {
|
|
918
|
+
if (!payload || typeof payload !== "object") {
|
|
919
|
+
return void 0;
|
|
920
|
+
}
|
|
921
|
+
const maybeError = payload.error;
|
|
922
|
+
if (!maybeError || typeof maybeError !== "object") {
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
return typeof maybeError.message === "string" ? maybeError.message : "Unknown MCP JSON-RPC error.";
|
|
926
|
+
}
|
|
927
|
+
function readSessionId(response) {
|
|
928
|
+
return response.headers.get("mcp-session-id") ?? response.headers.get("Mcp-Session-Id") ?? void 0;
|
|
929
|
+
}
|
|
930
|
+
async function checkHostedMcpToolAccess(options = {}, toolName = DEFAULT_TOOL_NAME) {
|
|
931
|
+
const auth = await resolveOrgxAuth(options);
|
|
932
|
+
if (!auth) {
|
|
933
|
+
return {
|
|
934
|
+
configured: false,
|
|
935
|
+
ok: false,
|
|
936
|
+
skipped: true,
|
|
937
|
+
source: "none",
|
|
938
|
+
toolName,
|
|
939
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
940
|
+
error: "No OrgX API key configured; hosted MCP tool verification skipped.",
|
|
941
|
+
details: []
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
const initializeResponse = await fetch(ORGX_HOSTED_MCP_URL, {
|
|
946
|
+
method: "POST",
|
|
947
|
+
headers: buildHeaders(auth.apiKey),
|
|
948
|
+
body: JSON.stringify({
|
|
949
|
+
jsonrpc: "2.0",
|
|
950
|
+
id: 1,
|
|
951
|
+
method: "initialize",
|
|
952
|
+
params: {
|
|
953
|
+
protocolVersion: "2024-11-05",
|
|
954
|
+
clientInfo: {
|
|
955
|
+
name: "orgx-wizard",
|
|
956
|
+
version: "0.1.0"
|
|
957
|
+
},
|
|
958
|
+
capabilities: {}
|
|
959
|
+
}
|
|
960
|
+
}),
|
|
961
|
+
signal: AbortSignal.timeout(1e4)
|
|
962
|
+
});
|
|
963
|
+
const initializePayload = await readJsonSafe(initializeResponse);
|
|
964
|
+
const initializeError = readJsonRpcError(initializePayload);
|
|
965
|
+
const sessionId = readSessionId(initializeResponse);
|
|
966
|
+
if (!initializeResponse.ok) {
|
|
967
|
+
return {
|
|
968
|
+
configured: true,
|
|
969
|
+
ok: false,
|
|
970
|
+
skipped: false,
|
|
971
|
+
source: auth.source,
|
|
972
|
+
toolName,
|
|
973
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
974
|
+
baseUrl: auth.baseUrl,
|
|
975
|
+
initializeStatus: initializeResponse.status,
|
|
976
|
+
error: `MCP initialize failed with HTTP ${initializeResponse.status}.`,
|
|
977
|
+
details: [`auth source: ${auth.source}`],
|
|
978
|
+
response: initializePayload
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
if (initializeError) {
|
|
982
|
+
return {
|
|
983
|
+
configured: true,
|
|
984
|
+
ok: false,
|
|
985
|
+
skipped: false,
|
|
986
|
+
source: auth.source,
|
|
987
|
+
toolName,
|
|
988
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
989
|
+
baseUrl: auth.baseUrl,
|
|
990
|
+
initializeStatus: initializeResponse.status,
|
|
991
|
+
error: `MCP initialize returned JSON-RPC error: ${initializeError}`,
|
|
992
|
+
details: [`auth source: ${auth.source}`],
|
|
993
|
+
response: initializePayload
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
if (!sessionId) {
|
|
997
|
+
return {
|
|
998
|
+
configured: true,
|
|
999
|
+
ok: false,
|
|
1000
|
+
skipped: false,
|
|
1001
|
+
source: auth.source,
|
|
1002
|
+
toolName,
|
|
1003
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
1004
|
+
baseUrl: auth.baseUrl,
|
|
1005
|
+
initializeStatus: initializeResponse.status,
|
|
1006
|
+
error: "MCP initialize succeeded but no Mcp-Session-Id header was returned.",
|
|
1007
|
+
details: [`auth source: ${auth.source}`],
|
|
1008
|
+
response: initializePayload
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
const callResponse = await fetch(ORGX_HOSTED_MCP_URL, {
|
|
1012
|
+
method: "POST",
|
|
1013
|
+
headers: buildHeaders(auth.apiKey, sessionId),
|
|
1014
|
+
body: JSON.stringify({
|
|
1015
|
+
jsonrpc: "2.0",
|
|
1016
|
+
id: 2,
|
|
1017
|
+
method: "tools/call",
|
|
1018
|
+
params: {
|
|
1019
|
+
name: toolName,
|
|
1020
|
+
arguments: {}
|
|
1021
|
+
}
|
|
1022
|
+
}),
|
|
1023
|
+
signal: AbortSignal.timeout(1e4)
|
|
1024
|
+
});
|
|
1025
|
+
const callPayload = await readJsonSafe(callResponse);
|
|
1026
|
+
const callError = readJsonRpcError(callPayload);
|
|
1027
|
+
if (!callResponse.ok) {
|
|
1028
|
+
return {
|
|
1029
|
+
configured: true,
|
|
1030
|
+
ok: false,
|
|
1031
|
+
skipped: false,
|
|
1032
|
+
source: auth.source,
|
|
1033
|
+
toolName,
|
|
1034
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
1035
|
+
baseUrl: auth.baseUrl,
|
|
1036
|
+
initializeStatus: initializeResponse.status,
|
|
1037
|
+
callStatus: callResponse.status,
|
|
1038
|
+
error: `MCP tool call failed with HTTP ${callResponse.status}.`,
|
|
1039
|
+
details: [
|
|
1040
|
+
`auth source: ${auth.source}`,
|
|
1041
|
+
`session established: ${sessionId}`
|
|
1042
|
+
],
|
|
1043
|
+
response: callPayload
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
if (callError) {
|
|
1047
|
+
return {
|
|
1048
|
+
configured: true,
|
|
1049
|
+
ok: false,
|
|
1050
|
+
skipped: false,
|
|
1051
|
+
source: auth.source,
|
|
1052
|
+
toolName,
|
|
1053
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
1054
|
+
baseUrl: auth.baseUrl,
|
|
1055
|
+
initializeStatus: initializeResponse.status,
|
|
1056
|
+
callStatus: callResponse.status,
|
|
1057
|
+
error: `MCP tool call returned JSON-RPC error: ${callError}`,
|
|
1058
|
+
details: [
|
|
1059
|
+
`auth source: ${auth.source}`,
|
|
1060
|
+
`session established: ${sessionId}`
|
|
1061
|
+
],
|
|
1062
|
+
response: callPayload
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
configured: true,
|
|
1067
|
+
ok: true,
|
|
1068
|
+
skipped: false,
|
|
1069
|
+
source: auth.source,
|
|
1070
|
+
toolName,
|
|
1071
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
1072
|
+
baseUrl: auth.baseUrl,
|
|
1073
|
+
initializeStatus: initializeResponse.status,
|
|
1074
|
+
callStatus: callResponse.status,
|
|
1075
|
+
details: [
|
|
1076
|
+
`auth source: ${auth.source}`,
|
|
1077
|
+
`session established: ${sessionId}`,
|
|
1078
|
+
`tool call: ${toolName}`
|
|
1079
|
+
],
|
|
1080
|
+
response: callPayload
|
|
1081
|
+
};
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
return {
|
|
1084
|
+
configured: true,
|
|
1085
|
+
ok: false,
|
|
1086
|
+
skipped: false,
|
|
1087
|
+
source: auth.source,
|
|
1088
|
+
toolName,
|
|
1089
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
1090
|
+
baseUrl: auth.baseUrl,
|
|
1091
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1092
|
+
details: [`auth source: ${auth.source}`]
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/lib/npm-registry-health.ts
|
|
1098
|
+
function resolvePackageUrl(packageName) {
|
|
1099
|
+
return `${NPM_REGISTRY_BASE_URL}/${encodeURIComponent(packageName)}`;
|
|
1100
|
+
}
|
|
1101
|
+
function getLatestVersion(payload) {
|
|
1102
|
+
if (!isRecord(payload) || !isRecord(payload["dist-tags"])) {
|
|
1103
|
+
return void 0;
|
|
1104
|
+
}
|
|
1105
|
+
const latest = payload["dist-tags"].latest;
|
|
1106
|
+
return typeof latest === "string" && latest.trim().length > 0 ? latest : void 0;
|
|
1107
|
+
}
|
|
1108
|
+
async function checkNpmRegistryHealth(packageName = ORGX_WIZARD_NPM_PACKAGE_NAME) {
|
|
1109
|
+
const url = resolvePackageUrl(packageName);
|
|
1110
|
+
try {
|
|
1111
|
+
const response = await fetch(url, {
|
|
1112
|
+
method: "GET",
|
|
1113
|
+
headers: { Accept: "application/json" },
|
|
1114
|
+
signal: AbortSignal.timeout(7e3)
|
|
1115
|
+
});
|
|
1116
|
+
const text2 = await response.text();
|
|
1117
|
+
let payload = text2;
|
|
1118
|
+
try {
|
|
1119
|
+
payload = text2.length > 0 ? JSON.parse(text2) : null;
|
|
1120
|
+
} catch {
|
|
1121
|
+
payload = text2;
|
|
1122
|
+
}
|
|
1123
|
+
if (response.ok) {
|
|
1124
|
+
const version = getLatestVersion(payload);
|
|
1125
|
+
return {
|
|
1126
|
+
ok: true,
|
|
1127
|
+
skipped: false,
|
|
1128
|
+
reachable: true,
|
|
1129
|
+
published: true,
|
|
1130
|
+
url,
|
|
1131
|
+
packageName,
|
|
1132
|
+
...version ? { version } : {},
|
|
1133
|
+
details: [
|
|
1134
|
+
`http status: ${response.status}`,
|
|
1135
|
+
...version ? [`latest version: ${version}`] : ["latest version: unavailable"]
|
|
1136
|
+
]
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
if (response.status === 404) {
|
|
1140
|
+
return {
|
|
1141
|
+
ok: true,
|
|
1142
|
+
skipped: false,
|
|
1143
|
+
reachable: true,
|
|
1144
|
+
published: false,
|
|
1145
|
+
url,
|
|
1146
|
+
packageName,
|
|
1147
|
+
error: `${packageName} is not published on npm yet.`,
|
|
1148
|
+
details: [`http status: ${response.status}`]
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return {
|
|
1152
|
+
ok: false,
|
|
1153
|
+
skipped: false,
|
|
1154
|
+
reachable: false,
|
|
1155
|
+
published: false,
|
|
1156
|
+
url,
|
|
1157
|
+
packageName,
|
|
1158
|
+
error: `npm registry returned HTTP ${response.status}`,
|
|
1159
|
+
details: [`http status: ${response.status}`]
|
|
1160
|
+
};
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
return {
|
|
1163
|
+
ok: false,
|
|
1164
|
+
skipped: false,
|
|
1165
|
+
reachable: false,
|
|
1166
|
+
published: false,
|
|
1167
|
+
url,
|
|
1168
|
+
packageName,
|
|
1169
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1170
|
+
details: []
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/lib/workspaces.ts
|
|
1176
|
+
async function requireOrgxAuth(options = {}) {
|
|
1177
|
+
const auth = await resolveOrgxAuth(options);
|
|
1178
|
+
if (!auth) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
"No OrgX API key configured. Run `wizard auth login` or `wizard auth set-key <oxk_...>` first."
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
return auth;
|
|
1184
|
+
}
|
|
1185
|
+
async function parseResponseBody2(response) {
|
|
1186
|
+
const text2 = await response.text();
|
|
1187
|
+
if (!text2) return null;
|
|
1188
|
+
try {
|
|
1189
|
+
return JSON.parse(text2);
|
|
1190
|
+
} catch {
|
|
1191
|
+
return text2;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function parseWorkspace(value) {
|
|
1195
|
+
if (!isRecord(value)) return null;
|
|
1196
|
+
const id = typeof value.id === "string" ? value.id : null;
|
|
1197
|
+
const name = typeof value.name === "string" ? value.name : null;
|
|
1198
|
+
if (!id || !name) return null;
|
|
1199
|
+
return {
|
|
1200
|
+
id,
|
|
1201
|
+
name,
|
|
1202
|
+
...typeof value.description === "string" && value.description.trim().length > 0 ? { description: value.description } : {},
|
|
1203
|
+
isDefault: value.is_default === true,
|
|
1204
|
+
...typeof value.created_at === "string" ? { createdAt: value.created_at } : {},
|
|
1205
|
+
...typeof value.updated_at === "string" ? { updatedAt: value.updated_at } : {}
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function sortWorkspaces(workspaces) {
|
|
1209
|
+
return [...workspaces].sort((left, right) => {
|
|
1210
|
+
if (left.isDefault !== right.isDefault) {
|
|
1211
|
+
return left.isDefault ? -1 : 1;
|
|
1212
|
+
}
|
|
1213
|
+
const nameCompare = left.name.localeCompare(right.name);
|
|
1214
|
+
if (nameCompare !== 0) {
|
|
1215
|
+
return nameCompare;
|
|
1216
|
+
}
|
|
1217
|
+
return left.id.localeCompare(right.id);
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
function parseWorkspaceList(payload) {
|
|
1221
|
+
if (!isRecord(payload) || !Array.isArray(payload.data)) {
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
return sortWorkspaces(payload.data.map(parseWorkspace).filter((item) => item !== null));
|
|
1225
|
+
}
|
|
1226
|
+
function parseWorkspaceResponse(payload) {
|
|
1227
|
+
if (!isRecord(payload)) return null;
|
|
1228
|
+
return parseWorkspace(payload.data);
|
|
1229
|
+
}
|
|
1230
|
+
function parseCurrentWorkspaceResponse(payload) {
|
|
1231
|
+
if (!isRecord(payload)) return null;
|
|
1232
|
+
const id = typeof payload.id === "string" ? payload.id : null;
|
|
1233
|
+
const name = typeof payload.name === "string" ? payload.name : null;
|
|
1234
|
+
if (!id || !name) return null;
|
|
1235
|
+
return {
|
|
1236
|
+
id,
|
|
1237
|
+
name,
|
|
1238
|
+
isDefault: true,
|
|
1239
|
+
...typeof payload.createdAt === "string" ? { createdAt: payload.createdAt } : {}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function buildRequestHeaders(apiKey) {
|
|
1243
|
+
return {
|
|
1244
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1245
|
+
"Content-Type": "application/json"
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function formatHttpError(status, body) {
|
|
1249
|
+
if (typeof body === "string" && body.trim().length > 0) {
|
|
1250
|
+
return `HTTP ${status}: ${body}`;
|
|
1251
|
+
}
|
|
1252
|
+
if (isRecord(body) && typeof body.error === "string" && body.error.trim().length > 0) {
|
|
1253
|
+
return `HTTP ${status}: ${body.error}`;
|
|
1254
|
+
}
|
|
1255
|
+
return `HTTP ${status}`;
|
|
1256
|
+
}
|
|
1257
|
+
async function listWorkspaces(options = {}) {
|
|
1258
|
+
const auth = await requireOrgxAuth(options);
|
|
1259
|
+
const url = buildOrgxApiUrl("/entities?type=command_center&limit=100", auth.baseUrl);
|
|
1260
|
+
const response = await fetch(url, {
|
|
1261
|
+
method: "GET",
|
|
1262
|
+
headers: {
|
|
1263
|
+
Authorization: `Bearer ${auth.apiKey}`
|
|
1264
|
+
},
|
|
1265
|
+
signal: AbortSignal.timeout(7e3)
|
|
1266
|
+
});
|
|
1267
|
+
const body = await parseResponseBody2(response);
|
|
1268
|
+
if (!response.ok) {
|
|
1269
|
+
throw new Error(`Failed to list workspaces. ${formatHttpError(response.status, body)}`);
|
|
1270
|
+
}
|
|
1271
|
+
return parseWorkspaceList(body);
|
|
1272
|
+
}
|
|
1273
|
+
async function getCurrentWorkspace(options = {}) {
|
|
1274
|
+
const auth = await requireOrgxAuth(options);
|
|
1275
|
+
const url = buildOrgxApiUrl("/v1/workspaces/current", auth.baseUrl);
|
|
1276
|
+
const response = await fetch(url, {
|
|
1277
|
+
method: "GET",
|
|
1278
|
+
headers: {
|
|
1279
|
+
Authorization: `Bearer ${auth.apiKey}`
|
|
1280
|
+
},
|
|
1281
|
+
signal: AbortSignal.timeout(7e3)
|
|
1282
|
+
});
|
|
1283
|
+
const body = await parseResponseBody2(response);
|
|
1284
|
+
if (response.ok) {
|
|
1285
|
+
const workspace = parseCurrentWorkspaceResponse(body);
|
|
1286
|
+
if (!workspace) {
|
|
1287
|
+
throw new Error("OrgX returned an unexpected current workspace payload.");
|
|
1288
|
+
}
|
|
1289
|
+
return workspace;
|
|
1290
|
+
}
|
|
1291
|
+
if (response.status === 404) {
|
|
1292
|
+
const workspaces = await listWorkspaces(options);
|
|
1293
|
+
return workspaces[0] ?? null;
|
|
1294
|
+
}
|
|
1295
|
+
throw new Error(`Failed to load current workspace. ${formatHttpError(response.status, body)}`);
|
|
1296
|
+
}
|
|
1297
|
+
async function createWorkspace(input, options = {}) {
|
|
1298
|
+
const name = input.name.trim();
|
|
1299
|
+
if (!name) {
|
|
1300
|
+
throw new Error("Workspace name is required.");
|
|
1301
|
+
}
|
|
1302
|
+
const auth = await requireOrgxAuth(options);
|
|
1303
|
+
const url = buildOrgxApiUrl("/entities", auth.baseUrl);
|
|
1304
|
+
const payload = {
|
|
1305
|
+
type: "command_center",
|
|
1306
|
+
name,
|
|
1307
|
+
...input.description?.trim() ? { description: input.description.trim() } : {}
|
|
1308
|
+
};
|
|
1309
|
+
const response = await fetch(url, {
|
|
1310
|
+
method: "POST",
|
|
1311
|
+
headers: buildRequestHeaders(auth.apiKey),
|
|
1312
|
+
body: JSON.stringify(payload),
|
|
1313
|
+
signal: AbortSignal.timeout(7e3)
|
|
1314
|
+
});
|
|
1315
|
+
const body = await parseResponseBody2(response);
|
|
1316
|
+
if (!response.ok) {
|
|
1317
|
+
throw new Error(`Failed to create workspace. ${formatHttpError(response.status, body)}`);
|
|
1318
|
+
}
|
|
1319
|
+
const workspace = parseWorkspaceResponse(body);
|
|
1320
|
+
if (!workspace) {
|
|
1321
|
+
throw new Error("OrgX returned an unexpected workspace payload.");
|
|
1322
|
+
}
|
|
1323
|
+
return workspace;
|
|
1324
|
+
}
|
|
1325
|
+
async function updateWorkspace(input, patch, options = {}) {
|
|
1326
|
+
const auth = await requireOrgxAuth(options);
|
|
1327
|
+
const url = buildOrgxApiUrl("/entities", auth.baseUrl);
|
|
1328
|
+
const response = await fetch(url, {
|
|
1329
|
+
method: "PATCH",
|
|
1330
|
+
headers: buildRequestHeaders(auth.apiKey),
|
|
1331
|
+
body: JSON.stringify({
|
|
1332
|
+
type: "command_center",
|
|
1333
|
+
id: input.id,
|
|
1334
|
+
...patch
|
|
1335
|
+
}),
|
|
1336
|
+
signal: AbortSignal.timeout(7e3)
|
|
1337
|
+
});
|
|
1338
|
+
const body = await parseResponseBody2(response);
|
|
1339
|
+
if (!response.ok) {
|
|
1340
|
+
throw new Error(`Failed to update workspace. ${formatHttpError(response.status, body)}`);
|
|
1341
|
+
}
|
|
1342
|
+
const workspace = parseWorkspaceResponse(body);
|
|
1343
|
+
if (!workspace) {
|
|
1344
|
+
throw new Error("OrgX returned an unexpected workspace update payload.");
|
|
1345
|
+
}
|
|
1346
|
+
return workspace;
|
|
1347
|
+
}
|
|
1348
|
+
async function setDefaultWorkspace(input, options = {}) {
|
|
1349
|
+
const workspaceId = input.id.trim();
|
|
1350
|
+
if (!workspaceId) {
|
|
1351
|
+
throw new Error("Workspace id is required.");
|
|
1352
|
+
}
|
|
1353
|
+
const workspaces = await listWorkspaces(options);
|
|
1354
|
+
const target = workspaces.find((workspace) => workspace.id === workspaceId);
|
|
1355
|
+
if (!target) {
|
|
1356
|
+
throw new Error(`Workspace ${workspaceId} was not found in the current OrgX account.`);
|
|
1357
|
+
}
|
|
1358
|
+
if (target.isDefault) {
|
|
1359
|
+
return {
|
|
1360
|
+
changed: false,
|
|
1361
|
+
workspace: target
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
const currentDefault = workspaces.find((workspace) => workspace.isDefault);
|
|
1365
|
+
if (currentDefault) {
|
|
1366
|
+
await updateWorkspace({ id: currentDefault.id }, { is_default: false }, options);
|
|
1367
|
+
}
|
|
1368
|
+
try {
|
|
1369
|
+
const updated = await updateWorkspace({ id: workspaceId }, { is_default: true }, options);
|
|
1370
|
+
return {
|
|
1371
|
+
changed: true,
|
|
1372
|
+
workspace: updated
|
|
1373
|
+
};
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
if (currentDefault) {
|
|
1376
|
+
try {
|
|
1377
|
+
await updateWorkspace({ id: currentDefault.id }, { is_default: true }, options);
|
|
1378
|
+
} catch {
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
throw error;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/lib/workspace-connectivity.ts
|
|
1386
|
+
async function checkWorkspaceConnectivity(options = {}) {
|
|
1387
|
+
const auth = await resolveOrgxAuth(options);
|
|
1388
|
+
if (!auth) {
|
|
1389
|
+
return {
|
|
1390
|
+
configured: false,
|
|
1391
|
+
ok: false,
|
|
1392
|
+
skipped: true,
|
|
1393
|
+
source: "none",
|
|
1394
|
+
error: "No OrgX API key configured; workspace connectivity check skipped.",
|
|
1395
|
+
details: []
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
try {
|
|
1399
|
+
const workspace = await getCurrentWorkspace(options);
|
|
1400
|
+
if (!workspace) {
|
|
1401
|
+
return {
|
|
1402
|
+
configured: true,
|
|
1403
|
+
ok: false,
|
|
1404
|
+
skipped: false,
|
|
1405
|
+
source: auth.source,
|
|
1406
|
+
baseUrl: auth.baseUrl,
|
|
1407
|
+
error: "OrgX auth resolved, but no accessible workspace was returned.",
|
|
1408
|
+
details: []
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
return {
|
|
1412
|
+
configured: true,
|
|
1413
|
+
ok: true,
|
|
1414
|
+
skipped: false,
|
|
1415
|
+
source: auth.source,
|
|
1416
|
+
baseUrl: auth.baseUrl,
|
|
1417
|
+
workspace,
|
|
1418
|
+
details: [
|
|
1419
|
+
`workspace id: ${workspace.id}`,
|
|
1420
|
+
...workspace.isDefault ? ["workspace is marked as default"] : []
|
|
1421
|
+
]
|
|
1422
|
+
};
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
return {
|
|
1425
|
+
configured: true,
|
|
1426
|
+
ok: false,
|
|
1427
|
+
skipped: false,
|
|
1428
|
+
source: auth.source,
|
|
1429
|
+
baseUrl: auth.baseUrl,
|
|
1430
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1431
|
+
details: []
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/surfaces/mcp-config.ts
|
|
1437
|
+
import * as TOML from "@iarna/toml";
|
|
1438
|
+
function cloneConfigCollection(config, key) {
|
|
1439
|
+
return { ...readObject(config[key]) };
|
|
1440
|
+
}
|
|
1441
|
+
function removeLegacyScopedMcpServers(servers) {
|
|
1442
|
+
const scopedPrefix = `${ORGX_LOCAL_MCP_KEY}-`;
|
|
1443
|
+
let updated = false;
|
|
1444
|
+
for (const key of Object.keys(servers)) {
|
|
1445
|
+
if (key === ORGX_LOCAL_MCP_KEY) continue;
|
|
1446
|
+
if (!key.startsWith(scopedPrefix)) continue;
|
|
1447
|
+
delete servers[key];
|
|
1448
|
+
updated = true;
|
|
1449
|
+
}
|
|
1450
|
+
return updated;
|
|
1451
|
+
}
|
|
1452
|
+
function stringifyJsonConfig(config) {
|
|
1453
|
+
return `${JSON.stringify(config, null, 2)}
|
|
1454
|
+
`;
|
|
1455
|
+
}
|
|
1456
|
+
function clearStdioFields(entry) {
|
|
1457
|
+
const next = { ...entry };
|
|
1458
|
+
delete next.command;
|
|
1459
|
+
delete next.args;
|
|
1460
|
+
delete next.env;
|
|
1461
|
+
return next;
|
|
1462
|
+
}
|
|
1463
|
+
function matchHttpEntry(value, url) {
|
|
1464
|
+
if (!isRecord(value)) return false;
|
|
1465
|
+
return value.type === "http" && value.url === url;
|
|
1466
|
+
}
|
|
1467
|
+
function matchUrlEntry(value, url) {
|
|
1468
|
+
if (!isRecord(value)) return false;
|
|
1469
|
+
return value.url === url;
|
|
1470
|
+
}
|
|
1471
|
+
function patchClaudeMcpConfig(input, localMcpUrl) {
|
|
1472
|
+
const current = parseJsonObject(input);
|
|
1473
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1474
|
+
const existingOrgx = readObject(servers[ORGX_HOSTED_MCP_KEY]);
|
|
1475
|
+
const existingLocal = readObject(servers[ORGX_LOCAL_MCP_KEY]);
|
|
1476
|
+
const hostedUrl = typeof existingOrgx.url === "string" ? existingOrgx.url : "";
|
|
1477
|
+
const hostedType = typeof existingOrgx.type === "string" ? existingOrgx.type : "";
|
|
1478
|
+
const shouldSetHostedOrgx = !isRecord(servers[ORGX_HOSTED_MCP_KEY]) || hostedUrl === localMcpUrl && hostedType === "http";
|
|
1479
|
+
if (shouldSetHostedOrgx) {
|
|
1480
|
+
servers[ORGX_HOSTED_MCP_KEY] = {
|
|
1481
|
+
...existingOrgx,
|
|
1482
|
+
type: "http",
|
|
1483
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
1484
|
+
description: typeof existingOrgx.description === "string" && existingOrgx.description.trim().length > 0 ? existingOrgx.description : "OrgX cloud MCP (OAuth)"
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
if (localMcpUrl) {
|
|
1488
|
+
servers[ORGX_LOCAL_MCP_KEY] = {
|
|
1489
|
+
...existingLocal,
|
|
1490
|
+
type: "http",
|
|
1491
|
+
url: localMcpUrl,
|
|
1492
|
+
description: typeof existingLocal.description === "string" && existingLocal.description.trim().length > 0 ? existingLocal.description : "OrgX platform via local OpenClaw plugin (no OAuth)"
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
removeLegacyScopedMcpServers(servers);
|
|
1496
|
+
return stringifyJsonConfig({
|
|
1497
|
+
...current,
|
|
1498
|
+
mcpServers: servers
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
function removeClaudeMcpConfig(input) {
|
|
1502
|
+
const current = parseJsonObject(input);
|
|
1503
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1504
|
+
if (isRecord(servers[ORGX_LOCAL_MCP_KEY])) {
|
|
1505
|
+
delete servers[ORGX_LOCAL_MCP_KEY];
|
|
1506
|
+
}
|
|
1507
|
+
if (matchHttpEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL)) {
|
|
1508
|
+
delete servers[ORGX_HOSTED_MCP_KEY];
|
|
1509
|
+
}
|
|
1510
|
+
removeLegacyScopedMcpServers(servers);
|
|
1511
|
+
return stringifyJsonConfig({
|
|
1512
|
+
...current,
|
|
1513
|
+
mcpServers: servers
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
function inspectClaudeMcpConfig(input) {
|
|
1517
|
+
const current = parseJsonObject(input);
|
|
1518
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1519
|
+
const local = readObject(servers[ORGX_LOCAL_MCP_KEY]);
|
|
1520
|
+
const hostedConfigured = matchHttpEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL);
|
|
1521
|
+
const localConfigured = local.type === "http" && typeof local.url === "string";
|
|
1522
|
+
const localUrl = localConfigured ? String(local.url) : "missing";
|
|
1523
|
+
return {
|
|
1524
|
+
configured: hostedConfigured && localConfigured,
|
|
1525
|
+
hostedConfigured,
|
|
1526
|
+
localConfigured,
|
|
1527
|
+
details: [
|
|
1528
|
+
`hosted orgx: ${hostedConfigured ? "ok" : "missing"}`,
|
|
1529
|
+
`local orgx-openclaw: ${localUrl}`
|
|
1530
|
+
]
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
function patchCursorMcpConfig(input, localMcpUrl) {
|
|
1534
|
+
const current = parseJsonObject(input);
|
|
1535
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1536
|
+
const existingLocal = readObject(servers[ORGX_LOCAL_MCP_KEY]);
|
|
1537
|
+
servers[ORGX_LOCAL_MCP_KEY] = {
|
|
1538
|
+
...clearStdioFields(existingLocal),
|
|
1539
|
+
url: localMcpUrl
|
|
1540
|
+
};
|
|
1541
|
+
removeLegacyScopedMcpServers(servers);
|
|
1542
|
+
return stringifyJsonConfig({
|
|
1543
|
+
...current,
|
|
1544
|
+
mcpServers: servers
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
function removeCursorMcpConfig(input) {
|
|
1548
|
+
const current = parseJsonObject(input);
|
|
1549
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1550
|
+
if (isRecord(servers[ORGX_LOCAL_MCP_KEY])) {
|
|
1551
|
+
delete servers[ORGX_LOCAL_MCP_KEY];
|
|
1552
|
+
}
|
|
1553
|
+
removeLegacyScopedMcpServers(servers);
|
|
1554
|
+
return stringifyJsonConfig({
|
|
1555
|
+
...current,
|
|
1556
|
+
mcpServers: servers
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
function inspectCursorMcpConfig(input) {
|
|
1560
|
+
const current = parseJsonObject(input);
|
|
1561
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1562
|
+
const local = readObject(servers[ORGX_LOCAL_MCP_KEY]);
|
|
1563
|
+
const localConfigured = typeof local.url === "string";
|
|
1564
|
+
const localUrl = localConfigured ? String(local.url) : "missing";
|
|
1565
|
+
return {
|
|
1566
|
+
configured: localConfigured,
|
|
1567
|
+
localConfigured,
|
|
1568
|
+
details: [`local orgx-openclaw: ${localUrl}`]
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function patchVscodeMcpConfig(input) {
|
|
1572
|
+
const current = parseJsonObject(input);
|
|
1573
|
+
const servers = cloneConfigCollection(current, "servers");
|
|
1574
|
+
const existingHosted = readObject(servers[ORGX_HOSTED_MCP_KEY]);
|
|
1575
|
+
servers[ORGX_HOSTED_MCP_KEY] = {
|
|
1576
|
+
...clearStdioFields(existingHosted),
|
|
1577
|
+
type: "http",
|
|
1578
|
+
url: ORGX_HOSTED_MCP_URL
|
|
1579
|
+
};
|
|
1580
|
+
return stringifyJsonConfig({
|
|
1581
|
+
...current,
|
|
1582
|
+
servers
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
function removeVscodeMcpConfig(input) {
|
|
1586
|
+
const current = parseJsonObject(input);
|
|
1587
|
+
const servers = cloneConfigCollection(current, "servers");
|
|
1588
|
+
if (matchHttpEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL)) {
|
|
1589
|
+
delete servers[ORGX_HOSTED_MCP_KEY];
|
|
1590
|
+
}
|
|
1591
|
+
return stringifyJsonConfig({
|
|
1592
|
+
...current,
|
|
1593
|
+
servers
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
function inspectVscodeMcpConfig(input) {
|
|
1597
|
+
const current = parseJsonObject(input);
|
|
1598
|
+
const servers = cloneConfigCollection(current, "servers");
|
|
1599
|
+
const hostedConfigured = matchHttpEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL);
|
|
1600
|
+
return {
|
|
1601
|
+
configured: hostedConfigured,
|
|
1602
|
+
hostedConfigured,
|
|
1603
|
+
details: [`hosted orgx: ${hostedConfigured ? "ok" : "missing"}`]
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function matchServerUrlEntry(value, url) {
|
|
1607
|
+
if (!isRecord(value)) return false;
|
|
1608
|
+
return value.serverUrl === url || value.url === url;
|
|
1609
|
+
}
|
|
1610
|
+
function patchWindsurfMcpConfig(input) {
|
|
1611
|
+
const current = parseJsonObject(input);
|
|
1612
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1613
|
+
const existingHosted = readObject(servers[ORGX_HOSTED_MCP_KEY]);
|
|
1614
|
+
const nextEntry = clearStdioFields(existingHosted);
|
|
1615
|
+
delete nextEntry.url;
|
|
1616
|
+
servers[ORGX_HOSTED_MCP_KEY] = {
|
|
1617
|
+
...nextEntry,
|
|
1618
|
+
serverUrl: ORGX_HOSTED_MCP_URL
|
|
1619
|
+
};
|
|
1620
|
+
return stringifyJsonConfig({
|
|
1621
|
+
...current,
|
|
1622
|
+
mcpServers: servers
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
function removeWindsurfMcpConfig(input) {
|
|
1626
|
+
const current = parseJsonObject(input);
|
|
1627
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1628
|
+
if (matchServerUrlEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL)) {
|
|
1629
|
+
delete servers[ORGX_HOSTED_MCP_KEY];
|
|
1630
|
+
}
|
|
1631
|
+
return stringifyJsonConfig({
|
|
1632
|
+
...current,
|
|
1633
|
+
mcpServers: servers
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
function inspectWindsurfMcpConfig(input) {
|
|
1637
|
+
const current = parseJsonObject(input);
|
|
1638
|
+
const servers = cloneConfigCollection(current, "mcpServers");
|
|
1639
|
+
const hostedConfigured = matchServerUrlEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL);
|
|
1640
|
+
return {
|
|
1641
|
+
configured: hostedConfigured,
|
|
1642
|
+
hostedConfigured,
|
|
1643
|
+
details: [`hosted orgx: ${hostedConfigured ? "ok" : "missing"}`]
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
function patchZedMcpConfig(input) {
|
|
1647
|
+
const current = parseJsonObject(input);
|
|
1648
|
+
const servers = cloneConfigCollection(current, "context_servers");
|
|
1649
|
+
const existingHosted = readObject(servers[ORGX_HOSTED_MCP_KEY]);
|
|
1650
|
+
servers[ORGX_HOSTED_MCP_KEY] = {
|
|
1651
|
+
...clearStdioFields(existingHosted),
|
|
1652
|
+
url: ORGX_HOSTED_MCP_URL
|
|
1653
|
+
};
|
|
1654
|
+
return stringifyJsonConfig({
|
|
1655
|
+
...current,
|
|
1656
|
+
context_servers: servers
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
function removeZedMcpConfig(input) {
|
|
1660
|
+
const current = parseJsonObject(input);
|
|
1661
|
+
const servers = cloneConfigCollection(current, "context_servers");
|
|
1662
|
+
if (matchUrlEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL)) {
|
|
1663
|
+
delete servers[ORGX_HOSTED_MCP_KEY];
|
|
1664
|
+
}
|
|
1665
|
+
return stringifyJsonConfig({
|
|
1666
|
+
...current,
|
|
1667
|
+
context_servers: servers
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
function inspectZedMcpConfig(input) {
|
|
1671
|
+
const current = parseJsonObject(input);
|
|
1672
|
+
const servers = cloneConfigCollection(current, "context_servers");
|
|
1673
|
+
const hostedConfigured = matchUrlEntry(servers[ORGX_HOSTED_MCP_KEY], ORGX_HOSTED_MCP_URL);
|
|
1674
|
+
return {
|
|
1675
|
+
configured: hostedConfigured,
|
|
1676
|
+
hostedConfigured,
|
|
1677
|
+
details: [`hosted orgx: ${hostedConfigured ? "ok" : "missing"}`]
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
function parseTomlDocument(input) {
|
|
1681
|
+
if (!input) return {};
|
|
1682
|
+
try {
|
|
1683
|
+
const parsed = TOML.parse(input);
|
|
1684
|
+
return isRecord(parsed) ? parsed : {};
|
|
1685
|
+
} catch {
|
|
1686
|
+
return {};
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
function stringifyTomlDocument(document) {
|
|
1690
|
+
const rendered = TOML.stringify(document);
|
|
1691
|
+
return rendered.endsWith("\n") ? rendered : `${rendered}
|
|
1692
|
+
`;
|
|
1693
|
+
}
|
|
1694
|
+
function upsertCodexEntry(servers, key, url) {
|
|
1695
|
+
const current = readObject(servers[key]);
|
|
1696
|
+
const next = {
|
|
1697
|
+
...current,
|
|
1698
|
+
url
|
|
1699
|
+
};
|
|
1700
|
+
delete next.command;
|
|
1701
|
+
delete next.args;
|
|
1702
|
+
delete next.startup_timeout_sec;
|
|
1703
|
+
servers[key] = next;
|
|
1704
|
+
}
|
|
1705
|
+
function removeLegacyCodexEntries(servers) {
|
|
1706
|
+
const scopedPrefix = `${ORGX_LOCAL_MCP_KEY}-`;
|
|
1707
|
+
let updated = false;
|
|
1708
|
+
for (const key of Object.keys(servers)) {
|
|
1709
|
+
if (key === ORGX_LOCAL_MCP_KEY) continue;
|
|
1710
|
+
if (!key.startsWith(scopedPrefix)) continue;
|
|
1711
|
+
delete servers[key];
|
|
1712
|
+
updated = true;
|
|
1713
|
+
}
|
|
1714
|
+
return updated;
|
|
1715
|
+
}
|
|
1716
|
+
function patchCodexConfigToml(input, localMcpUrl) {
|
|
1717
|
+
const current = parseTomlDocument(input);
|
|
1718
|
+
const servers = readObject(current.mcp_servers);
|
|
1719
|
+
upsertCodexEntry(servers, ORGX_HOSTED_MCP_KEY, ORGX_HOSTED_MCP_URL);
|
|
1720
|
+
if (localMcpUrl) {
|
|
1721
|
+
upsertCodexEntry(servers, ORGX_LOCAL_MCP_KEY, localMcpUrl);
|
|
1722
|
+
}
|
|
1723
|
+
removeLegacyCodexEntries(servers);
|
|
1724
|
+
current.mcp_servers = servers;
|
|
1725
|
+
return stringifyTomlDocument(current);
|
|
1726
|
+
}
|
|
1727
|
+
function removeCodexConfigToml(input) {
|
|
1728
|
+
const current = parseTomlDocument(input);
|
|
1729
|
+
const servers = readObject(current.mcp_servers);
|
|
1730
|
+
const hosted = readObject(servers[ORGX_HOSTED_MCP_KEY]);
|
|
1731
|
+
if (isRecord(servers[ORGX_LOCAL_MCP_KEY])) {
|
|
1732
|
+
delete servers[ORGX_LOCAL_MCP_KEY];
|
|
1733
|
+
}
|
|
1734
|
+
if (hosted.url === ORGX_HOSTED_MCP_URL) {
|
|
1735
|
+
delete servers[ORGX_HOSTED_MCP_KEY];
|
|
1736
|
+
}
|
|
1737
|
+
removeLegacyCodexEntries(servers);
|
|
1738
|
+
current.mcp_servers = servers;
|
|
1739
|
+
return stringifyTomlDocument(current);
|
|
1740
|
+
}
|
|
1741
|
+
function inspectCodexConfigToml(input) {
|
|
1742
|
+
const current = parseTomlDocument(input);
|
|
1743
|
+
const servers = readObject(current.mcp_servers);
|
|
1744
|
+
const hosted = readObject(servers[ORGX_HOSTED_MCP_KEY]);
|
|
1745
|
+
const local = readObject(servers[ORGX_LOCAL_MCP_KEY]);
|
|
1746
|
+
const hostedConfigured = hosted.url === ORGX_HOSTED_MCP_URL;
|
|
1747
|
+
const localConfigured = typeof local.url === "string";
|
|
1748
|
+
return {
|
|
1749
|
+
configured: hostedConfigured && localConfigured,
|
|
1750
|
+
hostedConfigured,
|
|
1751
|
+
localConfigured,
|
|
1752
|
+
details: [
|
|
1753
|
+
`hosted orgx: ${hostedConfigured ? "ok" : "missing"}`,
|
|
1754
|
+
`local orgx-openclaw: ${localConfigured ? local.url : "missing"}`
|
|
1755
|
+
]
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// src/surfaces/names.ts
|
|
1760
|
+
var SURFACE_NAMES = [
|
|
1761
|
+
"claude",
|
|
1762
|
+
"cursor",
|
|
1763
|
+
"codex",
|
|
1764
|
+
"openclaw",
|
|
1765
|
+
"vscode",
|
|
1766
|
+
"windsurf",
|
|
1767
|
+
"zed",
|
|
1768
|
+
"chatgpt"
|
|
1769
|
+
];
|
|
1770
|
+
var AUTOMATED_SURFACE_NAMES = [
|
|
1771
|
+
"claude",
|
|
1772
|
+
"cursor",
|
|
1773
|
+
"codex",
|
|
1774
|
+
"openclaw",
|
|
1775
|
+
"vscode",
|
|
1776
|
+
"windsurf",
|
|
1777
|
+
"zed"
|
|
1778
|
+
];
|
|
1779
|
+
var MCP_SURFACE_NAMES = [
|
|
1780
|
+
"claude",
|
|
1781
|
+
"cursor",
|
|
1782
|
+
"codex",
|
|
1783
|
+
"vscode",
|
|
1784
|
+
"windsurf",
|
|
1785
|
+
"zed"
|
|
1786
|
+
];
|
|
1787
|
+
|
|
1788
|
+
// src/surfaces/detection.ts
|
|
1789
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1790
|
+
import { dirname as dirname2 } from "path";
|
|
1791
|
+
var SURFACE_LOCATORS = {
|
|
1792
|
+
claude: {
|
|
1793
|
+
configPaths: CLAUDE_MCP_PATHS,
|
|
1794
|
+
installPaths: CLAUDE_INSTALL_PATHS
|
|
1795
|
+
},
|
|
1796
|
+
cursor: {
|
|
1797
|
+
configPaths: CURSOR_MCP_PATHS,
|
|
1798
|
+
installPaths: CURSOR_INSTALL_PATHS
|
|
1799
|
+
},
|
|
1800
|
+
codex: {
|
|
1801
|
+
configPaths: CODEX_CONFIG_PATHS,
|
|
1802
|
+
installPaths: CODEX_INSTALL_PATHS
|
|
1803
|
+
},
|
|
1804
|
+
openclaw: {
|
|
1805
|
+
configPaths: OPENCLAW_CONFIG_PATHS,
|
|
1806
|
+
installPaths: OPENCLAW_INSTALL_PATHS
|
|
1807
|
+
},
|
|
1808
|
+
vscode: {
|
|
1809
|
+
configPaths: VSCODE_MCP_PATHS,
|
|
1810
|
+
installPaths: VSCODE_INSTALL_PATHS
|
|
1811
|
+
},
|
|
1812
|
+
windsurf: {
|
|
1813
|
+
configPaths: WINDSURF_MCP_PATHS,
|
|
1814
|
+
installPaths: WINDSURF_INSTALL_PATHS
|
|
1815
|
+
},
|
|
1816
|
+
zed: {
|
|
1817
|
+
configPaths: ZED_SETTINGS_PATHS,
|
|
1818
|
+
installPaths: ZED_INSTALL_PATHS
|
|
1819
|
+
},
|
|
1820
|
+
chatgpt: {
|
|
1821
|
+
configPaths: [],
|
|
1822
|
+
installPaths: CHATGPT_APP_PATHS
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
function firstExistingPath(paths, exists) {
|
|
1826
|
+
return paths.find((path) => exists(path));
|
|
1827
|
+
}
|
|
1828
|
+
function buildFallbackInstallPaths(configPaths) {
|
|
1829
|
+
return [...new Set(configPaths.map((path) => dirname2(path)))];
|
|
1830
|
+
}
|
|
1831
|
+
function getSurfaceLocator(name) {
|
|
1832
|
+
return SURFACE_LOCATORS[name];
|
|
1833
|
+
}
|
|
1834
|
+
function detectSurface(name, exists = existsSync2) {
|
|
1835
|
+
const locator = getSurfaceLocator(name);
|
|
1836
|
+
const existingConfigPath = firstExistingPath(locator.configPaths, exists);
|
|
1837
|
+
const preferredPath = locator.configPaths[0] ?? locator.installPaths[0];
|
|
1838
|
+
const installCandidates = [
|
|
1839
|
+
...locator.installPaths,
|
|
1840
|
+
...buildFallbackInstallPaths(locator.configPaths)
|
|
1841
|
+
];
|
|
1842
|
+
const existingInstallPath = firstExistingPath(installCandidates, exists);
|
|
1843
|
+
const existingPath = existingConfigPath ?? existingInstallPath;
|
|
1844
|
+
const evidence = [];
|
|
1845
|
+
if (existingConfigPath) {
|
|
1846
|
+
evidence.push(`config found: ${existingConfigPath}`);
|
|
1847
|
+
}
|
|
1848
|
+
if (existingInstallPath && existingInstallPath !== existingConfigPath) {
|
|
1849
|
+
evidence.push(`install marker found: ${existingInstallPath}`);
|
|
1850
|
+
}
|
|
1851
|
+
if (!existingConfigPath && !existingInstallPath) {
|
|
1852
|
+
evidence.push("no candidate paths found on disk");
|
|
1853
|
+
}
|
|
1854
|
+
const detection = {
|
|
1855
|
+
detected: Boolean(existingConfigPath || existingInstallPath),
|
|
1856
|
+
evidence
|
|
1857
|
+
};
|
|
1858
|
+
if (preferredPath) {
|
|
1859
|
+
detection.preferredPath = preferredPath;
|
|
1860
|
+
}
|
|
1861
|
+
if (existingPath) {
|
|
1862
|
+
detection.existingPath = existingPath;
|
|
1863
|
+
}
|
|
1864
|
+
return detection;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// src/surfaces/registry.ts
|
|
1868
|
+
function getSurfacePath(name) {
|
|
1869
|
+
const detection = detectSurface(name);
|
|
1870
|
+
const locator = getSurfaceLocator(name);
|
|
1871
|
+
const existingPath = detection.existingPath;
|
|
1872
|
+
if (existingPath && locator.configPaths.includes(existingPath)) {
|
|
1873
|
+
return existingPath;
|
|
1874
|
+
}
|
|
1875
|
+
return locator.configPaths[0] ?? detection.preferredPath ?? existingPath;
|
|
1876
|
+
}
|
|
1877
|
+
function getOpenClawDependencyState() {
|
|
1878
|
+
const detection = detectSurface("openclaw");
|
|
1879
|
+
const path = detection.existingPath ?? detection.preferredPath;
|
|
1880
|
+
const raw = path ? readTextIfExists(path) : null;
|
|
1881
|
+
const inspection = inspectOpenClawConfig(raw);
|
|
1882
|
+
const result = {
|
|
1883
|
+
detected: detection.detected,
|
|
1884
|
+
configured: inspection.configured
|
|
1885
|
+
};
|
|
1886
|
+
if (path) {
|
|
1887
|
+
result.path = path;
|
|
1888
|
+
}
|
|
1889
|
+
if (detection.detected) {
|
|
1890
|
+
result.url = buildLocalMcpUrl(raw);
|
|
1891
|
+
}
|
|
1892
|
+
return result;
|
|
1893
|
+
}
|
|
1894
|
+
function automatedSurfaceStatus(name) {
|
|
1895
|
+
const detection = detectSurface(name);
|
|
1896
|
+
const path = detection.existingPath ?? detection.preferredPath;
|
|
1897
|
+
const openclaw = getOpenClawDependencyState();
|
|
1898
|
+
if (name === "claude") {
|
|
1899
|
+
const inspection2 = inspectClaudeMcpConfig(path ? readTextIfExists(path) : null);
|
|
1900
|
+
const configured = inspection2.hostedConfigured === true && (openclaw.detected ? inspection2.localConfigured === true : true);
|
|
1901
|
+
return {
|
|
1902
|
+
name,
|
|
1903
|
+
mode: "automated",
|
|
1904
|
+
detected: detection.detected,
|
|
1905
|
+
configured,
|
|
1906
|
+
...path ? { path } : {},
|
|
1907
|
+
details: [
|
|
1908
|
+
...inspection2.details,
|
|
1909
|
+
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; hosted OrgX is the only available route."],
|
|
1910
|
+
...detection.evidence
|
|
1911
|
+
],
|
|
1912
|
+
summary: configured ? openclaw.detected ? "OrgX is connected in Claude with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Claude." : detection.detected ? openclaw.detected ? `Claude is missing either hosted OrgX or the local OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Claude is missing the hosted OrgX MCP entry." : "Claude config directory was not detected on disk."
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
if (name === "cursor") {
|
|
1916
|
+
const inspection2 = inspectCursorMcpConfig(path ? readTextIfExists(path) : null);
|
|
1917
|
+
const configured = openclaw.detected && inspection2.localConfigured === true;
|
|
1918
|
+
return {
|
|
1919
|
+
name,
|
|
1920
|
+
mode: "automated",
|
|
1921
|
+
detected: detection.detected,
|
|
1922
|
+
configured,
|
|
1923
|
+
...path ? { path } : {},
|
|
1924
|
+
details: [
|
|
1925
|
+
...inspection2.details,
|
|
1926
|
+
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; Cursor cannot use the local OrgX bridge yet."],
|
|
1927
|
+
...detection.evidence
|
|
1928
|
+
],
|
|
1929
|
+
summary: configured ? "OrgX is connected in Cursor through the local OpenClaw bridge." : detection.detected ? openclaw.detected ? `Cursor is missing the local OrgX OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Cursor is ready to wire, but OpenClaw must be installed first." : "Cursor config directory was not detected on disk."
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
if (name === "codex") {
|
|
1933
|
+
const inspection2 = inspectCodexConfigToml(path ? readTextIfExists(path) : null);
|
|
1934
|
+
const configured = inspection2.hostedConfigured === true && (openclaw.detected ? inspection2.localConfigured === true : true);
|
|
1935
|
+
return {
|
|
1936
|
+
name,
|
|
1937
|
+
mode: "automated",
|
|
1938
|
+
detected: detection.detected,
|
|
1939
|
+
configured,
|
|
1940
|
+
...path ? { path } : {},
|
|
1941
|
+
details: [
|
|
1942
|
+
...inspection2.details,
|
|
1943
|
+
...openclaw.detected ? [`OpenClaw bridge: ${openclaw.configured ? "configured" : "detected but not configured"}`] : ["OpenClaw bridge: not detected; hosted OrgX is the only available route."],
|
|
1944
|
+
...detection.evidence
|
|
1945
|
+
],
|
|
1946
|
+
summary: configured ? openclaw.detected ? "OrgX is connected in Codex with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Codex." : detection.detected ? openclaw.detected ? `Codex is missing either hosted OrgX or the local OpenClaw bridge (${openclaw.url ?? "unknown"}).` : "Codex is missing the hosted OrgX MCP entry." : "Codex config directory was not detected on disk."
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
if (name === "vscode") {
|
|
1950
|
+
const inspection2 = inspectVscodeMcpConfig(path ? readTextIfExists(path) : null);
|
|
1951
|
+
return {
|
|
1952
|
+
name,
|
|
1953
|
+
mode: "automated",
|
|
1954
|
+
detected: detection.detected,
|
|
1955
|
+
configured: inspection2.hostedConfigured === true,
|
|
1956
|
+
...path ? { path } : {},
|
|
1957
|
+
details: [...inspection2.details, ...detection.evidence],
|
|
1958
|
+
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in VS Code." : detection.detected ? "VS Code is missing the hosted OrgX MCP entry." : "VS Code config directory was not detected on disk."
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
if (name === "windsurf") {
|
|
1962
|
+
const inspection2 = inspectWindsurfMcpConfig(path ? readTextIfExists(path) : null);
|
|
1963
|
+
return {
|
|
1964
|
+
name,
|
|
1965
|
+
mode: "automated",
|
|
1966
|
+
detected: detection.detected,
|
|
1967
|
+
configured: inspection2.hostedConfigured === true,
|
|
1968
|
+
...path ? { path } : {},
|
|
1969
|
+
details: [...inspection2.details, ...detection.evidence],
|
|
1970
|
+
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in Windsurf." : detection.detected ? "Windsurf is missing the hosted OrgX MCP entry." : "Windsurf config directory was not detected on disk."
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
if (name === "zed") {
|
|
1974
|
+
const inspection2 = inspectZedMcpConfig(path ? readTextIfExists(path) : null);
|
|
1975
|
+
return {
|
|
1976
|
+
name,
|
|
1977
|
+
mode: "automated",
|
|
1978
|
+
detected: detection.detected,
|
|
1979
|
+
configured: inspection2.hostedConfigured === true,
|
|
1980
|
+
...path ? { path } : {},
|
|
1981
|
+
details: [...inspection2.details, ...detection.evidence],
|
|
1982
|
+
summary: inspection2.hostedConfigured ? "OrgX cloud MCP is connected in Zed." : detection.detected ? "Zed is missing the hosted OrgX MCP entry." : "Zed config directory was not detected on disk."
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
const inspection = inspectOpenClawConfig(path ? readTextIfExists(path) : null);
|
|
1986
|
+
return {
|
|
1987
|
+
name,
|
|
1988
|
+
mode: "automated",
|
|
1989
|
+
detected: detection.detected,
|
|
1990
|
+
configured: inspection.configured,
|
|
1991
|
+
...path ? { path } : {},
|
|
1992
|
+
details: [...inspection.details, ...detection.evidence],
|
|
1993
|
+
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."
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
function manualSurfaceStatus(name) {
|
|
1997
|
+
const detection = detectSurface(name);
|
|
1998
|
+
const path = detection.existingPath ?? detection.preferredPath;
|
|
1999
|
+
const shared = {
|
|
2000
|
+
name,
|
|
2001
|
+
mode: "manual",
|
|
2002
|
+
detected: detection.detected,
|
|
2003
|
+
configured: false,
|
|
2004
|
+
...path ? { path } : {}
|
|
2005
|
+
};
|
|
2006
|
+
switch (name) {
|
|
2007
|
+
case "chatgpt":
|
|
2008
|
+
return {
|
|
2009
|
+
...shared,
|
|
2010
|
+
details: [
|
|
2011
|
+
...detection.evidence,
|
|
2012
|
+
"ChatGPT desktop support is not automated yet.",
|
|
2013
|
+
"Manual follow-up: wire the OrgX surface once the app-side config contract is finalized."
|
|
2014
|
+
],
|
|
2015
|
+
summary: detection.detected ? "ChatGPT support files detected; manual OrgX wiring still required." : "ChatGPT desktop support files were not found on disk."
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
function getSurfaceStatus(name) {
|
|
2020
|
+
if (AUTOMATED_SURFACE_NAMES.includes(name)) {
|
|
2021
|
+
return automatedSurfaceStatus(
|
|
2022
|
+
name
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
return manualSurfaceStatus(name);
|
|
2026
|
+
}
|
|
2027
|
+
function listSurfaceStatuses() {
|
|
2028
|
+
return SURFACE_NAMES.map(getSurfaceStatus);
|
|
2029
|
+
}
|
|
2030
|
+
function resolveTargets(selection, allTargets) {
|
|
2031
|
+
const values = Array.isArray(selection) ? selection : [selection];
|
|
2032
|
+
if (values.includes("all")) {
|
|
2033
|
+
return [...allTargets];
|
|
2034
|
+
}
|
|
2035
|
+
return [...new Set(values)];
|
|
2036
|
+
}
|
|
2037
|
+
function resolveSurfaceSelection(selection) {
|
|
2038
|
+
return resolveTargets(selection, SURFACE_NAMES);
|
|
2039
|
+
}
|
|
2040
|
+
function resolveMcpSurfaceSelection(selection) {
|
|
2041
|
+
return resolveTargets(selection, MCP_SURFACE_NAMES);
|
|
2042
|
+
}
|
|
2043
|
+
async function addAutomatedSurface(name) {
|
|
2044
|
+
const path = getSurfacePath(name);
|
|
2045
|
+
const openclaw = getOpenClawDependencyState();
|
|
2046
|
+
if (!path) {
|
|
2047
|
+
return {
|
|
2048
|
+
name,
|
|
2049
|
+
changed: false,
|
|
2050
|
+
message: `${name} has no writable config path on this platform.`
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
switch (name) {
|
|
2054
|
+
case "claude": {
|
|
2055
|
+
const previous = readTextIfExists(path);
|
|
2056
|
+
const next = patchClaudeMcpConfig(previous, openclaw.url);
|
|
2057
|
+
writeTextFile(path, next);
|
|
2058
|
+
return {
|
|
2059
|
+
name,
|
|
2060
|
+
changed: previous !== next,
|
|
2061
|
+
path,
|
|
2062
|
+
message: openclaw.detected ? "OrgX is connected in Claude with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Claude."
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
case "cursor": {
|
|
2066
|
+
if (!openclaw.detected || !openclaw.url) {
|
|
2067
|
+
return {
|
|
2068
|
+
name,
|
|
2069
|
+
changed: false,
|
|
2070
|
+
path,
|
|
2071
|
+
message: "Cursor needs OpenClaw installed before OrgX can be connected locally."
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
const previous = readTextIfExists(path);
|
|
2075
|
+
const next = patchCursorMcpConfig(previous, openclaw.url);
|
|
2076
|
+
writeTextFile(path, next);
|
|
2077
|
+
return {
|
|
2078
|
+
name,
|
|
2079
|
+
changed: previous !== next,
|
|
2080
|
+
path,
|
|
2081
|
+
message: "OrgX is connected in Cursor through the local OpenClaw bridge."
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
case "codex": {
|
|
2085
|
+
const previous = readTextIfExists(path);
|
|
2086
|
+
const next = patchCodexConfigToml(previous, openclaw.url);
|
|
2087
|
+
writeTextFile(path, next);
|
|
2088
|
+
return {
|
|
2089
|
+
name,
|
|
2090
|
+
changed: previous !== next,
|
|
2091
|
+
path,
|
|
2092
|
+
message: openclaw.detected ? "OrgX is connected in Codex with the local OpenClaw bridge." : "OrgX cloud MCP is connected in Codex."
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
case "openclaw": {
|
|
2096
|
+
const previous = readTextIfExists(path);
|
|
2097
|
+
const auth = await resolveOrgxAuth();
|
|
2098
|
+
const next = patchOpenClawConfig(previous, auth ?? {});
|
|
2099
|
+
writeTextFile(path, next);
|
|
2100
|
+
return {
|
|
2101
|
+
name,
|
|
2102
|
+
changed: previous !== next,
|
|
2103
|
+
path,
|
|
2104
|
+
message: auth ? "OpenClaw is configured as the local OrgX bridge with the resolved OrgX auth." : "OpenClaw is configured as the local OrgX bridge."
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
case "vscode": {
|
|
2108
|
+
const previous = readTextIfExists(path);
|
|
2109
|
+
const next = patchVscodeMcpConfig(previous);
|
|
2110
|
+
writeTextFile(path, next);
|
|
2111
|
+
return {
|
|
2112
|
+
name,
|
|
2113
|
+
changed: previous !== next,
|
|
2114
|
+
path,
|
|
2115
|
+
message: "OrgX cloud MCP is connected in VS Code."
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
case "windsurf": {
|
|
2119
|
+
const previous = readTextIfExists(path);
|
|
2120
|
+
const next = patchWindsurfMcpConfig(previous);
|
|
2121
|
+
writeTextFile(path, next);
|
|
2122
|
+
return {
|
|
2123
|
+
name,
|
|
2124
|
+
changed: previous !== next,
|
|
2125
|
+
path,
|
|
2126
|
+
message: "OrgX cloud MCP is connected in Windsurf."
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
case "zed": {
|
|
2130
|
+
const previous = readTextIfExists(path);
|
|
2131
|
+
const next = patchZedMcpConfig(previous);
|
|
2132
|
+
writeTextFile(path, next);
|
|
2133
|
+
return {
|
|
2134
|
+
name,
|
|
2135
|
+
changed: previous !== next,
|
|
2136
|
+
path,
|
|
2137
|
+
message: "OrgX cloud MCP is connected in Zed."
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
default:
|
|
2141
|
+
return {
|
|
2142
|
+
name,
|
|
2143
|
+
changed: false,
|
|
2144
|
+
message: `${name} is not automated yet.`
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function removeAutomatedSurface(name) {
|
|
2149
|
+
const path = getSurfacePath(name);
|
|
2150
|
+
if (!path) {
|
|
2151
|
+
return {
|
|
2152
|
+
name,
|
|
2153
|
+
changed: false,
|
|
2154
|
+
message: `${name} has no writable config path on this platform.`
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
switch (name) {
|
|
2158
|
+
case "claude": {
|
|
2159
|
+
const previous = readTextIfExists(path);
|
|
2160
|
+
const next = removeClaudeMcpConfig(previous);
|
|
2161
|
+
writeTextFile(path, next);
|
|
2162
|
+
return {
|
|
2163
|
+
name,
|
|
2164
|
+
changed: previous !== next,
|
|
2165
|
+
path,
|
|
2166
|
+
message: "OrgX connection was removed from Claude."
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
case "cursor": {
|
|
2170
|
+
const previous = readTextIfExists(path);
|
|
2171
|
+
const next = removeCursorMcpConfig(previous);
|
|
2172
|
+
writeTextFile(path, next);
|
|
2173
|
+
return {
|
|
2174
|
+
name,
|
|
2175
|
+
changed: previous !== next,
|
|
2176
|
+
path,
|
|
2177
|
+
message: "OrgX connection was removed from Cursor."
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
case "codex": {
|
|
2181
|
+
const previous = readTextIfExists(path);
|
|
2182
|
+
const next = removeCodexConfigToml(previous);
|
|
2183
|
+
writeTextFile(path, next);
|
|
2184
|
+
return {
|
|
2185
|
+
name,
|
|
2186
|
+
changed: previous !== next,
|
|
2187
|
+
path,
|
|
2188
|
+
message: "OrgX connection was removed from Codex."
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
case "openclaw": {
|
|
2192
|
+
const previous = readTextIfExists(path);
|
|
2193
|
+
const next = removeOpenClawConfig(previous);
|
|
2194
|
+
writeTextFile(path, next);
|
|
2195
|
+
return {
|
|
2196
|
+
name,
|
|
2197
|
+
changed: previous !== next,
|
|
2198
|
+
path,
|
|
2199
|
+
message: "OpenClaw OrgX defaults were removed."
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
case "vscode": {
|
|
2203
|
+
const previous = readTextIfExists(path);
|
|
2204
|
+
const next = removeVscodeMcpConfig(previous);
|
|
2205
|
+
writeTextFile(path, next);
|
|
2206
|
+
return {
|
|
2207
|
+
name,
|
|
2208
|
+
changed: previous !== next,
|
|
2209
|
+
path,
|
|
2210
|
+
message: "OrgX connection was removed from VS Code."
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
case "windsurf": {
|
|
2214
|
+
const previous = readTextIfExists(path);
|
|
2215
|
+
const next = removeWindsurfMcpConfig(previous);
|
|
2216
|
+
writeTextFile(path, next);
|
|
2217
|
+
return {
|
|
2218
|
+
name,
|
|
2219
|
+
changed: previous !== next,
|
|
2220
|
+
path,
|
|
2221
|
+
message: "OrgX connection was removed from Windsurf."
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
case "zed": {
|
|
2225
|
+
const previous = readTextIfExists(path);
|
|
2226
|
+
const next = removeZedMcpConfig(previous);
|
|
2227
|
+
writeTextFile(path, next);
|
|
2228
|
+
return {
|
|
2229
|
+
name,
|
|
2230
|
+
changed: previous !== next,
|
|
2231
|
+
path,
|
|
2232
|
+
message: "OrgX connection was removed from Zed."
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
default:
|
|
2236
|
+
return {
|
|
2237
|
+
name,
|
|
2238
|
+
changed: false,
|
|
2239
|
+
message: `${name} is not automated yet.`
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
async function addSurface(selection) {
|
|
2244
|
+
const targets = resolveSurfaceSelection(selection);
|
|
2245
|
+
const results = [];
|
|
2246
|
+
for (const surface of targets) {
|
|
2247
|
+
results.push(await addAutomatedSurface(surface));
|
|
2248
|
+
}
|
|
2249
|
+
return results;
|
|
2250
|
+
}
|
|
2251
|
+
function removeSurface(selection) {
|
|
2252
|
+
const targets = resolveSurfaceSelection(selection);
|
|
2253
|
+
return targets.map((surface) => removeAutomatedSurface(surface));
|
|
2254
|
+
}
|
|
2255
|
+
async function addMcpSurface(selection) {
|
|
2256
|
+
const targets = resolveMcpSurfaceSelection(selection);
|
|
2257
|
+
const results = [];
|
|
2258
|
+
for (const surface of targets) {
|
|
2259
|
+
results.push(await addAutomatedSurface(surface));
|
|
2260
|
+
}
|
|
2261
|
+
return results;
|
|
2262
|
+
}
|
|
2263
|
+
function removeMcpSurface(selection) {
|
|
2264
|
+
const targets = resolveMcpSurfaceSelection(selection);
|
|
2265
|
+
return targets.map((surface) => removeAutomatedSurface(surface));
|
|
2266
|
+
}
|
|
2267
|
+
async function setupDetectedSurfaces() {
|
|
2268
|
+
const targets = AUTOMATED_SURFACE_NAMES.filter((name) => getSurfaceStatus(name).detected);
|
|
2269
|
+
targets.sort((left, right) => {
|
|
2270
|
+
if (left === "openclaw") return -1;
|
|
2271
|
+
if (right === "openclaw") return 1;
|
|
2272
|
+
return 0;
|
|
2273
|
+
});
|
|
2274
|
+
const results = [];
|
|
2275
|
+
for (const name of targets) {
|
|
2276
|
+
results.push(await addAutomatedSurface(name));
|
|
2277
|
+
}
|
|
2278
|
+
return results;
|
|
2279
|
+
}
|
|
2280
|
+
function skippedOpenClawHealth() {
|
|
2281
|
+
return {
|
|
2282
|
+
ok: false,
|
|
2283
|
+
skipped: true,
|
|
2284
|
+
method: "none",
|
|
2285
|
+
error: "OpenClaw support files were not detected; skipping gateway health check.",
|
|
2286
|
+
details: []
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
async function runDoctor() {
|
|
2290
|
+
const surfaces = listSurfaceStatuses();
|
|
2291
|
+
const openclawSurface = surfaces.find((surface) => surface.name === "openclaw");
|
|
2292
|
+
const auth = await checkOrgxAuth();
|
|
2293
|
+
const hostedMcp = await checkHostedMcpHealth();
|
|
2294
|
+
const hostedMcpTool = await checkHostedMcpToolAccess();
|
|
2295
|
+
const npmRegistry = await checkNpmRegistryHealth();
|
|
2296
|
+
const workspace = await checkWorkspaceConnectivity();
|
|
2297
|
+
const remote = await fetchSetupStatus();
|
|
2298
|
+
const openclaw = openclawSurface?.detected ? await checkOpenClawHealth(openclawSurface.path) : skippedOpenClawHealth();
|
|
2299
|
+
return { surfaces, auth, hostedMcp, hostedMcpTool, npmRegistry, workspace, remote, openclaw };
|
|
2300
|
+
}
|
|
2301
|
+
function assessDoctorReport(report) {
|
|
2302
|
+
const issues = [];
|
|
2303
|
+
if (!report.hostedMcp.ok) {
|
|
2304
|
+
issues.push({
|
|
2305
|
+
level: "error",
|
|
2306
|
+
title: "Hosted OrgX MCP is unreachable.",
|
|
2307
|
+
suggestion: "Retry later or verify https://mcp.useorgx.com/health. Hosted-only clients will stay broken until that endpoint recovers."
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
if (report.hostedMcp.ok && report.hostedMcpTool.configured && !report.hostedMcpTool.ok && !report.hostedMcpTool.skipped) {
|
|
2311
|
+
issues.push({
|
|
2312
|
+
level: "error",
|
|
2313
|
+
title: `Hosted OrgX MCP tool verification failed (${report.hostedMcpTool.toolName}).`,
|
|
2314
|
+
suggestion: "Re-run `wizard auth login` to refresh the per-user key, then retry `wizard doctor` to confirm the hosted MCP can initialize and execute tools."
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
if (report.auth.configured && !report.auth.ok && !report.auth.skipped) {
|
|
2318
|
+
issues.push({
|
|
2319
|
+
level: "error",
|
|
2320
|
+
title: "OrgX user auth could not be verified.",
|
|
2321
|
+
suggestion: "Run `wizard auth set-key <oxk_...>` again or correct the resolved ORGX_API_KEY / base URL before retrying."
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
if (report.workspace.configured && !report.workspace.ok) {
|
|
2325
|
+
issues.push({
|
|
2326
|
+
level: "error",
|
|
2327
|
+
title: "Current workspace lookup failed.",
|
|
2328
|
+
suggestion: "Verify the resolved OrgX base URL and that the authenticated user still has access to a workspace."
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
if (!report.npmRegistry.reachable) {
|
|
2332
|
+
issues.push({
|
|
2333
|
+
level: "warning",
|
|
2334
|
+
title: "npm registry is unreachable.",
|
|
2335
|
+
suggestion: "Check outbound network access to https://registry.npmjs.org before relying on npm-based installation flows."
|
|
2336
|
+
});
|
|
2337
|
+
} else if (!report.npmRegistry.published) {
|
|
2338
|
+
issues.push({
|
|
2339
|
+
level: "warning",
|
|
2340
|
+
title: `${report.npmRegistry.packageName} is not published on npm yet.`,
|
|
2341
|
+
suggestion: "This is expected before launch. Publish the package in the release workstream before asking users to install from npm."
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
if (!report.openclaw.skipped && !report.openclaw.ok) {
|
|
2345
|
+
issues.push({
|
|
2346
|
+
level: "warning",
|
|
2347
|
+
title: "OpenClaw is installed but its local gateway health check failed.",
|
|
2348
|
+
suggestion: "Run `openclaw doctor` or `openclaw doctor --repair` before relying on local bridge clients like Cursor."
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
if (!report.remote.skipped && !report.remote.ok) {
|
|
2352
|
+
issues.push({
|
|
2353
|
+
level: "warning",
|
|
2354
|
+
title: "Remote setup status check failed.",
|
|
2355
|
+
suggestion: "If you expected the service-key check to pass, verify ORGX_SERVICE_KEY before trusting setup-status output."
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
return {
|
|
2359
|
+
ok: !issues.some((issue) => issue.level === "error"),
|
|
2360
|
+
issues
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/lib/wizard-state.ts
|
|
2365
|
+
import { randomUUID } from "crypto";
|
|
2366
|
+
function isNonEmptyString2(value) {
|
|
2367
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2368
|
+
}
|
|
2369
|
+
function isSurfaceName(value) {
|
|
2370
|
+
return typeof value === "string" && SURFACE_NAMES.includes(value);
|
|
2371
|
+
}
|
|
2372
|
+
function parseContinuityDefaults(value) {
|
|
2373
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2374
|
+
return void 0;
|
|
2375
|
+
}
|
|
2376
|
+
const record = value;
|
|
2377
|
+
const parsed = {};
|
|
2378
|
+
if (record.executionMode === "cloud" || record.executionMode === "local") {
|
|
2379
|
+
parsed.executionMode = record.executionMode;
|
|
2380
|
+
}
|
|
2381
|
+
if (isSurfaceName(record.reviewSurface)) {
|
|
2382
|
+
parsed.reviewSurface = record.reviewSurface;
|
|
2383
|
+
}
|
|
2384
|
+
if (isNonEmptyString2(record.workspaceId)) {
|
|
2385
|
+
parsed.workspaceId = record.workspaceId.trim();
|
|
2386
|
+
}
|
|
2387
|
+
if (isNonEmptyString2(record.workspaceName)) {
|
|
2388
|
+
parsed.workspaceName = record.workspaceName.trim();
|
|
2389
|
+
}
|
|
2390
|
+
return Object.keys(parsed).length > 0 ? parsed : void 0;
|
|
2391
|
+
}
|
|
2392
|
+
function parseDemoInitiative(value) {
|
|
2393
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2394
|
+
return void 0;
|
|
2395
|
+
}
|
|
2396
|
+
const record = value;
|
|
2397
|
+
if (!isNonEmptyString2(record.id) || !isNonEmptyString2(record.title) || !isNonEmptyString2(record.liveUrl) || !isNonEmptyString2(record.createdAt)) {
|
|
2398
|
+
return void 0;
|
|
2399
|
+
}
|
|
2400
|
+
return {
|
|
2401
|
+
createdAt: record.createdAt.trim(),
|
|
2402
|
+
id: record.id.trim(),
|
|
2403
|
+
liveUrl: record.liveUrl.trim(),
|
|
2404
|
+
title: record.title.trim(),
|
|
2405
|
+
...isNonEmptyString2(record.workspaceId) ? { workspaceId: record.workspaceId.trim() } : {},
|
|
2406
|
+
...isNonEmptyString2(record.workspaceName) ? { workspaceName: record.workspaceName.trim() } : {}
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2410
|
+
return {
|
|
2411
|
+
installationId: `wizard-${randomUUID()}`,
|
|
2412
|
+
createdAt: now,
|
|
2413
|
+
updatedAt: now
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
function sanitizeWizardStateRecord(record) {
|
|
2417
|
+
const continuity = parseContinuityDefaults(record.continuity);
|
|
2418
|
+
const demoInitiative = parseDemoInitiative(record.demoInitiative);
|
|
2419
|
+
return {
|
|
2420
|
+
installationId: record.installationId.trim(),
|
|
2421
|
+
createdAt: record.createdAt.trim(),
|
|
2422
|
+
updatedAt: record.updatedAt.trim(),
|
|
2423
|
+
...continuity ? { continuity } : {},
|
|
2424
|
+
...demoInitiative ? { demoInitiative } : {}
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
|
|
2428
|
+
const parsed = parseJsonObject(readTextIfExists(statePath));
|
|
2429
|
+
if (!isNonEmptyString2(parsed.installationId) || !isNonEmptyString2(parsed.createdAt) || !isNonEmptyString2(parsed.updatedAt)) {
|
|
2430
|
+
return null;
|
|
2431
|
+
}
|
|
2432
|
+
const state = {
|
|
2433
|
+
installationId: parsed.installationId.trim(),
|
|
2434
|
+
createdAt: parsed.createdAt.trim(),
|
|
2435
|
+
updatedAt: parsed.updatedAt.trim()
|
|
2436
|
+
};
|
|
2437
|
+
const continuity = parseContinuityDefaults(parsed.continuity);
|
|
2438
|
+
if (continuity !== void 0) state.continuity = continuity;
|
|
2439
|
+
const demoInitiative = parseDemoInitiative(parsed.demoInitiative);
|
|
2440
|
+
if (demoInitiative !== void 0) state.demoInitiative = demoInitiative;
|
|
2441
|
+
return state;
|
|
2442
|
+
}
|
|
2443
|
+
function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
|
|
2444
|
+
const record = sanitizeWizardStateRecord(value);
|
|
2445
|
+
writeJsonFile(statePath, record, { mode: 384 });
|
|
2446
|
+
return record;
|
|
2447
|
+
}
|
|
2448
|
+
function updateWizardState(updater, statePath = ORGX_WIZARD_STATE_PATH) {
|
|
2449
|
+
const current = readWizardState(statePath) ?? createWizardState();
|
|
2450
|
+
const next = updater(current);
|
|
2451
|
+
const sanitized = sanitizeWizardStateRecord({
|
|
2452
|
+
...next,
|
|
2453
|
+
installationId: next.installationId || current.installationId,
|
|
2454
|
+
createdAt: next.createdAt || current.createdAt,
|
|
2455
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2456
|
+
});
|
|
2457
|
+
writeJsonFile(statePath, sanitized, { mode: 384 });
|
|
2458
|
+
return sanitized;
|
|
2459
|
+
}
|
|
2460
|
+
function getOrCreateWizardInstallationId(statePath = ORGX_WIZARD_STATE_PATH) {
|
|
2461
|
+
const existing = readWizardState(statePath);
|
|
2462
|
+
if (existing) {
|
|
2463
|
+
return existing.installationId;
|
|
2464
|
+
}
|
|
2465
|
+
const record = createWizardState();
|
|
2466
|
+
writeWizardState(record, statePath);
|
|
2467
|
+
return record.installationId;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
// src/lib/continuity.ts
|
|
2471
|
+
var REVIEW_SURFACE_ORDER = [
|
|
2472
|
+
"claude",
|
|
2473
|
+
"cursor",
|
|
2474
|
+
"codex",
|
|
2475
|
+
"vscode",
|
|
2476
|
+
"windsurf",
|
|
2477
|
+
"zed",
|
|
2478
|
+
"chatgpt"
|
|
2479
|
+
];
|
|
2480
|
+
function selectReviewSurface(statuses) {
|
|
2481
|
+
for (const name of REVIEW_SURFACE_ORDER) {
|
|
2482
|
+
const configured = statuses.find((status) => status.name === name && status.configured);
|
|
2483
|
+
if (configured) {
|
|
2484
|
+
return configured.name;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
for (const name of REVIEW_SURFACE_ORDER) {
|
|
2488
|
+
const detected = statuses.find((status) => status.name === name && status.detected);
|
|
2489
|
+
if (detected) {
|
|
2490
|
+
return detected.name;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return void 0;
|
|
2494
|
+
}
|
|
2495
|
+
function inferExecutionMode(explicitMode, statuses) {
|
|
2496
|
+
if (explicitMode) {
|
|
2497
|
+
return explicitMode;
|
|
2498
|
+
}
|
|
2499
|
+
const openclaw = statuses.find((status) => status.name === "openclaw");
|
|
2500
|
+
if (openclaw?.configured || openclaw?.detected) {
|
|
2501
|
+
return "local";
|
|
2502
|
+
}
|
|
2503
|
+
const hostedSurface = statuses.find(
|
|
2504
|
+
(status) => status.name !== "openclaw" && (status.configured || status.detected)
|
|
2505
|
+
);
|
|
2506
|
+
return hostedSurface ? "cloud" : void 0;
|
|
2507
|
+
}
|
|
2508
|
+
function inferContinuityDefaults(seed = {}) {
|
|
2509
|
+
const statuses = seed.statuses ?? listSurfaceStatuses();
|
|
2510
|
+
const continuity = {};
|
|
2511
|
+
const reviewSurface = selectReviewSurface(statuses);
|
|
2512
|
+
if (reviewSurface) {
|
|
2513
|
+
continuity.reviewSurface = reviewSurface;
|
|
2514
|
+
}
|
|
2515
|
+
const executionMode = inferExecutionMode(seed.executionMode, statuses);
|
|
2516
|
+
if (executionMode) {
|
|
2517
|
+
continuity.executionMode = executionMode;
|
|
2518
|
+
}
|
|
2519
|
+
if (seed.workspace?.id) {
|
|
2520
|
+
continuity.workspaceId = seed.workspace.id;
|
|
2521
|
+
}
|
|
2522
|
+
const workspaceName = seed.workspace?.name ?? seed.workspaceName ?? void 0;
|
|
2523
|
+
if (workspaceName?.trim()) {
|
|
2524
|
+
continuity.workspaceName = workspaceName.trim();
|
|
2525
|
+
}
|
|
2526
|
+
return continuity;
|
|
2527
|
+
}
|
|
2528
|
+
function mergeContinuityDefaults(current, next) {
|
|
2529
|
+
return {
|
|
2530
|
+
...current ?? {},
|
|
2531
|
+
...next
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function persistContinuityDefaults(seed = {}, statePath) {
|
|
2535
|
+
const next = inferContinuityDefaults(seed);
|
|
2536
|
+
return updateWizardState(
|
|
2537
|
+
(current) => ({
|
|
2538
|
+
...current,
|
|
2539
|
+
continuity: mergeContinuityDefaults(current.continuity, next)
|
|
2540
|
+
}),
|
|
2541
|
+
statePath
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
// src/lib/initiatives.ts
|
|
2546
|
+
var FOUNDER_DEMO_INITIATIVE_TITLE = "Founder Demo Initiative";
|
|
2547
|
+
var FOUNDER_DEMO_INITIATIVE_SUMMARY = "Starter initiative created by @useorgx/wizard to validate workspace routing, live views, and the default OrgX skill pack.";
|
|
2548
|
+
function parseResponseBody3(text2) {
|
|
2549
|
+
if (!text2) {
|
|
2550
|
+
return null;
|
|
2551
|
+
}
|
|
2552
|
+
try {
|
|
2553
|
+
return JSON.parse(text2);
|
|
2554
|
+
} catch {
|
|
2555
|
+
return text2;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
function formatHttpError2(status, body) {
|
|
2559
|
+
if (typeof body === "string" && body.trim().length > 0) {
|
|
2560
|
+
return `HTTP ${status}: ${body}`;
|
|
2561
|
+
}
|
|
2562
|
+
if (isRecord(body) && typeof body.error === "string" && body.error.trim().length > 0) {
|
|
2563
|
+
return `HTTP ${status}: ${body.error}`;
|
|
2564
|
+
}
|
|
2565
|
+
return `HTTP ${status}`;
|
|
2566
|
+
}
|
|
2567
|
+
function parseInitiative(payload) {
|
|
2568
|
+
const entity = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
|
|
2569
|
+
if (!isRecord(entity)) {
|
|
2570
|
+
throw new Error("OrgX returned an unexpected initiative payload.");
|
|
2571
|
+
}
|
|
2572
|
+
const id = typeof entity.id === "string" ? entity.id.trim() : "";
|
|
2573
|
+
const title = typeof entity.title === "string" ? entity.title.trim() : typeof entity.name === "string" ? entity.name.trim() : "";
|
|
2574
|
+
if (!id || !title) {
|
|
2575
|
+
throw new Error("OrgX returned an incomplete initiative payload.");
|
|
2576
|
+
}
|
|
2577
|
+
return {
|
|
2578
|
+
id,
|
|
2579
|
+
title,
|
|
2580
|
+
...typeof entity.summary === "string" && entity.summary.trim().length > 0 ? { summary: entity.summary.trim() } : {}
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
function toDemoInitiativeRecord(initiative, liveUrl, workspace) {
|
|
2584
|
+
return {
|
|
2585
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2586
|
+
id: initiative.id,
|
|
2587
|
+
liveUrl,
|
|
2588
|
+
title: initiative.title,
|
|
2589
|
+
workspaceId: workspace.id,
|
|
2590
|
+
workspaceName: workspace.name
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
async function requireOrgxAuth2(options = {}) {
|
|
2594
|
+
const auth = await resolveOrgxAuth(options);
|
|
2595
|
+
if (!auth) {
|
|
2596
|
+
throw new Error(
|
|
2597
|
+
"No OrgX API key configured. Run `wizard auth login` or `wizard auth set-key <oxk_...>` first."
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
return auth;
|
|
2601
|
+
}
|
|
2602
|
+
async function createInitiative(input, options = {}) {
|
|
2603
|
+
const title = input.title.trim();
|
|
2604
|
+
if (!title) {
|
|
2605
|
+
throw new Error("Initiative title is required.");
|
|
2606
|
+
}
|
|
2607
|
+
const auth = await requireOrgxAuth2(options);
|
|
2608
|
+
const response = await fetch(buildOrgxApiUrl("/entities", auth.baseUrl), {
|
|
2609
|
+
method: "POST",
|
|
2610
|
+
headers: {
|
|
2611
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
2612
|
+
"Content-Type": "application/json"
|
|
2613
|
+
},
|
|
2614
|
+
body: JSON.stringify({
|
|
2615
|
+
type: "initiative",
|
|
2616
|
+
title,
|
|
2617
|
+
status: "active",
|
|
2618
|
+
...input.summary?.trim() ? { summary: input.summary.trim() } : {},
|
|
2619
|
+
...input.workspaceId?.trim() ? { workspace_id: input.workspaceId.trim() } : {}
|
|
2620
|
+
}),
|
|
2621
|
+
signal: AbortSignal.timeout(7e3)
|
|
2622
|
+
});
|
|
2623
|
+
const body = parseResponseBody3(await response.text());
|
|
2624
|
+
if (!response.ok) {
|
|
2625
|
+
throw new Error(`Failed to create initiative. ${formatHttpError2(response.status, body)}`);
|
|
2626
|
+
}
|
|
2627
|
+
return parseInitiative(body);
|
|
2628
|
+
}
|
|
2629
|
+
async function ensureFounderDemoInitiative(workspace, options = {}) {
|
|
2630
|
+
const existingRecord = readWizardState(options.statePath)?.demoInitiative;
|
|
2631
|
+
if (existingRecord?.workspaceId === workspace.id) {
|
|
2632
|
+
return {
|
|
2633
|
+
created: false,
|
|
2634
|
+
initiative: {
|
|
2635
|
+
id: existingRecord.id,
|
|
2636
|
+
title: existingRecord.title
|
|
2637
|
+
},
|
|
2638
|
+
liveUrl: existingRecord.liveUrl
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
const auth = await requireOrgxAuth2(options);
|
|
2642
|
+
const initiative = await createInitiative(
|
|
2643
|
+
{
|
|
2644
|
+
title: FOUNDER_DEMO_INITIATIVE_TITLE,
|
|
2645
|
+
summary: FOUNDER_DEMO_INITIATIVE_SUMMARY,
|
|
2646
|
+
workspaceId: workspace.id
|
|
2647
|
+
},
|
|
2648
|
+
options
|
|
2649
|
+
);
|
|
2650
|
+
const liveUrl = `${auth.baseUrl.replace(/\/+$/, "")}/live/${initiative.id}`;
|
|
2651
|
+
const record = toDemoInitiativeRecord(initiative, liveUrl, workspace);
|
|
2652
|
+
updateWizardState(
|
|
2653
|
+
(current) => ({
|
|
2654
|
+
...current,
|
|
2655
|
+
demoInitiative: record
|
|
2656
|
+
}),
|
|
2657
|
+
options.statePath
|
|
2658
|
+
);
|
|
2659
|
+
return {
|
|
2660
|
+
created: true,
|
|
2661
|
+
initiative,
|
|
2662
|
+
liveUrl
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// src/lib/skills.ts
|
|
2667
|
+
import { join as join2 } from "path";
|
|
2668
|
+
var DEFAULT_ORGX_SKILL_PACKS = [
|
|
2669
|
+
"morning-briefing",
|
|
2670
|
+
"initiative-kickoff",
|
|
2671
|
+
"bulk-create",
|
|
2672
|
+
"nightly-recap"
|
|
2673
|
+
];
|
|
2674
|
+
var EXCLUDED_PACK_DIRS = /* @__PURE__ */ new Set([".github", "scripts"]);
|
|
2675
|
+
var ORGX_SKILLS_OWNER = "useorgx";
|
|
2676
|
+
var ORGX_SKILLS_REPO = "skills";
|
|
2677
|
+
var ORGX_SKILLS_REF = "main";
|
|
2678
|
+
var CURSOR_RULES_CONTENT = `# OrgX Rules
|
|
2679
|
+
|
|
2680
|
+
- Prefer \`workspace_id\` on OrgX tool calls. \`command_center_id\` is a deprecated alias and should only be used for backwards compatibility. If both are present, they must match.
|
|
2681
|
+
- Treat \`entity_action\` with \`force: true\` as an escape hatch. It bypasses hierarchy and proof checks, so only use it when the user explicitly wants that behavior and say why.
|
|
2682
|
+
- Treat \`scaffold_initiative continue_on_error\` as best-effort creation. Use it only when partial scaffolds are acceptable, and follow up on any failed children.
|
|
2683
|
+
- Preserve \`_context\` when widget or app-rendering flows depend on client, conversation, session, or user metadata. Do not strip or rename it.
|
|
2684
|
+
- Prefer \`list_entities\` for pending approvals and state inspection before reaching for legacy aliases.
|
|
2685
|
+
- Keep hosted OrgX MCP calls distinct from local OpenClaw/plugin surfaces before mutating config or entity state.
|
|
2686
|
+
`;
|
|
2687
|
+
var CLAUDE_ORGX_SKILL_CONTENT = `---
|
|
2688
|
+
name: orgx
|
|
2689
|
+
description: Use when working against the hosted OrgX MCP server for workspace-scoped planning, entity mutations, progress reporting, and decision handling.
|
|
2690
|
+
version: 1.0.0
|
|
2691
|
+
user-invocable: true
|
|
2692
|
+
tags:
|
|
2693
|
+
- orchestration
|
|
2694
|
+
- productivity
|
|
2695
|
+
- workspace
|
|
2696
|
+
---
|
|
2697
|
+
|
|
2698
|
+
# OrgX Hosted MCP
|
|
2699
|
+
|
|
2700
|
+
Use this skill when Claude is connected to the hosted OrgX MCP server at \`https://mcp.useorgx.com/mcp\`.
|
|
2701
|
+
|
|
2702
|
+
This skill is for the hosted OrgX tool surface, not the local OpenClaw plugin. Keep those surfaces distinct before mutating config or entity state.
|
|
2703
|
+
|
|
2704
|
+
## Operating Rules
|
|
2705
|
+
|
|
2706
|
+
- Prefer \`workspace_id\` on OrgX tools. \`command_center_id\` is a deprecated alias kept for compatibility.
|
|
2707
|
+
- Treat \`entity_action\` with \`force: true\` as a deliberate override only. It bypasses hierarchy and proof-chain checks.
|
|
2708
|
+
- Treat \`scaffold_initiative continue_on_error\` as best-effort scaffolding. Use it only when partial creation is acceptable and inspect failures afterward.
|
|
2709
|
+
- Preserve \`_context\` when widget or app rendering depends on client, conversation, session, or user metadata.
|
|
2710
|
+
- Prefer \`list_entities\` over older aliases when you need pending decisions, blockers, or entity state.
|
|
2711
|
+
|
|
2712
|
+
## Suggested Skill Packs
|
|
2713
|
+
|
|
2714
|
+
Install and use these packs alongside this base skill:
|
|
2715
|
+
|
|
2716
|
+
- \`morning-briefing\`
|
|
2717
|
+
- \`initiative-kickoff\`
|
|
2718
|
+
- \`bulk-create\`
|
|
2719
|
+
- \`nightly-recap\`
|
|
2720
|
+
|
|
2721
|
+
## Suggested Workflow
|
|
2722
|
+
|
|
2723
|
+
1. Inspect state with \`mcp__orgx__list_entities\`, \`mcp__orgx__get_initiative_pulse\`, or the relevant workspace-scoped read tool.
|
|
2724
|
+
2. Mutate state with the narrowest tool that matches the task.
|
|
2725
|
+
3. When you need a hard override, explain why before using \`force: true\`.
|
|
2726
|
+
4. When you scaffold, decide up front whether \`continue_on_error\` is acceptable.
|
|
2727
|
+
5. Carry \`_context\` through any widget-producing flows so the UI can render and resume correctly.
|
|
2728
|
+
`;
|
|
2729
|
+
function encodeRepoPath(value) {
|
|
2730
|
+
return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
|
|
2731
|
+
}
|
|
2732
|
+
function buildContentsUrl(path, ref) {
|
|
2733
|
+
const encodedPath = encodeRepoPath(path);
|
|
2734
|
+
return `https://api.github.com/repos/${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`;
|
|
2735
|
+
}
|
|
2736
|
+
function resolveGitHubError(status, path) {
|
|
2737
|
+
if (status === 404) {
|
|
2738
|
+
return `OrgX skill pack '${path}' was not found in ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`;
|
|
2739
|
+
}
|
|
2740
|
+
return `GitHub returned HTTP ${status} while loading '${path}' from ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`;
|
|
2741
|
+
}
|
|
2742
|
+
async function readResponseText(response) {
|
|
2743
|
+
return response.text();
|
|
2744
|
+
}
|
|
2745
|
+
async function fetchDirectoryEntries(path, fetchImpl, ref) {
|
|
2746
|
+
const response = await fetchImpl(buildContentsUrl(path, ref), {
|
|
2747
|
+
headers: {
|
|
2748
|
+
Accept: "application/vnd.github+json"
|
|
2749
|
+
},
|
|
2750
|
+
signal: AbortSignal.timeout(1e4)
|
|
2751
|
+
});
|
|
2752
|
+
if (!response.ok) {
|
|
2753
|
+
throw new Error(resolveGitHubError(response.status, path));
|
|
2754
|
+
}
|
|
2755
|
+
const data = await response.json();
|
|
2756
|
+
if (!Array.isArray(data)) {
|
|
2757
|
+
throw new Error(`Expected '${path}' to be a directory in ${ORGX_SKILLS_OWNER}/${ORGX_SKILLS_REPO}.`);
|
|
2758
|
+
}
|
|
2759
|
+
return data.filter((entry) => {
|
|
2760
|
+
if (!entry || typeof entry !== "object") return false;
|
|
2761
|
+
return entry.type === "file" || entry.type === "dir";
|
|
2762
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
2763
|
+
}
|
|
2764
|
+
async function listRemoteSkillFiles(path, fetchImpl, ref) {
|
|
2765
|
+
const entries = await fetchDirectoryEntries(path, fetchImpl, ref);
|
|
2766
|
+
const files = [];
|
|
2767
|
+
for (const entry of entries) {
|
|
2768
|
+
if (entry.type === "dir") {
|
|
2769
|
+
files.push(...await listRemoteSkillFiles(entry.path, fetchImpl, ref));
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
if (!entry.download_url) {
|
|
2773
|
+
throw new Error(`GitHub did not provide a download URL for '${entry.path}'.`);
|
|
2774
|
+
}
|
|
2775
|
+
files.push({
|
|
2776
|
+
path: entry.path,
|
|
2777
|
+
sourceUrl: entry.download_url
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
return files;
|
|
2781
|
+
}
|
|
2782
|
+
async function fetchRemoteText(sourceUrl, fetchImpl) {
|
|
2783
|
+
const response = await fetchImpl(sourceUrl, {
|
|
2784
|
+
headers: {
|
|
2785
|
+
Accept: "text/plain"
|
|
2786
|
+
},
|
|
2787
|
+
signal: AbortSignal.timeout(1e4)
|
|
2788
|
+
});
|
|
2789
|
+
if (!response.ok) {
|
|
2790
|
+
throw new Error(`GitHub returned HTTP ${response.status} while downloading ${sourceUrl}.`);
|
|
2791
|
+
}
|
|
2792
|
+
return readResponseText(response);
|
|
2793
|
+
}
|
|
2794
|
+
function writeManagedFile(path, content, label, sourceUrl) {
|
|
2795
|
+
const existing = readTextIfExists(path);
|
|
2796
|
+
const changed = existing !== content;
|
|
2797
|
+
if (changed) {
|
|
2798
|
+
writeTextFile(path, content);
|
|
2799
|
+
}
|
|
2800
|
+
return {
|
|
2801
|
+
label,
|
|
2802
|
+
path,
|
|
2803
|
+
changed,
|
|
2804
|
+
...sourceUrl ? { sourceUrl } : {}
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
function resolveSkillPackNames(requested = []) {
|
|
2808
|
+
if (requested.length === 0 || requested.includes("all")) {
|
|
2809
|
+
return [...DEFAULT_ORGX_SKILL_PACKS];
|
|
2810
|
+
}
|
|
2811
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2812
|
+
const normalized = [];
|
|
2813
|
+
for (const name of requested) {
|
|
2814
|
+
const next = name.trim();
|
|
2815
|
+
if (!next || seen.has(next)) {
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
seen.add(next);
|
|
2819
|
+
normalized.push(next);
|
|
2820
|
+
}
|
|
2821
|
+
return normalized;
|
|
2822
|
+
}
|
|
2823
|
+
async function fetchAvailablePackNames(fetchImpl, ref) {
|
|
2824
|
+
const entries = await fetchDirectoryEntries("", fetchImpl, ref);
|
|
2825
|
+
return entries.filter((e) => e.type === "dir" && !EXCLUDED_PACK_DIRS.has(e.name)).map((e) => e.name);
|
|
2826
|
+
}
|
|
2827
|
+
async function installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref) {
|
|
2828
|
+
const rootPath = skillName;
|
|
2829
|
+
const files = await listRemoteSkillFiles(rootPath, fetchImpl, ref);
|
|
2830
|
+
const writes = [];
|
|
2831
|
+
for (const file of files) {
|
|
2832
|
+
const content = await fetchRemoteText(file.sourceUrl, fetchImpl);
|
|
2833
|
+
const relativePath = file.path.slice(`${rootPath}/`.length);
|
|
2834
|
+
writes.push(
|
|
2835
|
+
writeManagedFile(
|
|
2836
|
+
join2(claudeSkillsDir, skillName, relativePath),
|
|
2837
|
+
content,
|
|
2838
|
+
`${skillName}/${relativePath}`,
|
|
2839
|
+
file.sourceUrl
|
|
2840
|
+
)
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
return {
|
|
2844
|
+
name: skillName,
|
|
2845
|
+
files: writes
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
async function installOrgxSkills(options = {}) {
|
|
2849
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
2850
|
+
if (!fetchImpl) {
|
|
2851
|
+
throw new Error("Global fetch is unavailable in this runtime.");
|
|
2852
|
+
}
|
|
2853
|
+
const ref = options.ref ?? ORGX_SKILLS_REF;
|
|
2854
|
+
const claudeSkillsDir = options.claudeSkillsDir ?? CLAUDE_SKILLS_DIR;
|
|
2855
|
+
const claudeOrgxSkillPath = options.claudeOrgxSkillPath ?? CLAUDE_ORGX_SKILL_PATH;
|
|
2856
|
+
const cursorRulePath = options.cursorRulePath ?? CURSOR_ORGX_RULE_PATH;
|
|
2857
|
+
const requestedNames = options.skillNames ?? [];
|
|
2858
|
+
let skillNames;
|
|
2859
|
+
if (requestedNames.length === 0 || requestedNames.includes("all")) {
|
|
2860
|
+
try {
|
|
2861
|
+
skillNames = await fetchAvailablePackNames(fetchImpl, ref);
|
|
2862
|
+
} catch {
|
|
2863
|
+
skillNames = [...DEFAULT_ORGX_SKILL_PACKS];
|
|
2864
|
+
}
|
|
2865
|
+
} else {
|
|
2866
|
+
skillNames = resolveSkillPackNames(requestedNames);
|
|
2867
|
+
}
|
|
2868
|
+
const writes = [
|
|
2869
|
+
writeManagedFile(cursorRulePath, CURSOR_RULES_CONTENT, "cursor-rules"),
|
|
2870
|
+
writeManagedFile(claudeOrgxSkillPath, CLAUDE_ORGX_SKILL_CONTENT, "claude-orgx-skill")
|
|
2871
|
+
];
|
|
2872
|
+
const packs = [];
|
|
2873
|
+
for (const skillName of skillNames) {
|
|
2874
|
+
packs.push(await installSkillPack(skillName, claudeSkillsDir, fetchImpl, ref));
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
writes,
|
|
2878
|
+
packs
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
// src/lib/setup-workspace.ts
|
|
2883
|
+
var CREATE_WORKSPACE_VALUE = "__create_workspace__";
|
|
2884
|
+
var SKIP_WORKSPACE_VALUE = "__skip_workspace__";
|
|
2885
|
+
function trimDescription(value) {
|
|
2886
|
+
const trimmed = value.trim();
|
|
2887
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2888
|
+
}
|
|
2889
|
+
function cancelResult(prompts, message = "Workspace bootstrap cancelled.") {
|
|
2890
|
+
prompts.cancel(message);
|
|
2891
|
+
return {
|
|
2892
|
+
message,
|
|
2893
|
+
status: "cancelled"
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
function buildWorkspaceLabel(workspace, currentWorkspaceId) {
|
|
2897
|
+
const suffixes = [];
|
|
2898
|
+
if (workspace.isDefault) {
|
|
2899
|
+
suffixes.push("default");
|
|
2900
|
+
}
|
|
2901
|
+
if (workspace.id === currentWorkspaceId) {
|
|
2902
|
+
suffixes.push("current");
|
|
2903
|
+
}
|
|
2904
|
+
if (suffixes.length === 0) {
|
|
2905
|
+
return workspace.name;
|
|
2906
|
+
}
|
|
2907
|
+
return `${workspace.name} (${suffixes.join(", ")})`;
|
|
2908
|
+
}
|
|
2909
|
+
function buildWorkspaceSelectOptions(workspaces, currentWorkspaceId) {
|
|
2910
|
+
const options = workspaces.map((workspace) => ({
|
|
2911
|
+
value: workspace.id,
|
|
2912
|
+
label: buildWorkspaceLabel(workspace, currentWorkspaceId),
|
|
2913
|
+
...workspace.description ? { hint: workspace.description } : {}
|
|
2914
|
+
}));
|
|
2915
|
+
options.push(
|
|
2916
|
+
{
|
|
2917
|
+
value: CREATE_WORKSPACE_VALUE,
|
|
2918
|
+
label: "Create a new workspace",
|
|
2919
|
+
hint: "Name it here and set it as the default for this machine."
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
value: SKIP_WORKSPACE_VALUE,
|
|
2923
|
+
label: "Skip for now",
|
|
2924
|
+
hint: "Leave workspace selection unchanged and finish setup."
|
|
2925
|
+
}
|
|
2926
|
+
);
|
|
2927
|
+
return options;
|
|
2928
|
+
}
|
|
2929
|
+
async function promptForWorkspaceName(prompts) {
|
|
2930
|
+
return prompts.text({
|
|
2931
|
+
message: "Workspace name",
|
|
2932
|
+
placeholder: "Founders",
|
|
2933
|
+
validate(value) {
|
|
2934
|
+
if (!value || value.trim().length === 0) {
|
|
2935
|
+
return "Workspace name is required.";
|
|
2936
|
+
}
|
|
2937
|
+
return void 0;
|
|
2938
|
+
}
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
async function promptForWorkspaceDescription(prompts) {
|
|
2942
|
+
return prompts.text({
|
|
2943
|
+
message: "Workspace description",
|
|
2944
|
+
placeholder: "Optional"
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
async function createAndSelectWorkspace(client, prompts) {
|
|
2948
|
+
const name = await promptForWorkspaceName(prompts);
|
|
2949
|
+
if (prompts.isCancel(name)) {
|
|
2950
|
+
return cancelResult(prompts);
|
|
2951
|
+
}
|
|
2952
|
+
if (typeof name !== "string") {
|
|
2953
|
+
return cancelResult(prompts);
|
|
2954
|
+
}
|
|
2955
|
+
const description = await promptForWorkspaceDescription(prompts);
|
|
2956
|
+
if (prompts.isCancel(description)) {
|
|
2957
|
+
return cancelResult(prompts);
|
|
2958
|
+
}
|
|
2959
|
+
if (typeof description !== "string") {
|
|
2960
|
+
return cancelResult(prompts);
|
|
2961
|
+
}
|
|
2962
|
+
const trimmedDescription = trimDescription(description);
|
|
2963
|
+
const createWorkspaceInput = trimmedDescription ? { name, description: trimmedDescription } : { name };
|
|
2964
|
+
const created = await client.createWorkspace(createWorkspaceInput);
|
|
2965
|
+
if (created.isDefault) {
|
|
2966
|
+
return {
|
|
2967
|
+
created: true,
|
|
2968
|
+
defaultChanged: false,
|
|
2969
|
+
message: `Created "${created.name}" and it is already the default OrgX workspace.`,
|
|
2970
|
+
status: "updated",
|
|
2971
|
+
workspace: created
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
const promoted = await client.setDefaultWorkspace({ id: created.id });
|
|
2975
|
+
return {
|
|
2976
|
+
created: true,
|
|
2977
|
+
defaultChanged: promoted.changed,
|
|
2978
|
+
message: `Created "${promoted.workspace.name}" and set it as the default OrgX workspace.`,
|
|
2979
|
+
status: "updated",
|
|
2980
|
+
workspace: promoted.workspace
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
async function runWorkspaceSetup(client, prompts, options) {
|
|
2984
|
+
if (!options.interactive) {
|
|
2985
|
+
return {
|
|
2986
|
+
message: "Interactive workspace bootstrap skipped because this shell is not attached to a TTY.",
|
|
2987
|
+
status: "skipped"
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
const workspaces = await client.listWorkspaces();
|
|
2991
|
+
const currentWorkspace = workspaces.length > 0 ? await client.getCurrentWorkspace().catch(() => workspaces.find((workspace) => workspace.isDefault) ?? null) : null;
|
|
2992
|
+
if (workspaces.length === 0) {
|
|
2993
|
+
const action = await prompts.select({
|
|
2994
|
+
message: "No OrgX workspaces were found. What should setup do next?",
|
|
2995
|
+
options: [
|
|
2996
|
+
{
|
|
2997
|
+
value: CREATE_WORKSPACE_VALUE,
|
|
2998
|
+
label: "Create a new workspace",
|
|
2999
|
+
hint: "Create one now and set it as the default for this machine."
|
|
3000
|
+
},
|
|
3001
|
+
{
|
|
3002
|
+
value: SKIP_WORKSPACE_VALUE,
|
|
3003
|
+
label: "Skip for now",
|
|
3004
|
+
hint: "Finish surface setup without creating a workspace."
|
|
3005
|
+
}
|
|
3006
|
+
],
|
|
3007
|
+
initialValue: CREATE_WORKSPACE_VALUE
|
|
3008
|
+
});
|
|
3009
|
+
if (prompts.isCancel(action)) {
|
|
3010
|
+
return cancelResult(prompts);
|
|
3011
|
+
}
|
|
3012
|
+
if (action === SKIP_WORKSPACE_VALUE) {
|
|
3013
|
+
return {
|
|
3014
|
+
message: "Workspace bootstrap skipped.",
|
|
3015
|
+
status: "skipped"
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
return createAndSelectWorkspace(client, prompts);
|
|
3019
|
+
}
|
|
3020
|
+
const initialWorkspaceId = currentWorkspace?.id ?? workspaces.find((workspace) => workspace.isDefault)?.id ?? workspaces[0]?.id;
|
|
3021
|
+
const selected = await prompts.select({
|
|
3022
|
+
message: "Choose the OrgX workspace this machine should use by default.",
|
|
3023
|
+
options: buildWorkspaceSelectOptions(
|
|
3024
|
+
workspaces,
|
|
3025
|
+
currentWorkspace?.id ?? workspaces.find((workspace) => workspace.isDefault)?.id
|
|
3026
|
+
),
|
|
3027
|
+
...initialWorkspaceId ? { initialValue: initialWorkspaceId } : {}
|
|
3028
|
+
});
|
|
3029
|
+
if (prompts.isCancel(selected)) {
|
|
3030
|
+
return cancelResult(prompts);
|
|
3031
|
+
}
|
|
3032
|
+
if (typeof selected !== "string") {
|
|
3033
|
+
return cancelResult(prompts);
|
|
3034
|
+
}
|
|
3035
|
+
if (selected === SKIP_WORKSPACE_VALUE) {
|
|
3036
|
+
return {
|
|
3037
|
+
message: "Workspace bootstrap skipped.",
|
|
3038
|
+
status: "skipped",
|
|
3039
|
+
...currentWorkspace ? { workspace: currentWorkspace } : {}
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
if (selected === CREATE_WORKSPACE_VALUE) {
|
|
3043
|
+
return createAndSelectWorkspace(client, prompts);
|
|
3044
|
+
}
|
|
3045
|
+
const chosenWorkspace = workspaces.find((workspace) => workspace.id === selected);
|
|
3046
|
+
if (!chosenWorkspace) {
|
|
3047
|
+
throw new Error(`Selected workspace ${selected} is no longer available.`);
|
|
3048
|
+
}
|
|
3049
|
+
if (chosenWorkspace.isDefault) {
|
|
3050
|
+
return {
|
|
3051
|
+
defaultChanged: false,
|
|
3052
|
+
message: `"${chosenWorkspace.name}" is already the default OrgX workspace.`,
|
|
3053
|
+
status: "unchanged",
|
|
3054
|
+
workspace: chosenWorkspace
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
const promoted = await client.setDefaultWorkspace({ id: chosenWorkspace.id });
|
|
3058
|
+
return {
|
|
3059
|
+
defaultChanged: promoted.changed,
|
|
3060
|
+
message: `Set "${promoted.workspace.name}" as the default OrgX workspace.`,
|
|
3061
|
+
status: promoted.changed ? "updated" : "unchanged",
|
|
3062
|
+
workspace: promoted.workspace
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
// src/lib/founder-preset.ts
|
|
3067
|
+
async function runFounderPreset(prompts, options) {
|
|
3068
|
+
const surfaceResults = await setupDetectedSurfaces();
|
|
3069
|
+
const skillReport = await installOrgxSkills({
|
|
3070
|
+
skillNames: [...DEFAULT_ORGX_SKILL_PACKS]
|
|
3071
|
+
});
|
|
3072
|
+
let workspaceSetup;
|
|
3073
|
+
let workspace = await getCurrentWorkspace().catch(() => null);
|
|
3074
|
+
if (workspace || options.interactive) {
|
|
3075
|
+
workspaceSetup = await runWorkspaceSetup(
|
|
3076
|
+
{
|
|
3077
|
+
createWorkspace,
|
|
3078
|
+
getCurrentWorkspace,
|
|
3079
|
+
listWorkspaces,
|
|
3080
|
+
setDefaultWorkspace
|
|
3081
|
+
},
|
|
3082
|
+
prompts,
|
|
3083
|
+
options
|
|
3084
|
+
);
|
|
3085
|
+
if (workspaceSetup.status === "cancelled") {
|
|
3086
|
+
return workspaceSetup;
|
|
3087
|
+
}
|
|
3088
|
+
workspace = workspaceSetup.workspace ?? workspace ?? await getCurrentWorkspace().catch(() => null);
|
|
3089
|
+
}
|
|
3090
|
+
const continuity = persistContinuityDefaults({
|
|
3091
|
+
statuses: listSurfaceStatuses(),
|
|
3092
|
+
workspace
|
|
3093
|
+
});
|
|
3094
|
+
let demoInitiative;
|
|
3095
|
+
if (workspace) {
|
|
3096
|
+
demoInitiative = await ensureFounderDemoInitiative(workspace);
|
|
3097
|
+
}
|
|
3098
|
+
return {
|
|
3099
|
+
continuity,
|
|
3100
|
+
...demoInitiative ? { demoInitiative } : {},
|
|
3101
|
+
skillReport,
|
|
3102
|
+
surfaceResults,
|
|
3103
|
+
workspace,
|
|
3104
|
+
...workspaceSetup ? { workspaceSetup } : {}
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
// src/lib/mutation-output.ts
|
|
3109
|
+
function summarizeMutationResults(results) {
|
|
3110
|
+
return results.map((result) => ({
|
|
3111
|
+
name: result.name,
|
|
3112
|
+
state: result.changed ? "updated" : "unchanged",
|
|
3113
|
+
message: result.message
|
|
3114
|
+
}));
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/spinner.ts
|
|
3118
|
+
import ora from "ora";
|
|
3119
|
+
import pc2 from "picocolors";
|
|
3120
|
+
var frames = ["[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]"];
|
|
3121
|
+
function createOrgxSpinner(text2) {
|
|
3122
|
+
return ora({
|
|
3123
|
+
text: pc2.cyan(text2),
|
|
3124
|
+
spinner: {
|
|
3125
|
+
interval: 90,
|
|
3126
|
+
frames
|
|
3127
|
+
}
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
// src/cli.ts
|
|
3132
|
+
function formatAuthSource(source) {
|
|
3133
|
+
switch (source) {
|
|
3134
|
+
case "environment":
|
|
3135
|
+
return "ORGX_API_KEY";
|
|
3136
|
+
case "wizard-store":
|
|
3137
|
+
return "wizard auth store";
|
|
3138
|
+
case "openclaw-config-file":
|
|
3139
|
+
return "OpenClaw config";
|
|
3140
|
+
default:
|
|
3141
|
+
return "not configured";
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
function printSurfaceTable(statuses) {
|
|
3145
|
+
for (const status of statuses) {
|
|
3146
|
+
const state = status.configured ? pc3.green("configured") : status.detected ? pc3.yellow("detected") : pc3.dim("not detected");
|
|
3147
|
+
const mode = status.mode === "automated" ? pc3.cyan("auto") : pc3.magenta("manual");
|
|
3148
|
+
console.log(`${pc3.bold(status.name.padEnd(10))} ${mode.padEnd(14)} ${state}`);
|
|
3149
|
+
console.log(` ${status.summary}`);
|
|
3150
|
+
if (status.path) {
|
|
3151
|
+
console.log(` path: ${status.path}`);
|
|
3152
|
+
}
|
|
3153
|
+
for (const detail of status.details) {
|
|
3154
|
+
console.log(` - ${detail}`);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function printMutationResults(results) {
|
|
3159
|
+
for (const result of summarizeMutationResults(results)) {
|
|
3160
|
+
const marker = result.state === "updated" ? pc3.green("updated") : pc3.yellow("unchanged");
|
|
3161
|
+
console.log(`${pc3.bold(result.name)} ${marker} ${result.message}`);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function printSkillInstallReport(report) {
|
|
3165
|
+
for (const write of report.writes) {
|
|
3166
|
+
const marker = write.changed ? pc3.green("updated") : pc3.yellow("unchanged");
|
|
3167
|
+
console.log(`${pc3.bold(write.label)} ${marker} ${write.path}`);
|
|
3168
|
+
}
|
|
3169
|
+
for (const pack of report.packs) {
|
|
3170
|
+
const changedCount = pack.files.filter((file) => file.changed).length;
|
|
3171
|
+
const marker = changedCount > 0 ? pc3.green("updated") : pc3.yellow("unchanged");
|
|
3172
|
+
console.log(
|
|
3173
|
+
`${pc3.bold(pack.name)} ${marker} ${changedCount}/${pack.files.length} files written`
|
|
3174
|
+
);
|
|
3175
|
+
for (const file of pack.files) {
|
|
3176
|
+
const state = file.changed ? "updated" : "unchanged";
|
|
3177
|
+
console.log(` - ${state} ${file.path}`);
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
function printWorkspace(workspace) {
|
|
3182
|
+
console.log(
|
|
3183
|
+
`${pc3.bold(workspace.name)}${workspace.isDefault ? ` ${pc3.green("(default)")}` : ""}`
|
|
3184
|
+
);
|
|
3185
|
+
console.log(` id: ${workspace.id}`);
|
|
3186
|
+
if (workspace.description) {
|
|
3187
|
+
console.log(` description: ${workspace.description}`);
|
|
3188
|
+
}
|
|
3189
|
+
if (workspace.createdAt) {
|
|
3190
|
+
console.log(` created: ${workspace.createdAt}`);
|
|
3191
|
+
}
|
|
3192
|
+
if (workspace.updatedAt) {
|
|
3193
|
+
console.log(` updated: ${workspace.updatedAt}`);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
function printWorkspaceList(workspaces) {
|
|
3197
|
+
if (workspaces.length === 0) {
|
|
3198
|
+
console.log(pc3.yellow("No workspaces found for this OrgX user."));
|
|
3199
|
+
return;
|
|
3200
|
+
}
|
|
3201
|
+
for (const workspace of workspaces) {
|
|
3202
|
+
printWorkspace(workspace);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
function printWorkspaceSetupResult(result) {
|
|
3206
|
+
console.log(pc3.bold("workspace bootstrap"));
|
|
3207
|
+
const statusLabel = result.status === "updated" ? pc3.green("updated") : result.status === "unchanged" ? pc3.yellow("unchanged") : pc3.dim(result.status);
|
|
3208
|
+
console.log(` ${statusLabel} ${result.message}`);
|
|
3209
|
+
if (result.workspace) {
|
|
3210
|
+
console.log(` workspace: ${result.workspace.name}`);
|
|
3211
|
+
console.log(` id: ${result.workspace.id}`);
|
|
3212
|
+
if (result.workspace.isDefault) {
|
|
3213
|
+
console.log(" default: yes");
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
function printFounderPresetResult(result) {
|
|
3218
|
+
printMutationResults(result.surfaceResults);
|
|
3219
|
+
console.log("");
|
|
3220
|
+
printSkillInstallReport(result.skillReport);
|
|
3221
|
+
if (result.workspaceSetup) {
|
|
3222
|
+
console.log("");
|
|
3223
|
+
printWorkspaceSetupResult(result.workspaceSetup);
|
|
3224
|
+
}
|
|
3225
|
+
if (result.demoInitiative) {
|
|
3226
|
+
console.log("");
|
|
3227
|
+
console.log(pc3.bold("demo initiative"));
|
|
3228
|
+
console.log(
|
|
3229
|
+
` ${result.demoInitiative.created ? pc3.green("created") : pc3.yellow("unchanged")} ${result.demoInitiative.initiative.title}`
|
|
3230
|
+
);
|
|
3231
|
+
console.log(` live: ${result.demoInitiative.liveUrl}`);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function normalizePromptResult(value) {
|
|
3235
|
+
return value;
|
|
3236
|
+
}
|
|
3237
|
+
async function selectPrompt(input) {
|
|
3238
|
+
const promptInput = input.initialValue ? { ...input, initialValue: input.initialValue } : { message: input.message, options: input.options };
|
|
3239
|
+
return normalizePromptResult(await clack.select(promptInput));
|
|
3240
|
+
}
|
|
3241
|
+
async function textPrompt(input) {
|
|
3242
|
+
const promptInput = input.initialValue ? { ...input, initialValue: input.initialValue } : {
|
|
3243
|
+
message: input.message,
|
|
3244
|
+
...input.placeholder ? { placeholder: input.placeholder } : {},
|
|
3245
|
+
...input.validate ? { validate: input.validate } : {}
|
|
3246
|
+
};
|
|
3247
|
+
return normalizePromptResult(await clack.text(promptInput));
|
|
3248
|
+
}
|
|
3249
|
+
async function verifyAndPersistAuth(apiKey, options) {
|
|
3250
|
+
const trimmedKey = apiKey.trim();
|
|
3251
|
+
if (!trimmedKey.toLowerCase().startsWith("oxk_")) {
|
|
3252
|
+
throw new Error("Expected a per-user OrgX API key that starts with oxk_.");
|
|
3253
|
+
}
|
|
3254
|
+
const baseUrl = normalizeOrgxBaseUrl(options.baseUrl);
|
|
3255
|
+
const verification = await verifyOrgxAuth({
|
|
3256
|
+
apiKey: trimmedKey,
|
|
3257
|
+
keyPrefix: trimmedKey.slice(0, 12),
|
|
3258
|
+
baseUrl,
|
|
3259
|
+
source: "wizard-store"
|
|
3260
|
+
});
|
|
3261
|
+
if (!verification.ok) {
|
|
3262
|
+
return { verification };
|
|
3263
|
+
}
|
|
3264
|
+
const stored = await writeWizardAuth({
|
|
3265
|
+
apiKey: trimmedKey,
|
|
3266
|
+
baseUrl,
|
|
3267
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3268
|
+
});
|
|
3269
|
+
const openclawResults = detectSurface("openclaw").detected ? await addSurface("openclaw") : [];
|
|
3270
|
+
return { openclawResults, stored, verification };
|
|
3271
|
+
}
|
|
3272
|
+
async function syncContinuityAfterAuth(seed = {}) {
|
|
3273
|
+
try {
|
|
3274
|
+
const workspace = await getCurrentWorkspace().catch(() => null);
|
|
3275
|
+
persistContinuityDefaults({
|
|
3276
|
+
...seed.executionMode !== void 0 ? { executionMode: seed.executionMode } : {},
|
|
3277
|
+
workspace,
|
|
3278
|
+
workspaceName: seed.workspaceName ?? workspace?.name ?? null
|
|
3279
|
+
});
|
|
3280
|
+
} catch {
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
function printOpenClawHealth(check) {
|
|
3284
|
+
console.log(pc3.bold("openclaw runtime"));
|
|
3285
|
+
if (check.skipped) {
|
|
3286
|
+
console.log(` ${pc3.yellow(check.error ?? "Gateway health check skipped.")}`);
|
|
3287
|
+
} else if (check.ok) {
|
|
3288
|
+
const via = check.method === "cli" ? "CLI" : "HTTP";
|
|
3289
|
+
console.log(` ${pc3.green(`healthy via ${via}`)}`);
|
|
3290
|
+
} else {
|
|
3291
|
+
const via = check.method === "none" ? "unavailable" : check.method.toUpperCase();
|
|
3292
|
+
console.log(` ${pc3.red(`unhealthy via ${via}`)} ${check.error ?? "unknown error"}`);
|
|
3293
|
+
}
|
|
3294
|
+
if (check.url) {
|
|
3295
|
+
console.log(` url: ${check.url}`);
|
|
3296
|
+
}
|
|
3297
|
+
for (const detail of check.details) {
|
|
3298
|
+
console.log(` - ${detail}`);
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
function printHostedMcpHealth(check) {
|
|
3302
|
+
console.log(pc3.bold("hosted mcp"));
|
|
3303
|
+
if (check.skipped) {
|
|
3304
|
+
console.log(` ${pc3.yellow(check.error ?? "Hosted MCP health check skipped.")}`);
|
|
3305
|
+
} else if (check.ok) {
|
|
3306
|
+
console.log(` ${pc3.green("reachable")}`);
|
|
3307
|
+
} else {
|
|
3308
|
+
console.log(` ${pc3.red("unreachable")} ${check.error ?? "unknown error"}`);
|
|
3309
|
+
}
|
|
3310
|
+
console.log(` url: ${check.url}`);
|
|
3311
|
+
for (const detail of check.details) {
|
|
3312
|
+
console.log(` - ${detail}`);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
function printHostedMcpToolCheck(check) {
|
|
3316
|
+
console.log(pc3.bold("hosted mcp tool call"));
|
|
3317
|
+
if (check.skipped) {
|
|
3318
|
+
console.log(` ${pc3.yellow(check.error ?? "Hosted MCP tool verification skipped.")}`);
|
|
3319
|
+
} else if (check.ok) {
|
|
3320
|
+
console.log(` ${pc3.green("authenticated tool call ok")}`);
|
|
3321
|
+
} else {
|
|
3322
|
+
console.log(` ${pc3.red("authenticated tool call failed")} ${check.error ?? "unknown error"}`);
|
|
3323
|
+
}
|
|
3324
|
+
console.log(` tool: ${check.toolName}`);
|
|
3325
|
+
console.log(` url: ${check.url}`);
|
|
3326
|
+
if (check.source !== "none") {
|
|
3327
|
+
console.log(` auth source: ${formatAuthSource(check.source)}`);
|
|
3328
|
+
}
|
|
3329
|
+
if (check.baseUrl) {
|
|
3330
|
+
console.log(` base url: ${check.baseUrl}`);
|
|
3331
|
+
}
|
|
3332
|
+
if (check.initializeStatus !== void 0) {
|
|
3333
|
+
console.log(` initialize status: ${check.initializeStatus}`);
|
|
3334
|
+
}
|
|
3335
|
+
if (check.callStatus !== void 0) {
|
|
3336
|
+
console.log(` tool call status: ${check.callStatus}`);
|
|
3337
|
+
}
|
|
3338
|
+
for (const detail of check.details) {
|
|
3339
|
+
console.log(` - ${detail}`);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
function printNpmRegistryHealth(check) {
|
|
3343
|
+
console.log(pc3.bold("npm registry"));
|
|
3344
|
+
if (!check.reachable) {
|
|
3345
|
+
console.log(` ${pc3.red("unreachable")} ${check.error ?? "unknown error"}`);
|
|
3346
|
+
} else if (check.published) {
|
|
3347
|
+
console.log(
|
|
3348
|
+
` ${pc3.green("reachable")} ${check.packageName}${check.version ? `@${check.version}` : ""}`
|
|
3349
|
+
);
|
|
3350
|
+
} else {
|
|
3351
|
+
console.log(` ${pc3.yellow("reachable")} ${check.error ?? `${check.packageName} is not published yet.`}`);
|
|
3352
|
+
}
|
|
3353
|
+
console.log(` url: ${check.url}`);
|
|
3354
|
+
for (const detail of check.details) {
|
|
3355
|
+
console.log(` - ${detail}`);
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
function parseTimeoutSeconds(value) {
|
|
3359
|
+
const parsed = Number(value);
|
|
3360
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3361
|
+
throw new Error("--timeout must be a positive number of seconds.");
|
|
3362
|
+
}
|
|
3363
|
+
return parsed;
|
|
3364
|
+
}
|
|
3365
|
+
function describeBrowserPairingFailure(error) {
|
|
3366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3367
|
+
return `${message} Use \`wizard auth login --api-key <oxk_...>\` or \`wizard auth set-key <oxk_...>\` to fall back to a manual key.`;
|
|
3368
|
+
}
|
|
3369
|
+
function printAuthStatus(status) {
|
|
3370
|
+
console.log(pc3.bold("orgx auth"));
|
|
3371
|
+
if (!status.configured) {
|
|
3372
|
+
console.log(` ${pc3.yellow(status.error ?? "No OrgX API key configured.")}`);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
const state = status.ok ? pc3.green("verified") : status.skipped ? pc3.yellow("skipped") : pc3.red("invalid");
|
|
3376
|
+
console.log(
|
|
3377
|
+
` ${state} ${status.keyPrefix ?? "unknown"} via ${formatAuthSource(status.source)}`
|
|
3378
|
+
);
|
|
3379
|
+
if (status.baseUrl) {
|
|
3380
|
+
console.log(` base url: ${status.baseUrl}`);
|
|
3381
|
+
}
|
|
3382
|
+
if (status.path) {
|
|
3383
|
+
console.log(` path: ${status.path}`);
|
|
3384
|
+
}
|
|
3385
|
+
if (status.verifiedAt) {
|
|
3386
|
+
console.log(` verified at: ${status.verifiedAt}`);
|
|
3387
|
+
}
|
|
3388
|
+
if (!status.ok && status.error) {
|
|
3389
|
+
console.log(` error: ${status.error}`);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
function printRemoteSetupStatus(status) {
|
|
3393
|
+
if (status.skipped) {
|
|
3394
|
+
console.log(pc3.yellow(status.error ?? "Remote setup check skipped."));
|
|
3395
|
+
console.log(pc3.dim(describeSetupStatusScope()));
|
|
3396
|
+
return;
|
|
3397
|
+
}
|
|
3398
|
+
if (!status.ok) {
|
|
3399
|
+
console.log(pc3.red(`Remote setup check failed: ${status.error ?? "unknown error"}`));
|
|
3400
|
+
if (status.status) {
|
|
3401
|
+
console.log(`status: ${status.status}`);
|
|
3402
|
+
}
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
console.log(pc3.green(`Remote setup check passed via ${status.url}`));
|
|
3406
|
+
if (status.data) {
|
|
3407
|
+
console.log(JSON.stringify(status.data, null, 2));
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
function printWorkspaceConnectivity(check) {
|
|
3411
|
+
console.log(pc3.bold("workspace connectivity"));
|
|
3412
|
+
if (!check.configured) {
|
|
3413
|
+
console.log(` ${pc3.yellow(check.error ?? "Workspace connectivity check skipped.")}`);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
if (!check.ok) {
|
|
3417
|
+
console.log(` ${pc3.red("unreachable")} ${check.error ?? "unknown error"}`);
|
|
3418
|
+
} else if (check.workspace) {
|
|
3419
|
+
console.log(` ${pc3.green("reachable")} ${check.workspace.name}`);
|
|
3420
|
+
console.log(` workspace id: ${check.workspace.id}`);
|
|
3421
|
+
if (check.workspace.isDefault) {
|
|
3422
|
+
console.log(" default: yes");
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (check.baseUrl) {
|
|
3426
|
+
console.log(` base url: ${check.baseUrl}`);
|
|
3427
|
+
}
|
|
3428
|
+
for (const detail of check.details) {
|
|
3429
|
+
console.log(` - ${detail}`);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
function printDoctorAssessment(report) {
|
|
3433
|
+
console.log(pc3.bold("doctor summary"));
|
|
3434
|
+
if (report.issues.length === 0) {
|
|
3435
|
+
console.log(` ${pc3.green("ready")} No blocking issues detected.`);
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
const headline = report.ok ? pc3.yellow("warnings only") : pc3.red("blocking issues detected");
|
|
3439
|
+
console.log(` ${headline}`);
|
|
3440
|
+
for (const issue of report.issues) {
|
|
3441
|
+
const label = issue.level === "error" ? pc3.red("error") : pc3.yellow("warning");
|
|
3442
|
+
console.log(` ${label} ${issue.title}`);
|
|
3443
|
+
console.log(` fix: ${issue.suggestion}`);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
async function main() {
|
|
3447
|
+
const program = new Command();
|
|
3448
|
+
program.name("orgx-wizard").description("One-line CLI onboarding for OrgX surfaces.").showHelpAfterError();
|
|
3449
|
+
program.hook("preAction", () => {
|
|
3450
|
+
console.log(renderBanner());
|
|
3451
|
+
});
|
|
3452
|
+
program.command("setup").description("Patch all detected automated OrgX surfaces.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
|
|
3453
|
+
if (options.preset) {
|
|
3454
|
+
if (options.preset !== "founder") {
|
|
3455
|
+
throw new Error(`Unknown setup preset '${options.preset}'. Supported presets: founder.`);
|
|
3456
|
+
}
|
|
3457
|
+
const spinner2 = createOrgxSpinner("Running founder setup preset");
|
|
3458
|
+
spinner2.start();
|
|
3459
|
+
const presetResult = await runFounderPreset(
|
|
3460
|
+
{
|
|
3461
|
+
cancel: clack.cancel,
|
|
3462
|
+
isCancel: clack.isCancel,
|
|
3463
|
+
select: selectPrompt,
|
|
3464
|
+
text: textPrompt
|
|
3465
|
+
},
|
|
3466
|
+
{
|
|
3467
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
3468
|
+
}
|
|
3469
|
+
);
|
|
3470
|
+
if ("status" in presetResult) {
|
|
3471
|
+
spinner2.stop();
|
|
3472
|
+
if (presetResult.status === "cancelled") {
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
printWorkspaceSetupResult(presetResult);
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
spinner2.succeed("Founder setup preset complete");
|
|
3479
|
+
printFounderPresetResult(presetResult);
|
|
3480
|
+
console.log("");
|
|
3481
|
+
const doctor2 = await runDoctor();
|
|
3482
|
+
printSurfaceTable(doctor2.surfaces);
|
|
3483
|
+
console.log("");
|
|
3484
|
+
printAuthStatus(doctor2.auth);
|
|
3485
|
+
console.log("");
|
|
3486
|
+
printHostedMcpHealth(doctor2.hostedMcp);
|
|
3487
|
+
console.log("");
|
|
3488
|
+
printHostedMcpToolCheck(doctor2.hostedMcpTool);
|
|
3489
|
+
console.log("");
|
|
3490
|
+
printNpmRegistryHealth(doctor2.npmRegistry);
|
|
3491
|
+
console.log("");
|
|
3492
|
+
printWorkspaceConnectivity(doctor2.workspace);
|
|
3493
|
+
console.log("");
|
|
3494
|
+
printOpenClawHealth(doctor2.openclaw);
|
|
3495
|
+
console.log("");
|
|
3496
|
+
printRemoteSetupStatus(doctor2.remote);
|
|
3497
|
+
console.log("");
|
|
3498
|
+
const assessment2 = assessDoctorReport(doctor2);
|
|
3499
|
+
printDoctorAssessment(assessment2);
|
|
3500
|
+
if (!assessment2.ok) {
|
|
3501
|
+
process.exitCode = 1;
|
|
3502
|
+
}
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
const spinner = createOrgxSpinner("Configuring detected OrgX surfaces");
|
|
3506
|
+
spinner.start();
|
|
3507
|
+
const results = await setupDetectedSurfaces();
|
|
3508
|
+
spinner.succeed("Detected surfaces configured");
|
|
3509
|
+
printMutationResults(results);
|
|
3510
|
+
const resolvedAuth = await resolveOrgxAuth();
|
|
3511
|
+
if (resolvedAuth) {
|
|
3512
|
+
console.log("");
|
|
3513
|
+
const workspaceSetup = await runWorkspaceSetup(
|
|
3514
|
+
{
|
|
3515
|
+
createWorkspace,
|
|
3516
|
+
getCurrentWorkspace,
|
|
3517
|
+
listWorkspaces,
|
|
3518
|
+
setDefaultWorkspace
|
|
3519
|
+
},
|
|
3520
|
+
{
|
|
3521
|
+
cancel: clack.cancel,
|
|
3522
|
+
isCancel: clack.isCancel,
|
|
3523
|
+
select: selectPrompt,
|
|
3524
|
+
text: textPrompt
|
|
3525
|
+
},
|
|
3526
|
+
{
|
|
3527
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
3528
|
+
}
|
|
3529
|
+
);
|
|
3530
|
+
if (workspaceSetup.status !== "cancelled") {
|
|
3531
|
+
printWorkspaceSetupResult(workspaceSetup);
|
|
3532
|
+
} else {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
console.log("");
|
|
3537
|
+
const doctor = await runDoctor();
|
|
3538
|
+
printSurfaceTable(doctor.surfaces);
|
|
3539
|
+
console.log("");
|
|
3540
|
+
printAuthStatus(doctor.auth);
|
|
3541
|
+
console.log("");
|
|
3542
|
+
printHostedMcpHealth(doctor.hostedMcp);
|
|
3543
|
+
console.log("");
|
|
3544
|
+
printHostedMcpToolCheck(doctor.hostedMcpTool);
|
|
3545
|
+
console.log("");
|
|
3546
|
+
printNpmRegistryHealth(doctor.npmRegistry);
|
|
3547
|
+
console.log("");
|
|
3548
|
+
printWorkspaceConnectivity(doctor.workspace);
|
|
3549
|
+
console.log("");
|
|
3550
|
+
printOpenClawHealth(doctor.openclaw);
|
|
3551
|
+
console.log("");
|
|
3552
|
+
printRemoteSetupStatus(doctor.remote);
|
|
3553
|
+
console.log("");
|
|
3554
|
+
const assessment = assessDoctorReport(doctor);
|
|
3555
|
+
printDoctorAssessment(assessment);
|
|
3556
|
+
if (!assessment.ok) {
|
|
3557
|
+
process.exitCode = 1;
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
const auth = program.command("auth").description("Manage OrgX API key auth for the wizard.");
|
|
3561
|
+
auth.command("status").description("Show the resolved OrgX API key source and verify it against OrgX.").action(async () => {
|
|
3562
|
+
const spinner = createOrgxSpinner("Checking OrgX auth");
|
|
3563
|
+
spinner.start();
|
|
3564
|
+
const status = await checkOrgxAuth();
|
|
3565
|
+
spinner.stop();
|
|
3566
|
+
printAuthStatus(status);
|
|
3567
|
+
});
|
|
3568
|
+
auth.command("login").description("Start browser pairing for OrgX auth, with direct API key fallback for CI and blocked browsers.").option("--api-key <key>", "Bypass browser pairing and verify this OrgX API key directly.").option("--base-url <url>", "OrgX base URL").option("--device-name <name>", "Device name shown during browser approval.").option("--no-open", "Do not automatically open the browser connect URL.").option("--timeout <seconds>", "How long to wait for browser pairing before giving up.", parseTimeoutSeconds, 600).action(async (options) => {
|
|
3569
|
+
if (options.apiKey) {
|
|
3570
|
+
const spinner2 = createOrgxSpinner("Verifying OrgX API key");
|
|
3571
|
+
spinner2.start();
|
|
3572
|
+
const result = await verifyAndPersistAuth(options.apiKey, options);
|
|
3573
|
+
if (!("stored" in result)) {
|
|
3574
|
+
spinner2.fail("OrgX API key verification failed");
|
|
3575
|
+
printAuthStatus(result.verification);
|
|
3576
|
+
return;
|
|
3577
|
+
}
|
|
3578
|
+
spinner2.succeed("OrgX API key verified and saved");
|
|
3579
|
+
await syncContinuityAfterAuth();
|
|
3580
|
+
printAuthStatus({
|
|
3581
|
+
...result.verification,
|
|
3582
|
+
source: "wizard-store",
|
|
3583
|
+
path: ORGX_WIZARD_AUTH_PATH,
|
|
3584
|
+
verifiedAt: result.stored.verifiedAt
|
|
3585
|
+
});
|
|
3586
|
+
if (result.openclawResults.length > 0) {
|
|
3587
|
+
console.log("");
|
|
3588
|
+
printMutationResults(result.openclawResults);
|
|
3589
|
+
}
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
const installationId = getOrCreateWizardInstallationId();
|
|
3593
|
+
const spinner = createOrgxSpinner("Starting OrgX browser pairing");
|
|
3594
|
+
spinner.start();
|
|
3595
|
+
try {
|
|
3596
|
+
const pairing = await startBrowserPairing({
|
|
3597
|
+
installationId,
|
|
3598
|
+
baseUrl: options.baseUrl,
|
|
3599
|
+
deviceName: options.deviceName ?? hostname(),
|
|
3600
|
+
platform: process.platform
|
|
3601
|
+
});
|
|
3602
|
+
spinner.succeed("OrgX browser pairing started");
|
|
3603
|
+
console.log(`Open to continue: ${pairing.connectUrl}`);
|
|
3604
|
+
if (options.open !== false) {
|
|
3605
|
+
const openResult = openBrowser(pairing.connectUrl);
|
|
3606
|
+
if (!openResult.ok && openResult.error) {
|
|
3607
|
+
console.log(pc3.yellow(`Browser open failed: ${openResult.error}`));
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
spinner.start();
|
|
3611
|
+
spinner.text = pc3.cyan("Waiting for OrgX browser approval");
|
|
3612
|
+
const ready = await waitForBrowserPairing({
|
|
3613
|
+
baseUrl: options.baseUrl,
|
|
3614
|
+
pairingId: pairing.pairingId,
|
|
3615
|
+
pollIntervalMs: pairing.pollIntervalMs,
|
|
3616
|
+
pollToken: pairing.pollToken,
|
|
3617
|
+
timeoutMs: options.timeout * 1e3
|
|
3618
|
+
});
|
|
3619
|
+
spinner.text = pc3.cyan("Verifying OrgX API key");
|
|
3620
|
+
const result = await verifyAndPersistAuth(ready.key, options);
|
|
3621
|
+
if (!("stored" in result)) {
|
|
3622
|
+
spinner.fail("OrgX browser pairing delivered an invalid API key");
|
|
3623
|
+
printAuthStatus(result.verification);
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
await acknowledgeBrowserPairing({
|
|
3627
|
+
baseUrl: options.baseUrl,
|
|
3628
|
+
pairingId: pairing.pairingId,
|
|
3629
|
+
pollToken: pairing.pollToken
|
|
3630
|
+
}).catch((error) => {
|
|
3631
|
+
console.log(pc3.yellow(`Pairing acknowledge failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
3632
|
+
});
|
|
3633
|
+
spinner.succeed("OrgX browser pairing completed");
|
|
3634
|
+
await syncContinuityAfterAuth({
|
|
3635
|
+
executionMode: ready.executionMode,
|
|
3636
|
+
workspaceName: ready.workspaceName
|
|
3637
|
+
});
|
|
3638
|
+
printAuthStatus({
|
|
3639
|
+
...result.verification,
|
|
3640
|
+
source: "wizard-store",
|
|
3641
|
+
path: ORGX_WIZARD_AUTH_PATH,
|
|
3642
|
+
verifiedAt: result.stored.verifiedAt
|
|
3643
|
+
});
|
|
3644
|
+
if (ready.workspaceName) {
|
|
3645
|
+
console.log(`workspace: ${ready.workspaceName}`);
|
|
3646
|
+
}
|
|
3647
|
+
if (result.openclawResults.length > 0) {
|
|
3648
|
+
console.log("");
|
|
3649
|
+
printMutationResults(result.openclawResults);
|
|
3650
|
+
}
|
|
3651
|
+
} catch (error) {
|
|
3652
|
+
spinner.fail("OrgX browser pairing failed");
|
|
3653
|
+
console.log(pc3.red(describeBrowserPairingFailure(error)));
|
|
3654
|
+
}
|
|
3655
|
+
});
|
|
3656
|
+
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) => {
|
|
3657
|
+
const spinner = createOrgxSpinner("Verifying OrgX API key");
|
|
3658
|
+
spinner.start();
|
|
3659
|
+
const result = await verifyAndPersistAuth(apiKey, options);
|
|
3660
|
+
if (!("stored" in result)) {
|
|
3661
|
+
spinner.fail("OrgX API key verification failed");
|
|
3662
|
+
printAuthStatus(result.verification);
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
spinner.succeed("OrgX API key verified and saved");
|
|
3666
|
+
await syncContinuityAfterAuth();
|
|
3667
|
+
printAuthStatus({
|
|
3668
|
+
...result.verification,
|
|
3669
|
+
source: "wizard-store",
|
|
3670
|
+
path: ORGX_WIZARD_AUTH_PATH,
|
|
3671
|
+
verifiedAt: result.stored.verifiedAt
|
|
3672
|
+
});
|
|
3673
|
+
if (result.openclawResults.length > 0) {
|
|
3674
|
+
console.log("");
|
|
3675
|
+
printMutationResults(result.openclawResults);
|
|
3676
|
+
}
|
|
3677
|
+
});
|
|
3678
|
+
auth.command("clear").description("Remove the wizard-local saved OrgX API key.").action(async () => {
|
|
3679
|
+
const removed = await clearWizardAuth();
|
|
3680
|
+
if (removed) {
|
|
3681
|
+
console.log(pc3.green("Removed the wizard-local OrgX API key."));
|
|
3682
|
+
} else {
|
|
3683
|
+
console.log(pc3.yellow("No wizard-local OrgX API key was stored."));
|
|
3684
|
+
}
|
|
3685
|
+
const resolved = await resolveOrgxAuth();
|
|
3686
|
+
if (resolved) {
|
|
3687
|
+
console.log(
|
|
3688
|
+
`Effective OrgX auth still resolves from ${formatAuthSource(resolved.source)} (${resolved.keyPrefix}).`
|
|
3689
|
+
);
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
const surface = program.command("surface").description("Manage supported OrgX surfaces.");
|
|
3693
|
+
surface.command("list").description("Show supported surfaces and their current status.").action(() => {
|
|
3694
|
+
printSurfaceTable(listSurfaceStatuses());
|
|
3695
|
+
});
|
|
3696
|
+
surface.command("add").description("Patch one surface or all automated surfaces.").argument("<names...>", "surface names or 'all'").action(async (names) => {
|
|
3697
|
+
const results = await addSurface(names);
|
|
3698
|
+
printMutationResults(results);
|
|
3699
|
+
});
|
|
3700
|
+
surface.command("remove").description("Remove OrgX-managed config from one surface or all automated surfaces.").argument("<names...>", "surface names or 'all'").action((names) => {
|
|
3701
|
+
const results = removeSurface(names);
|
|
3702
|
+
printMutationResults(results);
|
|
3703
|
+
});
|
|
3704
|
+
const mcp = program.command("mcp").description("Manage OrgX MCP entries for Claude, Cursor, Codex, VS Code, Windsurf, and Zed.");
|
|
3705
|
+
mcp.command("add").description("Add OrgX MCP entries to one client or all supported MCP clients.").argument("[surfaces...]", "claude, cursor, codex, vscode, windsurf, zed, or all", ["all"]).action(async (names) => {
|
|
3706
|
+
const results = await addMcpSurface(names);
|
|
3707
|
+
printMutationResults(results);
|
|
3708
|
+
});
|
|
3709
|
+
mcp.command("remove").description("Remove OrgX MCP entries from one client or all supported MCP clients.").argument("[surfaces...]", "claude, cursor, codex, vscode, windsurf, zed, or all", ["all"]).action((names) => {
|
|
3710
|
+
const results = removeMcpSurface(names);
|
|
3711
|
+
printMutationResults(results);
|
|
3712
|
+
});
|
|
3713
|
+
const workspace = program.command("workspace").description("Inspect and create OrgX workspaces.");
|
|
3714
|
+
workspace.command("current").description("Show the current OrgX workspace.").action(async () => {
|
|
3715
|
+
const spinner = createOrgxSpinner("Loading current OrgX workspace");
|
|
3716
|
+
spinner.start();
|
|
3717
|
+
const current = await getCurrentWorkspace();
|
|
3718
|
+
spinner.stop();
|
|
3719
|
+
if (!current) {
|
|
3720
|
+
console.log(pc3.yellow("No OrgX workspaces found for the current user."));
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
printWorkspace(current);
|
|
3724
|
+
});
|
|
3725
|
+
workspace.command("list").description("List OrgX workspaces accessible with the current API key.").action(async () => {
|
|
3726
|
+
const spinner = createOrgxSpinner("Loading OrgX workspaces");
|
|
3727
|
+
spinner.start();
|
|
3728
|
+
const workspaces = await listWorkspaces();
|
|
3729
|
+
spinner.stop();
|
|
3730
|
+
printWorkspaceList(workspaces);
|
|
3731
|
+
});
|
|
3732
|
+
workspace.command("create").description("Create a new OrgX workspace.").argument("<name>", "workspace name").option("--description <text>", "workspace description").action(async (name, options) => {
|
|
3733
|
+
const spinner = createOrgxSpinner("Creating OrgX workspace");
|
|
3734
|
+
spinner.start();
|
|
3735
|
+
const created = await createWorkspace(
|
|
3736
|
+
{
|
|
3737
|
+
name,
|
|
3738
|
+
...options.description ? { description: options.description } : {}
|
|
3739
|
+
}
|
|
3740
|
+
);
|
|
3741
|
+
spinner.succeed("OrgX workspace created");
|
|
3742
|
+
printWorkspace(created);
|
|
3743
|
+
if (!created.isDefault) {
|
|
3744
|
+
console.log("");
|
|
3745
|
+
console.log(pc3.dim(`Run \`wizard workspace set-default ${created.id}\` to promote it.`));
|
|
3746
|
+
}
|
|
3747
|
+
});
|
|
3748
|
+
workspace.command("set-default").description("Set the default OrgX workspace.").argument("<id>", "workspace id").action(async (id) => {
|
|
3749
|
+
const spinner = createOrgxSpinner("Updating default OrgX workspace");
|
|
3750
|
+
spinner.start();
|
|
3751
|
+
const result = await setDefaultWorkspace({ id });
|
|
3752
|
+
spinner.succeed(
|
|
3753
|
+
result.changed ? "Default OrgX workspace updated" : "OrgX workspace already set as default"
|
|
3754
|
+
);
|
|
3755
|
+
printWorkspace(result.workspace);
|
|
3756
|
+
});
|
|
3757
|
+
program.command("doctor").description("Verify local OrgX surface config and optional remote setup status.").action(async () => {
|
|
3758
|
+
const spinner = createOrgxSpinner("Running OrgX doctor");
|
|
3759
|
+
spinner.start();
|
|
3760
|
+
const report = await runDoctor();
|
|
3761
|
+
spinner.stop();
|
|
3762
|
+
printSurfaceTable(report.surfaces);
|
|
3763
|
+
console.log("");
|
|
3764
|
+
printAuthStatus(report.auth);
|
|
3765
|
+
console.log("");
|
|
3766
|
+
printHostedMcpHealth(report.hostedMcp);
|
|
3767
|
+
console.log("");
|
|
3768
|
+
printHostedMcpToolCheck(report.hostedMcpTool);
|
|
3769
|
+
console.log("");
|
|
3770
|
+
printNpmRegistryHealth(report.npmRegistry);
|
|
3771
|
+
console.log("");
|
|
3772
|
+
printWorkspaceConnectivity(report.workspace);
|
|
3773
|
+
console.log("");
|
|
3774
|
+
printOpenClawHealth(report.openclaw);
|
|
3775
|
+
console.log("");
|
|
3776
|
+
printRemoteSetupStatus(report.remote);
|
|
3777
|
+
console.log("");
|
|
3778
|
+
const assessment = assessDoctorReport(report);
|
|
3779
|
+
printDoctorAssessment(assessment);
|
|
3780
|
+
if (!assessment.ok) {
|
|
3781
|
+
process.exitCode = 1;
|
|
3782
|
+
}
|
|
3783
|
+
});
|
|
3784
|
+
const skills = program.command("skills").description("Install OrgX rules and Claude skill packs.");
|
|
3785
|
+
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) => {
|
|
3786
|
+
const selectedPacks = resolveSkillPackNames(packs);
|
|
3787
|
+
const spinner = createOrgxSpinner("Installing OrgX rules and skills");
|
|
3788
|
+
spinner.start();
|
|
3789
|
+
const report = await installOrgxSkills({ skillNames: selectedPacks });
|
|
3790
|
+
spinner.succeed("OrgX rules and skills installed");
|
|
3791
|
+
printSkillInstallReport(report);
|
|
3792
|
+
});
|
|
3793
|
+
await program.parseAsync(process.argv);
|
|
3794
|
+
}
|
|
3795
|
+
main().catch((error) => {
|
|
3796
|
+
console.error(pc3.red(error instanceof Error ? error.message : String(error)));
|
|
3797
|
+
process.exitCode = 1;
|
|
3798
|
+
});
|
|
3799
|
+
//# sourceMappingURL=cli.js.map
|