@diagrammo/dgmo 0.8.3 → 0.8.4

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 (112) 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 +185 -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 +153 -153
  9. package/dist/editor.cjs +336 -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 +305 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/index.cjs +3336 -1055
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +3336 -1055
  18. package/dist/index.js.map +1 -1
  19. package/docs/language-reference.md +30 -29
  20. package/gallery/fixtures/arc.dgmo +18 -0
  21. package/gallery/fixtures/area.dgmo +19 -0
  22. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  23. package/gallery/fixtures/bar.dgmo +10 -0
  24. package/gallery/fixtures/c4-full.dgmo +52 -0
  25. package/gallery/fixtures/c4.dgmo +17 -0
  26. package/gallery/fixtures/chord.dgmo +12 -0
  27. package/gallery/fixtures/class-basic.dgmo +14 -0
  28. package/gallery/fixtures/class-full.dgmo +43 -0
  29. package/gallery/fixtures/doughnut.dgmo +8 -0
  30. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  31. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  32. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  33. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  34. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  35. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  36. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  37. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  38. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  39. package/gallery/fixtures/function.dgmo +8 -0
  40. package/gallery/fixtures/funnel.dgmo +7 -0
  41. package/gallery/fixtures/gantt-full.dgmo +49 -0
  42. package/gallery/fixtures/gantt.dgmo +42 -0
  43. package/gallery/fixtures/heatmap.dgmo +8 -0
  44. package/gallery/fixtures/infra-full.dgmo +78 -0
  45. package/gallery/fixtures/infra-overload.dgmo +25 -0
  46. package/gallery/fixtures/infra.dgmo +47 -0
  47. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  48. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  49. package/gallery/fixtures/initiative-status.dgmo +9 -0
  50. package/gallery/fixtures/line.dgmo +19 -0
  51. package/gallery/fixtures/multi-line.dgmo +11 -0
  52. package/gallery/fixtures/org-basic.dgmo +16 -0
  53. package/gallery/fixtures/org-full.dgmo +69 -0
  54. package/gallery/fixtures/org-teams.dgmo +25 -0
  55. package/gallery/fixtures/pie.dgmo +9 -0
  56. package/gallery/fixtures/polar-area.dgmo +8 -0
  57. package/gallery/fixtures/quadrant.dgmo +18 -0
  58. package/gallery/fixtures/radar.dgmo +8 -0
  59. package/gallery/fixtures/sankey.dgmo +31 -0
  60. package/gallery/fixtures/scatter.dgmo +21 -0
  61. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  62. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  63. package/gallery/fixtures/sequence.dgmo +35 -0
  64. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  65. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  66. package/gallery/fixtures/slope.dgmo +8 -0
  67. package/gallery/fixtures/spr-eras.dgmo +62 -0
  68. package/gallery/fixtures/state.dgmo +30 -0
  69. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  70. package/gallery/fixtures/timeline.dgmo +32 -0
  71. package/gallery/fixtures/venn.dgmo +10 -0
  72. package/gallery/fixtures/wordcloud.dgmo +24 -0
  73. package/package.json +51 -2
  74. package/src/c4/layout.ts +372 -90
  75. package/src/c4/parser.ts +100 -55
  76. package/src/chart.ts +91 -28
  77. package/src/class/parser.ts +41 -12
  78. package/src/cli.ts +168 -61
  79. package/src/completion.ts +378 -183
  80. package/src/d3.ts +887 -288
  81. package/src/dgmo-mermaid.ts +16 -13
  82. package/src/dgmo-router.ts +69 -23
  83. package/src/echarts.ts +646 -153
  84. package/src/editor/dgmo.grammar +69 -0
  85. package/src/editor/dgmo.grammar.d.ts +2 -0
  86. package/src/editor/dgmo.grammar.js +18 -0
  87. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  88. package/src/editor/dgmo.grammar.terms.js +35 -0
  89. package/src/editor/highlight.ts +36 -0
  90. package/src/editor/index.ts +28 -0
  91. package/src/editor/keywords.ts +220 -0
  92. package/src/editor/tokens.ts +30 -0
  93. package/src/er/parser.ts +48 -14
  94. package/src/er/renderer.ts +112 -53
  95. package/src/gantt/calculator.ts +91 -29
  96. package/src/gantt/parser.ts +197 -71
  97. package/src/gantt/renderer.ts +1120 -350
  98. package/src/graph/flowchart-parser.ts +46 -25
  99. package/src/graph/state-parser.ts +47 -17
  100. package/src/infra/parser.ts +157 -53
  101. package/src/infra/renderer.ts +723 -271
  102. package/src/initiative-status/parser.ts +138 -44
  103. package/src/kanban/parser.ts +25 -14
  104. package/src/org/layout.ts +111 -44
  105. package/src/org/parser.ts +69 -22
  106. package/src/palettes/index.ts +3 -2
  107. package/src/sequence/parser.ts +193 -61
  108. package/src/sitemap/parser.ts +65 -29
  109. package/src/utils/arrows.ts +2 -22
  110. package/src/utils/duration.ts +39 -21
  111. package/src/utils/legend-constants.ts +0 -2
  112. 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
 
