@0xwork/cli 1.0.0 → 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.
@@ -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