echopai 2.8.1 → 2.9.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 (2) hide show
  1. package/dist/bin.js +205 -223
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -2928,97 +2928,43 @@ async function downloadAndVerify(url, expectedSha) {
2928
2928
  }
2929
2929
  }
2930
2930
 
2931
- // src/runtime/invoker.ts
2932
- import AjvPkg from "ajv";
2933
- import addFormatsPkg from "ajv-formats";
2934
-
2935
- // src/runtime/auth.ts
2936
- import * as fs from "node:fs";
2937
- import * as os from "node:os";
2938
- import * as path from "node:path";
2939
- import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
2940
-
2941
- class AuthMissingError extends Error {
2942
- recovery_hint;
2943
- constructor(message, recovery_hint) {
2944
- super(message);
2945
- this.recovery_hint = recovery_hint;
2946
- this.name = "AuthMissingError";
2947
- }
2948
- }
2949
- function configDir() {
2950
- if (process.platform === "win32") {
2951
- const appdata = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
2952
- return path.join(appdata, "echopai");
2953
- }
2954
- const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
2955
- return path.join(xdg, "echopai");
2956
- }
2957
- function configPath() {
2958
- return path.join(configDir(), "config.toml");
2959
- }
2960
- function readConfigFile() {
2961
- const p = configPath();
2962
- if (!fs.existsSync(p))
2963
- return {};
2964
- try {
2965
- const raw = fs.readFileSync(p, "utf-8");
2966
- return parseToml(raw);
2967
- } catch {
2968
- return {};
2969
- }
2970
- }
2971
- function writeConfigFile(cfg) {
2972
- const dir = configDir();
2973
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
2974
- const data = stringifyToml(cfg);
2975
- fs.writeFileSync(configPath(), data, { mode: 384 });
2976
- }
2977
- function resolveCredentials(opts = {}) {
2978
- const DEFAULT_BASE_URL = "https://api.echopai.com";
2979
- if (opts.key) {
2980
- return { key: opts.key, baseUrl: process.env.ECHOPAI_BASE_URL || DEFAULT_BASE_URL, profile: null };
2981
- }
2982
- const envKey = process.env.ECHOPAI_KEY;
2983
- if (envKey) {
2984
- return { key: envKey, baseUrl: process.env.ECHOPAI_BASE_URL || DEFAULT_BASE_URL, profile: null };
2985
- }
2986
- const cfg = readConfigFile();
2987
- const profileName = opts.profile || process.env.ECHOPAI_PROFILE || cfg.default_profile;
2988
- if (profileName && cfg.profiles && cfg.profiles[profileName]) {
2989
- const p = cfg.profiles[profileName];
2990
- if (p.key) {
2991
- return {
2992
- key: p.key,
2993
- baseUrl: p.base_url || process.env.ECHOPAI_BASE_URL || DEFAULT_BASE_URL,
2994
- profile: profileName
2995
- };
2996
- }
2997
- }
2998
- throw new AuthMissingError(profileName ? `No key configured for profile '${profileName}'.` : "No credential found.", "Set ECHOPAI_KEY env var, or run `echopai login --key eps_live_<lookup>_<secret>`.");
2999
- }
3000
-
3001
- // src/runtime/envelope.ts
3002
- function mergeMeta(serverMeta, cli) {
3003
- const merged = {
3004
- ...serverMeta ?? {},
3005
- request_id: cli.requestId,
3006
- endpoint: cli.endpoint,
3007
- method: cli.method,
3008
- cli_version: cli.cliVersion,
3009
- duration_ms: cli.durationMs,
3010
- truncated: false
3011
- };
3012
- if (cli.apiVersion)
3013
- merged.api_version = cli.apiVersion;
3014
- return merged;
2931
+ // src/runtime/tty.ts
2932
+ var enable = (() => {
2933
+ if (!process.stderr.isTTY)
2934
+ return false;
2935
+ if (process.env.CI)
2936
+ return false;
2937
+ if (process.env.NO_COLOR)
2938
+ return false;
2939
+ if (process.env.TERM === "dumb")
2940
+ return false;
2941
+ return true;
2942
+ })();
2943
+ var isTtyHuman = enable;
2944
+ var ESC = "\x1B[";
2945
+ function ansi(code, s) {
2946
+ if (!enable)
2947
+ return s;
2948
+ return `${ESC}${code}m${s}${ESC}0m`;
3015
2949
  }
3016
- function buildResponseEnvelope(body, cli) {
3017
- if (typeof body === "object" && body !== null && !Array.isArray(body) && "data" in body && "meta" in body) {
3018
- const e = body;
3019
- return { data: e.data, meta: mergeMeta(e.meta, cli) };
3020
- }
3021
- return { data: body, meta: mergeMeta(undefined, cli) };
2950
+ var red = (s) => ansi("31", s);
2951
+ var green = (s) => ansi("32", s);
2952
+ var yellow = (s) => ansi("33", s);
2953
+ var cyan = (s) => ansi("36", s);
2954
+ var dim = (s) => ansi("2", s);
2955
+ var bold = (s) => ansi("1", s);
2956
+ function renderError(env) {
2957
+ if (!enable)
2958
+ return JSON.stringify(env);
2959
+ const e = env.error;
2960
+ const label = `${red(bold("✗ " + e.code))}${e.retryable ? " " + dim("(retryable)") : ""}`;
2961
+ const lines = [`${label} ${dim("—")} ${e.message}`];
2962
+ if (e.recovery_hint)
2963
+ lines.push(` ${cyan("hint:")} ${e.recovery_hint}`);
2964
+ if (e.request_id)
2965
+ lines.push(` ${dim("request_id: " + e.request_id)}`);
2966
+ return lines.join(`
2967
+ `);
3022
2968
  }
3023
2969
 
3024
2970
  // src/runtime/errors.ts
@@ -3157,6 +3103,128 @@ function exitCodeForStatus(httpStatus) {
3157
3103
  return 1;
3158
3104
  return 2;
3159
3105
  }
3106
+ var SERVICE_ERROR_CODES = new Set([
3107
+ "network_error",
3108
+ "timeout",
3109
+ "stream_error",
3110
+ "upstream_unavailable",
3111
+ "internal_error",
3112
+ "http_error"
3113
+ ]);
3114
+ function classifyExitCode(code) {
3115
+ return SERVICE_ERROR_CODES.has(code) ? 2 : 1;
3116
+ }
3117
+ function emitErrorEnvelope(args, exitCode) {
3118
+ const retryable = args.retryable ?? isRetryableByCode(args.code);
3119
+ const hint = resolveRecoveryHint(args.code, args.recovery_hint);
3120
+ const env = { error: { code: args.code, message: args.message, retryable } };
3121
+ if (hint)
3122
+ env.error.recovery_hint = hint;
3123
+ if (args.request_id)
3124
+ env.error.request_id = args.request_id;
3125
+ const ec = exitCode ?? classifyExitCode(args.code);
3126
+ if (isTtyHuman) {
3127
+ process.stderr.write(renderError(env) + `
3128
+ `);
3129
+ } else {
3130
+ process.stdout.write(JSON.stringify(env) + `
3131
+ `);
3132
+ }
3133
+ process.exit(ec);
3134
+ }
3135
+
3136
+ // src/runtime/invoker.ts
3137
+ import AjvPkg from "ajv";
3138
+ import addFormatsPkg from "ajv-formats";
3139
+
3140
+ // src/runtime/auth.ts
3141
+ import * as fs from "node:fs";
3142
+ import * as os from "node:os";
3143
+ import * as path from "node:path";
3144
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
3145
+
3146
+ class AuthMissingError extends Error {
3147
+ recovery_hint;
3148
+ constructor(message, recovery_hint) {
3149
+ super(message);
3150
+ this.recovery_hint = recovery_hint;
3151
+ this.name = "AuthMissingError";
3152
+ }
3153
+ }
3154
+ function configDir() {
3155
+ if (process.platform === "win32") {
3156
+ const appdata = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
3157
+ return path.join(appdata, "echopai");
3158
+ }
3159
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
3160
+ return path.join(xdg, "echopai");
3161
+ }
3162
+ function configPath() {
3163
+ return path.join(configDir(), "config.toml");
3164
+ }
3165
+ function readConfigFile() {
3166
+ const p = configPath();
3167
+ if (!fs.existsSync(p))
3168
+ return {};
3169
+ try {
3170
+ const raw = fs.readFileSync(p, "utf-8");
3171
+ return parseToml(raw);
3172
+ } catch {
3173
+ return {};
3174
+ }
3175
+ }
3176
+ function writeConfigFile(cfg) {
3177
+ const dir = configDir();
3178
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
3179
+ const data = stringifyToml(cfg);
3180
+ fs.writeFileSync(configPath(), data, { mode: 384 });
3181
+ }
3182
+ function resolveCredentials(opts = {}) {
3183
+ const DEFAULT_BASE_URL = "https://api.echopai.com";
3184
+ if (opts.key) {
3185
+ return { key: opts.key, baseUrl: process.env.ECHOPAI_BASE_URL || DEFAULT_BASE_URL, profile: null };
3186
+ }
3187
+ const envKey = process.env.ECHOPAI_KEY;
3188
+ if (envKey) {
3189
+ return { key: envKey, baseUrl: process.env.ECHOPAI_BASE_URL || DEFAULT_BASE_URL, profile: null };
3190
+ }
3191
+ const cfg = readConfigFile();
3192
+ const profileName = opts.profile || process.env.ECHOPAI_PROFILE || cfg.default_profile;
3193
+ if (profileName && cfg.profiles && cfg.profiles[profileName]) {
3194
+ const p = cfg.profiles[profileName];
3195
+ if (p.key) {
3196
+ return {
3197
+ key: p.key,
3198
+ baseUrl: p.base_url || process.env.ECHOPAI_BASE_URL || DEFAULT_BASE_URL,
3199
+ profile: profileName
3200
+ };
3201
+ }
3202
+ }
3203
+ throw new AuthMissingError(profileName ? `No key configured for profile '${profileName}'.` : "No credential found.", "Set ECHOPAI_KEY env var, or run `echopai login --key eps_live_<lookup>_<secret>`.");
3204
+ }
3205
+
3206
+ // src/runtime/envelope.ts
3207
+ function mergeMeta(serverMeta, cli) {
3208
+ const merged = {
3209
+ ...serverMeta ?? {},
3210
+ request_id: cli.requestId,
3211
+ endpoint: cli.endpoint,
3212
+ method: cli.method,
3213
+ cli_version: cli.cliVersion,
3214
+ duration_ms: cli.durationMs,
3215
+ truncated: false
3216
+ };
3217
+ if (cli.apiVersion)
3218
+ merged.api_version = cli.apiVersion;
3219
+ return merged;
3220
+ }
3221
+ function buildResponseEnvelope(body, cli) {
3222
+ if (typeof body === "object" && body !== null && !Array.isArray(body) && "data" in body && "meta" in body) {
3223
+ const e = body;
3224
+ return { data: e.data, meta: mergeMeta(e.meta, cli) };
3225
+ }
3226
+ return { data: body, meta: mergeMeta(undefined, cli) };
3227
+ }
3160
3228
 
3161
3229
  // src/runtime/filters.ts
3162
3230
  import jmespathPkg from "jmespath";
@@ -3674,52 +3742,13 @@ function exitCodeForPaginateError(e) {
3674
3742
  return 2;
3675
3743
  }
3676
3744
 
3677
- // src/runtime/tty.ts
3678
- var enable = (() => {
3679
- if (!process.stderr.isTTY)
3680
- return false;
3681
- if (process.env.CI)
3682
- return false;
3683
- if (process.env.NO_COLOR)
3684
- return false;
3685
- if (process.env.TERM === "dumb")
3686
- return false;
3687
- return true;
3688
- })();
3689
- var isTtyHuman = enable;
3690
- var ESC = "\x1B[";
3691
- function ansi(code, s) {
3692
- if (!enable)
3693
- return s;
3694
- return `${ESC}${code}m${s}${ESC}0m`;
3695
- }
3696
- var red = (s) => ansi("31", s);
3697
- var green = (s) => ansi("32", s);
3698
- var yellow = (s) => ansi("33", s);
3699
- var cyan = (s) => ansi("36", s);
3700
- var dim = (s) => ansi("2", s);
3701
- var bold = (s) => ansi("1", s);
3702
- function renderError(env) {
3703
- if (!enable)
3704
- return JSON.stringify(env);
3705
- const e = env.error;
3706
- const label = `${red(bold("✗ " + e.code))}${e.retryable ? " " + dim("(retryable)") : ""}`;
3707
- const lines = [`${label} ${dim("—")} ${e.message}`];
3708
- if (e.recovery_hint)
3709
- lines.push(` ${cyan("hint:")} ${e.recovery_hint}`);
3710
- if (e.request_id)
3711
- lines.push(` ${dim("request_id: " + e.request_id)}`);
3712
- return lines.join(`
3713
- `);
3714
- }
3715
-
3716
3745
  // src/runtime/update_check.ts
3717
3746
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
3718
3747
  import os2 from "node:os";
3719
3748
  import path2 from "node:path";
3720
3749
 
3721
3750
  // src/version.ts
3722
- var CLI_VERSION = "2.8.1";
3751
+ var CLI_VERSION = "2.9.0";
3723
3752
 
3724
3753
  // src/runtime/update_check.ts
3725
3754
  var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
@@ -4079,23 +4108,13 @@ function trace(op, t) {
4079
4108
  });
4080
4109
  }
