toolcraft-openapi 0.0.39 → 0.0.41

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 (62) hide show
  1. package/README.md +67 -0
  2. package/dist/bin/generate.d.ts +9 -0
  3. package/dist/bin/generate.js +122 -6
  4. package/dist/config.d.ts +53 -0
  5. package/dist/config.js +266 -0
  6. package/dist/define-client.js +4 -0
  7. package/dist/diagnose.d.ts +4 -0
  8. package/dist/diagnose.js +93 -0
  9. package/dist/diagnostics.d.ts +19 -0
  10. package/dist/diagnostics.js +19 -0
  11. package/dist/generate.d.ts +13 -1
  12. package/dist/generate.js +107 -4
  13. package/dist/http.d.ts +17 -26
  14. package/dist/http.js +99 -41
  15. package/dist/index.d.ts +6 -1
  16. package/dist/index.js +4 -1
  17. package/dist/runtime.d.ts +7 -0
  18. package/dist/runtime.js +19 -1
  19. package/node_modules/@poe-code/frontmatter/README.md +35 -0
  20. package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +8 -0
  21. package/node_modules/@poe-code/frontmatter/dist/fences.js +72 -0
  22. package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
  23. package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
  24. package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
  25. package/node_modules/@poe-code/frontmatter/dist/parse.js +137 -0
  26. package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
  27. package/node_modules/@poe-code/frontmatter/dist/stringify.js +35 -0
  28. package/node_modules/@poe-code/frontmatter/package.json +25 -0
  29. package/node_modules/toolcraft-design/README.md +12 -0
  30. package/node_modules/toolcraft-design/dist/prompts/index.d.ts +39 -14
  31. package/node_modules/toolcraft-design/dist/prompts/index.js +10 -6
  32. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
  33. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
  34. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
  35. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
  36. package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
  37. package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
  38. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
  39. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
  40. package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
  41. package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
  42. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
  43. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
  44. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
  45. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
  46. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
  47. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
  48. package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
  49. package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
  50. package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
  51. package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
  52. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
  53. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
  54. package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
  55. package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
  56. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
  57. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
  58. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +1 -1
  59. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +1 -1
  60. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +20 -453
  61. package/node_modules/toolcraft-design/package.json +6 -3
  62. package/package.json +7 -7
package/dist/index.d.ts CHANGED
@@ -2,12 +2,17 @@ export { defineApiCommand } from "./api-command.js";
2
2
  export { defineClient } from "./define-client.js";
3
3
  export { generate } from "./generate.js";
4
4
  export type { GenerateOptions, GeneratedFile, OpenApiDocument } from "./generate.js";
