@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.
- package/.claude/commands/dgmo-diagram-this.md +60 -0
- package/.claude/commands/dgmo-document-project.md +128 -0
- package/.claude/commands/dgmo.md +452 -50
- package/.cursorrules +32 -37
- package/.github/copilot-instructions.md +35 -44
- package/.windsurfrules +32 -37
- package/README.md +4 -4
- package/dist/cli.cjs +188 -185
- package/dist/editor.cjs +338 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +27 -0
- package/dist/editor.d.ts +27 -0
- package/dist/editor.js +307 -0
- package/dist/editor.js.map +1 -0
- package/dist/highlight.cjs +560 -0
- package/dist/highlight.cjs.map +1 -0
- package/dist/highlight.d.cts +32 -0
- package/dist/highlight.d.ts +32 -0
- package/dist/highlight.js +530 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.cjs +3467 -1078
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +3466 -1078
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +46 -37
- package/gallery/fixtures/arc.dgmo +18 -0
- package/gallery/fixtures/area.dgmo +19 -0
- package/gallery/fixtures/bar-stacked.dgmo +10 -0
- package/gallery/fixtures/bar.dgmo +10 -0
- package/gallery/fixtures/c4-full.dgmo +52 -0
- package/gallery/fixtures/c4.dgmo +17 -0
- package/gallery/fixtures/chord.dgmo +12 -0
- package/gallery/fixtures/class-basic.dgmo +14 -0
- package/gallery/fixtures/class-full.dgmo +43 -0
- package/gallery/fixtures/doughnut.dgmo +8 -0
- package/gallery/fixtures/flowchart-basic.dgmo +3 -0
- package/gallery/fixtures/flowchart-colors.dgmo +5 -0
- package/gallery/fixtures/flowchart-complex.dgmo +17 -0
- package/gallery/fixtures/flowchart-decision.dgmo +5 -0
- package/gallery/fixtures/flowchart-full.dgmo +13 -0
- package/gallery/fixtures/flowchart-groups.dgmo +10 -0
- package/gallery/fixtures/flowchart-loop.dgmo +7 -0
- package/gallery/fixtures/flowchart-nested.dgmo +7 -0
- package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
- package/gallery/fixtures/function.dgmo +8 -0
- package/gallery/fixtures/funnel.dgmo +7 -0
- package/gallery/fixtures/gantt-full.dgmo +49 -0
- package/gallery/fixtures/gantt.dgmo +42 -0
- package/gallery/fixtures/heatmap.dgmo +8 -0
- package/gallery/fixtures/infra-full.dgmo +78 -0
- package/gallery/fixtures/infra-overload.dgmo +25 -0
- package/gallery/fixtures/infra.dgmo +47 -0
- package/gallery/fixtures/initiative-status-full.dgmo +46 -0
- package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
- package/gallery/fixtures/initiative-status.dgmo +9 -0
- package/gallery/fixtures/line.dgmo +19 -0
- package/gallery/fixtures/multi-line.dgmo +11 -0
- package/gallery/fixtures/org-basic.dgmo +16 -0
- package/gallery/fixtures/org-full.dgmo +69 -0
- package/gallery/fixtures/org-teams.dgmo +25 -0
- package/gallery/fixtures/pie.dgmo +9 -0
- package/gallery/fixtures/polar-area.dgmo +8 -0
- package/gallery/fixtures/quadrant.dgmo +18 -0
- package/gallery/fixtures/radar.dgmo +8 -0
- package/gallery/fixtures/sankey.dgmo +31 -0
- package/gallery/fixtures/scatter.dgmo +21 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
- package/gallery/fixtures/sequence-tags.dgmo +41 -0
- package/gallery/fixtures/sequence.dgmo +35 -0
- package/gallery/fixtures/sitemap-basic.dgmo +12 -0
- package/gallery/fixtures/sitemap-full.dgmo +156 -0
- package/gallery/fixtures/slope.dgmo +9 -0
- package/gallery/fixtures/spr-eras.dgmo +62 -0
- package/gallery/fixtures/state.dgmo +30 -0
- package/gallery/fixtures/timeline-intraday.dgmo +14 -0
- package/gallery/fixtures/timeline.dgmo +32 -0
- package/gallery/fixtures/venn.dgmo +10 -0
- package/gallery/fixtures/wordcloud.dgmo +24 -0
- package/package.json +71 -2
- package/src/c4/layout.ts +372 -90
- package/src/c4/parser.ts +100 -55
- package/src/chart.ts +91 -28
- package/src/class/parser.ts +41 -12
- package/src/cli.ts +211 -62
- package/src/completion.ts +378 -183
- package/src/d3.ts +1044 -303
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +69 -23
- package/src/echarts.ts +646 -153
- package/src/editor/dgmo.grammar +69 -0
- package/src/editor/dgmo.grammar.d.ts +2 -0
- package/src/editor/dgmo.grammar.js +18 -0
- package/src/editor/dgmo.grammar.terms.d.ts +5 -0
- package/src/editor/dgmo.grammar.terms.js +35 -0
- package/src/editor/highlight-api.ts +444 -0
- package/src/editor/highlight.ts +36 -0
- package/src/editor/index.ts +28 -0
- package/src/editor/keywords.ts +222 -0
- package/src/editor/tokens.ts +30 -0
- package/src/er/parser.ts +48 -14
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +197 -71
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +46 -25
- package/src/graph/state-parser.ts +47 -17
- package/src/index.ts +96 -31
- package/src/infra/parser.ts +157 -53
- package/src/infra/renderer.ts +723 -271
- package/src/initiative-status/parser.ts +138 -44
- package/src/kanban/parser.ts +25 -14
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +69 -22
- package/src/palettes/index.ts +3 -2
- package/src/sequence/parser.ts +193 -61
- package/src/sitemap/parser.ts +65 -29
- package/src/utils/arrows.ts +2 -22
- package/src/utils/duration.ts +39 -21
- package/src/utils/legend-constants.ts +0 -2
- package/src/utils/parsing.ts +75 -31
package/src/class/parser.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { resolveColor } from '../colors';
|
|
2
2
|
import type { PaletteColors } from '../palettes';
|
|
3
|
-
import { makeDgmoError, formatDgmoError
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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(
|
|
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 (
|
|
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 (
|
|
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':
|
|
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
|
|
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 === '
|
|
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 (
|
|
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({
|
|
749
|
-
|
|
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(
|
|
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 {
|
|
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(
|
|
775
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
872
|
+
try {
|
|
873
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
874
|
+
} catch {
|
|
875
|
+
/* use empty */
|
|
876
|
+
}
|
|
800
877
|
}
|
|
801
|
-
const mcpServers =
|
|
878
|
+
const mcpServers =
|
|
879
|
+
(settings.mcpServers as Record<string, unknown> | undefined) ?? {};
|
|
802
880
|
mcpServers['dgmo'] = mcpEntry;
|
|
803
881
|
settings.mcpServers = mcpServers;
|
|
804
|
-
writeFileSync(
|
|
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 {
|
|
892
|
+
try {
|
|
893
|
+
mcp = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
894
|
+
} catch {
|
|
895
|
+
/* use empty */
|
|
896
|
+
}
|
|
811
897
|
}
|
|
812
|
-
const mcpServers =
|
|
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(
|
|
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({
|
|
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 === '' ||
|
|
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 {
|
|
863
|
-
|
|
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({
|
|
870
|
-
|
|
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 {
|
|
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(
|
|
878
|
-
|
|
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(
|
|
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(
|
|
898
|
-
|
|
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 (
|
|
901
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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(
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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(
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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)[
|
|
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(
|
|
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(
|
|
1242
|
+
exitWithJsonError(
|
|
1243
|
+
'Error: --c4-system is required when --c4-level is components'
|
|
1244
|
+
);
|
|
1104
1245
|
}
|
|
1105
1246
|
if (!opts.c4Container) {
|
|
1106
|
-
exitWithJsonError(
|
|
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(
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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);
|