@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 +5 -3
- package/package.json +1 -1
- package/src/api.js +6 -3
- package/src/config.js +7 -0
- package/src/credits.js +4 -4
- package/src/setup.js +184 -52
- package/src/status.js +11 -5
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
|
-
|
|
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
|
|
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
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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' : ''}
|
|
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
|
|
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
|
-
|
|
222
|
+
setupKey = result.setup_key || setupKey;
|
|
145
223
|
|
|
146
|
-
if (result.
|
|
147
|
-
s.stop(pc.green(`
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
328
|
+
s.start('Installing the Obol Python SDK from GitHub...');
|
|
226
329
|
try {
|
|
227
|
-
execSync(
|
|
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(
|
|
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
|
|
350
|
+
const [epData, connData] = await Promise.all([
|
|
351
|
+
listEndpoints(url, jwt),
|
|
352
|
+
listConnections(url, jwt),
|
|
353
|
+
]);
|
|
248
354
|
const eps = epData.endpoints || [];
|
|
249
|
-
const
|
|
250
|
-
|
|
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' : '
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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('
|
|
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('
|
|
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
|
|
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
|
-
//
|
|
62
|
-
if (env.
|
|
63
|
-
|
|
64
|
-
|
|
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('
|
|
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)}`);
|