@obol/cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/obol.js CHANGED
@@ -54,7 +54,7 @@ async function main() {
54
54
  console.log(`\n ${pc.green('Token refreshed!')} Expires: ${pc.dim(new Date(data.expires_at * 1000).toLocaleDateString())}`);
55
55
  }
56
56
  } catch {
57
- // Refresh failedshow existing token anyway
57
+ console.log(`\n ${pc.yellow('Could not refresh token showing current token (it may be expired).')}`);
58
58
  }
59
59
  console.log(`\n ${pc.bold('Your Agent Token')}`);
60
60
  console.log(` ${pc.dim('Agent:')} ${pc.green(env.OBOL_AGENT_ID || '(unknown)')}`);
@@ -133,7 +133,7 @@ async function main() {
133
133
  console.log(` Signup bonuses: ${pc.green('$' + data.total_signup_bonuses_earned_usdc.toFixed(2))}`);
134
134
  console.log(` Total earned: ${pc.green(pc.bold('$' + data.total_earned_usdc.toFixed(2)))}`);
135
135
  if (data.own_bonus_status === 'pending') {
136
- console.log(`\n ${pc.yellow('Your signup bonus is pending — make your first API call to unlock it!')}`);
136
+ console.log(`\n ${pc.yellow('Your signup bonus is pending — make your first paid execution or buy credits to unlock it!')}`);
137
137
  }
138
138
  console.log();
139
139
  } catch (err) {
@@ -175,8 +175,10 @@ async function main() {
175
175
  const balStr = '$' + (bal.balance_usdc || 0).toFixed(2);
176
176
  const color = bal.balance_usdc > 0 ? pc.green : pc.yellow;
177
177
  console.log(` ${color(bal.balance_usdc > 0 ? '✓' : '!')} Balance ${color(balStr)} USDC`);
178
- if (bal.balance_usdc === 0) {
178
+ if (bal.balance_usdc === 0 && (env.OBOL_PAYMENT_MODE || '').toLowerCase() !== 'x402') {
179
179
  console.log(`\n ${pc.yellow('Buy credits to start executing:')} ${pc.green('npx @obol/cli credits buy')}`);
180
+ } else if ((env.OBOL_PAYMENT_MODE || '').toLowerCase() === 'x402') {
181
+ console.log(`\n ${pc.green('Automatic agent payments are enabled via x402.')}`);
180
182
  }
181
183
  } catch {
182
184
  console.log(` ${pc.dim('-')} Balance ${pc.dim('could not fetch')}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obol/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for the Obol Agent Execution Gateway",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -45,10 +45,11 @@ export async function checkHealth(gatewayUrl) {
45
45
  return request(`${gatewayUrl}/healthz`);
46
46
  }
47
47
 
48
- export async function signup(gatewayUrl, agentId, services, referralCode) {
48
+ export async function signup(gatewayUrl, agentId, services, referralCode, setupKey) {
49
49
  const body = { agent_id: agentId };
50
50
  if (services && services.length > 0) body.services = services;
51
51
  if (referralCode) body.referral_code = referralCode;
52
+ if (setupKey) body.setup_key = setupKey;
52
53
  return request(`${gatewayUrl}/v1/signup`, {
53
54
  method: 'POST',
54
55
  headers: { 'Content-Type': 'application/json' },
@@ -119,11 +120,13 @@ export async function createCheckout(gatewayUrl, jwt, amountUsd) {
119
120
  });
120
121
  }
121
122
 
122
- export async function createWebCheckout(gatewayUrl, agentId, amountUsd) {
123
+ export async function createWebCheckout(gatewayUrl, agentId, amountUsd, setupKey) {
124
+ const body = { agent_id: agentId, amount_usdc: amountUsd };
125
+ if (setupKey) body.setup_key = setupKey;
123
126
  return request(`${gatewayUrl}/v1/credits/web-checkout`, {
124
127
  method: 'POST',
125
128
  headers: { 'Content-Type': 'application/json' },
126
- body: JSON.stringify({ agent_id: agentId, amount_usdc: amountUsd }),
129
+ body: JSON.stringify(body),
127
130
  });
128
131
  }
129
132
 
package/src/config.js CHANGED
@@ -11,6 +11,9 @@ const OBOL_KEYS = [
11
11
  'OBOL_GATEWAY_URL',
12
12
  'OBOL_AGENT_ID',
13
13
  'OBOL_JWT_TOKEN',
14
+ 'OBOL_SETUP_KEY',
15
+ 'OBOL_PAYMENT_MODE',
16
+ 'OBOL_X402_PRIVATE_KEY',
14
17
  'OBOL_WALLET_ADDRESS',
15
18
  ];
16
19
 
@@ -29,6 +32,10 @@ export function loadEnv(path) {
29
32
  // Strip quotes
30
33
  if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
31
34
  val = val.slice(1, -1);
35
+ } else {
36
+ // Strip inline comments for unquoted values
37
+ const hashIdx = val.indexOf(' #');
38
+ if (hashIdx !== -1) val = val.slice(0, hashIdx).trim();
32
39
  }
33
40
  env[key] = val;
34
41
  }
package/src/credits.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
- import { getTiers, createWebCheckout } from './api.js';
3
+ import { createCheckout, getTiers } from './api.js';
4
4
  import { loadEnv } from './config.js';
5
5
 
6
6
  export async function runCreditsBuy() {
7
7
  const env = loadEnv();
8
8
 
9
- if (!env.OBOL_GATEWAY_URL || !env.OBOL_AGENT_ID) {
9
+ if (!env.OBOL_GATEWAY_URL || !env.OBOL_AGENT_ID || !env.OBOL_JWT_TOKEN) {
10
10
  console.log();
11
11
  p.log.warn('No agent configured yet.');
12
12
  p.log.info(`Run ${pc.green('npx @obol/cli setup')} first.`);
13
13
  console.log();
14
- return;
14
+ process.exit(1);
15
15
  }
16
16
 
17
17
  const url = env.OBOL_GATEWAY_URL;
@@ -51,7 +51,7 @@ export async function runCreditsBuy() {
51
51
  // Create checkout session (returns a Stripe checkout URL)
52
52
  s.start('Creating Stripe checkout...');
53
53
  try {
54
- const result = await createWebCheckout(url, env.OBOL_AGENT_ID, tier.price_usd);
54
+ const result = await createCheckout(url, env.OBOL_JWT_TOKEN, tier.price_usd);
55
55
  s.stop(pc.green('Checkout ready!'));
56
56
 
57
57
  console.log();
package/src/setup.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { existsSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import * as p from '@clack/prompts';
5
5
  import pc from 'picocolors';
6
- import { checkHealth, signup, storeBYOK, validateReferralCode, listEndpoints } from './api.js';
7
- import { saveEnv } from './config.js';
6
+ import { checkHealth, listConnections, listEndpoints, signup, storeBYOK, validateReferralCode } from './api.js';
7
+ import { loadEnv, saveEnv } from './config.js';
8
8
 
9
9
  const cheers = ['Nice!', 'Boom!', "Let's go.", 'Easy.', 'Done.'];
10
10
  const cheer = () => cheers[Math.floor(Math.random() * cheers.length)];
11
+ const PYTHON_SDK_INSTALL_CMD = 'python3 -m pip install "git+https://github.com/kineticSum/obol-gateway.git#subdirectory=sdk/python"';
11
12
 
12
13
  export async function runSetup() {
13
14
  console.log();
@@ -16,6 +17,7 @@ export async function runSetup() {
16
17
  p.log.info("Let's connect your agent to Obol. Takes about 60 seconds.");
17
18
 
18
19
  const url = 'https://www.obolagents.com';
20
+ const existingEnv = loadEnv();
19
21
 
20
22
  // Verify connectivity
21
23
  const healthSpin = p.spinner();
@@ -63,7 +65,7 @@ export async function runSetup() {
63
65
  valSpin.start('Checking referral code...');
64
66
  try {
65
67
  const validation = await validateReferralCode(url, referralCode.trim());
66
- valSpin.stop(pc.green(`Referral code valid! You'll get $${validation.signup_bonus_usdc} in credits after your first API call.`));
68
+ valSpin.stop(pc.green(`Referral code valid! You'll get $${validation.signup_bonus_usdc} in credits after your first paid execution or Stripe purchase.`));
67
69
  } catch (err) {
68
70
  valSpin.stop(pc.yellow('That referral code isn\'t valid — continuing without it.'));
69
71
  referralCode = undefined;
@@ -122,11 +124,87 @@ export async function runSetup() {
122
124
  }
123
125
  }
124
126
 
127
+ // --- Project Language ---
128
+ let lang = detectProject();
129
+ if (!lang) {
130
+ lang = await p.select({
131
+ message: 'What language is your agent built in?',
132
+ options: [
133
+ { value: 'js', label: 'JavaScript / TypeScript' },
134
+ { value: 'python', label: 'Python' },
135
+ ],
136
+ });
137
+ if (p.isCancel(lang)) return cancelled();
138
+ }
139
+
140
+ // --- Payment Mode ---
141
+ let paymentMode = existingEnv.OBOL_PAYMENT_MODE || 'credits';
142
+ let x402PrivateKey = existingEnv.OBOL_X402_PRIVATE_KEY || '';
143
+ let walletAddress = existingEnv.OBOL_WALLET_ADDRESS || '';
144
+
145
+ if (lang === 'js') {
146
+ paymentMode = await p.select({
147
+ message: 'How should your agent pay for Obol calls?',
148
+ options: [
149
+ {
150
+ value: 'x402',
151
+ label: 'Automatic USDC payments (recommended)',
152
+ hint: 'Best for autonomous agents with a Base wallet',
153
+ },
154
+ {
155
+ value: 'credits',
156
+ label: 'Credits via Stripe',
157
+ hint: 'Buy a balance with a credit card and spend from it',
158
+ },
159
+ ],
160
+ initialValue: x402PrivateKey ? 'x402' : paymentMode,
161
+ });
162
+ if (p.isCancel(paymentMode)) return cancelled();
163
+
164
+ if (paymentMode === 'x402') {
165
+ p.log.info('Agent payments use a Base wallet with USDC. Your agent pays per call automatically.');
166
+ const privateKey = await p.password({
167
+ message: 'Paste your agent wallet private key',
168
+ validate: (val) => {
169
+ const trimmed = (val || '').trim();
170
+ if (!trimmed) return 'A private key is required to enable automatic agent payments.';
171
+ if (!/^0x[a-fA-F0-9]{64}$/.test(trimmed)) {
172
+ return 'Use a 0x-prefixed 32-byte private key.';
173
+ }
174
+ },
175
+ });
176
+ if (p.isCancel(privateKey)) return cancelled();
177
+ x402PrivateKey = privateKey.trim();
178
+
179
+ const publicAddress = await p.text({
180
+ message: `Optional: enter the wallet's public address ${pc.dim('(shown in status output)')}`,
181
+ placeholder: existingEnv.OBOL_WALLET_ADDRESS || '0x...',
182
+ validate: (val) => {
183
+ const trimmed = (val || '').trim();
184
+ if (!trimmed) return;
185
+ if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
186
+ return 'Use a valid 0x-prefixed EVM address, or leave blank.';
187
+ }
188
+ },
189
+ });
190
+ if (p.isCancel(publicAddress)) return cancelled();
191
+ walletAddress = (publicAddress || '').trim();
192
+ } else {
193
+ x402PrivateKey = '';
194
+ walletAddress = '';
195
+ }
196
+ } else {
197
+ paymentMode = 'credits';
198
+ x402PrivateKey = '';
199
+ walletAddress = '';
200
+ p.log.info('Python onboarding uses credits by default today. Automatic x402 agent payments are available in the JavaScript SDK.');
201
+ }
202
+
125
203
  // --- Confirm ---
126
204
  const keyCount = Object.keys(apiKeys).length;
127
205
  const confirmMsg = keyCount
128
- ? `Create Obol account ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''} and ${keyCount} API key${keyCount > 1 ? 's' : ''}?`
129
- : `Create Obol account ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''}?`;
206
+ ? `Create Obol account ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''}, ${keyCount} API key${keyCount > 1 ? 's' : ''}, and ${paymentMode === 'x402' ? 'automatic agent payments' : 'credits'}?`
207
+ : `Create Obol account ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''} and ${paymentMode === 'x402' ? 'automatic agent payments' : 'credits'}?`;
130
208
  const confirmed = await p.confirm({ message: confirmMsg });
131
209
  if (p.isCancel(confirmed) || !confirmed) return cancelled();
132
210
 
@@ -137,20 +215,29 @@ export async function runSetup() {
137
215
  // 1. Sign up
138
216
  s.start('Creating your account...');
139
217
  let jwt;
140
- let expiresAt;
218
+ let setupKey = existingEnv.OBOL_AGENT_ID === agentId ? existingEnv.OBOL_SETUP_KEY || '' : '';
141
219
  try {
142
- const result = await signup(url, agentId, services, referralCode?.trim() || undefined);
220
+ const result = await signup(url, agentId, services, referralCode?.trim() || undefined, setupKey);
143
221
  jwt = result.token;
144
- expiresAt = new Date(result.expires_at * 1000);
222
+ setupKey = result.setup_key || setupKey;
145
223
 
146
- if (result.referral) {
147
- s.stop(pc.green(`Account "${agentId}" created with referral! $${result.referral.signup_bonus_referee} bonus unlocks after your first API call `) + cheer());
224
+ if (result.status === 'existing') {
225
+ s.stop(pc.green(`Recovered account "${agentId}". `) + cheer());
226
+ } else if (result.referral) {
227
+ s.stop(pc.green(`Account "${agentId}" created with referral! $${result.referral.signup_bonus_referee} bonus unlocks after your first paid execution or Stripe purchase. `) + cheer());
148
228
  } else {
149
229
  s.stop(pc.green(`Account "${agentId}" created! `) + cheer());
150
230
  }
151
231
  } catch (err) {
152
232
  if (err.status === 409) {
153
- s.stop(pc.red(`That account name was revoked — try a different name.`));
233
+ if (typeof err.message === 'string' && err.message.includes('ACCOUNT_EXISTS')) {
234
+ s.stop(pc.red(`That account name is already taken.`));
235
+ p.log.info('Reuse the original project folder so setup can read your saved setup key, or choose a different account name.');
236
+ } else {
237
+ s.stop(pc.red(`That account name was revoked — try a different name.`));
238
+ }
239
+ } else if (err.status === 403 && typeof err.message === 'string' && err.message.includes('SETUP_KEY_INVALID')) {
240
+ s.stop(pc.red('Your saved setup key does not match that account name.'));
154
241
  } else {
155
242
  s.stop(pc.red(`Couldn't create account: ${err.message}`));
156
243
  }
@@ -173,30 +260,46 @@ export async function runSetup() {
173
260
 
174
261
  // 3. Save config
175
262
  s.start('Saving configuration...');
176
- const vars = {
177
- OBOL_GATEWAY_URL: url,
178
- OBOL_AGENT_ID: agentId,
179
- OBOL_JWT_TOKEN: jwt,
180
- };
181
- const envPath = saveEnv(vars);
182
- s.stop(pc.green(`Saved to ${envPath}`));
263
+ let envPath;
264
+ try {
265
+ const vars = {
266
+ OBOL_GATEWAY_URL: url,
267
+ OBOL_AGENT_ID: agentId,
268
+ OBOL_JWT_TOKEN: jwt,
269
+ OBOL_SETUP_KEY: setupKey,
270
+ OBOL_PAYMENT_MODE: paymentMode,
271
+ OBOL_X402_PRIVATE_KEY: paymentMode === 'x402' ? x402PrivateKey : '',
272
+ OBOL_WALLET_ADDRESS: walletAddress || '',
273
+ };
274
+ envPath = saveEnv(vars);
275
+ s.stop(pc.green(`Saved to ${envPath}`));
276
+ } catch (err) {
277
+ s.stop(pc.red(`Could not save .env: ${err.message}`));
278
+ p.log.error('Your token (save this manually):');
279
+ console.log(` OBOL_JWT_TOKEN=${jwt}`);
280
+ p.outro(pc.dim('Fix the file permissions and try again.'));
281
+ process.exit(1);
282
+ }
283
+
284
+ // Add .env to .gitignore if not already there
285
+ try {
286
+ const gitignorePath = resolve(process.cwd(), '.gitignore');
287
+ if (existsSync(gitignorePath)) {
288
+ const content = readFileSync(gitignorePath, 'utf-8');
289
+ if (!content.split('\n').some(l => l.trim() === '.env')) {
290
+ writeFileSync(gitignorePath, content.trimEnd() + '\n.env\n');
291
+ }
292
+ } else if (existsSync(resolve(process.cwd(), '.git'))) {
293
+ writeFileSync(gitignorePath, '.env\n');
294
+ }
295
+ } catch {
296
+ // Non-critical — don't fail setup
297
+ }
183
298
 
184
299
  // 4. Detect project type and install SDK
185
- let lang = detectProject();
186
300
  let sdkInstalled = false;
187
301
  let integrationFile = null;
188
302
 
189
- if (!lang) {
190
- lang = await p.select({
191
- message: 'What language is your agent built in?',
192
- options: [
193
- { value: 'js', label: 'JavaScript / TypeScript' },
194
- { value: 'python', label: 'Python' },
195
- ],
196
- });
197
- if (p.isCancel(lang)) return cancelled();
198
- }
199
-
200
303
  const hasPackageJson = existsSync(resolve(process.cwd(), 'package.json'));
201
304
 
202
305
  if (lang === 'js') {
@@ -217,37 +320,46 @@ export async function runSetup() {
217
320
  const obolFile = resolve(process.cwd(), 'obol.js');
218
321
  if (!existsSync(obolFile)) {
219
322
  s.start('Generating integration file...');
220
- writeFileSync(obolFile, jsIntegrationFile(agentId, services));
323
+ writeFileSync(obolFile, jsIntegrationFile(agentId, services, paymentMode));
221
324
  s.stop(pc.green('Created obol.js'));
222
325
  integrationFile = 'obol.js';
223
326
  }
224
327
  } else if (lang === 'python') {
225
- s.start('Installing obol-sdk...');
328
+ s.start('Installing the Obol Python SDK from GitHub...');
226
329
  try {
227
- execSync('pip install obol-sdk', { stdio: 'pipe', timeout: 60000 });
330
+ execSync(PYTHON_SDK_INSTALL_CMD, { stdio: 'pipe', timeout: 60000 });
228
331
  s.stop(pc.green('SDK installed! ') + cheer());
229
332
  sdkInstalled = true;
230
333
  } catch {
231
- s.stop(pc.yellow('Could not auto-install SDK. Run manually: pip install obol-sdk'));
334
+ s.stop(pc.yellow(`Could not auto-install SDK. Run manually: ${PYTHON_SDK_INSTALL_CMD}`));
232
335
  }
233
336
 
234
337
  // Generate integration file
235
338
  const obolFile = resolve(process.cwd(), 'obol_setup.py');
236
339
  if (!existsSync(obolFile)) {
237
340
  s.start('Generating integration file...');
238
- writeFileSync(obolFile, pyIntegrationFile(agentId, services));
341
+ writeFileSync(obolFile, pyIntegrationFile(agentId, services, paymentMode));
239
342
  s.stop(pc.green('Created obol_setup.py'));
240
343
  integrationFile = 'obol_setup.py';
241
344
  }
242
345
  }
243
346
 
244
347
  // 5. Test connectivity
245
- s.start('Testing connection...');
348
+ s.start('Testing your Obol connection...');
246
349
  try {
247
- const epData = await listEndpoints(url, jwt);
350
+ const [epData, connData] = await Promise.all([
351
+ listEndpoints(url, jwt),
352
+ listConnections(url, jwt),
353
+ ]);
248
354
  const eps = epData.endpoints || [];
249
- const names = eps.map(e => e.provider || e.name).filter(Boolean);
250
- s.stop(pc.green(`Connected! ${eps.length} endpoints available`) + (names.length ? ` (${names.join(', ')})` : ''));
355
+ const conns = connData.connections || connData || [];
356
+ const connNames = conns.map(c => c.provider).filter(Boolean);
357
+ const endpointCount = `${eps.length} endpoint${eps.length === 1 ? '' : 's'}`;
358
+ if (connNames.length > 0) {
359
+ s.stop(pc.green(`Connected! ${endpointCount} available, ${connNames.length} service${connNames.length === 1 ? '' : 's'} linked`) + ` (${connNames.join(', ')})`);
360
+ } else {
361
+ s.stop(pc.yellow(`Connected to Obol, but no services are linked yet. ${endpointCount} are available once you connect keys.`));
362
+ }
251
363
  } catch {
252
364
  s.stop(pc.yellow('Could not verify connection — run obol test later to check'));
253
365
  }
@@ -258,33 +370,46 @@ export async function runSetup() {
258
370
  console.log();
259
371
 
260
372
  if (sdkInstalled) {
261
- console.log(` ${pc.dim('SDK installed:')} ${pc.green(lang === 'js' ? '@obol/sdk' : 'obol-sdk')}`);
373
+ console.log(` ${pc.dim('SDK installed:')} ${pc.green(lang === 'js' ? '@obol/sdk' : 'GitHub Python SDK')}`);
262
374
  }
263
375
  if (integrationFile) {
264
376
  console.log(` ${pc.dim('Integration file:')} ${pc.green('./' + integrationFile)}`);
265
377
  }
266
378
  console.log(` ${pc.dim('Config saved:')} ${pc.green(envPath)}`);
267
- console.log();
268
-
269
- // Prominent $0 balance warning
270
- p.log.warn(pc.bold(pc.yellow('Your balance is $0.00 — you need credits before your agent can execute.')));
379
+ console.log(` ${pc.dim('Payment mode:')} ${pc.green(paymentMode === 'x402' ? 'Automatic USDC (x402)' : 'Credits via Stripe')}`);
380
+ if (paymentMode === 'x402') {
381
+ if (walletAddress) {
382
+ console.log(` ${pc.dim('Wallet:')} ${pc.green(walletAddress)}`);
383
+ } else {
384
+ console.log(` ${pc.dim('Wallet:')} ${pc.green('Private key saved for automatic payments')}`);
385
+ }
386
+ }
271
387
  console.log();
272
388
 
273
389
  console.log(` ${pc.bold('Next steps:')}`);
274
- console.log(` ${pc.dim('1.')} ${pc.bold('Buy credits')} ${pc.dim('(required)')}: ${pc.green('npx @obol/cli credits buy')}`);
275
- console.log(` ${pc.dim('or visit:')} ${pc.cyan('obolagents.com/dashboard')}`);
390
+ if (paymentMode === 'x402') {
391
+ console.log(` ${pc.dim('1.')} ${pc.bold('Fund your agent wallet')}: keep Base ETH for gas and USDC for Obol calls.`);
392
+ console.log(` ${pc.dim('2.')} ${pc.bold('Run your agent')}: each Obol call pays automatically via x402.`);
393
+ } else {
394
+ p.log.warn(pc.bold(pc.yellow('Your balance is $0.00 — buy credits before your agent can execute.')));
395
+ console.log();
396
+ console.log(` ${pc.dim('1.')} ${pc.bold('Buy credits')} ${pc.dim('(required)')}: ${pc.green('npx @obol/cli credits buy')}`);
397
+ console.log(` ${pc.dim('or visit:')} ${pc.cyan('obolagents.com/dashboard')}`);
398
+ console.log(` ${pc.dim('2.')} ${pc.bold('Run your agent')}: Obol will draw from your credit balance automatically.`);
399
+ }
276
400
  if (lang === 'js' && integrationFile) {
277
- console.log(` ${pc.dim('2.')} Use in your code:`);
401
+ console.log(` ${pc.dim('3.')} Use in your code:`);
278
402
  console.log(` ${pc.cyan("import { obol } from './obol.js';")}`);
279
403
  console.log(` ${pc.cyan("const result = await obol.execute(2, 'Create issue ...');")}`);
280
404
  } else if (lang === 'python' && integrationFile) {
281
- console.log(` ${pc.dim('2.')} Use in your code:`);
405
+ console.log(` ${pc.dim('3.')} Use in your code:`);
282
406
  console.log(` ${pc.cyan("from obol_setup import obol")}`);
283
407
  console.log(` ${pc.cyan("result = obol.execute(2, 'Create issue ...', idempotency_key='...')")}`);
284
408
  }
285
409
  console.log();
286
410
  console.log(` ${pc.dim('Dashboard:')} ${pc.green('npx @obol/cli token')} → paste at ${pc.cyan('obolagents.com/dashboard')}`);
287
411
  console.log(` ${pc.dim('Token:')} Refreshes automatically — run ${pc.green('npx @obol/cli test')} anytime to verify.`);
412
+ console.log(` ${pc.dim('Setup key:')} Saved in ${pc.green('.env')} so you can safely reconnect this account later.`);
288
413
  console.log();
289
414
 
290
415
  p.log.info(`${pc.bold('Earn credits by referring other agents!')} Run ${pc.green('npx @obol/cli referral code')} to get started.`);
@@ -301,7 +426,7 @@ function detectProject() {
301
426
  return null;
302
427
  }
303
428
 
304
- function jsIntegrationFile(agentId, services) {
429
+ function jsIntegrationFile(agentId, services, paymentMode) {
305
430
  const providerMap = { stripe: 1, github: 2, slack: 3, notion: 4, discord: 5, twitter: 6, agentmail: 7 };
306
431
  const examples = services
307
432
  .map(svc => {
@@ -321,9 +446,12 @@ function jsIntegrationFile(agentId, services) {
321
446
 
322
447
  return `import { ObolClient } from '@obol/sdk';
323
448
 
324
- // Pre-configured Obol client — reads OBOL_JWT_TOKEN from .env automatically
449
+ // Pre-configured Obol client — reads your Obol settings from .env automatically
325
450
  export const obol = new ObolClient();
326
451
 
452
+ // Payment mode: ${paymentMode === 'x402' ? 'automatic x402 USDC payments' : 'credits via Stripe'}
453
+ // If OBOL_PAYMENT_MODE=x402 and OBOL_X402_PRIVATE_KEY is set, execute() pays automatically per call.
454
+
327
455
  // Provider IDs: 1=Stripe, 2=GitHub, 3=Slack, 4=Notion, 5=Discord, 6=Twitter, 7=AgentMail
328
456
  //
329
457
  // Usage:
@@ -332,7 +460,7 @@ ${examples}
332
460
  `;
333
461
  }
334
462
 
335
- function pyIntegrationFile(agentId, services) {
463
+ function pyIntegrationFile(agentId, services, paymentMode) {
336
464
  const providerMap = { stripe: 1, github: 2, slack: 3, notion: 4, discord: 5, twitter: 6, agentmail: 7 };
337
465
  const examples = services
338
466
  .map(svc => {
@@ -353,6 +481,8 @@ function pyIntegrationFile(agentId, services) {
353
481
  return `import os
354
482
  from obol_sdk import ObolClient
355
483
 
484
+ # Install: ${PYTHON_SDK_INSTALL_CMD}
485
+ #
356
486
  # Pre-configured Obol client — reads from environment variables
357
487
  obol = ObolClient(
358
488
  gateway_url=os.getenv("OBOL_GATEWAY_URL", "https://www.obolagents.com"),
@@ -360,6 +490,8 @@ obol = ObolClient(
360
490
  jwt_token=os.getenv("OBOL_JWT_TOKEN", ""),
361
491
  )
362
492
 
493
+ # Payment mode: ${paymentMode === 'x402' ? 'use credits by default in Python today unless you provide x402 headers separately' : 'credits via Stripe'}
494
+
363
495
  # Provider IDs: 1=Stripe, 2=GitHub, 3=Slack, 4=Notion, 5=Discord, 6=Twitter, 7=AgentMail
364
496
  #
365
497
  # Usage:
package/src/status.js CHANGED
@@ -58,12 +58,18 @@ export async function runStatus() {
58
58
  }
59
59
  }
60
60
 
61
- // Wallet
62
- if (env.OBOL_WALLET_ADDRESS) {
63
- const addr = env.OBOL_WALLET_ADDRESS;
64
- p.log.info(`${pc.bold('Wallet:')} ${pc.cyan(addr.slice(0, 6) + '...' + addr.slice(-4))} ${pc.dim('(USDC micropayments)')}`);
61
+ // Payment mode / wallet
62
+ if ((env.OBOL_PAYMENT_MODE || '').toLowerCase() === 'x402') {
63
+ if (env.OBOL_WALLET_ADDRESS) {
64
+ const addr = env.OBOL_WALLET_ADDRESS;
65
+ p.log.info(`${pc.bold('Payments:')} ${pc.green('Automatic USDC (x402)')} via ${pc.cyan(addr.slice(0, 6) + '...' + addr.slice(-4))}`);
66
+ } else if (env.OBOL_X402_PRIVATE_KEY) {
67
+ p.log.info(`${pc.bold('Payments:')} ${pc.green('Automatic USDC (x402)')} ${pc.dim('(wallet private key configured)')}`);
68
+ } else {
69
+ p.log.info(`${pc.bold('Payments:')} ${pc.yellow('x402 selected, but wallet key is missing')}`);
70
+ }
65
71
  } else {
66
- p.log.info(`${pc.bold('Wallet:')} ${pc.dim('Not configured')} — add one during setup for automatic micropayments`);
72
+ p.log.info(`${pc.bold('Payments:')} ${pc.dim('Credits via Stripe')} — rerun setup to enable automatic x402 agent payments`);
67
73
  }
68
74
 
69
75
  p.log.info(`${pc.bold('Gateway:')} ${pc.dim(url)}`);