treeship-cli 0.1.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/bin/treeship.js +19 -0
- package/commands/attest.js +55 -0
- package/commands/config.js +42 -0
- package/commands/verify.js +57 -0
- package/commands/whoami.js +33 -0
- package/lib/client.js +74 -0
- package/lib/config.js +25 -0
- package/package.json +35 -0
package/bin/treeship.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import { attestCommand } from '../commands/attest.js';
|
|
5
|
+
import { verifyCommand } from '../commands/verify.js';
|
|
6
|
+
import { whoamiCommand } from '../commands/whoami.js';
|
|
7
|
+
import { configCommand } from '../commands/config.js';
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('treeship')
|
|
11
|
+
.description('Treeship CLI - Cryptographic attestations for AI agents')
|
|
12
|
+
.version('0.1.0');
|
|
13
|
+
|
|
14
|
+
program.addCommand(attestCommand);
|
|
15
|
+
program.addCommand(verifyCommand);
|
|
16
|
+
program.addCommand(whoamiCommand);
|
|
17
|
+
program.addCommand(configCommand);
|
|
18
|
+
|
|
19
|
+
program.parse();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { TreeshipClient } from '../lib/client.js';
|
|
4
|
+
import { getDefaultAgent } from '../lib/config.js';
|
|
5
|
+
|
|
6
|
+
export const attestCommand = new Command('attest')
|
|
7
|
+
.description('Create a cryptographic attestation for an agent action')
|
|
8
|
+
.requiredOption('--action <text>', 'Description of the action being attested')
|
|
9
|
+
.requiredOption('--inputs-hash <hash>', 'SHA256 hash of the inputs')
|
|
10
|
+
.option('--agent <slug>', 'Agent slug (or set TREESHIP_AGENT)')
|
|
11
|
+
.option('--json', 'Output as JSON')
|
|
12
|
+
.option('--quiet', 'Output only the verification URL')
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const agentSlug = opts.agent ?? getDefaultAgent();
|
|
16
|
+
|
|
17
|
+
if (!agentSlug) {
|
|
18
|
+
console.error(chalk.red('✗ Agent slug required. Use --agent or set TREESHIP_AGENT'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const client = new TreeshipClient();
|
|
23
|
+
const result = await client.attest({
|
|
24
|
+
agentSlug,
|
|
25
|
+
action: opts.action,
|
|
26
|
+
inputsHash: opts.inputsHash,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (opts.json) {
|
|
30
|
+
console.log(JSON.stringify(result, null, 2));
|
|
31
|
+
} else if (opts.quiet) {
|
|
32
|
+
console.log(result.public_url);
|
|
33
|
+
} else {
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(chalk.green('✓ Attestation created'));
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(chalk.dim(' ID ') + result.attestation_id);
|
|
38
|
+
console.log(chalk.dim(' Agent ') + result.agent_slug);
|
|
39
|
+
console.log(chalk.dim(' Action ') + result.action);
|
|
40
|
+
console.log(chalk.dim(' Hash ') + result.payload_hash.slice(0, 16) + '...');
|
|
41
|
+
console.log(chalk.dim(' Signature ') + result.signature.slice(0, 24) + '...');
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.dim(' Verify ') + chalk.cyan.underline(result.public_url));
|
|
44
|
+
console.log(chalk.dim(' CLI ') + chalk.dim(result.verify_command));
|
|
45
|
+
console.log('');
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (opts.json) {
|
|
49
|
+
console.log(JSON.stringify({ error: err.message }));
|
|
50
|
+
} else {
|
|
51
|
+
console.error(chalk.red('✗ ' + err.message));
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getConfig } from '../lib/config.js';
|
|
3
|
+
|
|
4
|
+
export const configCommand = new Command('config')
|
|
5
|
+
.description('Manage CLI configuration');
|
|
6
|
+
|
|
7
|
+
configCommand
|
|
8
|
+
.command('set <key> <value>')
|
|
9
|
+
.description('Set a configuration value')
|
|
10
|
+
.action((key, value) => {
|
|
11
|
+
const config = getConfig();
|
|
12
|
+
const normalizedKey = key.replace(/-/g, '_');
|
|
13
|
+
config.set(normalizedKey, value);
|
|
14
|
+
console.log(`✓ Set ${key} = ${value}`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
configCommand
|
|
18
|
+
.command('get <key>')
|
|
19
|
+
.description('Get a configuration value')
|
|
20
|
+
.action((key) => {
|
|
21
|
+
const config = getConfig();
|
|
22
|
+
const normalizedKey = key.replace(/-/g, '_');
|
|
23
|
+
const value = config.get(normalizedKey);
|
|
24
|
+
console.log(value ?? '(not set)');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
configCommand
|
|
28
|
+
.command('list')
|
|
29
|
+
.description('List all configuration values')
|
|
30
|
+
.action(() => {
|
|
31
|
+
const config = getConfig();
|
|
32
|
+
console.log(JSON.stringify(config.store, null, 2));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
configCommand
|
|
36
|
+
.command('reset')
|
|
37
|
+
.description('Reset all configuration')
|
|
38
|
+
.action(() => {
|
|
39
|
+
const config = getConfig();
|
|
40
|
+
config.clear();
|
|
41
|
+
console.log('✓ Configuration reset');
|
|
42
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { TreeshipClient } from '../lib/client.js';
|
|
4
|
+
|
|
5
|
+
export const verifyCommand = new Command('verify')
|
|
6
|
+
.description('Verify an attestation by ID')
|
|
7
|
+
.argument('<attestation-id>', 'The attestation ID to verify')
|
|
8
|
+
.option('--json', 'Output as JSON')
|
|
9
|
+
.option('--quiet', 'Output only valid/invalid')
|
|
10
|
+
.action(async (attestationId, opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const client = new TreeshipClient();
|
|
13
|
+
const result = await client.verify(attestationId);
|
|
14
|
+
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
console.log(JSON.stringify(result, null, 2));
|
|
17
|
+
} else if (opts.quiet) {
|
|
18
|
+
console.log(result.valid ? 'valid' : 'invalid');
|
|
19
|
+
process.exit(result.valid ? 0 : 1);
|
|
20
|
+
} else {
|
|
21
|
+
console.log('');
|
|
22
|
+
if (result.valid) {
|
|
23
|
+
console.log(chalk.green('✓ Attestation verified'));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.red('✗ Attestation INVALID'));
|
|
26
|
+
}
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(chalk.dim(' ID ') + result.attestation_id);
|
|
29
|
+
console.log(chalk.dim(' Agent ') + result.agent_slug);
|
|
30
|
+
console.log(chalk.dim(' Action ') + result.action);
|
|
31
|
+
console.log(chalk.dim(' Time ') + result.timestamp);
|
|
32
|
+
console.log(chalk.dim(' Hash ') + result.payload_hash.slice(0, 16) + '...');
|
|
33
|
+
console.log(chalk.dim(' Key ID ') + result.key_id);
|
|
34
|
+
console.log('');
|
|
35
|
+
|
|
36
|
+
if (result.has_zk_proof) {
|
|
37
|
+
console.log(chalk.blue('⬡ ZK Proof attached'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.dim(' Independent verification:'));
|
|
41
|
+
console.log(chalk.dim(' 1. ') + result.independent_verification.curl_pubkey);
|
|
42
|
+
console.log(chalk.dim(' 2. Save payload: ') + chalk.dim('echo \'' + result.independent_verification.recreate_payload.slice(0, 40) + '...\' > payload.txt'));
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
if (!result.valid) {
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (opts.json) {
|
|
51
|
+
console.log(JSON.stringify({ error: err.message, valid: false }));
|
|
52
|
+
} else {
|
|
53
|
+
console.error(chalk.red('✗ ' + err.message));
|
|
54
|
+
}
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { getConfig, getApiUrl, getApiKey, getDefaultAgent } from '../lib/config.js';
|
|
4
|
+
|
|
5
|
+
export const whoamiCommand = new Command('whoami')
|
|
6
|
+
.description('Show current configuration and status')
|
|
7
|
+
.option('--json', 'Output as JSON')
|
|
8
|
+
.action(async (opts) => {
|
|
9
|
+
const apiUrl = getApiUrl();
|
|
10
|
+
const apiKey = getApiKey();
|
|
11
|
+
const agent = getDefaultAgent();
|
|
12
|
+
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
console.log(JSON.stringify({
|
|
15
|
+
api_url: apiUrl,
|
|
16
|
+
api_key_set: !!apiKey,
|
|
17
|
+
default_agent: agent || null,
|
|
18
|
+
}, null, 2));
|
|
19
|
+
} else {
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(chalk.bold('Treeship CLI'));
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(chalk.dim(' API URL ') + apiUrl);
|
|
24
|
+
console.log(chalk.dim(' API Key ') + (apiKey ? chalk.green('set') : chalk.red('not set')));
|
|
25
|
+
console.log(chalk.dim(' Agent ') + (agent || chalk.dim('not set')));
|
|
26
|
+
|
|
27
|
+
if (agent) {
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(chalk.dim(' Feed ') + chalk.cyan.underline(`https://treeship.dev/verify/${agent}`));
|
|
30
|
+
}
|
|
31
|
+
console.log('');
|
|
32
|
+
}
|
|
33
|
+
});
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getApiUrl, getApiKey } from './config.js';
|
|
2
|
+
|
|
3
|
+
export class TreeshipClient {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.apiUrl = options.apiUrl ?? getApiUrl();
|
|
6
|
+
this.apiKey = options.apiKey ?? getApiKey();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async attest({ agentSlug, action, inputsHash, metadata }) {
|
|
10
|
+
if (!this.apiKey) {
|
|
11
|
+
throw new Error('TREESHIP_API_KEY not set. Run: export TREESHIP_API_KEY=your-key');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const res = await fetch(`${this.apiUrl}/v1/attest`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
agent_slug: agentSlug,
|
|
22
|
+
action,
|
|
23
|
+
inputs_hash: inputsHash,
|
|
24
|
+
metadata,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
throw new Error(`Attestation failed (${res.status}): ${text}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async verify(attestationId) {
|
|
37
|
+
const res = await fetch(`${this.apiUrl}/v1/verify/${attestationId}`);
|
|
38
|
+
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
if (res.status === 404) {
|
|
41
|
+
throw new Error(`Attestation not found: ${attestationId}`);
|
|
42
|
+
}
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
throw new Error(`Verification failed (${res.status}): ${text}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return res.json();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getAgent(slug) {
|
|
51
|
+
const res = await fetch(`${this.apiUrl}/v1/agent/${slug}`);
|
|
52
|
+
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
if (res.status === 404) {
|
|
55
|
+
throw new Error(`Agent not found: ${slug}`);
|
|
56
|
+
}
|
|
57
|
+
const text = await res.text();
|
|
58
|
+
throw new Error(`Failed to get agent (${res.status}): ${text}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return res.json();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getPubkey() {
|
|
65
|
+
const res = await fetch(`${this.apiUrl}/v1/pubkey`);
|
|
66
|
+
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
const text = await res.text();
|
|
69
|
+
throw new Error(`Failed to get pubkey (${res.status}): ${text}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return res.json();
|
|
73
|
+
}
|
|
74
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
let _config;
|
|
4
|
+
|
|
5
|
+
export function getConfig() {
|
|
6
|
+
if (!_config) {
|
|
7
|
+
_config = new Conf({ projectName: '@treeship/cli' });
|
|
8
|
+
}
|
|
9
|
+
return _config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getApiUrl() {
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
return config.get('api_url') ?? process.env.TREESHIP_API_URL ?? 'https://api.treeship.dev';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getApiKey() {
|
|
18
|
+
const config = getConfig();
|
|
19
|
+
return config.get('api_key') ?? process.env.TREESHIP_API_KEY ?? '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getDefaultAgent() {
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
return config.get('default_agent') ?? process.env.TREESHIP_AGENT ?? '';
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "treeship-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Treeship - cryptographic attestations for AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"treeship": "./bin/treeship.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test tests/*.test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"treeship",
|
|
14
|
+
"attestation",
|
|
15
|
+
"ai",
|
|
16
|
+
"agents",
|
|
17
|
+
"cryptography",
|
|
18
|
+
"verification"
|
|
19
|
+
],
|
|
20
|
+
"author": "Treeship",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/treeship-dev/treeship.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://treeship.dev",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"commander": "^12.0.0",
|
|
30
|
+
"conf": "^12.0.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|