botanary 0.1.0 → 0.2.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 (73) hide show
  1. package/README.md +186 -9
  2. package/dist/bin/botanary.js +13 -3
  3. package/dist/bin/botanary.js.map +1 -1
  4. package/dist/package.json +2 -2
  5. package/dist/src/app-url.js +32 -0
  6. package/dist/src/app-url.js.map +1 -0
  7. package/dist/src/cli.js +113 -23
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/commands/agent.js +190 -49
  10. package/dist/src/commands/agent.js.map +1 -1
  11. package/dist/src/commands/mcp.js +136 -5
  12. package/dist/src/commands/mcp.js.map +1 -1
  13. package/dist/src/commands/system.js +109 -12
  14. package/dist/src/commands/system.js.map +1 -1
  15. package/dist/src/commands/utilities.js +44 -0
  16. package/dist/src/commands/utilities.js.map +1 -0
  17. package/dist/src/commands/wallet.js +332 -94
  18. package/dist/src/commands/wallet.js.map +1 -1
  19. package/dist/src/completion.js +69 -0
  20. package/dist/src/completion.js.map +1 -0
  21. package/dist/src/config-store.js +52 -0
  22. package/dist/src/config-store.js.map +1 -0
  23. package/dist/src/context.js +14 -4
  24. package/dist/src/context.js.map +1 -1
  25. package/dist/src/errors.js +116 -0
  26. package/dist/src/errors.js.map +1 -0
  27. package/dist/src/help/examples.js +357 -0
  28. package/dist/src/help/examples.js.map +1 -0
  29. package/dist/src/help/format.js +144 -0
  30. package/dist/src/help/format.js.map +1 -0
  31. package/dist/src/help/groups.js +39 -0
  32. package/dist/src/help/groups.js.map +1 -0
  33. package/dist/src/mcp-snippets.js +6 -0
  34. package/dist/src/mcp-snippets.js.map +1 -1
  35. package/dist/src/outcome.js +63 -0
  36. package/dist/src/outcome.js.map +1 -0
  37. package/dist/src/parser.js +18 -39
  38. package/dist/src/parser.js.map +1 -1
  39. package/dist/src/progress.js +37 -0
  40. package/dist/src/progress.js.map +1 -0
  41. package/dist/src/prompt-doc.js +92 -0
  42. package/dist/src/prompt-doc.js.map +1 -0
  43. package/dist/src/render/accounts.js +21 -0
  44. package/dist/src/render/accounts.js.map +1 -0
  45. package/dist/src/render/activity.js +34 -0
  46. package/dist/src/render/activity.js.map +1 -0
  47. package/dist/src/render/agents.js +42 -0
  48. package/dist/src/render/agents.js.map +1 -0
  49. package/dist/src/render/balance.js +68 -0
  50. package/dist/src/render/balance.js.map +1 -0
  51. package/dist/src/render/chains.js +52 -0
  52. package/dist/src/render/chains.js.map +1 -0
  53. package/dist/src/render/gas.js +34 -0
  54. package/dist/src/render/gas.js.map +1 -0
  55. package/dist/src/render/grant.js +34 -0
  56. package/dist/src/render/grant.js.map +1 -0
  57. package/dist/src/render/kv.js +60 -0
  58. package/dist/src/render/kv.js.map +1 -0
  59. package/dist/src/render/mandates.js +111 -0
  60. package/dist/src/render/mandates.js.map +1 -0
  61. package/dist/src/render/print.js +18 -0
  62. package/dist/src/render/print.js.map +1 -0
  63. package/dist/src/repl.js +114 -10
  64. package/dist/src/repl.js.map +1 -1
  65. package/dist/src/review.js +52 -0
  66. package/dist/src/review.js.map +1 -0
  67. package/dist/src/settings.js +88 -0
  68. package/dist/src/settings.js.map +1 -0
  69. package/dist/src/update-check.js +85 -0
  70. package/dist/src/update-check.js.map +1 -0
  71. package/dist/src/validate.js +92 -0
  72. package/dist/src/validate.js.map +1 -0
  73. package/package.json +2 -2
