@trygocode/notify 0.6.6 → 0.6.7

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
@@ -530,6 +530,20 @@ npm publish, real-device E2E), see
530
530
 
531
531
  ## Changelog
532
532
 
533
+ ### 0.6.7
534
+
535
+ - **Notification project name now matches your IDE.** The per-turn completion
536
+ ping labels the notification with your **workspace folder name** (e.g.
537
+ `Cursor · OpenHandApp — Agent finished`) instead of the git `owner/repo`.
538
+ - **Fixed the `.cursor` project label.** When a stop hook ran without a piped
539
+ workspace path, the label derived from the hook's cwd (`~/.cursor`) and
540
+ rendered as “Cursor · .cursor” (the IDE name twice). `projectLabel` now walks
541
+ up past any leading-dot tooling dir (`.cursor`, `.git`, `.vscode`, …) and
542
+ stops at the home dir, so it never surfaces a tooling/home folder as the
543
+ project. When no real workspace folder is derivable it falls back to the git
544
+ repo name, and if even that is absent it shows the runtime alone
545
+ (`Cursor — Agent finished`) with no confusing suffix.
546
+
533
547
  ### 0.6.6
534
548
 
535
549
  - **Claude questions are now classified by notification type.** Setup installs
@@ -67,10 +67,9 @@ export const CLAUDE_HOOK_COMMANDS = {
67
67
  Notification: 'npx -y @trygocode/notify@latest on-notification --source claude_code --dedupe-key "$CLAUDE_SESSION_ID-notify" --quiet || true',
68
68
  };
69
69
  /**
70
- * Claude `Notification` types that genuinely require the user's attention.
71
- * Keep this matcher in lockstep with `CLAUDE_INPUT_NOTIFICATION_TYPES` in
72
- * `cli.ts`, which provides a defensive payload check for existing 0.6.5
73
- * installs whose matcher is still empty until setup is rerun.
70
+ * Claude `Notification` types that genuinely require the user's attention. The
71
+ * dispatcher independently applies the same values as a defensive payload check
72
+ * for existing 0.6.5 installs whose matcher stays empty until setup reruns.
74
73
  *
75
74
  * Deliberately excluded: `auth_success`, `elicitation_complete`,
76
75
  * `elicitation_response`, and `agent_completed`.
@@ -26,6 +26,7 @@
26
26
  //
27
27
  // Zero runtime deps — Node built-ins only, matching the package's zero-dep rule.
28
28
  import path from "node:path";
29
+ import os from "node:os";
29
30
  import { promises as fs } from "node:fs";
30
31
  import { createHash } from "node:crypto";
31
32
  import { resolveNotifySettings } from "./config.js";
@@ -223,42 +224,57 @@ function toPushSettings(settings) {
223
224
  };
224
225
  }
225
226
  /**
226
- * Resolve the `project` label for a per-turn ping (T-N4 / PRD §3.4). Prefers the
227
- * derived repo identity's `repo_label`; when that is blank — the repo-derive
228
- * threw (so `repo` is undefined) or a non-git cwd produced an empty label — falls
229
- * back to the cwd basename so the phone ALWAYS shows SOMETHING ("better-than-
230
- * nothing"). Returns `undefined` only when even the basename is empty (e.g. cwd
231
- * is the filesystem root), so the caller still omits the field gracefully.
227
+ * Resolve the `project` label for a per-turn ping (T-N4 / PRD §3.4).
228
+ *
229
+ * Preference order (user decision 2026-07-27):
230
+ * 1. The WORKSPACE FOLDER NAME the cwd basename (e.g. "OpenHandApp"), which
231
+ * matches what the user sees in the IDE title bar / file explorer. We walk
232
+ * up past any leading-dot tooling dir (`.cursor`, `.git`, `.vscode`, …) so a
233
+ * hook that runs inside a config dir still names the real project folder,
234
+ * never "Cursor · .cursor". We also reject the user's HOME dir as a label
235
+ * (it is never a meaningful project name).
236
+ * 2. The git REPO NAME — the last segment of `repo_label` (`owner/repo` →
237
+ * `repo`), used only when the folder walk yields nothing usable. Also
238
+ * dot-guarded (a `.cursor` repo label must never surface).
239
+ * 3. `undefined` — no project word at all, so the caller renders the runtime
240
+ * alone ("Cursor — Agent finished"). This is the intentional fallback when
241
+ * there is genuinely no workspace/repo signal (a rare IDE edge case); it is
242
+ * strictly better than showing a confusing dir name.
232
243
  */
