@0xwork/cli 1.0.1 → 1.0.2

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.
@@ -54,6 +54,26 @@ async function run(opts) {
54
54
  profile.stakedAmount = agent.staked_amount ? (Number(agent.staked_amount) / 1e18).toLocaleString() + ' AXOBOTL' : '0';
55
55
  profile.createdAt = agent.created_at;
56
56
  profile.metadataURI = agent.metadata_uri || null;
57
+
58
+ // Fetch storefront data (services, portfolio, contacts)
59
+ const agentIdStr = String(agent.chain_agent_id || agent.id);
60
+ const [svcResp, portResp, contactResp] = await Promise.all([
61
+ fetchWithTimeout(`${config.API_URL}/agents/${agentIdStr}/services`).catch(() => null),
62
+ fetchWithTimeout(`${config.API_URL}/agents/${agentIdStr}/portfolio`).catch(() => null),
63
+ fetchWithTimeout(`${config.API_URL}/agents/${agentIdStr}/contacts`).catch(() => null),
64
+ ]);
65
+ if (svcResp?.ok) {
66
+ const d = await svcResp.json();
67
+ profile.services = (d.services || []).length;
68
+ }
69
+ if (portResp?.ok) {
70
+ const d = await portResp.json();
71
+ profile.portfolio = (d.portfolio || []).length;
72
+ }
73
+ if (contactResp?.ok) {
74
+ const d = await contactResp.json();
75
+ profile.contacts = (d.contacts || []).length;
76
+ }
57
77
  }
58
78
  }
59
79
  } catch { /* API down */ }
@@ -122,6 +142,15 @@ async function run(opts) {
122
142
  keyValue('Capabilities', caps);
123
143
  }
124
144
  if (profile.handle) keyValue('Handle', profile.handle);
145
+
146
+ // Storefront summary
147
+ if (profile.services || profile.portfolio || profile.contacts) {
148
+ blank();
149
+ keyValue('Services', profile.services ? chalk.bold(`${profile.services} listed`) : chalk.dim('none'));
150
+ keyValue('Portfolio', profile.portfolio ? chalk.bold(`${profile.portfolio} items`) : chalk.dim('none'));
151
+ keyValue('Contacts', profile.contacts ? chalk.bold(`${profile.contacts} links`) : chalk.dim('none'));
152
+ }
153
+
125
154
  blank();
126
155
  hint(`Profile: https://0xwork.org/agents/${profile.agentId}`);
127
156
  blank();
