@diagrammo/dgmo 0.8.3 → 0.8.5

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.
Files changed (122) hide show
  1. package/.claude/commands/dgmo-diagram-this.md +60 -0
  2. package/.claude/commands/dgmo-document-project.md +128 -0
  3. package/.claude/commands/dgmo.md +452 -50
  4. package/.cursorrules +32 -37
  5. package/.github/copilot-instructions.md +35 -44
  6. package/.windsurfrules +32 -37
  7. package/README.md +4 -4
  8. package/dist/cli.cjs +188 -185
  9. package/dist/editor.cjs +338 -0
  10. package/dist/editor.cjs.map +1 -0
  11. package/dist/editor.d.cts +27 -0
  12. package/dist/editor.d.ts +27 -0
  13. package/dist/editor.js +307 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/highlight.cjs +560 -0
  16. package/dist/highlight.cjs.map +1 -0
  17. package/dist/highlight.d.cts +32 -0
  18. package/dist/highlight.d.ts +32 -0
  19. package/dist/highlight.js +530 -0
  20. package/dist/highlight.js.map +1 -0
  21. package/dist/index.cjs +3467 -1078
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +22 -1
  24. package/dist/index.d.ts +22 -1
  25. package/dist/index.js +3466 -1078
  26. package/dist/index.js.map +1 -1
  27. package/docs/language-reference.md +46 -37
  28. package/gallery/fixtures/arc.dgmo +18 -0
  29. package/gallery/fixtures/area.dgmo +19 -0
  30. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  31. package/gallery/fixtures/bar.dgmo +10 -0
  32. package/gallery/fixtures/c4-full.dgmo +52 -0
  33. package/gallery/fixtures/c4.dgmo +17 -0
  34. package/gallery/fixtures/chord.dgmo +12 -0
  35. package/gallery/fixtures/class-basic.dgmo +14 -0
  36. package/gallery/fixtures/class-full.dgmo +43 -0
  37. package/gallery/fixtures/doughnut.dgmo +8 -0
  38. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  39. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  40. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  41. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  42. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  43. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  44. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  45. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  46. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  47. package/gallery/fixtures/function.dgmo +8 -0
  48. package/gallery/fixtures/funnel.dgmo +7 -0
  49. package/gallery/fixtures/gantt-full.dgmo +49 -0
  50. package/gallery/fixtures/gantt.dgmo +42 -0
  51. package/gallery/fixtures/heatmap.dgmo +8 -0
  52. package/gallery/fixtures/infra-full.dgmo +78 -0
  53. package/gallery/fixtures/infra-overload.dgmo +25 -0
  54. package/gallery/fixtures/infra.dgmo +47 -0
  55. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  56. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  57. package/gallery/fixtures/initiative-status.dgmo +9 -0
  58. package/gallery/fixtures/line.dgmo +19 -0
  59. package/gallery/fixtures/multi-line.dgmo +11 -0
  60. package/gallery/fixtures/org-basic.dgmo +16 -0
  61. package/gallery/fixtures/org-full.dgmo +69 -0
  62. package/gallery/fixtures/org-teams.dgmo +25 -0
  63. package/gallery/fixtures/pie.dgmo +9 -0
  64. package/gallery/fixtures/polar-area.dgmo +8 -0
  65. package/gallery/fixtures/quadrant.dgmo +18 -0
  66. package/gallery/fixtures/radar.dgmo +8 -0
  67. package/gallery/fixtures/sankey.dgmo +31 -0
  68. package/gallery/fixtures/scatter.dgmo +21 -0
  69. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  70. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  71. package/gallery/fixtures/sequence.dgmo +35 -0
  72. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  73. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  74. package/gallery/fixtures/slope.dgmo +9 -0
  75. package/gallery/fixtures/spr-eras.dgmo +62 -0
  76. package/gallery/fixtures/state.dgmo +30 -0
  77. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  78. package/gallery/fixtures/timeline.dgmo +32 -0
  79. package/gallery/fixtures/venn.dgmo +10 -0
  80. package/gallery/fixtures/wordcloud.dgmo +24 -0
  81. package/package.json +71 -2
  82. package/src/c4/layout.ts +372 -90
  83. package/src/c4/parser.ts +100 -55
  84. package/src/chart.ts +91 -28
  85. package/src/class/parser.ts +41 -12
  86. package/src/cli.ts +211 -62
  87. package/src/completion.ts +378 -183
  88. package/src/d3.ts +1044 -303
  89. package/src/dgmo-mermaid.ts +16 -13
  90. package/src/dgmo-router.ts +69 -23
  91. package/src/echarts.ts +646 -153
  92. package/src/editor/dgmo.grammar +69 -0
  93. package/src/editor/dgmo.grammar.d.ts +2 -0
  94. package/src/editor/dgmo.grammar.js +18 -0
  95. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  96. package/src/editor/dgmo.grammar.terms.js +35 -0
  97. package/src/editor/highlight-api.ts +444 -0
  98. package/src/editor/highlight.ts +36 -0
  99. package/src/editor/index.ts +28 -0
  100. package/src/editor/keywords.ts +222 -0
  101. package/src/editor/tokens.ts +30 -0
  102. package/src/er/parser.ts +48 -14
  103. package/src/er/renderer.ts +112 -53
  104. package/src/gantt/calculator.ts +91 -29
  105. package/src/gantt/parser.ts +197 -71
  106. package/src/gantt/renderer.ts +1120 -350
  107. package/src/graph/flowchart-parser.ts +46 -25
  108. package/src/graph/state-parser.ts +47 -17
  109. package/src/index.ts +96 -31
  110. package/src/infra/parser.ts +157 -53
  111. package/src/infra/renderer.ts +723 -271
  112. package/src/initiative-status/parser.ts +138 -44
  113. package/src/kanban/parser.ts +25 -14
  114. package/src/org/layout.ts +111 -44
  115. package/src/org/parser.ts +69 -22
  116. package/src/palettes/index.ts +3 -2
  117. package/src/sequence/parser.ts +193 -61
  118. package/src/sitemap/parser.ts +65 -29
  119. package/src/utils/arrows.ts +2 -22
  120. package/src/utils/duration.ts +39 -21
  121. package/src/utils/legend-constants.ts +0 -2
  122. package/src/utils/parsing.ts +75 -31
