@canonical/pragma-cli 0.20.0 → 0.22.0-experimental.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.
Files changed (203) hide show
  1. package/dist/pragma +0 -0
  2. package/package.json +31 -18
  3. package/src/bin.test.ts +2 -0
  4. package/src/bin.ts +1 -0
  5. package/src/compile-validation.ts +3 -4
  6. package/src/constants.ts +1 -1
  7. package/src/domains/block/blockConfig.ts +47 -3
  8. package/src/domains/block/commands/list.test.ts +1 -1
  9. package/src/domains/block/commands/list.ts +27 -0
  10. package/src/domains/block/commands/lookup.ts +26 -52
  11. package/src/domains/block/formatters/index.ts +1 -1
  12. package/src/domains/block/formatters/list.test.ts +1 -1
  13. package/src/domains/block/formatters/list.ts +28 -42
  14. package/src/domains/block/formatters/lookup.test.ts +3 -2
  15. package/src/domains/block/formatters/lookup.ts +91 -296
  16. package/src/domains/block/formatters/types.ts +1 -1
  17. package/src/domains/block/operations/list.test.ts +1 -1
  18. package/src/domains/block/operations/list.ts +1 -1
  19. package/src/domains/block/operations/lookup.test.ts +29 -8
  20. package/src/domains/block/operations/lookup.ts +94 -16
  21. package/src/domains/block/orchestration/blockEmptyError.ts +1 -1
  22. package/src/domains/block/orchestration/buildBlockFilters.ts +1 -1
  23. package/src/domains/block/orchestration/enrichBlocks.ts +6 -2
  24. package/src/domains/block/orchestration/resolveBlockList.ts +1 -1
  25. package/src/domains/block/orchestration/resolveBlockLookup.ts +8 -2
  26. package/src/domains/block/types.ts +1 -1
  27. package/src/domains/config/commands/channel.ts +6 -4
  28. package/src/domains/config/commands/tier.ts +6 -4
  29. package/src/domains/config/mcp/specs.ts +18 -6
  30. package/src/domains/config/operations/validateTier.ts +1 -1
  31. package/src/domains/config/tasks/index.ts +3 -0
  32. package/src/domains/config/tasks/setChannel.ts +30 -0
  33. package/src/domains/config/tasks/setTier.ts +33 -0
  34. package/src/domains/create/commands/package.test.ts +2 -2
  35. package/src/domains/doctor/mcp/index.ts +1 -1
  36. package/src/domains/doctor/mcp/specs.ts +2 -49
  37. package/src/domains/graph/commands/inspect.test.ts +1 -1
  38. package/src/domains/graph/formatters/inspect.ts +12 -7
  39. package/src/domains/graph/operations/inspectUri.ts +4 -1
  40. package/src/domains/info/commands/upgrade.ts +6 -3
  41. package/src/domains/info/index.ts +3 -1
  42. package/src/domains/info/mcp/index.ts +2 -0
  43. package/src/domains/info/mcp/specs.ts +65 -0
  44. package/src/domains/llm/data/commandReference.ts +4 -4
  45. package/src/domains/llm/data/decisionTrees.ts +14 -12
  46. package/src/domains/llm/operations/collectContext.ts +1 -1
  47. package/src/domains/modifier/commands/list.ts +9 -0
  48. package/src/domains/modifier/commands/lookup.ts +22 -42
  49. package/src/domains/modifier/formatters/index.ts +4 -1
  50. package/src/domains/modifier/formatters/list.test.ts +1 -1
  51. package/src/domains/modifier/formatters/list.ts +13 -26
  52. package/src/domains/modifier/formatters/lookup.test.ts +1 -1
  53. package/src/domains/modifier/formatters/lookup.ts +41 -29
  54. package/src/domains/modifier/index.ts +1 -0
  55. package/src/domains/modifier/modifierConfig.ts +36 -0
  56. package/src/domains/modifier/operations/list.ts +1 -1
  57. package/src/domains/modifier/operations/lookup.ts +4 -3
  58. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +1 -1
  59. package/src/domains/modifier/types.ts +1 -1
  60. package/src/domains/ontology/commands/list.test.ts +1 -1
  61. package/src/domains/ontology/commands/list.ts +18 -0
  62. package/src/domains/ontology/commands/show.test.ts +1 -1
  63. package/src/domains/ontology/formatters/list.ts +5 -3
  64. package/src/domains/ontology/formatters/show.ts +38 -13
  65. package/src/domains/ontology/helpers/queryClasses.ts +1 -1
  66. package/src/domains/ontology/helpers/queryProperties.ts +1 -1
  67. package/src/domains/ontology/operations/listOntologies.ts +1 -1
  68. package/src/domains/ontology/operations/showOntology.ts +1 -1
  69. package/src/domains/setup/helpers/completionScripts.test.ts +3 -3
  70. package/src/domains/setup/helpers/completionScripts.ts +4 -4
  71. package/src/domains/shared/ToolSpec.ts +1 -1
  72. package/src/domains/shared/context.ts +3 -2
  73. package/src/domains/shared/contracts.ts +40 -1
  74. package/src/domains/shared/filters/buildFilterConfig.ts +1 -2
  75. package/src/domains/shared/filters/buildFilters.ts +1 -1
  76. package/src/domains/shared/filters/describeFilters.ts +1 -1
  77. package/src/domains/shared/formatters.ts +47 -0
  78. package/src/domains/shared/renderers.ts +410 -0
  79. package/src/domains/shared/runtime.ts +7 -22
  80. package/src/domains/shared/types/block.ts +107 -0
  81. package/src/domains/shared/types/common.ts +33 -0
  82. package/src/domains/shared/types/graph.ts +17 -0
  83. package/src/domains/shared/types/index.ts +49 -0
  84. package/src/domains/shared/types/modifier.ts +22 -0
  85. package/src/domains/shared/types/ontology.ts +50 -0
  86. package/src/domains/shared/types/runtime.ts +21 -0
  87. package/src/domains/shared/types/standard.ts +64 -0
  88. package/src/domains/shared/types/tier.ts +15 -0
  89. package/src/domains/shared/types/token.ts +30 -0
  90. package/src/domains/standard/commands/list.ts +9 -0
  91. package/src/domains/standard/commands/lookup.ts +22 -50
  92. package/src/domains/standard/formatters/categories.test.ts +1 -1
  93. package/src/domains/standard/formatters/categories.ts +1 -1
  94. package/src/domains/standard/formatters/index.ts +4 -1
  95. package/src/domains/standard/formatters/list.test.ts +4 -1
  96. package/src/domains/standard/formatters/list.ts +112 -84
  97. package/src/domains/standard/formatters/lookup.test.ts +1 -1
  98. package/src/domains/standard/formatters/lookup.ts +85 -70
  99. package/src/domains/standard/formatters/types.ts +1 -1
  100. package/src/domains/standard/index.ts +1 -0
  101. package/src/domains/standard/operations/categories.ts +1 -1
  102. package/src/domains/standard/operations/list.ts +1 -1
  103. package/src/domains/standard/operations/lookup.ts +1 -1
  104. package/src/domains/standard/orchestration/buildStandardFilters.ts +1 -1
  105. package/src/domains/standard/orchestration/resolveStandardList.ts +4 -1
  106. package/src/domains/standard/orchestration/resolveStandardLookup.ts +1 -1
  107. package/src/domains/standard/orchestration/standardEmptyError.ts +1 -1
  108. package/src/domains/standard/standardConfig.ts +42 -0
  109. package/src/domains/standard/types.ts +1 -1
  110. package/src/domains/tier/commands/list.ts +16 -0
  111. package/src/domains/tier/formatters/list.test.ts +1 -1
  112. package/src/domains/tier/formatters/list.ts +11 -6
  113. package/src/domains/tier/operations/list.ts +1 -1
  114. package/src/domains/token/commands/list.ts +9 -0
  115. package/src/domains/token/commands/lookup.ts +22 -48
  116. package/src/domains/token/formatters/index.ts +4 -1
  117. package/src/domains/token/formatters/list.test.ts +1 -1
  118. package/src/domains/token/formatters/list.ts +15 -28
  119. package/src/domains/token/formatters/lookup.test.ts +2 -2
  120. package/src/domains/token/formatters/lookup.ts +80 -45
  121. package/src/domains/token/index.ts +1 -0
  122. package/src/domains/token/operations/list.ts +1 -1
  123. package/src/domains/token/operations/lookup.ts +1 -1
  124. package/src/domains/token/orchestration/resolveTokenLookup.ts +1 -1
  125. package/src/domains/token/tokenConfig.ts +38 -0
  126. package/src/domains/token/types.ts +1 -1
  127. package/src/embedWasm.ts +42 -0
  128. package/src/index.ts +1 -1
  129. package/src/mcp/runMcpServer.test.ts +2 -2
  130. package/src/mcp/tools/index.ts +2 -0
  131. package/src/mcp/tools/registerTools.test.ts +8 -3
  132. package/src/package-manager/types.ts +1 -3
  133. package/src/pipeline/createProgram.ts +20 -1
  134. package/src/pipeline/index.ts +4 -1
  135. package/src/pipeline/parseGlobalFlags.test.ts +92 -0
  136. package/src/pipeline/parseGlobalFlags.ts +25 -0
  137. package/src/pipeline/runCli.orchestration.test.ts +1 -0
  138. package/src/pipeline/runCli.ts +11 -4
  139. package/src/pipeline/runInteractiveCommand.ts +198 -0
  140. package/src/testing/cli.ts +2 -2
  141. package/src/testing/fixtures.ts +2 -2
  142. package/src/testing/integration/boot.test.ts +2 -2
  143. package/src/testing/integration/cli-user-stories.test.ts +198 -0
  144. package/src/testing/integration/edge-cases.test.ts +263 -0
  145. package/src/testing/integration/parity.test.ts +377 -27
  146. package/src/testing/store.ts +1 -1
  147. package/src/tui/components/BulletList/BulletList.tsx +46 -0
  148. package/src/tui/components/BulletList/index.ts +2 -0
  149. package/src/tui/components/BulletList/types.ts +6 -0
  150. package/src/tui/components/Card/Card.tsx +73 -0
  151. package/src/tui/components/Card/index.ts +1 -0
  152. package/src/tui/components/Card/types.ts +12 -0
  153. package/src/tui/components/CodeBlock/CodeBlock.tsx +37 -0
  154. package/src/tui/components/CodeBlock/index.ts +2 -0
  155. package/src/tui/components/CodeBlock/types.ts +6 -0
  156. package/src/tui/components/DataRow/DataRow.tsx +80 -0
  157. package/src/tui/components/DataRow/index.ts +2 -0
  158. package/src/tui/components/DataRow/types.ts +14 -0
  159. package/src/tui/components/FieldRow/FieldRow.tsx +24 -0
  160. package/src/tui/components/FieldRow/index.ts +2 -0
  161. package/src/tui/components/FieldRow/types.ts +10 -0
  162. package/src/tui/components/HeaderRow/HeaderRow.tsx +32 -0
  163. package/src/tui/components/HeaderRow/index.ts +2 -0
  164. package/src/tui/components/HeaderRow/types.ts +8 -0
  165. package/src/tui/components/MarkdownText/MarkdownText.tsx +19 -0
  166. package/src/tui/components/MarkdownText/index.ts +2 -0
  167. package/src/tui/components/MarkdownText/types.ts +4 -0
  168. package/src/tui/components/MiniTable/MiniTable.tsx +82 -0
  169. package/src/tui/components/MiniTable/index.ts +2 -0
  170. package/src/tui/components/MiniTable/types.ts +6 -0
  171. package/src/tui/components/SectionRenderer/SectionRenderer.tsx +91 -0
  172. package/src/tui/components/SectionRenderer/index.ts +2 -0
  173. package/src/tui/components/SectionRenderer/types.ts +15 -0
  174. package/src/tui/components/TreeView/TreeView.tsx +84 -0
  175. package/src/tui/components/TreeView/index.ts +2 -0
  176. package/src/tui/components/TreeView/types.ts +6 -0
  177. package/src/tui/components/index.ts +19 -0
  178. package/src/tui/constants.ts +39 -0
  179. package/src/tui/createListView.ts +23 -0
  180. package/src/tui/createLookupView.ts +26 -0
  181. package/src/tui/helpers/computeWidths.ts +53 -0
  182. package/src/tui/helpers/fitColumns.ts +33 -0
  183. package/src/tui/helpers/formatMarkdown.ts +19 -0
  184. package/src/tui/helpers/getTerminalSize.ts +16 -0
  185. package/src/tui/helpers/index.ts +6 -0
  186. package/src/tui/helpers/syntaxColor.ts +67 -0
  187. package/src/tui/helpers/truncateText.ts +12 -0
  188. package/src/tui/hooks/index.ts +3 -0
  189. package/src/tui/hooks/types.ts +7 -0
  190. package/src/tui/hooks/useAutoExit.ts +17 -0
  191. package/src/tui/hooks/useTerminalSize.ts +19 -0
  192. package/src/tui/index.ts +15 -0
  193. package/src/tui/renderInk.tsx +18 -0
  194. package/src/tui/types.ts +12 -0
  195. package/src/tui/views/ListView/ListView.tsx +104 -0
  196. package/src/tui/views/ListView/index.ts +2 -0
  197. package/src/tui/views/ListView/types.ts +14 -0
  198. package/src/tui/views/LookupView/LookupView.tsx +244 -0
  199. package/src/tui/views/LookupView/index.ts +2 -0
  200. package/src/tui/views/LookupView/types.ts +16 -0
  201. package/src/tui/views/index.ts +4 -0
  202. package/src/domains/shared/types.test-d.ts +0 -127
  203. package/src/domains/shared/types.ts +0 -348