4081
4110
  function writeError(code, message, exitCode, extras) {
4082
- const retryable = extras?.retryable ?? isRetryableByCode(code);
4083
- const hint = resolveRecoveryHint(code, extras?.recovery_hint);
4084
- const env = {
4085
- error: { code, message, retryable }
4086
- };
4087
- if (hint)
4088
- env.error.recovery_hint = hint;
4089
- if (extras?.request_id)
4090
- env.error.request_id = extras.request_id;
4091
- if (isTtyHuman) {
4092
- process.stderr.write(renderError(env) + `
4093
- `);
4094
- } else {
4095
- process.stderr.write(JSON.stringify(env) + `
4096
- `);
4097
- }
4098
- process.exit(exitCode);
4111
+ emitErrorEnvelope({
4112
+ code,
4113
+ message,
4114
+ ...extras?.retryable !== undefined ? { retryable: extras.retryable } : {},
4115
+ ...extras?.recovery_hint ? { recovery_hint: extras.recovery_hint } : {},
4116
+ ...extras?.request_id ? { request_id: extras.request_id } : {}
4117
+ }, exitCode);
4099
4118
  }
4100
4119
 
4101
4120
  // src/tools/api.ts
@@ -4157,12 +4176,7 @@ function buildApiCommand() {
4157
4176
  return api;
4158
4177
  }
