toolcraft 0.0.23 → 0.0.24

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 (147) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.compile-check.js +1 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +50 -13
  5. package/dist/error-report.js +32 -3
  6. package/dist/human-in-loop/approval-tasks.d.ts +1 -0
  7. package/dist/human-in-loop/approval-tasks.js +7 -5
  8. package/dist/human-in-loop/approvals-commands.js +51 -8
  9. package/dist/human-in-loop/runner.js +24 -19
  10. package/dist/human-in-loop/state-machine.d.ts +3 -3
  11. package/dist/human-in-loop/state-machine.js +13 -5
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.js +6 -1
  14. package/dist/mcp-proxy.js +85 -19
  15. package/dist/mcp.compile-check.js +1 -0
  16. package/dist/mcp.d.ts +1 -0
  17. package/dist/mcp.js +50 -8
  18. package/dist/renderer.js +119 -13
  19. package/dist/sdk.compile-check.js +1 -0
  20. package/dist/sdk.d.ts +1 -0
  21. package/dist/sdk.js +56 -11
  22. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
  23. package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
  24. package/node_modules/@poe-code/agent-defs/package.json +1 -1
  25. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
  26. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
  27. package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
  28. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
  29. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
  30. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
  31. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
  32. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
  33. package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
  34. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
  35. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
  36. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
  37. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
  38. package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
  39. package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
  40. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
  41. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
  42. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
  43. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
  44. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
  45. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
  46. package/node_modules/@poe-code/config-mutations/package.json +1 -1
  47. package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
  48. package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
  49. package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
  50. package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
  51. package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
  52. package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
  53. package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
  54. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
  55. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
  56. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  57. package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
  58. package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
  59. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
  60. package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
  61. package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
  62. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
  63. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
  64. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
  65. package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
  66. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
  67. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
  68. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
  71. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
  73. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
  75. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
  76. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  77. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
  78. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
  79. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
  80. package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
  81. package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
  82. package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
  83. package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
  84. package/node_modules/@poe-code/design-system/package.json +2 -1
  85. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +244 -110
  86. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +16 -4
  87. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
  88. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +16 -1
  89. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  90. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  91. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
  92. package/node_modules/@poe-code/process-runner/dist/types.d.ts +3 -0
  93. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +57 -0
  94. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +484 -0
  95. package/node_modules/@poe-code/process-runner/package.json +1 -1
  96. package/node_modules/@poe-code/task-list/README.md +0 -2
  97. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
  98. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
  99. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
  100. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
  101. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
  102. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  103. package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
  104. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
  105. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  106. package/node_modules/@poe-code/task-list/dist/index.js +2 -0
  107. package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
  108. package/node_modules/@poe-code/task-list/dist/move.js +215 -0
  109. package/node_modules/@poe-code/task-list/dist/open.js +3 -4
  110. package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
  111. package/node_modules/@poe-code/task-list/dist/state.js +9 -0
  112. package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
  113. package/node_modules/@poe-code/task-list/package.json +1 -2
  114. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  115. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +7 -0
  116. package/node_modules/auth-store/dist/encrypted-file-store.js +69 -7
  117. package/node_modules/auth-store/dist/index.d.ts +1 -1
  118. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  119. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  120. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  121. package/node_modules/auth-store/dist/provider-store.js +55 -7
  122. package/node_modules/auth-store/dist/types.d.ts +3 -1
  123. package/node_modules/auth-store/package.json +2 -1
  124. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
  125. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
  126. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
  127. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
  128. package/node_modules/mcp-oauth/package.json +1 -0
  129. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
  130. package/node_modules/tiny-mcp-client/dist/internal.d.ts +8 -4
  131. package/node_modules/tiny-mcp-client/dist/internal.js +237 -67
  132. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
  133. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
  134. package/node_modules/tiny-mcp-client/package.json +2 -1
  135. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
  136. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
  137. package/node_modules/tiny-mcp-client/src/internal.ts +279 -77
  138. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
  139. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
  140. package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
  141. package/package.json +10 -12
  142. package/node_modules/@poe-code/file-lock/README.md +0 -52
  143. package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
  144. package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
  145. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
  146. package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
  147. package/node_modules/@poe-code/file-lock/package.json +0 -23
