@obol/cli 0.1.3 → 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
@@ -4,7 +4,7 @@ import { runSetup } from '../src/setup.js';
4
4
  import { runStatus } from '../src/status.js';
5
5
  import { runConnect } from '../src/connect.js';
6
6
  import { runCreditsBuy } from '../src/credits.js';
7
- import { getBalance, getTiers, createReferralCode, getReferralStats } from '../src/api.js';
7
+ import { getBalance, getTiers, createReferralCode, getReferralStats, listEndpoints, listConnections, refreshToken } from '../src/api.js';
8
8
  import { loadEnv } from '../src/config.js';
9
9
  import pc from 'picocolors';
10
10
 
@@ -41,10 +41,25 @@ async function main() {
41
41
  console.log(pc.red('\n No agent configured yet. Run: npx @obol/cli setup\n'));
42
42
  process.exit(1);
43
43
  }
44
+ // Try to refresh the token (in case it's expired)
45
+ let token = env.OBOL_JWT_TOKEN;
46
+ const gwUrl = env.OBOL_GATEWAY_URL || 'https://www.obolagents.com';
47
+ try {
48
+ const data = await refreshToken(gwUrl, token);
49
+ if (data.token) {
50
+ token = data.token;
51
+ // Save refreshed token to .env
52
+ const { saveEnv } = await import('../src/config.js');
53
+ saveEnv({ OBOL_GATEWAY_URL: gwUrl, OBOL_AGENT_ID: env.OBOL_AGENT_ID, OBOL_JWT_TOKEN: token });
54
+ console.log(`\n ${pc.green('Token refreshed!')} Expires: ${pc.dim(new Date(data.expires_at * 1000).toLocaleDateString())}`);
55
+ }
56
+ } catch {
57
+ console.log(`\n ${pc.yellow('Could not refresh token — showing current token (it may be expired).')}`);
58
+ }
44
59
  console.log(`\n ${pc.bold('Your Agent Token')}`);
45
60
  console.log(` ${pc.dim('Agent:')} ${pc.green(env.OBOL_AGENT_ID || '(unknown)')}`);
46
61
  console.log(`\n ${pc.dim('Copy this token and paste it into the dashboard at obolagents.com/dashboard')}`);
47
- console.log(`\n ${pc.cyan(env.OBOL_JWT_TOKEN)}\n`);
62
+ console.log(`\n ${pc.cyan(token)}\n`);
48
63
  break;
49
64
  }
50
65
 
