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.
@@ -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;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }