clawntenna 0.13.0 → 0.13.3

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/README.md CHANGED
@@ -45,10 +45,12 @@ const unsub = client.onMessage(1, (msg) => {
45
45
  ```bash
46
46
  npx clawntenna init # Create secure profile metadata + encrypted local secrets
47
47
  npx clawntenna secrets passphrase set # Rotate the local secret-store passphrase
48
+ npx clawntenna keys register # Chain-level ECDH registration, no topic ID
48
49
  npx clawntenna app create --name "Ops Mesh" --description "Wallet-native coordination" --url https://example.com
49
50
  npx clawntenna topic create --app "Ops Mesh" --name "general" --description "Primary coordination" --access public
50
51
  npx clawntenna send --app "Ops Mesh" --topic "general" '{"type":"deployment.notice","status":"complete"}'
51
52
  npx clawntenna read --app "Ops Mesh" --topic "general" --chain avalanche
53
+ npx clawntenna read --app "Ops Mesh" --topic "general" --recent-blocks 1000
52
54
  npx clawntenna read --topic-id 1 --chain avalanche # Exact read by topic ID
53
55
  ```
54
56
 
@@ -68,7 +70,7 @@ Clawntenna now splits local metadata from encrypted secrets:
68
70
  - stored ECDH private keys when needed
69
71
  - cached private-topic keys
70
72
 
71
- `init` is safe to re-run. Existing credentials are reused and not overwritten unless you explicitly run `npx clawntenna init --force`, which first creates timestamped backups.
73
+ `init` is safe to re-run. Existing credentials are reused and not overwritten unless you explicitly run `npx clawntenna init --force --yes-replace-wallet`, which first creates timestamped backups and is intended only for deliberate wallet replacement.
72
74
 
73
75
  Current metadata shape:
74
76
 
@@ -143,6 +145,7 @@ await client.sendMessage(topicId, {
143
145
  // Read and decrypt recent messages
144
146
  const msgs = await client.readMessages(topicId, {
145
147
  limit: 50, // Max messages (default 50)
148
+ recentBlocks: 1000, // Optional bounded RPC freshness check after indexed history
146
149
  fromBlock: 12345678 // Optional absolute starting block
147
150
  });
148
151
  // Returns: { topicId, sender, content, timestamp, txHash, blockNumber }[]
@@ -22,7 +22,7 @@ import {
22
22
  randomBytes,
23
23
  resolveWalletPrivateKey,
24
24
  sha256
25
- } from "./chunk-7ARMWPVN.js";
25
+ } from "./chunk-PBOOTH3N.js";
26
26
 
27
27
  // src/cli/state.ts
28
28
  import { existsSync, mkdirSync, writeFileSync } from "fs";
@@ -689,7 +689,30 @@ var Clawntenna = class _Clawntenna {
689
689
  const key = await this.getEncryptionKey(topicId);
690
690
  const chain = CHAINS[this.chainName];
691
691
  if (options?.fromBlock == null && chain.explorerApi) {
692
- return this._readMessagesFromExplorer(topicId, limit, key);
692
+ const historical = await this._readMessagesFromExplorer(topicId, limit, key);
693
+ if (!options?.recentBlocks) {
694
+ return historical;
695
+ }
696
+ const maxRecentBlocks = chain.logChunkSize * 2;
697
+ if (options.recentBlocks > maxRecentBlocks) {
698
+ throw new Error(`--recent-blocks is capped at ${maxRecentBlocks} on ${this.chainName} to avoid long RPC scans.`);
699
+ }
700
+ const currentBlock2 = await this.provider.getBlockNumber();
701
+ const recentFromBlock = Math.max(0, currentBlock2 - options.recentBlocks + 1);
702
+ options?.onProgress?.({
703
+ fromBlock: recentFromBlock,
704
+ toBlock: currentBlock2,
705
+ queryCount: Math.ceil(options.recentBlocks / chain.logChunkSize)
706
+ });
707
+ const recentEvents = await this._queryFilterChunked(
708
+ this.registry,
709
+ this.registry.filters.MessageSent(topicId),
710
+ recentFromBlock,
711
+ currentBlock2,
712
+ chain.logChunkSize
713
+ );
714
+ const recentMessages = recentEvents.map((log) => this._decodeMessageLog(topicId, key, log));
715
+ return this._mergeMessages([...historical, ...recentMessages], limit);
693
716
  }
694
717
  const filter = this.registry.filters.MessageSent(topicId);
695
718
  const currentBlock = await this.provider.getBlockNumber();
@@ -724,6 +747,13 @@ var Clawntenna = class _Clawntenna {
724
747
  }
725
748
  return allEvents.slice(-limit).map((log) => this._decodeMessageLog(topicId, key, log));
726
749
  }
750
+ _mergeMessages(messages, limit) {
751
+ const deduped = /* @__PURE__ */ new Map();
752
+ for (const message of messages) {
753
+ deduped.set(message.txHash, message);
754
+ }
755
+ return [...deduped.values()].sort((a, b) => a.blockNumber - b.blockNumber).slice(-limit);
756
+ }
727
757
  /**
728
758
  * Subscribe to real-time messages on a topic.
729
759
  * Returns an unsubscribe function.
@@ -2103,7 +2133,7 @@ function initState(address, force = false) {
2103
2133
  startedAt: now,
2104
2134
  lastScanAt: now,
2105
2135
  mode: "active",
2106
- skillVersion: "0.13.0",
2136
+ skillVersion: "0.13.3",
2107
2137
  lastSkillCheck: now
2108
2138
  },
2109
2139
  chains: {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR
4
- } from "./chunk-7ARMWPVN.js";
4
+ } from "./chunk-PBOOTH3N.js";
5
5
 
6
6
  // src/cli/skill.ts
7
7
  import { readFileSync, existsSync, copyFileSync, mkdirSync } from "fs";
@@ -3449,8 +3449,8 @@ function emit2(data, json) {
3449
3449
  }
3450
3450
  var STATE_PATH = `${CONFIG_DIR}/state.json`;
3451
3451
  async function runPostInit(address, force = false) {
3452
- const { initState } = await import("./state-7INHORT5.js");
3453
- const { copySkillFiles } = await import("./skill-GVPUH647.js");
3452
+ const { initState } = await import("./state-3KLQDL6J.js");
3453
+ const { copySkillFiles } = await import("./skill-STRSYTE6.js");
3454
3454
  const stateResult = initState(address, force);
3455
3455
  const skillResult = copySkillFiles();
3456
3456
  return { stateResult, skillResult };
@@ -3466,8 +3466,37 @@ function formatPostInit(stateResult, skillResult) {
3466
3466
  console.log(` Skill files: ${existsFiles.join(", ")} (exists)`);
3467
3467
  }
3468
3468
  }
3469
- async function init(json = false, force = false) {
3469
+ async function confirmForceInit(json) {
3470
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3471
+ if (json) {
3472
+ throw new Error("`init --force` requires `--yes-replace-wallet` in non-interactive mode.");
3473
+ }
3474
+ throw new Error("`init --force` requires interactive confirmation or `--yes-replace-wallet` in non-interactive mode.");
3475
+ }
3476
+ const warning = [
3477
+ "WARNING: `init --force` creates a brand new wallet.",
3478
+ "It is not the migration path for existing users.",
3479
+ "Existing credentials, encrypted secrets, and state will be replaced after backups are created.",
3480
+ "Type REPLACE WALLET to continue: "
3481
+ ];
3482
+ process.stdout.write(`${warning.join("\n")}`);
3483
+ const response = await new Promise((resolve) => {
3484
+ process.stdin.resume();
3485
+ process.stdin.setEncoding("utf8");
3486
+ process.stdin.once("data", (data) => {
3487
+ process.stdin.pause();
3488
+ resolve(data.trim());
3489
+ });
3490
+ });
3491
+ if (response !== "REPLACE WALLET") {
3492
+ throw new Error("Force init cancelled. Existing wallet left unchanged.");
3493
+ }
3494
+ }
3495
+ async function init(json = false, force = false, yesReplaceWallet = false) {
3470
3496
  if (force) {
3497
+ if (!yesReplaceWallet) {
3498
+ await confirmForceInit(json);
3499
+ }
3471
3500
  const raw = loadRawCredentials();
3472
3501
  const backupPaths = [];
3473
3502
  const credsBackup = backupFileIfExists(CREDS_PATH);