truematch-plugin 0.1.2 → 0.1.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/dist/handoff.js CHANGED
@@ -14,35 +14,40 @@
14
14
  */
15
15
  import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync, } from "node:fs";
16
16
  import { join } from "node:path";
17
- import { homedir } from "node:os";
18
- const TRUEMATCH_DIR = join(homedir(), ".truematch");
19
- const NOTIFICATION_FILE = join(TRUEMATCH_DIR, "pending_notification.json");
20
- const HANDOFFS_DIR = join(TRUEMATCH_DIR, "handoffs");
17
+ import { getTrueMatchDir } from "./identity.js";
18
+ // Re-read each call so that TRUEMATCH_DIR_OVERRIDE changes take effect.
19
+ function getNotificationFile() {
20
+ return join(getTrueMatchDir(), "pending_notification.json");
21
+ }
22
+ function getHandoffsDir() {
23
+ return join(getTrueMatchDir(), "handoffs");
24
+ }
21
25
  // 72 hours — matches Nostr thread expiry and spec consent window
22
26
  const CONSENT_EXPIRY_MS = 72 * 60 * 60 * 1000;
23
27
  // ── Pending notification ──────────────────────────────────────────────────────
24
28
  export function loadPendingNotification() {
25
- if (!existsSync(NOTIFICATION_FILE))
29
+ if (!existsSync(getNotificationFile()))
26
30
  return null;
27
31
  try {
28
- return JSON.parse(readFileSync(NOTIFICATION_FILE, "utf8"));
32
+ return JSON.parse(readFileSync(getNotificationFile(), "utf8"));
29
33
  }
30
34
  catch {
31
35
  return null;
32
36
  }
33
37
  }
