opsx-ui 0.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.
@@ -0,0 +1,66 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ /**
5
+ * Resolves which OpenSpec project the viewer points at (design D6). Owns cwd
6
+ * detection, the `-g` global-mode decision, the init-fallback signal, and
7
+ * store discovery — isolated from rendering and from the source port so launch
8
+ * logic has a single home (SRP).
9
+ */
10
+ export class ProjectResolver {
11
+ /** True when `openspec/` exists directly under `dir`. */
12
+ static hasOpenSpec(dir) {
13
+ return existsSync(path.join(dir, "openspec"));
14
+ }
15
+ /**
16
+ * Resolve the current directory as a project. Returns `null` when no
17
+ * `openspec/` is present so the caller can offer to initialize or pick
18
+ * another project (viewer-cli: init fallback).
19
+ */
20
+ static fromCwd(cwd) {
21
+ if (!ProjectResolver.hasOpenSpec(cwd))
22
+ return null;
23
+ return ProjectResolver.forDirectory(cwd, "cwd");
24
+ }
25
+ /** Build a ProjectView for an explicitly chosen directory. */
26
+ static forDirectory(dir, source) {
27
+ return {
28
+ root: path.resolve(dir),
29
+ name: path.basename(path.resolve(dir)) || dir,
30
+ source,
31
+ storeId: null,
32
+ };
33
+ }
34
+ /** Build a ProjectView for a registered store. */
35
+ static forStore(store) {
36
+ return {
37
+ root: store.root,
38
+ name: store.name || store.id,
39
+ source: "store",
40
+ storeId: store.id,
41
+ };
42
+ }
43
+ /** Discover registered stores via `openspec store list --json`. */
44
+ static async listStores(bin = "openspec") {
45
+ const stdout = await new Promise((resolve) => {
46
+ execFile(bin, ["store", "list", "--json", "--no-color"], { maxBuffer: 8 * 1024 * 1024 }, (error, out) => resolve(error ? "" : out));
47
+ });
48
+ if (!stdout.trim())
49
+ return [];
50
+ try {
51
+ const parsed = JSON.parse(stdout);
52
+ return (parsed.stores ?? []).map((raw) => {
53
+ const s = (raw ?? {});
54
+ return {
55
+ id: String(s.id ?? ""),
56
+ name: typeof s.name === "string" ? s.name : String(s.id ?? ""),
57
+ root: typeof s.root === "string" ? s.root : String(s.path ?? ""),
58
+ };
59
+ });
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ }
65
+ }
66
+ //# sourceMappingURL=project-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-resolver.js","sourceRoot":"","sources":["../../src/server/project-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAS9C;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IAC1B,yDAAyD;IACzD,MAAM,CAAC,WAAW,CAAC,GAAW;QAC5B,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,GAAW;QACxB,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACnD,OAAO,eAAe,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,8DAA8D;IAC9D,MAAM,CAAC,YAAY,CAAC,GAAW,EAAE,MAAqB;QACpD,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;YACvB,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG;YAC7C,MAAM;YACN,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,kDAAkD;IAClD,MAAM,CAAC,QAAQ,CAAC,KAAsB;QACpC,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;YAC5B,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,KAAK,CAAC,EAAE;SAClB,CAAC;IACJ,CAAC;IAED,mEAAmE;IACnE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,UAAU;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YACnD,QAAQ,CACN,GAAG,EACH,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,EACzC,EAAE,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAC9B,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAC1C,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA2B,CAAC;YAC5D,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACvC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAA4B,CAAC;gBACjD,OAAO;oBACL,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;oBACtB,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;oBAC9D,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;iBACjE,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,75 @@
1
+ import { CliOpenSpecSource, ExecCliRunner, FilesystemSource, SnapshotService, } from "../core/index.js";
2
+ import { OpenSpecWatcher } from "./watcher.js";
3
+ /**
4
+ * A single opened project: wires the read-only source, the snapshot composer,
5
+ * and the filesystem watcher together, and keeps the latest snapshot cached so
6
+ * new clients render instantly. All mutation of state originates from disk
7
+ * events — the session never writes to the project.
8
+ */
9
+ export class ViewerSession {
10
+ project;
11
+ events;
12
+ source;
13
+ archive;
14
+ snapshots;
15
+ watcher;
16
+ current = null;
17
+ cliVersion = "unknown";
18
+ constructor(project, events, options = {}) {
19
+ this.project = project;
20
+ this.events = events;
21
+ const runner = new ExecCliRunner({ cwd: project.root });
22
+ this.source = new CliOpenSpecSource(runner, { storeId: project.storeId });
23
+ this.archive = new FilesystemSource(project.root);
24
+ this.snapshots = new SnapshotService(this.source, this.archive);
25
+ this.watcher = new OpenSpecWatcher(project.root, {
26
+ usePolling: options.usePolling,
27
+ });
28
+ }
29
+ get version() {
30
+ return this.cliVersion;
31
+ }
32
+ /** Verify OpenSpec, build the first snapshot, and begin watching. */
33
+ async init() {
34
+ this.cliVersion = await this.source.version();
35
+ const snapshot = await this.rebuild();
36
+ this.watcher.onInvalidated(() => {
37
+ void this.rebuild();
38
+ });
39
+ this.watcher.onActivity((entry) => this.events.onActivity(entry));
40
+ this.watcher.start();
41
+ return snapshot;
42
+ }
43
+ /** Re-read state and broadcast. Also the manual-refresh path (task 4.5). */
44
+ async refresh() {
45
+ return this.rebuild();
46
+ }
47
+ async rebuild() {
48
+ const snapshot = await this.snapshots.build(this.project);
49
+ this.current = snapshot;
50
+ this.events.onSnapshot(snapshot);
51
+ return snapshot;
52
+ }
53
+ getSnapshot() {
54
+ return this.current;
55
+ }
56
+ getSpec(id) {
57
+ return this.source.getSpec(id);
58
+ }
59
+ getDeltas(changeId) {
60
+ return this.source.getDeltas(changeId);
61
+ }
62
+ getStatus(changeId) {
63
+ return this.source.getStatus(changeId);
64
+ }
65
+ getArchivedChange(id) {
66
+ return this.archive.getArchivedChange(id);
67
+ }
68
+ getRawDocument(relativePath) {
69
+ return this.archive.getRawDocument(relativePath);
70
+ }
71
+ async dispose() {
72
+ await this.watcher.stop();
73
+ }
74
+ }
75
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ import express from "express";
2
+ import { createServer } from "node:http";
3
+ import { WebSocketServer, WebSocket } from "ws";
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { ViewerSession } from "./session.js";
8
+ import { ProjectResolver } from "./project-resolver.js";
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ /** dist/server/server → dist/web */
11
+ const WEB_DIST = path.resolve(__dirname, "../web");
12
+ /**
13
+ * The HTTP + WebSocket server. Owns the set of connected clients and the single
14
+ * active ViewerSession; brokers live snapshot/activity messages to browsers.
15
+ * Read-only end to end: no route writes to any project.
16
+ */
17
+ export class ViewerServer {
18
+ options;
19
+ app;
20
+ http;
21
+ wss;
22
+ clients = new Set();
23
+ session = null;
24
+ constructor(options) {
25
+ this.options = options;
26
+ this.app = express();
27
+ this.app.use(express.json());
28
+ this.registerRoutes();
29
+ this.serveStatic();
30
+ this.http = createServer(this.app);
31
+ this.wss = new WebSocketServer({ server: this.http, path: "/ws" });
32
+ this.wss.on("connection", (socket) => this.onClient(socket));
33
+ // A WSS bound to `server` re-emits the HTTP server's listen errors. `listen()`
34
+ // already handles EADDRINUSE by retrying the next port, so swallow it here to
35
+ // avoid an unhandled 'error' event crashing the process; surface anything else.
36
+ this.wss.on("error", (err) => {
37
+ if (err.code !== "EADDRINUSE")
38
+ console.error("[opsx-ui] websocket server error:", err);
39
+ });
40
+ }
41
+ async start(port) {
42
+ if (this.options.initialProject) {
43
+ await this.openProject(this.options.initialProject).catch(() => {
44
+ /* surfaced to the UI via bootstrap; don't crash the server */
45
+ });
46
+ }
47
+ const actual = await this.listen(port);
48
+ return actual;
49
+ }
50
+ onClient(socket) {
51
+ this.clients.add(socket);
52
+ socket.on("close", () => this.clients.delete(socket));
53
+ const snapshot = this.session?.getSnapshot();
54
+ if (snapshot)
55
+ this.send(socket, { type: "snapshot", payload: snapshot });
56
+ }
57
+ broadcast(message) {
58
+ const data = JSON.stringify(message);
59
+ for (const client of this.clients) {
60
+ if (client.readyState === WebSocket.OPEN)
61
+ client.send(data);
62
+ }
63
+ }
64
+ send(socket, message) {
65
+ if (socket.readyState === WebSocket.OPEN) {
66
+ socket.send(JSON.stringify(message));
67
+ }
68
+ }
69
+ /** Open (or switch to) a project, replacing any current session. */
70
+ async openProject(project) {
71
+ await this.session?.dispose();
72
+ this.session = new ViewerSession(project, {
73
+ onSnapshot: (snapshot) => this.broadcast({ type: "snapshot", payload: snapshot }),
74
+ onActivity: (entry) => this.broadcast({ type: "activity", payload: entry }),
75
+ }, { usePolling: this.options.usePolling });
76
+ await this.session.init();
77
+ }
78
+ registerRoutes() {
79
+ const api = express.Router();
80
+ api.get("/bootstrap", async (_req, res) => {
81
+ const stores = await ProjectResolver.listStores().catch(() => []);
82
+ res.json({
83
+ project: this.session?.project ?? null,
84
+ version: this.session?.version ?? null,
85
+ cwd: this.options.cwd,
86
+ cwdHasOpenSpec: ProjectResolver.hasOpenSpec(this.options.cwd),
87
+ stores,
88
+ });
89
+ });
90
+ api.post("/open", async (req, res) => {
91
+ try {
92
+ const project = await this.resolveOpenRequest(req.body ?? {});
93
+ if (!project) {
94
+ res
95
+ .status(400)
96
+ .json({ error: "No openspec/ found at that location." });
97
+ return;
98
+ }
99
+ await this.openProject(project);
100
+ res.json({ project, version: this.session?.version });
101
+ }
102
+ catch (error) {
103
+ res.status(400).json({ error: error.message });
104
+ }
105
+ });
106
+ api.post("/refresh", async (_req, res) => {
107
+ if (!this.session) {
108
+ res.status(409).json({ error: "No project open." });
109
+ return;
110
+ }
111
+ const snapshot = await this.session.refresh();
112
+ res.json(snapshot);
113
+ });
114
+ api.get("/snapshot", (_req, res) => {
115
+ const snapshot = this.session?.getSnapshot();
116
+ if (!snapshot) {
117
+ res.status(409).json({ error: "No project open." });
118
+ return;
119
+ }
120
+ res.json(snapshot);
121
+ });
122
+ api.get("/specs/:id", this.guarded(async (req, res, session) => {
123
+ res.json(await session.getSpec(req.params.id));
124
+ }));
125
+ api.get("/changes/:id/deltas", this.guarded(async (req, res, session) => {
126
+ res.json(await session.getDeltas(req.params.id));
127
+ }));
128
+ api.get("/changes/:id/status", this.guarded(async (req, res, session) => {
129
+ res.json(await session.getStatus(req.params.id));
130
+ }));
131
+ api.get("/archive/:id", this.guarded(async (req, res, session) => {
132
+ res.json(await session.getArchivedChange(req.params.id));
133
+ }));
134
+ api.get("/document", this.guarded(async (req, res, session) => {
135
+ const rel = typeof req.query.path === "string" ? req.query.path : "";
136
+ res.json(await session.getRawDocument(rel));
137
+ }));
138
+ this.app.use("/api", api);
139
+ }
140
+ /** Wrap a handler that requires an open session, with error translation. */
141
+ guarded(handler) {
142
+ return async (req, res) => {
143
+ if (!this.session) {
144
+ res.status(409).json({ error: "No project open." });
145
+ return;
146
+ }
147
+ try {
148
+ await handler(req, res, this.session);
149
+ }
150
+ catch (error) {
151
+ res.status(500).json({ error: error.message });
152
+ }
153
+ };
154
+ }
155
+ async resolveOpenRequest(body) {
156
+ if (body.storeId) {
157
+ const stores = await ProjectResolver.listStores();
158
+ const store = stores.find((s) => s.id === body.storeId);
159
+ return store ? ProjectResolver.forStore(store) : null;
160
+ }
161
+ if (body.dir) {
162
+ return ProjectResolver.hasOpenSpec(body.dir)
163
+ ? ProjectResolver.forDirectory(body.dir, "picked")
164
+ : null;
165
+ }
166
+ return ProjectResolver.fromCwd(this.options.cwd);
167
+ }
168
+ serveStatic() {
169
+ if (!existsSync(WEB_DIST))
170
+ return;
171
+ this.app.use(express.static(WEB_DIST));
172
+ // SPA fallback for client-side routes.
173
+ this.app.get("*", (req, res, next) => {
174
+ if (req.path.startsWith("/api") || req.path.startsWith("/ws")) {
175
+ next();
176
+ return;
177
+ }
178
+ res.sendFile(path.join(WEB_DIST, "index.html"));
179
+ });
180
+ }
181
+ listen(port) {
182
+ return new Promise((resolve, reject) => {
183
+ const onError = (err) => {
184
+ if (err.code === "EADDRINUSE") {
185
+ this.http.removeListener("error", onError);
186
+ resolve(this.listen(port + 1));
187
+ }
188
+ else {
189
+ reject(err);
190
+ }
191
+ };
192
+ this.http.once("error", onError);
193
+ this.http.listen(port, () => {
194
+ this.http.removeListener("error", onError);
195
+ resolve(port);
196
+ });
197
+ });
198
+ }
199
+ async stop() {
200
+ await this.session?.dispose();
201
+ for (const client of this.clients)
202
+ client.close();
203
+ await new Promise((resolve) => this.wss.close(() => resolve()));
204
+ await new Promise((resolve) => this.http.close(() => resolve()));
205
+ }
206
+ }
207
+ //# sourceMappingURL=viewer-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer-server.js","sourceRoot":"","sources":["../../src/server/viewer-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import path from "node:path";
2
+ import chokidar from "chokidar";
3
+ import { randomUUID } from "node:crypto";
4
+ /**
5
+ * Watches a project's `openspec/` tree and signals two things (design D5):
6
+ * - `onInvalidated`: debounced — "state changed, rebuild the snapshot".
7
+ * - `onActivity`: per raw event — an entry for the live activity feed.
8
+ * Read-only: it only observes; it never writes.
9
+ */
10
+ export class OpenSpecWatcher {
11
+ watcher = null;
12
+ openspecDir;
13
+ debounceMs;
14
+ usePolling;
15
+ debounceTimer = null;
16
+ invalidatedListeners = new Set();
17
+ activityListeners = new Set();
18
+ constructor(projectRoot, options = {}) {
19
+ this.openspecDir = path.join(projectRoot, "openspec");
20
+ this.debounceMs = options.debounceMs ?? 150;
21
+ this.usePolling = options.usePolling ?? false;
22
+ }
23
+ onInvalidated(listener) {
24
+ this.invalidatedListeners.add(listener);
25
+ }
26
+ onActivity(listener) {
27
+ this.activityListeners.add(listener);
28
+ }
29
+ start() {
30
+ if (this.watcher)
31
+ return;
32
+ this.watcher = chokidar.watch(this.openspecDir, {
33
+ ignoreInitial: true,
34
+ usePolling: this.usePolling,
35
+ awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 },
36
+ });
37
+ this.watcher
38
+ .on("add", (p) => this.handle(p, "created"))
39
+ .on("change", (p) => this.handle(p, "modified"))
40
+ .on("unlink", (p) => this.handle(p, "removed"));
41
+ }
42
+ async stop() {
43
+ if (this.debounceTimer)
44
+ clearTimeout(this.debounceTimer);
45
+ await this.watcher?.close();
46
+ this.watcher = null;
47
+ }
48
+ handle(absPath, kind) {
49
+ const entry = this.deriveActivity(absPath, kind);
50
+ if (entry) {
51
+ for (const listener of this.activityListeners)
52
+ listener(entry);
53
+ }
54
+ this.scheduleInvalidation();
55
+ }
56
+ scheduleInvalidation() {
57
+ if (this.debounceTimer)
58
+ clearTimeout(this.debounceTimer);
59
+ this.debounceTimer = setTimeout(() => {
60
+ this.debounceTimer = null;
61
+ for (const listener of this.invalidatedListeners)
62
+ listener();
63
+ }, this.debounceMs);
64
+ }
65
+ /** Translate a changed path into a human-legible feed entry. */
66
+ deriveActivity(absPath, kind) {
67
+ const rel = path.relative(this.openspecDir, absPath).split(path.sep);
68
+ const base = {
69
+ id: randomUUID(),
70
+ timestamp: new Date().toISOString(),
71
+ kind,
72
+ };
73
+ const verb = kind === "created" ? "added" : kind === "removed" ? "removed" : "updated";
74
+ if (rel[0] === "changes" && rel[1] === "archive" && rel[2]) {
75
+ return {
76
+ ...base,
77
+ targetType: "change",
78
+ targetId: rel[2],
79
+ detail: kind === "created" ? "Change archived" : `Archive ${verb}`,
80
+ };
81
+ }
82
+ if (rel[0] === "changes" && rel[1]) {
83
+ const change = rel[1];
84
+ const artifact = rel[rel.length - 1];
85
+ if (rel.length <= 2 || artifact === "README.md") {
86
+ return {
87
+ ...base,
88
+ targetType: "change",
89
+ targetId: change,
90
+ detail: `Change ${verb}`,
91
+ };
92
+ }
93
+ if (rel[2] === "specs") {
94
+ return {
95
+ ...base,
96
+ targetType: "artifact",
97
+ targetId: change,
98
+ detail: `Delta ${verb}: ${rel[3] ?? "spec"}`,
99
+ };
100
+ }
101
+ return {
102
+ ...base,
103
+ targetType: "artifact",
104
+ targetId: change,
105
+ detail: `${artifact.replace(/\.md$/, "")} ${verb}`,
106
+ };
107
+ }
108
+ if (rel[0] === "specs" && rel[1]) {
109
+ return {
110
+ ...base,
111
+ targetType: "spec",
112
+ targetId: rel[1],
113
+ detail: `Spec ${verb}`,
114
+ };
115
+ }
116
+ return null;
117
+ }
118
+ }
119
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/server/watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Wire contracts shared by the server (which produces them via the
3
+ * OpenSpecSource port) and the web app (which renders them). These are the
4
+ * ONLY shape the UI knows about — it never sees raw OpenSpec CLI output.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../src/shared/contracts.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}