@cruxy/cli 0.3.0 → 0.6.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 (76) hide show
  1. package/README.md +46 -1
  2. package/dist/agent/index.d.ts +0 -1
  3. package/dist/agent/index.js +0 -1
  4. package/dist/agent/loop.js +2 -2
  5. package/dist/agent/prompts.d.ts +0 -2
  6. package/dist/agent/prompts.js +3 -3
  7. package/dist/approval/classify.d.ts +18 -0
  8. package/dist/approval/classify.js +162 -0
  9. package/dist/approval/index.d.ts +5 -0
  10. package/dist/approval/index.js +5 -0
  11. package/dist/approval/policy.d.ts +37 -0
  12. package/dist/approval/policy.js +81 -0
  13. package/dist/approval/prompt.d.ts +33 -0
  14. package/dist/approval/prompt.js +212 -0
  15. package/dist/approval/service.d.ts +36 -0
  16. package/dist/approval/service.js +37 -0
  17. package/dist/approval/types.d.ts +64 -0
  18. package/dist/approval/types.js +1 -0
  19. package/dist/cli/commands/config.js +2 -3
  20. package/dist/cli/commands/index.js +5 -2
  21. package/dist/cli/commands/pr.d.ts +8 -0
  22. package/dist/cli/commands/pr.js +87 -0
  23. package/dist/cli/commands/run.js +31 -23
  24. package/dist/cli/program.js +26 -4
  25. package/dist/cli/repl.js +15 -2
  26. package/dist/config/manager.js +5 -4
  27. package/dist/config/schema.d.ts +38 -9
  28. package/dist/config/schema.js +13 -4
  29. package/dist/errors/boundary.d.ts +43 -0
  30. package/dist/errors/boundary.js +73 -0
  31. package/dist/errors/constructors.d.ts +52 -0
  32. package/dist/errors/constructors.js +329 -0
  33. package/dist/errors/format.d.ts +31 -0
  34. package/dist/errors/format.js +60 -0
  35. package/dist/errors/index.d.ts +4 -0
  36. package/dist/errors/index.js +4 -0
  37. package/dist/errors/types.d.ts +79 -0
  38. package/dist/errors/types.js +118 -0
  39. package/dist/index.js +8 -5
  40. package/dist/indexing/embedder.js +3 -3
  41. package/dist/indexing/service.js +4 -1
  42. package/dist/skills/loader.d.ts +2 -1
  43. package/dist/skills/loader.js +0 -0
  44. package/dist/skills/parser.d.ts +6 -4
  45. package/dist/skills/parser.js +13 -5
  46. package/dist/tools/create-pull-request.d.ts +24 -0
  47. package/dist/tools/create-pull-request.js +83 -0
  48. package/dist/tools/file/apply-patch.js +3 -3
  49. package/dist/tools/file/edit-file.js +6 -3
  50. package/dist/tools/file/paths.d.ts +4 -2
  51. package/dist/tools/file/paths.js +5 -3
  52. package/dist/tools/file/write-file.js +6 -3
  53. package/dist/tools/index.d.ts +1 -0
  54. package/dist/tools/index.js +1 -0
  55. package/dist/tools/registry.js +2 -0
  56. package/dist/tools/shell/run-command.js +11 -3
  57. package/dist/tools/types.d.ts +25 -6
  58. package/dist/vcs/auth.d.ts +22 -0
  59. package/dist/vcs/auth.js +29 -0
  60. package/dist/vcs/generate.d.ts +72 -0
  61. package/dist/vcs/generate.js +265 -0
  62. package/dist/vcs/git.d.ts +52 -0
  63. package/dist/vcs/git.js +152 -0
  64. package/dist/vcs/github.d.ts +44 -0
  65. package/dist/vcs/github.js +145 -0
  66. package/dist/vcs/guidance.d.ts +20 -0
  67. package/dist/vcs/guidance.js +76 -0
  68. package/dist/vcs/index.d.ts +7 -0
  69. package/dist/vcs/index.js +7 -0
  70. package/dist/vcs/service.d.ts +53 -0
  71. package/dist/vcs/service.js +79 -0
  72. package/dist/vcs/types.d.ts +57 -0
  73. package/dist/vcs/types.js +6 -0
  74. package/package.json +1 -1
  75. package/dist/agent/approval.d.ts +0 -41
  76. package/dist/agent/approval.js +0 -179
