@obol/cli 0.1.2 → 0.2.1
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 +133 -3
- package/package.json +1 -1
- package/src/api.js +55 -2
- package/src/config.js +0 -1
- package/src/connect.js +2 -1
- package/src/credits.js +14 -9
- package/src/setup.js +279 -73
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 } 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
|
|
|
@@ -35,6 +35,34 @@ async function main() {
|
|
|
35
35
|
}
|
|
36
36
|
break;
|
|
37
37
|
|
|
38
|
+
case 'token': {
|
|
39
|
+
const env = loadEnv();
|
|
40
|
+
if (!env.OBOL_JWT_TOKEN) {
|
|
41
|
+
console.log(pc.red('\n No agent configured yet. Run: npx @obol/cli setup\n'));
|
|
42
|
+
process.exit(1);
|
|
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
|
+
// Refresh failed — show existing token anyway
|
|
58
|
+
}
|
|
59
|
+
console.log(`\n ${pc.bold('Your Agent Token')}`);
|
|
60
|
+
console.log(` ${pc.dim('Agent:')} ${pc.green(env.OBOL_AGENT_ID || '(unknown)')}`);
|
|
61
|
+
console.log(`\n ${pc.dim('Copy this token and paste it into the dashboard at obolagents.com/dashboard')}`);
|
|
62
|
+
console.log(`\n ${pc.cyan(token)}\n`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
case 'balance': {
|
|
39
67
|
const env = loadEnv();
|
|
40
68
|
if (!env.OBOL_GATEWAY_URL || !env.OBOL_JWT_TOKEN) {
|
|
@@ -74,20 +102,122 @@ async function main() {
|
|
|
74
102
|
break;
|
|
75
103
|
}
|
|
76
104
|
|
|
105
|
+
case 'referral': {
|
|
106
|
+
const env = loadEnv();
|
|
107
|
+
if (!env.OBOL_GATEWAY_URL || !env.OBOL_JWT_TOKEN) {
|
|
108
|
+
console.log(pc.red('No agent configured. Run: obol setup'));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
if (subcommand === 'code') {
|
|
112
|
+
try {
|
|
113
|
+
const data = await createReferralCode(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
|
|
114
|
+
console.log(`\n ${pc.bold('Your Referral Code')}`);
|
|
115
|
+
console.log(` ${pc.green(pc.bold(data.code))}`);
|
|
116
|
+
console.log(`\n Share this code. Both you and the new agent earn free credits!`);
|
|
117
|
+
console.log();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.log(pc.red(` ${err.message}`));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
} else if (subcommand === 'stats') {
|
|
123
|
+
try {
|
|
124
|
+
const data = await getReferralStats(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
|
|
125
|
+
console.log(`\n ${pc.bold('Referral Stats')}`);
|
|
126
|
+
console.log(` Code: ${data.referral_code ? pc.green(data.referral_code) : pc.dim('(none yet)')}`);
|
|
127
|
+
console.log(` Eligible: ${data.referral_eligible ? pc.green('Yes') : pc.yellow('No — make a purchase first')}`);
|
|
128
|
+
const pending = data.direct_referrals_pending || 0;
|
|
129
|
+
const active = data.direct_referrals - pending;
|
|
130
|
+
console.log(` Direct refs: ${pc.bold(String(active))} active${pending > 0 ? `, ${pc.yellow(String(pending) + ' pending first payment')}` : ''}`);
|
|
131
|
+
console.log(` 2nd-level refs: ${pc.bold(String(data.second_level_referrals))}`);
|
|
132
|
+
console.log(` Commissions: ${pc.green('$' + data.total_commission_earned_usdc.toFixed(2))}`);
|
|
133
|
+
console.log(` Signup bonuses: ${pc.green('$' + data.total_signup_bonuses_earned_usdc.toFixed(2))}`);
|
|
134
|
+
console.log(` Total earned: ${pc.green(pc.bold('$' + data.total_earned_usdc.toFixed(2)))}`);
|
|
135
|
+
if (data.own_bonus_status === 'pending') {
|
|
136
|
+
console.log(`\n ${pc.yellow('Your signup bonus is pending — make your first API call to unlock it!')}`);
|
|
137
|
+
}
|
|
138
|
+
console.log();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.log(pc.red(` ${err.message}`));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
console.log(`\n ${pc.bold('obol referral')} commands:\n`);
|
|
145
|
+
console.log(` ${pc.green('code')} Generate or show your referral code`);
|
|
146
|
+
console.log(` ${pc.green('stats')} View referral earnings and stats\n`);
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
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) {
|
|
179
|
+
console.log(`\n ${pc.yellow('Buy credits to start executing:')} ${pc.green('npx @obol/cli credits buy')}`);
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
console.log(` ${pc.dim('-')} Balance ${pc.dim('could not fetch')}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check connections
|
|
186
|
+
try {
|
|
187
|
+
const connData = await listConnections(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
|
|
188
|
+
const conns = connData.connections || connData || [];
|
|
189
|
+
if (Array.isArray(conns) && conns.length > 0) {
|
|
190
|
+
const connNames = conns.map(c => c.provider).filter(Boolean);
|
|
191
|
+
console.log(` ${pc.green('✓')} Connections ${conns.length} active${connNames.length ? ` (${connNames.join(', ')})` : ''}`);
|
|
192
|
+
} else {
|
|
193
|
+
console.log(` ${pc.dim('-')} Connections none yet`);
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
console.log(` ${pc.dim('-')} Connections ${pc.dim('could not fetch')}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log();
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
77
203
|
case 'help':
|
|
78
204
|
case '--help':
|
|
79
205
|
case '-h':
|
|
80
206
|
case undefined:
|
|
81
207
|
console.log(`
|
|
82
|
-
${pc.bold(pc.green('obol'))} —
|
|
208
|
+
${pc.bold(pc.green('obol'))} — Connect your agent to the Obol Execution Gateway
|
|
83
209
|
|
|
84
210
|
${pc.bold('Commands:')}
|
|
85
|
-
${pc.green('setup')}
|
|
211
|
+
${pc.green('setup')} Connect your agent to Obol (interactive wizard)
|
|
212
|
+
${pc.green('test')} Verify your Obol connection is working
|
|
86
213
|
${pc.green('status')} Check agent health, balance & connections
|
|
214
|
+
${pc.green('token')} Show your JWT token (for the dashboard)
|
|
87
215
|
${pc.green('connect')} ${pc.dim('<provider>')} Add a service API key (github, slack, etc.)
|
|
88
216
|
${pc.green('credits buy')} Purchase credits via Stripe
|
|
89
217
|
${pc.green('balance')} Check your credit balance
|
|
90
218
|
${pc.green('tiers')} Show credit purchase tiers
|
|
219
|
+
${pc.green('referral code')} Generate or show your referral code
|
|
220
|
+
${pc.green('referral stats')} View referral earnings
|
|
91
221
|
${pc.green('help')} Show this help message
|
|
92
222
|
|
|
93
223
|
${pc.bold('Quick start:')}
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -25,7 +25,10 @@ async function request(url, options = {}) {
|
|
|
25
25
|
} catch { detail = res.statusText; }
|
|
26
26
|
throw new ApiError(detail, res.status);
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
const ct = res.headers.get('content-type') || '';
|
|
29
|
+
if (ct.includes('application/json')) return res.json();
|
|
30
|
+
const text = await res.text();
|
|
31
|
+
return text ? { raw: text } : {};
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
function adminHeaders(adminKey) {
|
|
@@ -42,9 +45,21 @@ export async function checkHealth(gatewayUrl) {
|
|
|
42
45
|
return request(`${gatewayUrl}/healthz`);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
export async function
|
|
48
|
+
export async function signup(gatewayUrl, agentId, services, referralCode) {
|
|
49
|
+
const body = { agent_id: agentId };
|
|
50
|
+
if (services && services.length > 0) body.services = services;
|
|
51
|
+
if (referralCode) body.referral_code = referralCode;
|
|
52
|
+
return request(`${gatewayUrl}/v1/signup`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function registerAgent(gatewayUrl, adminKey, agentId, secret, ownerId, referralCode) {
|
|
46
60
|
const body = { agent_id: agentId, secret };
|
|
47
61
|
if (ownerId) body.owner_id = ownerId;
|
|
62
|
+
if (referralCode) body.referral_code = referralCode;
|
|
48
63
|
return request(`${gatewayUrl}/v1/agents`, {
|
|
49
64
|
method: 'POST',
|
|
50
65
|
headers: adminHeaders(adminKey),
|
|
@@ -104,8 +119,46 @@ export async function createCheckout(gatewayUrl, jwt, amountUsd) {
|
|
|
104
119
|
});
|
|
105
120
|
}
|
|
106
121
|
|
|
122
|
+
export async function createWebCheckout(gatewayUrl, agentId, amountUsd) {
|
|
123
|
+
return request(`${gatewayUrl}/v1/credits/web-checkout`, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/json' },
|
|
126
|
+
body: JSON.stringify({ agent_id: agentId, amount_usdc: amountUsd }),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function createReferralCode(gatewayUrl, jwt) {
|
|
131
|
+
return request(`${gatewayUrl}/v1/referrals/code`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: jwtHeaders(jwt),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function getReferralStats(gatewayUrl, jwt) {
|
|
138
|
+
return request(`${gatewayUrl}/v1/referrals/stats`, { headers: jwtHeaders(jwt) });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function validateReferralCode(gatewayUrl, code) {
|
|
142
|
+
return request(`${gatewayUrl}/v1/referrals/validate`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: { 'Content-Type': 'application/json' },
|
|
145
|
+
body: JSON.stringify({ code }),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function listEndpoints(gatewayUrl, jwt) {
|
|
150
|
+
return request(`${gatewayUrl}/v1/endpoints`, { headers: jwtHeaders(jwt) });
|
|
151
|
+
}
|
|
152
|
+
|
|
107
153
|
export async function getAgentInfo(gatewayUrl, adminKey, agentId) {
|
|
108
154
|
return request(`${gatewayUrl}/v1/agents/${agentId}`, {
|
|
109
155
|
headers: adminHeaders(adminKey),
|
|
110
156
|
});
|
|
111
157
|
}
|
|
158
|
+
|
|
159
|
+
export async function refreshToken(gatewayUrl, jwt) {
|
|
160
|
+
return request(`${gatewayUrl}/v1/token/refresh`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: jwtHeaders(jwt),
|
|
163
|
+
});
|
|
164
|
+
}
|
package/src/config.js
CHANGED
package/src/connect.js
CHANGED
|
@@ -9,7 +9,8 @@ const providerInfo = {
|
|
|
9
9
|
slack: { name: 'Slack', keyHint: 'Bot token (xoxb-...)', prefix: 'xoxb-' },
|
|
10
10
|
notion: { name: 'Notion', keyHint: 'Internal integration token (ntn_ or secret_)', prefix: '' },
|
|
11
11
|
discord: { name: 'Discord', keyHint: 'Bot token from Discord Developer Portal', prefix: '' },
|
|
12
|
-
twitter: { name: 'Twitter/X', keyHint: '
|
|
12
|
+
twitter: { name: 'Twitter/X', keyHint: 'OAuth 2.0 user access token (for posting tweets)', prefix: '' },
|
|
13
|
+
agentmail: { name: 'AgentMail', keyHint: 'API key (am_...)', prefix: 'am_' },
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export async function runConnect(providerArg) {
|
package/src/credits.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import { getTiers,
|
|
3
|
+
import { getTiers, createWebCheckout } 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.
|
|
9
|
+
if (!env.OBOL_GATEWAY_URL || !env.OBOL_AGENT_ID) {
|
|
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.`);
|
|
@@ -48,18 +48,23 @@ export async function runCreditsBuy() {
|
|
|
48
48
|
});
|
|
49
49
|
if (p.isCancel(confirmed) || !confirmed) { p.cancel('Cancelled.'); return; }
|
|
50
50
|
|
|
51
|
-
// Create checkout
|
|
51
|
+
// Create checkout session (returns a Stripe checkout URL)
|
|
52
52
|
s.start('Creating Stripe checkout...');
|
|
53
53
|
try {
|
|
54
|
-
const result = await
|
|
55
|
-
s.stop(pc.green('Checkout
|
|
54
|
+
const result = await createWebCheckout(url, env.OBOL_AGENT_ID, tier.price_usd);
|
|
55
|
+
s.stop(pc.green('Checkout ready!'));
|
|
56
56
|
|
|
57
57
|
console.log();
|
|
58
|
-
p.log.info(`Tier: ${pc.bold(
|
|
59
|
-
p.log.info(`Payment Intent: ${pc.dim(result.payment_intent_id)}`);
|
|
58
|
+
p.log.info(`Tier: ${pc.bold(tier.name)} — $${tier.price_usd} → ${pc.green(tier.credits_usdc + ' credits')}`);
|
|
60
59
|
console.log();
|
|
61
|
-
p.log.
|
|
62
|
-
|
|
60
|
+
p.log.info(`Open this link to complete payment:`);
|
|
61
|
+
console.log(` ${pc.cyan(result.checkout_url)}`);
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
// Try to open in browser
|
|
65
|
+
const { exec } = await import('node:child_process');
|
|
66
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
67
|
+
exec(`${cmd} "${result.checkout_url}"`);
|
|
63
68
|
} catch (err) {
|
|
64
69
|
s.stop(pc.red(`Couldn't create checkout: ${err.message}`));
|
|
65
70
|
}
|
package/src/setup.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, 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 {
|
|
4
|
-
import { checkHealth, registerAgent, issueToken, createPolicy } from './api.js';
|
|
6
|
+
import { checkHealth, signup, storeBYOK, validateReferralCode, listEndpoints } from './api.js';
|
|
5
7
|
import { saveEnv } from './config.js';
|
|
6
8
|
|
|
7
9
|
const cheers = ['Nice!', 'Boom!', "Let's go.", 'Easy.', 'Done.'];
|
|
@@ -11,39 +13,30 @@ export async function runSetup() {
|
|
|
11
13
|
console.log();
|
|
12
14
|
p.intro(pc.bgGreen(pc.black(' Welcome to Obol! ')));
|
|
13
15
|
|
|
14
|
-
p.log.info("Let's
|
|
16
|
+
p.log.info("Let's connect your agent to Obol. Takes about 60 seconds.");
|
|
15
17
|
|
|
16
18
|
const url = 'https://www.obolagents.com';
|
|
17
19
|
|
|
18
20
|
// Verify connectivity
|
|
19
21
|
const healthSpin = p.spinner();
|
|
20
|
-
healthSpin.start('
|
|
22
|
+
healthSpin.start('Connecting to Obol...');
|
|
21
23
|
try {
|
|
22
24
|
await checkHealth(url);
|
|
23
|
-
healthSpin.stop(pc.green('
|
|
25
|
+
healthSpin.stop(pc.green('Connected! ') + cheer());
|
|
24
26
|
} catch (err) {
|
|
25
27
|
healthSpin.stop(pc.red('Hmm, couldn\'t reach the gateway.'));
|
|
26
|
-
p.log.error(`Make sure
|
|
28
|
+
p.log.error(`Make sure you have an internet connection.`);
|
|
27
29
|
p.log.error(`Details: ${err.message}`);
|
|
28
|
-
p.outro(pc.dim('
|
|
30
|
+
p.outro(pc.dim('Try again in a moment.'));
|
|
29
31
|
process.exit(1);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
// ---
|
|
33
|
-
const adminKey = await p.password({
|
|
34
|
-
message: 'Paste your admin API key',
|
|
35
|
-
validate: (val) => {
|
|
36
|
-
if (!val || val.length < 8) return 'That looks too short. Check your Render dashboard → Environment Variables → OBOL_ADMIN_API_KEY';
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
if (p.isCancel(adminKey)) return cancelled();
|
|
40
|
-
|
|
41
|
-
// --- Agent Name ---
|
|
34
|
+
// --- Account Name ---
|
|
42
35
|
const agentId = await p.text({
|
|
43
|
-
message: '
|
|
36
|
+
message: 'Pick a name for your Obol account',
|
|
44
37
|
placeholder: 'my-agent',
|
|
45
38
|
validate: (val) => {
|
|
46
|
-
if (!val) return 'Your
|
|
39
|
+
if (!val) return 'Your account needs a name!';
|
|
47
40
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{1,62}[a-zA-Z0-9]$/.test(val)) {
|
|
48
41
|
return 'Letters, numbers, hyphens, and underscores only (3-64 chars)';
|
|
49
42
|
}
|
|
@@ -51,18 +44,31 @@ export async function runSetup() {
|
|
|
51
44
|
});
|
|
52
45
|
if (p.isCancel(agentId)) return cancelled();
|
|
53
46
|
|
|
54
|
-
// ---
|
|
55
|
-
|
|
56
|
-
message: `Got a
|
|
57
|
-
placeholder: '
|
|
47
|
+
// --- Referral Code ---
|
|
48
|
+
let referralCode = await p.text({
|
|
49
|
+
message: `Got a referral code? ${pc.dim('(earns you $5 in free credits — press Enter to skip)')}`,
|
|
50
|
+
placeholder: 'e.g. X7K9M2QP',
|
|
58
51
|
validate: (val) => {
|
|
59
|
-
if (!val) return;
|
|
60
|
-
if (!/^
|
|
61
|
-
return '
|
|
52
|
+
if (!val) return;
|
|
53
|
+
if (!/^[A-Za-z0-9]{6,12}$/.test(val.trim())) {
|
|
54
|
+
return 'Referral codes are 6-12 alphanumeric characters';
|
|
62
55
|
}
|
|
63
56
|
},
|
|
64
57
|
});
|
|
65
|
-
if (p.isCancel(
|
|
58
|
+
if (p.isCancel(referralCode)) return cancelled();
|
|
59
|
+
|
|
60
|
+
// --- Validate Referral Code ---
|
|
61
|
+
if (referralCode && referralCode.trim()) {
|
|
62
|
+
const valSpin = p.spinner();
|
|
63
|
+
valSpin.start('Checking referral code...');
|
|
64
|
+
try {
|
|
65
|
+
const validation = await validateReferralCode(url, referralCode.trim());
|
|
66
|
+
valSpin.stop(pc.green(`Referral code valid! You'll get $${validation.signup_bonus_usdc} in credits after your first API call.`));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
valSpin.stop(pc.yellow('That referral code isn\'t valid — continuing without it.'));
|
|
69
|
+
referralCode = undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
66
72
|
|
|
67
73
|
// --- Services ---
|
|
68
74
|
const services = await p.multiselect({
|
|
@@ -74,94 +80,294 @@ export async function runSetup() {
|
|
|
74
80
|
{ value: 'notion', label: 'Notion', hint: 'pages, databases' },
|
|
75
81
|
{ value: 'discord', label: 'Discord', hint: 'messages, servers' },
|
|
76
82
|
{ value: 'twitter', label: 'Twitter/X', hint: 'tweets, threads' },
|
|
83
|
+
{ value: 'agentmail', label: 'AgentMail', hint: 'email inboxes, send & receive' },
|
|
77
84
|
],
|
|
78
85
|
required: true,
|
|
79
86
|
});
|
|
80
87
|
if (p.isCancel(services)) return cancelled();
|
|
81
88
|
|
|
82
|
-
// ---
|
|
83
|
-
const
|
|
84
|
-
|
|
89
|
+
// --- API Keys (optional) ---
|
|
90
|
+
const keyHints = {
|
|
91
|
+
stripe: 'Starts with sk_live_ or sk_test_',
|
|
92
|
+
github: 'Personal access token (ghp_ or github_pat_)',
|
|
93
|
+
slack: 'Bot token (xoxb-...)',
|
|
94
|
+
notion: 'Internal integration token (ntn_ or secret_)',
|
|
95
|
+
discord: 'Bot token from Discord Developer Portal',
|
|
96
|
+
twitter: 'OAuth 2.0 user access token',
|
|
97
|
+
agentmail: 'API key (am_...)',
|
|
98
|
+
};
|
|
99
|
+
const serviceNames = {
|
|
100
|
+
stripe: 'Stripe', github: 'GitHub', slack: 'Slack', notion: 'Notion',
|
|
101
|
+
discord: 'Discord', twitter: 'Twitter/X', agentmail: 'AgentMail',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const addKeys = await p.confirm({
|
|
105
|
+
message: 'Want to add your API keys now? (you can also do this later)',
|
|
106
|
+
initialValue: true,
|
|
85
107
|
});
|
|
108
|
+
if (p.isCancel(addKeys)) return cancelled();
|
|
109
|
+
|
|
110
|
+
const apiKeys = {};
|
|
111
|
+
if (addKeys) {
|
|
112
|
+
for (const svc of services) {
|
|
113
|
+
const key = await p.password({
|
|
114
|
+
message: `${serviceNames[svc] || svc} API key ${pc.dim(`(${keyHints[svc] || 'paste key'})`)}`,
|
|
115
|
+
validate: (val) => {
|
|
116
|
+
if (!val) return; // allow skip
|
|
117
|
+
if (val.length < 10) return 'That looks too short — press Enter to skip';
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
if (p.isCancel(key)) return cancelled();
|
|
121
|
+
if (key && key.trim()) apiKeys[svc] = key.trim();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --- Confirm ---
|
|
126
|
+
const keyCount = Object.keys(apiKeys).length;
|
|
127
|
+
const confirmMsg = keyCount
|
|
128
|
+
? `Create Obol account ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''} and ${keyCount} API key${keyCount > 1 ? 's' : ''}?`
|
|
129
|
+
: `Create Obol account ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''}?`;
|
|
130
|
+
const confirmed = await p.confirm({ message: confirmMsg });
|
|
86
131
|
if (p.isCancel(confirmed) || !confirmed) return cancelled();
|
|
87
132
|
|
|
88
133
|
// --- Execute! ---
|
|
89
134
|
console.log();
|
|
90
135
|
const s = p.spinner();
|
|
91
136
|
|
|
92
|
-
// 1.
|
|
93
|
-
s.start('
|
|
94
|
-
|
|
137
|
+
// 1. Sign up
|
|
138
|
+
s.start('Creating your account...');
|
|
139
|
+
let jwt;
|
|
140
|
+
let expiresAt;
|
|
95
141
|
try {
|
|
96
|
-
await
|
|
97
|
-
|
|
142
|
+
const result = await signup(url, agentId, services, referralCode?.trim() || undefined);
|
|
143
|
+
jwt = result.token;
|
|
144
|
+
expiresAt = new Date(result.expires_at * 1000);
|
|
145
|
+
|
|
146
|
+
if (result.referral) {
|
|
147
|
+
s.stop(pc.green(`Account "${agentId}" created with referral! $${result.referral.signup_bonus_referee} bonus unlocks after your first API call `) + cheer());
|
|
148
|
+
} else {
|
|
149
|
+
s.stop(pc.green(`Account "${agentId}" created! `) + cheer());
|
|
150
|
+
}
|
|
98
151
|
} catch (err) {
|
|
99
152
|
if (err.status === 409) {
|
|
100
|
-
s.stop(pc.
|
|
153
|
+
s.stop(pc.red(`That account name was revoked — try a different name.`));
|
|
101
154
|
} else {
|
|
102
|
-
s.stop(pc.red(`Couldn't
|
|
103
|
-
p.outro(pc.dim('Check your admin key and try again.'));
|
|
104
|
-
process.exit(1);
|
|
155
|
+
s.stop(pc.red(`Couldn't create account: ${err.message}`));
|
|
105
156
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// 2. Issue JWT
|
|
109
|
-
s.start('Getting your agent a JWT token...');
|
|
110
|
-
let jwt;
|
|
111
|
-
try {
|
|
112
|
-
const tokenData = await issueToken(url, adminKey, agentId);
|
|
113
|
-
jwt = tokenData.token;
|
|
114
|
-
const expiresAt = new Date(tokenData.expires_at * 1000);
|
|
115
|
-
s.stop(pc.green('JWT issued ') + pc.dim(`(expires ${expiresAt.toLocaleDateString()})`));
|
|
116
|
-
} catch (err) {
|
|
117
|
-
s.stop(pc.red(`Couldn't issue token: ${err.message}`));
|
|
118
|
-
p.outro(pc.dim('Something went wrong. Try again or check the gateway logs.'));
|
|
157
|
+
p.outro(pc.dim('Try again.'));
|
|
119
158
|
process.exit(1);
|
|
120
159
|
}
|
|
121
160
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
161
|
+
// 2. Store API keys (if any)
|
|
162
|
+
if (Object.keys(apiKeys).length > 0) {
|
|
163
|
+
for (const [svc, key] of Object.entries(apiKeys)) {
|
|
164
|
+
s.start(`Encrypting ${serviceNames[svc] || svc} key...`);
|
|
165
|
+
try {
|
|
166
|
+
await storeBYOK(url, jwt, svc, key);
|
|
167
|
+
s.stop(pc.green(`${serviceNames[svc] || svc} connected! `) + cheer());
|
|
168
|
+
} catch (err) {
|
|
169
|
+
s.stop(pc.yellow(`Couldn't store ${serviceNames[svc] || svc} key: ${err.message}`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
130
172
|
}
|
|
131
173
|
|
|
132
|
-
//
|
|
174
|
+
// 3. Save config
|
|
133
175
|
s.start('Saving configuration...');
|
|
134
176
|
const vars = {
|
|
135
177
|
OBOL_GATEWAY_URL: url,
|
|
136
178
|
OBOL_AGENT_ID: agentId,
|
|
137
179
|
OBOL_JWT_TOKEN: jwt,
|
|
138
180
|
};
|
|
139
|
-
if (walletAddress) {
|
|
140
|
-
vars.OBOL_WALLET_ADDRESS = walletAddress;
|
|
141
|
-
}
|
|
142
181
|
const envPath = saveEnv(vars);
|
|
143
182
|
s.stop(pc.green(`Saved to ${envPath}`));
|
|
144
183
|
|
|
145
|
-
//
|
|
184
|
+
// 4. Detect project type and install SDK
|
|
185
|
+
let lang = detectProject();
|
|
186
|
+
let sdkInstalled = false;
|
|
187
|
+
let integrationFile = null;
|
|
188
|
+
|
|
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
|
+
const hasPackageJson = existsSync(resolve(process.cwd(), 'package.json'));
|
|
201
|
+
|
|
202
|
+
if (lang === 'js') {
|
|
203
|
+
if (hasPackageJson) {
|
|
204
|
+
s.start('Installing @obol/sdk...');
|
|
205
|
+
try {
|
|
206
|
+
execSync('npm install @obol/sdk', { stdio: 'pipe', timeout: 60000 });
|
|
207
|
+
s.stop(pc.green('SDK installed! ') + cheer());
|
|
208
|
+
sdkInstalled = true;
|
|
209
|
+
} catch {
|
|
210
|
+
s.stop(pc.yellow('Could not auto-install SDK. Run manually: npm install @obol/sdk'));
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
p.log.info(`Install the SDK when you're ready: ${pc.green('npm install @obol/sdk')}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Generate integration file
|
|
217
|
+
const obolFile = resolve(process.cwd(), 'obol.js');
|
|
218
|
+
if (!existsSync(obolFile)) {
|
|
219
|
+
s.start('Generating integration file...');
|
|
220
|
+
writeFileSync(obolFile, jsIntegrationFile(agentId, services));
|
|
221
|
+
s.stop(pc.green('Created obol.js'));
|
|
222
|
+
integrationFile = 'obol.js';
|
|
223
|
+
}
|
|
224
|
+
} else if (lang === 'python') {
|
|
225
|
+
s.start('Installing obol-sdk...');
|
|
226
|
+
try {
|
|
227
|
+
execSync('pip install obol-sdk', { stdio: 'pipe', timeout: 60000 });
|
|
228
|
+
s.stop(pc.green('SDK installed! ') + cheer());
|
|
229
|
+
sdkInstalled = true;
|
|
230
|
+
} catch {
|
|
231
|
+
s.stop(pc.yellow('Could not auto-install SDK. Run manually: pip install obol-sdk'));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Generate integration file
|
|
235
|
+
const obolFile = resolve(process.cwd(), 'obol_setup.py');
|
|
236
|
+
if (!existsSync(obolFile)) {
|
|
237
|
+
s.start('Generating integration file...');
|
|
238
|
+
writeFileSync(obolFile, pyIntegrationFile(agentId, services));
|
|
239
|
+
s.stop(pc.green('Created obol_setup.py'));
|
|
240
|
+
integrationFile = 'obol_setup.py';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 5. Test connectivity
|
|
245
|
+
s.start('Testing connection...');
|
|
246
|
+
try {
|
|
247
|
+
const epData = await listEndpoints(url, jwt);
|
|
248
|
+
const eps = epData.endpoints || [];
|
|
249
|
+
const names = eps.map(e => e.provider || e.name).filter(Boolean);
|
|
250
|
+
s.stop(pc.green(`Connected! ${eps.length} endpoints available`) + (names.length ? ` (${names.join(', ')})` : ''));
|
|
251
|
+
} catch {
|
|
252
|
+
s.stop(pc.yellow('Could not verify connection — run obol test later to check'));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// --- Summary ---
|
|
146
256
|
console.log();
|
|
147
|
-
p.log.success(pc.bold(`Your agent
|
|
257
|
+
p.log.success(pc.bold(`Your agent is connected to Obol!`));
|
|
148
258
|
console.log();
|
|
149
259
|
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
260
|
+
if (sdkInstalled) {
|
|
261
|
+
console.log(` ${pc.dim('SDK installed:')} ${pc.green(lang === 'js' ? '@obol/sdk' : 'obol-sdk')}`);
|
|
262
|
+
}
|
|
263
|
+
if (integrationFile) {
|
|
264
|
+
console.log(` ${pc.dim('Integration file:')} ${pc.green('./' + integrationFile)}`);
|
|
154
265
|
}
|
|
266
|
+
console.log(` ${pc.dim('Config saved:')} ${pc.green(envPath)}`);
|
|
267
|
+
console.log();
|
|
155
268
|
|
|
269
|
+
// Prominent $0 balance warning
|
|
270
|
+
p.log.warn(pc.bold(pc.yellow('Your balance is $0.00 — you need credits before your agent can execute.')));
|
|
156
271
|
console.log();
|
|
157
|
-
|
|
158
|
-
console.log(` ${pc.
|
|
159
|
-
console.log(`
|
|
272
|
+
|
|
273
|
+
console.log(` ${pc.bold('Next steps:')}`);
|
|
274
|
+
console.log(` ${pc.dim('1.')} ${pc.bold('Buy credits')} ${pc.dim('(required)')}: ${pc.green('npx @obol/cli credits buy')}`);
|
|
275
|
+
console.log(` ${pc.dim('or visit:')} ${pc.cyan('obolagents.com/dashboard')}`);
|
|
276
|
+
if (lang === 'js' && integrationFile) {
|
|
277
|
+
console.log(` ${pc.dim('2.')} Use in your code:`);
|
|
278
|
+
console.log(` ${pc.cyan("import { obol } from './obol.js';")}`);
|
|
279
|
+
console.log(` ${pc.cyan("const result = await obol.execute(2, 'Create issue ...');")}`);
|
|
280
|
+
} else if (lang === 'python' && integrationFile) {
|
|
281
|
+
console.log(` ${pc.dim('2.')} Use in your code:`);
|
|
282
|
+
console.log(` ${pc.cyan("from obol_setup import obol")}`);
|
|
283
|
+
console.log(` ${pc.cyan("result = obol.execute(2, 'Create issue ...', idempotency_key='...')")}`);
|
|
284
|
+
}
|
|
285
|
+
console.log();
|
|
286
|
+
console.log(` ${pc.dim('Dashboard:')} ${pc.green('npx @obol/cli token')} → paste at ${pc.cyan('obolagents.com/dashboard')}`);
|
|
287
|
+
console.log(` ${pc.dim('Token:')} Refreshes automatically — run ${pc.green('npx @obol/cli test')} anytime to verify.`);
|
|
288
|
+
console.log();
|
|
289
|
+
|
|
290
|
+
p.log.info(`${pc.bold('Earn credits by referring other agents!')} Run ${pc.green('npx @obol/cli referral code')} to get started.`);
|
|
160
291
|
console.log();
|
|
161
292
|
|
|
162
293
|
p.outro('Go build something awesome. ' + pc.green('⚡'));
|
|
163
294
|
}
|
|
164
295
|
|
|
296
|
+
function detectProject() {
|
|
297
|
+
const cwd = process.cwd();
|
|
298
|
+
if (existsSync(resolve(cwd, 'package.json'))) return 'js';
|
|
299
|
+
if (existsSync(resolve(cwd, 'requirements.txt'))) return 'python';
|
|
300
|
+
if (existsSync(resolve(cwd, 'pyproject.toml'))) return 'python';
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function jsIntegrationFile(agentId, services) {
|
|
305
|
+
const providerMap = { stripe: 1, github: 2, slack: 3, notion: 4, discord: 5, twitter: 6, agentmail: 7 };
|
|
306
|
+
const examples = services
|
|
307
|
+
.map(svc => {
|
|
308
|
+
const id = providerMap[svc];
|
|
309
|
+
const ex = {
|
|
310
|
+
stripe: `await obol.execute(${id}, 'Create invoice for $50 to customer@example.com');`,
|
|
311
|
+
github: `await obol.execute(${id}, 'Create issue titled "Bug report" in owner/repo');`,
|
|
312
|
+
slack: `await obol.execute(${id}, 'Send message "Hello team" to #general');`,
|
|
313
|
+
notion: `await obol.execute(${id}, 'Create page titled "Meeting Notes"');`,
|
|
314
|
+
discord: `await obol.execute(${id}, 'Send message "Hello" to channel 123456');`,
|
|
315
|
+
twitter: `await obol.execute(${id}, 'Post tweet: Just shipped a new feature!');`,
|
|
316
|
+
agentmail: `await obol.execute(${id}, 'Send email to user@example.com subject "Hello"');`,
|
|
317
|
+
};
|
|
318
|
+
return `// ${ex[svc] || `await obol.execute(${id}, 'your intent here');`}`;
|
|
319
|
+
})
|
|
320
|
+
.join('\n');
|
|
321
|
+
|
|
322
|
+
return `import { ObolClient } from '@obol/sdk';
|
|
323
|
+
|
|
324
|
+
// Pre-configured Obol client — reads OBOL_JWT_TOKEN from .env automatically
|
|
325
|
+
export const obol = new ObolClient();
|
|
326
|
+
|
|
327
|
+
// Provider IDs: 1=Stripe, 2=GitHub, 3=Slack, 4=Notion, 5=Discord, 6=Twitter, 7=AgentMail
|
|
328
|
+
//
|
|
329
|
+
// Usage:
|
|
330
|
+
// import { obol } from './obol.js';
|
|
331
|
+
${examples}
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function pyIntegrationFile(agentId, services) {
|
|
336
|
+
const providerMap = { stripe: 1, github: 2, slack: 3, notion: 4, discord: 5, twitter: 6, agentmail: 7 };
|
|
337
|
+
const examples = services
|
|
338
|
+
.map(svc => {
|
|
339
|
+
const id = providerMap[svc];
|
|
340
|
+
const ex = {
|
|
341
|
+
stripe: `# obol.execute(${id}, 'Create invoice for $50 to customer@example.com', idempotency_key='unique-key')`,
|
|
342
|
+
github: `# obol.execute(${id}, 'Create issue titled "Bug report" in owner/repo', idempotency_key='unique-key')`,
|
|
343
|
+
slack: `# obol.execute(${id}, 'Send message "Hello team" to #general', idempotency_key='unique-key')`,
|
|
344
|
+
notion: `# obol.execute(${id}, 'Create page titled "Meeting Notes"', idempotency_key='unique-key')`,
|
|
345
|
+
discord: `# obol.execute(${id}, 'Send message "Hello" to channel 123456', idempotency_key='unique-key')`,
|
|
346
|
+
twitter: `# obol.execute(${id}, 'Post tweet: Just shipped a new feature!', idempotency_key='unique-key')`,
|
|
347
|
+
agentmail: `# obol.execute(${id}, 'Send email to user@example.com subject "Hello"', idempotency_key='unique-key')`,
|
|
348
|
+
};
|
|
349
|
+
return ex[svc] || `# obol.execute(${id}, 'your intent here', idempotency_key='unique-key')`;
|
|
350
|
+
})
|
|
351
|
+
.join('\n');
|
|
352
|
+
|
|
353
|
+
return `import os
|
|
354
|
+
from obol_sdk import ObolClient
|
|
355
|
+
|
|
356
|
+
# Pre-configured Obol client — reads from environment variables
|
|
357
|
+
obol = ObolClient(
|
|
358
|
+
gateway_url=os.getenv("OBOL_GATEWAY_URL", "https://www.obolagents.com"),
|
|
359
|
+
agent_id=os.getenv("OBOL_AGENT_ID", "${agentId}"),
|
|
360
|
+
jwt_token=os.getenv("OBOL_JWT_TOKEN", ""),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Provider IDs: 1=Stripe, 2=GitHub, 3=Slack, 4=Notion, 5=Discord, 6=Twitter, 7=AgentMail
|
|
364
|
+
#
|
|
365
|
+
# Usage:
|
|
366
|
+
# from obol_setup import obol
|
|
367
|
+
${examples}
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
|
|
165
371
|
function cancelled() {
|
|
166
372
|
p.cancel('Setup cancelled. Come back anytime!');
|
|
167
373
|
process.exit(0);
|