@workflow-code/cli 0.2.4-20261010003.codex-cli-web → 0.2.4-20261010004.codex-cli-web

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ async function main(argv = process.argv.slice(2)) {
24
24
  const prepared = prepareCliInvocation(argv);
25
25
  argv = prepared.argv;
26
26
  if (argv[0] === "web") {
27
- const webCli = await import("./web-7QLJGSAQ.js");
27
+ const webCli = await import("./web-KUC6BL6E.js");
28
28
  return webCli.main(argv.slice(1));
29
29
  }
30
30
  if (argv[0] === "workspace") {
@@ -1,11 +1,12 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { readFile, writeFile, readdir, rm, stat, mkdir, link } from 'node:fs/promises';
2
+ import { readFile, writeFile, readdir, rm, stat, mkdir, link, mkdtemp } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { register } from 'tsx/esm/api';
6
6
  import { resolve } from 'import-meta-resolve';
7
7
  import { pathToFileURL, fileURLToPath } from 'node:url';
8
8
  let ts;
9
+ let assistantScopeDirectory;
9
10
 
10
11
 
11
12
  const write = process.stdout.write.bind(process.stdout);
@@ -93,8 +94,21 @@ try {
93
94
  }
94
95
  send('result', { projects, errors });
95
96
  } else if (request.project?.folderPath) {
96
- const projectKey = createHash('sha256').update(request.project.folderPath).digest('hex').slice(0, 24);
97
+ const projectKey = createHash('sha256').update(request.project.assistant ? `assistant:${request.project.assistant.workdir}` : request.project.folderPath).digest('hex').slice(0, 24);
97
98
  const dataDir = path.join(request.storageDir, projectKey);
99
+ if (request.project.assistant) {
100
+ const assistant = request.project.assistant;
101
+ const primary = assistant.roots.find(root => root.folderPath === assistant.workdir);
102
+ if (!primary) { throw new Error('Assistant working directory is not an opened workspace folder.'); }
103
+ await mkdir(dataDir, { recursive: true });
104
+ assistantScopeDirectory = await mkdtemp(path.join(dataDir, 'scope-'));
105
+ const manifestPath = path.join(assistantScopeDirectory, 'workspace.json');
106
+ await writeFile(manifestPath, JSON.stringify({ schemaVersion: 2, primaryTarget: { kind: 'folder', folderId: primary.uri }, warnings: [],
107
+ roots: assistant.roots.map(root => ({ target: { kind: 'folder', folderId: root.uri }, name: root.name, rootPath: root.folderPath, role: root === primary ? 'primary' : 'linked', depth: root === primary ? 0 : 1 })) }));
108
+ process.env.WORKFLOW_DEVELOPER_ASSISTANT_HOST = 'vscode';
109
+ process.env.WORKFLOW_DEVELOPER_ASSISTANT_WORKDIR = assistant.workdir;
110
+ process.env.WORKFLOW_DEVELOPER_ASSISTANT_WORKSPACE_MANIFEST = manifestPath;
111
+ }
98
112
  const storage = { host: 'local', local: { kvStoreDir: path.join(dataDir, 'kv'), persistentValueStoreDir: path.join(dataDir, 'values') } };
99
113
  const filesDir = path.join(dataDir, 'files');
100
114
  const createdFiles = [];
@@ -205,4 +219,6 @@ try {
205
219
  } catch (error) {
206
220
  write(`__WORKFLOW_WEB__${JSON.stringify({ kind: 'error', error: error.message, status: error.metadata?.status })}\n`);
207
221
  process.exitCode = 1;
222
+ } finally {
223
+ if (assistantScopeDirectory) { await rm(assistantScopeDirectory, { recursive: true, force: true }); }
208
224
  }
@@ -4102,6 +4102,7 @@ ${state.user?.avatarUrl ?? ""}`;
4102
4102
  constructor(emit) {
4103
4103
  this.emit = emit;
4104
4104
  if (this.token) {
4105
+ sessionStorage.setItem("workflow-web-session", this.token);
4105
4106
  history.replaceState(null, "", `${location.pathname}${location.search}`);
4106
4107
  }
4107
4108
  queueMicrotask(() => {
@@ -4139,7 +4140,7 @@ ${state.user?.avatarUrl ?? ""}`;
4139
4140
  status = "connecting";
4140
4141
  attempts = 0;
4141
4142
  closed = false;
4142
- token = new URLSearchParams(location.hash.slice(1)).get("session");
4143
+ token = new URLSearchParams(location.hash.slice(1)).get("session") ?? sessionStorage.getItem("workflow-web-session");
4143
4144
  update(status) {
4144
4145
  this.status = status;
4145
4146
  this.emit({ type: "connection", status });
@@ -4186,9 +4187,8 @@ ${state.user?.avatarUrl ?? ""}`;
4186
4187
  this.update("expired");
4187
4188
  return;
4188
4189
  }
4189
- this.token = null;
4190
4190
  }
4191
- const session = await fetch("/api/session", { signal: AbortSignal.timeout(1e4) });
4191
+ const session = await fetch("/api/session", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
4192
4192
  if (session.status === 401) {
4193
4193
  this.update("expired");
4194
4194
  return;
@@ -4196,13 +4196,13 @@ ${state.user?.avatarUrl ?? ""}`;
4196
4196
  if (!session.ok) {
4197
4197
  throw new Error("Cannot connect to the project.");
4198
4198
  }
4199
- const source = this.source = new EventSource("/api/events");
4199
+ const source = this.source = new EventSource(this.fileUrl("/api/events"));
4200
4200
  source.onopen = () => {
4201
4201
  this.attempts = 0;
4202
4202
  this.update("connected");
4203
4203
  };
4204
4204
  source.onmessage = (event) => {
4205
- this.emit(JSON.parse(event.data));
4205
+ this.emit(this.publicFiles(JSON.parse(event.data)));
4206
4206
  };
4207
4207
  source.onerror = () => {
4208
4208
  if (this.source === source && !this.closed) {
@@ -4229,7 +4229,7 @@ ${state.user?.avatarUrl ?? ""}`;
4229
4229
  }
4230
4230
  let response;
4231
4231
  try {
4232
- response = await fetch("/api/message", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message) });
4232
+ response = await fetch("/api/message", { method: "POST", headers: { ...this.headers(), "Content-Type": "application/json" }, body: JSON.stringify(message) });
4233
4233
  } catch {
4234
4234
  this.interrupted();
4235
4235
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
@@ -4249,7 +4249,25 @@ ${state.user?.avatarUrl ?? ""}`;
4249
4249
  if (!response.ok) {
4250
4250
  throw new Error(reply.message || "Request failed.");
4251
4251
  }
4252
- return reply.messages ?? [];
4252
+ return (reply.messages ?? []).map((message2) => this.publicFiles(message2));
4253
+ }
4254
+ headers() {
4255
+ return this.token ? { Authorization: `Bearer ${this.token}` } : {};
4256
+ }
4257
+ fileUrl(path) {
4258
+ return this.token ? `${path}?session=${encodeURIComponent(this.token)}` : path;
4259
+ }
4260
+ publicFiles(value) {
4261
+ if (Array.isArray(value)) {
4262
+ return value.map((entry) => this.publicFiles(entry));
4263
+ }
4264
+ if (!value || typeof value !== "object") {
4265
+ return value;
4266
+ }
4267
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
4268
+ key,
4269
+ key === "previewUrl" && typeof entry === "string" && entry.startsWith("/api/file/") ? this.fileUrl(entry) : this.publicFiles(entry)
4270
+ ]));
4253
4271
  }
4254
4272
  };