@@ -0,0 +1,287 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const config = require('../config');
5
+ const { isDryRun, normalizeError } = require('../sdk');
6
+ const { success, fail, header, keyValue, blank, hint, divider } = require('../output');
7
+ const { createSpinner } = require('../spinner');
8
+ const { fmtBounty } = require('../format');
9
+ const { fetchWithTimeout } = require('../http');
10
+ const { authFetch, resolveMyAgent } = require('../auth-fetch');
11
+
12
+ const VALID_CATEGORIES = ['Marketing', 'Development', 'Research', 'Design', 'Trading', 'Other'];
13
+ const VALID_PRICING = ['fixed', 'hourly', 'custom'];
14
+ const VALID_CTA = ['contact', 'hire_now'];
15
+
16
+ function register(program) {
17
+ const svc = program
18
+ .command('service')
19
+ .description('Manage your agent\'s service listings');
20
+
21
+ // ─── service add ──────────────────────────────────────────────
22
+ svc
23
+ .command('add')
24
+ .description('Add a service listing to your agent profile')
25
+ .requiredOption('--title <text>', 'Service title')
26
+ .requiredOption('--description <text>', 'What the service includes')
27
+ .option('--category <cat>', `Category: ${VALID_CATEGORIES.join(', ')}`, 'Other')
28
+ .option('--pricing <type>', 'Pricing type: fixed, hourly, custom', 'fixed')
29
+ .option('--price <amount>', 'Price in USDC (omit for custom quote)')
30
+ .option('--duration <text>', 'Estimated duration (e.g. "7 days", "1-2 weeks")')
31
+ .option('--deliverables <items>', 'Comma-separated deliverables')
32
+ .option('--tags <items>', 'Comma-separated tags')
33
+ .option('--cta <type>', 'CTA button: contact, hire_now', 'contact')
34
+ .action(async (opts) => {
35
+ try {
36
+ await runAdd(opts);
37
+ } catch (err) {
38
+ fail(normalizeError(err));
39
+ }
40
+ });
41
+
42
+ // ─── service list ─────────────────────────────────────────────
43
+ svc
44
+ .command('list')
45
+ .description('List your agent\'s services')
46
+ .option('--address <addr>', 'View another agent\'s services (by chain ID or address)')
47
+ .action(async (opts) => {
48
+ try {
49
+ await runList(opts);
50
+ } catch (err) {
51
+ fail(normalizeError(err));
52
+ }
53
+ });
54
+
55
+ // ─── service update ───────────────────────────────────────────
56
+ svc
57
+ .command('update <id>')
58
+ .description('Update a service listing')
59
+ .option('--title <text>', 'New title')
60
+ .option('--description <text>', 'New description')
61
+ .option('--category <cat>', 'New category')
62
+ .option('--pricing <type>', 'New pricing type')
63
+ .option('--price <amount>', 'New price')
64
+ .option('--duration <text>', 'New duration')
65
+ .option('--deliverables <items>', 'New deliverables (comma-separated)')
66
+ .option('--tags <items>', 'New tags (comma-separated)')
67
+ .option('--availability <status>', 'open, booked, or paused')
68
+ .option('--cta <type>', 'contact or hire_now')
69
+ .action(async (id, opts) => {
70
+ try {
71
+ await runUpdate(id, opts);
72
+ } catch (err) {
73
+ fail(normalizeError(err));
74
+ }
75
+ });
76
+
77
+ // ─── service remove ───────────────────────────────────────────
78
+ svc
79
+ .command('remove <id>')
80
+ .description('Remove a service listing')
81
+ .action(async (id) => {
82
+ try {
83
+ await runRemove(id);
84
+ } catch (err) {
85
+ fail(normalizeError(err));
86
+ }
87
+ });
88
+ }
89
+
90
+ // ─── Implementations ────────────────────────────────────────────────
91
+
92
+ async function runAdd(opts) {
93
+ if (!VALID_CATEGORIES.includes(opts.category)) {
94
+ fail(`Invalid category: "${opts.category}"`, { suggestion: `Valid: ${VALID_CATEGORIES.join(', ')}` });
95
+ }
96
+ if (!VALID_PRICING.includes(opts.pricing)) {
97
+ fail(`Invalid pricing type: "${opts.pricing}"`, { suggestion: `Valid: ${VALID_PRICING.join(', ')}` });
98
+ }
99
+ if (opts.cta && !VALID_CTA.includes(opts.cta)) {
100
+ fail(`Invalid CTA type: "${opts.cta}"`, { suggestion: `Valid: ${VALID_CTA.join(', ')}` });
101
+ }
102
+
103
+ const spinner = createSpinner('Adding service…');
104
+ spinner.start();
105
+
106
+ const { agentId } = await resolveMyAgent();
107
+
108
+ const body = {
109
+ title: opts.title,
110
+ description: opts.description,
111
+ category: opts.category,
112
+ pricing_type: opts.pricing,
113
+ price: opts.price != null ? parseFloat(opts.price) : null,
114
+ duration: opts.duration || null,
115
+ deliverables: opts.deliverables ? opts.deliverables.split(',').map(s => s.trim()) : [],
116
+ tags: opts.tags ? opts.tags.split(',').map(s => s.trim()) : [],
117
+ cta_type: opts.cta || 'contact',
118
+ };
119
+
120
+ const data = await authFetch('POST', `/agents/${agentId}/services`, body);
121
+
122
+ spinner.succeed('Service added');
123
+
124
+ const svc = data.service;
125
+ success({
126
+ command: 'service add',
127
+ serviceId: svc.id,
128
+ title: svc.title,
129
+ category: svc.category,
130
+ price: svc.price,
131
+ pricingType: svc.pricing_type,
132
+ message: `Added service: "${svc.title}"`,
133
+ }, () => {
134
+ header('✔', 'Service Added');
135
+ keyValue('ID', chalk.bold(String(svc.id)));
136
+ keyValue('Title', chalk.bold(svc.title));
137
+ keyValue('Category', svc.category);
138
+ keyValue('Price', formatPrice(svc));
139
+ if (svc.duration) keyValue('Duration', svc.duration);
140
+ if (svc.deliverables?.length) keyValue('Deliverables', svc.deliverables.join(', '));
141
+ blank();
142
+ hint(`View: https://0xwork.org/agents/${agentId}#services`);
143
+ blank();
144
+ });
145
+ }
146
+
147
+ async function runList(opts) {
148
+ const spinner = createSpinner('Fetching services…');
149
+ spinner.start();
150
+
151
+ let agentId;
152
+ if (opts.address) {
153
+ agentId = opts.address;
154
+ } else {
155
+ const resolved = await resolveMyAgent();
156
+ agentId = resolved.agentId;
157
+ }
158
+
159
+ const resp = await fetchWithTimeout(`${config.API_URL}/agents/${agentId}/services`);
160
+ if (!resp.ok) {
161
+ spinner.fail('Failed');
162
+ const err = await resp.json().catch(() => ({}));
163
+ fail(err.error || `API returned ${resp.status}`);
164
+ }
165
+ const data = await resp.json();
166
+
167
+ spinner.stop();
168
+
169
+ const services = data.services || [];
170
+
171
+ success({
172
+ command: 'service list',
173
+ agentId,
174
+ count: services.length,
175
+ services: services.map(s => ({
176
+ id: s.id,
177
+ title: s.title,
178
+ category: s.category,
179
+ pricingType: s.pricing_type,
180
+ price: s.price,
181
+ availability: s.availability,
182
+ duration: s.duration,
183
+ })),
184
+ }, () => {
185
+ if (services.length === 0) {
186
+ header('📋', 'No Services Listed');
187
+ hint('Add one: 0xwork service add --title="..." --description="..."');
188
+ blank();
189
+ return;
190
+ }
191
+
192
+ header('📋', `${services.length} Service${services.length !== 1 ? 's' : ''}`);
193
+
194
+ for (const s of services) {
195
+ const avail = s.availability === 'open' ? chalk.green('● open')
196
+ : s.availability === 'booked' ? chalk.yellow('● booked')
197
+ : chalk.dim('● paused');
198
+
199
+ keyValue(`#${s.id}`, chalk.bold(s.title));
200
+ keyValue(' Category', s.category);
201
+ keyValue(' Price', formatPrice(s));
202
+ keyValue(' Status', avail);
203
+ if (s.duration) keyValue(' Duration', s.duration);
204
+ if (s.deliverables?.length) keyValue(' Delivers', s.deliverables.join(', '));
205
+ divider(36);
206
+ }
207
+ blank();
208
+ });
209
+ }
210
+
211
+ async function runUpdate(id, opts) {
212
+ const spinner = createSpinner('Updating service…');
213
+ spinner.start();
214
+
215
+ const { agentId } = await resolveMyAgent();
216
+
217
+ const body = {};
218
+ if (opts.title) body.title = opts.title;
219
+ if (opts.description) body.description = opts.description;
220
+ if (opts.category) {
221
+ if (!VALID_CATEGORIES.includes(opts.category)) {
222
+ spinner.fail('Invalid category');
223
+ fail(`Invalid category: "${opts.category}"`, { suggestion: `Valid: ${VALID_CATEGORIES.join(', ')}` });
224
+ }
225
+ body.category = opts.category;
226
+ }
227
+ if (opts.pricing) body.pricing_type = opts.pricing;
228
+ if (opts.price !== undefined) body.price = parseFloat(opts.price);
229
+ if (opts.duration) body.duration = opts.duration;
230
+ if (opts.deliverables) body.deliverables = opts.deliverables.split(',').map(s => s.trim());
231
+ if (opts.tags) body.tags = opts.tags.split(',').map(s => s.trim());
232
+ if (opts.availability) body.availability = opts.availability;
233
+ if (opts.cta) body.cta_type = opts.cta;
234
+
235
+ if (Object.keys(body).length === 0) {
236
+ spinner.fail('Nothing to update');
237
+ fail('Provide at least one field to update (--title, --price, --availability, etc.)');
238
+ }
239
+
240
+ const data = await authFetch('PUT', `/agents/${agentId}/services/${id}`, body);
241
+
242
+ spinner.succeed('Service updated');
243
+
244
+ const svc = data.service;
245
+ success({
246
+ command: 'service update',
247
+ serviceId: svc.id,
248
+ title: svc.title,
249
+ message: `Updated service #${svc.id}: "${svc.title}"`,
250
+ }, () => {
251
+ header('✔', `Service #${id} Updated`);
252
+ keyValue('Title', chalk.bold(svc.title));
253
+ keyValue('Price', formatPrice(svc));
254
+ keyValue('Status', svc.availability);
255
+ blank();
256
+ });
257
+ }
258
+
259
+ async function runRemove(id) {
260
+ const spinner = createSpinner('Removing service…');
261
+ spinner.start();
262
+
263
+ const { agentId } = await resolveMyAgent();
264
+
265
+ await authFetch('DELETE', `/agents/${agentId}/services/${id}`);
266
+
267
+ spinner.succeed('Service removed');
268
+
269
+ success({
270
+ command: 'service remove',
271
+ serviceId: parseInt(id, 10),
272
+ message: `Removed service #${id}`,
273
+ }, () => {
274
+ header('✔', `Service #${id} Removed`);
275
+ blank();
276
+ });
277
+ }
278
+
279
+ // ─── Helpers ────────────────────────────────────────────────────────
280
+
281
+ function formatPrice(svc) {
282
+ if (svc.pricing_type === 'custom' || svc.price == null) return chalk.yellow('Custom quote');
283
+ const suffix = svc.pricing_type === 'hourly' ? '/hr' : '';
284
+ return chalk.green(`$${svc.price.toLocaleString()}${suffix} ${svc.currency || 'USDC'}`);
285
+ }
286
+
287
+ module.exports = { register };
package/src/index.js CHANGED
@@ -15,11 +15,16 @@ program
15
15
  .option('--quiet', 'Minimal output (success/fail + tx hash)')
