tenjin-cli 0.1.0-alpha.1 → 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +33 -0
- package/README.md +167 -16
- package/dist/{_esm-ZH2J4QEH.js → _esm-C5CRWCYU.js} +2 -2
- package/dist/_esm-FEASBWRP.js +881 -0
- package/dist/buy-EE6SEXP6.js +1598 -0
- package/dist/buy-EE6SEXP6.js.map +1 -0
- package/dist/candidate-ERMSYYQZ.js +127 -0
- package/dist/candidate-ERMSYYQZ.js.map +1 -0
- package/dist/{ccip-UCLT5VOW.js → ccip-NQXCEK2U.js} +6 -5
- package/dist/ccip-XR5NE5QK.js +19 -0
- package/dist/chunk-2I4QTOLB.js +11 -0
- package/dist/chunk-2I4QTOLB.js.map +1 -0
- package/dist/chunk-3OV7E5G5.js +18 -0
- package/dist/chunk-3OV7E5G5.js.map +1 -0
- package/dist/chunk-4ONXEEQL.js +152 -0
- package/dist/chunk-4ONXEEQL.js.map +1 -0
- package/dist/chunk-5CLSD5YN.js +209 -0
- package/dist/chunk-5CLSD5YN.js.map +1 -0
- package/dist/chunk-662MSGZB.js +286 -0
- package/dist/chunk-662MSGZB.js.map +1 -0
- package/dist/chunk-7KUW4MJD.js +59 -0
- package/dist/chunk-7KUW4MJD.js.map +1 -0
- package/dist/{doctor-3QBYN46N.js → chunk-AWPAWHNX.js} +88 -93
- package/dist/chunk-AWPAWHNX.js.map +1 -0
- package/dist/chunk-CYOF7ROH.js +70 -0
- package/dist/chunk-CYOF7ROH.js.map +1 -0
- package/dist/chunk-DXTQ3CSI.js +171 -0
- package/dist/chunk-DXTQ3CSI.js.map +1 -0
- package/dist/{chunk-EZROUI5U.js → chunk-ESPNTPQ2.js} +80 -8
- package/dist/chunk-ESPNTPQ2.js.map +1 -0
- package/dist/chunk-FJPLRISB.js +79 -0
- package/dist/chunk-FJPLRISB.js.map +1 -0
- package/dist/chunk-GO75V3NZ.js +160 -0
- package/dist/chunk-GO75V3NZ.js.map +1 -0
- package/dist/chunk-HIVZG55D.js +204 -0
- package/dist/chunk-HIVZG55D.js.map +1 -0
- package/dist/chunk-HSDCI7OV.js +7806 -0
- package/dist/chunk-HSDCI7OV.js.map +1 -0
- package/dist/chunk-JPHG2JW3.js +96 -0
- package/dist/chunk-JPHG2JW3.js.map +1 -0
- package/dist/{chunk-ZPZFPVDN.js → chunk-MUBUQVC3.js} +1 -1
- package/dist/chunk-MUBUQVC3.js.map +1 -0
- package/dist/{chunk-T4LKNY7T.js → chunk-NCE5NSN6.js} +166 -11
- package/dist/chunk-NCE5NSN6.js.map +1 -0
- package/dist/{chunk-YG2DO46P.js → chunk-OYMYCW7J.js} +18 -3
- package/dist/{chunk-YG2DO46P.js.map → chunk-OYMYCW7J.js.map} +1 -1
- package/dist/{chunk-Q53IYV2G.js → chunk-QVX5MWJZ.js} +20 -17
- package/dist/{chunk-Q53IYV2G.js.map → chunk-QVX5MWJZ.js.map} +1 -1
- package/dist/{chunk-CUWUHTIL.js → chunk-RBJPAZFI.js} +16 -202
- package/dist/chunk-RBJPAZFI.js.map +1 -0
- package/dist/chunk-RRN3YTK7.js +6199 -0
- package/dist/chunk-RRN3YTK7.js.map +1 -0
- package/dist/chunk-RY4XKNV3.js +140 -0
- package/dist/chunk-RY4XKNV3.js.map +1 -0
- package/dist/chunk-S3IGX57D.js +14682 -0
- package/dist/chunk-S3IGX57D.js.map +1 -0
- package/dist/chunk-TLCMMYLY.js +126 -0
- package/dist/chunk-TLCMMYLY.js.map +1 -0
- package/dist/{chunk-IPOWB3G3.js → chunk-UGX7YPX2.js} +3314 -306
- package/dist/chunk-UGX7YPX2.js.map +1 -0
- package/dist/chunk-UO22YNY4.js +69 -0
- package/dist/chunk-UO22YNY4.js.map +1 -0
- package/dist/{config-CEFTL5ZL.js → chunk-XHC6FCGZ.js} +115 -18
- package/dist/chunk-XHC6FCGZ.js.map +1 -0
- package/dist/chunk-XJIW7USF.js +217 -0
- package/dist/chunk-XJIW7USF.js.map +1 -0
- package/dist/{chunk-BRJHUQSY.js → chunk-YALGASEU.js} +1548 -1452
- package/dist/chunk-YALGASEU.js.map +1 -0
- package/dist/{chunk-DXPYFVKG.js → chunk-YN5SHIXQ.js} +6 -6
- package/dist/chunk-YN5SHIXQ.js.map +1 -0
- package/dist/chunk-Z52RA3NC.js +137 -0
- package/dist/chunk-Z52RA3NC.js.map +1 -0
- package/dist/{chunk-EXBMF5X7.js → chunk-Z6MFJV5Z.js} +151 -19
- package/dist/chunk-Z6MFJV5Z.js.map +1 -0
- package/dist/chunk-ZABPQJ3M.js +83 -0
- package/dist/chunk-ZABPQJ3M.js.map +1 -0
- package/dist/{chunk-LPRVHM7P.js → chunk-ZMHYXMO6.js} +1655 -114
- package/dist/chunk-ZMHYXMO6.js.map +1 -0
- package/dist/{chunk-3IMLRUZE.js → chunk-ZXTAQYNA.js} +6 -2
- package/dist/{cli-WA65NP4Y.js → cli-SBXI5AMS.js} +177 -177
- package/dist/cli-SBXI5AMS.js.map +1 -0
- package/dist/config-VELDKLHY.js +21 -0
- package/dist/doctor-T3KWNNP2.js +20 -0
- package/dist/doctor-T3KWNNP2.js.map +1 -0
- package/dist/ethersCompat-Df4a6QIZ-3YZIAXDK.js +18 -0
- package/dist/ethersCompat-Df4a6QIZ-3YZIAXDK.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspect-LMKOXE4Y.js +96 -0
- package/dist/inspect-LMKOXE4Y.js.map +1 -0
- package/dist/install-LEX3ENQ6.js +387 -0
- package/dist/install-LEX3ENQ6.js.map +1 -0
- package/dist/lookup-ZPN2Q67N.js +129 -0
- package/dist/lookup-ZPN2Q67N.js.map +1 -0
- package/dist/{mine.wasm-GQECMSGN.js → mine.wasm-6PIT3DK6.js} +2 -2
- package/dist/outcome-N7TAK6WR.js +66 -0
- package/dist/outcome-N7TAK6WR.js.map +1 -0
- package/dist/publish-BKBQQAW4.js +1478 -0
- package/dist/publish-BKBQQAW4.js.map +1 -0
- package/dist/{secp256k1-BEZS642W.js → secp256k1-N3ZHU7LB.js} +5 -4
- package/dist/secp256k1-N3ZHU7LB.js.map +1 -0
- package/dist/usdc-JIAJW4SV.js +22 -0
- package/dist/usdc-JIAJW4SV.js.map +1 -0
- package/dist/viemAdapter-By13KInL-ZZE6UV4Y.js +14 -0
- package/dist/viemAdapter-By13KInL-ZZE6UV4Y.js.map +1 -0
- package/dist/{wallet-XD576HF3.js → wallet-CWEMYVIH.js} +28 -22
- package/dist/{wallet-XD576HF3.js.map → wallet-CWEMYVIH.js.map} +1 -1
- package/dist/wallet-PA54CRBL.js +30 -0
- package/dist/wallet-PA54CRBL.js.map +1 -0
- package/package.json +19 -16
- package/skills/tenjin/SKILL.md +199 -0
- package/skills/tenjin-publish/SKILL.md +111 -0
- package/skills/tenjin-search/SKILL.md +133 -0
- package/dist/chunk-BRJHUQSY.js.map +0 -1
- package/dist/chunk-CUWUHTIL.js.map +0 -1
- package/dist/chunk-DXPYFVKG.js.map +0 -1
- package/dist/chunk-EXBMF5X7.js.map +0 -1
- package/dist/chunk-EZROUI5U.js.map +0 -1
- package/dist/chunk-IPOWB3G3.js.map +0 -1
- package/dist/chunk-LPRVHM7P.js.map +0 -1
- package/dist/chunk-T4LKNY7T.js.map +0 -1
- package/dist/chunk-ZPZFPVDN.js.map +0 -1
- package/dist/cli-WA65NP4Y.js.map +0 -1
- package/dist/config-CEFTL5ZL.js.map +0 -1
- package/dist/doctor-3QBYN46N.js.map +0 -1
- package/dist/usdc-44R6XZF2.js +0 -18
- package/dist/wallet-ELBTBMXO.js +0 -20
- /package/dist/{_esm-ZH2J4QEH.js.map → _esm-C5CRWCYU.js.map} +0 -0
- /package/dist/{ccip-UCLT5VOW.js.map → _esm-FEASBWRP.js.map} +0 -0
- /package/dist/{chunk-3IMLRUZE.js.map → ccip-NQXCEK2U.js.map} +0 -0
- /package/dist/{secp256k1-BEZS642W.js.map → ccip-XR5NE5QK.js.map} +0 -0
- /package/dist/{usdc-44R6XZF2.js.map → chunk-ZXTAQYNA.js.map} +0 -0
- /package/dist/{wallet-ELBTBMXO.js.map → config-VELDKLHY.js.map} +0 -0
- /package/dist/{mine.wasm-GQECMSGN.js.map → mine.wasm-6PIT3DK6.js.map} +0 -0
|
@@ -1,94 +1,27 @@
|
|
|
1
1
|
import { createRequire as __tenjinCreateRequire } from 'node:module'; const require = __tenjinCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
CLIENT_HEADER,
|
|
4
|
+
fetchJson,
|
|
5
|
+
trimSlash
|
|
6
|
+
} from "./chunk-4ONXEEQL.js";
|
|
2
7
|
import {
|
|
3
8
|
loadRawConfig,
|
|
4
9
|
resolveSettings,
|
|
5
10
|
toMoney
|
|
6
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ESPNTPQ2.js";
|
|
7
12
|
import {
|
|
8
13
|
walletFileExists
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-ZPZFPVDN.js";
|
|
14
|
+
} from "./chunk-YN5SHIXQ.js";
|
|
11
15
|
import {
|
|
12
16
|
CliError,
|
|
13
17
|
configPath
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-3IMLRUZE.js";
|
|
18
|
+
} from "./chunk-OYMYCW7J.js";
|
|
16
19
|
|
|
17
20
|
// src/commands/doctor.ts
|
|
18
21
|
import { styleText } from "util";
|
|
19
22
|
import { Stream } from "stream";
|
|
20
|
-
|
|
21
|
-
|
|
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 = {}) {
|
|
23
|
+
var POCKET_MONEY_CEILING_ATOMIC = 20000000n;
|
|
24
|
+
async function collectDoctorChecks(ctx, deps = {}) {
|
|
92
25
|
const env = deps.env ?? process.env;
|
|
93
26
|
const { config, check: configCheck } = await loadConfigForDoctor(ctx.dataDir);
|
|
94
27
|
const settings = resolveSettings({ config, flags: { baseUrl: ctx.flags.baseUrl }, env });
|
|
@@ -97,21 +30,27 @@ async function runDoctor(ctx, deps = {}) {
|
|
|
97
30
|
checkNode(),
|
|
98
31
|
configCheck,
|
|
99
32
|
await checkApiContract(baseUrl, ctx.flags.timeout, deps.fetchImpl),
|
|
100
|
-
await checkReadPath(baseUrl, ctx.flags.timeout, deps.fetchImpl)
|
|
33
|
+
await checkReadPath(baseUrl, ctx.flags.timeout, deps.fetchImpl),
|
|
34
|
+
await checkLookupContract(baseUrl, ctx.flags.timeout, deps.fetchImpl)
|
|
101
35
|
];
|
|
102
36
|
for (const result of await checkWallet(ctx, deps, env, settings.rpcUrl.value)) {
|
|
103
37
|
built.push({ result });
|
|
104
38
|
}
|
|
105
39
|
const checks = built.map((b) => b.result);
|
|
106
40
|
const firstFail = built.find((b) => b.result.required && b.result.status === "fail");
|
|
107
|
-
if (firstFail
|
|
108
|
-
|
|
109
|
-
|
|
41
|
+
if (firstFail === void 0) return { checks };
|
|
42
|
+
return { checks, failure: { code: firstFail.failCode ?? "INTERNAL", result: firstFail.result } };
|
|
43
|
+
}
|
|
44
|
+
async function runDoctor(ctx, deps = {}) {
|
|
45
|
+
const { checks, failure } = await collectDoctorChecks(ctx, deps);
|
|
46
|
+
if (failure !== void 0) {
|
|
47
|
+
const r = failure.result;
|
|
48
|
+
throw new CliError(failure.code, r.detail, {
|
|
110
49
|
...r.fix !== void 0 ? { fix: r.fix } : {},
|
|
111
50
|
details: { checks }
|
|
112
51
|
});
|
|
113
52
|
}
|
|
114
|
-
return { data: { status: "pass", checks }, humanLines:
|
|
53
|
+
return { data: { status: "pass", checks }, humanLines: renderDoctorHuman(ctx.io, checks) };
|
|
115
54
|
}
|
|
116
55
|
function checkNode() {
|
|
117
56
|
const version = process.versions.node;
|
|
@@ -156,7 +95,11 @@ async function loadConfigForDoctor(dataDir) {
|
|
|
156
95
|
}
|
|
157
96
|
async function checkApiContract(baseUrl, timeoutMs, fetchImpl) {
|
|
158
97
|
const url = `${trimSlash(baseUrl)}/openapi.json`;
|
|
159
|
-
const res = await fetchJson(url, {
|
|
98
|
+
const res = await fetchJson(url, {
|
|
99
|
+
timeoutMs,
|
|
100
|
+
fetchImpl,
|
|
101
|
+
headers: { "x-tenjin-client": CLIENT_HEADER }
|
|
102
|
+
});
|
|
160
103
|
if (!res.ok) {
|
|
161
104
|
const malformed = res.kind === "invalid-json";
|
|
162
105
|
return {
|
|
@@ -192,9 +135,52 @@ async function checkApiContract(baseUrl, timeoutMs, fetchImpl) {
|
|
|
192
135
|
}
|
|
193
136
|
};
|
|
194
137
|
}
|
|
138
|
+
async function checkLookupContract(baseUrl, timeoutMs, fetchImpl) {
|
|
139
|
+
const url = `${trimSlash(baseUrl)}/openapi.json`;
|
|
140
|
+
const res = await fetchJson(url, {
|
|
141
|
+
timeoutMs,
|
|
142
|
+
fetchImpl,
|
|
143
|
+
headers: { "x-tenjin-client": CLIENT_HEADER }
|
|
144
|
+
});
|
|
145
|
+
if (!res.ok) {
|
|
146
|
+
return {
|
|
147
|
+
result: {
|
|
148
|
+
name: "lookup-contract",
|
|
149
|
+
status: "warn",
|
|
150
|
+
required: false,
|
|
151
|
+
detail: `Could not confirm the lookup endpoint at ${url}`,
|
|
152
|
+
fix: "Check --base-url; lookup/buy need the A2 endpoints deployed."
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const present = hasLookupPath(res.json);
|
|
157
|
+
return {
|
|
158
|
+
result: present ? {
|
|
159
|
+
name: "lookup-contract",
|
|
160
|
+
status: "ok",
|
|
161
|
+
required: false,
|
|
162
|
+
detail: "Lookup endpoint advertised"
|
|
163
|
+
} : {
|
|
164
|
+
name: "lookup-contract",
|
|
165
|
+
status: "warn",
|
|
166
|
+
required: false,
|
|
167
|
+
detail: "This deployment does not advertise POST /api/agent/lookup (A2 not deployed)",
|
|
168
|
+
fix: "lookup/buy need A2 deployed; point --base-url at a deploy that has it."
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function hasLookupPath(json) {
|
|
173
|
+
if (!isRecord(json)) return false;
|
|
174
|
+
const paths = json.paths;
|
|
175
|
+
return isRecord(paths) && "/api/agent/lookup" in paths;
|
|
176
|
+
}
|
|
195
177
|
async function checkReadPath(baseUrl, timeoutMs, fetchImpl) {
|
|
196
178
|
const url = `${trimSlash(baseUrl)}/api/articles?limit=1`;
|
|
197
|
-
const res = await fetchJson(url, {
|
|
179
|
+
const res = await fetchJson(url, {
|
|
180
|
+
timeoutMs,
|
|
181
|
+
fetchImpl,
|
|
182
|
+
headers: { "x-tenjin-client": CLIENT_HEADER }
|
|
183
|
+
});
|
|
198
184
|
if (!res.ok) {
|
|
199
185
|
return {
|
|
200
186
|
result: {
|
|
@@ -227,7 +213,7 @@ async function checkReadPath(baseUrl, timeoutMs, fetchImpl) {
|
|
|
227
213
|
async function checkWallet(ctx, deps, env, rpcUrl) {
|
|
228
214
|
const provider = deps.provider ?? await resolveLocalProviderOrNull(ctx, env);
|
|
229
215
|
if (provider === null) return [noWalletCheck()];
|
|
230
|
-
const { describeWallet } = await import("./wallet-
|
|
216
|
+
const { describeWallet } = await import("./wallet-PA54CRBL.js");
|
|
231
217
|
let desc;
|
|
232
218
|
try {
|
|
233
219
|
desc = await describeWallet(provider);
|
|
@@ -253,7 +239,7 @@ async function resolveLocalProviderOrNull(ctx, env) {
|
|
|
253
239
|
const envKey = env.TENJIN_WALLET_KEY;
|
|
254
240
|
const envKeySet = typeof envKey === "string" && envKey.length > 0;
|
|
255
241
|
if (!envKeySet && !await walletFileExists(ctx.dataDir)) return null;
|
|
256
|
-
const { resolveWalletProvider } = await import("./wallet-
|
|
242
|
+
const { resolveWalletProvider } = await import("./wallet-PA54CRBL.js");
|
|
257
243
|
return resolveWalletProvider(ctx);
|
|
258
244
|
}
|
|
259
245
|
function noWalletCheck() {
|
|
@@ -276,7 +262,7 @@ function walletWarn(err) {
|
|
|
276
262
|
}
|
|
277
263
|
async function checkBalance(address, rpcUrl) {
|
|
278
264
|
try {
|
|
279
|
-
const { getUsdcBalance } = await import("./usdc-
|
|
265
|
+
const { getUsdcBalance } = await import("./usdc-JIAJW4SV.js");
|
|
280
266
|
const balance = await getUsdcBalance(address, rpcUrl);
|
|
281
267
|
if (balance === 0n) {
|
|
282
268
|
return {
|
|
@@ -288,6 +274,15 @@ async function checkBalance(address, rpcUrl) {
|
|
|
288
274
|
};
|
|
289
275
|
}
|
|
290
276
|
const money = toMoney(balance.toString());
|
|
277
|
+
if (balance > POCKET_MONEY_CEILING_ATOMIC) {
|
|
278
|
+
return {
|
|
279
|
+
name: "balance",
|
|
280
|
+
status: "warn",
|
|
281
|
+
required: false,
|
|
282
|
+
detail: `Balance ${money.usd} USDC is above the ~$20 pocket-money ceiling`,
|
|
283
|
+
fix: "Keep only small change in the CLI wallet; sweep the excess to cold storage."
|
|
284
|
+
};
|
|
285
|
+
}
|
|
291
286
|
return {
|
|
292
287
|
name: "balance",
|
|
293
288
|
status: "ok",
|
|
@@ -304,7 +299,7 @@ async function checkBalance(address, rpcUrl) {
|
|
|
304
299
|
};
|
|
305
300
|
}
|
|
306
301
|
}
|
|
307
|
-
function
|
|
302
|
+
function renderDoctorHuman(io, checks) {
|
|
308
303
|
const nameWidth = Math.max(...checks.map((c) => c.name.length));
|
|
309
304
|
const lines = [];
|
|
310
305
|
for (const c of checks) {
|
|
@@ -329,10 +324,10 @@ function infoVersion(json) {
|
|
|
329
324
|
function isRecord(value) {
|
|
330
325
|
return typeof value === "object" && value !== null;
|
|
331
326
|
}
|
|
332
|
-
|
|
333
|
-
return url.replace(/\/+$/, "");
|
|
334
|
-
}
|
|
327
|
+
|
|
335
328
|
export {
|
|
336
|
-
|
|
329
|
+
collectDoctorChecks,
|
|
330
|
+
runDoctor,
|
|
331
|
+
renderDoctorHuman
|
|
337
332
|
};
|
|
338
|
-
//# sourceMappingURL=
|
|
333
|
+
//# sourceMappingURL=chunk-AWPAWHNX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { styleText } from 'node:util';\nimport { Stream } from 'node:stream';\nimport { CliError } from '../lib/errors';\nimport { fetchJson } from '../lib/http';\nimport { CLIENT_HEADER } from '../lib/client-meta';\nimport { loadRawConfig, resolveSettings } from '../lib/config';\nimport { trimSlash } from '../lib/url';\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/** ~$20 in atomic USDC (6 decimals). Above this, the pocket-money wallet warns. */\nconst POCKET_MONEY_CEILING_ATOMIC = 20_000_000n;\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\n/**\n * The full check list plus the first required failure, if any. `install` reuses\n * this to run the doctor checks as its last step and EMBED the summary without\n * throwing (D39: doctor is diagnostics, it never blocks the caller); `runDoctor`\n * wraps it and turns a required failure into the thrown failure envelope.\n */\nexport interface DoctorChecks {\n checks: CheckResult[];\n failure?: { code: ErrorCode; result: CheckResult };\n}\n\nexport async function collectDoctorChecks(\n ctx: CommandContext,\n deps: DoctorDeps = {},\n): Promise<DoctorChecks> {\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 await checkLookupContract(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) return { checks };\n return { checks, failure: { code: firstFail.failCode ?? 'INTERNAL', result: firstFail.result } };\n}\n\nexport async function runDoctor(\n ctx: CommandContext,\n deps: DoctorDeps = {},\n): Promise<CommandResult> {\n const { checks, failure } = await collectDoctorChecks(ctx, deps);\n if (failure !== undefined) {\n const r = failure.result;\n throw new CliError(failure.code, r.detail, {\n ...(r.fix !== undefined ? { fix: r.fix } : {}),\n details: { checks },\n });\n }\n\n return { data: { status: 'pass', checks }, humanLines: renderDoctorHuman(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, {\n timeoutMs,\n fetchImpl,\n headers: { 'x-tenjin-client': CLIENT_HEADER },\n });\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\n/**\n * WARN-level (never fails doctor): is the A2 lookup endpoint advertised in the\n * OpenAPI doc? Absent means the deployment predates A2 (the buy/lookup path will\n * not work against it yet). Warn-only because doctor's job is a working READ path,\n * and lookup is additive.\n */\nasync function checkLookupContract(\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, {\n timeoutMs,\n fetchImpl,\n headers: { 'x-tenjin-client': CLIENT_HEADER },\n });\n if (!res.ok) {\n return {\n result: {\n name: 'lookup-contract',\n status: 'warn',\n required: false,\n detail: `Could not confirm the lookup endpoint at ${url}`,\n fix: 'Check --base-url; lookup/buy need the A2 endpoints deployed.',\n },\n };\n }\n const present = hasLookupPath(res.json);\n return {\n result: present\n ? {\n name: 'lookup-contract',\n status: 'ok',\n required: false,\n detail: 'Lookup endpoint advertised',\n }\n : {\n name: 'lookup-contract',\n status: 'warn',\n required: false,\n detail: 'This deployment does not advertise POST /api/agent/lookup (A2 not deployed)',\n fix: 'lookup/buy need A2 deployed; point --base-url at a deploy that has it.',\n },\n };\n}\n\nfunction hasLookupPath(json: unknown): boolean {\n if (!isRecord(json)) return false;\n const paths = json.paths;\n return isRecord(paths) && '/api/agent/lookup' in paths;\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, {\n timeoutMs,\n fetchImpl,\n headers: { 'x-tenjin-client': CLIENT_HEADER },\n });\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 // Pocket-money posture (roadmap B2): a plaintext-adjacent local key should\n // hold only small change, so a balance over ~$20 is a warn, not an error.\n if (balance > POCKET_MONEY_CEILING_ATOMIC) {\n return {\n name: 'balance',\n status: 'warn',\n required: false,\n detail: `Balance ${money.usd} USDC is above the ~$20 pocket-money ceiling`,\n fix: 'Keep only small change in the CLI wallet; sweep the excess to cold storage.',\n };\n }\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\nexport function renderDoctorHuman(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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createRequire as __tenjinCreateRequire } from 'node:module'; const require = __tenjinCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
a2 as a,
|
|
4
|
+
i2 as i,
|
|
5
|
+
p,
|
|
6
|
+
r,
|
|
7
|
+
t
|
|
8
|
+
} from "./chunk-HSDCI7OV.js";
|
|
9
|
+
|
|
10
|
+
// node_modules/.pnpm/@signinwithethereum+siwe@4.2.0_viem@2.55.2_typescript@6.0.3_zod@4.4.3_/node_modules/@signinwithethereum/siwe/dist/viemAdapter-By13KInL.js
|
|
11
|
+
var a2 = /* @__PURE__ */ t({ createViemConfig: () => s });
|
|
12
|
+
var o = [{
|
|
13
|
+
inputs: [{
|
|
14
|
+
name: "_message",
|
|
15
|
+
type: "bytes32"
|
|
16
|
+
}, {
|
|
17
|
+
name: "_signature",
|
|
18
|
+
type: "bytes"
|
|
19
|
+
}],
|
|
20
|
+
name: "isValidSignature",
|
|
21
|
+
outputs: [{
|
|
22
|
+
name: "",
|
|
23
|
+
type: "bytes4"
|
|
24
|
+
}],
|
|
25
|
+
stateMutability: "view",
|
|
26
|
+
type: "function"
|
|
27
|
+
}];
|
|
28
|
+
async function s(e) {
|
|
29
|
+
let a3;
|
|
30
|
+
try {
|
|
31
|
+
a3 = await import("./_esm-FEASBWRP.js");
|
|
32
|
+
} catch {
|
|
33
|
+
throw new r(i.MISSING_PROVIDER_LIBRARY, "viem installed (npm install viem)", "viem not found");
|
|
34
|
+
}
|
|
35
|
+
let s2 = {
|
|
36
|
+
verifyMessage: async (e2, t2) => a3.recoverMessageAddress({
|
|
37
|
+
message: e2,
|
|
38
|
+
signature: t2
|
|
39
|
+
}),
|
|
40
|
+
hashMessage: (e2) => a3.hashMessage(e2),
|
|
41
|
+
getAddress: (e2) => a3.getAddress(e2)
|
|
42
|
+
};
|
|
43
|
+
if (e?.publicClient) {
|
|
44
|
+
let t2 = e.publicClient;
|
|
45
|
+
s2.checkContractWalletSignature = async (e2, i2, s3, c) => {
|
|
46
|
+
let l = t2.chain?.id;
|
|
47
|
+
if (l == null && t2.getChainId && (l = await t2.getChainId()), l == null) throw new p("EIP-1271/EIP-6492 verification requires a viem publicClient with chain.id or getChainId().");
|
|
48
|
+
if (l !== c) throw new p(`publicClient chainId ${l} does not match message chainId ${c}.`);
|
|
49
|
+
if (t2.verifyMessage) return t2.verifyMessage({
|
|
50
|
+
address: e2,
|
|
51
|
+
message: i2,
|
|
52
|
+
signature: s3
|
|
53
|
+
});
|
|
54
|
+
let u = a3.hashMessage(i2);
|
|
55
|
+
return await t2.readContract({
|
|
56
|
+
address: e2,
|
|
57
|
+
abi: o,
|
|
58
|
+
functionName: "isValidSignature",
|
|
59
|
+
args: [u, s3]
|
|
60
|
+
}) === a;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return s2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
a2 as a,
|
|
68
|
+
s
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=chunk-CYOF7ROH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../node_modules/.pnpm/@signinwithethereum+siwe@4.2.0_viem@2.55.2_typescript@6.0.3_zod@4.4.3_/node_modules/@signinwithethereum/siwe/dist/viemAdapter-By13KInL.js"],"sourcesContent":["import { t as e } from \"./rolldown-runtime-CAFD8bLK.js\";\nimport { d as t, o as n, t as r, u as i } from \"./utils-9-kzgPFC.js\";\n//#region lib/viemAdapter.ts\nvar a = /* @__PURE__ */ e({ createViemConfig: () => s }), o = [{\n\tinputs: [{\n\t\tname: \"_message\",\n\t\ttype: \"bytes32\"\n\t}, {\n\t\tname: \"_signature\",\n\t\ttype: \"bytes\"\n\t}],\n\tname: \"isValidSignature\",\n\toutputs: [{\n\t\tname: \"\",\n\t\ttype: \"bytes4\"\n\t}],\n\tstateMutability: \"view\",\n\ttype: \"function\"\n}];\nasync function s(e) {\n\tlet a;\n\ttry {\n\t\ta = await import(\"viem\");\n\t} catch {\n\t\tthrow new i(t.MISSING_PROVIDER_LIBRARY, \"viem installed (npm install viem)\", \"viem not found\");\n\t}\n\tlet s = {\n\t\tverifyMessage: async (e, t) => a.recoverMessageAddress({\n\t\t\tmessage: e,\n\t\t\tsignature: t\n\t\t}),\n\t\thashMessage: (e) => a.hashMessage(e),\n\t\tgetAddress: (e) => a.getAddress(e)\n\t};\n\tif (e?.publicClient) {\n\t\tlet t = e.publicClient;\n\t\ts.checkContractWalletSignature = async (e, i, s, c) => {\n\t\t\tlet l = t.chain?.id;\n\t\t\tif (l == null && t.getChainId && (l = await t.getChainId()), l == null) throw new r(\"EIP-1271/EIP-6492 verification requires a viem publicClient with chain.id or getChainId().\");\n\t\t\tif (l !== c) throw new r(`publicClient chainId ${l} does not match message chainId ${c}.`);\n\t\t\tif (t.verifyMessage) return t.verifyMessage({\n\t\t\t\taddress: e,\n\t\t\t\tmessage: i,\n\t\t\t\tsignature: s\n\t\t\t});\n\t\t\tlet u = a.hashMessage(i);\n\t\t\treturn await t.readContract({\n\t\t\t\taddress: e,\n\t\t\t\tabi: o,\n\t\t\t\tfunctionName: \"isValidSignature\",\n\t\t\t\targs: [u, s]\n\t\t\t}) === n;\n\t\t};\n\t}\n\treturn s;\n}\n//#endregion\nexport { a as n, s as t };\n"],"mappings":";;;;;;;;;;AAGA,IAAIA,KAAoB,kBAAE,EAAE,kBAAkB,MAAM,EAAE,CAAC;AAAvD,IAA0D,IAAI,CAAC;AAAA,EAC9D,QAAQ,CAAC;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,EACP,GAAG;AAAA,IACF,MAAM;AAAA,IACN,MAAM;AAAA,EACP,CAAC;AAAA,EACD,MAAM;AAAA,EACN,SAAS,CAAC;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,EACP,CAAC;AAAA,EACD,iBAAiB;AAAA,EACjB,MAAM;AACP,CAAC;AACD,eAAe,EAAE,GAAG;AACnB,MAAIA;AACJ,MAAI;AACH,IAAAA,KAAI,MAAM,OAAO,oBAAM;AAAA,EACxB,QAAQ;AACP,UAAM,IAAI,EAAE,EAAE,0BAA0B,qCAAqC,gBAAgB;AAAA,EAC9F;AACA,MAAIC,KAAI;AAAA,IACP,eAAe,OAAOC,IAAGC,OAAMH,GAAE,sBAAsB;AAAA,MACtD,SAASE;AAAA,MACT,WAAWC;AAAA,IACZ,CAAC;AAAA,IACD,aAAa,CAACD,OAAMF,GAAE,YAAYE,EAAC;AAAA,IACnC,YAAY,CAACA,OAAMF,GAAE,WAAWE,EAAC;AAAA,EAClC;AACA,MAAI,GAAG,cAAc;AACpB,QAAIC,KAAI,EAAE;AACV,IAAAF,GAAE,+BAA+B,OAAOC,IAAGE,IAAGH,IAAG,MAAM;AACtD,UAAI,IAAIE,GAAE,OAAO;AACjB,UAAI,KAAK,QAAQA,GAAE,eAAe,IAAI,MAAMA,GAAE,WAAW,IAAI,KAAK,KAAM,OAAM,IAAI,EAAE,4FAA4F;AAChL,UAAI,MAAM,EAAG,OAAM,IAAI,EAAE,wBAAwB,CAAC,mCAAmC,CAAC,GAAG;AACzF,UAAIA,GAAE,cAAe,QAAOA,GAAE,cAAc;AAAA,QAC3C,SAASD;AAAA,QACT,SAASE;AAAA,QACT,WAAWH;AAAA,MACZ,CAAC;AACD,UAAI,IAAID,GAAE,YAAYI,EAAC;AACvB,aAAO,MAAMD,GAAE,aAAa;AAAA,QAC3B,SAASD;AAAA,QACT,KAAK;AAAA,QACL,cAAc;AAAA,QACd,MAAM,CAAC,GAAGD,EAAC;AAAA,MACZ,CAAC,MAAM;AAAA,IACR;AAAA,EACD;AACA,SAAOA;AACR;","names":["a","s","e","t","i"]}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createRequire as __tenjinCreateRequire } from 'node:module'; const require = __tenjinCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
SIWX_HEADER,
|
|
4
|
+
decodePaymentRequiredHeader,
|
|
5
|
+
decodePaymentResponseHeader
|
|
6
|
+
} from "./chunk-S3IGX57D.js";
|
|
7
|
+
import {
|
|
8
|
+
rateLimitError
|
|
9
|
+
} from "./chunk-662MSGZB.js";
|
|
10
|
+
import {
|
|
11
|
+
SLUG_RE,
|
|
12
|
+
UUID_RE
|
|
13
|
+
} from "./chunk-3OV7E5G5.js";
|
|
14
|
+
import {
|
|
15
|
+
CLIENT_HEADER,
|
|
16
|
+
httpRequest
|
|
17
|
+
} from "./chunk-4ONXEEQL.js";
|
|
18
|
+
import {
|
|
19
|
+
CliError,
|
|
20
|
+
external_exports
|
|
21
|
+
} from "./chunk-OYMYCW7J.js";
|
|
22
|
+
|
|
23
|
+
// src/lib/read-client.ts
|
|
24
|
+
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
25
|
+
var PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
26
|
+
var readBodySchema = external_exports.object({
|
|
27
|
+
id: external_exports.string().regex(UUID_RE, "resource id must be a uuid"),
|
|
28
|
+
slug: external_exports.string().regex(SLUG_RE, "unsafe slug").max(80),
|
|
29
|
+
title: external_exports.string(),
|
|
30
|
+
bodyMd: external_exports.string(),
|
|
31
|
+
price: external_exports.string(),
|
|
32
|
+
creator: external_exports.object({
|
|
33
|
+
handle: external_exports.string().nullish(),
|
|
34
|
+
walletAddress: external_exports.string().nullish()
|
|
35
|
+
}).passthrough()
|
|
36
|
+
}).passthrough();
|
|
37
|
+
var previewSchema = external_exports.object({
|
|
38
|
+
id: external_exports.string().optional(),
|
|
39
|
+
title: external_exports.string().optional(),
|
|
40
|
+
price: external_exports.string().optional(),
|
|
41
|
+
creator: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
42
|
+
}).passthrough();
|
|
43
|
+
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
44
|
+
var paymentRequiredSchema = external_exports.object({
|
|
45
|
+
x402Version: external_exports.literal(2),
|
|
46
|
+
accepts: external_exports.array(
|
|
47
|
+
external_exports.object({
|
|
48
|
+
scheme: external_exports.string(),
|
|
49
|
+
network: external_exports.string().regex(/^eip155:\d+$/, "network must be CAIP-2 eip155"),
|
|
50
|
+
asset: external_exports.string().regex(ADDRESS_RE, "asset must be a 0x address"),
|
|
51
|
+
amount: external_exports.string().regex(/^\d{1,39}$/, "amount must be an atomic integer string"),
|
|
52
|
+
payTo: external_exports.string().regex(ADDRESS_RE, "payTo must be a 0x address")
|
|
53
|
+
}).passthrough()
|
|
54
|
+
)
|
|
55
|
+
}).passthrough();
|
|
56
|
+
async function fetchRead(url, opts) {
|
|
57
|
+
const headers = {
|
|
58
|
+
accept: "application/json",
|
|
59
|
+
"x-tenjin-client": CLIENT_HEADER
|
|
60
|
+
};
|
|
61
|
+
if (opts.lookupId !== void 0) headers["x-tenjin-lookup-id"] = opts.lookupId;
|
|
62
|
+
if (opts.siwxHeader !== void 0) headers[SIWX_HEADER] = opts.siwxHeader;
|
|
63
|
+
if (opts.paymentHeaders !== void 0) Object.assign(headers, opts.paymentHeaders);
|
|
64
|
+
const res = await httpRequest(url, {
|
|
65
|
+
method: "GET",
|
|
66
|
+
timeoutMs: opts.timeoutMs,
|
|
67
|
+
headers,
|
|
68
|
+
fetchImpl: opts.fetchImpl
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
const code = res.kind === "network" || res.kind === "timeout" ? "NETWORK_ERROR" : "API_UNREACHABLE";
|
|
72
|
+
throw new CliError(code, `${url}: ${res.message}`, {
|
|
73
|
+
fix: "Check the resource URL and your network, then retry."
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (res.status === 200) {
|
|
77
|
+
const parsed = readBodySchema.safeParse(res.json);
|
|
78
|
+
if (!parsed.success) {
|
|
79
|
+
throw new CliError(
|
|
80
|
+
"CONTRACT_MISMATCH",
|
|
81
|
+
"The read response did not match the expected contract",
|
|
82
|
+
{
|
|
83
|
+
fix: "Update tenjin-cli; the read contract may have changed.",
|
|
84
|
+
details: parsed.error.issues
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const settlement = decodeSettlement(res.header(PAYMENT_RESPONSE_HEADER));
|
|
89
|
+
return {
|
|
90
|
+
kind: "entitled",
|
|
91
|
+
body: parsed.data,
|
|
92
|
+
...settlement !== void 0 ? { settlementTxHash: settlement } : {}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (res.status === 402) {
|
|
96
|
+
const encoded = res.header(PAYMENT_REQUIRED_HEADER);
|
|
97
|
+
if (encoded === void 0) {
|
|
98
|
+
throw new CliError("CONTRACT_MISMATCH", "The 402 carried no PAYMENT-REQUIRED header", {
|
|
99
|
+
fix: "The server may be misconfigured; try another resource or update tenjin-cli."
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
let decoded;
|
|
103
|
+
try {
|
|
104
|
+
decoded = decodePaymentRequiredHeader(encoded);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw new CliError("CONTRACT_MISMATCH", "Could not decode the PAYMENT-REQUIRED header", {
|
|
107
|
+
fix: "Update tenjin-cli; the x402 header format may have changed.",
|
|
108
|
+
cause: err
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const validated = paymentRequiredSchema.safeParse(decoded);
|
|
112
|
+
if (!validated.success) {
|
|
113
|
+
throw new CliError(
|
|
114
|
+
"CONTRACT_MISMATCH",
|
|
115
|
+
"The 402 challenge is not a valid x402 v2 payment declaration.",
|
|
116
|
+
{
|
|
117
|
+
fix: "Update tenjin-cli, or check --base-url points at a Tenjin deployment.",
|
|
118
|
+
details: validated.error.issues
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const paymentRequired = decoded;
|
|
123
|
+
const preview = previewSchema.safeParse(res.json);
|
|
124
|
+
return {
|
|
125
|
+
kind: "payment_required",
|
|
126
|
+
paymentRequired,
|
|
127
|
+
preview: preview.success ? preview.data : {}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (res.status === 409) {
|
|
131
|
+
return { kind: "already_purchased", message: readMessage(res.json) ?? "Already purchased." };
|
|
132
|
+
}
|
|
133
|
+
if (res.status === 404) {
|
|
134
|
+
throw new CliError("RESOURCE_NOT_FOUND", `No resource at ${url}`, {
|
|
135
|
+
fix: "Check the handle/slug or resource id."
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (res.status === 429) throw rateLimitError(url, (n) => res.header(n));
|
|
139
|
+
throw new CliError(
|
|
140
|
+
"API_UNREACHABLE",
|
|
141
|
+
readMessage(res.json) ?? `Unexpected status ${res.status} from ${url}`,
|
|
142
|
+
{
|
|
143
|
+
fix: "Retry; if it persists the resource may be unavailable.",
|
|
144
|
+
details: res.json
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function decodeSettlement(header) {
|
|
149
|
+
if (header === void 0) return void 0;
|
|
150
|
+
try {
|
|
151
|
+
const settle = decodePaymentResponseHeader(header);
|
|
152
|
+
const tx = settle.transaction;
|
|
153
|
+
return typeof tx === "string" ? tx : void 0;
|
|
154
|
+
} catch {
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function readMessage(json) {
|
|
159
|
+
if (typeof json === "object" && json !== null) {
|
|
160
|
+
const rec = json;
|
|
161
|
+
if (typeof rec.message === "string") return rec.message;
|
|
162
|
+
if (typeof rec.error === "string") return rec.error;
|
|
163
|
+
if (typeof rec.code === "string") return rec.code;
|
|
164
|
+
}
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export {
|
|
169
|
+
fetchRead
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=chunk-DXTQ3CSI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/read-client.ts"],"sourcesContent":["import { z } from 'zod';\nimport { decodePaymentRequiredHeader, decodePaymentResponseHeader } from '@x402/core/http';\nimport type { PaymentRequired } from '@x402/core/types';\nimport { CliError } from './errors';\nimport { rateLimitError } from './agent-api';\nimport { httpRequest } from './http';\nimport { CLIENT_HEADER } from './client-meta';\nimport { SIWX_HEADER } from './siwx';\n\n/**\n * The read-route (`GET /api/read/<handle>/<slug>`) client, always JSON/x402, so\n * it is the robust agent target (llms-full). It surfaces the three outcomes the\n * buy flow branches on: `entitled` (200 with the full body, free post, SIWX\n * re-read, or freshly paid), `payment_required` (402 with the decoded\n * PAYMENT-REQUIRED header + the leak-safe preview body), and `already_purchased`\n * (409 owned-re-pay gate, nothing charged). Purchase attribution rides\n * `X-Tenjin-Client` always and `X-Tenjin-Lookup-Id` when a lookup preceded the buy.\n */\n\nconst PAYMENT_REQUIRED_HEADER = 'PAYMENT-REQUIRED';\nconst PAYMENT_RESPONSE_HEADER = 'PAYMENT-RESPONSE';\n\n// id/slug become filesystem path segments (the local library), so they are\n// validated as a uuid + the server's slug charset HERE, at the trust boundary, so\n// a hostile id='../../evil' or slug fails as CONTRACT_MISMATCH before delivery.\nimport { SLUG_RE, UUID_RE as RESOURCE_ID_RE } from './ids';\n\n/** The full essay body a 200 returns (loose: require what the CLI reads, keep the rest). */\nconst readBodySchema = z\n .object({\n id: z.string().regex(RESOURCE_ID_RE, 'resource id must be a uuid'),\n slug: z.string().regex(SLUG_RE, 'unsafe slug').max(80),\n title: z.string(),\n bodyMd: z.string(),\n price: z.string(),\n creator: z\n .object({\n handle: z.string().nullish(),\n walletAddress: z.string().nullish(),\n })\n .passthrough(),\n })\n .passthrough();\n\nexport type ReadBody = z.infer<typeof readBodySchema>;\n\n/** The leak-safe 402 preview body, never the paid content. */\nconst previewSchema = z\n .object({\n id: z.string().optional(),\n title: z.string().optional(),\n price: z.string().optional(),\n creator: z.record(z.string(), z.unknown()).optional(),\n })\n .passthrough();\n\nexport type Preview = z.infer<typeof previewSchema>;\n\n// The decoded PAYMENT-REQUIRED is money-path input: validate the fields the pay\n// path consumes (amount as an atomic integer, CAIP-2 network, 0x asset/payTo)\n// at THIS boundary instead of trusting raw base64 JSON. Loose otherwise so new\n// server fields never break an older CLI.\nconst ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\nconst paymentRequiredSchema = z\n .object({\n x402Version: z.literal(2),\n accepts: z.array(\n z\n .object({\n scheme: z.string(),\n network: z.string().regex(/^eip155:\\d+$/, 'network must be CAIP-2 eip155'),\n asset: z.string().regex(ADDRESS_RE, 'asset must be a 0x address'),\n amount: z.string().regex(/^\\d{1,39}$/, 'amount must be an atomic integer string'),\n payTo: z.string().regex(ADDRESS_RE, 'payTo must be a 0x address'),\n })\n .passthrough(),\n ),\n })\n .passthrough();\n\nexport type ReadResult =\n | { kind: 'entitled'; body: ReadBody; settlementTxHash?: string }\n | { kind: 'payment_required'; paymentRequired: PaymentRequired; preview: Preview }\n | { kind: 'already_purchased'; message: string };\n\nexport interface ReadRequestOptions {\n timeoutMs: number;\n fetchImpl?: typeof fetch;\n /** A `SIGN-IN-WITH-X` header for the entitled free re-read. */\n siwxHeader?: string;\n /** The `PAYMENT-SIGNATURE` header(s) for a paid request. */\n paymentHeaders?: Record<string, string>;\n /** Attribute a following purchase to the lookup that surfaced it. */\n lookupId?: string;\n}\n\nexport async function fetchRead(url: string, opts: ReadRequestOptions): Promise<ReadResult> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'x-tenjin-client': CLIENT_HEADER,\n };\n if (opts.lookupId !== undefined) headers['x-tenjin-lookup-id'] = opts.lookupId;\n if (opts.siwxHeader !== undefined) headers[SIWX_HEADER] = opts.siwxHeader;\n if (opts.paymentHeaders !== undefined) Object.assign(headers, opts.paymentHeaders);\n\n const res = await httpRequest(url, {\n method: 'GET',\n timeoutMs: opts.timeoutMs,\n headers,\n fetchImpl: opts.fetchImpl,\n });\n if (!res.ok) {\n const code =\n res.kind === 'network' || res.kind === 'timeout' ? 'NETWORK_ERROR' : 'API_UNREACHABLE';\n throw new CliError(code, `${url}: ${res.message}`, {\n fix: 'Check the resource URL and your network, then retry.',\n });\n }\n\n if (res.status === 200) {\n const parsed = readBodySchema.safeParse(res.json);\n if (!parsed.success) {\n throw new CliError(\n 'CONTRACT_MISMATCH',\n 'The read response did not match the expected contract',\n {\n fix: 'Update tenjin-cli; the read contract may have changed.',\n details: parsed.error.issues,\n },\n );\n }\n const settlement = decodeSettlement(res.header(PAYMENT_RESPONSE_HEADER));\n return {\n kind: 'entitled',\n body: parsed.data,\n ...(settlement !== undefined ? { settlementTxHash: settlement } : {}),\n };\n }\n\n if (res.status === 402) {\n const encoded = res.header(PAYMENT_REQUIRED_HEADER);\n if (encoded === undefined) {\n throw new CliError('CONTRACT_MISMATCH', 'The 402 carried no PAYMENT-REQUIRED header', {\n fix: 'The server may be misconfigured; try another resource or update tenjin-cli.',\n });\n }\n let decoded: PaymentRequired;\n try {\n decoded = decodePaymentRequiredHeader(encoded);\n } catch (err) {\n throw new CliError('CONTRACT_MISMATCH', 'Could not decode the PAYMENT-REQUIRED header', {\n fix: 'Update tenjin-cli; the x402 header format may have changed.',\n cause: err,\n });\n }\n const validated = paymentRequiredSchema.safeParse(decoded);\n if (!validated.success) {\n throw new CliError(\n 'CONTRACT_MISMATCH',\n 'The 402 challenge is not a valid x402 v2 payment declaration.',\n {\n fix: 'Update tenjin-cli, or check --base-url points at a Tenjin deployment.',\n details: validated.error.issues,\n },\n );\n }\n const paymentRequired = decoded;\n const preview = previewSchema.safeParse(res.json);\n return {\n kind: 'payment_required',\n paymentRequired,\n preview: preview.success ? preview.data : {},\n };\n }\n\n if (res.status === 409) {\n // The owned-re-pay gate: a payment for a post this wallet already bought is\n // refused, nothing charged. The caller falls back to a SIWX free re-read.\n return { kind: 'already_purchased', message: readMessage(res.json) ?? 'Already purchased.' };\n }\n\n if (res.status === 404) {\n throw new CliError('RESOURCE_NOT_FOUND', `No resource at ${url}`, {\n fix: 'Check the handle/slug or resource id.',\n });\n }\n\n // 429 on the paid-read path is a recoverable pause, not an outage: surface\n // RATE_LIMITED + retryAfterSeconds like postLookup/postOutcomes do, so a looping\n // agent (the caller most likely to hit this) can back off instead of treating it\n // as API_UNREACHABLE.\n if (res.status === 429) throw rateLimitError(url, (n) => res.header(n));\n\n throw new CliError(\n 'API_UNREACHABLE',\n readMessage(res.json) ?? `Unexpected status ${res.status} from ${url}`,\n {\n fix: 'Retry; if it persists the resource may be unavailable.',\n details: res.json,\n },\n );\n}\n\nfunction decodeSettlement(header: string | undefined): string | undefined {\n if (header === undefined) return undefined;\n try {\n const settle = decodePaymentResponseHeader(header);\n const tx = (settle as { transaction?: unknown }).transaction;\n return typeof tx === 'string' ? tx : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readMessage(json: unknown): string | undefined {\n if (typeof json === 'object' && json !== null) {\n const rec = json as Record<string, unknown>;\n if (typeof rec.message === 'string') return rec.message;\n if (typeof rec.error === 'string') return rec.error;\n if (typeof rec.code === 'string') return rec.code;\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
|