compartment 3.4__tar.gz → 3.5__tar.gz

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 (80) hide show
  1. {compartment-3.4 → compartment-3.5}/PKG-INFO +23 -21
  2. {compartment-3.4 → compartment-3.5}/README.md +22 -20
  3. {compartment-3.4 → compartment-3.5}/pyproject.toml +8 -8
  4. {compartment-3.4 → compartment-3.5}/src/compartment/__init__.py +1 -1
  5. {compartment-3.4 → compartment-3.5}/src/compartment/cli.py +108 -37
  6. compartment-3.5/src/compartment/data/app.icns +0 -0
  7. compartment-3.5/src/compartment/data/app.png +0 -0
  8. {compartment-3.4 → compartment-3.5}/src/compartment/menubar.py +321 -13
  9. {compartment-3.4 → compartment-3.5}/src/compartment/systray.py +238 -52
  10. {compartment-3.4 → compartment-3.5}/src/compartment.egg-info/PKG-INFO +23 -21
  11. {compartment-3.4 → compartment-3.5}/src/compartment.egg-info/SOURCES.txt +6 -0
  12. {compartment-3.4 → compartment-3.5}/tests/test_connect_buttons.py +7 -3
  13. compartment-3.5/tests/test_connect_environment.py +256 -0
  14. compartment-3.5/tests/test_linux_panel.py +229 -0
  15. compartment-3.5/tests/test_login_item_identity.py +145 -0
  16. {compartment-3.4 → compartment-3.5}/tests/test_menubar.py +2 -1
  17. compartment-3.5/tests/test_single_instance.py +152 -0
  18. {compartment-3.4 → compartment-3.5}/tests/test_status_bar_install.py +4 -3
  19. {compartment-3.4 → compartment-3.5}/tests/test_systray.py +8 -4
  20. {compartment-3.4 → compartment-3.5}/setup.cfg +0 -0
  21. {compartment-3.4 → compartment-3.5}/src/compartment/acl.py +0 -0
  22. {compartment-3.4 → compartment-3.5}/src/compartment/audit.py +0 -0
  23. {compartment-3.4 → compartment-3.5}/src/compartment/bench.py +0 -0
  24. {compartment-3.4 → compartment-3.5}/src/compartment/claude_hooks.py +0 -0
  25. {compartment-3.4 → compartment-3.5}/src/compartment/claude_memory.py +0 -0
  26. {compartment-3.4 → compartment-3.5}/src/compartment/crypto.py +0 -0
  27. {compartment-3.4 → compartment-3.5}/src/compartment/dash.py +0 -0
  28. {compartment-3.4 → compartment-3.5}/src/compartment/data/hermes-plugin/__init__.py +0 -0
  29. {compartment-3.4 → compartment-3.5}/src/compartment/data/hermes-plugin/plugin.yaml +0 -0
  30. {compartment-3.4 → compartment-3.5}/src/compartment/data/menubar.png +0 -0
  31. {compartment-3.4 → compartment-3.5}/src/compartment/data/menubar@2x.png +0 -0
  32. {compartment-3.4 → compartment-3.5}/src/compartment/data/starter.mpack +0 -0
  33. {compartment-3.4 → compartment-3.5}/src/compartment/data/tray.ico +0 -0
  34. {compartment-3.4 → compartment-3.5}/src/compartment/embed.py +0 -0
  35. {compartment-3.4 → compartment-3.5}/src/compartment/home.py +0 -0
  36. {compartment-3.4 → compartment-3.5}/src/compartment/longmemeval.py +0 -0
  37. {compartment-3.4 → compartment-3.5}/src/compartment/models/bge-small-en-v1.5-int8/model_quantized.onnx +0 -0
  38. {compartment-3.4 → compartment-3.5}/src/compartment/models/bge-small-en-v1.5-int8/tokenizer.json +0 -0
  39. {compartment-3.4 → compartment-3.5}/src/compartment/offline_guard.py +0 -0
  40. {compartment-3.4 → compartment-3.5}/src/compartment/packs.py +0 -0
  41. {compartment-3.4 → compartment-3.5}/src/compartment/platforms.py +0 -0
  42. {compartment-3.4 → compartment-3.5}/src/compartment/salience.py +0 -0
  43. {compartment-3.4 → compartment-3.5}/src/compartment/selftest.py +0 -0
  44. {compartment-3.4 → compartment-3.5}/src/compartment/server.py +0 -0
  45. {compartment-3.4 → compartment-3.5}/src/compartment/session.py +0 -0
  46. {compartment-3.4 → compartment-3.5}/src/compartment/store.py +0 -0
  47. {compartment-3.4 → compartment-3.5}/src/compartment/vault.py +0 -0
  48. {compartment-3.4 → compartment-3.5}/src/compartment/vaultfile.py +0 -0
  49. {compartment-3.4 → compartment-3.5}/src/compartment/vindex.py +0 -0
  50. {compartment-3.4 → compartment-3.5}/src/compartment/wire.py +0 -0
  51. {compartment-3.4 → compartment-3.5}/src/compartment.egg-info/dependency_links.txt +0 -0
  52. {compartment-3.4 → compartment-3.5}/src/compartment.egg-info/entry_points.txt +0 -0
  53. {compartment-3.4 → compartment-3.5}/src/compartment.egg-info/requires.txt +0 -0
  54. {compartment-3.4 → compartment-3.5}/src/compartment.egg-info/top_level.txt +0 -0
  55. {compartment-3.4 → compartment-3.5}/tests/test_acl_audit_packs.py +0 -0
  56. {compartment-3.4 → compartment-3.5}/tests/test_acl_precedence.py +0 -0
  57. {compartment-3.4 → compartment-3.5}/tests/test_attempt_pacing.py +0 -0
  58. {compartment-3.4 → compartment-3.5}/tests/test_audit_chain.py +0 -0
  59. {compartment-3.4 → compartment-3.5}/tests/test_claude_hooks_and_integrate.py +0 -0
  60. {compartment-3.4 → compartment-3.5}/tests/test_claude_memory_and_recent.py +0 -0
  61. {compartment-3.4 → compartment-3.5}/tests/test_crypto.py +0 -0
  62. {compartment-3.4 → compartment-3.5}/tests/test_dash.py +0 -0
  63. {compartment-3.4 → compartment-3.5}/tests/test_instructions.py +0 -0
  64. {compartment-3.4 → compartment-3.5}/tests/test_journal_framing.py +0 -0
  65. {compartment-3.4 → compartment-3.5}/tests/test_macos_bundle.py +0 -0
  66. {compartment-3.4 → compartment-3.5}/tests/test_offline_guard_paths.py +0 -0
  67. {compartment-3.4 → compartment-3.5}/tests/test_offline_selftest_bench.py +0 -0
  68. {compartment-3.4 → compartment-3.5}/tests/test_ondisk_format.py +0 -0
  69. {compartment-3.4 → compartment-3.5}/tests/test_pack_trust.py +0 -0
  70. {compartment-3.4 → compartment-3.5}/tests/test_platforms.py +0 -0
  71. {compartment-3.4 → compartment-3.5}/tests/test_relations.py +0 -0
  72. {compartment-3.4 → compartment-3.5}/tests/test_salience.py +0 -0
  73. {compartment-3.4 → compartment-3.5}/tests/test_session_entropy.py +0 -0
  74. {compartment-3.4 → compartment-3.5}/tests/test_session_lock.py +0 -0
  75. {compartment-3.4 → compartment-3.5}/tests/test_starter_data_quality.py +0 -0
  76. {compartment-3.4 → compartment-3.5}/tests/test_starter_packs.py +0 -0
  77. {compartment-3.4 → compartment-3.5}/tests/test_starter_visibility.py +0 -0
  78. {compartment-3.4 → compartment-3.5}/tests/test_tamper.py +0 -0
  79. {compartment-3.4 → compartment-3.5}/tests/test_twofa.py +0 -0
  80. {compartment-3.4 → compartment-3.5}/tests/test_vault_ops.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: compartment
