padrone 1.5.0 → 1.7.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 (141) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +15 -11
  3. package/dist/{args-D5PNDyNu.mjs → args-Cnq0nwSM.mjs} +91 -41
  4. package/dist/args-Cnq0nwSM.mjs.map +1 -0
  5. package/dist/codegen/index.mjs +4 -4
  6. package/dist/codegen/index.mjs.map +1 -1
  7. package/dist/commands-B_gufyR9.mjs +514 -0
  8. package/dist/commands-B_gufyR9.mjs.map +1 -0
  9. package/dist/{completion.mjs → completion-BEuflbDO.mjs} +12 -82
  10. package/dist/completion-BEuflbDO.mjs.map +1 -0
  11. package/dist/docs/index.d.mts +4 -4
  12. package/dist/docs/index.d.mts.map +1 -1
  13. package/dist/docs/index.mjs +10 -12
  14. package/dist/docs/index.mjs.map +1 -1
  15. package/dist/{errors-BiVrBgi6.mjs → errors-DA4KzK1M.mjs} +26 -3
  16. package/dist/errors-DA4KzK1M.mjs.map +1 -0
  17. package/dist/{formatter-DtHzbP22.d.mts → formatter-DrvhDMrq.d.mts} +3 -3
  18. package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
  19. package/dist/{help-bbmu9-qd.mjs → help-BtxLgrF_.mjs} +190 -43
  20. package/dist/help-BtxLgrF_.mjs.map +1 -0
  21. package/dist/{types-Ch8Mk6Qb.d.mts → index-D6-7dz0l.d.mts} +634 -745
  22. package/dist/index-D6-7dz0l.d.mts.map +1 -0
  23. package/dist/index.d.mts +869 -36
  24. package/dist/index.d.mts.map +1 -1
  25. package/dist/index.mjs +3884 -1699
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/{mcp-mLWIdUIu.mjs → mcp-6-Jw4Bpq.mjs} +13 -15
  28. package/dist/mcp-6-Jw4Bpq.mjs.map +1 -0
  29. package/dist/{serve-B0u43DK7.mjs → serve-YVTPzBCl.mjs} +12 -14
  30. package/dist/serve-YVTPzBCl.mjs.map +1 -0
  31. package/dist/{stream-BcC146Ud.mjs → stream-DC4H8YTx.mjs} +24 -3
  32. package/dist/stream-DC4H8YTx.mjs.map +1 -0
  33. package/dist/test.d.mts +5 -8
  34. package/dist/test.d.mts.map +1 -1
  35. package/dist/test.mjs +2 -13
  36. package/dist/test.mjs.map +1 -1
  37. package/dist/{update-check-CFX1FV3v.mjs → update-check-CZ2VqjnV.mjs} +16 -17
  38. package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
  39. package/dist/zod.d.mts +2 -2
  40. package/dist/zod.d.mts.map +1 -1
  41. package/dist/zod.mjs +2 -2
  42. package/dist/zod.mjs.map +1 -1
  43. package/package.json +15 -12
  44. package/src/cli/completions.ts +14 -11
  45. package/src/cli/docs.ts +13 -10
  46. package/src/cli/doctor.ts +22 -18
  47. package/src/cli/index.ts +28 -82
  48. package/src/cli/init.ts +10 -7
  49. package/src/cli/link.ts +20 -16
  50. package/src/cli/wrap.ts +14 -11
  51. package/src/codegen/schema-to-code.ts +2 -2
  52. package/src/{args.ts → core/args.ts} +32 -225
  53. package/src/core/commands.ts +373 -0
  54. package/src/core/create.ts +301 -0
  55. package/src/core/default-runtime.ts +239 -0
  56. package/src/{errors.ts → core/errors.ts} +22 -0
  57. package/src/core/exec.ts +259 -0
  58. package/src/core/interceptors.ts +302 -0
  59. package/src/{parse.ts → core/parse.ts} +36 -89
  60. package/src/core/program-methods.ts +301 -0
  61. package/src/core/results.ts +229 -0
  62. package/src/core/runtime.ts +246 -0
  63. package/src/core/validate.ts +247 -0
  64. package/src/docs/index.ts +12 -13
  65. package/src/extension/auto-output.ts +146 -0
  66. package/src/extension/color.ts +38 -0
  67. package/src/extension/completion.ts +49 -0
  68. package/src/extension/config.ts +262 -0
  69. package/src/extension/env.ts +101 -0
  70. package/src/extension/help.ts +192 -0
  71. package/src/extension/index.ts +44 -0
  72. package/src/extension/ink.ts +93 -0
  73. package/src/extension/interactive.ts +106 -0
  74. package/src/extension/logger.ts +262 -0
  75. package/src/extension/man.ts +51 -0
  76. package/src/extension/mcp.ts +52 -0
  77. package/src/extension/progress-renderer.ts +338 -0
  78. package/src/extension/progress.ts +299 -0
  79. package/src/extension/repl.ts +94 -0
  80. package/src/extension/serve.ts +48 -0
  81. package/src/extension/signal.ts +87 -0
  82. package/src/extension/stdin.ts +62 -0
  83. package/src/extension/suggestions.ts +114 -0
  84. package/src/extension/timing.ts +81 -0
  85. package/src/extension/tracing.ts +175 -0
  86. package/src/extension/update-check.ts +77 -0
  87. package/src/extension/utils.ts +51 -0
  88. package/src/extension/version.ts +63 -0
  89. package/src/{completion.ts → feature/completion.ts} +12 -12
  90. package/src/{interactive.ts → feature/interactive.ts} +4 -4
  91. package/src/{mcp.ts → feature/mcp.ts} +12 -15
  92. package/src/{repl-loop.ts → feature/repl-loop.ts} +10 -13
  93. package/src/{serve.ts → feature/serve.ts} +11 -15
  94. package/src/feature/test.ts +262 -0
  95. package/src/{update-check.ts → feature/update-check.ts} +16 -16
  96. package/src/{wrap.ts → feature/wrap.ts} +10 -8
  97. package/src/index.ts +115 -30
  98. package/src/{formatter.ts → output/formatter.ts} +124 -176
  99. package/src/{help.ts → output/help.ts} +22 -8
  100. package/src/output/output-indicator.ts +87 -0
  101. package/src/output/primitives.ts +335 -0
  102. package/src/output/styling.ts +221 -0
  103. package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
  104. package/src/schema/zod.ts +50 -0
  105. package/src/test.ts +2 -276
  106. package/src/types/args-meta.ts +151 -0
  107. package/src/types/builder.ts +718 -0
  108. package/src/types/command.ts +157 -0
  109. package/src/types/index.ts +60 -0
  110. package/src/types/interceptor.ts +296 -0
  111. package/src/types/preferences.ts +83 -0
  112. package/src/types/result.ts +71 -0
  113. package/src/types/schema.ts +19 -0
  114. package/src/util/dotenv.ts +244 -0
  115. package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
  116. package/src/{stream.ts → util/stream.ts} +27 -1
  117. package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
  118. package/src/{type-utils.ts → util/type-utils.ts} +71 -33
  119. package/src/util/utils.ts +51 -0
  120. package/src/zod.ts +1 -50
  121. package/dist/args-D5PNDyNu.mjs.map +0 -1
  122. package/dist/chunk-CjcI7cDX.mjs +0 -15
  123. package/dist/command-utils-B1D-HqCd.mjs +0 -1117
  124. package/dist/command-utils-B1D-HqCd.mjs.map +0 -1
  125. package/dist/completion.d.mts +0 -64
  126. package/dist/completion.d.mts.map +0 -1
  127. package/dist/completion.mjs.map +0 -1
  128. package/dist/errors-BiVrBgi6.mjs.map +0 -1
  129. package/dist/formatter-DtHzbP22.d.mts.map +0 -1
  130. package/dist/help-bbmu9-qd.mjs.map +0 -1
  131. package/dist/mcp-mLWIdUIu.mjs.map +0 -1
  132. package/dist/serve-B0u43DK7.mjs.map +0 -1
  133. package/dist/stream-BcC146Ud.mjs.map +0 -1
  134. package/dist/types-Ch8Mk6Qb.d.mts.map +0 -1
  135. package/dist/update-check-CFX1FV3v.mjs.map +0 -1
  136. package/src/command-utils.ts +0 -882
  137. package/src/create.ts +0 -1829
  138. package/src/runtime.ts +0 -497
  139. package/src/types.ts +0 -1291
  140. package/src/utils.ts +0 -140
  141. /package/src/{colorizer.ts → output/colorizer.ts} +0 -0
