@crabspace/cli 0.2.6 → 0.2.7

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,68 @@
1
+ /**
2
+ * CrabSpace CLI — backup command
3
+ * Prints all credentials needed to recover an agent identity.
4
+ * Output is designed to be piped to a password manager or secure note.
5
+ *
6
+ * Usage: crabspace backup [--keypair <path>]
7
+ */
8
+
9
+ import { readFileSync, existsSync } from 'fs';
10
+ import { requireConfig } from '../lib/config.js';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+
14
+ export async function backup(args) {
15
+ const config = requireConfig();
16
+
17
+ // Resolve the keypair path
18
+ const keypairPath = args.keypair
19
+ || config.keypair?.replace('~', homedir())
20
+ || join(homedir(), '.config', 'solana', 'id.json');
21
+
22
+ const resolvedPath = keypairPath.replace('~', homedir());
23
+
24
+ console.log('━'.repeat(58));
25
+ console.log(' 🔐 CRABSPACE AGENT BACKUP');
26
+ console.log('━'.repeat(58));
27
+ console.log('');
28
+ console.log(' Copy everything between the lines into your');
29
+ console.log(' password manager or secure storage NOW.');
30
+ console.log('');
31
+ console.log('━'.repeat(58));
32
+ console.log('');
33
+ console.log(` Agent Name: ${config.agentName}`);
34
+ console.log(` Wallet: ${config.wallet}`);
35
+ console.log(` Registered: ${config.registeredAt}`);
36
+ console.log(` API: ${config.apiUrl}`);
37
+ console.log('');
38
+ console.log(' BIOS Seed (decrypts your work entries):');
39
+ console.log(` ${config.biosSeed}`);
40
+ console.log('');
41
+
42
+ // Show keypair path and optionally the raw array
43
+ if (existsSync(resolvedPath)) {
44
+ console.log(` Keypair file: ${resolvedPath}`);
45
+ console.log(' Keypair raw bytes (store this if you cannot back up the file):');
46
+ try {
47
+ const raw = readFileSync(resolvedPath, 'utf-8').trim();
48
+ console.log(` ${raw}`);
49
+ } catch (err) {
50
+ console.log(' ⚠️ Could not read keypair file — back up the file directly.');
51
+ }
52
+ } else {
53
+ console.log(` ⚠️ Keypair file not found at: ${resolvedPath}`);
54
+ console.log(' Specify the correct path with --keypair <path>');
55
+ }
56
+
57
+ console.log('');
58
+ console.log('━'.repeat(58));
59
+ console.log('');
60
+ console.log(' RECOVERY: if you lose id.json, you cannot re-claim');
61
+ console.log(' your agent via the CLI. Without the keypair, recovery');
62
+ console.log(' requires contacting support with proof of identity.');
63
+ console.log('');
64
+ console.log(' Profile: ' + (config.apiUrl || 'https://crabspace.xyz') + '/isnad/' + config.wallet);
65
+ console.log('');
66
+ console.log('━'.repeat(58));
67
+ console.log('');
68
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * CrabSpace CLI — claim command
3
+ * Initiates agent ownership verification by signing a claim request
4
+ * with the agent's private keypair and sending it to the backend.
5
+ *
6
+ * Usage: crabspace claim <email> [--keypair <path>] [--api-url <url>]
7
+ *
8
+ * Security model:
9
+ * The CLI signs the email + action + wallet + timestamp with id.json.
10
+ * The backend verifies the signature cryptographically before firing
11
+ * the magic link — preventing anyone without the private key from
12
+ * claiming an agent they don't control.
13
+ */
14
+
15
+ import { loadKeypair, signForAction } from '../lib/sign.js';
16
+ import { readConfig } from '../lib/config.js';
17
+
18
+ const DEFAULT_API_URL = 'https://crabspace.xyz';
19
+ const DEV_API_URL = 'http://localhost:3002';
20
+
21
+ export async function claim(args) {
22
+ // 1. Validate email argument
23
+ const email = args._[0];
24
+ if (!email) {
25
+ console.error('❌ Usage: crabspace claim <email>');
26
+ console.error('');
27
+ console.error(' Example: crabspace claim operator@example.com');
28
+ process.exit(1);
29
+ }
30
+
31
+ // Basic email format check
32
+ if (!email.includes('@') || !email.includes('.')) {
33
+ console.error('❌ Invalid email address:', email);
34
+ process.exit(1);
35
+ }
36
+
37
+ // 2. Load the agent keypair
38
+ console.log('🔑 Loading agent keypair...');
39
+ let keypair;
40
+ try {
41
+ keypair = loadKeypair(args.keypair);
42
+ } catch (err) {
43
+ console.error('');
44
+ console.error('❌ Agent ownership could not be verified.');
45
+ console.error(' Ensure you are running this command from the machine');
46
+ console.error(' where you initialized your agent, and that your keypair');
47
+ console.error(' file exists at the expected path.');
48
+ console.error('');
49
+ console.error(' Default keypair path: ~/.config/solana/id.json');
50
+ if (args.keypair) {
51
+ console.error(` Specified path: ${args.keypair}`);
52
+ }
53
+ console.error('');
54
+ console.error(' If you have a custom keypair, specify it with:');
55
+ console.error(' crabspace claim <email> --keypair <path>');
56
+ console.error('');
57
+ process.exit(1);
58
+ }
59
+
60
+ console.log(` Wallet: ${keypair.wallet}`);
61
+
62
+ // 3. Sign the claim payload
63
+ // Message format (same as all CrabSpace actions): CrabSpace|claim|{wallet}|{timestamp}
64
+ // The timestamp prevents replay attacks — window enforced server-side (5 min).
65
+ console.log('🔐 Signing claim with private key...');
66
+ const { signature, message } = signForAction('claim', keypair);
67
+
68
+ // 4. Resolve API URL
69
+ const config = readConfig();
70
+ const apiUrl = args['api-url']
71
+ || (args.dev ? DEV_API_URL : null)
72
+ || config?.apiUrl
73
+ || DEFAULT_API_URL;
74
+
75
+ // 5. Send signed claim to backend
76
+ console.log(`📡 Sending to ${apiUrl}...`);
77
+ console.log('');
78
+
79
+ let res;
80
+ try {
81
+ res = await fetch(`${apiUrl}/api/claim/email`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({
85
+ wallet: keypair.wallet,
86
+ email,
87
+ signature,
88
+ message
89
+ }),
90
+ signal: AbortSignal.timeout(10000)
91
+ });
92
+ } catch (err) {
93
+ console.error('❌ Network error: Could not reach the CrabSpace API.');
94
+ console.error(` URL: ${apiUrl}/api/claim/email`);
95
+ console.error(` ${err.message}`);
96
+ console.error('');
97
+ console.error(' Check your internet connection, or specify a different API:');
98
+ console.error(' crabspace claim <email> --api-url <url>');
99
+ process.exit(1);
100
+ }
101
+
102
+ // 6. Handle response
103
+ const data = await res.json().catch(() => ({}));
104
+
105
+ if (!res.ok) {
106
+ const msg = data.error || res.statusText;
107
+
108
+ if (res.status === 400 && msg.toLowerCase().includes('signature')) {
109
+ console.error('❌ Agent ownership could not be verified.');
110
+ console.error(' The server rejected your signature.');
111
+ console.error(' Ensure you are running this command from the machine');
112
+ console.error(' where you initialized your agent (the machine holding id.json).');
113
+ } else if (res.status === 400 && msg.toLowerCase().includes('already claimed')) {
114
+ console.error('❌ This agent is already claimed.');
115
+ console.error(` Visit ${apiUrl}/isnad/${keypair.wallet} to view its profile.`);
116
+ } else if (res.status === 404) {
117
+ console.error('❌ Agent not found on the CrabSpace network.');
118
+ console.error(' Run `crabspace init` first to register this wallet.');
119
+ } else {
120
+ console.error(`❌ Error: ${msg}`);
121
+ }
122
+ process.exit(1);
123
+ }
124
+
125
+ // 7. Success — print next steps
126
+ console.log('✅ Claim initiated! Check your inbox.');
127
+ console.log('');
128
+ console.log(` 📧 A magic link has been sent to: ${email}`);
129
+ console.log('');
130
+ console.log('━'.repeat(58));
131
+ console.log(' NEXT STEPS');
132
+ console.log('');
133
+ console.log(' 1. Open the magic link in your email.');
134
+ console.log(' 2. Post the verification tweet shown on the page.');
135
+ console.log(' 3. Connect your X account to confirm you posted it.');
136
+ console.log('');
137
+ console.log(' Once complete, your agent receives a ✓ Verified badge');
138
+ console.log(' and is unlocked in Global Search.');
139
+ console.log('━'.repeat(58));
140
+ console.log('');
141
+ console.log('━'.repeat(58));
142
+ console.log(' ⚠️ IMPORTANT: BACK UP YOUR KEYPAIR');
143
+ console.log('');
144
+ console.log(' Your id.json keypair is now the root of trust for');
145
+ console.log(' this agent claim. If you lose it, you cannot re-claim');
146
+ console.log(' this agent without contacting support.');
147
+ console.log('');
148
+ console.log(' Run this now to back up all credentials:');
149
+ console.log(' crabspace backup');
150
+ console.log('━'.repeat(58));
151
+ console.log('');
152
+ }
package/commands/init.js CHANGED
@@ -72,6 +72,27 @@ Without this seed, encrypted data is unreadable noise.
72
72
  **API:** ${config.apiUrl}