3
- Version: 3.4
3
+ Version: 3.5
4
4
  Summary: Compartment - high-security, fully offline, encrypted vector memory for AI agents (MCP)
5
5
  Project-URL: Homepage, https://github.com/MaxFreedomPollard/Compartment
6
6
  Project-URL: Source, https://github.com/MaxFreedomPollard/Compartment
@@ -96,8 +96,9 @@ configuration step.
96
96
  key, no cloud account, no daemon.
97
97
  - On the Mac, open one `.pkg` and you are done. Python, the embedding model
98
98
  and every dependency are inside it.
99
- - A menu bar app runs the whole thing without a terminal: vault state,
100
- unlock, lock, and the last five memories it saved.
99
+ - An app runs the whole thing without a terminal on all three systems: vault
100
+ state, unlock, lock, and the last five memories it saved. The macOS menu
101
+ bar, the Windows notification area, a window on Linux.
101
102
  - Every feature toggles in that panel instead of a config file:
102
103
  model-independent capture, starter facts in search, auto-lock.
103
104
  - Your vault ships full. The 6,718 seeded facts are ordinary memories,
@@ -196,28 +197,29 @@ backed up first), it exits successfully no matter what - a memory tool must
196
197
  never break your editor - and it stays quiet when the vault is locked.
197
198
  `compartment hook status | install | uninstall`, or `integrate claude --no-hooks`.
