@obol/cli 0.1.3 → 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 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
+ // Refresh failed — show existing token anyway
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(env.OBOL_JWT_TOKEN)}\n`);
62
+ console.log(`\n ${pc.cyan(token)}\n`);
48
63
  break;
49
64
  }
50
65
 
@@ -133,15 +148,68 @@ 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) {
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
+
136
203
  case 'help':
137
204
  case '--help':
138
205
  case '-h':
139
206
  case undefined:
140
207
  console.log(`
141
- ${pc.bold(pc.green('obol'))} — CLI for the Obol Agent Execution Gateway
208
+ ${pc.bold(pc.green('obol'))} — Connect your agent to the Obol Execution Gateway
142
209
 
143
210
  ${pc.bold('Commands:')}
144
- ${pc.green('setup')} Create a new agent (interactive wizard)
211
+ ${pc.green('setup')} Connect your agent to Obol (interactive wizard)
212
+ ${pc.green('test')} Verify your Obol connection is working
145
213
  ${pc.green('status')} Check agent health, balance & connections
146
214
  ${pc.green('token')} Show your JWT token (for the dashboard)
147
215
  ${pc.green('connect')} ${pc.dim('<provider>')} Add a service API key (github, slack, etc.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obol/cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "CLI for the Obol Agent Execution Gateway",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -146,8 +146,19 @@ export async function validateReferralCode(gatewayUrl, code) {
146
146
  });
147
147
  }
148
148
 
149
+ export async function listEndpoints(gatewayUrl, jwt) {
150
+ return request(`${gatewayUrl}/v1/endpoints`, { headers: jwtHeaders(jwt) });
151
+ }
152
+
149
153
  export async function getAgentInfo(gatewayUrl, adminKey, agentId) {
150
154
  return request(`${gatewayUrl}/v1/agents/${agentId}`, {
151
155
  headers: adminHeaders(adminKey),
152
156
  });
153
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/setup.js CHANGED
@@ -1,6 +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 { checkHealth, signup, storeBYOK, validateReferralCode } from './api.js';
6
+ import { checkHealth, signup, storeBYOK, validateReferralCode, listEndpoints } from './api.js';
4
7
  import { saveEnv } from './config.js';
5
8
 
6
9
  const cheers = ['Nice!', 'Boom!', "Let's go.", 'Easy.', 'Done.'];
@@ -10,7 +13,7 @@ export async function runSetup() {
10
13
  console.log();
11
14
  p.intro(pc.bgGreen(pc.black(' Welcome to Obol! ')));
12
15
 
13
- p.log.info("Let's get your agent connected. Takes about 30 seconds.");
16
+ p.log.info("Let's connect your agent to Obol. Takes about 60 seconds.");
14
17
 
15
18
  const url = 'https://www.obolagents.com';
16
19
 
@@ -28,12 +31,12 @@ export async function runSetup() {
28
31
  process.exit(1);
29
32
  }
30
33
 
31
- // --- Agent Name ---
34
+ // --- Account Name ---
32
35
  const agentId = await p.text({
33
- message: 'Pick a name for your agent',
36
+ message: 'Pick a name for your Obol account',
34
37
  placeholder: 'my-agent',
35
38
  validate: (val) => {
36
- if (!val) return 'Your agent needs a name!';
39
+ if (!val) return 'Your account needs a name!';
37
40
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{1,62}[a-zA-Z0-9]$/.test(val)) {
38
41
  return 'Letters, numbers, hyphens, and underscores only (3-64 chars)';
39
42
  }
@@ -122,8 +125,8 @@ export async function runSetup() {
122
125
  // --- Confirm ---
123
126
  const keyCount = Object.keys(apiKeys).length;
124
127
  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' : ''}?`;
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' : ''}?`;
127
130
  const confirmed = await p.confirm({ message: confirmMsg });
128
131
  if (p.isCancel(confirmed) || !confirmed) return cancelled();
129
132
 
@@ -131,8 +134,8 @@ export async function runSetup() {
131
134
  console.log();
132
135
  const s = p.spinner();
133
136
 
134
- // 1. Sign up (creates agent + JWT + policy in one call)
135
- s.start('Creating your agent...');
137
+ // 1. Sign up
138
+ s.start('Creating your account...');
136
139
  let jwt;
137
140
  let expiresAt;
138
141
  try {
@@ -141,17 +144,17 @@ export async function runSetup() {
141
144
  expiresAt = new Date(result.expires_at * 1000);
142
145
 
143
146
  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());
147
+ s.stop(pc.green(`Account "${agentId}" created with referral! $${result.referral.signup_bonus_referee} bonus unlocks after your first API call `) + cheer());
145
148
  } else {
146
- s.stop(pc.green(`Agent "${agentId}" created! `) + cheer());
149
+ s.stop(pc.green(`Account "${agentId}" created! `) + cheer());
147
150
  }
148
151
  } catch (err) {
149
152
  if (err.status === 409) {
150
- s.stop(pc.red(`The name "${agentId}" is already taken — try a different name.`));
153
+ s.stop(pc.red(`That account name was revoked — try a different name.`));
151
154
  } else {
152
- s.stop(pc.red(`Couldn't create agent: ${err.message}`));
155
+ s.stop(pc.red(`Couldn't create account: ${err.message}`));
153
156
  }
154
- p.outro(pc.dim('Try again with a different name.'));
157
+ p.outro(pc.dim('Try again.'));
155
158
  process.exit(1);
156
159
  }
157
160
 
@@ -178,23 +181,193 @@ export async function runSetup() {
178
181
  const envPath = saveEnv(vars);
179
182
  s.stop(pc.green(`Saved to ${envPath}`));
180
183
 
181
- // --- Celebration! ---
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 ---
256
+ console.log();
257
+ p.log.success(pc.bold(`Your agent is connected to Obol!`));
182
258
  console.log();
183
- p.log.success(pc.bold(`Your agent "${agentId}" is live on Obol!`));
259
+
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)}`);
265
+ }
266
+ console.log(` ${pc.dim('Config saved:')} ${pc.green(envPath)}`);
267
+ console.log();
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.')));
184
271
  console.log();
185
272
 
186
- console.log(` ${pc.dim('Check status:')} ${pc.green('npx @obol/cli status')}`);
187
- console.log(` ${pc.dim('Add a key:')} ${pc.green('npx @obol/cli connect github')}`);
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')}`);
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.`);
190
288
  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.'));
289
+
290
+ p.log.info(`${pc.bold('Earn credits by referring other agents!')} Run ${pc.green('npx @obol/cli referral code')} to get started.`);
193
291
  console.log();
194
292
 
195
293
  p.outro('Go build something awesome. ' + pc.green('⚡'));
196
294
  }
197
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
+
198
371
  function cancelled() {
199
372
  p.cancel('Setup cancelled. Come back anytime!');
200
373
  process.exit(0);