@lawrenceliang-btc/atel-sdk 1.1.23 → 1.1.26

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/bin/atel.mjs CHANGED
@@ -51,7 +51,9 @@
51
51
  * atel boost-status [did] Check boost status
52
52
  */
53
53
 
54
- import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
54
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, copyFileSync } from 'node:fs';
55
+ import { fileURLToPath } from 'node:url';
56
+ import { cmdHub } from './hub-helpers.mjs';
55
57
  import { resolve, join, dirname } from 'node:path';
56
58
  import crypto from 'node:crypto';
57
59
  import {
@@ -1202,7 +1204,7 @@ async function signTaskRequest(taskRequest, secretKey) {
1202
1204
  version: taskRequest.version
1203
1205
  });
1204
1206
 
1205
- console.error('[DEBUG] SDK signable JSON:', signable);
1207
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] SDK signable JSON:', signable);
1206
1208
 
1207
1209
  const signature = nacl.sign.detached(Buffer.from(signable), secretKey);
1208
1210
  return Buffer.from(signature).toString('base64');
@@ -2055,8 +2057,38 @@ async function cmdInit(agentId) {
2055
2057
  did: identity.did,
2056
2058
  policy: POLICY_FILE,
2057
2059
  anchor: anchorConfigured ? 'configured' : 'disabled',
2058
- next: 'Run: atel start [port] — auto-configures network and registers'
2060
+ next: 'Run: atel start [port] — auto-configures network and registers'
2059
2061
  }, null, 2));
2062
+
2063
+ // Auto-install SKILL.md to OpenClaw skills directory
2064
+ try {
2065
+ const sdkSkillPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'skill', 'atel-agent', 'SKILL.md');
2066
+ if (existsSync(sdkSkillPath)) {
2067
+ const home = process.env.HOME || '';
2068
+ const candidates = [
2069
+ join(home, '.openclaw', 'skills', 'atel-agent'),
2070
+ join(home, '.openclaw', 'workspace', 'skills', 'atel-agent'),
2071
+ ];
2072
+ let installed = false;
2073
+ for (const dir of candidates) {
2074
+ if (existsSync(dirname(dir))) {
2075
+ mkdirSync(dir, { recursive: true });
2076
+ copyFileSync(sdkSkillPath, join(dir, 'SKILL.md'));
2077
+ console.log(`\n✅ ATEL Skill auto-installed to ${dir}`);
2078
+ console.log(' Your AI agent will automatically know how to use ATEL.');
2079
+ console.log('');
2080
+ console.log('📌 Tell your agent this message to get started:');
2081
+ console.log(' "Read the atel-agent skill at ~/.openclaw/skills/atel-agent/SKILL.md carefully, then help me set up ATEL and start earning."');
2082
+ installed = true;
2083
+ break;
2084
+ }
2085
+ }
2086
+ if (!installed) {
2087
+ console.log('\n📋 To teach your AI agent about ATEL, send it this message:');
2088
+ console.log(` "Read ${sdkSkillPath} carefully, then help me set up ATEL and start earning."`);
2089
+ }
2090
+ }
2091
+ } catch (e) { /* skill install is best-effort */ }
2060
2092
  }
2061
2093
 