198
199
 
199
- <p align="center">
200
- <img src="https://raw.githubusercontent.com/MaxFreedomPollard/Compartment/main/docs/images/menubar-panel.png" width="330"
201
- alt="The Compartment panel in the macOS menu bar.">
202
- <img src="https://raw.githubusercontent.com/MaxFreedomPollard/Compartment/main/docs/images/windows-tray-panel.png" width="330"
203
- alt="The Compartment panel in the Windows notification area.">
204
- </p>
205
-
206
- **A menu bar and system tray app, from the same one install.** `pip install
207
- compartment && compartment init` sets it up on macOS and on Windows: the app
208
- starts straight away and comes back at every login. There is no separate
209
- package, no extra to remember and no second command. On the Mac you can
210
- instead open **Compartment.pkg** from the
200
+ **An app on all three systems, from the same one install.** `pip install
201
+ compartment && compartment init` sets it up on macOS, Windows and Linux.
202
+ There is no separate package, no extra to remember and no second command. On
203
+ the Mac you can instead open **Compartment.pkg** from the
211
204
  [latest release](https://github.com/MaxFreedomPollard/Compartment/releases/latest),
212
205
  which carries Python and every dependency inside it, so there is nothing to
213
206
  install first. `compartment init --no-app` skips the app for headless boxes
214
- and CI. Either way it puts Compartment in the status bar: click the icon and a
215
- panel shows whether the vault is open, how much it has learned, the three
207
+ and CI.
208
+
209
+ The same panel, in the place each system keeps things like this: the **menu
210
+ bar** on macOS, the **notification area** on Windows, and on **Linux** an
211
+ ordinary window, with Compartment in your applications menu. Linux gets a
212
+ window rather than an icon deliberately. Whether a tray icon appears there
213
+ depends on the desktop, and on GNOME or Wayland it can simply never show up
214
+ with nothing said, which is the worst way for the control that unlocks your
215
+ memories to fail.
216
+
217
+ The panel shows whether the vault is open, how much it has learned, the three
216
218
  settings worth changing day to day (capture hook, whether starter facts join
217
- searches, auto-lock), and the last five things it remembered. Unlock, lock and
218
- change your passphrase there too, without opening a terminal. No dock icon,
219
- no window to manage, and it holds no vault in memory - state comes from the
220
- CLI, so an idle app costs nothing.
219
+ searches, auto-lock), which agents are connected and buttons to connect them,
220
+ and the last five things it remembered. Unlock, lock and change your
221
+ passphrase there too, without opening a terminal. It holds no vault in
222
+ memory - state comes from the CLI, so an idle app costs nothing.
221
223
 
