@jackwener/opencli 1.5.4 → 1.5.5

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/src/errors.ts CHANGED
@@ -4,48 +4,96 @@
4
4
  * All errors thrown by the framework should extend CliError so that
5
5
  * the top-level handler in commanderAdapter.ts can render consistent,
6
6
  * helpful output with emoji-coded severity and actionable hints.
7
+ *
8
+ * ## Exit codes
9
+ *
10
+ * opencli follows Unix conventions (sysexits.h) for process exit codes:
11
+ *
12
+ * 0 Success
13
+ * 1 Generic / unexpected error
14
+ * 2 Argument / usage error (ArgumentError)
15
+ * 66 No input / empty result (EmptyResultError)
16
+ * 69 Service unavailable (BrowserConnectError, AdapterLoadError)
17
+ * 75 Temporary failure, retry later (TimeoutError) EX_TEMPFAIL
18
+ * 77 Permission denied / auth needed (AuthRequiredError)
19
+ * 78 Configuration error (ConfigError)
20
+ * 130 Interrupted by Ctrl-C (set by tui.ts SIGINT handler)
7
21
  */
8
22
 
23
+ // ── Exit code table ──────────────────────────────────────────────────────────
24
+
25
+ export const EXIT_CODES = {
26
+ SUCCESS: 0,
27
+ GENERIC_ERROR: 1,
28
+ USAGE_ERROR: 2, // Bad arguments / command misuse
29
+ EMPTY_RESULT: 66, // No data / not found (EX_NOINPUT)
30
+ SERVICE_UNAVAIL:69, // Daemon / browser unavailable (EX_UNAVAILABLE)
31
+ TEMPFAIL: 75, // Timeout — try again later (EX_TEMPFAIL)
32
+ NOPERM: 77, // Auth required / permission (EX_NOPERM)
33
+ CONFIG_ERROR: 78, // Missing / invalid config (EX_CONFIG)
34
+ INTERRUPTED: 130, // Ctrl-C / SIGINT
35
+ } as const;
36
+
37
+ export type ExitCode = typeof EXIT_CODES[keyof typeof EXIT_CODES];
38
+
39
+ // ── Base class ───────────────────────────────────────────────────────────────
40
+
9
41
  export class CliError extends Error {
10
42
  /** Machine-readable error code (e.g. 'BROWSER_CONNECT', 'AUTH_REQUIRED') */
11
43
  readonly code: string;
12
44
  /** Human-readable hint on how to fix the problem */
13
45
  readonly hint?: string;
46
+ /** Unix process exit code — defaults to 1 (generic error) */
47
+ readonly exitCode: ExitCode;
14
48
 
15
- constructor(code: string, message: string, hint?: string) {
49
+ constructor(code: string, message: string, hint?: string, exitCode: ExitCode = EXIT_CODES.GENERIC_ERROR) {
16
50
  super(message);
17
51
  this.name = new.target.name;
18
52
  this.code = code;
19
53
  this.hint = hint;
54
+ this.exitCode = exitCode;
20
55
  }
21
56
  }
22
57
 
58
+ // ── Typed subclasses ─────────────────────────────────────────────────────────
59
+
23
60
  export type BrowserConnectKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
24
61
 
25
62
  export class BrowserConnectError extends CliError {
26
63
  readonly kind: BrowserConnectKind;
27
64
  constructor(message: string, hint?: string, kind: BrowserConnectKind = 'unknown') {
28
- super('BROWSER_CONNECT', message, hint);
65
+ super('BROWSER_CONNECT', message, hint, EXIT_CODES.SERVICE_UNAVAIL);
29
66
  this.kind = kind;
30
67
  }
31
68
  }
32
69
 
33
70
  export class AdapterLoadError extends CliError {
34
- constructor(message: string, hint?: string) { super('ADAPTER_LOAD', message, hint); }
71
+ constructor(message: string, hint?: string) {
72
+ super('ADAPTER_LOAD', message, hint, EXIT_CODES.SERVICE_UNAVAIL);
73
+ }
35
74
  }
36
75
 
37
76
  export class CommandExecutionError extends CliError {
38
- constructor(message: string, hint?: string) { super('COMMAND_EXEC', message, hint); }
77
+ constructor(message: string, hint?: string) {
78
+ super('COMMAND_EXEC', message, hint, EXIT_CODES.GENERIC_ERROR);
79
+ }
39
80
  }
40
81
 
41
82
  export class ConfigError extends CliError {
42
- constructor(message: string, hint?: string) { super('CONFIG', message, hint); }
83
+ constructor(message: string, hint?: string) {
84
+ super('CONFIG', message, hint, EXIT_CODES.CONFIG_ERROR);
85
+ }
43
86
  }
44
87
 
45
88
  export class AuthRequiredError extends CliError {
46
89
  readonly domain: string;
47
90
  constructor(domain: string, message?: string) {
48
- super('AUTH_REQUIRED', message ?? `Not logged in to ${domain}`, `Please open Chrome and log in to https://${domain}`);
91
+ super(
92
+ 'AUTH_REQUIRED',
93
+ message ?? `Not logged in to ${domain}`,
94
+ `Please open Chrome and log in to https://${domain}`,
95
+ EXIT_CODES.NOPERM,
96
+ );
49
97
  this.domain = domain;
50
98
  }
51
99
  }
@@ -56,27 +104,40 @@ export class TimeoutError extends CliError {
56
104
  'TIMEOUT',
57
105
  `${label} timed out after ${seconds}s`,
58
106
  hint ?? 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var',
107
+ EXIT_CODES.TEMPFAIL,
59
108
  );
60
109
  }