@@ -554,7 +556,11 @@ function parseArgs(argv: string[]): {
554
556
  installClaudeSkill: false,
555
557
  installClaudeCodeIntegration: false,
556
558
  installCodexIntegration: false,
557
- c4Level: 'context' as 'context' | 'containers' | 'components' | 'deployment',
559
+ c4Level: 'context' as
560
+ | 'context'
561
+ | 'containers'
562
+ | 'components'
563
+ | 'deployment',
558
564
  c4System: undefined as string | undefined,
559
565
  c4Container: undefined as string | undefined,
560
566
  tagGroup: undefined as string | undefined,
@@ -597,7 +603,12 @@ function parseArgs(argv: string[]): {
597
603
  i++;
598
604
  } else if (arg === '--c4-level') {
599
605
  const val = args[++i];
600
- if (val !== 'context' && val !== 'containers' && val !== 'components' && val !== 'deployment') {
606
+ if (
607
+ val !== 'context' &&
608
+ val !== 'containers' &&
609
+ val !== 'components' &&
610
+ val !== 'deployment'
611
+ ) {
601
612
  console.error(
602
613
  `Error: Invalid C4 level "${val}". Valid levels: context, containers, components, deployment`
603
614
  );
@@ -745,8 +756,14 @@ async function main(): Promise<void> {
745
756
 
746
757
  function ask(prompt: string): Promise<string> {
747
758
  return new Promise((resolve) => {
748
- const rl = createInterface({ input: process.stdin, output: process.stdout });
749
- rl.question(prompt, (answer) => { rl.close(); resolve(answer); });
759
+ const rl = createInterface({
760
+ input: process.stdin,
761
+ output: process.stdout,
762
+ });
763
+ rl.question(prompt, (answer) => {
764
+ rl.close();
765
+ resolve(answer);
766
+ });
750
767
  });
751
768
  }
752
769
 
@@ -756,7 +773,9 @@ async function main(): Promise<void> {
756
773
  const skillExists = existsSync(skillPath);
757
774
  let installSkill = true;
758
775
  if (skillExists) {
759
- const ans = await ask('~/.claude/commands/dgmo.md already exists. Overwrite? [y/N] ');
776
+ const ans = await ask(
777
+ '~/.claude/commands/dgmo.md already exists. Overwrite? [y/N] '
778
+ );
760
779
  installSkill = ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
761
780
  }
762
781
  if (installSkill) {
@@ -769,16 +788,26 @@ async function main(): Promise<void> {
769
788
 
770
789
  // --- Step 2: Check / install dgmo-mcp binary ---
771
790
  let dgmoMcpInstalled = false;
772
- try { execSync('which dgmo-mcp', { stdio: 'pipe' }); dgmoMcpInstalled = true; } catch { /* not found */ }
791
+ try {
792
+ execSync('which dgmo-mcp', { stdio: 'pipe' });
793
+ dgmoMcpInstalled = true;
794
+ } catch {
795
+ /* not found */
796
+ }
773
797
  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';
798
+ const ans = await ask(
799
+ '\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] '
800
+ );
801
+ const yes =
802
+ ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
776
803
  if (yes) {
777
804
  console.log('Installing @diagrammo/dgmo-mcp...');
778
805
  execSync('npm install -g @diagrammo/dgmo-mcp', { stdio: 'inherit' });
779
806
  console.log('✓ @diagrammo/dgmo-mcp installed');
780
807
  } else {
781
- console.log(' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp');
808
+ console.log(
809
+ ' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
810
+ );
782
811
  }
783
812
  } else {
784
813
  console.log('✓ dgmo-mcp already installed');
@@ -787,7 +816,9 @@ async function main(): Promise<void> {
787
816
  // --- Step 3: Configure MCP server ---
788
817
  console.log('\nWhere should the MCP server be configured?');
789
818
  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)');
819
+ console.log(
820
+ ' 2) Globally — add to ~/.claude/settings.json (works in all projects)'
821
+ );
791
822
  const scopeAns = await ask('\nChoice [1]: ');
792
823
  const useGlobal = scopeAns.trim() === '2';
793
824
  const mcpEntry = { command: 'dgmo-mcp' };
@@ -796,24 +827,40 @@ async function main(): Promise<void> {
796
827
  const settingsPath = join(claudeDir, 'settings.json');
797
828
  let settings: Record<string, unknown> = {};
798
829
  if (existsSync(settingsPath)) {
799
- try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch { /* use empty */ }
830
+ try {
831
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
832
+ } catch {
833
+ /* use empty */
834
+ }
800
835
  }
801
- const mcpServers = (settings.mcpServers as Record<string, unknown> | undefined) ?? {};
836
+ const mcpServers =
837
+ (settings.mcpServers as Record<string, unknown> | undefined) ?? {};
802
838
  mcpServers['dgmo'] = mcpEntry;
803
839
  settings.mcpServers = mcpServers;
804
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
840
+ writeFileSync(
841
+ settingsPath,
842
+ JSON.stringify(settings, null, 2) + '\n',
843
+ 'utf-8'
844
+ );
805
845
  console.log('✓ MCP server added to ~/.claude/settings.json');
806
846
  } else {
807
847
  const mcpPath = join(process.cwd(), '.mcp.json');
808
848
  let mcp: Record<string, unknown> = {};
809
849
  if (existsSync(mcpPath)) {
810
- try { mcp = JSON.parse(readFileSync(mcpPath, 'utf-8')); } catch { /* use empty */ }
850
+ try {
851
+ mcp = JSON.parse(readFileSync(mcpPath, 'utf-8'));
852
+ } catch {
853
+ /* use empty */
854
+ }
811
855
  }
812
- const mcpServers = (mcp.mcpServers as Record<string, unknown> | undefined) ?? {};
856
+ const mcpServers =
857
+ (mcp.mcpServers as Record<string, unknown> | undefined) ?? {};
813
858
  mcpServers['dgmo'] = mcpEntry;
814
859
  mcp.mcpServers = mcpServers;
815
860
  writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n', 'utf-8');
816
- console.log(`✓ MCP server configured: ${join(process.cwd(), '.mcp.json')}`);
861
+ console.log(
862
+ `✓ MCP server configured: ${join(process.cwd(), '.mcp.json')}`
863
+ );
817
864
  }
818
865
 
819
866
  console.log('\nRestart Claude Code to activate the MCP server.');
@@ -835,12 +882,17 @@ async function main(): Promise<void> {
835
882
  ? `~/.claude/commands/dgmo.md already exists. Overwrite? [y/N] `
836
883
  : `Install dgmo Claude Code skill to ~/.claude/commands/dgmo.md? [Y/n] `;
837
884
  await new Promise<void>((done) => {
838
- const rl = createInterface({ input: process.stdin, output: process.stdout });
885
+ const rl = createInterface({
886
+ input: process.stdin,
887
+ output: process.stdout,
888
+ });
839
889
  rl.question(prompt, (answer) => {
840
890
  rl.close();
841
891
  const yes = alreadyExists
842
892
  ? answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'
843
- : answer === '' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
893
+ : answer === '' ||
894
+ answer.toLowerCase() === 'y' ||
895
+ answer.toLowerCase() === 'yes';
844
896
  if (!yes) {
845
897
  console.error('Aborted.');
846
898
  process.exit(0);
@@ -859,23 +911,41 @@ async function main(): Promise<void> {
859
911
 
860
912
  if (opts.installCodexIntegration) {
861
913
  // 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');
914
+ try {
915
+ execSync('which codex', { stdio: 'pipe' });
916
+ } catch {
917
+ console.error(
918
+ 'codex not found. Install Codex CLI first: https://openai.com/codex'
919
+ );
864
920
  process.exit(1);
865
921
  }
866
922
 
867
923
  const ask = (prompt: string): Promise<string> =>
868
924
  new Promise((resolve) => {
869
- const rl = createInterface({ input: process.stdin, output: process.stdout });
870
- rl.question(prompt, (answer) => { rl.close(); resolve(answer); });
925
+ const rl = createInterface({
926
+ input: process.stdin,
927
+ output: process.stdout,
928
+ });
929
+ rl.question(prompt, (answer) => {
930
+ rl.close();
931
+ resolve(answer);
932
+ });
871
933
  });
872
934
 
873
935
  // Check / install dgmo-mcp binary
874
936
  let dgmoMcpInstalled = false;
875
- try { execSync('which dgmo-mcp', { stdio: 'pipe' }); dgmoMcpInstalled = true; } catch { /* not found */ }
937
+ try {
938
+ execSync('which dgmo-mcp', { stdio: 'pipe' });
939
+ dgmoMcpInstalled = true;
940
+ } catch {
941
+ /* not found */
942
+ }
876
943
  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';
944
+ const ans = await ask(
945
+ '\ndgmo-mcp not found. Install @diagrammo/dgmo-mcp globally now? [Y/n] '
946
+ );
947
+ const yes =
948
+ ans === '' || ans.toLowerCase() === 'y' || ans.toLowerCase() === 'yes';
879
949
  if (yes) {
880
950
  console.log('Installing @diagrammo/dgmo-mcp...');
881
951
  try {
@@ -886,7 +956,9 @@ async function main(): Promise<void> {
886
956
  console.error('Try manually: npm install -g @diagrammo/dgmo-mcp');
887
957
  }
888
958
  } else {
889
- console.log(' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp');
959
+ console.log(
960
+ ' Skipped. Install later with: npm install -g @diagrammo/dgmo-mcp'
961
+ );
890
962
  }
891
963
  } else {
892
964
  console.log('✓ dgmo-mcp already installed');
@@ -894,11 +966,21 @@ async function main(): Promise<void> {
894
966
 
895
967
  // Configure MCP server
896
968
  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)');
969
+ console.log(
970
+ ' 1) This project only write .codex/config.toml here [default]'
971
+ );
972
+ console.log(
973
+ ' 2) Globally — add to ~/.codex/config.toml (works in all projects)'
974
+ );
899
975
  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.`);
976
+ if (
977
+ scopeAns.trim() !== '' &&
978
+ scopeAns.trim() !== '1' &&
979
+ scopeAns.trim() !== '2'
980
+ ) {
981
+ console.log(
982
+ ` Unrecognized input "${scopeAns.trim()}", defaulting to option 1.`
983
+ );
902
984
  }
903
985
  const useGlobal = scopeAns.trim() === '2';
904
986
  const tomlEntry = '[mcp_servers.dgmo]\ncommand = ["dgmo-mcp"]\n';
@@ -906,7 +988,9 @@ async function main(): Promise<void> {
906
988
  if (useGlobal) {
907
989
  const configPath = join(homedir(), '.codex', 'config.toml');
908
990
  mkdirSync(join(homedir(), '.codex'), { recursive: true });
909
- const existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
991
+ const existing = existsSync(configPath)
992
+ ? readFileSync(configPath, 'utf-8')
993
+ : '';
910
994
  if (existing.includes('[mcp_servers.dgmo]')) {
911
995
  console.log('✓ MCP server already configured in ~/.codex/config.toml');
912
996
  } else {
@@ -918,7 +1002,9 @@ async function main(): Promise<void> {
918
1002
  const codexDir = join(process.cwd(), '.codex');
919
1003
  const configPath = join(codexDir, 'config.toml');
920
1004
  mkdirSync(codexDir, { recursive: true });
921
- const existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
1005
+ const existing = existsSync(configPath)
1006
+ ? readFileSync(configPath, 'utf-8')
1007
+ : '';
922
1008
  if (existing.includes('[mcp_servers.dgmo]')) {
923
1009
  console.log(`✓ MCP server already configured in .codex/config.toml`);
924
1010
  } else {
@@ -983,10 +1069,8 @@ async function main(): Promise<void> {
983
1069
  // Resolve org chart imports (tags and import directives)
984
1070
  if (opts.input && parseDgmoChartType(content) === 'org') {
985
1071
  const inputPath = resolve(opts.input);
986
- const resolved = await resolveOrgImports(
987
- content,
988
- inputPath,
989
- (p) => readFileSync(p, 'utf-8'),
1072
+ const resolved = await resolveOrgImports(content, inputPath, (p) =>
1073
+ readFileSync(p, 'utf-8')
990
1074
  );
991
1075
  for (const diag of resolved.diagnostics) {
992
1076
  console.error(formatDgmoError(diag));
@@ -1008,12 +1092,18 @@ async function main(): Promise<void> {
1008
1092
  // Helper for JSON error output
1009
1093
  function exitWithJsonError(error: string, line?: number): never {
1010
1094
  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');
1095
+ process.stdout.write(
1096
+ JSON.stringify(
1097
+ {
1098
+ success: false,
1099
+ error,
1100
+ ...(line != null ? { line } : {}),
1101
+ ...(chartType ? { chartType } : {}),
1102
+ },
1103
+ null,
1104
+ 2
1105
+ ) + '\n'
1106
+ );
1017
1107
  } else {
1018
1108
  console.error(error);
1019
1109
  }
@@ -1046,18 +1136,26 @@ async function main(): Promise<void> {
1046
1136
  }
1047
1137
 
1048
1138
  if (opts.json) {
1049
- process.stdout.write(JSON.stringify({
1050
- success: true,
1051
- url: result.url,
1052
- ...(chartType ? { chartType } : {}),
1053
- }, null, 2) + '\n');
1139
+ process.stdout.write(
1140
+ JSON.stringify(
1141
+ {
1142
+ success: true,
1143
+ url: result.url,
1144
+ ...(chartType ? { chartType } : {}),
1145
+ },
1146
+ null,
1147
+ 2
1148
+ ) + '\n'
1149
+ );
1054
1150
  } else {
1055
1151
  process.stdout.write(result.url + '\n');
1056
1152
  }
1057
1153
  return;
1058
1154
  }
1059
1155
 
1060
- const paletteColors = getPalette(opts.palette)[opts.theme === 'dark' ? 'dark' : 'light'];
1156
+ const paletteColors = getPalette(opts.palette)[
1157
+ opts.theme === 'dark' ? 'dark' : 'light'
1158
+ ];
1061
1159
 
1062
1160
  // Word clouds require Canvas APIs (HTMLCanvasElement.getContext('2d'))
1063
1161
  // which are unavailable in Node.js — check before attempting render.
@@ -1084,10 +1182,7 @@ async function main(): Promise<void> {
1084
1182
  if (errors.length > 0) {
1085
1183
  if (opts.json) {
1086
1184
  const firstError = errors[0];
1087
- exitWithJsonError(
1088
- formatDgmoError(firstError),
1089
- firstError.line,
1090
- );
1185
+ exitWithJsonError(formatDgmoError(firstError), firstError.line);
1091
1186
  }
1092
1187
  for (const e of errors) {
1093
1188
  console.error(`\u2716 ${formatDgmoError(e)}`);
@@ -1096,14 +1191,20 @@ async function main(): Promise<void> {
1096
1191
 
1097
1192
  // Validate C4 options
1098
1193
  if (opts.c4Level === 'containers' && !opts.c4System) {
1099
- exitWithJsonError('Error: --c4-system is required when --c4-level is containers');
1194
+ exitWithJsonError(
1195
+ 'Error: --c4-system is required when --c4-level is containers'
1196
+ );
1100
1197
  }
1101
1198
  if (opts.c4Level === 'components') {
1102
1199
  if (!opts.c4System) {
1103
- exitWithJsonError('Error: --c4-system is required when --c4-level is components');
1200
+ exitWithJsonError(
1201
+ 'Error: --c4-system is required when --c4-level is components'
1202
+ );
1104
1203
  }
1105
1204
  if (!opts.c4Container) {
1106
- exitWithJsonError('Error: --c4-container is required when --c4-level is components');
1205
+ exitWithJsonError(
1206
+ 'Error: --c4-container is required when --c4-level is components'
1207
+ );
1107
1208
  }
1108
1209
  }
1109
1210
 
@@ -1143,11 +1244,17 @@ async function main(): Promise<void> {
1143
1244
  outputPath = resolve(`${inputBasename}.png`);
1144
1245
  writeFileSync(outputPath, svgToPng(svg, pngBg));
1145
1246
  }
1146
- process.stdout.write(JSON.stringify({
1147
- success: true,
1148
- ...(outputPath ? { output: outputPath } : {}),
1149
- ...(chartType ? { chartType } : {}),
1150
- }, null, 2) + '\n');
1247
+ process.stdout.write(
1248
+ JSON.stringify(
1249
+ {
1250
+ success: true,
1251
+ ...(outputPath ? { output: outputPath } : {}),
1252
+ ...(chartType ? { chartType } : {}),
1253
+ },
1254
+ null,
1255
+ 2
1256
+ ) + '\n'
1257
+ );
1151
1258
  } else if (opts.output) {
1152
1259
  // Explicit output path
1153
1260
  const outputPath = resolve(opts.output);