222
224
  **See what it just learned.** `compartment recent` lists the newest memories,
223
225
  newest last, hiding the thousands of seeded starting facts so the handful
@@ -50,8 +50,9 @@ configuration step.
50
50
  key, no cloud account, no daemon.
51
51
  - On the Mac, open one `.pkg` and you are done. Python, the embedding model
52
52
  and every dependency are inside it.
53
- - A menu bar app runs the whole thing without a terminal: vault state,
54
- unlock, lock, and the last five memories it saved.
53
+ - An app runs the whole thing without a terminal on all three systems: vault
54
+ state, unlock, lock, and the last five memories it saved. The macOS menu
55
+ bar, the Windows notification area, a window on Linux.
55
56
  - Every feature toggles in that panel instead of a config file:
56
57
  model-independent capture, starter facts in search, auto-lock.
57
58
  - Your vault ships full. The 6,718 seeded facts are ordinary memories,
@@ -150,28 +151,29 @@ backed up first), it exits successfully no matter what - a memory tool must
150
151
  never break your editor - and it stays quiet when the vault is locked.
151
152
  `compartment hook status | install | uninstall`, or `integrate claude --no-hooks`.
152
153
 
153
- <p align="center">
154
- <img src="https://raw.githubusercontent.com/MaxFreedomPollard/Compartment/main/docs/images/menubar-panel.png" width="330"
155
- alt="The Compartment panel in the macOS menu bar.">
156
- <img src="https://raw.githubusercontent.com/MaxFreedomPollard/Compartment/main/docs/images/windows-tray-panel.png" width="330"
157
- alt="The Compartment panel in the Windows notification area.">
158
- </p>
159
-
160
- **A menu bar and system tray app, from the same one install.** `pip install
161
- compartment && compartment init` sets it up on macOS and on Windows: the app
162
- starts straight away and comes back at every login. There is no separate
163
- package, no extra to remember and no second command. On the Mac you can
164
- instead open **Compartment.pkg** from the
154
+ **An app on all three systems, from the same one install.** `pip install
155
+ compartment && compartment init` sets it up on macOS, Windows and Linux.
156
+ There is no separate package, no extra to remember and no second command. On
157
+ the Mac you can instead open **Compartment.pkg** from the
165
158
  [latest release](https://github.com/MaxFreedomPollard/Compartment/releases/latest),
166
159
  which carries Python and every dependency inside it, so there is nothing to
167
160
  install first. `compartment init --no-app` skips the app for headless boxes
168
- and CI. Either way it puts Compartment in the status bar: click the icon and a
169
- panel shows whether the vault is open, how much it has learned, the three
161
+ and CI.
162
+
163
+ The same panel, in the place each system keeps things like this: the **menu
164
+ bar** on macOS, the **notification area** on Windows, and on **Linux** an
165
+ ordinary window, with Compartment in your applications menu. Linux gets a
166
+ window rather than an icon deliberately. Whether a tray icon appears there
167
+ depends on the desktop, and on GNOME or Wayland it can simply never show up
168
+ with nothing said, which is the worst way for the control that unlocks your
169
+ memories to fail.
170
+
171
+ The panel shows whether the vault is open, how much it has learned, the three
170
172
  settings worth changing day to day (capture hook, whether starter facts join
171
- searches, auto-lock), and the last five things it remembered. Unlock, lock and
172
- change your passphrase there too, without opening a terminal. No dock icon,
173
- no window to manage, and it holds no vault in memory - state comes from the
174
- CLI, so an idle app costs nothing.
173
+ searches, auto-lock), which agents are connected and buttons to connect them,
174
+ and the last five things it remembered. Unlock, lock and change your
175
+ passphrase there too, without opening a terminal. It holds no vault in
176
+ memory - state comes from the CLI, so an idle app costs nothing.
175
177
 
176
178
  **See what it just learned.** `compartment recent` lists the newest memories,