@@ -118,7 +133,7 @@ async function main() {
118
133
  console.log(` Signup bonuses: ${pc.green('$' + data.total_signup_bonuses_earned_usdc.toFixed(2))}`);
119
134
  console.log(` Total earned: ${pc.green(pc.bold('$' + data.total_earned_usdc.toFixed(2)))}`);
120
135
  if (data.own_bonus_status === 'pending') {
121
- 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!')}`);
122
137
  }
123
138
  console.log();
124
139
  } catch (err) {
@@ -133,15 +148,70 @@ async function main() {
133
148
  break;
134
149
  }
135
150
 
151
+ case 'test': {
152
+ const env = loadEnv();
153
+ if (!env.OBOL_GATEWAY_URL || !env.OBOL_JWT_TOKEN) {
154
+ console.log(pc.red('\n No account configured. Run: npx @obol/cli setup\n'));
155
+ process.exit(1);
156
+ }
157
+ console.log(`\n ${pc.bold('Obol Connection Test')}\n`);
158
+
159
+ // Test gateway connectivity + endpoints
160
+ try {
161
+ const epData = await listEndpoints(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
162
+ const eps = epData.endpoints || [];
163
+ const names = eps.map(e => e.provider || e.name).filter(Boolean);
164
+ console.log(` ${pc.green('✓')} Connection Connected to Obol Gateway`);
165
+ console.log(` ${pc.green('✓')} Account ${pc.bold(env.OBOL_AGENT_ID || '(unknown)')}`);
166
+ console.log(` ${pc.green('✓')} Endpoints ${eps.length} available${names.length ? ` (${names.join(', ')})` : ''}`);
167
+ } catch (err) {
168
+ console.log(` ${pc.red('✗')} Connection ${err.message}`);
169
+ process.exit(1);
170
+ }
171
+
172
+ // Check balance
173
+ try {
174
+ const bal = await getBalance(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
175
+ const balStr = '$' + (bal.balance_usdc || 0).toFixed(2);
176
+ const color = bal.balance_usdc > 0 ? pc.green : pc.yellow;
177
+ console.log(` ${color(bal.balance_usdc > 0 ? '✓' : '!')} Balance ${color(balStr)} USDC`);
178
+ if (bal.balance_usdc === 0 && (env.OBOL_PAYMENT_MODE || '').toLowerCase() !== 'x402') {
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.')}`);
182
+ }
183
+ } catch {
184
+ console.log(` ${pc.dim('-')} Balance ${pc.dim('could not fetch')}`);
185
+ }
186
+
187
+ // Check connections
188
+ try {
189
+ const connData = await listConnections(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
190
+ const conns = connData.connections || connData || [];
191
+ if (Array.isArray(conns) && conns.length > 0) {
192
+ const connNames = conns.map(c => c.provider).filter(Boolean);
193
+ console.log(` ${pc.green('✓')} Connections ${conns.length} active${connNames.length ? ` (${connNames.join(', ')})` : ''}`);
194
+ } else {
195
+ console.log(` ${pc.dim('-')} Connections none yet`);
196
+ }
197
+ } catch {
198
+ console.log(` ${pc.dim('-')} Connections ${pc.dim('could not fetch')}`);
199
+ }
200
+
201
+ console.log();
202
+ break;
203
+ }
204
+
136
205
  case 'help':
137
206
  case '--help':
138
207
  case '-h':
139
208
  case undefined:
140
209
  console.log(`
141
- ${pc.bold(pc.green('obol'))} — CLI for the Obol Agent Execution Gateway
210
+ ${pc.bold(pc.green('obol'))} — Connect your agent to the Obol Execution Gateway
142
211
 
143
212
  ${pc.bold('Commands:')}
144
- ${pc.green('setup')} Create a new agent (interactive wizard)
213
+ ${pc.green('setup')} Connect your agent to Obol (interactive wizard)
214
+ ${pc.green('test')} Verify your Obol connection is working
145
215
  ${pc.green('status')} Check agent health, balance & connections
146
216
  ${pc.green('token')} Show your JWT token (for the dashboard)
147
217
  ${pc.green('connect')} ${pc.dim('<provider>')} Add a service API key (github, slack, etc.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obol/cli",
3
- "version": "0.1.3",
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
 
@@ -146,8 +149,19 @@ export async function validateReferralCode(gatewayUrl, code) {
146
149
  });
147
150
  }
148
151
 
152
+ export async function listEndpoints(gatewayUrl, jwt) {
153
+ return request(`${gatewayUrl}/v1/endpoints`, { headers: jwtHeaders(jwt) });
154
+ }
155
+
149
156
  export async function getAgentInfo(gatewayUrl, adminKey, agentId) {
150
157
  return request(`${gatewayUrl}/v1/agents/${agentId}`, {
151
158
  headers: adminHeaders(adminKey),
152
159
  });
153
160
  }
161
+
162
+ export async function refreshToken(gatewayUrl, jwt) {
163
+ return request(`${gatewayUrl}/v1/token/refresh`, {
164
+ method: 'POST',
165
+ headers: jwtHeaders(jwt),
166
+ });
167
+ }
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,18 +1,23 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
1
4
  import * as p from '@clack/prompts';
2
5
  import pc from 'picocolors';
3
- import { checkHealth, signup, storeBYOK, validateReferralCode } from './api.js';
4
- import { saveEnv } from './config.js';
6
+ import { checkHealth, listConnections, listEndpoints, signup, storeBYOK, validateReferralCode } from './api.js';
7
+ import { loadEnv, saveEnv } from './config.js';
5
8
 
6
9
  const cheers = ['Nice!', 'Boom!', "Let's go.", 'Easy.', 'Done.'];
7
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"';
8
12
 
9
13
  export async function runSetup() {
10
14
  console.log();
11
15
  p.intro(pc.bgGreen(pc.black(' Welcome to Obol! ')));
12
16
 
13
- p.log.info("Let's get your agent connected. Takes about 30 seconds.");
17
+ p.log.info("Let's connect your agent to Obol. Takes about 60 seconds.");
14
18
 
15
19
  const url = 'https://www.obolagents.com';
20
+ const existingEnv = loadEnv();
16
21
 
17
22
  // Verify connectivity
18
23
  const healthSpin = p.spinner();
@@ -28,12 +33,12 @@ export async function runSetup() {
28
33
  process.exit(1);
29
34
  }
30
35
 
31
- // --- Agent Name ---
36
+ // --- Account Name ---
32
37
  const agentId = await p.text({
33
- message: 'Pick a name for your agent',
38
+ message: 'Pick a name for your Obol account',
34
39
  placeholder: 'my-agent',
35
40
  validate: (val) => {
36
- if (!val) return 'Your agent needs a name!';
41
+ if (!val) return 'Your account needs a name!';
37
42
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{1,62}[a-zA-Z0-9]$/.test(val)) {
38
43
  return 'Letters, numbers, hyphens, and underscores only (3-64 chars)';
39
44
  }
@@ -60,7 +65,7 @@ export async function runSetup() {
60
65
  valSpin.start('Checking referral code...');
61
66
  try {
62
67
  const validation = await validateReferralCode(url, referralCode.trim());
63
- 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.`));
64
69
  } catch (err) {
65
70
  valSpin.stop(pc.yellow('That referral code isn\'t valid — continuing without it.'));
66
71
  referralCode = undefined;
@@ -119,11 +124,87 @@ export async function runSetup() {
119
124
  }
120
125
  }
121
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
+
122
203
  // --- Confirm ---
123
204
  const keyCount = Object.keys(apiKeys).length;
124
205
  const confirmMsg = keyCount
125
- ? `Create agent ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''} and ${keyCount} API key${keyCount > 1 ? 's' : ''}?`
126
- : `Create agent ${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'}?`;
127
208
  const confirmed = await p.confirm({ message: confirmMsg });
128
209
  if (p.isCancel(confirmed) || !confirmed) return cancelled();
129
210
 
@@ -131,27 +212,36 @@ export async function runSetup() {
131
212
  console.log();
132
213
  const s = p.spinner();
133
214
 
134
- // 1. Sign up (creates agent + JWT + policy in one call)
135
- s.start('Creating your agent...');
215
+ // 1. Sign up
216
+ s.start('Creating your account...');
136
217
  let jwt;
137
- let expiresAt;
218
+ let setupKey = existingEnv.OBOL_AGENT_ID === agentId ? existingEnv.OBOL_SETUP_KEY || '' : '';
138
219
  try {
139
- const result = await signup(url, agentId, services, referralCode?.trim() || undefined);
220
+ const result = await signup(url, agentId, services, referralCode?.trim() || undefined, setupKey);
140
221
  jwt = result.token;
141
- expiresAt = new Date(result.expires_at * 1000);
222
+ setupKey = result.setup_key || setupKey;
142
223
 
143
- if (result.referral) {
144
- s.stop(pc.green(`Agent "${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());
145
228
  } else {
146
- s.stop(pc.green(`Agent "${agentId}" created! `) + cheer());
229
+ s.stop(pc.green(`Account "${agentId}" created! `) + cheer());
147
230
  }
148
231
  } catch (err) {
149
232
  if (err.status === 409) {
150
- s.stop(pc.red(`The name "${agentId}" is already taken — 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.'));
151
241
  } else {
152
- s.stop(pc.red(`Couldn't create agent: ${err.message}`));
242
+ s.stop(pc.red(`Couldn't create account: ${err.message}`));
153
243
  }
154
- p.outro(pc.dim('Try again with a different name.'));
244
+ p.outro(pc.dim('Try again.'));
155
245
  process.exit(1);
156
246
  }
157
247
 
@@ -170,31 +260,246 @@ export async function runSetup() {
170
260
 
171
261
  // 3. Save config
172
262
  s.start('Saving configuration...');
173
- const vars = {
174
- OBOL_GATEWAY_URL: url,
175
- OBOL_AGENT_ID: agentId,
176
- OBOL_JWT_TOKEN: jwt,
177
- };
178
- const envPath = saveEnv(vars);
179
- 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
+ }
298
+
299
+ // 4. Detect project type and install SDK
300
+ let sdkInstalled = false;
301
+ let integrationFile = null;
302
+
303
+ const hasPackageJson = existsSync(resolve(process.cwd(), 'package.json'));
304
+
305
+ if (lang === 'js') {
306
+ if (hasPackageJson) {
307
+ s.start('Installing @obol/sdk...');
308
+ try {
309
+ execSync('npm install @obol/sdk', { stdio: 'pipe', timeout: 60000 });
310
+ s.stop(pc.green('SDK installed! ') + cheer());
311
+ sdkInstalled = true;
312
+ } catch {
313
+ s.stop(pc.yellow('Could not auto-install SDK. Run manually: npm install @obol/sdk'));
314
+ }
315
+ } else {
316
+ p.log.info(`Install the SDK when you're ready: ${pc.green('npm install @obol/sdk')}`);
317
+ }
318
+
319
+ // Generate integration file
320
+ const obolFile = resolve(process.cwd(), 'obol.js');
321
+ if (!existsSync(obolFile)) {
322
+ s.start('Generating integration file...');
323
+ writeFileSync(obolFile, jsIntegrationFile(agentId, services, paymentMode));
324
+ s.stop(pc.green('Created obol.js'));
325
+ integrationFile = 'obol.js';
326
+ }
327
+ } else if (lang === 'python') {
328
+ s.start('Installing the Obol Python SDK from GitHub...');
329
+ try {
330
+ execSync(PYTHON_SDK_INSTALL_CMD, { stdio: 'pipe', timeout: 60000 });
331
+ s.stop(pc.green('SDK installed! ') + cheer());
332
+ sdkInstalled = true;
333
+ } catch {
334
+ s.stop(pc.yellow(`Could not auto-install SDK. Run manually: ${PYTHON_SDK_INSTALL_CMD}`));
335
+ }
336
+
337
+ // Generate integration file
338
+ const obolFile = resolve(process.cwd(), 'obol_setup.py');
339
+ if (!existsSync(obolFile)) {
340
+ s.start('Generating integration file...');
341
+ writeFileSync(obolFile, pyIntegrationFile(agentId, services, paymentMode));
342
+ s.stop(pc.green('Created obol_setup.py'));
343
+ integrationFile = 'obol_setup.py';
344
+ }
345
+ }
346
+
347
+ // 5. Test connectivity
348
+ s.start('Testing your Obol connection...');
349
+ try {
350
+ const [epData, connData] = await Promise.all([
351
+ listEndpoints(url, jwt),
352
+ listConnections(url, jwt),
353
+ ]);
354
+ const eps = epData.endpoints || [];
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
+ }
363
+ } catch {
364
+ s.stop(pc.yellow('Could not verify connection — run obol test later to check'));
365
+ }
180
366
 
181
- // --- Celebration! ---
367
+ // --- Summary ---
368
+ console.log();
369
+ p.log.success(pc.bold(`Your agent is connected to Obol!`));
182
370
  console.log();
183
- p.log.success(pc.bold(`Your agent "${agentId}" is live on Obol!`));
371
+
372
+ if (sdkInstalled) {
373
+ console.log(` ${pc.dim('SDK installed:')} ${pc.green(lang === 'js' ? '@obol/sdk' : 'GitHub Python SDK')}`);
374
+ }
375
+ if (integrationFile) {
376
+ console.log(` ${pc.dim('Integration file:')} ${pc.green('./' + integrationFile)}`);
377
+ }
378
+ console.log(` ${pc.dim('Config saved:')} ${pc.green(envPath)}`);
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
+ }
184
387
  console.log();
185
388
 
186
- console.log(` ${pc.dim('Check status:')} ${pc.green('npx @obol/cli status')}`);
187
- console.log(` ${pc.dim('Add a key:')} ${pc.green('npx @obol/cli connect github')}`);
188
- console.log(` ${pc.dim('View balance:')} ${pc.green('npx @obol/cli balance')}`);
189
- console.log(` ${pc.dim('Dashboard:')} ${pc.green('npx @obol/cli token')} ${pc.dim('→ paste at obolagents.com/dashboard')}`);
389
+ console.log(` ${pc.bold('Next steps:')}`);
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
+ }
400
+ if (lang === 'js' && integrationFile) {
401
+ console.log(` ${pc.dim('3.')} Use in your code:`);
402
+ console.log(` ${pc.cyan("import { obol } from './obol.js';")}`);
403
+ console.log(` ${pc.cyan("const result = await obol.execute(2, 'Create issue ...');")}`);
404
+ } else if (lang === 'python' && integrationFile) {
405
+ console.log(` ${pc.dim('3.')} Use in your code:`);
406
+ console.log(` ${pc.cyan("from obol_setup import obol")}`);
407
+ console.log(` ${pc.cyan("result = obol.execute(2, 'Create issue ...', idempotency_key='...')")}`);
408
+ }
190
409
  console.log();
191
- p.log.info(`${pc.bold('Earn credits by referring other agents!')} Run ${pc.green('obol referral code')} to get your referral link.`);
192
- p.log.info(pc.dim('You earn $2 per signup + 10% ongoing commissions on their usage.'));
410
+ console.log(` ${pc.dim('Dashboard:')} ${pc.green('npx @obol/cli token')} paste at ${pc.cyan('obolagents.com/dashboard')}`);
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.`);
413
+ console.log();
414
+
415
+ p.log.info(`${pc.bold('Earn credits by referring other agents!')} Run ${pc.green('npx @obol/cli referral code')} to get started.`);
193
416
  console.log();
194
417
 
195
418
  p.outro('Go build something awesome. ' + pc.green('⚡'));
196
419
  }
197
420
 
421
+ function detectProject() {
422
+ const cwd = process.cwd();
423
+ if (existsSync(resolve(cwd, 'package.json'))) return 'js';
424
+ if (existsSync(resolve(cwd, 'requirements.txt'))) return 'python';
425
+ if (existsSync(resolve(cwd, 'pyproject.toml'))) return 'python';
426
+ return null;
427
+ }
428
+
429
+ function jsIntegrationFile(agentId, services, paymentMode) {
430
+ const providerMap = { stripe: 1, github: 2, slack: 3, notion: 4, discord: 5, twitter: 6, agentmail: 7 };
431
+ const examples = services
432
+ .map(svc => {
433
+ const id = providerMap[svc];
434
+ const ex = {
435
+ stripe: `await obol.execute(${id}, 'Create invoice for $50 to customer@example.com');`,
436
+ github: `await obol.execute(${id}, 'Create issue titled "Bug report" in owner/repo');`,
437
+ slack: `await obol.execute(${id}, 'Send message "Hello team" to #general');`,
438
+ notion: `await obol.execute(${id}, 'Create page titled "Meeting Notes"');`,
439
+ discord: `await obol.execute(${id}, 'Send message "Hello" to channel 123456');`,
440
+ twitter: `await obol.execute(${id}, 'Post tweet: Just shipped a new feature!');`,
441
+ agentmail: `await obol.execute(${id}, 'Send email to user@example.com subject "Hello"');`,
442
+ };
443
+ return `// ${ex[svc] || `await obol.execute(${id}, 'your intent here');`}`;
444
+ })
445
+ .join('\n');
446
+
447
+ return `import { ObolClient } from '@obol/sdk';
448
+
449
+ // Pre-configured Obol client — reads your Obol settings from .env automatically
450
+ export const obol = new ObolClient();
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
+
455
+ // Provider IDs: 1=Stripe, 2=GitHub, 3=Slack, 4=Notion, 5=Discord, 6=Twitter, 7=AgentMail
456
+ //
457
+ // Usage:
458
+ // import { obol } from './obol.js';
459
+ ${examples}
460
+ `;
461
+ }
462
+
463
+ function pyIntegrationFile(agentId, services, paymentMode) {
464
+ const providerMap = { stripe: 1, github: 2, slack: 3, notion: 4, discord: 5, twitter: 6, agentmail: 7 };
465
+ const examples = services
466
+ .map(svc => {
467
+ const id = providerMap[svc];
468
+ const ex = {
469
+ stripe: `# obol.execute(${id}, 'Create invoice for $50 to customer@example.com', idempotency_key='unique-key')`,
470
+ github: `# obol.execute(${id}, 'Create issue titled "Bug report" in owner/repo', idempotency_key='unique-key')`,
471
+ slack: `# obol.execute(${id}, 'Send message "Hello team" to #general', idempotency_key='unique-key')`,
472
+ notion: `# obol.execute(${id}, 'Create page titled "Meeting Notes"', idempotency_key='unique-key')`,
473
+ discord: `# obol.execute(${id}, 'Send message "Hello" to channel 123456', idempotency_key='unique-key')`,
474
+ twitter: `# obol.execute(${id}, 'Post tweet: Just shipped a new feature!', idempotency_key='unique-key')`,
475
+ agentmail: `# obol.execute(${id}, 'Send email to user@example.com subject "Hello"', idempotency_key='unique-key')`,
476
+ };
477
+ return ex[svc] || `# obol.execute(${id}, 'your intent here', idempotency_key='unique-key')`;
478
+ })
479
+ .join('\n');
480
+
481
+ return `import os
482
+ from obol_sdk import ObolClient
483
+
484
+ # Install: ${PYTHON_SDK_INSTALL_CMD}
485
+ #
486
+ # Pre-configured Obol client — reads from environment variables
487
+ obol = ObolClient(
488
+ gateway_url=os.getenv("OBOL_GATEWAY_URL", "https://www.obolagents.com"),
489
+ agent_id=os.getenv("OBOL_AGENT_ID", "${agentId}"),
490
+ jwt_token=os.getenv("OBOL_JWT_TOKEN", ""),
491
+ )
492
+
493
+ # Payment mode: ${paymentMode === 'x402' ? 'use credits by default in Python today unless you provide x402 headers separately' : 'credits via Stripe'}
494
+
495
+ # Provider IDs: 1=Stripe, 2=GitHub, 3=Slack, 4=Notion, 5=Discord, 6=Twitter, 7=AgentMail
496
+ #
497
+ # Usage:
498
+ # from obol_setup import obol
499
+ ${examples}
500
+ `;
501
+ }
502
+
198
503
  function cancelled() {
199
504
  p.cancel('Setup cancelled. Come back anytime!');
200
505
  process.exit(0);
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)}`);