@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 +75 -5
- package/package.json +1 -1
- package/src/api.js +17 -3
- package/src/config.js +7 -0
- package/src/credits.js +4 -4
- package/src/setup.js +340 -35
- package/src/status.js +11 -5
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(
|
|
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
|
|
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'))} —
|
|
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')}
|
|
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
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
|
|
|
@@ -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 {
|
|
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,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
|
|
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
|
-
// ---
|
|
36
|
+
// --- Account Name ---
|
|
32
37
|
const agentId = await p.text({
|
|
33
|
-
message: 'Pick a name for your
|
|
38
|
+
message: 'Pick a name for your Obol account',
|
|
34
39
|
placeholder: 'my-agent',
|
|
35
40
|
validate: (val) => {
|
|
36
|
-
if (!val) return 'Your
|
|
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
|
|
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
|
|
126
|
-
: `Create
|
|
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
|
|
135
|
-
s.start('Creating your
|
|
215
|
+
// 1. Sign up
|
|
216
|
+
s.start('Creating your account...');
|
|
136
217
|
let jwt;
|
|
137
|
-
let
|
|
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
|
-
|
|
222
|
+
setupKey = result.setup_key || setupKey;
|
|
142
223
|
|
|
143
|
-
if (result.
|
|
144
|
-
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());
|
|
145
228
|
} else {
|
|
146
|
-
s.stop(pc.green(`
|
|
229
|
+
s.stop(pc.green(`Account "${agentId}" created! `) + cheer());
|
|
147
230
|
}
|
|
148
231
|
} catch (err) {
|
|
149
232
|
if (err.status === 409) {
|
|
150
|
-
|
|
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
|
|
242
|
+
s.stop(pc.red(`Couldn't create account: ${err.message}`));
|
|
153
243
|
}
|
|
154
|
-
p.outro(pc.dim('Try again
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
// ---
|
|
367
|
+
// --- Summary ---
|
|
368
|
+
console.log();
|
|
369
|
+
p.log.success(pc.bold(`Your agent is connected to Obol!`));
|
|
182
370
|
console.log();
|
|
183
|
-
|
|
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.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
//
|
|
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)}`);
|