compartment 4.2__tar.gz → 4.4__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.
- {compartment-4.2/src/compartment.egg-info → compartment-4.4}/PKG-INFO +6 -2
- {compartment-4.2 → compartment-4.4}/README.md +5 -1
- {compartment-4.2 → compartment-4.4}/pyproject.toml +1 -1
- {compartment-4.2 → compartment-4.4}/src/compartment/__init__.py +1 -1
- {compartment-4.2 → compartment-4.4}/src/compartment/cli.py +98 -24
- {compartment-4.2 → compartment-4.4}/src/compartment/menubar.py +397 -28
- {compartment-4.2 → compartment-4.4}/src/compartment/server.py +9 -3
- {compartment-4.2 → compartment-4.4}/src/compartment/systray.py +554 -31
- {compartment-4.2 → compartment-4.4/src/compartment.egg-info}/PKG-INFO +6 -2
- {compartment-4.2 → compartment-4.4}/src/compartment.egg-info/SOURCES.txt +2 -0
- {compartment-4.2 → compartment-4.4}/tests/test_attempt_pacing.py +22 -4
- {compartment-4.2 → compartment-4.4}/tests/test_linux_panel.py +16 -6
- compartment-4.4/tests/test_menu_bar_persistence.py +1001 -0
- {compartment-4.2 → compartment-4.4}/tests/test_single_instance.py +7 -1
- compartment-4.4/tests/test_stays_running.py +169 -0
- {compartment-4.2 → compartment-4.4}/LICENSE +0 -0
- {compartment-4.2 → compartment-4.4}/setup.cfg +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/acl.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/agent_skill.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/audit.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/bench.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/claude_desktop.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/claude_hooks.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/claude_memory.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/clients.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/crypto.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/dash.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/agent-skill/SKILL.md +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/app.icns +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/app.png +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/hermes-plugin/__init__.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/hermes-plugin/plugin.yaml +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/menubar.png +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/menubar@2x.png +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/starter.mpack +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/data/tray.ico +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/embed.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/home.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/longmemeval.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/models/bge-small-en-v1.5-int8/model_quantized.onnx +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/models/bge-small-en-v1.5-int8/tokenizer.json +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/offline_guard.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/packs.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/platforms.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/ranking.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/salience.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/selftest.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/session.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/store.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/vault.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/vaultfile.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/vindex.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment/wire.py +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment.egg-info/dependency_links.txt +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment.egg-info/entry_points.txt +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment.egg-info/requires.txt +0 -0
- {compartment-4.2 → compartment-4.4}/src/compartment.egg-info/top_level.txt +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_acl_audit_packs.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_acl_precedence.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_agent_skill.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_audit_chain.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_autoconnect.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_claude_desktop.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_claude_hooks_and_integrate.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_claude_memory_and_recent.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_clients.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_connect_buttons.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_connect_environment.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_crypto.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_dash.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_instructions.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_journal_framing.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_login_item_identity.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_long_memory_recall.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_macos_bundle.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_menubar.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_offline_guard_paths.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_offline_selftest_bench.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_ondisk_format.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_pack_trust.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_platforms.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_ranking.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_relations.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_salience.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_session_entropy.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_session_lock.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_starter_data_quality.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_starter_packs.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_starter_visibility.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_status_bar_install.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_systray.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_tamper.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_twofa.py +0 -0
- {compartment-4.2 → compartment-4.4}/tests/test_vault_ops.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compartment
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4
|
|
4
4
|
Summary: Compartment - high-security, fully offline, encrypted vector memory for AI agents (MCP)
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/MaxFreedomPollard/Compartment
|
|
@@ -50,7 +50,11 @@ Dynamic: license-file
|
|
|
50
50
|
|
|
51
51
|
# Compartment
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
[](https://mcptoplist.com/server/io.github.MaxFreedomPollard%2Fcompartment)
|
|
54
|
+
|
|
55
|
+
<a href="https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment"><img width="380" height="200" src="https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment/badge" alt="Compartment MCP server" /></a>
|
|
56
|
+
|
|
57
|
+
### Durable agentic memory, encrypted at rest.
|
|
54
58
|
|
|
55
59
|
Your AI agent forgets you the moment the session ends. Compartment ends
|
|
56
60
|
that. With Compartment, your AI agent gets better with experience: it keeps
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Compartment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://mcptoplist.com/server/io.github.MaxFreedomPollard%2Fcompartment)
|
|
4
|
+
|
|
5
|
+
<a href="https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment"><img width="380" height="200" src="https://glama.ai/mcp/servers/MaxFreedomPollard/Compartment/badge" alt="Compartment MCP server" /></a>
|
|
6
|
+
|
|
7
|
+
### Durable agentic memory, encrypted at rest.
|
|
4
8
|
|
|
5
9
|
Your AI agent forgets you the moment the session ends. Compartment ends
|
|
6
10
|
that. With Compartment, your AI agent gets better with experience: it keeps
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "compartment"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.4"
|
|
8
8
|
description = "Compartment - high-security, fully offline, encrypted vector memory for AI agents (MCP)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -252,6 +252,14 @@ def _linux_gui_available() -> bool:
|
|
|
252
252
|
return False
|
|
253
253
|
|
|
254
254
|
|
|
255
|
+
#: Where to tell the user to look, once there is something to look at.
|
|
256
|
+
_APP_IS_AT = {
|
|
257
|
+
"darwin": "look for the icon in your menu bar",
|
|
258
|
+
"win32": "look for the icon in your notification area",
|
|
259
|
+
"": "its window is open, and Compartment is in your applications menu",
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
255
263
|
def _start_status_bar_app(vault: str) -> None:
|
|
256
264
|
"""Put the status bar app up now, and again at every login.
|
|
257
265
|
|
|
@@ -273,14 +281,18 @@ def _start_status_bar_app(vault: str) -> None:
|
|
|
273
281
|
print(" The CLI and the MCP server are ready, and `compartment "
|
|
274
282
|
"dash` opens the vault in a browser.")
|
|
275
283
|
return
|
|
284
|
+
state = ""
|
|
276
285
|
try:
|
|
277
|
-
state = app.set_login(True)
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
286
|
+
state = app.set_login(True, vault)
|
|
287
|
+
# Linux used to be told about its applications menu entry here,
|
|
288
|
+
# because nothing started at login there. That stopped being true
|
|
289
|
+
# when it got a real autostart entry, and it is further from true
|
|
290
|
+
# now that it gets a systemd user service, so it is reported the
|
|
291
|
+
# same way as everywhere else - and the menu entry, which is a
|
|
292
|
+
# different thing, is named separately.
|
|
293
|
+
print(f" start at login: {state}")
|
|
294
|
+
if linux:
|
|
295
|
+
print(" applications menu entry: on")
|
|
284
296
|
except Exception as exc: # noqa: BLE001
|
|
285
297
|
print(f" could not register the app ({exc})")
|
|
286
298
|
if linux and not _linux_gui_available():
|
|
@@ -292,6 +304,24 @@ def _start_status_bar_app(vault: str) -> None:
|
|
|
292
304
|
print(" Everything else works now: the CLI, the MCP server, and "
|
|
293
305
|
"`compartment dash`.")
|
|
294
306
|
return
|
|
307
|
+
# Let the supervisor start it where there is one, so the copy that comes
|
|
308
|
+
# up is the copy that gets restarted if it dies. On macOS bootstrapping
|
|
309
|
+
# the agent a moment ago already did it (RunAtLoad); on Linux and Windows
|
|
310
|
+
# it is one more call. Starting a second copy after that is what used to
|
|
311
|
+
# put two icons in the menu bar, and the copy started here is the one
|
|
312
|
+
# that dies with the terminal.
|
|
313
|
+
supervised = False
|
|
314
|
+
if state == "on":
|
|
315
|
+
try:
|
|
316
|
+
supervised = (sys.platform == "darwin" or app.start_supervised())
|
|
317
|
+
except Exception: # noqa: BLE001
|
|
318
|
+
supervised = False
|
|
319
|
+
if supervised:
|
|
320
|
+
if _wait_for_status_bar_app():
|
|
321
|
+
print(f" app started - {_APP_IS_AT.get(sys.platform, _APP_IS_AT[''])}")
|
|
322
|
+
return
|
|
323
|
+
print(" start at login is registered, but no icon appeared; "
|
|
324
|
+
"starting one now")
|
|
295
325
|
try:
|
|
296
326
|
exe = shutil.which("compartment")
|
|
297
327
|
argv = ([exe, "--vault", vault, "menubar"] if exe else
|
|
@@ -303,20 +333,43 @@ def _start_status_bar_app(vault: str) -> None:
|
|
|
303
333
|
kwargs["creationflags"] = 0x00000008 # DETACHED_PROCESS
|
|
304
334
|
else:
|
|
305
335
|
kwargs["start_new_session"] = True # outlive this shell
|
|
306
|
-
subprocess.Popen(argv, **kwargs)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
336
|
+
proc = subprocess.Popen(argv, **kwargs)
|
|
337
|
+
# Spawning is not starting. The app exits straight away for several
|
|
338
|
+
# ordinary reasons - PyObjC missing, another copy already holding
|
|
339
|
+
# the lock, a broken interpreter - and every one of them used to end
|
|
340
|
+
# with the install saying "look for the icon in your menu bar" over
|
|
341
|
+
# a menu bar that had nothing new in it. Still running after a
|
|
342
|
+
# couple of seconds is the most an installer can honestly claim.
|
|
343
|
+
try:
|
|
344
|
+
status = proc.wait(timeout=2.0)
|
|
345
|
+
except subprocess.TimeoutExpired:
|
|
346
|
+
status = None
|
|
347
|
+
if status is not None:
|
|
348
|
+
print(f" the app exited immediately (status {status}). "
|
|
349
|
+
"Run `compartment panel` to see what it says.")
|
|
350
|
+
return
|
|
351
|
+
print(f" app started - {_APP_IS_AT.get(sys.platform, _APP_IS_AT[''])}")
|
|
315
352
|
except Exception as exc: # noqa: BLE001
|
|
316
353
|
print(f" could not start the app ({exc}); run "
|
|
317
354
|
"`compartment panel` yourself")
|
|
318
355
|
|
|
319
356
|
|
|
357
|
+
def _wait_for_status_bar_app(timeout: float = 15.0) -> bool:
|
|
358
|
+
"""Did the app actually come up?
|
|
359
|
+
|
|
360
|
+
Asked because "the login item is registered" and "the app is running"
|
|
361
|
+
are different claims, and only the second one is what the user was
|
|
362
|
+
promised.
|
|
363
|
+
"""
|
|
364
|
+
app = _tray_app()
|
|
365
|
+
deadline = time.monotonic() + timeout
|
|
366
|
+
while time.monotonic() < deadline:
|
|
367
|
+
if app.running_pids():
|
|
368
|
+
return True
|
|
369
|
+
time.sleep(0.3)
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
|
|
320
373
|
def _install_kind() -> tuple[str, list[str]]:
|
|
321
374
|
"""How this copy was installed, and the command that upgrades it."""
|
|
322
375
|
exe = Path(sys.argv[0]).resolve()
|
|
@@ -359,6 +412,21 @@ def cmd_update(args) -> None:
|
|
|
359
412
|
|
|
360
413
|
def _restart_status_bar_app(vault: str) -> None:
|
|
361
414
|
"""Stop the running status bar app and start the new build."""
|
|
415
|
+
# One supervisor operation rather than kill-then-start. The supervisor
|
|
416
|
+
# relaunches a process that exited non-zero, so the copy this function
|
|
417
|
+
# used to start raced the one launchd or systemd had already brought
|
|
418
|
+
# back, and whichever lost the lock left with exit 0. Since the job is
|
|
419
|
+
# unchanged, the process that comes back runs whatever the upgrade just
|
|
420
|
+
# wrote at the path it names.
|
|
421
|
+
try:
|
|
422
|
+
app = _tray_app()
|
|
423
|
+
restart = (app.restart_agent if sys.platform == "darwin"
|
|
424
|
+
else app.restart_supervised)
|
|
425
|
+
if restart() and _wait_for_status_bar_app():
|
|
426
|
+
print(" app restarted - the supervisor is running the new build")
|
|
427
|
+
return
|
|
428
|
+
except Exception: # noqa: BLE001
|
|
429
|
+
pass
|
|
362
430
|
try:
|
|
363
431
|
app = _tray_app()
|
|
364
432
|
app.quit_running()
|
|
@@ -372,14 +440,19 @@ def cmd_uninstall(args) -> None:
|
|
|
372
440
|
print(f"Compartment {__version__} - uninstalling")
|
|
373
441
|
try:
|
|
374
442
|
app = _tray_app()
|
|
443
|
+
linux = sys.platform not in ("darwin", "win32")
|
|
444
|
+
# Deregister before killing, not after. On macOS the agent has
|
|
445
|
+
# KeepAlive, so a copy stopped while the job is still registered
|
|
446
|
+
# exits non-zero and launchd puts the icon straight back - during an
|
|
447
|
+
# uninstall, from a plist that is about to be deleted.
|
|
448
|
+
print(f" start at login: {app.set_login(False)}")
|
|
449
|
+
if linux:
|
|
450
|
+
print(" applications menu entry: removed")
|
|
375
451
|
try:
|
|
376
|
-
app.quit_running()
|
|
377
|
-
|
|
452
|
+
print(" app stopped" if app.quit_running()
|
|
453
|
+
else " app: nothing was running")
|
|
378
454
|
except Exception: # noqa: BLE001
|
|
379
455
|
pass
|
|
380
|
-
linux = sys.platform not in ("darwin", "win32")
|
|
381
|
-
print(f" {'applications menu entry' if linux else 'start at login'}"
|
|
382
|
-
f": {app.set_login(False)}")
|
|
383
456
|
except Exception as exc: # noqa: BLE001
|
|
384
457
|
print(f" app: nothing to remove ({exc})")
|
|
385
458
|
if sys.platform == "darwin":
|
|
@@ -583,7 +656,7 @@ def cmd_menubar(args) -> None:
|
|
|
583
656
|
"""The status bar / tray app."""
|
|
584
657
|
app = _tray_app()
|
|
585
658
|
if args.login is not None:
|
|
586
|
-
print(app.set_login(args.login == "on")
|
|
659
|
+
print(app.set_login(args.login == "on", args.vault)
|
|
587
660
|
if args.login in ("on", "off") else app.login_status())
|
|
588
661
|
sys.exit(0)
|
|
589
662
|
if args.self_check:
|
|
@@ -1648,8 +1721,9 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
1648
1721
|
p.add_argument("--login", nargs="?", const="status",
|
|
1649
1722
|
choices=["on", "off", "status"],
|
|
1650
1723
|
help="start Compartment at login or sign-in (on/off), or "
|
|
1651
|
-
"show the state.
|
|
1652
|
-
"
|
|
1724
|
+
"show the state. Where the system can supervise it "
|
|
1725
|
+
"(launchd, a systemd user service, a scheduled task) "
|
|
1726
|
+
"the app is also put back if it stops on its own")
|
|
1653
1727
|
p.set_defaults(fn=cmd_menubar)
|
|
1654
1728
|
|
|
1655
1729
|
ph = sub.add_parser("hook", help="Claude Code capture hook (deterministic "
|