spets 0.1.98 → 0.1.99

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
@@ -4,7 +4,7 @@ import {
4
4
  renderConfigJSON,
5
5
  runConfigInteractive,
6
6
  setConfigField
7
- } from "./chunk-HXNXQAVN.js";
7
+ } from "./chunk-BCOGSLCX.js";
8
8
  import {
9
9
  CLIDashboardRenderer,
10
10
  DashboardClient,
@@ -12,36 +12,43 @@ import {
12
12
  generateTaskId,
13
13
  getWorkflowState,
14
14
  listTasks,
15
+ loadDocument,
15
16
  loadTaskMetadata,
16
17
  renderTasksJSON,
17
18
  runTasksInteractive
18
- } from "./chunk-NYBOEOGE.js";
19
+ } from "./chunk-ZRWVDWBF.js";
19
20
  import {
20
21
  renderDocsJSON,
21
22
  runDocsInteractive
22
- } from "./chunk-3OOPOFUK.js";
23
+ } from "./chunk-3JIHGW47.js";
23
24
  import {
24
25
  buildAvailableKnowledgeSection,
26
+ getKnowledgeDir,
27
+ listKnowledgeFiles,
25
28
  loadGuide,
29
+ loadKnowledgeFile,
26
30
  loadKnowledgeFiles,
27
31
  renderKnowledgeJSON,
28
32
  runKnowledgeInteractive,
29
33
  saveKnowledge
30
- } from "./chunk-TXGA3CZZ.js";
34
+ } from "./chunk-3QH66XVU.js";
31
35
  import {
36
+ clearConfigCache,
37
+ getConfigPath,
32
38
  getGitHubConfig,
33
39
  getOutputsDir,
34
40
  getSpetsDir,
35
41
  getStepsDir,
42
+ loadAllSteps,
36
43
  loadConfig,
37
44
  spetsExists
38
- } from "./chunk-BQWHTHA3.js";
45
+ } from "./chunk-2MUV3F53.js";
39
46
 
40
47
  // src/index.ts
41
48
  import { Command } from "commander";
42
- import { readFileSync as readFileSync18 } from "fs";
43
- import { dirname as dirname7, join as join14 } from "path";
44
- import { fileURLToPath as fileURLToPath2 } from "url";
49
+ import { readFileSync as readFileSync20 } from "fs";
50
+ import { dirname as dirname8, join as join17 } from "path";
51
+ import { fileURLToPath as fileURLToPath3 } from "url";
45
52
 
46
53
  // src/commands/init.ts
47
54
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, cpSync, readFileSync } from "fs";
@@ -822,7 +829,7 @@ function formatDocStatus(status) {
822
829
  import { execSync as execSync2 } from "child_process";
823
830
 
824
831
  // src/orchestrator/index.ts
825
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "fs";
832
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync11, readdirSync as readdirSync2 } from "fs";
826
833
  import matter from "gray-matter";
827
834
 
828
835
  // src/orchestrator/state.ts
@@ -2167,19 +2174,31 @@ var Orchestrator = class {
2167
2174
  return buildCompleteResponse(this.cwd, state, "stopped");
2168
2175
  }
2169
2176
  /**
2170
- * Resume a stopped workflow by restoring the pre-stop phase
2177
+ * Resume a workflow by restoring the pre-stop phase.
2178
+ * If no taskId provided, auto-find most recent resumable task.
2171
2179
  */