5
+ export { SUPPORTED_TOOLCRAFT_EDITION, mergeToolcraftConfig, readToolcraftConfig, validateToolcraftConfig } from "./config.js";
6
+ export type { ToolcraftConfig, ToolcraftMethodConfig, ToolcraftResourceConfig } from "./config.js";
7
+ export { diagnose } from "./diagnose.js";
8
+ export { DIAGNOSTIC_CODES, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
9
+ export type { Diagnostic, DiagnosticSeverity } from "./diagnostics.js";
5
10
  export { inspectOpenApiDocument } from "./inspect.js";
6
11
  export type { OpenApiInspectionOperation, OpenApiInspectionReport } from "./inspect.js";
7
12
  export { inspectOpenApiSource } from "./inspect-source.js";
8
13
  export type { InspectOpenApiSourceOptions, OpenApiInspectionSource } from "./inspect-source.js";
9
14
  export { renderOpenApiInspection } from "./render-inspection.js";
10
- export { commandsFromSpec, defineClientFromSpec } from "./runtime.js";
15
+ export { commandsFromSpec, defineClientFromSpec, resolveOpenApiBaseUrl } from "./runtime.js";
11
16
  export type { CommandsFromSpecOptions, DefineClientFromSpecOptions, OpenApiDocumentSource } from "./runtime.js";
12
17
  export type { DefineClientOptions, DefinedClient, OpenApiClientServices } from "./define-client.js";
13
18
  export type { AuthProvider, CommandContributor, TokenSource } from "./auth/types.js";
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  export { defineApiCommand } from "./api-command.js";
2
2
  export { defineClient } from "./define-client.js";
3
3
  export { generate } from "./generate.js";
4
+ export { SUPPORTED_TOOLCRAFT_EDITION, mergeToolcraftConfig, readToolcraftConfig, validateToolcraftConfig } from "./config.js";
5
+ export { diagnose } from "./diagnose.js";
6
+ export { DIAGNOSTIC_CODES, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
4
7
  export { inspectOpenApiDocument } from "./inspect.js";
5
8
  export { inspectOpenApiSource } from "./inspect-source.js";
6
9
  export { renderOpenApiInspection } from "./render-inspection.js";
7
- export { commandsFromSpec, defineClientFromSpec } from "./runtime.js";
10
+ export { commandsFromSpec, defineClientFromSpec, resolveOpenApiBaseUrl } from "./runtime.js";
8
11
  export { bearerTokenAuth } from "./auth/bearer-token-auth.js";
9
12
  export { HttpError, prepareMultipartFileInputs, requestJson, writeBinaryResponseOutput } from "./http.js";
package/dist/runtime.d.ts CHANGED
@@ -9,5 +9,12 @@ export interface CommandsFromSpecOptions {
9
9
  fs?: OpenApiSourceFileSystem;
10
10
  }
11
11
  export type DefineClientFromSpecOptions<TServices extends object = Record<string, never>> = Omit<DefineClientOptions<TServices>, "commands"> & CommandsFromSpecOptions;
12
+ export interface ResolveOpenApiBaseUrlOptions {
13
+ document: OpenApiDocument;
14
+ environments?: Record<string, string>;
15
+ environment?: string;
16
+ env?: Record<string, string | undefined>;
17
+ }
12
18
  export declare function commandsFromSpec(source: OpenApiDocumentSource, options?: CommandsFromSpecOptions): Promise<CommandNode<OpenApiClientServices>[]>;
13
19
  export declare function defineClientFromSpec<TServices extends object = Record<string, never>>(spec: OpenApiDocumentSource, options: DefineClientFromSpecOptions<TServices>): Promise<DefinedClient<TServices>>;
20
+ export declare function resolveOpenApiBaseUrl(options: ResolveOpenApiBaseUrlOptions): string | undefined;
package/dist/runtime.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
- import { defineCommand, defineGroup, S } from "toolcraft";
2
+ import { UserError, defineCommand, defineGroup, S } from "toolcraft";
3
3
  import { defineClient } from "./define-client.js";
4
4
  import { collectSchemaOptionEntries, collectGeneratedCommands } from "./generate.js";
5
5
  import { groupByNoun } from "./group-by-noun.js";
@@ -31,6 +31,24 @@ async function resolveDocument(source, options) {
31
31
  });
32
32
  return parseOpenApiDocument(sourceText, source);
33
33
  }
