mcp-memory-bucket 0.1.0 → 0.2.0
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/README.md +40 -14
- package/dist/client/assets/index-CXe_noum.js +344 -0
- package/dist/client/index.html +1 -1
- package/dist/src/config.js +77 -10
- package/dist/src/memory/repository.js +51 -7
- package/dist/src/memory/tools.js +11 -5
- package/dist/src/server.js +17 -9
- package/dist/src/shared/relocate-tool.js +2 -1
- package/dist/src/shared/relocate.js +2 -1
- package/dist/src/skills/builtin/memory-bucket-authoring/SKILL.md +24 -0
- package/dist/src/skills/repository.js +67 -13
- package/dist/src/skills/tools.js +12 -6
- package/dist/src/store/db.js +11 -0
- package/dist/src/store/sync.js +43 -10
- package/dist/src/web/routes.js +119 -7
- package/package.json +2 -2
- package/dist/client/assets/index-CxhInmjj.js +0 -150
package/README.md
CHANGED
|
@@ -29,18 +29,21 @@ Starts a stateless StreamableHTTP MCP server at `http://localhost:8767/mcp`
|
|
|
29
29
|
the SQLite cache is kept current by a file watcher for as long as the
|
|
30
30
|
server runs.
|
|
31
31
|
|
|
32
|
-
The same process also serves a
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
The same process also serves a browser UI at `http://localhost:8767/` for
|
|
33
|
+
searching/filtering skills and memory docs by tag, root, status, owner,
|
|
34
|
+
and fulltext (SQLite FTS5) — a way to review what's in the index without
|
|
35
|
+
going through an agent. It also manages **roots**: add a skill or memory
|
|
36
|
+
root by browsing the filesystem, or remove one (unregisters it and drops
|
|
37
|
+
its cached rows — never deletes files on disk). Editing individual skills
|
|
38
|
+
or memory docs still goes through the `skill_*`/`memory_*` tools or the
|
|
39
|
+
files directly. From an MCP session connected to this server, call
|
|
40
|
+
`bucket_open_ui` to get the URL. If no roots are configured yet, the UI
|
|
41
|
+
opens straight into a first-run "add your first root" screen. The UI is a
|
|
42
|
+
Lit + `avosignals` app built with Vite (`src/client/`, bundled to
|
|
43
|
+
`dist/client/`) — `npm run build` builds it (along with the server);
|
|
44
|
+
`npm start` does **not** rebuild it, so run `npm run build` again after
|
|
45
|
+
changing anything under `src/client/`. `npm run dev` rebuilds the client
|
|
46
|
+
on change alongside the server, for active UI development.
|
|
44
47
|
|
|
45
48
|
### Configuration
|
|
46
49
|
|
|
@@ -54,8 +57,31 @@ memory/skill sources. Override that with one of:
|
|
|
54
57
|
}
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
Paths are resolved relative to the working directory.
|
|
58
|
-
|
|
60
|
+
Paths are resolved relative to the working directory. If no
|
|
61
|
+
`skill_sources`/`memory_sources` key is present, each defaults to the
|
|
62
|
+
example above only when that directory already exists on disk;
|
|
63
|
+
otherwise the server starts with zero roots and the UI's first-run
|
|
64
|
+
screen offers to add one.
|
|
65
|
+
|
|
66
|
+
**Multiple roots** (e.g. a personal skills folder plus a shared company
|
|
67
|
+
repo) are supported — give each source a name instead of a bare path:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"skill_sources": [
|
|
72
|
+
{ "name": "personal", "path": "~/skills" },
|
|
73
|
+
{ "name": "company", "path": "../company-repo/skills" }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Bare-string and `{name, path}` entries can be mixed in the same array.
|
|
79
|
+
With a single root of a kind, `skill_create`/`memory_create`/etc. work
|
|
80
|
+
exactly as before. Once 2+ roots exist, those tools require an explicit
|
|
81
|
+
`root` argument (and `skill_list`/`memory_list` gain an optional `root`
|
|
82
|
+
filter) — every list/get response also includes which `root` each item
|
|
83
|
+
came from. Roots can be added or removed at runtime through the web UI
|
|
84
|
+
without a restart; adding one there also appends it to this config file.
|
|
59
85
|
|
|
60
86
|
- the `MEMORY_BUCKET_DIR` environment variable, or the `--memory-dir <path>`
|
|
61
87
|
CLI flag — either overrides the base directory that the (still-defaultable)
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const n of r.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&s(n)}).observe(document,{childList:!0,subtree:!0});function e(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function s(i){if(i.ep)return;i.ep=!0;const r=e(i);fetch(i.href,r)}})();/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2019 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/const O=globalThis,D=O.ShadowRoot&&(O.ShadyCSS===void 0||O.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,I=Symbol(),V=new WeakMap;let ot=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==I)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(D&&t===void 0){const s=e!==void 0&&e.length===1;s&&(t=V.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&V.set(e,t))}return t}toString(){return this.cssText}};const pt=o=>new ot(typeof o=="string"?o:o+"",void 0,I),T=(o,...t)=>{const e=o.length===1?o[0]:t.reduce((s,i,r)=>s+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+o[r+1],o[0]);return new ot(e,o,I)},ut=(o,t)=>{if(D)o.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const e of t){const s=document.createElement("style"),i=O.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,o.appendChild(s)}},F=D?o=>o:o=>o instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return pt(e)})(o):o;/**
|
|
6
|
+
* @license
|
|
7
|
+
* Copyright 2017 Google LLC
|
|
8
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
9
|
+
*/const{is:ft,defineProperty:$t,getOwnPropertyDescriptor:gt,getOwnPropertyNames:mt,getOwnPropertySymbols:bt,getPrototypeOf:vt}=Object,M=globalThis,Y=M.trustedTypes,yt=Y?Y.emptyScript:"",xt=M.reactiveElementPolyfillSupport,S=(o,t)=>o,L={toAttribute(o,t){switch(t){case Boolean:o=o?yt:null;break;case Object:case Array:o=o==null?o:JSON.stringify(o)}return o},fromAttribute(o,t){let e=o;switch(t){case Boolean:e=o!==null;break;case Number:e=o===null?null:Number(o);break;case Object:case Array:try{e=JSON.parse(o)}catch{e=null}}return e}},rt=(o,t)=>!ft(o,t),J={attribute:!0,type:String,converter:L,reflect:!1,useDefault:!1,hasChanged:rt};Symbol.metadata??=Symbol("metadata"),M.litPropertyMetadata??=new WeakMap;let _=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=J){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&$t(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){const{get:i,set:r}=gt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:i,set(n){const d=i?.call(this);r?.call(this,n),this.requestUpdate(t,d,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??J}static _$Ei(){if(this.hasOwnProperty(S("elementProperties")))return;const t=vt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(S("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(S("properties"))){const e=this.properties,s=[...mt(e),...bt(e)];for(const i of s)this.createProperty(i,e[i])}const t=this[Symbol.metadata];if(t!==null){const e=litPropertyMetadata.get(t);if(e!==void 0)for(const[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(const[e,s]of this.elementProperties){const i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const i of s)e.unshift(F(i))}else t!==void 0&&e.push(F(t));return e}static _$Eu(t,e){const s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return ut(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){const s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){const r=(s.converter?.toAttribute!==void 0?s.converter:L).toAttribute(e,s.type);this._$Em=t,r==null?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(t,e){const s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){const r=s.getPropertyOptions(i),n=typeof r.converter=="function"?{fromAttribute:r.converter}:r.converter?.fromAttribute!==void 0?r.converter:L;this._$Em=i;const d=n.fromAttribute(e,r.type);this[i]=d??this._$Ej?.get(i)??d,this._$Em=null}}requestUpdate(t,e,s,i=!1,r){if(t!==void 0){const n=this.constructor;if(i===!1&&(r=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??rt)(r,e)||s.useDefault&&s.reflect&&r===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:r},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),r!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[i,r]of this._$Ep)this[i]=r;this._$Ep=void 0}const s=this.constructor.elementProperties;if(s.size>0)for(const[i,r]of s){const{wrapped:n}=r,d=this[i];n!==!0||this._$AL.has(i)||d===void 0||this.C(i,void 0,r,d)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};_.elementStyles=[],_.shadowRootOptions={mode:"open"},_[S("elementProperties")]=new Map,_[S("finalized")]=new Map,xt?.({ReactiveElement:_}),(M.reactiveElementVersions??=[]).push("2.1.2");/**
|
|
10
|
+
* @license
|
|
11
|
+
* Copyright 2017 Google LLC
|
|
12
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
13
|
+
*/const B=globalThis,Z=o=>o,U=B.trustedTypes,Q=U?U.createPolicy("lit-html",{createHTML:o=>o}):void 0,nt="$lit$",m=`lit$${Math.random().toFixed(9).slice(2)}$`,at="?"+m,_t=`<${at}>`,x=document,E=()=>x.createComment(""),C=o=>o===null||typeof o!="object"&&typeof o!="function",q=Array.isArray,wt=o=>q(o)||typeof o?.[Symbol.iterator]=="function",H=`[
|
|
14
|
+
\f\r]`,k=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,G=/-->/g,X=/>/g,v=RegExp(`>|${H}(?:([^\\s"'>=/]+)(${H}*=${H}*(?:[^
|
|
15
|
+
\f\r"'\`<>=]|("|')|))|$)`,"g"),tt=/'/g,et=/"/g,lt=/^(?:script|style|textarea|title)$/i,At=o=>(t,...e)=>({_$litType$:o,strings:t,values:e}),h=At(1),w=Symbol.for("lit-noChange"),p=Symbol.for("lit-nothing"),st=new WeakMap,y=x.createTreeWalker(x,129);function ct(o,t){if(!q(o)||!o.hasOwnProperty("raw"))throw Error("invalid template strings array");return Q!==void 0?Q.createHTML(t):t}const kt=(o,t)=>{const e=o.length-1,s=[];let i,r=t===2?"<svg>":t===3?"<math>":"",n=k;for(let d=0;d<e;d++){const a=o[d];let u,f,c=-1,$=0;for(;$<a.length&&(n.lastIndex=$,f=n.exec(a),f!==null);)$=n.lastIndex,n===k?f[1]==="!--"?n=G:f[1]!==void 0?n=X:f[2]!==void 0?(lt.test(f[2])&&(i=RegExp("</"+f[2],"g")),n=v):f[3]!==void 0&&(n=v):n===v?f[0]===">"?(n=i??k,c=-1):f[1]===void 0?c=-2:(c=n.lastIndex-f[2].length,u=f[1],n=f[3]===void 0?v:f[3]==='"'?et:tt):n===et||n===tt?n=v:n===G||n===X?n=k:(n=v,i=void 0);const g=n===v&&o[d+1].startsWith("/>")?" ":"";r+=n===k?a+_t:c>=0?(s.push(u),a.slice(0,c)+nt+a.slice(c)+m+g):a+m+(c===-2?d:g)}return[ct(o,r+(o[e]||"<?>")+(t===2?"</svg>":t===3?"</math>":"")),s]};class P{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let r=0,n=0;const d=t.length-1,a=this.parts,[u,f]=kt(t,e);if(this.el=P.createElement(u,s),y.currentNode=this.el.content,e===2||e===3){const c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(i=y.nextNode())!==null&&a.length<d;){if(i.nodeType===1){if(i.hasAttributes())for(const c of i.getAttributeNames())if(c.endsWith(nt)){const $=f[n++],g=i.getAttribute(c).split(m),z=/([.?@])?(.*)/.exec($);a.push({type:1,index:r,name:z[2],strings:g,ctor:z[1]==="."?Et:z[1]==="?"?Ct:z[1]==="@"?Pt:N}),i.removeAttribute(c)}else c.startsWith(m)&&(a.push({type:6,index:r}),i.removeAttribute(c));if(lt.test(i.tagName)){const c=i.textContent.split(m),$=c.length-1;if($>0){i.textContent=U?U.emptyScript:"";for(let g=0;g<$;g++)i.append(c[g],E()),y.nextNode(),a.push({type:2,index:++r});i.append(c[$],E())}}}else if(i.nodeType===8)if(i.data===at)a.push({type:2,index:r});else{let c=-1;for(;(c=i.data.indexOf(m,c+1))!==-1;)a.push({type:7,index:r}),c+=m.length-1}r++}}static createElement(t,e){const s=x.createElement("template");return s.innerHTML=t,s}}function A(o,t,e=o,s){if(t===w)return t;let i=s!==void 0?e._$Co?.[s]:e._$Cl;const r=C(t)?void 0:t._$litDirective$;return i?.constructor!==r&&(i?._$AO?.(!1),r===void 0?i=void 0:(i=new r(o),i._$AT(o,e,s)),s!==void 0?(e._$Co??=[])[s]=i:e._$Cl=i),i!==void 0&&(t=A(o,i._$AS(o,t.values),i,s)),t}class St{constructor(t,e){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:e},parts:s}=this._$AD,i=(t?.creationScope??x).importNode(e,!0);y.currentNode=i;let r=y.nextNode(),n=0,d=0,a=s[0];for(;a!==void 0;){if(n===a.index){let u;a.type===2?u=new R(r,r.nextSibling,this,t):a.type===1?u=new a.ctor(r,a.name,a.strings,this,t):a.type===6&&(u=new Rt(r,this,t)),this._$AV.push(u),a=s[++d]}n!==a?.index&&(r=y.nextNode(),n++)}return y.currentNode=x,i}p(t){let e=0;for(const s of this._$AV)s!==void 0&&(s.strings!==void 0?(s._$AI(t,s,e),e+=s.strings.length-2):s._$AI(t[e])),e++}}class R{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,e,s,i){this.type=2,this._$AH=p,this._$AN=void 0,this._$AA=t,this._$AB=e,this._$AM=s,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let t=this._$AA.parentNode;const e=this._$AM;return e!==void 0&&t?.nodeType===11&&(t=e.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,e=this){t=A(this,t,e),C(t)?t===p||t==null||t===""?(this._$AH!==p&&this._$AR(),this._$AH=p):t!==this._$AH&&t!==w&&this._(t):t._$litType$!==void 0?this.$(t):t.nodeType!==void 0?this.T(t):wt(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==p&&C(this._$AH)?this._$AA.nextSibling.data=t:this.T(x.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:s}=t,i=typeof s=="number"?this._$AC(t):(s.el===void 0&&(s.el=P.createElement(ct(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===i)this._$AH.p(e);else{const r=new St(i,this),n=r.u(this.options);r.p(e),this.T(n),this._$AH=r}}_$AC(t){let e=st.get(t.strings);return e===void 0&&st.set(t.strings,e=new P(t)),e}k(t){q(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let s,i=0;for(const r of t)i===e.length?e.push(s=new R(this.O(E()),this.O(E()),this,this.options)):s=e[i],s._$AI(r),i++;i<e.length&&(this._$AR(s&&s._$AB.nextSibling,i),e.length=i)}_$AR(t=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);t!==this._$AB;){const s=Z(t).nextSibling;Z(t).remove(),t=s}}setConnected(t){this._$AM===void 0&&(this._$Cv=t,this._$AP?.(t))}}class N{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,e,s,i,r){this.type=1,this._$AH=p,this._$AN=void 0,this.element=t,this.name=e,this._$AM=i,this.options=r,s.length>2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=p}_$AI(t,e=this,s,i){const r=this.strings;let n=!1;if(r===void 0)t=A(this,t,e,0),n=!C(t)||t!==this._$AH&&t!==w,n&&(this._$AH=t);else{const d=t;let a,u;for(t=r[0],a=0;a<r.length-1;a++)u=A(this,d[s+a],e,a),u===w&&(u=this._$AH[a]),n||=!C(u)||u!==this._$AH[a],u===p?t=p:t!==p&&(t+=(u??"")+r[a+1]),this._$AH[a]=u}n&&!i&&this.j(t)}j(t){t===p?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"")}}class Et extends N{constructor(){super(...arguments),this.type=3}j(t){this.element[this.name]=t===p?void 0:t}}class Ct extends N{constructor(){super(...arguments),this.type=4}j(t){this.element.toggleAttribute(this.name,!!t&&t!==p)}}class Pt extends N{constructor(t,e,s,i,r){super(t,e,s,i,r),this.type=5}_$AI(t,e=this){if((t=A(this,t,e,0)??p)===w)return;const s=this._$AH,i=t===p&&s!==p||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,r=t!==p&&(s===p||i);i&&this.element.removeEventListener(this.name,this,s),r&&this.element.addEventListener(this.name,this,t),this._$AH=t}handleEvent(t){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t)}}class Rt{constructor(t,e,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=e,this.options=s}get _$AU(){return this._$AM._$AU}_$AI(t){A(this,t)}}const zt=B.litHtmlPolyfillSupport;zt?.(P,R),(B.litHtmlVersions??=[]).push("3.3.3");const Ot=(o,t,e)=>{const s=e?.renderBefore??t;let i=s._$litPart$;if(i===void 0){const r=e?.renderBefore??null;s._$litPart$=i=new R(t.insertBefore(E(),r),r,void 0,e??{})}return i._$AI(o),i};/**
|
|
16
|
+
* @license
|
|
17
|
+
* Copyright 2017 Google LLC
|
|
18
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
19
|
+
*/const K=globalThis;class b extends _{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=Ot(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return w}}b._$litElement$=!0,b.finalized=!0,K.litElementHydrateSupport?.({LitElement:b});const Ut=K.litElementPolyfillSupport;Ut?.({LitElement:b});(K.litElementVersions??=[]).push("4.2.2");var ht;let j=0;const it=new Set;function Tt(o){j++;try{o()}finally{j--,j===0&&Mt()}}function Mt(){const o=new Set(it);it.clear();for(const t of o)t()}function Nt(o,t){return o!=o?t==t:o!==t||o&&typeof o=="object"||typeof o=="function"}function Ht(o){return o&&typeof o.deref=="function"}class W{static#e=0;#t;#i;#s=new Set;#o=ht.#e++;name;constructor(t,e){this.#t=t,this.#i=t,this.name=e}_get(){return l.activeConsumer&&l.activeConsumer.track(this),this.#t}_set(t){return Nt(t,this.#t)?(this.#i=this.#t,this.#t=t,this._notify(),!0):!1}subscribe(t,e=!1){let s=t;return e&&(s=new WeakRef(t)),this.#s.add(s),()=>this.#s.delete(s)}_notify(){const t=[...this.#s];for(const e of t){let s;if(Ht(e)){if(s=e.deref(),!s){this.#s.delete(e);continue}}else s=e;s()}}get id(){return this.#o}get previousValue(){return this.#i}toString(){return this.name?`${this.constructor.name}(${this.name})`:`${this.constructor.name}#${this.id}`}}ht=W;class l extends W{static#e=[];constructor(t,e){super(t,e)}static get activeConsumer(){return this.#e[this.#e.length-1]}static push(t){this.#e.push(t)}static pop(){this.#e.pop()}get(){return this._get()}set(t){Tt(()=>{this._set(t)})}get value(){return this.get()}set value(t){this.set(t)}update(t){this.set(t(this.get()))}}class jt extends W{#e;#t=!0;#i=!1;#s=new Map;_debugParent;#o;#a=()=>{this.#t||(this.#t=!0,this._notify())};constructor(t,e,s){super(void 0,e),this.#e=t,this.#o=s?.weak??!0}track(t){if(this.#s.has(t))return;const e=t.subscribe(this.#a,this.#o);this.#s.set(t,e)}dispose(){this.#n()}#n(){for(const t of this.#s.values())t();this.#s.clear()}get(){if(l.activeConsumer&&l.activeConsumer.track(this),!this.#t)return this._get();if(this.#i)throw new Error(`Cycle detected in ${this}
|
|
20
|
+
↳ while computing ${this._debugParent??"root"}`);this.#i=!0,this.#n(),l.push(this);try{const t=this.#e();this._set(t)}finally{l.pop(),this.#t=!1,this.#i=!1}return this._get()}get value(){return this.get()}set value(t){throw new Error(`Cannot set value of Computed ${this}. Computed values are read-only.`)}subscribe(t,e=!1){const s=super.subscribe(t,e);return l.activeConsumer||this.get(),s}}class dt{#e;#t=new Map;_debugParent;constructor(t){this.#e=t,t.addController(this);const e=t,s=e.update,i=this;e.update=function(r){l.push(i);try{s.call(this,r)}finally{l.pop()}}}track(t){this.#t.has(t)||this.#t.set(t,t.subscribe(this.#i,!0))}#i=()=>{this.#e.requestUpdate()};hostUpdate(){for(const t of this.#t.values())t();this.#t.clear()}hostDisconnected(){for(const t of this.#t.values())t();this.#t.clear()}toString(){const t=this.#e;return`SignalWatcher(${t?.localName??t?.tagName??t?.constructor?.name??"unknown"})`}}class Lt extends b{static{this.properties={results:{attribute:!1},showRoot:{attribute:!1},onSelect:{attribute:!1}}}static{this.styles=T`
|
|
21
|
+
:host { display: block; }
|
|
22
|
+
.row { padding: 10px 14px; border-bottom: 1px solid #8882; cursor: pointer; }
|
|
23
|
+
.row:hover { background: #8881; }
|
|
24
|
+
.top { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; }
|
|
25
|
+
.name { font-weight: 600; }
|
|
26
|
+
.meta { opacity: 0.65; font-size: 11px; white-space: nowrap; }
|
|
27
|
+
.desc { font-size: 12px; opacity: 0.8; margin-top: 3px; }
|
|
28
|
+
.tags { margin-top: 4px; display: flex; gap: 4px; flex-wrap: wrap; }
|
|
29
|
+
.tag { font-size: 10px; border: 1px solid #8886; border-radius: 999px; padding: 1px 6px; }
|
|
30
|
+
.type-badge { font-size: 10px; text-transform: uppercase; opacity: 0.6; }
|
|
31
|
+
.root-badge {
|
|
32
|
+
font-size: 10px; text-transform: uppercase; opacity: 0.75; border: 1px solid #7c3aed88;
|
|
33
|
+
color: #7c3aed; border-radius: 4px; padding: 0 4px;
|
|
34
|
+
}
|
|
35
|
+
.empty { padding: 24px; opacity: 0.6; font-size: 13px; }
|
|
36
|
+
`}render(){return!this.results||this.results.length===0?h`<div class="empty">No results.</div>`:h`
|
|
37
|
+
${this.results.map(t=>h`
|
|
38
|
+
<div class="row" @click=${()=>this.onSelect(t)}>
|
|
39
|
+
<div class="top">
|
|
40
|
+
<span class="name">${t.name}</span>
|
|
41
|
+
<span class="meta">${t.owner??"—"} · ${t.status}</span>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="desc">${t.description}</div>
|
|
44
|
+
<div class="tags">
|
|
45
|
+
<span class="type-badge">${t._table==="skills"?"skill":"memory"}</span>
|
|
46
|
+
${this.showRoot&&t.root?h`<span class="root-badge">${t.root}</span>`:""}
|
|
47
|
+
${t.tags.map(e=>h`<span class="tag">${e}</span>`)}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
`)}
|
|
51
|
+
`}}customElements.define("result-list",Lt);class Dt extends b{static{this.properties={selected:{attribute:!1},_doc:{state:!0}}}static{this.styles=T`
|
|
52
|
+
:host { display: block; padding: 16px; }
|
|
53
|
+
h2 { margin: 0 0 4px; font-size: 16px; }
|
|
54
|
+
.meta { font-size: 12px; opacity: 0.7; margin-bottom: 12px; }
|
|
55
|
+
.source-path {
|
|
56
|
+
font-family: monospace;
|
|
57
|
+
font-size: 11px;
|
|
58
|
+
opacity: 0.6;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
word-break: break-all;
|
|
61
|
+
}
|
|
62
|
+
pre {
|
|
63
|
+
white-space: pre-wrap;
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
background: #8881;
|
|
66
|
+
padding: 12px;
|
|
67
|
+
border-radius: 6px;
|
|
68
|
+
max-height: 60vh;
|
|
69
|
+
overflow-y: auto;
|
|
70
|
+
}
|
|
71
|
+
.empty { opacity: 0.5; font-size: 13px; }
|
|
72
|
+
`}updated(t){t.has("selected")&&this.selected&&this.#e()}async#e(){const{table:t,id:e}=this.selected,s=await fetch(`/api/entries/${t}/${encodeURIComponent(e)}`);this._doc=s.ok?await s.json():null}#t(){this._doc?.source_path&&navigator.clipboard?.writeText(this._doc.source_path)}render(){if(!this.selected)return h`<div class="empty">Select an entry to view its details.</div>`;if(!this._doc)return p;const t=this._doc;return h`
|
|
73
|
+
<h2>${t.name??t.key??t.id}</h2>
|
|
74
|
+
<div class="meta">
|
|
75
|
+
${t.tags?.join(", ")||"no tags"} · ${t.status}${t.owner?` · owner: ${t.owner}`:""}
|
|
76
|
+
</div>
|
|
77
|
+
<div class="meta">${t.description}</div>
|
|
78
|
+
<pre>${t.body}</pre>
|
|
79
|
+
<div class="source-path" title="click to copy" @click=${()=>this.#t()}>${t.source_path}</div>
|
|
80
|
+
`}}customElements.define("detail-panel",Dt);class It extends b{static{this.properties={defaultKind:{attribute:!1},lockKind:{attribute:!1},onAdded:{attribute:!1},onCancel:{attribute:!1}}}#e=new l("skill");#t=new l("");#i=new l([]);#s=new l(null);#o=new l("");#a=new l("");#n=new l("");#c=new l(!1);#r=new l(!1);static{this.styles=T`
|
|
81
|
+
:host { display: block; }
|
|
82
|
+
.backdrop {
|
|
83
|
+
position: fixed; inset: 0; background: #0008;
|
|
84
|
+
display: flex; align-items: center; justify-content: center; z-index: 100;
|
|
85
|
+
}
|
|
86
|
+
.modal {
|
|
87
|
+
background: var(--modal-bg, Canvas);
|
|
88
|
+
color: inherit;
|
|
89
|
+
border-radius: 10px;
|
|
90
|
+
width: min(560px, 92vw);
|
|
91
|
+
max-height: 82vh;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
box-shadow: 0 8px 40px #0006;
|
|
95
|
+
border: 1px solid #8884;
|
|
96
|
+
}
|
|
97
|
+
.header { padding: 16px 20px 8px; }
|
|
98
|
+
h2 { margin: 0; font-size: 16px; }
|
|
99
|
+
.kind-toggle { display: flex; gap: 8px; padding: 0 20px 12px; }
|
|
100
|
+
.kind-toggle button {
|
|
101
|
+
flex: 1; padding: 8px; border: 1px solid #8886; background: none; color: inherit;
|
|
102
|
+
border-radius: 6px; cursor: pointer; font-size: 13px;
|
|
103
|
+
}
|
|
104
|
+
.kind-toggle button.active { background: #2563eb; border-color: #2563eb; color: white; }
|
|
105
|
+
.breadcrumb-row {
|
|
106
|
+
padding: 8px 20px; display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
107
|
+
}
|
|
108
|
+
.breadcrumb {
|
|
109
|
+
font-size: 12px; opacity: 0.7; font-family: monospace;
|
|
110
|
+
word-break: break-all;
|
|
111
|
+
}
|
|
112
|
+
.hidden-toggle {
|
|
113
|
+
font-size: 11px; opacity: 0.7; display: flex; align-items: center; gap: 4px;
|
|
114
|
+
cursor: pointer; white-space: nowrap; user-select: none;
|
|
115
|
+
}
|
|
116
|
+
.browser {
|
|
117
|
+
flex: 1; overflow-y: auto; border-top: 1px solid #8883; border-bottom: 1px solid #8883;
|
|
118
|
+
min-height: 200px;
|
|
119
|
+
}
|
|
120
|
+
.entry {
|
|
121
|
+
padding: 8px 20px; cursor: pointer; font-size: 13px; display: flex; align-items: center; gap: 8px;
|
|
122
|
+
}
|
|
123
|
+
.entry:hover { background: #8881; }
|
|
124
|
+
.entry .icon { opacity: 0.6; }
|
|
125
|
+
.up { opacity: 0.7; }
|
|
126
|
+
.empty { padding: 16px 20px; opacity: 0.6; font-size: 13px; }
|
|
127
|
+
.footer { padding: 14px 20px; display: flex; flex-direction: column; gap: 10px; }
|
|
128
|
+
.selected-row { font-size: 12px; }
|
|
129
|
+
.selected-row .path { font-family: monospace; opacity: 0.85; word-break: break-all; }
|
|
130
|
+
label { font-size: 12px; opacity: 0.7; display: block; margin-bottom: 4px; }
|
|
131
|
+
input[type='text'] { width: 100%; box-sizing: border-box; padding: 7px 9px; font-size: 13px; }
|
|
132
|
+
.actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
|
|
133
|
+
button.primary {
|
|
134
|
+
background: #2563eb; border: 1px solid #2563eb; color: white; padding: 7px 16px;
|
|
135
|
+
border-radius: 6px; cursor: pointer; font-size: 13px;
|
|
136
|
+
}
|
|
137
|
+
button.primary:disabled { opacity: 0.5; cursor: default; }
|
|
138
|
+
button.secondary {
|
|
139
|
+
background: none; border: 1px solid #8886; color: inherit; padding: 7px 16px;
|
|
140
|
+
border-radius: 6px; cursor: pointer; font-size: 13px;
|
|
141
|
+
}
|
|
142
|
+
.error { color: #dc2626; font-size: 12px; }
|
|
143
|
+
`}constructor(){super(),new dt(this),this.defaultKind="skill",this.lockKind=!1}connectedCallback(){super.connectedCallback(),this.#e.set(this.defaultKind),this.#h()}async#h(t){const e=new URLSearchParams;t&&e.set("path",t),this.#r.value&&e.set("hidden","1");const s=await fetch(`/api/fs/list?${e.toString()}`);if(!s.ok){this.#n.set((await s.json()).error??"failed to list directory");return}const i=await s.json();this.#t.set(i.path),this.#s.set(i.parent),this.#i.set(i.entries),this.#n.set("")}#d(){this.#r.set(!this.#r.value),this.#h(this.#t.value)}#u(){this.#o.set(this.#t.value),this.#a.value||this.#a.set(this.#t.value.split("/").filter(Boolean).pop()??"")}async#l(){const t=this.#o.value||this.#t.value;if(t){this.#c.set(!0),this.#n.set("");try{const e=await fetch("/api/roots",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:this.#e.value,name:this.#a.value||void 0,path:t})}),s=await e.json();if(!e.ok){this.#n.set(s.error??"failed to add root");return}this.onAdded()}finally{this.#c.set(!1)}}}render(){const t=this.#i.value;return h`
|
|
144
|
+
<div class="backdrop" @click=${e=>e.target===e.currentTarget&&this.onCancel()}>
|
|
145
|
+
<div class="modal">
|
|
146
|
+
<div class="header"><h2>Add a root</h2></div>
|
|
147
|
+
${this.lockKind?h``:h`
|
|
148
|
+
<div class="kind-toggle">
|
|
149
|
+
<button
|
|
150
|
+
class=${this.#e.value==="skill"?"active":""}
|
|
151
|
+
@click=${()=>this.#e.set("skill")}
|
|
152
|
+
>
|
|
153
|
+
Skill root
|
|
154
|
+
</button>
|
|
155
|
+
<button
|
|
156
|
+
class=${this.#e.value==="memory"?"active":""}
|
|
157
|
+
@click=${()=>this.#e.set("memory")}
|
|
158
|
+
>
|
|
159
|
+
Memory root
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
`}
|
|
163
|
+
<div class="breadcrumb-row">
|
|
164
|
+
<div class="breadcrumb">${this.#t.value}</div>
|
|
165
|
+
<label class="hidden-toggle">
|
|
166
|
+
<input type="checkbox" .checked=${this.#r.value} @change=${()=>this.#d()} />
|
|
167
|
+
Show hidden
|
|
168
|
+
</label>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="browser">
|
|
171
|
+
${this.#s.value?h`<div class="entry up" @click=${()=>this.#h(this.#s.value)}>
|
|
172
|
+
<span class="icon">📁</span> ..
|
|
173
|
+
</div>`:""}
|
|
174
|
+
${t.length===0&&!this.#s.value?h`<div class="empty">No subdirectories here.</div>`:t.map(e=>h`<div class="entry" @click=${()=>this.#h(e.path)}>
|
|
175
|
+
<span class="icon">📁</span> ${e.name}
|
|
176
|
+
</div>`)}
|
|
177
|
+
</div>
|
|
178
|
+
<div class="footer">
|
|
179
|
+
<button class="secondary" @click=${()=>this.#u()}>
|
|
180
|
+
Select this folder
|
|
181
|
+
</button>
|
|
182
|
+
${this.#o.value?h`<div class="selected-row">Selected: <span class="path">${this.#o.value}</span></div>`:""}
|
|
183
|
+
<div>
|
|
184
|
+
<label>Root name</label>
|
|
185
|
+
<input
|
|
186
|
+
type="text"
|
|
187
|
+
.value=${this.#a.value}
|
|
188
|
+
placeholder="e.g. personal, company"
|
|
189
|
+
@input=${e=>this.#a.set(e.target.value)}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
${this.#n.value?h`<div class="error">${this.#n.value}</div>`:""}
|
|
193
|
+
<div class="actions">
|
|
194
|
+
<button class="secondary" @click=${()=>this.onCancel()}>Cancel</button>
|
|
195
|
+
<button
|
|
196
|
+
class="primary"
|
|
197
|
+
?disabled=${!this.#o.value||this.#c.value}
|
|
198
|
+
@click=${()=>this.#l()}
|
|
199
|
+
>
|
|
200
|
+
${this.#c.value?"Adding…":"Add root"}
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
`}}customElements.define("add-root-modal",It);const Bt=[{value:"all",label:"All"},{value:"skill",label:"Skills"},{value:"memory",label:"Memories"}],qt={tags:[],statuses:[],owners:[],doc_types:[],key_types:[],roots:[]},Kt={skill:[],memory:[]};class Wt extends b{static{this.styles=T`
|
|
207
|
+
:host { display: block; height: 100vh; }
|
|
208
|
+
.filters {
|
|
209
|
+
padding: 12px 16px;
|
|
210
|
+
border-bottom: 1px solid #8883;
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-direction: column;
|
|
213
|
+
gap: 8px;
|
|
214
|
+
}
|
|
215
|
+
.filters input[type='search'] {
|
|
216
|
+
font-size: 14px;
|
|
217
|
+
padding: 8px 10px;
|
|
218
|
+
width: 100%;
|
|
219
|
+
max-width: 480px;
|
|
220
|
+
box-sizing: border-box;
|
|
221
|
+
}
|
|
222
|
+
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
|
223
|
+
.filter-label { font-size: 12px; opacity: 0.6; font-weight: 600; }
|
|
224
|
+
.chip {
|
|
225
|
+
border: 1px solid #8886;
|
|
226
|
+
border-radius: 999px;
|
|
227
|
+
padding: 3px 10px;
|
|
228
|
+
font-size: 12px;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
background: none;
|
|
231
|
+
color: inherit;
|
|
232
|
+
}
|
|
233
|
+
.chip.active { background: #2563eb; border-color: #2563eb; color: white; }
|
|
234
|
+
.type-toggle button {
|
|
235
|
+
border: 1px solid #8886;
|
|
236
|
+
background: none;
|
|
237
|
+
color: inherit;
|
|
238
|
+
padding: 5px 12px;
|
|
239
|
+
font-size: 13px;
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
}
|
|
242
|
+
.type-toggle button.active { background: #2563eb; border-color: #2563eb; color: white; }
|
|
243
|
+
.type-toggle button:first-child { border-radius: 6px 0 0 6px; }
|
|
244
|
+
.type-toggle button:last-child { border-radius: 0 6px 6px 0; }
|
|
245
|
+
.body-region { display: flex; height: calc(100vh - 130px); }
|
|
246
|
+
result-list { flex: 1 1 40%; overflow-y: auto; border-right: 1px solid #8883; }
|
|
247
|
+
detail-panel { flex: 1 1 60%; overflow-y: auto; }
|
|
248
|
+
label.small { font-size: 12px; opacity: 0.7; }
|
|
249
|
+
.roots-bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
|
250
|
+
.root-chip {
|
|
251
|
+
border: 1px solid #8886; border-radius: 999px; padding: 3px 8px 3px 10px; font-size: 12px;
|
|
252
|
+
cursor: pointer; background: none; color: inherit; display: inline-flex; align-items: center; gap: 6px;
|
|
253
|
+
}
|
|
254
|
+
.root-chip.active { background: #7c3aed; border-color: #7c3aed; color: white; }
|
|
255
|
+
.root-chip .remove {
|
|
256
|
+
opacity: 0.6; font-size: 12px; line-height: 1; padding: 2px; border-radius: 50%;
|
|
257
|
+
}
|
|
258
|
+
.root-chip .remove:hover { opacity: 1; background: #0002; }
|
|
259
|
+
.add-root-btn {
|
|
260
|
+
border: 1px dashed #8886; border-radius: 999px; padding: 3px 10px; font-size: 12px;
|
|
261
|
+
cursor: pointer; background: none; color: inherit; opacity: 0.75;
|
|
262
|
+
}
|
|
263
|
+
.add-root-btn:hover { opacity: 1; }
|
|
264
|
+
.first-run {
|
|
265
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
266
|
+
height: 100vh; gap: 16px; text-align: center; padding: 24px;
|
|
267
|
+
}
|
|
268
|
+
.first-run h1 { font-size: 18px; margin: 0; }
|
|
269
|
+
.first-run p { opacity: 0.7; font-size: 13px; max-width: 420px; margin: 0; }
|
|
270
|
+
`}#e=new l("");#t=new l("all");#i=new l([]);#s=new l([]);#o=new l([]);#a=new l(qt);#n=new l(null);#c=new l(Kt);#r=new l(!1);#h=new l("");#d=new l(!1);constructor(){super(),new dt(this)}#u=new jt(()=>{const t=new URLSearchParams;this.#t.value!=="all"&&t.set("type",this.#t.value),this.#e.value.trim()&&t.set("q",this.#e.value.trim());for(const e of this.#i.value)t.append("tag",e);for(const e of this.#s.value)t.append("root",e);return t.toString()});connectedCallback(){super.connectedCallback(),this.#l(),this.#p(),this.#f()}async#l(){const t=await fetch(`/api/entries?${this.#u.value}`);this.#o.set(await t.json())}async#p(){const t=this.#t.value==="all"?"":`?type=${this.#t.value}`,e=await fetch(`/api/facets${t}`);this.#a.set(await e.json())}async#f(){const t=await fetch("/api/roots");this.#c.set(await t.json()),this.#d.set(!0)}#g(t){const e=this.#s.value;this.#s.set(e.includes(t)?e.filter(s=>s!==t):[...e,t]),this.#l()}async#m(t,e){if(e.stopPropagation(),window.prompt(`Type "${t.name}" to remove this ${t.kind} root (files on disk are untouched):`)===t.name){this.#h.set(t.name);try{await fetch(`/api/roots/${t.kind}/${encodeURIComponent(t.name)}`,{method:"DELETE"}),this.#s.set(this.#s.value.filter(i=>i!==t.name)),await Promise.all([this.#f(),this.#p(),this.#l()])}finally{this.#h.set("")}}}#$(){this.#r.set(!1),this.#f(),this.#p(),this.#l()}#b(){return[...this.#c.value.skill,...this.#c.value.memory]}#v(t){this.#t.set(t),this.#i.set([]),this.#p(),this.#l()}#y(t){const e=this.#i.value;this.#i.set(e.includes(t)?e.filter(s=>s!==t):[...e,t]),this.#l()}#x(t){this.#e.set(t.target.value),this.#l()}#_(t){this.#n.set({table:t._table,id:t.id})}render(){const t=this.#a.value,e=this.#b();return this.#d.value&&e.length===0&&!this.#r.value?h`
|
|
271
|
+
<div class="first-run">
|
|
272
|
+
<h1>No roots configured yet</h1>
|
|
273
|
+
<p>
|
|
274
|
+
Add a folder of skills or memory docs to get started. You can add more roots later —
|
|
275
|
+
e.g. a personal skills repo plus a shared company repo.
|
|
276
|
+
</p>
|
|
277
|
+
<button class="add-root-btn" @click=${()=>this.#r.set(!0)}>+ Add your first root</button>
|
|
278
|
+
</div>
|
|
279
|
+
${this.#r.value?h`<add-root-modal
|
|
280
|
+
.defaultKind=${"skill"}
|
|
281
|
+
.lockKind=${!1}
|
|
282
|
+
.onAdded=${()=>this.#$()}
|
|
283
|
+
.onCancel=${()=>this.#r.set(!1)}
|
|
284
|
+
></add-root-modal>`:""}
|
|
285
|
+
`:h`
|
|
286
|
+
<div class="filters">
|
|
287
|
+
<input
|
|
288
|
+
type="search"
|
|
289
|
+
placeholder="Search descriptions, bodies, tags..."
|
|
290
|
+
.value=${this.#e.value}
|
|
291
|
+
@input=${s=>this.#x(s)}
|
|
292
|
+
/>
|
|
293
|
+
<div class="row type-toggle">
|
|
294
|
+
${Bt.map(s=>h`
|
|
295
|
+
<button
|
|
296
|
+
class=${this.#t.value===s.value?"active":""}
|
|
297
|
+
@click=${()=>this.#v(s.value)}
|
|
298
|
+
>
|
|
299
|
+
${s.label}
|
|
300
|
+
</button>
|
|
301
|
+
`)}
|
|
302
|
+
</div>
|
|
303
|
+
<div class="row">
|
|
304
|
+
<span class="filter-label">Tags:</span>
|
|
305
|
+
${t.tags.length===0?h`<label class="small">no tags yet</label>`:t.tags.map(s=>h`
|
|
306
|
+
<button
|
|
307
|
+
class="chip ${this.#i.value.includes(s)?"active":""}"
|
|
308
|
+
@click=${()=>this.#y(s)}
|
|
309
|
+
>
|
|
310
|
+
${s}
|
|
311
|
+
</button>
|
|
312
|
+
`)}
|
|
313
|
+
</div>
|
|
314
|
+
<div class="roots-bar">
|
|
315
|
+
<span class="filter-label">Roots:</span>
|
|
316
|
+
${e.map(s=>h`
|
|
317
|
+
<button class="root-chip ${this.#s.value.includes(s.name)?"active":""}" @click=${()=>this.#g(s.name)}>
|
|
318
|
+
${s.name}
|
|
319
|
+
<span
|
|
320
|
+
class="remove"
|
|
321
|
+
title="Remove root"
|
|
322
|
+
@click=${i=>this.#m(s,i)}
|
|
323
|
+
>${this.#h.value===s.name?"…":"✕"}</span
|
|
324
|
+
>
|
|
325
|
+
</button>
|
|
326
|
+
`)}
|
|
327
|
+
<button class="add-root-btn" @click=${()=>this.#r.set(!0)}>+ Add root</button>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="body-region">
|
|
331
|
+
<result-list
|
|
332
|
+
.results=${this.#o.value}
|
|
333
|
+
.showRoot=${e.length>1}
|
|
334
|
+
.onSelect=${s=>this.#_(s)}
|
|
335
|
+
></result-list>
|
|
336
|
+
<detail-panel .selected=${this.#n.value}></detail-panel>
|
|
337
|
+
</div>
|
|
338
|
+
${this.#r.value?h`<add-root-modal
|
|
339
|
+
.defaultKind=${"skill"}
|
|
340
|
+
.lockKind=${!1}
|
|
341
|
+
.onAdded=${()=>this.#$()}
|
|
342
|
+
.onCancel=${()=>this.#r.set(!1)}
|
|
343
|
+
></add-root-modal>`:""}
|
|
344
|
+
`}}customElements.define("mem-bucket-app",Wt);
|
package/dist/client/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
:root { color-scheme: light dark; }
|
|
8
8
|
body { font-family: system-ui, sans-serif; margin: 0; }
|
|
9
9
|
</style>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CXe_noum.js"></script>
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<mem-bucket-app></mem-bucket-app>
|
package/dist/src/config.js
CHANGED
|
@@ -4,20 +4,87 @@ function memoryDirFlag(argv) {
|
|
|
4
4
|
const idx = argv.indexOf('--memory-dir');
|
|
5
5
|
return idx !== -1 ? argv[idx + 1] : undefined;
|
|
6
6
|
}
|
|
7
|
+
function readConfigFile(configPath) {
|
|
8
|
+
if (!fs.existsSync(configPath))
|
|
9
|
+
return {};
|
|
10
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
function nameFromPath(p) {
|
|
13
|
+
return path.basename(p);
|
|
14
|
+
}
|
|
15
|
+
function resolveRoots(entries, baseDir) {
|
|
16
|
+
const roots = entries.map((entry) => {
|
|
17
|
+
if (typeof entry === 'string') {
|
|
18
|
+
return { name: nameFromPath(entry), path: path.resolve(baseDir, entry) };
|
|
19
|
+
}
|
|
20
|
+
return { name: entry.name, path: path.resolve(baseDir, entry.path) };
|
|
21
|
+
});
|
|
22
|
+
// de-dupe by resolved path (e.g. default + explicit config both pointing at ./skills)
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
return roots.filter((r) => (seen.has(r.path) ? false : (seen.add(r.path), true)));
|
|
25
|
+
}
|
|
7
26
|
export function loadConfig(cwd = process.cwd(), argv = process.argv) {
|
|
8
|
-
const configPath = path.join(cwd, 'memory-bucket.config.json');
|
|
9
|
-
const hasConfigFile = fs.existsSync(configPath);
|
|
10
|
-
let overrides = {};
|
|
11
|
-
if (hasConfigFile) {
|
|
12
|
-
overrides = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
13
|
-
}
|
|
14
27
|
const explicitDir = memoryDirFlag(argv) ?? process.env.MEMORY_BUCKET_DIR;
|
|
15
28
|
const baseDir = explicitDir ? path.resolve(cwd, explicitDir) : cwd;
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
// The config file lives alongside the cache DB in baseDir (which is cwd
|
|
30
|
+
// itself unless --memory-dir/MEMORY_BUCKET_DIR points elsewhere) — this
|
|
31
|
+
// must match where saveRoot/removeRoot write back to.
|
|
32
|
+
const configPath = path.join(baseDir, 'memory-bucket.config.json');
|
|
33
|
+
const overrides = readConfigFile(configPath);
|
|
34
|
+
// Without an explicit config, only default to ./skills or ./docs when that
|
|
35
|
+
// directory already exists on disk — otherwise a genuinely fresh directory
|
|
36
|
+
// should report zero roots and trigger the first-run "add a root" UI,
|
|
37
|
+
// rather than silently registering a directory that was never created.
|
|
38
|
+
const defaultSkillSources = overrides.skill_sources ?? (fs.existsSync(path.resolve(baseDir, './skills')) ? ['./skills'] : []);
|
|
39
|
+
const defaultMemorySources = overrides.memory_sources ?? (fs.existsSync(path.resolve(baseDir, './docs')) ? ['./docs'] : []);
|
|
40
|
+
const skillRoots = resolveRoots(defaultSkillSources, baseDir);
|
|
41
|
+
const memoryRoots = resolveRoots(defaultMemorySources, baseDir);
|
|
18
42
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
43
|
+
skillRoots,
|
|
44
|
+
memoryRoots,
|
|
21
45
|
cacheDbPath: path.join(baseDir, '.memory-bucket-cache.sqlite'),
|
|
46
|
+
configPath,
|
|
47
|
+
baseDir,
|
|
22
48
|
};
|
|
23
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Appends a new named root to the config file's skill_sources/memory_sources array,
|
|
52
|
+
* creating the file if absent. Existing entries (bare strings or objects) are left
|
|
53
|
+
* untouched — this only ever appends, never rewrites/normalizes prior entries.
|
|
54
|
+
* Path is stored relative to baseDir when possible, for a portable config file.
|
|
55
|
+
*/
|
|
56
|
+
export function saveRoot(config, kind, root) {
|
|
57
|
+
const current = readConfigFile(config.configPath);
|
|
58
|
+
const key = kind === 'skill' ? 'skill_sources' : 'memory_sources';
|
|
59
|
+
// Only append to whatever's already in the file — never inject the
|
|
60
|
+
// ./skills or ./docs implicit default, since that default is applied at
|
|
61
|
+
// load time (loadConfig), not stored; writing it here would silently turn
|
|
62
|
+
// an implicit default into an explicit, possibly-unwanted config entry.
|
|
63
|
+
const existing = current[key] ?? [];
|
|
64
|
+
const storedPath = path.isAbsolute(root.path) ? path.relative(config.baseDir, root.path) || '.' : root.path;
|
|
65
|
+
const next = {
|
|
66
|
+
...current,
|
|
67
|
+
[key]: [...existing, { name: root.name, path: storedPath }],
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(config.configPath, JSON.stringify(next, null, 2) + '\n');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Removes a named root from the config file by name. Matches both explicit
|
|
73
|
+
* {name, path} entries and bare-string entries (via their derived name).
|
|
74
|
+
* No-op if the name isn't found (e.g. it only ever existed in-memory).
|
|
75
|
+
*/
|
|
76
|
+
export function removeRoot(config, kind, name) {
|
|
77
|
+
const current = readConfigFile(config.configPath);
|
|
78
|
+
const key = kind === 'skill' ? 'skill_sources' : 'memory_sources';
|
|
79
|
+
const existing = current[key];
|
|
80
|
+
if (!existing)
|
|
81
|
+
return;
|
|
82
|
+
const filtered = existing.filter((entry) => {
|
|
83
|
+
const entryName = typeof entry === 'string' ? nameFromPath(entry) : entry.name;
|
|
84
|
+
return entryName !== name;
|
|
85
|
+
});
|
|
86
|
+
if (filtered.length === existing.length)
|
|
87
|
+
return; // nothing matched, don't rewrite the file
|
|
88
|
+
const next = { ...current, [key]: filtered };
|
|
89
|
+
fs.writeFileSync(config.configPath, JSON.stringify(next, null, 2) + '\n');
|
|
90
|
+
}
|