4255
4273
 
@@ -4280,6 +4298,17 @@ ${state.user?.avatarUrl ?? ""}`;
4280
4298
  handleReply(entry);
4281
4299
  }
4282
4300
  };
4301
+ addEventListener("message", (event) => {
4302
+ if (window.parent === window || event.source !== window.parent || event.data?.type !== "workflow-host") {
4303
+ return;
4304
+ }
4305
+ if (event.data.theme === "dark" || event.data.theme === "light") {
4306
+ document.documentElement.dataset.theme = event.data.theme;
4307
+ }
4308
+ if (event.data.command === "new" || event.data.command === "history") {
4309
+ emit({ type: "assistant-command", command: event.data.command });
4310
+ }
4311
+ });
4283
4312
  const handleReply = (reply) => {
4284
4313
  if (reply.type === "download") {
4285
4314
  const url = URL.createObjectURL(new Blob([reply.content], { type: reply.mimeType ?? "application/json" }));
@@ -4364,7 +4393,7 @@ ${state.user?.avatarUrl ?? ""}`;
4364
4393
  return;
4365
4394
  }
4366
4395
  if (message.type === "open-file") {
4367
- window.open(`/api/file/${encodeURIComponent(message.id)}`, "_blank", "noopener");
4396
+ window.open(connection.fileUrl(`/api/file/${encodeURIComponent(message.id)}`), "_blank", "noopener");
4368
4397
  return;
4369
4398
  }