package/dist/src/repl.js CHANGED
@@ -1,9 +1,80 @@
1
1
  import readline from 'node:readline';
2
2
  import { CommanderError } from 'commander';
3
+ import { botanaryMcpHome } from 'botanary-mcp';
4
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
3
6
  import { parseLine } from './parser.js';
4
7
  import { renderBanner } from './render/banner.js';
5
8
  import { colors } from './render/colors.js';
6
- const COMMAND_NAMES = ['login', 'logout', 'status', 'balance', 'send', 'activity', 'markets', 'yield', 'mandates', 'agents', 'accounts', 'api', 'agent', 'mcp', 'diagnose', 'config', 'help', 'exit', 'quit'];
9
+ /** Words the REPL itself answers, which are not commands on the program. */
10
+ const REPL_WORDS = ['help', 'clear', 'exit', 'quit', '?'];
11
+ /** Global flags that must be set at startup, not per REPL line. */
12
+ const GLOBAL_FLAGS = new Set([
13
+ '--json', '--raw', '--quiet', '--verbose', '--no-color', '--no-interactive',
14
+ '--reveal', '--chain', '--account', '--api-url', '--home', '--timeout', '--debug', '--yes', '--dry-run',
15
+ '-q', '-y',
16
+ ]);
17
+ /**
18
+ * `botanary balance` typed INSIDE the REPL used to fail with "unknown command 'botanary'" - the exact
19
+ * papercut the Alchemy CLI reference has too. People type the program name out of habit; stripping it is
20
+ * three lines and removes a guaranteed first-run error.
21
+ */
22
+ export function normalizeReplLine(line) {
23
+ const trimmed = line.trim().replace(/\s+/g, ' ');
24
+ if (trimmed === '?')
25
+ return 'help';
26
+ if (trimmed === 'botanary')
27
+ return '';
28
+ if (trimmed.startsWith('botanary '))
29
+ return trimmed.slice('botanary '.length).trim();
30
+ return trimmed;
31
+ }
32
+ /**
33
+ * Completions from the parser tree rather than a hardcoded list: 0.1.0 kept a second copy of every
34
+ * command name in this file, which drifted the moment a command was added, and only completed at position
35
+ * zero because it returned the whole line as the token being completed.
36
+ */
37
+ export function replCompletions(program, line) {
38
+ const normalized = normalizeReplLine(line);
39
+ const tokens = parseLine(normalized);
40
+ const trailingSpace = /\s$/.test(line) || normalized === '';
41
+ const partial = trailingSpace ? '' : (tokens[tokens.length - 1] ?? '');
42
+ const consumed = trailingSpace ? tokens : tokens.slice(0, -1);
43
+ let cmd = program;
44
+ for (const token of consumed) {
45
+ const next = cmd.commands.find((c) => c.name() === token || c.aliases().includes(token));
46
+ if (!next)
47
+ break;
48
+ cmd = next;
49
+ }
50
+ const candidates = [
51
+ ...cmd.commands.map((c) => c.name()),
52
+ ...cmd.options.map((o) => o.long ?? o.short ?? '').filter((f) => f !== ''),
53
+ ...(cmd === program ? REPL_WORDS : []),
54
+ ];
55
+ const hits = candidates.filter((c) => c.startsWith(partial)).sort();
56
+ return [hits.length ? hits : candidates.sort(), partial];
57
+ }
58
+ const HISTORY_LIMIT = 200;
59
+ const historyPath = () => join(botanaryMcpHome(), 'cli-history');
60
+ async function loadHistory() {
61
+ try {
62
+ const lines = (await readFile(historyPath(), 'utf8')).split('\n').filter((l) => l !== '');
63
+ return lines.slice(0, HISTORY_LIMIT);
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ }
69
+ async function saveHistory(history) {
70
+ try {
71
+ await mkdir(botanaryMcpHome(), { recursive: true });
72
+ await writeFile(historyPath(), `${history.slice(0, HISTORY_LIMIT).join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
73
+ }
74
+ catch {
75
+ // A REPL that cannot write its history is still a working REPL - never fail the session over it.
76
+ }
77
+ }
7
78
  /** A dispatched command (e.g. `send`, `agent spend`) may itself build an interactive prompt via
8
79
  * @clack/prompts, which creates its OWN readline interface on this same process.stdin. Verified live:
9
80
  * two readline interfaces alive on one stdin at once is not just cosmetically risky - the REPL's own
@@ -13,18 +84,19 @@ const COMMAND_NAMES = ['login', 'logout', 'status', 'balance', 'send', 'activity
13
84
  * the REPL's still-open outer interface of further input with no way to recover (confirmed: it never
14
85
  * sees a subsequent line again, including "exit"). The fix is to never let two interfaces be alive at
15
86
  * once: close this interface before dispatching, then build a fresh one once the command settles. */
16
- function createReplInterface() {
87
+ function createReplInterface(program, history) {
17
88
  return readline.createInterface({
18
89
  input: process.stdin,
19
90
  output: process.stdout,
20
91
  prompt: colors.brand('> '),
21
- completer: (line) => {
22
- const hits = COMMAND_NAMES.filter((c) => c.startsWith(line));
23
- return [hits.length ? hits : COMMAND_NAMES, line];
24
- },
92
+ // readline wants most-recent-first, which is the order `history` is maintained in.
93
+ history: [...history],
94
+ removeHistoryDuplicates: true,
95
+ completer: (line) => replCompletions(program, line),
25
96
  });
26
97
  }
27
98
  export async function runRepl(program, state) {
99
+ const history = await loadHistory();
28
100
  console.log(renderBanner('BOTANARY'));
29
101
  console.log(`${colors.brand('◆')} ${colors.bold('Welcome to Botanary')}`);
30
102
  console.log(state.loggedIn ? `${colors.success('✔')} Logged in` : `${colors.muted('not logged in')} - try \`login\``);
@@ -33,7 +105,9 @@ export async function runRepl(program, state) {
33
105
  const quick = state.loggedIn ? ['balance', 'send', 'status'] : ['login', 'agent pair'];
34
106
  for (const q of quick)
35
107
  console.log(colors.dim(` - ${q}`));
36
- console.log(colors.dim("\nPress TAB for autocomplete. Type 'exit' or 'quit' to leave.\n"));
108
+ console.log(colors.dim("Press TAB for autocomplete. Type 'clear' to clear, 'exit' or 'quit' to leave."));
109
+ console.log(colors.dim('Ctrl-C leaves immediately.\n'));
110
+ let savePromise = Promise.resolve();
37
111
  await new Promise((resolve) => {
38
112
  // The REPL's readline interface gets closed and rebuilt around every dispatched command (see
39
113
  // createReplInterface's doc comment), so a *lot* more 'close' events fire than just "the user is
@@ -55,6 +129,7 @@ export async function runRepl(program, state) {
55
129
  if (finished)
56
130
  return;
57
131
  finished = true;
132
+ savePromise = saveHistory(history);
58
133
  console.log(colors.dim('\nbye.'));
59
134
  resolve();
60
135
  };
@@ -64,18 +139,43 @@ export async function runRepl(program, state) {
64
139
  queue = queue.then(async () => {
65
140
  if (finished)
66
141
  return;
67
- const trimmed = line.trim();
142
+ const trimmed = normalizeReplLine(line);
68
143
  if (trimmed === 'exit' || trimmed === 'quit') {
69
144
  current.close();
70
145
  finish();
71
146
  return;
72
147
  }
148
+ if (trimmed === 'clear') {
149
+ // \x1bc is a full terminal reset, which also drops the scrollback the banner filled.
150
+ process.stdout.write('\x1bc');
151
+ current.prompt();
152
+ return;
153
+ }
73
154
  if (trimmed) {
155
+ // Check if the line contains a global flag - these must be set at startup, not per REPL line.
156
+ const tokens = parseLine(trimmed);
157
+ for (const rawToken of tokens) {
158
+ // Split on `=` first: `--chain=8453` is valid syntax everywhere else in this CLI, and
159
+ // matching only the bare spelling let that form through silently - which is the exact
160
+ // "typed here, quietly ignored" bug this guard exists to prevent.
161
+ const token = rawToken.split('=')[0];
162
+ if (GLOBAL_FLAGS.has(token)) {
163
+ console.log(colors.warning('Global flags (like ' + token + ') must be set when starting botanary, not per REPL line.\n' +
164
+ ' Exit with `quit`, restart with: botanary ' + token + ' ... \n' +
165
+ ' Global flags: --json --raw --quiet --verbose --no-color --no-interactive --reveal ' +
166
+ '--chain --account --api-url --home --timeout --debug --yes --dry-run'));
167
+ current.prompt();
168
+ return;
169
+ }
170
+ }
74
171
  // A dispatched command may itself open an interactive prompt (e.g. `send`/`agent spend`
75
172
  // use @clack/prompts, which builds its OWN readline interface on this same process.stdin).
76
173
  // Two live readline interfaces on one stdin fight over raw-mode/keypress state - closing
77
174
  // this one first (and building a fresh one once the command settles) keeps exactly one
78
175
  // interface alive at a time, never two.
176
+ history.unshift(trimmed);
177
+ if (history.length > HISTORY_LIMIT)
178
+ history.length = HISTORY_LIMIT;
79
179
  closingForDispatch = true;
80
180
  current.close();
81
181
  try {
@@ -96,7 +196,7 @@ export async function runRepl(program, state) {
96
196
  finally {
97
197
  closingForDispatch = false;
98
198
  if (!finished) {
99
- attach(createReplInterface());
199
+ attach(createReplInterface(program, history));
100
200
  current.prompt();
101
201
  }
102
202
  }
@@ -120,9 +220,13 @@ export async function runRepl(program, state) {
120
220
  });
121
221
  });
122
222
  };
123
- attach(createReplInterface());
223
+ attach(createReplInterface(program, history));
124
224
  // attach() assigns `current` synchronously before returning.
125
225
  current.prompt();
126
226
  });
227
+ // The history save was initiated in finish() but we waited for the REPL to resolve first. Now
228
+ // wait for the actual file write to complete before returning, so process.exit() (called by
229
+ // bin/botanary.ts immediately after await runCli() resolves) doesn't race the filesystem I/O.
230
+ await savePromise;
127
231
  }
128
232
  //# sourceMappingURL=repl.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"repl.js","sourceRoot":"","sources":["../../src/repl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repl.js","sourceRoot":"","sources":["../../src/repl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import * as clack from '@clack/prompts';
2
+ import { colors } from './render/colors.js';
3
+ import { kv, section } from './render/kv.js';
4
+ import { CliError, EXIT, invalidArgs } from './errors.js';
5
+ export function renderReview(title, rows, warnings) {
6
+ const blocks = [section(title), kv(rows.map((row) => [row.label, row.value]))];
7
+ if (warnings.length) {
8
+ blocks.push('');
9
+ for (const warning of warnings)
10
+ blocks.push(colors.warning(` ⚠ ${warning}`));
11
+ }
12
+ return blocks.join('\n');
13
+ }
14
+ /** The same panel, flat, for `--dry-run --json`. */
15
+ export function dryRunPayload(title, rows, warnings) {
16
+ return {
17
+ dryRun: true,
18
+ title,
19
+ review: Object.fromEntries(rows.map((row) => [row.label, row.value])),
20
+ warnings,
21
+ };
22
+ }
23
+ /**
24
+ * Show what is about to happen and get a yes, or refuse.
25
+ *
26
+ * 0.1.0 went from four free-text prompts straight to relaying funds. The gate has one hard rule: it never
27
+ * proceeds on an assumption. `--yes` is an explicit yes; an interactive terminal is asked; and anything
28
+ * else - a script, a pipe, `--json`, `--no-interactive` - is exit 2 naming the flag it needed, rather than
29
+ * a prompt that cannot render or a silent self-confirmation.
30
+ */
31
+ export async function confirmOrThrow(ctx, title, rows, warnings, opts) {
32
+ // When yes came from the config file (confirm: never), still print the review panel so the user sees
33
+ // what is happening. When --yes was passed explicitly, skip the panel.
34
+ const yesFromConfig = ctx.yes && ctx.settings?.provenance?.yes === 'file';
35
+ if (yesFromConfig) {
36
+ if (!ctx.quiet)
37
+ console.log(renderReview(title, rows, warnings));
38
+ return;
39
+ }
40
+ if (ctx.yes)
41
+ return;
42
+ if (ctx.json || !ctx.interactive) {
43
+ throw invalidArgs(`${opts?.because ?? 'This command moves funds and needs a confirmation it cannot ask for here.'} Pass --yes to confirm non-interactively, or --dry-run to preflight it instead.`);
44
+ }
45
+ if (!ctx.quiet)
46
+ console.log(renderReview(title, rows, warnings));
47
+ const answer = await clack.confirm({ message: opts?.question ?? 'Send it?', initialValue: false });
48
+ if (answer !== true) {
49
+ throw new CliError('Cancelled - nothing was signed and nothing was sent.', 'cancelled', EXIT.error);
50
+ }
51
+ }
52
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/review.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB1D,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,IAAiB,EAAE,QAAkB;IAC/E,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAqB,CAAC,CAAC,CAAC,CAAC;IACnG,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,KAAK,MAAM,OAAO,IAAI,QAAQ;YAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,IAAiB,EAAE,QAAkB;IAChF,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,KAAK;QACL,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAc,EACd,KAAa,EACb,IAAiB,EACjB,QAAkB,EAClB,IAA8C;IAE9C,qGAAqG;IACrG,uEAAuE;IACvE,MAAM,aAAa,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,KAAK,MAAM,CAAC;IAC1E,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG,CAAC,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;QACjE,OAAO;IACT,CAAC;IACD,IAAI,GAAG,CAAC,GAAG;QAAE,OAAO;IACpB,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACjC,MAAM,WAAW,CACf,GAAG,IAAI,EAAE,OAAO,IAAI,2EAA2E,iFAAiF,CACjL,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;IACnG,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,QAAQ,CAAC,sDAAsD,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACtG,CAAC;AACH,CAAC"}
@@ -0,0 +1,88 @@
1
+ import pc from 'picocolors';
2
+ import { invalidArgs } from './errors.js';
3
+ function truthy(value) {
4
+ return value === '1' || value === 'true' || value === 'yes';
5
+ }
6
+ /** One resolution order for everything: flag, then env, then the config file, then a built-in default. */
7
+ export function resolveSettings(input) {
8
+ const { opts, env, file, stdout, stdin } = input;
9
+ const provenance = {};
10
+ function pick(key, candidates, fallback) {
11
+ for (const [from, value] of candidates) {
12
+ if (value !== undefined) {
13
+ provenance[key] = from;
14
+ return value;
15
+ }
16
+ }
17
+ provenance[key] = 'default';
18
+ return fallback;
19
+ }
20
+ const json = pick('json', [
21
+ ['flag', opts.json === true ? true : undefined],
22
+ ['env', truthy(env.BOTANARY_JSON) ? true : undefined],
23
+ ['file', file.output === 'json' ? true : file.output === 'raw' ? false : undefined],
24
+ ], !stdout.isTTY);
25
+ const rawRequested = pick('raw', [
26
+ ['flag', opts.raw ? true : undefined],
27
+ ['file', file.output === 'raw' ? true : undefined],
28
+ ], false);
29
+ // `--json` wins, including the implicit one a pipe brings: `--raw` is a human affordance, and a machine
30
+ // consumer always gets the compact single-line form it can parse.
31
+ const raw = json ? false : rawRequested;
32
+ // When json overrides a requested raw, update provenance to match the actual value: the source was
33
+ // overridden, so reporting it would mislead a config list display.
34
+ if (json && rawRequested) {
35
+ provenance['raw'] = 'default';
36
+ }
37
+ const color = pick('color', [
38
+ ['flag', opts.color === false ? false : undefined],
39
+ ['env', env.NO_COLOR ? false : undefined],
40
+ ['file', file.color === 'never' ? false : file.color === 'auto' ? pc.isColorSupported : undefined],
41
+ ], pc.isColorSupported);
42
+ const interactive = pick('interactive', [['flag', opts.interactive === false ? false : undefined]], !!stdin.isTTY && !!stdout.isTTY);
43
+ const yes = pick('yes', [
44
+ ['flag', opts.yes ? true : undefined],
45
+ ['file', file.confirm === 'never' ? true : file.confirm === 'always' ? false : undefined],
46
+ ], false);
47
+ const timeoutMs = pick('timeoutMs', [
48
+ ['flag', opts.timeout === undefined ? undefined : parseTimeout(opts.timeout, '--timeout')],
49
+ ['env', env.BOTANARY_TIMEOUT_MS === undefined ? undefined : parseTimeout(env.BOTANARY_TIMEOUT_MS, 'BOTANARY_TIMEOUT_MS')],
50
+ ], null);
51
+ return {
52
+ json,
53
+ raw,
54
+ quiet: pick('quiet', [['flag', opts.quiet ? true : undefined]], false),
55
+ verbose: pick('verbose', [['flag', opts.verbose ? true : undefined]], false),
56
+ debug: pick('debug', [['flag', opts.debug ? true : undefined]], false),
57
+ color,
58
+ reveal: pick('reveal', [['flag', opts.reveal ? true : undefined]], false),
59
+ interactive,
60
+ yes,
61
+ dryRun: pick('dryRun', [['flag', opts.dryRun ? true : undefined]], false),
62
+ chain: pick('chain', [
63
+ ['flag', opts.chain],
64
+ ['env', env.BOTANARY_CHAIN_ID],
65
+ ['file', file.chainId],
66
+ ], null),
67
+ account: pick('account', [
68
+ ['flag', opts.account],
69
+ ['env', env.BOTANARY_ACCOUNT_ID],
70
+ ['file', file.accountId],
71
+ ], null),
72
+ apiUrl: pick('apiUrl', [
73
+ ['flag', opts.apiUrl],
74
+ ['env', env.BOTANARY_API_URL],
75
+ ['file', file.apiUrl],
76
+ ], null),
77
+ timeoutMs,
78
+ provenance,
79
+ };
80
+ }
81
+ function parseTimeout(value, source) {
82
+ const parsed = Number(value);
83
+ if (!Number.isFinite(parsed) || parsed <= 0) {
84
+ throw invalidArgs(`${source} must be a positive number of milliseconds, got "${value}".`);
85
+ }
86
+ return parsed;
87
+ }
88
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { botanaryMcpHome } from 'botanary-mcp';
4
+ /** One check a day is plenty: this is a courtesy, not a dependency. */
5
+ export const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
6
+ const REGISTRY_URL = 'https://registry.npmjs.org/botanary/latest';
7
+ const DEFAULT_TIMEOUT_MS = 1_500;
8
+ export function cachePath() {
9
+ return join(botanaryMcpHome(), 'update-check.json');
10
+ }
11
+ export async function readCache() {
12
+ try {
13
+ const parsed = JSON.parse(await readFile(cachePath(), 'utf8'));
14
+ if (typeof parsed.checkedAt !== 'number')
15
+ return null;
16
+ if (typeof parsed.latest !== 'string' && parsed.latest !== null)
17
+ return null;
18
+ return { checkedAt: parsed.checkedAt, latest: parsed.latest };
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export async function writeCache(entry) {
25
+ try {
26
+ await mkdir(botanaryMcpHome(), { recursive: true });
27
+ await writeFile(cachePath(), `${JSON.stringify(entry)}\n`, { encoding: 'utf8', mode: 0o600 });
28
+ }
29
+ catch {
30
+ // A cache we cannot write just means we check again next time. Never fail a command over it.
31
+ }
32
+ }
33
+ /** Numeric segment comparison. A prerelease sorts BELOW its release, so a beta never nags a stable user. */
34
+ export function isNewer(latest, current) {
35
+ const parse = (version) => {
36
+ const match = /^(\d+)\.(\d+)\.(\d+)(-.*)?$/.exec(version.trim());
37
+ if (!match)
38
+ return null;
39
+ return [Number(match[1]), Number(match[2]), Number(match[3]), match[4] ? 0 : 1];
40
+ };
41
+ const a = parse(latest);
42
+ const b = parse(current);
43
+ if (!a || !b)
44
+ return false;
45
+ for (let i = 0; i < 4; i += 1) {
46
+ if (a[i] !== b[i])
47
+ return a[i] > b[i];
48
+ }
49
+ return false;
50
+ }
51
+ /** Never throws, never waits long. A registry that is slow or unreachable simply produces no answer. */
52
+ export async function fetchLatest(deps) {
53
+ const controller = new AbortController();
54
+ const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? DEFAULT_TIMEOUT_MS);
55
+ try {
56
+ const response = await deps.fetchImpl(REGISTRY_URL, { signal: controller.signal });
57
+ if (!response.ok)
58
+ return null;
59
+ const body = (await response.json());
60
+ return typeof body.version === 'string' ? body.version : null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ finally {
66
+ clearTimeout(timer);
67
+ }
68
+ }
69
+ export async function checkForUpdate(current, deps) {
70
+ const cached = await readCache();
71
+ if (!deps.force && cached && deps.now() - cached.checkedAt < CACHE_TTL_MS) {
72
+ return { current, latest: cached.latest, newer: cached.latest ? isNewer(cached.latest, current) : false, fromCache: true };
73
+ }
74
+ const latest = await fetchLatest(deps);
75
+ await writeCache({ checkedAt: deps.now(), latest });
76
+ return { current, latest, newer: latest ? isNewer(latest, current) : false, fromCache: false };
77
+ }
78
+ export function nagLine(latest, current) {
79
+ return `Update available: ${current} to ${latest}. Run \`npm i -g botanary\` (or use \`npx botanary\`).`;
80
+ }
81
+ /** CI is included because a nag in a build log is noise nobody can act on from there. */
82
+ export function nagSuppressed(env) {
83
+ return !!env.BOTANARY_NO_UPDATE_NOTIFIER || !!env.CI;
84
+ }
85
+ //# sourceMappingURL=update-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../../src/update-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { CliError, EXIT, invalidArgs } from './errors.js';
2
+ /** Validate an amount at the one boundary that can still say which flag was wrong. 0.1.0 passed the raw
3
+ * string to `Number()`, so `--amount abc` became NaN and travelled all the way to a build call. */
4
+ export function parseAmount(raw, flag = '--amount') {
5
+ const value = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw.trim()) : Number.NaN;
6
+ if (!Number.isFinite(value) || value <= 0) {
7
+ throw invalidArgs(`${flag} must be a positive number, got ${JSON.stringify(raw)}.`, 'Amounts are in display units, e.g. 25 for 25 USDC.');
8
+ }
9
+ return value;
10
+ }
11
+ /** CAIP-2 namespaces map one-to-one onto address formats. Unknown or absent defaults to EVM, which is the
12
+ * only family a chain row without a namespace could be under the current contract. */
13
+ export function familyOf(namespace) {
14
+ if (namespace === 'solana')
15
+ return 'solana';
16
+ if (namespace === 'stellar')
17
+ return 'stellar';
18
+ return 'evm';
19
+ }
20
+ const PATTERNS = {
21
+ evm: { re: /^0x[0-9a-fA-F]{40}$/, label: 'an EVM address', example: '0x' + 'ab'.repeat(20) },
22
+ solana: { re: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/, label: 'a Solana address', example: '4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T' },
23
+ stellar: { re: /^G[A-Z2-7]{55}$/, label: 'a Stellar address', example: `G${'A'.repeat(55)}` },
24
+ };
25
+ export function assertAddress(raw, family) {
26
+ if (typeof raw !== 'string' || raw.trim() === '') {
27
+ throw invalidArgs('--recipient is required.', `Expected ${PATTERNS[family].label}, e.g. ${PATTERNS[family].example}.`);
28
+ }
29
+ const value = raw.trim();
30
+ const pattern = PATTERNS[family];
31
+ if (!pattern.re.test(value)) {
32
+ throw invalidArgs(`"${value}" is not ${pattern.label}.`, `This chain expects ${pattern.label}, e.g. ${pattern.example}.`);
33
+ }
34
+ return value;
35
+ }
36
+ /** Resolve whatever the user typed against the chains the backend actually serves. Accepts a chain id, a
37
+ * chain key, a CAIP-2 chainRef, or a case-insensitive name, because all four appear in the product. */
38
+ export function findChain(chains, ref) {
39
+ const list = (Array.isArray(chains) ? chains : []);
40
+ if (!list.length) {
41
+ throw new CliError('The backend reported no chains at all.', 'no_chains', EXIT.upstream, 'Try again shortly, or check `botanary config` for the API URL in use.');
42
+ }
43
+ if (ref === null || ref === undefined || ref === '') {
44
+ throw invalidArgs('--chain is required.', `Pass --chain-id for EVM chains or --chain for any family, or set one with \`botanary config set chainId <id>\`. See \`botanary chains\`.`);
45
+ }
46
+ const needle = String(ref).trim().toLowerCase();
47
+ const match = list.find((chain) => String(chain.key ?? '').toLowerCase() === needle ||
48
+ String(chain.chainId ?? '').toLowerCase() === needle ||
49
+ String(chain.chainRef ?? '').toLowerCase() === needle ||
50
+ String(chain.name ?? '').toLowerCase() === needle);
51
+ if (!match) {
52
+ const known = list.map((chain) => chain.key).filter(Boolean).slice(0, 12).join(', ');
53
+ throw invalidArgs(`"${ref}" is not a chain this backend serves. Try one of: ${known}.`, `Full list: \`botanary chains\`.`);
54
+ }
55
+ return {
56
+ key: String(match.key ?? ''),
57
+ name: String(match.name ?? ''),
58
+ chainId: typeof match.chainId === 'number' ? match.chainId : null,
59
+ chainRef: String(match.chainRef ?? ''),
60
+ namespace: String(match.namespace ?? 'eip155'),
61
+ tier: String(match.tier ?? ''),
62
+ testnet: match.testnet === true,
63
+ };
64
+ }
65
+ /**
66
+ * Refuse before building what the chain's own tier cannot do. This is an OFF-CHAIN preflight, never an
67
+ * on-chain revert - product copy must not imply otherwise (workspace CLAUDE.md, "Chain families and
68
+ * tiers") - and it reports as a decline rather than a usage error, because nothing about the command was
69
+ * malformed.
70
+ */
71
+ export function assertTierAllows(chain, need) {
72
+ if (need === 'read')
73
+ return;
74
+ if (chain.tier === 'watch') {
75
+ throw new CliError(`${chain.name} (${chain.key}) is a watch-tier chain: read-only, with no bundler.`, 'chain_capability_unavailable', EXIT.declined, 'Pick a chain with tier basic or botanary - see `botanary chains`.');
76
+ }
77
+ if (need === 'authority' && chain.tier !== 'botanary') {
78
+ throw new CliError(`${chain.name} (${chain.key}) carries no AgentGuard, so authority actions are unavailable there.`, 'chain_capability_unavailable', EXIT.declined, 'Authority actions need a botanary-tier chain - see `botanary chains`.');
79
+ }
80
+ }
81
+ /** Tokens actually held on one chain, largest holding first, for a `select` prompt. Never a catalog: a
82
+ * token the account does not hold is not a sendable choice. */
83
+ export function tokenChoicesFor(balance, chainRef) {
84
+ const holdings = balance?.holdings;
85
+ const list = Array.isArray(holdings) ? holdings : [];
86
+ return list
87
+ .filter((h) => typeof h.assetRef === 'string' && h.assetRef.split('/')[0] === chainRef)
88
+ .filter((h) => typeof h.symbol === 'string' && typeof h.amount === 'number' && h.amount > 0)
89
+ .map((h) => ({ symbol: h.symbol, amount: h.amount }))
90
+ .sort((a, b) => b.amount - a.amount);
91
+ }
92
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "botanary",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "Botanary from your terminal - a guided CLI over the same wallet and agent lanes botanary-mcp exposes to a coding agent.",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://docs.botanary.xyz",
@@ -23,7 +23,7 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
- "botanary-mcp": "^0.5.0",
26
+ "botanary-mcp": "^0.6.0",
27
27
  "commander": "^13.1.0",
28
28
  "@clack/prompts": "^0.9.1",
29
29
  "picocolors": "^1.1.1",