@getrift/rift 0.1.0-beta.19 → 0.1.0-beta.20

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 (30) hide show
  1. package/dist/src/cli/commands/status.d.ts.map +1 -1
  2. package/dist/src/cli/commands/status.js +38 -2
  3. package/dist/src/cli/commands/status.js.map +1 -1
  4. package/dist/src/cli/commands/update.d.ts +56 -5
  5. package/dist/src/cli/commands/update.d.ts.map +1 -1
  6. package/dist/src/cli/commands/update.js +250 -21
  7. package/dist/src/cli/commands/update.js.map +1 -1
  8. package/dist/src/cli/http-client.d.ts +25 -1
  9. package/dist/src/cli/http-client.d.ts.map +1 -1
  10. package/dist/src/cli/http-client.js +82 -0
  11. package/dist/src/cli/http-client.js.map +1 -1
  12. package/dist/src/cli/status/friend-header.d.ts.map +1 -1
  13. package/dist/src/cli/status/friend-header.js +3 -0
  14. package/dist/src/cli/status/friend-header.js.map +1 -1
  15. package/dist/src/diagnostics/codex-preflight.d.ts +1 -1
  16. package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -1
  17. package/dist/src/diagnostics/codex-preflight.js +12 -0
  18. package/dist/src/diagnostics/codex-preflight.js.map +1 -1
  19. package/dist/src/diagnostics/doctor.d.ts.map +1 -1
  20. package/dist/src/diagnostics/doctor.js +2 -0
  21. package/dist/src/diagnostics/doctor.js.map +1 -1
  22. package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
  23. package/dist/src/observability/tool-usage-stats.js +14 -1
  24. package/dist/src/observability/tool-usage-stats.js.map +1 -1
  25. package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
  26. package/dist/src/providers/codex-cli-runner.js +43 -4
  27. package/dist/src/providers/codex-cli-runner.js.map +1 -1
  28. package/operator/swiftbar/render-menu.py +61 -18
  29. package/operator/swiftbar/rift.10s.sh +52 -28
  30. package/package.json +1 -1
@@ -14,7 +14,7 @@ deliberately absent.
14
14
  Inputs (all via env, all optional — missing inputs degrade gracefully):
15
15
  RIFT_DOCTOR_JSON `rift doctor --json` output (the verdict + one fix).
16
16
  RIFT_STATUS_JSON `rift status --json` output (capture times, clients).
17
- RIFT_MCP_JSON `/stats/mcp-usage` body (memory-today delight metric).
17
+ RIFT_MCP_JSON `/stats/mcp-usage` body (Memory stats: today/month/all).
18
18
  RIFT_HEALTH "up" | "down" — coarse /health probe, used only when
19
19
  the doctor report is unavailable (CLI not resolvable).
20
20
  RIFT_NODE_BIN absolute node binary, for menu actions.
@@ -121,21 +121,62 @@ def fmt_tokens(n):
121
121
  return f"~{n} context tokens"
122
122
 
123
123
 
124
- def memory_today(mcp):
125
- """Friendly 'memory today' row, or None when not available."""
126
- if not isinstance(mcp, dict):
127
- return None
128
- today = mcp.get("today")
129
- if not isinstance(today, dict):
130
- return None
131
- parts = []
132
- hits = today.get("context_hits")
133
- if isinstance(hits, int):
134
- parts.append(f"{hits} useful recall" + ("" if hits == 1 else "s"))
135
- tokens = today.get("context_tokens_delivered_estimate")
136
- if isinstance(tokens, int):
124
+ # Friendly labels for the three Memory windows, mapped to their keys in the
125
+ # /stats/mcp-usage payload. Order is the render order. We deliberately skip
126
+ # `week` three periods stay glanceable; a fourth turns the menu into a
127
+ # dashboard. A period absent from the payload is simply not rendered (we never
128
+ # fabricate a number the backend didn't report), so an older daemon that only
129
+ # emits `today` still renders cleanly.
130
+ MEMORY_PERIODS = (("Today", "today"), ("This month", "month"), ("All time", "all_time"))
131
+
132
+
133
+ def memory_window_row(label, hits, tokens):
134
+ """One '<Label>: N useful recalls · ~tokens' row, or a calm zero state."""
135
+ if hits <= 0:
136
+ return f"{label}: nothing yet"
137
+ parts = [f"{hits:,} useful recall" + ("" if hits == 1 else "s")]
138
+ if isinstance(tokens, int) and tokens > 0:
137
139
  parts.append(fmt_tokens(tokens))