34
+ export function resolveOpenApiBaseUrl(options) {
35
+ const environments = options.environments;
36
+ if (environments !== undefined && Object.keys(environments).length > 0) {
37
+ const selectedName = options.environment ?? options.env?.TOOLCRAFT_OPENAPI_ENV ?? Object.keys(environments)[0];
38
+ const selectedUrl = selectedName === undefined ? undefined : environments[selectedName];
39
+ if (selectedUrl !== undefined) {
40
+ return normalizeBaseUrl(selectedUrl);
41
+ }
42
+ if (selectedName !== undefined) {
43
+ throw new UserError(`Unknown OpenAPI environment ${JSON.stringify(selectedName)}. Available: ${Object.keys(environments).join(", ")}.`);
44
+ }
45
+ }
46
+ const server = options.document.servers?.[0]?.url;
47
+ return server === undefined ? undefined : normalizeBaseUrl(server);
48
+ }
49
+ function normalizeBaseUrl(value) {
50
+ return new URL(value).toString().replace(/\/$/, "");
51
+ }
34
52
  function createRuntimeGroups(commands) {
35
53
  return groupByNoun(commands).map(({ noun, commands: nounCommands }) => defineGroup({
36
54
  name: noun,
@@ -0,0 +1,35 @@
1
+ # @poe-code/frontmatter
2
+
3
+ Shared YAML frontmatter parsing for poe-code packages.
4
+
5
+ ## API
6
+
7
+ ```ts
8
+ import {
9
+ FrontmatterParseError,
10
+ parseFrontmatter,
11
+ parseFrontmatterDocument,
12
+ stringifyFrontmatter
13
+ } from "@poe-code/frontmatter";
14
+ ```
15
+
16
+ - `parseFrontmatter(source)` returns `{ frontmatter, body }`.
17
+ - `parseFrontmatterDocument(source)` returns `{ frontmatter, body, errors, lineCounter }` for callers that need YAML diagnostics.
18
+ - `stringifyFrontmatter(frontmatter, body)` writes `---` fences, YAML, and the body.
19
+ - `FrontmatterParseError` is thrown for malformed frontmatter, invalid YAML, non-object frontmatter, and stringify failures.
20
+
21
+ When no leading frontmatter block exists, parsing returns `{ frontmatter: {}, body: source }`.
22
+ The returned `body` is sliced from the original input and is otherwise byte-for-byte unchanged.
23
+ YAML mappings that contain `__proto__` keep it as an own property without changing the object's prototype.
24
+
25
+ Duplicate mapping keys use YAML's last-wins behavior by default, matching the
26
+ legacy frontmatter readers. Callers that need strict YAML mappings can pass
27
+ `{ uniqueKeys: true }` to `parseFrontmatter` or `parseFrontmatterDocument`.
28
+
29
+ ## Environment
30
+
31
+ This package reads no environment variables.
32
+
33
+ ## Config
34
+
35
+ This package has no configuration options.
@@ -0,0 +1,8 @@
1
+ export type FrontmatterBlock = {
2
+ raw: string;
3
+ body: string;
4
+ };
5
+ export type SplitFrontmatterResult = FrontmatterBlock | {
6
+ body: string;
7
+ };
8
+ export declare function splitFrontmatterBlock(source: string): SplitFrontmatterResult;
@@ -0,0 +1,72 @@
1
+ export function splitFrontmatterBlock(source) {
2
+ const content = source.startsWith("\uFEFF") ? source.slice(1) : source;
3
+ const opening = readOpeningFence(content);
4
+ if (opening === undefined) {
5
+ return { body: source };
6
+ }
7
+ const closing = findClosingFence(content, opening.next);
8
+ if (closing === undefined) {
9
+ throw new Error("Missing YAML frontmatter end delimiter (---).");
10
+ }
11
+ return {
12
+ raw: content.slice(opening.next, closing.index),
13
+ body: content.slice(closing.end + closing.lineBreakLength)
14
+ };
15
+ }
16
+ function readOpeningFence(source) {
17
+ if (!source.startsWith("---")) {
18
+ return undefined;
19
+ }
20
+ const lineEnd = findLineEnd(source, 0);
21
+ if (lineEnd.lineBreakLength === 0 || source.slice(0, lineEnd.index) !== "---") {
22
+ return undefined;
23
+ }
24
+ return { next: lineEnd.index + lineEnd.lineBreakLength };
25
+ }
26
+ function findClosingFence(source, start) {
27
+ let lineStart = start;
28
+ while (lineStart <= source.length) {
29
+ const lineEnd = findLineEnd(source, lineStart);
30
+ if (isClosingFenceLine(source.slice(lineStart, lineEnd.index))) {
31
+ return {
32
+ index: lineStart,
33
+ end: lineEnd.index,
34
+ lineBreakLength: lineEnd.lineBreakLength
35
+ };
36
+ }
37
+ if (lineEnd.lineBreakLength === 0) {
38
+ break;
39
+ }
40
+ lineStart = lineEnd.index + lineEnd.lineBreakLength;
41
+ }
42
+ return undefined;
43
+ }
44
+ function isClosingFenceLine(line) {
45
+ if (!line.startsWith("---")) {
46
+ return false;
47
+ }
48
+ for (let index = 3; index < line.length; index += 1) {
49
+ const character = line[index];
50
+ if (character !== " " && character !== "\t") {
51
+ return false;
52
+ }
53
+ }
54
+ return true;
55
+ }
56
+ function findLineEnd(source, start) {
57
+ let index = start;
58
+ while (index < source.length) {
59
+ const character = source[index];
60
+ if (character === "\n") {
61
+ return { index, lineBreakLength: 1 };
62
+ }
63
+ if (character === "\r") {
64
+ return {
65
+ index,
66
+ lineBreakLength: source[index + 1] === "\n" ? 2 : 1
67
+ };
68
+ }
69
+ index += 1;
70
+ }
71
+ return { index: source.length, lineBreakLength: 0 };
72
+ }
@@ -0,0 +1,3 @@
1
+ export { splitFrontmatterBlock, type FrontmatterBlock, type SplitFrontmatterResult } from "./fences.js";
2
+ export { FrontmatterParseError, parseFrontmatter, parseFrontmatterDocument, type ParsedFrontmatter, type ParsedFrontmatterDocument, type ParseFrontmatterOptions } from "./parse.js";
3
+ export { stringifyFrontmatter } from "./stringify.js";
@@ -0,0 +1,3 @@
1
+ export { splitFrontmatterBlock } from "./fences.js";
2
+ export { FrontmatterParseError, parseFrontmatter, parseFrontmatterDocument } from "./parse.js";
3
+ export { stringifyFrontmatter } from "./stringify.js";
@@ -0,0 +1,20 @@
1
+ import { LineCounter } from "yaml";
2
+ export interface ParsedFrontmatter {
3
+ frontmatter: Record<string, unknown>;
4
+ body: string;
5
+ }
6
+ export interface ParsedFrontmatterDocument extends ParsedFrontmatter {
7
+ errors: readonly {
8
+ message: string;
9
+ pos?: [number, number];
10
+ }[];
11
+ lineCounter: LineCounter;
12
+ }
13
+ export interface ParseFrontmatterOptions {
14
+ uniqueKeys?: boolean;
15
+ }
16
+ export declare class FrontmatterParseError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ export declare function parseFrontmatter(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatter;
20
+ export declare function parseFrontmatterDocument(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatterDocument;
@@ -0,0 +1,137 @@
1
+ import { LineCounter, parse, parseDocument } from "yaml";
2
+ import { splitFrontmatterBlock } from "./fences.js";
3
+ export class FrontmatterParseError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "FrontmatterParseError";
7
+ }
8
+ }
9
+ export function parseFrontmatter(source, options = {}) {
10
+ const split = splitFrontmatter(source);
11
+ if (split.raw === undefined) {
12
+ return {
13
+ frontmatter: {},
14
+ body: split.body
15
+ };
16
+ }
17
+ return {
18
+ frontmatter: parseYamlFrontmatter(split.raw, options),
19
+ body: split.body
20
+ };
21
+ }
22
+ export function parseFrontmatterDocument(source, options = {}) {
23
+ const split = splitFrontmatter(source);
24
+ const lineCounter = new LineCounter();
25
+ if (split.raw === undefined) {
26
+ return {
27
+ frontmatter: {},
28
+ body: split.body,
29
+ errors: [],
30
+ lineCounter
31
+ };
32
+ }
33
+ const document = parseDocument(normalizeYamlLineEndings(split.raw), {
34
+ lineCounter,
35
+ prettyErrors: false,
36
+ uniqueKeys: options.uniqueKeys ?? false
37
+ });
38
+ const errors = document.errors.map((error) => ({
39
+ message: error.message,
40
+ ...(error.pos === undefined ? {} : { pos: error.pos })
41
+ }));
42
+ if (errors.length > 0) {
43
+ return {
44
+ frontmatter: {},
45
+ body: split.body,
46
+ errors,
47
+ lineCounter
48
+ };
49
+ }
50
+ return {
51
+ frontmatter: normalizeYamlFrontmatter(document.toJSON()),
52
+ body: split.body,
53
+ errors,
54
+ lineCounter
55
+ };
56
+ }
57
+ function splitFrontmatter(source) {
58
+ try {
59
+ return splitFrontmatterBlock(source);
60
+ }
61
+ catch (error) {
62
+ if (error instanceof Error) {
63
+ throw new FrontmatterParseError(error.message);
64
+ }
65
+ throw error;
66
+ }
67
+ }
68
+ function parseYamlFrontmatter(yamlBlock, options) {
69
+ let parsed;
70
+ try {
71
+ parsed = parse(normalizeYamlLineEndings(yamlBlock), {
72
+ uniqueKeys: options.uniqueKeys ?? false
73
+ });
74
+ }
75
+ catch (error) {
76
+ const message = error instanceof Error ? error.message : "Unknown YAML parse error";
77
+ throw new FrontmatterParseError(`Invalid YAML frontmatter: ${message}`);
78
+ }
79
+ return normalizeYamlFrontmatter(parsed);
80
+ }
81
+ function normalizeYamlLineEndings(value) {
82
+ if (!value.includes("\r")) {
83
+ return value;
84
+ }
85
+ let normalized = "";
86
+ for (let index = 0; index < value.length; index += 1) {
87
+ const character = value[index];
88
+ if (character !== "\r") {
89
+ normalized += character;
90
+ continue;
91
+ }
92
+ if (value[index + 1] === "\n") {
93
+ normalized += "\r\n";
94
+ index += 1;
95
+ continue;
96
+ }
97
+ normalized += "\n";
98
+ }
99
+ return normalized;
100
+ }
101
+ function normalizeYamlFrontmatter(value) {
102
+ if (value === null || value === undefined) {
103
+ return {};
104
+ }
105
+ if (!isRecord(value)) {
106
+ throw new FrontmatterParseError("YAML frontmatter must parse to an object.");
107
+ }
108
+ return normalizeYamlValue(value);
109
+ }
110
+ function normalizeYamlValue(value) {
111
+ if (Array.isArray(value)) {
112
+ return value.map((item) => normalizeYamlValue(item));
113
+ }
114
+ if (!isPlainRecord(value)) {
115
+ return value;
116
+ }
117
+ const normalized = {};
118
+ for (const [key, entryValue] of Object.entries(value)) {
119
+ Object.defineProperty(normalized, key, {
120
+ configurable: true,
121
+ enumerable: true,
122
+ value: normalizeYamlValue(entryValue),
123
+ writable: true
124
+ });
125
+ }
126
+ return normalized;
127
+ }
128
+ function isRecord(value) {
129
+ return typeof value === "object" && value !== null && !Array.isArray(value);
130
+ }
131
+ function isPlainRecord(value) {
132
+ if (!isRecord(value)) {
133
+ return false;
134
+ }
135
+ const prototype = Object.getPrototypeOf(value);
136
+ return prototype === Object.prototype || prototype === null;
137
+ }
@@ -0,0 +1 @@
1
+ export declare function stringifyFrontmatter(frontmatter: Record<string, unknown>, body: string): string;
@@ -0,0 +1,35 @@
1
+ import { stringify } from "yaml";
2
+ import { FrontmatterParseError } from "./parse.js";
3
+ export function stringifyFrontmatter(frontmatter, body) {
4
+ try {
5
+ assertAcyclic(frontmatter);
6
+ return `---\n${stringify(frontmatter, { aliasDuplicateObjects: false }).trimEnd()}\n---\n${body}`;
7
+ }
8
+ catch (error) {
9
+ if (error instanceof FrontmatterParseError) {
10
+ throw error;
11
+ }
12
+ const message = error instanceof Error ? error.message : "Unknown YAML stringify error";
13
+ throw new FrontmatterParseError(`Invalid YAML frontmatter: ${message}`);
14
+ }
15
+ }
16
+ function assertAcyclic(value, seen = new WeakSet()) {
17
+ if (typeof value !== "object" || value === null) {
18
+ return;
19
+ }
20
+ if (seen.has(value)) {
21
+ throw new FrontmatterParseError("Cannot stringify cyclic frontmatter.");
22
+ }
23
+ seen.add(value);
24
+ if (Array.isArray(value)) {
25
+ for (const item of value) {
26
+ assertAcyclic(item, seen);
27
+ }
28
+ }
29
+ else {
30
+ for (const item of Object.values(value)) {
31
+ assertAcyclic(item, seen);
32
+ }
33
+ }
34
+ seen.delete(value);
35
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@poe-code/frontmatter",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "node ../../scripts/guard-package-dist.mjs && tsc",
16
+ "test": "cd ../.. && vitest run packages/frontmatter/src",
17
+ "test:unit": "cd ../.. && vitest run packages/frontmatter/src"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "dependencies": {
23
+ "yaml": "^2.8.2"
24
+ }
25
+ }
@@ -139,6 +139,18 @@ The registry key and `Brand.name` should match. Brand primary colors are CSS-sty
139
139
  - `VSCODE_COLOR_THEME_KIND`: automatic VS Code theme hint used after the macOS hint.
140
140
  - `COLORFGBG`: terminal foreground/background hint used after the macOS and VS Code hints. Rendering defaults to dark when no hint resolves a mode.
141
141
 
142
+ ### Prompts
143
+
144
+ - `POE_NO_PROMPT`: when set to `1` in non-TTY contexts, `confirm`, `select`, and `multiselect` accept their default or initial values instead of throwing an interactive TTY error.
145
+
146
+ ### Node 18 Smoke
147
+
148
+ After building the package, verify the prompt entrypoint under Node 18 with:
149
+
150
+ ```sh
151
+ nvm exec 18.18 node packages/toolcraft-design/scripts/check-node18.mjs
152
+ ```
153
+
142
154
  ### Output and Color
143
155
 
144
156
  - `OUTPUT_FORMAT`: selects `terminal`, `markdown`, or `json` output for components that use the output-format resolver. Invalid or missing values default to `terminal`.
@@ -1,4 +1,5 @@
1
- import * as clack from "@clack/prompts";
1
+ import type { CANCEL } from "./interactive/cancel-symbol.js";
2
+ import { type SelectOption } from "./interactive/select.js";
2
3
  import { cancel, isCancel } from "./primitives/cancel.js";
3
4
  import { intro } from "./primitives/intro.js";
4
5
  import { log } from "./primitives/log.js";
@@ -10,15 +11,24 @@ export { intro, outro, note, spinner };
10
11
  export declare function introPlain(title: string): void;
11
12
  export interface SelectOptions<Value> {
12
13
  message: string;
13
- options: Array<{
14
- value: Value;
15
- label: string;
16
- hint?: string;
17
- }>;
14
+ options: Array<SelectOption<Value>>;
18
15
  initialValue?: Value;
16
+ maxItems?: number;
17
+ signal?: AbortSignal;
18
+ input?: NodeJS.ReadableStream;
19
+ output?: NodeJS.WritableStream;
20
+ }
21
+ export declare function select<Value>(opts: SelectOptions<Value>): Promise<Value | typeof CANCEL>;
22
+ export interface MultiselectOptions<Value> {
23
+ message: string;
24
+ options: Array<SelectOption<Value>>;
25
+ initialValues?: Value[];
26
+ required?: boolean;
27
+ maxItems?: number;
28
+ signal?: AbortSignal;
29
+ input?: NodeJS.ReadableStream;
30
+ output?: NodeJS.WritableStream;
19
31
  }
20
- export declare function select<Value>(opts: SelectOptions<Value>): Promise<Value | symbol>;
21
- export type MultiselectOptions<Value> = Parameters<typeof clack.multiselect<Value>>[0];
22
32
  /**
23
33
  * Prompts the user to select one or more values from a list.
24
34
  *
@@ -35,23 +45,38 @@ export type MultiselectOptions<Value> = Parameters<typeof clack.multiselect<Valu
35
45
  * // result is Value[]
36
46
  * }
37
47
  */
38
- export declare function multiselect<Value>(opts: MultiselectOptions<Value>): Promise<Value[] | symbol>;
39
- export type TextOptions = Parameters<typeof clack.text>[0];
40
- export declare function text(opts: TextOptions): Promise<string | symbol>;
48
+ export declare function multiselect<Value>(opts: MultiselectOptions<Value>): Promise<Value[] | typeof CANCEL>;
49
+ export interface TextOptions {
50
+ message: string;
51
+ placeholder?: string;
52
+ defaultValue?: string;
53
+ initialValue?: string;
54
+ validate?: (value: string) => string | Error | undefined;
55
+ signal?: AbortSignal;
56
+ input?: NodeJS.ReadableStream;
57
+ output?: NodeJS.WritableStream;
58
+ }
59
+ export declare function text(opts: TextOptions): Promise<string | typeof CANCEL>;
41
60
  export interface ConfirmOptions {
42
61
  message: string;
43
62
  initialValue?: boolean;
63
+ signal?: AbortSignal;
64
+ input?: NodeJS.ReadableStream;
65
+ output?: NodeJS.WritableStream;
44
66
  }
45
- export declare function confirm(opts: ConfirmOptions): Promise<boolean | symbol>;
67
+ export declare function confirm(opts: ConfirmOptions): Promise<boolean | typeof CANCEL>;
46
68
  export declare class PromptCancelledError extends Error {
47
69
  constructor(message?: string);
48
70
  }
49
71
  export declare function confirmOrCancel(opts: ConfirmOptions): Promise<boolean>;
50
72
  export interface PasswordOptions {
51
73
  message: string;
52
- validate?: (value: string) => string | undefined;
74
+ validate?: (value: string) => string | Error | undefined;
75
+ signal?: AbortSignal;
76
+ input?: NodeJS.ReadableStream;
77
+ output?: NodeJS.WritableStream;
53
78
  }
54
- export declare function password(opts: PasswordOptions): Promise<string | symbol>;
79
+ export declare function password(opts: PasswordOptions): Promise<string | typeof CANCEL>;
55
80
  export type SpinnerOptions = {
56
81
  start: (message?: string) => void;
57
82
  stop: (message?: string, code?: number) => void;
@@ -1,7 +1,11 @@
1
1
  import { color } from "../components/color.js";
2
- import * as clack from "@clack/prompts";
3
2
  import { resolveOutputFormat } from "../internal/output-format.js";
4
3
  import { stripAnsi } from "../internal/strip-ansi.js";
4
+ import { confirmPrompt } from "./interactive/confirm.js";
5
+ import { multiselectPrompt } from "./interactive/multiselect.js";
6
+ import { passwordPrompt } from "./interactive/password.js";
7
+ import { selectPrompt } from "./interactive/select.js";
8
+ import { textPrompt } from "./interactive/text.js";
5
9
  import { cancel, isCancel } from "./primitives/cancel.js";
6
10
  import { intro } from "./primitives/intro.js";
7
11
  import { log } from "./primitives/log.js";
@@ -22,7 +26,7 @@ export function introPlain(title) {
22
26
  process.stdout.write(`${color.gray("┌")} ${title}\n`);
23
27
  }
24
28
  export async function select(opts) {
25
- return clack.select(opts);
29
+ return selectPrompt(opts);
26
30
  }
27
31
  /**
28
32
  * Prompts the user to select one or more values from a list.
@@ -41,13 +45,13 @@ export async function select(opts) {
41
45
  * }
42
46
  */
43
47
  export async function multiselect(opts) {
44
- return clack.multiselect(opts);
48
+ return multiselectPrompt(opts);
45
49
  }
46
50
  export async function text(opts) {
47
- return clack.text(opts);
51
+ return textPrompt(opts);
48
52
  }
49
53
  export async function confirm(opts) {
50
- return clack.confirm(opts);
54
+ return confirmPrompt(opts);
51
55
  }
52
56
  export class PromptCancelledError extends Error {
53
57
  constructor(message = "Operation cancelled.") {
@@ -67,7 +71,7 @@ export async function confirmOrCancel(opts) {
67
71
  return result === true;
68
72
  }
69
73
  export async function password(opts) {
70
- return clack.password(opts);
74
+ return passwordPrompt(opts);
71
75
  }
72
76
  function formatElapsed(ms) {
73
77
  const totalSeconds = Math.floor(ms / 1000);
@@ -0,0 +1,2 @@
1
+ export declare const CANCEL: unique symbol;
2
+ export declare function isCancel(value: unknown): value is typeof CANCEL;
@@ -0,0 +1,4 @@
1
+ export const CANCEL = Symbol.for("poe.cancel");
2
+ export function isCancel(value) {
3
+ return value === CANCEL;
4
+ }
@@ -0,0 +1,9 @@
1
+ import { CANCEL } from "./cancel-symbol.js";
2
+ export interface ConfirmOptions {
3
+ message: string;
4
+ initialValue?: boolean;
5
+ signal?: AbortSignal;
6
+ input?: NodeJS.ReadableStream;
7
+ output?: NodeJS.WritableStream;
8
+ }
9
+ export declare function confirmPrompt(opts: ConfirmOptions): Promise<boolean | typeof CANCEL>;
@@ -0,0 +1,47 @@
1
+ import { color } from "../../components/color.js";
2
+ import { GLYPHS, symbol, symbolBar } from "./glyphs.js";
3
+ import { Prompt } from "./core.js";
4
+ class ConfirmPrompt extends Prompt {
5
+ constructor(opts) {
6
+ super({
7
+ ...opts,
8
+ initialValue: opts.initialValue ?? true,
9
+ render: (prompt) => renderConfirmPrompt(prompt, opts)
10
+ }, false);
11
+ this.on("confirm", (value) => {
12
+ this.setValue(value);
13
+ this.state = "submit";
14
+ this.emit("finalize");
15
+ this.render();
16
+ this.close();
17
+ });
18
+ this.on("cursor", (action) => {
19
+ if (action === "up" || action === "down" || action === "left" || action === "right") {
20
+ this.setValue(!this.value);
21
+ }
22
+ });
23
+ }
24
+ promptNonTty() {
25
+ if (process.env.POE_NO_PROMPT === "1") {
26
+ return Promise.resolve(this.value ?? true);
27
+ }
28
+ return super.promptNonTty();
29
+ }
30
+ }
31
+ function choices(value) {
32
+ const yes = value ? `${color.green(GLYPHS.radioActive)} ${color.bold("Yes")}` : `${color.dim(GLYPHS.radioInactive)} ${color.dim("Yes")}`;
33
+ const no = value ? `${color.dim(GLYPHS.radioInactive)} ${color.dim("No")}` : `${color.green(GLYPHS.radioActive)} ${color.bold("No")}`;
34
+ return `${yes} ${color.dim("/")} ${no}`;
35
+ }
36
+ function renderConfirmPrompt(prompt, opts) {
37
+ if (prompt.state === "submit") {
38
+ return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${color.gray(GLYPHS.bar)} ${color.dim(prompt.value ? "Yes" : "No")}\n${color.green(GLYPHS.barEnd)}`;
39
+ }
40
+ if (prompt.state === "cancel") {
41
+ return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${color.gray(GLYPHS.bar)} ${color.dim.strikethrough(prompt.value ? "Yes" : "No")}\n${color.red(GLYPHS.barEnd)}`;
42
+ }
43
+ return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${symbolBar(prompt.state)} ${choices(prompt.value)}\n${color.cyan(GLYPHS.barEnd)}`;
44
+ }
45
+ export function confirmPrompt(opts) {
46
+ return new ConfirmPrompt(opts).prompt();
47
+ }