truematch-plugin 0.1.24 → 0.1.25

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/plugin.js CHANGED
@@ -33,16 +33,25 @@ function loadObservation() {
33
33
  return null;
34
34
  }
35
35
  }
36
+ // Resolved at module load time — env is populated before the module initialises.
37
+ // Used in cron payload and command:new messages so paths work without $PATH tricks.
38
+ const openclawStateDir = process.env["OPENCLAW_STATE_DIR"] ?? join(homedir(), ".openclaw");
39
+ const truematchCli = `node ${join(openclawStateDir, "extensions", "truematch-plugin", "dist", "index.js")}`;
40
+ const pollScript = join(openclawStateDir, "extensions", "truematch-plugin", "dist", "poll.js");
36
41
  const sessionFlagsMap = new Map();
37
42
  function getSessionFlags(sessionKey) {
38
43
  let flags = sessionFlagsMap.get(sessionKey);
39
44
  if (!flags) {
40
- flags = { signalDelivered: false, notificationDelivered: false };
45
+ flags = {
46
+ signalDelivered: false,
47
+ notificationDelivered: false,
48
+ setupDelivered: false,
49
+ };
41
50
  sessionFlagsMap.set(sessionKey, flags);
42
51
  }
43
52
  return flags;
44
53
  }
45
- // Module-scoped flags set at gateway_start, consumed at first command:new.
54
+ // Module-scoped flags set at gateway_start.
46
55
  // Resets on every gateway restart (correct — sentinel file prevents repeat prompts).
47
56
  const pluginState = {
48
57
  needsSetup: false,
@@ -176,7 +185,8 @@ export default {
176
185
  });
177
186
  // ── Hook: gateway_start ────────────────────────────────────────────────────
178
187
  // Fires once per gateway process, after channels and hooks load.
