openspecui 2.0.2 → 2.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.
Files changed (33) hide show
  1. package/dist/cli.mjs +272 -23
  2. package/dist/index.mjs +1 -1
  3. package/dist/{open-DDagk2eo.mjs → open-DfwCb8mL.mjs} +1 -1
  4. package/dist/{src-Cx7GJTGT.mjs → src-B5XQ-ERE.mjs} +63 -46
  5. package/package.json +6 -4
  6. package/web/assets/{BufferResource-Cipj3hQ5.js → BufferResource-WPSrP4iZ.js} +1 -1
  7. package/web/assets/{CanvasRenderer-C151fKcy.js → CanvasRenderer-yL0hHLLA.js} +1 -1
  8. package/web/assets/{Filter-BpxymkGv.js → Filter-MPAI5-Oj.js} +1 -1
  9. package/web/assets/{RenderTargetSystem-C0zk81Mh.js → RenderTargetSystem-D4qtRaUW.js} +1 -1
  10. package/web/assets/{WebGLRenderer-De3GrxdN.js → WebGLRenderer-BHfrXEBc.js} +1 -1
  11. package/web/assets/{WebGPURenderer-BkpejvST.js → WebGPURenderer-DE5Q1Ilu.js} +1 -1
  12. package/web/assets/{browserAll-B8KqF-Xa.js → browserAll-qg0ACMg1.js} +1 -1
  13. package/web/assets/{ghostty-web-BO5ww0eG.js → ghostty-web-D1cPLYOq.js} +1 -1
  14. package/web/assets/{index-Nl7iD8Yi.js → index-B2AIdjev.js} +1 -1
  15. package/web/assets/{index-CDX9fioY.js → index-BEpMDdcu.js} +1 -1
  16. package/web/assets/{index-B-vvVL83.js → index-BLmH2_gh.js} +197 -193
  17. package/web/assets/{index-Bp2dpDFJ.js → index-BM_5Tg9c.js} +1 -1
  18. package/web/assets/{index-Cj_nv8ue.js → index-BQWvXpvf.js} +1 -1
  19. package/web/assets/{index-a8osabOh.js → index-BTEmB46j.js} +1 -1
  20. package/web/assets/{index-CxDip8xJ.js → index-C8igR5_C.js} +1 -1
  21. package/web/assets/{index-DL08rl2O.js → index-CQRaC2Gn.js} +1 -1
  22. package/web/assets/{index-2DXouwnE.js → index-CSledjj-.js} +1 -1
  23. package/web/assets/index-D1IhiEyy.css +1 -0
  24. package/web/assets/{index-CwA_uPvf.js → index-D7oe-Hg-.js} +1 -1
  25. package/web/assets/{index-Cv-LON1K.js → index-DBI0U-_m.js} +1 -1
  26. package/web/assets/{index-CFAzjIVm.js → index-DMpt0Kcu.js} +1 -1
  27. package/web/assets/{index-iROJdLzk.js → index-DYz8XhtF.js} +1 -1
  28. package/web/assets/{index-DtTSWBHJ.js → index-DfNI0fNY.js} +1 -1
  29. package/web/assets/{index-Bxm-n0do.js → index-DlK0frtF.js} +1 -1
  30. package/web/assets/{index-DzK6gT7C.js → index-Gknjn7E7.js} +1 -1
  31. package/web/assets/{webworkerAll-DPcI1cDK.js → webworkerAll-iHUuedbL.js} +1 -1
  32. package/web/index.html +2 -2
  33. package/web/assets/index-aWxXp1oO.css +0 -1
package/dist/cli.mjs CHANGED
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as DEFAULT_CONFIG, f as OpenSpecAdapter, i as SchemaDetailSchema, l as CliExecutor, m as __toESM, o as SchemaResolutionSchema, p as __commonJS, r as require_dist, s as TemplatesSchema, t as startServer, u as ConfigManager } from "./src-Cx7GJTGT.mjs";
2
+ import { a as SchemaInfoSchema, c as toOpsxDisplayPath, d as CliExecutor, f as ConfigManager, g as __toESM, h as __commonJS, i as SchemaDetailSchema, l as buildHostedLaunchUrl, m as OpenSpecAdapter, o as SchemaResolutionSchema, p as DEFAULT_CONFIG, r as require_dist, s as TemplatesSchema, t as startServer, u as resolveHostedAppBaseUrl } from "./src-B5XQ-ERE.mjs";
3
3
  import { createRequire } from "node:module";
4
+ import { createServer } from "node:net";
4
5
  import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
5
6
  import { readFile } from "node:fs/promises";
6
7
  import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
7
8
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
8
9
  import { readFileSync as readFileSync$1, readdirSync as readdirSync$1, statSync as statSync$1, writeFile as writeFile$1 } from "fs";
9
10
  import { format, inspect } from "util";
10
- import { fileURLToPath } from "url";
11
+ import { fileURLToPath } from "node:url";
12
+ import { fileURLToPath as fileURLToPath$1 } from "url";
11
13
  import { execFile, spawn } from "node:child_process";