61
110
  }
62
111
 
63
112
  export class ArgumentError extends CliError {
64
- constructor(message: string, hint?: string) { super('ARGUMENT', message, hint); }
113
+ constructor(message: string, hint?: string) {
114
+ super('ARGUMENT', message, hint, EXIT_CODES.USAGE_ERROR);
115
+ }
65
116
  }
66
117
 
67
118
  export class EmptyResultError extends CliError {
68
119
  constructor(command: string, hint?: string) {
69
- super('EMPTY_RESULT', `${command} returned no data`, hint ?? 'The page structure may have changed, or you may need to log in');
120
+ super(
121
+ 'EMPTY_RESULT',
122
+ `${command} returned no data`,
123
+ hint ?? 'The page structure may have changed, or you may need to log in',
124
+ EXIT_CODES.EMPTY_RESULT,
125
+ );
70
126
  }
71
127
  }
72
128
 
73
129
  export class SelectorError extends CliError {
74
130
  constructor(selector: string, hint?: string) {
75
- super('SELECTOR', `Could not find element: ${selector}`, hint ?? 'The page UI may have changed. Please report this issue.');
131
+ super(
132
+ 'SELECTOR',
133
+ `Could not find element: ${selector}`,
134
+ hint ?? 'The page UI may have changed. Please report this issue.',
135
+ EXIT_CODES.GENERIC_ERROR,
136
+ );
76
137
  }
77
138
  }
78
139
 
79
- // ── Utilities ───────────────────────────────────────────────────────────
140
+ // ── Utilities ───────────────────────────────────────────────────────────────
80
141
 
81
142
  /** Extract a human-readable message from an unknown caught value. */
82
143
  export function getErrorMessage(error: unknown): string {
package/src/external.ts CHANGED
@@ -6,7 +6,7 @@ import { spawnSync, execFileSync } from 'node:child_process';
6
6
  import yaml from 'js-yaml';
7
7
  import chalk from 'chalk';
8
8
  import { log } from './logger.js';
9
- import { getErrorMessage } from './errors.js';
9
+ import { EXIT_CODES, getErrorMessage } from './errors.js';
10
10
 
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
 
@@ -180,7 +180,7 @@ export function executeExternalCli(name: string, args: string[], preloaded?: Ext
180
180
  // 2. Try to auto install
181
181
  const success = installExternalCli(cli);
182
182
  if (!success) {
183
- process.exitCode = 1;
183
+ process.exitCode = EXIT_CODES.SERVICE_UNAVAIL;
184
184
  return;
185
185
  }
186
186
  }
@@ -189,7 +189,7 @@ export function executeExternalCli(name: string, args: string[], preloaded?: Ext
189
189
  const result = spawnSync(cli.binary, args, { stdio: 'inherit' });
190
190
  if (result.error) {
191
191
  console.error(chalk.red(`Failed to execute '${cli.binary}': ${result.error.message}`));
192
- process.exitCode = 1;
192
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
193
193
  return;
194
194
  }
195
195
 
package/src/main.ts CHANGED
@@ -22,6 +22,7 @@ import { runCli } from './cli.js';
22
22
  import { emitHook } from './hooks.js';
23
23
  import { installNodeNetwork } from './node-network.js';
24
24
  import { registerUpdateNoticeOnExit, checkForUpdateBackground } from './update-check.js';
25
+ import { EXIT_CODES } from './errors.js';
25
26
 
26
27
  installNodeNetwork();
27
28
 
@@ -57,7 +58,7 @@ if (getCompIdx !== -1) {
57
58
  if (cursor === undefined) cursor = words.length;
58
59
  const candidates = getCompletions(words, cursor);
59
60
  process.stdout.write(candidates.join('\n') + '\n');
60
- process.exit(0);
61
+ process.exit(EXIT_CODES.SUCCESS);
61
62
  }
62
63
 
63
64
  await emitHook('onStartup', { command: '__startup__', args: {} });
package/src/tui.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * Uses raw stdin mode + ANSI escape codes for interactive prompts.
5
5
  */
6
6
  import chalk from 'chalk';
7
+ import { EXIT_CODES } from './errors.js';
7
8
 
8
9
  export interface CheckboxItem {
9
10
  label: string;
@@ -161,7 +162,7 @@ export async function checkboxPrompt(
161
162
  // Ctrl+C — exit process
162
163
  if (key === '\x03') {
163
164
  cleanup();
164
- process.exit(130);
165
+ process.exit(EXIT_CODES.INTERRUPTED);
165
166
  }
166
167
  }
167
168