@@ -1,7 +1,9 @@
1
1
  import chalk from "chalk";
2
2
  import { formatHeading, formatSection } from "#pipeline";
3
+ import compactUri from "../../shared/compactUri.js";
3
4
  import type { Formatters } from "../../shared/formatters.js";
4
- import type { OntologyDetailed } from "../../shared/types.js";
5
+ import { PREFIX_MAP } from "../../shared/prefixes.js";
6
+ import type { OntologyDetailed } from "../../shared/types/index.js";
5
7
 
6
8
  /**
7
9
  * Formatters for `pragma ontology show` output.
@@ -14,13 +16,19 @@ const formatters: Formatters<OntologyDetailed> = {
14
16
  plain(data) {
15
17
  const lines: string[] = [];
16
18
 
17
- lines.push(formatHeading(`${data.prefix}: (${data.namespace})`));
19
+ lines.push(
20
+ formatHeading(
21
+ `${data.prefix}: ${compactUri(data.namespace, PREFIX_MAP)}`,
22
+ ),
23
+ );
18
24
 
19
25
  if (data.classes.length > 0) {
20
26
  const classLines = data.classes
21
27
  .map((c) => {
22
- const sup = c.superclass ? chalk.dim(` extends ${c.superclass}`) : "";
23
- return ` ${chalk.bold(c.label)}${sup}`;
28
+ const sup = c.superclass
29
+ ? chalk.dim(` extends ${compactUri(c.superclass, PREFIX_MAP)}`)
30
+ : "";
31
+ return ` ${chalk.bold(c.label)} ${chalk.dim(`(${compactUri(c.uri, PREFIX_MAP)})`)}${sup}`;
24
32
  })
25
33
  .join("\n");
26
34
  lines.push("");
@@ -30,9 +38,13 @@ const formatters: Formatters<OntologyDetailed> = {
30
38
  if (data.properties.length > 0) {
31
39
  const propLines = data.properties
32
40
  .map((p) => {
33
- const domain = p.domain ? chalk.dim(` domain: ${p.domain}`) : "";
34
- const range = p.range ? chalk.dim(` range: ${p.range}`) : "";
35
- return ` ${chalk.bold(p.label)} (${p.type})${domain}${range}`;
41
+ const domain = p.domain
42
+ ? chalk.dim(` domain: ${compactUri(p.domain, PREFIX_MAP)}`)
43
+ : "";
44
+ const range = p.range
45
+ ? chalk.dim(` range: ${compactUri(p.range, PREFIX_MAP)}`)
46
+ : "";
47
+ return ` ${chalk.bold(p.label)} ${chalk.dim(`(${compactUri(p.uri, PREFIX_MAP)})`)} (${p.type})${domain}${range}`;
36
48
  })
37
49
  .join("\n");
38
50
  lines.push("");
@@ -43,13 +55,20 @@ const formatters: Formatters<OntologyDetailed> = {
43
55
  },
44
56
 
45
57
  llm(data) {
46
- const lines = [`## ${data.prefix}: (${data.namespace})`, ""];
58
+ const lines = [
59
+ `## ${data.prefix}: ${compactUri(data.namespace, PREFIX_MAP)}`,
60
+ "",
61
+ ];
47
62
 
48
63
  if (data.classes.length > 0) {
49
64
  lines.push("### Classes");
50
65
  for (const c of data.classes) {
51
- const sup = c.superclass ? ` extends ${c.superclass}` : "";
52
- lines.push(`- **${c.label}**${sup}`);
66
+ const sup = c.superclass
67
+ ? ` extends ${compactUri(c.superclass, PREFIX_MAP)}`
68
+ : "";
69
+ lines.push(
70
+ `- \`${compactUri(c.uri, PREFIX_MAP)}\` — **${c.label}**${sup}`,
71
+ );
53
72
  }
54
73
  lines.push("");
55
74
  }
@@ -57,9 +76,15 @@ const formatters: Formatters<OntologyDetailed> = {
57
76
  if (data.properties.length > 0) {
58
77
  lines.push("### Properties");
59
78
  for (const p of data.properties) {
60
- const domain = p.domain ? ` domain: ${p.domain}` : "";
61
- const range = p.range ? ` range: ${p.range}` : "";
62
- lines.push(`- **${p.label}** (${p.type})${domain}${range}`);
79
+ const domain = p.domain
80
+ ? ` domain: ${compactUri(p.domain, PREFIX_MAP)}`
81
+ : "";
82
+ const range = p.range
83
+ ? ` range: ${compactUri(p.range, PREFIX_MAP)}`
84
+ : "";
85
+ lines.push(
86
+ `- \`${compactUri(p.uri, PREFIX_MAP)}\` — **${p.label}** (${p.type})${domain}${range}`,
87
+ );
63
88
  }
64
89
  }
65
90
 
@@ -2,7 +2,7 @@ import type { Store } from "@canonical/ke";
2
2
  import { buildQuery } from "../../shared/buildQuery.js";
3
3
  import extractLocalName from "../../shared/extractLocalName.js";
4
4
  import { P } from "../../shared/prefixes.js";
5
- import type { OntologyClass } from "../../shared/types.js";
5
+ import type { OntologyClass } from "../../shared/types/index.js";
6
6
 
7
7
  /**
8
8
  * Queries all `owl:Class` definitions within a namespace.
@@ -2,7 +2,7 @@ import type { Store } from "@canonical/ke";
2
2
  import { buildQuery } from "../../shared/buildQuery.js";
3
3
  import extractLocalName from "../../shared/extractLocalName.js";
4
4
  import { P } from "../../shared/prefixes.js";
5
- import type { OntologyProperty } from "../../shared/types.js";
5
+ import type { OntologyProperty } from "../../shared/types/index.js";
6
6
 
7
7
  /**
8
8
  * Queries all `owl:ObjectProperty` and `owl:DatatypeProperty` definitions
@@ -1,7 +1,7 @@
1
1
  import type { Store } from "@canonical/ke";
2
2
  import { buildQuery } from "../../shared/buildQuery.js";
3
3
  import { P } from "../../shared/prefixes.js";
4
- import type { OntologySummary } from "../../shared/types.js";
4
+ import type { OntologySummary } from "../../shared/types/index.js";
5
5
  import findNamespace from "../helpers/findNamespace.js";
6
6
 
7
7
  /** Check whether a URI belongs to the anatomy namespace. */