12
14
  import { promisify as promisify$1 } from "node:util";
13
- import { fileURLToPath as fileURLToPath$1 } from "node:url";
14
15
  import { notStrictEqual, strictEqual } from "assert";
15
16
 
16
17
  //#region ../../node_modules/.pnpm/cliui@9.0.1/node_modules/cliui/build/lib/index.js
@@ -1606,7 +1607,7 @@ var require_get_caller_file = /* @__PURE__ */ __commonJS({ "../../node_modules/.
1606
1607
  //#endregion
1607
1608
  //#region ../../node_modules/.pnpm/yargs@18.0.0/node_modules/yargs/lib/platform-shims/esm.mjs
1608
1609
  var import_get_caller_file = /* @__PURE__ */ __toESM(require_get_caller_file(), 1);
1609
- const __dirname$2 = fileURLToPath(import.meta.url);
1610
+ const __dirname$2 = fileURLToPath$1(import.meta.url);
1610
1611
  const mainFilename = __dirname$2.substring(0, __dirname$2.lastIndexOf("node_modules"));
1611
1612
  const require = createRequire(import.meta.url);
1612
1613
  var esm_default = {
@@ -1647,7 +1648,7 @@ var esm_default = {
1647
1648
  require,
1648
1649
  getCallerFile: () => {
1649
1650
  const callerFile = (0, import_get_caller_file.default)(3);
1650
- return callerFile.match(/^file:\/\//) ? fileURLToPath(callerFile) : callerFile;
1651
+ return callerFile.match(/^file:\/\//) ? fileURLToPath$1(callerFile) : callerFile;
1651
1652
  },
1652
1653
  stringWidth,
1653
1654
  y18n: y18n_default({
@@ -4507,11 +4508,11 @@ var yargs_default = Yargs;
4507
4508
  //#endregion
4508
4509
  //#region package.json
4509
4510
  var import_dist = require_dist();
4510
- var version = "2.0.2";
4511
+ var version = "2.1.0";
4511
4512
 
4512
4513
  //#endregion
4513
4514
  //#region src/export.ts
4514
- const __dirname$1 = dirname$1(fileURLToPath$1(import.meta.url));
4515
+ const __dirname$1 = dirname$1(fileURLToPath(import.meta.url));
4515
4516
  const execFileAsync = promisify$1(execFile);
4516
4517
  function parseCliJson(raw, schema, label) {
4517
4518
  const trimmed = raw.trim();
@@ -4943,7 +4944,7 @@ function findLocalWebPackage() {
4943
4944
  /**
4944
4945
  * Run a command and wait for it to complete
4945
4946
  */
4946
- function runCommand(cmd, args, cwd) {
4947
+ function runCommand$1(cmd, args, cwd) {
4947
4948
  return new Promise((resolvePromise, reject) => {
4948
4949
  const child = spawn(cmd, args, {
4949
4950
  stdio: "inherit",
@@ -5110,7 +5111,7 @@ async function exportHtml(options) {
5110
5111
  if (isLocalPackageRange(webPackageRange)) {
5111
5112
  if (!localWebPkg) throw new Error(`Detected local/dev @openspecui/web range "${webPackageRange}" but local web package was not found`);
5112
5113
  console.log("\n[Local dev mode] Running SSG from local web package...");
5113
- await runCommand("pnpm", [
5114
+ await runCommand$1("pnpm", [
5114
5115
  "tsx",
5115
5116
  join$1(localWebPkg, "src", "ssg", "cli.ts"),
5116
5117
  "--data",
@@ -5125,7 +5126,7 @@ async function exportHtml(options) {
5125
5126
  const pm = detectPackageManager();
5126
5127
  const execCmd = getExecCommand(pm, `@openspecui/web@${webPackageRange || version}`);
5127
5128
  try {
5128
- await runCommand(execCmd.cmd, [
5129
+ await runCommand$1(execCmd.cmd, [
5129
5130
  ...execCmd.args,
5130
5131
  "--data",
5131
5132
  dataJsonPath,
@@ -5152,7 +5153,7 @@ async function exportHtml(options) {
5152
5153
  if (previewHost) viteArgs.push("--host", previewHost);
5153
5154
  viteArgs.push("--open");
5154
5155
  const { cmd, args } = getRunCommand(detectPackageManager(), "vite");
5155
- await runCommand(cmd, [...args, ...viteArgs], outputDir);
5156
+ await runCommand$1(cmd, [...args, ...viteArgs], outputDir);
5156
5157
  }
5157
5158
  }
5158
5159
  /**
@@ -5166,12 +5167,210 @@ async function exportStaticSite(options) {
5166
5167
  else await exportHtml(options);
5167
5168
  }
5168
5169
 
5170
+ //#endregion
5171
+ //#region src/hosted-app.ts
5172
+ function buildHostedAppLaunchUrl(options) {
5173
+ return buildHostedLaunchUrl({
5174
+ baseUrl: options.baseUrl,
5175
+ apiBaseUrl: options.apiBaseUrl
5176
+ });
5177
+ }
5178
+ function resolveEffectiveHostedAppBaseUrl(options) {
5179
+ return resolveHostedAppBaseUrl(options);
5180
+ }
5181
+
5182
+ //#endregion
5183
+ //#region src/local-hosted-app-dev.ts
5184
+ const LOCAL_HOSTED_APP_DEV_DEFAULT_PORT = 13005;
5185
+ function resolveLocalHostedAppWorkspace(cliDir) {
5186
+ const repoRoot = resolve$1(cliDir, "..", "..", "..");
5187
+ const rootPackageJson = join$1(repoRoot, "package.json");
5188
+ const appPackageJson = join$1(repoRoot, "packages", "app", "package.json");
5189
+ if (!existsSync(rootPackageJson) || !existsSync(appPackageJson)) return null;
5190
+ return {
5191
+ repoRoot,
5192
+ appDir: join$1(repoRoot, "packages", "app"),
5193
+ webDistDir: join$1(repoRoot, "packages", "web", "dist")
5194
+ };
5195
+ }
5196
+ function shouldUseLocalHostedAppDevMode(options) {
5197
+ return options.appValue === "" && options.workspace !== null;
5198
+ }
5199
+ function createHostedAppWebBuildCommand(workspace) {
5200
+ return {
5201
+ command: resolvePnpmCommand(),
5202
+ args: [
5203
+ "--filter",
5204
+ "@openspecui/web",
5205
+ "build"
5206
+ ],
5207
+ cwd: workspace.repoRoot,
5208
+ env: { ...process.env }
5209
+ };
5210
+ }
5211
+ function createLocalHostedAppDevCommand(options) {
5212
+ return {
5213
+ command: resolvePnpmCommand(),
5214
+ args: [
5215
+ "--filter",
5216
+ "@openspecui/app",
5217
+ "exec",
5218
+ "vite",
5219
+ "--host",
5220
+ "127.0.0.1",
5221
+ "--port",
5222
+ String(options.port),
5223
+ "--strictPort"
5224
+ ],
5225
+ cwd: options.workspace.repoRoot,
5226
+ env: {
5227
+ ...process.env,
5228
+ OPENSPECUI_APP_DEV_MODE: "1",
5229
+ OPENSPECUI_APP_DEV_WEB_DIST: options.workspace.webDistDir,
5230
+ OPENSPECUI_APP_DEV_VERSION: options.resolvedVersion
5231
+ }
5232
+ };
5233
+ }
5234
+ async function startLocalHostedAppDev(options) {
5235
+ await ensureLocalHostedAppWebDist(options.workspace);
5236
+ const port = await findAvailablePort(options.preferredPort ?? LOCAL_HOSTED_APP_DEV_DEFAULT_PORT);
5237
+ const command$1 = createLocalHostedAppDevCommand({
5238
+ workspace: options.workspace,
5239
+ port,
5240
+ resolvedVersion: options.resolvedVersion
5241
+ });
5242
+ const child = spawn(command$1.command, command$1.args, {
5243
+ cwd: command$1.cwd,
5244
+ env: command$1.env,
5245
+ stdio: "inherit",
5246
+ detached: process.platform !== "win32"
5247
+ });
5248
+ const baseUrl = `http://127.0.0.1:${port}`;
5249
+ try {
5250
+ await waitForHostedAppDevReady({
5251
+ baseUrl,
5252
+ child,
5253
+ timeoutMs: options.readinessTimeoutMs ?? 15e3
5254
+ });
5255
+ } catch (error) {
5256
+ await stopChildProcess(child);
5257
+ throw error;
5258
+ }
5259
+ return {
5260
+ baseUrl,
5261
+ close: () => stopChildProcess(child)
5262
+ };
5263
+ }
5264
+ async function ensureLocalHostedAppWebDist(workspace) {
5265
+ const command$1 = createHostedAppWebBuildCommand(workspace);
5266
+ console.log("🛠️ Building packages/web for local hosted app dev...");
5267
+ await runCommand(command$1);
5268
+ }
5269
+ function resolvePnpmCommand() {
5270
+ return process.platform === "win32" ? "pnpm.cmd" : "pnpm";
5271
+ }
5272
+ async function runCommand(command$1) {
5273
+ await new Promise((resolvePromise, rejectPromise) => {
5274
+ const child = spawn(command$1.command, command$1.args, {
5275
+ cwd: command$1.cwd,
5276
+ env: command$1.env,
5277
+ stdio: "inherit"
5278
+ });
5279
+ child.once("error", (error) => {
5280
+ rejectPromise(error);
5281
+ });
5282
+ child.once("exit", (code, signal) => {
5283
+ if (code === 0) {
5284
+ resolvePromise();
5285
+ return;
5286
+ }
5287
+ rejectPromise(/* @__PURE__ */ new Error(`Command failed: ${command$1.command} ${command$1.args.join(" ")} (${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`})`));
5288
+ });
5289
+ });
5290
+ }
5291
+ async function waitForHostedAppDevReady(options) {
5292
+ let exitMessage = null;
5293
+ let startupError = null;
5294
+ options.child.once("error", (error) => {
5295
+ startupError = error;
5296
+ });
5297
+ options.child.once("exit", (code, signal) => {
5298
+ exitMessage = signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`;
5299
+ });
5300
+ const deadline = Date.now() + options.timeoutMs;
5301
+ while (Date.now() < deadline) {
5302
+ if (startupError) throw startupError;
5303
+ if (exitMessage) throw new Error(`Local hosted app dev server exited before becoming ready (${exitMessage})`);
5304
+ try {
5305
+ if ((await fetch(`${options.baseUrl}/version.json`, {
5306
+ headers: { accept: "application/json" },
5307
+ cache: "no-store"
5308
+ })).ok) return;
5309
+ } catch {}
5310
+ await delay(250);
5311
+ }
5312
+ throw new Error(`Timed out waiting for local hosted app dev server at ${options.baseUrl}`);
5313
+ }
5314
+ async function stopChildProcess(child) {
5315
+ if (child.exitCode !== null || child.signalCode !== null) return;
5316
+ killChildProcess(child, "SIGTERM");
5317
+ if (await waitForChildExit(child, 5e3)) return;
5318
+ killChildProcess(child, "SIGKILL");
5319
+ await waitForChildExit(child, 1e3);
5320
+ }
5321
+ function killChildProcess(child, signal) {
5322
+ if (process.platform !== "win32" && child.pid) try {
5323
+ process.kill(-child.pid, signal);
5324
+ return;
5325
+ } catch {}
5326
+ child.kill(signal);
5327
+ }
5328
+ function waitForChildExit(child, timeoutMs) {
5329
+ return new Promise((resolvePromise) => {
5330
+ const timer = setTimeout(() => {
5331
+ cleanup();
5332
+ resolvePromise(false);
5333
+ }, timeoutMs);
5334
+ const onExit = () => {
5335
+ cleanup();
5336
+ resolvePromise(true);
5337
+ };
5338
+ const cleanup = () => {
5339
+ clearTimeout(timer);
5340
+ child.off("exit", onExit);
5341
+ };
5342
+ child.once("exit", onExit);
5343
+ });
5344
+ }
5345
+ function delay(ms) {
5346
+ return new Promise((resolve$2) => {
5347
+ setTimeout(resolve$2, ms);
5348
+ });
5349
+ }
5350
+ async function isPortAvailable(port) {
5351
+ return await new Promise((resolve$2) => {
5352
+ const server = createServer();
5353
+ server.once("error", () => {
5354
+ resolve$2(false);
5355
+ });
5356
+ server.once("listening", () => {
5357
+ server.close(() => resolve$2(true));
5358
+ });
5359
+ server.listen(port, "127.0.0.1");
5360
+ });
5361
+ }
5362
+ async function findAvailablePort(startPort, maxAttempts = 10) {
5363
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
5364
+ const port = startPort + attempt;
5365
+ if (await isPortAvailable(port)) return port;
5366
+ }
5367
+ throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);
5368
+ }
5369
+
5169
5370
  //#endregion
5170
5371
  //#region src/cli.ts
5171
- const __dirname = dirname$1(fileURLToPath$1(import.meta.url));
5172
- /**
5173
- * Read version from package.json
5174
- */
5372
+ const __dirname = dirname$1(fileURLToPath(import.meta.url));
5373
+ const DEFAULT_HOSTED_CORS_ORIGINS = ["http://localhost:5173", "http://localhost:3000"];
5175
5374
  function getVersion() {
5176
5375
  try {
5177
5376
  const pkgPath = join$1(__dirname, "..", "package.json");
@@ -5180,6 +5379,11 @@ function getVersion() {
5180
5379
  return "0.0.0";
5181
5380
  }
5182
5381
  }
5382
+ function buildHostedCorsOrigins(baseUrl) {
5383
+ const origins = new Set(DEFAULT_HOSTED_CORS_ORIGINS);
5384
+ origins.add(new URL(baseUrl).origin);
5385
+ return [...origins];
5386
+ }
5183
5387
  async function main() {
5184
5388
  const originalCwd = process.env.INIT_CWD || process.cwd();
5185
5389
  await yargs_default(hideBin(process.argv)).scriptName("openspecui").command(["$0 [project-dir]", "start [project-dir]"], "Start the OpenSpec UI server", (yargs) => {
@@ -5199,9 +5403,13 @@ async function main() {
5199
5403
  describe: "Automatically open the browser",
5200
5404
  type: "boolean",
5201
5405
  default: true
5406
+ }).option("app", {
5407
+ describe: "Open the hosted app at the official or custom base URL. Supports --app and --app=<baseUrl>.",
5408
+ type: "string"
5202
5409
  });
5203
5410
  }, async (argv) => {
5204
5411
  const projectDir = resolve$1(originalCwd, argv["project-dir"] || argv.dir || ".");
5412
+ const useHostedApp = argv.app !== void 0;
5205
5413
  console.log(`
5206
5414
  ┌─────────────────────────────────────────────┐
5207
5415
  │ OpenSpec UI │
@@ -5210,31 +5418,69 @@ async function main() {
5210
5418
  `);
5211
5419
  console.log(`📁 Project: ${projectDir}`);
5212
5420
  console.log("");
5421
+ let server = null;
5422
+ let localHostedApp = null;
5213
5423
  try {
5214
- const server = await startServer({
5424
+ const localVersion = getVersion();
5425
+ let hostedBaseUrl = null;
5426
+ if (useHostedApp) {
5427
+ const workspace = resolveLocalHostedAppWorkspace(__dirname);
5428
+ const localHostedAppMode = {
5429
+ appValue: argv.app,
5430
+ workspace
5431
+ };
5432
+ if (shouldUseLocalHostedAppDevMode(localHostedAppMode)) {
5433
+ localHostedApp = await startLocalHostedAppDev({
5434
+ workspace: localHostedAppMode.workspace,
5435
+ resolvedVersion: localVersion
5436
+ });
5437
+ hostedBaseUrl = localHostedApp.baseUrl;
5438
+ } else {
5439
+ const config = await new ConfigManager(projectDir).readConfig();
5440
+ hostedBaseUrl = resolveEffectiveHostedAppBaseUrl({
5441
+ override: argv.app,
5442
+ configured: config.appBaseUrl
5443
+ });
5444
+ }
5445
+ }
5446
+ server = await startServer({
5215
5447
  projectDir,
5216
5448
  port: argv.port,
5217
- open: argv.open
5449
+ open: false,
5450
+ corsOrigins: hostedBaseUrl ? buildHostedCorsOrigins(hostedBaseUrl) : void 0
5218
5451
  });
5219
5452
  if (server.port !== server.preferredPort) console.log(`⚠️ Port ${server.preferredPort} is in use, using ${server.port} instead`);
5220
5453
  console.log(`✅ Server running at ${server.url}`);
5454
+ let browserUrl = server.url;
5455
+ if (useHostedApp && hostedBaseUrl) {
5456
+ browserUrl = buildHostedAppLaunchUrl({
5457
+ baseUrl: hostedBaseUrl,
5458
+ apiBaseUrl: server.url
5459
+ });
5460
+ console.log(`🌐 Hosted app base: ${hostedBaseUrl}`);
5461
+ console.log(`🔗 Hosted URL: ${browserUrl}`);
5462
+ }
5221
5463
  console.log("");
5222
5464
  if (argv.open) {
5223
- await (await import("./open-DDagk2eo.mjs")).default(server.url);
5224
- console.log("🌐 Browser opened");
5465
+ await (await import("./open-DfwCb8mL.mjs")).default(browserUrl);
5466
+ console.log(useHostedApp ? "🌐 Hosted app opened" : "🌐 Browser opened");
5225
5467
  }
5226
5468
  console.log("");
5227
5469
  console.log("Press Ctrl+C to stop the server");
5228
5470
  process.on("SIGINT", async () => {
5229
5471
  console.log("\n\n👋 Shutting down...");
5230
- await server.close();
5472
+ await localHostedApp?.close();
5473
+ await server?.close();
5231
5474
  process.exit(0);
5232
5475
  });
5233
5476
  process.on("SIGTERM", async () => {
5234
- await server.close();
5477
+ await localHostedApp?.close();
5478
+ await server?.close();
5235
5479
  process.exit(0);
5236
5480
  });
5237
5481
  } catch (error) {
5482
+ await localHostedApp?.close();
5483
+ await server?.close();
5238
5484
  console.error("❌ Failed to start server:", error);
5239
5485
  process.exit(1);
5240
5486
  }
@@ -5297,9 +5543,12 @@ async function main() {
5297
5543
  console.error("❌ Export failed:", error);
5298
5544
  process.exit(1);
5299
5545
  }
5300
- }).example("$0", "Start server in current directory").example("$0 ./my-project", "Start server with specific project").example("$0 -p 8080", "Start server on custom port").example("$0 export -o ./dist", "Export HTML to ./dist directory").example("$0 export -o ./dist -f json", "Export JSON data only").example("$0 export -o ./dist -p 8092", "Export and open preview on port 8092").example("$0 export -o ./dist --base-path=/docs/", "Export for subdirectory deployment").example("$0 export -o ./dist --clean", "Clean output directory before export").version(getVersion()).alias("v", "version").help().alias("h", "help").parse();
5546
+ }).help().alias("help", "h").version(getVersion()).alias("version", "v").strict().parseAsync();
5301
5547
  }
5302
- main();
5548
+ main().catch((error) => {
5549
+ console.error("Fatal error:", error);
5550
+ process.exit(1);
5551
+ });
5303
5552
 
5304
5553
  //#endregion
5305
5554
  export { };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { n as createServer, t as startServer } from "./src-Cx7GJTGT.mjs";
1
+ import { n as createServer, t as startServer } from "./src-B5XQ-ERE.mjs";
2
2
 
3
3
  export { createServer, startServer };
@@ -1,9 +1,9 @@
1
1
  import fs, { constants } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import fs$1 from "node:fs";
4
+ import { fileURLToPath } from "node:url";
4
5
  import childProcess, { execFile } from "node:child_process";
5
6
  import { promisify } from "node:util";
6
- import { fileURLToPath } from "node:url";
7
7
  import process from "node:process";
8
8
  import { Buffer } from "node:buffer";
9
9
  import os from "node:os";
@@ -8,18 +8,18 @@ import { mkdir, readFile, rename, writeFile } from "fs/promises";
8
8
  import { dirname, join } from "path";
9
9
  import { AsyncLocalStorage } from "node:async_hooks";
10
10
  import { mkdir as mkdir$1, readFile as readFile$1, readdir, rm, stat, writeFile as writeFile$1 } from "node:fs/promises";
11
- import { dirname as dirname$1, join as join$1, matchesGlob, relative as relative$1, resolve as resolve$1, sep } from "node:path";
11
+ import { basename as basename$1, dirname as dirname$1, join as join$1, matchesGlob, relative as relative$1, resolve as resolve$1, sep } from "node:path";
12
12
  import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
13
13
  import { EventEmitter } from "events";
14
14
  import { watch } from "fs";
15
15
  import { exec, spawn } from "child_process";
16
16
  import { promisify } from "util";
17
+ import { fileURLToPath } from "node:url";
17
18
  import * as pty from "@lydell/node-pty";
18
19
  import { execFile } from "node:child_process";
19
20
  import { EventEmitter as EventEmitter$1 } from "node:events";
20
21
  import { promisify as promisify$1 } from "node:util";
21
22
  import { Worker as Worker$1 } from "node:worker_threads";
22
- import { fileURLToPath } from "node:url";
23
23
 
24
24
  //#region rolldown:runtime
25
25
  var __create$1 = Object.create;
@@ -6308,6 +6308,7 @@ const OpenSpecUIConfigSchema = objectType({
6308
6308
  }).default({}),
6309
6309
  theme: enumType(THEME_VALUES).default("system"),
6310
6310
  codeEditor: CodeEditorConfigSchema.default(CodeEditorConfigSchema.parse({})),
6311
+ appBaseUrl: stringType().default(""),
6311
6312
  terminal: TerminalConfigSchema.default(TerminalConfigSchema.parse({})),
6312
6313
  dashboard: DashboardConfigSchema.default(DashboardConfigSchema.parse({}))
6313
6314
  });
@@ -6316,6 +6317,7 @@ const DEFAULT_CONFIG = {
6316
6317
  cli: {},
6317
6318
  theme: "system",
6318
6319
  codeEditor: CodeEditorConfigSchema.parse({}),
6320
+ appBaseUrl: "",
6319
6321
  terminal: TerminalConfigSchema.parse({}),
6320
6322
  dashboard: DashboardConfigSchema.parse({})
6321
6323
  };
@@ -6384,6 +6386,7 @@ var ConfigManager = class {
6384
6386
  ...current.codeEditor,
6385
6387
  ...config.codeEditor
6386
6388
  },
6389
+ appBaseUrl: config.appBaseUrl ?? current.appBaseUrl,
6387
6390
  terminal: {
6388
6391
  ...current.terminal,
6389
6392
  ...config.terminal
@@ -7134,6 +7137,37 @@ const DASHBOARD_METRIC_KEYS = [
7134
7137
  "taskCompletionPercent"
7135
7138
  ];
7136
7139
 
7140
+ //#endregion
7141
+ //#region ../core/src/hosted-app.ts
7142
+ const OFFICIAL_APP_BASE_URL = "https://app.openspecui.com";
7143
+ function withHttpsProtocol(value) {
7144
+ if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(value)) return value;
7145
+ return `https://${value}`;
7146
+ }
7147
+ function normalizeHostedAppBaseUrl(input) {
7148
+ const trimmed = input.trim();
7149
+ if (!trimmed) throw new Error("Hosted app base URL must not be empty");
7150
+ let parsed;
7151
+ try {
7152
+ parsed = new URL(withHttpsProtocol(trimmed));
7153
+ } catch (error) {
7154
+ throw new Error(`Invalid hosted app base URL: ${error instanceof Error ? error.message : String(error)}`);
7155
+ }
7156
+ parsed.hash = "";
7157
+ parsed.search = "";
7158
+ const pathname = parsed.pathname.replace(/\/+$/, "");
7159
+ parsed.pathname = pathname.length > 0 ? pathname : "/";
7160
+ return parsed.toString().replace(/\/$/, parsed.pathname === "/" ? "" : "");
7161
+ }
7162
+ function resolveHostedAppBaseUrl(options) {
7163
+ return normalizeHostedAppBaseUrl(options.override?.trim() || options.configured?.trim() || OFFICIAL_APP_BASE_URL);
7164
+ }
7165
+ function buildHostedLaunchUrl(options) {
7166
+ const url = new URL(normalizeHostedAppBaseUrl(options.baseUrl));
7167
+ url.searchParams.set("api", options.apiBaseUrl);
7168
+ return url.toString();
7169
+ }
7170
+
7137
7171
  //#endregion
7138
7172
  //#region ../core/src/opsx-display-path.ts
7139
7173
  const VIRTUAL_PROJECT_DIRNAME = "project";
@@ -24287,35 +24321,12 @@ async function fetchDashboardOverview(ctx, reason = "dashboard-refresh") {
24287
24321
  ctx.adapter.listChangesWithMeta(),
24288
24322
  ctx.adapter.listArchivedChangesWithMeta()
24289
24323
  ]);
24290
- await ctx.kernel.waitForWarmup();
24291
- await ctx.kernel.ensureStatusList();
24292
- const statusList = ctx.kernel.getStatusList();
24293
- const changeMetaMap = new Map(changeMetas.map((change) => [change.id, change]));
24294
- const activeChangeIds = new Set([...changeMetas.map((change) => change.id), ...statusList.map((status) => status.changeName)]);
24295
- const statusByChange = new Map(statusList.map((status) => [status.changeName, status]));
24296
- const activeChanges = (await Promise.all([...activeChangeIds].map(async (changeId) => {
24297
- const status = statusByChange.get(changeId);
24298
- const changeMeta = changeMetaMap.get(changeId);
24299
- const statInfo = await reactiveStat(join$1(ctx.projectDir, "openspec", "changes", changeId));
24300
- let progress = changeMeta?.progress ?? {
24301
- total: 0,
24302
- completed: 0
24303
- };
24304
- if (status) try {
24305
- await ctx.kernel.ensureApplyInstructions(changeId, status.schemaName);
24306
- const apply = ctx.kernel.getApplyInstructions(changeId, status.schemaName);
24307
- progress = {
24308
- total: apply.progress.total,
24309
- completed: apply.progress.complete
24310
- };
24311
- } catch {}
24312
- return {
24313
- id: changeId,
24314
- name: changeMeta?.name ?? changeId,
24315
- progress,
24316
- updatedAt: changeMeta?.updatedAt ?? statInfo?.mtime ?? 0
24317
- };
24318
- }))).sort((a, b) => b.updatedAt - a.updatedAt);
24324
+ const activeChanges = changeMetas.map((changeMeta) => ({
24325
+ id: changeMeta.id,
24326
+ name: changeMeta.name ?? changeMeta.id,
24327
+ progress: changeMeta.progress,
24328
+ updatedAt: changeMeta.updatedAt
24329
+ })).sort((a, b) => b.updatedAt - a.updatedAt);
24319
24330
  const archivedChanges = (await Promise.all(archiveMetas.map(async (meta) => {
24320
24331
  const change = await ctx.adapter.readArchivedChange(meta.id);
24321
24332
  if (!change) return null;
@@ -24667,6 +24678,7 @@ const configRouter = router({
24667
24678
  "system"
24668
24679
  ]).optional(),
24669
24680
  codeEditor: objectType({ theme: CodeEditorThemeSchema.optional() }).optional(),
24681
+ appBaseUrl: stringType().optional(),
24670
24682
  terminal: TerminalConfigSchema.omit({ rendererEngine: true }).partial().extend({ rendererEngine: TerminalRendererEngineSchema.optional() }).optional(),
24671
24683
  dashboard: DashboardConfigSchema.partial().optional()
24672
24684
  })).mutation(async ({ ctx, input }) => {
@@ -24674,9 +24686,10 @@ const configRouter = router({
24674
24686
  const hasCliArgs = input.cli !== void 0 && Object.prototype.hasOwnProperty.call(input.cli, "args");
24675
24687
  if (hasCliCommand && !hasCliArgs) {
24676
24688
  await ctx.configManager.setCliCommand(input.cli?.command ?? "");
24677
- if (input.theme !== void 0 || input.codeEditor !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0) await ctx.configManager.writeConfig({
24689
+ if (input.theme !== void 0 || input.codeEditor !== void 0 || input.appBaseUrl !== void 0 || input.terminal !== void 0 || input.dashboard !== void 0) await ctx.configManager.writeConfig({
24678
24690
  theme: input.theme,
24679
24691
  codeEditor: input.codeEditor,
24692
+ appBaseUrl: input.appBaseUrl,
24680
24693
  terminal: input.terminal,
24681
24694
  dashboard: input.dashboard
24682
24695
  });
@@ -25438,6 +25451,17 @@ var SearchService = class {
25438
25451
  *
25439
25452
  * @module server
25440
25453
  */
25454
+ const __dirname$1 = dirname$1(fileURLToPath(import.meta.url));
25455
+ function getServerPackageVersion() {
25456
+ try {
25457
+ const packageJsonPath = join$1(__dirname$1, "..", "package.json");
25458
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
25459
+ return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
25460
+ } catch {
25461
+ return "0.0.0";
25462
+ }
25463
+ }
25464
+ const SERVER_PACKAGE_VERSION = getServerPackageVersion();
25441
25465
  /**
25442
25466
  * Create an OpenSpecUI HTTP server with optional WebSocket support
25443
25467
  */
@@ -25458,7 +25482,9 @@ function createServer$2(config) {
25458
25482
  return c.json({
25459
25483
  status: "ok",
25460
25484
  projectDir: config.projectDir,
25461
- watcherEnabled: !!watcher
25485
+ projectName: basename$1(config.projectDir) || config.projectDir,
25486
+ watcherEnabled: !!watcher,
25487
+ openspecuiVersion: SERVER_PACKAGE_VERSION
25462
25488
  });
25463
25489
  });
25464
25490
  app.use("/trpc/*", async (c) => {
@@ -25590,9 +25616,6 @@ async function startServer(config, setupApp) {
25590
25616
  //#endregion
25591
25617
  //#region src/index.ts
25592
25618
  const __dirname = dirname$1(fileURLToPath(import.meta.url));
25593
- /**
25594
- * Get the path to the web assets directory
25595
- */
25596
25619
  function getWebAssetsDir() {
25597
25620
  const devPath = join$1(__dirname, "..", "..", "web", "dist");
25598
25621
  const prodPath = join$1(__dirname, "..", "web");
@@ -25600,9 +25623,6 @@ function getWebAssetsDir() {
25600
25623
  if (existsSync(devPath)) return devPath;
25601
25624
  throw new Error("Web assets not found. Make sure to build the web package first.");
25602
25625
  }
25603
- /**
25604
- * Setup static file serving middleware for the Hono app
25605
- */
25606
25626
  function setupStaticFiles(app) {
25607
25627
  const webDir = getWebAssetsDir();
25608
25628
  const mimeTypes = {
@@ -25639,18 +25659,15 @@ function setupStaticFiles(app) {
25639
25659
  return c.notFound();
25640
25660
  });
25641
25661
  }
25642
- /**
25643
- * Start the OpenSpec UI server with WebSocket support for realtime updates.
25644
- * Includes static file serving for the web UI.
25645
- */
25646
25662
  async function startServer$1(options = {}) {
25647
- const { projectDir = process.cwd(), port = 3100, enableWatcher = true } = options;
25663
+ const { projectDir = process.cwd(), port = 3100, enableWatcher = true, corsOrigins } = options;
25648
25664
  return await startServer({
25649
25665
  projectDir,
25650
25666
  port,
25651
- enableWatcher
25667
+ enableWatcher,
25668
+ corsOrigins
25652
25669
  }, setupStaticFiles);
25653
25670
  }
25654
25671
 
25655
25672
  //#endregion
25656
- export { SchemaInfoSchema as a, toOpsxDisplayPath as c, DEFAULT_CONFIG as d, OpenSpecAdapter as f, SchemaDetailSchema as i, CliExecutor as l, __toESM$1 as m, createServer$2 as n, SchemaResolutionSchema as o, __commonJS$1 as p, require_dist as r, TemplatesSchema as s, startServer$1 as t, ConfigManager as u };
25673
+ export { SchemaInfoSchema as a, toOpsxDisplayPath as c, CliExecutor as d, ConfigManager as f, __toESM$1 as g, __commonJS$1 as h, SchemaDetailSchema as i, buildHostedLaunchUrl as l, OpenSpecAdapter as m, createServer$2 as n, SchemaResolutionSchema as o, DEFAULT_CONFIG as p, require_dist as r, TemplatesSchema as s, startServer$1 as t, resolveHostedAppBaseUrl as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspecui",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "OpenSpec UI - Visual interface for spec-driven development",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -19,7 +19,8 @@
19
19
  "dependencies": {
20
20
  "@lydell/node-pty": "^1.1.0",
21
21
  "@parcel/watcher": "^2.5.1",
22
- "yaml": "^2.8.0"
22
+ "yaml": "^2.8.0",
23
+ "semver": "^7.7.3"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@hono/node-server": "^1.14.1",
@@ -33,8 +34,9 @@
33
34
  "typescript": "^5.7.2",
34
35
  "vitest": "^2.1.8",
35
36
  "yargs": "^18.0.0",
36
- "@openspecui/web": "2.0.1",
37
- "@openspecui/server": "2.0.2"
37
+ "@types/semver": "^7.7.1",
38
+ "@openspecui/server": "2.1.0",
39
+ "@openspecui/web": "2.1.0"
38
40
  },
39
41
  "scripts": {
40
42
  "build": "pnpm run build:web && pnpm run build:copy-web && tsdown",