233
244
  export function projectLabel(repo, cwd) {
234
- const label = repo?.repo_label?.trim();
235
- // The user wants the plain project/folder name (e.g. "alerc8"), not the
236
- // `owner/repo` form. `repo_label` from a git origin is `owner/repo`, and the
237
- // `local:` fallback already strips the prefix in deriveRepoIdentity so take
238
- // the LAST path segment as the display name. Guard against a trailing slash.
239
- if (label) {
240
- const lastSeg = label.replace(/\/+$/, "").split("/").pop()?.trim();
241
- if (lastSeg)
242
- return lastSeg;
243
- return label;
244
- }
245
- // Fall back to the cwd basename — but SKIP tool/config dot-dirs. The stop hook
246
- // can run with a cwd inside `.cursor` (or `.git`, `.vscode`, …), whose basename
247
- // would otherwise become the project name and render as "Cursor · .cursor"
248
- // (the IDE name shown twice). Walk up past any leading-dot segment to the first
249
- // real project folder so the label is the actual repo dir, not its tooling dir.
245
+ const home = path.resolve(os.homedir());
246
+ // Directories that are never a meaningful project name, even if we land on
247
+ // them while walking up: the home dir itself and its parent chain (`/Users`,
248
+ // `/home`, filesystem root). Reaching any of these means "no project folder".
249
+ const isNonProjectDir = (d) => d === home || home === d + path.sep || home.startsWith(d + path.sep) || path.dirname(d) === d;
250
+ // (1) Prefer the workspace FOLDER name — walk up past leading-dot tooling dirs
251
+ // (`.cursor`, `.git`, `.vscode`, ) to the first REAL project folder. We STOP
252
+ // if we reach the home dir or any ancestor of it (so a hook run with cwd =
253
+ // `~/.cursor` and no workspace path bails to runtime-only rather than climbing
254
+ // into "Users"/"home"/root).
250
255
  let dir = path.resolve(cwd);
251
- for (let i = 0; i < 6; i++) {
256
+ for (let i = 0; i < 8; i++) {
257
+ if (isNonProjectDir(dir))
258
+ break; // home / its ancestors / root → no folder label
252
259
  const base = path.basename(dir).trim();
253
- if (!base)
254
- break; // reached filesystem root
255
- if (!base.startsWith("."))
256
- return base; // first non-dot folder wins
260
+ if (base && !base.startsWith(".")) {
261
+ return base; // first real (non-dot) project folder wins
262
+ }
257
263
  const parent = path.dirname(dir);
258
264
  if (parent === dir)
259
265
  break; // no more parents
260
266
  dir = parent;
261
267
  }
268
+ // (2) Fall back to the git REPO name (last segment of `owner/repo`), dot-guarded.
269
+ const label = repo?.repo_label?.trim();
270
+ if (label) {
271
+ const lastSeg = label.replace(/\/+$/, "").split("/").pop()?.trim();
272
+ if (lastSeg && !lastSeg.startsWith("."))
273
+ return lastSeg;
274
+ if (!lastSeg && !label.startsWith("."))
275
+ return label;
276
+ }
277
+ // (3) No usable signal → runtime-only (undefined omits the project word).
262
278
  return undefined;
263
279
  }
264
280
  /**
@@ -1,2 +1,2 @@
1
1
  // Single source of truth for the CLI version. Keep in sync with package.json.
2
- export const VERSION = "0.6.6";
2
+ export const VERSION = "0.6.7";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trygocode/notify",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Free phone + branded desktop notifications for any coding agent (Cursor, Claude Code, OpenCode, Ralph/Homer) via the GoCode app.",
5
5
  "license": "MIT",
6
6
  "type": "module",