@diagrammo/dgmo 0.34.0 → 0.36.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/.claude/commands/dgmo.md +41 -43
- package/.github/copilot-instructions.md +1 -1
- package/README.md +7 -1
- package/SKILL.md +12 -7
- package/dist/advanced.cjs +460 -463
- package/dist/advanced.d.cts +2 -51
- package/dist/advanced.d.ts +2 -51
- package/dist/advanced.js +460 -464
- package/dist/auto.cjs +461 -140
- package/dist/auto.js +91 -91
- package/dist/auto.mjs +461 -140
- package/dist/cli.cjs +143 -326
- package/dist/index.cjs +460 -139
- package/dist/index.js +460 -139
- package/docs/ai-integration.md +47 -25
- package/docs/language-reference.md +2 -2
- package/package.json +2 -2
- package/src/advanced.ts +0 -12
- package/src/c4/parser.ts +15 -6
- package/src/chart.ts +1 -1
- package/src/class/parser.ts +6 -2
- package/src/cli-banner.ts +2 -3
- package/src/cli.ts +539 -1223
- package/src/cycle/parser.ts +4 -1
- package/src/dgmo-router.ts +16 -2
- package/src/diagnostics.ts +0 -0
- package/src/echarts.ts +5 -1
- package/src/er/parser.ts +5 -1
- package/src/gantt/parser.ts +13 -4
- package/src/graph/flowchart-parser.ts +5 -1
- package/src/graph/state-parser.ts +5 -1
- package/src/infra/parser.ts +10 -6
- package/src/journey-map/parser.ts +5 -2
- package/src/kanban/parser.ts +6 -2
- package/src/map/context-labels.ts +29 -19
- package/src/map/layout.ts +403 -108
- package/src/map/legend-band.ts +28 -0
- package/src/map/parser.ts +13 -4
- package/src/map/renderer.ts +74 -23
- package/src/map/resolver.ts +2 -2
- package/src/org/parser.ts +8 -2
- package/src/pyramid/parser.ts +4 -1
- package/src/raci/parser.ts +4 -1
- package/src/ring/parser.ts +4 -1
- package/src/sequence/parser.ts +6 -3
- package/src/sitemap/parser.ts +12 -3
- package/src/tech-radar/parser.ts +6 -3
- package/src/utils/parsing.ts +2 -2
- package/src/utils/tag-groups.ts +3 -3
- package/src/visualizations/parse.ts +9 -3
- package/src/migrate/data-comma.ts +0 -117
- package/src/migrate/embedded.ts +0 -238
- package/src/migrate/index.ts +0 -295
- package/src/migrate/line-classifier.ts +0 -128
- package/src/migrate/line-transformer.ts +0 -218
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
3
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
4
4
|
import { homedir, platform } from 'node:os';
|
|
5
|
-
import { resolve, join, basename, extname } from 'node:path';
|
|
6
|
-
import { createInterface } from 'node:readline';
|
|
5
|
+
import { resolve, join, dirname, basename, extname } from 'node:path';
|
|
7
6
|
import { Resvg } from '@resvg/resvg-js';
|
|
8
7
|
import { render } from './render';
|
|
9
8
|
import {
|
|
@@ -18,13 +17,6 @@ import { DEFAULT_FONT_NAME } from './fonts';
|
|
|
18
17
|
import { encodeDiagramUrl } from './sharing';
|
|
19
18
|
import { resolveOrgImports } from './org/resolver';
|
|
20
19
|
import { normalizePertSourceForShare } from './pert/share-normalize';
|
|
21
|
-
import {
|
|
22
|
-
collectDgmoFiles,
|
|
23
|
-
collectEmbeddedFiles,
|
|
24
|
-
formatLineDiff,
|
|
25
|
-
migrateFile,
|
|
26
|
-
} from './migrate';
|
|
27
|
-
import { migrateEmbedded } from './migrate/embedded';
|
|
28
20
|
import { renderBanner } from './cli-banner';
|
|
29
21
|
import { searchMapLocations } from './map/completion';
|
|
30
22
|
import { loadMapData } from './map/load-data';
|
|
@@ -34,430 +26,37 @@ const PALETTES = getAvailablePalettes().map((p) => p.id);
|
|
|
34
26
|
|
|
35
27
|
const THEMES = ['light', 'dark', 'transparent'] as const;
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
### Step 2 — Install the MCP server (if missing)
|
|
58
|
-
|
|
59
|
-
\`\`\`bash
|
|
60
|
-
which dgmo-mcp || npm install -g @diagrammo/dgmo-mcp
|
|
61
|
-
\`\`\`
|
|
62
|
-
|
|
63
|
-
### Step 3 — Configure the MCP server
|
|
64
|
-
|
|
65
|
-
Ask the user:
|
|
66
|
-
|
|
67
|
-
> "Where should I configure the MCP server?
|
|
68
|
-
> 1) This project only — write \`.mcp.json\` here [default]
|
|
69
|
-
> 2) Globally — add to \`~/.claude/settings.json\` (works in all projects)"
|
|
70
|
-
|
|
71
|
-
**Option 1 (default):** Create or update \`.mcp.json\` in the current working directory:
|
|
72
|
-
|
|
73
|
-
\`\`\`json
|
|
74
|
-
{
|
|
75
|
-
"mcpServers": {
|
|
76
|
-
"dgmo": {
|
|
77
|
-
"command": "dgmo-mcp"
|
|
78
|
-
}
|
|
29
|
+
// The Claude Code skill (~/.claude/commands/dgmo.md) and the Codex skill
|
|
30
|
+
// (SKILL.md — gen-ai-core covered, with a gate-enforced COMPLETE chart-type
|
|
31
|
+
// index) are SHIPPED in this package via the "files" allowlist and are the
|
|
32
|
+
// single source of truth. The installers below read those files rather than
|
|
33
|
+
// carrying inline copies, so the chart-type list and syntax can never drift
|
|
34
|
+
// from the maintained originals (the stale inline table was the root cause of
|
|
35
|
+
// agents wrongly reporting that a chart type "does not exist").
|
|
36
|
+
const PKG_ROOT = resolve(__dirname, '..');
|
|
37
|
+
|
|
38
|
+
function readPackagedFile(...parts: string[]): string {
|
|
39
|
+
const p = join(PKG_ROOT, ...parts);
|
|
40
|
+
try {
|
|
41
|
+
return readFileSync(p, 'utf-8');
|
|
42
|
+
} catch {
|
|
43
|
+
console.error(`Could not read packaged file: ${p}`);
|
|
44
|
+
console.error(
|
|
45
|
+
'Your @diagrammo/dgmo install may be incomplete — try reinstalling it.'
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
79
48
|
}
|
|
80
49
|
}
|
|
81
|
-
\`\`\`
|
|
82
|
-
|
|
83
|
-
If \`.mcp.json\` already exists and has other servers, merge the \`dgmo\` entry in — do not overwrite the file.
|
|
84
|
-
|
|
85
|
-
**Option 2 (global):** Add the \`dgmo\` entry to the \`mcpServers\` object in \`~/.claude/settings.json\`. Read the file first and merge — do not overwrite other keys.
|
|
86
|
-
|
|
87
|
-
### Step 4 — Prompt restart
|
|
88
|
-
|
|
89
|
-
Tell the user:
|
|
90
|
-
|
|
91
|
-
> "Done. **Restart Claude Code** to activate the MCP server — diagram preview and rendering will be available in the next session."
|
|
92
|
-
|
|
93
|
-
Then proceed with the user's original request using CLI fallback (see "Other output options" below).
|
|
94
|
-
|
|
95
|
-
> **Note for future users:** To set up in one step from the terminal before starting a Claude Code session, run \`dgmo --install-claude-code-integration\`. It handles everything: installs \`@diagrammo/dgmo-mcp\`, writes the skill, and configures the MCP server.
|
|
96
|
-
|
|
97
|
-
## Getting Syntax Help
|
|
98
|
-
|
|
99
|
-
**Always use the MCP tool first** if it's available in this session:
|
|
100
|
-
|
|
101
|
-
\`\`\`
|
|
102
|
-
mcp__dgmo__get_language_reference // full reference
|
|
103
|
-
mcp__dgmo__get_language_reference("sequence") // specific chart type
|
|
104
|
-
\`\`\`
|
|
105
|
-
|
|
106
|
-
This is the authoritative, always-up-to-date syntax reference. Use it before guessing syntax.
|
|
107
|
-
|
|
108
|
-
## Your Workflow
|
|
109
|
-
|
|
110
|
-
When the user asks you to create or edit a diagram:
|
|
111
|
-
|
|
112
|
-
1. **Get syntax** — call \`mcp__dgmo__get_language_reference("<type>")\` if you're unsure of the syntax.
|
|
113
|
-
2. **Write the \`.dgmo\` content** — compose the markup.
|
|
114
|
-
3. **Save the source file** (if working in a project) — write it to \`<name>.dgmo\` so the user has an editable file.
|
|
115
|
-
4. **Render and show** — pick the right output based on what the user wants (see below).
|
|
116
|
-
|
|
117
|
-
### Output options — always offer these proactively after creating a diagram
|
|
118
|
-
|
|
119
|
-
| What the user wants | How to do it |
|
|
120
|
-
|---|---|
|
|
121
|
-
| **Quick look in the desktop app** | \`mcp__dgmo__open_in_app(dgmo)\` — opens directly in Diagrammo (macOS) |
|
|
122
|
-
| **Browser preview with theme toggle** | \`mcp__dgmo__preview_diagram([{dgmo, title}])\` — opens HTML in browser |
|
|
123
|
-
| **View in macOS Preview (or default image viewer)** | \`mcp__dgmo__render_diagram(dgmo, format:"png")\` → get temp path → \`open <path>\` |
|
|
124
|
-
| **View SVG in browser** | \`mcp__dgmo__render_diagram(dgmo, format:"svg")\` → write SVG to a temp \`.svg\` file → \`open <path>\` |
|
|
125
|
-
| **Save as PNG** | \`mcp__dgmo__render_diagram(dgmo, format:"png")\` → returns temp path; offer to copy to their preferred location. Or CLI: \`dgmo file.dgmo -o out.png\` |
|
|
126
|
-
| **Save as SVG** | \`mcp__dgmo__render_diagram(dgmo, format:"svg")\` returns SVG text — write it to the desired path. Or CLI: \`dgmo file.dgmo -o out.svg\` |
|
|
127
|
-
| **Shareable URL** | \`mcp__dgmo__share_diagram(dgmo)\` or CLI: \`dgmo file.dgmo -o url --copy\` |
|
|
128
|
-
|
|
129
|
-
**After creating a diagram, always present these options to the user** — don't just render silently and stop. A good response ends with something like: *"I've saved the file as \`diagram.dgmo\`. Want me to open it in the app, export it as a PNG, or generate a shareable link?"*
|
|
130
|
-
|
|
131
|
-
## CLI Reference
|
|
132
|
-
|
|
133
|
-
\`\`\`
|
|
134
|
-
dgmo <input.dgmo> [options]
|
|
135
|
-
cat input.dgmo | dgmo [options]
|
|
136
|
-
\`\`\`
|
|
137
|
-
|
|
138
|
-
Key options:
|
|
139
|
-
- \`-o <file>\` — output file; format inferred from extension (\`.svg\` → SVG, else PNG)
|
|
140
|
-
- \`-o url\` — output a shareable diagrammo.app URL
|
|
141
|
-
- \`--theme <theme>\` — \`light\` (default), \`dark\`, \`transparent\`
|
|
142
|
-
- \`--palette <name>\` — \`slate\` (default), \`atlas\`, \`blueprint\`, \`nord\`, \`tidewater\`, \`catppuccin\`, \`tokyo-night\`
|
|
143
|
-
- \`--copy\` — copy the URL to clipboard (use with \`-o url\`)
|
|
144
|
-
- \`--chart-types\` — list all supported chart types
|
|
145
|
-
|
|
146
|
-
## Supported Chart Types
|
|
147
|
-
|
|
148
|
-
| Type | Use case |
|
|
149
|
-
|------|----------|
|
|
150
|
-
| \`bar\` | Categorical comparisons |
|
|
151
|
-
| \`line\` / \`multi-line\` / \`area\` | Trends over time |
|
|
152
|
-
| \`pie\` / \`doughnut\` | Part-to-whole |
|
|
153
|
-
| \`radar\` / \`polar-area\` | Multi-dimensional metrics |
|
|
154
|
-
| \`bar-stacked\` | Multi-series categorical |
|
|
155
|
-
| \`scatter\` | 2D data points or bubble chart |
|
|
156
|
-
| \`sankey\` | Flow / allocation |
|
|
157
|
-
| \`chord\` | Circular flow relationships |
|
|
158
|
-
| \`function\` | Mathematical expressions |
|
|
159
|
-
| \`heatmap\` | Matrix intensity |
|
|
160
|
-
| \`funnel\` | Conversion pipeline |
|
|
161
|
-
| \`slope\` | Change between two periods |
|
|
162
|
-
| \`wordcloud\` | Term frequency |
|
|
163
|
-
| \`arc\` | Network relationships |
|
|
164
|
-
| \`timeline\` | Events, eras, date ranges |
|
|
165
|
-
| \`venn\` | Set overlaps |
|
|
166
|
-
| \`quadrant\` | 2x2 positioning matrix |
|
|
167
|
-
| \`sequence\` | Message / interaction flows |
|
|
168
|
-
| \`flowchart\` | Decision trees, process flows |
|
|
169
|
-
| \`state\` | State machine / lifecycle |
|
|
170
|
-
| \`class\` | UML class hierarchies |
|
|
171
|
-
| \`er\` | Database schemas |
|
|
172
|
-
| \`org\` | Hierarchical tree structures |
|
|
173
|
-
| \`kanban\` | Task / workflow columns |
|
|
174
|
-
| \`c4\` | System architecture (context → container → component → deployment) |
|
|
175
|
-
| \`sitemap\` | Website / app navigation structure |
|
|
176
|
-
| \`infra\` | Infrastructure traffic flow with rps computation |
|
|
177
|
-
| \`gantt\` | Project scheduling with dependencies |
|
|
178
|
-
| \`boxes-and-lines\` | General-purpose node-edge diagrams with groups and tags |
|
|
179
|
-
|
|
180
|
-
## Key Syntax Patterns
|
|
181
|
-
|
|
182
|
-
### Common to all diagrams
|
|
183
|
-
|
|
184
|
-
\`\`\`
|
|
185
|
-
sequence // explicit type (optional — auto-detected)
|
|
186
|
-
title: My Diagram
|
|
187
|
-
palette: catppuccin // override palette
|
|
188
|
-
|
|
189
|
-
// This is a comment (only // syntax — not #)
|
|
190
|
-
\`\`\`
|
|
191
|
-
|
|
192
|
-
Inline colors on most elements: append the color name as the trailing token — e.g. \`North red 850\`, \`[Process] blue\`. To use a color word as a literal label, capitalize it (\`Red\` stays as the word Red).
|
|
193
|
-
Named colors: \`red\`, \`orange\`, \`yellow\`, \`green\`, \`blue\`, \`purple\`, \`teal\`, \`cyan\`, \`gray\`, \`black\`, \`white\`.
|
|
194
|
-
|
|
195
|
-
### sequence (most commonly used)
|
|
196
|
-
|
|
197
|
-
\`\`\`
|
|
198
|
-
chart: sequence
|
|
199
|
-
title: Auth Flow
|
|
200
|
-
|
|
201
|
-
// Participants auto-inferred, or declare explicitly:
|
|
202
|
-
User is an actor
|
|
203
|
-
DB is a database
|
|
204
|
-
Cache is a cache
|
|
205
|
-
|
|
206
|
-
User -Login-> API
|
|
207
|
-
API -Find user-> DB
|
|
208
|
-
DB -user record-> API
|
|
209
|
-
|
|
210
|
-
if credentials valid
|
|
211
|
-
API -200 OK + token-> User
|
|
212
|
-
else
|
|
213
|
-
API -401 Unauthorized-> User
|
|
214
|
-
|
|
215
|
-
== Logout ==
|
|
216
|
-
|
|
217
|
-
User -Logout-> API
|
|
218
|
-
API -Delete session-> DB
|
|
219
|
-
\`\`\`
|
|
220
|
-
|
|
221
|
-
- Sync: \`A -label-> B\` · Async: \`A ~label~> B\` · Unlabeled: \`A -> B\`
|
|
222
|
-
- Blocks: \`if\` / \`else\`, \`loop\`, \`parallel\` — closed by indentation (no \`end\` keyword)
|
|
223
|
-
- Notes: \`note on API: text\` or \`note: text\`
|
|
224
|
-
- Sections: \`== Title ==\`
|
|
225
|
-
- Groups: \`[Group Name]\` with indented participants
|
|
226
|
-
|
|
227
|
-
### flowchart
|
|
228
|
-
|
|
229
|
-
\`\`\`
|
|
230
|
-
(Start) -> <Valid Input?>
|
|
231
|
-
-yes-> [Process Data] -> (Done)
|
|
232
|
-
-no-> /Get Input/ -> <Valid Input?>
|
|
233
|
-
\`\`\`
|
|
234
|
-
|
|
235
|
-
Shapes: \`(oval)\` \`[rect]\` \`<diamond>\` \`/parallelogram/\` \`[[subroutine]]\` \`[document~]\`
|
|
236
|
-
|
|
237
|
-
### bar / line / pie (data charts)
|
|
238
|
-
|
|
239
|
-
\`\`\`
|
|
240
|
-
// bar
|
|
241
|
-
title: Revenue by Region
|
|
242
|
-
series: Revenue
|
|
243
|
-
North: 850
|
|
244
|
-
South: 620
|
|
245
|
-
|
|
246
|
-
// line (multi-series)
|
|
247
|
-
series: Sales red, Costs blue
|
|
248
|
-
Q1: 100, 50
|
|
249
|
-
Q2: 120, 55
|
|
250
|
-
|
|
251
|
-
// pie
|
|
252
|
-
chart: pie
|
|
253
|
-
labels: percent
|
|
254
|
-
Company A: 40
|
|
255
|
-
Company B: 35
|
|
256
|
-
\`\`\`
|
|
257
|
-
|
|
258
|
-
### er
|
|
259
|
-
|
|
260
|
-
\`\`\`
|
|
261
|
-
users
|
|
262
|
-
id: int [pk]
|
|
263
|
-
email: varchar [unique]
|
|
264
|
-
1-writes-* posts
|
|
265
|
-
|
|
266
|
-
posts
|
|
267
|
-
id: int [pk]
|
|
268
|
-
author_id: int [fk]
|
|
269
|
-
\`\`\`
|
|
270
|
-
|
|
271
|
-
### org
|
|
272
|
-
|
|
273
|
-
\`\`\`
|
|
274
|
-
CEO
|
|
275
|
-
VP Engineering
|
|
276
|
-
[Platform Team]
|
|
277
|
-
Lead
|
|
278
|
-
Dev 1
|
|
279
|
-
Dev 2
|
|
280
|
-
VP Marketing
|
|
281
|
-
\`\`\`
|
|
282
|
-
|
|
283
|
-
### infra
|
|
284
|
-
|
|
285
|
-
\`\`\`
|
|
286
|
-
chart: infra
|
|
287
|
-
edge
|
|
288
|
-
rps: 10000
|
|
289
|
-
-> CDN
|
|
290
|
-
|
|
291
|
-
CDN
|
|
292
|
-
cache-hit: 80%
|
|
293
|
-
-> API
|
|
294
|
-
|
|
295
|
-
API
|
|
296
|
-
instances: 3
|
|
297
|
-
max-rps: 500
|
|
298
|
-
latency-ms: 45
|
|
299
|
-
\`\`\`
|
|
300
|
-
|
|
301
|
-
## Anti-Patterns
|
|
302
|
-
|
|
303
|
-
\`\`\`
|
|
304
|
-
# comment ❌ use // comment
|
|
305
|
-
async A -> B: msg ❌ use A ~msg~> B
|
|
306
|
-
A <- B ❌ left-pointing arrows removed — use B -> A
|
|
307
|
-
parallel else ❌ not supported — use separate parallel blocks
|
|
308
|
-
== Foo #ff0000 == ❌ hex colors not supported — use named colors: == Foo red ==
|
|
309
|
-
A -routes to /api-> B ❌ -> inside a label is ambiguous — rephrase the label
|
|
310
|
-
end ❌ not needed — indentation closes blocks in sequence diagrams
|
|
311
|
-
\`\`\`
|
|
312
|
-
|
|
313
|
-
## Tips
|
|
314
|
-
|
|
315
|
-
- Default theme: \`light\`, default palette: \`slate\` — ask the user their preference before a final export.
|
|
316
|
-
- Stdin mode for quick renders: \`echo "..." | dgmo -o out.png\`
|
|
317
|
-
- For C4, \`--c4-level\` drills from context → containers → components → deployment.
|
|
318
|
-
- When auto-detection picks the wrong chart type, add an explicit \`chart:\` directive.
|
|
319
|
-
- \`mcp__dgmo__preview_diagram\` accepts multiple diagrams at once — useful for showing variants side by side.
|
|
320
|
-
`;
|
|
321
|
-
|
|
322
|
-
const CODEX_SKILL_FRONTMATTER = `---
|
|
323
|
-
name: dgmo-diagramming
|
|
324
|
-
description: Use when the user asks for a diagram, chart, sequence diagram, flowchart, ER diagram, org chart, kanban, sitemap, infra/architecture diagram, or any visual based on the DGMO diagram markup language. Provides syntax, MCP tool guidance, and rendering/sharing workflows.
|
|
325
|
-
---
|
|
326
|
-
|
|
327
|
-
`;
|
|
328
|
-
|
|
329
|
-
const CODEX_SKILL_CONTENT = `# DGMO Diagram Language
|
|
330
|
-
|
|
331
|
-
Use dgmo tools to create, render, and share diagrams. dgmo is a text-based diagram markup language that renders to SVG/PNG.
|
|
332
|
-
|
|
333
|
-
## MCP Tools — preferred order
|
|
334
|
-
|
|
335
|
-
When the \`dgmo\` MCP server is configured, prefer tools in this order:
|
|
336
|
-
1. \`open_in_app\` — opens the diagram in the Diagrammo desktop app (macOS). **Best UX** — chart + editor side-by-side, full editing.
|
|
337
|
-
2. \`share_diagram\` — returns a \`https://online.diagrammo.app/...\` URL. Tell the user to open it; same chart + editor view in the browser. **Preferred fallback** when the desktop app is not available.
|
|
338
|
-
3. \`render_diagram\` — renders to PNG or SVG and returns a file path. Use when the user wants an image artifact (export, embed, attach).
|
|
339
|
-
4. \`generate_report\` — renders multiple diagrams into an HTML report with table of contents.
|
|
340
|
-
5. \`preview_diagram\` — local HTML preview in the browser. Last resort — only when none of the above fit.
|
|
341
|
-
6. \`list_chart_types\` / \`get_language_reference\` — discovery; call \`get_language_reference\` before generating an unfamiliar chart type.
|
|
342
|
-
|
|
343
|
-
## When to use dgmo
|
|
344
|
-
|
|
345
|
-
- Architecture diagrams, sequence diagrams, flowcharts
|
|
346
|
-
- Data charts (bar, line, pie, scatter, heatmap, etc.)
|
|
347
|
-
- ER diagrams, class diagrams, org charts
|
|
348
|
-
- Project roadmaps, kanban boards, timelines
|
|
349
|
-
|
|
350
|
-
## Quick syntax reference
|
|
351
|
-
|
|
352
|
-
### Sequence diagram
|
|
353
|
-
\`\`\`
|
|
354
|
-
sequence Auth Flow
|
|
355
|
-
|
|
356
|
-
User -Login-> API
|
|
357
|
-
API -Find user-> DB
|
|
358
|
-
DB -user-> API
|
|
359
|
-
if valid
|
|
360
|
-
API -200 OK-> User
|
|
361
|
-
else
|
|
362
|
-
API -401-> User
|
|
363
|
-
\`\`\`
|
|
364
|
-
|
|
365
|
-
### Flowchart
|
|
366
|
-
\`\`\`
|
|
367
|
-
flowchart Process
|
|
368
|
-
|
|
369
|
-
(Start) -> <Valid?>
|
|
370
|
-
-yes-> [Process] -> (Done)
|
|
371
|
-
-no-> /Get Input/ -> <Valid?>
|
|
372
|
-
\`\`\`
|
|
373
|
-
|
|
374
|
-
### Bar chart
|
|
375
|
-
\`\`\`
|
|
376
|
-
bar Revenue
|
|
377
|
-
series USD
|
|
378
|
-
|
|
379
|
-
North 850
|
|
380
|
-
South 620
|
|
381
|
-
East 1100
|
|
382
|
-
\`\`\`
|
|
383
50
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
51
|
+
// Claude Code skill source. Carries NO static chart-type table — it directs the
|
|
52
|
+
// agent to query list_chart_types (MCP) / dgmo types (CLI) for the
|
|
53
|
+
// authoritative, complete list.
|
|
54
|
+
const readClaudeSkill = (): string =>
|
|
55
|
+
readPackagedFile('.claude', 'commands', 'dgmo.md');
|
|
387
56
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
posts
|
|
393
|
-
id int pk
|
|
394
|
-
user_id int fk
|
|
395
|
-
|
|
396
|
-
users 1-writes-* posts
|
|
397
|
-
\`\`\`
|
|
398
|
-
|
|
399
|
-
### Org chart
|
|
400
|
-
\`\`\`
|
|
401
|
-
org
|
|
402
|
-
|
|
403
|
-
CEO
|
|
404
|
-
VP Engineering
|
|
405
|
-
Team Lead A
|
|
406
|
-
Team Lead B
|
|
407
|
-
VP Marketing
|
|
408
|
-
\`\`\`
|
|
409
|
-
|
|
410
|
-
### Infra chart
|
|
411
|
-
\`\`\`
|
|
412
|
-
infra
|
|
413
|
-
|
|
414
|
-
edge
|
|
415
|
-
rps: 10000
|
|
416
|
-
-> CDN
|
|
417
|
-
|
|
418
|
-
CDN
|
|
419
|
-
cache-hit: 80%
|
|
420
|
-
-> LB
|
|
421
|
-
|
|
422
|
-
LB
|
|
423
|
-
-> API | split: 70%
|
|
424
|
-
-> Web | split: 30%
|
|
425
|
-
|
|
426
|
-
API
|
|
427
|
-
instances: 3
|
|
428
|
-
max-rps: 500
|
|
429
|
-
latency-ms: 45
|
|
430
|
-
\`\`\`
|
|
431
|
-
|
|
432
|
-
## All 31 chart types
|
|
433
|
-
|
|
434
|
-
bar, line, multi-line, area, pie, doughnut, radar, polar-area, bar-stacked, scatter, sankey, chord, function, heatmap, funnel, slope, wordcloud, arc, timeline, venn, quadrant, sequence, flowchart, state, class, er, org, kanban, c4, sitemap, infra
|
|
435
|
-
|
|
436
|
-
## Common patterns
|
|
437
|
-
|
|
438
|
-
- First line: chart type keyword (e.g. \`sequence\`, \`flowchart\`, \`bar\`), optionally followed by a title (\`bar Revenue\`)
|
|
439
|
-
- \`// comment\` — only \`//\` comments (not \`#\`)
|
|
440
|
-
- Trailing color name — inline colors on data series, tag values, kanban columns: \`Label red 100\`
|
|
441
|
-
- \`series A red, B blue\` — multi-series with colors
|
|
442
|
-
|
|
443
|
-
## Rendering via CLI
|
|
444
|
-
|
|
445
|
-
\`\`\`bash
|
|
446
|
-
dgmo file.dgmo -o output.svg # SVG
|
|
447
|
-
dgmo file.dgmo -o url # shareable link
|
|
448
|
-
dgmo file.dgmo --json # structured JSON output
|
|
449
|
-
\`\`\`
|
|
450
|
-
|
|
451
|
-
## Mistakes to avoid
|
|
452
|
-
|
|
453
|
-
- Don't use \`#\` for comments — use \`//\`
|
|
454
|
-
- Don't use \`end\` to close sequence blocks — indentation closes them
|
|
455
|
-
- Don't use hex colors in section headers — use named colors
|
|
456
|
-
- Start the file with the chart type keyword when content is ambiguous
|
|
457
|
-
- Sequence arrows: \`->\` (sync), \`~>\` (async) — always left-to-right
|
|
458
|
-
|
|
459
|
-
Full reference: call \`get_language_reference\` MCP tool or visit diagrammo.app/docs
|
|
460
|
-
`;
|
|
57
|
+
// Codex skill source. Already includes the dgmo-diagramming frontmatter and the
|
|
58
|
+
// gen-ai-core 45-type index.
|
|
59
|
+
const readCodexSkill = (): string => readPackagedFile('SKILL.md');
|
|
461
60
|
|
|
462
61
|
const CODEX_AGENTS_NOTE_MARKER = '<!-- dgmo-integration -->';
|
|
463
62
|
const CODEX_AGENTS_NOTE = `${CODEX_AGENTS_NOTE_MARKER}
|
|
@@ -468,39 +67,25 @@ For architecture diagrams, sequence diagrams, flowcharts, and charts, use the \`
|
|
|
468
67
|
|
|
469
68
|
function printHelp(): void {
|
|
470
69
|
console.log(renderBanner());
|
|
471
|
-
console.log(`Usage: dgmo <input> [options]
|
|
472
|
-
cat input.dgmo | dgmo [options]
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
70
|
+
console.log(`Usage: dgmo <input> [options] Render a .dgmo file to PNG (default) or SVG
|
|
71
|
+
cat input.dgmo | dgmo [options] Render from stdin
|
|
72
|
+
|
|
73
|
+
Commands:
|
|
74
|
+
dgmo share <input> Print a shareable diagrammo.app URL (copies to clipboard)
|
|
75
|
+
dgmo types List all supported chart types
|
|
76
|
+
dgmo install [target] Set up AI assistants (auto-detects all if no target).
|
|
77
|
+
Targets: claude-code, codex, claude-desktop,
|
|
78
|
+
cursor, windsurf, copilot. --scope user|project
|
|
79
|
+
dgmo mcp Run the MCP server (invoked by AI client configs)
|
|
80
|
+
dgmo map-search <query> Find the map place token (city or IATA code)
|
|
81
|
+
|
|
82
|
+
Render options:
|
|
480
83
|
-o <file> Output file (default: <input>.png in cwd)
|
|
481
84
|
Format inferred from extension: .svg → SVG, else PNG
|
|
482
|
-
Use -o url to output a shareable diagrammo.app URL
|
|
483
85
|
With stdin and no -o, PNG is written to stdout
|
|
484
86
|
--theme <theme> Theme: ${THEMES.join(', ')} (default: light)
|
|
485
87
|
--palette <name> Palette: ${PALETTES.join(', ')} (default: slate)
|
|
486
|
-
--copy Copy URL to clipboard (only with -o url)
|
|
487
88
|
--json Output structured JSON to stdout
|
|
488
|
-
--chart-types List all supported chart types
|
|
489
|
-
--install-claude-code-integration
|
|
490
|
-
Full Claude Code setup: install the /dgmo skill and configure
|
|
491
|
-
the dgmo MCP server — installs @diagrammo/dgmo-mcp if needed,
|
|
492
|
-
then writes .mcp.json (project) or ~/.claude/settings.json (global)
|
|
493
|
-
--install-claude-skill Install only the /dgmo skill to ~/.claude/commands/dgmo.md
|
|
494
|
-
--install-codex-integration
|
|
495
|
-
Full Codex CLI setup: install the dgmo-diagramming skill at
|
|
496
|
-
~/.codex/skills/dgmo-diagramming/SKILL.md, configure the dgmo MCP
|
|
497
|
-
server in .codex/config.toml (project) or ~/.codex/config.toml (global),
|
|
498
|
-
and append a non-destructive note to AGENTS.md if one already exists.
|
|
499
|
-
--install-claude-desktop-integration
|
|
500
|
-
Full Claude Desktop setup: install @diagrammo/dgmo-mcp if needed,
|
|
501
|
-
then merge the dgmo MCP entry into Claude Desktop's config file
|
|
502
|
-
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS,
|
|
503
|
-
%APPDATA%/Claude/... on Windows, ~/.config/Claude/... on Linux)
|
|
504
89
|
--help Show this help
|
|
505
90
|
--version Show version`);
|
|
506
91
|
}
|
|
@@ -519,15 +104,7 @@ function parseArgs(argv: string[]): {
|
|
|
519
104
|
palette: string;
|
|
520
105
|
help: boolean;
|
|
521
106
|
version: boolean;
|
|
522
|
-
copy: boolean;
|
|
523
107
|
json: boolean;
|
|
524
|
-
chartTypes: boolean;
|
|
525
|
-
cat: boolean;
|
|
526
|
-
noColor: boolean;
|
|
527
|
-
installClaudeSkill: boolean;
|
|
528
|
-
installClaudeCodeIntegration: boolean;
|
|
529
|
-
installCodexIntegration: boolean;
|
|
530
|
-
installClaudeDesktopIntegration: boolean;
|
|
531
108
|
} {
|
|
532
109
|
const result = {
|
|
533
110
|
input: undefined as string | undefined,
|
|
@@ -536,15 +113,7 @@ function parseArgs(argv: string[]): {
|
|
|
536
113
|
palette: 'slate',
|
|
537
114
|
help: false,
|
|
538
115
|
version: false,
|
|
539
|
-
copy: false,
|
|
540
116
|
json: false,
|
|
541
|
-
chartTypes: false,
|
|
542
|
-
cat: false,
|
|
543
|
-
noColor: false,
|
|
544
|
-
installClaudeSkill: false,
|
|
545
|
-
installClaudeCodeIntegration: false,
|
|
546
|
-
installCodexIntegration: false,
|
|
547
|
-
installClaudeDesktopIntegration: false,
|
|
548
117
|
};
|
|
549
118
|
|
|
550
119
|
const args = argv.slice(2); // skip node + script
|
|
@@ -553,13 +122,7 @@ function parseArgs(argv: string[]): {
|
|
|
553
122
|
while (i < args.length) {
|
|
554
123
|
const arg = args[i];
|
|
555
124
|
|
|
556
|
-
if (arg === '
|
|
557
|
-
result.cat = true;
|
|
558
|
-
i++;
|
|
559
|
-
} else if (arg === '--no-color') {
|
|
560
|
-
result.noColor = true;
|
|
561
|
-
i++;
|
|
562
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
125
|
+
if (arg === '--help' || arg === '-h') {
|
|
563
126
|
result.help = true;
|
|
564
127
|
i++;
|
|
565
128
|
} else if (arg === '--version' || arg === '-v') {
|
|
@@ -591,24 +154,6 @@ function parseArgs(argv: string[]): {
|
|
|
591
154
|
} else if (arg === '--json') {
|
|
592
155
|
result.json = true;
|
|
593
156
|
i++;
|
|
594
|
-
} else if (arg === '--chart-types') {
|
|
595
|
-
result.chartTypes = true;
|
|
596
|
-
i++;
|
|
597
|
-
} else if (arg === '--install-claude-code-integration') {
|
|
598
|
-
result.installClaudeCodeIntegration = true;
|
|
599
|
-
i++;
|
|
600
|
-
} else if (arg === '--install-claude-skill') {
|
|
601
|
-
result.installClaudeSkill = true;
|
|
602
|
-
i++;
|
|
603
|
-
} else if (arg === '--install-codex-integration') {
|
|
604
|
-
result.installCodexIntegration = true;
|
|
605
|
-
i++;
|
|
606
|
-
} else if (arg === '--install-claude-desktop-integration') {
|
|
607
|
-
result.installClaudeDesktopIntegration = true;
|
|
608
|
-
i++;
|
|
609
|
-
} else if (arg === '--copy') {
|
|
610
|
-
result.copy = true;
|
|
611
|
-
i++;
|
|
612
157
|
} else if (!result.input) {
|
|
613
158
|
result.input = arg;
|
|
614
159
|
i++;
|
|
@@ -621,10 +166,7 @@ function parseArgs(argv: string[]): {
|
|
|
621
166
|
return result;
|
|
622
167
|
}
|
|
623
168
|
|
|
624
|
-
function inferFormat(outputPath: string | undefined): 'svg' | 'png'
|
|
625
|
-
if (outputPath === 'url') {
|
|
626
|
-
return 'url';
|
|
627
|
-
}
|
|
169
|
+
function inferFormat(outputPath: string | undefined): 'svg' | 'png' {
|
|
628
170
|
if (outputPath && extname(outputPath).toLowerCase() === '.svg') {
|
|
629
171
|
return 'svg';
|
|
630
172
|
}
|
|
@@ -688,193 +230,33 @@ function noInput(): never {
|
|
|
688
230
|
process.exit(0);
|
|
689
231
|
}
|
|
690
232
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
help: boolean;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function parseMigrateArgs(args: string[]): MigrateCommandOpts {
|
|
702
|
-
const opts: MigrateCommandOpts = {
|
|
703
|
-
path: undefined,
|
|
704
|
-
dryRun: true,
|
|
705
|
-
apply: false,
|
|
706
|
-
diff: false,
|
|
707
|
-
noBackup: false,
|
|
708
|
-
embedded: false,
|
|
709
|
-
help: false,
|
|
710
|
-
};
|
|
711
|
-
let i = 0;
|
|
712
|
-
while (i < args.length) {
|
|
713
|
-
const arg = args[i]!;
|
|
714
|
-
if (arg === '--help' || arg === '-h') {
|
|
715
|
-
opts.help = true;
|
|
716
|
-
i++;
|
|
717
|
-
} else if (arg === '--dry-run') {
|
|
718
|
-
opts.dryRun = true;
|
|
719
|
-
opts.apply = false;
|
|
720
|
-
i++;
|
|
721
|
-
} else if (arg === '--apply') {
|
|
722
|
-
opts.apply = true;
|
|
723
|
-
opts.dryRun = false;
|
|
724
|
-
i++;
|
|
725
|
-
} else if (arg === '--diff') {
|
|
726
|
-
opts.diff = true;
|
|
727
|
-
i++;
|
|
728
|
-
} else if (arg === '--backup') {
|
|
729
|
-
opts.noBackup = false;
|
|
730
|
-
i++;
|
|
731
|
-
} else if (arg === '--no-backup') {
|
|
732
|
-
opts.noBackup = true;
|
|
733
|
-
i++;
|
|
734
|
-
} else if (arg === '--embedded') {
|
|
735
|
-
opts.embedded = true;
|
|
736
|
-
i++;
|
|
737
|
-
} else if (!opts.path) {
|
|
738
|
-
opts.path = arg;
|
|
739
|
-
i++;
|
|
233
|
+
// Best-effort clipboard copy across platforms. Returns true on success.
|
|
234
|
+
function copyToClipboard(text: string): boolean {
|
|
235
|
+
try {
|
|
236
|
+
if (process.platform === 'darwin') {
|
|
237
|
+
execSync('pbcopy', { input: text });
|
|
238
|
+
} else if (process.platform === 'win32') {
|
|
239
|
+
execSync('clip', { input: text });
|
|
740
240
|
} else {
|
|
741
|
-
|
|
742
|
-
process.exit(1);
|
|
241
|
+
execSync('xclip -selection clipboard', { input: text });
|
|
743
242
|
}
|
|
243
|
+
return true;
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
744
246
|
}
|
|
745
|
-
return opts;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function printMigrateHelp(): void {
|
|
749
|
-
console.log(`Usage: dgmo migrate <path> [options]
|
|
750
|
-
|
|
751
|
-
Convert legacy "|" metadata syntax to the unified §1.4 same-line form
|
|
752
|
-
("Foo k: v, k: v" — no pipe). Handles bare-positional promotions:
|
|
753
|
-
- gantt "| 80%" → "progress: 80"
|
|
754
|
-
- journey "| 4 Delighted" → "score: 4, emotion: Delighted"
|
|
755
|
-
- pyramid "| description" → "description: ..."
|
|
756
|
-
- ring "| description" → "description: ..."
|
|
757
|
-
|
|
758
|
-
Wireframe option braces "{A | B}", arrow-label "|" characters (§1.10),
|
|
759
|
-
and quoted-string content are preserved.
|
|
760
|
-
|
|
761
|
-
Options:
|
|
762
|
-
--dry-run Print proposed changes; do not write (default)
|
|
763
|
-
--apply Write migrated files to disk
|
|
764
|
-
--diff Print per-file unified-style diffs
|
|
765
|
-
--backup Write "<file>.bak" before applying (default with --apply)
|
|
766
|
-
--no-backup Skip writing .bak sidecars
|
|
767
|
-
--embedded Walk .md/.mdx files; migrate fenced \`\`\`dgmo blocks
|
|
768
|
-
atomically per file (single parse-error block aborts file)
|
|
769
|
-
-h, --help Show this help`);
|
|
770
247
|
}
|
|
771
248
|
|
|
772
|
-
|
|
773
|
-
const opts = parseMigrateArgs(args);
|
|
774
|
-
|
|
775
|
-
if (opts.help) {
|
|
776
|
-
printMigrateHelp();
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
if (!opts.path) {
|
|
781
|
-
console.error('Error: dgmo migrate requires a path argument');
|
|
782
|
-
console.error('Try: dgmo migrate --help');
|
|
783
|
-
process.exit(1);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
const resolvedPath = resolve(opts.path);
|
|
787
|
-
if (!existsSync(resolvedPath)) {
|
|
788
|
-
console.error(`Error: Path not found: ${resolvedPath}`);
|
|
789
|
-
process.exit(1);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
if (opts.embedded) {
|
|
793
|
-
const files = collectEmbeddedFiles(resolvedPath);
|
|
794
|
-
if (files.length === 0) {
|
|
795
|
-
console.error('No .md / .mdx files found at the given path.');
|
|
796
|
-
process.exit(1);
|
|
797
|
-
}
|
|
798
|
-
let migrated = 0;
|
|
799
|
-
let skipped = 0;
|
|
800
|
-
let unchanged = 0;
|
|
801
|
-
for (const file of files) {
|
|
802
|
-
const result = migrateEmbedded(file, {
|
|
803
|
-
dryRun: opts.dryRun,
|
|
804
|
-
noBackup: opts.noBackup,
|
|
805
|
-
});
|
|
806
|
-
if (result.skipped) {
|
|
807
|
-
console.log(`SKIP ${file} — ${result.skipReason}`);
|
|
808
|
-
skipped++;
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
if (!result.changed) {
|
|
812
|
-
unchanged++;
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
migrated++;
|
|
816
|
-
const verb = result.written ? 'MIGRATE' : 'DRY-RUN';
|
|
817
|
-
console.log(
|
|
818
|
-
`${verb} ${file} — ${result.changedBlocks}/${result.blockCount} blocks changed`
|
|
819
|
-
);
|
|
820
|
-
if (opts.diff) {
|
|
821
|
-
console.log(formatLineDiff(file, result.original, result.migrated));
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
console.log('');
|
|
825
|
-
console.log(
|
|
826
|
-
`Done. ${migrated} file(s) ${opts.dryRun ? 'would migrate' : 'migrated'}; ${unchanged} unchanged; ${skipped} skipped.`
|
|
827
|
-
);
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const files = collectDgmoFiles(resolvedPath);
|
|
832
|
-
if (files.length === 0) {
|
|
833
|
-
console.error('No .dgmo files found at the given path.');
|
|
834
|
-
process.exit(1);
|
|
835
|
-
}
|
|
836
|
-
let migrated = 0;
|
|
837
|
-
let unchanged = 0;
|
|
838
|
-
for (const file of files) {
|
|
839
|
-
const result = migrateFile(file, {
|
|
840
|
-
dryRun: opts.dryRun,
|
|
841
|
-
noBackup: opts.noBackup,
|
|
842
|
-
});
|
|
843
|
-
if (!result.changed) {
|
|
844
|
-
unchanged++;
|
|
845
|
-
continue;
|
|
846
|
-
}
|
|
847
|
-
migrated++;
|
|
848
|
-
const verb = result.written ? 'MIGRATE' : 'DRY-RUN';
|
|
849
|
-
console.log(
|
|
850
|
-
`${verb} ${file} — ${result.changedLines.length} line(s) changed`
|
|
851
|
-
);
|
|
852
|
-
if (opts.diff) {
|
|
853
|
-
console.log(formatLineDiff(file, result.original, result.migrated));
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
console.log('');
|
|
857
|
-
console.log(
|
|
858
|
-
`Done. ${migrated} file(s) ${opts.dryRun ? 'would migrate' : 'migrated'}; ${unchanged} unchanged.`
|
|
859
|
-
);
|
|
860
|
-
if (opts.dryRun && migrated > 0) {
|
|
861
|
-
console.log('Re-run with --apply to write changes.');
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// `dgmo map search <query>` — discover the exact place token the map resolver
|
|
249
|
+
// `dgmo map-search <query>` — discover the exact place token the map resolver
|
|
866
250
|
// expects (city name or bundled IATA airport code), so authors never guess
|
|
867
251
|
// (e.g. "New York" → `New York City`; "kennedy" → `JFK`). Searches cities +
|
|
868
252
|
// the bundled airport set; `--json` for machine output, `--limit N` to widen.
|
|
869
|
-
async function
|
|
870
|
-
const sub = args[0];
|
|
253
|
+
async function runMapSearchCommand(args: string[]): Promise<void> {
|
|
871
254
|
const json = args.includes('--json');
|
|
872
255
|
const limFlag = args.indexOf('--limit');
|
|
873
256
|
const limit =
|
|
874
257
|
limFlag >= 0 ? Math.max(1, Number(args[limFlag + 1]) || 20) : 20;
|
|
875
258
|
// The query is the positional args (allow unquoted multi-word: `... New York`).
|
|
876
259
|
const query = args
|
|
877
|
-
.slice(1)
|
|
878
260
|
.filter(
|
|
879
261
|
(a, i, arr) =>
|
|
880
262
|
a !== '--json' && a !== '--limit' && arr[i - 1] !== '--limit'
|
|
@@ -882,18 +264,18 @@ async function runMapCommand(args: string[]): Promise<void> {
|
|
|
882
264
|
.join(' ')
|
|
883
265
|
.trim();
|
|
884
266
|
|
|
885
|
-
if (
|
|
886
|
-
console.log('Usage: dgmo map
|
|
267
|
+
if (!query) {
|
|
268
|
+
console.log('Usage: dgmo map-search <query> [--json] [--limit N]');
|
|
887
269
|
console.log(
|
|
888
270
|
' Find the place token the map resolver expects (city or IATA airport code).'
|
|
889
271
|
);
|
|
890
272
|
console.log(' Examples:');
|
|
891
273
|
console.log(
|
|
892
|
-
' dgmo map
|
|
274
|
+
' dgmo map-search "new york" # → New York City (+ JFK/LGA/EWR airports)'
|
|
893
275
|
);
|
|
894
|
-
console.log(' dgmo map
|
|
895
|
-
console.log(' dgmo map
|
|
896
|
-
|
|
276
|
+
console.log(' dgmo map-search kennedy # → JFK by airport name');
|
|
277
|
+
console.log(' dgmo map-search heathrow --json');
|
|
278
|
+
process.exitCode = 1;
|
|
897
279
|
return;
|
|
898
280
|
}
|
|
899
281
|
|
|
@@ -922,537 +304,519 @@ async function runMapCommand(args: string[]): Promise<void> {
|
|
|
922
304
|
}
|
|
923
305
|
}
|
|
924
306
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
307
|
+
// `dgmo types [--json]` — list every supported chart type. Was the
|
|
308
|
+
// `--chart-types` render flag; promoted to a subcommand since it's a
|
|
309
|
+
// query, not a rendering option.
|
|
310
|
+
function runTypesCommand(args: string[]): void {
|
|
311
|
+
const json = args.includes('--json');
|
|
312
|
+
const types = getAllChartTypes();
|
|
313
|
+
if (json) {
|
|
314
|
+
const chartTypes = types.map((id) => ({
|
|
315
|
+
id,
|
|
316
|
+
description: CHART_TYPE_DESCRIPTIONS[id] ?? id,
|
|
317
|
+
}));
|
|
318
|
+
process.stdout.write(JSON.stringify({ chartTypes }, null, 2) + '\n');
|
|
319
|
+
} else {
|
|
320
|
+
for (const id of types) {
|
|
321
|
+
const desc = CHART_TYPE_DESCRIPTIONS[id];
|
|
322
|
+
console.log(desc ? `${id} — ${desc}` : id);
|
|
323
|
+
}
|
|
931
324
|
}
|
|
325
|
+
}
|
|
932
326
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
327
|
+
// `dgmo share <input> [--no-copy] [--json]` — encode the diagram into a
|
|
328
|
+
// shareable diagrammo.app URL. Replaces the old `-o url [--copy]` pair: the
|
|
329
|
+
// URL is printed to stdout (pipeable) and copied to the clipboard by default
|
|
330
|
+
// unless --no-copy is given.
|
|
331
|
+
async function runShareCommand(args: string[]): Promise<void> {
|
|
332
|
+
const json = args.includes('--json');
|
|
333
|
+
const noCopy = args.includes('--no-copy');
|
|
334
|
+
const inputArg = args.find((a) => a !== '-' && !a.startsWith('-'));
|
|
335
|
+
|
|
336
|
+
let content: string;
|
|
337
|
+
if (inputArg) {
|
|
338
|
+
try {
|
|
339
|
+
content = readFileSync(resolve(inputArg), 'utf-8');
|
|
340
|
+
} catch {
|
|
341
|
+
console.error(`Error: Cannot read file "${resolve(inputArg)}"`);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
} else if (!process.stdin.isTTY) {
|
|
345
|
+
try {
|
|
346
|
+
content = readFileSync(0, 'utf-8');
|
|
347
|
+
} catch {
|
|
348
|
+
console.error('Error: No input provided to dgmo share');
|
|
349
|
+
console.error('Usage: dgmo share <input> [--no-copy] [--json]');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
console.error('Error: dgmo share requires an input file or piped stdin');
|
|
354
|
+
console.error('Usage: dgmo share <input> [--no-copy] [--json]');
|
|
355
|
+
process.exit(1);
|
|
936
356
|
}
|
|
937
357
|
|
|
938
|
-
|
|
358
|
+
// eslint-disable-next-line no-control-regex
|
|
359
|
+
content = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
939
360
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
361
|
+
const chartType = parseDgmoChartType(content);
|
|
362
|
+
// PERT diagrams may contain `start-date now`; resolve to today before
|
|
363
|
+
// encoding so the share-link captures authoring intent (spec D9).
|
|
364
|
+
const sourceForUrl =
|
|
365
|
+
chartType === 'pert' ? normalizePertSourceForShare(content) : content;
|
|
366
|
+
const result = encodeDiagramUrl(sourceForUrl);
|
|
367
|
+
|
|
368
|
+
if (result.error) {
|
|
369
|
+
const msg = `Error: Diagram too large for URL sharing (${result.compressedSize} bytes, limit ${result.limit} bytes)`;
|
|
370
|
+
if (json) {
|
|
371
|
+
process.stdout.write(
|
|
372
|
+
JSON.stringify({ success: false, error: msg }, null, 2) + '\n'
|
|
373
|
+
);
|
|
374
|
+
} else {
|
|
375
|
+
console.error(msg);
|
|
376
|
+
}
|
|
377
|
+
process.exit(1);
|
|
943
378
|
}
|
|
944
379
|
|
|
945
|
-
if (
|
|
946
|
-
|
|
947
|
-
|
|
380
|
+
if (!noCopy) {
|
|
381
|
+
console.error(
|
|
382
|
+
copyToClipboard(result.url)
|
|
383
|
+
? 'URL copied to clipboard'
|
|
384
|
+
: 'Warning: Could not copy to clipboard'
|
|
385
|
+
);
|
|
948
386
|
}
|
|
949
387
|
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const desc = CHART_TYPE_DESCRIPTIONS[id];
|
|
961
|
-
console.log(desc ? `${id} — ${desc}` : id);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return;
|
|
388
|
+
if (json) {
|
|
389
|
+
process.stdout.write(
|
|
390
|
+
JSON.stringify(
|
|
391
|
+
{ success: true, url: result.url, ...(chartType ? { chartType } : {}) },
|
|
392
|
+
null,
|
|
393
|
+
2
|
|
394
|
+
) + '\n'
|
|
395
|
+
);
|
|
396
|
+
} else {
|
|
397
|
+
process.stdout.write(result.url + '\n');
|
|
965
398
|
}
|
|
399
|
+
}
|
|
966
400
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
401
|
+
// Single source of truth for how every AI client launches the MCP server: the
|
|
402
|
+
// `dgmo` binary the user already has, plus the `mcp` subcommand. No generated
|
|
403
|
+
// config ever references a separate `dgmo-mcp` command — the user installs one
|
|
404
|
+
// binary and that's it. (The server itself still lives in the
|
|
405
|
+
// @diagrammo/dgmo-mcp package, which depends on this library; `dgmo mcp` locates
|
|
406
|
+
// and execs it, so there's no dependency cycle and no extra deps bundled here.)
|
|
407
|
+
const MCP_LAUNCH = { command: 'dgmo', args: ['mcp'] } as const;
|
|
408
|
+
|
|
409
|
+
const INSTALL_TARGETS = [
|
|
410
|
+
'claude-code',
|
|
411
|
+
'codex',
|
|
412
|
+
'claude-desktop',
|
|
413
|
+
'cursor',
|
|
414
|
+
'windsurf',
|
|
415
|
+
'copilot',
|
|
416
|
+
] as const;
|
|
417
|
+
type InstallTarget = (typeof INSTALL_TARGETS)[number];
|
|
418
|
+
|
|
419
|
+
interface InstallOpts {
|
|
420
|
+
scope: 'user' | 'project';
|
|
421
|
+
dryRun: boolean;
|
|
422
|
+
}
|
|
972
423
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
424
|
+
// `dgmo mcp` — run the MCP server. This is what AI client configs invoke. It
|
|
425
|
+
// execs the installed `dgmo-mcp` binary if present, otherwise fetches and runs
|
|
426
|
+
// it on demand via npx so a config pointing at `dgmo mcp` works even before the
|
|
427
|
+
// server package has been installed. stdio is inherited so the stdio MCP
|
|
428
|
+
// transport is fully transparent.
|
|
429
|
+
function runMcpCommand(args: string[]): never {
|
|
430
|
+
const onWin = process.platform === 'win32';
|
|
431
|
+
const run = (cmd: string, cmdArgs: string[]) =>
|
|
432
|
+
spawnSync(cmd, cmdArgs, { stdio: 'inherit', shell: onWin });
|
|
433
|
+
// npx path is pinned to @latest so the on-demand server is never stale.
|
|
434
|
+
const npx = () => run('npx', ['-y', '@diagrammo/dgmo-mcp@latest', ...args]);
|
|
435
|
+
// Default: prefer the installed binary — fast, works offline, and
|
|
436
|
+
// `dgmo install` keeps it on @latest. Set DGMO_MCP_LATEST=1 to always fetch
|
|
437
|
+
// the newest server via npx instead (needs network; adds cold-start).
|
|
438
|
+
const res =
|
|
439
|
+
process.env['DGMO_MCP_LATEST'] === '1'
|
|
440
|
+
? npx()
|
|
441
|
+
: commandExists('dgmo-mcp')
|
|
442
|
+
? run('dgmo-mcp', args)
|
|
443
|
+
: npx();
|
|
444
|
+
if (res.error) {
|
|
445
|
+
console.error(
|
|
446
|
+
'Could not launch the dgmo MCP server. Install it with: npm install -g @diagrammo/dgmo-mcp'
|
|
447
|
+
);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
process.exit(res.status ?? 0);
|
|
451
|
+
}
|
|
992
452
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
453
|
+
// Cross-platform "is this command on PATH?" check.
|
|
454
|
+
function commandExists(cmd: string): boolean {
|
|
455
|
+
try {
|
|
456
|
+
const probe =
|
|
457
|
+
process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
458
|
+
execSync(probe, { stdio: 'pipe' });
|
|
459
|
+
return true;
|
|
460
|
+
} catch {
|
|
461
|
+
return false;
|
|
998
462
|
}
|
|
463
|
+
}
|
|
999
464
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
465
|
+
// Claude Desktop config path for the current platform. macOS/Windows use the
|
|
466
|
+
// documented locations; Linux follows the XDG convention community builds use.
|
|
467
|
+
function claudeDesktopConfigPath(): string {
|
|
468
|
+
const os = platform();
|
|
469
|
+
if (os === 'darwin') {
|
|
470
|
+
return join(
|
|
471
|
+
homedir(),
|
|
472
|
+
'Library',
|
|
473
|
+
'Application Support',
|
|
474
|
+
'Claude',
|
|
475
|
+
'claude_desktop_config.json'
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
if (os === 'win32') {
|
|
479
|
+
const appData =
|
|
480
|
+
process.env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');
|
|
481
|
+
return join(appData, 'Claude', 'claude_desktop_config.json');
|
|
1007
482
|
}
|
|
483
|
+
return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
484
|
+
}
|
|
1008
485
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
486
|
+
// Best-effort, NON-fatal pre-install of the MCP server so the first tool call
|
|
487
|
+
// has no cold-start. Not required for correctness — `dgmo mcp` falls back to
|
|
488
|
+
// npx — so any failure is a note, not an error. Memoized: in auto mode we set
|
|
489
|
+
// up several surfaces in one run and only need to check once.
|
|
490
|
+
// True when this CLI is running from a Homebrew install (its files live under
|
|
491
|
+
// a Cellar prefix). Homebrew owns the MCP server in that case.
|
|
492
|
+
function isHomebrewManaged(): boolean {
|
|
493
|
+
return PKG_ROOT.includes('/Cellar/') || PKG_ROOT.includes('/homebrew/');
|
|
494
|
+
}
|
|
1016
495
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
496
|
+
let mcpEnsured = false;
|
|
497
|
+
function ensureDgmoMcp(opts: InstallOpts): void {
|
|
498
|
+
if (mcpEnsured) return;
|
|
499
|
+
mcpEnsured = true;
|
|
500
|
+
// Homebrew vendors and upgrades the MCP server alongside the CLI (the formula
|
|
501
|
+
// installs `dgmo-mcp` into the same prefix). Don't npm-install a second global
|
|
502
|
+
// copy that would shadow brew's — `brew upgrade dgmo` keeps it current.
|
|
503
|
+
if (isHomebrewManaged()) {
|
|
504
|
+
console.log(
|
|
505
|
+
commandExists('dgmo-mcp')
|
|
506
|
+
? '✓ MCP server managed by Homebrew (upgrades with `brew upgrade dgmo`)'
|
|
507
|
+
: ' MCP server not found — it will be fetched on first use via npx.'
|
|
508
|
+
);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (opts.dryRun) {
|
|
512
|
+
console.log(' [dry-run] would install/upgrade @diagrammo/dgmo-mcp@latest');
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
// Always pull @latest, not just install-if-absent: the MCP tools live in a
|
|
516
|
+
// separately-versioned package, so re-running `dgmo install` (e.g. after a
|
|
517
|
+
// `dgmo` upgrade) is what refreshes the server. Non-fatal — `dgmo mcp` falls
|
|
518
|
+
// back to `npx …@latest`.
|
|
519
|
+
try {
|
|
520
|
+
const verb = commandExists('dgmo-mcp') ? 'Updating' : 'Installing';
|
|
521
|
+
console.log(`${verb} the MCP server (@diagrammo/dgmo-mcp@latest)…`);
|
|
522
|
+
execSync('npm install -g @diagrammo/dgmo-mcp@latest', { stdio: 'inherit' });
|
|
523
|
+
console.log('✓ MCP server up to date');
|
|
524
|
+
} catch {
|
|
525
|
+
console.log(
|
|
526
|
+
' Could not install now — it will be fetched on first use via npx.'
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
1029
530
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
} else {
|
|
1046
|
-
console.log(' Skipped skill install.');
|
|
1047
|
-
}
|
|
531
|
+
// Write helper that honors --dry-run and creates parent dirs.
|
|
532
|
+
function writeOut(
|
|
533
|
+
opts: InstallOpts,
|
|
534
|
+
path: string,
|
|
535
|
+
content: string,
|
|
536
|
+
label: string
|
|
537
|
+
): void {
|
|
538
|
+
if (opts.dryRun) {
|
|
539
|
+
console.log(` [dry-run] would write ${label}`);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
543
|
+
writeFileSync(path, content, 'utf-8');
|
|
544
|
+
console.log(`✓ ${label}`);
|
|
545
|
+
}
|
|
1048
546
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const yes =
|
|
1062
|
-
ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
|
|
1063
|
-
if (yes) {
|
|
1064
|
-
console.log('Installing @diagrammo/dgmo-mcp...');
|
|
1065
|
-
execSync('npm install -g @diagrammo/dgmo-mcp', { stdio: 'inherit' });
|
|
1066
|
-
console.log('✓ @diagrammo/dgmo-mcp installed');
|
|
1067
|
-
} else {
|
|
1068
|
-
console.log(
|
|
1069
|
-
' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
|
|
547
|
+
// Merge `{ mcpServers: { dgmo: dgmo mcp } }` into a JSON config (Claude Code /
|
|
548
|
+
// Claude Desktop style), preserving any other entries.
|
|
549
|
+
function upsertJsonMcp(opts: InstallOpts, path: string): void {
|
|
550
|
+
let cfg: Record<string, unknown> = {};
|
|
551
|
+
if (existsSync(path)) {
|
|
552
|
+
const raw = readFileSync(path, 'utf-8');
|
|
553
|
+
if (raw.trim().length > 0) {
|
|
554
|
+
try {
|
|
555
|
+
cfg = JSON.parse(raw);
|
|
556
|
+
} catch {
|
|
557
|
+
console.error(
|
|
558
|
+
`Error: ${path} is not valid JSON — fix it and re-run (skipping it for now).`
|
|
1070
559
|
);
|
|
560
|
+
return;
|
|
1071
561
|
}
|
|
1072
|
-
} else {
|
|
1073
|
-
console.log('✓ dgmo-mcp already installed');
|
|
1074
562
|
}
|
|
563
|
+
}
|
|
564
|
+
const servers =
|
|
565
|
+
(cfg['mcpServers'] as Record<string, unknown> | undefined) ?? {};
|
|
566
|
+
servers['dgmo'] = MCP_LAUNCH;
|
|
567
|
+
cfg['mcpServers'] = servers;
|
|
568
|
+
writeOut(opts, path, JSON.stringify(cfg, null, 2) + '\n', `MCP server → ${path}`);
|
|
569
|
+
}
|
|
1075
570
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
);
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
const mcpServers =
|
|
1097
|
-
(settings['mcpServers'] as Record<string, unknown> | undefined) ?? {};
|
|
1098
|
-
mcpServers['dgmo'] = mcpEntry;
|
|
1099
|
-
settings['mcpServers'] = mcpServers;
|
|
1100
|
-
writeFileSync(
|
|
1101
|
-
settingsPath,
|
|
1102
|
-
JSON.stringify(settings, null, 2) + '\n',
|
|
1103
|
-
'utf-8'
|
|
1104
|
-
);
|
|
1105
|
-
console.log('✓ MCP server added to ~/.claude/settings.json');
|
|
1106
|
-
} else {
|
|
1107
|
-
const mcpPath = join(process.cwd(), '.mcp.json');
|
|
1108
|
-
let mcp: Record<string, unknown> = {};
|
|
1109
|
-
if (existsSync(mcpPath)) {
|
|
1110
|
-
try {
|
|
1111
|
-
mcp = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
1112
|
-
} catch {
|
|
1113
|
-
/* use empty */
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
const mcpServers =
|
|
1117
|
-
(mcp['mcpServers'] as Record<string, unknown> | undefined) ?? {};
|
|
1118
|
-
mcpServers['dgmo'] = mcpEntry;
|
|
1119
|
-
mcp['mcpServers'] = mcpServers;
|
|
1120
|
-
writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n', 'utf-8');
|
|
1121
|
-
console.log(
|
|
1122
|
-
`✓ MCP server configured: ${join(process.cwd(), '.mcp.json')}`
|
|
1123
|
-
);
|
|
1124
|
-
}
|
|
571
|
+
// Which AI surfaces are present on this machine? Drives zero-prompt auto setup.
|
|
572
|
+
function detectSurfaces(): InstallTarget[] {
|
|
573
|
+
const found: InstallTarget[] = [];
|
|
574
|
+
if (existsSync(join(homedir(), '.claude'))) found.push('claude-code');
|
|
575
|
+
if (existsSync(join(homedir(), '.codex')) || commandExists('codex'))
|
|
576
|
+
found.push('codex');
|
|
577
|
+
if (existsSync(dirname(claudeDesktopConfigPath())))
|
|
578
|
+
found.push('claude-desktop');
|
|
579
|
+
if (existsSync(join(homedir(), '.cursor'))) found.push('cursor');
|
|
580
|
+
if (
|
|
581
|
+
existsSync(join(homedir(), '.windsurf')) ||
|
|
582
|
+
existsSync(join(homedir(), '.codeium'))
|
|
583
|
+
)
|
|
584
|
+
found.push('windsurf');
|
|
585
|
+
// Copilot is project-scoped: only when the current repo has a .github dir.
|
|
586
|
+
if (existsSync(join(process.cwd(), '.github'))) found.push('copilot');
|
|
587
|
+
return found;
|
|
588
|
+
}
|
|
1125
589
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
590
|
+
async function runOneInstall(
|
|
591
|
+
target: InstallTarget,
|
|
592
|
+
opts: InstallOpts
|
|
593
|
+
): Promise<void> {
|
|
594
|
+
switch (target) {
|
|
595
|
+
case 'claude-code':
|
|
596
|
+
return installClaudeCode(opts);
|
|
597
|
+
case 'codex':
|
|
598
|
+
return installCodex(opts);
|
|
599
|
+
case 'claude-desktop':
|
|
600
|
+
return installClaudeDesktop(opts);
|
|
601
|
+
case 'cursor':
|
|
602
|
+
return installCursor(opts);
|
|
603
|
+
case 'windsurf':
|
|
604
|
+
return installWindsurf(opts);
|
|
605
|
+
case 'copilot':
|
|
606
|
+
return installCopilot(opts);
|
|
1129
607
|
}
|
|
608
|
+
}
|
|
1130
609
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
});
|
|
1149
|
-
rl.question(prompt, (answer) => {
|
|
1150
|
-
rl.close();
|
|
1151
|
-
const yes = alreadyExists
|
|
1152
|
-
? answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'
|
|
1153
|
-
: answer === '' ||
|
|
1154
|
-
answer.toLowerCase() === 'y' ||
|
|
1155
|
-
answer.toLowerCase() === 'yes';
|
|
1156
|
-
if (!yes) {
|
|
1157
|
-
console.error('Aborted.');
|
|
1158
|
-
process.exit(0);
|
|
1159
|
-
}
|
|
1160
|
-
done();
|
|
1161
|
-
});
|
|
1162
|
-
});
|
|
1163
|
-
if (!existsSync(commandsDir)) {
|
|
1164
|
-
mkdirSync(commandsDir, { recursive: true });
|
|
1165
|
-
}
|
|
1166
|
-
writeFileSync(destPath, CLAUDE_SKILL_CONTENT, 'utf-8');
|
|
1167
|
-
console.log(`Installed: ${destPath}`);
|
|
1168
|
-
console.log('Use /dgmo in Claude Code to activate the skill.');
|
|
610
|
+
function printInstallHelp(): void {
|
|
611
|
+
console.log(`Usage: dgmo install [target] [--scope user|project] [--dry-run]
|
|
612
|
+
|
|
613
|
+
With no target, auto-detects every installed AI assistant and sets up each one
|
|
614
|
+
(no prompts). The only binary you ever install is \`dgmo\` — it provides the MCP
|
|
615
|
+
server via \`dgmo mcp\`.
|
|
616
|
+
|
|
617
|
+
Targets: ${INSTALL_TARGETS.join(', ')}
|
|
618
|
+
--scope user = configure globally for all projects (default)
|
|
619
|
+
project = write config into the current directory
|
|
620
|
+
--dry-run print what would change without writing anything`);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// `dgmo install [target] [--scope ...] [--dry-run]`. No target → auto-detect.
|
|
624
|
+
async function runInstallCommand(args: string[]): Promise<void> {
|
|
625
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
626
|
+
printInstallHelp();
|
|
1169
627
|
return;
|
|
1170
628
|
}
|
|
629
|
+
const scopeIdx = args.indexOf('--scope');
|
|
630
|
+
const scope: 'user' | 'project' =
|
|
631
|
+
scopeIdx >= 0 && args[scopeIdx + 1] === 'project' ? 'project' : 'user';
|
|
632
|
+
const opts: InstallOpts = { scope, dryRun: args.includes('--dry-run') };
|
|
1171
633
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
} catch {
|
|
1177
|
-
console.error(
|
|
1178
|
-
'codex not found. Install Codex CLI first: https://openai.com/codex'
|
|
1179
|
-
);
|
|
1180
|
-
process.exit(1);
|
|
1181
|
-
}
|
|
634
|
+
const positional = args.filter(
|
|
635
|
+
(a, i) => !a.startsWith('-') && args[i - 1] !== '--scope'
|
|
636
|
+
);
|
|
637
|
+
const target = positional[0] as InstallTarget | undefined;
|
|
1182
638
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
rl.close();
|
|
1191
|
-
resolve(answer);
|
|
1192
|
-
});
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
// Check / install dgmo-mcp binary
|
|
1196
|
-
let dgmoMcpInstalled = false;
|
|
1197
|
-
try {
|
|
1198
|
-
execSync('which dgmo-mcp', { stdio: 'pipe' });
|
|
1199
|
-
dgmoMcpInstalled = true;
|
|
1200
|
-
} catch {
|
|
1201
|
-
/* not found */
|
|
1202
|
-
}
|
|
1203
|
-
if (!dgmoMcpInstalled) {
|
|
1204
|
-
const ans = await ask(
|
|
1205
|
-
'\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] '
|
|
1206
|
-
);
|
|
1207
|
-
const yes =
|
|
1208
|
-
ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
|
|
1209
|
-
if (yes) {
|
|
1210
|
-
console.log('Installing @diagrammo/dgmo-mcp...');
|
|
1211
|
-
try {
|
|
1212
|
-
execSync('npm install -g @diagrammo/dgmo-mcp', { stdio: 'inherit' });
|
|
1213
|
-
console.log('✓ @diagrammo/dgmo-mcp installed');
|
|
1214
|
-
} catch {
|
|
1215
|
-
console.error('Error: Failed to install @diagrammo/dgmo-mcp.');
|
|
1216
|
-
console.error('Try manually: npm install -g @diagrammo/dgmo-mcp');
|
|
1217
|
-
}
|
|
1218
|
-
} else {
|
|
1219
|
-
console.log(
|
|
1220
|
-
' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
} else {
|
|
1224
|
-
console.log('✓ dgmo-mcp already installed');
|
|
639
|
+
console.log(renderBanner());
|
|
640
|
+
|
|
641
|
+
if (target) {
|
|
642
|
+
if (!INSTALL_TARGETS.includes(target)) {
|
|
643
|
+
console.error(`Error: Unknown install target "${target}".`);
|
|
644
|
+
console.error(`Valid targets: ${INSTALL_TARGETS.join(', ')}`);
|
|
645
|
+
process.exit(1);
|
|
1225
646
|
}
|
|
647
|
+
await runOneInstall(target, opts);
|
|
648
|
+
console.log('\nRestart the assistant to activate the dgmo tools.');
|
|
649
|
+
console.log('(Re-run `dgmo install` after upgrading dgmo to refresh.)');
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
1226
652
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
console.log(
|
|
1230
|
-
' 1) This project only — write .codex/config.toml here [default]'
|
|
1231
|
-
);
|
|
653
|
+
const detected = detectSurfaces();
|
|
654
|
+
if (detected.length === 0) {
|
|
1232
655
|
console.log(
|
|
1233
|
-
'
|
|
656
|
+
'No AI assistants detected (looked for Claude Code, Codex, Claude Desktop, Cursor, Windsurf, a .github dir).'
|
|
1234
657
|
);
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
scopeAns.trim() !== '' &&
|
|
1238
|
-
scopeAns.trim() !== '1' &&
|
|
1239
|
-
scopeAns.trim() !== '2'
|
|
1240
|
-
) {
|
|
1241
|
-
console.log(
|
|
1242
|
-
` Unrecognized input "${scopeAns.trim()}", defaulting to option 1.`
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
const useGlobal = scopeAns.trim() === '2';
|
|
1246
|
-
const tomlEntry = '[mcp_servers.dgmo]\ncommand = "dgmo-mcp"\n';
|
|
1247
|
-
|
|
1248
|
-
if (useGlobal) {
|
|
1249
|
-
const configPath = join(homedir(), '.codex', 'config.toml');
|
|
1250
|
-
mkdirSync(join(homedir(), '.codex'), { recursive: true });
|
|
1251
|
-
const existing = existsSync(configPath)
|
|
1252
|
-
? readFileSync(configPath, 'utf-8')
|
|
1253
|
-
: '';
|
|
1254
|
-
if (existing.includes('[mcp_servers.dgmo]')) {
|
|
1255
|
-
console.log('✓ MCP server already configured in ~/.codex/config.toml');
|
|
1256
|
-
} else {
|
|
1257
|
-
const separator = existing.length > 0 ? '\n' : '';
|
|
1258
|
-
writeFileSync(configPath, existing + separator + tomlEntry, 'utf-8');
|
|
1259
|
-
console.log('✓ MCP server added to ~/.codex/config.toml');
|
|
1260
|
-
}
|
|
1261
|
-
} else {
|
|
1262
|
-
const codexDir = join(process.cwd(), '.codex');
|
|
1263
|
-
const configPath = join(codexDir, 'config.toml');
|
|
1264
|
-
mkdirSync(codexDir, { recursive: true });
|
|
1265
|
-
const existing = existsSync(configPath)
|
|
1266
|
-
? readFileSync(configPath, 'utf-8')
|
|
1267
|
-
: '';
|
|
1268
|
-
if (existing.includes('[mcp_servers.dgmo]')) {
|
|
1269
|
-
console.log(`✓ MCP server already configured in .codex/config.toml`);
|
|
1270
|
-
} else {
|
|
1271
|
-
const separator = existing.length > 0 ? '\n' : '';
|
|
1272
|
-
writeFileSync(configPath, existing + separator + tomlEntry, 'utf-8');
|
|
1273
|
-
console.log(`✓ MCP server configured: ${configPath}`);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// Install the dgmo-diagramming skill at ~/.codex/skills/dgmo-diagramming/SKILL.md
|
|
1278
|
-
const skillDir = join(homedir(), '.codex', 'skills', 'dgmo-diagramming');
|
|
1279
|
-
const skillPath = join(skillDir, 'SKILL.md');
|
|
1280
|
-
const skillBody = CODEX_SKILL_FRONTMATTER + CODEX_SKILL_CONTENT;
|
|
1281
|
-
if (existsSync(skillPath)) {
|
|
1282
|
-
const existingSkill = readFileSync(skillPath, 'utf-8');
|
|
1283
|
-
if (existingSkill === skillBody) {
|
|
1284
|
-
console.log('✓ dgmo-diagramming skill already up to date');
|
|
1285
|
-
} else {
|
|
1286
|
-
const ans = await ask(
|
|
1287
|
-
'\n~/.codex/skills/dgmo-diagramming/SKILL.md exists. Overwrite? [Y/n] '
|
|
1288
|
-
);
|
|
1289
|
-
const yes =
|
|
1290
|
-
ans === '' ||
|
|
1291
|
-
ans.toLowerCase() === 'y' ||
|
|
1292
|
-
ans.toLowerCase() === 'yes';
|
|
1293
|
-
if (yes) {
|
|
1294
|
-
writeFileSync(skillPath, skillBody, 'utf-8');
|
|
1295
|
-
console.log(`✓ Skill updated: ${skillPath}`);
|
|
1296
|
-
} else {
|
|
1297
|
-
console.log(' Skipped skill update.');
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
} else {
|
|
1301
|
-
mkdirSync(skillDir, { recursive: true });
|
|
1302
|
-
writeFileSync(skillPath, skillBody, 'utf-8');
|
|
1303
|
-
console.log(`✓ Skill installed: ${skillPath}`);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// Non-destructive AGENTS.md handling: append a marked note only if AGENTS.md
|
|
1307
|
-
// already exists and doesn't already contain the marker. Never create it.
|
|
1308
|
-
const agentsPath = join(process.cwd(), 'AGENTS.md');
|
|
1309
|
-
if (existsSync(agentsPath)) {
|
|
1310
|
-
const existingAgents = readFileSync(agentsPath, 'utf-8');
|
|
1311
|
-
if (existingAgents.includes(CODEX_AGENTS_NOTE_MARKER)) {
|
|
1312
|
-
console.log('✓ AGENTS.md already mentions dgmo');
|
|
1313
|
-
} else {
|
|
1314
|
-
const separator = existingAgents.endsWith('\n') ? '\n' : '\n\n';
|
|
1315
|
-
writeFileSync(
|
|
1316
|
-
agentsPath,
|
|
1317
|
-
existingAgents + separator + CODEX_AGENTS_NOTE,
|
|
1318
|
-
'utf-8'
|
|
1319
|
-
);
|
|
1320
|
-
console.log(`✓ Appended dgmo note to: ${agentsPath}`);
|
|
1321
|
-
}
|
|
1322
|
-
} else {
|
|
1323
|
-
console.log(
|
|
1324
|
-
' No AGENTS.md found in cwd — skipped (the skill is enough).'
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
console.log('\nRestart Codex to activate the skill and MCP server.');
|
|
658
|
+
console.log('Install one and re-run `dgmo install`, or name a target:');
|
|
659
|
+
console.log(` dgmo install <${INSTALL_TARGETS.join('|')}>`);
|
|
1329
660
|
return;
|
|
1330
661
|
}
|
|
662
|
+
console.log(`Detected: ${detected.join(', ')} — setting up each.\n`);
|
|
663
|
+
for (const t of detected) {
|
|
664
|
+
console.log(`── ${t} ──`);
|
|
665
|
+
await runOneInstall(t, opts);
|
|
666
|
+
console.log('');
|
|
667
|
+
}
|
|
668
|
+
console.log('Done. Restart your AI assistant(s) to activate the dgmo tools.');
|
|
669
|
+
console.log('(Re-run `dgmo install` after upgrading dgmo to refresh.)');
|
|
670
|
+
}
|
|
1331
671
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
/* not found */
|
|
1352
|
-
}
|
|
1353
|
-
if (!dgmoMcpInstalled) {
|
|
1354
|
-
const ans = await ask(
|
|
1355
|
-
'\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] '
|
|
1356
|
-
);
|
|
1357
|
-
const yes =
|
|
1358
|
-
ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
|
|
1359
|
-
if (yes) {
|
|
1360
|
-
console.log('Installing @diagrammo/dgmo-mcp...');
|
|
1361
|
-
try {
|
|
1362
|
-
execSync('npm install -g @diagrammo/dgmo-mcp', { stdio: 'inherit' });
|
|
1363
|
-
console.log('✓ @diagrammo/dgmo-mcp installed');
|
|
1364
|
-
} catch {
|
|
1365
|
-
console.error('Error: Failed to install @diagrammo/dgmo-mcp.');
|
|
1366
|
-
console.error('Try manually: npm install -g @diagrammo/dgmo-mcp');
|
|
1367
|
-
}
|
|
1368
|
-
} else {
|
|
1369
|
-
console.log(
|
|
1370
|
-
' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
|
|
1371
|
-
);
|
|
1372
|
-
}
|
|
1373
|
-
} else {
|
|
1374
|
-
console.log('✓ dgmo-mcp already installed');
|
|
1375
|
-
}
|
|
672
|
+
async function installClaudeCode(opts: InstallOpts): Promise<void> {
|
|
673
|
+
const claudeDir = join(homedir(), '.claude');
|
|
674
|
+
// Skill — single-sourced and always refreshed to the maintained latest.
|
|
675
|
+
writeOut(
|
|
676
|
+
opts,
|
|
677
|
+
join(claudeDir, 'commands', 'dgmo.md'),
|
|
678
|
+
readClaudeSkill(),
|
|
679
|
+
'Skill → ~/.claude/commands/dgmo.md'
|
|
680
|
+
);
|
|
681
|
+
ensureDgmoMcp(opts);
|
|
682
|
+
if (opts.scope === 'user') {
|
|
683
|
+
upsertJsonMcp(opts, join(claudeDir, 'settings.json'));
|
|
684
|
+
console.log(' (scope: all projects)');
|
|
685
|
+
} else {
|
|
686
|
+
upsertJsonMcp(opts, join(process.cwd(), '.mcp.json'));
|
|
687
|
+
console.log(' (scope: this project)');
|
|
688
|
+
}
|
|
689
|
+
console.log(' Then type /dgmo in any Claude Code session.');
|
|
690
|
+
}
|
|
1376
691
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
homedir(),
|
|
1398
|
-
'.config',
|
|
1399
|
-
'Claude',
|
|
1400
|
-
'claude_desktop_config.json'
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
692
|
+
async function installCodex(opts: InstallOpts): Promise<void> {
|
|
693
|
+
ensureDgmoMcp(opts);
|
|
694
|
+
|
|
695
|
+
// MCP config (TOML). The block migrates in place if a legacy entry exists.
|
|
696
|
+
const tomlEntry = '[mcp_servers.dgmo]\ncommand = "dgmo"\nargs = ["mcp"]\n';
|
|
697
|
+
const configPath =
|
|
698
|
+
opts.scope === 'user'
|
|
699
|
+
? join(homedir(), '.codex', 'config.toml')
|
|
700
|
+
: join(process.cwd(), '.codex', 'config.toml');
|
|
701
|
+
const existing = existsSync(configPath)
|
|
702
|
+
? readFileSync(configPath, 'utf-8')
|
|
703
|
+
: '';
|
|
704
|
+
let next: string;
|
|
705
|
+
if (existing.includes('[mcp_servers.dgmo]')) {
|
|
706
|
+
next = existing.replace(/\[mcp_servers\.dgmo\][^[]*/, tomlEntry);
|
|
707
|
+
} else {
|
|
708
|
+
const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
709
|
+
next = existing + sep + tomlEntry;
|
|
710
|
+
}
|
|
711
|
+
writeOut(opts, configPath, next, `MCP server → ${configPath}`);
|
|
1403
712
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
>;
|
|
1412
|
-
[key: string]: unknown;
|
|
1413
|
-
};
|
|
1414
|
-
let config: ClaudeDesktopConfig = {};
|
|
1415
|
-
if (existsSync(configPath)) {
|
|
1416
|
-
const raw = readFileSync(configPath, 'utf-8');
|
|
1417
|
-
if (raw.trim().length > 0) {
|
|
1418
|
-
try {
|
|
1419
|
-
config = JSON.parse(raw) as ClaudeDesktopConfig;
|
|
1420
|
-
} catch {
|
|
1421
|
-
console.error(
|
|
1422
|
-
`Error: ${configPath} exists but is not valid JSON. Fix it manually and re-run, or remove the file to regenerate.`
|
|
1423
|
-
);
|
|
1424
|
-
process.exit(1);
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
713
|
+
// Skill — single-sourced, always refreshed.
|
|
714
|
+
writeOut(
|
|
715
|
+
opts,
|
|
716
|
+
join(homedir(), '.codex', 'skills', 'dgmo-diagramming', 'SKILL.md'),
|
|
717
|
+
readCodexSkill(),
|
|
718
|
+
'Skill → ~/.codex/skills/dgmo-diagramming/SKILL.md'
|
|
719
|
+
);
|
|
1428
720
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
`\nA "dgmo" entry already exists in ${configPath}. Overwrite? [y/N] `
|
|
1436
|
-
);
|
|
1437
|
-
if (ans.toLowerCase() !== 'y' && ans.toLowerCase() !== 'yes') {
|
|
1438
|
-
console.log(' Skipped.');
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
config.mcpServers = {
|
|
1443
|
-
...(config.mcpServers ?? {}),
|
|
1444
|
-
dgmo: { command: 'dgmo-mcp' },
|
|
1445
|
-
};
|
|
1446
|
-
mkdirSync(join(configPath, '..'), { recursive: true });
|
|
721
|
+
// Non-destructive AGENTS.md note: only append to an existing file, never create.
|
|
722
|
+
const agentsPath = join(process.cwd(), 'AGENTS.md');
|
|
723
|
+
if (!opts.dryRun && existsSync(agentsPath)) {
|
|
724
|
+
const existingAgents = readFileSync(agentsPath, 'utf-8');
|
|
725
|
+
if (!existingAgents.includes(CODEX_AGENTS_NOTE_MARKER)) {
|
|
726
|
+
const separator = existingAgents.endsWith('\n') ? '\n' : '\n\n';
|
|
1447
727
|
writeFileSync(
|
|
1448
|
-
|
|
1449
|
-
|
|
728
|
+
agentsPath,
|
|
729
|
+
existingAgents + separator + CODEX_AGENTS_NOTE,
|
|
1450
730
|
'utf-8'
|
|
1451
731
|
);
|
|
1452
|
-
console.log(
|
|
732
|
+
console.log('✓ Appended dgmo note to AGENTS.md');
|
|
1453
733
|
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async function installClaudeDesktop(opts: InstallOpts): Promise<void> {
|
|
738
|
+
ensureDgmoMcp(opts);
|
|
739
|
+
// Claude Desktop config is always the per-user app config (scope is N/A).
|
|
740
|
+
upsertJsonMcp(opts, claudeDesktopConfigPath());
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Cursor speaks MCP via ~/.cursor/mcp.json (user) or .cursor/mcp.json (project),
|
|
744
|
+
// using the same { mcpServers } schema as Claude — so it gets the full tools,
|
|
745
|
+
// not just syntax guidance. We also drop the shipped .cursorrules so the model
|
|
746
|
+
// has inline context without a tool round-trip.
|
|
747
|
+
function installCursor(opts: InstallOpts): void {
|
|
748
|
+
ensureDgmoMcp(opts);
|
|
749
|
+
const path =
|
|
750
|
+
opts.scope === 'project'
|
|
751
|
+
? join(process.cwd(), '.cursor', 'mcp.json')
|
|
752
|
+
: join(homedir(), '.cursor', 'mcp.json');
|
|
753
|
+
upsertJsonMcp(opts, path);
|
|
754
|
+
writeOut(
|
|
755
|
+
opts,
|
|
756
|
+
join(process.cwd(), '.cursorrules'),
|
|
757
|
+
readPackagedFile('.cursorrules'),
|
|
758
|
+
'Cursor rules → .cursorrules'
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Windsurf (Codeium) reads MCP from ~/.codeium/windsurf/mcp_config.json.
|
|
763
|
+
function installWindsurf(opts: InstallOpts): void {
|
|
764
|
+
ensureDgmoMcp(opts);
|
|
765
|
+
upsertJsonMcp(
|
|
766
|
+
opts,
|
|
767
|
+
join(homedir(), '.codeium', 'windsurf', 'mcp_config.json')
|
|
768
|
+
);
|
|
769
|
+
writeOut(
|
|
770
|
+
opts,
|
|
771
|
+
join(process.cwd(), '.windsurfrules'),
|
|
772
|
+
readPackagedFile('.windsurfrules'),
|
|
773
|
+
'Windsurf rules → .windsurfrules'
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Copilot has no MCP path — ship the project-root instructions file it auto-loads.
|
|
778
|
+
function installCopilot(opts: InstallOpts): void {
|
|
779
|
+
writeOut(
|
|
780
|
+
opts,
|
|
781
|
+
join(process.cwd(), '.github', 'copilot-instructions.md'),
|
|
782
|
+
readPackagedFile('.github', 'copilot-instructions.md'),
|
|
783
|
+
'Copilot context → .github/copilot-instructions.md'
|
|
784
|
+
);
|
|
785
|
+
}
|
|
1454
786
|
|
|
1455
|
-
|
|
787
|
+
async function main(): Promise<void> {
|
|
788
|
+
// Subcommand dispatch — each is parsed independently from the rendering
|
|
789
|
+
// flags so their surface doesn't pollute `parseArgs`.
|
|
790
|
+
const sub = process.argv[2];
|
|
791
|
+
if (sub === 'map-search') {
|
|
792
|
+
await runMapSearchCommand(process.argv.slice(3));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (sub === 'share') {
|
|
796
|
+
await runShareCommand(process.argv.slice(3));
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (sub === 'types') {
|
|
800
|
+
runTypesCommand(process.argv.slice(3));
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (sub === 'install') {
|
|
804
|
+
await runInstallCommand(process.argv.slice(3));
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (sub === 'mcp') {
|
|
808
|
+
runMcpCommand(process.argv.slice(3));
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const opts = parseArgs(process.argv);
|
|
812
|
+
|
|
813
|
+
if (opts.help) {
|
|
814
|
+
printHelp();
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (opts.version) {
|
|
819
|
+
printVersion();
|
|
1456
820
|
return;
|
|
1457
821
|
}
|
|
1458
822
|
|
|
@@ -1502,15 +866,16 @@ async function main(): Promise<void> {
|
|
|
1502
866
|
content = resolved.content;
|
|
1503
867
|
}
|
|
1504
868
|
|
|
1505
|
-
//
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
console.error('Error: --copy can only be used with -o url');
|
|
869
|
+
// `-o url` was replaced by `dgmo share`; catch the old usage with a helpful
|
|
870
|
+
// redirect instead of writing a file literally named "url".
|
|
871
|
+
if (opts.output === 'url') {
|
|
872
|
+
console.error('Error: `-o url` was replaced by `dgmo share <input>`.');
|
|
873
|
+
console.error(`Try: dgmo share ${opts.input ?? '<input>'}`);
|
|
1511
874
|
process.exit(1);
|
|
1512
875
|
}
|
|
1513
876
|
|
|
877
|
+
const format = inferFormat(opts.output);
|
|
878
|
+
|
|
1514
879
|
const chartType = parseDgmoChartType(content);
|
|
1515
880
|
|
|
1516
881
|
// Helper for JSON error output
|
|
@@ -1534,55 +899,6 @@ async function main(): Promise<void> {
|
|
|
1534
899
|
process.exit(1);
|
|
1535
900
|
}
|
|
1536
901
|
|
|
1537
|
-
// URL output — encode DSL directly, no rendering needed.
|
|
1538
|
-
// PERT diagrams may contain `start-date now`; resolve to today's
|
|
1539
|
-
// date BEFORE encoding so the share-link captures authoring intent
|
|
1540
|
-
// (per spec D9). Cheap to call on non-PERT content — the regex
|
|
1541
|
-
// matches nothing.
|
|
1542
|
-
if (format === 'url') {
|
|
1543
|
-
const sourceForUrl =
|
|
1544
|
-
chartType === 'pert' ? normalizePertSourceForShare(content) : content;
|
|
1545
|
-
const result = encodeDiagramUrl(sourceForUrl);
|
|
1546
|
-
if (result.error) {
|
|
1547
|
-
exitWithJsonError(
|
|
1548
|
-
`Error: Diagram too large for URL sharing (${result.compressedSize} bytes, limit ${result.limit} bytes)`
|
|
1549
|
-
);
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
if (opts.copy) {
|
|
1553
|
-
try {
|
|
1554
|
-
const platform = process.platform;
|
|
1555
|
-
if (platform === 'darwin') {
|
|
1556
|
-
execSync('pbcopy', { input: result.url });
|
|
1557
|
-
} else if (platform === 'win32') {
|
|
1558
|
-
execSync('clip', { input: result.url });
|
|
1559
|
-
} else {
|
|
1560
|
-
execSync('xclip -selection clipboard', { input: result.url });
|
|
1561
|
-
}
|
|
1562
|
-
console.error('URL copied to clipboard');
|
|
1563
|
-
} catch {
|
|
1564
|
-
console.error('Warning: Could not copy to clipboard');
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
if (opts.json) {
|
|
1569
|
-
process.stdout.write(
|
|
1570
|
-
JSON.stringify(
|
|
1571
|
-
{
|
|
1572
|
-
success: true,
|
|
1573
|
-
url: result.url,
|
|
1574
|
-
...(chartType ? { chartType } : {}),
|
|
1575
|
-
},
|
|
1576
|
-
null,
|
|
1577
|
-
2
|
|
1578
|
-
) + '\n'
|
|
1579
|
-
);
|
|
1580
|
-
} else {
|
|
1581
|
-
process.stdout.write(result.url + '\n');
|
|
1582
|
-
}
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
902
|
const paletteColors = getPalette(opts.palette)[
|
|
1587
903
|
opts.theme === 'dark' ? 'dark' : 'light'
|
|
1588
904
|
];
|
|
@@ -1595,7 +911,7 @@ async function main(): Promise<void> {
|
|
|
1595
911
|
// Print warnings even if rendering succeeds
|
|
1596
912
|
if (!opts.json) {
|
|
1597
913
|
for (const w of warnings) {
|
|
1598
|
-
console.error(
|
|
914
|
+
console.error(`⚠ ${formatDgmoError(w)}`);
|
|
1599
915
|
}
|
|
1600
916
|
}
|
|
1601
917
|
|
|
@@ -1606,7 +922,7 @@ async function main(): Promise<void> {
|
|
|
1606
922
|
exitWithJsonError(formatDgmoError(firstError), firstError.line);
|
|
1607
923
|
}
|
|
1608
924
|
for (const e of errors) {
|
|
1609
|
-
console.error(
|
|
925
|
+
console.error(`✖ ${formatDgmoError(e)}`);
|
|
1610
926
|
}
|
|
1611
927
|
}
|
|
1612
928
|
|