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.
Files changed (133) hide show
  1. package/NOTICE.md +33 -0
  2. package/README.md +167 -16
  3. package/dist/{_esm-ZH2J4QEH.js → _esm-C5CRWCYU.js} +2 -2
  4. package/dist/_esm-FEASBWRP.js +881 -0
  5. package/dist/buy-EE6SEXP6.js +1598 -0
  6. package/dist/buy-EE6SEXP6.js.map +1 -0
  7. package/dist/candidate-ERMSYYQZ.js +127 -0
  8. package/dist/candidate-ERMSYYQZ.js.map +1 -0
  9. package/dist/{ccip-UCLT5VOW.js → ccip-NQXCEK2U.js} +6 -5
  10. package/dist/ccip-XR5NE5QK.js +19 -0
  11. package/dist/chunk-2I4QTOLB.js +11 -0
  12. package/dist/chunk-2I4QTOLB.js.map +1 -0
  13. package/dist/chunk-3OV7E5G5.js +18 -0
  14. package/dist/chunk-3OV7E5G5.js.map +1 -0
  15. package/dist/chunk-4ONXEEQL.js +152 -0
  16. package/dist/chunk-4ONXEEQL.js.map +1 -0
  17. package/dist/chunk-5CLSD5YN.js +209 -0
  18. package/dist/chunk-5CLSD5YN.js.map +1 -0
  19. package/dist/chunk-662MSGZB.js +286 -0
  20. package/dist/chunk-662MSGZB.js.map +1 -0
  21. package/dist/chunk-7KUW4MJD.js +59 -0
  22. package/dist/chunk-7KUW4MJD.js.map +1 -0
  23. package/dist/{doctor-3QBYN46N.js → chunk-AWPAWHNX.js} +88 -93
  24. package/dist/chunk-AWPAWHNX.js.map +1 -0
  25. package/dist/chunk-CYOF7ROH.js +70 -0
  26. package/dist/chunk-CYOF7ROH.js.map +1 -0
  27. package/dist/chunk-DXTQ3CSI.js +171 -0
  28. package/dist/chunk-DXTQ3CSI.js.map +1 -0
  29. package/dist/{chunk-EZROUI5U.js → chunk-ESPNTPQ2.js} +80 -8
  30. package/dist/chunk-ESPNTPQ2.js.map +1 -0
  31. package/dist/chunk-FJPLRISB.js +79 -0
  32. package/dist/chunk-FJPLRISB.js.map +1 -0
  33. package/dist/chunk-GO75V3NZ.js +160 -0
  34. package/dist/chunk-GO75V3NZ.js.map +1 -0
  35. package/dist/chunk-HIVZG55D.js +204 -0
  36. package/dist/chunk-HIVZG55D.js.map +1 -0
  37. package/dist/chunk-HSDCI7OV.js +7806 -0
  38. package/dist/chunk-HSDCI7OV.js.map +1 -0
  39. package/dist/chunk-JPHG2JW3.js +96 -0
  40. package/dist/chunk-JPHG2JW3.js.map +1 -0
  41. package/dist/{chunk-ZPZFPVDN.js → chunk-MUBUQVC3.js} +1 -1
  42. package/dist/chunk-MUBUQVC3.js.map +1 -0
  43. package/dist/{chunk-T4LKNY7T.js → chunk-NCE5NSN6.js} +166 -11
  44. package/dist/chunk-NCE5NSN6.js.map +1 -0
  45. package/dist/{chunk-YG2DO46P.js → chunk-OYMYCW7J.js} +18 -3
  46. package/dist/{chunk-YG2DO46P.js.map → chunk-OYMYCW7J.js.map} +1 -1
  47. package/dist/{chunk-Q53IYV2G.js → chunk-QVX5MWJZ.js} +20 -17
  48. package/dist/{chunk-Q53IYV2G.js.map → chunk-QVX5MWJZ.js.map} +1 -1
  49. package/dist/{chunk-CUWUHTIL.js → chunk-RBJPAZFI.js} +16 -202
  50. package/dist/chunk-RBJPAZFI.js.map +1 -0
  51. package/dist/chunk-RRN3YTK7.js +6199 -0
  52. package/dist/chunk-RRN3YTK7.js.map +1 -0
  53. package/dist/chunk-RY4XKNV3.js +140 -0
  54. package/dist/chunk-RY4XKNV3.js.map +1 -0
  55. package/dist/chunk-S3IGX57D.js +14682 -0
  56. package/dist/chunk-S3IGX57D.js.map +1 -0
  57. package/dist/chunk-TLCMMYLY.js +126 -0
  58. package/dist/chunk-TLCMMYLY.js.map +1 -0
  59. package/dist/{chunk-IPOWB3G3.js → chunk-UGX7YPX2.js} +3314 -306
  60. package/dist/chunk-UGX7YPX2.js.map +1 -0
  61. package/dist/chunk-UO22YNY4.js +69 -0
  62. package/dist/chunk-UO22YNY4.js.map +1 -0
  63. package/dist/{config-CEFTL5ZL.js → chunk-XHC6FCGZ.js} +115 -18
  64. package/dist/chunk-XHC6FCGZ.js.map +1 -0
  65. package/dist/chunk-XJIW7USF.js +217 -0
  66. package/dist/chunk-XJIW7USF.js.map +1 -0
  67. package/dist/{chunk-BRJHUQSY.js → chunk-YALGASEU.js} +1548 -1452
  68. package/dist/chunk-YALGASEU.js.map +1 -0
  69. package/dist/{chunk-DXPYFVKG.js → chunk-YN5SHIXQ.js} +6 -6
  70. package/dist/chunk-YN5SHIXQ.js.map +1 -0
  71. package/dist/chunk-Z52RA3NC.js +137 -0
  72. package/dist/chunk-Z52RA3NC.js.map +1 -0
  73. package/dist/{chunk-EXBMF5X7.js → chunk-Z6MFJV5Z.js} +151 -19
  74. package/dist/chunk-Z6MFJV5Z.js.map +1 -0
  75. package/dist/chunk-ZABPQJ3M.js +83 -0
  76. package/dist/chunk-ZABPQJ3M.js.map +1 -0
  77. package/dist/{chunk-LPRVHM7P.js → chunk-ZMHYXMO6.js} +1655 -114
  78. package/dist/chunk-ZMHYXMO6.js.map +1 -0
  79. package/dist/{chunk-3IMLRUZE.js → chunk-ZXTAQYNA.js} +6 -2
  80. package/dist/{cli-WA65NP4Y.js → cli-SBXI5AMS.js} +177 -177
  81. package/dist/cli-SBXI5AMS.js.map +1 -0
  82. package/dist/config-VELDKLHY.js +21 -0
  83. package/dist/doctor-T3KWNNP2.js +20 -0
  84. package/dist/doctor-T3KWNNP2.js.map +1 -0
  85. package/dist/ethersCompat-Df4a6QIZ-3YZIAXDK.js +18 -0
  86. package/dist/ethersCompat-Df4a6QIZ-3YZIAXDK.js.map +1 -0
  87. package/dist/index.js +1 -1
  88. package/dist/inspect-LMKOXE4Y.js +96 -0
  89. package/dist/inspect-LMKOXE4Y.js.map +1 -0
  90. package/dist/install-LEX3ENQ6.js +387 -0
  91. package/dist/install-LEX3ENQ6.js.map +1 -0
  92. package/dist/lookup-ZPN2Q67N.js +129 -0
  93. package/dist/lookup-ZPN2Q67N.js.map +1 -0
  94. package/dist/{mine.wasm-GQECMSGN.js → mine.wasm-6PIT3DK6.js} +2 -2
  95. package/dist/outcome-N7TAK6WR.js +66 -0
  96. package/dist/outcome-N7TAK6WR.js.map +1 -0
  97. package/dist/publish-BKBQQAW4.js +1478 -0
  98. package/dist/publish-BKBQQAW4.js.map +1 -0
  99. package/dist/{secp256k1-BEZS642W.js → secp256k1-N3ZHU7LB.js} +5 -4
  100. package/dist/secp256k1-N3ZHU7LB.js.map +1 -0
  101. package/dist/usdc-JIAJW4SV.js +22 -0
  102. package/dist/usdc-JIAJW4SV.js.map +1 -0
  103. package/dist/viemAdapter-By13KInL-ZZE6UV4Y.js +14 -0
  104. package/dist/viemAdapter-By13KInL-ZZE6UV4Y.js.map +1 -0
  105. package/dist/{wallet-XD576HF3.js → wallet-CWEMYVIH.js} +28 -22
  106. package/dist/{wallet-XD576HF3.js.map → wallet-CWEMYVIH.js.map} +1 -1
  107. package/dist/wallet-PA54CRBL.js +30 -0
  108. package/dist/wallet-PA54CRBL.js.map +1 -0
  109. package/package.json +19 -16
  110. package/skills/tenjin/SKILL.md +199 -0
  111. package/skills/tenjin-publish/SKILL.md +111 -0
  112. package/skills/tenjin-search/SKILL.md +133 -0
  113. package/dist/chunk-BRJHUQSY.js.map +0 -1
  114. package/dist/chunk-CUWUHTIL.js.map +0 -1
  115. package/dist/chunk-DXPYFVKG.js.map +0 -1
  116. package/dist/chunk-EXBMF5X7.js.map +0 -1
  117. package/dist/chunk-EZROUI5U.js.map +0 -1
  118. package/dist/chunk-IPOWB3G3.js.map +0 -1
  119. package/dist/chunk-LPRVHM7P.js.map +0 -1
  120. package/dist/chunk-T4LKNY7T.js.map +0 -1
  121. package/dist/chunk-ZPZFPVDN.js.map +0 -1
  122. package/dist/cli-WA65NP4Y.js.map +0 -1
  123. package/dist/config-CEFTL5ZL.js.map +0 -1
  124. package/dist/doctor-3QBYN46N.js.map +0 -1
  125. package/dist/usdc-44R6XZF2.js +0 -18
  126. package/dist/wallet-ELBTBMXO.js +0 -20
  127. /package/dist/{_esm-ZH2J4QEH.js.map → _esm-C5CRWCYU.js.map} +0 -0
  128. /package/dist/{ccip-UCLT5VOW.js.map → _esm-FEASBWRP.js.map} +0 -0
  129. /package/dist/{chunk-3IMLRUZE.js.map → ccip-NQXCEK2U.js.map} +0 -0
  130. /package/dist/{secp256k1-BEZS642W.js.map → ccip-XR5NE5QK.js.map} +0 -0
  131. /package/dist/{usdc-44R6XZF2.js.map → chunk-ZXTAQYNA.js.map} +0 -0
  132. /package/dist/{wallet-ELBTBMXO.js.map → config-VELDKLHY.js.map} +0 -0
  133. /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-EZROUI5U.js";
11
+ } from "./chunk-ESPNTPQ2.js";
7
12
  import {
8
13
  walletFileExists
9
- } from "./chunk-DXPYFVKG.js";
10
- import "./chunk-ZPZFPVDN.js";
14
+ } from "./chunk-YN5SHIXQ.js";
11
15
  import {
12
16
  CliError,
13
17
  configPath
14
- } from "./chunk-YG2DO46P.js";
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
- // 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 = {}) {
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 !== void 0) {
108
- const r = firstFail.result;
109
- throw new CliError(firstFail.failCode ?? "INTERNAL", r.detail, {
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: renderHuman(ctx.io, checks) };
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, { timeoutMs, fetchImpl });
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, { timeoutMs, fetchImpl });
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-ELBTBMXO.js");
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-ELBTBMXO.js");
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-44R6XZF2.js");
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 renderHuman(io, checks) {
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
- function trimSlash(url) {
333
- return url.replace(/\/+$/, "");
334
- }
327
+
335
328
  export {
336
- runDoctor
329
+ collectDoctorChecks,
330
+ runDoctor,
331
+ renderDoctorHuman
337
332
  };
338
- //# sourceMappingURL=doctor-3QBYN46N.js.map
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":[]}