openspecui 0.7.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { a as __commonJS, o as __toESM, t as startServer } from "./src-BTqFItR8.mjs";
2
+ import { o as __commonJS, r as OpenSpecAdapter, s as __toESM, t as startServer } from "./src-BtPH0zlP.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { fileURLToPath } from "url";
5
5
  import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
6
6
  import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
7
- import { readFileSync, readdirSync } from "node:fs";
7
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
8
8
  import { readFileSync as readFileSync$1, readdirSync as readdirSync$1, statSync as statSync$1, writeFile } from "fs";
9
9
  import { format, inspect } from "util";
10
10
  import { fileURLToPath as fileURLToPath$1 } from "node:url";
11
11
  import { notStrictEqual, strictEqual } from "assert";
12
+ import { spawn } from "node:child_process";
12
13
 
13
14
  //#region ../../node_modules/.pnpm/cliui@9.0.1/node_modules/cliui/build/lib/index.js
14
15
  const align = {
@@ -1603,8 +1604,8 @@ var require_get_caller_file = /* @__PURE__ */ __commonJS({ "../../node_modules/.
1603
1604
  //#endregion
1604
1605
  //#region ../../node_modules/.pnpm/yargs@18.0.0/node_modules/yargs/lib/platform-shims/esm.mjs
1605
1606
  var import_get_caller_file = /* @__PURE__ */ __toESM(require_get_caller_file(), 1);
1606
- const __dirname$1 = fileURLToPath(import.meta.url);
1607
- const mainFilename = __dirname$1.substring(0, __dirname$1.lastIndexOf("node_modules"));
1607
+ const __dirname$2 = fileURLToPath(import.meta.url);
1608
+ const mainFilename = __dirname$2.substring(0, __dirname$2.lastIndexOf("node_modules"));
1608
1609
  const require = createRequire(import.meta.url);
1609
1610
  var esm_default = {
1610
1611
  assert: {
@@ -1648,7 +1649,7 @@ var esm_default = {
1648
1649
  },
1649
1650
  stringWidth,
1650
1651
  y18n: y18n_default({
1651
- directory: resolve(__dirname$1, "../../../locales"),
1652
+ directory: resolve(__dirname$2, "../../../locales"),
1652
1653
  updateFiles: false
1653
1654
  })
1654
1655
  };
@@ -4501,6 +4502,210 @@ function isYargsInstance(y) {
4501
4502
  const Yargs = YargsFactory(esm_default);
4502
4503
  var yargs_default = Yargs;
4503
4504
 
4505
+ //#endregion
4506
+ //#region src/export.ts
4507
+ const __dirname$1 = dirname$1(fileURLToPath$1(import.meta.url));
4508
+ const WEB_PACKAGE_VERSION = "0.8.0";
4509
+ /**
4510
+ * Generate a complete data snapshot of the OpenSpec project
4511
+ * (Kept for backwards compatibility and testing)
4512
+ */
4513
+ async function generateSnapshot(projectDir) {
4514
+ const adapter = new OpenSpecAdapter(projectDir);
4515
+ if (!await adapter.isInitialized()) throw new Error(`OpenSpec not initialized in ${projectDir}`);
4516
+ const specsMeta = await adapter.listSpecsWithMeta();
4517
+ const specs = await Promise.all(specsMeta.map(async (meta) => {
4518
+ const raw = await adapter.readSpecRaw(meta.id);
4519
+ const parsed = await adapter.readSpec(meta.id);
4520
+ return {
4521
+ id: meta.id,
4522
+ name: meta.name,
4523
+ content: raw || "",
4524
+ overview: parsed?.overview || "",
4525
+ requirements: parsed?.requirements || [],
4526
+ createdAt: meta.createdAt,
4527
+ updatedAt: meta.updatedAt
4528
+ };
4529
+ }));
4530
+ const changesMeta = await adapter.listChangesWithMeta();
4531
+ const changes = await Promise.all(changesMeta.map(async (meta) => {
4532
+ const change = await adapter.readChange(meta.id);
4533
+ if (!change) return null;
4534
+ const files = await adapter.readChangeFiles(meta.id);
4535
+ const proposalFile = files.find((f) => f.path === "proposal.md");
4536
+ const tasksFile = files.find((f) => f.path === "tasks.md");
4537
+ const designFile = files.find((f) => f.path === "design.md");
4538
+ const deltas = (change.deltaSpecs || []).map((ds) => ({
4539
+ capability: ds.specId,
4540
+ content: ds.content || ""
4541
+ }));
4542
+ return {
4543
+ id: meta.id,
4544
+ name: meta.name,
4545
+ proposal: proposalFile?.content || "",
4546
+ tasks: tasksFile?.content,
4547
+ design: designFile?.content,
4548
+ why: change.why || "",
4549
+ whatChanges: change.whatChanges || "",
4550
+ parsedTasks: change.tasks || [],
4551
+ deltas,
4552
+ progress: meta.progress,
4553
+ createdAt: meta.createdAt,
4554
+ updatedAt: meta.updatedAt
4555
+ };
4556
+ }));
4557
+ const archivesMeta = await adapter.listArchivedChangesWithMeta();
4558
+ const archives = await Promise.all(archivesMeta.map(async (meta) => {
4559
+ const files = await adapter.readArchivedChangeFiles(meta.id);
4560
+ const proposalFile = files.find((f) => f.path === "proposal.md");
4561
+ const tasksFile = files.find((f) => f.path === "tasks.md");
4562
+ const designFile = files.find((f) => f.path === "design.md");
4563
+ const change = await adapter.readArchivedChange(meta.id);
4564
+ return {
4565
+ id: meta.id,
4566
+ name: meta.name || meta.id,
4567
+ proposal: proposalFile?.content || "",
4568
+ tasks: tasksFile?.content,
4569
+ design: designFile?.content,
4570
+ why: change?.why || "",
4571
+ whatChanges: change?.whatChanges || "",
4572
+ parsedTasks: change?.tasks || [],
4573
+ createdAt: meta.createdAt,
4574
+ updatedAt: meta.updatedAt
4575
+ };
4576
+ }));
4577
+ let projectMd;
4578
+ let agentsMd;
4579
+ try {
4580
+ projectMd = await adapter.readProjectMd() ?? void 0;
4581
+ } catch {}
4582
+ try {
4583
+ agentsMd = await adapter.readAgentsMd() ?? void 0;
4584
+ } catch {}
4585
+ return {
4586
+ meta: {
4587
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4588
+ version: WEB_PACKAGE_VERSION,
4589
+ projectDir
4590
+ },
4591
+ dashboard: {
4592
+ specsCount: specs.length,
4593
+ changesCount: changes.filter((c) => c !== null).length,
4594
+ archivesCount: archives.length
4595
+ },
4596
+ specs,
4597
+ changes: changes.filter((c) => c !== null),
4598
+ archives,
4599
+ projectMd,
4600
+ agentsMd
4601
+ };
4602
+ }
4603
+ /**
4604
+ * Detect the package manager being used
4605
+ */
4606
+ function detectPackageManager() {
4607
+ const userAgent = process.env.npm_config_user_agent || "";
4608
+ if (userAgent.includes("pnpm")) return "pnpm";
4609
+ if (userAgent.includes("yarn")) return "yarn";
4610
+ if (userAgent.includes("bun")) return "bun";
4611
+ return "npm";
4612
+ }
4613
+ /**
4614
+ * Get the command to execute a package binary
4615
+ */
4616
+ function getExecCommand(pm) {
4617
+ const packageSpec = WEB_PACKAGE_VERSION.startsWith("__") ? "@openspecui/web" : `@openspecui/web@${WEB_PACKAGE_VERSION}`;
4618
+ switch (pm) {
4619
+ case "pnpm": return {
4620
+ cmd: "pnpm",
4621
+ args: ["dlx", packageSpec]
4622
+ };
4623
+ case "yarn": return {
4624
+ cmd: "yarn",
4625
+ args: ["dlx", packageSpec]
4626
+ };
4627
+ case "bun": return {
4628
+ cmd: "bunx",
4629
+ args: [packageSpec]
4630
+ };
4631
+ case "npm":
4632
+ default: return {
4633
+ cmd: "npx",
4634
+ args: [packageSpec]
4635
+ };
4636
+ }
4637
+ }
4638
+ /**
4639
+ * Check if running in local monorepo development mode
4640
+ * Returns the path to local SSG CLI if available, null otherwise
4641
+ */
4642
+ function findLocalSSGCli() {
4643
+ const localSsgTs = join$1(__dirname$1, "..", "..", "web", "src", "ssg", "cli.ts");
4644
+ if (existsSync(localSsgTs)) return localSsgTs;
4645
+ return null;
4646
+ }
4647
+ /**
4648
+ * Export the OpenSpec UI as a static website with SSG (pre-rendered HTML)
4649
+ *
4650
+ * This function:
4651
+ * 1. Generates a data snapshot from the openspec/ directory
4652
+ * 2. Writes data.json to the output directory
4653
+ * 3. Delegates to @openspecui/web's SSG CLI for rendering
4654
+ *
4655
+ * In development (monorepo), it uses the local web package.
4656
+ * In production, it uses npx/pnpm dlx to fetch the published package.
4657
+ */
4658
+ async function exportStaticSite(options) {
4659
+ const { projectDir, outputDir, basePath, clean, open, previewPort, previewHost } = options;
4660
+ if (clean && existsSync(outputDir)) rmSync(outputDir, { recursive: true });
4661
+ mkdirSync(outputDir, { recursive: true });
4662
+ console.log("Generating data snapshot...");
4663
+ const snapshot = await generateSnapshot(projectDir);
4664
+ const dataJsonPath = join$1(outputDir, "data.json");
4665
+ writeFileSync(dataJsonPath, JSON.stringify(snapshot, null, 2));
4666
+ console.log(`Data snapshot written to ${dataJsonPath}`);
4667
+ const ssgOnlyArgs = [
4668
+ "--data",
4669
+ dataJsonPath,
4670
+ "--output",
4671
+ outputDir
4672
+ ];
4673
+ if (basePath !== void 0) ssgOnlyArgs.push("--base-path", basePath);
4674
+ if (open) {
4675
+ ssgOnlyArgs.push("--open");
4676
+ if (previewPort !== void 0) ssgOnlyArgs.push("--preview-port", String(previewPort));
4677
+ if (previewHost !== void 0) ssgOnlyArgs.push("--host", previewHost);
4678
+ }
4679
+ const localCli = findLocalSSGCli();
4680
+ let cmd;
4681
+ let args;
4682
+ if (localCli) {
4683
+ cmd = "npx";
4684
+ args = [
4685
+ "tsx",
4686
+ localCli,
4687
+ ...ssgOnlyArgs
4688
+ ];
4689
+ } else {
4690
+ const execCmd = getExecCommand(detectPackageManager());
4691
+ cmd = execCmd.cmd;
4692
+ args = [...execCmd.args, ...ssgOnlyArgs];
4693
+ }
4694
+ return new Promise((resolvePromise, reject) => {
4695
+ const child = spawn(cmd, args, {
4696
+ stdio: "inherit",
4697
+ cwd: projectDir
4698
+ });
4699
+ child.on("close", (code) => {
4700
+ if (code === 0) resolvePromise();
4701
+ else reject(/* @__PURE__ */ new Error(`SSG export failed with exit code ${code}`));
4702
+ });
4703
+ child.on("error", (err) => {
4704
+ reject(/* @__PURE__ */ new Error(`Failed to start SSG CLI: ${err.message}`));
4705
+ });
4706
+ });
4707
+ }
4708
+
4504
4709
  //#endregion
4505
4710
  //#region src/cli.ts
4506
4711
  const __dirname = dirname$1(fileURLToPath$1(import.meta.url));
@@ -4517,62 +4722,109 @@ function getVersion() {
4517
4722
  }
4518
4723
  async function main() {
4519
4724
  const originalCwd = process.env.INIT_CWD || process.cwd();
4520
- const argv = await yargs_default(hideBin(process.argv)).scriptName("openspecui").usage("$0 [project-dir]", "Visual interface for spec-driven development", (yargs) => {
4725
+ await yargs_default(hideBin(process.argv)).scriptName("openspecui").command(["$0 [project-dir]", "start [project-dir]"], "Start the OpenSpec UI server", (yargs) => {
4521
4726
  return yargs.positional("project-dir", {
4522
4727
  describe: "Project directory containing openspec/",
4523
4728
  type: "string"
4729
+ }).option("port", {
4730
+ alias: "p",
4731
+ describe: "Port to run the server on",
4732
+ type: "number",
4733
+ default: 3100
4734
+ }).option("dir", {
4735
+ alias: "d",
4736
+ describe: "Project directory containing openspec/",
4737
+ type: "string"
4738
+ }).option("open", {
4739
+ describe: "Automatically open the browser",
4740
+ type: "boolean",
4741
+ default: true
4524
4742
  });
4525
- }).option("port", {
4526
- alias: "p",
4527
- describe: "Port to run the server on",
4528
- type: "number",
4529
- default: 3100
4530
- }).option("dir", {
4531
- alias: "d",
4532
- describe: "Project directory containing openspec/",
4533
- type: "string"
4534
- }).option("open", {
4535
- describe: "Automatically open the browser",
4536
- type: "boolean",
4537
- default: true
4538
- }).example("$0", "Start in current directory").example("$0 ./my-project", "Start with specific project").example("$0 -p 8080", "Start on custom port").example("$0 --dir=./my-project", "Start with specific project").example("$0 --no-open", "Start without opening browser").version(getVersion()).alias("v", "version").help().alias("h", "help").parse();
4539
- const projectDir = resolve$1(originalCwd, argv["project-dir"] || argv.dir || ".");
4540
- console.log(`
4743
+ }, async (argv) => {
4744
+ const projectDir = resolve$1(originalCwd, argv["project-dir"] || argv.dir || ".");
4745
+ console.log(`
4541
4746
  ┌─────────────────────────────────────────────┐
4542
4747
  │ OpenSpec UI │
4543
4748
  │ Visual interface for spec-driven dev │
4544
4749
  └─────────────────────────────────────────────┘
4545
4750
  `);
4546
- console.log(`📁 Project: ${projectDir}`);
4547
- console.log("");
4548
- try {
4549
- const server = await startServer({
4550
- projectDir,
4551
- port: argv.port,
4552
- open: argv.open
4553
- });
4554
- if (server.port !== server.preferredPort) console.log(`⚠️ Port ${server.preferredPort} is in use, using ${server.port} instead`);
4555
- console.log(`✅ Server running at ${server.url}`);
4751
+ console.log(`📁 Project: ${projectDir}`);
4556
4752
  console.log("");
4557
- if (argv.open) {
4558
- await (await import("./open-BTGRjBxH.mjs")).default(server.url);
4559
- console.log("🌐 Browser opened");
4753
+ try {
4754
+ const server = await startServer({
4755
+ projectDir,
4756
+ port: argv.port,
4757
+ open: argv.open
4758
+ });
4759
+ if (server.port !== server.preferredPort) console.log(`⚠️ Port ${server.preferredPort} is in use, using ${server.port} instead`);
4760
+ console.log(`✅ Server running at ${server.url}`);
4761
+ console.log("");
4762
+ if (argv.open) {
4763
+ await (await import("./open-CgagzOBo.mjs")).default(server.url);
4764
+ console.log("🌐 Browser opened");
4765
+ }
4766
+ console.log("");
4767
+ console.log("Press Ctrl+C to stop the server");
4768
+ process.on("SIGINT", async () => {
4769
+ console.log("\n\n👋 Shutting down...");
4770
+ await server.close();
4771
+ process.exit(0);
4772
+ });
4773
+ process.on("SIGTERM", async () => {
4774
+ await server.close();
4775
+ process.exit(0);
4776
+ });
4777
+ } catch (error) {
4778
+ console.error("❌ Failed to start server:", error);
4779
+ process.exit(1);
4560
4780
  }
4561
- console.log("");
4562
- console.log("Press Ctrl+C to stop the server");
4563
- process.on("SIGINT", async () => {
4564
- console.log("\n\n👋 Shutting down...");
4565
- await server.close();
4566
- process.exit(0);
4567
- });
4568
- process.on("SIGTERM", async () => {
4569
- await server.close();
4570
- process.exit(0);
4781
+ }).command("export", "Export OpenSpec UI as a static website (alias for @openspecui/web ssg)", (yargs) => {
4782
+ return yargs.option("output", {
4783
+ alias: "o",
4784
+ describe: "Output directory for static export",
4785
+ type: "string",
4786
+ demandOption: true
4787
+ }).option("dir", {
4788
+ alias: "d",
4789
+ describe: "Project directory containing openspec/",
4790
+ type: "string"
4791
+ }).option("base-path", {
4792
+ alias: "b",
4793
+ describe: "Base path for deployment (e.g., /docs/ or ./)",
4794
+ type: "string"
4795
+ }).option("clean", {
4796
+ alias: "c",
4797
+ describe: "Clean output directory before export",
4798
+ type: "boolean"
4799
+ }).option("open", {
4800
+ describe: "Start preview server and open in browser after export",
4801
+ type: "boolean"
4802
+ }).option("preview-port", {
4803
+ describe: "Port for the preview server (used with --open)",
4804
+ type: "number"
4805
+ }).option("preview-host", {
4806
+ describe: "Host for the preview server (used with --open)",
4807
+ type: "string"
4571
4808
  });
4572
- } catch (error) {
4573
- console.error("❌ Failed to start server:", error);
4574
- process.exit(1);
4575
- }
4809
+ }, async (argv) => {
4810
+ const projectDir = resolve$1(originalCwd, argv.dir || ".");
4811
+ const outputDir = resolve$1(originalCwd, argv.output);
4812
+ try {
4813
+ await exportStaticSite({
4814
+ projectDir,
4815
+ outputDir,
4816
+ basePath: argv["base-path"],
4817
+ clean: argv.clean,
4818
+ open: argv.open,
4819
+ previewPort: argv["preview-port"],
4820
+ previewHost: argv["preview-host"]
4821
+ });
4822
+ if (!argv.open) process.exit(0);
4823
+ } catch (error) {
4824
+ console.error("❌ Export failed:", error);
4825
+ process.exit(1);
4826
+ }
4827
+ }).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 to ./dist directory").example("$0 export --output ./public", "Export to ./public directory").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();
4576
4828
  }
4577
4829
  main();
4578
4830
 
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { i as ACPAgents, n as createServer, r as ProviderManager, t as startServer } from "./src-BTqFItR8.mjs";
1
+ import { a as ACPAgents, i as ProviderManager, n as createServer, t as startServer } from "./src-BtPH0zlP.mjs";
2
2
 
3
3
  export { ACPAgents, ProviderManager, createServer, startServer };
@@ -3,10 +3,10 @@ import path from "node:path";
3
3
  import fs$1 from "node:fs";
4
4
  import os from "node:os";
5
5
  import { fileURLToPath } from "node:url";
6
+ import childProcess, { execFile } from "node:child_process";
6
7
  import process from "node:process";
7
8
  import { Buffer } from "node:buffer";
8
9
  import { promisify } from "node:util";
9
- import childProcess, { execFile } from "node:child_process";
10
10
 
11
11
  //#region ../../node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js
12
12
  let isDockerCached;
@@ -13395,10 +13395,7 @@ async function initWatcherPool(projectDir) {
13395
13395
  * @returns 释放函数,调用后取消订阅
13396
13396
  */
13397
13397
  function acquireWatcher(path$1, onChange, options = {}) {
13398
- if (!globalProjectWatcher || !globalProjectWatcher.isInitialized) {
13399
- console.warn("[watcher-pool] ProjectWatcher not initialized. Call initWatcherPool first.");
13400
- return () => {};
13401
- }
13398
+ if (!globalProjectWatcher || !globalProjectWatcher.isInitialized) return () => {};
13402
13399
  const normalizedPath = getRealPath(path$1);
13403
13400
  const debounceMs = options.debounceMs ?? DEBOUNCE_MS;
13404
13401
  const isRecursive = options.recursive ?? false;
@@ -15850,4 +15847,4 @@ async function startServer$1(options = {}) {
15850
15847
  }
15851
15848
 
15852
15849
  //#endregion
15853
- export { __commonJS$1 as a, ACPAgents as i, createServer$2 as n, __toESM$1 as o, ProviderManager as r, startServer$1 as t };
15850
+ export { ACPAgents as a, ProviderManager as i, createServer$2 as n, __commonJS$1 as o, OpenSpecAdapter as r, __toESM$1 as s, startServer$1 as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspecui",
3
- "version": "0.7.7",
3
+ "version": "0.8.0",
4
4
  "description": "OpenSpec UI - Visual interface for spec-driven development",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",