package/dist/cli/repl.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import readline from "node:readline";
2
2
  import pc from "picocolors";
3
+ import { formatError, fromUnknown, isVerbose, shouldUseColor, } from "../errors/index.js";
3
4
  import { logger } from "../utils/logger.js";
4
5
  import { createStreamPrinter } from "./stream-print.js";
5
6
  const PROMPT = `${pc.cyan("cruxy")} ${pc.dim("›")} `;
@@ -17,7 +18,7 @@ const defaultIO = () => ({
17
18
  /**
18
19
  * Read one line using a readline interface that is created and then **closed
19
20
  * before this resolves**. This is the crux of the stdin coordination: while a
20
- * turn runs (`session.send`), approvals grab stdin in raw mode via the Approver
21
+ * turn runs (`session.send`), approvals grab stdin in raw mode via the approval prompt
21
22
  * — so no readline interface may be live at that moment. Creating a fresh
22
23
  * interface per line, and closing it the instant we have input, guarantees the
23
24
  * two never contend.
@@ -49,6 +50,18 @@ function readLine(io, prompt) {
49
50
  });
50
51
  });
51
52
  }
53
+ /**
54
+ * Render a non-fatal error inline (classified + formatted, the same 4-part
55
+ * shape as the fatal boundary) and return to the prompt — the REPL must survive
56
+ * a failed turn rather than exit.
57
+ */
58
+ function printReplError(err) {
59
+ const cruxy = fromUnknown(err);
60
+ logger.print(formatError(cruxy, {
61
+ verbose: isVerbose(),
62
+ color: shouldUseColor(process.stdout),
63
+ }));
64
+ }
52
65
  /**
53
66
  * Drive an interactive multi-turn session: prompt, read a line, dispatch slash
54
67
  * commands or run a turn, repeat. Assistant text streams to stdout from within
@@ -83,7 +96,7 @@ export async function runInteractive(session, io = defaultIO()) {
83
96
  logger.print(pc.dim(n ? `compacted ${n} older messages` : "nothing to compact yet"));
84
97
  }
85
98
  catch (err) {
86
- logger.error(err.message);
99
+ printReplError(err);
87
100
  }
88
101
  continue;
89
102
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { configInvalid, configParse } from "../errors/index.js";
3
4
  import { CruxyConfigSchema } from "./schema.js";
4
5
  import { globalConfigPath, findProjectConfig } from "./paths.js";
5
6
  function isPlainObject(v) {
@@ -23,13 +24,13 @@ function readJsonFile(path) {
23
24
  try {
24
25
  const parsed = JSON.parse(readFileSync(path, "utf8"));
25
26
  if (!isPlainObject(parsed)) {
26
- throw new Error(`config at ${path} must be a JSON object`);
27
+ throw configParse(path, new Error("top-level value is not a JSON object"));
27
28
  }
28
29
  return parsed;
29
30
  }
30
31
  catch (err) {
31
32
  if (err instanceof SyntaxError) {
32
- throw new Error(`config at ${path} is not valid JSON: ${err.message}`);
33
+ throw configParse(path, err);
33
34
  }
34
35
  throw err;
35
36
  }
@@ -81,7 +82,7 @@ export function loadConfig(opts = {}) {
81
82
  const issues = result.error.issues
82
83
  .map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
83
84
  .join("\n");
84
- throw new Error(`invalid configuration:\n${issues}`);
85
+ throw configInvalid(issues, sources.project ?? sources.global ?? undefined);
85
86
  }
86
87
  return { config: result.data, sources };
87
88
  }
@@ -121,7 +122,7 @@ export function setValue(path, rawValue, file) {
121
122
  const issues = check.error.issues
122
123
  .map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
123
124
  .join("\n");
124
- throw new Error(`refusing to write invalid config:\n${issues}`);
125
+ throw configInvalid(issues, file);
125
126
  }
126
127
  mkdirSync(dirname(file), { recursive: true });
127
128
  writeFileSync(file, JSON.stringify(current, null, 2) + "\n", "utf8");
@@ -52,10 +52,20 @@ export declare const ToolsConfigSchema: z.ZodObject<{
52
52
  }>;
53
53
  export declare const GitConfigSchema: z.ZodObject<{
54
54
  autoCommit: z.ZodDefault<z.ZodBoolean>;
55
+ /** Branches cruxy never commits/pushes to directly (PR flow branches off
56
+ * first). `main` and `master` are always protected; these add to them. */
57
+ protectedBranches: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
58
+ /** Default base branch for generated pull requests; falls back to the
59
+ * repo's default branch, then `main`, when unset. */
60
+ defaultBase: z.ZodOptional<z.ZodString>;
55
61
  }, "strict", z.ZodTypeAny, {
56
62
  autoCommit: boolean;
63
+ protectedBranches: string[];
64
+ defaultBase?: string | undefined;
57
65
  }, {
58
66
  autoCommit?: boolean | undefined;
67
+ protectedBranches?: string[] | undefined;
68
+ defaultBase?: string | undefined;
59
69
  }>;
60
70
  /** Execution bounds for the `run_command` shell tool (distinct from the
61
71
  * `tools.shell` enable flag above). */
@@ -89,13 +99,18 @@ export declare const ContextConfigSchema: z.ZodObject<{
89
99
  compactThreshold?: number | undefined;
90
100
  keepRecentMessages?: number | undefined;
91
101
  }>;
92
- /** How tool-action approval is resolved. */
102
+ /**
103
+ * How tool-action approval is resolved. Only `prompt` exists: ask interactively
104
+ * and **deny by default** when non-interactive. There is deliberately no
105
+ * auto-approve / skip mode — the policy seam for a future CI mode lives in
106
+ * `src/approval` (see U.3), not behind a footgun flag.
107
+ */
93
108
  export declare const ApprovalConfigSchema: z.ZodObject<{
94
- mode: z.ZodDefault<z.ZodEnum<["prompt", "auto"]>>;
109
+ mode: z.ZodDefault<z.ZodEnum<["prompt"]>>;
95
110
  }, "strict", z.ZodTypeAny, {
96
- mode: "auto" | "prompt";
111
+ mode: "prompt";
97
112
  }, {
98
- mode?: "auto" | "prompt" | undefined;
113
+ mode?: "prompt" | undefined;
99
114
  }>;
100
115
  /**
101
116
  * Codebase semantic index (C.17): the local embedding index that backs the
@@ -249,10 +264,20 @@ export declare const CruxyConfigSchema: z.ZodObject<{
249
264
  }>>;
250
265
  git: z.ZodDefault<z.ZodObject<{
251
266
  autoCommit: z.ZodDefault<z.ZodBoolean>;
267
+ /** Branches cruxy never commits/pushes to directly (PR flow branches off
268
+ * first). `main` and `master` are always protected; these add to them. */
269
+ protectedBranches: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
270
+ /** Default base branch for generated pull requests; falls back to the
271
+ * repo's default branch, then `main`, when unset. */
272
+ defaultBase: z.ZodOptional<z.ZodString>;
252
273
  }, "strict", z.ZodTypeAny, {
253
274
  autoCommit: boolean;
275
+ protectedBranches: string[];
276
+ defaultBase?: string | undefined;
254
277
  }, {
255
278
  autoCommit?: boolean | undefined;
279
+ protectedBranches?: string[] | undefined;
280
+ defaultBase?: string | undefined;
256
281
  }>>;
