@canonical/pragma-cli 0.18.0 → 0.21.0
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/LICENSE +166 -0
- package/dist/pragma +0 -0
- package/package.json +31 -18
- package/src/constants.ts +1 -1
- package/src/domains/block/blockConfig.ts +47 -3
- package/src/domains/block/commands/list.test.ts +1 -1
- package/src/domains/block/commands/list.ts +27 -0
- package/src/domains/block/commands/lookup.ts +26 -52
- package/src/domains/block/formatters/index.ts +1 -1
- package/src/domains/block/formatters/list.test.ts +1 -1
- package/src/domains/block/formatters/list.ts +28 -42
- package/src/domains/block/formatters/lookup.test.ts +3 -2
- package/src/domains/block/formatters/lookup.ts +91 -296
- package/src/domains/block/formatters/types.ts +1 -1
- package/src/domains/block/operations/list.test.ts +1 -1
- package/src/domains/block/operations/list.ts +1 -1
- package/src/domains/block/operations/lookup.test.ts +29 -8
- package/src/domains/block/operations/lookup.ts +94 -16
- package/src/domains/block/orchestration/blockEmptyError.ts +1 -1
- package/src/domains/block/orchestration/buildBlockFilters.ts +1 -1
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -2
- package/src/domains/block/orchestration/resolveBlockList.ts +1 -1
- package/src/domains/block/orchestration/resolveBlockLookup.ts +8 -2
- package/src/domains/block/types.ts +1 -1
- package/src/domains/config/commands/channel.ts +6 -4
- package/src/domains/config/commands/tier.ts +6 -4
- package/src/domains/config/mcp/specs.ts +18 -6
- package/src/domains/config/operations/validateTier.ts +1 -1
- package/src/domains/config/tasks/index.ts +3 -0
- package/src/domains/config/tasks/setChannel.ts +30 -0
- package/src/domains/config/tasks/setTier.ts +33 -0
- package/src/domains/create/commands/package.test.ts +2 -2
- package/src/domains/doctor/mcp/index.ts +1 -1
- package/src/domains/doctor/mcp/specs.ts +2 -49
- package/src/domains/graph/commands/inspect.test.ts +1 -1
- package/src/domains/graph/formatters/inspect.ts +12 -7
- package/src/domains/graph/operations/inspectUri.ts +4 -1
- package/src/domains/info/commands/upgrade.ts +6 -3
- package/src/domains/info/index.ts +3 -1
- package/src/domains/info/mcp/index.ts +2 -0
- package/src/domains/info/mcp/specs.ts +65 -0
- package/src/domains/llm/data/commandReference.ts +4 -4
- package/src/domains/llm/data/decisionTrees.ts +14 -12
- package/src/domains/llm/operations/collectContext.ts +1 -1
- package/src/domains/modifier/commands/list.ts +9 -0
- package/src/domains/modifier/commands/lookup.ts +22 -42
- package/src/domains/modifier/formatters/index.ts +4 -1
- package/src/domains/modifier/formatters/list.test.ts +1 -1
- package/src/domains/modifier/formatters/list.ts +13 -26
- package/src/domains/modifier/formatters/lookup.test.ts +1 -1
- package/src/domains/modifier/formatters/lookup.ts +41 -29
- package/src/domains/modifier/index.ts +1 -0
- package/src/domains/modifier/modifierConfig.ts +36 -0
- package/src/domains/modifier/operations/list.ts +1 -1
- package/src/domains/modifier/operations/lookup.ts +4 -3
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +1 -1
- package/src/domains/modifier/types.ts +1 -1
- package/src/domains/ontology/commands/list.test.ts +1 -1
- package/src/domains/ontology/commands/list.ts +18 -0
- package/src/domains/ontology/commands/show.test.ts +1 -1
- package/src/domains/ontology/formatters/list.ts +5 -3
- package/src/domains/ontology/formatters/show.ts +38 -13
- package/src/domains/ontology/helpers/queryClasses.ts +1 -1
- package/src/domains/ontology/helpers/queryProperties.ts +1 -1
- package/src/domains/ontology/operations/listOntologies.ts +1 -1
- package/src/domains/ontology/operations/showOntology.ts +1 -1
- package/src/domains/setup/helpers/completionScripts.test.ts +3 -3
- package/src/domains/setup/helpers/completionScripts.ts +4 -4
- package/src/domains/shared/ToolSpec.ts +1 -1
- package/src/domains/shared/context.ts +3 -2
- package/src/domains/shared/contracts.ts +40 -1
- package/src/domains/shared/filters/buildFilterConfig.ts +1 -2
- package/src/domains/shared/filters/buildFilters.ts +1 -1
- package/src/domains/shared/filters/describeFilters.ts +1 -1
- package/src/domains/shared/formatters.ts +47 -0
- package/src/domains/shared/renderers.ts +410 -0
- package/src/domains/shared/runtime.ts +7 -22
- package/src/domains/shared/types/block.ts +107 -0
- package/src/domains/shared/types/common.ts +33 -0
- package/src/domains/shared/types/graph.ts +17 -0
- package/src/domains/shared/types/index.ts +49 -0
- package/src/domains/shared/types/modifier.ts +22 -0
- package/src/domains/shared/types/ontology.ts +50 -0
- package/src/domains/shared/types/runtime.ts +21 -0
- package/src/domains/shared/types/standard.ts +64 -0
- package/src/domains/shared/types/tier.ts +15 -0
- package/src/domains/shared/types/token.ts +30 -0
- package/src/domains/standard/commands/list.ts +9 -0
- package/src/domains/standard/commands/lookup.ts +22 -50
- package/src/domains/standard/formatters/categories.test.ts +1 -1
- package/src/domains/standard/formatters/categories.ts +1 -1
- package/src/domains/standard/formatters/index.ts +4 -1
- package/src/domains/standard/formatters/list.test.ts +4 -1
- package/src/domains/standard/formatters/list.ts +112 -84
- package/src/domains/standard/formatters/lookup.test.ts +1 -1
- package/src/domains/standard/formatters/lookup.ts +85 -70
- package/src/domains/standard/formatters/types.ts +1 -1
- package/src/domains/standard/index.ts +1 -0
- package/src/domains/standard/operations/categories.ts +1 -1
- package/src/domains/standard/operations/list.ts +1 -1
- package/src/domains/standard/operations/lookup.ts +1 -1
- package/src/domains/standard/orchestration/buildStandardFilters.ts +1 -1
- package/src/domains/standard/orchestration/resolveStandardList.ts +4 -1
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +1 -1
- package/src/domains/standard/orchestration/standardEmptyError.ts +1 -1
- package/src/domains/standard/standardConfig.ts +42 -0
- package/src/domains/standard/types.ts +1 -1
- package/src/domains/tier/commands/list.ts +16 -0
- package/src/domains/tier/formatters/list.test.ts +1 -1
- package/src/domains/tier/formatters/list.ts +11 -6
- package/src/domains/tier/operations/list.ts +1 -1
- package/src/domains/token/commands/list.ts +9 -0
- package/src/domains/token/commands/lookup.ts +22 -48
- package/src/domains/token/formatters/index.ts +4 -1
- package/src/domains/token/formatters/list.test.ts +1 -1
- package/src/domains/token/formatters/list.ts +15 -28
- package/src/domains/token/formatters/lookup.test.ts +2 -2
- package/src/domains/token/formatters/lookup.ts +80 -45
- package/src/domains/token/index.ts +1 -0
- package/src/domains/token/operations/list.ts +1 -1
- package/src/domains/token/operations/lookup.ts +1 -1
- package/src/domains/token/orchestration/resolveTokenLookup.ts +1 -1
- package/src/domains/token/tokenConfig.ts +38 -0
- package/src/domains/token/types.ts +1 -1
- package/src/index.ts +1 -1
- package/src/mcp/runMcpServer.test.ts +2 -2
- package/src/mcp/tools/index.ts +2 -0
- package/src/mcp/tools/registerTools.test.ts +8 -3
- package/src/package-manager/types.ts +1 -3
- package/src/pipeline/createProgram.ts +20 -1
- package/src/pipeline/index.ts +4 -1
- package/src/pipeline/parseGlobalFlags.test.ts +92 -0
- package/src/pipeline/parseGlobalFlags.ts +25 -0
- package/src/pipeline/runCli.orchestration.test.ts +1 -0
- package/src/pipeline/runCli.ts +11 -4
- package/src/pipeline/runInteractiveCommand.ts +198 -0
- package/src/testing/cli.ts +2 -2
- package/src/testing/fixtures.ts +2 -2
- package/src/testing/integration/boot.test.ts +2 -2
- package/src/testing/integration/cli-user-stories.test.ts +198 -0
- package/src/testing/integration/edge-cases.test.ts +263 -0
- package/src/testing/integration/parity.test.ts +377 -27
- package/src/testing/store.ts +1 -1
- package/src/tui/components/BulletList/BulletList.tsx +46 -0
- package/src/tui/components/BulletList/index.ts +2 -0
- package/src/tui/components/BulletList/types.ts +6 -0
- package/src/tui/components/Card/Card.tsx +73 -0
- package/src/tui/components/Card/index.ts +1 -0
- package/src/tui/components/Card/types.ts +12 -0
- package/src/tui/components/CodeBlock/CodeBlock.tsx +37 -0
- package/src/tui/components/CodeBlock/index.ts +2 -0
- package/src/tui/components/CodeBlock/types.ts +6 -0
- package/src/tui/components/DataRow/DataRow.tsx +80 -0
- package/src/tui/components/DataRow/index.ts +2 -0
- package/src/tui/components/DataRow/types.ts +14 -0
- package/src/tui/components/FieldRow/FieldRow.tsx +24 -0
- package/src/tui/components/FieldRow/index.ts +2 -0
- package/src/tui/components/FieldRow/types.ts +10 -0
- package/src/tui/components/HeaderRow/HeaderRow.tsx +32 -0
- package/src/tui/components/HeaderRow/index.ts +2 -0
- package/src/tui/components/HeaderRow/types.ts +8 -0
- package/src/tui/components/MarkdownText/MarkdownText.tsx +19 -0
- package/src/tui/components/MarkdownText/index.ts +2 -0
- package/src/tui/components/MarkdownText/types.ts +4 -0
- package/src/tui/components/MiniTable/MiniTable.tsx +82 -0
- package/src/tui/components/MiniTable/index.ts +2 -0
- package/src/tui/components/MiniTable/types.ts +6 -0
- package/src/tui/components/SectionRenderer/SectionRenderer.tsx +91 -0
- package/src/tui/components/SectionRenderer/index.ts +2 -0
- package/src/tui/components/SectionRenderer/types.ts +15 -0
- package/src/tui/components/TreeView/TreeView.tsx +84 -0
- package/src/tui/components/TreeView/index.ts +2 -0
- package/src/tui/components/TreeView/types.ts +6 -0
- package/src/tui/components/index.ts +19 -0
- package/src/tui/constants.ts +39 -0
- package/src/tui/createListView.ts +23 -0
- package/src/tui/createLookupView.ts +26 -0
- package/src/tui/helpers/computeWidths.ts +53 -0
- package/src/tui/helpers/fitColumns.ts +33 -0
- package/src/tui/helpers/formatMarkdown.ts +19 -0
- package/src/tui/helpers/getTerminalSize.ts +16 -0
- package/src/tui/helpers/index.ts +6 -0
- package/src/tui/helpers/syntaxColor.ts +67 -0
- package/src/tui/helpers/truncateText.ts +12 -0
- package/src/tui/hooks/index.ts +3 -0
- package/src/tui/hooks/types.ts +7 -0
- package/src/tui/hooks/useAutoExit.ts +17 -0
- package/src/tui/hooks/useTerminalSize.ts +19 -0
- package/src/tui/index.ts +15 -0
- package/src/tui/renderInk.tsx +18 -0
- package/src/tui/types.ts +12 -0
- package/src/tui/views/ListView/ListView.tsx +104 -0
- package/src/tui/views/ListView/index.ts +2 -0
- package/src/tui/views/ListView/types.ts +14 -0
- package/src/tui/views/LookupView/LookupView.tsx +244 -0
- package/src/tui/views/LookupView/index.ts +2 -0
- package/src/tui/views/LookupView/types.ts +16 -0
- package/src/tui/views/index.ts +4 -0
- package/src/domains/shared/types.test-d.ts +0 -127
- package/src/domains/shared/types.ts +0 -348
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Text } from "ink";
|
|
2
|
+
import compactUri from "../../../domains/shared/compactUri.js";
|
|
3
|
+
import { PREFIX_MAP } from "../../../domains/shared/prefixes.js";
|
|
4
|
+
import { COL_GAP, DOMAIN_COLORS } from "../../constants.js";
|
|
5
|
+
import { truncateText } from "../../helpers/index.js";
|
|
6
|
+
import type { DataRowProps } from "./types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A single data row in the list view table.
|
|
10
|
+
*
|
|
11
|
+
* Formats each column value, truncates to fit the computed width,
|
|
12
|
+
* and applies domain-aware coloring: IRI columns render dim,
|
|
13
|
+
* name columns render in the domain's instance color.
|
|
14
|
+
*/
|
|
15
|
+
export default function DataRow<T>({
|
|
16
|
+
item,
|
|
17
|
+
columns,
|
|
18
|
+
widths,
|
|
19
|
+
prefixes,
|
|
20
|
+
domain,
|
|
21
|
+
}: DataRowProps<T>) {
|
|
22
|
+
const prefixMap = prefixes ?? PREFIX_MAP;
|
|
23
|
+
const gap = " ".repeat(COL_GAP);
|
|
24
|
+
const colors = DOMAIN_COLORS[domain];
|
|
25
|
+
|
|
26
|
+
const cells = columns.map((col, i) => {
|
|
27
|
+
const w = widths[i] ?? 20;
|
|
28
|
+
const raw = item[col.key];
|
|
29
|
+
const formatted = formatCellValue(raw, col, prefixMap);
|
|
30
|
+
const truncated = truncateText(formatted, w).padEnd(w);
|
|
31
|
+
|
|
32
|
+
if (col.key === "uri") {
|
|
33
|
+
return (
|
|
34
|
+
<Text key={col.key} dimColor>
|
|
35
|
+
{truncated}
|
|
36
|
+
</Text>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (col.key === "name" && colors) {
|
|
40
|
+
return (
|
|
41
|
+
<Text key={col.key} color={colors.instanceFg}>
|
|
42
|
+
{truncated}
|
|
43
|
+
</Text>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return <Text key={col.key}>{truncated}</Text>;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Text>
|
|
51
|
+
{cells.map((cell, i) => (
|
|
52
|
+
<Text key={columns[i]?.key ?? i}>
|
|
53
|
+
{i > 0 ? gap : ""}
|
|
54
|
+
{cell}
|
|
55
|
+
</Text>
|
|
56
|
+
))}
|
|
57
|
+
</Text>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatCellValue(
|
|
62
|
+
raw: unknown,
|
|
63
|
+
col: { format?: (value: unknown) => string },
|
|
64
|
+
prefixes: Readonly<Record<string, string>>,
|
|
65
|
+
): string {
|
|
66
|
+
if (col.format) return col.format(raw);
|
|
67
|
+
if (raw === null || raw === undefined) return "";
|
|
68
|
+
if (typeof raw === "string" && looksLikeUri(raw))
|
|
69
|
+
return compactUri(raw, prefixes);
|
|
70
|
+
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
71
|
+
return String(raw);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function looksLikeUri(value: string): boolean {
|
|
75
|
+
return (
|
|
76
|
+
value.startsWith("http://") ||
|
|
77
|
+
value.startsWith("https://") ||
|
|
78
|
+
/^[a-z][a-z0-9+.-]*:/i.test(value)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ColumnDef } from "../../../domains/shared/contracts.js";
|
|
2
|
+
|
|
3
|
+
export interface DataRowProps<T> {
|
|
4
|
+
/** The data item for this row. */
|
|
5
|
+
readonly item: T;
|
|
6
|
+
/** Visible columns to render. */
|
|
7
|
+
readonly columns: readonly ColumnDef<T>[];
|
|
8
|
+
/** Computed widths for each column. */
|
|
9
|
+
readonly widths: readonly number[];
|
|
10
|
+
/** URI prefix map for compacting IRIs. */
|
|
11
|
+
readonly prefixes?: Readonly<Record<string, string>>;
|
|
12
|
+
/** Domain name — indexes into DOMAIN_COLORS for coloring. */
|
|
13
|
+
readonly domain: string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { FieldRowProps } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A label-value pair rendered as a single row in a lookup card.
|
|
6
|
+
*
|
|
7
|
+
* The label is bold and padded to a fixed width so that values
|
|
8
|
+
* align vertically across multiple field rows.
|
|
9
|
+
*/
|
|
10
|
+
export default function FieldRow({
|
|
11
|
+
label,
|
|
12
|
+
value,
|
|
13
|
+
labelWidth,
|
|
14
|
+
valueColor,
|
|
15
|
+
}: FieldRowProps) {
|
|
16
|
+
const paddedLabel = label.padEnd(labelWidth);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Box>
|
|
20
|
+
<Text bold>{` ${paddedLabel}`}</Text>
|
|
21
|
+
<Text color={valueColor}>{` ${value}`}</Text>
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface FieldRowProps {
|
|
2
|
+
/** Field label (rendered bold, fixed-width). */
|
|
3
|
+
readonly label: string;
|
|
4
|
+
/** Field value (rendered after the label). */
|
|
5
|
+
readonly value: string;
|
|
6
|
+
/** Label column width for alignment across rows. */
|
|
7
|
+
readonly labelWidth: number;
|
|
8
|
+
/** Optional chalk color name for the value text. */
|
|
9
|
+
readonly valueColor?: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { COL_GAP } from "../../constants.js";
|
|
3
|
+
import { truncateText } from "../../helpers/index.js";
|
|
4
|
+
import type { HeaderRowProps } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Column header labels with a separator line below.
|
|
8
|
+
*
|
|
9
|
+
* Renders each column label in bold, padded to its computed width,
|
|
10
|
+
* followed by a horizontal rule of `─` characters.
|
|
11
|
+
*/
|
|
12
|
+
export default function HeaderRow<T>({ columns, widths }: HeaderRowProps<T>) {
|
|
13
|
+
const gap = " ".repeat(COL_GAP);
|
|
14
|
+
|
|
15
|
+
const labels = columns
|
|
16
|
+
.map((col, i) => {
|
|
17
|
+
const w = widths[i] ?? col.label.length;
|
|
18
|
+
return truncateText(col.label, w).padEnd(w);
|
|
19
|
+
})
|
|
20
|
+
.join(gap);
|
|
21
|
+
|
|
22
|
+
const totalWidth =
|
|
23
|
+
widths.reduce((sum, w) => sum + w, 0) + (widths.length - 1) * COL_GAP;
|
|
24
|
+
const separator = "─".repeat(totalWidth);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Box flexDirection="column">
|
|
28
|
+
<Text bold>{labels}</Text>
|
|
29
|
+
<Text dimColor>{separator}</Text>
|
|
30
|
+
</Box>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ColumnDef } from "../../../domains/shared/contracts.js";
|
|
2
|
+
|
|
3
|
+
export interface HeaderRowProps<T> {
|
|
4
|
+
/** Visible columns to render labels for. */
|
|
5
|
+
readonly columns: readonly ColumnDef<T>[];
|
|
6
|
+
/** Computed widths for each column. */
|
|
7
|
+
readonly widths: readonly number[];
|
|
8
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { formatMarkdown } from "../../helpers/index.js";
|
|
3
|
+
import type { MarkdownTextProps } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal Markdown-to-ANSI renderer for terminal display.
|
|
7
|
+
*
|
|
8
|
+
* Converts `**bold**` to chalk bold, `` `code` `` to dim, and
|
|
9
|
+
* `- item` to bullet `•`. No heading parsing, no link rendering.
|
|
10
|
+
*/
|
|
11
|
+
export default function MarkdownText({ content }: MarkdownTextProps) {
|
|
12
|
+
const formatted = formatMarkdown(content);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Box paddingLeft={2}>
|
|
16
|
+
<Text>{formatted}</Text>
|
|
17
|
+
</Box>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import compactUri from "../../../domains/shared/compactUri.js";
|
|
3
|
+
import { PREFIX_MAP } from "../../../domains/shared/prefixes.js";
|
|
4
|
+
import type { MiniTableProps } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A compact aligned table for key-value or multi-column data.
|
|
8
|
+
*
|
|
9
|
+
* Renders each entry as a row with values aligned by computing
|
|
10
|
+
* the maximum width of each key across all entries.
|
|
11
|
+
*/
|
|
12
|
+
export default function MiniTable({ data, prefixes }: MiniTableProps) {
|
|
13
|
+
const prefixMap = prefixes ?? PREFIX_MAP;
|
|
14
|
+
|
|
15
|
+
if (data.length === 0) return null;
|
|
16
|
+
|
|
17
|
+
const allKeys = collectKeys(data);
|
|
18
|
+
const widths = computeKeyWidths(data, allKeys, prefixMap);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Box flexDirection="column" paddingLeft={2}>
|
|
22
|
+
{data.map((entry, i) => (
|
|
23
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: static table rows
|
|
24
|
+
<Text key={i}>
|
|
25
|
+
{allKeys
|
|
26
|
+
.map((key) => {
|
|
27
|
+
const value = formatValue(entry[key], prefixMap);
|
|
28
|
+
const w = widths.get(key) ?? value.length;
|
|
29
|
+
return value.padEnd(w);
|
|
30
|
+
})
|
|
31
|
+
.join(" ")}
|
|
32
|
+
</Text>
|
|
33
|
+
))}
|
|
34
|
+
</Box>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectKeys(data: readonly Record<string, unknown>[]): string[] {
|
|
39
|
+
const seen = new Set<string>();
|
|
40
|
+
for (const entry of data) {
|
|
41
|
+
for (const key of Object.keys(entry)) {
|
|
42
|
+
seen.add(key);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [...seen];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function computeKeyWidths(
|
|
49
|
+
data: readonly Record<string, unknown>[],
|
|
50
|
+
keys: string[],
|
|
51
|
+
prefixes: Readonly<Record<string, string>>,
|
|
52
|
+
): Map<string, number> {
|
|
53
|
+
const widths = new Map<string, number>();
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
let max = 0;
|
|
56
|
+
for (const entry of data) {
|
|
57
|
+
const formatted = formatValue(entry[key], prefixes);
|
|
58
|
+
max = Math.max(max, formatted.length);
|
|
59
|
+
}
|
|
60
|
+
widths.set(key, max);
|
|
61
|
+
}
|
|
62
|
+
return widths;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatValue(
|
|
66
|
+
value: unknown,
|
|
67
|
+
prefixes: Readonly<Record<string, string>>,
|
|
68
|
+
): string {
|
|
69
|
+
if (value === null || value === undefined) return "";
|
|
70
|
+
if (typeof value === "string") {
|
|
71
|
+
if (
|
|
72
|
+
value.startsWith("http://") ||
|
|
73
|
+
value.startsWith("https://") ||
|
|
74
|
+
/^[a-z][a-z0-9+.-]*:/i.test(value)
|
|
75
|
+
) {
|
|
76
|
+
return compactUri(value, prefixes);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface MiniTableProps {
|
|
2
|
+
/** Array of objects to render as rows. Each object's entries become columns. */
|
|
3
|
+
readonly data: readonly Record<string, unknown>[];
|
|
4
|
+
/** URI prefix map for compacting IRIs. */
|
|
5
|
+
readonly prefixes?: Readonly<Record<string, string>>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { PREFIX_MAP } from "../../../domains/shared/prefixes.js";
|
|
3
|
+
import { BulletList } from "../BulletList/index.js";
|
|
4
|
+
import { CodeBlock } from "../CodeBlock/index.js";
|
|
5
|
+
import { MarkdownText } from "../MarkdownText/index.js";
|
|
6
|
+
import { MiniTable } from "../MiniTable/index.js";
|
|
7
|
+
import { TreeView } from "../TreeView/index.js";
|
|
8
|
+
import type { SectionRendererProps } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dispatches a lookup section to the appropriate rendering component
|
|
12
|
+
* based on the section's `kind`.
|
|
13
|
+
*
|
|
14
|
+
* Skips rendering when the section value is empty and `showWhenEmpty`
|
|
15
|
+
* is not set. Renders a section heading followed by the kind-specific
|
|
16
|
+
* component.
|
|
17
|
+
*/
|
|
18
|
+
export default function SectionRenderer<T>({
|
|
19
|
+
section,
|
|
20
|
+
entity,
|
|
21
|
+
options,
|
|
22
|
+
domain: _domain,
|
|
23
|
+
}: SectionRendererProps<T>) {
|
|
24
|
+
const value = entity[section.key];
|
|
25
|
+
if (!section.showWhenEmpty && isEmpty(value)) return null;
|
|
26
|
+
|
|
27
|
+
const prefixes = options.prefixes ?? PREFIX_MAP;
|
|
28
|
+
const content = renderByKind(value, section.kind, options, section, prefixes);
|
|
29
|
+
if (!content) return null;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Box flexDirection="column" marginTop={1}>
|
|
33
|
+
<Text bold>{` ${section.heading}`}</Text>
|
|
34
|
+
{content}
|
|
35
|
+
</Box>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderByKind<T>(
|
|
40
|
+
value: unknown,
|
|
41
|
+
kind: string,
|
|
42
|
+
options: SectionRendererProps<T>["options"],
|
|
43
|
+
section: SectionRendererProps<T>["section"],
|
|
44
|
+
prefixes: Readonly<Record<string, string>>,
|
|
45
|
+
): React.JSX.Element | null {
|
|
46
|
+
switch (kind) {
|
|
47
|
+
case "field": {
|
|
48
|
+
if (typeof value !== "string" || value.trim().length === 0) return null;
|
|
49
|
+
return <MarkdownText content={value} />;
|
|
50
|
+
}
|
|
51
|
+
case "code": {
|
|
52
|
+
if (typeof value !== "string" || value.trim().length === 0) return null;
|
|
53
|
+
const language =
|
|
54
|
+
options.codeLanguage?.(section, value) ?? inferCodeLanguage(value);
|
|
55
|
+
return <CodeBlock code={value} language={language} />;
|
|
56
|
+
}
|
|
57
|
+
case "tree": {
|
|
58
|
+
return <TreeView data={value} prefixes={prefixes} />;
|
|
59
|
+
}
|
|
60
|
+
case "table":
|
|
61
|
+
case "nested-table": {
|
|
62
|
+
if (!Array.isArray(value) || value.length === 0) return null;
|
|
63
|
+
return (
|
|
64
|
+
<MiniTable
|
|
65
|
+
data={value as Record<string, unknown>[]}
|
|
66
|
+
prefixes={prefixes}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
case "list": {
|
|
71
|
+
if (!Array.isArray(value) || value.length === 0) return null;
|
|
72
|
+
return <BulletList items={value} prefixes={prefixes} />;
|
|
73
|
+
}
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function inferCodeLanguage(value: string): string {
|
|
80
|
+
const trimmed = value.trim();
|
|
81
|
+
if (trimmed.startsWith("@prefix") || trimmed.includes(" a ")) return "ttl";
|
|
82
|
+
if (trimmed.includes(":") && trimmed.includes("\n")) return "yaml";
|
|
83
|
+
return "text";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isEmpty(value: unknown): boolean {
|
|
87
|
+
if (value === null || value === undefined) return true;
|
|
88
|
+
if (typeof value === "string") return value.trim().length === 0;
|
|
89
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RenderLookupOptions,
|
|
3
|
+
SectionDef,
|
|
4
|
+
} from "../../../domains/shared/contracts.js";
|
|
5
|
+
|
|
6
|
+
export interface SectionRendererProps<T> {
|
|
7
|
+
/** The section definition to render. */
|
|
8
|
+
readonly section: SectionDef<T>;
|
|
9
|
+
/** The entity data containing the section value. */
|
|
10
|
+
readonly entity: T;
|
|
11
|
+
/** Full lookup options (for code language inference and prefix map). */
|
|
12
|
+
readonly options: RenderLookupOptions<T>;
|
|
13
|
+
/** Domain name for coloring. */
|
|
14
|
+
readonly domain: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import compactUri from "../../../domains/shared/compactUri.js";
|
|
3
|
+
import { PREFIX_MAP } from "../../../domains/shared/prefixes.js";
|
|
4
|
+
import type { TreeViewProps } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A tree display with box-drawing connectors.
|
|
8
|
+
*
|
|
9
|
+
* Recursively renders tree nodes with `├─` / `└─` connectors and
|
|
10
|
+
* proper indentation. Handles nodes with `name`, `uri`, and
|
|
11
|
+
* `children` fields, as well as rooted trees with a `root` field.
|
|
12
|
+
*/
|
|
13
|
+
export default function TreeView({ data, prefixes }: TreeViewProps) {
|
|
14
|
+
const prefixMap = prefixes ?? PREFIX_MAP;
|
|
15
|
+
const lines = buildTreeLines(data, prefixMap);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Box flexDirection="column" paddingLeft={2}>
|
|
19
|
+
{lines.map((line, i) => (
|
|
20
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: static tree lines
|
|
21
|
+
<Text key={i}>{line}</Text>
|
|
22
|
+
))}
|
|
23
|
+
</Box>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildTreeLines(
|
|
28
|
+
value: unknown,
|
|
29
|
+
prefixes: Readonly<Record<string, string>>,
|
|
30
|
+
depth = 0,
|
|
31
|
+
): string[] {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return value.flatMap((entry) => buildTreeLines(entry, prefixes, depth));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!value || typeof value !== "object") {
|
|
37
|
+
return typeof value === "string" && value.length > 0
|
|
38
|
+
? [
|
|
39
|
+
`${" ".repeat(depth)}${depth > 0 ? "├─ " : ""}${formatValue(value, prefixes)}`,
|
|
40
|
+
]
|
|
41
|
+
: [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rootedTree = value as { root?: unknown };
|
|
45
|
+
if (rootedTree.root) {
|
|
46
|
+
return buildTreeLines(rootedTree.root, prefixes, depth);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const node = value as {
|
|
50
|
+
name?: unknown;
|
|
51
|
+
uri?: unknown;
|
|
52
|
+
children?: unknown;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const label =
|
|
56
|
+
typeof node.name === "string" && node.name.length > 0
|
|
57
|
+
? node.name
|
|
58
|
+
: typeof node.uri === "string"
|
|
59
|
+
? compactUri(node.uri, prefixes)
|
|
60
|
+
: "item";
|
|
61
|
+
|
|
62
|
+
const prefix = depth > 0 ? "├─ " : "";
|
|
63
|
+
const lines = [`${" ".repeat(depth)}${prefix}${label}`];
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(node.children)) {
|
|
66
|
+
lines.push(...buildTreeLines(node.children, prefixes, depth + 1));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return lines;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatValue(
|
|
73
|
+
value: string,
|
|
74
|
+
prefixes: Readonly<Record<string, string>>,
|
|
75
|
+
): string {
|
|
76
|
+
if (
|
|
77
|
+
value.startsWith("http://") ||
|
|
78
|
+
value.startsWith("https://") ||
|
|
79
|
+
/^[a-z][a-z0-9+.-]*:/i.test(value)
|
|
80
|
+
) {
|
|
81
|
+
return compactUri(value, prefixes);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type { BulletListProps } from "./BulletList/index.js";
|
|
2
|
+
export { BulletList } from "./BulletList/index.js";
|
|
3
|
+
export { buildCard } from "./Card/index.js";
|
|
4
|
+
export type { CodeBlockProps } from "./CodeBlock/index.js";
|
|
5
|
+
export { CodeBlock } from "./CodeBlock/index.js";
|
|
6
|
+
export type { DataRowProps } from "./DataRow/index.js";
|
|
7
|
+
export { DataRow } from "./DataRow/index.js";
|
|
8
|
+
export type { FieldRowProps } from "./FieldRow/index.js";
|
|
9
|
+
export { FieldRow } from "./FieldRow/index.js";
|
|
10
|
+
export type { HeaderRowProps } from "./HeaderRow/index.js";
|
|
11
|
+
export { HeaderRow } from "./HeaderRow/index.js";
|
|
12
|
+
export type { MarkdownTextProps } from "./MarkdownText/index.js";
|
|
13
|
+
export { MarkdownText } from "./MarkdownText/index.js";
|
|
14
|
+
export type { MiniTableProps } from "./MiniTable/index.js";
|
|
15
|
+
export { MiniTable } from "./MiniTable/index.js";
|
|
16
|
+
export type { SectionRendererProps } from "./SectionRenderer/index.js";
|
|
17
|
+
export { SectionRenderer } from "./SectionRenderer/index.js";
|
|
18
|
+
export type { TreeViewProps } from "./TreeView/index.js";
|
|
19
|
+
export { TreeView } from "./TreeView/index.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DomainColorPair } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-domain color assignments for TUI rendering.
|
|
5
|
+
*
|
|
6
|
+
* Each domain gets a distinct hue. Class-level labels (e.g., "Component",
|
|
7
|
+
* "Token") use the background variant; instance values (e.g., "Button",
|
|
8
|
+
* "color.primary") use the foreground text variant of the same hue.
|
|
9
|
+
* This creates visual coherence within a domain while distinguishing
|
|
10
|
+
* domains from each other.
|
|
11
|
+
*/
|
|
12
|
+
export const DOMAIN_COLORS: Readonly<Record<string, DomainColorPair>> = {
|
|
13
|
+
block: { classBg: "bgBlue", instanceFg: "blue" },
|
|
14
|
+
token: { classBg: "bgGreen", instanceFg: "green" },
|
|
15
|
+
modifier: { classBg: "bgYellow", instanceFg: "yellow" },
|
|
16
|
+
standard: { classBg: "bgCyan", instanceFg: "cyan" },
|
|
17
|
+
tier: { classBg: "bgMagenta", instanceFg: "magenta" },
|
|
18
|
+
ontology: { classBg: "bgWhite", instanceFg: "white" },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Box-drawing characters for card borders and separators. */
|
|
22
|
+
export const BOX = {
|
|
23
|
+
topLeft: "╔",
|
|
24
|
+
topRight: "╗",
|
|
25
|
+
bottomLeft: "╚",
|
|
26
|
+
bottomRight: "╝",
|
|
27
|
+
horizontal: "═",
|
|
28
|
+
vertical: "║",
|
|
29
|
+
dividerLeft: "╠",
|
|
30
|
+
dividerRight: "╣",
|
|
31
|
+
dividerHorizontal: "─",
|
|
32
|
+
thinVertical: "│",
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/** Column gap width (characters between columns in list view). */
|
|
36
|
+
export const COL_GAP = 2;
|
|
37
|
+
|
|
38
|
+
/** Minimum width for any visible column (characters). */
|
|
39
|
+
export const MIN_COL_WIDTH = 8;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ColumnDef } from "../domains/shared/contracts.js";
|
|
2
|
+
import { ListView } from "./views/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a ListView string output with proper generic type inference.
|
|
6
|
+
*
|
|
7
|
+
* Wraps the ListView function call to preserve the generic `T` at
|
|
8
|
+
* the call site. Domain commands call this from plain `.ts` files
|
|
9
|
+
* without needing JSX or React.
|
|
10
|
+
*
|
|
11
|
+
* @param props - ListView props with typed items and columns.
|
|
12
|
+
* @returns A chalk-styled string ready for stdout.
|
|
13
|
+
*/
|
|
14
|
+
export default function createListView<T>(props: {
|
|
15
|
+
readonly heading: string;
|
|
16
|
+
readonly domain: string;
|
|
17
|
+
readonly items: readonly T[];
|
|
18
|
+
readonly columns: readonly ColumnDef<T>[];
|
|
19
|
+
readonly prefixes?: Readonly<Record<string, string>>;
|
|
20
|
+
}): string {
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: generic erasure at function boundary
|
|
22
|
+
return (ListView as (props: any) => string)(props);
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RenderLookupOptions } from "../domains/shared/contracts.js";
|
|
2
|
+
import { LookupView } from "./views/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a LookupView string output with proper generic type inference.
|
|
6
|
+
*
|
|
7
|
+
* Wraps the LookupView function call to preserve the generic `T` at
|
|
8
|
+
* the call site. Domain commands call this from plain `.ts` files
|
|
9
|
+
* without needing JSX or React.
|
|
10
|
+
*
|
|
11
|
+
* @param props - LookupView props with typed results and options.
|
|
12
|
+
* @returns A chalk-styled string ready for stdout.
|
|
13
|
+
*/
|
|
14
|
+
export default function createLookupView<T>(props: {
|
|
15
|
+
readonly results: readonly T[];
|
|
16
|
+
readonly errors: readonly {
|
|
17
|
+
readonly query: string;
|
|
18
|
+
readonly code: string;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
}[];
|
|
21
|
+
readonly options: RenderLookupOptions<T>;
|
|
22
|
+
readonly domain: string;
|
|
23
|
+
}): string {
|
|
24
|
+
// biome-ignore lint/suspicious/noExplicitAny: generic erasure at function boundary
|
|
25
|
+
return (LookupView as (props: any) => string)(props);
|
|
26
|
+
}
|