@@ -1,6 +1,6 @@
1
1
  import type { Store } from "@canonical/ke";
2
2
  import { PragmaError } from "#error";
3
- import type { OntologyDetailed } from "../../shared/types.js";
3
+ import type { OntologyDetailed } from "../../shared/types/index.js";
4
4
  import queryClasses from "../helpers/queryClasses.js";
5
5
  import queryProperties from "../helpers/queryProperties.js";
6
6
  import resolvePrefix from "../helpers/resolvePrefix.js";
@@ -9,19 +9,19 @@ describe("completionScriptContent", () => {
9
9
  it("produces zsh completion with compdef", () => {
10
10
  const script = completionScriptContent("zsh");
11
11
  expect(script).toContain("#compdef pragma");
12
- expect(script).toContain("pragma completions zsh");
12
+ expect(script).toContain("pragma --completions");
13
13
  });
14
14
 
15
15
  it("produces bash completion with complete -F", () => {
16
16
  const script = completionScriptContent("bash");
17
17
  expect(script).toContain("complete -F _pragma pragma");
18
- expect(script).toContain("pragma completions bash");
18
+ expect(script).toContain("pragma --completions");
19
19
  });
20
20
 
21
21
  it("produces fish completion with complete -c", () => {
22
22
  const script = completionScriptContent("fish");
23
23
  expect(script).toContain("complete -c pragma");
24
- expect(script).toContain("pragma completions fish");
24
+ expect(script).toContain("pragma --completions");
25
25
  });
26
26
  });