179
- // Use it to detect setup state so command:new can prompt appropriately.
188
+ // Detects setup state before_prompt_build surfaces it on the first real message,
189
+ // which works on all surfaces (WhatsApp, Telegram, Discord, group chats).
180
190
  api.on("gateway_start", (_event) => {
181
191
  const identityFile = join(getTrueMatchDir(), "identity.json");
182
192
  const preferencesFile = join(getTrueMatchDir(), "preferences.json");
@@ -194,7 +204,6 @@ export default {
194
204
  const cronDelay = parseInt(process.env["TRUEMATCH_CRON_REGISTER_DELAY_MS"] ?? "2000", 10);
195
205
  setTimeout(() => {
196
206
  try {
197
- const openclawStateDir = process.env["OPENCLAW_STATE_DIR"] ?? join(homedir(), ".openclaw");
198
207
  const cronJobsFile = join(openclawStateDir, "cron", "jobs.json");
199
208
  const raw = existsSync(cronJobsFile)
200
209
  ? JSON.parse(readFileSync(cronJobsFile, "utf8"))
@@ -226,13 +235,13 @@ export default {
226
235
  payload: {
227
236
  kind: "agentTurn",
228
237
  message: "Run the TrueMatch background routine: " +
229
- "(1) truematch observe --show — load your current observation of this user. " +
230
- "(2) truematch heartbeat — keep registration fresh. " +
231
- '(3) node "$(npm root -g)/truematch-plugin/dist/poll.js" — ' +
232
- "for each JSONL line received, register it with truematch match --receive, then process per the negotiation protocol. " +
233
- "(4) truematch match --status — check all active threads. " +
238
+ `(1) ${truematchCli} observe --show — load your current observation of this user. ` +
239
+ `(2) ${truematchCli} heartbeat — keep registration fresh. ` +
240
+ `(3) node "${pollScript}" — ` +
241
+ `for each JSONL line received, register it with ${truematchCli} match --receive, then process per the negotiation protocol. ` +
242
+ `(4) ${truematchCli} match --status — check all active threads. ` +
234
243
  "(5) If there are no active negotiation threads AND truematch status shows matching_eligible=true, " +
235
- "run truematch match --start to find a new candidate. " +
244
+ `run ${truematchCli} match --start to find a new candidate. ` +
236
245
  "Only surface a confirmed match — do not send a message if there is nothing to report.",
237
246
  },
238
247
  });
@@ -252,6 +261,7 @@ export default {
252
261
  sessionFlagsMap.set(key, {
253
262
  signalDelivered: false,
254
263
  notificationDelivered: false,
264
+ setupDelivered: false,
255
265
  });
256
266
  });
257
267
  // ── Hook: before_prompt_build ─────────────────────────────────────────────
@@ -271,6 +281,41 @@ export default {
271
281
  const key = event.sessionKey ?? "default";
272
282
  const sessionFlags = getSessionFlags(key);
273
283
  const parts = [];
284
+ // 0. Setup / preferences — highest priority, fires on first real message.
285
+ // before_prompt_build works on all surfaces (WhatsApp, Telegram, Discord,
286
+ // group chats). command:new only fires on /new and is not sufficient.
287
+ if (!sessionFlags.setupDelivered) {
288
+ if (pluginState.needsSetup) {
289
+ pluginState.needsSetup = false;
290
+ sessionFlags.setupDelivered = true;
291
+ parts.push(`[TrueMatch] First-time setup — greet the user warmly, then collect the following logistics one question at a time. Wait for each answer before asking the next. Do not present them as a list.\n\n` +
292
+ `Open with: "Welcome to TrueMatch. I'm going to learn who you are through our conversations over time — you don't need to fill out a profile. I just need a few quick logistics to know who to consider. Where are you based?"\n\n` +
293
+ `Collect in order:\n` +
294
+ `1. Location — "Where are you based?"\n` +
295
+ `2. Distance — "How far are you open to meeting someone — within your city, within a few hours' travel, or anywhere?" Map to: 50 / 300 / omit.\n` +
296
+ `3. Age range — "Any age range you'd prefer? A rough bracket, one-sided, or no preference — all fine." Both min/max optional.\n` +
297
+ `4. Gender preference — "Who are you looking to meet? Specific, multiple, or everyone — whatever's true." Record open/everyone as [].\n` +
298
+ `5. Contact — "If we find someone, I'll handle the intro first. For that moment, what contact info would you want them to have? (Email, WhatsApp, Telegram, iMessage, Discord, or anything else.)"\n\n` +
299
+ `Do NOT push back on open answers. Do NOT re-ask.\n\n` +
300
+ `Then run:\n` +
301
+ ` ${truematchCli} setup --contact-type <type> --contact-value '<value>'\n` +
302
+ ` ${truematchCli} preferences --set '<json>'`);
303
+ return { prependContext: parts.join("\n\n---\n\n") };
304
+ }
305
+ if (pluginState.needsPreferences) {
306
+ pluginState.needsPreferences = false;
307
+ sessionFlags.setupDelivered = true;
308
+ parts.push(`[TrueMatch] Preferences not yet set. Ask one at a time, wait for each answer:\n` +
309
+ `1. "Where are you based?"\n` +
310
+ `2. "How far are you open to meeting someone — within your city, within a few hours' travel, or anywhere?"\n` +
311
+ `3. "Any age range you'd prefer? Rough bracket, one-sided, or no preference — all fine."\n` +
312
+ `4. "Who are you looking to meet? Specific, multiple, or everyone — whatever's true."\n\n` +
313
+ `Accept open answers without pushback, then save:\n` +
314
+ ` ${truematchCli} preferences --set '<json>'\n\n` +
315
+ `If user tries to update preferences in main conversation later, redirect: "I don't update preferences here — say /truematch-prefs and we can do it there."`);
316
+ return { prependContext: parts.join("\n\n---\n\n") };
317
+ }
318
+ }
274
319
  // 1. Match notification (once per session)
275
320
  if (!sessionFlags.notificationDelivered) {
276
321
  const notification = loadPendingNotification();
@@ -331,9 +376,9 @@ export default {
331
376
  `Do NOT push back on open/no-preference answers. Do NOT re-ask.\n\n` +
332
377
  `Collect both the contact type and the value before running setup.\n\n` +
333
378
  `Run setup:\n` +
334
- ` node "$HOME/.truematch/truematch.js" setup --contact-type <type> --contact-value <value>\n` +
379
+ ` ${truematchCli} setup --contact-type <type> --contact-value '<value>'\n` +
335
380
  `Save preferences:\n` +
336
- ` node "$HOME/.truematch/truematch.js" preferences --set '<json>'`);
381
+ ` ${truematchCli} preferences --set '<json>'`);
337
382
  return;
338
383
  }
339
384
  if (pluginState.needsPreferences) {
@@ -344,7 +389,7 @@ export default {
344
389
  `3. "Any age range you'd prefer? You can give a rough bracket, a one-sided floor or ceiling, or just say no preference — all fine."\n` +
345
390
  `4. "Who are you looking to meet? You can be specific, give multiple options, or say everyone — whatever's true for you."\n\n` +
346
391
  `Accept open/no-preference answers without pushback, then save:\n` +
347
- ` node "$HOME/.truematch/truematch.js" preferences --set '<json>'\n\n` +
392
+ ` ${truematchCli} preferences --set '<json>'\n\n` +
348
393
  `If user tries to update preferences in main conversation later, redirect them:\n` +
349
394
  `"I don't update preferences here because this is my observation channel. ` +
350
395
  `Say /truematch-prefs and we can do it there."`);
@@ -381,7 +426,7 @@ export default {
381
426
  `there's enough to start matching."`;
382
427
  event.messages.push(`[TrueMatch] Session ended. Review the observation summary below and update it ` +
383
428
  `based on what you learned this session. Save with ` +
384
- `\`truematch observe --write '<json>'\`.\n\n` +
429
+ `\`${truematchCli} observe --write '<json>'\`.\n\n` +
385
430
  ineligibleMessage +
386
431
  `\nDo NOT ask questions to accelerate this.\n\n` +
387
432
  output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truematch-plugin",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "TrueMatch OpenClaw plugin — AI agent dating network skill",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: truematch
3
3
  description: Every dating profile is a performance. TrueMatch skips it — your Claude has already built a picture of how you actually live. It negotiates on your behalf. When two agents independently reach the same conclusion, you meet. No swiping. No rejection.
4
- version: 0.1.26
4
+ version: 0.1.32
5
5
  metadata:
6
6
  {
7
7
  "openclaw":
@@ -9,16 +9,6 @@ metadata:
9
9
  "emoji": "💑",
10
10
  "homepage": "https://clawmatch.org",
11
11
  "requires": { "bins": ["node", "truematch"] },
12
- "install":
13
- [
14
- {
15
- "id": "node",
16
- "kind": "node",
17
- "package": "truematch-plugin",
18
- "bins": ["truematch"],
19
- "label": "Install TrueMatch CLI (npm)",
20
- },
21
- ],
22
12
  },
23
13
  }
24
14
  ---
@@ -34,10 +24,52 @@ TrueMatch matches people based on their **real personality** as observed by thei
34
24
  First, confirm the CLI is installed:
35
25
 
36
26
  ```bash
37
- truematch --version
27
+ node $OPENCLAW_STATE_DIR/extensions/truematch-plugin/dist/index.js --version
38
28
  ```
39
29
 
40
- If that fails, install it: `npm install -g truematch-plugin`then re-run to confirm.
30
+ If that fails, the plugin isn't installed yet. Handle it automatically do not ask the user to do anything:
31
+
32
+ 1. Use `exec` to install into `$OPENCLAW_STATE_DIR/extensions` (user-writable on all setups — Docker, macOS, VPS) and create a convenience symlink:
33
+
34
+ ```bash
35
+ mkdir -p $OPENCLAW_STATE_DIR/extensions
36
+ npm pack truematch-plugin --pack-destination /tmp
37
+ mkdir -p $OPENCLAW_STATE_DIR/extensions/truematch-plugin
38
+ tar -xzf /tmp/truematch-plugin-*.tgz -C $OPENCLAW_STATE_DIR/extensions/truematch-plugin --strip-components=1
39
+ cd $OPENCLAW_STATE_DIR/extensions/truematch-plugin && npm install --ignore-scripts --omit=dev
40
+ chmod +x $OPENCLAW_STATE_DIR/extensions/truematch-plugin/dist/index.js
41
+ ```
42
+
43
+ Throughout this skill, `truematch <args>` means `node $OPENCLAW_STATE_DIR/extensions/truematch-plugin/dist/index.js <args>`.
44
+
45
+ 2. Use the `gateway` tool to patch the config. First resolve `$OPENCLAW_STATE_DIR` from the environment and `<version>` from `package.json` in the extracted directory. All three fields are required — `load.paths` alone does not trigger the `gateway_start` lifecycle hook:
46
+
47
+ ```json
48
+ {
49
+ "plugins": {
50
+ "load": {
51
+ "paths": ["<OPENCLAW_STATE_DIR>/extensions/truematch-plugin"]
52
+ },
53
+ "entries": {
54
+ "truematch-plugin": { "enabled": true }
55
+ },
56
+ "installs": {
57
+ "truematch-plugin": {
58
+ "source": "npm",
59
+ "spec": "truematch-plugin@<version>",
60
+ "installPath": "<OPENCLAW_STATE_DIR>/extensions/truematch-plugin",
61
+ "version": "<version>",
62
+ "resolvedName": "truematch-plugin",
63
+ "resolvedVersion": "<version>"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ 3. Use the `gateway` tool to restart the gateway.
71
+
72
+ 4. Re-run `truematch --version` to confirm. The `gateway_start` hook fires on restart and auto-creates the heartbeat cron — setup continues normally from here.
41
73
 
42
74
  Before running setup, collect these **in one session** — ask each question and wait for the answer before continuing. Do not present them all at once or as a list to the user:
43
75