tenjin-cli 0.1.0-alpha.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +52 -0
  3. package/README.md +124 -0
  4. package/dist/_esm-ZH2J4QEH.js +3737 -0
  5. package/dist/_esm-ZH2J4QEH.js.map +1 -0
  6. package/dist/ccip-FXWZDDF5.js +17 -0
  7. package/dist/ccip-FXWZDDF5.js.map +1 -0
  8. package/dist/chunk-376LHMK2.js +100 -0
  9. package/dist/chunk-376LHMK2.js.map +1 -0
  10. package/dist/chunk-3IMLRUZE.js +44 -0
  11. package/dist/chunk-3IMLRUZE.js.map +1 -0
  12. package/dist/chunk-CPUZRZ2A.js +410 -0
  13. package/dist/chunk-CPUZRZ2A.js.map +1 -0
  14. package/dist/chunk-F5DZJLU3.js +2058 -0
  15. package/dist/chunk-F5DZJLU3.js.map +1 -0
  16. package/dist/chunk-FIYZLITH.js +14577 -0
  17. package/dist/chunk-FIYZLITH.js.map +1 -0
  18. package/dist/chunk-LTTRND4A.js +135 -0
  19. package/dist/chunk-LTTRND4A.js.map +1 -0
  20. package/dist/chunk-PAAL45FJ.js +7307 -0
  21. package/dist/chunk-PAAL45FJ.js.map +1 -0
  22. package/dist/chunk-QHIEYBLV.js +320 -0
  23. package/dist/chunk-QHIEYBLV.js.map +1 -0
  24. package/dist/chunk-SXQQ2ZSR.js +887 -0
  25. package/dist/chunk-SXQQ2ZSR.js.map +1 -0
  26. package/dist/chunk-TX5T3LKJ.js +2249 -0
  27. package/dist/chunk-TX5T3LKJ.js.map +1 -0
  28. package/dist/chunk-WOHCAMDQ.js +4474 -0
  29. package/dist/chunk-WOHCAMDQ.js.map +1 -0
  30. package/dist/chunk-ZPZFPVDN.js +67 -0
  31. package/dist/chunk-ZPZFPVDN.js.map +1 -0
  32. package/dist/cli-LMHGCZ5S.js +3763 -0
  33. package/dist/cli-LMHGCZ5S.js.map +1 -0
  34. package/dist/config-XVXZ3V2Q.js +206 -0
  35. package/dist/config-XVXZ3V2Q.js.map +1 -0
  36. package/dist/doctor-MVLZRVPU.js +338 -0
  37. package/dist/doctor-MVLZRVPU.js.map +1 -0
  38. package/dist/index.js +26 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/mine.wasm-GQECMSGN.js +11 -0
  41. package/dist/mine.wasm-GQECMSGN.js.map +1 -0
  42. package/dist/secp256k1-35YNNB6F.js +18 -0
  43. package/dist/secp256k1-35YNNB6F.js.map +1 -0
  44. package/dist/usdc-OQUOFF6R.js +18 -0
  45. package/dist/usdc-OQUOFF6R.js.map +1 -0
  46. package/dist/wallet-NRKWLKHX.js +153 -0
  47. package/dist/wallet-NRKWLKHX.js.map +1 -0
  48. package/dist/wallet-QUWK2PA4.js +22 -0
  49. package/dist/wallet-QUWK2PA4.js.map +1 -0
  50. package/package.json +63 -0