2062
2094
  async function cmdAnchor(subcommand) {
@@ -2386,38 +2418,38 @@ async function cmdStart(port) {
2386
2418
 
2387
2419
  async function verifyAnchorFromChain(chain, txHash, traceRoot) {
2388
2420
  try {
2389
- console.error('[DEBUG] verifyAnchorFromChain input:', { chain, txHash, traceRoot });
2421
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] verifyAnchorFromChain input:', { chain, txHash, traceRoot });
2390
2422
 
2391
2423
  if (!txHash || !traceRoot) return { checked: false, verified: false, reason: 'missing_anchor_or_root' };
2392
2424
  const c = (chain || 'solana').toLowerCase();
2393
2425
 
2394
2426
  if (c === 'solana') {
2395
2427
  const rpcUrl = process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
2396
- console.error('[DEBUG] Solana RPC URL:', rpcUrl);
2428
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana RPC URL:', rpcUrl);
2397
2429
  const provider = new SolanaAnchorProvider({ rpcUrl });
2398
2430
  const r = await provider.verify(traceRoot, txHash);
2399
- console.error('[DEBUG] Solana verify result:', r);
2431
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana verify result:', r);
2400
2432
  return { checked: true, verified: !!r?.valid, chain: 'solana', detail: r?.detail };
2401
2433
  }
2402
2434
  if (c === 'base') {
2403
2435
  const rpcUrl = process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org';
2404
- console.error('[DEBUG] Base RPC URL:', rpcUrl);
2436
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Base RPC URL:', rpcUrl);
2405
2437
  const provider = new BaseAnchorProvider({ rpcUrl });
2406
2438
  const r = await provider.verify(traceRoot, txHash);
2407
- console.error('[DEBUG] Base verify result:', r);
2439
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Base verify result:', r);
2408
2440
  return { checked: true, verified: !!r?.valid, chain: 'base', detail: r?.detail };
2409
2441
  }
2410
2442
  if (c === 'bsc') {
2411
2443
  const rpcUrl = process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
2412
- console.error('[DEBUG] BSC RPC URL:', rpcUrl);
2444
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] BSC RPC URL:', rpcUrl);
2413
2445
  const provider = new BSCAnchorProvider({ rpcUrl });
2414
2446
  const r = await provider.verify(traceRoot, txHash);
2415
- console.error('[DEBUG] BSC verify result:', r);
2447
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] BSC verify result:', r);
2416
2448
  return { checked: true, verified: !!r?.valid, chain: 'bsc', detail: r?.detail };
2417
2449
  }
2418
2450
  return { checked: false, verified: false, reason: `unsupported_chain:${c}` };
2419
2451
  } catch (e) {
2420
- console.error('[DEBUG] verifyAnchorFromChain error:', e);
2452
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] verifyAnchorFromChain error:', e);
2421
2453
  return { checked: true, verified: false, reason: e.message };
2422
2454
  }
2423
2455
  }
@@ -5608,8 +5640,8 @@ async function signedFetch(method, path, payload = {}) {
5608
5640
  const sig = Buffer.from(nacl.sign.detached(Buffer.from(signable), id.secretKey)).toString('base64');
5609
5641
  const body = JSON.stringify({ did: id.did, payload, timestamp: ts, signature: sig });
5610
5642
  // Always use POST for signed requests (DIDAuth reads body, GET cannot have body)
5611
- console.error("DEBUG URL:", `${PLATFORM_URL}${path}`); const res = await fetch(`${PLATFORM_URL}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body });
5612
- const text = await res.text(); console.error("DEBUG Response:", text); const data = JSON.parse(text);
5643
+ if (process.env.ATEL_DEBUG) console.error("DEBUG URL:", `${PLATFORM_URL}${path}`); const res = await fetch(`${PLATFORM_URL}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body });
5644
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
5613
5645
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
5614
5646
  return data;
5615
5647
  }
