omnimem 0.1.5 → 0.1.6
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/omnimem/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.6"
|
package/omnimem/webui.py
CHANGED
|
@@ -74,6 +74,7 @@ HTML_PAGE = """<!doctype html>
|
|
|
74
74
|
<div class=\"tabs\">
|
|
75
75
|
<button class=\"tab-btn active\" data-tab=\"statusTab\" data-i18n=\"tab_status\">Status & Actions</button>
|
|
76
76
|
<button class=\"tab-btn\" data-tab=\"configTab\" data-i18n=\"tab_config\">Configuration</button>
|
|
77
|
+
<button class=\"tab-btn\" data-tab=\"projectTab\" data-i18n=\"tab_project\">Project Integration</button>
|
|
77
78
|
<button class=\"tab-btn\" data-tab=\"memoryTab\" data-i18n=\"tab_memory\">Memory</button>
|
|
78
79
|
</div>
|
|
79
80
|
</div>
|
|
@@ -121,6 +122,22 @@ HTML_PAGE = """<!doctype html>
|
|
|
121
122
|
</div>
|
|
122
123
|
</div>
|
|
123
124
|
|
|
125
|
+
<div id=\"projectTab\" class=\"panel\">
|
|
126
|
+
<div class=\"grid\">
|
|
127
|
+
<div class=\"card wide\">
|
|
128
|
+
<h3 data-i18n=\"project_title\">Project Integration</h3>
|
|
129
|
+
<label><span data-i18n=\"project_path\">Project Path</span><input id=\"projectPath\" placeholder=\"/path/to/your/project\" /></label>
|
|
130
|
+
<label><span data-i18n=\"project_id\">Project ID</span><input id=\"projectId\" placeholder=\"my-project\" /></label>
|
|
131
|
+
<div class=\"row-btn\">
|
|
132
|
+
<button id=\"btnProjectAttach\" data-i18n=\"btn_project_attach\">Attach Project + Install Agent Rules</button>
|
|
133
|
+
<button id=\"btnProjectDetach\" data-i18n=\"btn_project_detach\">Detach Project</button>
|
|
134
|
+
</div>
|
|
135
|
+
<div class=\"small\" data-i18n=\"project_hint\">Attach will create .omnimem files and inject managed memory protocol blocks into AGENTS.md / CLAUDE.md / .cursorrules.</div>
|
|
136
|
+
<pre id=\"projectOut\" class=\"small\"></pre>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
124
141
|
<div id=\"memoryTab\" class=\"panel\">
|
|
125
142
|
<div class=\"grid\">
|
|
126
143
|
<div class=\"card wide\">
|
|
@@ -151,14 +168,18 @@ HTML_PAGE = """<!doctype html>
|
|
|
151
168
|
const I18N = {
|
|
152
169
|
en: {
|
|
153
170
|
title: 'OmniMem WebUI', subtitle: 'Simple mode: Status & Actions / Configuration / Memory', language: 'Language',
|
|
154
|
-
tab_status: 'Status & Actions', tab_config: 'Configuration', tab_memory: 'Memory',
|
|
171
|
+
tab_status: 'Status & Actions', tab_config: 'Configuration', tab_project: 'Project Integration', tab_memory: 'Memory',
|
|
155
172
|
system_status: 'System Status', actions: 'Actions',
|
|
156
173
|
btn_status: 'Check Sync Status', btn_bootstrap: 'Bootstrap Device Sync', btn_push: 'Push', btn_pull: 'Pull',
|
|
157
174
|
btn_daemon_on: 'Enable Daemon', btn_daemon_off: 'Disable Daemon',
|
|
158
175
|
config_title: 'Configuration', cfg_path: 'Config Path', cfg_home: 'Home', cfg_markdown: 'Markdown Path', cfg_jsonl: 'JSONL Path', cfg_sqlite: 'SQLite Path', cfg_remote_name: 'Git Remote Name', cfg_remote_url: 'Git Remote URL', cfg_branch: 'Git Branch', btn_save: 'Save Configuration',
|
|
159
176
|
mem_recent: 'Recent Memories', mem_hint: 'Click an ID to open full content', mem_content: 'Memory Content',
|
|
160
177
|
th_id: 'ID', th_layer: 'Layer', th_kind: 'Kind', th_summary: 'Summary', th_updated: 'Updated At',
|
|
178
|
+
project_title: 'Project Integration', project_path: 'Project Path', project_id: 'Project ID',
|
|
179
|
+
btn_project_attach: 'Attach Project + Install Agent Rules', btn_project_detach: 'Detach Project',
|
|
180
|
+
project_hint: 'Attach will create .omnimem files and inject managed memory protocol blocks into AGENTS.md / CLAUDE.md / .cursorrules.',
|
|
161
181
|
cfg_saved: 'Configuration saved', cfg_failed: 'Save failed',
|
|
182
|
+
project_attach_ok: 'Project attached', project_detach_ok: 'Project detached', project_failed: 'Project action failed',
|
|
162
183
|
init_ok: 'Config state: initialized', init_hint_ok: 'Daemon runs quasi-realtime sync in background (can be disabled).',
|
|
163
184
|
init_missing: 'Config state: not initialized (save configuration first)', init_hint_missing: 'Daemon is disabled until configuration is initialized.',
|
|
164
185
|
daemon_state: (d) => `Daemon: ${d.running ? 'running' : 'stopped'}, enabled=${d.enabled}, initialized=${d.initialized}`
|
|
@@ -363,6 +384,23 @@ HTML_PAGE = """<!doctype html>
|
|
|
363
384
|
await loadDaemon();
|
|
364
385
|
}
|
|
365
386
|
|
|
387
|
+
async function attachProject() {
|
|
388
|
+
const project_path = document.getElementById('projectPath').value.trim();
|
|
389
|
+
const project_id = document.getElementById('projectId').value.trim();
|
|
390
|
+
const out = document.getElementById('projectOut');
|
|
391
|
+
const d = await jpost('/api/project/attach', {project_path, project_id});
|
|
392
|
+
out.textContent = JSON.stringify(d, null, 2);
|
|
393
|
+
document.getElementById('status').innerHTML = d.ok ? `<span class=\"ok\">${t('project_attach_ok')}</span>` : `<span class=\"err\">${t('project_failed')}</span>`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function detachProject() {
|
|
397
|
+
const project_path = document.getElementById('projectPath').value.trim();
|
|
398
|
+
const out = document.getElementById('projectOut');
|
|
399
|
+
const d = await jpost('/api/project/detach', {project_path});
|
|
400
|
+
out.textContent = JSON.stringify(d, null, 2);
|
|
401
|
+
document.getElementById('status').innerHTML = d.ok ? `<span class=\"ok\">${t('project_detach_ok')}</span>` : `<span class=\"err\">${t('project_failed')}</span>`;
|
|
402
|
+
}
|
|
403
|
+
|
|
366
404
|
function bindTabs() {
|
|
367
405
|
const btns = document.querySelectorAll('.tab-btn');
|
|
368
406
|
btns.forEach(btn => {
|
|
@@ -389,6 +427,8 @@ HTML_PAGE = """<!doctype html>
|
|
|
389
427
|
document.getElementById('btnSyncPull').onclick = () => runSync('github-pull');
|
|
390
428
|
document.getElementById('btnDaemonOn').onclick = () => toggleDaemon(true);
|
|
391
429
|
document.getElementById('btnDaemonOff').onclick = () => toggleDaemon(false);
|
|
430
|
+
document.getElementById('btnProjectAttach').onclick = () => attachProject();
|
|
431
|
+
document.getElementById('btnProjectDetach').onclick = () => detachProject();
|
|
392
432
|
}
|
|
393
433
|
|
|
394
434
|
window.addEventListener('error', (e) => {
|
|
@@ -425,6 +465,137 @@ def _cfg_to_ui(cfg: dict[str, Any], cfg_path: Path) -> dict[str, Any]:
|
|
|
425
465
|
}
|
|
426
466
|
|
|
427
467
|
|
|
468
|
+
def _upsert_managed_block(path: Path, block: str) -> None:
|
|
469
|
+
start = "<!-- OMNIMEM:START -->"
|
|
470
|
+
end = "<!-- OMNIMEM:END -->"
|
|
471
|
+
managed = f"{start}\n{block.rstrip()}\n{end}\n"
|
|
472
|
+
if path.exists():
|
|
473
|
+
old = path.read_text(encoding="utf-8")
|
|
474
|
+
if start in old and end in old:
|
|
475
|
+
left = old.split(start, 1)[0].rstrip()
|
|
476
|
+
right = old.split(end, 1)[1].lstrip()
|
|
477
|
+
new_text = f"{left}\n\n{managed}"
|
|
478
|
+
if right:
|
|
479
|
+
new_text += f"\n{right}"
|
|
480
|
+
path.write_text(new_text, encoding="utf-8")
|
|
481
|
+
return
|
|
482
|
+
sep = "\n\n" if old and not old.endswith("\n\n") else ""
|
|
483
|
+
path.write_text(old + sep + managed, encoding="utf-8")
|
|
484
|
+
return
|
|
485
|
+
path.write_text(managed, encoding="utf-8")
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _agent_protocol_block(project_id: str) -> str:
|
|
489
|
+
return (
|
|
490
|
+
"# OmniMem Memory Protocol\n"
|
|
491
|
+
"\n"
|
|
492
|
+
f"- Project ID: `{project_id}`\n"
|
|
493
|
+
"- Session start: run `omnimem brief --project-id <PROJECT_ID> --limit 8` and use it as active context.\n"
|
|
494
|
+
"- During task: when a stable decision/fact appears, run `omnimem write` with concise summary + evidence.\n"
|
|
495
|
+
"- Phase end: run `omnimem checkpoint` with goal/result/next-step/risks.\n"
|
|
496
|
+
"- If confidence is low or info is temporary, store in `instant`/`short`; promote to `long` only when repeated and stable.\n"
|
|
497
|
+
"- Never write raw secrets. Use credential references only (for example `op://...` or `env://...`).\n"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _attach_project_in_webui(project_path: str, project_id: str, cfg_home: str) -> dict[str, Any]:
|
|
502
|
+
if not project_path:
|
|
503
|
+
return {"ok": False, "error": "project_path is required"}
|
|
504
|
+
project = Path(project_path).expanduser().resolve()
|
|
505
|
+
if not project.exists() or not project.is_dir():
|
|
506
|
+
return {"ok": False, "error": f"project path not found: {project}"}
|
|
507
|
+
if not project_id:
|
|
508
|
+
project_id = project.name
|
|
509
|
+
|
|
510
|
+
repo_root = Path(__file__).resolve().parent.parent
|
|
511
|
+
tpl = repo_root / "templates" / "project-minimal"
|
|
512
|
+
created: list[str] = []
|
|
513
|
+
updated: list[str] = []
|
|
514
|
+
|
|
515
|
+
files = [
|
|
516
|
+
(tpl / ".omnimem.json", project / ".omnimem.json"),
|
|
517
|
+
(tpl / ".omnimem-session.md", project / ".omnimem-session.md"),
|
|
518
|
+
(tpl / ".omnimem-ignore", project / ".omnimem-ignore"),
|
|
519
|
+
]
|
|
520
|
+
for src, dst in files:
|
|
521
|
+
text = src.read_text(encoding="utf-8")
|
|
522
|
+
text = text.replace("replace-with-project-id", project_id)
|
|
523
|
+
text = text.replace("~/.omnimem", cfg_home or "~/.omnimem")
|
|
524
|
+
exists = dst.exists()
|
|
525
|
+
dst.write_text(text, encoding="utf-8")
|
|
526
|
+
(updated if exists else created).append(str(dst))
|
|
527
|
+
|
|
528
|
+
block = _agent_protocol_block(project_id=project_id)
|
|
529
|
+
managed_targets = [
|
|
530
|
+
project / "AGENTS.md",
|
|
531
|
+
project / "CLAUDE.md",
|
|
532
|
+
project / ".cursorrules",
|
|
533
|
+
]
|
|
534
|
+
for fp in managed_targets:
|
|
535
|
+
exists = fp.exists()
|
|
536
|
+
_upsert_managed_block(fp, block)
|
|
537
|
+
(updated if exists else created).append(str(fp))
|
|
538
|
+
|
|
539
|
+
cursor_rule = project / ".cursor" / "rules" / "omnimem.mdc"
|
|
540
|
+
cursor_exists = cursor_rule.exists()
|
|
541
|
+
cursor_rule.parent.mkdir(parents=True, exist_ok=True)
|
|
542
|
+
cursor_rule.write_text(
|
|
543
|
+
(
|
|
544
|
+
"---\n"
|
|
545
|
+
"description: OmniMem project memory protocol\n"
|
|
546
|
+
"alwaysApply: true\n"
|
|
547
|
+
"---\n\n"
|
|
548
|
+
+ block
|
|
549
|
+
),
|
|
550
|
+
encoding="utf-8",
|
|
551
|
+
)
|
|
552
|
+
(updated if cursor_exists else created).append(str(cursor_rule))
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
"ok": True,
|
|
556
|
+
"project_path": str(project),
|
|
557
|
+
"project_id": project_id,
|
|
558
|
+
"created": created,
|
|
559
|
+
"updated": updated,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _detach_project_in_webui(project_path: str) -> dict[str, Any]:
|
|
564
|
+
if not project_path:
|
|
565
|
+
return {"ok": False, "error": "project_path is required"}
|
|
566
|
+
project = Path(project_path).expanduser().resolve()
|
|
567
|
+
if not project.exists() or not project.is_dir():
|
|
568
|
+
return {"ok": False, "error": f"project path not found: {project}"}
|
|
569
|
+
|
|
570
|
+
removed: list[str] = []
|
|
571
|
+
for name in [
|
|
572
|
+
".omnimem.json",
|
|
573
|
+
".omnimem-session.md",
|
|
574
|
+
".omnimem-ignore",
|
|
575
|
+
".cursorrules",
|
|
576
|
+
"CLAUDE.md",
|
|
577
|
+
"AGENTS.md",
|
|
578
|
+
".cursor/rules/omnimem.mdc",
|
|
579
|
+
]:
|
|
580
|
+
fp = project / name
|
|
581
|
+
if fp.exists():
|
|
582
|
+
txt = fp.read_text(encoding="utf-8", errors="ignore")
|
|
583
|
+
if "<!-- OMNIMEM:START -->" in txt and "<!-- OMNIMEM:END -->" in txt:
|
|
584
|
+
start = txt.index("<!-- OMNIMEM:START -->")
|
|
585
|
+
end = txt.index("<!-- OMNIMEM:END -->") + len("<!-- OMNIMEM:END -->")
|
|
586
|
+
new_txt = (txt[:start] + txt[end:]).strip()
|
|
587
|
+
if new_txt:
|
|
588
|
+
fp.write_text(new_txt + "\n", encoding="utf-8")
|
|
589
|
+
else:
|
|
590
|
+
fp.unlink()
|
|
591
|
+
removed.append(str(fp))
|
|
592
|
+
continue
|
|
593
|
+
if fp.name in {".omnimem.json", ".omnimem-session.md", ".omnimem-ignore", "omnimem.mdc"}:
|
|
594
|
+
fp.unlink()
|
|
595
|
+
removed.append(str(fp))
|
|
596
|
+
return {"ok": True, "project_path": str(project), "removed": removed}
|
|
597
|
+
|
|
598
|
+
|
|
428
599
|
def run_webui(
|
|
429
600
|
*,
|
|
430
601
|
host: str,
|
|
@@ -516,6 +687,16 @@ def run_webui(
|
|
|
516
687
|
self._send_json({"ok": True, **daemon_state})
|
|
517
688
|
return
|
|
518
689
|
|
|
690
|
+
if parsed.path == "/api/project/defaults":
|
|
691
|
+
self._send_json(
|
|
692
|
+
{
|
|
693
|
+
"ok": True,
|
|
694
|
+
"project_path": "",
|
|
695
|
+
"project_id": "",
|
|
696
|
+
}
|
|
697
|
+
)
|
|
698
|
+
return
|
|
699
|
+
|
|
519
700
|
if parsed.path == "/api/memories":
|
|
520
701
|
q = parse_qs(parsed.query)
|
|
521
702
|
limit = int(q.get("limit", ["20"])[0])
|
|
@@ -612,6 +793,26 @@ def run_webui(
|
|
|
612
793
|
)
|
|
613
794
|
return
|
|
614
795
|
|
|
796
|
+
if parsed.path == "/api/project/attach":
|
|
797
|
+
try:
|
|
798
|
+
out = _attach_project_in_webui(
|
|
799
|
+
project_path=str(data.get("project_path", "")).strip(),
|
|
800
|
+
project_id=str(data.get("project_id", "")).strip(),
|
|
801
|
+
cfg_home=str(cfg.get("home", "")).strip(),
|
|
802
|
+
)
|
|
803
|
+
self._send_json(out, 200 if out.get("ok") else 400)
|
|
804
|
+
except Exception as exc: # pragma: no cover
|
|
805
|
+
self._send_json({"ok": False, "error": str(exc)}, 500)
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
if parsed.path == "/api/project/detach":
|
|
809
|
+
try:
|
|
810
|
+
out = _detach_project_in_webui(str(data.get("project_path", "")).strip())
|
|
811
|
+
self._send_json(out, 200 if out.get("ok") else 400)
|
|
812
|
+
except Exception as exc: # pragma: no cover
|
|
813
|
+
self._send_json({"ok": False, "error": str(exc)}, 500)
|
|
814
|
+
return
|
|
815
|
+
|
|
615
816
|
self._send_json({"ok": False, "error": "not found"}, 404)
|
|
616
817
|
|
|
617
818
|
server = ThreadingHTTPServer((host, port), Handler)
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OmniMem Session Entry
|
|
2
2
|
|
|
3
3
|
- Current phase:
|
|
4
4
|
- Current objective:
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
## Quick start
|
|
10
10
|
|
|
11
|
-
1. Run `
|
|
11
|
+
1. Run `omnimem brief --project-id <project_id>`
|
|
12
12
|
2. Review top 3 recent checkpoints
|
|
13
13
|
3. Continue task or start a new session
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.0",
|
|
3
3
|
"project_id": "replace-with-project-id",
|
|
4
|
-
"memory_home": "~/.
|
|
4
|
+
"memory_home": "~/.omnimem",
|
|
5
5
|
"default_tags": ["project:replace-with-project-id"],
|
|
6
6
|
"collect": {
|
|
7
7
|
"enabled": true,
|
|
8
|
-
"respect_ignore_file": ".
|
|
8
|
+
"respect_ignore_file": ".omnimem-ignore"
|
|
9
9
|
}
|
|
10
10
|
}
|