openspecui 1.5.0 → 1.6.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 (32) hide show
  1. package/dist/cli.mjs +280 -32
  2. package/dist/index.mjs +1 -1
  3. package/dist/{src-16GA3our.mjs → src-Brh3druE.mjs} +101 -4
  4. package/package.json +3 -3
  5. package/web/assets/{BufferResource-Bn1UWy0D.js → BufferResource-LpIscPOh.js} +1 -1
  6. package/web/assets/{CanvasRenderer-D8NiU8la.js → CanvasRenderer-hdBAgk8Z.js} +1 -1
  7. package/web/assets/{Filter-CRwq487x.js → Filter-DJVBXWdE.js} +1 -1
  8. package/web/assets/{RenderTargetSystem-CtoB_qTm.js → RenderTargetSystem-CEuUrT8d.js} +1 -1
  9. package/web/assets/{WebGLRenderer-BgKO8R0a.js → WebGLRenderer-DUSFltAk.js} +1 -1
  10. package/web/assets/{WebGPURenderer-CQeL2efC.js → WebGPURenderer-DafJULoy.js} +1 -1
  11. package/web/assets/{browserAll-DP6sOYev.js → browserAll-DYvdsmhE.js} +1 -1
  12. package/web/assets/{ghostty-web-evxujSxm.js → ghostty-web-BitlvuYh.js} +1 -1
  13. package/web/assets/{index-BnT52DZ8.js → index-5zLYkWge.js} +1 -1
  14. package/web/assets/{index-B0IbsqHi.js → index-B8AH-4mO.js} +1 -1
  15. package/web/assets/{index-D2Tp4F9B.js → index-BXlwoClD.js} +1 -1
  16. package/web/assets/{index-dSf1u0YV.js → index-BhxOfd1V.js} +1 -1
  17. package/web/assets/{index-f0QdJSzm.js → index-BwqUcS4o.js} +1 -1
  18. package/web/assets/{index-DTeOcXKn.js → index-CGTRVPmS.js} +1 -1
  19. package/web/assets/{index-T8xoxmUb.js → index-CKfGQiMr.js} +236 -205
  20. package/web/assets/{index-BejnsZfY.js → index-CYTyn82I.js} +1 -1
  21. package/web/assets/{index-DJqmTRAR.js → index-CjHce-bH.js} +1 -1
  22. package/web/assets/{index-B147AOgf.js → index-CuiBRRWA.js} +1 -1
  23. package/web/assets/{index-BPZ3nG0r.js → index-DEgCfC-b.js} +1 -1
  24. package/web/assets/{index-DcXyAs0z.js → index-DbrGteUX.js} +1 -1
  25. package/web/assets/{index-BMashGQn.js → index-Dge9ymDE.js} +1 -1
  26. package/web/assets/{index-CBCPR3Qb.js → index-DtJ1xKq6.js} +1 -1
  27. package/web/assets/index-USotIqwU.css +1 -0
  28. package/web/assets/{index-D6ardy54.js → index-gPPYc26D.js} +1 -1
  29. package/web/assets/{index-4MAU81Qk.js → index-hXwe0Xwc.js} +1 -1
  30. package/web/assets/{webworkerAll-DA2HufNb.js → webworkerAll-CFbGR7bA.js} +1 -1
  31. package/web/index.html +2 -2
  32. package/web/assets/index-Ys2MTD3W.css +0 -1
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as SchemaInfoSchema, c as CliExecutor, d as OpenSpecAdapter, f as __commonJS, i as SchemaDetailSchema, l as ConfigManager, o as SchemaResolutionSchema, p as __toESM, r as require_dist, s as TemplatesSchema, t as startServer, u as DEFAULT_CONFIG } from "./src-16GA3our.mjs";
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-Brh3druE.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
5
5
  import { readFile } from "node:fs/promises";
@@ -8,7 +8,8 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
8
8
  import { readFileSync as readFileSync$1, readdirSync as readdirSync$1, statSync as statSync$1, writeFile as writeFile$1 } from "fs";
9
9
  import { format, inspect } from "util";
10
10
  import { fileURLToPath } from "url";
11
- import { spawn } from "node:child_process";
11
+ import { execFile, spawn } from "node:child_process";
12
+ import { promisify as promisify$1 } from "node:util";
12
13
  import { fileURLToPath as fileURLToPath$1 } from "node:url";
13
14
  import { notStrictEqual, strictEqual } from "assert";
14
15
 
@@ -4506,26 +4507,12 @@ var yargs_default = Yargs;
4506
4507
  //#endregion
