@diagrammo/dgmo 0.47.0 → 0.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.js +11 -11
- package/dist/auto.js +16 -16
- package/dist/auto.mjs +41 -12
- package/dist/block.js +4 -4
- package/dist/{chunk-FNB3PNGF.js → chunk-7DTWEFUE.js} +3 -3
- package/dist/{chunk-2DXMZRWJ.js → chunk-B65G5DNL.js} +8 -1
- package/dist/{chunk-RZCF2VVM.js → chunk-B7JUCQUA.js} +3 -1
- package/dist/{chunk-DU7W62N5.js → chunk-FANX6UJR.js} +3 -3
- package/dist/{chunk-6FZYCKXB.js → chunk-HSH73DP6.js} +2 -2
- package/dist/{chunk-P4RH4OSP.js → chunk-ILKORZ3H.js} +2 -2
- package/dist/{chunk-Y5QGVTVC.js → chunk-JTSMEYIZ.js} +7 -3
- package/dist/{chunk-RCERWOJR.js → chunk-ODZVHU3R.js} +1 -2
- package/dist/{chunk-DZAQBW35.js → chunk-SGZYXELT.js} +3 -7
- package/dist/{chunk-CUVHQHZ7.js → chunk-THXCFRTU.js} +3 -3
- package/dist/{chunk-IH6ZMVQ5.js → chunk-UC7TFHUM.js} +29 -11
- package/dist/{chunk-EMS2SK26.js → chunk-ZBWMSD5N.js} +3 -1
- package/dist/cli.cjs +15 -15
- package/dist/{dgmo-router-DZCIDUO5.js → dgmo-router-X35VLZLM.js} +3 -3
- package/dist/element.js +16 -16
- package/dist/element.mjs +41 -12
- package/dist/{flowchart-renderer-ZAEOL4DX.js → flowchart-renderer-SNDSII4D.js} +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -4
- package/dist/{parser-FUSKFGZN.js → parser-2EWECDSA.js} +2 -2
- package/dist/{renderer-UWA5XS74.js → renderer-4JHRUBE6.js} +3 -3
- package/dist/{renderer-WZRREQ2P.js → renderer-5MYBMBYO.js} +2 -2
- package/dist/{renderer-TOCOVLHS.js → renderer-B2OMQIQG.js} +3 -3
- package/dist/{renderer-2XQQQ6QR.js → renderer-N43WR5GV.js} +3 -3
- package/dist/{renderer-T7YKC5VP.js → renderer-ZRNG54CW.js} +3 -3
- package/dist/{state-renderer-RFYHSP4K.js → state-renderer-B4DQBM6M.js} +2 -2
- package/package.json +23 -71
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -5719
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.cjs +0 -526
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -160
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/dist/themes-1-CuKpeH.d.cts +0 -908
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/parser.ts +0 -427
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/layout.ts +0 -2441
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/calculator.ts +0 -979
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/time-ticks.ts +0 -253
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
package/src/cli.ts
DELETED
|
@@ -1,1080 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from 'node:fs';
|
|
9
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
10
|
-
import { homedir, platform } from 'node:os';
|
|
11
|
-
import { resolve, join, dirname, basename, extname } from 'node:path';
|
|
12
|
-
import { Resvg } from '@resvg/resvg-js';
|
|
13
|
-
import { render } from './render';
|
|
14
|
-
import {
|
|
15
|
-
parseDgmo,
|
|
16
|
-
getAllChartTypes,
|
|
17
|
-
CHART_TYPE_DESCRIPTIONS,
|
|
18
|
-
} from './dgmo-router';
|
|
19
|
-
import { parseDgmoChartType } from './dgmo-router';
|
|
20
|
-
import { formatDgmoError } from './diagnostics';
|
|
21
|
-
import { listDiagnosticCodes } from './diagnostics-registry';
|
|
22
|
-
import { getPalette, getAvailablePalettes } from './palettes';
|
|
23
|
-
import { DEFAULT_FONT_NAME } from './fonts';
|
|
24
|
-
import { encodeDiagramUrl } from './sharing';
|
|
25
|
-
import { resolveOrgImports } from './org/resolver';
|
|
26
|
-
import { normalizePertSourceForShare } from './pert/share-normalize';
|
|
27
|
-
import { renderBanner } from './cli-banner';
|
|
28
|
-
import { searchMapLocations } from './map/completion';
|
|
29
|
-
import { loadMapData } from './map/load-data';
|
|
30
|
-
|
|
31
|
-
// Derived from the palette registry so new palettes are auto-included.
|
|
32
|
-
const PALETTES = getAvailablePalettes().map((p) => p.id);
|
|
33
|
-
|
|
34
|
-
const THEMES = ['light', 'dark', 'transparent'] as const;
|
|
35
|
-
|
|
36
|
-
// The Claude Code skill (~/.claude/commands/dgmo.md) and the Codex skill
|
|
37
|
-
// (SKILL.md — gen-ai-core covered, with a gate-enforced COMPLETE chart-type
|
|
38
|
-
// index) are SHIPPED in this package via the "files" allowlist and are the
|
|
39
|
-
// single source of truth. The installers below read those files rather than
|
|
40
|
-
// carrying inline copies, so the chart-type list and syntax can never drift
|
|
41
|
-
// from the maintained originals (the stale inline table was the root cause of
|
|
42
|
-
// agents wrongly reporting that a chart type "does not exist").
|
|
43
|
-
const PKG_ROOT = resolve(__dirname, '..');
|
|
44
|
-
|
|
45
|
-
function readPackagedFile(...parts: string[]): string {
|
|
46
|
-
const p = join(PKG_ROOT, ...parts);
|
|
47
|
-
try {
|
|
48
|
-
return readFileSync(p, 'utf-8');
|
|
49
|
-
} catch {
|
|
50
|
-
console.error(`Could not read packaged file: ${p}`);
|
|
51
|
-
console.error(
|
|
52
|
-
'Your @diagrammo/dgmo install may be incomplete — try reinstalling it.'
|
|
53
|
-
);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Claude Code skill source. Carries NO static chart-type table — it directs the
|
|
59
|
-
// agent to query list_chart_types (MCP) / dgmo types (CLI) for the
|
|
60
|
-
// authoritative, complete list.
|
|
61
|
-
const readClaudeSkill = (): string =>
|
|
62
|
-
readPackagedFile('.claude', 'commands', 'dgmo.md');
|
|
63
|
-
|
|
64
|
-
// Codex skill source. Already includes the dgmo-diagramming frontmatter and the
|
|
65
|
-
// gen-ai-core 45-type index.
|
|
66
|
-
const readCodexSkill = (): string => readPackagedFile('SKILL.md');
|
|
67
|
-
|
|
68
|
-
// Sibling Claude Code slash-command files shipped alongside dgmo.md
|
|
69
|
-
// (dgmo-diagram-this, dgmo-document-project, dgmo-codebase-report, ...). dgmo.md
|
|
70
|
-
// is single-sourced via readClaudeSkill(); these are copied verbatim. Returns
|
|
71
|
-
// the *.md basenames in .claude/commands, dgmo.md excluded.
|
|
72
|
-
const listPackagedCommands = (): string[] => {
|
|
73
|
-
try {
|
|
74
|
-
return readdirSync(join(PKG_ROOT, '.claude', 'commands'))
|
|
75
|
-
.filter((f) => f.endsWith('.md') && f !== 'dgmo.md')
|
|
76
|
-
.sort();
|
|
77
|
-
} catch {
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// The /dgmo-codebase-report command, re-skinned as a Codex skill: same body,
|
|
83
|
-
// single-sourced from the Claude command file, with Codex frontmatter prepended
|
|
84
|
-
// so it never drifts from the maintained original.
|
|
85
|
-
const readCodexCodebaseReportSkill = (): string =>
|
|
86
|
-
`---\nname: dgmo-codebase-report\ndescription: Use when the user wants an architecture or onboarding report for a codebase — a single Markdown document explaining how the system works, with embedded DGMO diagrams (module/dependency map, C4, sequence, ER, infra). Produces committable Markdown with \`\`\`dgmo fences, not images.\n---\n\n${readPackagedFile('.claude', 'commands', 'dgmo-codebase-report.md')}`;
|
|
87
|
-
|
|
88
|
-
const CODEX_AGENTS_NOTE_MARKER = '<!-- dgmo-integration -->';
|
|
89
|
-
const CODEX_AGENTS_NOTE = `${CODEX_AGENTS_NOTE_MARKER}
|
|
90
|
-
## Diagrams
|
|
91
|
-
|
|
92
|
-
For architecture diagrams, sequence diagrams, flowcharts, and charts, use the \`dgmo-diagramming\` skill and the configured \`dgmo\` MCP tools (\`open_in_app\`, \`share_diagram\`, \`render_diagram\`, etc.).
|
|
93
|
-
`;
|
|
94
|
-
|
|
95
|
-
function printHelp(): void {
|
|
96
|
-
console.log(renderBanner());
|
|
97
|
-
console.log(`Usage: dgmo <input> [options] Render a .dgmo file to PNG (default) or SVG
|
|
98
|
-
cat input.dgmo | dgmo [options] Render from stdin
|
|
99
|
-
|
|
100
|
-
Commands:
|
|
101
|
-
dgmo share <input> Print a shareable diagrammo.app URL (copies to clipboard)
|
|
102
|
-
dgmo types List all supported chart types
|
|
103
|
-
dgmo diagnostics List every error/warning code (add --json for full catalog)
|
|
104
|
-
dgmo install [target] Set up AI assistants (auto-detects all if no target).
|
|
105
|
-
Targets: claude-code, codex, claude-desktop,
|
|
106
|
-
cursor, windsurf, copilot. --scope user|project
|
|
107
|
-
dgmo mcp Run the MCP server (invoked by AI client configs)
|
|
108
|
-
dgmo map-search <query> Find the map place token (city or IATA code)
|
|
109
|
-
|
|
110
|
-
Render options:
|
|
111
|
-
-o <file> Output file (default: <input>.png in cwd)
|
|
112
|
-
Format inferred from extension: .svg → SVG, else PNG
|
|
113
|
-
With stdin and no -o, PNG is written to stdout
|
|
114
|
-
--theme <theme> Theme: ${THEMES.join(', ')} (default: light)
|
|
115
|
-
--palette <name> Palette: ${PALETTES.join(', ')} (default: slate)
|
|
116
|
-
--json Output structured JSON to stdout
|
|
117
|
-
--help Show this help
|
|
118
|
-
--version Show version`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function printVersion(): void {
|
|
122
|
-
const pkg = JSON.parse(
|
|
123
|
-
readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8')
|
|
124
|
-
);
|
|
125
|
-
console.log(pkg.version);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function parseArgs(argv: string[]): {
|
|
129
|
-
input: string | undefined;
|
|
130
|
-
output: string | undefined;
|
|
131
|
-
theme: (typeof THEMES)[number];
|
|
132
|
-
palette: string;
|
|
133
|
-
help: boolean;
|
|
134
|
-
version: boolean;
|
|
135
|
-
json: boolean;
|
|
136
|
-
} {
|
|
137
|
-
const result = {
|
|
138
|
-
input: undefined as string | undefined,
|
|
139
|
-
output: undefined as string | undefined,
|
|
140
|
-
theme: 'light' as (typeof THEMES)[number],
|
|
141
|
-
palette: 'slate',
|
|
142
|
-
help: false,
|
|
143
|
-
version: false,
|
|
144
|
-
json: false,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const args = argv.slice(2); // skip node + script
|
|
148
|
-
let i = 0;
|
|
149
|
-
|
|
150
|
-
while (i < args.length) {
|
|
151
|
-
const arg = args[i];
|
|
152
|
-
|
|
153
|
-
if (arg === '--help' || arg === '-h') {
|
|
154
|
-
result.help = true;
|
|
155
|
-
i++;
|
|
156
|
-
} else if (arg === '--version' || arg === '-v') {
|
|
157
|
-
result.version = true;
|
|
158
|
-
i++;
|
|
159
|
-
} else if (arg === '-o') {
|
|
160
|
-
result.output = args[++i];
|
|
161
|
-
i++;
|
|
162
|
-
} else if (arg === '--theme') {
|
|
163
|
-
const val = args[++i];
|
|
164
|
-
if (!THEMES.includes(val as (typeof THEMES)[number])) {
|
|
165
|
-
console.error(
|
|
166
|
-
`Error: Invalid theme "${val}". Valid themes: ${THEMES.join(', ')}`
|
|
167
|
-
);
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
result.theme = val as (typeof THEMES)[number];
|
|
171
|
-
i++;
|
|
172
|
-
} else if (arg === '--palette') {
|
|
173
|
-
const val = args[++i];
|
|
174
|
-
if (val === undefined || !PALETTES.includes(val)) {
|
|
175
|
-
console.error(
|
|
176
|
-
`Error: Unknown palette "${val}". Valid palettes: ${PALETTES.join(', ')}`
|
|
177
|
-
);
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
result.palette = val;
|
|
181
|
-
i++;
|
|
182
|
-
} else if (arg === '--json') {
|
|
183
|
-
result.json = true;
|
|
184
|
-
i++;
|
|
185
|
-
} else if (!result.input) {
|
|
186
|
-
result.input = arg;
|
|
187
|
-
i++;
|
|
188
|
-
} else {
|
|
189
|
-
console.error(`Error: Unexpected argument "${arg}"`);
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function inferFormat(outputPath: string | undefined): 'svg' | 'png' {
|
|
198
|
-
if (outputPath && extname(outputPath).toLowerCase() === '.svg') {
|
|
199
|
-
return 'svg';
|
|
200
|
-
}
|
|
201
|
-
return 'png';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const BUNDLED_FONTS = [
|
|
205
|
-
join(__dirname, '..', 'fonts', 'Inter-Regular.ttf'),
|
|
206
|
-
join(__dirname, '..', 'fonts', 'Inter-Bold.ttf'),
|
|
207
|
-
];
|
|
208
|
-
|
|
209
|
-
function svgToPng(svg: string, background?: string): Buffer {
|
|
210
|
-
const fontFiles = BUNDLED_FONTS.filter((f) => existsSync(f));
|
|
211
|
-
const resvg = new Resvg(svg, {
|
|
212
|
-
fitTo: { mode: 'zoom', value: 2 },
|
|
213
|
-
...(background ? { background } : {}),
|
|
214
|
-
font: {
|
|
215
|
-
loadSystemFonts: fontFiles.length === 0,
|
|
216
|
-
...(fontFiles.length > 0 ? { fontFiles } : {}),
|
|
217
|
-
defaultFontFamily: DEFAULT_FONT_NAME,
|
|
218
|
-
sansSerifFamily: DEFAULT_FONT_NAME,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
const rendered = resvg.render();
|
|
222
|
-
return rendered.asPng();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function noInput(): never {
|
|
226
|
-
console.error(renderBanner());
|
|
227
|
-
const samplePath = resolve('sample.dgmo');
|
|
228
|
-
if (existsSync(samplePath)) {
|
|
229
|
-
console.error('Error: No input file specified');
|
|
230
|
-
console.error(`Try: dgmo ${basename(samplePath)}`);
|
|
231
|
-
console.error('Run dgmo --help for all options.');
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
writeFileSync(
|
|
235
|
-
samplePath,
|
|
236
|
-
[
|
|
237
|
-
'sequence',
|
|
238
|
-
'no-activations',
|
|
239
|
-
'',
|
|
240
|
-
'Client -POST /login-> API',
|
|
241
|
-
' API -validate credentials-> Auth',
|
|
242
|
-
' Auth -SELECT user-> DB',
|
|
243
|
-
' DB -user record-> Auth',
|
|
244
|
-
' Auth -JWT token-> API',
|
|
245
|
-
'API -200 OK-> Client',
|
|
246
|
-
'',
|
|
247
|
-
].join('\n'),
|
|
248
|
-
'utf-8'
|
|
249
|
-
);
|
|
250
|
-
console.error(`Created ${samplePath}`);
|
|
251
|
-
console.error('');
|
|
252
|
-
console.error(' Render it: dgmo sample.dgmo');
|
|
253
|
-
console.error(' As SVG: dgmo sample.dgmo -o sample.svg');
|
|
254
|
-
console.error('');
|
|
255
|
-
console.error(
|
|
256
|
-
'Edit sample.dgmo to make it your own, or run dgmo --help for all options.'
|
|
257
|
-
);
|
|
258
|
-
process.exit(0);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Best-effort clipboard copy across platforms. Returns true on success.
|
|
262
|
-
function copyToClipboard(text: string): boolean {
|
|
263
|
-
try {
|
|
264
|
-
if (process.platform === 'darwin') {
|
|
265
|
-
execSync('pbcopy', { input: text });
|
|
266
|
-
} else if (process.platform === 'win32') {
|
|
267
|
-
execSync('clip', { input: text });
|
|
268
|
-
} else {
|
|
269
|
-
execSync('xclip -selection clipboard', { input: text });
|
|
270
|
-
}
|
|
271
|
-
return true;
|
|
272
|
-
} catch {
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// `dgmo map-search <query>` — discover the exact place token the map resolver
|
|
278
|
-
// expects (city name or bundled IATA airport code), so authors never guess
|
|
279
|
-
// (e.g. "New York" → `New York City`; "kennedy" → `JFK`). Searches cities +
|
|
280
|
-
// the bundled airport set; `--json` for machine output, `--limit N` to widen.
|
|
281
|
-
async function runMapSearchCommand(args: string[]): Promise<void> {
|
|
282
|
-
const json = args.includes('--json');
|
|
283
|
-
const limFlag = args.indexOf('--limit');
|
|
284
|
-
const limit =
|
|
285
|
-
limFlag >= 0 ? Math.max(1, Number(args[limFlag + 1]) || 20) : 20;
|
|
286
|
-
// The query is the positional args (allow unquoted multi-word: `... New York`).
|
|
287
|
-
const query = args
|
|
288
|
-
.filter(
|
|
289
|
-
(a, i, arr) =>
|
|
290
|
-
a !== '--json' && a !== '--limit' && arr[i - 1] !== '--limit'
|
|
291
|
-
)
|
|
292
|
-
.join(' ')
|
|
293
|
-
.trim();
|
|
294
|
-
|
|
295
|
-
if (!query) {
|
|
296
|
-
console.log('Usage: dgmo map-search <query> [--json] [--limit N]');
|
|
297
|
-
console.log(
|
|
298
|
-
' Find the place token the map resolver expects (city or IATA airport code).'
|
|
299
|
-
);
|
|
300
|
-
console.log(' Examples:');
|
|
301
|
-
console.log(
|
|
302
|
-
' dgmo map-search "new york" # → New York City (+ JFK/LGA/EWR airports)'
|
|
303
|
-
);
|
|
304
|
-
console.log(' dgmo map-search kennedy # → JFK by airport name');
|
|
305
|
-
console.log(' dgmo map-search heathrow --json');
|
|
306
|
-
process.exitCode = 1;
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const data = await loadMapData();
|
|
311
|
-
const results = searchMapLocations(query, data.gazetteer, {
|
|
312
|
-
limit,
|
|
313
|
-
...(data.airports && { airports: data.airports }),
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
if (json) {
|
|
317
|
-
process.stdout.write(JSON.stringify({ query, results }, null, 2) + '\n');
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (!results.length) {
|
|
321
|
-
console.log(
|
|
322
|
-
`No cities or airports match "${query}". Use coordinates: poi Name as <lat,lng>`
|
|
323
|
-
);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
console.log(
|
|
327
|
-
`Places matching "${query}" — use the token column in your DGMO:`
|
|
328
|
-
);
|
|
329
|
-
for (const r of results) {
|
|
330
|
-
const tag = r.kind === 'airport' ? '✈' : '•';
|
|
331
|
-
console.log(` ${tag} ${r.token.padEnd(22)} ${r.detail}`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// `dgmo types [--json]` — list every supported chart type. Was the
|
|
336
|
-
// `--chart-types` render flag; promoted to a subcommand since it's a
|
|
337
|
-
// query, not a rendering option.
|
|
338
|
-
function runTypesCommand(args: string[]): void {
|
|
339
|
-
const json = args.includes('--json');
|
|
340
|
-
const types = getAllChartTypes();
|
|
341
|
-
if (json) {
|
|
342
|
-
const chartTypes = types.map((id) => ({
|
|
343
|
-
id,
|
|
344
|
-
description: CHART_TYPE_DESCRIPTIONS[id] ?? id,
|
|
345
|
-
}));
|
|
346
|
-
process.stdout.write(JSON.stringify({ chartTypes }, null, 2) + '\n');
|
|
347
|
-
} else {
|
|
348
|
-
for (const id of types) {
|
|
349
|
-
const desc = CHART_TYPE_DESCRIPTIONS[id];
|
|
350
|
-
console.log(desc ? `${id} — ${desc}` : id);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// `dgmo diagnostics [--json]` — enumerate the diagnostic catalog: every
|
|
356
|
-
// error/warning code dgmo can emit, its severity, owning chart type,
|
|
357
|
-
// canonical message, fix hint, and a triggering example. Plain mode
|
|
358
|
-
// prints one line per code; `--json` emits the full catalog (consumed by
|
|
359
|
-
// the console error-review surface, MCP, and the language-spec docs).
|
|
360
|
-
function runDiagnosticsCommand(args: string[]): void {
|
|
361
|
-
const json = args.includes('--json');
|
|
362
|
-
const specs = listDiagnosticCodes();
|
|
363
|
-
if (json) {
|
|
364
|
-
const catalog = specs.map((s) => ({
|
|
365
|
-
code: s.code,
|
|
366
|
-
severity: s.severity,
|
|
367
|
-
chartType: s.chartType,
|
|
368
|
-
title: s.title,
|
|
369
|
-
message: typeof s.message === 'function' ? s.message({}) : s.message,
|
|
370
|
-
hint: s.hint,
|
|
371
|
-
example: s.example,
|
|
372
|
-
}));
|
|
373
|
-
process.stdout.write(
|
|
374
|
-
JSON.stringify({ diagnostics: catalog }, null, 2) + '\n'
|
|
375
|
-
);
|
|
376
|
-
} else {
|
|
377
|
-
for (const s of specs) {
|
|
378
|
-
const badge = s.severity === 'error' ? 'ERR ' : 'WARN';
|
|
379
|
-
const chart = s.chartType ?? 'global';
|
|
380
|
-
console.log(`${badge} ${s.code} [${chart}] ${s.title}`);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// `dgmo share <input> [--no-copy] [--json]` — encode the diagram into a
|
|
386
|
-
// shareable diagrammo.app URL. Replaces the old `-o url [--copy]` pair: the
|
|
387
|
-
// URL is printed to stdout (pipeable) and copied to the clipboard by default
|
|
388
|
-
// unless --no-copy is given.
|
|
389
|
-
async function runShareCommand(args: string[]): Promise<void> {
|
|
390
|
-
const json = args.includes('--json');
|
|
391
|
-
const noCopy = args.includes('--no-copy');
|
|
392
|
-
const inputArg = args.find((a) => a !== '-' && !a.startsWith('-'));
|
|
393
|
-
|
|
394
|
-
let content: string;
|
|
395
|
-
if (inputArg) {
|
|
396
|
-
try {
|
|
397
|
-
content = readFileSync(resolve(inputArg), 'utf-8');
|
|
398
|
-
} catch {
|
|
399
|
-
console.error(`Error: Cannot read file "${resolve(inputArg)}"`);
|
|
400
|
-
process.exit(1);
|
|
401
|
-
}
|
|
402
|
-
} else if (!process.stdin.isTTY) {
|
|
403
|
-
try {
|
|
404
|
-
content = readFileSync(0, 'utf-8');
|
|
405
|
-
} catch {
|
|
406
|
-
console.error('Error: No input provided to dgmo share');
|
|
407
|
-
console.error('Usage: dgmo share <input> [--no-copy] [--json]');
|
|
408
|
-
process.exit(1);
|
|
409
|
-
}
|
|
410
|
-
} else {
|
|
411
|
-
console.error('Error: dgmo share requires an input file or piped stdin');
|
|
412
|
-
console.error('Usage: dgmo share <input> [--no-copy] [--json]');
|
|
413
|
-
process.exit(1);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// eslint-disable-next-line no-control-regex
|
|
417
|
-
content = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
418
|
-
|
|
419
|
-
const chartType = parseDgmoChartType(content);
|
|
420
|
-
// PERT diagrams may contain `start-date now`; resolve to today before
|
|
421
|
-
// encoding so the share-link captures authoring intent (spec D9).
|
|
422
|
-
const sourceForUrl =
|
|
423
|
-
chartType === 'pert' ? normalizePertSourceForShare(content) : content;
|
|
424
|
-
const result = encodeDiagramUrl(sourceForUrl);
|
|
425
|
-
|
|
426
|
-
if (result.error) {
|
|
427
|
-
const msg = `Error: Diagram too large for URL sharing (${result.compressedSize} bytes, limit ${result.limit} bytes)`;
|
|
428
|
-
if (json) {
|
|
429
|
-
process.stdout.write(
|
|
430
|
-
JSON.stringify({ success: false, error: msg }, null, 2) + '\n'
|
|
431
|
-
);
|
|
432
|
-
} else {
|
|
433
|
-
console.error(msg);
|
|
434
|
-
}
|
|
435
|
-
process.exit(1);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (!noCopy) {
|
|
439
|
-
console.error(
|
|
440
|
-
copyToClipboard(result.url)
|
|
441
|
-
? 'URL copied to clipboard'
|
|
442
|
-
: 'Warning: Could not copy to clipboard'
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (json) {
|
|
447
|
-
process.stdout.write(
|
|
448
|
-
JSON.stringify(
|
|
449
|
-
{ success: true, url: result.url, ...(chartType ? { chartType } : {}) },
|
|
450
|
-
null,
|
|
451
|
-
2
|
|
452
|
-
) + '\n'
|
|
453
|
-
);
|
|
454
|
-
} else {
|
|
455
|
-
process.stdout.write(result.url + '\n');
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Single source of truth for how every AI client launches the MCP server: the
|
|
460
|
-
// `dgmo` binary the user already has, plus the `mcp` subcommand. No generated
|
|
461
|
-
// config ever references a separate `dgmo-mcp` command — the user installs one
|
|
462
|
-
// binary and that's it. (The server itself still lives in the
|
|
463
|
-
// @diagrammo/dgmo-mcp package, which depends on this library; `dgmo mcp` locates
|
|
464
|
-
// and execs it, so there's no dependency cycle and no extra deps bundled here.)
|
|
465
|
-
const MCP_LAUNCH = { command: 'dgmo', args: ['mcp'] } as const;
|
|
466
|
-
|
|
467
|
-
const INSTALL_TARGETS = [
|
|
468
|
-
'claude-code',
|
|
469
|
-
'codex',
|
|
470
|
-
'claude-desktop',
|
|
471
|
-
'cursor',
|
|
472
|
-
'windsurf',
|
|
473
|
-
'copilot',
|
|
474
|
-
] as const;
|
|
475
|
-
type InstallTarget = (typeof INSTALL_TARGETS)[number];
|
|
476
|
-
|
|
477
|
-
interface InstallOpts {
|
|
478
|
-
scope: 'user' | 'project';
|
|
479
|
-
dryRun: boolean;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// `dgmo mcp` — run the MCP server. This is what AI client configs invoke. It
|
|
483
|
-
// execs the installed `dgmo-mcp` binary if present, otherwise fetches and runs
|
|
484
|
-
// it on demand via npx so a config pointing at `dgmo mcp` works even before the
|
|
485
|
-
// server package has been installed. stdio is inherited so the stdio MCP
|
|
486
|
-
// transport is fully transparent.
|
|
487
|
-
function runMcpCommand(args: string[]): never {
|
|
488
|
-
const onWin = process.platform === 'win32';
|
|
489
|
-
const run = (cmd: string, cmdArgs: string[]) =>
|
|
490
|
-
spawnSync(cmd, cmdArgs, { stdio: 'inherit', shell: onWin });
|
|
491
|
-
// npx path is pinned to @latest so the on-demand server is never stale.
|
|
492
|
-
const npx = () => run('npx', ['-y', '@diagrammo/dgmo-mcp@latest', ...args]);
|
|
493
|
-
// Default: prefer the installed binary — fast, works offline, and
|
|
494
|
-
// `dgmo install` keeps it on @latest. Set DGMO_MCP_LATEST=1 to always fetch
|
|
495
|
-
// the newest server via npx instead (needs network; adds cold-start).
|
|
496
|
-
const res =
|
|
497
|
-
process.env['DGMO_MCP_LATEST'] === '1'
|
|
498
|
-
? npx()
|
|
499
|
-
: commandExists('dgmo-mcp')
|
|
500
|
-
? run('dgmo-mcp', args)
|
|
501
|
-
: npx();
|
|
502
|
-
if (res.error) {
|
|
503
|
-
console.error(
|
|
504
|
-
'Could not launch the dgmo MCP server. Install it with: npm install -g @diagrammo/dgmo-mcp'
|
|
505
|
-
);
|
|
506
|
-
process.exit(1);
|
|
507
|
-
}
|
|
508
|
-
process.exit(res.status ?? 0);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Cross-platform "is this command on PATH?" check.
|
|
512
|
-
function commandExists(cmd: string): boolean {
|
|
513
|
-
try {
|
|
514
|
-
const probe =
|
|
515
|
-
process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
|
516
|
-
execSync(probe, { stdio: 'pipe' });
|
|
517
|
-
return true;
|
|
518
|
-
} catch {
|
|
519
|
-
return false;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Claude Desktop config path for the current platform. macOS/Windows use the
|
|
524
|
-
// documented locations; Linux follows the XDG convention community builds use.
|
|
525
|
-
function claudeDesktopConfigPath(): string {
|
|
526
|
-
const os = platform();
|
|
527
|
-
if (os === 'darwin') {
|
|
528
|
-
return join(
|
|
529
|
-
homedir(),
|
|
530
|
-
'Library',
|
|
531
|
-
'Application Support',
|
|
532
|
-
'Claude',
|
|
533
|
-
'claude_desktop_config.json'
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
if (os === 'win32') {
|
|
537
|
-
const appData =
|
|
538
|
-
process.env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');
|
|
539
|
-
return join(appData, 'Claude', 'claude_desktop_config.json');
|
|
540
|
-
}
|
|
541
|
-
return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Best-effort, NON-fatal pre-install of the MCP server so the first tool call
|
|
545
|
-
// has no cold-start. Not required for correctness — `dgmo mcp` falls back to
|
|
546
|
-
// npx — so any failure is a note, not an error. Memoized: in auto mode we set
|
|
547
|
-
// up several surfaces in one run and only need to check once.
|
|
548
|
-
// True when this CLI is running from a Homebrew install (its files live under
|
|
549
|
-
// a Cellar prefix). Homebrew owns the MCP server in that case.
|
|
550
|
-
function isHomebrewManaged(): boolean {
|
|
551
|
-
return PKG_ROOT.includes('/Cellar/') || PKG_ROOT.includes('/homebrew/');
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
let mcpEnsured = false;
|
|
555
|
-
function ensureDgmoMcp(opts: InstallOpts): void {
|
|
556
|
-
if (mcpEnsured) return;
|
|
557
|
-
mcpEnsured = true;
|
|
558
|
-
// Homebrew vendors and upgrades the MCP server alongside the CLI (the formula
|
|
559
|
-
// installs `dgmo-mcp` into the same prefix). Don't npm-install a second global
|
|
560
|
-
// copy that would shadow brew's — `brew upgrade dgmo` keeps it current.
|
|
561
|
-
if (isHomebrewManaged()) {
|
|
562
|
-
console.log(
|
|
563
|
-
commandExists('dgmo-mcp')
|
|
564
|
-
? '✓ MCP server managed by Homebrew (upgrades with `brew upgrade dgmo`)'
|
|
565
|
-
: ' MCP server not found — it will be fetched on first use via npx.'
|
|
566
|
-
);
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
if (opts.dryRun) {
|
|
570
|
-
console.log(' [dry-run] would install/upgrade @diagrammo/dgmo-mcp@latest');
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
// Always pull @latest, not just install-if-absent: the MCP tools live in a
|
|
574
|
-
// separately-versioned package, so re-running `dgmo install` (e.g. after a
|
|
575
|
-
// `dgmo` upgrade) is what refreshes the server. Non-fatal — `dgmo mcp` falls
|
|
576
|
-
// back to `npx …@latest`.
|
|
577
|
-
try {
|
|
578
|
-
const verb = commandExists('dgmo-mcp') ? 'Updating' : 'Installing';
|
|
579
|
-
console.log(`${verb} the MCP server (@diagrammo/dgmo-mcp@latest)…`);
|
|
580
|
-
execSync('npm install -g @diagrammo/dgmo-mcp@latest', { stdio: 'inherit' });
|
|
581
|
-
console.log('✓ MCP server up to date');
|
|
582
|
-
} catch {
|
|
583
|
-
console.log(
|
|
584
|
-
' Could not install now — it will be fetched on first use via npx.'
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Write helper that honors --dry-run and creates parent dirs.
|
|
590
|
-
function writeOut(
|
|
591
|
-
opts: InstallOpts,
|
|
592
|
-
path: string,
|
|
593
|
-
content: string,
|
|
594
|
-
label: string
|
|
595
|
-
): void {
|
|
596
|
-
if (opts.dryRun) {
|
|
597
|
-
console.log(` [dry-run] would write ${label}`);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
601
|
-
writeFileSync(path, content, 'utf-8');
|
|
602
|
-
console.log(`✓ ${label}`);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Merge `{ mcpServers: { dgmo: dgmo mcp } }` into a JSON config (Claude Code /
|
|
606
|
-
// Claude Desktop style), preserving any other entries.
|
|
607
|
-
function upsertJsonMcp(opts: InstallOpts, path: string): void {
|
|
608
|
-
let cfg: Record<string, unknown> = {};
|
|
609
|
-
if (existsSync(path)) {
|
|
610
|
-
const raw = readFileSync(path, 'utf-8');
|
|
611
|
-
if (raw.trim().length > 0) {
|
|
612
|
-
try {
|
|
613
|
-
cfg = JSON.parse(raw);
|
|
614
|
-
} catch {
|
|
615
|
-
console.error(
|
|
616
|
-
`Error: ${path} is not valid JSON — fix it and re-run (skipping it for now).`
|
|
617
|
-
);
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
const servers =
|
|
623
|
-
(cfg['mcpServers'] as Record<string, unknown> | undefined) ?? {};
|
|
624
|
-
servers['dgmo'] = MCP_LAUNCH;
|
|
625
|
-
cfg['mcpServers'] = servers;
|
|
626
|
-
writeOut(
|
|
627
|
-
opts,
|
|
628
|
-
path,
|
|
629
|
-
JSON.stringify(cfg, null, 2) + '\n',
|
|
630
|
-
`MCP server → ${path}`
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Which AI surfaces are present on this machine? Drives zero-prompt auto setup.
|
|
635
|
-
function detectSurfaces(): InstallTarget[] {
|
|
636
|
-
const found: InstallTarget[] = [];
|
|
637
|
-
if (existsSync(join(homedir(), '.claude'))) found.push('claude-code');
|
|
638
|
-
if (existsSync(join(homedir(), '.codex')) || commandExists('codex'))
|
|
639
|
-
found.push('codex');
|
|
640
|
-
if (existsSync(dirname(claudeDesktopConfigPath())))
|
|
641
|
-
found.push('claude-desktop');
|
|
642
|
-
if (existsSync(join(homedir(), '.cursor'))) found.push('cursor');
|
|
643
|
-
if (
|
|
644
|
-
existsSync(join(homedir(), '.windsurf')) ||
|
|
645
|
-
existsSync(join(homedir(), '.codeium'))
|
|
646
|
-
)
|
|
647
|
-
found.push('windsurf');
|
|
648
|
-
// Copilot is project-scoped: only when the current repo has a .github dir.
|
|
649
|
-
if (existsSync(join(process.cwd(), '.github'))) found.push('copilot');
|
|
650
|
-
return found;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
async function runOneInstall(
|
|
654
|
-
target: InstallTarget,
|
|
655
|
-
opts: InstallOpts
|
|
656
|
-
): Promise<void> {
|
|
657
|
-
switch (target) {
|
|
658
|
-
case 'claude-code':
|
|
659
|
-
return installClaudeCode(opts);
|
|
660
|
-
case 'codex':
|
|
661
|
-
return installCodex(opts);
|
|
662
|
-
case 'claude-desktop':
|
|
663
|
-
return installClaudeDesktop(opts);
|
|
664
|
-
case 'cursor':
|
|
665
|
-
return installCursor(opts);
|
|
666
|
-
case 'windsurf':
|
|
667
|
-
return installWindsurf(opts);
|
|
668
|
-
case 'copilot':
|
|
669
|
-
return installCopilot(opts);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
function printInstallHelp(): void {
|
|
674
|
-
console.log(`Usage: dgmo install [target] [--scope user|project] [--dry-run]
|
|
675
|
-
|
|
676
|
-
With no target, auto-detects every installed AI assistant and sets up each one
|
|
677
|
-
(no prompts). The only binary you ever install is \`dgmo\` — it provides the MCP
|
|
678
|
-
server via \`dgmo mcp\`.
|
|
679
|
-
|
|
680
|
-
Targets: ${INSTALL_TARGETS.join(', ')}
|
|
681
|
-
--scope user = configure globally for all projects (default)
|
|
682
|
-
project = write config into the current directory
|
|
683
|
-
--dry-run print what would change without writing anything`);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// `dgmo install [target] [--scope ...] [--dry-run]`. No target → auto-detect.
|
|
687
|
-
async function runInstallCommand(args: string[]): Promise<void> {
|
|
688
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
689
|
-
printInstallHelp();
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
const scopeIdx = args.indexOf('--scope');
|
|
693
|
-
const scope: 'user' | 'project' =
|
|
694
|
-
scopeIdx >= 0 && args[scopeIdx + 1] === 'project' ? 'project' : 'user';
|
|
695
|
-
const opts: InstallOpts = { scope, dryRun: args.includes('--dry-run') };
|
|
696
|
-
|
|
697
|
-
const positional = args.filter(
|
|
698
|
-
(a, i) => !a.startsWith('-') && args[i - 1] !== '--scope'
|
|
699
|
-
);
|
|
700
|
-
const target = positional[0] as InstallTarget | undefined;
|
|
701
|
-
|
|
702
|
-
console.log(renderBanner());
|
|
703
|
-
|
|
704
|
-
if (target) {
|
|
705
|
-
if (!INSTALL_TARGETS.includes(target)) {
|
|
706
|
-
console.error(`Error: Unknown install target "${target}".`);
|
|
707
|
-
console.error(`Valid targets: ${INSTALL_TARGETS.join(', ')}`);
|
|
708
|
-
process.exit(1);
|
|
709
|
-
}
|
|
710
|
-
await runOneInstall(target, opts);
|
|
711
|
-
console.log('\nRestart the assistant to activate the dgmo tools.');
|
|
712
|
-
console.log('(Re-run `dgmo install` after upgrading dgmo to refresh.)');
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const detected = detectSurfaces();
|
|
717
|
-
if (detected.length === 0) {
|
|
718
|
-
console.log(
|
|
719
|
-
'No AI assistants detected (looked for Claude Code, Codex, Claude Desktop, Cursor, Windsurf, a .github dir).'
|
|
720
|
-
);
|
|
721
|
-
console.log('Install one and re-run `dgmo install`, or name a target:');
|
|
722
|
-
console.log(` dgmo install <${INSTALL_TARGETS.join('|')}>`);
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
console.log(`Detected: ${detected.join(', ')} — setting up each.\n`);
|
|
726
|
-
for (const t of detected) {
|
|
727
|
-
console.log(`── ${t} ──`);
|
|
728
|
-
await runOneInstall(t, opts);
|
|
729
|
-
console.log('');
|
|
730
|
-
}
|
|
731
|
-
console.log('Done. Restart your AI assistant(s) to activate the dgmo tools.');
|
|
732
|
-
console.log('(Re-run `dgmo install` after upgrading dgmo to refresh.)');
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
async function installClaudeCode(opts: InstallOpts): Promise<void> {
|
|
736
|
-
const claudeDir = join(homedir(), '.claude');
|
|
737
|
-
// Skill — single-sourced and always refreshed to the maintained latest.
|
|
738
|
-
writeOut(
|
|
739
|
-
opts,
|
|
740
|
-
join(claudeDir, 'commands', 'dgmo.md'),
|
|
741
|
-
readClaudeSkill(),
|
|
742
|
-
'Skill → ~/.claude/commands/dgmo.md'
|
|
743
|
-
);
|
|
744
|
-
// Sibling slash commands (/dgmo-codebase-report, /dgmo-diagram-this,
|
|
745
|
-
// /dgmo-document-project). They ship in the package but were previously never
|
|
746
|
-
// installed — copy them so the commands actually land for the user.
|
|
747
|
-
for (const name of listPackagedCommands()) {
|
|
748
|
-
writeOut(
|
|
749
|
-
opts,
|
|
750
|
-
join(claudeDir, 'commands', name),
|
|
751
|
-
readPackagedFile('.claude', 'commands', name),
|
|
752
|
-
`Command → ~/.claude/commands/${name}`
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
ensureDgmoMcp(opts);
|
|
756
|
-
if (opts.scope === 'user') {
|
|
757
|
-
upsertJsonMcp(opts, join(claudeDir, 'settings.json'));
|
|
758
|
-
console.log(' (scope: all projects)');
|
|
759
|
-
} else {
|
|
760
|
-
upsertJsonMcp(opts, join(process.cwd(), '.mcp.json'));
|
|
761
|
-
console.log(' (scope: this project)');
|
|
762
|
-
}
|
|
763
|
-
console.log(' Then type /dgmo in any Claude Code session.');
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
async function installCodex(opts: InstallOpts): Promise<void> {
|
|
767
|
-
ensureDgmoMcp(opts);
|
|
768
|
-
|
|
769
|
-
// MCP config (TOML). The block migrates in place if a legacy entry exists.
|
|
770
|
-
const tomlEntry = '[mcp_servers.dgmo]\ncommand = "dgmo"\nargs = ["mcp"]\n';
|
|
771
|
-
const configPath =
|
|
772
|
-
opts.scope === 'user'
|
|
773
|
-
? join(homedir(), '.codex', 'config.toml')
|
|
774
|
-
: join(process.cwd(), '.codex', 'config.toml');
|
|
775
|
-
const existing = existsSync(configPath)
|
|
776
|
-
? readFileSync(configPath, 'utf-8')
|
|
777
|
-
: '';
|
|
778
|
-
let next: string;
|
|
779
|
-
if (existing.includes('[mcp_servers.dgmo]')) {
|
|
780
|
-
next = existing.replace(/\[mcp_servers\.dgmo\][^[]*/, tomlEntry);
|
|
781
|
-
} else {
|
|
782
|
-
const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
783
|
-
next = existing + sep + tomlEntry;
|
|
784
|
-
}
|
|
785
|
-
writeOut(opts, configPath, next, `MCP server → ${configPath}`);
|
|
786
|
-
|
|
787
|
-
// Skill — single-sourced, always refreshed.
|
|
788
|
-
writeOut(
|
|
789
|
-
opts,
|
|
790
|
-
join(homedir(), '.codex', 'skills', 'dgmo-diagramming', 'SKILL.md'),
|
|
791
|
-
readCodexSkill(),
|
|
792
|
-
'Skill → ~/.codex/skills/dgmo-diagramming/SKILL.md'
|
|
793
|
-
);
|
|
794
|
-
|
|
795
|
-
// Codebase-report skill (single-sourced from the Claude command file).
|
|
796
|
-
writeOut(
|
|
797
|
-
opts,
|
|
798
|
-
join(homedir(), '.codex', 'skills', 'dgmo-codebase-report', 'SKILL.md'),
|
|
799
|
-
readCodexCodebaseReportSkill(),
|
|
800
|
-
'Skill → ~/.codex/skills/dgmo-codebase-report/SKILL.md'
|
|
801
|
-
);
|
|
802
|
-
|
|
803
|
-
// Non-destructive AGENTS.md note: only append to an existing file, never create.
|
|
804
|
-
const agentsPath = join(process.cwd(), 'AGENTS.md');
|
|
805
|
-
if (!opts.dryRun && existsSync(agentsPath)) {
|
|
806
|
-
const existingAgents = readFileSync(agentsPath, 'utf-8');
|
|
807
|
-
if (!existingAgents.includes(CODEX_AGENTS_NOTE_MARKER)) {
|
|
808
|
-
const separator = existingAgents.endsWith('\n') ? '\n' : '\n\n';
|
|
809
|
-
writeFileSync(
|
|
810
|
-
agentsPath,
|
|
811
|
-
existingAgents + separator + CODEX_AGENTS_NOTE,
|
|
812
|
-
'utf-8'
|
|
813
|
-
);
|
|
814
|
-
console.log('✓ Appended dgmo note to AGENTS.md');
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
async function installClaudeDesktop(opts: InstallOpts): Promise<void> {
|
|
820
|
-
ensureDgmoMcp(opts);
|
|
821
|
-
// Claude Desktop config is always the per-user app config (scope is N/A).
|
|
822
|
-
upsertJsonMcp(opts, claudeDesktopConfigPath());
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// Cursor speaks MCP via ~/.cursor/mcp.json (user) or .cursor/mcp.json (project),
|
|
826
|
-
// using the same { mcpServers } schema as Claude — so it gets the full tools,
|
|
827
|
-
// not just syntax guidance. We also drop the shipped .cursorrules so the model
|
|
828
|
-
// has inline context without a tool round-trip.
|
|
829
|
-
function installCursor(opts: InstallOpts): void {
|
|
830
|
-
ensureDgmoMcp(opts);
|
|
831
|
-
const path =
|
|
832
|
-
opts.scope === 'project'
|
|
833
|
-
? join(process.cwd(), '.cursor', 'mcp.json')
|
|
834
|
-
: join(homedir(), '.cursor', 'mcp.json');
|
|
835
|
-
upsertJsonMcp(opts, path);
|
|
836
|
-
writeOut(
|
|
837
|
-
opts,
|
|
838
|
-
join(process.cwd(), '.cursorrules'),
|
|
839
|
-
readPackagedFile('.cursorrules'),
|
|
840
|
-
'Cursor rules → .cursorrules'
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Windsurf (Codeium) reads MCP from ~/.codeium/windsurf/mcp_config.json.
|
|
845
|
-
function installWindsurf(opts: InstallOpts): void {
|
|
846
|
-
ensureDgmoMcp(opts);
|
|
847
|
-
upsertJsonMcp(
|
|
848
|
-
opts,
|
|
849
|
-
join(homedir(), '.codeium', 'windsurf', 'mcp_config.json')
|
|
850
|
-
);
|
|
851
|
-
writeOut(
|
|
852
|
-
opts,
|
|
853
|
-
join(process.cwd(), '.windsurfrules'),
|
|
854
|
-
readPackagedFile('.windsurfrules'),
|
|
855
|
-
'Windsurf rules → .windsurfrules'
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// Copilot has no MCP path — ship the project-root instructions file it auto-loads.
|
|
860
|
-
function installCopilot(opts: InstallOpts): void {
|
|
861
|
-
writeOut(
|
|
862
|
-
opts,
|
|
863
|
-
join(process.cwd(), '.github', 'copilot-instructions.md'),
|
|
864
|
-
readPackagedFile('.github', 'copilot-instructions.md'),
|
|
865
|
-
'Copilot context → .github/copilot-instructions.md'
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
async function main(): Promise<void> {
|
|
870
|
-
// Subcommand dispatch — each is parsed independently from the rendering
|
|
871
|
-
// flags so their surface doesn't pollute `parseArgs`.
|
|
872
|
-
const sub = process.argv[2];
|
|
873
|
-
if (sub === 'map-search') {
|
|
874
|
-
await runMapSearchCommand(process.argv.slice(3));
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
if (sub === 'share') {
|
|
878
|
-
await runShareCommand(process.argv.slice(3));
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
if (sub === 'types') {
|
|
882
|
-
runTypesCommand(process.argv.slice(3));
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
if (sub === 'diagnostics') {
|
|
886
|
-
runDiagnosticsCommand(process.argv.slice(3));
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
if (sub === 'install') {
|
|
890
|
-
await runInstallCommand(process.argv.slice(3));
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
if (sub === 'mcp') {
|
|
894
|
-
runMcpCommand(process.argv.slice(3));
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
const opts = parseArgs(process.argv);
|
|
898
|
-
|
|
899
|
-
if (opts.help) {
|
|
900
|
-
printHelp();
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
if (opts.version) {
|
|
905
|
-
printVersion();
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// Determine input source
|
|
910
|
-
let content: string;
|
|
911
|
-
let inputBasename: string | undefined;
|
|
912
|
-
const stdinIsPiped = !process.stdin.isTTY;
|
|
913
|
-
|
|
914
|
-
if (opts.input) {
|
|
915
|
-
// File argument provided
|
|
916
|
-
const inputPath = resolve(opts.input);
|
|
917
|
-
try {
|
|
918
|
-
content = readFileSync(inputPath, 'utf-8');
|
|
919
|
-
} catch {
|
|
920
|
-
console.error(`Error: Cannot read file "${inputPath}"`);
|
|
921
|
-
process.exit(1);
|
|
922
|
-
}
|
|
923
|
-
// Strip extension for default output name
|
|
924
|
-
const name = basename(opts.input);
|
|
925
|
-
const ext = extname(name);
|
|
926
|
-
inputBasename = ext ? name.slice(0, -ext.length) : name;
|
|
927
|
-
} else if (stdinIsPiped) {
|
|
928
|
-
// Read from stdin
|
|
929
|
-
try {
|
|
930
|
-
content = readFileSync(0, 'utf-8');
|
|
931
|
-
} catch {
|
|
932
|
-
noInput();
|
|
933
|
-
}
|
|
934
|
-
} else {
|
|
935
|
-
noInput();
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// Strip any ANSI escape codes that may have leaked into input
|
|
939
|
-
// (e.g. from shell aliases like cat=bat with --color always)
|
|
940
|
-
// eslint-disable-next-line no-control-regex
|
|
941
|
-
content = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
942
|
-
|
|
943
|
-
// Resolve org chart imports (tags and import directives)
|
|
944
|
-
if (opts.input && parseDgmoChartType(content) === 'org') {
|
|
945
|
-
const inputPath = resolve(opts.input);
|
|
946
|
-
const resolved = await resolveOrgImports(content, inputPath, (p) =>
|
|
947
|
-
readFileSync(p, 'utf-8')
|
|
948
|
-
);
|
|
949
|
-
for (const diag of resolved.diagnostics) {
|
|
950
|
-
console.error(formatDgmoError(diag));
|
|
951
|
-
}
|
|
952
|
-
content = resolved.content;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// `-o url` was replaced by `dgmo share`; catch the old usage with a helpful
|
|
956
|
-
// redirect instead of writing a file literally named "url".
|
|
957
|
-
if (opts.output === 'url') {
|
|
958
|
-
console.error('Error: `-o url` was replaced by `dgmo share <input>`.');
|
|
959
|
-
console.error(`Try: dgmo share ${opts.input ?? '<input>'}`);
|
|
960
|
-
process.exit(1);
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
const format = inferFormat(opts.output);
|
|
964
|
-
|
|
965
|
-
const chartType = parseDgmoChartType(content);
|
|
966
|
-
|
|
967
|
-
// Helper for JSON error output
|
|
968
|
-
function exitWithJsonError(error: string, line?: number): never {
|
|
969
|
-
if (opts.json) {
|
|
970
|
-
process.stdout.write(
|
|
971
|
-
JSON.stringify(
|
|
972
|
-
{
|
|
973
|
-
success: false,
|
|
974
|
-
error,
|
|
975
|
-
...(line != null ? { line } : {}),
|
|
976
|
-
...(chartType ? { chartType } : {}),
|
|
977
|
-
},
|
|
978
|
-
null,
|
|
979
|
-
2
|
|
980
|
-
) + '\n'
|
|
981
|
-
);
|
|
982
|
-
} else {
|
|
983
|
-
console.error(error);
|
|
984
|
-
}
|
|
985
|
-
process.exit(1);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
const paletteColors = getPalette(opts.palette)[
|
|
989
|
-
opts.theme === 'dark' ? 'dark' : 'light'
|
|
990
|
-
];
|
|
991
|
-
|
|
992
|
-
// Parse first to collect diagnostics
|
|
993
|
-
const { diagnostics } = parseDgmo(content);
|
|
994
|
-
const errors = diagnostics.filter((d) => d.severity === 'error');
|
|
995
|
-
const warnings = diagnostics.filter((d) => d.severity === 'warning');
|
|
996
|
-
|
|
997
|
-
// Print warnings even if rendering succeeds
|
|
998
|
-
if (!opts.json) {
|
|
999
|
-
for (const w of warnings) {
|
|
1000
|
-
console.error(`⚠ ${formatDgmoError(w)}`);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Print errors and exit
|
|
1005
|
-
if (errors.length > 0) {
|
|
1006
|
-
if (opts.json) {
|
|
1007
|
-
const firstError = errors[0]!; // In-bounds by length > 0 check above.
|
|
1008
|
-
exitWithJsonError(formatDgmoError(firstError), firstError.line);
|
|
1009
|
-
}
|
|
1010
|
-
for (const e of errors) {
|
|
1011
|
-
console.error(`✖ ${formatDgmoError(e)}`);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const { svg } = await render(content, {
|
|
1016
|
-
theme: opts.theme,
|
|
1017
|
-
palette: opts.palette,
|
|
1018
|
-
});
|
|
1019
|
-
|
|
1020
|
-
if (!svg) {
|
|
1021
|
-
if (errors.length === 0) {
|
|
1022
|
-
exitWithJsonError(
|
|
1023
|
-
'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type.'
|
|
1024
|
-
);
|
|
1025
|
-
}
|
|
1026
|
-
process.exit(1);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// Determine output destination
|
|
1030
|
-
const pngBg = opts.theme === 'transparent' ? undefined : paletteColors.bg;
|
|
1031
|
-
|
|
1032
|
-
if (opts.json) {
|
|
1033
|
-
// JSON mode: write file as normal but output JSON result to stdout
|
|
1034
|
-
let outputPath: string | undefined;
|
|
1035
|
-
if (opts.output) {
|
|
1036
|
-
outputPath = resolve(opts.output);
|
|
1037
|
-
if (format === 'svg') {
|
|
1038
|
-
writeFileSync(outputPath, svg, 'utf-8');
|
|
1039
|
-
} else {
|
|
1040
|
-
writeFileSync(outputPath, svgToPng(svg, pngBg));
|
|
1041
|
-
}
|
|
1042
|
-
} else if (inputBasename) {
|
|
1043
|
-
outputPath = resolve(`${inputBasename}.png`);
|
|
1044
|
-
writeFileSync(outputPath, svgToPng(svg, pngBg));
|
|
1045
|
-
}
|
|
1046
|
-
process.stdout.write(
|
|
1047
|
-
JSON.stringify(
|
|
1048
|
-
{
|
|
1049
|
-
success: true,
|
|
1050
|
-
...(outputPath ? { output: outputPath } : {}),
|
|
1051
|
-
...(chartType ? { chartType } : {}),
|
|
1052
|
-
},
|
|
1053
|
-
null,
|
|
1054
|
-
2
|
|
1055
|
-
) + '\n'
|
|
1056
|
-
);
|
|
1057
|
-
} else if (opts.output) {
|
|
1058
|
-
// Explicit output path
|
|
1059
|
-
const outputPath = resolve(opts.output);
|
|
1060
|
-
if (format === 'svg') {
|
|
1061
|
-
writeFileSync(outputPath, svg, 'utf-8');
|
|
1062
|
-
} else {
|
|
1063
|
-
writeFileSync(outputPath, svgToPng(svg, pngBg));
|
|
1064
|
-
}
|
|
1065
|
-
console.error(`Wrote ${outputPath}`);
|
|
1066
|
-
} else if (inputBasename) {
|
|
1067
|
-
// File input, no -o → write <basename>.png in cwd
|
|
1068
|
-
const outputPath = resolve(`${inputBasename}.png`);
|
|
1069
|
-
writeFileSync(outputPath, svgToPng(svg, pngBg));
|
|
1070
|
-
console.error(`Wrote ${outputPath}`);
|
|
1071
|
-
} else {
|
|
1072
|
-
// Stdin input, no -o → write PNG to stdout
|
|
1073
|
-
process.stdout.write(svgToPng(svg, pngBg));
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
main().catch((err: Error) => {
|
|
1078
|
-
console.error(`Error: ${err.message}`);
|
|
1079
|
-
process.exit(1);
|
|
1080
|
-
});
|