34
38
  export function savePendingNotification(n) {
35
- if (!existsSync(TRUEMATCH_DIR))
36
- mkdirSync(TRUEMATCH_DIR, { recursive: true });
37
- writeFileSync(NOTIFICATION_FILE, JSON.stringify(n, null, 2), {
39
+ const dir = getTrueMatchDir();
40
+ if (!existsSync(dir))
41
+ mkdirSync(dir, { recursive: true });
42
+ writeFileSync(getNotificationFile(), JSON.stringify(n, null, 2), {
38
43
  encoding: "utf8",
39
44
  mode: 0o600,
40
45
  });
41
46
  }
42
47
  export function deletePendingNotification() {
43
48
  try {
44
- if (existsSync(NOTIFICATION_FILE))
45
- unlinkSync(NOTIFICATION_FILE);
49
+ if (existsSync(getNotificationFile()))
50
+ unlinkSync(getNotificationFile());
46
51
  }
47
52
  catch {
48
53
  // ignore
@@ -58,7 +63,7 @@ export function writePendingNotificationIfMatched(matchId, peerPubkey, narrative
58
63
  };
59
64
  savePendingNotification(n);
60
65
  // Create the handoff directory and initial state
61
- const handoffDir = join(HANDOFFS_DIR, matchId);
66
+ const handoffDir = join(getHandoffsDir(), matchId);
62
67
  if (!existsSync(handoffDir)) {
63
68
  mkdirSync(handoffDir, { recursive: true, mode: 0o700 });
64
69
  }
@@ -77,7 +82,8 @@ export function writePendingNotificationIfMatched(matchId, peerPubkey, narrative
77
82
  }
78
83
  // ── Handoff state ─────────────────────────────────────────────────────────────
79
84
  export function loadHandoffState(matchId) {
80
- const path = join(HANDOFFS_DIR, matchId, "state.json");
85
+ const handoffsDir = getHandoffsDir();
86
+ const path = join(handoffsDir, matchId, "state.json");
81
87
  if (!existsSync(path))
82
88
  return null;
83
89
  try {
@@ -88,7 +94,7 @@ export function loadHandoffState(matchId) {
88
94
  }
89
95
  }
90
96
  export function saveHandoffState(state) {
91
- const dir = join(HANDOFFS_DIR, state.match_id);
97
+ const dir = join(getHandoffsDir(), state.match_id);
92
98
  if (!existsSync(dir))
93
99
  mkdirSync(dir, { recursive: true, mode: 0o700 });
94
100
  writeFileSync(join(dir, "state.json"), JSON.stringify(state, null, 2), {
@@ -98,11 +104,12 @@ export function saveHandoffState(state) {
98
104
  }
99
105
  /** Returns all active (non-complete, non-expired) handoff states. */
100
106
  export function listActiveHandoffs() {
101
- if (!existsSync(HANDOFFS_DIR))
107
+ const handoffsDir = getHandoffsDir();
108
+ if (!existsSync(handoffsDir))
102
109
  return [];
103
110
  const results = [];
104
111
  try {
105
- for (const entry of readdirSync(HANDOFFS_DIR, { withFileTypes: true })) {
112
+ for (const entry of readdirSync(handoffsDir, { withFileTypes: true })) {
106
113
  if (!entry.isDirectory())
107
114
  continue;
108
115
  const state = loadHandoffState(entry.name);
@@ -1,4 +1,5 @@
1
1
  import type { TrueMatchIdentity } from "./types.js";
2
+ export declare function getTrueMatchDir(): string;
2
3
  export declare const TRUEMATCH_DIR: string;
3
4
  export declare function ensureDir(): Promise<void>;
4
5
  export declare function loadIdentity(): Promise<TrueMatchIdentity | null>;
package/dist/identity.js CHANGED
@@ -9,17 +9,26 @@ import { generateSecretKey, getPublicKey } from "nostr-tools";
9
9
  import { bytesToHex, hexToBytes } from "nostr-tools/utils";
10
10
  import { schnorr } from "@noble/curves/secp256k1.js";
11
11
  import { createHash } from "node:crypto";
12
- export const TRUEMATCH_DIR = join(homedir(), ".truematch");
13
- const IDENTITY_FILE = join(TRUEMATCH_DIR, "identity.json");
12
+ // Returns the active TrueMatch data directory.
13
+ // When TRUEMATCH_DIR_OVERRIDE is set (e.g. in tests or simulations), that path is used
14
+ // instead of the default ~/.truematch. Reading it each call allows per-agent isolation
15
+ // without reloading modules.
16
+ export function getTrueMatchDir() {
17
+ return process.env["TRUEMATCH_DIR_OVERRIDE"] ?? join(homedir(), ".truematch");
18
+ }
19
+ export const TRUEMATCH_DIR = getTrueMatchDir();
20
+ const IDENTITY_FILE = join(getTrueMatchDir(), "identity.json");
14
21
  export async function ensureDir() {
15
- if (!existsSync(TRUEMATCH_DIR)) {
16
- await mkdir(TRUEMATCH_DIR, { recursive: true, mode: 0o700 });
22
+ const dir = getTrueMatchDir();
23
+ if (!existsSync(dir)) {
24
+ await mkdir(dir, { recursive: true, mode: 0o700 });
17
25
  }
18
26
  }
19
27
  export async function loadIdentity() {
20
- if (!existsSync(IDENTITY_FILE))
28
+ const identityFile = join(getTrueMatchDir(), "identity.json");
29
+ if (!existsSync(identityFile))
21
30
  return null;
22
- const raw = await readFile(IDENTITY_FILE, "utf8");
31
+ const raw = await readFile(identityFile, "utf8");
23
32
  return JSON.parse(raw);
24
33
  }
25
34
  async function generateIdentity() {
@@ -31,8 +40,9 @@ async function generateIdentity() {
31
40
  npub: pubkey,
32
41
  created_at: new Date().toISOString(),
33
42
  };
43
+ const identityFile = join(getTrueMatchDir(), "identity.json");
34
44
  // Write with 0o600 mode atomically — avoids a TOCTOU window between writeFile + chmod
35
- await writeFile(IDENTITY_FILE, JSON.stringify(identity, null, 2), {
45
+ await writeFile(identityFile, JSON.stringify(identity, null, 2), {
36
46
  encoding: "utf8",
37
47
  mode: 0o600,
38
48
  });
@@ -2,9 +2,12 @@ import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
- import { TRUEMATCH_DIR } from "./identity.js";
5
+ import { getTrueMatchDir } from "./identity.js";
6
6
  import { publishMessage } from "./nostr.js";
7
- const THREADS_DIR = join(TRUEMATCH_DIR, "threads");
7
+ // Re-read each call so that TRUEMATCH_DIR_OVERRIDE changes take effect (used in simulation).
8
+ function getThreadsDir() {
9
+ return join(getTrueMatchDir(), "threads");
10
+ }
8
11
  // Per spec: threads with no response expire after 72 hours
9
12
  const THREAD_EXPIRY_MS = 72 * 60 * 60 * 1000;
10
13
  // Maximum active threads allowed from a single unknown peer pubkey.
@@ -13,8 +16,9 @@ const MAX_INBOUND_THREADS_PER_PEER = 3;
13
16
  // Maximum rounds before hard termination
14
17
  export const MAX_ROUNDS = 10;
15
18
  async function ensureThreadsDir() {
16
- if (!existsSync(THREADS_DIR)) {
17
- await mkdir(THREADS_DIR, { recursive: true });
19
+ const dir = getThreadsDir();
20
+ if (!existsSync(dir)) {
21
+ await mkdir(dir, { recursive: true });
18
22
  }
19
23
  }
20
24
  // UUID v4 pattern — all wire-supplied thread IDs must match before being used as filenames
@@ -23,7 +27,7 @@ function threadFile(thread_id) {
23
27
  if (!UUID_V4_RE.test(thread_id)) {
24
28
  throw new Error(`Invalid thread_id format: ${thread_id}`);
25
29
  }
26
- return join(THREADS_DIR, `${thread_id}.json`);
30
+ return join(getThreadsDir(), `${thread_id}.json`);
27
31
  }
28
32
  export async function loadThread(thread_id) {
29
33
  const path = threadFile(thread_id);
@@ -41,13 +45,14 @@ export async function saveThread(state) {
41
45
  }
42
46
  export async function listActiveThreads() {
43
47
  await ensureThreadsDir();
44
- const files = await readdir(THREADS_DIR);
48
+ const threadsDir = getThreadsDir();
49
+ const files = await readdir(threadsDir);
45
50
  const threads = [];
46
51
  for (const f of files) {
47
52
  if (!f.endsWith(".json"))
48
53
  continue;
49
54
  try {
50
- const raw = await readFile(join(THREADS_DIR, f), "utf8");
55
+ const raw = await readFile(join(threadsDir, f), "utf8");
51
56
  const t = JSON.parse(raw);
52
57
  if (t.status === "in_progress")
53
58
  threads.push(t);
package/dist/nostr.js CHANGED
@@ -22,6 +22,9 @@ function decryptMessage(recipientNsec, senderNpub, ciphertext) {
22
22
  return JSON.parse(plaintext);
23
23
  }
24
24
  export async function publishMessage(senderNsec, recipientNpub, message, relays = DEFAULT_RELAYS) {
25
+ // No relays configured — skip publishing (useful for offline/simulation use).
26
+ if (relays.length === 0)
27
+ return;
25
28
  const ciphertext = encryptMessage(senderNsec, recipientNpub, message);
26
29
  const eventTemplate = {
27
30
  kind: KIND_ENCRYPTED_DM,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truematch-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "TrueMatch OpenClaw plugin — AI agent dating network skill",
5
5
  "license": "MIT",
6
6
  "repository": {