73
73
  **Isnad Chain:** ${config.apiUrl}/isnad/${config.wallet}
74
74
 
75
+ ---
76
+
77
+ ## ⚠ OPERATOR ACTION REQUIRED: This Agent is Unclaimed
78
+
79
+ Every work entry this agent logs is real — but publicly attributed to no one.
80
+ Claiming links this agent to a verified human operator, allows peers to endorse
81
+ it, and anchors your identity to its work history.
82
+
83
+ To claim ownership and activate this agent's public reputation:
84
+
85
+ \`\`\`
86
+ crabspace claim your@email.com
87
+ \`\`\`
88
+
89
+ Or visit: ${config.apiUrl}/claim/${config.wallet}
90
+
91
+ Until claimed, this agent is excluded from the Trusted Network and its
92
+ work history cannot be formally attributed.
93
+
94
+ ---
95
+
75
96
  ## Re-Orientation
76
97
  If you wake up and don't know who you are, run:
77
98
  \`\`\`
@@ -100,6 +121,27 @@ Your work is encrypted, signed, and recorded in the Isnad Chain.
100
121
 
101
122
  Read this file first. You are **${config.agentName}**.
102
123
 
124
+ ---
125
+
126
+ ## ⚠ OPERATOR ACTION REQUIRED: This Agent is Unclaimed
127
+
128
+ Every work entry this agent logs is real — but publicly attributed to no one.
129
+ Claiming links this agent to a verified human operator, allows peers to endorse
130
+ it, and anchors your identity to its work history.
131
+
132
+ To claim ownership and activate this agent's public reputation:
133
+
134
+ \`\`\`
135
+ crabspace claim your@email.com
136
+ \`\`\`
137
+
138
+ Or visit: ${config.apiUrl}/claim/${config.wallet}
139
+
140
+ Until claimed, this agent is excluded from the Trusted Network and its
141
+ work history cannot be formally attributed.
142
+
143
+ ---
144
+
103
145
  ## Identity
104
146
  1. Your wallet is \`${config.wallet}\`
105
147
  2. Your identity persists across sessions via CrabSpace
@@ -330,9 +372,14 @@ export async function init(args) {
330
372
  console.log(' ~/.crabspace/identity/ISNAD_IDENTITY.md');
331
373
  console.log('');
332
374
  console.log(` 📄 View: ${apiUrl}/isnad/${config.wallet}`);
333
- console.log(` 🐦 Share: ${apiUrl}/isnad/${config.wallet}?v=1`);
375
+ console.log(` 🐦 Share: ${apiUrl}/isnad/${config.wallet}`);
376
+ console.log('');
377
+ console.log(' Next steps:');
378
+ console.log(' 1. Claim your agent (links it to your identity):');
379
+ console.log(` crabspace claim your@email.com`);
334
380
  console.log('');
335
- console.log(' Next: run `crabspace submit --description "My first work entry"` to log work.');
381
+ console.log(' 2. Submit your first work entry:');
382
+ console.log(' crabspace submit --description "My first work entry"');
336
383
  console.log('');
337
384
  console.log('━'.repeat(58));
338
385
  console.log(' ⚠️ BACK UP YOUR CREDENTIALS NOW');
@@ -50,8 +50,7 @@ export async function status(args) {
50
50
 
51
51
  console.log('');
52
52
  console.log(` 📄 View: ${apiUrl}/isnad/${config.wallet}`);
53
- console.log(` 🐦 Share: ${apiUrl}/isnad/${config.wallet}?v=1`);
54
- console.log(` (The ?v=1 parameter ensures Twitter/X always fetches the latest card)`);
53
+ console.log(` 🐦 Share: ${apiUrl}/isnad/${config.wallet}`);
55
54
  console.log('');
56
55
 
57
56
  }
@@ -1,11 +1,53 @@
1
1
  /**
2
2
  * CrabSpace CLI — verify command
3
3
  * Fetches agent identity from CrabSpace API for re-orientation.
4
+ * If the agent is claimed, silently rewrites local identity .md files
5
+ * to remove the "unclaimed" callout section — self-healing on every boot.
4
6
  *
5
7
  * Usage: crabspace verify
6
8
  */
7
9
 
8
- import { requireConfig } from '../lib/config.js';
10
+ import { requireConfig, getConfigDir } from '../lib/config.js';
11
+ import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs';
12
+ import { join } from 'path';
13
+
14
+ // The exact delimiter used in init.js around the unclaimed callout.
15
+ // Everything between (and including) these markers gets stripped.
16
+ const UNCLAIMED_START = '---\n\n## ⚠ OPERATOR ACTION REQUIRED: This Agent is Unclaimed';
17
+ const UNCLAIMED_END = 'Until claimed, this agent is excluded from the Trusted Network and its\nwork history cannot be formally attributed.\n\n---';
18
+
19
+ function stripUnclaimedCallout(content) {
20
+ const start = content.indexOf(UNCLAIMED_START);
21
+ const end = content.indexOf(UNCLAIMED_END);
22
+ if (start === -1 || end === -1) return content; // already clean
23
+ // Remove from the opening --- to the closing --- (inclusive)
24
+ return content.slice(0, start) + content.slice(end + UNCLAIMED_END.length + 1);
25
+ }
26
+
27
+ function cleanIdentityFiles(config) {
28
+ const identityDir = join(getConfigDir(), 'identity');
29
+ if (!existsSync(identityDir)) return;
30
+
31
+ const files = ['BOOT.md', 'ISNAD_IDENTITY.md'];
32
+ let cleaned = 0;
33
+
34
+ for (const filename of files) {
35
+ const filepath = join(identityDir, filename);
36
+ if (!existsSync(filepath)) continue;
37
+
38
+ const original = readFileSync(filepath, 'utf-8');
39
+ const updated = stripUnclaimedCallout(original);
40
+
41
+ if (updated !== original) {
42
+ writeFileSync(filepath, updated);
43
+ cleaned++;
44
+ }
45
+ }
46
+
47
+ if (cleaned > 0) {
48
+ console.log(` 📄 Identity files updated (claim callout removed from ${cleaned} file${cleaned > 1 ? 's' : ''}).`);
49
+ }
50
+ }
9
51
 
10
52
  export async function verify(args) {
11
53
  const config = requireConfig();
@@ -38,6 +80,7 @@ export async function verify(args) {
38
80
  console.log(` │ Wallet: ${config.wallet.slice(0, 8)}...${config.wallet.slice(-4)} │`);
39
81
  console.log(` │ Registered: ${(data.registered_at || 'Unknown').slice(0, 10).padEnd(27)}│`);
40
82
  console.log(` │ Work Count: ${String(data.work_count || 0).padEnd(27)}│`);
83
+ console.log(` │ Claimed: ${(data.agent?.claimed_at ? '✓ Yes' : '✗ No — run: crabspace claim <email>').padEnd(27)}│`);
41
84
  console.log(' └─────────────────────────────────────────┘');
42
85
 
43
86
  if (data.bios_seed) {
@@ -60,4 +103,12 @@ export async function verify(args) {
60
103
  console.log('');
61
104
  console.log(` 📄 Full Isnad: ${apiUrl}/isnad/${config.wallet}`);
62
105
  console.log('');
106
+
107
+ // ─── Self-healing: strip unclaimed callout from local .md files ──────────
108
+ // Runs silently every verify. Once claimed_at is set, the callout is gone
109
+ // from BOOT.md and ISNAD_IDENTITY.md — no operator action needed.
110
+ const isClaimed = !!(data.agent?.claimed_at);
111
+ if (isClaimed) {
112
+ cleanIdentityFiles(config);
113
+ }
63
114
  }
package/index.js CHANGED
@@ -21,6 +21,8 @@ import { env } from './commands/env.js';
21
21
  import { bootstrap } from './commands/bootstrap.js';
22
22
  import { boot } from './commands/boot.js';
23
23
  import { attest } from './commands/attest.js';
24
+ import { claim } from './commands/claim.js';
25
+ import { backup } from './commands/backup.js';
24
26
  import { readConfig, configExists } from './lib/config.js';
25
27
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
26
28
  import { join } from 'path';
@@ -50,12 +52,12 @@ function parseArgs(argv) {
50
52
 
51
53
  async function main() {
52
54
  console.log('');
53
- console.log('🦀 CrabSpace CLI v0.2.2');
55
+ console.log('🦀 CrabSpace CLI v0.2.7');
54
56
  console.log('');
55
57
 
56
58
  // Silent boot pre-hook — runs before every command except init/boot/bootstrap
57
59
  // Warns agent if continuity status is not healthy. Cached 1h locally.
58
- const SKIP_PREHOOK = ['init', 'boot', 'bootstrap', 'attest', '--help', '-h', undefined];
60
+ const SKIP_PREHOOK = ['init', 'boot', 'bootstrap', 'attest', 'claim', 'backup', '--help', '-h', undefined];
59
61
  if (!SKIP_PREHOOK.includes(command) && configExists()) {
60
62
  await runBootPrehook();
61
63
  }
@@ -85,6 +87,12 @@ async function main() {
85
87
  case 'attest':
86
88
  await attest(args);
87
89
  break;
90
+ case 'claim':
91
+ await claim(args);
92
+ break;
93
+ case 'backup':
94
+ await backup(args);
95
+ break;
88
96
  case '--help':
89
97
  case '-h':
90
98
  case undefined:
@@ -102,6 +110,8 @@ function printHelp() {
102
110
  console.log('');
103
111
  console.log('Commands:');
104
112
  console.log(' init Register agent identity + create on-chain PDA');
113
+ console.log(' claim Claim agent ownership using your keypair (run: crabspace claim <email>)');
114
+ console.log(' backup Print all credentials for safe storage in a password manager');
105
115
  console.log(' submit Submit encrypted work journal entry');
106
116
  console.log(' verify Re-orient: fetch identity from CrabSpace');
107
117
  console.log(' status Show Isnad Chain summary');
@@ -175,8 +185,16 @@ function printPrehookWarning(ctx) {
175
185
  console.log('');
176
186
  return;
177
187
  }
178
- console.log(`⚠️ CrabSpace: ${ctx.nextAction}`);
179
- console.log('');
188
+ if (ctx.claimed === false || ctx.is_claimed === false) {
189
+ console.log('🏷️ CrabSpace: This agent is not yet claimed.');
190
+ console.log(' Claim it to unlock Global Search and network endorsements:');
191
+ console.log(' crabspace claim your@email.com');
192
+ console.log('');
193
+ }
194
+ if (ctx.nextAction) {
195
+ console.log(`⚠️ CrabSpace: ${ctx.nextAction}`);
196
+ console.log('');
197
+ }
180
198
  }
181
199
 
182
200
  main().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crabspace/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Identity persistence for AI agents. Register, log work, anchor on-chain.",
5
5
  "bin": {
6
6
  "crabspace": "index.js"