agentic-mermaid 0.1.0 → 0.1.2
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/CHANGELOG.md +149 -4
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +25 -32
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/fonts/FONT-LICENSES.md +9 -5
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +81 -1822
- package/dist/agent.js +336 -36839
- package/dist/agentic-mermaid-mcp.js +7 -37908
- package/dist/am.js +33638 -36261
- package/dist/chunk-4QECACIA.js +4702 -0
- package/dist/chunk-CDPKJ2BQ.js +37 -0
- package/dist/chunk-QKDLMOJJ.js +3487 -0
- package/dist/chunk-WI35L7IR.js +615 -0
- package/dist/chunk-WN3YDOBG.js +149 -0
- package/dist/chunk-ZYMMKQ3I.js +62637 -0
- package/dist/index-D54dqSdC.d.ts +6777 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -32836
- package/dist/mcp-cli-SBQSTJFC.js +17 -0
- package/docs/README.md +14 -6
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +70 -43
- package/server.json +41 -0
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/AGENT_NATIVE.md +0 -602
- package/Instructions_for_agents.md +0 -83
- package/TODO.md +0 -662
- package/assets/hero.png +0 -0
- package/dist/index-x2Cj1dVv.d.ts +0 -1089
- package/docs/agent-api-cookbook.md +0 -308
- package/docs/agent-mutation-policy.md +0 -104
- package/docs/agent-workflow-examples.md +0 -46
- package/docs/api.md +0 -203
- package/docs/ascii.md +0 -103
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/comparison.md +0 -94
- package/docs/config.md +0 -105
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -71
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/custom-style-cookbook.md +0 -154
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -433
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -125
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/diagram-families.md +0 -193
- package/docs/features.md +0 -147
- package/docs/fork-differences.md +0 -130
- package/docs/getting-started.md +0 -175
- package/docs/issue-derived-test-cases.md +0 -156
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -496
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -118
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/mcp-code-mode-rationale.md +0 -101
- package/docs/mcp-http-transport.md +0 -223
- package/docs/mermaid-layout-complaints.md +0 -837
- package/docs/mutation-testing.md +0 -206
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/docs/quality.md +0 -212
- package/docs/react.md +0 -97
- package/docs/style-authoring.md +0 -133
- package/docs/testing-strategy.md +0 -335
- package/docs/theming.md +0 -162
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -364
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -337
- package/src/agent/families-builtin.ts +0 -934
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -826
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -414
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -439
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -117
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -979
- package/src/agent/verify.ts +0 -571
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -189
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -128
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1117
- package/src/cli/init-agent.ts +0 -147
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/font-manifest.ts +0 -27
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -520
- package/src/journey/parser.ts +0 -218
- package/src/journey/renderer.ts +0 -811
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -387
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -72
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -639
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -454
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
package/src/cli/index.ts
DELETED
|
@@ -1,1117 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// am — agentic-mermaid CLI (v4).
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import { readFileSync, existsSync, writeFileSync, mkdtempSync, watch } from 'node:fs'
|
|
6
|
-
import { spawnSync } from 'node:child_process'
|
|
7
|
-
import { join, resolve } from 'node:path'
|
|
8
|
-
import { tmpdir } from 'node:os'
|
|
9
|
-
import { parseMermaid } from '../agent/parse.ts'
|
|
10
|
-
import { logToolInvocation } from '../agent/trace-log.ts'
|
|
11
|
-
import { serializeMermaid, synthesizeFromGraph } from '../agent/serialize.ts'
|
|
12
|
-
import { mutateChecked } from '../agent/mutate.ts'
|
|
13
|
-
import { verifyMermaid } from '../agent/verify.ts'
|
|
14
|
-
import { renderMermaidSVG, renderMermaidASCII, renderMermaidPNG, layoutMermaid } from '../agent/index.ts'
|
|
15
|
-
import { describeMermaid } from '../agent/describe.ts'
|
|
16
|
-
import { collectBatched } from '../shared/batched.ts'
|
|
17
|
-
import type {
|
|
18
|
-
ValidDiagram, WarningCode, AnyMutationOp,
|
|
19
|
-
MutationError, Result, MutableValidDiagram,
|
|
20
|
-
} from '../agent/types.ts'
|
|
21
|
-
import { WARNING_SEVERITY, WARNING_TIER } from '../agent/types.ts'
|
|
22
|
-
import { BUILTIN_FAMILY_METADATA, builtinFamilyMetadata, knownFamilies, getFamily } from '../agent/families.ts'
|
|
23
|
-
import { knownStyles, getStyle, validateStyleSpec, inferBackend, resolveStyleStack, styleKind } from '../scene/style-registry.ts'
|
|
24
|
-
import type { StyleInput, StyleSpec } from '../scene/style-registry.ts'
|
|
25
|
-
import type { BuiltinFamilyId } from '../agent/families.ts'
|
|
26
|
-
import '../agent/families-builtin.ts'
|
|
27
|
-
import { AGENT_INSTRUCTIONS } from './agent-instructions.ts'
|
|
28
|
-
import { initAgentFiles } from './init-agent.ts'
|
|
29
|
-
import { EXIT_OK, EXIT_ARG_ERROR, EXIT_VERIFY_FAILED, EXIT_INTERNAL } from './exit-codes.ts'
|
|
30
|
-
import type { ParseError } from '../agent/types.ts'
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Loop 12 M1: build a structured CLI error envelope. Keeps `message` a short
|
|
34
|
-
* human string (the first error's message) and carries the full structured
|
|
35
|
-
* ParseError[] in `details` — so an agent reads `error.details` instead of
|
|
36
|
-
* re-parsing a JSON string buried in `error.message`.
|
|
37
|
-
*/
|
|
38
|
-
function parseErrorEnvelope(errors: ParseError[]): { ok: false; error: { code: string; message: string; details: ParseError[] } } {
|
|
39
|
-
const first = errors[0]
|
|
40
|
-
const message = first ? `${first.code}: ${first.message}` : 'parse error'
|
|
41
|
-
return { ok: false, error: { code: 'PARSE_FAILED', message, details: errors } }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface ParsedArgs { command?: string; positional: string[]; flags: Record<string, string | boolean> }
|
|
45
|
-
|
|
46
|
-
// Single source of truth for CLI flags. A flag with no `arg` is a boolean;
|
|
47
|
-
// `arg` is the usage placeholder shown in `[--flag <arg>]`. BOOLEAN_FLAGS is
|
|
48
|
-
// DERIVED from this, so the parser's boolean classification cannot drift from
|
|
49
|
-
// the documented usage.
|
|
50
|
-
export const FLAG_SPECS: Record<string, { arg?: string }> = {
|
|
51
|
-
// booleans
|
|
52
|
-
'agent-instructions': {}, 'ascii': {}, 'certificates': {}, 'help': {}, 'json': {},
|
|
53
|
-
'watch': {}, 'open': {}, 'force': {}, 'canonical-wrapper': {},
|
|
54
|
-
// value flags (placeholder = what the usage shows after the flag)
|
|
55
|
-
'suppress': { arg: 'CODES' }, 'label-cap': { arg: 'N' }, 'op': { arg: 'JSON' },
|
|
56
|
-
'style': { arg: 'NAMES|file' }, 'seed': { arg: 'N' },
|
|
57
|
-
'ops': { arg: 'JSON|file' }, 'output': { arg: 'FILE' }, 'format': { arg: 'fmt' },
|
|
58
|
-
'security': { arg: 'mode' }, 'dir': { arg: 'DIR' },
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const BOOLEAN_FLAGS = new Set(Object.keys(FLAG_SPECS).filter(name => !FLAG_SPECS[name]!.arg))
|
|
62
|
-
|
|
63
|
-
export function parseArgs(argv: string[]): ParsedArgs {
|
|
64
|
-
const out: ParsedArgs = { positional: [], flags: {} }
|
|
65
|
-
let i = 0
|
|
66
|
-
while (i < argv.length) {
|
|
67
|
-
const arg = argv[i]!
|
|
68
|
-
if (arg.startsWith('--')) {
|
|
69
|
-
const eq = arg.indexOf('=')
|
|
70
|
-
if (eq !== -1) out.flags[arg.slice(2, eq)] = arg.slice(eq + 1)
|
|
71
|
-
else {
|
|
72
|
-
const name = arg.slice(2)
|
|
73
|
-
const next = argv[i + 1]
|
|
74
|
-
if (BOOLEAN_FLAGS.has(name) || next === undefined || next.startsWith('--')) out.flags[name] = true
|
|
75
|
-
else { out.flags[name] = next; i++ }
|
|
76
|
-
}
|
|
77
|
-
} else if (!out.command) out.command = arg
|
|
78
|
-
else out.positional.push(arg)
|
|
79
|
-
i++
|
|
80
|
-
}
|
|
81
|
-
return out
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function readSourceArg(arg: string | undefined): string {
|
|
85
|
-
if (!arg || arg === '-') {
|
|
86
|
-
// Loop 9 M5: TTY-stdin guard. When stdin is an interactive TTY (no
|
|
87
|
-
// pipe), the read blocks forever waiting for the user to paste + Ctrl-D
|
|
88
|
-
// — confusing UX. Fail fast with a clear hint.
|
|
89
|
-
if (process.stdin.isTTY) throw new Error('needs a file argument or piped stdin')
|
|
90
|
-
try { return readFileSync(0).toString('utf8') } catch { return '' }
|
|
91
|
-
}
|
|
92
|
-
if (!existsSync(arg)) throw new Error(`File not found: ${arg}`)
|
|
93
|
-
return readFileSync(arg, 'utf8')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function replacer(_k: string, v: unknown): unknown { return v instanceof Map ? Object.fromEntries(v) : v }
|
|
97
|
-
|
|
98
|
-
export const GLOBAL_USAGE = `Usage: am <command> [options] [file|-]
|
|
99
|
-
|
|
100
|
-
Commands:
|
|
101
|
-
render <file|-> Render to SVG/ASCII/Unicode/layout JSON/PNG
|
|
102
|
-
verify <file|-> Verify; emits structured JSON warnings
|
|
103
|
-
parse <file|-> Parse; emits ValidDiagram JSON
|
|
104
|
-
serialize Read ValidDiagram JSON from stdin; emit canonical source
|
|
105
|
-
mutate <file|-> --op '<JSON>' Apply MutationOp(s); verify + emit source
|
|
106
|
-
preview <file|-> Write standalone HTML preview; optionally --open
|
|
107
|
-
format <file|-> Idempotent reformat
|
|
108
|
-
describe <file|-> Prose summary or AX-tree JSON
|
|
109
|
-
capabilities Emit JSON describing families + warning codes + mutation ops
|
|
110
|
-
batch Read JSONL ops from stdin; emit one JSON envelope per line
|
|
111
|
-
render-markdown <file> Render fenced mermaid blocks in Markdown (SVG or --ascii)
|
|
112
|
-
styles List registered styles (looks + themes); see also render --style
|
|
113
|
-
llms-txt Emit the agent discovery digest
|
|
114
|
-
init-agent Write a repo-local agent drop-in (AGENTS section, skill, MCP config)
|
|
115
|
-
|
|
116
|
-
Flags:
|
|
117
|
-
--json Structured JSON output
|
|
118
|
-
--ascii For render/render-markdown: ASCII instead of SVG
|
|
119
|
-
--op <JSON> For mutate: one MutationOp
|
|
120
|
-
--ops <JSON|file> For mutate: JSON array of MutationOps
|
|
121
|
-
--output <FILE> For render png / preview output path
|
|
122
|
-
--open For preview: open generated HTML in browser
|
|
123
|
-
--force For init-agent: refresh generated skill/MCP files
|
|
124
|
-
--style <NAMES|file> For render svg/png: style stack — comma-separated names and/or .json spec files
|
|
125
|
-
--seed <N> For render svg/png: re-roll ink wobble of styled looks (never layout)
|
|
126
|
-
--suppress <CODES> For verify: comma-separated WarningCodes to suppress
|
|
127
|
-
--label-cap <N> For verify: LABEL_OVERFLOW char cap (default 40)
|
|
128
|
-
--certificates For render --format json: include route/family certificates
|
|
129
|
-
--agent-instructions Print the canonical agent-use guide
|
|
130
|
-
--help Show this message (or per-command help: am <cmd> --help)
|
|
131
|
-
|
|
132
|
-
Exit codes:
|
|
133
|
-
0 ok
|
|
134
|
-
2 arg / parse error (bad flag, missing file, malformed JSON)
|
|
135
|
-
3 verify reported errors (severity 'error')
|
|
136
|
-
4 uncaught internal failure
|
|
137
|
-
`
|
|
138
|
-
|
|
139
|
-
export const COMMAND_HELP: Record<string, string> = {
|
|
140
|
-
render: `am render <file|-> [--format svg|ascii|unicode|json|png] [--ascii] [--json]
|
|
141
|
-
Render a diagram. Default is SVG.
|
|
142
|
-
--format svg SVG markup (default)
|
|
143
|
-
--format ascii 7-bit ASCII art (also via --ascii)
|
|
144
|
-
--format unicode Unicode box-drawing ASCII art
|
|
145
|
-
--format json Layout JSON (nodes, edges, groups, bounds)
|
|
146
|
-
--certificates With --format json, include route/family certificates
|
|
147
|
-
--format png PNG bytes; requires --output <file.png>; no watch/multi-input
|
|
148
|
-
--style <S> Style stack: comma-separated names and/or .json spec files,
|
|
149
|
-
merged left → right (e.g. --style hand-drawn,dracula or
|
|
150
|
-
--style ./brand.json). Applies to svg/png. See: am styles
|
|
151
|
-
--seed <N> Re-roll ink wobble of styled looks; never moves layout
|
|
152
|
-
--security strict Remove external-fetch refs from SVG output
|
|
153
|
-
--watch Re-render one input file on change (non-PNG only)
|
|
154
|
-
Multiple inputs emit a JSON results array for non-PNG formats.
|
|
155
|
-
With --json, the svg/ascii/unicode forms wrap output as {"<format>": "..."}.`,
|
|
156
|
-
styles: `am styles [--json]
|
|
157
|
-
List every registered style. A style is a partial description of how diagrams
|
|
158
|
-
look; a colors-only style is a theme, a full look sets stroke/fill/typography
|
|
159
|
-
too. Stack them with render --style (e.g. --style hand-drawn,dracula) and
|
|
160
|
-
re-roll ink with --seed. Custom styles are JSON records (docs/style-authoring.md);
|
|
161
|
-
pass a .json file path anywhere a name is accepted.
|
|
162
|
-
With --json: [{ name, kind: default|look|theme, backend, intent?, blurb }].`,
|
|
163
|
-
verify: `am verify <file|-> [--suppress A,B] [--label-cap N]
|
|
164
|
-
Always emits JSON: {ok, warnings[], layout}.
|
|
165
|
-
Tier-1 error codes flip ok=false:
|
|
166
|
-
EMPTY_DIAGRAM, EDGE_MISANCHORED, OFF_CANVAS, GROUP_BREACH, UNRESOLVABLE_SCHEDULE,
|
|
167
|
-
RENDER_FAILED (source verifies structurally but the render parser rejects it). Warning codes:
|
|
168
|
-
UNKNOWN_SHAPE, LABEL_OVERFLOW (char-cap),
|
|
169
|
-
NODE_OVERLAP, ROUTE_SELF_CROSS, ROUTE_HITCH, ROUTE_UNEXPLAINED_BEND, ROUTE_LABEL_ON_SHARED_TRUNK,
|
|
170
|
-
ROUTE_CONTAINER_MISANCHOR, ROUTE_SHAPE_MISANCHOR, ROUTE_STALE_AFTER_NODE_MOVE,
|
|
171
|
-
DUPLICATE_EDGE, UNREACHABLE_NODE, DECISION_BRANCH_UNLABELED, COMMENT_DROPPED, UNSUPPORTED_SYNTAX,
|
|
172
|
-
CONTENT_DROPPED_ON_ROUNDTRIP. Tier-3 lint is advisory.
|
|
173
|
-
Exit 0 if ok, 3 if verify reports severity='error'.`,
|
|
174
|
-
parse: `am parse <file|->
|
|
175
|
-
Emits ValidDiagram JSON (Maps serialized to objects). Exit 2 on parse error.
|
|
176
|
-
Pipe to 'am serialize' to round-trip.`,
|
|
177
|
-
serialize: `am serialize (reads ValidDiagram JSON on stdin)
|
|
178
|
-
Emits canonical Mermaid source. Accepts the JSON shape that 'am parse' emits;
|
|
179
|
-
rebuilds the diagram via synthesizeFromGraph without re-parsing source.`,
|
|
180
|
-
mutate: `am mutate <file|-> (--op '<JSON>' | --ops '<JSON array|file.json>') [--json]
|
|
181
|
-
Applies one or more MutationOps, verifies the final diagram, then emits source.
|
|
182
|
-
Flowchart/state, sequence, timeline, class, ER, journey, architecture, xychart,
|
|
183
|
-
pie, quadrant, and gantt have typed mutation ops; opaque-fallback diagrams (unmodeled
|
|
184
|
-
syntax) return a structured UNSUPPORTED_FAMILY error (exit 2). Verify failures
|
|
185
|
-
exit 3 and omit source.`,
|
|
186
|
-
preview: `am preview <file|-> [--output preview.html] [--open] [--json] [--security strict]
|
|
187
|
-
Writes a standalone HTML preview containing strict-mode rendered SVG. Without
|
|
188
|
-
--output, emits HTML to stdout unless --open is set, in which case a temp HTML
|
|
189
|
-
file is written and opened. With --json, emits {ok,path,opened,bytes} for file output.`,
|
|
190
|
-
format: `am format <file|-> [--canonical-wrapper]
|
|
191
|
-
Parse then re-serialize. Idempotent. The leading source wrapper (frontmatter,
|
|
192
|
-
%%{init}%% directives, comments before the header) is preserved byte-verbatim
|
|
193
|
-
by default; --canonical-wrapper instead synthesizes Mermaid's documented shape
|
|
194
|
-
(title/displayMode top-level, everything else under config:, directives folded).`,
|
|
195
|
-
describe: `am describe <file|-> [--format text|json|facts] [--json]
|
|
196
|
-
Summarize a Mermaid diagram. Text format emits prose by default; --json wraps
|
|
197
|
-
it as {ok,text}. --format json emits the structured AX tree
|
|
198
|
-
{kind,nodes,edges,entryPoints,sinks}; with --json it wraps as {ok,tree}.
|
|
199
|
-
--format facts emits deterministic semantic fact lines; with --json it wraps as {ok,facts}.`,
|
|
200
|
-
capabilities: `am capabilities [--json]
|
|
201
|
-
Emits a single JSON object describing the SDK's capability surface:
|
|
202
|
-
{ sdkVersion, families: [{ id, hasMutate, hasExtractLabels, mutationOps, editPolicy, example }],
|
|
203
|
-
warningCodes: [{ code, tier, severity }],
|
|
204
|
-
outputFormats: ["svg", "ascii", "unicode", "png", "json"] }
|
|
205
|
-
editPolicy is "structured-when-narrowed" or "source-level-only". Use this to
|
|
206
|
-
introspect what the CLI can do without running every command.`,
|
|
207
|
-
batch: `am batch (reads JSONL from stdin)
|
|
208
|
-
Each line: { op: "render"|"verify"|"parse"|"serialize"|"mutate", source: string,
|
|
209
|
-
options?: {}, mutation?: MutationOp, mutations?: MutationOp[] }. Emits one JSON
|
|
210
|
-
envelope per line: { ok, op, data?, error? }. Malformed JSON or unknown ops
|
|
211
|
-
surface as ok:false; the batch continues and exits 0 even if lines errored.`,
|
|
212
|
-
'render-markdown': `am render-markdown <file.md> [--ascii]
|
|
213
|
-
Render fenced \`\`\`mermaid blocks. Bad diagrams yield ok:false entries and do
|
|
214
|
-
not abort the rest of the Markdown file.`,
|
|
215
|
-
'llms-txt': `am llms-txt
|
|
216
|
-
Emit the committed agent-discovery digest generated from current capabilities.`,
|
|
217
|
-
'init-agent': `am init-agent [--dir <path>] [--force] [--json]
|
|
218
|
-
Write a repo-local, agent-agnostic drop-in so coding agents discover the
|
|
219
|
-
parse → narrow → mutate → verify → serialize contract automatically. Creates
|
|
220
|
-
without clobbering by default:
|
|
221
|
-
- AGENTS.md marked section pointing agents at the workflow + hosted docs
|
|
222
|
-
- skills/agentic-mermaid-diagram-workflow/SKILL.md generic skill bundle
|
|
223
|
-
- .mcp.json sample agentic-mermaid MCP server config
|
|
224
|
-
--dir defaults to the current directory. --force overwrites skill/MCP files;
|
|
225
|
-
the AGENTS.md section is always appended only once, guarded by a marker.`,
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export function runCli(argv: string[]): number {
|
|
229
|
-
const args = parseArgs(argv)
|
|
230
|
-
if (args.flags['agent-instructions']) { process.stdout.write(AGENT_INSTRUCTIONS); return EXIT_OK }
|
|
231
|
-
if (args.flags.help && !args.command) { process.stdout.write(GLOBAL_USAGE); return EXIT_OK }
|
|
232
|
-
if (!args.command) { process.stdout.write(GLOBAL_USAGE); return EXIT_ARG_ERROR }
|
|
233
|
-
if (args.flags.help) {
|
|
234
|
-
process.stdout.write((COMMAND_HELP[args.command] ?? GLOBAL_USAGE) + '\n')
|
|
235
|
-
return EXIT_OK
|
|
236
|
-
}
|
|
237
|
-
const json = Boolean(args.flags.json)
|
|
238
|
-
// Opt-in invocation logging: when AM_TRACE_LOG names a file, append one JSON
|
|
239
|
-
// line per real command run. Shares the sink with the library and hosted MCP
|
|
240
|
-
// (src/agent/trace-log.ts) so the agent-usage eval can grade traceOk from
|
|
241
|
-
// observed calls on ANY channel. The library verify/mutate/create the CLI
|
|
242
|
-
// commands call also log, so `am verify`/`am mutate` are covered even here.
|
|
243
|
-
logToolInvocation(args.command)
|
|
244
|
-
try {
|
|
245
|
-
switch (args.command) {
|
|
246
|
-
case 'render': return cmdRender(args, json)
|
|
247
|
-
case 'verify': return cmdVerify(args)
|
|
248
|
-
case 'parse': return cmdParse(args)
|
|
249
|
-
case 'serialize': return cmdSerialize()
|
|
250
|
-
case 'mutate': return cmdMutate(args, json)
|
|
251
|
-
case 'preview': return cmdPreview(args, json)
|
|
252
|
-
case 'format': return cmdFormat(args)
|
|
253
|
-
case 'describe': return cmdDescribe(args, json)
|
|
254
|
-
case 'capabilities': return cmdCapabilities()
|
|
255
|
-
case 'styles': return cmdStyles(json)
|
|
256
|
-
case 'llms-txt': return cmdLlmsTxt()
|
|
257
|
-
case 'init-agent': return cmdInitAgent(args, json)
|
|
258
|
-
case 'batch': return cmdBatch()
|
|
259
|
-
case 'render-markdown': return cmdRenderMarkdown(args)
|
|
260
|
-
default:
|
|
261
|
-
process.stderr.write(`Unknown command: ${args.command}\n${GLOBAL_USAGE}`)
|
|
262
|
-
return EXIT_ARG_ERROR
|
|
263
|
-
}
|
|
264
|
-
} catch (e) {
|
|
265
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
266
|
-
// Loop 9 M5: argument-shape errors (missing file, TTY stdin, etc.) get
|
|
267
|
-
// exit 2 per the documented contract. Heuristic: messages thrown from
|
|
268
|
-
// readSourceArg / arg parsing are advisory rather than internal bugs.
|
|
269
|
-
const isArgError = /^needs a file argument|^File not found:/.test(msg)
|
|
270
|
-
if (json) process.stdout.write(JSON.stringify({ ok: false, error: { code: isArgError ? 'ARG' : 'INTERNAL', message: msg } }) + '\n')
|
|
271
|
-
else process.stderr.write(`Error: ${msg}\n`)
|
|
272
|
-
return isArgError ? EXIT_ARG_ERROR : EXIT_INTERNAL
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function cmdRender(args: ParsedArgs, json: boolean): number {
|
|
277
|
-
const format = typeof args.flags.format === 'string' ? args.flags.format : (args.flags.ascii ? 'ascii' : 'svg')
|
|
278
|
-
const security = args.flags.security === 'strict' ? 'strict' as const : 'default' as const
|
|
279
|
-
if (!['svg', 'ascii', 'unicode', 'json', 'png'].includes(format)) {
|
|
280
|
-
process.stderr.write(`am render: unsupported --format ${format}; expected svg, ascii, unicode, json, or png\n`)
|
|
281
|
-
return EXIT_ARG_ERROR
|
|
282
|
-
}
|
|
283
|
-
if (args.flags.security !== undefined && args.flags.security !== 'strict') {
|
|
284
|
-
process.stderr.write('am render --security accepts only strict\n')
|
|
285
|
-
return EXIT_ARG_ERROR
|
|
286
|
-
}
|
|
287
|
-
if (args.flags.watch && args.positional.length > 1) {
|
|
288
|
-
process.stderr.write('am render --watch accepts exactly one input file\n')
|
|
289
|
-
return EXIT_ARG_ERROR
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// --style: a stack of names and/or .json spec files; fail fast (exit 2) on
|
|
293
|
-
// unknown names or invalid specs instead of surfacing an internal error.
|
|
294
|
-
let style: StyleInput[] | undefined
|
|
295
|
-
if (typeof args.flags.style === 'string') {
|
|
296
|
-
try {
|
|
297
|
-
style = parseStyleFlag(args.flags.style)
|
|
298
|
-
resolveStyleStack(style)
|
|
299
|
-
} catch (e) {
|
|
300
|
-
process.stderr.write(`am render --style: ${e instanceof Error ? e.message : String(e)}\n`)
|
|
301
|
-
return EXIT_ARG_ERROR
|
|
302
|
-
}
|
|
303
|
-
if (format === 'ascii' || format === 'unicode' || format === 'json') {
|
|
304
|
-
process.stderr.write(`am render: --style applies to svg/png output; ignored for --format ${format}\n`)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const seed = typeof args.flags.seed === 'string' ? Number(args.flags.seed) : undefined
|
|
308
|
-
if (seed !== undefined && !Number.isFinite(seed)) {
|
|
309
|
-
process.stderr.write('am render --seed expects a finite number\n')
|
|
310
|
-
return EXIT_ARG_ERROR
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (args.positional.length > 1 && format === 'png') {
|
|
314
|
-
process.stderr.write('am render --format png accepts exactly one input; run once per file with a distinct --output path\n')
|
|
315
|
-
return EXIT_ARG_ERROR
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// #959: multi-input — when more than one positional file is given, render
|
|
319
|
-
// each and emit a JSON array of results, skipping bad files (like #543).
|
|
320
|
-
// Single-input keeps the existing single-output behavior. PNG is excluded
|
|
321
|
-
// because it writes bytes to a single --output path.
|
|
322
|
-
if (args.positional.length > 1) {
|
|
323
|
-
const results = args.positional.map((file, index) => {
|
|
324
|
-
try {
|
|
325
|
-
const src = readSourceArg(file)
|
|
326
|
-
const output = renderSourceToFormat(src, format, { security, certificates: args.flags.certificates === true, style, seed })
|
|
327
|
-
return { index, file, ok: true, output }
|
|
328
|
-
} catch (e) {
|
|
329
|
-
const parseEnvelope = e as { ok?: false; error?: { code?: string; message?: string; details?: unknown } }
|
|
330
|
-
if (parseEnvelope?.ok === false && parseEnvelope.error?.code === 'PARSE_FAILED') {
|
|
331
|
-
return { index, file, ok: false, error: parseEnvelope.error }
|
|
332
|
-
}
|
|
333
|
-
return { index, file, ok: false, error: { code: 'RENDER_FAILED', message: e instanceof Error ? e.message : String(e) } }
|
|
334
|
-
}
|
|
335
|
-
})
|
|
336
|
-
process.stdout.write(JSON.stringify({ ok: true, files: results }) + '\n')
|
|
337
|
-
return EXIT_OK
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// #930: watch mode — re-render on file change.
|
|
341
|
-
if (args.flags.watch && format === 'png') {
|
|
342
|
-
process.stderr.write('am render --format png does not support --watch; run non-watch PNG renders with --output <file.png>\n')
|
|
343
|
-
return EXIT_ARG_ERROR
|
|
344
|
-
}
|
|
345
|
-
if (args.flags.watch && typeof args.positional[0] === 'string' && args.positional[0] !== '-') {
|
|
346
|
-
return cmdRenderWatch(args.positional[0], format, args, json, { security, certificates: args.flags.certificates === true, style, seed })
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const source = readSourceArg(args.positional[0])
|
|
350
|
-
const parsed = parseMermaid(source)
|
|
351
|
-
if (!parsed.ok) {
|
|
352
|
-
process.stdout.write(JSON.stringify(parseErrorEnvelope(parsed.error)) + '\n')
|
|
353
|
-
return EXIT_ARG_ERROR
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (format === 'png') {
|
|
357
|
-
const outFile = typeof args.flags.o === 'string' ? args.flags.o : (typeof args.flags.output === 'string' ? args.flags.output : '')
|
|
358
|
-
if (!outFile) {
|
|
359
|
-
process.stderr.write('am render --format png requires --output <file.png> (PNG bytes corrupt terminals if piped to stdout)\n')
|
|
360
|
-
return EXIT_ARG_ERROR
|
|
361
|
-
}
|
|
362
|
-
const scale = typeof args.flags.scale === 'string' ? Number(args.flags.scale) : 2
|
|
363
|
-
const background = typeof args.flags.bg === 'string' ? args.flags.bg : 'white'
|
|
364
|
-
// PNG render is native-sync via resvg; keep bytes off stdout and write the
|
|
365
|
-
// raster artifact explicitly to the requested output path.
|
|
366
|
-
return renderPngSync(source, { scale, background, style, seed }, outFile, json)
|
|
367
|
-
}
|
|
368
|
-
// Loop 9 M3/M4, #7645: json = layout shape; unicode is the default text
|
|
369
|
-
// renderer; `--security strict` strips external-fetch refs from SVG.
|
|
370
|
-
const out = renderSourceToFormat(source, format, { security, certificates: args.flags.certificates === true, style, seed })
|
|
371
|
-
if (typeof out !== 'string') {
|
|
372
|
-
process.stdout.write(JSON.stringify(out) + '\n')
|
|
373
|
-
return EXIT_OK
|
|
374
|
-
}
|
|
375
|
-
process.stdout.write(json ? JSON.stringify({ [format]: out }) + '\n' : (out.endsWith('\n') ? out : out + '\n'))
|
|
376
|
-
return EXIT_OK
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
interface RenderFormatOptions { security?: 'default' | 'strict'; certificates?: boolean; style?: StyleInput[]; seed?: number }
|
|
380
|
-
|
|
381
|
-
/** The ONE format dispatch behind every render path — single-input,
|
|
382
|
-
* multi-input, and watch differ only in I/O and error envelopes, so they
|
|
383
|
-
* share this core instead of re-enumerating formats (the style/seed
|
|
384
|
-
* threading previously had to touch three copies). JSON parse failures
|
|
385
|
-
* throw the structured envelope; callers decide how to surface it. */
|
|
386
|
-
function renderSourceToFormat(source: string, format: string, opts: RenderFormatOptions = {}): string | object {
|
|
387
|
-
if (format === 'ascii' || format === 'unicode') return renderMermaidASCII(source, { useAscii: format === 'ascii' })
|
|
388
|
-
if (format === 'json') {
|
|
389
|
-
const parsed = parseMermaid(source)
|
|
390
|
-
if (!parsed.ok) throw parseErrorEnvelope(parsed.error)
|
|
391
|
-
return layoutMermaid(parsed.value, { debug: opts.certificates === true })
|
|
392
|
-
}
|
|
393
|
-
return renderMermaidSVG(source, { security: opts.security, style: opts.style, seed: opts.seed })
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* #930: pure re-render step for watch mode — reads the file, renders to the
|
|
398
|
-
* requested format, returns the output string. Extracted so it's unit-testable
|
|
399
|
-
* without fs.watch timing.
|
|
400
|
-
*/
|
|
401
|
-
export function renderFileOnce(file: string, format: string, opts: RenderFormatOptions = {}): string {
|
|
402
|
-
const src = readFileSync(file, 'utf8')
|
|
403
|
-
try {
|
|
404
|
-
const out = renderSourceToFormat(src, format, opts)
|
|
405
|
-
return typeof out === 'string' ? out : JSON.stringify(out)
|
|
406
|
-
} catch (e) {
|
|
407
|
-
// Watch mode wants a printable line, not a throw: the structured JSON
|
|
408
|
-
// parse envelope serializes; real render errors keep propagating.
|
|
409
|
-
if ((e as { ok?: boolean; error?: unknown })?.ok === false) return JSON.stringify(e)
|
|
410
|
-
throw e
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function cmdRenderWatch(file: string, format: string, args: ParsedArgs, _json: boolean, opts: { security?: 'default' | 'strict'; certificates?: boolean; style?: StyleInput[]; seed?: number } = {}): number {
|
|
415
|
-
const outFile = typeof args.flags.output === 'string' ? args.flags.output : ''
|
|
416
|
-
const emit = () => {
|
|
417
|
-
try {
|
|
418
|
-
const out = renderFileOnce(file, format, opts)
|
|
419
|
-
if (outFile) { writeFileSync(outFile, out) ; process.stderr.write(`rendered → ${outFile}\n`) }
|
|
420
|
-
else process.stdout.write(out + (out.endsWith('\n') ? '' : '\n'))
|
|
421
|
-
} catch (e) { process.stderr.write(`render error: ${(e as Error).message}\n`) }
|
|
422
|
-
}
|
|
423
|
-
emit() // initial render
|
|
424
|
-
process.stderr.write(`watching ${file} (Ctrl-C to stop)…\n`)
|
|
425
|
-
watch(file, { persistent: true }, (event) => { if (event === 'change') emit() })
|
|
426
|
-
// Block forever — the watcher keeps the event loop alive.
|
|
427
|
-
return EXIT_OK
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function renderPngSync(source: string, opts: { scale: number; background: string; style?: StyleInput[]; seed?: number }, outFile: string, json: boolean): number {
|
|
431
|
-
try {
|
|
432
|
-
const png = renderMermaidPNG(source, opts)
|
|
433
|
-
writeFileSync(outFile, png)
|
|
434
|
-
if (json) process.stdout.write(JSON.stringify({ ok: true, path: outFile, bytes: png.length }) + '\n')
|
|
435
|
-
return EXIT_OK
|
|
436
|
-
} catch (e) {
|
|
437
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
438
|
-
process.stderr.write(`am render --format png: ${msg}\n`)
|
|
439
|
-
return EXIT_INTERNAL
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function cmdVerify(args: ParsedArgs): number {
|
|
444
|
-
const source = readSourceArg(args.positional[0])
|
|
445
|
-
const suppressRaw = typeof args.flags.suppress === 'string' ? args.flags.suppress : ''
|
|
446
|
-
const suppress = suppressRaw ? (suppressRaw.split(',').map(s => s.trim()).filter(Boolean) as WarningCode[]) : undefined
|
|
447
|
-
const labelCharCap = typeof args.flags['label-cap'] === 'string' ? parseInt(args.flags['label-cap'], 10) : undefined
|
|
448
|
-
const r = verifyMermaid(source, { suppress, labelCharCap })
|
|
449
|
-
process.stdout.write(JSON.stringify(r, replacer) + '\n')
|
|
450
|
-
return r.ok ? EXIT_OK : EXIT_VERIFY_FAILED
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function cmdParse(args: ParsedArgs): number {
|
|
454
|
-
const r = parseMermaid(readSourceArg(args.positional[0]))
|
|
455
|
-
if (!r.ok) {
|
|
456
|
-
// Error envelope matches the documented batch shape (cli/index.ts:107).
|
|
457
|
-
// Success emits the bare ValidDiagram payload — pipeable into `am serialize`
|
|
458
|
-
// without unwrapping, which preserves existing consumer contracts.
|
|
459
|
-
process.stdout.write(JSON.stringify(parseErrorEnvelope(r.error)) + '\n')
|
|
460
|
-
return EXIT_ARG_ERROR
|
|
461
|
-
}
|
|
462
|
-
process.stdout.write(JSON.stringify(toJsonSafe(r.value), replacer) + '\n')
|
|
463
|
-
return EXIT_OK
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function cmdSerialize(): number {
|
|
467
|
-
const stdin = readSourceArg('-')
|
|
468
|
-
let payload: unknown
|
|
469
|
-
try { payload = JSON.parse(stdin) } catch (e) { process.stderr.write(`serialize: invalid JSON: ${(e as Error).message}\n`); return EXIT_ARG_ERROR }
|
|
470
|
-
const r = synthesizeFromGraph(payload as Parameters<typeof synthesizeFromGraph>[0])
|
|
471
|
-
if (!r.ok) { process.stderr.write(`serialize: ${JSON.stringify(r.error)}\n`); return EXIT_ARG_ERROR }
|
|
472
|
-
process.stdout.write(serializeMermaid(r.value))
|
|
473
|
-
return EXIT_OK
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
type CliMutationError = MutationError | { code: 'UNSUPPORTED_FAMILY' | 'VERIFY_FAILED' | 'PARSE_FAILED' | 'INVALID_OP'; message: string; details?: unknown }
|
|
477
|
-
|
|
478
|
-
type MutationRunResult =
|
|
479
|
-
| { ok: true; source: string; verify: ReturnType<typeof verifyMermaid> }
|
|
480
|
-
| { ok: false; error: CliMutationError; verify?: ReturnType<typeof verifyMermaid> }
|
|
481
|
-
|
|
482
|
-
function parseMutationOpsFlag(args: ParsedArgs): Result<AnyMutationOp[], CliMutationError> {
|
|
483
|
-
const opStr = typeof args.flags.op === 'string' ? args.flags.op : ''
|
|
484
|
-
const opsStr = typeof args.flags.ops === 'string' ? args.flags.ops : ''
|
|
485
|
-
if (opStr && opsStr) return { ok: false, error: { code: 'INVALID_OP', message: 'mutate accepts either --op or --ops, not both' } }
|
|
486
|
-
if (!opStr && !opsStr) return { ok: false, error: { code: 'INVALID_OP', message: 'mutate requires --op <JSON> or --ops <JSON array|file.json>' } }
|
|
487
|
-
try {
|
|
488
|
-
if (opStr) return { ok: true, value: [JSON.parse(opStr) as AnyMutationOp] }
|
|
489
|
-
const raw = existsSync(opsStr) ? readFileSync(opsStr, 'utf8') : opsStr
|
|
490
|
-
const parsed = JSON.parse(raw) as unknown
|
|
491
|
-
if (!Array.isArray(parsed)) return { ok: false, error: { code: 'INVALID_OP', message: '--ops must be a JSON array of MutationOps' } }
|
|
492
|
-
if (parsed.length === 0) return { ok: false, error: { code: 'INVALID_OP', message: '--ops must contain at least one MutationOp' } }
|
|
493
|
-
return { ok: true, value: parsed as AnyMutationOp[] }
|
|
494
|
-
} catch (e) {
|
|
495
|
-
return { ok: false, error: { code: 'INVALID_OP', message: `invalid mutation JSON: ${(e as Error).message}` } }
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function mutateAny(d: ValidDiagram, op: AnyMutationOp): Result<MutableValidDiagram, CliMutationError> {
|
|
500
|
-
// mutate() dispatches to the family's registered mutator by diagram kind,
|
|
501
|
-
// so the CLI only rules out what the registry cannot handle: opaque bodies
|
|
502
|
-
// (source-preserved syntax has no structured ops) and families without a
|
|
503
|
-
// mutate hook. The per-family narrowing cascade this replaces re-encoded
|
|
504
|
-
// the family list a 13th time.
|
|
505
|
-
const plugin = getFamily(d.kind)
|
|
506
|
-
if (d.body.kind !== 'opaque' && plugin?.mutate && plugin.serialize) {
|
|
507
|
-
// `--op`/`--ops` arrive as untyped JSON, so the CLI funnels through the same
|
|
508
|
-
// mutateChecked choke point the MCP paths use: shape is validated (a wrong/
|
|
509
|
-
// missing/mistyped field is a prescriptive INVALID_OP) before the mutator.
|
|
510
|
-
return mutateChecked(d as MutableValidDiagram, op)
|
|
511
|
-
}
|
|
512
|
-
return {
|
|
513
|
-
ok: false,
|
|
514
|
-
error: { code: 'UNSUPPORTED_FAMILY', message: `mutate supports ${knownFamilies().join(', ')} diagrams; got ${d.kind}${d.body.kind === 'opaque' ? ' (source-level/opaque body — structured mutation is not exposed for this family or syntax)' : ''}` },
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export function mutateSource(source: string, ops: AnyMutationOp[]): MutationRunResult {
|
|
519
|
-
const r0 = parseMermaid(source)
|
|
520
|
-
if (!r0.ok) {
|
|
521
|
-
const env = parseErrorEnvelope(r0.error)
|
|
522
|
-
return { ok: false, error: { code: 'PARSE_FAILED', message: env.error.message, details: env.error.details } }
|
|
523
|
-
}
|
|
524
|
-
let current: ValidDiagram = r0.value
|
|
525
|
-
for (let index = 0; index < ops.length; index++) {
|
|
526
|
-
const op = ops[index]!
|
|
527
|
-
const next = mutateAny(current, op)
|
|
528
|
-
if (!next.ok) {
|
|
529
|
-
const details = (next.error as { details?: unknown }).details
|
|
530
|
-
return { ok: false, error: { ...next.error, details: { index, op, ...(typeof details === 'object' && details ? details as Record<string, unknown> : {}) } } }
|
|
531
|
-
}
|
|
532
|
-
current = next.value
|
|
533
|
-
}
|
|
534
|
-
const verify = verifyMermaid(current)
|
|
535
|
-
if (!verify.ok) {
|
|
536
|
-
return {
|
|
537
|
-
ok: false,
|
|
538
|
-
error: { code: 'VERIFY_FAILED', message: 'mutated diagram failed verify; source was not emitted', details: verify.warnings },
|
|
539
|
-
verify,
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return { ok: true, source: serializeMermaid(current), verify }
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function cmdMutate(args: ParsedArgs, json: boolean): number {
|
|
546
|
-
const source = readSourceArg(args.positional[0])
|
|
547
|
-
const ops = parseMutationOpsFlag(args)
|
|
548
|
-
if (!ops.ok) { process.stderr.write(`mutate: ${ops.error.message}\n`); return EXIT_ARG_ERROR }
|
|
549
|
-
return emitMutationRun(mutateSource(source, ops.value), json)
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function emitMutationRun(r: MutationRunResult, json: boolean): number {
|
|
553
|
-
if (!r.ok) {
|
|
554
|
-
process.stdout.write(JSON.stringify({ ok: false, error: r.error, verify: r.verify }, replacer) + '\n')
|
|
555
|
-
return r.error.code === 'VERIFY_FAILED' ? EXIT_VERIFY_FAILED : EXIT_ARG_ERROR
|
|
556
|
-
}
|
|
557
|
-
process.stdout.write(json ? JSON.stringify({ ok: true, source: r.source, verify: r.verify }, replacer) + '\n' : r.source)
|
|
558
|
-
return EXIT_OK
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
export function buildPreviewHtml(source: string, opts: { security?: 'default' | 'strict' } = {}): Result<string, { code: string; message: string; details?: unknown }> {
|
|
562
|
-
const parsed = parseMermaid(source)
|
|
563
|
-
if (!parsed.ok) {
|
|
564
|
-
const env = parseErrorEnvelope(parsed.error)
|
|
565
|
-
return { ok: false, error: env.error }
|
|
566
|
-
}
|
|
567
|
-
const svg = renderMermaidSVG(source, { security: opts.security ?? 'strict' })
|
|
568
|
-
const title = parsed.value.meta.frontmatter?.title ?? 'Mermaid preview'
|
|
569
|
-
const escapedTitle = escapeHtml(String(title))
|
|
570
|
-
const escapedSource = escapeHtml(source)
|
|
571
|
-
return { ok: true, value: `<!doctype html>
|
|
572
|
-
<html lang="en">
|
|
573
|
-
<head>
|
|
574
|
-
<meta charset="utf-8" />
|
|
575
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
576
|
-
<title>${escapedTitle}</title>
|
|
577
|
-
<style>
|
|
578
|
-
body { margin: 0; min-height: 100vh; display: grid; grid-template-rows: auto 1fr; background: #f8fafc; color: #0f172a; font-family: system-ui, sans-serif; }
|
|
579
|
-
header { padding: 12px 16px; border-bottom: 1px solid #e2e8f0; background: white; }
|
|
580
|
-
main { padding: 24px; overflow: auto; }
|
|
581
|
-
.canvas { width: max-content; max-width: 100%; margin: 0 auto; background: white; box-shadow: 0 18px 70px rgba(15,23,42,.12); }
|
|
582
|
-
svg { display: block; max-width: 100%; height: auto; }
|
|
583
|
-
details { margin-top: 16px; }
|
|
584
|
-
pre { white-space: pre-wrap; background: #0f172a; color: #e2e8f0; padding: 12px; border-radius: 8px; }
|
|
585
|
-
</style>
|
|
586
|
-
</head>
|
|
587
|
-
<body>
|
|
588
|
-
<header><strong>${escapedTitle}</strong></header>
|
|
589
|
-
<main>
|
|
590
|
-
<div class="canvas">${svg}</div>
|
|
591
|
-
<details><summary>Mermaid source</summary><pre>${escapedSource}</pre></details>
|
|
592
|
-
</main>
|
|
593
|
-
</body>
|
|
594
|
-
</html>` }
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function escapeHtml(s: string): string {
|
|
598
|
-
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
export function openPreviewFile(path: string): { ok: true } | { ok: false; error: string } {
|
|
602
|
-
const override = process.env.AM_OPEN_COMMAND
|
|
603
|
-
const command = override || (process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open')
|
|
604
|
-
const args = override ? [path] : process.platform === 'win32' ? ['/c', 'start', '', path] : [path]
|
|
605
|
-
const r = spawnSync(command, args, { stdio: 'ignore' })
|
|
606
|
-
if (r.error || r.status !== 0) return { ok: false, error: `open command failed: ${command}` }
|
|
607
|
-
return { ok: true }
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function cmdPreview(args: ParsedArgs, json: boolean): number {
|
|
611
|
-
const source = readSourceArg(args.positional[0])
|
|
612
|
-
const security = 'strict' as const
|
|
613
|
-
if (args.flags.security !== undefined && args.flags.security !== 'strict') {
|
|
614
|
-
process.stderr.write('am preview --security accepts only strict\n')
|
|
615
|
-
return EXIT_ARG_ERROR
|
|
616
|
-
}
|
|
617
|
-
const html = buildPreviewHtml(source, { security })
|
|
618
|
-
if (!html.ok) { process.stdout.write(JSON.stringify({ ok: false, error: html.error }) + '\n'); return EXIT_ARG_ERROR }
|
|
619
|
-
let outFile = typeof args.flags.output === 'string' ? args.flags.output : ''
|
|
620
|
-
if (!outFile && args.flags.open) outFile = join(mkdtempSync(join(tmpdir(), 'am-preview-')), 'preview.html')
|
|
621
|
-
if (outFile) {
|
|
622
|
-
const path = resolve(outFile)
|
|
623
|
-
writeFileSync(path, html.value)
|
|
624
|
-
let opened = false
|
|
625
|
-
if (args.flags.open) {
|
|
626
|
-
const openedResult = openPreviewFile(path)
|
|
627
|
-
if (!openedResult.ok) { process.stderr.write(`am preview --open: ${openedResult.error}\n`); return EXIT_INTERNAL }
|
|
628
|
-
opened = true
|
|
629
|
-
}
|
|
630
|
-
if (json) process.stdout.write(JSON.stringify({ ok: true, path, opened, bytes: html.value.length }) + '\n')
|
|
631
|
-
else process.stdout.write(`${path}\n`)
|
|
632
|
-
return EXIT_OK
|
|
633
|
-
}
|
|
634
|
-
process.stdout.write(json ? JSON.stringify({ ok: true, html: html.value }) + '\n' : html.value)
|
|
635
|
-
return EXIT_OK
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function cmdFormat(args: ParsedArgs): number {
|
|
639
|
-
const r = parseMermaid(readSourceArg(args.positional[0]))
|
|
640
|
-
if (!r.ok) { process.stderr.write(`format: parse failed: ${JSON.stringify(r.error)}\n`); return EXIT_ARG_ERROR }
|
|
641
|
-
const wrapper = args.flags['canonical-wrapper'] ? 'canonical' as const : 'verbatim' as const
|
|
642
|
-
process.stdout.write(serializeMermaid(r.value, { wrapper }))
|
|
643
|
-
return EXIT_OK
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function cmdDescribe(args: ParsedArgs, json: boolean): number {
|
|
647
|
-
const source = readSourceArg(args.positional[0])
|
|
648
|
-
const rawFormat = args.flags.format
|
|
649
|
-
const format = rawFormat === 'json' || rawFormat === 'facts' || rawFormat === 'text' || rawFormat === undefined
|
|
650
|
-
? (rawFormat ?? 'text') as 'text' | 'json' | 'facts'
|
|
651
|
-
: undefined
|
|
652
|
-
if (!format) {
|
|
653
|
-
process.stderr.write('am describe --format must be one of: text, json, facts\n')
|
|
654
|
-
return EXIT_ARG_ERROR
|
|
655
|
-
}
|
|
656
|
-
const parsed = parseMermaid(source)
|
|
657
|
-
if (!parsed.ok) {
|
|
658
|
-
const env = parseErrorEnvelope(parsed.error)
|
|
659
|
-
if (json || format === 'json') process.stdout.write(JSON.stringify(env) + '\n')
|
|
660
|
-
else process.stderr.write(`describe: parse failed: ${env.error.message}\n`)
|
|
661
|
-
return EXIT_ARG_ERROR
|
|
662
|
-
}
|
|
663
|
-
const described = describeMermaid(parsed.value, { format })
|
|
664
|
-
if (format === 'json') {
|
|
665
|
-
const tree = JSON.parse(described)
|
|
666
|
-
process.stdout.write(JSON.stringify(json ? { ok: true, tree } : tree) + '\n')
|
|
667
|
-
return EXIT_OK
|
|
668
|
-
}
|
|
669
|
-
if (format === 'facts') {
|
|
670
|
-
const facts = described.split('\n').filter(Boolean)
|
|
671
|
-
process.stdout.write(json ? JSON.stringify({ ok: true, facts }) + '\n' : facts.join('\n') + '\n')
|
|
672
|
-
return EXIT_OK
|
|
673
|
-
}
|
|
674
|
-
process.stdout.write(json ? JSON.stringify({ ok: true, text: described }) + '\n' : described + '\n')
|
|
675
|
-
return EXIT_OK
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// ---- Loop 7 / A3.1: capabilities ------------------------------------------
|
|
679
|
-
|
|
680
|
-
type FamilyEditPolicy = 'structured-when-narrowed' | 'source-level-only'
|
|
681
|
-
|
|
682
|
-
interface FamilyCapability {
|
|
683
|
-
id: string
|
|
684
|
-
hasMutate: boolean
|
|
685
|
-
hasExtractLabels: boolean
|
|
686
|
-
mutationOps: string[]
|
|
687
|
-
/** Full field shapes for each op — { opKind: [{name, required, type}] }, with
|
|
688
|
-
* enum vocabularies spelled out inline in `type`. Lets a model fill an op
|
|
689
|
-
* correctly on the first try instead of guessing field names/values and
|
|
690
|
-
* learning them only from the INVALID_OP error. Absent for source-level-only
|
|
691
|
-
* families (no structured ops). */
|
|
692
|
-
opFields?: Record<string, OpFieldDoc[]>
|
|
693
|
-
editPolicy: FamilyEditPolicy
|
|
694
|
-
/** The `as*` narrower to call before structured mutation (e.g. `asState`). */
|
|
695
|
-
narrower?: string
|
|
696
|
-
/** Mermaid header keyword(s) that open this family (e.g. `stateDiagram-v2`,
|
|
697
|
-
* `architecture-beta`) — so a model authoring from blank opens with the right
|
|
698
|
-
* header instead of inferring it from `example` or defaulting to flowchart. */
|
|
699
|
-
headers?: readonly string[]
|
|
700
|
-
/** Minimal canonical source (header + core syntax); absent for
|
|
701
|
-
* registered non-builtin families that don't declare one. */
|
|
702
|
-
example?: string
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
interface WarningCodeCapability {
|
|
706
|
-
code: WarningCode
|
|
707
|
-
tier: string
|
|
708
|
-
severity: string
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
interface CapabilitiesEnvelope {
|
|
712
|
-
sdkVersion: string
|
|
713
|
-
families: FamilyCapability[]
|
|
714
|
-
warningCodes: WarningCodeCapability[]
|
|
715
|
-
outputFormats: string[]
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// Source of truth now lives in the agent layer (src/agent/mutation-ops.ts) so
|
|
719
|
-
// the mutators can name their valid ops in errors; imported and re-exported here
|
|
720
|
-
// to keep the capabilities envelope and existing `from '../cli/index.ts'`
|
|
721
|
-
// importers working.
|
|
722
|
-
import { MUTATION_OPS_BY_FAMILY } from '../agent/mutation-ops.ts'
|
|
723
|
-
export { MUTATION_OPS_BY_FAMILY }
|
|
724
|
-
import { describeOps, hasOpSchema, type OpFieldDoc } from '../agent/op-schema.ts'
|
|
725
|
-
|
|
726
|
-
type MutableFamilyId = keyof typeof MUTATION_OPS_BY_FAMILY
|
|
727
|
-
|
|
728
|
-
export function buildCapabilities(): CapabilitiesEnvelope {
|
|
729
|
-
const sdkVersion = (() => {
|
|
730
|
-
try {
|
|
731
|
-
const pkg = require('../../package.json') as { version: string }
|
|
732
|
-
return pkg.version
|
|
733
|
-
} catch {
|
|
734
|
-
return 'unknown'
|
|
735
|
-
}
|
|
736
|
-
})()
|
|
737
|
-
const mutableFamilies = new Set(Object.keys(MUTATION_OPS_BY_FAMILY))
|
|
738
|
-
const families: FamilyCapability[] = knownFamilies().map((id) => {
|
|
739
|
-
const p = getFamily(id)!
|
|
740
|
-
const mutationOps = id in MUTATION_OPS_BY_FAMILY ? [...MUTATION_OPS_BY_FAMILY[id as MutableFamilyId]] : []
|
|
741
|
-
const editPolicy: FamilyEditPolicy = mutationOps.length > 0 ? 'structured-when-narrowed' : 'source-level-only'
|
|
742
|
-
return {
|
|
743
|
-
id,
|
|
744
|
-
// Capabilities describe the public agent surface, not whether the
|
|
745
|
-
// implementation currently lives in a FamilyPlugin hook or central
|
|
746
|
-
// dispatch. Every registered family parses, serializes, verifies, and
|
|
747
|
-
// renders — so those constant booleans are omitted (they read as a
|
|
748
|
-
// "probe-me" menu with nothing to branch on). Only what actually varies
|
|
749
|
-
// is emitted: whether structured mutation and label extraction apply.
|
|
750
|
-
hasMutate: mutableFamilies.has(id),
|
|
751
|
-
hasExtractLabels: Boolean(p.extractLabels),
|
|
752
|
-
mutationOps,
|
|
753
|
-
opFields: hasOpSchema(id) ? describeOps(id) : undefined,
|
|
754
|
-
editPolicy,
|
|
755
|
-
narrower: builtinFamilyMetadata(id)?.narrower,
|
|
756
|
-
headers: builtinFamilyMetadata(id)?.headers,
|
|
757
|
-
example: builtinFamilyMetadata(id)?.example,
|
|
758
|
-
}
|
|
759
|
-
})
|
|
760
|
-
const warningCodes: WarningCodeCapability[] = (Object.keys(WARNING_SEVERITY) as WarningCode[]).map(code => ({
|
|
761
|
-
code,
|
|
762
|
-
tier: WARNING_TIER[code],
|
|
763
|
-
severity: WARNING_SEVERITY[code],
|
|
764
|
-
}))
|
|
765
|
-
return { sdkVersion, families, warningCodes, outputFormats: ['svg', 'ascii', 'unicode', 'png', 'json'] }
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/** Resolve the --style flag: comma-separated style names and/or .json spec
|
|
769
|
-
* files forming a stack (merged left → right by resolveStyleStack). JSON
|
|
770
|
-
* specs are validated before use so bad files are arg errors, not throws. */
|
|
771
|
-
function parseStyleFlag(value: string): StyleInput[] {
|
|
772
|
-
return value.split(',').map(entry => entry.trim()).filter(Boolean).map(entry => {
|
|
773
|
-
if (entry.endsWith('.json') || existsSync(entry)) {
|
|
774
|
-
if (!existsSync(entry)) throw new Error(`style spec file not found: ${entry}`)
|
|
775
|
-
const spec = JSON.parse(readFileSync(entry, 'utf8')) as unknown
|
|
776
|
-
const problems = validateStyleSpec(spec)
|
|
777
|
-
if (problems.length > 0) throw new Error(`invalid style spec ${entry}: ${problems.join('; ')}`)
|
|
778
|
-
return spec as StyleSpec
|
|
779
|
-
}
|
|
780
|
-
return entry
|
|
781
|
-
})
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
function cmdStyles(json: boolean): number {
|
|
785
|
-
const rows = knownStyles().map(name => {
|
|
786
|
-
if (name === 'crisp') {
|
|
787
|
-
return { name, kind: 'default' as const, backend: 'default' as const, blurb: 'The byte-identical default renderer (style unset).' }
|
|
788
|
-
}
|
|
789
|
-
const spec = getStyle(name)!
|
|
790
|
-
return {
|
|
791
|
-
name,
|
|
792
|
-
kind: styleKind(spec),
|
|
793
|
-
backend: inferBackend(spec),
|
|
794
|
-
...(spec.intent ? { intent: spec.intent } : {}),
|
|
795
|
-
blurb: spec.blurb ?? '',
|
|
796
|
-
}
|
|
797
|
-
})
|
|
798
|
-
if (json) {
|
|
799
|
-
process.stdout.write(JSON.stringify(rows) + '\n')
|
|
800
|
-
return EXIT_OK
|
|
801
|
-
}
|
|
802
|
-
const nameWidth = Math.max(...rows.map(r => r.name.length))
|
|
803
|
-
for (const r of rows) {
|
|
804
|
-
process.stdout.write(`${r.name.padEnd(nameWidth)} ${r.kind.padEnd(7)} ${r.blurb}\n`)
|
|
805
|
-
}
|
|
806
|
-
process.stdout.write(`\nStack styles with: am render <file> --style <name[,name|file.json...]> [--seed N]\nAuthor your own: docs/style-authoring.md (JSON records; validate with validateStyleSpec).\n`)
|
|
807
|
-
return EXIT_OK
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function cmdCapabilities(): number {
|
|
811
|
-
process.stdout.write(JSON.stringify(buildCapabilities()) + '\n')
|
|
812
|
-
return EXIT_OK
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// ---- Loop 11 / M4 (#6430): llms.txt agent-discovery digest ----------------
|
|
816
|
-
|
|
817
|
-
/**
|
|
818
|
-
* Build an llms.txt digest (https://llmstxt.org) for agent discovery.
|
|
819
|
-
* Derived from buildCapabilities() so the family list, warning codes, and
|
|
820
|
-
* output formats stay in sync with the actual SDK surface.
|
|
821
|
-
*/
|
|
822
|
-
export function buildLlmsTxt(): string {
|
|
823
|
-
const cap = buildCapabilities()
|
|
824
|
-
const families = cap.families.map(f => f.id).join(', ')
|
|
825
|
-
const structured = cap.families.filter(f => f.hasMutate).map(f => f.id)
|
|
826
|
-
const narrowers = BUILTIN_FAMILY_METADATA.map(f => f.narrower).join(', ')
|
|
827
|
-
const formats = cap.outputFormats.join(', ')
|
|
828
|
-
const codes = cap.warningCodes.map(w => `${w.code} (${w.tier}/${w.severity})`).join(', ')
|
|
829
|
-
return `# Agentic Mermaid
|
|
830
|
-
|
|
831
|
-
> Agent-native Mermaid runtime: parse, verify, mutate, and round-trip
|
|
832
|
-
> Mermaid diagrams with a typed IR. Deterministic ASCII / PNG / SVG. No
|
|
833
|
-
> browser required. CLI + MCP + library.
|
|
834
|
-
|
|
835
|
-
Version: ${cap.sdkVersion}
|
|
836
|
-
|
|
837
|
-
## What it does
|
|
838
|
-
|
|
839
|
-
A typed editing surface over Mermaid for AI agents. Parse a diagram to a
|
|
840
|
-
ValidDiagram, mutate it with typed ops, verify structurally (no pixels),
|
|
841
|
-
serialize back to canonical source. Layout is deterministic (verified
|
|
842
|
-
cross-process and same-machine cross-runtime on x86_64/ARM64).
|
|
843
|
-
|
|
844
|
-
## The agent loop
|
|
845
|
-
|
|
846
|
-
New diagrams: author Mermaid source → parse → verify → render/return.
|
|
847
|
-
Existing structured diagrams: parse → narrow → mutate → verify → serialize.
|
|
848
|
-
Run verify at every commit point. Never serialize a diagram whose verify result
|
|
849
|
-
you haven't inspected.
|
|
850
|
-
|
|
851
|
-
## CLI verbs (\`am <verb>\`)
|
|
852
|
-
|
|
853
|
-
- render --format svg, ascii, unicode, json [--security strict] — render a diagram
|
|
854
|
-
- render --format png --output file.png — render one input to PNG (no PNG bytes on stdout, no multi-input/watch)
|
|
855
|
-
- render --style <names|file.json> --seed N — styled svg/png; comma-separate to stack (--style hand-drawn,dracula)
|
|
856
|
-
- styles [--json] — list registered styles (default + full looks + palette-only themes)
|
|
857
|
-
- parse — diagram → ValidDiagram JSON
|
|
858
|
-
- verify — structural validation (exit 3 if invalid)
|
|
859
|
-
- mutate --op '<json>' / --ops '<json array|file>' — apply typed mutation(s), verify, then emit source
|
|
860
|
-
- preview [--output file.html] [--open] — standalone strict-mode HTML preview for user inspection
|
|
861
|
-
- format — normalize / canonicalize source
|
|
862
|
-
- describe [--format text|json|facts] — natural-language, AX-tree, or semantic facts summary
|
|
863
|
-
- capabilities --json — machine-readable capability envelope incl. editPolicy + mutationOps
|
|
864
|
-
- batch --jsonl — bulk render/verify/parse/serialize/mutate ops, one JSON envelope per line
|
|
865
|
-
- render-markdown <file.md> [--ascii] — render fenced mermaid blocks, skip invalid ones
|
|
866
|
-
- llms-txt — this document
|
|
867
|
-
- init-agent [--dir .] [--force] — write AGENTS.md section, root skills/ bundle, and .mcp.json sample
|
|
868
|
-
|
|
869
|
-
Exit codes: 0 ok, 2 arg error, 3 verify-failed, 4 internal.
|
|
870
|
-
|
|
871
|
-
## MCP tools
|
|
872
|
-
|
|
873
|
-
Code Mode \`execute(code)\` (JavaScript in a node:vm sandbox with a typed
|
|
874
|
-
mermaid.* SDK declaration) plus narrow helper tools: render_png and describe.
|
|
875
|
-
render_png is offline.
|
|
876
|
-
|
|
877
|
-
## Output formats
|
|
878
|
-
|
|
879
|
-
Agentic Mermaid outputs SVG, PNG, ASCII, Unicode, and JSON layout. Full capability list: ${formats}. SVG strict mode (security: 'strict') emits zero external-fetch
|
|
880
|
-
references — safe for untrusted/agent-generated diagrams. See SECURITY.md.
|
|
881
|
-
|
|
882
|
-
## Diagram families
|
|
883
|
-
|
|
884
|
-
All families parse, verify, render, round-trip: ${families}.
|
|
885
|
-
Structured mutation (${cap.families.find(f => f.hasMutate)?.editPolicy}): ${structured.join(', ')}.
|
|
886
|
-
Narrowers: ${narrowers}.
|
|
887
|
-
State diagrams own a dedicated body (BUILD-19): narrow with asState; state-shaped ops apply (asFlowchart returns null on them).
|
|
888
|
-
Source-level-only: ${cap.families.filter(f => !f.hasMutate).map(f => f.id).join(', ') || 'none — every renderable family ships structured mutation'}.
|
|
889
|
-
Opaque-fallback bodies (unmodeled syntax) round-trip losslessly via preserved source (never silently dropped) and stay source-level only.
|
|
890
|
-
|
|
891
|
-
## Warning codes
|
|
892
|
-
|
|
893
|
-
${codes}
|
|
894
|
-
|
|
895
|
-
## Library
|
|
896
|
-
|
|
897
|
-
\`import { parseMermaid, mutate, verifyMermaid, analyzeMermaid,
|
|
898
|
-
analyzeMermaidSource, serializeMermaid, renderMermaidASCII,
|
|
899
|
-
renderMermaidPNG, renderMermaidSVG, renderMermaidASCIIWithMeta,
|
|
900
|
-
describeMermaid, asciiToMermaid, verifyNoExternalRefs,
|
|
901
|
-
registerStyle, knownStyles, validateStyleSpec } from 'agentic-mermaid/agent'\`
|
|
902
|
-
|
|
903
|
-
## Styles
|
|
904
|
-
|
|
905
|
-
Every render call accepts style: a name ('hand-drawn', 'excalidraw',
|
|
906
|
-
'pen-and-ink', 'freehand', 'watercolor', 'blueprint', 'tufte', or any theme
|
|
907
|
-
name — a theme is a palette-only style), an inline JSON spec, or a stack
|
|
908
|
-
merged left → right ({ style: ['hand-drawn', 'dracula'] }). seed re-rolls
|
|
909
|
-
ink wobble, never layout — (source, style, seed) reproduces an image
|
|
910
|
-
exactly. Custom styles are data: validateStyleSpec(json) checks untrusted
|
|
911
|
-
records, registerStyle({ name, … }) makes them addressable by name.
|
|
912
|
-
Authoring guide + quality rubric: docs/style-authoring.md.
|
|
913
|
-
|
|
914
|
-
## Docs
|
|
915
|
-
|
|
916
|
-
- docs/README.md — documentation index and structure
|
|
917
|
-
- Instructions_for_agents.md — canonical agent-use guide
|
|
918
|
-
- AGENT_NATIVE.md — architecture/spec rationale
|
|
919
|
-
- docs/features.md — current capability inventory
|
|
920
|
-
- docs/api.md — library/CLI/MCP API reference, including SVG/PNG/ASCII output
|
|
921
|
-
- docs/diagram-families.md — family examples and edit policy
|
|
922
|
-
- docs/theming.md — themes, CSS variables, and Shiki compatibility
|
|
923
|
-
- docs/style-authoring.md — styles: the composable look system (a theme is a palette-only style; stack names/specs; author custom styles as data records with the quality rubric)
|
|
924
|
-
- docs/quality.md — determinism + "good looking" rubric
|
|
925
|
-
- TODO.md — only active backlog
|
|
926
|
-
- SECURITY.md — threat model + strict-mode guarantee
|
|
927
|
-
- docs/agent-mutation-policy.md — structured-vs-source-level policy
|
|
928
|
-
- docs/agent-api-cookbook.md — copy-pasteable library/CLI/MCP recipes
|
|
929
|
-
- docs/mcp-code-mode-rationale.md — MCP surface rationale
|
|
930
|
-
- docs/agent-workflow-examples.md — runnable MCP/CLI + agent-improvement examples
|
|
931
|
-
- skills/ — agent-agnostic SKILL.md bundles for diagram workflow and live-editor development
|
|
932
|
-
- skill-evals/ — skill-eval-harness manifest, fixtures, and benchmark instructions
|
|
933
|
-
`
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
function cmdLlmsTxt(): number {
|
|
937
|
-
process.stdout.write(buildLlmsTxt())
|
|
938
|
-
return EXIT_OK
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
function cmdInitAgent(args: ParsedArgs, json: boolean): number {
|
|
942
|
-
const dir = typeof args.flags.dir === 'string' ? resolve(args.flags.dir) : process.cwd()
|
|
943
|
-
const force = Boolean(args.flags.force)
|
|
944
|
-
const result = initAgentFiles({ dir, force })
|
|
945
|
-
if (json) {
|
|
946
|
-
process.stdout.write(JSON.stringify({ ok: true, ...result }) + '\n')
|
|
947
|
-
return EXIT_OK
|
|
948
|
-
}
|
|
949
|
-
const rel = (p: string) => p.startsWith(dir) ? '.' + p.slice(dir.length) : p
|
|
950
|
-
for (const p of result.written) process.stdout.write(` created ${rel(p)}\n`)
|
|
951
|
-
for (const p of result.appended) process.stdout.write(` updated ${rel(p)}\n`)
|
|
952
|
-
for (const p of result.skipped) process.stdout.write(` skipped ${rel(p)} (exists; use --force if applicable)\n`)
|
|
953
|
-
process.stdout.write('\nNext: read AGENTS.md, connect the MCP server in .mcp.json, or run `am --agent-instructions`.\n')
|
|
954
|
-
return EXIT_OK
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// ---- Loop 12 M5 (#543): render-markdown — convert fenced blocks ------------
|
|
958
|
-
|
|
959
|
-
export interface MarkdownBlockResult {
|
|
960
|
-
index: number
|
|
961
|
-
ok: boolean
|
|
962
|
-
format?: string
|
|
963
|
-
output?: string
|
|
964
|
-
error?: { code: string; message: string }
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Extract ```mermaid fenced blocks from markdown and render each. A bad
|
|
969
|
-
* diagram yields an { ok:false } entry and does NOT abort the rest (#543:
|
|
970
|
-
* "skip invalid diagrams instead of failing the whole conversion").
|
|
971
|
-
*/
|
|
972
|
-
export function renderMarkdownBlocks(md: string, format: 'svg' | 'ascii' = 'svg'): MarkdownBlockResult[] {
|
|
973
|
-
const FENCE = /```mermaid[ \t]*\r?\n([\s\S]*?)\r?\n```/g
|
|
974
|
-
const blocks: string[] = []
|
|
975
|
-
for (const m of md.matchAll(FENCE)) blocks.push(m[1]!)
|
|
976
|
-
return collectBatched(blocks, (src, i): MarkdownBlockResult => {
|
|
977
|
-
try {
|
|
978
|
-
const output = format === 'ascii' ? renderMermaidASCII(src, { useAscii: true }) : renderMermaidSVG(src)
|
|
979
|
-
return { index: i, ok: true, format, output }
|
|
980
|
-
} catch (e) {
|
|
981
|
-
return { index: i, ok: false, error: { code: 'RENDER_FAILED', message: (e as Error).message } }
|
|
982
|
-
}
|
|
983
|
-
}, 'MARKDOWN_BLOCK_ERROR').map((r, i) => r.ok ? r.value : { index: i, ok: false, error: r.error })
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
function cmdRenderMarkdown(args: ParsedArgs): number {
|
|
987
|
-
const md = readSourceArg(args.positional[0])
|
|
988
|
-
const format = args.flags.ascii ? 'ascii' as const : 'svg' as const
|
|
989
|
-
const results = renderMarkdownBlocks(md, format)
|
|
990
|
-
process.stdout.write(JSON.stringify({ ok: true, blocks: results }) + '\n')
|
|
991
|
-
// Per-block failures don't fail the command (#543) — exit OK.
|
|
992
|
-
return EXIT_OK
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// ---- Loop 7 / A3.2: batch -------------------------------------------------
|
|
996
|
-
|
|
997
|
-
interface BatchLine {
|
|
998
|
-
op: 'render' | 'verify' | 'parse' | 'serialize' | 'mutate'
|
|
999
|
-
source: string
|
|
1000
|
-
options?: Record<string, unknown>
|
|
1001
|
-
mutation?: AnyMutationOp
|
|
1002
|
-
mutations?: AnyMutationOp[]
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
interface BatchOutput {
|
|
1006
|
-
ok: boolean
|
|
1007
|
-
op?: string
|
|
1008
|
-
data?: unknown
|
|
1009
|
-
error?: { code: string; message: string }
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* Execute a single batch line. Exported for tests; the CLI just shuttles
|
|
1014
|
-
* lines into and out of this function.
|
|
1015
|
-
*/
|
|
1016
|
-
export function runBatchLine(rawLine: string, lineIndex = 0): BatchOutput {
|
|
1017
|
-
let parsed: BatchLine
|
|
1018
|
-
try {
|
|
1019
|
-
parsed = JSON.parse(rawLine) as BatchLine
|
|
1020
|
-
} catch (e) {
|
|
1021
|
-
return { ok: false, error: { code: 'INVALID_JSON', message: (e as Error).message } }
|
|
1022
|
-
}
|
|
1023
|
-
if (!parsed || typeof parsed !== 'object' || typeof parsed.op !== 'string') {
|
|
1024
|
-
return { ok: false, error: { code: 'INVALID_PAYLOAD', message: 'missing op string' } }
|
|
1025
|
-
}
|
|
1026
|
-
const op = parsed.op
|
|
1027
|
-
try {
|
|
1028
|
-
switch (op) {
|
|
1029
|
-
case 'render': {
|
|
1030
|
-
const options = parsed.options ?? {}
|
|
1031
|
-
const asAscii = Boolean((options as { ascii?: boolean }).ascii)
|
|
1032
|
-
// #7540: auto-namespace SVG ids per batch line so the rendered
|
|
1033
|
-
// diagrams can coexist on one HTML page without def-id collisions.
|
|
1034
|
-
const out = asAscii
|
|
1035
|
-
? renderMermaidASCII(parsed.source)
|
|
1036
|
-
: renderMermaidSVG(parsed.source, { idPrefix: `d${lineIndex}-` })
|
|
1037
|
-
return { ok: true, op, data: asAscii ? { ascii: out } : { svg: out } }
|
|
1038
|
-
}
|
|
1039
|
-
case 'verify': {
|
|
1040
|
-
const options = parsed.options as { suppress?: WarningCode[]; labelCharCap?: number } | undefined
|
|
1041
|
-
const r = verifyMermaid(parsed.source, options ?? {})
|
|
1042
|
-
return { ok: true, op, data: JSON.parse(JSON.stringify(r, replacer)) }
|
|
1043
|
-
}
|
|
1044
|
-
case 'parse': {
|
|
1045
|
-
const r = parseMermaid(parsed.source)
|
|
1046
|
-
if (!r.ok) { const env = parseErrorEnvelope(r.error); return { ok: false, op, error: env.error } }
|
|
1047
|
-
return { ok: true, op, data: JSON.parse(JSON.stringify(toJsonSafe(r.value), replacer)) }
|
|
1048
|
-
}
|
|
1049
|
-
case 'serialize': {
|
|
1050
|
-
// For batch, the line's `source` field is a JSON-stringified ValidDiagram
|
|
1051
|
-
// (same shape as `am parse` emits). We re-hydrate it via synthesizeFromGraph.
|
|
1052
|
-
let payload: unknown
|
|
1053
|
-
try {
|
|
1054
|
-
payload = JSON.parse(parsed.source)
|
|
1055
|
-
} catch (e) {
|
|
1056
|
-
return { ok: false, op, error: { code: 'INVALID_JSON', message: (e as Error).message } }
|
|
1057
|
-
}
|
|
1058
|
-
const r = synthesizeFromGraph(payload as Parameters<typeof synthesizeFromGraph>[0])
|
|
1059
|
-
if (!r.ok) return { ok: false, op, error: { code: 'SYNTHESIZE_FAILED', message: JSON.stringify(r.error) } }
|
|
1060
|
-
return { ok: true, op, data: { source: serializeMermaid(r.value) } }
|
|
1061
|
-
}
|
|
1062
|
-
case 'mutate': {
|
|
1063
|
-
const ops = Array.isArray(parsed.mutations)
|
|
1064
|
-
? parsed.mutations
|
|
1065
|
-
: parsed.mutation
|
|
1066
|
-
? [parsed.mutation]
|
|
1067
|
-
: []
|
|
1068
|
-
if (ops.length === 0) return { ok: false, op, error: { code: 'INVALID_OP', message: 'mutate batch line requires mutation or mutations[]' } }
|
|
1069
|
-
const r = mutateSource(parsed.source, ops)
|
|
1070
|
-
if (!r.ok) return { ok: false, op, error: { code: r.error.code, message: r.error.message } }
|
|
1071
|
-
return { ok: true, op, data: { source: r.source, verify: JSON.parse(JSON.stringify(r.verify, replacer)) } }
|
|
1072
|
-
}
|
|
1073
|
-
default:
|
|
1074
|
-
return { ok: false, op, error: { code: 'UNKNOWN_OP', message: `unknown op: ${op}` } }
|
|
1075
|
-
}
|
|
1076
|
-
} catch (e) {
|
|
1077
|
-
return { ok: false, op, error: { code: 'INTERNAL', message: (e as Error).message } }
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
function cmdBatch(): number {
|
|
1082
|
-
const stdin = readSourceArg('-')
|
|
1083
|
-
const lines = stdin.split('\n').filter(l => l.trim() !== '')
|
|
1084
|
-
// Loop 9 M8: shared per-item iteration via collectBatched. Same scaffold
|
|
1085
|
-
// as runWithJudge — see src/shared/batched.ts.
|
|
1086
|
-
const results = collectBatched(lines, (line, i) => runBatchLine(line, i), 'BATCH_HANDLER_ERROR')
|
|
1087
|
-
for (const r of results) {
|
|
1088
|
-
if (r.ok) process.stdout.write(JSON.stringify(r.value) + '\n')
|
|
1089
|
-
else process.stdout.write(JSON.stringify({ ok: false, error: r.error }) + '\n')
|
|
1090
|
-
}
|
|
1091
|
-
// Per-line errors don't abort the batch; the process always exits OK.
|
|
1092
|
-
return EXIT_OK
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
function toJsonSafe(d: ValidDiagram): unknown {
|
|
1096
|
-
if (d.body.kind === 'flowchart') {
|
|
1097
|
-
const g = d.body.graph
|
|
1098
|
-
return {
|
|
1099
|
-
kind: d.kind, meta: d.meta,
|
|
1100
|
-
body: { kind: 'flowchart', graph: {
|
|
1101
|
-
direction: g.direction,
|
|
1102
|
-
nodes: Object.fromEntries(g.nodes),
|
|
1103
|
-
edges: g.edges,
|
|
1104
|
-
subgraphs: g.subgraphs,
|
|
1105
|
-
// Preserve styling so `am parse | am serialize` is lossless.
|
|
1106
|
-
classDefs: Object.fromEntries(g.classDefs),
|
|
1107
|
-
classAssignments: Object.fromEntries(g.classAssignments),
|
|
1108
|
-
nodeStyles: Object.fromEntries(g.nodeStyles),
|
|
1109
|
-
linkStyles: Object.fromEntries([...g.linkStyles].map(([k, v]) => [String(k), v])),
|
|
1110
|
-
} },
|
|
1111
|
-
canonicalSource: d.canonicalSource,
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
return { kind: d.kind, meta: d.meta, body: d.body, canonicalSource: d.canonicalSource }
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
if (import.meta.main) process.exit(runCli(process.argv.slice(2)))
|