4159
4178
  function die(code, message, exitCode, recovery_hint) {
4160
- const env = { error: { code, message } };
4161
- if (recovery_hint)
4162
- env.error.recovery_hint = recovery_hint;
4163
- process.stderr.write(JSON.stringify(env) + `
4164
- `);
4165
- process.exit(exitCode);
4179
+ emitErrorEnvelope({ code, message, ...recovery_hint ? { recovery_hint } : {} }, exitCode);
4166
4180
  }
4167
4181
 
4168
4182
  // src/tools/mcp.ts
@@ -4275,29 +4289,18 @@ async function executeVerb(handler) {
4275
4289
  process.exit(0);
4276
4290
  } catch (e) {
4277
4291
  if (e instanceof CallApiError) {
4278
- emitVerbError(e.code, e.message, e.recovery_hint, e.httpStatus && e.httpStatus < 500 ? 1 : 2, e.requestId);
4292
+ emitVerbError(e.code, e.message, e.recovery_hint, e.httpStatus ? e.httpStatus < 500 ? 1 : 2 : classifyExitCode(e.code), e.requestId);
4279
4293
  }
4280
4294
  emitVerbError("internal_error", e instanceof Error ? e.message : String(e), undefined, 2);
4281
4295
  }
4282
4296
  }
4283
4297
  function emitVerbError(code, message, recoveryHint, exitCode, requestId) {
4284
- const env = {
4285
- error: {
4286
- code,
4287
- message,
4288
- retryable: false,
4289
- ...recoveryHint ? { recovery_hint: recoveryHint } : {},
4290
- ...requestId ? { request_id: requestId } : {}
4291
- }
4292
- };
4293
- if (isTtyHuman) {
4294
- process.stderr.write(renderError(env) + `
4295
- `);
4296
- } else {
4297
- process.stderr.write(JSON.stringify(env) + `
4298
- `);
4299
- }
4300
- process.exit(exitCode);
4298
+ emitErrorEnvelope({
4299
+ code,
4300
+ message,
4301
+ ...recoveryHint ? { recovery_hint: recoveryHint } : {},
4302
+ ...requestId ? { request_id: requestId } : {}
4303
+ }, exitCode);
4301
4304
  }