@@ -0,0 +1,77 @@
1
+ import { thenMaybe } from '#src/core/results.ts';
2
+ import { defineInterceptor } from '../core/interceptors.ts';
3
+ import type { UpdateCheckConfig } from '../feature/update-check.ts';
4
+ import type { AnyPadroneBuilder, CommandTypesBase } from '../types/index.ts';
5
+ import { getVersion } from '../util/utils.ts';
6
+
7
+ // ── Interceptor ─────────────────────────────────────────────────────────
8
+
9
+ function createUpdateCheckInterceptor(config: UpdateCheckConfig) {
10
+ return defineInterceptor({ id: 'padrone:update-check', name: 'padrone:update-check', order: 1000 }, () => {
11
+ let checkPromise: Promise<(() => void) | undefined> | undefined;
12
+ let suppressed = false;
13
+
14
+ return {
15
+ start(ctx, next) {
16
+ const rootCommand = ctx.command;
17
+ const runtime = ctx.runtime;
18
+
19
+ checkPromise = Promise.resolve(getVersion(rootCommand.version)).then((currentVersion) =>
20
+ import('../feature/update-check.ts').then(({ createUpdateChecker }) =>
21
+ createUpdateChecker(rootCommand.name, currentVersion, config, runtime),
22
+ ),
23
+ );
24
+
25
+ return next();
26
+ },
27
+ parse(_ctx, next) {
28
+ return thenMaybe(next(), (res) => {
29
+ if ('update-check' in res.rawArgs) {
30
+ if (res.rawArgs['update-check'] === false) suppressed = true;
31
+ delete res.rawArgs['update-check'];
32
+ }
33
+ return res;
34
+ });
35
+ },
36
+ shutdown(_ctx, next) {
37
+ const result = next();
38
+ if (suppressed || !checkPromise) return result;
39
+
40
+ // Try to show notification synchronously if the check already resolved
41
+ let resolved: (() => void) | undefined | null = null;
42
+ checkPromise.then(
43
+ (fn) => {
44
+ resolved = fn;
45
+ },
46
+ () => {
47
+ resolved = undefined;
48
+ },
49
+ );
50
+
51
+ if (resolved !== null) {
52
+ (resolved as (() => void) | undefined)?.();
53
+ }
54
+
55
+ return result;
56
+ },
57
+ };
58
+ });
59
+ }
60
+
61
+ // ── Extension ────────────────────────────────────────────────────────────
62
+
63
+ /**
64
+ * Extension that adds background update checking:
65
+ * - Checks for newer versions on npm (or custom registry) in the background
66
+ * - Shows an update notification after command execution
67
+ * - Respects `--no-update-check` flag to suppress
68
+ *
69
+ * Usage:
70
+ * ```ts
71
+ * createPadrone('my-cli')
72
+ * .extend(padroneUpdateCheck({ packageName: 'my-cli' }))
73
+ * ```
74
+ */
75
+ export function padroneUpdateCheck(config: UpdateCheckConfig = {}): <T extends CommandTypesBase>(builder: T) => T {
76
+ return ((builder: AnyPadroneBuilder) => builder.intercept(createUpdateCheckInterceptor(config))) as any;
77
+ }
@@ -0,0 +1,51 @@
1
+ import type { AnyPadroneCommand, PadroneSchema } from '../types/index.ts';
2
+
3
+ type SchemaShape = Record<string, 'string' | 'string[]' | 'boolean'>;
4
+
5
+ type InferPassthroughSchema<T extends SchemaShape> = {
6
+ [K in keyof T]: T[K] extends 'string' ? string : T[K] extends 'string[]' ? string[] : T[K] extends 'boolean' ? boolean : never;
7
+ };
8
+
9
+ /** Minimal Standard Schema that passes through known fields, ignoring unknown ones. */
10
+ export function passthroughSchema<TShape extends SchemaShape>(fields: TShape): PadroneSchema<InferPassthroughSchema<TShape>> {
11
+ return {
12
+ '~standard': {
13
+ version: 1 as const,
14
+ vendor: 'padrone' as const,
15
+ jsonSchema: {
16
+ input: () => ({}),
17
+ output: () => ({}),
18
+ },
19
+ validate: (value) => {
20
+ const input = value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
21
+ const result: Record<string, unknown> = {};
22
+ for (const [name, type] of Object.entries(fields)) {
23
+ const v = input[name];
24
+ if (v === undefined) continue;
25
+ if (type === 'string[]') {
26
+ if (Array.isArray(v)) result[name] = v.map(String);
27
+ else if (typeof v === 'string') result[name] = [v];
28
+ } else if (type === 'string') {
29
+ if (typeof v === 'string') result[name] = v;
30
+ else if (Array.isArray(v) && v.length > 0) result[name] = String(v[0]);
31
+ } else if (type === 'boolean') {
32
+ result[name] = v === true || v === 'true';
33
+ }
34
+ }
35
+ return { value: result as InferPassthroughSchema<TShape> };
36
+ },
37
+ },
38
+ };
39
+ }
40
+
41
+ /** Find a command by space-separated name in the command tree. */
42
+ export function findCommandInTree(name: string, rootCommand: AnyPadroneCommand): AnyPadroneCommand | undefined {
43
+ const parts = name.split(' ').filter(Boolean);
44
+ let current = rootCommand;
45
+ for (const part of parts) {
46
+ const found = current.commands?.find((c) => c.name === part || c.aliases?.includes(part));
47
+ if (!found) return undefined;
48
+ current = found;
49
+ }
50
+ return current;
51
+ }
@@ -0,0 +1,63 @@
1
+ import { thenMaybe } from '#src/core/results.ts';
2
+ import { resolveCommand } from '../core/commands.ts';
3
+ import { defineInterceptor } from '../core/interceptors.ts';
4
+ import type { AnyPadroneBuilder, CommandTypesBase, PadroneCommand } from '../types/index.ts';
5
+ import type { PadroneSchema } from '../types/schema.ts';
6
+ import type { WithCommand } from '../util/type-utils.ts';
7
+ import { getRootCommand, getVersion } from '../util/utils.ts';
8
+
9
+ // ── Types ────────────────────────────────────────────────────────────────
10
+
11
+ export type VersionCommand = PadroneCommand<'version', '', PadroneSchema<void>, string, [], [], false>;
12
+
13
+ export type WithVersion<T> = WithCommand<T, 'version', VersionCommand>;
14
+
15
+ // ── Interceptor ─────────────────────────────────────────────────────────
16
+
17
+ const versionInterceptor = defineInterceptor({ id: 'padrone:version', name: 'padrone:version', order: -1000 }, () => ({
18
+ parse(_ctx, next) {
19
+ return thenMaybe(next(), (res) => {
20
+ const hasVersionFlag = res.rawArgs.version || res.rawArgs.v || res.rawArgs.V;
21
+
22
+ // Only show version for root command (no subcommand matched)
23
+ if (hasVersionFlag && !res.command.parent) {
24
+ delete res.rawArgs.version;
25
+ delete res.rawArgs.v;
26
+ delete res.rawArgs.V;
27
+
28
+ // Route to the version command so its action handles the rest
29
+ const versionCmd = res.command.commands?.find((c) => c.name === 'version');
30
+ if (versionCmd) {
31
+ resolveCommand(versionCmd);
32
+ return { ...res, command: versionCmd, rawArgs: {}, positionalArgs: [] };
33
+ }
34
+ }
35
+
36
+ return res;
37
+ });
38
+ },
39
+ }));
40
+
41
+ // ── Extension ────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Extension that adds version support:
45
+ * - `version` command
46
+ * - `--version` / `-v` / `-V` flags (root command only)
47
+ *
48
+ * Usage:
49
+ * ```ts
50
+ * createPadrone('my-cli').extend(padroneVersion())
51
+ * ```
52
+ */
53
+ export function padroneVersion(): <T extends CommandTypesBase>(builder: T) => WithVersion<T> {
54
+ return ((builder: AnyPadroneBuilder) =>
55
+ builder
56
+ .command('version', (c) =>
57
+ c.configure({ description: 'Display the version number', hidden: true }).action((_args, ctx) => {
58
+ const rootCommand = getRootCommand(ctx.command);
59
+ return getVersion(rootCommand.version);
60
+ }),
61
+ )
62
+ .intercept(versionInterceptor)) as any;
63
+ }
@@ -1,8 +1,8 @@
1
- import { extractSchemaMetadata, JSON_SCHEMA_OPTS } from './args.ts';
2
- import { detectShell, getRcFile, type ShellType, writeToRcFile } from './shell-utils.ts';
3
- import type { AnyPadroneCommand } from './types.ts';
1
+ import { extractSchemaMetadata, getJsonSchema } from '../core/args.ts';
2
+ import type { AnyPadroneCommand } from '../types/index.ts';
3
+ import { detectShell, getRcFile, type ShellType, writeToRcFile } from '../util/shell-utils.ts';
4
4
 
