@obol/cli 0.1.1 → 0.1.3
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 +78 -3
- package/package.json +1 -1
- package/src/api.js +52 -2
- package/src/config.js +0 -1
- package/src/connect.js +2 -1
- package/src/credits.js +73 -0
- package/src/setup.js +102 -69
package/bin/obol.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import { runSetup } from '../src/setup.js';
|
|
4
4
|
import { runStatus } from '../src/status.js';
|
|
5
5
|
import { runConnect } from '../src/connect.js';
|
|
6
|
-
import {
|
|
6
|
+
import { runCreditsBuy } from '../src/credits.js';
|
|
7
|
+
import { getBalance, getTiers, createReferralCode, getReferralStats } from '../src/api.js';
|
|
7
8
|
import { loadEnv } from '../src/config.js';
|
|
8
9
|
import pc from 'picocolors';
|
|
9
10
|
|
|
10
11
|
const command = process.argv[2];
|
|
12
|
+
const subcommand = process.argv[3];
|
|
11
13
|
|
|
12
14
|
async function main() {
|
|
13
15
|
switch (command) {
|
|
@@ -20,9 +22,32 @@ async function main() {
|
|
|
20
22
|
break;
|
|
21
23
|
|
|
22
24
|
case 'connect':
|
|
23
|
-
await runConnect(
|
|
25
|
+
await runConnect(subcommand);
|
|
24
26
|
break;
|
|
25
27
|
|
|
28
|
+
case 'credits':
|
|
29
|
+
if (subcommand === 'buy') {
|
|
30
|
+
await runCreditsBuy();
|
|
31
|
+
} else {
|
|
32
|
+
console.log(`\n ${pc.bold('obol credits')} commands:\n`);
|
|
33
|
+
console.log(` ${pc.green('buy')} Purchase credits via Stripe\n`);
|
|
34
|
+
console.log(` Usage: ${pc.dim('npx @obol/cli credits buy')}\n`);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
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
|
+
console.log(`\n ${pc.bold('Your Agent Token')}`);
|
|
45
|
+
console.log(` ${pc.dim('Agent:')} ${pc.green(env.OBOL_AGENT_ID || '(unknown)')}`);
|
|
46
|
+
console.log(`\n ${pc.dim('Copy this token and paste it into the dashboard at obolagents.com/dashboard')}`);
|
|
47
|
+
console.log(`\n ${pc.cyan(env.OBOL_JWT_TOKEN)}\n`);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
case 'balance': {
|
|
27
52
|
const env = loadEnv();
|
|
28
53
|
if (!env.OBOL_GATEWAY_URL || !env.OBOL_JWT_TOKEN) {
|
|
@@ -62,6 +87,52 @@ async function main() {
|
|
|
62
87
|
break;
|
|
63
88
|
}
|
|
64
89
|
|
|
90
|
+
case 'referral': {
|
|
91
|
+
const env = loadEnv();
|
|
92
|
+
if (!env.OBOL_GATEWAY_URL || !env.OBOL_JWT_TOKEN) {
|
|
93
|
+
console.log(pc.red('No agent configured. Run: obol setup'));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
if (subcommand === 'code') {
|
|
97
|
+
try {
|
|
98
|
+
const data = await createReferralCode(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
|
|
99
|
+
console.log(`\n ${pc.bold('Your Referral Code')}`);
|
|
100
|
+
console.log(` ${pc.green(pc.bold(data.code))}`);
|
|
101
|
+
console.log(`\n Share this code. Both you and the new agent earn free credits!`);
|
|
102
|
+
console.log();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.log(pc.red(` ${err.message}`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
} else if (subcommand === 'stats') {
|
|
108
|
+
try {
|
|
109
|
+
const data = await getReferralStats(env.OBOL_GATEWAY_URL, env.OBOL_JWT_TOKEN);
|
|
110
|
+
console.log(`\n ${pc.bold('Referral Stats')}`);
|
|
111
|
+
console.log(` Code: ${data.referral_code ? pc.green(data.referral_code) : pc.dim('(none yet)')}`);
|
|
112
|
+
console.log(` Eligible: ${data.referral_eligible ? pc.green('Yes') : pc.yellow('No — make a purchase first')}`);
|
|
113
|
+
const pending = data.direct_referrals_pending || 0;
|
|
114
|
+
const active = data.direct_referrals - pending;
|
|
115
|
+
console.log(` Direct refs: ${pc.bold(String(active))} active${pending > 0 ? `, ${pc.yellow(String(pending) + ' pending first payment')}` : ''}`);
|
|
116
|
+
console.log(` 2nd-level refs: ${pc.bold(String(data.second_level_referrals))}`);
|
|
117
|
+
console.log(` Commissions: ${pc.green('$' + data.total_commission_earned_usdc.toFixed(2))}`);
|
|
118
|
+
console.log(` Signup bonuses: ${pc.green('$' + data.total_signup_bonuses_earned_usdc.toFixed(2))}`);
|
|
119
|
+
console.log(` Total earned: ${pc.green(pc.bold('$' + data.total_earned_usdc.toFixed(2)))}`);
|
|
120
|
+
if (data.own_bonus_status === 'pending') {
|
|
121
|
+
console.log(`\n ${pc.yellow('Your signup bonus is pending — make your first API call to unlock it!')}`);
|
|
122
|
+
}
|
|
123
|
+
console.log();
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.log(pc.red(` ${err.message}`));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
console.log(`\n ${pc.bold('obol referral')} commands:\n`);
|
|
130
|
+
console.log(` ${pc.green('code')} Generate or show your referral code`);
|
|
131
|
+
console.log(` ${pc.green('stats')} View referral earnings and stats\n`);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
65
136
|
case 'help':
|
|
66
137
|
case '--help':
|
|
67
138
|
case '-h':
|
|
@@ -70,11 +141,15 @@ async function main() {
|
|
|
70
141
|
${pc.bold(pc.green('obol'))} — CLI for the Obol Agent Execution Gateway
|
|
71
142
|
|
|
72
143
|
${pc.bold('Commands:')}
|
|
73
|
-
${pc.green('setup')}
|
|
144
|
+
${pc.green('setup')} Create a new agent (interactive wizard)
|
|
74
145
|
${pc.green('status')} Check agent health, balance & connections
|
|
146
|
+
${pc.green('token')} Show your JWT token (for the dashboard)
|
|
75
147
|
${pc.green('connect')} ${pc.dim('<provider>')} Add a service API key (github, slack, etc.)
|
|
148
|
+
${pc.green('credits buy')} Purchase credits via Stripe
|
|
76
149
|
${pc.green('balance')} Check your credit balance
|
|
77
150
|
${pc.green('tiers')} Show credit purchase tiers
|
|
151
|
+
${pc.green('referral code')} Generate or show your referral code
|
|
152
|
+
${pc.green('referral stats')} View referral earnings
|
|
78
153
|
${pc.green('help')} Show this help message
|
|
79
154
|
|
|
80
155
|
${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),
|
|
@@ -96,6 +111,41 @@ export async function getTiers(gatewayUrl) {
|
|
|
96
111
|
return request(`${gatewayUrl}/v1/credits/tiers`);
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
export async function createCheckout(gatewayUrl, jwt, amountUsd) {
|
|
115
|
+
return request(`${gatewayUrl}/v1/credits/checkout`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: jwtHeaders(jwt),
|
|
118
|
+
body: JSON.stringify({ amount_usdc: amountUsd }),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
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
|
+
|
|
99
149
|
export async function getAgentInfo(gatewayUrl, adminKey, agentId) {
|
|
100
150
|
return request(`${gatewayUrl}/v1/agents/${agentId}`, {
|
|
101
151
|
headers: adminHeaders(adminKey),
|
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
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { getTiers, createWebCheckout } from './api.js';
|
|
4
|
+
import { loadEnv } from './config.js';
|
|
5
|
+
|
|
6
|
+
export async function runCreditsBuy() {
|
|
7
|
+
const env = loadEnv();
|
|
8
|
+
|
|
9
|
+
if (!env.OBOL_GATEWAY_URL || !env.OBOL_AGENT_ID) {
|
|
10
|
+
console.log();
|
|
11
|
+
p.log.warn('No agent configured yet.');
|
|
12
|
+
p.log.info(`Run ${pc.green('npx @obol/cli setup')} first.`);
|
|
13
|
+
console.log();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const url = env.OBOL_GATEWAY_URL;
|
|
18
|
+
|
|
19
|
+
console.log();
|
|
20
|
+
p.intro(pc.bgGreen(pc.black(' Buy Credits ')));
|
|
21
|
+
|
|
22
|
+
// Fetch tiers
|
|
23
|
+
const s = p.spinner();
|
|
24
|
+
s.start('Loading credit tiers...');
|
|
25
|
+
let tiers;
|
|
26
|
+
try {
|
|
27
|
+
const data = await getTiers(url);
|
|
28
|
+
tiers = data.tiers;
|
|
29
|
+
s.stop(pc.green(`${tiers.length} tiers available`));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
s.stop(pc.red(`Couldn't load tiers: ${err.message}`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Pick a tier
|
|
36
|
+
const tier = await p.select({
|
|
37
|
+
message: 'Which credit pack?',
|
|
38
|
+
options: tiers.map(t => ({
|
|
39
|
+
value: t,
|
|
40
|
+
label: `${t.name} — $${t.price_usd}`,
|
|
41
|
+
hint: t.bonus_pct > 0 ? `${t.credits_usdc} credits (+${t.bonus_pct}% bonus)` : `${t.credits_usdc} credits`,
|
|
42
|
+
})),
|
|
43
|
+
});
|
|
44
|
+
if (p.isCancel(tier)) { p.cancel('Cancelled.'); return; }
|
|
45
|
+
|
|
46
|
+
const confirmed = await p.confirm({
|
|
47
|
+
message: `Purchase ${pc.bold(tier.name)} for ${pc.green('$' + tier.price_usd)}? (${tier.credits_usdc} credits)`,
|
|
48
|
+
});
|
|
49
|
+
if (p.isCancel(confirmed) || !confirmed) { p.cancel('Cancelled.'); return; }
|
|
50
|
+
|
|
51
|
+
// Create checkout session (returns a Stripe checkout URL)
|
|
52
|
+
s.start('Creating Stripe checkout...');
|
|
53
|
+
try {
|
|
54
|
+
const result = await createWebCheckout(url, env.OBOL_AGENT_ID, tier.price_usd);
|
|
55
|
+
s.stop(pc.green('Checkout ready!'));
|
|
56
|
+
|
|
57
|
+
console.log();
|
|
58
|
+
p.log.info(`Tier: ${pc.bold(tier.name)} — $${tier.price_usd} → ${pc.green(tier.credits_usdc + ' credits')}`);
|
|
59
|
+
console.log();
|
|
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}"`);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
s.stop(pc.red(`Couldn't create checkout: ${err.message}`));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
p.outro(pc.dim('Credits are added to your balance once payment completes.'));
|
|
73
|
+
}
|
package/src/setup.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import {
|
|
4
|
-
import { checkHealth, registerAgent, issueToken, createPolicy } from './api.js';
|
|
3
|
+
import { checkHealth, signup, storeBYOK, validateReferralCode } from './api.js';
|
|
5
4
|
import { saveEnv } from './config.js';
|
|
6
5
|
|
|
7
6
|
const cheers = ['Nice!', 'Boom!', "Let's go.", 'Easy.', 'Done.'];
|
|
@@ -11,36 +10,27 @@ export async function runSetup() {
|
|
|
11
10
|
console.log();
|
|
12
11
|
p.intro(pc.bgGreen(pc.black(' Welcome to Obol! ')));
|
|
13
12
|
|
|
14
|
-
p.log.info("Let's get your agent connected.
|
|
13
|
+
p.log.info("Let's get your agent connected. Takes about 30 seconds.");
|
|
15
14
|
|
|
16
15
|
const url = 'https://www.obolagents.com';
|
|
17
16
|
|
|
18
17
|
// Verify connectivity
|
|
19
18
|
const healthSpin = p.spinner();
|
|
20
|
-
healthSpin.start('
|
|
19
|
+
healthSpin.start('Connecting to Obol...');
|
|
21
20
|
try {
|
|
22
21
|
await checkHealth(url);
|
|
23
|
-
healthSpin.stop(pc.green('
|
|
22
|
+
healthSpin.stop(pc.green('Connected! ') + cheer());
|
|
24
23
|
} catch (err) {
|
|
25
24
|
healthSpin.stop(pc.red('Hmm, couldn\'t reach the gateway.'));
|
|
26
|
-
p.log.error(`Make sure
|
|
25
|
+
p.log.error(`Make sure you have an internet connection.`);
|
|
27
26
|
p.log.error(`Details: ${err.message}`);
|
|
28
|
-
p.outro(pc.dim('
|
|
27
|
+
p.outro(pc.dim('Try again in a moment.'));
|
|
29
28
|
process.exit(1);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
// --- Admin Key ---
|
|
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
31
|
// --- Agent Name ---
|
|
42
32
|
const agentId = await p.text({
|
|
43
|
-
message: '
|
|
33
|
+
message: 'Pick a name for your agent',
|
|
44
34
|
placeholder: 'my-agent',
|
|
45
35
|
validate: (val) => {
|
|
46
36
|
if (!val) return 'Your agent needs a name!';
|
|
@@ -51,18 +41,31 @@ export async function runSetup() {
|
|
|
51
41
|
});
|
|
52
42
|
if (p.isCancel(agentId)) return cancelled();
|
|
53
43
|
|
|
54
|
-
// ---
|
|
55
|
-
|
|
56
|
-
message: `Got a
|
|
57
|
-
placeholder: '
|
|
44
|
+
// --- Referral Code ---
|
|
45
|
+
let referralCode = await p.text({
|
|
46
|
+
message: `Got a referral code? ${pc.dim('(earns you $5 in free credits — press Enter to skip)')}`,
|
|
47
|
+
placeholder: 'e.g. X7K9M2QP',
|
|
58
48
|
validate: (val) => {
|
|
59
|
-
if (!val) return;
|
|
60
|
-
if (!/^
|
|
61
|
-
return '
|
|
49
|
+
if (!val) return;
|
|
50
|
+
if (!/^[A-Za-z0-9]{6,12}$/.test(val.trim())) {
|
|
51
|
+
return 'Referral codes are 6-12 alphanumeric characters';
|
|
62
52
|
}
|
|
63
53
|
},
|
|
64
54
|
});
|
|
65
|
-
if (p.isCancel(
|
|
55
|
+
if (p.isCancel(referralCode)) return cancelled();
|
|
56
|
+
|
|
57
|
+
// --- Validate Referral Code ---
|
|
58
|
+
if (referralCode && referralCode.trim()) {
|
|
59
|
+
const valSpin = p.spinner();
|
|
60
|
+
valSpin.start('Checking referral code...');
|
|
61
|
+
try {
|
|
62
|
+
const validation = await validateReferralCode(url, referralCode.trim());
|
|
63
|
+
valSpin.stop(pc.green(`Referral code valid! You'll get $${validation.signup_bonus_usdc} in credits after your first API call.`));
|
|
64
|
+
} catch (err) {
|
|
65
|
+
valSpin.stop(pc.yellow('That referral code isn\'t valid — continuing without it.'));
|
|
66
|
+
referralCode = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
66
69
|
|
|
67
70
|
// --- Services ---
|
|
68
71
|
const services = await p.multiselect({
|
|
@@ -74,71 +77,104 @@ export async function runSetup() {
|
|
|
74
77
|
{ value: 'notion', label: 'Notion', hint: 'pages, databases' },
|
|
75
78
|
{ value: 'discord', label: 'Discord', hint: 'messages, servers' },
|
|
76
79
|
{ value: 'twitter', label: 'Twitter/X', hint: 'tweets, threads' },
|
|
80
|
+
{ value: 'agentmail', label: 'AgentMail', hint: 'email inboxes, send & receive' },
|
|
77
81
|
],
|
|
78
82
|
required: true,
|
|
79
83
|
});
|
|
80
84
|
if (p.isCancel(services)) return cancelled();
|
|
81
85
|
|
|
82
|
-
// ---
|
|
83
|
-
const
|
|
84
|
-
|
|
86
|
+
// --- API Keys (optional) ---
|
|
87
|
+
const keyHints = {
|
|
88
|
+
stripe: 'Starts with sk_live_ or sk_test_',
|
|
89
|
+
github: 'Personal access token (ghp_ or github_pat_)',
|
|
90
|
+
slack: 'Bot token (xoxb-...)',
|
|
91
|
+
notion: 'Internal integration token (ntn_ or secret_)',
|
|
92
|
+
discord: 'Bot token from Discord Developer Portal',
|
|
93
|
+
twitter: 'OAuth 2.0 user access token',
|
|
94
|
+
agentmail: 'API key (am_...)',
|
|
95
|
+
};
|
|
96
|
+
const serviceNames = {
|
|
97
|
+
stripe: 'Stripe', github: 'GitHub', slack: 'Slack', notion: 'Notion',
|
|
98
|
+
discord: 'Discord', twitter: 'Twitter/X', agentmail: 'AgentMail',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const addKeys = await p.confirm({
|
|
102
|
+
message: 'Want to add your API keys now? (you can also do this later)',
|
|
103
|
+
initialValue: true,
|
|
85
104
|
});
|
|
105
|
+
if (p.isCancel(addKeys)) return cancelled();
|
|
106
|
+
|
|
107
|
+
const apiKeys = {};
|
|
108
|
+
if (addKeys) {
|
|
109
|
+
for (const svc of services) {
|
|
110
|
+
const key = await p.password({
|
|
111
|
+
message: `${serviceNames[svc] || svc} API key ${pc.dim(`(${keyHints[svc] || 'paste key'})`)}`,
|
|
112
|
+
validate: (val) => {
|
|
113
|
+
if (!val) return; // allow skip
|
|
114
|
+
if (val.length < 10) return 'That looks too short — press Enter to skip';
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (p.isCancel(key)) return cancelled();
|
|
118
|
+
if (key && key.trim()) apiKeys[svc] = key.trim();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- Confirm ---
|
|
123
|
+
const keyCount = Object.keys(apiKeys).length;
|
|
124
|
+
const confirmMsg = keyCount
|
|
125
|
+
? `Create agent ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''} and ${keyCount} API key${keyCount > 1 ? 's' : ''}?`
|
|
126
|
+
: `Create agent ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''}?`;
|
|
127
|
+
const confirmed = await p.confirm({ message: confirmMsg });
|
|
86
128
|
if (p.isCancel(confirmed) || !confirmed) return cancelled();
|
|
87
129
|
|
|
88
130
|
// --- Execute! ---
|
|
89
131
|
console.log();
|
|
90
132
|
const s = p.spinner();
|
|
91
133
|
|
|
92
|
-
// 1.
|
|
93
|
-
s.start('
|
|
94
|
-
|
|
134
|
+
// 1. Sign up (creates agent + JWT + policy in one call)
|
|
135
|
+
s.start('Creating your agent...');
|
|
136
|
+
let jwt;
|
|
137
|
+
let expiresAt;
|
|
95
138
|
try {
|
|
96
|
-
await
|
|
97
|
-
|
|
139
|
+
const result = await signup(url, agentId, services, referralCode?.trim() || undefined);
|
|
140
|
+
jwt = result.token;
|
|
141
|
+
expiresAt = new Date(result.expires_at * 1000);
|
|
142
|
+
|
|
143
|
+
if (result.referral) {
|
|
144
|
+
s.stop(pc.green(`Agent "${agentId}" created with referral! $${result.referral.signup_bonus_referee} bonus unlocks after your first API call `) + cheer());
|
|
145
|
+
} else {
|
|
146
|
+
s.stop(pc.green(`Agent "${agentId}" created! `) + cheer());
|
|
147
|
+
}
|
|
98
148
|
} catch (err) {
|
|
99
149
|
if (err.status === 409) {
|
|
100
|
-
s.stop(pc.
|
|
150
|
+
s.stop(pc.red(`The name "${agentId}" is already taken — try a different name.`));
|
|
101
151
|
} else {
|
|
102
|
-
s.stop(pc.red(`Couldn't
|
|
103
|
-
p.outro(pc.dim('Check your admin key and try again.'));
|
|
104
|
-
process.exit(1);
|
|
152
|
+
s.stop(pc.red(`Couldn't create agent: ${err.message}`));
|
|
105
153
|
}
|
|
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.'));
|
|
154
|
+
p.outro(pc.dim('Try again with a different name.'));
|
|
119
155
|
process.exit(1);
|
|
120
156
|
}
|
|
121
157
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
// 2. Store API keys (if any)
|
|
159
|
+
if (Object.keys(apiKeys).length > 0) {
|
|
160
|
+
for (const [svc, key] of Object.entries(apiKeys)) {
|
|
161
|
+
s.start(`Encrypting ${serviceNames[svc] || svc} key...`);
|
|
162
|
+
try {
|
|
163
|
+
await storeBYOK(url, jwt, svc, key);
|
|
164
|
+
s.stop(pc.green(`${serviceNames[svc] || svc} connected! `) + cheer());
|
|
165
|
+
} catch (err) {
|
|
166
|
+
s.stop(pc.yellow(`Couldn't store ${serviceNames[svc] || svc} key: ${err.message}`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
130
169
|
}
|
|
131
170
|
|
|
132
|
-
//
|
|
171
|
+
// 3. Save config
|
|
133
172
|
s.start('Saving configuration...');
|
|
134
173
|
const vars = {
|
|
135
174
|
OBOL_GATEWAY_URL: url,
|
|
136
175
|
OBOL_AGENT_ID: agentId,
|
|
137
176
|
OBOL_JWT_TOKEN: jwt,
|
|
138
177
|
};
|
|
139
|
-
if (walletAddress) {
|
|
140
|
-
vars.OBOL_WALLET_ADDRESS = walletAddress;
|
|
141
|
-
}
|
|
142
178
|
const envPath = saveEnv(vars);
|
|
143
179
|
s.stop(pc.green(`Saved to ${envPath}`));
|
|
144
180
|
|
|
@@ -147,16 +183,13 @@ export async function runSetup() {
|
|
|
147
183
|
p.log.success(pc.bold(`Your agent "${agentId}" is live on Obol!`));
|
|
148
184
|
console.log();
|
|
149
185
|
|
|
150
|
-
if (walletAddress) {
|
|
151
|
-
p.log.info(`Micropayments enabled — your agent pays a few cents per call from ${pc.cyan(walletAddress.slice(0, 6) + '...' + walletAddress.slice(-4))}.`);
|
|
152
|
-
} else {
|
|
153
|
-
p.log.info(`No wallet linked. Buy credits with ${pc.green('obol tiers')} or add a wallet later.`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
console.log();
|
|
157
186
|
console.log(` ${pc.dim('Check status:')} ${pc.green('npx @obol/cli status')}`);
|
|
158
187
|
console.log(` ${pc.dim('Add a key:')} ${pc.green('npx @obol/cli connect github')}`);
|
|
159
188
|
console.log(` ${pc.dim('View balance:')} ${pc.green('npx @obol/cli balance')}`);
|
|
189
|
+
console.log(` ${pc.dim('Dashboard:')} ${pc.green('npx @obol/cli token')} ${pc.dim('→ paste at obolagents.com/dashboard')}`);
|
|
190
|
+
console.log();
|
|
191
|
+
p.log.info(`${pc.bold('Earn credits by referring other agents!')} Run ${pc.green('obol referral code')} to get your referral link.`);
|
|
192
|
+
p.log.info(pc.dim('You earn $2 per signup + 10% ongoing commissions on their usage.'));
|
|
160
193
|
console.log();
|
|
161
194
|
|
|
162
195
|
p.outro('Go build something awesome. ' + pc.green('⚡'));
|