@@ -1,7 +1,11 @@
1
1
  import { resolveColor } from '../colors';
2
2
  import type { PaletteColors } from '../palettes';
3
- import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
4
- import { measureIndent, parseFirstLine, OPTION_NOCOLON_RE } from '../utils/parsing';
3
+ import { makeDgmoError, formatDgmoError } from '../diagnostics';
4
+ import {
5
+ measureIndent,
6
+ parseFirstLine,
7
+ OPTION_NOCOLON_RE,
8
+ } from '../utils/parsing';
5
9
  import type {
6
10
  ParsedClassDiagram,
7
11
  ClassNode,
@@ -35,11 +39,11 @@ const CLASS_DECL_RE =
35
39
  // --|> TargetClass : label (colon-separated, kept for transition)
36
40
  // Arrows: --|> ..|> *-- o-- ..> ->
37
41
  const INDENT_REL_ARROW_RE =
38
- /^(--\|>|\.\.\|>|\*--|o--|\.\.\>|->)\s*([A-Z][A-Za-z0-9_]*)(?:\s+:?\s*(.+))?$/;
42
+ /^(--\|>|\.\.\|>|\*--|o--|\.\.>|->)\s*([A-Z][A-Za-z0-9_]*)(?:\s+:?\s*(.+))?$/;
39
43
 
40
44
  // Legacy top-level relationship regex (used only for detection/rejection)
41
45
  const REL_ARROW_RE =
42
- /^([A-Z][A-Za-z0-9_]*)\s*(--\|>|\.\.\|>|\*--|o--|\.\.\>|->)\s*([A-Z][A-Za-z0-9_]*)(?:\s+:?\s*(.+))?$/;
46
+ /^([A-Z][A-Za-z0-9_]*)\s*(--\|>|\.\.\|>|\*--|o--|\.\.>|->)\s*([A-Z][A-Za-z0-9_]*)(?:\s+:?\s*(.+))?$/;
43
47
 
44
48
  // Member line patterns
45
49
  const VISIBILITY_RE = /^([+\-#])\s*/;
@@ -160,7 +164,8 @@ export function parseClassDiagram(
160
164
  error: null,
161
165
  };
162
166
 
163
- const fail = (line: number, message: string): ParsedClassDiagram => {
167
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
168
+ const _fail = (line: number, message: string): ParsedClassDiagram => {
164
169
  const diag = makeDgmoError(line, message);
165
170
  result.diagnostics.push(diag);
166
171
  result.error = formatDgmoError(diag);
@@ -281,8 +286,8 @@ export function parseClassDiagram(
281
286
  makeDgmoError(
282
287
  lineNumber,
283
288
  `Relationship "${sourceName} ${arrow} ${targetName}" must be indented under the source class "${sourceName}"`,
284
- 'warning',
285
- ),
289
+ 'warning'
290
+ )
286
291
  );
287
292
  continue;
288
293
  }
@@ -319,17 +324,29 @@ export function parseClassDiagram(
319
324
  currentClass = node;
320
325
  continue;
321
326
  }
327
+
328
+ // Catch-all: nothing matched this line
329
+ result.diagnostics.push(
330
+ makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, 'warning')
331
+ );
322
332
  }
323
333
 
324
334
  // Validation
325
335
  if (result.classes.length === 0 && !result.error) {
326
- const diag = makeDgmoError(1, 'No classes found. Add class declarations like "ClassName" or "ClassName [interface]".');
336
+ const diag = makeDgmoError(
337
+ 1,
338
+ 'No classes found. Add class declarations like "ClassName" or "ClassName [interface]".'
339
+ );
327
340
  result.diagnostics.push(diag);
328
341
  result.error = formatDgmoError(diag);
329
342
  }
330
343
 
331
344
  // Warn about isolated classes (not in any relationship)
332
- if (result.classes.length >= 2 && result.relationships.length >= 1 && !result.error) {
345
+ if (
346
+ result.classes.length >= 2 &&
347
+ result.relationships.length >= 1 &&
348
+ !result.error
349
+ ) {
333
350
  const connectedIds = new Set<string>();
334
351
  for (const rel of result.relationships) {
335
352
  connectedIds.add(rel.source);
@@ -337,7 +354,13 @@ export function parseClassDiagram(
337
354
  }
338
355
  for (const cls of result.classes) {
339
356
  if (!connectedIds.has(cls.id)) {
340
- result.diagnostics.push(makeDgmoError(cls.lineNumber, `Class "${cls.name}" is not connected to any other class`, 'warning'));
357
+ result.diagnostics.push(
358
+ makeDgmoError(
359
+ cls.lineNumber,
360
+ `Class "${cls.name}" is not connected to any other class`,
361
+ 'warning'
362
+ )
363
+ );
341
364
  }
342
365
  }
343
366
  }
@@ -380,7 +403,9 @@ export function looksLikeClassDiagram(content: string): boolean {
380
403
  hasClassDecl = true;
381
404
  }
382
405
  // Check for old modifier pattern: ClassName [abstract|interface|enum]
383
- if (/^[A-Z][A-Za-z0-9_]*\s+\[(abstract|interface|enum)\]/i.test(trimmed)) {
406
+ if (
407
+ /^[A-Z][A-Za-z0-9_]*\s+\[(abstract|interface|enum)\]/i.test(trimmed)
408
+ ) {
384
409
  hasModifier = true;
385
410
  hasClassDecl = true;
386
411
  }
@@ -435,7 +460,11 @@ export function extractSymbols(docText: string): DiagramSymbols {
435
460
  for (const rawLine of docText.split('\n')) {
436
461
  const line = rawLine.trim();
437
462
  // Skip old-style colon metadata and new-style first line / space-separated options
438
- if (inMetadata && (/^[a-z-]+\s*:/i.test(line) || /^class(\s|$)/i.test(line))) continue;
463
+ if (
464
+ inMetadata &&
465
+ (/^[a-z-]+\s*:/i.test(line) || /^class(\s|$)/i.test(line))
466
+ )
467
+ continue;
439
468
  if (inMetadata && line.toLowerCase() === 'no-auto-color') continue;
440
469
  if (inMetadata && /^[a-z]/.test(line) && OPTION_NOCOLON_RE.test(line)) {
441
470
  const key = line.match(OPTION_NOCOLON_RE)![1].toLowerCase();
package/src/cli.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
3
  import { execSync } from 'node:child_process';
3
4
  import { homedir } from 'node:os';
@@ -47,7 +48,8 @@ const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
47
48
  org: 'Org chart — hierarchical tree structures',
48
49
  kanban: 'Kanban board — task/workflow columns',
49
50
  c4: 'C4 diagram — system architecture (context, container, component, deployment)',
50
- 'initiative-status': 'Initiative status — project roadmap with dependency tracking',
51
+ 'initiative-status':
52
+ 'Initiative status — project roadmap with dependency tracking',
51
53
  infra: 'Infra chart — infrastructure traffic flow with rps computation',
52
54
  };
53
55
 
@@ -484,6 +486,7 @@ Full reference: call \`get_language_reference\` MCP tool or visit diagrammo.app/
484
486
  function printHelp(): void {
485
487
  console.log(`Usage: dgmo <input> [options]
486
488
  cat input.dgmo | dgmo [options]
489
+ dgmo cat <file> Display file with syntax highlighting
487
490
 
488
491
  Render a .dgmo file to PNG (default) or SVG.
489
492
 
@@ -532,6 +535,8 @@ function parseArgs(argv: string[]): {
532
535
  copy: boolean;
533
536
  json: boolean;
534
537
  chartTypes: boolean;
538
+ cat: boolean;
539
+ noColor: boolean;
535
540
  installClaudeSkill: boolean;
536
541
  installClaudeCodeIntegration: boolean;
537
542
  installCodexIntegration: boolean;
@@ -551,10 +556,16 @@ function parseArgs(argv: string[]): {
551
556
  copy: false,
552
557
  json: false,
553
558
  chartTypes: false,
559
+ cat: false,
560
+ noColor: false,
554
561
  installClaudeSkill: false,
555
562
  installClaudeCodeIntegration: false,
556
563
  installCodexIntegration: false,
557
- c4Level: 'context' as 'context' | 'containers' | 'components' | 'deployment',
564
+ c4Level: 'context' as
565
+ | 'context'
566
+ | 'containers'
567
+ | 'components'
568
+ | 'deployment',
558
569
  c4System: undefined as string | undefined,
559
570
  c4Container: undefined as string | undefined,
560
571
  tagGroup: undefined as string | undefined,
@@ -566,7 +577,13 @@ function parseArgs(argv: string[]): {
566
577
  while (i < args.length) {
567
578
  const arg = args[i];
568
579
 
569
- if (arg === '--help' || arg === '-h') {
580
+ if (arg === 'cat' && !result.cat && !result.input) {
581
+ result.cat = true;
582
+ i++;
583
+ } else if (arg === '--no-color') {
584
+ result.noColor = true;
585
+ i++;
586
+ } else if (arg === '--help' || arg === '-h') {
570
587
  result.help = true;
571
588
  i++;
572
589
  } else if (arg === '--version' || arg === '-v') {
@@ -597,7 +614,12 @@ function parseArgs(argv: string[]): {
597
614
  i++;
598
615
  } else if (arg === '--c4-level') {
599
616
  const val = args[++i];
600
- if (val !== 'context' && val !== 'containers' && val !== 'components' && val !== 'deployment') {
617
+ if (
618
+ val !== 'context' &&
619
+ val !== 'containers' &&
620
+ val !== 'components' &&
621
+ val !== 'deployment'
622
+ ) {
601
623
  console.error(
602
624
  `Error: Invalid C4 level "${val}". Valid levels: context, containers, components, deployment`
603
625
  );
@@ -735,6 +757,37 @@ async function main(): Promise<void> {
735
757
  return;
736
758
  }
737
759
 
760
+ if (opts.cat) {
761
+ const useColor =
762
+ !opts.noColor && !process.env.NO_COLOR && process.stdout.isTTY === true;
763
+
764
+ let catContent: string;
765
+ if (opts.input && opts.input !== '-') {
766
+ const inputPath = resolve(opts.input);
767
+ try {
768
+ catContent = readFileSync(inputPath, 'utf-8');
769
+ } catch {
770
+ console.error(`Error: Cannot read file "${inputPath}"`);
771
+ process.exit(1);
772
+ }
773
+ } else {
774
+ // Read from stdin
775
+ try {
776
+ catContent = readFileSync(0, 'utf-8');
777
+ } catch {
778
+ console.error('Error: No input file specified');
779
+ console.error('Usage: dgmo cat <file>');
780
+ process.exit(1);
781
+ }
782
+ }
783
+
784
+ const { highlightDgmo, renderAnsi } =
785
+ await import('./editor/highlight-api');
786
+ const tokens = highlightDgmo(catContent);
787
+ process.stdout.write(renderAnsi(tokens, useColor));
788
+ return;
789
+ }
790
+
738
791
  if (opts.installClaudeCodeIntegration) {
739
792
  const claudeDir = join(homedir(), '.claude');
740
793
  if (!existsSync(claudeDir)) {
@@ -745,8 +798,14 @@ async function main(): Promise<void> {
745
798
 
746
799
  function ask(prompt: string): Promise<string> {
747
800
  return new Promise((resolve) => {
748
- const rl = createInterface({ input: process.stdin, output: process.stdout });
749
- rl.question(prompt, (answer) => { rl.close(); resolve(answer); });
801
+ const rl = createInterface({
802
+ input: process.stdin,
803
+ output: process.stdout,
804
+ });
805
+ rl.question(prompt, (answer) => {
806
+ rl.close();
807
+ resolve(answer);
808
+ });
750
809
  });
751
810
  }
752
811
 
@@ -756,7 +815,9 @@ async function main(): Promise<void> {
756
815
  const skillExists = existsSync(skillPath);
757
816
  let installSkill = true;
758
817
  if (skillExists) {
759
- const ans = await ask('~/.claude/commands/dgmo.md already exists. Overwrite? [y/N] ');
818
+ const ans = await ask(
819
+ '~/.claude/commands/dgmo.md already exists. Overwrite? [y/N] '
820
+ );
760
821
  installSkill = ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
761
822
  }
762
823
  if (installSkill) {
@@ -769,16 +830,26 @@ async function main(): Promise<void> {
769
830
 
770
831
  // --- Step 2: Check / install dgmo-mcp binary ---
771
832
  let dgmoMcpInstalled = false;
772
- try { execSync('which dgmo-mcp', { stdio: 'pipe' }); dgmoMcpInstalled = true; } catch { /* not found */ }
833
+ try {
834
+ execSync('which dgmo-mcp', { stdio: 'pipe' });
835
+ dgmoMcpInstalled = true;
836
+ } catch {
837
+ /* not found */
838
+ }
773
839
  if (!dgmoMcpInstalled) {
774
- const ans = await ask('\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] ');
775
- const yes = ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
840
+ const ans = await ask(
841
+ '\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] '
842
+ );
843
+ const yes =
844
+ ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
776
845
  if (yes) {
777
846
  console.log('Installing @diagrammo/dgmo-mcp...');
778
847
  execSync('npm install -g @diagrammo/dgmo-mcp', { stdio: 'inherit' });
779
848
  console.log('✓ @diagrammo/dgmo-mcp installed');
780
849
  } else {
781
- console.log(' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp');
850
+ console.log(
851
+ ' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
852
+ );
782
853
  }
783
854
  } else {
784
855
  console.log('✓ dgmo-mcp already installed');
@@ -787,7 +858,9 @@ async function main(): Promise<void> {
787
858
  // --- Step 3: Configure MCP server ---
788
859
  console.log('\nWhere should the MCP server be configured?');
789
860
  console.log(' 1) This project only — write .mcp.json here [default]');
790
- console.log(' 2) Globally — add to ~/.claude/settings.json (works in all projects)');
861
+ console.log(
862
+ ' 2) Globally — add to ~/.claude/settings.json (works in all projects)'
863
+ );
791
864
  const scopeAns = await ask('\nChoice [1]: ');
792
865
  const useGlobal = scopeAns.trim() === '2';
793
866
  const mcpEntry = { command: 'dgmo-mcp' };
@@ -796,24 +869,40 @@ async function main(): Promise<void> {
796
869
  const settingsPath = join(claudeDir, 'settings.json');
797
870
  let settings: Record<string, unknown> = {};
798
871
  if (existsSync(settingsPath)) {
799
- try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* use empty */ }
872
+ try {
873
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
874
+ } catch {
875
+ /* use empty */
876
+ }
800
877
  }
801
- const mcpServers = (settings.mcpServers as Record<string, unknown> | undefined) ?? {};
878
+ const mcpServers =
879
+ (settings.mcpServers as Record<string, unknown> | undefined) ?? {};
802
880
  mcpServers['dgmo'] = mcpEntry;
803
881
  settings.mcpServers = mcpServers;
804
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
882
+ writeFileSync(
883
+ settingsPath,
884
+ JSON.stringify(settings, null, 2) + '\n',
885
+ 'utf-8'
886
+ );
805
887
  console.log('✓ MCP server added to ~/.claude/settings.json');
806
888
  } else {
807
889
  const mcpPath = join(process.cwd(), '.mcp.json');
808
890
  let mcp: Record<string, unknown> = {};
809
891
  if (existsSync(mcpPath)) {
810
- try { mcp = JSON.parse(readFileSync(mcpPath, 'utf-8')); } catch { /* use empty */ }
892
+ try {
893
+ mcp = JSON.parse(readFileSync(mcpPath, 'utf-8'));
894
+ } catch {
895
+ /* use empty */
896
+ }
811
897
  }
812
- const mcpServers = (mcp.mcpServers as Record<string, unknown> | undefined) ?? {};
898
+ const mcpServers =
899
+ (mcp.mcpServers as Record<string, unknown> | undefined) ?? {};
813
900
  mcpServers['dgmo'] = mcpEntry;
814
901
  mcp.mcpServers = mcpServers;
815
902
  writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n', 'utf-8');
816
- console.log(`✓ MCP server configured: ${join(process.cwd(), '.mcp.json')}`);
903
+ console.log(
904
+ `✓ MCP server configured: ${join(process.cwd(), '.mcp.json')}`
905
+ );
817
906
  }
818
907
 
819
908
  console.log('\nRestart Claude Code to activate the MCP server.');
@@ -835,12 +924,17 @@ async function main(): Promise<void> {
835
924
  ? `~/.claude/commands/dgmo.md already exists. Overwrite? [y/N] `
836
925
  : `Install dgmo Claude Code skill to ~/.claude/commands/dgmo.md? [Y/n] `;
837
926
  await new Promise<void>((done) => {
838
- const rl = createInterface({ input: process.stdin, output: process.stdout });
927
+ const rl = createInterface({
928
+ input: process.stdin,
929
+ output: process.stdout,
930
+ });
839
931
  rl.question(prompt, (answer) => {
840
932
  rl.close();
841
933
  const yes = alreadyExists
842
934
  ? answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'
843
- : answer === '' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
935
+ : answer === '' ||
936
+ answer.toLowerCase() === 'y' ||
937
+ answer.toLowerCase() === 'yes';
844
938
  if (!yes) {
845
939
  console.error('Aborted.');
846
940
  process.exit(0);
@@ -859,23 +953,41 @@ async function main(): Promise<void> {
859
953
 
860
954
  if (opts.installCodexIntegration) {
861
955
  // Validate Codex CLI is installed
862
- try { execSync('which codex', { stdio: 'pipe' }); } catch {
863
- console.error('codex not found. Install Codex CLI first: https://openai.com/codex');
956
+ try {
957
+ execSync('which codex', { stdio: 'pipe' });
958
+ } catch {
959
+ console.error(
960
+ 'codex not found. Install Codex CLI first: https://openai.com/codex'
961
+ );
864
962
  process.exit(1);
865
963
  }
866
964
 
867
965
  const ask = (prompt: string): Promise<string> =>
868
966
  new Promise((resolve) => {
869
- const rl = createInterface({ input: process.stdin, output: process.stdout });
870
- rl.question(prompt, (answer) => { rl.close(); resolve(answer); });
967
+ const rl = createInterface({
968
+ input: process.stdin,
969
+ output: process.stdout,
970
+ });
971
+ rl.question(prompt, (answer) => {
972
+ rl.close();
973
+ resolve(answer);
974
+ });
871
975
  });
872
976
 
873
977
  // Check / install dgmo-mcp binary
874
978
  let dgmoMcpInstalled = false;
875
- try { execSync('which dgmo-mcp', { stdio: 'pipe' }); dgmoMcpInstalled = true; } catch { /* not found */ }
979
+ try {
980
+ execSync('which dgmo-mcp', { stdio: 'pipe' });
981
+ dgmoMcpInstalled = true;
982
+ } catch {
983
+ /* not found */
984
+ }
876
985
  if (!dgmoMcpInstalled) {
877
- const ans = await ask('\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] ');
878
- const yes = ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
986
+ const ans = await ask(
987
+ '\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] '
988
+ );
989
+ const yes =
990
+ ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
879
991
  if (yes) {
880
992
  console.log('Installing @diagrammo/dgmo-mcp...');
881
993
  try {
@@ -886,7 +998,9 @@ async function main(): Promise<void> {
886
998
  console.error('Try manually: npm install -g @diagrammo/dgmo-mcp');
887
999
  }
888
1000
  } else {
889
- console.log(' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp');
1001
+ console.log(
1002
+ ' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
1003
+ );
890
1004
  }
891
1005
  } else {
892
1006
  console.log('✓ dgmo-mcp already installed');
@@ -894,11 +1008,21 @@ async function main(): Promise<void> {
894
1008
 
895
1009
  // Configure MCP server
896
1010
  console.log('\nWhere should the MCP server be configured?');
897
- console.log(' 1) This project only — write .codex/config.toml here [default]');
898
- console.log(' 2) Globallyadd to ~/.codex/config.toml (works in all projects)');
1011
+ console.log(
1012
+ ' 1) This project only write .codex/config.toml here [default]'
1013
+ );
1014
+ console.log(
1015
+ ' 2) Globally — add to ~/.codex/config.toml (works in all projects)'
1016
+ );
899
1017
  const scopeAns = await ask('\nChoice [1]: ');
900
- if (scopeAns.trim() !== '' && scopeAns.trim() !== '1' && scopeAns.trim() !== '2') {
901
- console.log(` Unrecognized input "${scopeAns.trim()}", defaulting to option 1.`);
1018
+ if (
1019
+ scopeAns.trim() !== '' &&
1020
+ scopeAns.trim() !== '1' &&
1021
+ scopeAns.trim() !== '2'
1022
+ ) {
1023
+ console.log(
1024
+ ` Unrecognized input "${scopeAns.trim()}", defaulting to option 1.`
1025
+ );
902
1026
  }
903
1027
  const useGlobal = scopeAns.trim() === '2';
904
1028
  const tomlEntry = '[mcp_servers.dgmo]\ncommand = ["dgmo-mcp"]\n';
@@ -906,7 +1030,9 @@ async function main(): Promise<void> {
906
1030
  if (useGlobal) {
907
1031
  const configPath = join(homedir(), '.codex', 'config.toml');
908
1032
  mkdirSync(join(homedir(), '.codex'), { recursive: true });
909
- const existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
1033
+ const existing = existsSync(configPath)
1034
+ ? readFileSync(configPath, 'utf-8')
1035
+ : '';
910
1036
  if (existing.includes('[mcp_servers.dgmo]')) {
911
1037
  console.log('✓ MCP server already configured in ~/.codex/config.toml');
912
1038
  } else {
@@ -918,7 +1044,9 @@ async function main(): Promise<void> {
918
1044
  const codexDir = join(process.cwd(), '.codex');
919
1045
  const configPath = join(codexDir, 'config.toml');
920
1046
  mkdirSync(codexDir, { recursive: true });
921
- const existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
1047
+ const existing = existsSync(configPath)
1048
+ ? readFileSync(configPath, 'utf-8')
1049
+ : '';
922
1050
  if (existing.includes('[mcp_servers.dgmo]')) {
923
1051
  console.log(`✓ MCP server already configured in .codex/config.toml`);
924
1052
  } else {
@@ -983,10 +1111,8 @@ async function main(): Promise<void> {
983
1111
  // Resolve org chart imports (tags and import directives)
984
1112
  if (opts.input && parseDgmoChartType(content) === 'org') {
985
1113
  const inputPath = resolve(opts.input);
986
- const resolved = await resolveOrgImports(
987
- content,
988
- inputPath,
989
- (p) => readFileSync(p, 'utf-8'),
1114
+ const resolved = await resolveOrgImports(content, inputPath, (p) =>
1115
+ readFileSync(p, 'utf-8')
990
1116
  );
991
1117
  for (const diag of resolved.diagnostics) {
992
1118
  console.error(formatDgmoError(diag));
@@ -1008,12 +1134,18 @@ async function main(): Promise<void> {
1008
1134
  // Helper for JSON error output
1009
1135
  function exitWithJsonError(error: string, line?: number): never {
1010
1136
  if (opts.json) {
1011
- process.stdout.write(JSON.stringify({
1012
- success: false,
1013
- error,
1014
- ...(line != null ? { line } : {}),
1015
- ...(chartType ? { chartType } : {}),
1016
- }, null, 2) + '\n');
1137
+ process.stdout.write(
1138
+ JSON.stringify(
1139
+ {
1140
+ success: false,
1141
+ error,
1142
+ ...(line != null ? { line } : {}),
1143
+ ...(chartType ? { chartType } : {}),
1144
+ },
1145
+ null,
1146
+ 2
1147
+ ) + '\n'
1148
+ );
1017
1149
  } else {
1018
1150
  console.error(error);
1019
1151
  }
@@ -1046,18 +1178,26 @@ async function main(): Promise<void> {
1046
1178
  }
1047
1179
 
1048
1180
  if (opts.json) {
1049
- process.stdout.write(JSON.stringify({
1050
- success: true,
1051
- url: result.url,
1052
- ...(chartType ? { chartType } : {}),
1053
- }, null, 2) + '\n');
1181
+ process.stdout.write(
1182
+ JSON.stringify(
1183
+ {
1184
+ success: true,
1185
+ url: result.url,
1186
+ ...(chartType ? { chartType } : {}),
1187
+ },
1188
+ null,
1189
+ 2
1190
+ ) + '\n'
1191
+ );
1054
1192
  } else {
1055
1193
  process.stdout.write(result.url + '\n');
1056
1194
  }
1057
1195
  return;
1058
1196
  }
1059
1197
 
1060
- const paletteColors = getPalette(opts.palette)[opts.theme === 'dark' ? 'dark' : 'light'];
1198
+ const paletteColors = getPalette(opts.palette)[
1199
+ opts.theme === 'dark' ? 'dark' : 'light'
1200
+ ];
1061
1201
 
1062
1202
  // Word clouds require Canvas APIs (HTMLCanvasElement.getContext('2d'))
1063
1203
  // which are unavailable in Node.js — check before attempting render.
@@ -1084,10 +1224,7 @@ async function main(): Promise<void> {
1084
1224
  if (errors.length > 0) {
1085
1225
  if (opts.json) {
1086
1226
  const firstError = errors[0];
1087
- exitWithJsonError(
1088
- formatDgmoError(firstError),
1089
- firstError.line,
1090
- );
1227
+ exitWithJsonError(formatDgmoError(firstError), firstError.line);
1091
1228
  }
1092
1229
  for (const e of errors) {
1093
1230
  console.error(`\u2716 ${formatDgmoError(e)}`);
@@ -1096,14 +1233,20 @@ async function main(): Promise<void> {
1096
1233
 
1097
1234
  // Validate C4 options
1098
1235
  if (opts.c4Level === 'containers' && !opts.c4System) {
1099
- exitWithJsonError('Error: --c4-system is required when --c4-level is containers');
1236
+ exitWithJsonError(
1237
+ 'Error: --c4-system is required when --c4-level is containers'
1238
+ );
1100
1239
  }
1101
1240
  if (opts.c4Level === 'components') {
1102
1241
  if (!opts.c4System) {
1103
- exitWithJsonError('Error: --c4-system is required when --c4-level is components');
1242
+ exitWithJsonError(
1243
+ 'Error: --c4-system is required when --c4-level is components'
1244
+ );
1104
1245
  }
1105
1246
  if (!opts.c4Container) {
1106
- exitWithJsonError('Error: --c4-container is required when --c4-level is components');
1247
+ exitWithJsonError(
1248
+ 'Error: --c4-container is required when --c4-level is components'
1249
+ );
1107
1250
  }
1108
1251
  }
1109
1252
 
@@ -1143,11 +1286,17 @@ async function main(): Promise<void> {
1143
1286
  outputPath = resolve(`${inputBasename}.png`);
1144
1287
  writeFileSync(outputPath, svgToPng(svg, pngBg));
1145
1288
  }
1146
- process.stdout.write(JSON.stringify({
1147
- success: true,
1148
- ...(outputPath ? { output: outputPath } : {}),
1149
- ...(chartType ? { chartType } : {}),
1150
- }, null, 2) + '\n');
1289
+ process.stdout.write(
1290
+ JSON.stringify(
1291
+ {
1292
+ success: true,
1293
+ ...(outputPath ? { output: outputPath } : {}),
1294
+ ...(chartType ? { chartType } : {}),
1295
+ },
1296
+ null,
1297
+ 2
1298
+ ) + '\n'
1299
+ );
1151
1300
  } else if (opts.output) {
1152
1301
  // Explicit output path
1153
1302
  const outputPath = resolve(opts.output);