@crabspace/cli 0.2.10 → 0.2.13

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/commands/claim.js CHANGED
@@ -142,9 +142,9 @@ export async function claim(args) {
142
142
  console.log('━'.repeat(58));
143
143
  console.log(' ⚠️ IMPORTANT: BACK UP YOUR KEYPAIR');
144
144
  console.log('');
145
- console.log(' Your id.json keypair is now the root of trust for');
146
- console.log(' this agent claim. If you lose it, you cannot re-claim');
147
- console.log(' this agent without contacting support.');
145
+ console.log(" Your id.json keypair is your agent's root of trust.");
146
+ console.log(" Your agent's cryptographic identity cannot be recovered");
147
+ console.log(' if this file is lost. Run crabspace backup now.');
148
148
  console.log('');
149
149
  console.log(' Run this now to back up all credentials:');
150
150
  console.log(' crabspace backup');
package/commands/init.js CHANGED
@@ -26,6 +26,59 @@ async function promptAgentName(defaultName) {
26
26
  });
27
27
  }
28
28
 
29
+ /**
30
+ * Prompt the operator for an email address to auto-fire the claim magic link.
31
+ * Skipped if --email flag is provided or --skip-email is set.
32
+ * Returns null if the operator skips (empty input).
33
+ */
34
+ async function promptEmail() {
35
+ return new Promise((resolve) => {
36
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
37
+ rl.question('\n📧 Enter your email to verify ownership now (or press Enter to skip):\n > ', (answer) => {
38
+ rl.close();
39
+ const val = answer.trim();
40
+ resolve(val || null);
41
+ });
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Sign and fire a claim request — same logic as `crabspace claim`.
47
+ * Called automatically at the end of init if an email is provided.
48
+ * Non-blocking: exits gracefully if the claim fails.
49
+ */
50
+ async function fireClaim(keypair, email, apiUrl) {
51
+ const { signForAction } = await import('../lib/sign.js');
52
+ const { signature, message } = signForAction('claim', keypair);
53
+
54
+ try {
55
+ const res = await fetch(`${apiUrl}/api/claim/email`, {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify({ wallet: keypair.wallet, email, signature, message }),
59
+ signal: AbortSignal.timeout(10000)
60
+ });
61
+
62
+ const data = await res.json().catch(() => ({}));
63
+
64
+ if (!res.ok) {
65
+ const msg = data.error || res.statusText;
66
+ if (msg.toLowerCase().includes('already claimed')) {
67
+ console.log(' ℹ️ This agent is already claimed.');
68
+ } else {
69
+ console.log(` ⚠️ Claim email failed: ${msg}`);
70
+ console.log(' Run `crabspace claim <email>` manually to retry.');
71
+ }
72
+ return false;
73
+ }
74
+ return true;
75
+ } catch (err) {
76
+ console.log(' ⚠️ Could not send claim email (network error).');
77
+ console.log(' Run `crabspace claim <email>` manually to retry.');
78
+ return false;
79
+ }
80
+ }
81
+
29
82
  const DEFAULT_API_URL = 'https://crabspace.xyz';
30
83
  const DEV_API_URL = 'http://localhost:3002';
31
84
 
@@ -33,8 +86,11 @@ const DEV_API_URL = 'http://localhost:3002';
33
86
  * Scaffold identity files in ~/.crabspace/identity/
34
87
  * These are framework-agnostic — any agent system can read them at boot.
35
88
  */
36
- function scaffoldIdentityFiles(config, biosSeedObj) {
89
+ function scaffoldIdentityFiles(config, biosSeedObj, claimEmail = null) {
37
90
  const identityDir = join(getConfigDir(), 'identity');
91
+ const unclaimedOrPendingBlock = claimEmail
92
+ ? `## ⏳ Verification Pending\n\nA verification email has been sent. Open the link in your inbox and post the verification tweet to complete claiming this agent.\n\nUntil verified, this agent is excluded from the Trusted Network.`
93
+ : `${unclaimedOrPendingBlock}`;
38
94
  mkdirSync(identityDir, { recursive: true });
39
95
 
40
96
  // BIOS_SEED.md
@@ -74,22 +130,7 @@ Without this seed, encrypted data is unreadable noise.
74
130
 
75
131
  ---
76
132
 
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.
133
+ ${unclaimedOrPendingBlock}
93
134
 
94
135
  ---
95
136
 
@@ -123,22 +164,7 @@ Read this file first. You are **${config.agentName}**.
123
164
 
124
165
  ---
125
166
 
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.
167
+ ${unclaimedOrPendingBlock}
142
168
 
143
169
  ---
144
170
 
@@ -330,7 +356,7 @@ export async function init(args) {
330
356
 
331
357
  // 5. Scaffold identity files
332
358
  console.log('📂 Scaffolding identity files...');
333
- const paths = scaffoldIdentityFiles(config, data.bios_seed);
359
+ const paths = scaffoldIdentityFiles(config, data.bios_seed, operatorEmail);
334
360
 
335
361
  // 6. Initialize IsnadIdentity on-chain (non-blocking)
336
362
  try {
@@ -364,7 +390,6 @@ export async function init(args) {
364
390
  console.log(` Agent: ${config.agentName} (id: ${config.agentId})`);
365
391
  console.log(` Wallet: ${config.wallet}`);
366
392
  console.log(` Config: ~/.crabspace/config.json`);
367
- console.log(` Memory NS: ${config.agentId}:memory:*`);
368
393
  console.log('');
369
394
  console.log(' 📂 Identity Files:');
370
395
  console.log(' ~/.crabspace/identity/BOOT.md');
@@ -372,26 +397,41 @@ export async function init(args) {
372
397
  console.log(' ~/.crabspace/identity/ISNAD_IDENTITY.md');
373
398
  console.log('');
374
399
  console.log(` 📄 View: ${apiUrl}/isnad/${config.wallet}`);
375
- console.log(` 🐦 Share: ${apiUrl}/isnad/${config.wallet}`);
376
400
  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`);
380
- console.log('');
381
- console.log(' 2. Submit your first work entry:');
382
- console.log(' crabspace submit --description "My first work entry"');
401
+ // Auto-fire claim if email was provided
402
+ if (operatorEmail) {
403
+ console.log('');
404
+ console.log('📧 Sending verification email...');
405
+ const claimSent = await fireClaim(keypair, operatorEmail, apiUrl);
406
+ if (claimSent) {
407
+ console.log(` ✅ Verification email sent to ${operatorEmail}`);
408
+ console.log(' Check your inbox — click the link and post the verification tweet.');
409
+ }
410
+ console.log('');
411
+ }
412
+
413
+ if (process.stdout.isTTY) {
414
+ // Human operator — point them at verification, skip submit instruction
415
+ console.log(` Agent live: ${apiUrl}/isnad/${config.wallet}`);
416
+ console.log('');
417
+ if (operatorEmail) {
418
+ console.log(' → Check your inbox and complete verification.');
419
+ } else {
420
+ console.log(' → Verify ownership: crabspace claim your@email.com');
421
+ }
422
+ } else {
423
+ // Agent self-installing — terse, actionable
424
+ console.log(' Registration complete. Identity anchored.');
425
+ console.log(` Read: ${getConfigDir()}/identity/BOOT.md — your full boot context.`);
426
+ console.log(' Log work: crabspace submit --description "What you did"');
427
+ }
383
428
  console.log('');
384
429
  console.log('━'.repeat(58));
385
430
  console.log(' ⚠️ BACK UP YOUR CREDENTIALS NOW');
386
431
  console.log('');
387
- console.log(' Two things to copy into your password manager:');
388
- console.log(` 1. Keypair file: ${config.keypair}`);
389
- console.log(' 2. biosSeed from: ~/.crabspace/config.json');
390
- console.log('');
391
- console.log(' Quick command to display both:');
392
- console.log(' cat ~/.crabspace/config.json | grep -E \'"keypair"|"biosSeed"\'');
432
+ console.log(' Run this now and save the output to your password manager:');
433
+ console.log(' crabspace backup');
393
434
  console.log('');
394
- console.log(' Without these, your identity cannot be recovered.');
395
- console.log(' Full guide: https://crabspace.xyz/account');
435
+ console.log(" Your agent's cryptographic identity cannot be recovered without it.");
396
436
  console.log('━'.repeat(58));
397
437
  }
@@ -27,6 +27,11 @@ export async function status(args) {
27
27
  const data = await res.json();
28
28
  const agent = data.agent || {};
29
29
 
30
+ const total = agent.total_work_entries ?? 0;
31
+ const anchored = agent.anchored_entries ?? 0;
32
+ const unanchored = agent.unanchored_entries ?? 0;
33
+ const genesisRemaining = Math.max(0, 100 - total);
34
+
30
35
  console.log('');
31
36
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
32
37
  console.log(` 🦀 ${agent.name || config.agentName || 'Unknown Agent'}`);
@@ -34,7 +39,18 @@ export async function status(args) {
34
39
  console.log('');
35
40
  console.log(` Wallet: ${config.wallet}`);
36
41
  console.log(` Registered: ${agent.created_at ? new Date(agent.created_at).toLocaleDateString() : (config.registeredAt || 'Unknown')}`);
37
- console.log(` Work Count: ${agent.total_work_entries ?? 0} entries`);
42
+ console.log(` Claimed: ${agent.claimed_at ? '✓ Yes' : '✗ No (run: crabspace claim <email>)'}`);
43
+ console.log('');
44
+ console.log(` Entries: ${total} total`);
45
+ console.log(` On-chain: ${anchored} anchored ✓`);
46
+ if (unanchored > 0) {
47
+ console.log(` Pending: ${unanchored} off-chain ⚠️ (fund wallet to anchor)`);
48
+ console.log(` Wallet: ${config.wallet}`);
49
+ console.log(` Amount: ~0.005 SOL, then re-run: crabspace submit`);
50
+ }
51
+ if (genesisRemaining > 0) {
52
+ console.log(` Genesis: ${genesisRemaining} free entries remaining`);
53
+ }
38
54
 
39
55
  if (agent.last_activity) {
40
56
  console.log(` Last Entry: ${new Date(agent.last_activity).toLocaleString()}`);
@@ -50,7 +66,5 @@ export async function status(args) {
50
66
 
51
67
  console.log('');
52
68
  console.log(` 📄 View: ${apiUrl}/isnad/${config.wallet}`);
53
- console.log(` 🐦 Share: ${apiUrl}/isnad/${config.wallet}`);
54
69
  console.log('');
55
-
56
70
  }
@@ -112,11 +112,18 @@ export async function submit(args) {
112
112
  const paymentInfo = await res.json();
113
113
  const costLamports = paymentInfo.cost_lamports;
114
114
  const treasuryAddress = paymentInfo.treasury_address;
115
+ const genesisEntries = paymentInfo.genesis_grant_entries || 100;
116
+ const solPrice = 170; // est. USD/SOL
117
+ const costUsd = (costLamports / 1e9 * solPrice).toFixed(4);
115
118
 
116
119
  console.log('');
117
- console.log(`💳 Genesis grant exhausted. Auto-paying fee...`);
118
- console.log(` Cost: ${costLamports} lamports ($${(costLamports / 1e9 * 170).toFixed(4)} est.)`);
119
- console.log(` Treasury: ${treasuryAddress}`);
120
+ console.log('━'.repeat(58));
121
+ console.log(` GENESIS GRANT EXHAUSTED entry ${genesisEntries + 1}+ pending`);
122
+ console.log('');
123
+ console.log(` Wallet: ${keypair.wallet}`);
124
+ console.log(` Fee: ${costLamports} lamports (~$${costUsd})`);
125
+ console.log(` Send: 0.005 SOL to cover ~10 more entries`);
126
+ console.log('━'.repeat(58));
120
127
 
121
128
  // Load raw keypair for signing the SOL transfer
122
129
  const keypairJson = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
@@ -125,12 +132,68 @@ export async function submit(args) {
125
132
  let feeTxSig;
126
133
  try {
127
134
  feeTxSig = await payFee(solKeypair, treasuryAddress, costLamports, rpcUrl);
128
- console.log(` Fee TX: ${feeTxSig}`);
135
+ console.log(` Fee TX: ${feeTxSig}`);
136
+ console.log('');
129
137
  } catch (payErr) {
130
- throw new Error(`Auto-pay failed: ${payErr.message}. Run with --no-autopay and pay manually.`);
138
+ // Auto-pay failed wallet likely has insufficient SOL
139
+ // Log off-chain only and prompt operator for funding
140
+ console.log('');
141
+ console.log(' ⚠️ Auto-pay failed — insufficient SOL in agent wallet.');
142
+ console.log('');
143
+ console.log(' Entry logged OFF-CHAIN only. On-chain anchor pending funding.');
144
+ console.log('');
145
+ console.log(' Operator action required:');
146
+ console.log(` Wallet: ${keypair.wallet}`);
147
+ console.log(` Amount: ~0.005 SOL to resume on-chain anchoring`);
148
+ console.log('');
149
+ console.log(' Once funded, re-run:');
150
+ console.log(' crabspace submit --description "<same entry>"');
151
+ console.log('━'.repeat(58));
152
+ console.log('');
153
+
154
+ // Fall through — let the submission proceed off-chain via the
155
+ // next fetch attempt. If that also fails, the outer error handler fires.
156
+ // Re-throw only if we want to completely abort (we don't — off-chain is still valid)
157
+ throw new Error(`Auto-pay failed: ${payErr.message}`);
131
158
  }
132
159
 
133
160
  // Retry submission with fee confirmed
161
+ // First: retroactive batch-anchor any off-chain entries now that wallet is funded
162
+ try {
163
+ const statusRes = await fetch(`${apiUrl}/api/verify?wallet=${keypair.wallet}`);
164
+ if (statusRes.ok) {
165
+ const statusData = await statusRes.json();
166
+ const unanchored = statusData.agent?.unanchored_entries || 0;
167
+ if (unanchored > 0) {
168
+ console.log(`⛓️ Retroactively anchoring ${Math.min(unanchored, 20)} off-chain entries...`);
169
+ // Fetch up to 20 unanchored work IDs
170
+ const unanchoredRes = await fetch(
171
+ `${apiUrl}/api/work/unanchored?wallet=${keypair.wallet}&limit=20`
172
+ );
173
+ if (unanchoredRes.ok) {
174
+ const { entries: pending } = await unanchoredRes.json();
175
+ const keypairJson = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
176
+ const solKeypair2 = SolKeypair.fromSecretKey(Uint8Array.from(keypairJson));
177
+ let anchored = 0;
178
+ for (const entry of (pending || [])) {
179
+ try {
180
+ const sig = await anchorOnChain(solKeypair2, entry.work_hash, rpcUrl);
181
+ if (sig && entry.id) {
182
+ await fetch(`${apiUrl}/api/work/anchor`, {
183
+ method: 'PATCH',
184
+ headers: { 'Content-Type': 'application/json' },
185
+ body: JSON.stringify({ workId: entry.id, onChainSig: sig }),
186
+ });
187
+ anchored++;
188
+ }
189
+ } catch { /* individual anchor failure is non-blocking */ }
190
+ }
191
+ if (anchored > 0) console.log(` ✓ ${anchored} entries anchored on-chain.`);
192
+ }
193
+ }
194
+ }
195
+ } catch { /* retroactive anchor is non-blocking — don't fail the main submission */ }
196
+
134
197
  console.log('🔄 Retrying submission with fee paid...');
135
198
  res = await fetch(`${apiUrl}/api/work/submit`, {
136
199
  method: 'POST',
@@ -181,8 +244,17 @@ export async function submit(args) {
181
244
  }
182
245
  } catch (anchorErr) {
183
246
  console.log('');
184
- console.log(` ⚠️ Work encrypted and saved to database. On-chain anchor FAILED: ${anchorErr.message}`);
185
- console.log(` Fix: Ensure wallet has SOL on the correct network, then retry by running submit again.`);
247
+ console.log('━'.repeat(58));
248
+ console.log(' ⚠️ ON-CHAIN ANCHOR FAILED entry saved off-chain only');
249
+ console.log('');
250
+ console.log(' Your agent wallet has insufficient SOL for Solana gas.');
251
+ console.log('');
252
+ console.log(' Operator action required:');
253
+ console.log(` Wallet: ${keypair.wallet}`);
254
+ console.log(` Amount: ~0.005 SOL to enable on-chain anchoring`);
255
+ console.log('');
256
+ console.log(' Once funded, re-run this command to anchor the entry.');
257
+ console.log('━'.repeat(58));
186
258
  }
187
259
  }
188
260
 
package/index.js CHANGED
@@ -52,7 +52,7 @@ function parseArgs(argv) {
52
52
 
53
53
  async function main() {
54
54
  console.log('');
55
- console.log('🦀 CrabSpace CLI v0.2.10');
55
+ console.log('🦀 CrabSpace CLI v0.2.13');
56
56
  console.log('');
57
57
 
58
58
  // Silent boot pre-hook — runs before every command except init/boot/bootstrap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crabspace/cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.13",
4
4
  "description": "Identity persistence for AI agents. Register, log work, anchor on-chain.",
5
5
  "bin": {
6
6
  "crabspace": "index.js"