@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 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 { getBalance, getTiers } from '../src/api.js';
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(process.argv[3]);
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')} Set up a new agent (interactive wizard)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obol/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for the Obol Agent Execution Gateway",
5
5
  "type": "module",
6
6
  "bin": {
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
- return res.json();
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 registerAgent(gatewayUrl, adminKey, agentId, secret, ownerId) {
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
@@ -12,7 +12,6 @@ const OBOL_KEYS = [
12
12
  'OBOL_AGENT_ID',
13
13
  'OBOL_JWT_TOKEN',
14
14
  'OBOL_WALLET_ADDRESS',
15
- 'OBOL_ADMIN_API_KEY',
16
15
  ];
17
16
 
18
17
  export function loadEnv(path) {
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: 'Bearer token from Twitter Developer Portal', prefix: '' },
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 { randomBytes } from 'node:crypto';
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. This takes about 60 seconds.");
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('Saying hello to the gateway...');
19
+ healthSpin.start('Connecting to Obol...');
21
20
  try {
22
21
  await checkHealth(url);
23
- healthSpin.stop(pc.green('Gateway is up and running! ') + cheer());
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 it's running at ${pc.bold(url)}`);
25
+ p.log.error(`Make sure you have an internet connection.`);
27
26
  p.log.error(`Details: ${err.message}`);
28
- p.outro(pc.dim('Fix the URL and try again.'));
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: 'Give your agent a name',
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
- // --- Wallet Address ---
55
- const walletAddress = await p.text({
56
- message: `Got a USDC wallet on Base? ${pc.dim('(recommended for automatic micropayments)')}`,
57
- placeholder: '0x... or press Enter to skip',
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; // optional
60
- if (!/^0x[0-9a-fA-F]{40}$/.test(val)) {
61
- return 'That doesn\'t look like an EVM address. Should be 0x followed by 40 hex characters.';
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(walletAddress)) return cancelled();
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
- // --- Confirm ---
83
- const confirmed = await p.confirm({
84
- message: `Register ${pc.bold(pc.green(agentId))} with ${services.length} service${services.length > 1 ? 's' : ''}?`,
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. Register agent
93
- s.start('Registering your agent...');
94
- const secret = randomBytes(32).toString('base64url');
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 registerAgent(url, adminKey, agentId, secret);
97
- s.stop(pc.green(`Agent "${agentId}" registered. `) + cheer());
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.yellow(`Agent "${agentId}" already existsthat's fine, we'll keep going.`));
150
+ s.stop(pc.red(`The name "${agentId}" is already takentry a different name.`));
101
151
  } else {
102
- s.stop(pc.red(`Couldn't register agent: ${err.message}`));
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
- // 3. Create default policy
123
- s.start('Setting up execution policy...');
124
- try {
125
- await createPolicy(url, adminKey, agentId, services);
126
- s.stop(pc.green(`Policy created for ${services.join(', ')}. `) + cheer());
127
- } catch (err) {
128
- // Non-fatal agent can still work without a named policy
129
- s.stop(pc.yellow(`Couldn't create policy (${err.message}) — no worries, defaults apply.`));
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
- // 4. Save config
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('⚡'));