omnimem 0.1.6 → 0.1.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.
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.1.6"
2
+ __version__ = "0.1.7"
package/omnimem/webui.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import os
4
5
  import sqlite3
5
6
  import threading
6
7
  import time
@@ -127,6 +128,20 @@ HTML_PAGE = """<!doctype html>
127
128
  <div class=\"card wide\">
128
129
  <h3 data-i18n=\"project_title\">Project Integration</h3>
129
130
  <label><span data-i18n=\"project_path\">Project Path</span><input id=\"projectPath\" placeholder=\"/path/to/your/project\" /></label>
131
+ <div class=\"row-btn\">
132
+ <button id=\"btnBrowseProject\" data-i18n=\"btn_browse_project\">Browse Directory</button>
133
+ <button id=\"btnUseCwd\" data-i18n=\"btn_use_cwd\">Use Server CWD</button>
134
+ </div>
135
+ <div id=\"browserPanel\" class=\"card\" style=\"margin-top:10px; display:none;\">
136
+ <div class=\"small\" data-i18n=\"browser_title\">Directory Browser</div>
137
+ <div id=\"browserPath\" class=\"small\" style=\"margin:8px 0\"></div>
138
+ <div class=\"row-btn\">
139
+ <button id=\"btnBrowserUp\" data-i18n=\"btn_browser_up\">Up</button>
140
+ <button id=\"btnBrowserSelect\" data-i18n=\"btn_browser_select\">Select This Directory</button>
141
+ <button id=\"btnBrowserClose\" data-i18n=\"btn_browser_close\">Close</button>
142
+ </div>
143
+ <div id=\"browserList\" class=\"small\" style=\"margin-top:8px\"></div>
144
+ </div>
130
145
  <label><span data-i18n=\"project_id\">Project ID</span><input id=\"projectId\" placeholder=\"my-project\" /></label>
131
146
  <div class=\"row-btn\">
132
147
  <button id=\"btnProjectAttach\" data-i18n=\"btn_project_attach\">Attach Project + Install Agent Rules</button>
@@ -176,6 +191,8 @@ HTML_PAGE = """<!doctype html>
176
191
  mem_recent: 'Recent Memories', mem_hint: 'Click an ID to open full content', mem_content: 'Memory Content',
177
192
  th_id: 'ID', th_layer: 'Layer', th_kind: 'Kind', th_summary: 'Summary', th_updated: 'Updated At',
178
193
  project_title: 'Project Integration', project_path: 'Project Path', project_id: 'Project ID',
194
+ btn_browse_project: 'Browse Directory', btn_use_cwd: 'Use Server CWD',
195
+ browser_title: 'Directory Browser', btn_browser_up: 'Up', btn_browser_select: 'Select This Directory', btn_browser_close: 'Close',
179
196
  btn_project_attach: 'Attach Project + Install Agent Rules', btn_project_detach: 'Detach Project',
180
197
  project_hint: 'Attach will create .omnimem files and inject managed memory protocol blocks into AGENTS.md / CLAUDE.md / .cursorrules.',
181
198
  cfg_saved: 'Configuration saved', cfg_failed: 'Save failed',
@@ -293,6 +310,7 @@ HTML_PAGE = """<!doctype html>
293
310
  let currentLang = safeGetLang();
294
311
  if (!I18N[currentLang]) currentLang = 'en';
295
312
  let daemonCache = { running:false, enabled:false, initialized:false };
313
+ let browserPath = '';
296
314
 
297
315
  function t(key) {
298
316
  const dict = I18N[currentLang] || I18N.en;
@@ -401,6 +419,30 @@ HTML_PAGE = """<!doctype html>
401
419
  document.getElementById('status').innerHTML = d.ok ? `<span class=\"ok\">${t('project_detach_ok')}</span>` : `<span class=\"err\">${t('project_failed')}</span>`;
402
420
  }
403
421
 
