toolcraft 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 (70) hide show
  1. package/README.md +41 -4
  2. package/dist/cli.js +40 -9
  3. package/dist/http-errors.d.ts +57 -0
  4. package/dist/http-errors.js +72 -0
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.js +15 -0
  7. package/dist/mcp-proxy.js +16 -1
  8. package/dist/mcp.js +224 -12
  9. package/dist/number-schema.js +9 -2
  10. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +1 -1
  11. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +6 -1
  12. package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +1 -1
  13. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +1 -1
  14. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +1 -1
  15. package/node_modules/@poe-code/agent-defs/dist/specifier.js +15 -6
  16. package/node_modules/@poe-code/agent-defs/dist/types.d.ts +5 -0
  17. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +36 -3
  18. package/node_modules/@poe-code/frontmatter/README.md +35 -0
  19. package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +8 -0
  20. package/node_modules/@poe-code/frontmatter/dist/fences.js +72 -0
  21. package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
  22. package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
  23. package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
  24. package/node_modules/@poe-code/frontmatter/dist/parse.js +137 -0
  25. package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
  26. package/node_modules/@poe-code/frontmatter/dist/stringify.js +35 -0
  27. package/node_modules/@poe-code/frontmatter/package.json +25 -0
  28. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +1 -0
  29. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +25 -1
  30. package/node_modules/tiny-mcp-client/README.md +7 -0
  31. package/node_modules/tiny-mcp-client/dist/internal.d.ts +2 -0
  32. package/node_modules/tiny-mcp-client/dist/internal.js +3 -0
  33. package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +10 -0
  34. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +2 -8
  35. package/node_modules/tiny-mcp-client/src/internal.ts +6 -0
  36. package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +10 -0
  37. package/node_modules/toolcraft-design/README.md +12 -0
  38. package/node_modules/toolcraft-design/dist/prompts/index.d.ts +39 -14
  39. package/node_modules/toolcraft-design/dist/prompts/index.js +10 -6
  40. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
  41. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
  42. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
  43. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
  44. package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
  45. package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
  46. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
  47. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
  48. package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
  49. package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
  50. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
  51. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
  52. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
  53. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
  54. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
  55. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
  56. package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
  57. package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
  58. package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
  59. package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
  60. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
  61. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
  62. package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
  63. package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
  64. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
  65. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
  66. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +1 -1
  67. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +1 -1
  68. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +20 -453
  69. package/node_modules/toolcraft-design/package.json +6 -3
  70. package/package.json +9 -6
@@ -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
+ }
@@ -3,6 +3,7 @@ export interface JwksTokenVerifierOptions {
3
3
  jwksUrl: string | URL;
4
4
  clockSkewSeconds?: number;
5
5
  allowedAlgorithms?: readonly string[];
6
+ jwksCacheTtlMs?: number;
6
7
  fetch?: FetchLike;
7
8
  }