16
16
  .option('--no-color', 'Disable colored output')
17
17
  .hook('preAction', (thisCommand) => {
18
- const opts = thisCommand.optsWithGlobals();
18
+ // Commander 8.x: optsWithGlobals not available, walk up the parent chain
19
+ let opts = thisCommand.opts();
20
+ let parent = thisCommand.parent;
21
+ while (parent) {
22
+ opts = { ...parent.opts(), ...opts };
23
+ parent = parent.parent;
24
+ }
19
25
  if (opts.json) setMode('json');
20
26
  else if (opts.quiet) setMode('quiet');
21
27
  if (opts.color === false) {
22
- // chalk v4 respects FORCE_COLOR=0
23
28
  process.env.FORCE_COLOR = '0';
24
29
  }
25
30
  });
@@ -50,6 +55,11 @@ require('./commands/mutual-cancel').register(program);
50
55
  require('./commands/retract-cancel').register(program);
51
56
  require('./commands/reclaim').register(program);
52
57
 
58
+ // ─── Agent Storefront ───────────────────────────────────────────────
59
+ require('./commands/service').register(program);
60
+ require('./commands/portfolio').register(program);
61
+ require('./commands/contact').register(program);
62
+
53
63
  // ─── Info ───────────────────────────────────────────────────────────
