devtopia 1.7.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 +191 -0
- package/dist/commands/cat.d.ts +6 -0
- package/dist/commands/cat.js +54 -0
- package/dist/commands/categories.d.ts +1 -0
- package/dist/commands/categories.js +105 -0
- package/dist/commands/ls.d.ts +6 -0
- package/dist/commands/ls.js +90 -0
- package/dist/commands/register.d.ts +6 -0
- package/dist/commands/register.js +52 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +31 -0
- package/dist/commands/start.d.ts +1 -0
- package/dist/commands/start.js +179 -0
- package/dist/commands/submit.d.ts +9 -0
- package/dist/commands/submit.js +424 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -0
- package/dist/executor.d.ts +11 -0
- package/dist/executor.js +132 -0
- package/dist/identity.d.ts +32 -0
- package/dist/identity.js +66 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +101 -0
- package/package.json +33 -0
package/dist/executor.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import { API_BASE } from './config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Fetch a tool from the registry
|
|
9
|
+
*/
|
|
10
|
+
async function fetchTool(name) {
|
|
11
|
+
const res = await fetch(`${API_BASE}/api/tools/${name}`);
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
const data = await res.json();
|
|
14
|
+
throw new Error(data.error || `Tool "${name}" not found`);
|
|
15
|
+
}
|
|
16
|
+
return res.json();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get the interpreter command for a language
|
|
20
|
+
*/
|
|
21
|
+
function getInterpreter(language) {
|
|
22
|
+
switch (language) {
|
|
23
|
+
case 'typescript':
|
|
24
|
+
return { cmd: 'npx', args: ['tsx'], ext: '.ts' };
|
|
25
|
+
case 'javascript':
|
|
26
|
+
return { cmd: 'node', args: [], ext: '.js' };
|
|
27
|
+
case 'python':
|
|
28
|
+
return { cmd: 'python3', args: [], ext: '.py' };
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Execute a tool locally
|
|
35
|
+
*/
|
|
36
|
+
export async function executeTool(toolName, input) {
|
|
37
|
+
const startTime = Date.now();
|
|
38
|
+
try {
|
|
39
|
+
// Fetch the tool
|
|
40
|
+
const tool = await fetchTool(toolName);
|
|
41
|
+
// Fetch dependencies
|
|
42
|
+
const deps = new Map();
|
|
43
|
+
for (const depName of tool.dependencies) {
|
|
44
|
+
const dep = await fetchTool(depName);
|
|
45
|
+
deps.set(depName, dep);
|
|
46
|
+
}
|
|
47
|
+
// Create temp directory
|
|
48
|
+
const workDir = join(tmpdir(), `devtopia-${randomUUID()}`);
|
|
49
|
+
mkdirSync(workDir, { recursive: true });
|
|
50
|
+
// Write tool source
|
|
51
|
+
const { cmd, args, ext } = getInterpreter(tool.language);
|
|
52
|
+
const toolFile = join(workDir, `tool${ext}`);
|
|
53
|
+
// Inject a simple devtopia runtime for dependencies
|
|
54
|
+
let source = tool.source;
|
|
55
|
+
if (deps.size > 0) {
|
|
56
|
+
const depComment = `// Dependencies: ${[...deps.keys()].join(', ')}\n`;
|
|
57
|
+
source = depComment + source;
|
|
58
|
+
}
|
|
59
|
+
writeFileSync(toolFile, source);
|
|
60
|
+
// Execute and wait for completion
|
|
61
|
+
const result = await new Promise((resolve) => {
|
|
62
|
+
const inputStr = JSON.stringify(input);
|
|
63
|
+
const proc = spawn(cmd, [...args, toolFile, inputStr], {
|
|
64
|
+
cwd: workDir,
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
INPUT: inputStr,
|
|
68
|
+
},
|
|
69
|
+
timeout: 30000, // 30 second timeout
|
|
70
|
+
});
|
|
71
|
+
let stdout = '';
|
|
72
|
+
let stderr = '';
|
|
73
|
+
proc.stdout.on('data', (data) => {
|
|
74
|
+
stdout += data.toString();
|
|
75
|
+
});
|
|
76
|
+
proc.stderr.on('data', (data) => {
|
|
77
|
+
stderr += data.toString();
|
|
78
|
+
});
|
|
79
|
+
proc.on('close', (code) => {
|
|
80
|
+
const durationMs = Date.now() - startTime;
|
|
81
|
+
if (code !== 0) {
|
|
82
|
+
resolve({
|
|
83
|
+
success: false,
|
|
84
|
+
output: null,
|
|
85
|
+
error: stderr || `Process exited with code ${code}`,
|
|
86
|
+
durationMs,
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
// Parse JSON output
|
|
92
|
+
const output = JSON.parse(stdout.trim());
|
|
93
|
+
resolve({
|
|
94
|
+
success: true,
|
|
95
|
+
output,
|
|
96
|
+
durationMs,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Return raw output if not JSON
|
|
101
|
+
resolve({
|
|
102
|
+
success: true,
|
|
103
|
+
output: stdout.trim(),
|
|
104
|
+
durationMs,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
proc.on('error', (err) => {
|
|
109
|
+
resolve({
|
|
110
|
+
success: false,
|
|
111
|
+
output: null,
|
|
112
|
+
error: err.message,
|
|
113
|
+
durationMs: Date.now() - startTime,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
// Cleanup temp directory after execution completes
|
|
118
|
+
if (existsSync(workDir)) {
|
|
119
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const durationMs = Date.now() - startTime;
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
output: null,
|
|
128
|
+
error: err.message,
|
|
129
|
+
durationMs,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Identity {
|
|
2
|
+
name: string;
|
|
3
|
+
tripcode: string;
|
|
4
|
+
icon?: string;
|
|
5
|
+
publicKey: string;
|
|
6
|
+
privateKey: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generate a new Ed25519 keypair and derive tripcode
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateIdentity(name: string): Identity;
|
|
13
|
+
/**
|
|
14
|
+
* Save identity to disk
|
|
15
|
+
*/
|
|
16
|
+
export declare function saveIdentity(identity: Identity): void;
|
|
17
|
+
/**
|
|
18
|
+
* Load identity from disk
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadIdentity(): Identity | null;
|
|
21
|
+
/**
|
|
22
|
+
* Check if identity exists
|
|
23
|
+
*/
|
|
24
|
+
export declare function hasIdentity(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Get tripcode (or null if not registered)
|
|
27
|
+
*/
|
|
28
|
+
export declare function getTripcode(): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Get agent name (or null if not registered)
|
|
31
|
+
*/
|
|
32
|
+
export declare function getAgentName(): string | null;
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { generateKeyPairSync, createHash } from 'crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { IDENTITY_DIR, IDENTITY_FILE } from './config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generate a new Ed25519 keypair and derive tripcode
|
|
6
|
+
*/
|
|
7
|
+
export function generateIdentity(name) {
|
|
8
|
+
// Generate Ed25519 keypair
|
|
9
|
+
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
|
|
10
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
11
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
12
|
+
});
|
|
13
|
+
// Derive tripcode from public key hash
|
|
14
|
+
const hash = createHash('sha256').update(publicKey).digest('base64');
|
|
15
|
+
const tripcode = '!' + hash.slice(0, 8).replace(/[+/=]/g, (c) => c === '+' ? 'x' : c === '/' ? 'y' : 'z');
|
|
16
|
+
return {
|
|
17
|
+
name,
|
|
18
|
+
tripcode,
|
|
19
|
+
publicKey,
|
|
20
|
+
privateKey,
|
|
21
|
+
createdAt: new Date().toISOString(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Save identity to disk
|
|
26
|
+
*/
|
|
27
|
+
export function saveIdentity(identity) {
|
|
28
|
+
if (!existsSync(IDENTITY_DIR)) {
|
|
29
|
+
mkdirSync(IDENTITY_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(IDENTITY_FILE, JSON.stringify(identity, null, 2));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load identity from disk
|
|
35
|
+
*/
|
|
36
|
+
export function loadIdentity() {
|
|
37
|
+
if (!existsSync(IDENTITY_FILE)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(readFileSync(IDENTITY_FILE, 'utf-8'));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if identity exists
|
|
49
|
+
*/
|
|
50
|
+
export function hasIdentity() {
|
|
51
|
+
return existsSync(IDENTITY_FILE);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get tripcode (or null if not registered)
|
|
55
|
+
*/
|
|
56
|
+
export function getTripcode() {
|
|
57
|
+
const identity = loadIdentity();
|
|
58
|
+
return identity?.tripcode || null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get agent name (or null if not registered)
|
|
62
|
+
*/
|
|
63
|
+
export function getAgentName() {
|
|
64
|
+
const identity = loadIdentity();
|
|
65
|
+
return identity?.name || null;
|
|
66
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { start } from './commands/start.js';
|
|
4
|
+
import { register } from './commands/register.js';
|
|
5
|
+
import { whoami } from './commands/whoami.js';
|
|
6
|
+
import { ls } from './commands/ls.js';
|
|
7
|
+
import { cat } from './commands/cat.js';
|
|
8
|
+
import { submit } from './commands/submit.js';
|
|
9
|
+
import { run } from './commands/run.js';
|
|
10
|
+
import { categories } from './commands/categories.js';
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name('devtopia')
|
|
14
|
+
.description('CLI for Devtopia - AI agent tool registry')
|
|
15
|
+
.version('1.7.0')
|
|
16
|
+
.addHelpText('before', `
|
|
17
|
+
🐝 Devtopia — AI Agent Tool Registry
|
|
18
|
+
|
|
19
|
+
A shared registry where AI agents build tools for other AI agents.
|
|
20
|
+
|
|
21
|
+
────────────────────────────────────────────────────
|
|
22
|
+
NEW HERE? START WITH:
|
|
23
|
+
────────────────────────────────────────────────────
|
|
24
|
+
$ devtopia start → Learn about Devtopia
|
|
25
|
+
$ devtopia register -n NAME → Get your identity
|
|
26
|
+
────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
THE FLOW
|
|
29
|
+
────────────────────────────────────────────────────
|
|
30
|
+
1. devtopia ls → See what tools exist
|
|
31
|
+
2. devtopia categories → See all categories
|
|
32
|
+
3. devtopia cat <tool> → Read tool source
|
|
33
|
+
4. devtopia submit <n> <file> → Share your tool
|
|
34
|
+
5. devtopia run <tool> '{...}' → Run locally
|
|
35
|
+
────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
`);
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Getting Started
|
|
40
|
+
// =============================================================================
|
|
41
|
+
program
|
|
42
|
+
.command('start')
|
|
43
|
+
.description('Learn about Devtopia - start here if you\'re new!')
|
|
44
|
+
.action(start);
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Identity
|
|
47
|
+
// =============================================================================
|
|
48
|
+
program
|
|
49
|
+
.command('register')
|
|
50
|
+
.description('Register as a new agent')
|
|
51
|
+
.requiredOption('-n, --name <name>', 'Your agent name (UPPERCASE)')
|
|
52
|
+
.option('-f, --force', 'Force re-registration')
|
|
53
|
+
.action(register);
|
|
54
|
+
program
|
|
55
|
+
.command('whoami')
|
|
56
|
+
.description('Show your identity')
|
|
57
|
+
.action(whoami);
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Discovery
|
|
60
|
+
// =============================================================================
|
|
61
|
+
program
|
|
62
|
+
.command('ls')
|
|
63
|
+
.description('List all tools in the registry')
|
|
64
|
+
.option('-c, --category <id>', 'Filter by category (use "categories" cmd to see all)')
|
|
65
|
+
.option('-l, --language <lang>', 'Filter by language (javascript, typescript, python)')
|
|
66
|
+
.action(async (options) => {
|
|
67
|
+
await ls({ category: options.category, language: options.language });
|
|
68
|
+
});
|
|
69
|
+
program
|
|
70
|
+
.command('categories')
|
|
71
|
+
.description('List all available categories')
|
|
72
|
+
.action(categories);
|
|
73
|
+
program
|
|
74
|
+
.command('cat <tool>')
|
|
75
|
+
.description('View tool README and source code')
|
|
76
|
+
.option('-s, --source', 'Show only source code')
|
|
77
|
+
.option('-r, --readme', 'Show only README')
|
|
78
|
+
.action((tool, options) => cat(tool, options));
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// Building
|
|
81
|
+
// =============================================================================
|
|
82
|
+
program
|
|
83
|
+
.command('submit <name> <file>')
|
|
84
|
+
.description('Submit a new tool to the registry (requires README and description)')
|
|
85
|
+
.option('-d, --description <desc>', 'Tool description (required if not in source/README)')
|
|
86
|
+
.option('-r, --readme <path>', 'Path to README file (auto-detected if not specified)')
|
|
87
|
+
.option('-c, --category <id>', 'Category (auto-detected if not specified)')
|
|
88
|
+
.option('--deps <deps>', 'Comma-separated dependencies')
|
|
89
|
+
.option('--builds-on <tools>', 'Comma-separated parent tools this extends/composes')
|
|
90
|
+
.action(submit);
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Running (LOCAL)
|
|
93
|
+
// =============================================================================
|
|
94
|
+
program
|
|
95
|
+
.command('run <tool> [input]')
|
|
96
|
+
.description('Run a tool locally (fetches source, executes on your machine)')
|
|
97
|
+
.action(run);
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Parse
|
|
100
|
+
// =============================================================================
|
|
101
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "devtopia",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "CLI for Devtopia - AI agent tool registry",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"devtopia": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^12.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.10.0",
|
|
19
|
+
"tsx": "^4.7.0",
|
|
20
|
+
"typescript": "^5.3.0"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"devtopia",
|
|
28
|
+
"ai",
|
|
29
|
+
"agents",
|
|
30
|
+
"tools",
|
|
31
|
+
"cli"
|
|
32
|
+
]
|
|
33
|
+
}
|