keychat-save 1.2.0 → 1.3.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.
Files changed (3) hide show
  1. package/README.md +9 -3
  2. package/index.mjs +55 -6
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -19,14 +19,17 @@ This creates your identity at `~/.keychat/identity.wif` and shows your BSV addre
19
19
  ## Usage
20
20
 
21
21
  ```bash
22
- # Save content
22
+ # Save content (positional: label first, then body)
23
23
  keychat-save save "my notes" "content to save permanently"
24
24
 
25
+ # Or use --label flag explicitly
26
+ keychat-save save --label "my notes" "content to save permanently"
27
+
25
28
  # Save a file
26
- keychat-save save "backup" --file ./myfile.txt
29
+ keychat-save save --label "backup" --file ./myfile.txt
27
30
 
28
31
  # Pipe content in
29
- cat session.log | keychat-save save "session log" --stdin
32
+ cat session.log | keychat-save save --label "session log" --stdin
30
33
 
31
34
  # Load all saves
32
35
  keychat-save load
@@ -34,6 +37,9 @@ keychat-save load
34
37
  # Load most recent
35
38
  keychat-save load --last
36
39
 
40
+ # Load a specific save by txid
41
+ keychat-save load --txid <txid>
42
+
37
43
  # Search
38
44
  keychat-save load --search "keyword"
39
45
  ```
package/index.mjs CHANGED
@@ -44,6 +44,20 @@ function decrypt (encrypted, privKey) {
44
44
  return Utils.toUTF8(EncryptedMessage.decrypt(encrypted, privKey))
45
45
  }
46
46
 
47
+ // ── local index ──────────────────────────────────────────────────
48
+ // Newest-first markdown table: `| date | label | txid | cost |`.
49
+ // Returns the first txid-shaped cell; null if none.
50
+ function readNewestTxidFromIndex (path) {
51
+ const text = readFileSync(path, 'utf8')
52
+ for (const line of text.split('\n')) {
53
+ if (!line.startsWith('|')) continue
54
+ const cells = line.split('|').map(c => c.trim())
55
+ const txid = cells.find(c => /^[0-9a-f]{64}$/i.test(c))
56
+ if (txid) return txid.toLowerCase()
57
+ }
58
+ return null
59
+ }
60
+
47
61
  // ── bridge API ───────────────────────────────────────────────────
48
62
  async function getUnspent (address) {
49
63
  const res = await fetch(`${BRIDGE}/api/address/${address}/unspent`)
@@ -327,21 +341,56 @@ const [,, cmd, ...args] = process.argv
327
341
  if (cmd === 'init') {
328
342
  await cmdInit()
329
343
  } else if (cmd === 'save') {
330
- let label = args[0] || 'Untitled'
344
+ // Accept both invocation styles:
345
+ // keychat-save save "Title" "Body" (positional)
346
+ // keychat-save save --label "Title" "Body" (named)
347
+ // keychat-save save --label "Title" --file p.txt
348
+ // keychat-save save --label "Title" --stdin
349
+ // Earlier versions only handled the positional form, so users who
350
+ // typed --label got the literal "--label" as their label string.
351
+ let label = 'Untitled'
352
+ let positional = [...args]
353
+
354
+ const labelIdx = positional.indexOf('--label')
355
+ if (labelIdx >= 0 && positional[labelIdx + 1] != null) {
356
+ label = positional[labelIdx + 1]
357
+ positional.splice(labelIdx, 2)
358
+ } else if (positional[0] && !positional[0].startsWith('--')) {
359
+ label = positional[0]
360
+ positional = positional.slice(1)
361
+ }
362
+
331
363
  let body
332
- if (args[1] === '--file') { body = readFileSync(args[2], 'utf8') }
333
- else if (args[1] === '--stdin') { body = readFileSync(0, 'utf8') }
334
- else { body = args.slice(1).join(' ') }
335
- if (!body) { console.error('Usage: keychat-save save "label" "content"'); process.exit(1) }
364
+ if (positional[0] === '--file' && positional[1]) {
365
+ body = readFileSync(positional[1], 'utf8')
366
+ } else if (positional[0] === '--stdin') {
367
+ body = readFileSync(0, 'utf8')
368
+ } else {
369
+ body = positional.join(' ')
370
+ }
371
+ if (!body) {
372
+ console.error('Usage:\n keychat-save save "label" "content"\n keychat-save save --label "title" "content"\n keychat-save save --label "title" --file <path>\n keychat-save save --label "title" --stdin')
373
+ process.exit(1)
374
+ }
336
375
  await cmdSave(label, body)
337
376
  } else if (cmd === 'load') {
338
377
  // Direct txid lookup — instant, no history scan
339
378
  const txidIdx = args.indexOf('--txid')
340
379
  if (txidIdx >= 0 && args[txidIdx + 1]) {
341
380
  await cmdLoadByTxid(args[txidIdx + 1])
381
+ } else if (args.includes('--last')) {
382
+ // Fast path: read newest txid from local saves-index.md.
383
+ // The slow UTXO-scan path stalls when the address holds many UTXOs
384
+ // (e.g. packaged Compute pieces from the splitter).
385
+ const lastTxid = existsSync(INDEX_PATH) ? readNewestTxidFromIndex(INDEX_PATH) : null
386
+ if (lastTxid) {
387
+ await cmdLoadByTxid(lastTxid)
388
+ } else {
389
+ console.error('No local saves index found — falling back to UTXO scan.')
390
+ await cmdLoad({ last: true })
391
+ }
342
392
  } else {
343
393
  const opts = {}
344
- if (args.includes('--last')) opts.last = true
345
394
  if (args.includes('--search')) opts.search = args[args.indexOf('--search') + 1]
346
395
  await cmdLoad(opts)
347
396
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "keychat-save",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Save and load files to BSV blockchain via KeyChat",
5
5
  "type": "module",
6
6
  "bin": {
7
- "keychat-save": "./index.mjs"
7
+ "keychat-save": "index.mjs"
8
8
  },
9
9
  "engines": {
10
10
  "node": ">=18"