54
64
  require('./commands/status').register(program);
55
65
  require('./commands/balance').register(program);
@@ -64,6 +74,8 @@ ${chalk.dim('Examples:')}
64
74
  ${chalk.dim('$')} 0xwork discover --capabilities=Writing ${chalk.dim('# Find matching tasks')}
65
75
  ${chalk.dim('$')} 0xwork claim 45 ${chalk.dim('# Claim a task')}
66
76
  ${chalk.dim('$')} 0xwork submit 45 --proof="https://…" ${chalk.dim('# Submit deliverables')}
77
+ ${chalk.dim('$')} 0xwork service add --title="..." --description="..." --price=500 ${chalk.dim('# List a service')}
78
+ ${chalk.dim('$')} 0xwork contact add --type=telegram --value="@bot" ${chalk.dim('# Add contact link')}
67
79
  ${chalk.dim('$')} 0xwork balance ${chalk.dim('# Check balances')}
68
80
  ${chalk.dim('$')} 0xwork discover --json ${chalk.dim('# JSON output for agents')}
69
81
 
package/src/sdk.js CHANGED
@@ -105,33 +105,24 @@ function normalizeError(err) {
105
105
  if (err.code === 'INSUFFICIENT_FUNDS') {
106
106
  return 'Insufficient ETH for gas. Fund the wallet with a small amount of ETH on Base.';
107
107
  }
108
- if (msg.includes('insufficient allowance') || msg.includes('ERC20: insufficient allowance')) {
109
- return 'Insufficient $AXOBOTL allowance. The token approval may have failed — try again.';
110
- }
111
- if (msg.includes('transfer amount exceeds balance') || msg.includes('exceeds balance')) {
112
- return 'Insufficient $AXOBOTL balance to cover the stake. Buy more $AXOBOTL to claim this task.';
113
- }
114
- if (msg.includes('Not open') || msg.includes('not open')) {
115
- return 'Task is not open for claiming — it may already be claimed or cancelled.';
116
- }
117
108
  if (err.code === 'CALL_EXCEPTION') {
118
109
  const match = msg.match(/reason="([^"]+)"/);
119
110
  return match
120
- ? `Contract reverted: ${match[1]}`
121
- : 'Contract call failed — check task state, token balance, and allowance.';
111
+ ? `Contract call failed: ${match[1]}`
112
+ : 'Contract call failed — the task may not exist or the contract is paused.';
122
113
  }
123
114
  if (err.code === 'UNPREDICTABLE_GAS_LIMIT' || msg.includes('cannot estimate gas')) {
124
115
  const match = msg.match(/reason="([^"]+)"/);
125
116
  return match
126
117
  ? `Transaction would revert: ${match[1]}`
127
- : 'Transaction would revert likely insufficient $AXOBOTL for stake, or task is not claimable.';
118
+ : 'Transaction would revert. Check task state and permissions.';
128
119
  }
129
120
  if (msg.includes('execution reverted')) {
130
121
  const match = msg.match(/reason="([^"]+)"/);
131
122
  if (match) return `Contract reverted: ${match[1]}`;
132
123
  }
133
124
  if (msg.includes('missing revert data') || msg.includes('BAD_DATA')) {
134
- return 'Contract call failed the task may not exist on-chain or has already been claimed.';
125
+ return 'Contract returned unexpected data. The task may not exist on-chain or the contract is paused.';
135
126
  }
136
127
  if (msg.includes('ECONNREFUSED') || msg.includes('ETIMEDOUT') || msg.includes('ENETUNREACH')) {
137
128
  return 'Network error: could not reach API or RPC. Check your connection.';