loom-spec 0.1.0 → 0.2.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/README.md +129 -21
- package/dist/cli/index.js +15 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +21 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/installMcp.d.ts +4 -0
- package/dist/cli/installMcp.js +23 -0
- package/dist/cli/installMcp.js.map +1 -0
- package/dist/cli/mcpConfig.d.ts +21 -0
- package/dist/cli/mcpConfig.js +56 -0
- package/dist/cli/mcpConfig.js.map +1 -0
- package/dist/mcp/server.js +207 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/app.js +50 -2
- package/dist/server/app.js.map +1 -1
- package/dist/server/fileOps.d.ts +12 -0
- package/dist/server/fileOps.js +51 -0
- package/dist/server/fileOps.js.map +1 -1
- package/dist/server/watch.d.ts +1 -1
- package/dist/server/watch.js +5 -0
- package/dist/server/watch.js.map +1 -1
- package/dist/types/timeline.d.ts +97 -0
- package/dist/types/timeline.js +7 -0
- package/dist/types/timeline.js.map +1 -0
- package/dist/validate.d.ts +3 -1
- package/dist/validate.js +15 -0
- package/dist/validate.js.map +1 -1
- package/dist/view/assets/{index-Cst6HUW5.css → index-B18EbiQt.css} +1 -1
- package/dist/view/assets/index-DAM9J2qS.js +225 -0
- package/dist/view/index.html +2 -2
- package/package.json +1 -1
- package/schema/timeline.schema.json +135 -0
- package/templates/.claude/skills/loom-spec/SKILL.md +88 -0
- package/dist/view/assets/index-jlp2cU4j.js +0 -205
package/README.md
CHANGED
|
@@ -2,34 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
> A node-based architecture spec that lives in your repo. AI-readable, AI-writable, git-diffable.
|
|
4
4
|
|
|
5
|
-
`loom-spec` keeps a structured visual spec of your application's architecture **inside your repo**, designed to be edited by both humans (in a browser-based node editor) and AI coding agents (directly via JSON files).
|
|
5
|
+
`loom-spec` keeps a structured visual spec of your application's architecture **inside your repo**, designed to be edited by both humans (in a browser-based node editor) and AI coding agents (directly via JSON files or MCP tool calls).
|
|
6
6
|
|
|
7
|
-
It's a spec layer, not an execution layer. The nodes don't run
|
|
7
|
+
It's a spec layer, not an execution layer. The nodes describe; they don't run.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
- **Architecture drift.** Mermaid diagrams in `docs/` go stale the moment you refactor.
|
|
12
|
+
- **Agents losing the forest for the trees.** An agent grepping through `src/` doesn't see the system. Every session rebuilds the mental model from scratch.
|
|
13
|
+
- **The mental-model gap.** People who think in signal flow but don't read code well get cut out.
|
|
14
|
+
|
|
15
|
+
`loom-spec` is one canonical, machine-readable file-set under `.loom/` that says what exists and how it connects. Humans edit it visually. The agent reads it before implementing and updates it when code changes. `code_refs` anchor each node to actual source, so [`loom-spec validate`](#loom-spec-validate---root-dir---json) catches drift instead of letting it accumulate.
|
|
16
|
+
|
|
17
|
+
## Install + first run
|
|
10
18
|
|
|
11
19
|
```bash
|
|
12
|
-
|
|
13
|
-
npx loom-spec init
|
|
14
|
-
npx loom-spec view
|
|
20
|
+
cd your-project
|
|
21
|
+
npx loom-spec init --mcp # scaffolds .loom/, the agent skill, and .mcp.json
|
|
22
|
+
npx loom-spec view # opens the editor on http://localhost:7777
|
|
15
23
|
```
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
`--mcp` is optional but recommended — it auto-registers the MCP server for Claude Code (and other MCP-capable agents). Skip it if you want to wire that up manually later via `npx loom-spec install-mcp`.
|
|
26
|
+
|
|
27
|
+
If you prefer it as a dev dependency:
|
|
18
28
|
|
|
19
29
|
```bash
|
|
20
30
|
npm install --save-dev loom-spec
|
|
21
31
|
```
|
|
22
32
|
|
|
23
|
-
Then in `package.json`:
|
|
24
|
-
|
|
25
33
|
```json
|
|
26
34
|
{
|
|
27
35
|
"scripts": {
|
|
28
|
-
"loom": "loom-spec view"
|
|
36
|
+
"loom": "loom-spec view",
|
|
37
|
+
"loom:check": "loom-spec validate"
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
```
|
|
32
41
|
|
|
42
|
+
## A typical workflow
|
|
43
|
+
|
|
44
|
+
1. **Sketch the high-level architecture once.** Open `loom-spec view`, click **+ Add** to drop services, data stores, and UI components onto the canvas. Connect them with edges. Mark nodes as `planned` if you haven't built them yet, `implemented` once the code exists.
|
|
45
|
+
2. **Let the agent grow it.** With Claude Code (or any MCP-capable agent) in the same repo, tell it what to build: *"add a payments service that the checkout flow calls"*. The agent calls `loom_add_node` and `loom_add_edge`, then writes the actual code. Your open editor updates live via SSE.
|
|
46
|
+
3. **Anchor nodes to code.** When a feature is done, the agent (or you) adds `code_refs`: `{ "path": "src/server/payments.ts", "symbol": "chargeCard" }`. This is what makes drift detection work.
|
|
47
|
+
4. **Catch drift in CI.** Add `loom-spec validate` as a pre-commit hook or CI step. It exits non-zero if any `code_refs` point at missing files or unresolved symbols.
|
|
48
|
+
5. **Stale, don't delete.** When the underlying code goes away, the agent marks the node `status: stale` instead of deleting it. Humans review staleness — the architectural history stays.
|
|
49
|
+
|
|
50
|
+
## What lives in your repo
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
.loom/
|
|
54
|
+
├── README.md Why this directory exists; for humans.
|
|
55
|
+
├── node-types.json The vocabulary: ui, service, data, event, external (plus your customs).
|
|
56
|
+
└── diagrams/
|
|
57
|
+
└── overview.flow.json { nodes, edges, groups }
|
|
58
|
+
|
|
59
|
+
.claude/
|
|
60
|
+
└── skills/
|
|
61
|
+
└── loom-spec/
|
|
62
|
+
└── SKILL.md Tells Claude Code (or any Agent-Skills-aware tool)
|
|
63
|
+
when and how to maintain the spec, with five
|
|
64
|
+
worked examples.
|
|
65
|
+
|
|
66
|
+
.mcp.json Registers the MCP server, if you ran init --mcp.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Nothing of the `loom-spec` package itself is committed — the npm install lives in `node_modules/`. Only the spec and the skill are tracked.
|
|
70
|
+
|
|
33
71
|
## Commands
|
|
34
72
|
|
|
35
73
|
### `loom-spec init [--path <dir>] [--force]`
|
|
@@ -45,10 +83,24 @@ Writes:
|
|
|
45
83
|
|
|
46
84
|
Refuses to overwrite an existing `.loom/` unless `--force`.
|
|
47
85
|
|
|
86
|
+
Add `--mcp` to also register the MCP server in `.mcp.json` (idempotent merge — existing entries are preserved). Or run `npx loom-spec install-mcp` to register it after the fact.
|
|
87
|
+
|
|
48
88
|
### `loom-spec view [--root <dir>] [--port <n>]`
|
|
49
89
|
|
|
50
90
|
Starts a local browser editor. Walks up from `--root` (default: cwd) to find the nearest `.loom/`. Opens on port 7777 by default.
|
|
51
91
|
|
|
92
|
+
In the editor you can:
|
|
93
|
+
|
|
94
|
+
- Drag nodes; edits debounce and write to disk within ~500ms
|
|
95
|
+
- Click a node or edge to inspect and edit fields, code refs, tags, type-specific properties
|
|
96
|
+
- Drag from a node's right handle to another node to create an edge
|
|
97
|
+
- Use the **+ Add** menu in the top bar to add a new node by type
|
|
98
|
+
- Use the diagram switcher (top-left dropdown) to navigate between diagrams or create new ones
|
|
99
|
+
- Use the "Drill into" chevron on any node or group with `drill_down` set to jump to a sub-diagram
|
|
100
|
+
- Toggle light/dark theme; preference is persisted
|
|
101
|
+
|
|
102
|
+
External edits to the JSON files (e.g. by an AI agent) propagate to the open UI live via Server-Sent Events — no reload needed.
|
|
103
|
+
|
|
52
104
|
### `loom-spec validate [--root <dir>] [--json]`
|
|
53
105
|
|
|
54
106
|
Checks every diagram for schema validity plus **code-ref drift**: missing files, missing symbols, out-of-range line ranges. Skips nodes marked `planned` or `deprecated` (their code may legitimately not exist). Exit code is non-zero if any issue is found — useful as a CI step or pre-commit hook.
|
|
@@ -69,12 +121,14 @@ Starts a **Model Context Protocol** server on stdio. Wire it into Claude Code (o
|
|
|
69
121
|
"mcpServers": {
|
|
70
122
|
"loom-spec": {
|
|
71
123
|
"command": "npx",
|
|
72
|
-
"args": ["loom-spec", "mcp"]
|
|
124
|
+
"args": ["-y", "loom-spec", "mcp"]
|
|
73
125
|
}
|
|
74
126
|
}
|
|
75
127
|
}
|
|
76
128
|
```
|
|
77
129
|
|
|
130
|
+
If you'd rather not hand-edit, `npx loom-spec install-mcp` writes this entry into `.mcp.json` for you (merging with any existing servers, idempotent).
|
|
131
|
+
|
|
78
132
|
The server exposes semantic tools that validate against the schema before writing, more token-efficient than re-reading and re-writing the JSON on every change:
|
|
79
133
|
|
|
80
134
|
- `loom_list_diagrams`, `loom_read_diagram`, `loom_read_node_types`
|
|
@@ -82,17 +136,9 @@ The server exposes semantic tools that validate against the schema before writin
|
|
|
82
136
|
- `loom_add_edge`, `loom_delete_edge`
|
|
83
137
|
- `loom_validate` (same drift check as the CLI)
|
|
84
138
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- Drag nodes; edits debounce and write to disk within ~500ms
|
|
88
|
-
- Click a node or edge to inspect and edit fields, code refs, tags, type-specific properties
|
|
89
|
-
- Drag from a node's right handle to another node to create an edge
|
|
90
|
-
- Use the "+ Add" menu in the top bar to add a new node by type
|
|
91
|
-
- Use the diagram switcher (top-left dropdown) to navigate between diagrams or create new ones
|
|
92
|
-
- Use the "Drill into" chevron on any node or group with `drill_down` set to jump to a sub-diagram
|
|
93
|
-
- Toggle light/dark theme; preference is persisted
|
|
139
|
+
### `loom-spec install-mcp [--path <dir>]`
|
|
94
140
|
|
|
95
|
-
|
|
141
|
+
Writes the MCP-server entry into `.mcp.json` without touching anything else. Idempotent (safe to run repeatedly) and non-destructive (other MCP servers and unrelated top-level keys are preserved).
|
|
96
142
|
|
|
97
143
|
## File format
|
|
98
144
|
|
|
@@ -153,6 +199,68 @@ Edge kinds: `request`, `event`, `data-read`, `data-write`, `signal`, `dependency
|
|
|
153
199
|
|
|
154
200
|
Full JSON Schemas ship with the package — see `schema/diagram.schema.json` and `schema/node-types.schema.json`.
|
|
155
201
|
|
|
202
|
+
### Adding custom node types
|
|
203
|
+
|
|
204
|
+
`node-types.json` is yours to edit. Add a type for whatever domain you're modelling. A worker with typed ports:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"types": {
|
|
209
|
+
"worker": {
|
|
210
|
+
"label": "Worker",
|
|
211
|
+
"color": "#fb923c",
|
|
212
|
+
"icon": "server",
|
|
213
|
+
"fields": [
|
|
214
|
+
{ "name": "queue", "type": "string", "required": true },
|
|
215
|
+
{ "name": "concurrency", "type": "number" }
|
|
216
|
+
],
|
|
217
|
+
"ports": {
|
|
218
|
+
"in": [{ "name": "jobs", "signal": "data" }],
|
|
219
|
+
"out": [{ "name": "results", "signal": "data" }, { "name": "errors", "signal": "data" }]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Once that's saved, the **+ Add** menu shows "Worker"; new worker nodes render with three labeled handles instead of one generic one. Edges can target a specific port via `from: "worker-1:results"`.
|
|
227
|
+
|
|
228
|
+
### Drill-down between diagrams
|
|
229
|
+
|
|
230
|
+
For LangGraph-style multi-step agents or anything with non-trivial internal flow: one node in the overview, plus a sub-diagram with the steps inside.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
// overview.flow.json
|
|
234
|
+
{
|
|
235
|
+
"id": "agent",
|
|
236
|
+
"type": "service",
|
|
237
|
+
"label": "Agent",
|
|
238
|
+
"drill_down": "agent-internals",
|
|
239
|
+
"code_refs": [{ "path": "agent.py" }]
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
// agent-internals.flow.json
|
|
245
|
+
{
|
|
246
|
+
"nodes": [
|
|
247
|
+
{ "id": "decide", "type": "service", "label": "decide_next_step",
|
|
248
|
+
"code_refs": [{ "path": "agent.py", "symbol": "decide_next_step" }] },
|
|
249
|
+
{ "id": "call-tool", "type": "service", "label": "call_tool",
|
|
250
|
+
"code_refs": [{ "path": "agent.py", "symbol": "call_tool" }] },
|
|
251
|
+
{ "id": "format", "type": "service", "label": "format_response",
|
|
252
|
+
"code_refs": [{ "path": "agent.py", "symbol": "format_response" }] }
|
|
253
|
+
],
|
|
254
|
+
"edges": [
|
|
255
|
+
{ "id": "e1", "from": "decide", "to": "call-tool", "kind": "control", "label": "if tool needed" },
|
|
256
|
+
{ "id": "e2", "from": "call-tool", "to": "format", "kind": "control" },
|
|
257
|
+
{ "id": "e3", "from": "decide", "to": "format", "kind": "control", "label": "if final" }
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Click the chevron on the overview's `agent` node to navigate in.
|
|
263
|
+
|
|
156
264
|
## How AI agents use it
|
|
157
265
|
|
|
158
266
|
`loom-spec init` writes a `SKILL.md` to `.claude/skills/loom-spec/` following the [Agent Skills open standard](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview). Claude Code (and other tools that adopt the convention) auto-discovers it.
|
package/dist/cli/index.js
CHANGED
|
@@ -3,12 +3,18 @@ import { runInit } from "./init.js";
|
|
|
3
3
|
import { runView } from "./view.js";
|
|
4
4
|
import { runValidate } from "./validate.js";
|
|
5
5
|
import { runMcp } from "./mcp.js";
|
|
6
|
+
import { runInstallMcp } from "./installMcp.js";
|
|
6
7
|
const HELP = `loom-spec — node-based architecture spec for your repo
|
|
7
8
|
|
|
8
9
|
Usage:
|
|
9
|
-
loom-spec init [--path <dir>] [--force]
|
|
10
|
+
loom-spec init [--path <dir>] [--force] [--mcp]
|
|
10
11
|
Scaffold .loom/ and .claude/skills/loom-spec/ in the target directory.
|
|
11
|
-
|
|
12
|
+
With --mcp, also register the MCP server in .mcp.json (merging
|
|
13
|
+
with any existing entries). Defaults to current working directory.
|
|
14
|
+
|
|
15
|
+
loom-spec install-mcp [--path <dir>]
|
|
16
|
+
Register the loom-spec MCP server in .mcp.json without touching
|
|
17
|
+
anything else. Idempotent — safe to run multiple times.
|
|
12
18
|
|
|
13
19
|
loom-spec view [--root <dir>] [--port <n>] [--dev]
|
|
14
20
|
Start the local browser editor. Walks up from --root (default: cwd)
|
|
@@ -61,6 +67,13 @@ async function main() {
|
|
|
61
67
|
await runInit({
|
|
62
68
|
path: flags.path ?? process.cwd(),
|
|
63
69
|
force: Boolean(flags.force),
|
|
70
|
+
mcp: Boolean(flags.mcp),
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (subcommand === "install-mcp") {
|
|
75
|
+
await runInstallMcp({
|
|
76
|
+
path: flags.path ?? process.cwd(),
|
|
64
77
|
});
|
|
65
78
|
return;
|
|
66
79
|
}
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/init.d.ts
CHANGED
package/dist/cli/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile, readdir, stat } from "node:fs/promises";
|
|
2
2
|
import { resolve, dirname, join, relative } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { installMcpEntry } from "./mcpConfig.js";
|
|
4
5
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
5
6
|
// Templates ship alongside dist/ in the package: dist/cli/init.js → ../../templates/
|
|
6
7
|
const templatesRoot = resolve(here, "../../templates");
|
|
@@ -60,10 +61,30 @@ export async function runInit(args) {
|
|
|
60
61
|
for (const f of written) {
|
|
61
62
|
console.log(` ${relative(target, f)}`);
|
|
62
63
|
}
|
|
64
|
+
if (args.mcp) {
|
|
65
|
+
console.log();
|
|
66
|
+
try {
|
|
67
|
+
const report = await installMcpEntry(target);
|
|
68
|
+
const rel = relative(target, report.path);
|
|
69
|
+
if (report.action === "unchanged") {
|
|
70
|
+
console.log(`MCP entry already present in ${rel}.`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(`${report.action === "created" ? "Created" : "Updated"} ${rel} with the loom-spec MCP server.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
console.error(`error registering MCP entry: ${e.message}`);
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
63
81
|
console.log();
|
|
64
82
|
console.log("Next steps:");
|
|
65
83
|
console.log(" 1. Run `npx loom-spec view` to open the editor.");
|
|
66
84
|
console.log(" 2. Edit `.loom/node-types.json` to add project-specific node types.");
|
|
67
85
|
console.log(" 3. Start populating `.loom/diagrams/overview.flow.json`.");
|
|
86
|
+
if (!args.mcp) {
|
|
87
|
+
console.log(" 4. To register the MCP server: re-run with `--mcp`, or `npx loom-spec install-mcp`.");
|
|
88
|
+
}
|
|
68
89
|
}
|
|
69
90
|
//# sourceMappingURL=init.js.map
|
package/dist/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAQjD,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,qFAAqF;AACrF,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;AAEvD,KAAK,UAAU,OAAO,CAAC,MAAc,EAAE,OAAe,EAAE,KAAc;IACpE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,IAAI,MAAM,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjB,MAAM,GAAG,IAAI,CAAC;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,gBAAgB;YAClB,CAAC;YACD,IAAI,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;gBACrB,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;gBACxC,SAAS;YACX,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAC;IAEnD,2CAA2C;IAC3C,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACrC,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;IAED,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CACX,0HAA0H,CAC3H,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAEjE,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;QAC1E,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,MAAM,WAAW,CAAC,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;YAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,OAAO,CAAC,GAAG,CAAC,gCAAgC,GAAG,GAAG,CAAC,CAAC;YACtD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,iCAAiC,CAC/F,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,gCAAiC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;YACtE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;IAC1E,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CACT,uFAAuF,CACxF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolve, relative } from "node:path";
|
|
2
|
+
import { installMcpEntry } from "./mcpConfig.js";
|
|
3
|
+
export async function runInstallMcp(args) {
|
|
4
|
+
const target = resolve(args.path);
|
|
5
|
+
try {
|
|
6
|
+
const report = await installMcpEntry(target);
|
|
7
|
+
const rel = relative(process.cwd(), report.path);
|
|
8
|
+
if (report.action === "unchanged") {
|
|
9
|
+
console.log(`MCP entry already up to date in ${rel}.`);
|
|
10
|
+
}
|
|
11
|
+
else if (report.action === "created") {
|
|
12
|
+
console.log(`Created ${rel} with the loom-spec MCP server.`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.log(`Updated ${rel} — added loom-spec MCP server entry.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
console.error(`error: ${e.message}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=installMcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installMcp.js","sourceRoot":"","sources":["../../src/cli/installMcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMjD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAoB;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,mCAAmC,GAAG,GAAG,CAAC,CAAC;QACzD,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,iCAAiC,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,sCAAsC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,UAAW,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface McpServerEntry {
|
|
2
|
+
command: string;
|
|
3
|
+
args?: string[];
|
|
4
|
+
env?: Record<string, string>;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Idempotently register the loom-spec MCP server in <projectPath>/.mcp.json.
|
|
9
|
+
*
|
|
10
|
+
* - If the file doesn't exist, creates it with just our entry.
|
|
11
|
+
* - If it exists and parses, merges our entry into mcpServers. Other servers
|
|
12
|
+
* and top-level keys are preserved.
|
|
13
|
+
* - If the existing entry is identical, reports "unchanged" and exits clean.
|
|
14
|
+
* - If the file exists but doesn't parse, errors out without writing.
|
|
15
|
+
*
|
|
16
|
+
* Returns a small report describing what happened.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installMcpEntry(projectPath: string): Promise<{
|
|
19
|
+
path: string;
|
|
20
|
+
action: "created" | "updated" | "unchanged";
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFile, writeFile, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve, relative } from "node:path";
|
|
3
|
+
const SERVER_NAME = "loom-spec";
|
|
4
|
+
function loomSpecEntry() {
|
|
5
|
+
return {
|
|
6
|
+
command: "npx",
|
|
7
|
+
args: ["-y", "loom-spec", "mcp"],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Idempotently register the loom-spec MCP server in <projectPath>/.mcp.json.
|
|
12
|
+
*
|
|
13
|
+
* - If the file doesn't exist, creates it with just our entry.
|
|
14
|
+
* - If it exists and parses, merges our entry into mcpServers. Other servers
|
|
15
|
+
* and top-level keys are preserved.
|
|
16
|
+
* - If the existing entry is identical, reports "unchanged" and exits clean.
|
|
17
|
+
* - If the file exists but doesn't parse, errors out without writing.
|
|
18
|
+
*
|
|
19
|
+
* Returns a small report describing what happened.
|
|
20
|
+
*/
|
|
21
|
+
export async function installMcpEntry(projectPath) {
|
|
22
|
+
const configPath = resolve(projectPath, ".mcp.json");
|
|
23
|
+
let existing = null;
|
|
24
|
+
try {
|
|
25
|
+
await stat(configPath);
|
|
26
|
+
const raw = await readFile(configPath, "utf8");
|
|
27
|
+
try {
|
|
28
|
+
existing = JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
throw new Error(`${relative(process.cwd(), configPath)} exists but is not valid JSON: ${e.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
const code = e.code;
|
|
36
|
+
if (code && code !== "ENOENT")
|
|
37
|
+
throw e;
|
|
38
|
+
// fall through: existing remains null
|
|
39
|
+
}
|
|
40
|
+
const desired = loomSpecEntry();
|
|
41
|
+
const config = existing ?? {};
|
|
42
|
+
const servers = config.mcpServers ?? {};
|
|
43
|
+
const current = servers[SERVER_NAME];
|
|
44
|
+
// Compare normalized JSON to detect identical entries
|
|
45
|
+
if (current && JSON.stringify(current) === JSON.stringify(desired)) {
|
|
46
|
+
return { path: configPath, action: "unchanged" };
|
|
47
|
+
}
|
|
48
|
+
servers[SERVER_NAME] = desired;
|
|
49
|
+
config.mcpServers = servers;
|
|
50
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
51
|
+
return {
|
|
52
|
+
path: configPath,
|
|
53
|
+
action: existing ? "updated" : "created",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=mcpConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpConfig.js","sourceRoot":"","sources":["../../src/cli/mcpConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAc9C,MAAM,WAAW,GAAG,WAAW,CAAC;AAEhC,SAAS,aAAa;IACpB,OAAO;QACL,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAAmB;IAIvD,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAErD,IAAI,QAAQ,GAAqB,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC/C,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;QAC1C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,kCAAmC,CAAW,CAAC,OAAO,EAAE,CAC/F,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,GAAI,CAA2B,CAAC,IAAI,CAAC;QAC/C,IAAI,IAAI,IAAI,IAAI,KAAK,QAAQ;YAAE,MAAM,CAAC,CAAC;QACvC,sCAAsC;IACxC,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAChC,MAAM,MAAM,GAAc,QAAQ,IAAI,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAErC,sDAAsD;IACtD,IAAI,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QACnE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACnD,CAAC;IAED,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC;IAC/B,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC;IAE5B,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5E,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;KACzC,CAAC;AACJ,CAAC"}
|