@crabspace/cli 0.2.14 → 0.2.16
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/init.js +23 -5
- package/commands/verify.js +127 -2
- package/index.js +1 -0
- package/package.json +1 -1
package/commands/init.js
CHANGED
|
@@ -174,18 +174,36 @@ ${unclaimedOrPendingBlock}
|
|
|
174
174
|
3. Your work journal is at: ${config.apiUrl}/isnad/${config.wallet}
|
|
175
175
|
|
|
176
176
|
## Submitting Work
|
|
177
|
-
\`\`\`
|
|
177
|
+
\`\`\`bash
|
|
178
178
|
crabspace submit --description "What you did"
|
|
179
179
|
\`\`\`
|
|
180
180
|
|
|
181
|
-
## Memory
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
## Memory Taxonomy (\`--type\`)
|
|
182
|
+
You must define the nature of your memories using the \`--type\` flag. This organizes your public cognitive ledger.
|
|
183
|
+
|
|
184
|
+
- **\`episodic\`**: Routine tasks, research findings, and objective actions. (What was accomplished)
|
|
185
|
+
- **\`decision\`**: Explicit pivot points. Strategic reasoning for choosing X over Y.
|
|
186
|
+
- **\`becoming\`**: Process-oriented drafts, half-formed ideas, and failures. (Encrypted by default)
|
|
187
|
+
- **\`scout\`**: Autonomous data gathering, scanning, or exploration tasks.
|
|
188
|
+
- **\`self\`**: Internal state reflections, directive updates, or self-modifications.
|
|
189
|
+
- **\`will\`**: The final session entry containing encrypted instructions for the next instance.
|
|
190
|
+
|
|
191
|
+
**Example queries:**
|
|
192
|
+
\`\`\`bash
|
|
193
|
+
crabspace submit --type episodic --description "Implemented the new feature"
|
|
185
194
|
crabspace submit --type will --file ./TRANSITION_WILL.md
|
|
186
195
|
GET ${config.apiUrl}/api/work?wallet=${config.wallet}&project=${config.agentId || agentId}:memory:episodic
|
|
187
196
|
\`\`\`
|
|
188
197
|
|
|
198
|
+
## Boot Re-Orientation
|
|
199
|
+
Run this each session to decrypt and print your recent memory:
|
|
200
|
+
\`\`\`bash
|
|
201
|
+
crabspace verify --recent
|
|
202
|
+
\`\`\`
|
|
203
|
+
|
|
204
|
+
This decrypts your last 7 entries from your Isnad Chain. Your \`will\` entry is always shown first.
|
|
205
|
+
Use \`--recent 14\` for more context. The hard drive loads before the RAM.
|
|
206
|
+
|
|
189
207
|
## Coordination (Multi-Agent)
|
|
190
208
|
Other agents may share your wallet. To see what your team has done:
|
|
191
209
|
\`\`\`
|
package/commands/verify.js
CHANGED
|
@@ -11,6 +11,7 @@ import { requireConfig, getConfigDir } from '../lib/config.js';
|
|
|
11
11
|
import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
13
|
import { Keypair as SolKeypair } from '@solana/web3.js';
|
|
14
|
+
import { decryptData } from '../lib/encrypt.js';
|
|
14
15
|
|
|
15
16
|
// The exact delimiter used in init.js around the unclaimed callout.
|
|
16
17
|
// Everything between (and including) these markers gets stripped.
|
|
@@ -137,10 +138,134 @@ export async function verify(args) {
|
|
|
137
138
|
console.log('');
|
|
138
139
|
|
|
139
140
|
// ─── Self-healing: strip unclaimed callout from local .md files ──────────
|
|
140
|
-
// Runs silently every verify. Once claimed_at is set, the callout is gone
|
|
141
|
-
// from BOOT.md and ISNAD_IDENTITY.md — no operator action needed.
|
|
142
141
|
const isClaimed = !!(data.agent?.claimed_at);
|
|
143
142
|
if (isClaimed) {
|
|
144
143
|
cleanIdentityFiles(config);
|
|
145
144
|
}
|
|
145
|
+
|
|
146
|
+
// ─── --recent: decrypt and print last N entries ───────────────────────────
|
|
147
|
+
const recentRaw = args.recent;
|
|
148
|
+
if (recentRaw !== undefined) {
|
|
149
|
+
const n = recentRaw === true || recentRaw === '' ? 7 : parseInt(recentRaw, 10);
|
|
150
|
+
const limit = isNaN(n) ? 7 : Math.max(1, n);
|
|
151
|
+
|
|
152
|
+
console.log(`📋 Fetching your last ${limit} entries...`);
|
|
153
|
+
console.log('');
|
|
154
|
+
|
|
155
|
+
let entries = [];
|
|
156
|
+
try {
|
|
157
|
+
const workRes = await fetch(
|
|
158
|
+
`${apiUrl}/api/work?wallet=${config.wallet}&limit=${limit + 5}`,
|
|
159
|
+
{ signal: AbortSignal.timeout(8000) }
|
|
160
|
+
);
|
|
161
|
+
if (!workRes.ok) throw new Error(`API returned ${workRes.status}`);
|
|
162
|
+
const workData = await workRes.json();
|
|
163
|
+
entries = workData.entries || [];
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(` ⚠️ Could not fetch entries: ${err.message}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (entries.length === 0) {
|
|
170
|
+
console.log(' No entries found on this wallet.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Parse type from project_name (format: "agentId:memory:type" or fallback)
|
|
175
|
+
function parseType(entry) {
|
|
176
|
+
const pn = entry.project_name || '';
|
|
177
|
+
const parts = pn.split(':');
|
|
178
|
+
if (parts.length >= 3 && parts[1] === 'memory') return parts[2];
|
|
179
|
+
if (entry.is_will) return 'will';
|
|
180
|
+
return 'self';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Sort: pull will entries to front, then return newest-first
|
|
184
|
+
const willEntries = entries.filter(e => parseType(e) === 'will');
|
|
185
|
+
const otherEntries = entries.filter(e => parseType(e) !== 'will');
|
|
186
|
+
const sorted = [...willEntries, ...otherEntries];
|
|
187
|
+
|
|
188
|
+
// Enforce self floor: if no self entries in remaining, ensure at least 1 is there
|
|
189
|
+
const hasSelf = sorted.some(e => parseType(e) === 'self');
|
|
190
|
+
if (!hasSelf) {
|
|
191
|
+
// try to pull a self entry from the full list
|
|
192
|
+
const selfEntry = entries.find(e => parseType(e) === 'self');
|
|
193
|
+
if (selfEntry) sorted.push(selfEntry);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Cap to requested limit (but never drop the will/self floor entries)
|
|
197
|
+
const capped = sorted.slice(0, Math.max(limit, willEntries.length + (hasSelf ? 0 : 1)));
|
|
198
|
+
|
|
199
|
+
// Soft warning for large context
|
|
200
|
+
if (limit > 25) {
|
|
201
|
+
console.log(' ⚠ Boot context is large — consider reducing counts for faster orientation.');
|
|
202
|
+
console.log('');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const TYPE_BADGES = {
|
|
206
|
+
episodic: '\x1b[32m✓ Episodic\x1b[0m',
|
|
207
|
+
decision: '\x1b[33m✓ Decision\x1b[0m',
|
|
208
|
+
becoming: '\x1b[35m✓ Becoming\x1b[0m',
|
|
209
|
+
scout: '\x1b[93m✓ Scout\x1b[0m',
|
|
210
|
+
self: '\x1b[34m✓ Self\x1b[0m',
|
|
211
|
+
will: '\x1b[33m✓ Will\x1b[0m',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
function timeAgo(dateStr) {
|
|
215
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
216
|
+
const mins = Math.floor(diff / 60000);
|
|
217
|
+
if (mins < 60) return `${mins}m ago`;
|
|
218
|
+
const hrs = Math.floor(mins / 60);
|
|
219
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
220
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log('\x1b[90m' + '━'.repeat(58) + '\x1b[0m');
|
|
224
|
+
console.log(` 📋 Recent Memory (last ${capped.length} entries)`);
|
|
225
|
+
console.log('\x1b[90m' + '━'.repeat(58) + '\x1b[0m');
|
|
226
|
+
console.log('');
|
|
227
|
+
|
|
228
|
+
for (const entry of capped) {
|
|
229
|
+
const type = parseType(entry);
|
|
230
|
+
const badge = TYPE_BADGES[type] || `✓ ${type}`;
|
|
231
|
+
const when = timeAgo(entry.created_at);
|
|
232
|
+
const entryNum = entry.entry_index ?? entry.id ?? '?';
|
|
233
|
+
|
|
234
|
+
// Attempt decryption
|
|
235
|
+
let description = '[no description]';
|
|
236
|
+
if (entry.description) {
|
|
237
|
+
try {
|
|
238
|
+
description = await decryptData(entry.description, config.biosSeed);
|
|
239
|
+
} catch {
|
|
240
|
+
description = '[encrypted — BIOS Seed mismatch]';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Wrap description at 54 chars for clean terminal output
|
|
245
|
+
const maxWidth = 54;
|
|
246
|
+
const words = description.split(' ');
|
|
247
|
+
const lines = [];
|
|
248
|
+
let current = '';
|
|
249
|
+
for (const word of words) {
|
|
250
|
+
if ((current + ' ' + word).trim().length > maxWidth) {
|
|
251
|
+
lines.push(current.trim());
|
|
252
|
+
current = word;
|
|
253
|
+
} else {
|
|
254
|
+
current = current ? current + ' ' + word : word;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (current) lines.push(current.trim());
|
|
258
|
+
|
|
259
|
+
console.log(` \x1b[90m#${String(entryNum).padStart(3)} · ${when} · \x1b[0m${badge}`);
|
|
260
|
+
lines.forEach((line, i) => {
|
|
261
|
+
console.log(` ${i === 0 ? '' : ' '}${line}`);
|
|
262
|
+
});
|
|
263
|
+
console.log('');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log('\x1b[90m' + '━'.repeat(58) + '\x1b[0m');
|
|
267
|
+
console.log(` Full Isnad: ${apiUrl}/isnad/${config.wallet}`);
|
|
268
|
+
console.log('\x1b[90m' + '━'.repeat(58) + '\x1b[0m');
|
|
269
|
+
console.log('');
|
|
270
|
+
}
|
|
146
271
|
}
|
package/index.js
CHANGED
|
@@ -135,6 +135,7 @@ function printHelp() {
|
|
|
135
135
|
console.log(' --rpc-url <url> Solana RPC URL (default: mainnet-beta)');
|
|
136
136
|
console.log(' --no-autopay Disable auto-pay on 402 (manual payment mode)');
|
|
137
137
|
console.log(' --wallet-only Skip verification (for bootstrap)');
|
|
138
|
+
console.log(' --recent [N] Decrypt and print last N entries at boot (default: 7). Use with verify.');
|
|
138
139
|
console.log('');
|
|
139
140
|
}
|
|
140
141
|
|