177
179
  newest last, hiding the thousands of seeded starting facts so the handful
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "compartment"
7
- version = "3.4"
7
+ version = "3.5"
8
8
  description = "Compartment - high-security, fully offline, encrypted vector memory for AI agents (MCP)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -36,13 +36,13 @@ dependencies = [
36
36
  "tokenizers>=0.15",
37
37
  "numpy>=1.26",
38
38
  "mcp>=1.0",
39
- # The status bar app ships with every install, on the platforms that have
40
- # one. It is not an add-on: unlocking, locking and changing the passphrase
41
- # are things people do through the icon, not through a terminal, so an
42
- # install that only lays down a CLI is an incomplete install. The markers
43
- # keep it honest - nothing macOS-only is fetched on Windows, nothing
44
- # Windows-only on macOS, and Linux (which has no status bar app here)
45
- # installs neither and compiles nothing.
39
+ # The app ships with every install. It is not an add-on: unlocking,
40
+ # locking and changing the passphrase are things people do through a
41
+ # panel, not through a terminal, so an install that only lays down a CLI
42
+ # is an incomplete install. The markers keep it honest - nothing
43
+ # macOS-only is fetched on Windows and nothing Windows-only on macOS.
44
+ # Linux needs neither: its panel is Tk, which is in the standard library,
45
+ # so nothing here is fetched and nothing is compiled there.
46
46
  "pyobjc-framework-Cocoa>=10.0; sys_platform == 'darwin'",
47
47
  "pystray>=0.19; sys_platform == 'win32'",
48
48
  "pillow>=10; sys_platform == 'win32'",
@@ -1,6 +1,6 @@
1
1
  """Compartment - high-security, fully offline, encrypted vector memory for AI agents."""
2
2
 
3
- __version__ = "3.4"
3
+ __version__ = "3.5"
4
4
 
5
5
  from . import offline_guard as _og
6
6
 
@@ -167,10 +167,10 @@ def cmd_init(args) -> None:
167
167
  st = v.status()
168
168
  v.save()
169
169
  if args.no_app:
170
- print(" command-line only install (--no-app): no status bar app")
170
+ print(" command-line only install (--no-app): no app")
171
171
  elif _cli_only_requested():
172
- print(" command-line only install: no status bar app, no login item")
173
- print(" add it later with `compartment menubar --login on`")
172
+ print(" command-line only install: no app")
173
+ print(" add it later with `compartment panel --login on`")
174
174
  else:
175
175
  _start_status_bar_app(path)
