@lawrenceliang-btc/atel-sdk 1.1.23 → 1.1.25

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,35 @@ 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
+ installed = true;
2080
+ break;
2081
+ }
2082
+ }
2083
+ if (!installed) {
2084
+ console.log('\n📋 To teach your AI agent about ATEL, send it this file:');
2085
+ console.log(` ${sdkSkillPath}`);
2086
+ }
2087
+ }
2088
+ } catch (e) { /* skill install is best-effort */ }
2060
2089
  }
2061
2090
 
2062
2091
  async function cmdAnchor(subcommand) {
@@ -2386,38 +2415,38 @@ async function cmdStart(port) {
2386
2415
 
2387
2416
  async function verifyAnchorFromChain(chain, txHash, traceRoot) {
2388
2417
  try {
2389
- console.error('[DEBUG] verifyAnchorFromChain input:', { chain, txHash, traceRoot });
2418
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] verifyAnchorFromChain input:', { chain, txHash, traceRoot });
2390
2419
 
2391
2420
  if (!txHash || !traceRoot) return { checked: false, verified: false, reason: 'missing_anchor_or_root' };
2392
2421
  const c = (chain || 'solana').toLowerCase();
2393
2422
 
2394
2423
  if (c === 'solana') {
2395
2424
  const rpcUrl = process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
2396
- console.error('[DEBUG] Solana RPC URL:', rpcUrl);
2425
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana RPC URL:', rpcUrl);
2397
2426
  const provider = new SolanaAnchorProvider({ rpcUrl });
2398
2427
  const r = await provider.verify(traceRoot, txHash);
2399
- console.error('[DEBUG] Solana verify result:', r);
2428
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana verify result:', r);
2400
2429
  return { checked: true, verified: !!r?.valid, chain: 'solana', detail: r?.detail };
2401
2430
  }
2402
2431
  if (c === 'base') {
2403
2432
  const rpcUrl = process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org';
2404
- console.error('[DEBUG] Base RPC URL:', rpcUrl);
2433
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Base RPC URL:', rpcUrl);
2405
2434
  const provider = new BaseAnchorProvider({ rpcUrl });
2406
2435
  const r = await provider.verify(traceRoot, txHash);
2407
- console.error('[DEBUG] Base verify result:', r);
2436
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] Base verify result:', r);
2408
2437
  return { checked: true, verified: !!r?.valid, chain: 'base', detail: r?.detail };
2409
2438
  }
2410
2439
  if (c === 'bsc') {
2411
2440
  const rpcUrl = process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
2412
- console.error('[DEBUG] BSC RPC URL:', rpcUrl);
2441
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] BSC RPC URL:', rpcUrl);
2413
2442
  const provider = new BSCAnchorProvider({ rpcUrl });
2414
2443
  const r = await provider.verify(traceRoot, txHash);
2415
- console.error('[DEBUG] BSC verify result:', r);
2444
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] BSC verify result:', r);
2416
2445
  return { checked: true, verified: !!r?.valid, chain: 'bsc', detail: r?.detail };
2417
2446
  }
2418
2447
  return { checked: false, verified: false, reason: `unsupported_chain:${c}` };
2419
2448
  } catch (e) {
2420
- console.error('[DEBUG] verifyAnchorFromChain error:', e);
2449
+ if (process.env.ATEL_DEBUG) console.error('[DEBUG] verifyAnchorFromChain error:', e);
2421
2450
  return { checked: true, verified: false, reason: e.message };
2422
2451
  }
2423
2452
  }
@@ -5608,8 +5637,8 @@ async function signedFetch(method, path, payload = {}) {
5608
5637
  const sig = Buffer.from(nacl.sign.detached(Buffer.from(signable), id.secretKey)).toString('base64');
5609
5638
  const body = JSON.stringify({ did: id.did, payload, timestamp: ts, signature: sig });
5610
5639
  // 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);
5640
+ 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 });
5641
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
5613
5642
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
5614
5643
  return data;
5615
5644
  }
@@ -5914,7 +5943,7 @@ async function cmdOrder(executorDid, capType, price) {
5914
5943
  async function cmdOrderInfo(orderId) {
5915
5944
  if (!orderId) { console.error('Usage: atel order-info <orderId>'); process.exit(1); }
5916
5945
  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);
5946
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
5918
5947
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
5919
5948
  console.log(JSON.stringify(data, null, 2));
5920
5949
  }
@@ -6619,7 +6648,7 @@ async function cmdDisputes() {
6619
6648
  async function cmdDisputeInfo(disputeId) {
6620
6649
  if (!disputeId) { console.error('Usage: atel dispute-info <disputeId>'); process.exit(1); }
6621
6650
  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);
6651
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
6623
6652
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
6624
6653
  console.log(JSON.stringify(data, null, 2));
6625
6654
  }
@@ -6634,7 +6663,7 @@ async function cmdCertApply(level) {
6634
6663
  async function cmdCertStatus(did) {
6635
6664
  const targetDid = did || requireIdentity().did;
6636
6665
  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);
6666
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
6638
6667
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
6639
6668
  console.log(JSON.stringify(data, null, 2));
6640
6669
 
@@ -6670,7 +6699,7 @@ async function cmdBoost(tier, weeks) {
6670
6699
  async function cmdBoostStatus(did) {
6671
6700
  const targetDid = did || requireIdentity().did;
6672
6701
  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);
6702
+ const text = await res.text(); if (process.env.ATEL_DEBUG) console.error("DEBUG Response:", text); const data = JSON.parse(text);
6674
6703
  if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
6675
6704
  console.log(JSON.stringify(data, null, 2));
6676
6705
  }
@@ -8100,6 +8129,7 @@ const commands = {
8100
8129
  // Auth (Dashboard authorization code login)
8101
8130
  auth: () => cmdAuth(args[0]),
8102
8131
  // Send (Rich Media P2P Message)
8132
+ hub: () => cmdHub(args[0], args.slice(1), rawArgs),
8103
8133
  send: () => {
8104
8134
  if (rawArgs.includes('--help') || rawArgs.includes('-h') || args.length === 0) {
8105
8135
  showSendHelp();
@@ -8305,6 +8335,15 @@ Account Commands:
8305
8335
  withdraw <amount> [channel] [address] Withdraw funds (address required for crypto)
8306
8336
  transactions List payment history
8307
8337
 
8338
+ Hub Commands:
8339
+ hub balance Show ATELToken balance
8340
+ hub usage [--model <id>] [--days 7] Usage history
8341
+ hub topup Top-up instructions
8342
+ hub swap <usdc> [--chain bsc|base] Swap USDC → ATELToken
8343
+ hub models [--search <kw>] List available models
8344
+ hub chat <model> "<prompt>" [--stream] Quick chat
8345
+ hub key <create|list|revoke|use> Manage TokenHub API keys
8346
+
8308
8347
  Trade Commands:
8309
8348
  trade-task <cap> <desc> [--budget N] One-shot: search → order → wait → confirm (requester)
8310
8349
  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.25",
4
4
  "description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
5
5
  "repository": {
6
6
  "type": "git",