2172
2180
  cmdResume(taskId) {
2181
+ if (!taskId) {
2182
+ const listResult = this.cmdList();
2183
+ if (listResult.type === "list" && listResult.tasks.length > 0) {
2184
+ taskId = listResult.tasks[0].taskId;
2185
+ } else {
2186
+ return buildErrorResponse("No resumable workflows found");
2187
+ }
2188
+ }
2173
2189
  const state = loadState(this.cwd, taskId);
2174
2190
  if (!state) {
2175
2191
  return buildErrorResponse(`No workflow found: ${taskId}`, taskId);
2176
2192
  }
2193
+ if (state.status === "completed" || state.status === "rejected") {
2194
+ return buildErrorResponse(`Workflow is ${state.status} and cannot be resumed`, taskId);
2195
+ }
2177
2196
  if (state.status !== "stopped") {
2178
2197
  return this.cmdStatus(taskId);
2179
2198
  }
2180
2199
  const phaseToStatus = {
2181
2200
  "explore": "phase_explore",
2182
- "clarify": state.questions ? "clarify_pending" : "phase_clarify",
2201
+ "clarify": state.decisions ? "clarify_pending" : "phase_clarify",
2183
2202
  "execute": "phase_execute",
2184
2203
  "verify": "phase_verify",
2185
2204
  "review": "approve_pending"
@@ -2192,6 +2211,37 @@ var Orchestrator = class {
2192
2211
  saveState(this.cwd, state);
2193
2212
  return this.cmdStatus(taskId);
2194
2213
  }
2214
+ /**
2215
+ * List non-terminal workflows sorted by most recently updated
2216
+ */
2217
+ cmdList() {
2218
+ const outputPath = getOutputPath(this.cwd);
2219
+ const tasks = [];
2220
+ if (!existsSync11(outputPath)) {
2221
+ return { type: "list", tasks: [] };
2222
+ }
2223
+ const dirs = readdirSync2(outputPath, { withFileTypes: true }).filter((d) => d.isDirectory());
2224
+ for (const dir of dirs) {
2225
+ const state = loadState(this.cwd, dir.name);
2226
+ if (!state) continue;
2227
+ if (state.status === "completed" || state.status === "rejected") continue;
2228
+ tasks.push({
2229
+ taskId: state.taskId,
2230
+ description: state.description,
2231
+ status: state.status,
2232
+ currentStep: state.currentStep,
2233
+ stepIndex: state.stepIndex,
2234
+ totalSteps: state.totalSteps,
2235
+ updatedAt: state.updatedAt
2236
+ });
2237
+ }
2238
+ tasks.sort((a, b) => {
2239
+ const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
2240
+ const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
2241
+ return bTime - aTime;
2242
+ });
2243
+ return { type: "list", tasks };
2244
+ }
2195
2245
  /**
2196
2246
  * Get current workflow status
2197
2247
  */
@@ -4190,7 +4240,7 @@ Resume with: spets resume --task ${taskId}`);
4190
4240
 
4191
4241
  // src/commands/resume.ts
4192
4242
  import { select as select3 } from "@inquirer/prompts";
4193
- import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync16 } from "fs";
4243
+ import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync16 } from "fs";
4194
4244
  import { join as join11 } from "path";
4195
4245
  import { spawnSync as spawnSync3 } from "child_process";
4196
4246
  async function createPR(cwd, taskId, outputs) {
@@ -4224,7 +4274,7 @@ function listResumableTasks(cwd) {
4224
4274
  return [];
4225
4275
  }
4226
4276
  const tasks = [];
4227
- const taskDirs = readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4277
+ const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4228
4278
  const orchestrator = new Orchestrator(cwd);
4229
4279
  for (const taskId of taskDirs) {
4230
4280
  const stateFile = join11(outputsDir, taskId, ".state.json");
@@ -4574,7 +4624,7 @@ function parseGitHubCommand(comment) {
4574
4624
 
4575
4625
  // src/commands/github/git-info.ts
4576
4626
  import { execSync as execSync4 } from "child_process";
4577
- import { existsSync as existsSync19, readdirSync as readdirSync3 } from "fs";
4627
+ import { existsSync as existsSync19, readdirSync as readdirSync4 } from "fs";
4578
4628
  function getGitHubInfo2(cwd) {
4579
4629
  const config = getGitHubConfig(cwd);
4580
4630
  if (config?.owner && config?.repo) {
@@ -4610,7 +4660,7 @@ function findTaskId(cwd, owner, repo, issueOrPr) {
4610
4660
  }
4611
4661
  const outputsDir = getOutputsDir(cwd);
4612
4662
  if (existsSync19(outputsDir)) {
4613
- const taskDirs = readdirSync3(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
4663
+ const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
4614
4664
  if (taskDirs.length > 0) {
4615
4665
  return taskDirs[0];
4616
4666
  }
@@ -4620,7 +4670,7 @@ function findTaskId(cwd, owner, repo, issueOrPr) {
4620
4670
 
4621
4671
  // src/commands/github/pull-request.ts
4622
4672
  import { spawnSync as spawnSync4 } from "child_process";
4623
- import { existsSync as existsSync20, readdirSync as readdirSync4, readFileSync as readFileSync17 } from "fs";
4673
+ import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync17 } from "fs";
4624
4674
  import { join as join13 } from "path";
4625
4675
 
4626
4676
  // src/commands/github/decisions.ts
@@ -4796,7 +4846,7 @@ async function createPullRequestWithAI(config, taskId, userQuery, cwd) {
4796
4846
  const outputsDir = join13(getOutputsDir(cwd), taskId);
4797
4847
  const outputs = [];
4798
4848
  if (existsSync20(outputsDir)) {
4799
- const files = readdirSync4(outputsDir).filter((f) => f.endsWith(".md"));
4849
+ const files = readdirSync5(outputsDir).filter((f) => f.endsWith(".md"));
4800
4850
  for (const file of files) {
4801
4851
  const content = readFileSync17(join13(outputsDir, file), "utf-8");
4802
4852
  outputs.push({ step: file.replace(".md", ""), content });
@@ -5443,6 +5493,17 @@ async function orchestrateCommand(action, args) {
5443
5493
  outputJSON(result);
5444
5494
  break;
5445
5495
  }
5496
+ case "resume": {
5497
+ const taskId = args[0];
5498
+ const result = orchestrator.cmdResume(taskId);
5499
+ outputJSON(result);
5500
+ break;
5501
+ }
5502
+ case "list": {
5503
+ const result = orchestrator.cmdList();
5504
+ outputJSON(result);
5505
+ break;
5506
+ }
5446
5507
  case "knowledge-save": {
5447
5508
  const taskId = args[0];
5448
5509
  const entriesJson = args[1];
@@ -5499,7 +5560,7 @@ async function orchestrateCommand(action, args) {
5499
5560
  break;
5500
5561
  }
5501
5562
  default:
5502
- outputError(`Unknown action: ${action}. Valid actions: init, explore-done, clarify-done, execute-done, verify-done, clarified, approve, revise, reject, stop, status, knowledge-save, knowledge-skip, knowledge-extract-done`);
5563
+ outputError(`Unknown action: ${action}. Valid actions: init, explore-done, clarify-done, execute-done, verify-done, clarified, approve, revise, reject, stop, status, resume, list, knowledge-save, knowledge-skip, knowledge-extract-done`);
5503
5564
  }
5504
5565
  } catch (e) {
5505
5566
  outputError(e.message);
@@ -5674,7 +5735,7 @@ async function uiConfigCommand(action, args, options) {
5674
5735
  console.log(renderConfigJSON(cwd));
5675
5736
  return;
5676
5737
  }
5677
- const { runConfigInteractive: runConfigInteractive2 } = await import("./config-ALWYEUVJ.js");
5738
+ const { runConfigInteractive: runConfigInteractive2 } = await import("./config-FMQ5APSF.js");
5678
5739
  await runConfigInteractive2(cwd);
5679
5740
  }
5680
5741
  async function uiTasksCommand(taskId, options) {
@@ -5692,7 +5753,7 @@ async function uiTasksCommand(taskId, options) {
5692
5753
  console.log(renderTasksJSON(taskId, cwd));
5693
5754
  return;
5694
5755
  }
5695
- const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-YSWQLXLI.js");
5756
+ const { runTasksInteractive: runTasksInteractive2 } = await import("./tasks-2IAXZVDS.js");
5696
5757
  await runTasksInteractive2(cwd);
5697
5758
  }
5698
5759
  async function uiDocsCommand(docName, options) {
@@ -5714,7 +5775,7 @@ async function uiDocsCommand(docName, options) {
5714
5775
  console.log(renderDocsJSON(void 0, cwd));
5715
5776
  return;
5716
5777
  }
5717
- const { runDocsInteractive: runDocsInteractive2 } = await import("./docs-KUVQEWW4.js");
5778
+ const { runDocsInteractive: runDocsInteractive2 } = await import("./docs-FNCV4EQF.js");
5718
5779
  await runDocsInteractive2(cwd);
5719
5780
  }
5720
5781
  async function uiKnowledgeCommand(entryName, options) {
@@ -5736,13 +5797,589 @@ async function uiKnowledgeCommand(entryName, options) {
5736
5797
  console.log(renderKnowledgeJSON(void 0, cwd));
5737
5798
  return;
5738
5799
  }
5739
- const { runKnowledgeInteractive: runKnowledgeInteractive2 } = await import("./knowledge-3WOLZUWG.js");
5800
+ const { runKnowledgeInteractive: runKnowledgeInteractive2 } = await import("./knowledge-QC6464NS.js");
5740
5801
  await runKnowledgeInteractive2(cwd);
5741
5802
  }
5742
5803
 
5804
+ // src/server/index.ts
5805
+ import express from "express";
5806
+ import { createServer } from "http";
5807
+ import { join as join16, dirname as dirname7 } from "path";
5808
+ import { existsSync as existsSync23 } from "fs";
5809
+ import { fileURLToPath as fileURLToPath2 } from "url";
5810
+
5811
+ // src/server/routes/index.ts
5812
+ import { Router as Router7 } from "express";
5813
+
5814
+ // src/server/routes/tasks.ts
5815
+ import { Router } from "express";
5816
+ var tasksRouter = Router();
5817
+ tasksRouter.get("/", (req, res) => {
5818
+ try {
5819
+ const cwd = req.app.locals.cwd;
5820
+ const limit = parseInt(req.query.limit) || 20;
5821
+ const client = new DashboardClient(cwd);
5822
+ const data = client.listTasks({ limit });
5823
+ res.json(data);
5824
+ } catch (err) {
5825
+ res.status(500).json({ error: err.message });
5826
+ }
5827
+ });
5828
+ tasksRouter.get("/:id", (req, res) => {
5829
+ try {
5830
+ const cwd = req.app.locals.cwd;
5831
+ const client = new DashboardClient(cwd);
5832
+ const data = client.getTaskDetail(req.params.id);
5833
+ if (!data) {
5834
+ res.status(404).json({ error: "Task not found" });
5835
+ return;
5836
+ }
5837
+ res.json(data);
5838
+ } catch (err) {
5839
+ res.status(500).json({ error: err.message });
5840
+ }
5841
+ });
5842
+ tasksRouter.get("/:id/output/:step", (req, res) => {
5843
+ try {
5844
+ const cwd = req.app.locals.cwd;
5845
+ const doc = loadDocument(req.params.id, req.params.step, cwd);
5846
+ if (!doc) {
5847
+ res.status(404).json({ error: "Output not found" });
5848
+ return;
5849
+ }
5850
+ res.json(doc);
5851
+ } catch (err) {
5852
+ res.status(500).json({ error: err.message });
5853
+ }
5854
+ });
5855
+ tasksRouter.get("/:id/state", (req, res) => {
5856
+ try {
5857
+ const cwd = req.app.locals.cwd;
5858
+ const config = loadConfig(cwd);
5859
+ const state = getWorkflowState(req.params.id, config, cwd);
5860
+ if (!state) {
5861
+ res.status(404).json({ error: "State not found" });
5862
+ return;
5863
+ }
5864
+ const outputs = {};
5865
+ state.outputs.forEach((value, key) => {
5866
+ outputs[key] = value;
5867
+ });
5868
+ res.json({ ...state, outputs });
5869
+ } catch (err) {
5870
+ res.status(500).json({ error: err.message });
5871
+ }
5872
+ });
5873
+
5874
+ // src/server/routes/config.ts
5875
+ import { Router as Router2 } from "express";
5876
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
5877
+ var configRouter = Router2();
5878
+ configRouter.get("/", (req, res) => {
5879
+ try {
5880
+ const cwd = req.app.locals.cwd;
5881
+ const config = loadConfig(cwd);
5882
+ res.json(config);
5883
+ } catch (err) {
5884
+ res.status(500).json({ error: err.message });
5885
+ }
5886
+ });
5887
+ configRouter.get("/raw", (req, res) => {
5888
+ try {
5889
+ const cwd = req.app.locals.cwd;
5890
+ const configPath = getConfigPath(cwd);
5891
+ const raw = readFileSync18(configPath, "utf-8");
5892
+ res.json({ content: raw });
5893
+ } catch (err) {
5894
+ res.status(500).json({ error: err.message });
5895
+ }
5896
+ });
5897
+ configRouter.put("/", (req, res) => {
5898
+ try {
5899
+ const cwd = req.app.locals.cwd;
5900
+ const { content } = req.body;
5901
+ if (!content) {
5902
+ res.status(400).json({ error: "content field is required" });
5903
+ return;
5904
+ }
5905
+ const configPath = getConfigPath(cwd);
5906
+ writeFileSync9(configPath, content);
5907
+ clearConfigCache();
5908
+ const config = loadConfig(cwd);
5909
+ res.json(config);
5910
+ } catch (err) {
5911
+ res.status(500).json({ error: err.message });
5912
+ }
5913
+ });
5914
+
5915
+ // src/server/routes/steps.ts
5916
+ import { Router as Router3 } from "express";
5917
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, rmSync as rmSync2, existsSync as existsSync21 } from "fs";
5918
+ import { join as join14 } from "path";
5919
+ var stepsRouter = Router3();
5920
+ stepsRouter.get("/", (req, res) => {
5921
+ try {
5922
+ const cwd = req.app.locals.cwd;
5923
+ const config = loadConfig(cwd);
5924
+ const steps = loadAllSteps(config, cwd);
5925
+ res.json(steps);
5926
+ } catch (err) {
5927
+ res.status(500).json({ error: err.message });
5928
+ }
5929
+ });
5930
+ stepsRouter.get("/:name/template", (req, res) => {
5931
+ try {
5932
+ const cwd = req.app.locals.cwd;
5933
+ const stepsDir = getStepsDir(cwd);
5934
+ const templatePath = join14(stepsDir, req.params.name, "template.md");
5935
+ if (!existsSync21(templatePath)) {
5936
+ res.status(404).json({ error: "Template not found" });
5937
+ return;
5938
+ }
5939
+ const content = readFileSync19(templatePath, "utf-8");
5940
+ res.json({ name: req.params.name, content });
5941
+ } catch (err) {
5942
+ res.status(500).json({ error: err.message });
5943
+ }
5944
+ });
5945
+ stepsRouter.put("/:name/template", (req, res) => {
5946
+ try {
5947
+ const cwd = req.app.locals.cwd;
5948
+ const { content } = req.body;
5949
+ if (content === void 0) {
5950
+ res.status(400).json({ error: "content field is required" });
5951
+ return;
5952
+ }
5953
+ const stepsDir = getStepsDir(cwd);
5954
+ const templatePath = join14(stepsDir, req.params.name, "template.md");
5955
+ writeFileSync10(templatePath, content);
5956
+ res.json({ name: req.params.name, content });
5957
+ } catch (err) {
5958
+ res.status(500).json({ error: err.message });
5959
+ }
5960
+ });
5961
+ stepsRouter.post("/", (req, res) => {
5962
+ try {
5963
+ const cwd = req.app.locals.cwd;
5964
+ const { name, template } = req.body;
5965
+ if (!name) {
5966
+ res.status(400).json({ error: "name field is required" });
5967
+ return;
5968
+ }
5969
+ const stepsDir = getStepsDir(cwd);
5970
+ const stepDir = join14(stepsDir, name);
5971
+ mkdirSync8(stepDir, { recursive: true });
5972
+ writeFileSync10(join14(stepDir, "template.md"), template || `# ${name}
5973
+
5974
+ Define your template here.
5975
+ `);
5976
+ clearConfigCache();
5977
+ const config = loadConfig(cwd);
5978
+ if (!config.steps.includes(name)) {
5979
+ config.steps.push(name);
5980
+ const configPath = getConfigPath(cwd);
5981
+ const raw = readFileSync19(configPath, "utf-8");
5982
+ const updatedYaml = raw.replace(
5983
+ /steps:[\s\S]*?(?=\n\w|\n$|$)/,
5984
+ `steps:
5985
+ ${config.steps.map((s) => ` - ${s}`).join("\n")}`
5986
+ );
5987
+ writeFileSync10(configPath, updatedYaml);
5988
+ clearConfigCache();
5989
+ }
5990
+ res.json({ name, created: true });
5991
+ } catch (err) {
5992
+ res.status(500).json({ error: err.message });
5993
+ }
5994
+ });
5995
+ stepsRouter.delete("/:name", (req, res) => {
5996
+ try {
5997
+ const cwd = req.app.locals.cwd;
5998
+ const name = req.params.name;
5999
+ const stepsDir = getStepsDir(cwd);
6000
+ const stepDir = join14(stepsDir, name);
6001
+ if (existsSync21(stepDir)) {
6002
+ rmSync2(stepDir, { recursive: true });
6003
+ }
6004
+ clearConfigCache();
6005
+ const config = loadConfig(cwd);
6006
+ const updatedSteps = config.steps.filter((s) => s !== name);
6007
+ const configPath = getConfigPath(cwd);
6008
+ const raw = readFileSync19(configPath, "utf-8");
6009
+ const updatedYaml = raw.replace(
6010
+ /steps:[\s\S]*?(?=\n\w|\n$|$)/,
6011
+ `steps:
6012
+ ${updatedSteps.map((s) => ` - ${s}`).join("\n")}`
6013
+ );
6014
+ writeFileSync10(configPath, updatedYaml);
6015
+ clearConfigCache();
6016
+ res.json({ name, deleted: true });
6017
+ } catch (err) {
6018
+ res.status(500).json({ error: err.message });
6019
+ }
6020
+ });
6021
+ stepsRouter.put("/reorder", (req, res) => {
6022
+ try {
6023
+ const cwd = req.app.locals.cwd;
6024
+ const { steps } = req.body;
6025
+ if (!steps || !Array.isArray(steps)) {
6026
+ res.status(400).json({ error: "steps array is required" });
6027
+ return;
6028
+ }
6029
+ const configPath = getConfigPath(cwd);
6030
+ const raw = readFileSync19(configPath, "utf-8");
6031
+ const updatedYaml = raw.replace(
6032
+ /steps:[\s\S]*?(?=\n\w|\n$|$)/,
6033
+ `steps:
6034
+ ${steps.map((s) => ` - ${s}`).join("\n")}`
6035
+ );
6036
+ writeFileSync10(configPath, updatedYaml);
6037
+ clearConfigCache();
6038
+ res.json({ steps });
6039
+ } catch (err) {
6040
+ res.status(500).json({ error: err.message });
6041
+ }
6042
+ });
6043
+
6044
+ // src/server/routes/knowledge.ts
6045
+ import { Router as Router4 } from "express";
6046
+ import { unlinkSync, existsSync as existsSync22 } from "fs";
6047
+ import { join as join15 } from "path";
6048
+ var knowledgeRouter = Router4();
6049
+ knowledgeRouter.get("/", (req, res) => {
6050
+ try {
6051
+ const cwd = req.app.locals.cwd;
6052
+ const filenames = listKnowledgeFiles(cwd);
6053
+ const entries = filenames.map((name) => loadKnowledgeFile(name, cwd)).filter(Boolean);
6054
+ res.json(entries);
6055
+ } catch (err) {
6056
+ res.status(500).json({ error: err.message });
6057
+ }
6058
+ });
6059
+ knowledgeRouter.get("/guide", (req, res) => {
6060
+ try {
6061
+ const cwd = req.app.locals.cwd;
6062
+ const guide = loadGuide(cwd);
6063
+ res.json({ content: guide });
6064
+ } catch (err) {
6065
+ res.status(500).json({ error: err.message });
6066
+ }
6067
+ });
6068
+ knowledgeRouter.get("/:name", (req, res) => {
6069
+ try {
6070
+ const cwd = req.app.locals.cwd;
6071
+ const entry = loadKnowledgeFile(req.params.name, cwd);
6072
+ if (!entry) {
6073
+ res.status(404).json({ error: "Knowledge entry not found" });
6074
+ return;
6075
+ }
6076
+ res.json(entry);
6077
+ } catch (err) {
6078
+ res.status(500).json({ error: err.message });
6079
+ }
6080
+ });
6081
+ knowledgeRouter.post("/", (req, res) => {
6082
+ try {
6083
+ const cwd = req.app.locals.cwd;
6084
+ const { filename, content } = req.body;
6085
+ if (!filename || !content) {
6086
+ res.status(400).json({ error: "filename and content fields are required" });
6087
+ return;
6088
+ }
6089
+ saveKnowledge(filename, content, { taskId: "web-ui", step: "manual" }, cwd);
6090
+ const entry = loadKnowledgeFile(filename, cwd);
6091
+ res.json(entry);
6092
+ } catch (err) {
6093
+ res.status(500).json({ error: err.message });
6094
+ }
6095
+ });
6096
+ knowledgeRouter.put("/:name", (req, res) => {
6097
+ try {
6098
+ const cwd = req.app.locals.cwd;
6099
+ const { content } = req.body;
6100
+ if (content === void 0) {
6101
+ res.status(400).json({ error: "content field is required" });
6102
+ return;
6103
+ }
6104
+ saveKnowledge(req.params.name, content, { taskId: "web-ui", step: "manual" }, cwd);
6105
+ const entry = loadKnowledgeFile(req.params.name, cwd);
6106
+ res.json(entry);
6107
+ } catch (err) {
6108
+ res.status(500).json({ error: err.message });
6109
+ }
6110
+ });
6111
+ knowledgeRouter.delete("/:name", (req, res) => {
6112
+ try {
6113
+ const cwd = req.app.locals.cwd;
6114
+ const knowledgeDir = getKnowledgeDir(cwd);
6115
+ const filePath = join15(knowledgeDir, `${req.params.name}.md`);
6116
+ if (!existsSync22(filePath)) {
6117
+ res.status(404).json({ error: "Knowledge entry not found" });
6118
+ return;
6119
+ }
6120
+ unlinkSync(filePath);
6121
+ res.json({ name: req.params.name, deleted: true });
6122
+ } catch (err) {
6123
+ res.status(500).json({ error: err.message });
6124
+ }
6125
+ });
6126
+
6127
+ // src/server/routes/workflow.ts
6128
+ import { Router as Router5 } from "express";
6129
+ var workflowRouter = Router5();
6130
+ workflowRouter.post("/start", (req, res) => {
6131
+ try {
6132
+ const cwd = req.app.locals.cwd;
6133
+ const { query } = req.body;
6134
+ if (!query) {
6135
+ res.status(400).json({ error: "query field is required" });
6136
+ return;
6137
+ }
6138
+ const orchestrator = new Orchestrator(cwd);
6139
+ const response = orchestrator.cmdInit(query);
6140
+ res.json(response);
6141
+ } catch (err) {
6142
+ res.status(500).json({ error: err.message });
6143
+ }
6144
+ });
6145
+ workflowRouter.post("/:id/approve", (req, res) => {
6146
+ try {
6147
+ const cwd = req.app.locals.cwd;
6148
+ const orchestrator = new Orchestrator(cwd);
6149
+ const response = orchestrator.cmdApprove(req.params.id);
6150
+ res.json(response);
6151
+ } catch (err) {
6152
+ res.status(500).json({ error: err.message });
6153
+ }
6154
+ });
6155
+ workflowRouter.post("/:id/revise", (req, res) => {
6156
+ try {
6157
+ const cwd = req.app.locals.cwd;
6158
+ const { feedback } = req.body;
6159
+ if (!feedback) {
6160
+ res.status(400).json({ error: "feedback field is required" });
6161
+ return;
6162
+ }
6163
+ const orchestrator = new Orchestrator(cwd);
6164
+ const response = orchestrator.cmdRevise(req.params.id, feedback);
6165
+ res.json(response);
6166
+ } catch (err) {
6167
+ res.status(500).json({ error: err.message });
6168
+ }
6169
+ });
6170
+ workflowRouter.post("/:id/reject", (req, res) => {
6171
+ try {
6172
+ const cwd = req.app.locals.cwd;
6173
+ const orchestrator = new Orchestrator(cwd);
6174
+ const response = orchestrator.cmdReject(req.params.id);
6175
+ res.json(response);
6176
+ } catch (err) {
6177
+ res.status(500).json({ error: err.message });
6178
+ }
6179
+ });
6180
+ workflowRouter.post("/:id/stop", (req, res) => {
6181
+ try {
6182
+ const cwd = req.app.locals.cwd;
6183
+ const orchestrator = new Orchestrator(cwd);
6184
+ const response = orchestrator.cmdStop(req.params.id);
6185
+ res.json(response);
6186
+ } catch (err) {
6187
+ res.status(500).json({ error: err.message });
6188
+ }
6189
+ });
6190
+ workflowRouter.post("/:id/clarified", (req, res) => {
6191
+ try {
6192
+ const cwd = req.app.locals.cwd;
6193
+ const { answers } = req.body;
6194
+ if (!answers) {
6195
+ res.status(400).json({ error: "answers field is required" });
6196
+ return;
6197
+ }
6198
+ const orchestrator = new Orchestrator(cwd);
6199
+ const response = orchestrator.cmdClarified(req.params.id, answers);
6200
+ res.json(response);
6201
+ } catch (err) {
6202
+ res.status(500).json({ error: err.message });
6203
+ }
6204
+ });
6205
+ workflowRouter.get("/:id/status", (req, res) => {
6206
+ try {
6207
+ const cwd = req.app.locals.cwd;
6208
+ const orchestrator = new Orchestrator(cwd);
6209
+ const response = orchestrator.cmdStatus(req.params.id);
6210
+ res.json(response);
6211
+ } catch (err) {
6212
+ res.status(500).json({ error: err.message });
6213
+ }
6214
+ });
6215
+
6216
+ // src/server/routes/terminal.ts
6217
+ import { Router as Router6 } from "express";
6218
+ import { spawn as spawn8 } from "child_process";
6219
+ var sessions = /* @__PURE__ */ new Map();
6220
+ var terminalRouter = Router6();
6221
+ terminalRouter.post("/spawn", (req, res) => {
6222
+ try {
6223
+ const cwd = req.app.locals.cwd;
6224
+ const { command, args } = req.body;
6225
+ if (!command) {
6226
+ res.status(400).json({ error: "command field is required" });
6227
+ return;
6228
+ }
6229
+ const sessionId = `term-${Date.now().toString(36)}`;
6230
+ const child = spawn8(command, args || [], {
6231
+ cwd,
6232
+ stdio: "pipe",
6233
+ env: { ...process.env, FORCE_COLOR: "1" }
6234
+ });
6235
+ const session = { process: child, output: [] };
6236
+ sessions.set(sessionId, session);
6237
+ child.stdout?.on("data", (data) => {
6238
+ session.output.push(data.toString());
6239
+ });
6240
+ child.stderr?.on("data", (data) => {
6241
+ session.output.push(data.toString());
6242
+ });
6243
+ child.on("close", (code) => {
6244
+ session.output.push(`
6245
+ [Process exited with code ${code}]
6246
+ `);
6247
+ });
6248
+ res.json({ sessionId, pid: child.pid });
6249
+ } catch (err) {
6250
+ res.status(500).json({ error: err.message });
6251
+ }
6252
+ });
6253
+ terminalRouter.get("/:sessionId", (req, res) => {
6254
+ const session = sessions.get(req.params.sessionId);
6255
+ if (!session) {
6256
+ res.status(404).json({ error: "Session not found" });
6257
+ return;
6258
+ }
6259
+ const output = session.output.splice(0);
6260
+ const alive = !session.process.killed && session.process.exitCode === null;
6261
+ res.json({ output, alive });
6262
+ });
6263
+ terminalRouter.post("/:sessionId/input", (req, res) => {
6264
+ const session = sessions.get(req.params.sessionId);
6265
+ if (!session) {
6266
+ res.status(404).json({ error: "Session not found" });
6267
+ return;
6268
+ }
6269
+ const { input: input2 } = req.body;
6270
+ if (session.process.stdin?.writable) {
6271
+ session.process.stdin.write(input2);
6272
+ }
6273
+ res.json({ ok: true });
6274
+ });
6275
+ terminalRouter.delete("/:sessionId", (req, res) => {
6276
+ const session = sessions.get(req.params.sessionId);
6277
+ if (!session) {
6278
+ res.status(404).json({ error: "Session not found" });
6279
+ return;
6280
+ }
6281
+ session.process.kill();
6282
+ sessions.delete(req.params.sessionId);
6283
+ res.json({ killed: true });
6284
+ });
6285
+
6286
+ // src/server/routes/index.ts
6287
+ var apiRouter = Router7();
6288
+ apiRouter.use("/tasks", tasksRouter);
6289
+ apiRouter.use("/config", configRouter);
6290
+ apiRouter.use("/steps", stepsRouter);
6291
+ apiRouter.use("/knowledge", knowledgeRouter);
6292
+ apiRouter.use("/workflow", workflowRouter);
6293
+ apiRouter.use("/terminal", terminalRouter);
6294
+
6295
+ // src/server/ws/index.ts
6296
+ import { WebSocketServer, WebSocket } from "ws";
6297
+ function setupWebSocket(server) {
6298
+ const wss = new WebSocketServer({ server, path: "/ws" });
6299
+ wss.on("connection", (ws) => {
6300
+ ws.on("message", (raw) => {
6301
+ try {
6302
+ const msg = JSON.parse(raw.toString());
6303
+ handleMessage(ws, msg);
6304
+ } catch {
6305
+ }
6306
+ });
6307
+ });
6308
+ function broadcast(type, data) {
6309
+ const message = JSON.stringify({ type, data });
6310
+ wss.clients.forEach((client) => {
6311
+ if (client.readyState === WebSocket.OPEN) {
6312
+ client.send(message);
6313
+ }
6314
+ });
6315
+ }
6316
+ function handleMessage(_ws, _msg) {
6317
+ }
6318
+ return { wss, broadcast };
6319
+ }
6320
+
6321
+ // src/server/index.ts
6322
+ function startServer(options) {
6323
+ const { cwd, port } = options;
6324
+ const app = express();
6325
+ app.locals.cwd = cwd;
6326
+ app.use(express.json());
6327
+ app.use("/api", apiRouter);
6328
+ const __dirname3 = dirname7(fileURLToPath2(import.meta.url));
6329
+ const webDistPath = join16(__dirname3, "..", "web", "dist");
6330
+ const webDistDevPath = join16(__dirname3, "..", "..", "web", "dist");
6331
+ const staticPath = existsSync23(webDistPath) ? webDistPath : webDistDevPath;
6332
+ if (existsSync23(staticPath)) {
6333
+ app.use(express.static(staticPath));
6334
+ app.get("*", (_req, res) => {
6335
+ res.sendFile(join16(staticPath, "index.html"));
6336
+ });
6337
+ } else {
6338
+ app.get("*", (_req, res) => {
6339
+ res.status(200).send(
6340
+ '<html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0"><div style="text-align:center"><h1>spets</h1><p>Web UI not built yet. Run <code>npm run build:web</code> first.</p></div></body></html>'
6341
+ );
6342
+ });
6343
+ }
6344
+ app.use((err, _req, res, _next) => {
6345
+ console.error("Server error:", err.message);
6346
+ res.status(500).json({ error: err.message });
6347
+ });
6348
+ const server = createServer(app);
6349
+ setupWebSocket(server);
6350
+ server.listen(port, () => {
6351
+ console.log(`
6352
+ spets web UI running at http://localhost:${port}
6353
+ `);
6354
+ });
6355
+ return server;
6356
+ }
6357
+
6358
+ // src/commands/web.ts
6359
+ async function webCommand(options) {
6360
+ const cwd = process.cwd();
6361
+ if (!spetsExists(cwd)) {
6362
+ console.error("Error: spets not initialized in this directory.\nRun `spets init` first.");
6363
+ process.exit(1);
6364
+ }
6365
+ const port = parseInt(options.port, 10);
6366
+ if (isNaN(port)) {
6367
+ console.error("Error: Invalid port number");
6368
+ process.exit(1);
6369
+ }
6370
+ startServer({ cwd, port });
6371
+ if (options.open) {
6372
+ try {
6373
+ const open = await import("open");
6374
+ await open.default(`http://localhost:${port}`);
6375
+ } catch {
6376
+ }
6377
+ }
6378
+ }
6379
+
5743
6380
  // src/index.ts