@@ -0,0 +1,206 @@
1
+ import { createRequire as __tenjinCreateRequire } from 'node:module'; const require = __tenjinCreateRequire(import.meta.url);
2
+ import {
3
+ CONFIG_KEYS,
4
+ RawConfigSchema,
5
+ loadRawConfig,
6
+ parseUsdToAtomic,
7
+ resolveSettings,
8
+ toMoney
9
+ } from "./chunk-LTTRND4A.js";
10
+ import {
11
+ writeFileAtomic
12
+ } from "./chunk-ZPZFPVDN.js";
13
+ import {
14
+ CliError,
15
+ configPath
16
+ } from "./chunk-FIYZLITH.js";
17
+ import "./chunk-3IMLRUZE.js";
18
+
19
+ // src/commands/config.ts
20
+ import { mkdir as mkdir2 } from "fs/promises";
21
+ import { styleText } from "util";
22
+
23
+ // src/lib/lock.ts
24
+ import { mkdir, rm, writeFile } from "fs/promises";
25
+ import { join } from "path";
26
+ var DEFAULTS = { timeoutMs: 5e3, retryMs: 25 };
27
+ var LockTimeoutError = class extends Error {
28
+ lockPath;
29
+ constructor(lockPath, timeoutMs) {
30
+ super(
31
+ `Timed out waiting for lock at ${lockPath} after ${timeoutMs}ms; if no other tenjin process is running, remove that directory and retry.`
32
+ );
33
+ this.name = "LockTimeoutError";
34
+ this.lockPath = lockPath;
35
+ }
36
+ };
37
+ async function withFileLock(lockPath, fn, opts = {}) {
38
+ const { timeoutMs, retryMs } = { ...DEFAULTS, ...opts };
39
+ const metaPath = join(lockPath, "meta");
40
+ const deadline = Date.now() + timeoutMs;
41
+ for (; ; ) {
42
+ try {
43
+ await mkdir(lockPath);
44
+ break;
45
+ } catch (err) {
46
+ if (!isEexist(err)) throw err;
47
+ if (Date.now() >= deadline) throw new LockTimeoutError(lockPath, timeoutMs);
48
+ await delay(retryMs);
49
+ }
50
+ }
51
+ await writeFile(metaPath, JSON.stringify({ pid: process.pid, acquiredAt: Date.now() })).catch(
52
+ () => void 0
53
+ );
54
+ try {
55
+ return await fn();
56
+ } finally {
57
+ await rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
58
+ }
59
+ }
60
+ function isEexist(err) {
61
+ return typeof err === "object" && err !== null && err.code === "EEXIST";
62
+ }
63
+ function delay(ms) {
64
+ return new Promise((resolve) => setTimeout(resolve, ms));
65
+ }
66
+
67
+ // src/commands/config.ts
68
+ var CONFIRM_ABOVE = "above:";
69
+ var KEY_WIDTH = Math.max(...CONFIG_KEYS.map((key) => key.length));
70
+ async function runConfigList(ctx) {
71
+ const settings = await resolveFromContext(ctx);
72
+ const data = {};
73
+ const humanLines = [];
74
+ for (const key of CONFIG_KEYS) {
75
+ const entry = renderSetting(key, settings[key].value, settings[key].source);
76
+ data[key] = entry;
77
+ humanLines.push(formatLine(key, entry));
78
+ }
79
+ return { data, humanLines };
80
+ }
81
+ async function runConfigGet({ key }, ctx) {
82
+ const configKey = assertKey(key);
83
+ const settings = await resolveFromContext(ctx);
84
+ const entry = renderSetting(configKey, settings[configKey].value, settings[configKey].source);
85
+ return { data: { key: configKey, ...entry }, humanLines: [formatLine(configKey, entry)] };
86
+ }
87
+ async function runConfigSet({ key, value }, ctx) {
88
+ const configKey = assertKey(key);
89
+ const stored = parseValue(configKey, value);
90
+ await persist(ctx.dataDir, configKey, stored);
91
+ const entry = renderSetting(configKey, stored, "file");
92
+ return { data: { key: configKey, ...entry }, humanLines: [formatLine(configKey, entry)] };
93
+ }
94
+ async function resolveFromContext(ctx) {
95
+ const config = await loadRawConfig(ctx.dataDir);
96
+ return resolveSettings({ config, flags: { baseUrl: ctx.flags.baseUrl }, env: process.env });
97
+ }
98
+ function assertKey(key) {
99
+ if (CONFIG_KEYS.includes(key)) return key;
100
+ throw new CliError("USAGE", `Unknown config key: ${JSON.stringify(key)}`, {
101
+ fix: `Valid keys: ${CONFIG_KEYS.join(", ")}.`
102
+ });
103
+ }
104
+ function renderSetting(key, stored, source) {
105
+ return { ...renderValue(key, stored), source };
106
+ }
107
+ function renderValue(key, stored) {
108
+ if (Array.isArray(stored)) return { value: stored };
109
+ if (key === "maxAutoSpend" || key === "sessionBudget") return { value: toMoney(stored) };
110
+ if (key === "confirm" && stored.startsWith(CONFIRM_ABOVE)) {
111
+ return { value: stored, threshold: toMoney(stored.slice(CONFIRM_ABOVE.length)) };
112
+ }
113
+ return { value: stored };
114
+ }
115
+ function parseValue(key, value) {
116
+ switch (key) {
117
+ case "maxAutoSpend":
118
+ case "sessionBudget":
119
+ return parseUsdToAtomic(value);
120
+ // throws USAGE on a bad amount
121
+ case "confirm":
122
+ return parseConfirm(value);
123
+ case "allowlistCreators":
124
+ return parseAllowlist(value);
125
+ case "baseUrl":
126
+ case "rpcUrl":
127
+ return parseHttpUrl(value);
128
+ }
129
+ }
130
+ function parseConfirm(value) {
131
+ if (value === "always") return "always";
132
+ if (value.startsWith(CONFIRM_ABOVE)) {
133
+ return `${CONFIRM_ABOVE}${parseUsdToAtomic(value.slice(CONFIRM_ABOVE.length))}`;
134
+ }
135
+ throw new CliError("USAGE", `Invalid confirm value: ${JSON.stringify(value)}`, {
136
+ fix: 'Use "always" or "above:<usd>", e.g. above:0.25.'
137
+ });
138
+ }
139
+ function parseAllowlist(value) {
140
+ const entries = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
141
+ for (const entry of entries) {
142
+ if (/\s/.test(entry)) {
143
+ throw new CliError("USAGE", `Invalid creator entry: ${JSON.stringify(entry)}`, {
144
+ fix: "Creator entries cannot contain spaces; separate multiple with commas."
145
+ });
146
+ }
147
+ }
148
+ return entries;
149
+ }
150
+ function parseHttpUrl(value) {
151
+ let url;
152
+ try {
153
+ url = new URL(value);
154
+ } catch {
155
+ throw new CliError("USAGE", `Invalid URL: ${JSON.stringify(value)}`, {
156
+ fix: "Pass an absolute http(s) URL like https://tenjin.blog."
157
+ });
158
+ }
159
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
160
+ throw new CliError("USAGE", `URL must be http or https: ${JSON.stringify(value)}`, {
161
+ fix: "Pass an absolute http(s) URL like https://tenjin.blog."
162
+ });
163
+ }
164
+ return value;
165
+ }
166
+ async function persist(dir, key, stored) {
167
+ await mkdir2(dir, { recursive: true, mode: 448 });
168
+ const lockPath = `${configPath(dir)}.lock`;
169
+ try {
170
+ await withFileLock(lockPath, async () => {
171
+ const existing = await loadRawConfig(dir);
172
+ const merged = { ...existing, [key]: stored };
173
+ const validated = RawConfigSchema.parse(merged);
174
+ await writeFileAtomic(configPath(dir), `${JSON.stringify(validated, null, 2)}
175
+ `, {
176
+ mode: 420,
177
+ dirMode: 448
178
+ });
179
+ });
180
+ } catch (err) {
181
+ if (err instanceof LockTimeoutError) {
182
+ throw new CliError("INTERNAL", err.message, {
183
+ fix: `If no other tenjin process is running, remove ${lockPath} and retry.`,
184
+ cause: err
185
+ });
186
+ }
187
+ throw err;
188
+ }
189
+ }
190
+ function formatLine(key, entry) {
191
+ const label = key.padEnd(KEY_WIDTH);
192
+ return ` ${label} ${displayValue(entry)} ${styleText("dim", `(${entry.source})`)}`;
193
+ }
194
+ function displayValue(entry) {
195
+ const { value } = entry;
196
+ if (Array.isArray(value)) return value.length > 0 ? value.join(", ") : "(empty)";
197
+ if (typeof value === "object") return `${value.usd} USD`;
198
+ if (entry.threshold !== void 0) return `above ${entry.threshold.usd} USD`;
199
+ return value;
200
+ }
201
+ export {
202
+ runConfigGet,
203
+ runConfigList,
204
+ runConfigSet
205
+ };
206
+ //# sourceMappingURL=config-XVXZ3V2Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/config.ts","../src/lib/lock.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { styleText } from 'node:util';\nimport { CliError } from '../lib/errors';\nimport { CONFIG_KEYS, RawConfigSchema, loadRawConfig, resolveSettings } from '../lib/config';\nimport type { Config, EffectiveSettings, Provenance } from '../lib/config';\nimport { configPath } from '../lib/paths';\nimport { writeFileAtomic } from '../lib/atomic-json';\nimport { withFileLock, LockTimeoutError } from '../lib/lock';\nimport { parseUsdToAtomic, toMoney } from '../lib/money';\nimport type { Money } from '../schemas';\nimport type { CommandContext, CommandResult } from '../context';\n\n/**\n * How one config key is presented in `data`. `value` is the machine form: dual\n * Money for the spend keys, the stored string for `confirm`/URLs, the string[]\n * for the allowlist. `threshold` rides along only for `confirm: above:<atomic>`\n * so an agent reads the dollar amount without re-parsing the string.\n */\ninterface RenderedValue {\n value: Money | string | string[];\n threshold?: Money;\n}\ninterface RenderedSetting extends RenderedValue {\n source: Provenance;\n}\n\nconst CONFIRM_ABOVE = 'above:';\nconst KEY_WIDTH = Math.max(...CONFIG_KEYS.map((key) => key.length));\n\n/**\n * Show every effective key with its value and provenance. `data` is keyed by\n * config key; provenance comes from resolveSettings over the *raw* file (not the\n * defaults-merged config), so a key the user never set reads `default`, not\n * `file`.\n */\nexport async function runConfigList(ctx: CommandContext): Promise<CommandResult> {\n const settings = await resolveFromContext(ctx);\n const data: Record<string, RenderedSetting> = {};\n const humanLines: string[] = [];\n for (const key of CONFIG_KEYS) {\n const entry = renderSetting(key, settings[key].value, settings[key].source);\n data[key] = entry;\n humanLines.push(formatLine(key, entry));\n }\n return { data, humanLines };\n}\n\n/** Same per-key shape as list, for one key. Unknown key is a USAGE failure. */\nexport async function runConfigGet(\n { key }: { key: string },\n ctx: CommandContext,\n): Promise<CommandResult> {\n const configKey = assertKey(key);\n const settings = await resolveFromContext(ctx);\n const entry = renderSetting(configKey, settings[configKey].value, settings[configKey].source);\n return { data: { key: configKey, ...entry }, humanLines: [formatLine(configKey, entry)] };\n}\n\n/**\n * Parse the value for this key, then persist it merged into the existing raw\n * file — never materializing defaults for keys the user did not set, so\n * provenance stays truthful. The written key now reads `file`.\n */\nexport async function runConfigSet(\n { key, value }: { key: string; value: string },\n ctx: CommandContext,\n): Promise<CommandResult> {\n const configKey = assertKey(key);\n const stored = parseValue(configKey, value);\n await persist(ctx.dataDir, configKey, stored);\n const entry = renderSetting(configKey, stored, 'file');\n return { data: { key: configKey, ...entry }, humanLines: [formatLine(configKey, entry)] };\n}\n\nasync function resolveFromContext(ctx: CommandContext): Promise<EffectiveSettings> {\n const config = await loadRawConfig(ctx.dataDir);\n return resolveSettings({ config, flags: { baseUrl: ctx.flags.baseUrl }, env: process.env });\n}\n\nfunction assertKey(key: string): keyof Config {\n if ((CONFIG_KEYS as string[]).includes(key)) return key as keyof Config;\n throw new CliError('USAGE', `Unknown config key: ${JSON.stringify(key)}`, {\n fix: `Valid keys: ${CONFIG_KEYS.join(', ')}.`,\n });\n}\n\nfunction renderSetting(\n key: keyof Config,\n stored: string | string[],\n source: Provenance,\n): RenderedSetting {\n return { ...renderValue(key, stored), source };\n}\n\nfunction renderValue(key: keyof Config, stored: string | string[]): RenderedValue {\n if (Array.isArray(stored)) return { value: stored }; // allowlistCreators\n if (key === 'maxAutoSpend' || key === 'sessionBudget') return { value: toMoney(stored) };\n if (key === 'confirm' && stored.startsWith(CONFIRM_ABOVE)) {\n return { value: stored, threshold: toMoney(stored.slice(CONFIRM_ABOVE.length)) };\n }\n return { value: stored };\n}\n\n/** Per-key edge parsing. Returns the persisted form; throws USAGE on bad input. */\nfunction parseValue(key: keyof Config, value: string): string | string[] {\n switch (key) {\n case 'maxAutoSpend':\n case 'sessionBudget':\n return parseUsdToAtomic(value); // throws USAGE on a bad amount\n case 'confirm':\n return parseConfirm(value);\n case 'allowlistCreators':\n return parseAllowlist(value);\n case 'baseUrl':\n case 'rpcUrl':\n return parseHttpUrl(value);\n }\n}\n\nfunction parseConfirm(value: string): string {\n if (value === 'always') return 'always';\n if (value.startsWith(CONFIRM_ABOVE)) {\n return `${CONFIRM_ABOVE}${parseUsdToAtomic(value.slice(CONFIRM_ABOVE.length))}`;\n }\n throw new CliError('USAGE', `Invalid confirm value: ${JSON.stringify(value)}`, {\n fix: 'Use \"always\" or \"above:<usd>\", e.g. above:0.25.',\n });\n}\n\nfunction parseAllowlist(value: string): string[] {\n // \"\" clears to []; comma-split, trim, drop empties. Reject an entry with\n // internal whitespace — a creator handle/address is a single token.\n const entries = value\n .split(',')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0);\n for (const entry of entries) {\n if (/\\s/.test(entry)) {\n throw new CliError('USAGE', `Invalid creator entry: ${JSON.stringify(entry)}`, {\n fix: 'Creator entries cannot contain spaces; separate multiple with commas.',\n });\n }\n }\n return entries;\n}\n\nfunction parseHttpUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new CliError('USAGE', `Invalid URL: ${JSON.stringify(value)}`, {\n fix: 'Pass an absolute http(s) URL like https://tenjin.blog.',\n });\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new CliError('USAGE', `URL must be http or https: ${JSON.stringify(value)}`, {\n fix: 'Pass an absolute http(s) URL like https://tenjin.blog.',\n });\n }\n return value;\n}\n\n/**\n * Merge one key into the raw file and write it back atomically. Uses the partial\n * schema and writeFileAtomic (not writeConfig, which would materialize every\n * default into the file and corrupt provenance). loadRawConfig surfaces a\n * corrupt existing file as CONFIG_INVALID, which propagates.\n *\n * The whole read-merge-write runs under a cross-process file lock: without it two\n * concurrent `config set` on different keys race on the read, and the last writer\n * drops the other's update (worst case, a zeroed maxAutoSpend resurrected). The\n * data dir is ensured first so the lock's mkdir has a parent on a fresh install.\n */\nasync function persist(dir: string, key: keyof Config, stored: string | string[]): Promise<void> {\n await mkdir(dir, { recursive: true, mode: 0o700 });\n const lockPath = `${configPath(dir)}.lock`;\n try {\n await withFileLock(lockPath, async () => {\n const existing = await loadRawConfig(dir);\n const merged = { ...existing, [key]: stored };\n const validated = RawConfigSchema.parse(merged);\n await writeFileAtomic(configPath(dir), `${JSON.stringify(validated, null, 2)}\\n`, {\n mode: 0o644,\n dirMode: 0o700,\n });\n });\n } catch (err) {\n // A lock timeout is not the user's malformed input; surface it as INTERNAL with\n // the one manual step (there is no auto-steal), keeping the JSON error contract.\n if (err instanceof LockTimeoutError) {\n throw new CliError('INTERNAL', err.message, {\n fix: `If no other tenjin process is running, remove ${lockPath} and retry.`,\n cause: err,\n });\n }\n throw err;\n }\n}\n\nfunction formatLine(key: string, entry: RenderedSetting): string {\n const label = key.padEnd(KEY_WIDTH);\n return ` ${label} ${displayValue(entry)} ${styleText('dim', `(${entry.source})`)}`;\n}\n\nfunction displayValue(entry: RenderedSetting): string {\n const { value } = entry;\n if (Array.isArray(value)) return value.length > 0 ? value.join(', ') : '(empty)';\n if (typeof value === 'object') return `${value.usd} USD`; // Money (spend keys)\n if (entry.threshold !== undefined) return `above ${entry.threshold.usd} USD`; // confirm\n return value; // 'always', baseUrl, rpcUrl\n}\n","import { mkdir, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface FileLockOptions {\n /** Give up acquiring after this long (ms). */\n timeoutMs?: number;\n /** Poll interval between acquisition attempts (ms). */\n retryMs?: number;\n}\n\nconst DEFAULTS = { timeoutMs: 5000, retryMs: 25 };\n\n/** Thrown when the lock cannot be acquired within `timeoutMs`. Carries the lock\n * path so a caller can surface the manual-recovery step in its own error contract. */\nexport class LockTimeoutError extends Error {\n readonly lockPath: string;\n constructor(lockPath: string, timeoutMs: number) {\n super(\n `Timed out waiting for lock at ${lockPath} after ${timeoutMs}ms; if no other tenjin process is running, remove that directory and retry.`,\n );\n this.name = 'LockTimeoutError';\n this.lockPath = lockPath;\n }\n}\n\n/**\n * Minimal cross-process mutex: the lock IS a directory, and mkdir is atomic, so a\n * second holder gets EEXIST and retries with backoff until `timeoutMs`, then\n * throws LockTimeoutError. There is deliberately NO auto-steal of a \"stale\" lock:\n * concurrent waiters each judging a lock stale could remove a fresh holder's\n * directory and lose its update. A crashed holder's lock is recovered by hand (the\n * timeout error names the path); the pid+acquiredAt meta exists only to make that\n * manual call diagnosable. Always released in the finally.\n */\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n opts: FileLockOptions = {},\n): Promise<T> {\n const { timeoutMs, retryMs } = { ...DEFAULTS, ...opts };\n const metaPath = join(lockPath, 'meta');\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n await mkdir(lockPath);\n break; // acquired\n } catch (err) {\n if (!isEexist(err)) throw err;\n if (Date.now() >= deadline) throw new LockTimeoutError(lockPath, timeoutMs);\n await delay(retryMs);\n }\n }\n await writeFile(metaPath, JSON.stringify({ pid: process.pid, acquiredAt: Date.now() })).catch(\n () => undefined,\n );\n try {\n return await fn();\n } finally {\n // With no stealing, nothing else ever removes or replaces this directory, so\n // the lock here is provably the one we created — the unconditional rm is sound.\n await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n }\n}\n\nfunction isEexist(err: unknown): boolean {\n return typeof err === 'object' && err !== null && (err as { code?: unknown }).code === 'EEXIST';\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":["mkdir","mkdir"]}
@@ -0,0 +1,338 @@
1
+ import { createRequire as __tenjinCreateRequire } from 'node:module'; const require = __tenjinCreateRequire(import.meta.url);
2
+ import {
3
+ loadRawConfig,
4
+ resolveSettings,
5
+ toMoney
6
+ } from "./chunk-LTTRND4A.js";
7
+ import {
8
+ walletFileExists
9
+ } from "./chunk-376LHMK2.js";
10
+ import "./chunk-ZPZFPVDN.js";
11
+ import {
12
+ CliError,
13
+ configPath
14
+ } from "./chunk-FIYZLITH.js";
15
+ import "./chunk-3IMLRUZE.js";
16
+
17
+ // src/commands/doctor.ts
18
+ import { styleText } from "util";
19
+ import { Stream } from "stream";
20
+
21
+ // src/lib/http.ts
22
+ async function fetchJson(url, opts) {
23
+ const doFetch = opts.fetchImpl ?? fetch;
24
+ const controller = new AbortController();
25
+ let timedOut = false;
26
+ const timer = setTimeout(() => {
27
+ timedOut = true;
28
+ controller.abort();
29
+ }, opts.timeoutMs);
30
+ try {
31
+ let res;
32
+ try {
33
+ res = await doFetch(url, { signal: controller.signal });
34
+ } catch (err) {
35
+ return timedOut ? timeoutFailure(url, opts.timeoutMs) : { ok: false, kind: "network", message: `Request to ${url} failed: ${errorMessage(err)}` };
36
+ }
37
+ const requestId = res.headers.get("x-request-id") ?? void 0;
38
+ if (!res.ok) {
39
+ return {
40
+ ok: false,
41
+ kind: "http",
42
+ status: res.status,
43
+ ...requestId !== void 0 ? { requestId } : {},
44
+ message: `Request to ${url} failed with status ${res.status}`
45
+ };
46
+ }
47
+ let json;
48
+ try {
49
+ json = await res.json();
50
+ } catch (err) {
51
+ if (timedOut) return timeoutFailure(url, opts.timeoutMs);
52
+ if (err instanceof SyntaxError) {
53
+ return {
54
+ ok: false,
55
+ kind: "invalid-json",
56
+ status: res.status,
57
+ ...requestId !== void 0 ? { requestId } : {},
58
+ message: `Response from ${url} was not valid JSON`
59
+ };
60
+ }
61
+ return {
62
+ ok: false,
63
+ kind: "network",
64
+ status: res.status,
65
+ ...requestId !== void 0 ? { requestId } : {},
66
+ message: `Request to ${url} failed while reading the response body: ${errorMessage(err)}`
67
+ };
68
+ }
69
+ return {
70
+ ok: true,
71
+ status: res.status,
72
+ json,
73
+ ...requestId !== void 0 ? { requestId } : {}
74
+ };
75
+ } finally {
76
+ clearTimeout(timer);
77
+ }
78
+ }
79
+ function timeoutFailure(url, timeoutMs) {
80
+ return {
81
+ ok: false,
82
+ kind: "timeout",
83
+ message: `Request to ${url} timed out after ${timeoutMs}ms`
84
+ };
85
+ }
86
+ function errorMessage(err) {
87
+ return err instanceof Error ? err.message : String(err);
88
+ }
89
+
90
+ // src/commands/doctor.ts
91
+ async function runDoctor(ctx, deps = {}) {
92
+ const env = deps.env ?? process.env;
93
+ const { config, check: configCheck } = await loadConfigForDoctor(ctx.dataDir);
94
+ const settings = resolveSettings({ config, flags: { baseUrl: ctx.flags.baseUrl }, env });
95
+ const baseUrl = settings.baseUrl.value;
96
+ const built = [
97
+ checkNode(),
98
+ configCheck,
99
+ await checkApiContract(baseUrl, ctx.flags.timeout, deps.fetchImpl),
100
+ await checkReadPath(baseUrl, ctx.flags.timeout, deps.fetchImpl)
101
+ ];
102
+ for (const result of await checkWallet(ctx, deps, env, settings.rpcUrl.value)) {
103
+ built.push({ result });
104
+ }
105
+ const checks = built.map((b) => b.result);
106
+ const firstFail = built.find((b) => b.result.required && b.result.status === "fail");
107
+ if (firstFail !== void 0) {
108
+ const r = firstFail.result;
109
+ throw new CliError(firstFail.failCode ?? "INTERNAL", r.detail, {
110
+ ...r.fix !== void 0 ? { fix: r.fix } : {},
111
+ details: { checks }
112
+ });
113
+ }
114
+ return { data: { status: "pass", checks }, humanLines: renderHuman(ctx.io, checks) };
115
+ }
116
+ function checkNode() {
117
+ const version = process.versions.node;
118
+ const major = Number.parseInt(version.split(".")[0] ?? "0", 10);
119
+ if (major >= 22) {
120
+ return { result: { name: "node", status: "ok", required: true, detail: `Node ${version}` } };
121
+ }
122
+ return {
123
+ result: {
124
+ name: "node",
125
+ status: "fail",
126
+ required: true,
127
+ detail: `Node ${version} is unsupported (need >= 22)`,
128
+ fix: "Install Node 22 or newer"
129
+ },
130
+ failCode: "NODE_UNSUPPORTED"
131
+ };
132
+ }
133
+ async function loadConfigForDoctor(dataDir) {
134
+ try {
135
+ const config = await loadRawConfig(dataDir);
136
+ const detail = Object.keys(config).length === 0 ? "No config file; using defaults" : `Config at ${configPath(dataDir)} is valid`;
137
+ return { config, check: { result: { name: "config", status: "ok", required: true, detail } } };
138
+ } catch (err) {
139
+ if (err instanceof CliError && err.code === "CONFIG_INVALID") {
140
+ return {
141
+ config: {},
142
+ check: {
143
+ result: {
144
+ name: "config",
145
+ status: "fail",
146
+ required: true,
147
+ detail: err.message,
148
+ ...err.fix !== void 0 ? { fix: err.fix } : {}
149
+ },
150
+ failCode: "CONFIG_INVALID"
151
+ }
152
+ };
153
+ }
154
+ throw err;
155
+ }
156
+ }
157
+ async function checkApiContract(baseUrl, timeoutMs, fetchImpl) {
158
+ const url = `${trimSlash(baseUrl)}/openapi.json`;
159
+ const res = await fetchJson(url, { timeoutMs, fetchImpl });
160
+ if (!res.ok) {
161
+ const malformed = res.kind === "invalid-json";
162
+ return {
163
+ result: {
164
+ name: "api-contract",
165
+ status: "fail",
166
+ required: true,
167
+ detail: malformed ? `OpenAPI document at ${url} was not valid JSON` : `Could not reach the Tenjin API at ${url}: ${res.message}`,
168
+ fix: malformed ? "Point --base-url at a Tenjin API (expected an OpenAPI document)." : "Check your network connection and --base-url."
169
+ },
170
+ failCode: malformed ? "CONTRACT_MISMATCH" : "API_UNREACHABLE"
171
+ };
172
+ }
173
+ const version = infoVersion(res.json);
174
+ if (version === void 0) {
175
+ return {
176
+ result: {
177
+ name: "api-contract",
178
+ status: "fail",
179
+ required: true,
180
+ detail: `OpenAPI document at ${url} is missing a string info.version`,
181
+ fix: "Point --base-url at a Tenjin API (expected an OpenAPI document)."
182
+ },
183
+ failCode: "CONTRACT_MISMATCH"
184
+ };
185
+ }
186
+ return {
187
+ result: {
188
+ name: "api-contract",
189
+ status: "ok",
190
+ required: true,
191
+ detail: `Tenjin API ${version} at ${baseUrl}`
192
+ }
193
+ };
194
+ }
195
+ async function checkReadPath(baseUrl, timeoutMs, fetchImpl) {
196
+ const url = `${trimSlash(baseUrl)}/api/articles?limit=1`;
197
+ const res = await fetchJson(url, { timeoutMs, fetchImpl });
198
+ if (!res.ok) {
199
+ return {
200
+ result: {
201
+ name: "read-path",
202
+ status: "fail",
203
+ required: true,
204
+ detail: `Read path ${url} failed: ${res.message}`,
205
+ fix: "Check your network connection and --base-url."
206
+ },
207
+ failCode: "API_UNREACHABLE"
208
+ };
209
+ }
210
+ const items = isRecord(res.json) ? res.json.items : void 0;
211
+ if (!Array.isArray(items)) {
212
+ return {
213
+ result: {
214
+ name: "read-path",
215
+ status: "fail",
216
+ required: true,
217
+ detail: `Read path ${url} did not return an items array`,
218
+ fix: "Point --base-url at a Tenjin API."
219
+ },
220
+ failCode: "API_UNREACHABLE"
221
+ };
222
+ }
223
+ return {
224
+ result: { name: "read-path", status: "ok", required: true, detail: `Read path OK at ${url}` }
225
+ };
226
+ }
227
+ async function checkWallet(ctx, deps, env, rpcUrl) {
228
+ const provider = deps.provider ?? await resolveLocalProviderOrNull(ctx, env);
229
+ if (provider === null) return [noWalletCheck()];
230
+ const { describeWallet } = await import("./wallet-QUWK2PA4.js");
231
+ let desc;
232
+ try {
233
+ desc = await describeWallet(provider);
234
+ } catch (err) {
235
+ if (err instanceof CliError && err.code === "WALLET_MISSING") return [noWalletCheck()];
236
+ return [walletWarn(err)];
237
+ }
238
+ const checks = [
239
+ {
240
+ name: "wallet",
241
+ status: "ok",
242
+ required: false,
243
+ detail: `Wallet ${desc.address} (${desc.credentialSource})`
244
+ }
245
+ ];
246
+ for (const warning of (await provider.diagnostics()).warnings) {
247
+ checks.push({ name: "wallet-custody", status: "warn", required: false, detail: warning });
248
+ }
249
+ checks.push(await checkBalance(desc.address, rpcUrl));
250
+ return checks;
251
+ }
252
+ async function resolveLocalProviderOrNull(ctx, env) {
253
+ const envKey = env.TENJIN_WALLET_KEY;
254
+ const envKeySet = typeof envKey === "string" && envKey.length > 0;
255
+ if (!envKeySet && !await walletFileExists(ctx.dataDir)) return null;
256
+ const { resolveWalletProvider } = await import("./wallet-QUWK2PA4.js");
257
+ return resolveWalletProvider(ctx);
258
+ }
259
+ function noWalletCheck() {
260
+ return {
261
+ name: "wallet",
262
+ status: "warn",
263
+ required: false,
264
+ detail: "No wallet; needed only for buy/publish",
265
+ fix: "tenjin wallet create"
266
+ };
267
+ }
268
+ function walletWarn(err) {
269
+ return {
270
+ name: "wallet",
271
+ status: "warn",
272
+ required: false,
273
+ detail: err instanceof Error ? err.message : String(err),
274
+ ...err instanceof CliError && err.fix !== void 0 ? { fix: err.fix } : {}
275
+ };
276
+ }
277
+ async function checkBalance(address, rpcUrl) {
278
+ try {
279
+ const { getUsdcBalance } = await import("./usdc-OQUOFF6R.js");
280
+ const balance = await getUsdcBalance(address, rpcUrl);
281
+ if (balance === 0n) {
282
+ return {
283
+ name: "balance",
284
+ status: "warn",
285
+ required: false,
286
+ detail: "Wallet USDC balance is 0",
287
+ fix: "Send USDC on Base. $5 covers ~50 typical resources."
288
+ };
289
+ }
290
+ const money = toMoney(balance.toString());
291
+ return {
292
+ name: "balance",
293
+ status: "ok",
294
+ required: false,
295
+ detail: `Balance ${money.usd} USDC (${money.atomic} atomic)`
296
+ };
297
+ } catch (err) {
298
+ return {
299
+ name: "balance",
300
+ status: "warn",
301
+ required: false,
302
+ detail: `Could not read balance: ${err instanceof Error ? err.message : String(err)}`,
303
+ fix: "Check rpcUrl or retry; a balance read failure never fails doctor."
304
+ };
305
+ }
306
+ }
307
+ function renderHuman(io, checks) {
308
+ const nameWidth = Math.max(...checks.map((c) => c.name.length));
309
+ const lines = [];
310
+ for (const c of checks) {
311
+ const icon = c.status === "ok" ? paint(io, "green", "\u2713") : c.status === "warn" ? paint(io, "yellow", "!") : paint(io, "red", "\u2717");
312
+ lines.push(`${icon} ${c.name.padEnd(nameWidth)} ${paint(io, "dim", c.detail)}`);
313
+ if (c.status !== "ok" && c.fix !== void 0) {
314
+ lines.push(` ${paint(io, "dim", `fix: ${c.fix}`)}`);
315
+ }
316
+ }
317
+ return lines;
318
+ }
319
+ function paint(io, format, text) {
320
+ if (io.stderr instanceof Stream) return styleText(format, text, { stream: io.stderr });
321
+ return styleText(format, text);
322
+ }
323
+ function infoVersion(json) {
324
+ if (!isRecord(json)) return void 0;
325
+ const info = json.info;
326
+ if (!isRecord(info)) return void 0;
327
+ return typeof info.version === "string" ? info.version : void 0;
328
+ }
329
+ function isRecord(value) {
330
+ return typeof value === "object" && value !== null;
331
+ }
332
+ function trimSlash(url) {
333
+ return url.replace(/\/+$/, "");
334
+ }
335
+ export {
336
+ runDoctor
337
+ };
338
+ //# sourceMappingURL=doctor-MVLZRVPU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor.ts","../src/lib/http.ts"],"sourcesContent":["import { styleText } from 'node:util';\nimport { Stream } from 'node:stream';\nimport { CliError } from '../lib/errors';\nimport { fetchJson } from '../lib/http';\nimport { loadRawConfig, resolveSettings } from '../lib/config';\nimport { configPath } from '../lib/paths';\nimport { toMoney } from '../lib/money';\nimport { walletFileExists } from '../lib/wallet/store';\nimport type { PartialConfig } from '../lib/config';\nimport type { ErrorCode } from '../schemas';\nimport type { Io } from '../lib/output';\nimport type { WalletDescription, WalletProvider } from '../lib/wallet';\nimport type { CommandContext, CommandResult } from '../context';\n\n/**\n * One environment/reachability check. The doctor agent builds the check list\n * against this shape without changing it: `required` drives the exit code (exit\n * 0 iff every required check is ok), `status` drives the TTY rendering, and a\n * `fix` is mandatory on every failure (spec 10). Warn-level checks never fail\n * the command.\n */\nexport interface CheckResult {\n name: string;\n status: 'ok' | 'warn' | 'fail';\n required: boolean;\n detail: string;\n fix?: string;\n}\n\n/**\n * A CheckResult plus the error code to raise if it is a *required* failure. Only\n * required checks carry a `failCode`; the outcome step raises the first one, so\n * the failure envelope's `error.code` names what actually broke (api-contract\n * unreachable vs malformed differ) while still carrying the whole check list.\n */\ninterface BuiltCheck {\n result: CheckResult;\n failCode?: ErrorCode;\n}\n\nexport interface DoctorDeps {\n /** Environment for wallet-key detection and settings precedence. */\n env?: NodeJS.ProcessEnv;\n /** Injected fetch for the reachability checks; tests pass a canned stub. */\n fetchImpl?: typeof fetch;\n /** Inject the active wallet provider. When set, NO local fs/env is consulted —\n * the provider owns its own describe() and diagnostics(), so a remote provider's\n * checks can't be contaminated by a stale local wallet file. */\n provider?: WalletProvider;\n}\n\nexport async function runDoctor(\n ctx: CommandContext,\n deps: DoctorDeps = {},\n): Promise<CommandResult> {\n const env = deps.env ?? process.env;\n const { config, check: configCheck } = await loadConfigForDoctor(ctx.dataDir);\n const settings = resolveSettings({ config, flags: { baseUrl: ctx.flags.baseUrl }, env });\n const baseUrl = settings.baseUrl.value;\n\n const built: BuiltCheck[] = [\n checkNode(),\n configCheck,\n await checkApiContract(baseUrl, ctx.flags.timeout, deps.fetchImpl),\n await checkReadPath(baseUrl, ctx.flags.timeout, deps.fetchImpl),\n ];\n\n // The wallet/custody/balance checks all come from the ACTIVE provider: it owns\n // describe() and diagnostics(), so doctor never runs its own fs/env probe.\n for (const result of await checkWallet(ctx, deps, env, settings.rpcUrl.value)) {\n built.push({ result });\n }\n\n const checks = built.map((b) => b.result);\n const firstFail = built.find((b) => b.result.required && b.result.status === 'fail');\n if (firstFail !== undefined) {\n const r = firstFail.result;\n throw new CliError(firstFail.failCode ?? 'INTERNAL', r.detail, {\n ...(r.fix !== undefined ? { fix: r.fix } : {}),\n details: { checks },\n });\n }\n\n return { data: { status: 'pass', checks }, humanLines: renderHuman(ctx.io, checks) };\n}\n\nfunction checkNode(): BuiltCheck {\n const version = process.versions.node;\n const major = Number.parseInt(version.split('.')[0] ?? '0', 10);\n if (major >= 22) {\n return { result: { name: 'node', status: 'ok', required: true, detail: `Node ${version}` } };\n }\n return {\n result: {\n name: 'node',\n status: 'fail',\n required: true,\n detail: `Node ${version} is unsupported (need >= 22)`,\n fix: 'Install Node 22 or newer',\n },\n failCode: 'NODE_UNSUPPORTED',\n };\n}\n\n/**\n * loadRawConfig throws CONFIG_INVALID on a bad file (which we convert into a\n * failing check) but the config value is also needed for baseUrl/rpcUrl\n * resolution; an invalid file falls back to {} so the reachability checks still\n * run and appear in the list, and config is reported as the first required fail.\n */\nasync function loadConfigForDoctor(\n dataDir: string,\n): Promise<{ config: PartialConfig; check: BuiltCheck }> {\n try {\n const config = await loadRawConfig(dataDir);\n const detail =\n Object.keys(config).length === 0\n ? 'No config file; using defaults'\n : `Config at ${configPath(dataDir)} is valid`;\n return { config, check: { result: { name: 'config', status: 'ok', required: true, detail } } };\n } catch (err) {\n if (err instanceof CliError && err.code === 'CONFIG_INVALID') {\n return {\n config: {},\n check: {\n result: {\n name: 'config',\n status: 'fail',\n required: true,\n detail: err.message,\n ...(err.fix !== undefined ? { fix: err.fix } : {}),\n },\n failCode: 'CONFIG_INVALID',\n },\n };\n }\n throw err;\n }\n}\n\nasync function checkApiContract(\n baseUrl: string,\n timeoutMs: number,\n fetchImpl?: typeof fetch,\n): Promise<BuiltCheck> {\n const url = `${trimSlash(baseUrl)}/openapi.json`;\n const res = await fetchJson(url, { timeoutMs, fetchImpl });\n if (!res.ok) {\n const malformed = res.kind === 'invalid-json';\n return {\n result: {\n name: 'api-contract',\n status: 'fail',\n required: true,\n detail: malformed\n ? `OpenAPI document at ${url} was not valid JSON`\n : `Could not reach the Tenjin API at ${url}: ${res.message}`,\n fix: malformed\n ? 'Point --base-url at a Tenjin API (expected an OpenAPI document).'\n : 'Check your network connection and --base-url.',\n },\n failCode: malformed ? 'CONTRACT_MISMATCH' : 'API_UNREACHABLE',\n };\n }\n const version = infoVersion(res.json);\n if (version === undefined) {\n return {\n result: {\n name: 'api-contract',\n status: 'fail',\n required: true,\n detail: `OpenAPI document at ${url} is missing a string info.version`,\n fix: 'Point --base-url at a Tenjin API (expected an OpenAPI document).',\n },\n failCode: 'CONTRACT_MISMATCH',\n };\n }\n return {\n result: {\n name: 'api-contract',\n status: 'ok',\n required: true,\n detail: `Tenjin API ${version} at ${baseUrl}`,\n },\n };\n}\n\nasync function checkReadPath(\n baseUrl: string,\n timeoutMs: number,\n fetchImpl?: typeof fetch,\n): Promise<BuiltCheck> {\n // The shipped public read path. The A2 lookup-contract check is a B2 follow-up.\n // Probe the UNFILTERED listing: the server logs every nonblank first-page `q`\n // as agent search demand, so a `q` here would fabricate that demand into the\n // experiment this CLI exists to measure. Never add a `q` to this probe.\n const url = `${trimSlash(baseUrl)}/api/articles?limit=1`;\n const res = await fetchJson(url, { timeoutMs, fetchImpl });\n if (!res.ok) {\n return {\n result: {\n name: 'read-path',\n status: 'fail',\n required: true,\n detail: `Read path ${url} failed: ${res.message}`,\n fix: 'Check your network connection and --base-url.',\n },\n failCode: 'API_UNREACHABLE',\n };\n }\n const items = isRecord(res.json) ? res.json.items : undefined;\n if (!Array.isArray(items)) {\n return {\n result: {\n name: 'read-path',\n status: 'fail',\n required: true,\n detail: `Read path ${url} did not return an items array`,\n fix: 'Point --base-url at a Tenjin API.',\n },\n failCode: 'API_UNREACHABLE',\n };\n }\n return {\n result: { name: 'read-path', status: 'ok', required: true, detail: `Read path OK at ${url}` },\n };\n}\n\n/**\n * Diagnose the wallet the CLI would actually use, entirely through the ACTIVE\n * provider. An injected provider owns everything — no local file or env is touched.\n * With no injected provider we do ONE cheap fs/env probe purely to decide whether\n * any credential exists: none → emit the \"no wallet\" warn WITHOUT importing the\n * wallet lib (that import statically pulls viem, and a no-wallet run must not parse\n * it). Otherwise the provider describes itself (address + source), reports its own\n * custody warnings, and the balance probes describe()'s address. A custody problem\n * (bad key, provider refusal) is warn-level, never a hard fail.\n */\nasync function checkWallet(\n ctx: CommandContext,\n deps: DoctorDeps,\n env: NodeJS.ProcessEnv,\n rpcUrl: string,\n): Promise<CheckResult[]> {\n const provider = deps.provider ?? (await resolveLocalProviderOrNull(ctx, env));\n if (provider === null) return [noWalletCheck()];\n\n const { describeWallet } = await import('../lib/wallet');\n let desc: WalletDescription;\n try {\n desc = await describeWallet(provider);\n } catch (err) {\n if (err instanceof CliError && err.code === 'WALLET_MISSING') return [noWalletCheck()];\n return [walletWarn(err)];\n }\n\n const checks: CheckResult[] = [\n {\n name: 'wallet',\n status: 'ok',\n required: false,\n detail: `Wallet ${desc.address} (${desc.credentialSource})`,\n },\n ];\n // Custody warnings are the provider's own (perms, env-shadow for the local\n // provider; none for a remote one). Render each as a warn check; the fix text,\n // when there is one, is carried inline in the warning string.\n for (const warning of (await provider.diagnostics()).warnings) {\n checks.push({ name: 'wallet-custody', status: 'warn', required: false, detail: warning });\n }\n checks.push(await checkBalance(desc.address, rpcUrl));\n return checks;\n}\n\n/**\n * The active local provider, or null when no credential exists at all. The null\n * path never imports the wallet lib, keeping a no-wallet doctor run off viem.\n */\nasync function resolveLocalProviderOrNull(\n ctx: CommandContext,\n env: NodeJS.ProcessEnv,\n): Promise<WalletProvider | null> {\n const envKey = env.TENJIN_WALLET_KEY;\n const envKeySet = typeof envKey === 'string' && envKey.length > 0;\n if (!envKeySet && !(await walletFileExists(ctx.dataDir))) return null;\n const { resolveWalletProvider } = await import('../lib/wallet');\n return resolveWalletProvider(ctx);\n}\n\nfunction noWalletCheck(): CheckResult {\n return {\n name: 'wallet',\n status: 'warn',\n required: false,\n detail: 'No wallet; needed only for buy/publish',\n fix: 'tenjin wallet create',\n };\n}\n\nfunction walletWarn(err: unknown): CheckResult {\n return {\n name: 'wallet',\n status: 'warn',\n required: false,\n detail: err instanceof Error ? err.message : String(err),\n ...(err instanceof CliError && err.fix !== undefined ? { fix: err.fix } : {}),\n };\n}\n\n/**\n * Balance is best-effort: a zero balance is a fundable warning and an RPC flake\n * must never fail doctor. viem loads only here, via a lazy import, so a doctor\n * run without a wallet never parses the viem chunk.\n */\nasync function checkBalance(address: string, rpcUrl: string): Promise<CheckResult> {\n try {\n const { getUsdcBalance } = await import('../lib/usdc');\n const balance = await getUsdcBalance(address as `0x${string}`, rpcUrl);\n if (balance === 0n) {\n return {\n name: 'balance',\n status: 'warn',\n required: false,\n detail: 'Wallet USDC balance is 0',\n fix: 'Send USDC on Base. $5 covers ~50 typical resources.',\n };\n }\n const money = toMoney(balance.toString());\n return {\n name: 'balance',\n status: 'ok',\n required: false,\n detail: `Balance ${money.usd} USDC (${money.atomic} atomic)`,\n };\n } catch (err) {\n return {\n name: 'balance',\n status: 'warn',\n required: false,\n detail: `Could not read balance: ${err instanceof Error ? err.message : String(err)}`,\n fix: 'Check rpcUrl or retry; a balance read failure never fails doctor.',\n };\n }\n}\n\nfunction renderHuman(io: Io, checks: CheckResult[]): string[] {\n const nameWidth = Math.max(...checks.map((c) => c.name.length));\n const lines: string[] = [];\n for (const c of checks) {\n const icon =\n c.status === 'ok'\n ? paint(io, 'green', '✓')\n : c.status === 'warn'\n ? paint(io, 'yellow', '!')\n : paint(io, 'red', '✗');\n lines.push(`${icon} ${c.name.padEnd(nameWidth)} ${paint(io, 'dim', c.detail)}`);\n if (c.status !== 'ok' && c.fix !== undefined) {\n lines.push(` ${paint(io, 'dim', `fix: ${c.fix}`)}`);\n }\n }\n return lines;\n}\n\n/**\n * Color for stderr, honoring NO_COLOR and the target's color depth: styleText\n * takes the stderr stream when it is a genuine Stream (it throws on anything\n * else, so a test/redirected sink falls back to the default plain check).\n */\nfunction paint(io: Io, format: Parameters<typeof styleText>[0], text: string): string {\n if (io.stderr instanceof Stream) return styleText(format, text, { stream: io.stderr });\n return styleText(format, text);\n}\n\nfunction infoVersion(json: unknown): string | undefined {\n if (!isRecord(json)) return undefined;\n const info = json.info;\n if (!isRecord(info)) return undefined;\n return typeof info.version === 'string' ? info.version : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction trimSlash(url: string): string {\n return url.replace(/\\/+$/, '');\n}\n","import { CliError } from './errors';\n\n/**\n * The single JSON client the whole CLI grows on: `doctor` uses it in B1, `lookup`\n * reuses it in B2. It never throws on a transport/protocol failure — it returns a\n * discriminated result so callers decide how each failure class maps to a check\n * or an error. `fetchImpl` is injectable so tests never touch the network.\n */\nexport interface FetchJsonOptions {\n /** Abort the request after this many milliseconds. */\n timeoutMs: number;\n /** Override global fetch (tests inject a stub returning canned Responses). */\n fetchImpl?: typeof fetch;\n}\n\n/** A successful 2xx whose body parsed as JSON. */\nexport interface FetchJsonSuccess {\n ok: true;\n status: number;\n json: unknown;\n /** The server's `x-request-id`, when it sent one — useful in error reports. */\n requestId?: string;\n}\n\n/**\n * A failed request, tagged by class so the caller maps it deliberately:\n * `network`/`timeout` (never reached the server or no answer), `http` (a non-2xx\n * status), `invalid-json` (2xx but the body was not JSON).\n */\nexport interface FetchJsonFailure {\n ok: false;\n kind: 'network' | 'timeout' | 'http' | 'invalid-json';\n status?: number;\n requestId?: string;\n message: string;\n}\n\nexport type FetchJsonResult = FetchJsonSuccess | FetchJsonFailure;\n\nexport async function fetchJson(url: string, opts: FetchJsonOptions): Promise<FetchJsonResult> {\n const doFetch = opts.fetchImpl ?? fetch;\n const controller = new AbortController();\n let timedOut = false;\n // The timer stays armed until the body is fully consumed (cleared in the outer\n // finally), so a server that sends headers then stalls the body still trips the\n // deadline instead of hanging forever on res.json().\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, opts.timeoutMs);\n\n try {\n let res: Response;\n try {\n res = await doFetch(url, { signal: controller.signal });\n } catch (err) {\n // A timeout is a network failure the AbortController induced; distinguish it\n // from an organic one so the caller can say \"timed out\" rather than a raw\n // AbortError message.\n return timedOut\n ? timeoutFailure(url, opts.timeoutMs)\n : { ok: false, kind: 'network', message: `Request to ${url} failed: ${errorMessage(err)}` };\n }\n\n const requestId = res.headers.get('x-request-id') ?? undefined;\n\n if (!res.ok) {\n return {\n ok: false,\n kind: 'http',\n status: res.status,\n ...(requestId !== undefined ? { requestId } : {}),\n message: `Request to ${url} failed with status ${res.status}`,\n };\n }\n\n let json: unknown;\n try {\n json = await res.json();\n } catch (err) {\n // The body read can fail three ways. A timeout wins first: the abort fires\n // synchronously with `timedOut`, so any abort-induced rejection here is our\n // deadline. Otherwise a SyntaxError is a genuine parse failure (invalid\n // JSON); anything else is a transport failure mid-body (network).\n if (timedOut) return timeoutFailure(url, opts.timeoutMs);\n if (err instanceof SyntaxError) {\n return {\n ok: false,\n kind: 'invalid-json',\n status: res.status,\n ...(requestId !== undefined ? { requestId } : {}),\n message: `Response from ${url} was not valid JSON`,\n };\n }\n return {\n ok: false,\n kind: 'network',\n status: res.status,\n ...(requestId !== undefined ? { requestId } : {}),\n message: `Request to ${url} failed while reading the response body: ${errorMessage(err)}`,\n };\n }\n\n return {\n ok: true,\n status: res.status,\n json,\n ...(requestId !== undefined ? { requestId } : {}),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction timeoutFailure(url: string, timeoutMs: number): FetchJsonFailure {\n return {\n ok: false,\n kind: 'timeout',\n message: `Request to ${url} timed out after ${timeoutMs}ms`,\n };\n}\n\nexport interface FailureToCliErrorOptions {\n fix?: string;\n details?: unknown;\n}\n\n/**\n * Normalize a fetch failure into the CLI error contract: network/timeout are a\n * NETWORK_ERROR, an `http` status (server reachable but errored — commonly 5xx)\n * is API_UNREACHABLE, and a 2xx that failed to parse when JSON was required is a\n * CONTRACT_MISMATCH. Callers that need a check-specific code (doctor) build their\n * own CliError; this is the default B2's passthrough leans on.\n */\nexport function fetchFailureToCliError(\n failure: FetchJsonFailure,\n opts: FailureToCliErrorOptions = {},\n): CliError {\n const code =\n failure.kind === 'network' || failure.kind === 'timeout'\n ? 'NETWORK_ERROR'\n : failure.kind === 'invalid-json'\n ? 'CONTRACT_MISMATCH'\n : 'API_UNREACHABLE';\n return new CliError(code, failure.message, {\n ...(opts.fix !== undefined ? { fix: opts.fix } : {}),\n ...(opts.details !== undefined ? { details: opts.details } : {}),\n });\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n"],"mappings":"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","names":[]}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __tenjinCreateRequire } from 'node:module'; const require = __tenjinCreateRequire(import.meta.url);
3
+
4
+ // src/index.ts
5
+ var MIN_NODE_MAJOR = 22;
6
+ var nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
7
+ if (Number.isNaN(nodeMajor) || nodeMajor < MIN_NODE_MAJOR) {
8
+ const envelope = {
9
+ schemaVersion: 1,
10
+ command: "preflight",
11
+ ok: false,
12
+ error: {
13
+ code: "NODE_UNSUPPORTED",
14
+ message: `Tenjin CLI requires Node ${MIN_NODE_MAJOR} or newer (found ${process.versions.node}).`,
15
+ fix: "Install Node 22 or newer"
16
+ }
17
+ };
18
+ process.stdout.write(`${JSON.stringify(envelope, null, 2)}
19
+ `);
20
+ process.stderr.write(`error: Node ${MIN_NODE_MAJOR}+ required, found ${process.versions.node}
21
+ `);
22
+ process.exit(1);
23
+ }
24
+ var { main } = await import("./cli-LMHGCZ5S.js");
25
+ process.exit(await main(process.argv.slice(2)));
26
+ //# sourceMappingURL=index.js.map