4507
4508
  //#region package.json
4508
4509
  var import_dist = require_dist();
4509
- var version = "1.5.0";
4510
- var devDependencies = {
4511
- "@hono/node-server": "^1.14.1",
4512
- "@openspecui/server": "workspace:*",
4513
- "@openspecui/web": "workspace:*",
4514
- "@types/node": "^22.10.2",
4515
- "@types/ws": "^8.5.13",
4516
- "@types/yargs": "^17.0.35",
4517
- "hono": "^4.7.3",
4518
- "open": "^10.1.0",
4519
- "tsdown": "^0.16.6",
4520
- "tsx": "^4.19.2",
4521
- "typescript": "^5.7.2",
4522
- "vitest": "^2.1.8",
4523
- "yargs": "^18.0.0"
4524
- };
4510
+ var version = "1.6.0";
4525
4511
 
4526
4512
  //#endregion
4527
4513
  //#region src/export.ts
4528
4514
  const __dirname$1 = dirname$1(fileURLToPath$1(import.meta.url));
4515
+ const execFileAsync = promisify$1(execFile);
4529
4516
  function parseCliJson(raw, schema, label) {
4530
4517
  const trimmed = raw.trim();
4531
4518
  if (!trimmed) throw new Error(`${label} returned empty output`);
@@ -4574,6 +4561,184 @@ function parseSchemaYaml(content) {
4574
4561
  if (!validated.success) throw new Error(`Invalid schema.yaml detail: ${validated.error.message}`);
4575
4562
  return validated.data;
4576
4563
  }
4564
+ function isAbsoluteFsPath(path$1) {
4565
+ const normalized = path$1.replace(/\\/g, "/");
4566
+ return normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
4567
+ }
4568
+ function toAbsoluteProjectPath(projectDir, path$1) {
4569
+ return isAbsoluteFsPath(path$1) ? path$1 : resolve$1(projectDir, path$1);
4570
+ }
4571
+ function normalizeGitPath(path$1) {
4572
+ return path$1.replace(/\\/g, "/").replace(/^\.\//, "");
4573
+ }
4574
+ function parseRelatedChanges(paths) {
4575
+ const related = /* @__PURE__ */ new Set();
4576
+ for (const rawPath of paths) {
4577
+ const path$1 = normalizeGitPath(rawPath);
4578
+ const activeMatch = /^openspec\/changes\/([^/]+)\//.exec(path$1);
4579
+ if (activeMatch?.[1]) {
4580
+ related.add(activeMatch[1]);
4581
+ continue;
4582
+ }
4583
+ const archiveMatch = /^openspec\/changes\/archive\/([^/]+)\//.exec(path$1);
4584
+ if (archiveMatch?.[1]) related.add(archiveMatch[1].replace(/^\d{4}-\d{2}-\d{2}-/, ""));
4585
+ }
4586
+ return [...related].sort((a, b) => a.localeCompare(b));
4587
+ }
4588
+ function parseNumstat(numstatOutput) {
4589
+ let files = 0;
4590
+ let insertions = 0;
4591
+ let deletions = 0;
4592
+ for (const line of numstatOutput.split("\n")) {
4593
+ const trimmed = line.trim();
4594
+ if (!trimmed) continue;
4595
+ const [addRaw, delRaw] = trimmed.split(" ");
4596
+ if (!addRaw || !delRaw) continue;
4597
+ files += 1;
4598
+ if (addRaw !== "-") insertions += Number(addRaw) || 0;
4599
+ if (delRaw !== "-") deletions += Number(delRaw) || 0;
4600
+ }
4601
+ return {
4602
+ files,
4603
+ insertions,
4604
+ deletions
4605
+ };
4606
+ }
4607
+ async function readDefaultBranch(projectDir) {
4608
+ try {
4609
+ const { stdout } = await execFileAsync("git", [
4610
+ "symbolic-ref",
4611
+ "--quiet",
4612
+ "--short",
4613
+ "refs/remotes/origin/HEAD"
4614
+ ], {
4615
+ cwd: projectDir,
4616
+ encoding: "utf8",
4617
+ maxBuffer: 1024 * 1024
4618
+ });
4619
+ const branch = stdout.trim();
4620
+ if (branch.length > 0) return branch;
4621
+ } catch {}
4622
+ try {
4623
+ const { stdout } = await execFileAsync("git", [
4624
+ "rev-parse",
4625
+ "--abbrev-ref",
4626
+ "HEAD"
4627
+ ], {
4628
+ cwd: projectDir,
4629
+ encoding: "utf8",
4630
+ maxBuffer: 1024 * 1024
4631
+ });
4632
+ const branch = stdout.trim();
4633
+ if (branch.length > 0 && branch !== "HEAD") return branch;
4634
+ } catch {}
4635
+ return "main";
4636
+ }
4637
+ function parseRecentCommitLog(output) {
4638
+ const commits = [];
4639
+ let current = null;
4640
+ const pushCurrent = () => {
4641
+ if (!current) return;
4642
+ commits.push({
4643
+ hash: current.hash,
4644
+ title: current.title,
4645
+ committedAt: current.committedAt,
4646
+ relatedChanges: parseRelatedChanges(current.changedPaths),
4647
+ diff: parseNumstat(current.numstatLines.join("\n"))
4648
+ });
4649
+ current = null;
4650
+ };
4651
+ for (const line of output.split("\n")) {
4652
+ if (line.startsWith("__COMMIT__ ")) {
4653
+ pushCurrent();
4654
+ const [_, hash, tsRaw, ...titleParts] = line.split(" ");
4655
+ const committedAt = Number(tsRaw) * 1e3;
4656
+ current = {
4657
+ hash: hash ?? "",
4658
+ title: titleParts.join(" ").trim() || (hash ? hash.slice(0, 8) : "commit"),
4659
+ committedAt: Number.isFinite(committedAt) && committedAt > 0 ? committedAt : 0,
4660
+ numstatLines: [],
4661
+ changedPaths: []
4662
+ };
4663
+ continue;
4664
+ }
4665
+ if (!current) continue;
4666
+ const trimmed = line.trim();
4667
+ if (!trimmed) continue;
4668
+ const [addRaw, delRaw, ...pathParts] = trimmed.split(" ");
4669
+ if (!addRaw || !delRaw || pathParts.length === 0) continue;
4670
+ const path$1 = pathParts.join(" ");
4671
+ current.numstatLines.push(`${addRaw}\t${delRaw}\t${path$1}`);
4672
+ current.changedPaths.push(path$1);
4673
+ }
4674
+ pushCurrent();
4675
+ return commits.filter((commit) => commit.hash.length > 0);
4676
+ }
4677
+ function normalizeRepositoryUrl(raw) {
4678
+ const value = raw.trim();
4679
+ if (!value) return null;
4680
+ if (value.startsWith("http://") || value.startsWith("https://")) return value.replace(/\.git$/i, "");
4681
+ const gitAtMatch = /^git@([^:]+):(.+)$/.exec(value);
4682
+ if (gitAtMatch?.[1] && gitAtMatch[2]) return `https://${gitAtMatch[1]}/${gitAtMatch[2].replace(/\.git$/i, "")}`;
4683
+ if (value.startsWith("ssh://")) try {
4684
+ const parsed = new URL(value);
4685
+ const pathname = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/i, "");
4686
+ if (!pathname) return null;
4687
+ return `https://${parsed.hostname}/${pathname}`;
4688
+ } catch {}
4689
+ return value.replace(/\.git$/i, "");
4690
+ }
4691
+ async function readSnapshotGit(projectDir) {
4692
+ try {
4693
+ const defaultBranch = await readDefaultBranch(projectDir);
4694
+ const { stdout: latestTsRaw } = await execFileAsync("git", [
4695
+ "log",
4696
+ "-1",
4697
+ "--format=%ct"
4698
+ ], {
4699
+ cwd: projectDir,
4700
+ encoding: "utf8",
4701
+ maxBuffer: 1024 * 1024
4702
+ });
4703
+ const latestSeconds = Number(latestTsRaw.trim());
4704
+ const latestCommitTs = Number.isFinite(latestSeconds) && latestSeconds > 0 ? latestSeconds * 1e3 : null;
4705
+ let repositoryUrl = null;
4706
+ try {
4707
+ const { stdout: remoteRaw } = await execFileAsync("git", [
4708
+ "config",
4709
+ "--get",
4710
+ "remote.origin.url"
4711
+ ], {
4712
+ cwd: projectDir,
4713
+ encoding: "utf8",
4714
+ maxBuffer: 1024 * 1024
4715
+ });
4716
+ repositoryUrl = normalizeRepositoryUrl(remoteRaw);
4717
+ } catch {
4718
+ repositoryUrl = null;
4719
+ }
4720
+ const { stdout: logOutput } = await execFileAsync("git", [
4721
+ "log",
4722
+ "-n",
4723
+ "5",
4724
+ "--format=__COMMIT__%x09%H%x09%ct%x09%s",
4725
+ "--numstat",
4726
+ "--"
4727
+ ], {
4728
+ cwd: projectDir,
4729
+ encoding: "utf8",
4730
+ maxBuffer: 8 * 1024 * 1024
4731
+ });
4732
+ return {
4733
+ defaultBranch,
4734
+ repositoryUrl,
4735
+ latestCommitTs,
4736
+ recentCommits: parseRecentCommitLog(logOutput)
4737
+ };
4738
+ } catch {
4739
+ return;
4740
+ }
4741
+ }
4577
4742
  /**
4578
4743
  * Generate a complete data snapshot of the OpenSpec project
4579
4744
  * (Kept for backwards compatibility and testing)
@@ -4656,8 +4821,10 @@ async function generateSnapshot(projectDir) {
4656
4821
  let configYaml;
4657
4822
  let schemas = [];
4658
4823
  const schemaDetails = {};
4824
+ const schemaYamls = {};
4659
4825
  const schemaResolutions = {};
4660
4826
  const templates = {};
4827
+ const templateContents = {};
4661
4828
  const changeMetadata = {};
4662
4829
  try {
4663
4830
  configYaml = await readFile(join$1(projectDir, "openspec", "config.yaml"), "utf-8");
@@ -4675,16 +4842,56 @@ async function generateSnapshot(projectDir) {
4675
4842
  const resolutionResult = await cliExecutor.schemaWhich(schema.name);
4676
4843
  if (resolutionResult.success) {
4677
4844
  const resolution = parseCliJson(resolutionResult.stdout, SchemaResolutionSchema, "openspec schema which");
4678
- schemaResolutions[schema.name] = resolution;
4845
+ schemaResolutions[schema.name] = {
4846
+ ...resolution,
4847
+ displayPath: toOpsxDisplayPath(resolution.path, {
4848
+ source: resolution.source,
4849
+ projectDir
4850
+ }),
4851
+ shadows: resolution.shadows.map((shadow) => ({
4852
+ ...shadow,
4853
+ displayPath: toOpsxDisplayPath(shadow.path, {
4854
+ source: shadow.source,
4855
+ projectDir
4856
+ })
4857
+ }))
4858
+ };
4679
4859
  try {
4680
4860
  const schemaContent = await readFile(join$1(resolution.path, "schema.yaml"), "utf-8");
4681
4861
  schemaDetails[schema.name] = parseSchemaYaml(schemaContent);
4862
+ schemaYamls[schema.name] = schemaContent;
4682
4863
  } catch {}
4683
4864
  }
4684
4865
  } catch {}
4685
4866
  try {
4686
4867
  const templatesResult = await cliExecutor.templates(schema.name);
4687
- if (templatesResult.success) templates[schema.name] = parseCliJson(templatesResult.stdout, TemplatesSchema, "openspec templates");
4868
+ if (templatesResult.success) {
4869
+ const parsedTemplates = parseCliJson(templatesResult.stdout, TemplatesSchema, "openspec templates");
4870
+ const normalizedTemplates = Object.fromEntries(Object.entries(parsedTemplates).map(([artifactId, info]) => [artifactId, {
4871
+ ...info,
4872
+ path: toAbsoluteProjectPath(projectDir, info.path),
4873
+ displayPath: toOpsxDisplayPath(info.path, {
4874
+ source: info.source,
4875
+ projectDir
4876
+ })
4877
+ }]));
4878
+ templates[schema.name] = normalizedTemplates;
4879
+ const contents = await Promise.all(Object.entries(normalizedTemplates).map(async ([artifactId, info]) => {
4880
+ let content = null;
4881
+ try {
4882
+ content = await readFile(info.path, "utf-8");
4883
+ } catch {
4884
+ content = null;
4885
+ }
4886
+ return [artifactId, {
4887
+ content,
4888
+ path: info.path,
4889
+ displayPath: info.displayPath,
4890
+ source: info.source
4891
+ }];
4892
+ }));
4893
+ templateContents[schema.name] = Object.fromEntries(contents);
4894
+ }
4688
4895
  } catch {}
4689
4896
  }
4690
4897
  try {
@@ -4695,6 +4902,7 @@ async function generateSnapshot(projectDir) {
4695
4902
  changeMetadata[changeId] = null;
4696
4903
  }
4697
4904
  } catch {}
4905
+ const git = await readSnapshotGit(projectDir);
4698
4906
  return {
4699
4907
  meta: {
4700
4908
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4706,6 +4914,7 @@ async function generateSnapshot(projectDir) {
4706
4914
  changesCount: changes.filter((c) => c !== null).length,
4707
4915
  archivesCount: archives.length
4708
4916
  },
4917
+ git,
4709
4918
  config: uiConfig,
4710
4919
  specs,
4711
4920
  changes: changes.filter((c) => c !== null),
@@ -4716,8 +4925,10 @@ async function generateSnapshot(projectDir) {
4716
4925
  configYaml,
4717
4926
  schemas,
4718
4927
  schemaDetails,
4928
+ schemaYamls,
4719
4929
  schemaResolutions,
4720
4930
  templates,
4931
+ templateContents,
4721
4932
  changeMetadata
4722
4933
  }
4723
4934
  };
@@ -4738,7 +4949,7 @@ function runCommand(cmd, args, cwd) {
4738
4949
  const child = spawn(cmd, args, {
4739
4950
  stdio: "inherit",
4740
4951
  cwd,
4741
- shell: true
4952
+ shell: false
4742
4953
  });
4743
4954
  child.on("close", (code) => {
4744
4955
  if (code === 0) resolvePromise();
@@ -4747,6 +4958,11 @@ function runCommand(cmd, args, cwd) {
4747
4958
  child.on("error", (err) => reject(err));
4748
4959
  });
4749
4960
  }
4961
+ const LOCAL_PACKAGE_PROTOCOLS = [
4962
+ "workspace:",
4963
+ "file:",
4964
+ "link:"
4965
+ ];
4750
4966
  /**
4751
4967
  * Detect the package manager used in the current project
4752
4968
  */
@@ -4767,7 +4983,7 @@ function detectPackageManager() {
4767
4983
  return "npm";
4768
4984
  }
4769
4985
  /**
4770
- * Get the command to run a local binary (like vite)
4986
+ * Get the command to run a binary in a package-manager agnostic way.
4771
4987
  */
4772
4988
  function getRunCommand(pm, bin) {
4773
4989
  switch (pm) {
@@ -4777,11 +4993,11 @@ function getRunCommand(pm, bin) {
4777
4993
  };
4778
4994
  case "pnpm": return {
4779
4995
  cmd: "pnpm",
4780
- args: ["exec", bin]
4996
+ args: ["dlx", bin]
4781
4997
  };
4782
4998
  case "yarn": return {
4783
4999
  cmd: "yarn",
4784
- args: [bin]
5000
+ args: ["dlx", bin]
4785
5001
  };
4786
5002
  case "deno": return {
4787
5003
  cmd: "deno",
@@ -4797,12 +5013,36 @@ function getRunCommand(pm, bin) {
4797
5013
  };
4798
5014
  }
4799
5015
  }
5016
+ function findNearestPackageJson(startDir) {
5017
+ let currentDir = startDir;
5018
+ while (true) {
5019
+ const packageJsonPath = join$1(currentDir, "package.json");
5020
+ if (existsSync(packageJsonPath)) return packageJsonPath;
5021
+ const parentDir = dirname$1(currentDir);
5022
+ if (parentDir === currentDir) return null;
5023
+ currentDir = parentDir;
5024
+ }
5025
+ }
5026
+ function readWebPackageRangeFromPackageJson(startDir) {
5027
+ const packageJsonPath = findNearestPackageJson(startDir);
5028
+ if (!packageJsonPath) return null;
5029
+ try {
5030
+ const packageJsonRaw = readFileSync(packageJsonPath, "utf-8");
5031
+ const parsed = JSON.parse(packageJsonRaw);
5032
+ return parsed.dependencies?.["@openspecui/web"] ?? parsed.devDependencies?.["@openspecui/web"] ?? parsed.peerDependencies?.["@openspecui/web"] ?? parsed.optionalDependencies?.["@openspecui/web"] ?? null;
5033
+ } catch {
5034
+ return null;
5035
+ }
5036
+ }
5037
+ function isLocalPackageRange(range) {
5038
+ if (!range) return false;
5039
+ return LOCAL_PACKAGE_PROTOCOLS.some((protocol) => range.startsWith(protocol));
5040
+ }
4800
5041
  /**
4801
5042
  * Get the exec command for running a package binary
4802
5043
  * Uses appropriate flags to ensure the correct version of @openspecui/web is installed
4803
5044
  */
4804
- function getExecCommand(pm) {
4805
- const webPkgSpec = `@openspecui/web@${devDependencies["@openspecui/web"]}`;
5045
+ function getExecCommand(pm, webPkgSpec) {
4806
5046
  switch (pm) {
4807
5047
  case "bun": return {
4808
5048
  cmd: "bunx",
@@ -4867,7 +5107,9 @@ async function exportHtml(options) {
4867
5107
  writeFileSync(dataJsonPath, JSON.stringify(snapshot, null, 2));
4868
5108
  console.log(`Data snapshot written to ${dataJsonPath}`);
4869
5109
  const localWebPkg = findLocalWebPackage();
4870
- if (localWebPkg) {
5110
+ const webPackageRange = readWebPackageRangeFromPackageJson(__dirname$1);
5111
+ if (isLocalPackageRange(webPackageRange)) {
5112
+ if (!localWebPkg) throw new Error(`Detected local/dev @openspecui/web range "${webPackageRange}" but local web package was not found`);
4871
5113
  console.log("\n[Local dev mode] Running SSG from local web package...");
4872
5114
  await runCommand("pnpm", [
4873
5115
  "tsx",
@@ -4882,7 +5124,7 @@ async function exportHtml(options) {
4882
5124
  } else {
4883
5125
  console.log("\n[Production mode] Running SSG via @openspecui/web...");
4884
5126
  const pm = detectPackageManager();
4885
- const execCmd = getExecCommand(pm);
5127
+ const execCmd = getExecCommand(pm, `@openspecui/web@${webPackageRange || version}`);
4886
5128
  try {
4887
5129
  await runCommand(execCmd.cmd, [
4888
5130
  ...execCmd.args,
@@ -5027,6 +5269,10 @@ async function main() {
5027
5269
  }).option("preview-port", {
5028
5270
  describe: "Port for the preview server (used with --open)",
5029
5271
  type: "number"
5272
+ }).option("port", {
5273
+ alias: "p",
5274
+ describe: "Alias of --open --preview-port <port>",
5275
+ type: "number"
5030
5276
  }).option("preview-host", {
5031
5277
  describe: "Host for the preview server (used with --open)",
5032
5278
  type: "string"
@@ -5034,6 +5280,8 @@ async function main() {
5034
5280
  }, async (argv) => {
5035
5281
  const projectDir = resolve$1(originalCwd, argv.dir || ".");
5036
5282
  const outputDir = resolve$1(originalCwd, argv.output);
5283
+ const previewPort = argv.port ?? argv["preview-port"];
5284
+ const shouldOpen = argv.open || argv.port !== void 0;
5037
5285
  try {
5038
5286
  await exportStaticSite({
5039
5287
  projectDir,
@@ -5041,16 +5289,16 @@ async function main() {
5041
5289
  format: argv.format,
5042
5290
  basePath: argv["base-path"],
5043
5291
  clean: argv.clean,
5044
- open: argv.open,
5045
- previewPort: argv["preview-port"],
5292
+ open: shouldOpen,
5293
+ previewPort,
5046
5294
  previewHost: argv["preview-host"]
5047
5295
  });
5048
- if (!argv.open) process.exit(0);
5296
+ if (!shouldOpen) process.exit(0);
5049
5297
  } catch (error) {
5050
5298
  console.error("❌ Export failed:", error);
5051
5299
  process.exit(1);
5052
5300
  }
5053
- }).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 --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();
5301
+ }).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();
5054
5302
  }
5055
5303
  main();
5056
5304
 
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { n as createServer, t as startServer } from "./src-16GA3our.mjs";
1
+ import { n as createServer, t as startServer } from "./src-Brh3druE.mjs";
2
2
 
3
3
  export { createServer, startServer };
@@ -7094,6 +7094,68 @@ const DASHBOARD_METRIC_KEYS = [
7094
7094
  "taskCompletionPercent"
7095
7095
  ];
7096
7096
 
7097
+ //#endregion
7098
+ //#region ../core/src/opsx-display-path.ts
7099
+ const VIRTUAL_PROJECT_DIRNAME = "project";
7100
+ const WINDOWS_DRIVE_PREFIX = /^[A-Za-z]:\//;
7101
+ function normalizeFsPath(path$1) {
7102
+ return path$1.replace(/\\/g, "/").replace(/\/+$/g, "");
7103
+ }
7104
+ function isAbsolutePath(path$1) {
7105
+ return path$1.startsWith("/") || WINDOWS_DRIVE_PREFIX.test(path$1);
7106
+ }
7107
+ function stripRelativePrefix(path$1) {
7108
+ return path$1.replace(/^\.?\//, "");
7109
+ }
7110
+ function splitSegments(path$1) {
7111
+ return normalizeFsPath(path$1).split("/").filter(Boolean);
7112
+ }
7113
+ function toNpmSpecifier(path$1) {
7114
+ const normalized = normalizeFsPath(path$1);
7115
+ const match$1 = /(?:^|\/)node_modules\/(?:\.pnpm\/[^/]+\/node_modules\/)?(@[^/]+\/[^/]+|[^/]+)(\/.*)?/.exec(normalized);
7116
+ const pkgName = match$1?.[1];
7117
+ if (!pkgName) return null;
7118
+ return `npm:${pkgName}${match$1[2] ?? ""}`;
7119
+ }
7120
+ function toVirtualProjectPath(path$1) {
7121
+ return `${VIRTUAL_PROJECT_DIRNAME}:${stripRelativePrefix(path$1)}`;
7122
+ }
7123
+ function isPathInside(root, target) {
7124
+ const normalizedRoot = normalizeFsPath(root);
7125
+ const normalizedTarget = normalizeFsPath(target);
7126
+ const rootLower = normalizedRoot.toLowerCase();
7127
+ const targetLower = normalizedTarget.toLowerCase();
7128
+ return targetLower === rootLower || targetLower.startsWith(`${rootLower}/`);
7129
+ }
7130
+ function toRelativeFromRoot(root, target) {
7131
+ const rootSegments = splitSegments(root);
7132
+ const targetSegments = splitSegments(target);
7133
+ let index = 0;
7134
+ while (index < rootSegments.length && index < targetSegments.length) {
7135
+ if (rootSegments[index]?.toLowerCase() !== targetSegments[index]?.toLowerCase()) break;
7136
+ index += 1;
7137
+ }
7138
+ return targetSegments.slice(index).join("/");
7139
+ }
7140
+ function findOpspecSlice(path$1) {
7141
+ const segments = splitSegments(path$1);
7142
+ const idx = segments.lastIndexOf("openspec");
7143
+ if (idx < 0) return null;
7144
+ return segments.slice(idx).join("/");
7145
+ }
7146
+ function toOpsxDisplayPath(absoluteOrRelativePath, options) {
7147
+ const normalized = normalizeFsPath(absoluteOrRelativePath);
7148
+ const npmSpecifier = toNpmSpecifier(normalized);
7149
+ if (options?.source === "package" || npmSpecifier) {
7150
+ if (npmSpecifier) return npmSpecifier;
7151
+ }
7152
+ if (!isAbsolutePath(normalized)) return toVirtualProjectPath(normalized);
7153
+ if (options?.projectDir && isPathInside(options.projectDir, normalized)) return toVirtualProjectPath(toRelativeFromRoot(options.projectDir, normalized));
7154
+ const openspecSlice = findOpspecSlice(normalized);
7155
+ if (openspecSlice) return toVirtualProjectPath(openspecSlice);
7156
+ return toVirtualProjectPath(splitSegments(normalized).slice(-4).join("/"));
7157
+ }
7158
+
7097
7159
  //#endregion
7098
7160
  //#region ../core/src/opsx-types.ts
7099
7161
  const ArtifactStatusSchema = objectType({
@@ -7177,17 +7239,20 @@ const SchemaResolutionSchema = objectType({
7177
7239
  "package"
7178
7240
  ]),
7179
7241
  path: stringType(),
7242
+ displayPath: stringType().optional(),
7180
7243
  shadows: arrayType(objectType({
7181
7244
  source: enumType([
7182
7245
  "project",
7183
7246
  "user",
7184
7247
  "package"
7185
7248
  ]),
7186
- path: stringType()
7249
+ path: stringType(),
7250
+ displayPath: stringType().optional()
7187
7251
  }))
7188
7252
  });
7189
7253
  const TemplatesSchema = recordType(objectType({
7190
7254
  path: stringType(),
7255
+ displayPath: stringType().optional(),
7191
7256
  source: enumType([
7192
7257
  "project",
7193
7258
  "user",
@@ -13884,6 +13949,12 @@ function parseCliJson(raw$1, schema$6, label) {
13884
13949
  function toRelativePath(root, absolutePath) {
13885
13950
  return relative$1(root, absolutePath).split(sep).join("/");
13886
13951
  }
13952
+ function isAbsoluteFsPath(path$1) {
13953
+ return path$1.startsWith("/") || /^[A-Za-z]:\//.test(path$1);
13954
+ }
13955
+ function toAbsoluteProjectPath(projectDir, path$1) {
13956
+ return isAbsoluteFsPath(path$1.replace(/\\/g, "/")) ? path$1 : resolve$1(projectDir, path$1);
13957
+ }
13887
13958
  async function readEntriesUnderRoot(root) {
13888
13959
  if (!(await reactiveStat(root))?.isDirectory) return [];
13889
13960
  const collectEntries = async (dir) => {
@@ -14322,7 +14393,21 @@ var OpsxKernel = class {
14322
14393
  await touchOpsxProjectDeps(this.projectDir);
14323
14394
  const result = await this.cliExecutor.schemaWhich(name);
14324
14395
  if (!result.success) throw new Error(result.stderr || `openspec schema which failed (exit ${result.exitCode ?? "null"})`);
14325
- return parseCliJson(result.stdout, SchemaResolutionSchema, "openspec schema which");
14396
+ const parsed = parseCliJson(result.stdout, SchemaResolutionSchema, "openspec schema which");
14397
+ return {
14398
+ ...parsed,
14399
+ displayPath: toOpsxDisplayPath(parsed.path, {
14400
+ source: parsed.source,
14401
+ projectDir: this.projectDir
14402
+ }),
14403
+ shadows: parsed.shadows.map((shadow) => ({
14404
+ ...shadow,
14405
+ displayPath: toOpsxDisplayPath(shadow.path, {
14406
+ source: shadow.source,
14407
+ projectDir: this.projectDir
14408
+ })
14409
+ }))
14410
+ };
14326
14411
  }
14327
14412
  async fetchSchemaDetail(name) {
14328
14413
  await touchOpsxProjectDeps(this.projectDir);
@@ -14346,7 +14431,15 @@ var OpsxKernel = class {
14346
14431
  await touchOpsxProjectDeps(this.projectDir);
14347
14432
  const result = await this.cliExecutor.templates(schema$6);
14348
14433
  if (!result.success) throw new Error(result.stderr || `openspec templates failed (exit ${result.exitCode ?? "null"})`);
14349
- return parseCliJson(result.stdout, TemplatesSchema, "openspec templates");
14434
+ const templates = parseCliJson(result.stdout, TemplatesSchema, "openspec templates");
14435
+ return Object.fromEntries(Object.entries(templates).map(([artifactId, info]) => [artifactId, {
14436
+ ...info,
14437
+ path: toAbsoluteProjectPath(this.projectDir, info.path),
14438
+ displayPath: toOpsxDisplayPath(info.path, {
14439
+ source: info.source,
14440
+ projectDir: this.projectDir
14441
+ })
14442
+ }]));
14350
14443
  }
14351
14444
  async fetchTemplateContents(schema$6) {
14352
14445
  await this.ensureTemplates(schema$6);
@@ -14355,6 +14448,10 @@ var OpsxKernel = class {
14355
14448
  return [artifactId, {
14356
14449
  content: await reactiveReadFile(info.path),
14357
14450
  path: info.path,
14451
+ displayPath: info.displayPath ?? toOpsxDisplayPath(info.path, {
14452
+ source: info.source,
14453
+ projectDir: this.projectDir
14454
+ }),
14358
14455
  source: info.source
14359
14456
  }];
14360
14457
  }));
@@ -25370,4 +25467,4 @@ async function startServer$1(options = {}) {
25370
25467
  }
25371
25468
 
25372
25469
  //#endregion
25373
- export { SchemaInfoSchema as a, CliExecutor as c, OpenSpecAdapter as d, __commonJS$1 as f, SchemaDetailSchema as i, ConfigManager as l, createServer$2 as n, SchemaResolutionSchema as o, __toESM$1 as p, require_dist as r, TemplatesSchema as s, startServer$1 as t, DEFAULT_CONFIG as u };
25470
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspecui",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "OpenSpec UI - Visual interface for spec-driven development",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -33,8 +33,8 @@
33
33
  "typescript": "^5.7.2",
34
34
  "vitest": "^2.1.8",
35
35
  "yargs": "^18.0.0",
36
- "@openspecui/server": "1.5.0",
37
- "@openspecui/web": "1.5.0"
36
+ "@openspecui/server": "1.6.0",
37
+ "@openspecui/web": "1.6.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "pnpm run build:web && pnpm run build:copy-web && tsdown",
@@ -1,4 +1,4 @@
1
- import{y as U,z as g,A as c,B as S,D as _,F as m,H as I,J as p}from"./index-T8xoxmUb.js";const x={name:"local-uniform-bit",vertex:{header:`
1
+ import{y as U,z as g,A as c,B as S,D as _,F as m,H as I,J as p}from"./index-CKfGQiMr.js";const x={name:"local-uniform-bit",vertex:{header:`
2
2
 
3
3
  struct LocalUniforms {
4
4
  uTransformMatrix:mat3x3<f32>,