@effected/cli 0.22.2 → 0.24.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.
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Array as Array$1, Cause, Context, Duration, Effect, FileSystem, Layer, LogLevel, Logger, MutableRef, Option, Path, Runtime, Schema, Stdio, Terminal } from "effect";
2
2
  import { Audience, AudienceKind, ColorLevel, CurrentRuntimeEnv, RuntimeEnv, TerminalEnv } from "@effected/env";
3
3
  import { CliError, CliOutput, Command, Flag, Param, Prompt } from "effect/cli";
4
+ import { Distribution } from "@effected/engine";
4
5
  import { ConfigValidationError } from "@effected/config-file";
5
6
  declare const Cancelled_base: Schema.Class<Cancelled, Schema.TaggedStruct<"Cancelled", {
6
7
  readonly reason: Schema.Literals<readonly ["escape", "interrupt"]>;
@@ -1847,6 +1848,24 @@ export declare class CliLog {
1847
1848
  * cannot lower it below the level the layer installed, because Effect has already dropped those records.
1848
1849
  */
1849
1850
  static readonly Level: Context.Reference<LogLevel.LogLevel>;
1851
+ /**
1852
+ * Read a log level the way `CliLog.layer` reads its environment variable: `Option.some` of the level, or
1853
+ * `Option.none()` when the text is not one.
1854
+ *
1855
+ * @remarks
1856
+ * Case-insensitive, and not trimmed. The spellings are `all`, `trace`, `debug`, `info`, `warn` (or `warning`),
1857
+ * `error`, `fatal` and `none`. For a host that reads a level from its own flag or config and wants the same answer
1858
+ * the layer would give.
1859
+ *
1860
+ * @example
1861
+ * ```ts
1862
+ * CliLog.parseLevel("WARNING") // Option.some("Warn")
1863
+ * CliLog.parseLevel("loud") // Option.none()
1864
+ * ```
1865
+ *
1866
+ * @param text - the level's spelling
1867
+ */
1868
+ static parseLevel(text: string): Option.Option<LogLevel.LogLevel>;
1850
1869
  /**
1851
1870
  * The whole logger set of a program: a `CliLogger` for ordinary lines plus the diagnostics sink.
1852
1871
  *
@@ -2030,10 +2049,21 @@ interface CliEnvOptions {
2030
2049
  */
2031
2050
  readonly formatter?: Partial<CliOutput.Formatter> | undefined;
2032
2051
  /**
2033
- * Turns an absolute path into its display form, for example relative to the workspace: the stack frames of the
2034
- * default failure report are shown through it. Only `CliRuntime.main` reads this. The identity by default.
2052
+ * How an absolute path is shown: a function, or `{ relativeTo: root }` to show a path under `root` relative to it.
2053
+ * The identity by default.
2054
+ *
2055
+ * @remarks
2056
+ * It is every path the kit shows for this run: `Render.context` takes it as its default, so `Doc.print`, a
2057
+ * `Doc.path` or file link, and a live view's `final` document follow it, and so do the stack frames of the default
2058
+ * failure report. An explicit `displayPath` passed to `Doc.print` or `Render.context` still wins.
2059
+ *
2060
+ * `{ relativeTo: root }` takes an absolute `root`. A path under it is shown relative to it; `root` itself, and a path
2061
+ * outside it, are shown absolute, since a `../..` chain says less than the absolute path. It uses the `Path` service
2062
+ * when the platform provides one (as `CliRuntime.main`'s does), and otherwise a plain prefix test on `/` and `\`.
2035
2063
  */
2036
- readonly displayPath?: ((absolute: string) => string) | undefined;
2064
+ readonly displayPath?: ((absolute: string) => string) | {
2065
+ readonly relativeTo: string;
2066
+ } | undefined;
2037
2067
  /**
2038
2068
  * Which frames of a defect's stack the default failure report shows: `app`, the default, leaves out every
2039
2069
  * `node_modules` frame (Effect's and any other dependency's) and the runtime's own (every `node:` frame and every
@@ -2126,6 +2156,15 @@ type CliEnvTestServices = TerminalEnv | Audience | CliTheme;
2126
2156
  * `Console` or `Stdio`, never `Terminal`. It also installs `CliTheme.promptTheme`, so core's prompts follow the
2127
2157
  * theme.
2128
2158
  *
2159
+ * It sets core's `TerminalCapabilities` from `TerminalEnv` (through `TerminalEnv.capabilities` in `@effected/env`), so
2160
+ * the colour of core's own help, version and error output follows the kit's colour rule, and `canAnimate` is stdout
2161
+ * being a terminal that is not `dumb`. `canPrompt` alone is held at `true` whatever the terminal is: core skips a
2162
+ * fallback prompt entirely when it is `false`, which would lose `CliPrompt.fallback`'s and `CliUi.fallback`'s
2163
+ * `otherwise` in every piped run, and `CliInteractive` with `CliPrompt.gateTerminal` already owns that decision (the
2164
+ * gate quits any other prompt at once when the run is not interactive). A `TerminalCapabilities` with
2165
+ * `canPrompt: false` provided inside the program bypasses the kit's fallbacks: the parameter then fails as missing even
2166
+ * when an `otherwise` is given.
2167
+ *
2129
2168
  * @public
2130
2169
  */
2131
2170
  export declare class CliEnv {
@@ -2143,7 +2182,7 @@ export declare class CliEnv {
2143
2182
  * The environment services a test fixes, needing nothing and reading nothing of the host's: `TerminalEnv` and
2144
2183
  * `Audience` from the answers given, `CliTheme` built from them as {@link CliEnv.layer} builds it, and
2145
2184
  * `CliInteractive` set from them by the same rule (a human, every stream a terminal, and a `TERM` that is not
2146
- * `dumb`).
2185
+ * `dumb`), and core's `TerminalCapabilities` from them as `CliEnv.layer` sets it, `canPrompt` held at `true`.
2147
2186
  *
2148
2187
  * @remarks
2149
2188
  * `term` is handed to the theme and interactivity builds alone, through a `ConfigProvider` of their own: the
@@ -2310,7 +2349,8 @@ interface CliFailureOptions {
2310
2349
  * one line `interrupted`.
2311
2350
  *
2312
2351
  * All text goes through the document, so a control character in a message or a stack frame never reaches the terminal.
2313
- * Render it with `Render.context` and `Render.plain`, `ansi`, `markdown` or `githubLog`, or `Doc.print` it.
2352
+ * Building it never throws: an error whose `_tag`, `message`, `stack`, `cause` or `issue` getter throws is drawn without
2353
+ * that part (an unreadable `stack` as `no stack`, an unreadable `cause` ending the chain there). Render it with `Render.context` and `Render.plain`, `ansi`, `markdown` or `githubLog`, or `Doc.print` it.
2314
2354
  *
2315
2355
  * @public
2316
2356
  */
@@ -2324,6 +2364,143 @@ export declare class CliFailure {
2324
2364
  */
2325
2365
  static readonly toDoc: (cause: Cause.Cause<unknown>, options?: CliFailureOptions) => Document;
2326
2366
  }
2367
+ /**
2368
+ * An output format a `--format` flag can offer: `auto` (the renderer the audience gets, as `Doc.print` picks it),
2369
+ * one of the `Doc` renderers by name, or `json`, the command's data rather than its document.
2370
+ *
2371
+ * @public
2372
+ */
2373
+ type CliFormatName = "auto" | "plain" | "ansi" | "markdown" | "githubLog" | "json";
2374
+ /**
2375
+ * Options for {@link CliFormat.flag}.
2376
+ *
2377
+ * @typeParam F - the built-in formats offered under their own names
2378
+ * @typeParam A - the caller's alias names, each standing for a built-in format
2379
+ * @typeParam C - the caller's own formats, which the caller renders itself
2380
+ *
2381
+ * @public
2382
+ */
2383
+ interface CliFormatFlagOptions<F extends CliFormatName, A extends Readonly<Record<string, CliFormatName>> = Record<never, CliFormatName>, C extends string = never> {
2384
+ /**
2385
+ * The format when the flag is not given, spelled as on the command line (an alias by its own name): `auto` when it
2386
+ * is offered, otherwise the first one.
2387
+ */
2388
+ readonly default?: F | Extract<keyof A, string> | C | undefined;
2389
+ /** The flag's name; `format` by default. */
2390
+ readonly name?: string | undefined;
2391
+ /** The flag's description in help; one naming the offered formats by default. */
2392
+ readonly description?: string | undefined;
2393
+ /**
2394
+ * Names of the caller's own that stand for a built-in format: `{ human: "auto" }` offers `--format human`, whose
2395
+ * value is `auto`. The target need not be offered under its own name, so `["json"]` with `{ human: "auto" }` offers
2396
+ * exactly `json` and `human`. An alias may not reuse a built-in format's name or any other offered name.
2397
+ */
2398
+ readonly aliases?: A | undefined;
2399
+ /**
2400
+ * Formats the caller renders itself, such as `mermaid` or `dot`: offered under their own names and yielded as
2401
+ * themselves. {@link CliFormat.print} takes none of them; branch on a custom value before printing. A custom name
2402
+ * may not reuse a built-in format's name or any other offered name.
2403
+ */
2404
+ readonly custom?: ReadonlyArray<C> | undefined;
2405
+ }
2406
+ /**
2407
+ * What {@link CliFormat.print} prints: the command's document and, for `json`, its data.
2408
+ *
2409
+ * @public
2410
+ */
2411
+ interface CliFormatOutput {
2412
+ /** The document, built only when a document format is chosen. */
2413
+ readonly doc: () => Document;
2414
+ /** The data `json` prints, built only when `json` is chosen. A `json` format with no `json` here is a defect. */
2415
+ readonly json?: (() => unknown) | undefined;
2416
+ }
2417
+ /**
2418
+ * Options for {@link CliFormat.print}.
2419
+ *
2420
+ * @public
2421
+ */
2422
+ interface CliFormatPrintOptions {
2423
+ /** The stream to write to; stdout by default. */
2424
+ readonly stream?: "stdout" | "stderr" | undefined;
2425
+ /** The JSON indent, in spaces; `2` by default, `0` for one line. */
2426
+ readonly indent?: number | undefined;
2427
+ }
2428
+ /**
2429
+ * A `--format` flag and the one print that honours it, so a command builds its report once as a `Doc` and its data
2430
+ * once as a value, and never writes per-format output by hand.
2431
+ *
2432
+ * @remarks
2433
+ * The flag is core's `Flag.ChoiceWithValue` with the kit's names, default and description; the print hands every
2434
+ * document format to `Doc.print` and writes `json` itself.
2435
+ *
2436
+ * ```ts
2437
+ * const format = CliFormat.flag(["auto", "markdown", "json"])
2438
+ * const doctor = Command.make("doctor", { format }, ({ format }) =>
2439
+ * CliFormat.print(format, { doc: () => report(results), json: () => results }),
2440
+ * )
2441
+ * ```
2442
+ *
2443
+ * @public
2444
+ */
2445
+ export declare class CliFormat {
2446
+ private constructor();
2447
+ /**
2448
+ * A `--format` flag offering `formats`, and the caller's own `aliases` and `custom` formats.
2449
+ *
2450
+ * @remarks
2451
+ * Its default is `options.default`, else `auto` when it is offered, else the first format. Any other value is a
2452
+ * usage error from core's parser, naming the accepted ones. The value is the built-in format a name stands for
2453
+ * (`human` under `{ human: "auto" }` is `auto`), or a custom name as itself, so the value of a flag with no custom
2454
+ * formats goes straight to {@link CliFormat.print}.
2455
+ *
2456
+ * Two offered names that clash (an alias or custom name reusing a built-in name, or any name offered twice) and a
2457
+ * default that is not offered are programming mistakes: the call throws. A CLI that still accepts a `--json` of its
2458
+ * own declares and resolves that flag itself; {@link CliFormat.printJson} prints the JSON the same way.
2459
+ *
2460
+ * ```ts
2461
+ * // okfit's contract: --format human|json, and a graph command that also draws mermaid and dot
2462
+ * const format = CliFormat.flag(["json"], { aliases: { human: "auto" }, default: "human" })
2463
+ * const graph = CliFormat.flag(["json"], { custom: ["mermaid", "dot"], default: "mermaid" })
2464
+ * ```
2465
+ *
2466
+ * @param formats - the built-in formats the command offers under their own names, at least one
2467
+ * @param options - the default, the flag's name and description, aliases and custom formats
2468
+ */
2469
+ static readonly flag: <const F extends CliFormatName, const A extends Readonly<Record<string, CliFormatName>> = Record<never, CliFormatName>, const C extends string = never>(formats: readonly [F, ...ReadonlyArray<F>], options?: CliFormatFlagOptions<F, A, C>) => Flag.Flag<F | A[keyof A] | C>;
2470
+ /**
2471
+ * Print a command's output in `format`.
2472
+ *
2473
+ * @remarks
2474
+ * `json` writes `output.json()` exactly as {@link CliFormat.printJson} writes a value, which it delegates to. Every
2475
+ * other format prints `output.doc()` with `Doc.print`, which renders it as `auto` or the named renderer would.
2476
+ *
2477
+ * @param format - the chosen format, usually the value of {@link CliFormat.flag}
2478
+ * @param output - the document and the data
2479
+ * @param options - the stream and the JSON indent
2480
+ */
2481
+ static readonly print: (format: CliFormatName, output: CliFormatOutput, options?: CliFormatPrintOptions) => Effect.Effect<void, never, CliTheme | TerminalEnv | Audience | CliLinks>;
2482
+ /**
2483
+ * Print `value` as JSON, the way {@link CliFormat.print} prints the `json` format, for a CLI that keeps a JSON flag
2484
+ * of its own.
2485
+ *
2486
+ * @remarks
2487
+ * One `Console.log` (or `Console.error` for stderr), indented by `indent`: one document, never one line per item.
2488
+ * Under GitHub Actions a `##[` inside it is written as the JSON escape `#\u0023[`, which decodes to the same
2489
+ * text, so the runner cannot read it as a command. A value JSON cannot hold (a cycle, a bigint, `undefined`) is a
2490
+ * defect. It needs no theme, terminal or audience: whether this is GitHub Actions is read from `CurrentRuntimeEnv`
2491
+ * when the environment has it, and is "no" otherwise.
2492
+ *
2493
+ * ```ts
2494
+ * const status = Command.make("status", { json: Flag.Boolean("json").pipe(Flag.withDefault(false)) }, ({ json }) =>
2495
+ * json ? CliFormat.printJson(results) : Doc.print(report(results)),
2496
+ * )
2497
+ * ```
2498
+ *
2499
+ * @param value - the data to print
2500
+ * @param options - the stream and the JSON indent
2501
+ */
2502
+ static readonly printJson: (value: unknown, options?: CliFormatPrintOptions) => Effect.Effect<void>;
2503
+ }
2327
2504
  declare const CliInteractive_base: Context.Reference<boolean>;
2328
2505
  /**
2329
2506
  * Whether this run may prompt a person: a human audience, with a terminal on
@@ -2343,6 +2520,19 @@ declare const CliInteractive_base: Context.Reference<boolean>;
2343
2520
  * {@link CliInteractive.layerTest} are typed `Layer<never>`: they set the
2344
2521
  * reference rather than provide a service.
2345
2522
  *
2523
+ * Both layers also hold core's `TerminalCapabilities.canPrompt` at `true`,
2524
+ * keeping its `canColor` and `canAnimate` as they find them. Core skips a
2525
+ * `Flag.withFallbackPrompt` or `Argument.withFallbackPrompt` fallback entirely
2526
+ * when `canPrompt` is `false`, and decides that from the process's own
2527
+ * terminal, so a kit fallback (`CliPrompt.fallback`, `CliUi.fallback`) would
2528
+ * never be called in a piped run and its `otherwise` would be lost. With the
2529
+ * gate held open, this reference makes the prompt decision instead: the kit's
2530
+ * fallbacks answer without a person when it is `false`, and
2531
+ * `CliPrompt.gateTerminal` quits any other prompt at once. A
2532
+ * `TerminalCapabilities` with `canPrompt: false` provided inside the program,
2533
+ * under these layers, bypasses the kit's fallbacks again: the parameter fails
2534
+ * as missing even when an `otherwise` is given.
2535
+ *
2346
2536
  * @public
2347
2537
  */
2348
2538
  export declare class CliInteractive extends CliInteractive_base {
@@ -2356,11 +2546,13 @@ export declare class CliInteractive extends CliInteractive_base {
2356
2546
  * as `@effected/env` reads the environment, so it adds no requirement; a test fixes it with
2357
2547
  * `Effect.provideService(ConfigProvider.ConfigProvider, ConfigProvider.fromUnknown({ TERM: "dumb" }))`.
2358
2548
  *
2549
+ * It also holds core's `TerminalCapabilities.canPrompt` at `true`; see {@link CliInteractive}.
2550
+ *
2359
2551
  * Bind the layer to a constant and provide it once; `Audience` and `TerminalEnv` come from `@effected/env`.
2360
2552
  */
2361
2553
  static readonly layer: Layer.Layer<never, never, Audience | TerminalEnv>;
2362
2554
  /**
2363
- * A fixed answer that needs nothing.
2555
+ * A fixed answer that needs nothing. It holds core's `canPrompt` open too, as {@link CliInteractive.layer} does.
2364
2556
  *
2365
2557
  * @param value - whether the run is interactive
2366
2558
  */
@@ -2498,6 +2690,13 @@ export declare class CliPrompt {
2498
2690
  * input (core acquires it only when a prompt needs a key), so piped stdin is left untouched without any gate.
2499
2691
  * `CliPrompt.gateTerminal`, which `CliEnv.layer` installs, covers the prompts this function does not wrap.
2500
2692
  *
2693
+ * Core calls a fallback only when its `TerminalCapabilities.canPrompt` is `true`, and its own default decides that
2694
+ * from the process's terminal, so in a piped run it would fail the parameter as missing without calling this
2695
+ * function and `otherwise` would be lost. `CliInteractive.layer` and `CliInteractive.layerTest` (and so
2696
+ * `CliEnv.layer`) hold `canPrompt` at `true` for that reason: the decision is `CliInteractive`'s, made here. A
2697
+ * `TerminalCapabilities` with `canPrompt: false` provided inside the program bypasses this fallback again, and a
2698
+ * program that sets `CliInteractive` with neither layer keeps core's own gate.
2699
+ *
2501
2700
  * @param prompt - the prompt to show
2502
2701
  * @param options - the parameter it stands in for, and the non-interactive default
2503
2702
  */
@@ -2598,6 +2797,22 @@ interface FailureDetails {
2598
2797
  readonly status?: boolean | undefined;
2599
2798
  readonly spans?: "app" | "all" | "off" | undefined;
2600
2799
  }) => ReadonlyArray<string>;
2800
+ /**
2801
+ * The exit code this failure exits with: the error's own `Runtime.errorExitCode` when it carries one, else its
2802
+ * entry in `exitCodes` (a typed failure only), else `exitCode`, else `1`.
2803
+ *
2804
+ * @remarks
2805
+ * The code is decided before `render` is called, so a render can print it. A hook that parses a fixed stderr shape
2806
+ * builds it here, reading the tag off `error` itself:
2807
+ *
2808
+ * ```ts
2809
+ * const render = (error: unknown, details: FailureDetails) => {
2810
+ * const tag = Predicate.hasProperty(error, "_tag") && Predicate.isString(error._tag) ? error._tag : "Defect";
2811
+ * return `${details.exitCode} ${tag}: ${Fmt.sanitize(Fmt.describe(error))}`;
2812
+ * };
2813
+ * ```
2814
+ */
2815
+ readonly exitCode: number;
2601
2816
  }
2602
2817
  /**
2603
2818
  * How a failure is turned into output and an exit code.
@@ -2637,6 +2852,23 @@ interface ReportFailuresOptions {
2637
2852
  * POSIX exit status can carry — `256` wraps to `0` and passes a failed run.
2638
2853
  */
2639
2854
  readonly exitCode?: number | undefined;
2855
+ /**
2856
+ * Exit codes by tag: a typed failure whose `_tag` is a key here exits with that code.
2857
+ *
2858
+ * @remarks
2859
+ * Checked after the error's own `Runtime.errorExitCode` (an error that chose its code keeps it) and before
2860
+ * `exitCode`, which stays the fallback for every other failure, defects included. Only a typed failure is looked
2861
+ * up: a defect that happens to carry a `_tag` exits with `exitCode`. A usage error (`usageExitCode`), a cancel
2862
+ * (`130`) and a non-interactive prompt (`64`) carry their own codes and are not looked up either.
2863
+ *
2864
+ * Each code must be an integer in `1..255`: `0` would pass a failed run, and `256` wraps to `0`. Anything else
2865
+ * throws a `RangeError` when the options are passed, since it is a wiring mistake, not a runtime condition.
2866
+ *
2867
+ * ```ts
2868
+ * CliRuntime.main(program, { platform, exitCodes: { DataStoreError: 3, SidecarTimeoutError: 2 }, exitCode: 5 })
2869
+ * ```
2870
+ */
2871
+ readonly exitCodes?: Readonly<Record<string, number>> | undefined;
2640
2872
  /**
2641
2873
  * The exit code for a usage error: a `ShowHelp` carrying parse errors, or a
2642
2874
  * `CliError.UserError` `Command.runWith` already printed.
@@ -2702,17 +2934,40 @@ interface MainOptions<RP, EP> extends ReportFailuresOptions {
2702
2934
  * bare invocation of a command group still print help on stdout: neither
2703
2935
  * is an error.
2704
2936
  *
2937
+ * `main` routes through core's `CliOutput.Presenter`, so the help and
2938
+ * errors are rendered by whichever `CliOutput` Formatter is in effect
2939
+ * where `Command.runWith` runs, one provided inside `program` included,
2940
+ * and written to whichever `Console` is in effect there. To change the
2941
+ * Formatter `main` installs, pass `env.formatter` (for example
2942
+ * `formatVersion`); a Formatter the platform provides is shadowed by
2943
+ * `main`'s, so pass it through `env.formatter` as well.
2944
+ *
2705
2945
  * Two cases keep help on stdout even under `"stderr"`. A `CliOutput`
2706
- * Formatter or a `Console` provided inside `program` is not seen by
2707
- * `main`, so its help is not rerouted. To change the Formatter, pass
2708
- * `env.formatter` (for example `formatVersion`): `main` installs its own
2709
- * Formatter inside the platform, so that is the way in, and a Formatter
2710
- * provided inside the program is invisible to the routing. A Formatter
2711
- * the platform provides is shadowed by `main`'s, so pass it through
2712
- * `env.formatter` as well. And with `Command.runWith`'s `renderErrors: false`
2713
- * no errors are printed, so nothing marks the help as a usage error's.
2946
+ * Presenter provided inside `program` replaces the routing. And with
2947
+ * `Command.runWith`'s `renderErrors: false` core presents the help as
2948
+ * plain help, with no errors, so it is not a usage error's.
2714
2949
  */
2715
2950
  readonly helpOnUsageError?: "stdout" | "stderr" | undefined;
2951
+ /**
2952
+ * The carrier package the bin was installed through, when a carrier launched it: `null` (or `undefined`) for a
2953
+ * direct install.
2954
+ *
2955
+ * @remarks
2956
+ * Given (even as `null`), `main` provides `@effected/engine`'s `CurrentDistribution` around the program, as
2957
+ * `Option.some` of the carrier or `Option.none()`, so a program and the engine it calls read the same value. With a
2958
+ * carrier, `--version` ends with its `via <name> <version>` suffix (`distributionSuffix`), appended to whatever
2959
+ * `formatVersion` is in effect, the coloured default or `env.formatter`'s: do not also append it in
2960
+ * `env.formatter`.
2961
+ *
2962
+ * Core's default `formatVersion` prints `<name> v<version>`. A program moving off a hand-rolled `--version` that
2963
+ * printed another shape (no `v`, say) keeps it by passing that shape as `env.formatter`'s `formatVersion`; the
2964
+ * suffix is appended to it either way.
2965
+ *
2966
+ * ```ts
2967
+ * CliRuntime.main(CliAudience.run(root, { version }), { platform, env, distribution: options.distribution ?? null })
2968
+ * ```
2969
+ */
2970
+ readonly distribution?: Distribution | null | undefined;
2716
2971
  }
2717
2972
  /**
2718
2973
  * Report a CLI program's failures through the program's own logger.
@@ -2771,7 +3026,10 @@ interface MainOptions<RP, EP> extends ReportFailuresOptions {
2771
3026
  * already rendered through its `CliOutput` formatter. It exits with its own
2772
3027
  * `Runtime.errorExitCode` when it carries one, otherwise `usageExitCode`;
2773
3028
  * under runWith's `renderErrors: false` the mark stays set, and it renders
2774
- * here like any other failure. Every other
3029
+ * here like any other failure. A handler failure carrying a
3030
+ * `CliError.userMessage` is skipped the same way, since `Command.runWith`
3031
+ * renders it and sets the mark to `false`; it keeps the exit code a
3032
+ * handler failure gets (`exitCodes` by tag, then `exitCode`). Every other
2775
3033
  * error renders even when it already carries the reported mark — a gate
2776
3034
  * failure marked with `CliRuntime.reported` still prints its line. The
2777
3035
  * private `ExitRequested` sentinel `CliRuntime.main`
@@ -2884,8 +3142,10 @@ export declare class CliRuntime {
2884
3142
  *
2885
3143
  * Under `CliRuntime.main` or {@link CliRuntime.reportFailures}, do NOT
2886
3144
  * print the failure yourself before failing with it: `reportFailures`
2887
- * renders every error except a `ShowHelp` and a `CliError.UserError` whose
2888
- * reported mark is `false`, so it would print twice. Fail with the marked
3145
+ * renders every error except a `ShowHelp`, a `CliError.UserError` whose
3146
+ * reported mark is `false`, and any error carrying a string
3147
+ * `[CliError.userMessage]` whose reported mark is `false`, so it would
3148
+ * print twice. Fail with the marked
2889
3149
  * error and put any multi-line rendering in the `render` option instead. The
2890
3150
  * mark matters for a program run WITHOUT `reportFailures`, where it keeps the
2891
3151
  * runtime from reporting a failure the program already printed.
@@ -2897,6 +3157,12 @@ export declare class CliRuntime {
2897
3157
  * mark. It does keep the code you pass: `reported(userError, 3)` exits `3`,
2898
3158
  * not `usageExitCode`.
2899
3159
  *
3160
+ * The same holds for an error carrying a string `[CliError.userMessage]`:
3161
+ * `Command.runWith` prints such a handler failure and leaves the same
3162
+ * `false` mark, so one marked here is treated as already printed and is not
3163
+ * rendered. Mark an error without the annotation, or leave it unmarked, if
3164
+ * the program has not printed it. It keeps the code you pass.
3165
+ *
2900
3166
  * The marks are added in place, so a typed error comes back as its own
2901
3167
  * type: the `E` overload returns the very instance it was given, and a
2902
3168
  * program failing with it keeps `catchTags` narrowing downstream without a
@@ -2959,6 +3225,12 @@ export declare class ConfigIssueRenderer {
2959
3225
  * `Effect.catchTag("ConfigValidationError", …)` the error is already this
2960
3226
  * type.
2961
3227
  *
3228
+ * An unknown key the schema has a near match for at the same path ends in a
3229
+ * hint, `unknown key at theme.colour (did you mean "color"?)`, drawn from the
3230
+ * keys the struct declares there (every branch's, under a union) with
3231
+ * {@link Fmt.suggest}'s threshold. A key with no near match is drawn exactly
3232
+ * as {@link SchemaIssueRenderer} draws it.
3233
+ *
2962
3234
  * It cannot throw on a malformed value: the issue tree is validated by
2963
3235
  * a guard before it is read, so a renderer on an error path never becomes the
2964
3236
  * reason a program dies.
@@ -2988,6 +3260,29 @@ interface PercentOptions {
2988
3260
  */
2989
3261
  readonly scale?: 1 | 100 | undefined;
2990
3262
  }
3263
+ /**
3264
+ * Options for {@link Fmt.pad}.
3265
+ *
3266
+ * @public
3267
+ */
3268
+ interface PadOptions {
3269
+ /**
3270
+ * Where the text sits in the padded field: `left` (the default) fills on the right, `right` fills on the left, and
3271
+ * `center` splits the fill, the odd column going on the right.
3272
+ */
3273
+ readonly align?: "left" | "right" | "center" | undefined;
3274
+ /** The fill, one column wide; a space by default. Anything that is not exactly one column wide is a space. */
3275
+ readonly fill?: string | undefined;
3276
+ }
3277
+ /**
3278
+ * Options for {@link Fmt.suggest}.
3279
+ *
3280
+ * @public
3281
+ */
3282
+ interface SuggestOptions {
3283
+ /** The most edits a suggestion may be away from the input; `2` by default, as `effect/cli` uses. */
3284
+ readonly maxDistance?: number | undefined;
3285
+ }
2991
3286
  /**
2992
3287
  * Small, pure formatting primitives for terminal output.
2993
3288
  *
@@ -3067,6 +3362,129 @@ export declare class Fmt {
3067
3362
  * @param plural - the noun otherwise; `singular` with an `s` appended by default
3068
3363
  */
3069
3364
  static readonly plural: (n: number, singular: string, plural?: string) => string;
3365
+ /**
3366
+ * Pad `text` to `width` terminal columns, measured as {@link Fmt.width} measures: a wide character counts two and
3367
+ * an escape sequence none, so coloured text pads to the same column as plain text.
3368
+ *
3369
+ * @remarks
3370
+ * Text already `width` columns or wider is returned unchanged: compose with {@link Fmt.truncate} to cut it. A
3371
+ * `width` that is not a finite number pads nothing. Never throws.
3372
+ *
3373
+ * @param text - the text to pad
3374
+ * @param width - the columns the result should take
3375
+ * @param options - the alignment and the fill
3376
+ */
3377
+ static readonly pad: (text: string, width: number, options?: PadOptions) => string;
3378
+ /**
3379
+ * `text` without its escape sequences: CSI (colour, cursor moves, erases), OSC (titles, OSC 8 hyperlinks) ending in
3380
+ * BEL or ST, the DCS, SOS, PM and APC strings, and every other escape, the nF sequences (`ESC ( B`) and the
3381
+ * two-byte ones (`ESC 7`, `ESC 8`, `ESC =`, `ESC c`).
3382
+ *
3383
+ * @remarks
3384
+ * The same strip {@link Fmt.width} and {@link Fmt.sanitize} start from. Only complete sequences go: a lone ESC, or
3385
+ * any other control character, is kept, and so is an ESC in front of a lowercase letter other than `c`, which a
3386
+ * stray ESC before a word would otherwise cost the word its first letter. An unterminated CSI or OSC keeps its `[`
3387
+ * or `]`. Use {@link Fmt.sanitize} for text that will be laid out and printed.
3388
+ *
3389
+ * @param text - the text
3390
+ */
3391
+ static readonly stripAnsi: (text: string) => string;
3392
+ /**
3393
+ * Whether `text` is nothing but escape sequences, as {@link Fmt.stripAnsi} reads them: a cursor hide or show, a
3394
+ * clear-screen, an erase-scrollback. Empty text is not escape-only, and neither is a bare control character
3395
+ * (`\r`, BEL), which is not an escape sequence.
3396
+ *
3397
+ * @param text - the text
3398
+ */
3399
+ static readonly isEscapeOnly: (text: string) => boolean;
3400
+ /**
3401
+ * Integers as compact ranges: `[1, 2, 3, 5, 10, 11]` is `"1-3,5,10-11"`, a run of consecutive numbers written
3402
+ * `start-end` and the rest one by one, joined with commas.
3403
+ *
3404
+ * @remarks
3405
+ * The input is sorted and its duplicates dropped first, so order does not matter. Anything that is not a finite
3406
+ * integer is left out, and no input is `""`. Never throws.
3407
+ *
3408
+ * @param numbers - the integers, such as line numbers
3409
+ */
3410
+ static readonly ranges: (numbers: Iterable<number>) => string;
3411
+ /**
3412
+ * Any value as text, without ever throwing.
3413
+ *
3414
+ * @remarks
3415
+ * A string is itself; a number, boolean, bigint or symbol is `String(value)`; `undefined` and `null` are
3416
+ * `"undefined"` and `"null"`. A value with a string `message` (an `Error`, one from another realm, a plain
3417
+ * `{ message }`) is that message. Anything else is its JSON, or `String(value)` when it has none (a cycle, a
3418
+ * function), or `"[unprintable value]"` when that throws too. A getter that throws is a value with no message.
3419
+ *
3420
+ * The text is not sanitised: pass it through {@link Fmt.sanitize} before printing data you do not control. For
3421
+ * the messages along an error's `cause` chain, use {@link Fmt.causeMessage}; for the two combined, use
3422
+ * {@link Fmt.errorText}.
3423
+ *
3424
+ * It is `@effected/engine`'s `ErrorText.describe`, the same function, so a front end that cannot depend on this
3425
+ * package (an MCP server) describes a value with the same words.
3426
+ *
3427
+ * @param value - the value
3428
+ */
3429
+ static readonly describe: (value: unknown) => string;
3430
+ /**
3431
+ * Every distinct non-empty message along a value's `cause` chain, nearest first and joined with `: `, or
3432
+ * `undefined` when nothing along it has one.
3433
+ *
3434
+ * @remarks
3435
+ * The kit's one guarded cause-message read, the one its own errors render a preserved cause with. A node's message
3436
+ * is a thrown string or a non-empty string `message` property, read without `instanceof Error`, so an error from
3437
+ * another realm or a plain `{ message }` object counts. A deeper message the joined text already contains is not
3438
+ * repeated, and one that contains the joined text replaces it, so a `SqlError` wrapping SQLite's own text reads
3439
+ * `Failed to execute statement: SQLITE_BUSY: database is locked`. It is the whole chain, not the innermost
3440
+ * message alone. The walk stops after eight nodes, so a cyclic chain terminates.
3441
+ *
3442
+ * Never throws: a `message` getter that throws leaves that node without text, and a `cause` getter that throws ends
3443
+ * the walk with the text found so far, `undefined` when there is none, so a caller's own fallback
3444
+ * (`?? Fmt.describe(value)`, which {@link Fmt.errorText} spells once) applies.
3445
+ *
3446
+ * It is `@effected/engine`'s `ErrorText.causeMessage`, the same function.
3447
+ *
3448
+ * @param value - the error, or whatever was thrown
3449
+ */
3450
+ static readonly causeMessage: (value: unknown) => string | undefined;
3451
+ /**
3452
+ * The text of an unknown failure: {@link Fmt.causeMessage} when the chain carries a message, otherwise
3453
+ * {@link Fmt.describe}.
3454
+ *
3455
+ * @remarks
3456
+ * Exactly `Fmt.causeMessage(value) ?? Fmt.describe(value)`, the fallback an I/O site turning a caught value into
3457
+ * text would otherwise spell out by hand at every call. An error whose message is empty and whose chain carries none
3458
+ * is `""`, as `describe` reads it. Never throws, and not sanitised: pass it through {@link Fmt.sanitize} before
3459
+ * printing data you do not control. It is `@effected/engine`'s `ErrorText.of`, the same function.
3460
+ *
3461
+ * @param value - the error, or whatever was thrown
3462
+ */
3463
+ static readonly errorText: (value: unknown) => string;
3464
+ /**
3465
+ * The candidates nearest to `input`, for a "did you mean" hint: those at the smallest edit distance found, up to
3466
+ * `maxDistance` edits (two by default), in the order given, each once. No candidate that close is `[]`.
3467
+ *
3468
+ * @remarks
3469
+ * The distance is Levenshtein's (one insertion, deletion or substitution per edit), over UTF-16 code units and
3470
+ * case-sensitive, with the same two-edit threshold and the same ties-all-kept rule `effect/cli` uses for an unknown
3471
+ * subcommand or flag, so a program's own suggestions agree with the parser's. A candidate equal to `input` is at
3472
+ * distance zero and is the only one returned.
3473
+ *
3474
+ * The cost is linear in the candidates' total length: a candidate whose length differs from `input`'s by more than
3475
+ * `maxDistance` is skipped unmeasured, and only the band of the edit matrix within `maxDistance` of its diagonal is
3476
+ * measured, so a long typed value cannot make it quadratic. A `maxDistance` that is not a non-negative integer is a programming mistake and
3477
+ * throws a `RangeError`.
3478
+ *
3479
+ * ```ts
3480
+ * Fmt.suggest("stauts", ["status", "stack", "build"]) // => ["status"]
3481
+ * ```
3482
+ *
3483
+ * @param input - what was typed: an unknown key, a profile name, a phase name
3484
+ * @param candidates - the names it could have meant
3485
+ * @param options - the most edits a suggestion may be away
3486
+ */
3487
+ static readonly suggest: (input: string, candidates: Iterable<string>, options?: SuggestOptions) => ReadonlyArray<string>;
3070
3488
  }
3071
3489
  /**
3072
3490
  * The severity of a GitHub annotation.
@@ -3317,6 +3735,8 @@ export declare class Render {
3317
3735
  * - `groupDepth` is the fiber's count of open Actions log groups (the fiber-local keyed by
3318
3736
  * `WorkflowCommand.groupDepthKey`, which `ActionLogger.group` provides), set when it is above `0`, so a
3319
3737
  * document printed inside a group opens no second one; see {@link RenderContext.groupDepth}.
3738
+ * - `displayPath` is the option, else the run's own path display (`CliEnvOptions.displayPath`, which
3739
+ * `CliEnv.layer` sets), else the identity.
3320
3740
  *
3321
3741
  * @param stream - the stream the output is for
3322
3742
  * @param options - an explicit width and a path display function
@@ -3527,5 +3947,5 @@ export declare class SchemaIssueRenderer {
3527
3947
  */
3528
3948
  static readonly render: (issue: unknown) => ReadonlyArray<string>;
3529
3949
  }
3530
- export type { AnnotationLevel, AnnotationOptions, AudienceFlagInput, Block, BlockOf, CliAudienceFlagsOptions, CliDocSource, CliEnvOptions, CliEnvServices, CliEnvTestOptions, CliEnvTestServices, CliExitShape, CliFailureOptions, CliLinksLinkerOptions, CliLinksOptions, CliLinksShape, CliLogFile, CliLogFileOptions, CliLogOptions, CliLogStatusOptions, CliLoggerOptions, CliMessageOptions, CliPromptFallbackOptions, CliPromptTarget, CliThemeOptions, CliThemeShape, CliThemeTestOptions, Column, CoreStatusName, Counter, CountsOptions, CountsRow, CountsTableOptions, DocPrintOptions, Document, EditorLinks, FailureDetails, GithubAnnotationProperties, GlyphSelectOptions, GlyphSet, Inline, InlineInput, InlineOf, LinkOptions, LinkTarget, ListOptions, MainOptions, NamedColor, OverflowOptions, PercentOptions, RenderContext, RenderContextOfOptions, RenderContextOptions, ReportFailuresOptions, RequiresAudienceFlags, StatusDef, StatusRef, StreamTheme, Style, TableOptions, TokenName, TreeInput, TreeNode, TruncateOptions };
3950
+ export type { AnnotationLevel, AnnotationOptions, AudienceFlagInput, Block, BlockOf, CliAudienceFlagsOptions, CliDocSource, CliEnvOptions, CliEnvServices, CliEnvTestOptions, CliEnvTestServices, CliExitShape, CliFailureOptions, CliFormatFlagOptions, CliFormatName, CliFormatOutput, CliFormatPrintOptions, CliLinksLinkerOptions, CliLinksOptions, CliLinksShape, CliLogFile, CliLogFileOptions, CliLogOptions, CliLogStatusOptions, CliLoggerOptions, CliMessageOptions, CliPromptFallbackOptions, CliPromptTarget, CliThemeOptions, CliThemeShape, CliThemeTestOptions, Column, CoreStatusName, Counter, CountsOptions, CountsRow, CountsTableOptions, DocPrintOptions, Document, EditorLinks, FailureDetails, GithubAnnotationProperties, GlyphSelectOptions, GlyphSet, Inline, InlineInput, InlineOf, LinkOptions, LinkTarget, ListOptions, MainOptions, NamedColor, OverflowOptions, PadOptions, PercentOptions, RenderContext, RenderContextOfOptions, RenderContextOptions, ReportFailuresOptions, RequiresAudienceFlags, StatusDef, StatusRef, StreamTheme, Style, SuggestOptions, TableOptions, TokenName, TreeInput, TreeNode, TruncateOptions };
3531
3951
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -15,6 +15,7 @@ import { CliColor } from "./CliColor.js";
15
15
  import { CliPrompt } from "./CliPrompt.js";
16
16
  import { CliEnv } from "./CliEnv.js";
17
17
  import { CliExit } from "./CliExit.js";
18
+ import { CliFormat } from "./CliFormat.js";
18
19
  import { CliLogger } from "./CliLogger.js";
19
20
  import { CliLog } from "./CliLog.js";
20
21
  import { CliMessage } from "./CliMessage.js";
@@ -23,4 +24,4 @@ import { ConfigIssueRenderer } from "./ConfigIssueRenderer.js";
23
24
  import { GithubAnnotation } from "./GithubAnnotation.js";
24
25
  import { SchemaIssueRenderer } from "./SchemaIssueRenderer.js";
25
26
 
26
- export { Cancelled, CliAudience, CliColor, CliDoc, CliEnv, CliExit, CliFailure, CliInteractive, CliLinks, CliLog, CliLogger, CliMessage, CliPrompt, CliRuntime, CliTheme, ConfigIssueRenderer, Doc, Fmt, GithubAnnotation, Glyphs, NotInteractive, Render, SchemaIssueRenderer, Status, Token };
27
+ export { Cancelled, CliAudience, CliColor, CliDoc, CliEnv, CliExit, CliFailure, CliFormat, CliInteractive, CliLinks, CliLog, CliLogger, CliMessage, CliPrompt, CliRuntime, CliTheme, ConfigIssueRenderer, Doc, Fmt, GithubAnnotation, Glyphs, NotInteractive, Render, SchemaIssueRenderer, Status, Token };