27
27
 
@@ -5,7 +5,7 @@ const HOME = process.env.HOME ?? "";
5
5
  /**
6
6
  * Generate the shell-specific completion script content.
7
7
  *
8
- * Each script delegates to `pragma completions <shell>` at runtime
8
+ * Each script delegates to `pragma --completions` at runtime
9
9
  * so the completion logic stays in the CLI, not in the shell script.
10
10
  *
11
11
  * @param shell - Target shell identifier.
@@ -21,7 +21,7 @@ export function completionScriptContent(shell: ShellId): string {
21
21
  "_pragma() {",
22
22
  " local -a completions",
23
23
  // biome-ignore lint/suspicious/noTemplateCurlyInString: shell syntax, not JS template
24
- ' completions=("${(@f)$(pragma completions zsh -- "${words[@]}" 2>/dev/null)}")',
24
+ ' completions=("${(@f)$(pragma --completions "${words[@]}" 2>/dev/null)}")',
25
25
  " compadd -- $completions",
26
26
  "}",
27
27
  "",
@@ -38,7 +38,7 @@ export function completionScriptContent(shell: ShellId): string {
38
38
  ' local cur="${COMP_WORDS[COMP_CWORD]}"',
39
39
  " local completions",
40
40
  // biome-ignore lint/suspicious/noTemplateCurlyInString: shell syntax, not JS template
41
- ' completions=$(pragma completions bash -- "${COMP_WORDS[@]}" 2>/dev/null)',
41
+ ' completions=$(pragma --completions "${COMP_WORDS[@]}" 2>/dev/null)',
42
42
  " # shellcheck disable=SC2207",
43
43
  ' COMPREPLY=($(compgen -W "$completions" -- "$cur"))',
44
44
  "}",
@@ -51,7 +51,7 @@ export function completionScriptContent(shell: ShellId): string {
51
51
  return [
52
52
  "# Generated by `pragma setup completions`",
53
53
  "",
54
- "complete -c pragma -f -a '(pragma completions fish -- (commandline -cop) 2>/dev/null)'",
54
+ "complete -c pragma -f -a '(pragma --completions (commandline -cop) 2>/dev/null)'",
55
55
  "",
56
56
  ].join("\n");
57
57
  }
@@ -6,7 +6,7 @@
6
6
  * envelopes, and `server.registerTool()` calls.
7
7
  */