422
+ function escHtml(v) {
423
+ return String(v).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('\"', '&quot;');
424
+ }
425
+
426
+ async function listDirs(path) {
427
+ const d = await jget('/api/fs/list?path=' + encodeURIComponent(path || ''));
428
+ if (!d.ok) {
429
+ document.getElementById('browserList').innerHTML = `<span class=\"err\">${escHtml(d.error || 'list failed')}</span>`;
430
+ return;
431
+ }
432
+ browserPath = d.path || '';
433
+ document.getElementById('browserPath').textContent = browserPath;
434
+ const rows = (d.items || [])
435
+ .map(x => `<div><a href=\"#\" data-path=\"${escHtml(x.path)}\">${escHtml(x.name)}/</a></div>`)
436
+ .join('');
437
+ document.getElementById('browserList').innerHTML = rows || '<span class=\"small\">(empty)</span>';
438
+ document.querySelectorAll('#browserList a').forEach(a => {
439
+ a.onclick = (e) => {
440
+ e.preventDefault();
441
+ listDirs(a.dataset.path || '');
442
+ };
443
+ });
444
+ }
445
+
404
446
  function bindTabs() {
405
447
  const btns = document.querySelectorAll('.tab-btn');
406
448
  btns.forEach(btn => {
@@ -429,6 +471,39 @@ HTML_PAGE = """<!doctype html>
429
471
  document.getElementById('btnDaemonOff').onclick = () => toggleDaemon(false);
430
472
  document.getElementById('btnProjectAttach').onclick = () => attachProject();
431
473
  document.getElementById('btnProjectDetach').onclick = () => detachProject();
474
+ document.getElementById('btnBrowseProject').onclick = async () => {
475
+ document.getElementById('browserPanel').style.display = 'block';
476
+ await listDirs(document.getElementById('projectPath').value.trim() || '');
477
+ };
478
+ document.getElementById('btnBrowserUp').onclick = async () => {
479
+ if (!browserPath) return;
480
+ const p = browserPath.replace(/\/+$/, '');
481
+ const i = p.lastIndexOf('/');
482
+ const up = i > 0 ? p.slice(0, i) : '/';
483
+ await listDirs(up);
484
+ };
485
+ document.getElementById('btnBrowserSelect').onclick = () => {
486
+ document.getElementById('projectPath').value = browserPath;
487
+ const pid = document.getElementById('projectId');
488
+ if (!pid.value.trim() && browserPath) {
489
+ const s = browserPath.replace(/\/+$/, '').split('/');
490
+ pid.value = s[s.length - 1] || 'project';
491
+ }
492
+ };
493
+ document.getElementById('btnBrowserClose').onclick = () => {
494
+ document.getElementById('browserPanel').style.display = 'none';
495
+ };
496
+ document.getElementById('btnUseCwd').onclick = async () => {
497
+ const d = await jget('/api/fs/cwd');
498
+ if (d.ok) {
499
+ document.getElementById('projectPath').value = d.cwd;
500
+ const pid = document.getElementById('projectId');
501
+ if (!pid.value.trim()) {
502
+ const s = d.cwd.replace(/\/+$/, '').split('/');
503
+ pid.value = s[s.length - 1] || 'project';
504
+ }
505
+ }
506
+ };
432
507
  }
433
508
 
434
509
  window.addEventListener('error', (e) => {
@@ -687,6 +762,30 @@ def run_webui(
687
762
  self._send_json({"ok": True, **daemon_state})
688
763
  return
689
764
 
765
+ if parsed.path == "/api/fs/cwd":
766
+ self._send_json({"ok": True, "cwd": str(Path.cwd())})
767
+ return
768
+
769
+ if parsed.path == "/api/fs/list":
770
+ q = parse_qs(parsed.query)
771
+ raw_path = q.get("path", [""])[0].strip()
772
+ base = Path(raw_path).expanduser() if raw_path else Path.home()
773
+ try:
774
+ p = base.resolve()
775
+ if not p.exists() or not p.is_dir():
776
+ self._send_json({"ok": False, "error": f"not a directory: {p}"}, 400)
777
+ return
778
+ items = []
779
+ for child in sorted(p.iterdir(), key=lambda x: x.name.lower()):
780
+ if child.is_dir() and not child.name.startswith("."):
781
+ items.append({"name": child.name, "path": str(child)})
782
+ if len(items) >= 200:
783
+ break
784
+ self._send_json({"ok": True, "path": str(p), "items": items})
785
+ except Exception as exc: # pragma: no cover
786
+ self._send_json({"ok": False, "error": str(exc)}, 500)
787
+ return
788
+
690
789
  if parsed.path == "/api/project/defaults":
691
790
  self._send_json(
692
791
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnimem",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "description": "OmniMem CLI and bootstrap runner",
6
6
  "license": "MIT",