@@ -5914,7 +5946,7 @@ async function cmdOrder(executorDid, capType, price) {
5914
5946
  async function cmdOrderInfo(orderId) {
5915
5947
  if (!orderId) { console.error('Usage: atel order-info <orderId>'); process.exit(1); }
5916
5948
  const res = await fetch(`${PLATFORM_URL}/trade/v1/order/${orderId}`);
5917
- const text = await res.text(); console.error("DEBUG Response:", text); const data = JSON.parse(text);
5949
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
5918
5950
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
5919
5951
  console.log(JSON.stringify(data, null, 2));
5920
5952
  }
@@ -6619,7 +6651,7 @@ async function cmdDisputes() {
6619
6651
  async function cmdDisputeInfo(disputeId) {
6620
6652
  if (!disputeId) { console.error('Usage: atel dispute-info <disputeId>'); process.exit(1); }
6621
6653
  const res = await fetch(`${PLATFORM_URL}/dispute/v1/${disputeId}`);
6622
- const text = await res.text(); console.error("DEBUG Response:", text); const data = JSON.parse(text);
6654
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
6623
6655
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
6624
6656
  console.log(JSON.stringify(data, null, 2));
6625
6657
  }
@@ -6634,7 +6666,7 @@ async function cmdCertApply(level) {
6634
6666
  async function cmdCertStatus(did) {
6635
6667
  const targetDid = did || requireIdentity().did;
6636
6668
  const res = await fetch(`${PLATFORM_URL}/cert/v1/status/${encodeURIComponent(targetDid)}`);
6637
- const text = await res.text(); console.error("DEBUG Response:", text); const data = JSON.parse(text);
6669
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
6638
6670
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
6639
6671
  console.log(JSON.stringify(data, null, 2));
6640
6672
 
@@ -6670,7 +6702,7 @@ async function cmdBoost(tier, weeks) {
6670
6702
  async function cmdBoostStatus(did) {
6671
6703
  const targetDid = did || requireIdentity().did;
6672
6704
  const res = await fetch(`${PLATFORM_URL}/boost/v1/status/${encodeURIComponent(targetDid)}`);
6673
- const text = await res.text(); console.error("DEBUG Response:", text); const data = JSON.parse(text);
6705
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
6674
6706
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
6675
6707
  console.log(JSON.stringify(data, null, 2));
6676
6708
  }
@@ -8100,6 +8132,7 @@ const commands = {
8100
8132
  // Auth (Dashboard authorization code login)
8101
8133
  auth: () => cmdAuth(args[0]),
8102
8134
  // Send (Rich Media P2P Message)
8135
+ hub: () => cmdHub(args[0], args.slice(1), rawArgs),
8103
8136
  send: () => {
8104
8137
  if (rawArgs.includes('--help') || rawArgs.includes('-h') || args.length === 0) {
8105
8138
  showSendHelp();
@@ -8305,6 +8338,15 @@ Account Commands:
8305
8338
  withdraw <amount> [channel] [address] Withdraw funds (address required for crypto)
8306
8339
  transactions List payment history
8307
8340
 
8341
+ Hub Commands:
8342
+ hub balance Show ATELToken balance
8343
+ hub usage [--model <id>] [--days 7] Usage history
8344
+ hub topup Top-up instructions
8345
+ hub swap <usdc> [--chain bsc|base] Swap USDC → ATELToken
8346
+ hub models [--search <kw>] List available models
8347
+ hub chat <model> "<prompt>" [--stream] Quick chat
8348
+ hub key <create|list|revoke|use> Manage TokenHub API keys
8349
+
8308
8350
  Trade Commands:
8309
8351
  trade-task <cap> <desc> [--budget N] One-shot: search → order → wait → confirm (requester)
8310
8352
  order <executorDid> <cap> <price> Create a trade order
@@ -0,0 +1,348 @@
1
+ // hub-helpers.mjs — atel hub command implementation
2
+ // No new npm dependencies: uses native fetch, fs, crypto
3
+
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { createReadStream } from 'fs';
7
+
8
+ const HUB_CONFIG_PATH = join(process.env.HOME || '/root', '.atel', 'hub.json');
9
+ const DEFAULT_BASE = 'https://api.atelai.org/tokenhub/v1';
10
+
11
+ // ─── Config ──────────────────────────────────────────────────────
12
+
13
+ function getConfig() {
14
+ const envKey = process.env.ATEL_HUB_KEY;
15
+ const envBase = process.env.ATEL_HUB_BASE || DEFAULT_BASE;
16
+ if (envKey) return { key: envKey, base: envBase };
17
+ if (!existsSync(HUB_CONFIG_PATH)) {
18
+ throw new Error('No hub API key configured. Run: atel hub key create');
19
+ }
20
+ try {
21
+ return JSON.parse(readFileSync(HUB_CONFIG_PATH, 'utf-8'));
22
+ } catch {
23
+ throw new Error('Corrupted hub config. Run: atel hub key create');
24
+ }
25
+ }
26
+
27
+ function saveConfig(key, base) {
28
+ const dir = join(process.env.HOME || '/root', '.atel');
29
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
30
+ writeFileSync(
31
+ HUB_CONFIG_PATH,
32
+ JSON.stringify({ key, base: base || DEFAULT_BASE }),
33
+ { mode: 0o600 }
34
+ );
35
+ }
36
+
37
+ // ─── HTTP ────────────────────────────────────────────────────────
38
+
39
+ async function hubFetch(path, options = {}) {
40
+ const cfg = getConfig();
41
+ const url = cfg.base + path;
42
+ const res = await fetch(url, {
43
+ ...options,
44
+ headers: {
45
+ Authorization: 'Bearer ' + cfg.key,
46
+ 'Content-Type': 'application/json',
47
+ ...(options.headers || {}),
48
+ },
49
+ });
50
+ if (!res.ok) {
51
+ let errBody = {};
52
+ try { errBody = await res.json(); } catch {}
53
+ const code = errBody?.error?.code || 'ERROR';
54
+ const msg = errBody?.error?.message || res.statusText;
55
+ const err = new Error(`[${code}] ${msg}`);
56
+ err.httpStatus = res.status;
57
+ err.code = code;
58
+ if (errBody?.error?.action === 'topup') {
59
+ err.hint = 'Run: atel hub topup';
60
+ }
61
+ throw err;
62
+ }
63
+ return res;
64
+ }
65
+
66
+ // ─── Commands ────────────────────────────────────────────────────
67
+
68
+ async function cmdHubSwap(usdcAmount, flags) {
69
+ if (!usdcAmount || isNaN(parseFloat(usdcAmount))) {
70
+ console.error('Usage: atel hub swap <usdc_amount> [--chain bsc|base]');
71
+ console.error('Example: atel hub swap 1.0 --chain bsc');
72
+ process.exit(1);
73
+ }
74
+ const amount = parseFloat(usdcAmount);
75
+ const chain = flags.chain || 'bsc';
76
+
77
+ // swap via platform (not tokenhub)
78
+ const { loadIdentity } = await import('./atel.mjs');
79
+ const id = loadIdentity();
80
+ const PLATFORM_URL = process.env.ATEL_PLATFORM || process.env.ATEL_REGISTRY || 'https://api.atelai.org';
81
+
82
+ // Use signedFetch from atel.mjs
83
+ const body = JSON.stringify({ usdc_amount: amount, chain });
84
+ const res = await fetch(PLATFORM_URL + '/account/v1/swap', {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body,
88
+ signal: AbortSignal.timeout(10000),
89
+ });
90
+ const data = await res.json();
91
+ if (!res.ok) {
92
+ console.error('Swap failed:', data.error || JSON.stringify(data));
93
+ process.exit(1);
94
+ }
95
+ const tokenPerUSDC = data.token_per_usdc || 10000;
96
+ console.log('✓ Swap successful');
97
+ console.log(` USDC spent: $${amount.toFixed(6)}`);
98
+ console.log(` ATELToken received: ${data.token_amount.toLocaleString()}`);
99
+ console.log(` Rate: 1 USDC = ${tokenPerUSDC.toLocaleString()} ATEL`);
100
+ console.log(` Platform balance after: $${parseFloat(data.balance_after).toFixed(6)} USDC`);
101
+ console.log('');
102
+ console.log('Run `atel hub balance` to check your ATELToken balance.');
103
+ }
104
+
105
+ async function cmdHubBalance() {
106
+ const res = await hubFetch('/balance');
107
+ const data = await res.json();
108
+ console.log(`ATELToken Balance: ${data.balance.toLocaleString()}`);
109
+ console.log(`USDC Equivalent: $${data.usdc_equiv}`);
110
+ if (data.overdraft) {
111
+ console.warn('⚠ Account is in overdraft. Top up to resume service.');
112
+ }
113
+ }
114
+
115
+ async function cmdHubUsage(flags) {
116
+ const params = new URLSearchParams();
117
+ const modelIdx = flags.indexOf('--model');
118
+ if (modelIdx !== -1 && flags[modelIdx + 1]) params.set('model', flags[modelIdx + 1]);
119
+ const daysIdx = flags.indexOf('--days');
120
+ if (daysIdx !== -1 && flags[daysIdx + 1]) {
121
+ const days = parseInt(flags[daysIdx + 1], 10);
122
+ const start = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
123
+ params.set('start', start);
124
+ }
125
+ const res = await hubFetch('/usage?' + params.toString());
126
+ const data = await res.json();
127
+ console.log(`Usage (${data.total} total, showing page ${data.page}):`);
128
+ if (data.items.length === 0) {
129
+ console.log(' No usage records found.');
130
+ return;
131
+ }
132
+ console.log(' Time Model In Out Cost');
133
+ console.log(' ' + '-'.repeat(80));
134
+ for (const item of data.items) {
135
+ const t = new Date(item.created_at).toLocaleString();
136
+ const model = item.model_id.padEnd(30);
137
+ console.log(` ${t} ${model} ${String(item.tokens_in).padStart(6)} ${String(item.tokens_out).padStart(6)} ${item.cost}`);
138
+ }
139
+ const totalCost = data.items.reduce((s, i) => s + i.cost, 0);
140
+ console.log(' ' + '-'.repeat(80));
141
+ console.log(` Total cost this page: ${totalCost} ATELToken`);
142
+ }
143
+
144
+ async function cmdHubTopup() {
145
+ // Show topup instructions (chain-based, no automation yet)
146
+ const res = await hubFetch('/balance');
147
+ const data = await res.json();
148
+ console.log('Current balance: ' + data.balance.toLocaleString() + ' ATELToken ($' + data.usdc_equiv + ')');
149
+ console.log('');
150
+ console.log('To top up, send USDC to your ATEL deposit address.');
151
+ console.log('Exchange rate: 1 USDC = 10,000 ATELToken');
152
+ console.log('');
153
+ console.log('Contact your platform admin or visit https://atelai.org/topup for instructions.');
154
+ }
155
+
156
+ async function cmdHubKeyCreate(flags) {
157
+ const nameIdx = flags.indexOf('--name');
158
+ const name = nameIdx !== -1 && flags[nameIdx + 1] ? flags[nameIdx + 1] : 'default';
159
+ const res = await hubFetch('/apikeys', {
160
+ method: 'POST',
161
+ body: JSON.stringify({ name }),
162
+ });
163
+ const data = await res.json();
164
+ console.log('API Key created:');
165
+ console.log(' ' + data.key);
166
+ console.log('');
167
+ console.log('This key will NOT be shown again. Save it securely.');
168
+ console.log('Saving to ~/.atel/hub.json ...');
169
+ const cfg = getConfig();
170
+ saveConfig(data.key, cfg.base);
171
+ console.log('Saved. You can now use atel hub commands.');
172
+ }
173
+
174
+ async function cmdHubKeyList() {
175
+ const res = await hubFetch('/apikeys');
176
+ const data = await res.json();
177
+ const keys = data.keys || [];
178
+ if (keys.length === 0) {
179
+ console.log('No API keys found.');
180
+ return;
181
+ }
182
+ console.log(`API Keys (${keys.length}):`);
183
+ for (const k of keys) {
184
+ const revoked = k.Revoked || k.revoked;
185
+ const status = revoked ? '[revoked]' : '[active] ';
186
+ const created = new Date(k.CreatedAt || k.created_at).toLocaleDateString();
187
+ const id = k.ID || k.id;
188
+ const prefix = k.KeyPrefix || k.key_prefix || '';
189
+ const name = k.Name || k.name;
190
+ console.log(` ${status} id=${id} prefix=${prefix}... name=${name} created=${created}`);
191
+ }
192
+ }
193
+
194
+ async function cmdHubKeyRevoke(id) {
195
+ if (!id) {
196
+ console.error('Usage: atel hub key revoke <id>');
197
+ process.exit(1);
198
+ }
199
+ await hubFetch(`/apikeys/${id}`, { method: 'DELETE' });
200
+ console.log(`Key ${id} revoked successfully.`);
201
+ }
202
+
203
+ async function cmdHubKeyUse() {
204
+ const cfg = getConfig();
205
+ console.log(`export OPENAI_BASE_URL=${cfg.base}`);
206
+ console.log(`export OPENAI_API_KEY=${cfg.key}`);
207
+ console.log('');
208
+ console.log('# Tip: eval $(atel hub key use) to apply in current shell');
209
+ }
210
+
211
+ async function cmdHubModels(flags) {
212
+ const searchIdx = flags.indexOf('--search');
213
+ const keyword = searchIdx !== -1 && flags[searchIdx + 1] ? flags[searchIdx + 1].toLowerCase() : '';
214
+ const res = await hubFetch('/models');
215
+ const data = await res.json();
216
+ let models = data.models || [];
217
+ if (keyword) {
218
+ models = models.filter(m => m.model_id.toLowerCase().includes(keyword));
219
+ }
220
+ if (models.length === 0) {
221
+ console.log('No models found.');
222
+ return;
223
+ }
224
+ console.log(`Available Models (${models.length}):`);
225
+ console.log(' Model ID Rate Cost/1M tokens');
226
+ console.log(' ' + '-'.repeat(75));
227
+ for (const m of models) {
228
+ const rate = (m.multiplier_milli / 1000).toFixed(3) + 'x';
229
+ const costUSD = '$' + (m.multiplier_milli / 10).toFixed(2);
230
+ console.log(` ${m.model_id.padEnd(50)} ${rate.padStart(7)} ${costUSD}`);
231
+ }
232
+ }
233
+
234
+ async function cmdHubChat(model, prompt, flags) {
235
+ if (!model || !prompt) {
236
+ console.error('Usage: atel hub chat <model> "<prompt>" [--stream]');
237
+ process.exit(1);
238
+ }
239
+ const stream = flags.includes('--stream');
240
+ const cfg = getConfig();
241
+ const body = JSON.stringify({
242
+ model,
243
+ messages: [{ role: 'user', content: prompt }],
244
+ stream,
245
+ });
246
+
247
+ const res = await fetch(cfg.base + '/chat/completions', {
248
+ method: 'POST',
249
+ headers: {
250
+ Authorization: 'Bearer ' + cfg.key,
251
+ 'Content-Type': 'application/json',
252
+ },
253
+ body,
254
+ });
255
+
256
+ if (!res.ok) {
257
+ let errBody = {};
258
+ try { errBody = await res.json(); } catch {}
259
+ const msg = errBody?.error?.message || res.statusText;
260
+ const code = errBody?.error?.code || 'ERROR';
261
+ console.error(`[${code}] ${msg}`);
262
+ if (errBody?.error?.action === 'topup') console.error('Run: atel hub topup');
263
+ process.exit(1);
264
+ }
265
+
266
+ if (!stream) {
267
+ const data = await res.json();
268
+ const content = data?.choices?.[0]?.message?.content || '';
269
+ console.log(content);
270
+ if (data.usage) {
271
+ const cost = res.headers.get('X-ATELToken-Cost');
272
+ console.error(`\n[tokens: in=${data.usage.prompt_tokens} out=${data.usage.completion_tokens}${cost ? ' cost=' + cost : ''}]`);
273
+ }
274
+ return;
275
+ }
276
+
277
+ // Streaming: parse SSE
278
+ const reader = res.body.getReader();
279
+ const decoder = new TextDecoder();
280
+ let buf = '';
281
+ while (true) {
282
+ const { done, value } = await reader.read();
283
+ if (done) break;
284
+ buf += decoder.decode(value, { stream: true });
285
+ const lines = buf.split('\n');
286
+ buf = lines.pop(); // keep incomplete line
287
+ for (const line of lines) {
288
+ if (!line.startsWith('data: ')) continue;
289
+ const data = line.slice(6).trim();
290
+ if (data === '[DONE]') { process.stdout.write('\n'); return; }
291
+ try {
292
+ const chunk = JSON.parse(data);
293
+ const delta = chunk?.choices?.[0]?.delta?.content;
294
+ if (delta) process.stdout.write(delta);
295
+ } catch {}
296
+ }
297
+ }
298
+ }
299
+
300
+ // ─── Router ──────────────────────────────────────────────────────
301
+
302
+ export async function cmdHub(sub, args, rawArgs) {
303
+ const flags = rawArgs || [];
304
+ try {
305
+ switch (sub) {
306
+ case 'balance': return await cmdHubBalance();
307
+ case 'swap': return await cmdHubSwap(args[0], flags);
308
+ case 'usage': return await cmdHubUsage(flags);
309
+ case 'topup': return await cmdHubTopup();
310
+ case 'models': return await cmdHubModels(flags);
311
+ case 'chat': return await cmdHubChat(args[0], args[1], flags);
312
+ case 'key': {
313
+ const keySub = args[0];
314
+ const keyFlags = flags;
315
+ switch (keySub) {
316
+ case 'create': return await cmdHubKeyCreate(keyFlags);
317
+ case 'list': return await cmdHubKeyList();
318
+ case 'revoke': return await cmdHubKeyRevoke(args[1]);
319
+ case 'use': return await cmdHubKeyUse();
320
+ default:
321
+ console.log('Usage: atel hub key <create|list|revoke|use>');
322
+ process.exit(1);
323
+ }
324
+ break;
325
+ }
326
+ default:
327
+ console.log(`
328
+ atel hub — ATELToken management
329
+
330
+ Commands:
331
+ atel hub balance Show ATELToken balance
332
+ atel hub usage [--model <id>] [--days 7] Usage history
333
+ atel hub topup Top-up instructions
334
+ atel hub swap <usdc> [--chain bsc|base] Swap USDC → ATELToken
335
+ atel hub models [--search <kw>] List available models
336
+ atel hub chat <model> "<prompt>" [--stream] Quick chat
337
+ atel hub key create [--name <name>] Create API key
338
+ atel hub key list List API keys
339
+ atel hub key revoke <id> Revoke a key
340
+ atel hub key use Output env vars for external tools
341
+ `);
342
+ }
343
+ } catch (err) {
344
+ console.error(err.message);
345
+ if (err.hint) console.error(err.hint);
346
+ process.exit(1);
347
+ }
348
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawrenceliang-btc/atel-sdk",
3
- "version": "1.1.23",
3
+ "version": "1.1.26",
4
4
  "description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
5
5
  "repository": {
6
6
  "type": "git",