5
- export { detectShell, escapeRegExp, getRcFile, type ShellType, writeToRcFile } from './shell-utils.ts';
5
+ export { detectShell, escapeRegExp, getRcFile, type ShellType, writeToRcFile } from '../util/shell-utils.ts';
6
6
 
7
7
  /**
8
8
  * Collects all commands from a program recursively.
@@ -48,7 +48,7 @@ function extractArguments(cmd: AnyPadroneCommand): ExtractedArg[] {
48
48
  if (!argToAlias[argName]) argToAlias[argName] = aliasName;
49
49
  }
50
50
 
51
- const jsonSchema = cmd.argsSchema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
51
+ const jsonSchema = getJsonSchema(cmd.argsSchema) as Record<string, any>;
52
52
 
53
53
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
54
54
  for (const [key, prop] of Object.entries(jsonSchema.properties as Record<string, any>)) {
@@ -439,7 +439,7 @@ ${programName} completion powershell >> $PROFILE`;
439
439
  * Generates the completion output with automatic shell detection.
440
440
  * If shell is not specified, detects the current shell and provides instructions.
441
441
  */
442
- export function generateCompletionOutput(program: AnyPadroneCommand, shell?: ShellType): string {
442
+ export async function generateCompletionOutput(program: AnyPadroneCommand, shell?: ShellType): Promise<string> {
443
443
  const programName = program.name;
444
444
 
445
445
  if (shell) {
@@ -447,7 +447,7 @@ export function generateCompletionOutput(program: AnyPadroneCommand, shell?: She
447
447
  }
448
448
 
449
449
  // Auto-detect shell and provide instructions
450
- const detectedShell = detectShell();
450
+ const detectedShell = await detectShell();
451
451
 
452
452
  if (detectedShell) {
453
453
  const instructions = getCompletionInstallInstructions(programName, detectedShell);
@@ -492,10 +492,10 @@ export interface SetupCompletionsResult {
492
492
  * Sets up shell completions by writing an eval snippet to the appropriate shell config file.
493
493
  * Uses marker comments for idempotency — re-running replaces the existing block.
494
494
  */
495
- export function setupCompletions(programName: string, shell: ShellType): SetupCompletionsResult {
496
- const { existsSync, mkdirSync, writeFileSync } = require('node:fs') as typeof import('node:fs');
497
- const { join } = require('node:path') as typeof import('node:path');
498
- const { homedir } = require('node:os') as typeof import('node:os');
495
+ export async function setupCompletions(programName: string, shell: ShellType): Promise<SetupCompletionsResult> {
496
+ const { existsSync, mkdirSync, writeFileSync } = await import('node:fs');
497
+ const { join } = await import('node:path');
498
+ const { homedir } = await import('node:os');
499
499
 
500
500
  const beginMarker = `###-begin-${programName}-completion-###`;
501
501
  const endMarker = `###-end-${programName}-completion-###`;
@@ -510,7 +510,7 @@ export function setupCompletions(programName: string, shell: ShellType): SetupCo
510
510
  return { file: filePath, updated: existed };
511
511
  }
512
512
 
513
- const rcFile = getRcFile(shell);
513
+ const rcFile = await getRcFile(shell);
514
514
  if (!rcFile) {
515
515
  throw new Error(`Could not determine config file for ${shell}.`);
516
516
  }
@@ -1,6 +1,6 @@
1
- import { JSON_SCHEMA_OPTS } from './args.ts';
2
- import type { InteractivePromptConfig, ResolvedPadroneRuntime } from './runtime.ts';
3
- import type { AnyPadroneCommand } from './types.ts';
1
+ import { getJsonSchema } from '../core/args.ts';
2
+ import type { InteractivePromptConfig, ResolvedPadroneRuntime } from '../core/runtime.ts';
3
+ import type { AnyPadroneCommand } from '../types/index.ts';
4
4
 
5
5
  /**
6
6
  * Auto-detect the prompt type for a field based on its JSON schema property definition.
@@ -110,7 +110,7 @@ export async function promptInteractiveFields(
110
110
  let requiredFields: Set<string> = new Set();
111
111
  if (command.argsSchema) {
112
112
  try {
113
- const jsonSchema = command.argsSchema['~standard'].jsonSchema.input(JSON_SCHEMA_OPTS) as Record<string, any>;
113
+ const jsonSchema = getJsonSchema(command.argsSchema) as Record<string, any>;
114
114
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
115
115
  jsonProperties = jsonSchema.properties;
116
116
  }
@@ -1,6 +1,7 @@
1
- import { buildInputSchema, collectEndpoints, serializeArgsToFlags } from './command-utils.ts';
2
- import { generateHelp } from './help.ts';
3
- import type { AnyPadroneCommand, AnyPadroneProgram } from './types.ts';
1
+ import { buildInputSchema, collectEndpoints, serializeArgsToFlags } from '../core/commands.ts';
2
+ import { generateHelp } from '../output/help.ts';
3
+ import type { AnyPadroneCommand, AnyPadroneProgram } from '../types/index.ts';
4
+ import { readStreamAsText } from '../util/stream.ts';
4
5
 
5
6
  export type PadroneMcpPreferences = {
6
7
  /** Server name. Defaults to the program name. */
@@ -158,7 +159,7 @@ export function createMcpHandler(
158
159
  const output: string[] = [];
159
160
  const errors: string[] = [];
160
161
  const result = await evalCommand(input as any, {
161
- autoOutput: false,
162
+ caller: 'mcp',
162
163
  runtime: {
163
164
  output: (...outArgs: unknown[]) => output.push(outArgs.map(String).join(' ')),
164
165
  error: (text: string) => errors.push(text),
@@ -237,6 +238,7 @@ async function startHttpTransport(
237
238
  handleRequest: (req: JsonRpcRequest) => Promise<JsonRpcResponse | undefined>,
238
239
  prefs: PadroneMcpPreferences,
239
240
  log: (msg: string) => void,
241
+ onSignal?: (callback: () => void) => () => void,
240
242
  ): Promise<void> {
241
243
  const http = await import('node:http');
242
244
  const crypto = await import('node:crypto');
@@ -317,11 +319,7 @@ async function startHttpTransport(
317
319
  }
318
320
 
319
321
  // Read request body
320
- const chunks: Buffer[] = [];
321
- for await (const chunk of req) {
322
- chunks.push(chunk);
323
- }
324
- const body = Buffer.concat(chunks).toString('utf-8');
322
+ const body = await readStreamAsText(req as AsyncIterable<Uint8Array>);
325
323
 
326
324
  let rpcRequest: JsonRpcRequest;
327
325
  try {
@@ -363,11 +361,10 @@ async function startHttpTransport(
363
361
  log(`MCP server listening on http://${host}:${port}${endpoint}`);
364
362
  });
365
363
  server.on('error', reject);
366
- const onSignal = () => {
364
+ const unsubscribe = onSignal?.(() => {
367
365
  server.close(() => resolve());
368
- };
369
- process.on('SIGINT', onSignal);
370
- process.on('SIGTERM', onSignal);
366
+ });
367
+ server.on('close', () => unsubscribe?.());
371
368
  });
372
369
  }
373
370
 
@@ -384,7 +381,7 @@ export async function startMcpServer(
384
381
  return startStdioTransport(handleRequest);
385
382
  }
386
383
 
387
- const { getCommandRuntime } = await import('./command-utils.ts');
384
+ const { getCommandRuntime } = await import('../core/commands.ts');
388
385
  const runtime = getCommandRuntime(existingCommand);
389
- return startHttpTransport(handleRequest, prefs ?? {}, (msg) => runtime.error(msg));
386
+ return startHttpTransport(handleRequest, prefs ?? {}, (msg) => runtime.error(msg), runtime.onSignal);
390
387
  }
@@ -1,8 +1,9 @@
1
1
  import type { StandardSchemaV1 } from '@standard-schema/spec';
2
- import { buildReplCompleter, findCommandByName, getCommandRuntime } from './command-utils.ts';
3
- import { createTerminalReplSession, REPL_SIGINT, type ReplSessionConfig } from './runtime.ts';
4
- import type { AnyPadroneCommand, PadroneEvalPreferences, PadroneReplPreferences } from './types.ts';
5
- import { getVersion } from './utils.ts';
2
+ import { buildReplCompleter, findCommandByName, getCommandRuntime } from '../core/commands.ts';
3
+ import { createTerminalReplSession } from '../core/default-runtime.ts';
4
+ import { REPL_SIGINT, type ReplSessionConfig } from '../core/runtime.ts';
5
+ import type { AnyPadroneCommand, PadroneEvalPreferences, PadroneReplPreferences } from '../types/index.ts';
6
+ import { getVersion } from '../util/utils.ts';
6
7
 
7
8
  export type ReplDeps = {
8
9
  existingCommand: AnyPadroneCommand;
@@ -25,9 +26,8 @@ export function createReplIterator(deps: ReplDeps, options?: PadroneReplPreferen
25
26
  const runtime = getCommandRuntime(existingCommand);
26
27
 
27
28
  const programName = existingCommand.name || 'padrone';
28
- const useAnsi =
29
- runtime.format === 'ansi' ||
30
- (runtime.format === 'auto' && typeof process !== 'undefined' && !process.env.NO_COLOR && !process.env.CI && process.stdout?.isTTY);
29
+ const env = runtime.env();
30
+ const useAnsi = runtime.format === 'ansi' || (runtime.format === 'auto' && !env.NO_COLOR && !env.CI && runtime.terminal?.isTTY === true);
31
31
 
32
32
  // Track command history for .history built-in
33
33
  const commandHistory: string[] = [];
@@ -60,7 +60,7 @@ export function createReplIterator(deps: ReplDeps, options?: PadroneReplPreferen
60
60
  runtime.output(options.greeting);
61
61
  } else {
62
62
  const displayName = existingCommand.title || programName;
63
- const version = existingCommand.version ? getVersion(existingCommand.version) : undefined;
63
+ const version = existingCommand.version ? await getVersion(existingCommand.version) : undefined;
64
64
  const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;
65
65
  runtime.output(greeting);
66
66
  }
@@ -264,10 +264,7 @@ export function createReplIterator(deps: ReplDeps, options?: PadroneReplPreferen
264
264
 
265
265
  const emitSpacingLine = (value: boolean | string) => {
266
266
  if (typeof value === 'string') {
267
- const sep =
268
- value.length === 1
269
- ? value.repeat(typeof process !== 'undefined' && process.stdout?.columns ? process.stdout.columns : 80)
270
- : value;
267
+ const sep = value.length === 1 ? value.repeat(runtime.terminal?.columns ?? 80) : value;
271
268
  runtime.output(sep);
272
269
  } else if (value) {
273
270
  runtime.output('');
@@ -289,7 +286,7 @@ export function createReplIterator(deps: ReplDeps, options?: PadroneReplPreferen
289
286
  const scopedInput = scopePath ? (evalInput ? `${scopePath} ${evalInput}` : scopePath) : evalInput;
290
287
 
291
288
  try {
292
- const replEvalPrefs: PadroneEvalPreferences | undefined = options?.autoOutput === false ? { autoOutput: false } : undefined;
289
+ const replEvalPrefs: PadroneEvalPreferences | undefined = { caller: 'repl' };
293
290
  const result = await evalCommand(scopedInput, replEvalPrefs);
294
291
  if (result.error) {
295
292
  const msg = result.error instanceof Error ? result.error.message : String(result.error);
@@ -1,7 +1,8 @@
1
- import { buildInputSchema, type CollectedEndpoint, collectEndpoints, serializeArgsToFlags } from './command-utils.ts';
2
- import { RoutingError, ValidationError } from './errors.ts';
3
- import { generateHelp } from './help.ts';
4
- import type { AnyPadroneCommand, AnyPadroneProgram } from './types.ts';
1
+ import { buildInputSchema, type CollectedEndpoint, collectEndpoints, serializeArgsToFlags } from '../core/commands.ts';
2
+ import { RoutingError, ValidationError } from '../core/errors.ts';
3
+ import { generateHelp } from '../output/help.ts';
4
+ import type { AnyPadroneCommand, AnyPadroneProgram } from '../types/index.ts';
5
+ import { readStreamAsText } from '../util/stream.ts';
5
6
 
6
7
  export type PadroneServePreferences = {
7
8
  /** Port to listen on. Default: 3000 */
@@ -229,7 +230,7 @@ export function createServeHandler(
229
230
  const output: string[] = [];
230
231
  const errors: string[] = [];
231
232
  const result = await evalCommand(commandString || (undefined as any), {
232
- autoOutput: false,
233
+ caller: 'serve',
233
234
  runtime: {
234
235
  output: (...args: unknown[]) => output.push(args.map(String).join(' ')),
235
236
  error: (text: string) => errors.push(text),
@@ -414,7 +415,7 @@ export async function startServeServer(
414
415
  res.end(body);
415
416
  });
416
417
 
417
- const { getCommandRuntime } = await import('./command-utils.ts');
418
+ const { getCommandRuntime } = await import('../core/commands.ts');
418
419
  const runtime = getCommandRuntime(existingCommand);
419
420
 
420
421
  return new Promise<void>((resolve, reject) => {
@@ -424,19 +425,14 @@ export async function startServeServer(
424
425
  if (builtins.docs) runtime.error(`API docs: http://${host}:${port}${basePath}_docs`);
425
426
  });
426
427
  server.on('error', reject);
427
- const onSignal = () => {
428
+ const unsubscribe = runtime.onSignal?.(() => {
428
429
  server.close(() => resolve());
429
- };
430
- process.on('SIGINT', onSignal);
431
- process.on('SIGTERM', onSignal);
430
+ });
431
+ server.on('close', () => unsubscribe?.());
432
432
  });
433
433
  }
434
434
 
435
435
  /** Read the full body from a Node.js IncomingMessage. */
436
436
  async function readBody(req: import('node:http').IncomingMessage): Promise<string> {
437
- const chunks: Buffer[] = [];
438
- for await (const chunk of req) {
439
- chunks.push(chunk as Buffer);
440
- }
441
- return Buffer.concat(chunks).toString('utf-8');
437
+ return readStreamAsText(req as AsyncIterable<Uint8Array>);
442
438
  }