8
9
  export interface JwksVerifiedAccessToken {
@@ -243,16 +243,40 @@ export function createJwksTokenVerifier(options) {
243
243
  const jwksUrl = toUrl(options.jwksUrl, "jwksUrl");
244
244
  const clockSkewSeconds = options.clockSkewSeconds ?? 30;
245
245
  const allowedAlgorithms = options.allowedAlgorithms ?? DEFAULT_ALLOWED_ALGORITHMS;
246
+ const jwksCacheTtlMs = options.jwksCacheTtlMs ?? 300_000;
246
247
  const fetchImplementation = options.fetch ?? globalThis.fetch;
248
+ let cachedJwks;
249
+ let pendingJwks;
247
250
  if (typeof fetchImplementation !== "function") {
248
251
  throw new Error("fetch is not available; pass options.fetch explicitly");
249
252
  }
253
+ async function getJwks() {
254
+ const now = Date.now();
255
+ if (cachedJwks !== undefined && cachedJwks.expiresAt > now) {
256
+ return cachedJwks.value;
257
+ }
258
+ if (pendingJwks !== undefined) {
259
+ return pendingJwks;
260
+ }
261
+ pendingJwks = loadJwks(jwksUrl, fetchImplementation)
262
+ .then((jwks) => {
263
+ cachedJwks = {
264
+ value: jwks,
265
+ expiresAt: Date.now() + jwksCacheTtlMs,
266
+ };
267
+ return jwks;
268
+ })
269
+ .finally(() => {
270
+ pendingJwks = undefined;
271
+ });
272
+ return pendingJwks;
273
+ }
250
274
  return {
251
275
  async verify(input) {
252
276
  const expectedResource = canonicalizeResourceIndicator(input.resource);
253
277
  const algorithm = resolveAlgorithm(input.token, allowedAlgorithms);
254
278
  try {
255
- const jwks = await loadJwks(jwksUrl, fetchImplementation);
279
+ const jwks = await getJwks();
256
280
  const verified = await verifyJwtAgainstJwks({
257
281
  token: input.token,
258
282
  jwks,
@@ -14,9 +14,16 @@ const client = new McpClient({
14
14
  await client.connect(new HttpTransport({ url: "http://127.0.0.1:3000/mcp" }));
15
15
  const tools = await client.listTools();
16
16
  console.log(tools);
17
+ const result = await client.callTool({
18
+ name: "search",
19
+ arguments: { query: "typed outputs" },
20
+ });
21
+ console.log(result.structuredContent);
17
22
  await client.close();
18
23
  ```
19
24
 
25
+ `Tool` includes MCP `outputSchema` when a server advertises typed tool output. `CallToolResult` includes `structuredContent?: Record<string, unknown>` for typed results; legacy/content-block tools still use `content[]`.
26
+
20
27
  ## Transports
21
28
 
22
29
  | Transport | Description |
@@ -123,6 +123,7 @@ export interface Tool {
123
123
  name: string;
124
124
  description?: string;
125
125
  inputSchema: Record<string, unknown>;
126
+ outputSchema?: Record<string, unknown>;
126
127
  annotations?: ToolAnnotations;
127
128
  }
128
129
  export interface ToolAnnotations {
@@ -216,6 +217,7 @@ export interface GetPromptParams {
216
217
  }
217
218
  export interface CallToolResult {
218
219
  content: ContentItem[];
220
+ structuredContent?: Record<string, unknown>;
219
221
  isError?: boolean;
220
222
  }
221
223
  export interface Root {
@@ -2421,6 +2421,9 @@ function isCallToolResult(value) {
2421
2421
  if (!isObjectRecord(value) || !Array.isArray(value.content)) {
2422
2422
  return false;
2423
2423
  }
2424
+ if (value.structuredContent !== undefined && !isObjectRecord(value.structuredContent)) {
2425
+ return false;
2426
+ }
2424
2427
  if (value.isError !== undefined && typeof value.isError !== "boolean") {
2425
2428
  return false;
2426
2429
  }
@@ -14,6 +14,13 @@ const tool = {
14
14
  locale: { type: "string" },
15
15
  },
16
16
  },
17
+ outputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ word: { type: "string" },
21
+ },
22
+ required: ["word"],
23
+ },
17
24
  annotations,
18
25
  };
19
26
  const callToolParams = {
@@ -52,6 +59,9 @@ const contentItems = [
52
59
  ];
53
60
  const callToolResult = {
54
61
  content: contentItems,
62
+ structuredContent: {
63
+ word: "quotidian",
64
+ },
55
65
  };
56
66
  const callToolErrorResult = {
57
67
  content: [textContent],
@@ -1,5 +1,5 @@
1
- import http from "node:http";
2
1
  import { describe, expect, it, vi } from "vitest";
2
+ import { nodeFetch } from "tiny-http-mcp-server/testing";
3
3
  import type { OAuthSessionStore, StoredOAuthSession } from "mcp-oauth";
4
4
  import {
5
5
  HttpTransport,
@@ -918,11 +918,5 @@ function readTransportLine(transport: HttpTransport): Promise<string> {
918
918
  }
919
919
 
920
920
  async function requestLoopbackCallback(url: string): Promise<void> {
921
- await new Promise<void>((resolve, reject) => {
922
- const request = http.get(url, (response) => {
923
- response.resume();
924
- response.once("end", resolve);
925
- });
926
- request.once("error", reject);
927
- });
921
+ await nodeFetch(url);
928
922
  }
@@ -670,6 +670,7 @@ export interface Tool {
670
670
  name: string;
671
671
  description?: string;
672
672
  inputSchema: Record<string, unknown>;
673
+ outputSchema?: Record<string, unknown>;
673
674
  annotations?: ToolAnnotations;
674
675
  }
675
676
 
@@ -785,6 +786,7 @@ export interface GetPromptParams {
785
786
 
786
787
  export interface CallToolResult {
787
788
  content: ContentItem[];
789
+ structuredContent?: Record<string, unknown>;
788
790
  isError?: boolean;
789
791
  }
790
792
 
@@ -3578,6 +3580,10 @@ function isCallToolResult(value: unknown): value is CallToolResult {
3578
3580
  return false;
3579
3581
  }
3580
3582
 
3583
+ if (value.structuredContent !== undefined && !isObjectRecord(value.structuredContent)) {
3584
+ return false;
3585
+ }
3586
+
3581
3587
  if (value.isError !== undefined && typeof value.isError !== "boolean") {
3582
3588
  return false;
3583
3589
  }
@@ -28,6 +28,13 @@ const tool: Tool = {
28
28
  locale: { type: "string" },
29
29
  },
30
30
  },
31
+ outputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ word: { type: "string" },
35
+ },
36
+ required: ["word"],
37
+ },
31
38
  annotations,
32
39
  };
33
40
 
@@ -74,6 +81,9 @@ const contentItems: ContentItem[] = [
74
81
 
75
82
  const callToolResult: CallToolResult = {
76
83
  content: contentItems,
84
+ structuredContent: {
85
+ word: "quotidian",
86
+ },
77
87
  };
78
88
 
79
89
  const callToolErrorResult: CallToolResult = {
@@ -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
+ }
@@ -0,0 +1,55 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { CANCEL } from "./cancel-symbol.js";
3
+ export type PromptStateName = "initial" | "active" | "submit" | "cancel" | "error";
4
+ export interface PromptState<Value> {
5
+ state: PromptStateName;
6
+ value: Value | undefined;
7
+ error: string;
8
+ cursor: number;
9
+ userInput: string;
10
+ }
11
+ export interface PromptOptions<Value> {
12
+ render: (state: Prompt<Value>) => string;
13
+ initialValue?: Value;
14
+ initialUserInput?: string;
15
+ validate?: (value: Value | undefined) => string | Error | undefined;
16
+ signal?: AbortSignal;
17
+ input?: NodeJS.ReadableStream;
18
+ output?: NodeJS.WritableStream;
19
+ }
20
+ type InputStream = NodeJS.ReadableStream & {
21
+ isTTY?: boolean;
22
+ setRawMode?: (enabled: boolean) => void;
23
+ unpipe?: () => void;
24
+ };
25
+ export declare class Prompt<Value> extends EventEmitter {
26
+ state: PromptStateName;
27
+ value: Value | undefined;
28
+ error: string;
29
+ userInput: string;
30
+ protected _cursor: number;
31
+ protected input: InputStream;
32
+ protected output: NodeJS.WritableStream;
33
+ private readonly renderFrame;
34
+ private readonly validate?;
35
+ private readonly signal?;
36
+ private readonly trackValue;
37
+ private previousFrame;
38
+ private readlineInterface?;
39
+ private abortListener?;
40
+ private closed;
41
+ constructor(opts: PromptOptions<Value>, trackValue?: boolean);
42
+ get cursor(): number;
43
+ prompt(): Promise<Value | typeof CANCEL>;
44
+ protected promptNonTty(): Promise<Value | typeof CANCEL>;
45
+ protected readNonTtyLine(): Promise<string>;
46
+ protected setValue(value: Value | undefined): void;
47
+ protected setError(message: string): void;
48
+ protected setUserInput(value: string): void;
49
+ protected clearUserInput(): void;
50
+ private readonly onKeypress;
51
+ private updateTrackedInput;
52
+ protected readonly render: () => void;
53
+ protected close(): void;
54
+ }
55
+ export {};