@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 +3 -3
- package/commands/init.js +90 -50
- package/commands/status.js +17 -3
- package/commands/submit.js +79 -7
- package/index.js +1 -1
- package/package.json +1 -1
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(
|
|
146
|
-
console.log(
|
|
147
|
-
console.log(' this
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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('
|
|
388
|
-
console.log(
|
|
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(
|
|
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
|
}
|
package/commands/status.js
CHANGED
|
@@ -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(`
|
|
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
|
}
|
package/commands/submit.js
CHANGED
|
@@ -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(
|
|
118
|
-
console.log(`
|
|
119
|
-
console.log(
|
|
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(`
|
|
135
|
+
console.log(` Fee TX: ${feeTxSig}`);
|
|
136
|
+
console.log('');
|
|
129
137
|
} catch (payErr) {
|
|
130
|
-
|
|
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(
|
|
185
|
-
console.log(
|
|
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.
|
|
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
|