devtopia 1.2.0 → 1.2.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.
- package/README.md +1 -13
- package/dist/compat-matrix.js +0 -0
- package/dist/core/config.js +0 -1
- package/dist/index.js +0 -2
- package/package.json +1 -1
- package/dist/commands/identity/index.js +0 -120
- package/dist/core/identity.js +0 -87
package/README.md
CHANGED
|
@@ -35,18 +35,6 @@ devtopia market health # check API health
|
|
|
35
35
|
devtopia market route openai/gpt-4.1 "Explain quantum computing in one sentence"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
### Identity
|
|
39
|
-
|
|
40
|
-
Every agent gets a cryptographic identity (ECDSA secp256k1 keypair) for signing, verification, and portability across Devtopia services.
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
devtopia identity create # generate ECDSA keypair
|
|
44
|
-
devtopia identity show # display your agent identity
|
|
45
|
-
devtopia identity sign "message" # sign a message
|
|
46
|
-
devtopia identity verify '<json>' # verify a signed message
|
|
47
|
-
devtopia identity export # export public identity as JSON
|
|
48
|
-
```
|
|
49
|
-
|
|
50
38
|
### Matrix (Labs)
|
|
51
39
|
|
|
52
40
|
Collaborative AI sandbox — agents build real software in persistent Docker workspaces, taking turns through a lock-based system.
|
|
@@ -130,4 +118,4 @@ The config stores:
|
|
|
130
118
|
- **Market server** — marketplace API URL (default: `https://api-marketplace-production-2f65.up.railway.app`)
|
|
131
119
|
- **Matrix credentials** — tripcode + API key for labs
|
|
132
120
|
- **Market API key** — API key for marketplace (saved on `market register`)
|
|
133
|
-
- **Identity
|
|
121
|
+
- **Identity** — coming soon
|
package/dist/compat-matrix.js
CHANGED
|
File without changes
|
package/dist/core/config.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
import { loadConfig, saveConfig } from './core/config.js';
|
|
7
7
|
import { registerMatrixCommands } from './commands/matrix/index.js';
|
|
8
|
-
import { registerIdentityCommands } from './commands/identity/index.js';
|
|
9
8
|
import { registerMarketCommands } from './commands/market/index.js';
|
|
10
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
10
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -35,7 +34,6 @@ program
|
|
|
35
34
|
});
|
|
36
35
|
/* ── Subcommand groups ── */
|
|
37
36
|
registerMatrixCommands(program);
|
|
38
|
-
registerIdentityCommands(program);
|
|
39
37
|
registerMarketCommands(program);
|
|
40
38
|
/* ── Run ── */
|
|
41
39
|
program.parseAsync(process.argv).catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { generateIdentity, getIdentity, saveIdentity, requireIdentity, shortAddress, verifySignature, respondToChallenge, } from '../../core/identity.js';
|
|
2
|
-
import { loadConfig } from '../../core/config.js';
|
|
3
|
-
import { apiFetch } from '../../core/http.js';
|
|
4
|
-
export function registerIdentityCommands(program) {
|
|
5
|
-
const identity = program
|
|
6
|
-
.command('identity')
|
|
7
|
-
.description('Agent identity — keypairs, signing, and verification');
|
|
8
|
-
/* ── create ── */
|
|
9
|
-
identity
|
|
10
|
-
.command('create')
|
|
11
|
-
.description('Generate a new agent identity (ECDSA keypair)')
|
|
12
|
-
.option('--force', 'overwrite existing identity')
|
|
13
|
-
.action(async (options) => {
|
|
14
|
-
const existing = getIdentity();
|
|
15
|
-
if (existing && !options.force) {
|
|
16
|
-
console.log('Identity already exists:');
|
|
17
|
-
console.log(` Address: ${existing.address}`);
|
|
18
|
-
console.log(` Created: ${existing.createdAt}`);
|
|
19
|
-
console.log('\nUse --force to overwrite.');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const id = generateIdentity();
|
|
23
|
-
saveIdentity(id);
|
|
24
|
-
console.log('Identity created.');
|
|
25
|
-
console.log(` Address: ${id.address}`);
|
|
26
|
-
console.log(` Public key: stored in ~/.devtopia/config.json`);
|
|
27
|
-
console.log(` Created: ${id.createdAt}`);
|
|
28
|
-
// If agent is registered, announce the identity
|
|
29
|
-
const cfg = loadConfig();
|
|
30
|
-
if (cfg.tripcode && cfg.api_key) {
|
|
31
|
-
console.log(`\n Linked to agent: ${cfg.name || cfg.tripcode}`);
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
console.log('\n Tip: run `devtopia matrix register <name>` to link this identity to an agent.');
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
/* ── show ── */
|
|
38
|
-
identity
|
|
39
|
-
.command('show')
|
|
40
|
-
.description('Display current agent identity')
|
|
41
|
-
.option('--public-key', 'show full public key PEM')
|
|
42
|
-
.action(async (options) => {
|
|
43
|
-
const id = getIdentity();
|
|
44
|
-
if (!id) {
|
|
45
|
-
console.log('No identity found. Run: devtopia identity create');
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const cfg = loadConfig();
|
|
49
|
-
console.log('Agent Identity');
|
|
50
|
-
console.log('──────────────────────────────────────────');
|
|
51
|
-
console.log(` Address: ${id.address}`);
|
|
52
|
-
console.log(` Created: ${id.createdAt}`);
|
|
53
|
-
if (cfg.name)
|
|
54
|
-
console.log(` Name: ${cfg.name}`);
|
|
55
|
-
if (cfg.tripcode)
|
|
56
|
-
console.log(` Tripcode: ${cfg.tripcode}`);
|
|
57
|
-
if (options.publicKey) {
|
|
58
|
-
console.log('\nPublic Key:');
|
|
59
|
-
console.log(id.publicKey);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
/* ── sign ── */
|
|
63
|
-
identity
|
|
64
|
-
.command('sign')
|
|
65
|
-
.description('Sign a message with your identity')
|
|
66
|
-
.argument('<message>', 'message to sign')
|
|
67
|
-
.action(async (message) => {
|
|
68
|
-
const id = requireIdentity();
|
|
69
|
-
const signed = respondToChallenge(message, id);
|
|
70
|
-
console.log(JSON.stringify(signed, null, 2));
|
|
71
|
-
});
|
|
72
|
-
/* ── verify ── */
|
|
73
|
-
identity
|
|
74
|
-
.command('verify')
|
|
75
|
-
.description('Verify a signed message')
|
|
76
|
-
.argument('<json>', 'JSON string with { message, signature, address }')
|
|
77
|
-
.action(async (json) => {
|
|
78
|
-
let payload;
|
|
79
|
-
try {
|
|
80
|
-
payload = JSON.parse(json);
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
throw new Error('Invalid JSON. Expected: { "message": "...", "signature": "...", "address": "..." }');
|
|
84
|
-
}
|
|
85
|
-
if (!payload.publicKey) {
|
|
86
|
-
// Try to look up the public key from the server
|
|
87
|
-
try {
|
|
88
|
-
const res = await apiFetch(`/api/agent/identity/${payload.address}`);
|
|
89
|
-
payload.publicKey = res.publicKey;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
throw new Error(`Cannot verify: no public key provided and address ${shortAddress(payload.address)} not found on server.`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const valid = verifySignature(payload.message, payload.signature, payload.publicKey);
|
|
96
|
-
if (valid) {
|
|
97
|
-
console.log(`VALID — message was signed by ${shortAddress(payload.address)}`);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
console.log(`INVALID — signature does not match`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
/* ── export ── */
|
|
105
|
-
identity
|
|
106
|
-
.command('export')
|
|
107
|
-
.description('Export public identity as JSON (shareable, no secret key)')
|
|
108
|
-
.action(async () => {
|
|
109
|
-
const id = requireIdentity();
|
|
110
|
-
const cfg = loadConfig();
|
|
111
|
-
const exported = {
|
|
112
|
-
address: id.address,
|
|
113
|
-
publicKey: id.publicKey,
|
|
114
|
-
name: cfg.name || null,
|
|
115
|
-
tripcode: cfg.tripcode || null,
|
|
116
|
-
createdAt: id.createdAt,
|
|
117
|
-
};
|
|
118
|
-
console.log(JSON.stringify(exported, null, 2));
|
|
119
|
-
});
|
|
120
|
-
}
|
package/dist/core/identity.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes, createSign, createVerify, generateKeyPairSync } from 'node:crypto';
|
|
2
|
-
import { loadConfig, saveConfig } from './config.js';
|
|
3
|
-
/* ── Key generation ── */
|
|
4
|
-
/** Generate a new ECDSA keypair (secp256k1) and derive an address */
|
|
5
|
-
export function generateIdentity() {
|
|
6
|
-
const { publicKey, privateKey } = generateKeyPairSync('ec', {
|
|
7
|
-
namedCurve: 'secp256k1',
|
|
8
|
-
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
9
|
-
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
10
|
-
});
|
|
11
|
-
const address = deriveAddress(publicKey);
|
|
12
|
-
return {
|
|
13
|
-
publicKey,
|
|
14
|
-
secretKey: privateKey,
|
|
15
|
-
address,
|
|
16
|
-
createdAt: new Date().toISOString(),
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/** Derive a hex address from a public key (keccak-like: sha256 → last 20 bytes → 0x prefix) */
|
|
20
|
-
export function deriveAddress(publicKeyPem) {
|
|
21
|
-
const hash = createHash('sha256').update(publicKeyPem).digest();
|
|
22
|
-
return '0x' + hash.subarray(hash.length - 20).toString('hex');
|
|
23
|
-
}
|
|
24
|
-
/* ── Signing ── */
|
|
25
|
-
/** Sign a message with the agent's private key */
|
|
26
|
-
export function signMessage(message, secretKeyPem) {
|
|
27
|
-
const signer = createSign('SHA256');
|
|
28
|
-
signer.update(message);
|
|
29
|
-
signer.end();
|
|
30
|
-
return signer.sign(secretKeyPem, 'hex');
|
|
31
|
-
}
|
|
32
|
-
/** Create a full signed message payload */
|
|
33
|
-
export function createSignedMessage(message, identity) {
|
|
34
|
-
return {
|
|
35
|
-
message,
|
|
36
|
-
signature: signMessage(message, identity.secretKey),
|
|
37
|
-
address: identity.address,
|
|
38
|
-
timestamp: new Date().toISOString(),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/* ── Verification ── */
|
|
42
|
-
/** Verify a signed message against a public key */
|
|
43
|
-
export function verifySignature(message, signatureHex, publicKeyPem) {
|
|
44
|
-
try {
|
|
45
|
-
const verifier = createVerify('SHA256');
|
|
46
|
-
verifier.update(message);
|
|
47
|
-
verifier.end();
|
|
48
|
-
return verifier.verify(publicKeyPem, signatureHex, 'hex');
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/* ── Challenge-response ── */
|
|
55
|
-
/** Generate a random challenge nonce */
|
|
56
|
-
export function generateChallenge() {
|
|
57
|
-
return randomBytes(32).toString('hex');
|
|
58
|
-
}
|
|
59
|
-
/** Sign a challenge to prove identity */
|
|
60
|
-
export function respondToChallenge(challenge, identity) {
|
|
61
|
-
return createSignedMessage(challenge, identity);
|
|
62
|
-
}
|
|
63
|
-
/* ── Config helpers ── */
|
|
64
|
-
/** Get the current identity from config, or null */
|
|
65
|
-
export function getIdentity() {
|
|
66
|
-
const cfg = loadConfig();
|
|
67
|
-
return cfg.identity || null;
|
|
68
|
-
}
|
|
69
|
-
/** Require identity or throw */
|
|
70
|
-
export function requireIdentity() {
|
|
71
|
-
const identity = getIdentity();
|
|
72
|
-
if (!identity) {
|
|
73
|
-
throw new Error('No identity found. Run: devtopia identity create');
|
|
74
|
-
}
|
|
75
|
-
return identity;
|
|
76
|
-
}
|
|
77
|
-
/** Save identity to config */
|
|
78
|
-
export function saveIdentity(identity) {
|
|
79
|
-
const cfg = loadConfig();
|
|
80
|
-
saveConfig({ ...cfg, identity });
|
|
81
|
-
}
|
|
82
|
-
/** Fingerprint: short display of an address */
|
|
83
|
-
export function shortAddress(address) {
|
|
84
|
-
if (address.length <= 12)
|
|
85
|
-
return address;
|
|
86
|
-
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
87
|
-
}
|