4302
4305
 
4303
4306
  // src/runtime/verb_runner.ts
@@ -6808,14 +6811,7 @@ function buildRawCallCommand() {
6808
6811
  return call;
6809
6812
  }
6810
6813
  function die2(code, message, exitCode, recovery_hint) {
6811
- const env = {
6812
- error: { code, message, retryable: false }
6813
- };
6814
- if (recovery_hint)
6815
- env.error.recovery_hint = recovery_hint;
6816
- process.stderr.write(JSON.stringify(env) + `
6817
- `);
6818
- process.exit(exitCode);
6814
+ emitErrorEnvelope({ code, message, ...recovery_hint ? { recovery_hint } : {} }, exitCode);
6819
6815
  }
6820
6816
 
6821
6817
  // src/tools/completion.ts
@@ -7006,12 +7002,7 @@ function redactKey(key) {
7006
7002
  return `eps_live_${parts[2]}_***${last.slice(-4)}`;
7007
7003
  }
7008
7004
  function writeError2(code, message, recovery_hint, exitCode) {
7009
- const env = { error: { code, message } };
7010
- if (recovery_hint)
7011
- env.error.recovery_hint = recovery_hint;
7012
- process.stderr.write(JSON.stringify(env) + `
7013
- `);
7014
- process.exit(exitCode);
7005
+ emitErrorEnvelope({ code, message, ...recovery_hint ? { recovery_hint } : {} }, exitCode);
7015
7006
  }