4370
4399
  if (message.type === "login") {
@@ -17,7 +17,7 @@ import "./chunk-NSPRIPOP.js";
17
17
 
18
18
  // src/web.ts
19
19
  import { spawn as spawn3 } from "child_process";
20
- import path3 from "path";
20
+ import path4 from "path";
21
21
 
22
22
  // src/web/server.ts
23
23
  import { createServer } from "http";
@@ -965,7 +965,7 @@ async function loginWebAccount(serverUrl, email, password) {
965
965
  // src/web/server.ts
966
966
  async function startWebServer(options) {
967
967
  const projectDir = await realpath(path2.resolve(options.projectDir));
968
- const key = createHash("sha256").update(projectDir).digest("hex").slice(0, 24);
968
+ const key = createHash("sha256").update(options.host?.assistant ? `assistant:${options.host.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
969
969
  const storageDir = path2.resolve(options.dataDir ?? path2.join(os.homedir(), ".workflow-code", "web"));
970
970
  const historyPath = path2.join(storageDir, key, "conversations.json");
971
971
  const clients = /* @__PURE__ */ new Set();
@@ -975,6 +975,10 @@ async function startWebServer(options) {
975
975
  let authIdentity = "";
976
976
  let login;
977
977
  let passwordLogin = false;
978
+ let hostConnection = options.host?.connection;
979
+ let hostManaged = !!hostConnection;
980
+ let authEpoch = 0;
981
+ const sensitiveTokens = /* @__PURE__ */ new Set();
978
982
  const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection);
979
983
  let inspected;
980
984
  try {
@@ -996,6 +1000,14 @@ async function startWebServer(options) {
996
1000
  throw new Error("workflow-code web currently supports Conversation and Workflow projects.");
997
1001
  }
998
1002
  const project = { ...descriptor, uri: pathToFileURL(projectDir).href };
1003
+ if (options.host?.assistant) {
1004
+ project.assistant = options.host.assistant;
1005
+ project.uri = `studio-assistant:${encodeURIComponent(project.assistant.workdir)}`;
1006
+ project.requiresLogin = true;
1007
+ project.fields = project.fields.map((field) => field.name === "permissionPolicy" ? { ...field, defaultValue: "workspace" } : field);
1008
+ const supported = /* @__PURE__ */ new Set(["read", "write", "edit", "bash", "grep", "find", "ls", "skill", "workflow-memory_global_memory", "workflow-ui_present_card"]);
1009
+ project.tools = project.tools?.filter((tool) => supported.has(tool.name));
1010
+ }
999
1011
  let saved = {};
1000
1012
  try {
1001
1013
  saved = JSON.parse(await readFile(historyPath, "utf8"));
@@ -1024,7 +1036,7 @@ async function startWebServer(options) {
1024
1036
  }
1025
1037
  return value;
1026
1038
  };
1027
- const state = () => publicValue(redactWorkflowData({ type: "state", project, sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {}, account, authenticationRevision: authRevision }, [connection?.accessToken ?? ""]));
1039
+ const state = () => publicValue(redactWorkflowData({ type: "state", project, sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {}, account, authenticationRevision: authRevision }, [...sensitiveTokens]));
1028
1040
  const broadcast = () => {
1029
1041
  if (broadcastTimer) {
1030
1042
  return;
@@ -1058,9 +1070,16 @@ async function startWebServer(options) {
1058
1070
  if (passwordLogin || login) {
1059
1071
  return;
1060
1072
  }
1073
+ const epoch = ++authEpoch;
1061
1074
  const config = await readCliAuthConfig();
1062
- const serverUrl = process.env.WORKFLOW_SERVER_URL ?? config.serverUrl;
1063
- const token = process.env.WORKFLOW_SERVER_ADMIN_KEY ?? config.apiKey;
1075
+ if (epoch !== authEpoch) {
1076
+ return;
1077
+ }
1078
+ const serverUrl = hostManaged ? hostConnection?.serverUrl : process.env.WORKFLOW_SERVER_URL ?? config.serverUrl;
1079
+ const token = hostManaged ? hostConnection?.accessToken : process.env.WORKFLOW_SERVER_ADMIN_KEY ?? config.apiKey;
1080
+ if (token) {
1081
+ sensitiveTokens.add(token);
1082
+ }
1064
1083
  const identity = signedOut ? "" : `${serverUrl}
1065
1084
  ${token}`;
1066
1085
  if (!force && identity === authIdentity && account.status !== "checking") {
@@ -1081,11 +1100,17 @@ ${token}`;
1081
1100
  try {
1082
1101
  const response = await fetch(`${connection.serverUrl}/api/auth/me`, { headers: { Authorization: `Bearer ${connection.accessToken}` }, signal: AbortSignal.timeout(1e4), redirect: "error" });
1083
1102
  const result = await response.json();
1103
+ if (epoch !== authEpoch) {
1104
+ return;
1105
+ }
1084
1106
  if (!response.ok || result.errCode !== 0 || !result.data?.profile) {
1085
1107
  throw new Error("Workflow login is invalid or expired.");
1086
1108
  }
1087
1109
  account = { ...account, status: "authenticated", user: result.data.profile, expiresAt: result.data.principal?.expiresAt ?? config.expiresAt };
1088
1110
  } catch (error) {
1111
+ if (epoch !== authEpoch) {
1112
+ return;
1113
+ }
1089
1114
  connection = void 0;
1090
1115
  if (!changed) {
1091
1116
  authRevision++;
@@ -1341,7 +1366,7 @@ ${token}`;
1341
1366
  break;
1342
1367
  }
1343
1368
  case "login": {
1344
- if (process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
1369
+ if (!options.host && process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
1345
1370
  throw new Error("This session is supplied by the host. Sign in through the host.");
1346
1371
  }
1347
1372
  if (login || passwordLogin) {
@@ -1361,6 +1386,8 @@ ${token}`;
1361
1386
  throw new Error("Invalid login credentials.");
1362
1387
  }
1363
1388
  await loginWebAccount(url.toString().replace(/\/+$/, ""), message.email, message.password);
1389
+ hostManaged = false;
1390
+ hostConnection = void 0;
1364
1391
  } catch (error) {
1365
1392
  account = { ...account, status: "error", error: String(error.message) };
1366
1393
  broadcast();
@@ -1391,7 +1418,7 @@ ${token}`;
1391
1418
  case "logout":
1392
1419
  signedOut = true;
1393
1420
  login?.kill();
1394
- if (process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
1421
+ if (!hostManaged && process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
1395
1422
  await clearCliAuthConfig();
1396
1423
  }
1397
1424
  authIdentity = "signed-out";
@@ -1407,7 +1434,7 @@ ${token}`;
1407
1434
  void (async () => {
1408
1435
  response.setHeader("X-Content-Type-Options", "nosniff");
1409
1436
  response.setHeader("Referrer-Policy", "no-referrer");
1410
- if (request.headers.host !== new URL(origin).host || request.headers.origin && request.headers.origin !== origin || request.headers["sec-fetch-site"] === "cross-site") {
1437
+ if (request.headers.host !== new URL(origin).host || request.headers.origin && request.headers.origin !== origin) {
1411
1438
  json(response, 403, { message: "Invalid request origin." });
1412
1439
  return;
1413
1440
  }
@@ -1422,8 +1449,14 @@ ${token}`;
1422
1449
  return;
1423
1450
  }
1424
1451
  if (url.pathname.startsWith("/api/")) {
1452
+ if (request.headers["sec-fetch-site"] === "cross-site") {
1453
+ json(response, 403, { message: "Invalid request origin." });
1454
+ return;
1455
+ }
1425
1456
  const value = String(request.headers.cookie ?? "").split(";").map((value2) => value2.trim()).find((value2) => value2.startsWith(`${cookie}=`))?.slice(cookie.length + 1) ?? "";
1426
- if (!authMatches(value)) {
1457
+ const bearer = String(request.headers.authorization ?? "").replace(/^Bearer /, "");
1458
+ const readSession = request.method === "GET" && (url.pathname === "/api/events" || url.pathname.startsWith("/api/file/")) ? url.searchParams.get("session") ?? "" : "";
1459
+ if (!authMatches(value) && !authMatches(bearer) && !authMatches(readSession)) {
1427
1460
  json(response, 401, { message: "Open the URL printed by workflow-code web." });
1428
1461
  return;
1429
1462
  }
@@ -1455,7 +1488,7 @@ ${token}`;
1455
1488
  mutations = task;
1456
1489
  }
1457
1490
  await task;
1458
- json(response, 200, { messages: redactWorkflowData(messages, [connection?.accessToken ?? ""]) });
1491
+ json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens]) });
1459
1492
  return;
1460
1493
  }
1461
1494
  if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
@@ -1479,8 +1512,9 @@ ${token}`;
1479
1512
  return;
1480
1513
  }
1481
1514
  if (url.pathname === "/") {
1482
- response.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: http:; font-src 'self'; connect-src 'self'; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'self' vscode-webview:; form-action 'self'" });
1483
- response.end(`<!doctype html><html lang="zh-CN" data-theme="${options.theme ?? "auto"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="workflow-project" content="${key}"><title>Workflow</title><link rel="stylesheet" href="/assets/codicons/codicon.css"><link rel="stylesheet" href="/assets/workflow-ui.css"><link rel="stylesheet" href="/assets/theme.css"></head><body data-surface="editor"><div id="app"></div><script src="/assets/workflow-ui.js" defer></script></body></html>`);
1515
+ const ancestors = options.host ? "'self' vscode-webview: vscode-file: https://*.vscode-cdn.net https://*.vscode-webview.net http://127.0.0.1:* http://localhost:*" : "'self' vscode-webview:";
1516
+ response.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: http:; font-src 'self'; connect-src 'self'; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors ${ancestors}; form-action 'self'` });
1517
+ response.end(`<!doctype html><html lang="zh-CN" data-theme="${options.theme ?? "auto"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="workflow-project" content="${key}"><title>Workflow</title><link rel="stylesheet" href="/assets/codicons/codicon.css"><link rel="stylesheet" href="/assets/workflow-ui.css"><link rel="stylesheet" href="/assets/theme.css"></head><body data-surface="${options.host?.surface ?? "editor"}"><div id="app"></div><script src="/assets/workflow-ui.js" defer></script></body></html>`);
1484
1518
  return;
1485
1519
  }
1486
1520
  const assets = { "/assets/workflow-ui.js": "application/javascript", "/assets/workflow-ui.css": "text/css", "/assets/theme.css": "text/css", "/assets/codicons/codicon.css": "text/css", "/assets/codicons/codicon.ttf": "font/ttf" };
@@ -1493,7 +1527,7 @@ ${token}`;
1493
1527
  response.end(content);
1494
1528
  })().catch((error) => {
1495
1529
  if (!response.headersSent) {
1496
- json(response, 400, { message: String(redactWorkflowData(error.message, [connection?.accessToken ?? ""])) });
1530
+ json(response, 400, { message: String(redactWorkflowData(error.message, [...sensitiveTokens])) });
1497
1531
  } else {
1498
1532
  response.end();
1499
1533
  }
@@ -1520,6 +1554,12 @@ ${token}`;
1520
1554
  url: `${origin}/#session=${capability}`,
1521
1555
  project,
1522
1556
  controller,
1557
+ setHostConnection: async (value) => {
1558
+ hostConnection = value;
1559
+ hostManaged = true;
1560
+ signedOut = !value;
1561
+ await refreshAuth(true);
1562
+ },
1523
1563
  close: () => closing ??= (async () => {
1524
1564
  clearInterval(authPoll);
1525
1565
  if (broadcastTimer) {
@@ -1539,9 +1579,43 @@ ${token}`;
1539
1579
  };
1540
1580
  }
1541
1581
 
1582
+ // src/web.ts
1583
+ import { createInterface } from "readline";
1584
+
1585
+ // src/web/host-config.ts
1586
+ import path3 from "path";
1587
+ function parseHostConnection(value) {
1588
+ if (value === null || value === void 0) {
1589
+ return;
1590
+ }
1591
+ const connection = value;
1592
+ if (typeof connection.serverUrl !== "string" || typeof connection.accessToken !== "string" || !connection.accessToken || connection.accessToken.length > 16384) {
1593
+ throw new Error("Invalid host login.");
1594
+ }
1595
+ const url = new URL(connection.serverUrl);
1596
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
1597
+ throw new Error("Invalid host server URL.");
1598
+ }
1599
+ return { serverUrl: url.toString().replace(/\/+$/, ""), accessToken: connection.accessToken };
1600
+ }
1601
+ function parseHostConfiguration(value) {
1602
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1603
+ throw new Error("Invalid web host configuration.");
1604
+ }
1605
+ const configuration = value;
1606
+ if (configuration.type !== "configure" || !["editor", "assistant"].includes(configuration.surface)) {
1607
+ throw new Error("Invalid web host configuration.");
1608
+ }
1609
+ const assistant = configuration.assistant;
1610
+ if (assistant && (typeof assistant.workdir !== "string" || !path3.isAbsolute(assistant.workdir) || !Array.isArray(assistant.roots) || !assistant.roots.length || assistant.roots.length > 100 || assistant.roots.some((root) => typeof root.uri !== "string" || typeof root.name !== "string" || typeof root.folderPath !== "string" || !path3.isAbsolute(root.folderPath)) || !assistant.roots.some((root) => root.folderPath === assistant.workdir))) {
1611
+ throw new Error("Invalid assistant workspace.");
1612
+ }
1613
+ return { surface: configuration.surface, assistant, connection: parseHostConnection(configuration.connection) };
1614
+ }
1615
+
1542
1616
  // src/web.ts
1543
1617
  function parseWebArgs(argv) {
1544
- const options = { projectDir: process.cwd(), port: 0, theme: "auto", open: true, json: false, help: false };
1618
+ const options = { projectDir: process.cwd(), port: 0, theme: "auto", dataDir: void 0, open: true, json: false, help: false, inspect: false, hostStdio: false };
1545
1619
  let projectSet = false;
1546
1620
  for (let index = 0; index < argv.length; index++) {
1547
1621
  const arg = argv[index];
@@ -1549,6 +1623,10 @@ function parseWebArgs(argv) {
1549
1623
  options.open = false;
1550
1624
  } else if (arg === "--json") {
1551
1625
  options.json = true;
1626
+ } else if (arg === "--inspect") {
1627
+ options.inspect = true;
1628
+ } else if (arg === "--host-stdio") {
1629
+ options.hostStdio = true;
1552
1630
  } else if (["--help", "-h"].includes(arg)) {
1553
1631
  options.help = true;
1554
1632
  } else if (["--port", "--data-dir", "--theme"].includes(arg)) {
@@ -1567,12 +1645,12 @@ function parseWebArgs(argv) {
1567
1645
  }
1568
1646
  options.theme = value;
1569
1647
  } else {
1570
- options.dataDir = path3.resolve(value);
1648
+ options.dataDir = path4.resolve(value);
1571
1649
  }
1572
1650
  } else if (arg.startsWith("-") || projectSet) {
1573
1651
  throw new Error(`Unknown web argument: ${arg}`);
1574
1652
  } else {
1575
- options.projectDir = path3.resolve(arg);
1653
+ options.projectDir = path4.resolve(arg);
1576
1654
  projectSet = true;
1577
1655
  }
1578
1656
  }
@@ -1584,10 +1662,39 @@ async function main(argv) {
1584
1662
  console.log(i18n.language.startsWith("zh") ? "workflow-code web [\u9879\u76EE\u76EE\u5F55]\n --port <0-65535> \u76D1\u542C\u7AEF\u53E3\uFF0C0 \u81EA\u52A8\u5206\u914D\n --no-open \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\n --theme <auto|light|dark>\n --data-dir <\u76EE\u5F55> \u5BF9\u8BDD\u548C\u6587\u4EF6\u5B58\u50A8\u4F4D\u7F6E\n --json \u8F93\u51FA\u7ED3\u6784\u5316\u542F\u52A8\u4FE1\u606F" : "workflow-code web [project-directory]\n --port <0-65535> Listen port; 0 selects an available port\n --no-open Do not open a browser\n --theme <auto|light|dark>\n --data-dir <path> Conversation and file storage directory\n --json Print structured startup information");
1585
1663
  return 0;
1586
1664
  }
1587
- const app = await startWebServer(options);
1665
+ if (options.inspect) {
1666
+ const runtime = createWebRuntime(options.projectDir, options.dataDir ?? "", () => void 0);
1667
+ try {
1668
+ console.log(JSON.stringify(await runtime.inspect([options.projectDir])));
1669
+ } finally {
1670
+ runtime.dispose();
1671
+ }
1672
+ return 0;
1673
+ }
1674
+ const input = options.hostStdio ? createInterface({ input: process.stdin, crlfDelay: Infinity }) : void 0;
1675
+ const lines = input?.[Symbol.asyncIterator]();
1676
+ let host;
1677
+ try {
1678
+ if (lines) {
1679
+ const initial = await lines.next();
1680
+ if (initial.done || initial.value.length > 256e3) {
1681
+ throw new Error("Web host configuration is missing or too large.");
1682
+ }
1683
+ host = parseHostConfiguration(JSON.parse(initial.value));
1684
+ }
1685
+ } catch (error) {
1686
+ input?.close();
1687
+ throw error;
1688
+ }
1689
+ const app = await startWebServer({ ...options, host }).catch((error) => {
1690
+ input?.close();
1691
+ throw error;
1692
+ });
1588
1693
  const close = () => {
1589
1694
  process.off("SIGINT", close);
1590
1695
  process.off("SIGTERM", close);
1696
+ input?.close();
1697
+ process.stdin.pause();
1591
1698
  void app.close().catch((error) => {
1592
1699
  console.error(error);
1593
1700
  process.exitCode = 1;
@@ -1595,7 +1702,31 @@ async function main(argv) {
1595
1702
  };
1596
1703
  process.once("SIGINT", close);
1597
1704
  process.once("SIGTERM", close);
1598
- console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, url: app.url, project: { id: app.project.id, name: app.project.name, projectType: app.project.projectType }, runtime: app.project.runtime }) : `Workflow Web: ${app.url}`);
1705
+ console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, hostProtocolVersion: 1, url: app.url, project: { id: app.project.id, name: app.project.name, projectType: app.project.projectType }, runtime: app.project.runtime }) : `Workflow Web: ${app.url}`);
1706
+ if (lines) {
1707
+ void (async () => {
1708
+ try {
1709
+ for await (const line of { [Symbol.asyncIterator]: () => lines }) {
1710
+ if (line.length > 256e3) {
1711
+ throw new Error("Web host message is too large.");
1712
+ }
1713
+ const message = JSON.parse(line);
1714
+ if (message.type === "auth") {
1715
+ await app.setHostConnection(parseHostConnection(message.connection));
1716
+ } else if (message.type === "close") {
1717
+ break;
1718
+ } else {
1719
+ throw new Error("Unknown web host message.");
1720
+ }
1721
+ }
1722
+ } catch {
1723
+ console.error("Workflow web host connection failed.");
1724
+ process.exitCode = 1;
1725
+ } finally {
1726
+ close();
1727
+ }
1728
+ })();
1729
+ }
1599
1730
  if (options.open) {
1600
1731
  const invocation = process.platform === "darwin" ? { command: "open", args: [app.url] } : process.platform === "win32" ? { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", app.url] } : { command: "xdg-open", args: [app.url] };
1601
1732
  const browser = spawn3(invocation.command, invocation.args, { detached: true, shell: false, stdio: "ignore" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-code/cli",
3
- "version": "0.2.4-20261010003.codex-cli-web",
3
+ "version": "0.2.4-20261010004.codex-cli-web",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "workflow-code": "./dist/index.js"
@@ -37,8 +37,8 @@
37
37
  "workflow-code": "0.2.4-20260929001",
38
38
  "@workflow-code/auth-config": "0.1.0",
39
39
  "@workflow-code/execution-targets": "0.1.0",
40
- "@workflow-code/i18n": "0.1.0",
41
40
  "@workflow-code/openai-responses": "0.1.0",
41
+ "@workflow-code/i18n": "0.1.0",
42
42
  "@workflow-code/project-dependencies": "0.1.0",
43
43
  "@workflow-code/project-info": "0.1.0",
44
44
  "@workflow-code/project-upload": "0.1.0",