176
176
  print(f"\nVault ready: {st['records']} records, projected RAM "
@@ -219,6 +219,32 @@ def _cli_only_requested(seconds: float = 5.0) -> bool:
219
219
  return False # never let this block an install
220
220
 
221
221
 
222
+ def _linux_has_display() -> bool:
223
+ """Is there a desktop to draw on?
224
+
225
+ Most Linux installs of a memory server are headless: a box over SSH, a
226
+ container, a CI runner. Starting a window there fails, and an
227
+ applications menu entry on a machine with no applications menu is
228
+ litter. Neither is a broken install - it is a server, and the CLI and
229
+ the MCP server are the whole product on one."""
230
+ return bool(os.environ.get("DISPLAY")
231
+ or os.environ.get("WAYLAND_DISPLAY"))
232
+
233
+
234
+ def _linux_gui_available() -> bool:
235
+ """Can this Python draw the panel?
236
+
237
+ tkinter is in the standard library but not in every Linux distribution's
238
+ Python package, and pip cannot install it. Checked here so an install
239
+ says so plainly, rather than a launcher entry that opens nothing.
240
+ """
241
+ try:
242
+ import tkinter # noqa: F401
243
+ return True
244
+ except Exception: # noqa: BLE001
245
+ return False
246
+
247
+
222
248
  def _start_status_bar_app(vault: str) -> None:
223
249
  """Put the status bar app up now, and again at every login.
224
250
 
@@ -229,18 +255,36 @@ def _start_status_bar_app(vault: str) -> None:
229
255
  platform with no status bar still gets a perfectly good CLI and MCP
230
256
  server, so a failure here is reported and stepped over.
231
257
  """
232
- if sys.platform not in ("darwin", "win32"):
233
- return # no status bar app here
234
258
  try:
235
259
  app = _tray_app()
236
260
  except Exception as exc: # noqa: BLE001
237
- print(f" status bar app unavailable ({exc}); the CLI is unaffected")
261
+ print(f" app unavailable ({exc}); the CLI is unaffected")
262
+ return
263
+ linux = sys.platform not in ("darwin", "win32")
264
+ if linux and not _linux_has_display():
265
+ print(" headless machine (no DISPLAY): no panel and no menu entry.")
266
+ print(" The CLI and the MCP server are ready, and `compartment "
267
+ "dash` opens the vault in a browser.")
238
268
  return
239
269
  try:
240
270
  state = app.set_login(True)
241
- print(f" start at login: {state}")
271
+ # On Linux this is the applications menu entry. Nothing starts at
272
+ # login there: without a tray icon to sit quietly in, an app that
273
+ # started itself would just be a window in your face at every
274
+ # sign-in.
275
+ print(f" {'applications menu entry' if linux else 'start at login'}"
276
+ f": {state}")
242
277
  except Exception as exc: # noqa: BLE001
243
- print(f" could not enable start at login ({exc})")
278
+ print(f" could not register the app ({exc})")
279
+ if linux and not _linux_gui_available():
280
+ print(" the panel needs tkinter, which this Python does not have.")
281
+ print(" Quickest fix, a Python that includes it:")
282
+ print(" uv tool install compartment")
283
+ print(" or install your distribution's package (Debian/Ubuntu: "
284
+ "python3-tk, Fedora: python3-tkinter).")
285
+ print(" Everything else works now: the CLI, the MCP server, and "
286
+ "`compartment dash`.")
287
+ return
244
288
  try:
245
289
  exe = shutil.which("compartment")
246
290
  argv = ([exe, "--vault", vault, "menubar"] if exe else
@@ -253,12 +297,17 @@ def _start_status_bar_app(vault: str) -> None:
253
297
  else:
254
298
  kwargs["start_new_session"] = True # outlive this shell
255
299
  subprocess.Popen(argv, **kwargs)
256
- print(" status bar app started - look for the icon in your "
257
- + ("menu bar" if sys.platform == "darwin"
258
- else "notification area"))
300
+ if sys.platform == "darwin":
301
+ print(" app started - look for the icon in your menu bar")
302
+ elif sys.platform == "win32":
303
+ print(" app started - look for the icon in your notification "
304
+ "area")
305
+ else:
306
+ print(" app started - its window is open, and Compartment is "
307
+ "in your applications menu")
259
308
  except Exception as exc: # noqa: BLE001
260
- print(f" could not start the status bar app ({exc}); run "
261
- "`compartment menubar` yourself")
309
+ print(f" could not start the app ({exc}); run "
310
+ "`compartment panel` yourself")
262
311
 
263
312
 
264
313
  def _install_kind() -> tuple[str, list[str]]:
@@ -314,17 +363,29 @@ def _restart_status_bar_app(vault: str) -> None:
314
363
  def cmd_uninstall(args) -> None:
315
364
  """Remove Compartment from this machine. The vault is KEPT unless asked."""
316
365
  print(f"Compartment {__version__} - uninstalling")
317
- if sys.platform in ("darwin", "win32"):
366
+ try:
367
+ app = _tray_app()
318
368
  try:
319
- app = _tray_app()
320
- try:
321
- app.quit_running()
322
- print(" status bar app stopped")
323
- except Exception: # noqa: BLE001
324
- pass
325
- print(f" start at login: {app.set_login(False)}")
369
+ app.quit_running()
370
+ print(" app stopped")
371
+ except Exception: # noqa: BLE001
372
+ pass
373
+ linux = sys.platform not in ("darwin", "win32")
374
+ print(f" {'applications menu entry' if linux else 'start at login'}"
375
+ f": {app.set_login(False)}")
376
+ except Exception as exc: # noqa: BLE001
377
+ print(f" app: nothing to remove ({exc})")
378
+ if sys.platform == "darwin":
379
+ # Only the small bundle a pip install writes for its own login item.
380
+ # A .pkg install is left alone: that one was not ours to create and
381
+ # its receipt is what removes it.
382
+ try:
383
+ from .menubar import USER_APP_BUNDLE, _is_generated
384
+ if _is_generated(USER_APP_BUNDLE):
385
+ shutil.rmtree(USER_APP_BUNDLE, ignore_errors=True)
386
+ print(f" removed {USER_APP_BUNDLE}")
326
387
  except Exception as exc: # noqa: BLE001
327
- print(f" status bar app: nothing to remove ({exc})")
388
+ print(f" login bundle: {exc}")
328
389
  try:
329
390
  if claude_hooks.is_installed():
330
391
  claude_hooks.uninstall()
@@ -486,14 +547,20 @@ def cmd_recent(args) -> None:
486
547
 
487
548
 
488
549
  def _tray_app():
489
- """The platform's front end: the macOS menu bar item, or the Windows tray
490
- icon. Both are thin shells over the same data layer in `menubar`, so the
491
- command is the same everywhere and only the widgets differ."""
492
- if sys.platform == "win32":
493
- from . import systray
494
- return systray
495
- from . import menubar
496
- return menubar
550
+ """The platform's front end: the macOS menu bar item, the Windows tray
551
+ icon, or on Linux the same panel as an ordinary window. All three are
552
+ thin shells over the same data layer in `menubar`, so the command is the
553
+ same everywhere and only the widgets differ.
554
+
555
+ Linux gets a window rather than an icon on purpose. Whether a tray icon
556
+ appears there depends on the desktop, and on GNOME or Wayland it can
557
+ simply never show up with nothing said - which is the worst way for the
558
+ control that unlocks your memories to fail."""
559
+ if sys.platform == "darwin":
560
+ from . import menubar
561
+ return menubar
562
+ from . import systray
563
+ return systray
497
564
 
498
565
 
499
566
  def cmd_menubar(args) -> None:
@@ -1316,19 +1383,23 @@ def main(argv: list[str] | None = None) -> None:
1316
1383
  p.add_argument("--json", action="store_true")
1317
1384
  p.set_defaults(fn=cmd_recent)
1318
1385
 
1319
- p = sub.add_parser("menubar", aliases=["tray"],
1320
- help="menu bar app on macOS, tray app on Windows "
1321
- "(settings + recent memories)")
1386
+ # One command, three front ends. "panel" is the name that is true on
1387
+ # every platform, and the older names keep working.
1388
+ p = sub.add_parser("menubar", aliases=["tray", "panel"],
1389
+ help="the app: menu bar on macOS, notification area "
1390
+ "on Windows, a window on Linux (unlock, "
1391
+ "settings, connect an agent, recent memories)")
1322
1392
  p.add_argument("--show", action="store_true",
1323
- help="open the popover immediately on launch")
1393
+ help="open the panel immediately on launch")
1324
1394
  p.add_argument("--self-check", action="store_true",
1325
- help="print what the popover would show, no window")
1395
+ help="print what the panel would show, no window")
1326
1396
  p.add_argument("--render", metavar="PNG",
1327
- help="write the popover to a PNG and exit (macOS, UI check)")
1397
+ help="write the panel to a PNG and exit (macOS, UI check)")
1328
1398
  p.add_argument("--login", nargs="?", const="status",
1329
1399
  choices=["on", "off", "status"],
1330
1400
  help="start Compartment at login or sign-in (on/off), or "
1331
- "show the state")
1401
+ "show the state. On Linux this is the applications "
1402
+ "menu entry, since there is no icon to start")
1332
1403
  p.set_defaults(fn=cmd_menubar)
1333
1404
 
1334
1405
  ph = sub.add_parser("hook", help="Claude Code capture hook (deterministic "