8
8
 
9
- import type { PragmaRuntime } from "./runtime.js";
9
+ import type { PragmaRuntime } from "./types/index.js";
10
10
 
11
11
  /**
12
12
  * Parameter definition for a tool — neutral format.
@@ -7,9 +7,10 @@
7
7
  * cli-core remains unaware of Store or FilterConfig.
8
8
  */
9
9
 
10
- import type { GlobalFlags } from "@canonical/cli-core";
11
- import type { PragmaRuntime } from "./runtime.js";
10
+ import type { GlobalFlags, InteractiveHandler } from "@canonical/cli-core";
11
+ import type { PragmaRuntime } from "./types/index.js";
12
12
 
13
13
  export interface PragmaContext extends PragmaRuntime {
14
14
  readonly globalFlags: GlobalFlags;
15
+ readonly interactive?: InteractiveHandler | undefined;
15
16
  }
@@ -4,7 +4,7 @@
4
4
  * These types provide the structural common ground for CLI and MCP surfaces.
5
5
  */
6
6
 
7
- import type { FilterConfig } from "./types.js";
7
+ import type { FilterConfig } from "./types/index.js";
8
8
 
9
9
  export interface ColumnDef<T> {
10
10
  readonly key: keyof T & string;
@@ -83,6 +83,45 @@ export interface LookupContract<TDetailed> {
83
83
  readonly result: LookupResult<TDetailed>;
84
84
  }
85
85
 
86
+ // ---------------------------------------------------------------------------
87
+ // Renderer contracts
88
+ // ---------------------------------------------------------------------------
89
+
90
+ /** A single inline field rendered in a lookup heading block. */
91
+ export interface LookupField<T> {
92
+ readonly label: string;
93
+ readonly value: (entity: T) => unknown;
94
+ }
95
+
96
+ /** Per-mode override callbacks for a lookup section. */
97
+ export interface LookupSectionOverride<T> {
98
+ readonly plain?: (entity: T, section: SectionDef<T>) => string | null;
99
+ readonly llm?: (entity: T, section: SectionDef<T>) => string | null;
100
+ }
101
+
102
+ /** Options controlling the generic list renderer. */
103
+ export interface RenderListOptions<T> {
104
+ readonly heading: string;
105
+ readonly columns: readonly ColumnDef<T>[];
106
+ readonly prefixes?: Readonly<Record<string, string>>;
107
+ }
108
+
109
+ /** Options controlling the generic lookup renderer. */
110
+ export interface RenderLookupOptions<T> {
111
+ readonly title: (entity: T) => string;
112
+ readonly fields: readonly LookupField<T>[];
113
+ readonly sections: readonly SectionDef<T>[];
114
+ readonly prefixes?: Readonly<Record<string, string>>;
115
+ readonly sectionOverrides?: Partial<
116
+ Record<keyof T & string, LookupSectionOverride<T>>
117
+ >;
118
+ readonly codeLanguage?: (section: SectionDef<T>, value: unknown) => string;
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Entity display configuration
123
+ // ---------------------------------------------------------------------------
124
+
86
125
  export interface EntityDisplayConfig<
87
126
  TSummary,
88
127
  TDigest extends TSummary,
@@ -2,8 +2,7 @@
2
2
  * Build a FilterConfig from runtime config, optionally widening tier.
3
3
  */
4
4
 
5
- import type { PragmaRuntime } from "../runtime.js";
6
- import type { FilterConfig } from "../types.js";
5
+ import type { FilterConfig, PragmaRuntime } from "../types/index.js";
7
6
 
8
7
  export default function buildFilterConfig(
9
8
  runtime: PragmaRuntime,
@@ -4,7 +4,7 @@
4
4
  * Both tier and channel filters apply simultaneously as AND constraints.
5
5
  */
6
6
 
7
- import type { FilterConfig } from "../types.js";
7
+ import type { FilterConfig } from "../types/index.js";
8
8
  import { buildChannelFilter } from "./buildChannelFilter.js";
9
9
  import { buildTierFilter } from "./buildTierFilter.js";
10
10
 
@@ -2,7 +2,7 @@
2
2
  * Describe active filters as human-readable key-value pairs.
3
3
  */
4
4
 
5
- import type { FilterConfig } from "../types.js";
5
+ import type { FilterConfig } from "../types/index.js";
6
6
 
7
7
  export default function describeFilters(
8
8
  filters: FilterConfig,
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { CommandContext } from "@canonical/cli-core";
11
+ import type { LookupResult } from "./contracts.js";
11
12
 
12
13
  /**
13
14
  * The three output modes every command must support.
@@ -20,6 +21,13 @@ export interface Formatters<T> {
20
21
  readonly json: (data: T) => string;
21
22
  }
22
23
 
24
+ interface RenderLookupResultsOptions<TResult, TInput> {
25
+ readonly ctx: CommandContext;
26
+ readonly result: LookupResult<TResult>;
27
+ readonly formatters: Formatters<TInput>;
28
+ readonly mapResult: (item: TResult) => TInput;
29
+ }
30
+
23
31
  /**
24
32
  * Pick the right formatter based on global flags.
25
33
  *
@@ -33,3 +41,42 @@ export function selectFormatter<T>(
33
41
  if (ctx.globalFlags.llm) return formatters.llm;
34
42
  return formatters.plain;
35
43
  }
44
+
45
+ export function renderLookupResults<TResult, TInput>(
46
+ options: RenderLookupResultsOptions<TResult, TInput>,
47
+ ): string {
48
+ const { ctx, result, formatters, mapResult } = options;
49
+ const formatOne = selectFormatter(ctx, formatters);
50
+
51
+ if (ctx.globalFlags.format === "json") {
52
+ if (result.results.length === 1 && result.errors.length === 0) {
53
+ const only = result.results[0];
54
+ return only
55
+ ? formatters.json(mapResult(only))
56
+ : JSON.stringify({ results: [], errors: result.errors }, null, 2);
57
+ }
58
+
59
+ return JSON.stringify(
60
+ {
61
+ results: result.results.map((item) =>
62
+ JSON.parse(formatters.json(mapResult(item))),
63
+ ),
64
+ errors: result.errors,
65
+ },
66
+ null,
67
+ 2,
68
+ );
69
+ }
70
+
71
+ const parts = result.results.map((item) => formatOne(mapResult(item)));
72
+ if (result.errors.length > 0) {
73
+ parts.push(
74
+ [
75
+ "Errors:",
76
+ ...result.errors.map((error) => `- ${error.query}: ${error.message}`),
77
+ ].join("\n"),
78
+ );
79
+ }
80
+
81
+ return parts.join("\n\n");
82
+ }