5744
- var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
5745
- var pkg = JSON.parse(readFileSync18(join14(__dirname2, "..", "package.json"), "utf-8"));
6381
+ var __dirname2 = dirname8(fileURLToPath3(import.meta.url));
6382
+ var pkg = JSON.parse(readFileSync20(join17(__dirname2, "..", "package.json"), "utf-8"));
5746
6383
  var program = new Command();
5747
6384
  program.name("spets").description("Spec Driven Development Execution Framework").version(pkg.version).addHelpText("after", `
5748
6385
  Examples:
@@ -5767,6 +6404,7 @@ program.command("github").description("Handle GitHub Action callback (internal)"
5767
6404
  program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
5768
6405
  program.command("dashboard").description("Interactive task dashboard with drill-down").option("-t, --task <taskId>", "Show details for specific task").option("--json", "Output as JSON (for external consumers)").option("-l, --limit <number>", "Limit number of tasks shown", "20").action(dashboardCommand);
5769
6406
  program.command("orchestrate").description("Workflow orchestration (JSON API for Claude Code)").argument("<action>", "Action: init, done, clarified, approve, revise, reject, stop, status").argument("[args...]", "Action arguments").action(orchestrateCommand);
6407
+ program.command("web").description("Launch web UI for managing spets").option("-p, --port <port>", "Port number", "4777").option("--open", "Open browser automatically").action(webCommand);
5770
6408
  var ui = program.command("ui").description("Interactive TUI hub for managing spets").option("--json", "Output as JSON (for coding agents)").action(uiCommand);
5771
6409
  ui.command("config").description("View and edit configuration").argument("[action]", "Action: set").argument("[args...]", "Action arguments (field, value)").option("--json", "Output as JSON").option("--edit", "Open config in editor").action(uiConfigCommand);
5772
6410
  ui.command("tasks").description("Browse workflow tasks").argument("[taskId]", "Task ID for details").option("--json", "Output as JSON").action(uiTasksCommand);