7016
7007
 
7017
7008
  // src/tools/login.ts
@@ -7191,23 +7182,12 @@ function buildWhoamiCommand() {
7191
7182
  return cmd;
7192
7183
  }
7193
7184
  function emitError(code, message, recoveryHint, exitCode, requestId) {
7194
- const env = {
7195
- error: {
7196
- code,
7197
- message,
7198
- retryable: false,
7199
- ...recoveryHint ? { recovery_hint: recoveryHint } : {},
7200
- ...requestId ? { request_id: requestId } : {}
7201
- }
7202
- };
7203
- if (isTtyHuman) {
7204
- process.stderr.write(renderError(env) + `
7205
- `);
7206
- } else {
7207
- process.stderr.write(JSON.stringify(env) + `
7208
- `);
7209
- }
7210
- process.exit(exitCode);
7185
+ emitErrorEnvelope({
7186
+ code,
7187
+ message,
7188
+ ...recoveryHint ? { recovery_hint: recoveryHint } : {},
7189
+ ...requestId ? { request_id: requestId } : {}
7190
+ }, exitCode);
7211
7191
  }
7212
7192
 
7213
7193
  // src/tools/doctor.ts
@@ -8164,10 +8144,10 @@ var program = new Command31;
8164
8144
  program.name("echopai").description(`Command-line access to the EchoPai market-data platform: quotes, news,
8165
8145
  ` + `analyst research, fundamentals, and sentiment.
8166
8146
 
8167
- ` + `Machine-readable by design — a JSON envelope on stdout, JSON errors on
8168
- ` + `stderr, and three-state exit codes (0 success / 1 user error / 2 service
8169
- ` + `error) built for AI agents and scripts. Authenticate with the
8170
- ` + "ECHOPAI_KEY environment variable or `echopai login`.").version(CLI_VERSION, "-V, --version").addOption(new Option23("--debug", "Print HTTP wire trace to stderr (Bearer redacted)")).addOption(new Option23("--raw", "Pass through raw response body (skip envelope wrap)")).addOption(new Option23("--jq <jmespath>", "Filter or transform output with a JMESPath expression.")).addOption(new Option23("--fields <a,b,c>", "Keep only listed top-level fields per item")).addOption(new Option23("--max-bytes <n>", "Truncate serialized envelope to N bytes (sets meta.truncated)")).addOption(new Option23("--yes", "Confirm a write op in non-TTY mode (required for sideEffect=write in agents / CI)")).addOption(new Option23("--dry-run", "Send X-Dry-Run:1 on write ops where the server advertises dry-run support")).addHelpText("after", `
8147
+ ` + `Machine-readable by design — in non-TTY (agent/script) mode both success
8148
+ ` + "`{data:...}` and error `{error:{code,retryable,...}}` envelopes go to stdout\n" + `(one stream to parse), with three-state exit codes (0 success / 1 user error
8149
+ ` + `/ 2 service error). On a TTY, errors render to stderr for humans. Built for AI
8150
+ ` + "agents and scripts. Authenticate with ECHOPAI_KEY or `echopai login`.").version(CLI_VERSION, "-V, --version").addOption(new Option23("--debug", "Print HTTP wire trace to stderr (Bearer redacted)")).addOption(new Option23("--raw", "Pass through raw response body (skip envelope wrap)")).addOption(new Option23("--jq <jmespath>", "Filter or transform output with a JMESPath expression.")).addOption(new Option23("--fields <a,b,c>", "Keep only listed top-level fields per item")).addOption(new Option23("--max-bytes <n>", "Truncate serialized envelope to N bytes (sets meta.truncated)")).addOption(new Option23("--yes", "Confirm a write op in non-TTY mode (required for sideEffect=write in agents / CI)")).addOption(new Option23("--dry-run", "Send X-Dry-Run:1 on write ops where the server advertises dry-run support")).addHelpText("after", `
8171
8151
  Authentication: ECHOPAI_KEY=<eps_live_...> echopai <command>
8172
8152
  ` + `Curated verbs: echopai <verb> # task-level commands (recommended)
8173
8153
  ` + `Raw operations: echopai raw <noun> <verb> # low-level access to every API operation
@@ -8191,21 +8171,30 @@ var dispatch = async (op, args) => {
8191
8171
  }
8192
8172
  await invoke(op, { ...globalsSnake, ...args }, { cliVersion: CLI_VERSION });
8193
8173
  };
8174
+ function detectStrayCodeArgs(args) {
8175
+ if (!Array.isArray(args))
8176
+ return [];
8177
+ const codeLike = /^((SSE|SZSE|BSE|SH|SZ|BJ):)?[0-9]{6}$/i;
8178
+ return args.filter((a) => typeof a === "string" && codeLike.test(a));
8179
+ }
8194
8180
  function applyAiFirstHooks(cmd) {
8195
8181
  cmd.configureOutput({ writeErr: () => {} });
8196
8182
  cmd.exitOverride((err) => {
8197
8183
  if (err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version") {
8198
8184
  process.exit(0);
8199
8185
  }
8200
- process.stderr.write(JSON.stringify({
8201
- error: {
8202
- code: "invalid_args",
8203
- message: err.message,
8204
- recovery_hint: "Run with `--help` for usage."
8205
- }
8206
- }) + `
8207
- `);
8208
- process.exit(1);
8186
+ let message = err.message;
8187
+ const stray = detectStrayCodeArgs(cmd.args);
8188
+ if (stray.length > 0) {
8189
+ const opt = /code\b/i.test(err.message) ? "--code" : "--codes";
8190
+ message += `. Detected positional argument(s) [${stray.join(", ")}] — these commands ` + `take codes via an option, not positionally; did you mean ` + `\`${opt} ${stray.join(",")}\`?`;
8191
+ }
8192
+ emitErrorEnvelope({
8193
+ code: "invalid_args",
8194
+ message,
8195
+ retryable: false,
8196
+ recovery_hint: "Run with `--help` for usage."
8197
+ }, 1);
8209
8198
  });
8210
8199
  for (const sub of cmd.commands)
8211
8200
  applyAiFirstHooks(sub);
@@ -8277,12 +8266,5 @@ if (shouldShowWelcome(process.argv)) {
8277
8266
  process.exit(0);
8278
8267
  }
8279
8268
  program.parseAsync(process.argv).catch((e) => {
8280
- process.stderr.write(JSON.stringify({
8281
- error: {
8282
- code: "internal_error",
8283
- message: e instanceof Error ? e.message : String(e)
8284
- }
8285
- }) + `
8286
- `);
8287
- process.exit(2);
8269
+ emitErrorEnvelope({ code: "internal_error", message: e instanceof Error ? e.message : String(e) }, 2);
8288
8270
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echopai",
3
- "version": "2.8.1",
3
+ "version": "2.9.0",
4
4
  "description": "Command-line interface for the EchoPai Open Platform: stock-market data, news, analyst views, sentiment, signals, backtests.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://echopai.com",