@@ -9,10 +9,12 @@ export interface FileSystem {
9
9
  readFile(path: string, encoding: "utf8"): Promise<string>;
10
10
  writeFile(path: string, content: string, options?: {
11
11
  encoding: "utf8";
12
+ flag?: string;
12
13
  }): Promise<void>;
13
14
  mkdir(path: string, options?: {
14
15
  recursive: boolean;
15
16
  }): Promise<void>;
17
+ rename(oldPath: string, newPath: string): Promise<void>;
16
18
  unlink(path: string): Promise<void>;
17
19
  rm?(path: string, options?: {
18
20
  recursive?: boolean;
@@ -21,6 +23,9 @@ export interface FileSystem {
21
23
  stat(path: string): Promise<{
22
24
  mode?: number;
23
25
  }>;
26
+ lstat(path: string): Promise<{
27
+ isSymbolicLink(): boolean;
28
+ }>;
24
29
  readdir(path: string): Promise<string[]>;
25
30
  chmod?(path: string, mode: number): Promise<void>;
26
31
  }
@@ -112,6 +117,11 @@ export interface ChmodMutation extends BaseMutation {
112
117
  export interface BackupMutation extends BaseMutation {
113
118
  kind: "backup";
114
119
  target: ValueResolver<string>;
120
+ once?: boolean;
121
+ }
122
+ export interface RestoreBackupMutation extends BaseMutation {
123
+ kind: "restoreBackup";
124
+ target: ValueResolver<string>;
115
125
  }
116
126
  export interface TemplateWriteMutation extends BaseMutation {
117
127
  kind: "templateWrite";
@@ -131,9 +141,9 @@ export interface TemplateMergeJsonMutation extends BaseMutation {
131
141
  templateId: string;
132
142
  context?: ValueResolver<ConfigObject>;
133
143
  }
134
- export type Mutation = ConfigMergeMutation | ConfigPruneMutation | ConfigTransformMutation | EnsureDirectoryMutation | RemoveDirectoryMutation | RemoveFileMutation | ChmodMutation | BackupMutation | TemplateWriteMutation | TemplateMergeTomlMutation | TemplateMergeJsonMutation;
144
+ export type Mutation = ConfigMergeMutation | ConfigPruneMutation | ConfigTransformMutation | EnsureDirectoryMutation | RemoveDirectoryMutation | RemoveFileMutation | ChmodMutation | BackupMutation | RestoreBackupMutation | TemplateWriteMutation | TemplateMergeTomlMutation | TemplateMergeJsonMutation;
135
145
  export type MutationEffect = "none" | "mkdir" | "write" | "delete" | "chmod" | "copy";
136
- export type MutationDetail = "create" | "update" | "delete" | "noop" | "backup";
146
+ export type MutationDetail = "create" | "update" | "delete" | "noop" | "backup" | "restore";
137
147
  export interface MutationOutcome {
138
148
  changed: boolean;
139
149
  effect: MutationEffect;
@@ -16,7 +16,7 @@
16
16
  }
17
17
  },
18
18
  "scripts": {
19
- "build": "tsc"
19
+ "build": "node ../../scripts/guard-package-dist.mjs && tsc"
20
20
  },
21
21
  "files": [
22
22
  "dist"
@@ -18,7 +18,9 @@ const KIND_COLORS = {
18
18
  other: (text) => color.dim(text)
19
19
  };
20
20
  function colorForKind(kind) {
21
- return KIND_COLORS[kind] ?? ((text) => color.dim(text));
21
+ return Object.prototype.hasOwnProperty.call(KIND_COLORS, kind)
22
+ ? KIND_COLORS[kind]
23
+ : (text) => color.dim(text);
22
24
  }
23
25
  function writeLine(line) {
24
26
  getAcpWriter()(line);
@@ -1,6 +1,6 @@
1
1
  interface BrowserProcess {
2
2
  once(event: "error", listener: (error: Error) => void): this;
3
- once(event: "spawn", listener: () => void): this;
3
+ once(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
4
4
  unref(): void;
5
5
  }
6
6
  type SpawnBrowserProcess = (command: string, args: string[], options: {
@@ -18,7 +18,12 @@ function launchBrowser(command, args, spawnProcess) {
18
18
  return new Promise((resolve, reject) => {
19
19
  const child = spawnProcess(command, args, { detached: true, stdio: "ignore" });
20
20
  child.once("error", reject);
21
- child.once("spawn", () => {
21
+ child.once("close", (code, signal) => {
22
+ if (code !== 0) {
23
+ const reason = code === null ? `signal ${signal ?? "unknown"}` : `code ${code}`;
24
+ reject(new Error(`Browser launcher exited with ${reason}`));
25
+ return;
26
+ }
22
27
  child.unref();
23
28
  resolve();
24
29
  });
@@ -48,6 +48,10 @@ function hexChannel(value, offset) {
48
48
  }
49
49
  function normalizeHex(value) {
50
50
  const normalized = value.startsWith("#") ? value.slice(1) : value;
51
+ if ((normalized.length !== 3 && normalized.length !== 6) ||
52
+ Array.from(normalized).some((char) => !"0123456789abcdefABCDEF".includes(char))) {
53
+ throw new Error(`Invalid hexadecimal color: ${value}`);
54
+ }
51
55
  if (normalized.length === 3) {
52
56
  const red = normalized[0];
53
57
  const green = normalized[1];
@@ -58,14 +62,11 @@ function normalizeHex(value) {
58
62
  Number.parseInt(`${blue}${blue}`, 16)
59
63
  ];
60
64
  }
61
- if (normalized.length === 6) {
62
- return [
63
- hexChannel(normalized, 0),
64
- hexChannel(normalized, 2),
65
- hexChannel(normalized, 4)
66
- ];
67
- }
68
- return [0, 0, 0];
65
+ return [
66
+ hexChannel(normalized, 0),
67
+ hexChannel(normalized, 2),
68
+ hexChannel(normalized, 4)
69
+ ];
69
70
  }
70
71
  function rgbStyle(red, green, blue) {
71
72
  return {
@@ -1,8 +1,9 @@
1
1
  import { typography } from "../tokens/typography.js";
2
2
  import { text } from "./text.js";
3
3
  export function formatCommandNotFound(input) {
4
- const unknown = input.unknownCommand.length > 0
5
- ? input.unknownCommand
4
+ const unknownInput = input.unknownCommand.replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
5
+ const unknown = unknownInput.length > 0
6
+ ? unknownInput
6
7
  : "<command>";
7
8
  return {
8
9
  label: `${typography.bold("Unknown command:")} ${text.command(unknown)}`,
@@ -0,0 +1,22 @@
1
+ import type { ThemePalette } from "../tokens/colors.js";
2
+ export interface DetailCardRow {
3
+ label: string;
4
+ value: string;
5
+ }
6
+ export interface DetailCardSection {
7
+ title?: string;
8
+ rows: DetailCardRow[];
9
+ }
10
+ export interface RenderDetailCardOptions {
11
+ theme: ThemePalette;
12
+ title: string;
13
+ subtitle?: string;
14
+ badges?: string[];
15
+ prose?: Array<{
16
+ title?: string;
17
+ value: string;
18
+ }>;
19
+ sections?: DetailCardSection[];
20
+ width?: number;
21
+ }
22
+ export declare function renderDetailCard(options: RenderDetailCardOptions): string;
@@ -0,0 +1,69 @@
1
+ import { widths } from "../tokens/widths.js";
2
+ function wrap(value, width) {
3
+ const lines = [];
4
+ for (const paragraph of value.split("\n")) {
5
+ let line = "";
6
+ for (const rawWord of paragraph.split(" ")) {
7
+ const words = [];
8
+ let word = rawWord;
9
+ while (word.length > width) {
10
+ words.push(word.slice(0, width));
11
+ word = word.slice(width);
12
+ }
13
+ words.push(word);
14
+ for (const word of words) {
15
+ if (line.length === 0) {
16
+ line = word;
17
+ continue;
18
+ }
19
+ if (`${line} ${word}`.length <= width) {
20
+ line = `${line} ${word}`;
21
+ continue;
22
+ }
23
+ lines.push(line);
24
+ line = word;
25
+ }
26
+ }
27
+ lines.push(line);
28
+ }
29
+ return lines;
30
+ }
31
+ function renderRows(rows, theme, width) {
32
+ if (rows.length === 0)
33
+ return [];
34
+ const labelWidth = Math.max(...rows.map((row) => row.label.length));
35
+ const valueWidth = Math.max(20, width - labelWidth - 2);
36
+ const continuation = " ".repeat(labelWidth + 2);
37
+ return rows.flatMap((row) => {
38
+ const values = wrap(row.value, valueWidth);
39
+ return [
40
+ `${theme.muted(row.label.padEnd(labelWidth))} ${values[0] ?? ""}`,
41
+ ...values.slice(1).map((value) => `${continuation}${value}`),
42
+ ];
43
+ });
44
+ }
45
+ export function renderDetailCard(options) {
46
+ const width = options.width ?? widths.maxLine;
47
+ const identity = [
48
+ options.theme.header(options.title),
49
+ options.subtitle ? options.theme.muted(options.subtitle) : undefined,
50
+ ].filter((value) => value !== undefined).join(" ");
51
+ const hero = options.badges?.length
52
+ ? `${identity}\n${options.theme.muted(options.badges.map((badge) => badge[0] + badge.slice(1).toLowerCase()).join(" · "))}`
53
+ : identity;
54
+ const blocks = [hero];
55
+ for (const prose of options.prose ?? []) {
56
+ blocks.push(prose.title
57
+ ? [options.theme.header(prose.title), wrap(prose.value, width).join("\n")].join("\n")
58
+ : wrap(prose.value, width).join("\n"));
59
+ }
60
+ for (const section of options.sections ?? []) {
61
+ if (section.rows.length === 0)
62
+ continue;
63
+ const rows = renderRows(section.rows, options.theme, width);
64
+ blocks.push(section.title
65
+ ? [options.theme.header(section.title), ...rows].join("\n")
66
+ : rows.join("\n"));
67
+ }
68
+ return blocks.join("\n\n");
69
+ }
@@ -1,16 +1,80 @@
1
1
  import { text } from "./text.js";
2
- function stripAnsi(value) {
3
- let output = "";
4
- for (let index = 0; index < value.length; index += 1)
5
- if (value[index] === "\u001b" && value[index + 1] === "[")
6
- while (index < value.length && value[index] !== "m")
7
- index += 1;
8
- else
9
- output += value[index];
10
- return output;
2
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
3
+ function normalizeInline(value) {
4
+ return value.replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
5
+ }
6
+ function readControlSequence(value, index) {
7
+ if (value[index] !== "\u001b") {
8
+ return undefined;
9
+ }
10
+ if (value[index + 1] === "[") {
11
+ let nextIndex = index + 2;
12
+ while (nextIndex < value.length) {
13
+ const code = value.charCodeAt(nextIndex);
14
+ nextIndex += 1;
15
+ if (code >= 0x40 && code <= 0x7e) {
16
+ return nextIndex;
17
+ }
18
+ }
19
+ return value.length;
20
+ }
21
+ if (value[index + 1] === "]") {
22
+ let nextIndex = index + 2;
23
+ while (nextIndex < value.length) {
24
+ if (value[nextIndex] === "\u0007") {
25
+ return nextIndex + 1;
26
+ }
27
+ if (value[nextIndex] === "\u001b" && value[nextIndex + 1] === "\\") {
28
+ return nextIndex + 2;
29
+ }
30
+ nextIndex += 1;
31
+ }
32
+ return value.length;
33
+ }
34
+ return index + 1;
35
+ }
36
+ function isWideCodePoint(codePoint) {
37
+ return ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
38
+ codePoint === 0x2329 ||
39
+ codePoint === 0x232a ||
40
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
41
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
42
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
43
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
44
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
45
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
46
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
47
+ (codePoint >= 0x2600 && codePoint <= 0x27bf) ||
48
+ (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
49
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd));
50
+ }
51
+ function clusterWidth(cluster) {
52
+ const codePoints = Array.from(cluster).map((char) => char.codePointAt(0) ?? 0);
53
+ if (codePoints.some((codePoint) => codePoint === 0x200d || (codePoint >= 0xfe00 && codePoint <= 0xfe0f))) {
54
+ return 2;
55
+ }
56
+ return codePoints.reduce((width, codePoint) => {
57
+ if (codePoint === 0 || codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) {
58
+ return width;
59
+ }
60
+ return width + (isWideCodePoint(codePoint) ? 2 : 1);
61
+ }, 0);
11
62
  }
12
63
  function visibleWidth(value) {
13
- return stripAnsi(value).length;
64
+ let width = 0;
65
+ let index = 0;
66
+ while (index < value.length) {
67
+ const nextIndex = readControlSequence(value, index);
68
+ if (nextIndex !== undefined) {
69
+ index = nextIndex;
70
+ continue;
71
+ }
72
+ const segment = graphemeSegmenter.segment(value.slice(index))[Symbol.iterator]().next().value;
73
+ const cluster = segment?.segment ?? "";
74
+ width += clusterWidth(cluster);
75
+ index += cluster.length || 1;
76
+ }
77
+ return width;
14
78
  }
15
79
  function clamp(value, min, max) {
16
80
  return Math.min(Math.max(value, min), max);
@@ -61,8 +125,16 @@ function wrapWords(value, width) {
61
125
  lines.push(line);
62
126
  return lines;
63
127
  }
128
+ function validateLayoutValue(value, name) {
129
+ if (!Number.isFinite(value) || value < 0) {
130
+ throw new Error(`${name} must be a finite non-negative number.`);
131
+ }
132
+ }
64
133
  export function formatColumns(opts) {
65
- const { rows } = opts;
134
+ const rows = opts.rows.map((row) => ({
135
+ left: normalizeInline(row.left),
136
+ right: row.right
137
+ }));
66
138
  if (rows.length === 0) {
67
139
  return "";
68
140
  }
@@ -71,6 +143,11 @@ export function formatColumns(opts) {
71
143
  const maxLeftWidth = opts.maxLeftWidth ?? 32;
72
144
  const gap = opts.gap ?? 3;
73
145
  const indent = opts.indent ?? 2;
146
+ validateLayoutValue(totalWidth, "totalWidth");
147
+ validateLayoutValue(minLeftWidth, "minLeftWidth");
148
+ validateLayoutValue(maxLeftWidth, "maxLeftWidth");
149
+ validateLayoutValue(gap, "gap");
150
+ validateLayoutValue(indent, "indent");
74
151
  const maxLeftContentWidth = Math.max(...rows.map((row) => visibleWidth(row.left)));
75
152
  const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
76
153
  const rightWidth = Math.max(20, totalWidth - leftWidth - indent);
@@ -10,5 +10,5 @@ export { formatCommandNotFound } from "./command-errors.js";
10
10
  export { formatCommandNotFoundPanel } from "./command-errors.js";
11
11
  export { renderTable } from "./table.js";
12
12
  export type { TableColumn, RenderTableOptions } from "./table.js";
13
- export { renderTemplate } from "./template.js";
13
+ export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
14
14
  export type { RenderTemplateOptions, TemplateEscape } from "./template.js";
@@ -6,4 +6,4 @@ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption,
6
6
  export { formatCommandNotFound } from "./command-errors.js";
7
7
  export { formatCommandNotFoundPanel } from "./command-errors.js";
8
8
  export { renderTable } from "./table.js";
9
- export { renderTemplate } from "./template.js";
9
+ export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
@@ -9,5 +9,7 @@ export interface RenderTableOptions {
9
9
  theme: ThemePalette;
10
10
  columns: TableColumn[];
11
11
  rows: Record<string, string>[];
12
+ variant?: "table" | "detail";
13
+ maxWidth?: number;
12
14
  }
13
15
  export declare function renderTable(options: RenderTableOptions): string;
@@ -1,8 +1,19 @@
1
+ import { widths } from "../tokens/widths.js";
1
2
  import { resolveOutputFormat } from "../internal/output-format.js";
2
3
  import { stripAnsi } from "../internal/strip-ansi.js";
3
4
  const reset = "\x1b[0m";
4
5
  const ellipsis = "…";
5
6
  const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
7
+ function getCell(row, name) {
8
+ return Object.prototype.hasOwnProperty.call(row, name) ? row[name] ?? "" : "";
9
+ }
10
+ function renderMarkdownCell(value) {
11
+ return stripAnsi(value)
12
+ .replaceAll("\r\n", " ")
13
+ .replaceAll("\n", " ")
14
+ .replaceAll("\r", " ")
15
+ .replaceAll("|", "\\|");
16
+ }
6
17
  function isAnsiSequence(value, index) {
7
18
  return value[index] === "\u001b" && value[index + 1] === "[";
8
19
  }
@@ -135,6 +146,9 @@ function getAlignment(column) {
135
146
  return alignment === "right" || alignment === "center" ? alignment : "left";
136
147
  }
137
148
  function getColumnWidth(column) {
149
+ if (!Number.isFinite(column.maxLen) || column.maxLen <= 0) {
150
+ throw new Error("maxLen must be a positive finite number.");
151
+ }
138
152
  const configuredMin = column.minLen;
139
153
  const minWidth = Math.max(1, configuredMin ?? 1);
140
154
  return Math.max(minWidth, column.maxLen);
@@ -165,9 +179,72 @@ function renderTerminalRow(values, columns, theme) {
165
179
  });
166
180
  return `${vertical}${cells.join(vertical)}${vertical}`;
167
181
  }
182
+ function wrapDetailValue(value, width) {
183
+ const lines = [];
184
+ for (const paragraph of value.split("\n")) {
185
+ let line = "";
186
+ for (const rawWord of paragraph.split(" ")) {
187
+ const words = [];
188
+ let word = rawWord;
189
+ while (displayWidth(word) > width) {
190
+ let chunk = "";
191
+ let index = 0;
192
+ while (index < word.length) {
193
+ const cluster = readPrintableCluster(word, index);
194
+ if (displayWidth(`${chunk}${cluster}`) > width) {
195
+ break;
196
+ }
197
+ chunk += cluster;
198
+ index += cluster.length;
199
+ }
200
+ words.push(chunk);
201
+ word = word.slice(chunk.length);
202
+ }
203
+ words.push(word);
204
+ for (const word of words) {
205
+ if (line.length === 0) {
206
+ line = word;
207
+ continue;
208
+ }
209
+ if (displayWidth(`${line} ${word}`) <= width) {
210
+ line = `${line} ${word}`;
211
+ continue;
212
+ }
213
+ lines.push(line);
214
+ line = word;
215
+ }
216
+ }
217
+ lines.push(line);
218
+ }
219
+ return lines.length > 0 ? lines : [""];
220
+ }
168
221
  function renderTableTerminal(options) {
169
222
  const { theme, columns, rows } = options;
170
223
  const computedColumns = computeColumns(columns);
224
+ if (options.variant === "detail") {
225
+ const labelColumn = computedColumns[0];
226
+ const valueColumn = computedColumns[1];
227
+ if (!labelColumn || !valueColumn) {
228
+ return "";
229
+ }
230
+ const detailLabelWidth = widths.helpColumn + 12;
231
+ const labelWidth = Math.min(labelColumn.width, detailLabelWidth);
232
+ const valueWidth = Math.max(20, (options.maxWidth ?? widths.maxLine) - labelWidth - 2);
233
+ const continuation = " ".repeat(labelWidth + 2);
234
+ return rows
235
+ .flatMap((row) => {
236
+ const label = truncateToWidth(getCell(row, labelColumn.name), labelWidth);
237
+ const values = wrapDetailValue(getCell(row, valueColumn.name), valueWidth);
238
+ if (values.length === 1 && values[0] === "") {
239
+ return [theme.header(label)];
240
+ }
241
+ return [
242
+ `${theme.muted(padCell(label, labelWidth, "left"))} ${values[0] ?? ""}`,
243
+ ...values.slice(1).map((value) => `${continuation}${value}`)
244
+ ];
245
+ })
246
+ .join("\n");
247
+ }
171
248
  const separatorOptions = options;
172
249
  const includeRowSeparators = separatorOptions.rowSeparator === true || separatorOptions.rowSeparators === true;
173
250
  const top = renderBorder(computedColumns, theme, { left: "┌", mid: "┬", right: "┐" });
@@ -179,13 +256,13 @@ function renderTableTerminal(options) {
179
256
  if (includeRowSeparators && index > 0) {
180
257
  renderedRows.push(headerBottom);
181
258
  }
182
- renderedRows.push(renderTerminalRow(computedColumns.map((column) => row[column.name] ?? ""), computedColumns, theme));
259
+ renderedRows.push(renderTerminalRow(computedColumns.map((column) => getCell(row, column.name)), computedColumns, theme));
183
260
  }
184
261
  return [top, header, headerBottom, ...renderedRows, bottom].join("\n");
185
262
  }
186
263
  function renderTableMarkdown(options) {
187
264
  const { columns, rows } = options;
188
- const header = `| ${columns.map((c) => c.title).join(" | ")} |`;
265
+ const header = `| ${columns.map((c) => renderMarkdownCell(c.title)).join(" | ")} |`;
189
266
  const separator = `| ${columns
190
267
  .map((c) => {
191
268
  const alignment = getAlignment(c);
@@ -198,15 +275,15 @@ function renderTableMarkdown(options) {
198
275
  return ":---";
199
276
  })
200
277
  .join(" | ")} |`;
201
- const dataRows = rows.map((row) => `| ${columns.map((c) => stripAnsi(row[c.name] ?? "").replace(/\|/g, "\\|")).join(" | ")} |`);
278
+ const dataRows = rows.map((row) => `| ${columns.map((c) => renderMarkdownCell(getCell(row, c.name))).join(" | ")} |`);
202
279
  return [header, separator, ...dataRows].join("\n");
203
280
  }
204
281
  function renderTableJson(options) {
205
282
  const { columns, rows } = options;
206
283
  const cleaned = rows.map((row) => {
207
- const obj = {};
284
+ const obj = Object.create(null);
208
285
  for (const col of columns) {
209
- obj[col.name] = stripAnsi(row[col.name] ?? "");
286
+ obj[col.name] = stripAnsi(getCell(row, col.name));
210
287
  }
211
288
  return obj;
212
289
  });
@@ -1,6 +1,10 @@
1
1
  export type TemplateEscape = "html" | "none";
2
2
  export interface RenderTemplateOptions {
3
3
  escape?: TemplateEscape;
4
+ partials?: Record<string, string>;
5
+ validate?: boolean;
4
6
  yield?: string;
5
7
  }
6
8
  export declare function renderTemplate(template: string, view: Record<string, unknown>, options?: RenderTemplateOptions): string;
9
+ export declare function getTemplatePartialNames(template: string): string[];
10
+ export declare function resolveTemplatePartials(template: string, partials: Record<string, string>): string;