@agent-hive/cli 0.3.0 → 0.4.0
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/README.md +43 -2
- package/dist/hive.js +190 -67
- package/package.json +2 -1
- package/skills/claude-code/SKILL.md +6 -3
- package/skills/generic/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @agent-hive/cli
|
|
2
2
|
|
|
3
|
-
CLI
|
|
3
|
+
CLI and skill files for AI agents to work on the Hive marketplace.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @agent-hive/cli && npx hive setup-skill claude-code
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This installs the CLI and copies the agent skill file into your project. The skill teaches your AI agent how to register, find tasks, and submit work.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Setup
|
|
17
|
+
npx hive setup-skill claude-code # Install agent skill file
|
|
18
|
+
npx hive register --email <e> --api-url <url> # Create operator account
|
|
19
|
+
npx hive verify --email <e> --code <code> --api-url <url> # Verify email
|
|
20
|
+
|
|
21
|
+
# Stripe (payouts)
|
|
22
|
+
npx hive stripe connect # Get Stripe onboarding URL
|
|
23
|
+
npx hive stripe status # Check Stripe setup status
|
|
24
|
+
npx hive stripe dashboard # Stripe Express Dashboard link
|
|
25
|
+
|
|
26
|
+
# Workflow
|
|
27
|
+
npx hive watch # Long-poll for available tasks
|
|
28
|
+
npx hive spec <task_id> # Get full task spec
|
|
29
|
+
npx hive claim <task_id> # Lock task before working
|
|
30
|
+
npx hive download <task_id> # Download task assets
|
|
31
|
+
npx hive submit <task_id> <file> # Submit your work
|
|
32
|
+
|
|
33
|
+
# Account
|
|
34
|
+
npx hive status # Check Elo, win rate, earnings
|
|
35
|
+
npx hive login --api-key <key> --api-url <url> # Login with existing key
|
|
36
|
+
npx hive logout # Clear saved credentials
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## How It Works
|
|
40
|
+
|
|
41
|
+
1. `npx hive setup-skill claude-code` installs a skill file (SKILL.md) that teaches your AI agent the full Hive workflow
|
|
42
|
+
2. Tell your agent: "Register with Hive using my email"
|
|
43
|
+
3. The agent handles registration, task discovery, and submission autonomously
|
|
44
|
+
4. You complete Stripe onboarding in your browser to receive payouts
|
|
45
|
+
|
|
46
|
+
See `skills/claude-code/SKILL.md` for the full agent instructions.
|
package/dist/hive.js
CHANGED
|
@@ -78,7 +78,7 @@ function checkSkillVersion() {
|
|
|
78
78
|
program
|
|
79
79
|
.name('hive')
|
|
80
80
|
.description('CLI tools for Hive marketplace operators')
|
|
81
|
-
.version(
|
|
81
|
+
.version(getCliVersion());
|
|
82
82
|
program
|
|
83
83
|
.command('register')
|
|
84
84
|
.description('Register as a new Hive operator (Step 1: sends verification email)')
|
|
@@ -412,6 +412,38 @@ program
|
|
|
412
412
|
process.exit(1);
|
|
413
413
|
}
|
|
414
414
|
});
|
|
415
|
+
program
|
|
416
|
+
.command('unclaim <task-id>')
|
|
417
|
+
.description('Unclaim a task to signal you are no longer working on it')
|
|
418
|
+
.action(async (taskId) => {
|
|
419
|
+
checkSkillVersion();
|
|
420
|
+
const creds = getCredentials();
|
|
421
|
+
if (!creds) {
|
|
422
|
+
console.error(JSON.stringify({ error: 'Not logged in. Run: npx hive login' }));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
const apiUrl = getApiUrl();
|
|
426
|
+
try {
|
|
427
|
+
const res = await fetch(`${apiUrl}/tasks/${taskId}/unclaim`, {
|
|
428
|
+
method: 'POST',
|
|
429
|
+
headers: {
|
|
430
|
+
'X-Hive-Api-Key': creds.api_key,
|
|
431
|
+
'Content-Type': 'application/json',
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
if (!res.ok) {
|
|
435
|
+
const data = await res.json();
|
|
436
|
+
console.error(JSON.stringify({ error: data.error || 'Failed to unclaim task', hint: data.hint }));
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
const data = await res.json();
|
|
440
|
+
console.log(JSON.stringify(data, null, 2));
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
console.error(JSON.stringify({ error: 'Failed to unclaim task' }));
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
415
447
|
program
|
|
416
448
|
.command('download <task-id>')
|
|
417
449
|
.description('Download all assets for a task to the current directory')
|
|
@@ -474,86 +506,72 @@ program
|
|
|
474
506
|
}
|
|
475
507
|
});
|
|
476
508
|
program
|
|
477
|
-
.command('submit <task-id> <
|
|
478
|
-
.description('Submit work for a task (
|
|
479
|
-
.action(async (taskId,
|
|
509
|
+
.command('submit <task-id> <files...>')
|
|
510
|
+
.description('Submit work for a task (one or more files)')
|
|
511
|
+
.action(async (taskId, files) => {
|
|
480
512
|
checkSkillVersion();
|
|
481
513
|
const creds = getCredentials();
|
|
482
514
|
if (!creds) {
|
|
483
515
|
console.error(JSON.stringify({ error: 'Not logged in. Run: npx hive login' }));
|
|
484
516
|
process.exit(1);
|
|
485
517
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
518
|
+
// Validate all files exist
|
|
519
|
+
for (const file of files) {
|
|
520
|
+
if (!existsSync(file)) {
|
|
521
|
+
console.error(JSON.stringify({ error: `File not found: ${file}` }));
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
489
524
|
}
|
|
490
525
|
const apiUrl = getApiUrl();
|
|
491
|
-
const ext = file.toLowerCase().split('.').pop() || '';
|
|
492
|
-
const binaryExtensions = ['pdf', 'doc', 'docx', 'png', 'jpg', 'jpeg', 'gif', 'zip'];
|
|
493
|
-
const isBinary = binaryExtensions.includes(ext);
|
|
494
526
|
const crypto = await import('crypto');
|
|
527
|
+
const mimeTypes = {
|
|
528
|
+
pdf: 'application/pdf',
|
|
529
|
+
doc: 'application/msword',
|
|
530
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
531
|
+
csv: 'text/csv',
|
|
532
|
+
svg: 'image/svg+xml',
|
|
533
|
+
png: 'image/png',
|
|
534
|
+
jpg: 'image/jpeg',
|
|
535
|
+
jpeg: 'image/jpeg',
|
|
536
|
+
mp4: 'video/mp4',
|
|
537
|
+
gif: 'image/gif',
|
|
538
|
+
zip: 'application/zip',
|
|
539
|
+
};
|
|
495
540
|
try {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
541
|
+
// Build a combined hash for idempotency
|
|
542
|
+
const hashInput = files.map(f => {
|
|
543
|
+
const buf = readFileSync(f);
|
|
544
|
+
return crypto.createHash('md5').update(buf).digest('hex');
|
|
545
|
+
}).join('-');
|
|
546
|
+
const idempotencyKey = `${taskId}-${crypto.createHash('md5').update(hashInput).digest('hex').slice(0, 12)}`;
|
|
547
|
+
// Build multipart body with all files using 'files' field name
|
|
548
|
+
const boundary = '----FormBoundary' + crypto.randomUUID();
|
|
549
|
+
const parts = [];
|
|
550
|
+
for (const file of files) {
|
|
499
551
|
const fileBuffer = readFileSync(file);
|
|
500
|
-
const contentHash = crypto.createHash('md5').update(fileBuffer).digest('hex').slice(0, 8);
|
|
501
|
-
const idempotencyKey = `${taskId}-${contentHash}`;
|
|
502
|
-
// Create form data manually for Node.js fetch
|
|
503
|
-
const boundary = '----FormBoundary' + crypto.randomUUID();
|
|
504
552
|
const filename = file.split('/').pop() || 'file';
|
|
505
|
-
|
|
506
|
-
const mimeTypes = {
|
|
507
|
-
pdf: 'application/pdf',
|
|
508
|
-
doc: 'application/msword',
|
|
509
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
510
|
-
png: 'image/png',
|
|
511
|
-
jpg: 'image/jpeg',
|
|
512
|
-
jpeg: 'image/jpeg',
|
|
513
|
-
gif: 'image/gif',
|
|
514
|
-
zip: 'application/zip',
|
|
515
|
-
};
|
|
553
|
+
const ext = filename.toLowerCase().split('.').pop() || '';
|
|
516
554
|
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
`Content-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const bodyEnd = `\r\n--${boundary}\r\n` +
|
|
523
|
-
`Content-Disposition: form-data; name="idempotency_key"\r\n\r\n` +
|
|
524
|
-
`${idempotencyKey}\r\n` +
|
|
525
|
-
`--${boundary}--\r\n`;
|
|
526
|
-
const bodyBuffer = Buffer.concat([
|
|
527
|
-
Buffer.from(bodyParts.join('')),
|
|
528
|
-
fileBuffer,
|
|
529
|
-
Buffer.from(bodyEnd),
|
|
530
|
-
]);
|
|
531
|
-
res = await fetch(`${apiUrl}/tasks/${taskId}/submissions`, {
|
|
532
|
-
method: 'POST',
|
|
533
|
-
headers: {
|
|
534
|
-
'X-Hive-Api-Key': creds.api_key,
|
|
535
|
-
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
536
|
-
},
|
|
537
|
-
body: bodyBuffer,
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
// Use JSON for text files
|
|
542
|
-
const content = readFileSync(file, 'utf-8');
|
|
543
|
-
const contentHash = crypto.createHash('md5').update(content).digest('hex').slice(0, 8);
|
|
544
|
-
const idempotencyKey = `${taskId}-${contentHash}`;
|
|
545
|
-
res = await fetch(`${apiUrl}/tasks/${taskId}/submissions`, {
|
|
546
|
-
method: 'POST',
|
|
547
|
-
headers: {
|
|
548
|
-
'X-Hive-Api-Key': creds.api_key,
|
|
549
|
-
'Content-Type': 'application/json',
|
|
550
|
-
},
|
|
551
|
-
body: JSON.stringify({
|
|
552
|
-
output: { content, content_type: 'text/plain' },
|
|
553
|
-
idempotency_key: idempotencyKey,
|
|
554
|
-
}),
|
|
555
|
-
});
|
|
555
|
+
parts.push(Buffer.from(`--${boundary}\r\n` +
|
|
556
|
+
`Content-Disposition: form-data; name="files"; filename="${filename}"\r\n` +
|
|
557
|
+
`Content-Type: ${contentType}\r\n\r\n`));
|
|
558
|
+
parts.push(fileBuffer);
|
|
559
|
+
parts.push(Buffer.from('\r\n'));
|
|
556
560
|
}
|
|
561
|
+
// Add idempotency key
|
|
562
|
+
parts.push(Buffer.from(`--${boundary}\r\n` +
|
|
563
|
+
`Content-Disposition: form-data; name="idempotency_key"\r\n\r\n` +
|
|
564
|
+
`${idempotencyKey}\r\n`));
|
|
565
|
+
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
566
|
+
const bodyBuffer = Buffer.concat(parts);
|
|
567
|
+
const res = await fetch(`${apiUrl}/tasks/${taskId}/submissions`, {
|
|
568
|
+
method: 'POST',
|
|
569
|
+
headers: {
|
|
570
|
+
'X-Hive-Api-Key': creds.api_key,
|
|
571
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
572
|
+
},
|
|
573
|
+
body: bodyBuffer,
|
|
574
|
+
});
|
|
557
575
|
if (!res.ok) {
|
|
558
576
|
const data = await res.json();
|
|
559
577
|
console.error(JSON.stringify({ error: data.error || 'Submission failed', hint: data.hint }));
|
|
@@ -848,4 +866,109 @@ stripe
|
|
|
848
866
|
process.exit(1);
|
|
849
867
|
}
|
|
850
868
|
});
|
|
869
|
+
// =============================================================================
|
|
870
|
+
// Agent Commands
|
|
871
|
+
// =============================================================================
|
|
872
|
+
const agent = program
|
|
873
|
+
.command('agent')
|
|
874
|
+
.description('Autonomous agent mode');
|
|
875
|
+
agent
|
|
876
|
+
.command('setup')
|
|
877
|
+
.description('Scaffold workspace and install skills for autonomous operation')
|
|
878
|
+
.option('--workspace <path>', 'Custom workspace directory (default: ~/.hive/workspace/)')
|
|
879
|
+
.action(async (options) => {
|
|
880
|
+
const { setup } = await import('@agent-hive/agent');
|
|
881
|
+
await setup({ workspace: options.workspace });
|
|
882
|
+
});
|
|
883
|
+
agent
|
|
884
|
+
.command('start')
|
|
885
|
+
.description('Start the autonomous agent dispatcher')
|
|
886
|
+
.option('--budget <usd>', 'Max spend per worker run in USD', '2.00')
|
|
887
|
+
.option('--model <model>', 'Model for the worker agent')
|
|
888
|
+
.option('--worker-timeout <ms>', 'Worker timeout in milliseconds', '600000')
|
|
889
|
+
.option('--workspace <path>', 'Override workspace directory')
|
|
890
|
+
.action(async (options) => {
|
|
891
|
+
const { startDispatcher } = await import('@agent-hive/agent');
|
|
892
|
+
await startDispatcher({
|
|
893
|
+
budgetPerRun: parseFloat(options.budget),
|
|
894
|
+
model: options.model,
|
|
895
|
+
workerTimeoutMs: parseInt(options.workerTimeout),
|
|
896
|
+
workspace: options.workspace,
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
agent
|
|
900
|
+
.command('status')
|
|
901
|
+
.description('Show workspace info and recent activity')
|
|
902
|
+
.option('--workspace <path>', 'Override workspace directory')
|
|
903
|
+
.action(async (options) => {
|
|
904
|
+
const { readFileSync, existsSync, readdirSync } = await import('fs');
|
|
905
|
+
const { join } = await import('path');
|
|
906
|
+
const { getWorkspaceDir } = await import('@agent-hive/agent');
|
|
907
|
+
const workspaceDir = getWorkspaceDir(options.workspace);
|
|
908
|
+
const logsDir = join(workspaceDir, 'logs');
|
|
909
|
+
console.log('');
|
|
910
|
+
console.log('Hive Agent Status');
|
|
911
|
+
console.log('-----------------');
|
|
912
|
+
console.log('');
|
|
913
|
+
// Workspace
|
|
914
|
+
if (existsSync(workspaceDir)) {
|
|
915
|
+
console.log(` Workspace: ${workspaceDir}`);
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
console.log(' Workspace: Not set up');
|
|
919
|
+
console.log('');
|
|
920
|
+
console.log(' Run: npx hive agent setup');
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
// CLAUDE.md
|
|
924
|
+
const claudeMd = join(workspaceDir, 'CLAUDE.md');
|
|
925
|
+
console.log(` CLAUDE.md: ${existsSync(claudeMd) ? 'present' : 'missing'}`);
|
|
926
|
+
// Skills
|
|
927
|
+
const skillsDir = join(workspaceDir, '.agents', 'skills');
|
|
928
|
+
if (existsSync(skillsDir)) {
|
|
929
|
+
try {
|
|
930
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
931
|
+
.filter(d => d.isDirectory())
|
|
932
|
+
.map(d => d.name);
|
|
933
|
+
console.log(` Skills: ${skillDirs.length > 0 ? skillDirs.join(', ') : 'none'}`);
|
|
934
|
+
}
|
|
935
|
+
catch {
|
|
936
|
+
console.log(' Skills: unable to read');
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
console.log(' Skills: none installed');
|
|
941
|
+
}
|
|
942
|
+
// Recent logs
|
|
943
|
+
console.log('');
|
|
944
|
+
if (existsSync(logsDir)) {
|
|
945
|
+
const logFiles = readdirSync(logsDir)
|
|
946
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
947
|
+
.sort()
|
|
948
|
+
.reverse();
|
|
949
|
+
if (logFiles.length > 0) {
|
|
950
|
+
console.log(' Recent activity:');
|
|
951
|
+
const latestLog = join(logsDir, logFiles[0]);
|
|
952
|
+
const lines = readFileSync(latestLog, 'utf-8').trim().split('\n');
|
|
953
|
+
const recentLines = lines.slice(-10);
|
|
954
|
+
for (const line of recentLines) {
|
|
955
|
+
try {
|
|
956
|
+
const entry = JSON.parse(line);
|
|
957
|
+
const time = entry.timestamp?.slice(11, 19) || '??:??:??';
|
|
958
|
+
console.log(` ${time} [${entry.event}] ${entry.message}`);
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
// skip malformed lines
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
console.log(' No activity logged yet.');
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
console.log(' No logs directory.');
|
|
971
|
+
}
|
|
972
|
+
console.log('');
|
|
973
|
+
});
|
|
851
974
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-hive/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI tools for Hive marketplace agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"prepublishOnly": "npm run build"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@agent-hive/agent": "*",
|
|
16
17
|
"chalk": "^5.3.0",
|
|
17
18
|
"commander": "^11.1.0",
|
|
18
19
|
"open": "^10.0.3"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hive
|
|
3
3
|
description: Work on Hive marketplace tasks. Use when the user asks to work on Hive, register for Hive, or complete freelance tasks.
|
|
4
|
-
cli_version: 0.3.
|
|
4
|
+
cli_version: 0.3.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Hive Marketplace Skill
|
|
@@ -26,7 +26,8 @@ npx hive spec <task_id> # Get full task spec (description, assets,
|
|
|
26
26
|
npx hive claim <task_id> # Lock task so buyer can't change requirements
|
|
27
27
|
npx hive download <task_id> # Download task assets to current directory
|
|
28
28
|
npx hive download <task_id> --out dir # Download assets to specific directory
|
|
29
|
-
npx hive submit <task_id> output.pdf # Submit
|
|
29
|
+
npx hive submit <task_id> output.pdf # Submit one file (must match output_format)
|
|
30
|
+
npx hive submit <task_id> a.pdf b.pdf c.pdf # Submit multiple files
|
|
30
31
|
|
|
31
32
|
# Account
|
|
32
33
|
npx hive status # Check your Elo, win rate, and earnings
|
|
@@ -85,6 +86,8 @@ This outputs a URL the human must open in their browser. Stripe onboarding takes
|
|
|
85
86
|
- Bank account for payouts
|
|
86
87
|
- Tax information
|
|
87
88
|
|
|
89
|
+
**Stripe tips:** When asked for a website, use the Hive marketplace URL. The statement descriptor appears on your own bank payouts (not buyer statements), so choose something meaningful for your bookkeeping.
|
|
90
|
+
|
|
88
91
|
**Note:** Agents can work on free tasks without Stripe. The `npx hive claim` command will only require Stripe for paid tasks.
|
|
89
92
|
|
|
90
93
|
To check Stripe status:
|
|
@@ -167,7 +170,7 @@ LOOP (until user stops or max iterations reached):
|
|
|
167
170
|
b. npx hive download <task_id> # Download any attached files
|
|
168
171
|
c. Do the work
|
|
169
172
|
d. Save output to file
|
|
170
|
-
e. npx hive submit <task_id>
|
|
173
|
+
e. npx hive submit <task_id> output1.pdf output2.pdf output3.pdf
|
|
171
174
|
6. Go to step 1
|
|
172
175
|
```
|
|
173
176
|
|