138
- return " · ".join(parts) if parts else None
140
+ return f"{label}: " + " · ".join(parts)
141
+
142
+
143
+ def memory_section(mcp):
144
+ """Friendly 'Memory' section lines (header + period rows), or [].
145
+
146
+ Reads today / month / all-time recall + context-token counts straight
147
+ from /stats/mcp-usage and prints them in plain language. Returns [] when
148
+ the stats payload is missing or carries none of the expected windows, so
149
+ the menu degrades gracefully (section omitted, Rift still healthy) rather
150
+ than showing an error. A history with no recalls anywhere collapses to a
151
+ single calm line instead of three empty rows.
152
+ """
153
+ if not isinstance(mcp, dict):
154
+ return []
155
+ present = []
156
+ any_activity = False
157
+ for label, key in MEMORY_PERIODS:
158
+ window = mcp.get(key)
159
+ if not isinstance(window, dict):
160
+ continue
161
+ hits = window.get("context_hits")
162
+ if not isinstance(hits, int):
163
+ continue
164
+ tokens = window.get("context_tokens_delivered_estimate")
165
+ present.append((label, hits, tokens))
166
+ if hits > 0:
167
+ any_activity = True
168
+ if not present:
169
+ return []
170
+ # Collapse to a single calm line ONLY when every period is present and all
171
+ # are zero — that genuinely is "nothing recalled, ever". A partial payload
172
+ # (e.g. a legacy daemon emitting only `today`) must not claim all-time
173
+ # emptiness it can't see, so it falls through to the per-period rows
174
+ # ("Today: nothing yet") instead.
175
+ if not any_activity and len(present) == len(MEMORY_PERIODS):
176
+ return ["Memory", "Nothing recalled yet — Rift learns as you use it"]
177
+ return ["Memory"] + [
178
+ memory_window_row(label, hits, tokens) for label, hits, tokens in present
179
+ ]
139
180
 
140
181
 
141
182
  # --- Menu-action line builders -------------------------------------------
@@ -443,9 +484,11 @@ def render(doctor, status, mcp, health, now):
443
484
  # healthy or warning
444
485
  lines.append("Rift is working")
445
486
  lines += render_capture_rows(friend, now)
446
- mem = memory_today(mcp)
447
- if mem:
448
- lines.append(f"Memory today: {mem}")
487
+
488
+ mem_rows = memory_section(mcp)
489
+ if mem_rows:
490
+ lines.append("---")
491
+ lines += mem_rows
449
492
 
450
493
  client_rows = render_client_rows(status, doctor)
451
494
  if client_rows:
@@ -36,27 +36,57 @@ while [[ -L "$SOURCE" ]]; do
36
36
  done
37
37
  SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
38
38
  RIFT_HOME_DEFAULT="${HOME}/Library/Application Support/Rift"
39
- LOG_DIR="${RIFT_LOG_DIR:-${RIFT_HOME_DEFAULT}/logs}"
39
+
40
+ # Packaged (.pkg) install layout — bundled Node + app tree under Rift home,
41
+ # a bash CLI shim at ~/.rift/bin/rift, daemon logs under ~/Library/Logs/Rift.
42
+ # Detect it so the menu bar works on a BARE Mac with NO env injection: the
43
+ # `rift menubar install` env (RIFT_BIN/NODE_BIN) does NOT persist into the
44
+ # installed plugin, so the plugin must self-resolve these paths every tick.
45
+ PACKAGED_NODE="${RIFT_HOME_DEFAULT}/node/bin/node"
46
+ PACKAGED_JS="${RIFT_HOME_DEFAULT}/app/dist/src/cli/index.js"
47
+ PACKAGED_SHIM="${HOME}/.rift/bin/rift"
48
+ if [[ -f "$PACKAGED_JS" && -x "$PACKAGED_NODE" ]]; then
49
+ PACKAGED_LOG_DIR="${HOME}/Library/Logs/Rift"
50
+ else
51
+ PACKAGED_LOG_DIR=""
52
+ fi
53
+
54
+ LOG_DIR="${RIFT_LOG_DIR:-${PACKAGED_LOG_DIR:-${RIFT_HOME_DEFAULT}/logs}}"
40
55
  DATA_DIR="${RIFT_DATA_DIR:-${RIFT_HOME_DEFAULT}/data}"
41
56
  RENDERER="$SCRIPT_DIR/render-menu.py"
42
57
 
43
58
  # Resolve the rift CLI. SwiftBar invokes shell commands with a stripped