257
282
  shell: z.ZodDefault<z.ZodObject<{
258
283
  /** Kill the command (and its process tree) after this many ms. */
@@ -284,11 +309,11 @@ export declare const CruxyConfigSchema: z.ZodObject<{
284
309
  keepRecentMessages?: number | undefined;
285
310
  }>>;
286
311
  approval: z.ZodDefault<z.ZodObject<{
287
- mode: z.ZodDefault<z.ZodEnum<["prompt", "auto"]>>;
312
+ mode: z.ZodDefault<z.ZodEnum<["prompt"]>>;
288
313
  }, "strict", z.ZodTypeAny, {
289
- mode: "auto" | "prompt";
314
+ mode: "prompt";
290
315
  }, {
291
- mode?: "auto" | "prompt" | undefined;
316
+ mode?: "prompt" | undefined;
292
317
  }>>;
293
318
  index: z.ZodDefault<z.ZodObject<{
294
319
  /** Master switch for `search_codebase` and `cruxy index`. */
@@ -411,6 +436,8 @@ export declare const CruxyConfigSchema: z.ZodObject<{
411
436
  };
412
437
  git: {
413
438
  autoCommit: boolean;
439
+ protectedBranches: string[];
440
+ defaultBase?: string | undefined;
414
441
  };
415
442
  context: {
416
443
  maxTokens: number;
@@ -418,7 +445,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
418
445
  keepRecentMessages: number;
419
446
  };
420
447
  approval: {
421
- mode: "auto" | "prompt";
448
+ mode: "prompt";
422
449
  };
423
450
  index: {
424
451
  search: {
@@ -466,6 +493,8 @@ export declare const CruxyConfigSchema: z.ZodObject<{
466
493
  } | undefined;
467
494
  git?: {
468
495
  autoCommit?: boolean | undefined;
496
+ protectedBranches?: string[] | undefined;
497
+ defaultBase?: string | undefined;
469
498
  } | undefined;
470
499
  context?: {
471
500
  maxTokens?: number | undefined;
@@ -473,7 +502,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
473
502
  keepRecentMessages?: number | undefined;
474
503
  } | undefined;
475
504
  approval?: {
476
- mode?: "auto" | "prompt" | undefined;
505
+ mode?: "prompt" | undefined;
477
506
  } | undefined;
478
507
  index?: {
479
508
  search?: {
@@ -40,6 +40,12 @@ export const ToolsConfigSchema = z
40
40
  export const GitConfigSchema = z
41
41
  .object({
42
42
  autoCommit: z.boolean().default(false),
43
+ /** Branches cruxy never commits/pushes to directly (PR flow branches off
44
+ * first). `main` and `master` are always protected; these add to them. */
45
+ protectedBranches: z.array(z.string()).default([]),
46
+ /** Default base branch for generated pull requests; falls back to the
47
+ * repo's default branch, then `main`, when unset. */
48
+ defaultBase: z.string().optional(),
43
49
  })
44
50
  .strict();
45
51
  /** Execution bounds for the `run_command` shell tool (distinct from the
@@ -64,12 +70,15 @@ export const ContextConfigSchema = z
64
70
  keepRecentMessages: z.number().int().positive().default(6),
65
71
  })
66
72
  .strict();
67
- /** How tool-action approval is resolved. */
73
+ /**
74
+ * How tool-action approval is resolved. Only `prompt` exists: ask interactively
75
+ * and **deny by default** when non-interactive. There is deliberately no
76
+ * auto-approve / skip mode — the policy seam for a future CI mode lives in
77
+ * `src/approval` (see U.3), not behind a footgun flag.
78
+ */
68
79
  export const ApprovalConfigSchema = z
69
80
  .object({
70
- // "prompt": ask interactively (deny when non-interactive). "auto": approve
71
- // every action unattended (CI / explicit opt-in).
72
- mode: z.enum(["prompt", "auto"]).default("prompt"),
81
+ mode: z.enum(["prompt"]).default("prompt"),
73
82
  })
74
83
  .strict();
75
84
  /**
@@ -0,0 +1,43 @@
1
+ import { type Formatter } from "./format.js";
2
+ import { CruxyError } from "./types.js";
3
+ /**
4
+ * The single top-level error boundary. {@link handleFatal} classifies any thrown
5
+ * value into a {@link CruxyError}, formats it, writes it to stderr, and exits
6
+ * with the error's stable exit code. Nothing escapes to a raw Node stack trace:
7
+ * an unknown error becomes {@link internal} (CRUXY_E_INTERNAL) with a
8
+ * bug-report hint, and the underlying stack is shown only under `--verbose`.
9
+ */
10
+ /**
11
+ * Normalize any thrown value into a {@link CruxyError}:
12
+ * - already a CruxyError → returned as-is;
13
+ * - a Commander parse error → a usage error (CRUXY_E_USAGE);
14
+ * - a known provider/transport error → its mapped code;
15
+ * - anything else → CRUXY_E_INTERNAL, preserving the original as `underlying`.
16
+ */
17
+ export declare function fromUnknown(err: unknown): CruxyError;
18
+ /** A Commander "error" that's actually success (`--help`, `--version`). */
19
+ export declare function isCommanderSuccess(err: unknown): boolean;
20
+ export interface HandleFatalOptions {
21
+ /** Show the underlying error/stack. Defaults to {@link isVerbose}(). */
22
+ verbose?: boolean;
23
+ /** Override the formatter (e.g. a future JSON formatter). */
24
+ formatter?: Formatter;
25
+ /** Force color on/off. Defaults to {@link shouldUseColor}(). */
26
+ color?: boolean;
27
+ /** Sink for the formatted output (default: stderr). For tests. */
28
+ write?: (text: string) => void;
29
+ /** Process exit (default: process.exit). For tests. */
30
+ exit?: (code: number) => never;
31
+ }
32
+ /**
33
+ * Classify → format → print → exit. Install this as the *only* catch at the CLI
34
+ * entry. `write`/`exit` are injectable so the boundary is testable without
35
+ * touching the real process.
36
+ */
37
+ export declare function handleFatal(err: unknown, opts?: HandleFatalOptions): never;
38
+ /**
39
+ * Whether the invocation requested verbose output: `--verbose`,
40
+ * `--log-level debug`, `DEBUG`, or `CRUXY_LOG_LEVEL=debug`. Read from argv/env so
41
+ * it works even when a failure happens before the CLI finishes parsing flags.
42
+ */
43
+ export declare function isVerbose(argv?: string[], env?: NodeJS.ProcessEnv): boolean;
@@ -0,0 +1,73 @@
1
+ import { CommanderError } from "commander";
2
+ import { classifyProviderError, internal, usageError } from "./constructors.js";
3
+ import { shouldUseColor, terminalFormatter } from "./format.js";
4
+ import { CruxyError } from "./types.js";
5
+ /**
6
+ * The single top-level error boundary. {@link handleFatal} classifies any thrown
7
+ * value into a {@link CruxyError}, formats it, writes it to stderr, and exits
8
+ * with the error's stable exit code. Nothing escapes to a raw Node stack trace:
9
+ * an unknown error becomes {@link internal} (CRUXY_E_INTERNAL) with a
10
+ * bug-report hint, and the underlying stack is shown only under `--verbose`.
11
+ */
12
+ /**
13
+ * Normalize any thrown value into a {@link CruxyError}:
14
+ * - already a CruxyError → returned as-is;
15
+ * - a Commander parse error → a usage error (CRUXY_E_USAGE);
16
+ * - a known provider/transport error → its mapped code;
17
+ * - anything else → CRUXY_E_INTERNAL, preserving the original as `underlying`.
18
+ */
19
+ export function fromUnknown(err) {
20
+ if (err instanceof CruxyError)
21
+ return err;
22
+ if (err instanceof CommanderError) {
23
+ return usageError(stripErrorPrefix(err.message));
24
+ }
25
+ return classifyProviderError(err) ?? internal(err);
26
+ }
27
+ /** A Commander "error" that's actually success (`--help`, `--version`). */
28
+ export function isCommanderSuccess(err) {
29
+ return err instanceof CommanderError && err.exitCode === 0;
30
+ }
31
+ /**
32
+ * Classify → format → print → exit. Install this as the *only* catch at the CLI
33
+ * entry. `write`/`exit` are injectable so the boundary is testable without
34
+ * touching the real process.
35
+ */
36
+ export function handleFatal(err, opts = {}) {
37
+ const cruxy = fromUnknown(err);
38
+ const verbose = opts.verbose ?? isVerbose();
39
+ const color = opts.color ?? shouldUseColor();
40
+ const formatter = opts.formatter ?? terminalFormatter;
41
+ const write = opts.write ?? ((text) => void process.stderr.write(text));
42
+ const exit = opts.exit ?? ((code) => process.exit(code));
43
+ write(`${formatter.format(cruxy, { verbose, color })}\n`);
44
+ return exit(cruxy.exitCode);
45
+ }
46
+ /**
47
+ * Whether the invocation requested verbose output: `--verbose`,
48
+ * `--log-level debug`, `DEBUG`, or `CRUXY_LOG_LEVEL=debug`. Read from argv/env so
49
+ * it works even when a failure happens before the CLI finishes parsing flags.
50
+ */
51
+ export function isVerbose(argv = process.argv, env = process.env) {
52
+ if (argv.includes("--verbose"))
53
+ return true;
54
+ if (argv.includes("--log-level=debug"))
55
+ return true;
56
+ const idx = argv.indexOf("--log-level");
57
+ if (idx !== -1 && argv[idx + 1] === "debug")
58
+ return true;
59
+ if (env.CRUXY_LOG_LEVEL === "debug")
60
+ return true;
61
+ const debug = env.DEBUG;
62
+ if (debug !== undefined &&
63
+ debug !== "" &&
64
+ debug !== "0" &&
65
+ debug !== "false") {
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ /** Strip Commander's leading "error: " so the title reads cleanly. */
71
+ function stripErrorPrefix(message) {
72
+ return message.replace(/^error:\s*/i, "");
73
+ }
@@ -0,0 +1,52 @@
1
+ import { CruxyError } from "./types.js";
2
+ /** Best-effort human message for an arbitrary thrown value. */
3
+ export declare function messageOf(underlying: unknown): string | undefined;
4
+ export declare function usageError(title: string, nextSteps?: string[]): CruxyError;
5
+ export declare function configKeyUnknown(key: string): CruxyError;
6
+ export declare function providerUnsupported(provider: string): CruxyError;
7
+ export declare function configParse(path: string, underlying?: unknown): CruxyError;
8
+ export declare function configInvalid(issues: string, path?: string): CruxyError;
9
+ export declare function authMissingKey(provider: string, envVar: string): CruxyError;
10
+ export declare function authInvalid(underlying?: unknown): CruxyError;
11
+ export declare function gatewayUnreachable(underlying?: unknown): CruxyError;
12
+ export declare function apiError(underlying?: unknown): CruxyError;
13
+ export declare function apiRateLimit(underlying?: unknown): CruxyError;
14
+ export declare function apiOverloaded(underlying?: unknown): CruxyError;
15
+ export declare function budgetExhausted(underlying?: unknown): CruxyError;
16
+ export declare function fileNotFound(path: string, underlying?: unknown): CruxyError;
17
+ export declare function permissionDenied(path: string, underlying?: unknown): CruxyError;
18
+ export declare function indexEmbedderUnavailable(underlying?: unknown): CruxyError;
19
+ export declare function indexStoreUnavailable(underlying?: unknown): CruxyError;
20
+ export declare function indexFailed(underlying?: unknown): CruxyError;
21
+ /**
22
+ * A side-effecting action needs approval but cruxy can't ask (non-interactive,
23
+ * no policy). Default-deny — never auto-approve. A distinct exit code (10) so CI
24
+ * can tell "needed approval" apart from a usage error.
25
+ */
26
+ export declare function approvalRequired(summary: string): CruxyError;
27
+ /**
28
+ * No forge token could be resolved (PR generation, C.15). The chain is env →
29
+ * `gh auth token` → fail. We never prompt for, store, or persist a token, so the
30
+ * fix is always to provide one in the environment.
31
+ */
32
+ export declare function forgeAuth(host?: string): CruxyError;
33
+ /**
34
+ * A commit/push was attempted on a protected branch (`main`/`master`/configured).
35
+ * The PR flow must branch off first; this is the last-line guard.
36
+ */
37
+ export declare function gitProtectedBranch(branch: string): CruxyError;
38
+ /** The forge REST API returned an error (non-auth) while opening a PR. */
39
+ export declare function forgeApi(title: string, underlying?: unknown, meta?: Record<string, unknown>): CruxyError;
40
+ /**
41
+ * `git push` failed — most often the husky `pre-push` verify hook (build ·
42
+ * typecheck · lint · test) or a rejected non-fast-forward. We never `--force` or
43
+ * `--no-verify`, so the underlying reason is surfaced verbatim.
44
+ */
45
+ export declare function gitPushFailed(branch: string, stderr?: string): CruxyError;
46
+ export declare function internal(underlying?: unknown): CruxyError;
47
+ /**
48
+ * Map a known provider/transport error (from `@cruxy/sdk`) to a typed
49
+ * {@link CruxyError}, or `null` if it isn't one. Order matters: specific
50
+ * subclasses before the `ApiError` base.
51
+ */
52
+ export declare function classifyProviderError(underlying: unknown): CruxyError | null;