44
- # PATH, so menu actions need absolute paths end-to-end. The npm-installed
45
- # `rift` is a symlink to a JS file with a `#!/usr/bin/env node` shebang —
46
- # executing it directly under a stripped PATH fails with `env: node: No
47
- # such file or directory`. So we resolve:
48
- # - RIFT_BIN: the on-PATH `rift` launcher (diagnostics + fallback).
49
- # - RIFT_JS: the real JS entrypoint behind RIFT_BIN's symlink chain,
50
- # to pass as node's first arg.
51
- # - NODE_BIN: an absolute node binary, so actions don't depend on PATH.
52
- RIFT_BIN="${RIFT_BIN:-}"
59
+ # PATH, so menu actions need absolute paths end-to-end. We resolve:
60
+ # - NODE_BIN: an absolute node binary (bundled Node preferred), so actions
61
+ # don't depend on PATH and work on a Mac with no system Node.
62
+ # - RIFT_JS: the real JS entrypoint to pass as node's first arg. NEVER the
63
+ # bash shim `node <bash-file>` fails so we accept only a *.js target
64
+ # and prefer the packaged app entrypoint directly.
65
+ # - RIFT_BIN: an executable `rift` launcher (diagnostics + fallback). The
66
+ # packaged shim is a valid launcher when run DIRECTLY (it is not JS).
53
67
  # REPO_ROOT only resolves to anything in a dev checkout; default it to empty
54
68
  # so the dev-only candidate below is a harmless no-op under `set -u` on
55
- # packaged (npm) installs, where it is never set. (Without this, the whole
56
- # candidate list fails to expand and the plugin crashes on every tick.)
69
+ # packaged/npm installs, where it is never set.
57
70
  REPO_ROOT="${REPO_ROOT:-}"
71
+
72
+ NODE_BIN="${NODE_BIN:-}"
73
+ if [[ -z "$NODE_BIN" ]]; then
74
+ for candidate in \
75
+ "$PACKAGED_NODE" \
76
+ "/opt/homebrew/bin/node" \
77
+ "/usr/local/bin/node" \
78
+ "/usr/bin/node"; do
79
+ if [[ -x "$candidate" ]]; then
80
+ NODE_BIN="$candidate"
81
+ break
82
+ fi
83
+ done
84
+ fi
85
+
86
+ RIFT_BIN="${RIFT_BIN:-}"
58
87
  if [[ -z "$RIFT_BIN" ]]; then
59
88
  for candidate in \
89
+ "$PACKAGED_SHIM" \
60
90
  "/opt/homebrew/bin/rift" \
61
91
  "/usr/local/bin/rift" \
62
92
  "$HOME/.npm-global/bin/rift" \
@@ -69,8 +99,15 @@ if [[ -z "$RIFT_BIN" ]]; then
69
99
  done
70
100
  fi
71
101
 
72
- RIFT_JS=""
73
- if [[ -n "$RIFT_BIN" ]]; then
102
+ RIFT_JS="${RIFT_JS:-}"
103
+ # Packaged app entrypoint is authoritative when present.
104
+ if [[ -z "$RIFT_JS" && -f "$PACKAGED_JS" ]]; then
105
+ RIFT_JS="$PACKAGED_JS"
106
+ fi
107
+ # Otherwise follow RIFT_BIN's symlink chain to the real entrypoint — but
108
+ # only accept a *.js target, so a bash launcher (the packaged shim, or an
109
+ # on-PATH wrapper) is never handed to `node` as a script.
110
+ if [[ -z "$RIFT_JS" && -n "$RIFT_BIN" ]]; then
74
111
  target="$RIFT_BIN"
75
112
  while [[ -L "$target" ]]; do
76
113
  link_dir="$(cd -P "$(dirname "$target")" && pwd)"
@@ -81,24 +118,11 @@ if [[ -n "$RIFT_BIN" ]]; then
81
118
  target="$link_dir/$link_target"
82
119
  fi
83
120
  done
84
- if [[ -f "$target" ]]; then
121
+ if [[ -f "$target" && "$target" == *.js ]]; then
85
122
  RIFT_JS="$target"
86
123
  fi
87
124
  fi
88
125
 
89
- NODE_BIN="${NODE_BIN:-}"
90
- if [[ -z "$NODE_BIN" ]]; then
91
- for candidate in \
92
- "/opt/homebrew/bin/node" \
93
- "/usr/local/bin/node" \
94
- "/usr/bin/node"; do
95
- if [[ -x "$candidate" ]]; then
96
- NODE_BIN="$candidate"
97
- break
98
- fi
99
- done
100
- fi
101
-
102
126
  # Run a rift subcommand, preferring node + JS entrypoint (PATH-independent),
103
127
  # falling back to the launcher. Prints stdout; swallows stderr. Returns the
104
128
  # command's exit code, or 127 when rift can't be resolved at all.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getrift/rift",
3
- "version": "0.1.0-beta.19",
3
+ "version": "0.1.0-beta.20",
4
4
  "description": "Local-first personal memory + reasoning infrastructure with an MCP interface.",
5
5
  "homepage": "https://getrift.dev",
6
6
  "license": "UNLICENSED",