@pinkpixel/memi 0.0.0-stage → 1.0.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/LICENSE +202 -0
- package/README.md +298 -2
- package/dist/cli/commands/config.js +38 -0
- package/dist/cli/commands/doctor.js +53 -0
- package/dist/cli/commands/memories.js +146 -0
- package/dist/cli/commands/reindex.js +34 -0
- package/dist/cli/commands/serve.js +9 -0
- package/dist/cli/commands/ui.js +26 -0
- package/dist/cli/index.js +20 -0
- package/dist/cli/util.js +46 -0
- package/dist/core/config.js +89 -0
- package/dist/core/db.js +21 -0
- package/dist/core/format.js +16 -0
- package/dist/core/memories.js +192 -0
- package/dist/core/project.js +53 -0
- package/dist/core/rows.js +38 -0
- package/dist/core/runtime.js +10 -0
- package/dist/core/schema.js +60 -0
- package/dist/core/search.js +86 -0
- package/dist/core/types.js +12 -0
- package/dist/core/vectors.js +102 -0
- package/dist/embed/http.js +20 -0
- package/dist/embed/index.js +9 -0
- package/dist/embed/ollama.js +24 -0
- package/dist/embed/openai.js +22 -0
- package/dist/embed/types.js +2 -0
- package/dist/lib/app-info.js +5 -0
- package/dist/mcp/server.js +24 -0
- package/dist/mcp/tools.js +152 -0
- package/dist/ui/assets.js +38 -0
- package/dist/ui/routes.js +91 -0
- package/dist/ui/security.js +47 -0
- package/dist/ui/server.js +33 -0
- package/package.json +63 -4
- package/skills/memi-curate/SKILL.md +89 -0
- package/skills/memi-memory/SKILL.md +141 -0
- package/skills/memi-memory/references/examples.md +50 -0
- package/skills/memi-memory/references/rubrics.md +44 -0
- package/skills/memi-setup/SKILL.md +159 -0
- package/ui/css/controls.css +274 -0
- package/ui/css/dialogs.css +145 -0
- package/ui/css/layout.css +237 -0
- package/ui/css/memories.css +168 -0
- package/ui/css/tokens.css +125 -0
- package/ui/favicon.svg +1 -0
- package/ui/fonts/OFL.txt +192 -0
- package/ui/fonts/geist-latin-wght-normal.woff2 +0 -0
- package/ui/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
- package/ui/index.html +75 -0
- package/ui/js/api.js +30 -0
- package/ui/js/confirm.js +29 -0
- package/ui/js/dom.js +49 -0
- package/ui/js/editor.js +133 -0
- package/ui/js/icons.js +29 -0
- package/ui/js/list.js +66 -0
- package/ui/js/main.js +244 -0
- package/ui/js/sidebar.js +85 -0
- package/ui/js/state.js +61 -0
- package/ui/js/toast.js +10 -0
package/ui/js/sidebar.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { $, clear, h } from "./dom.js";
|
|
2
|
+
import { state } from "./state.js";
|
|
3
|
+
|
|
4
|
+
function navItem(key, label, count, current, onclick, extra) {
|
|
5
|
+
return h(
|
|
6
|
+
"li",
|
|
7
|
+
{},
|
|
8
|
+
h(
|
|
9
|
+
"button",
|
|
10
|
+
{ class: "nav-item", type: "button", "data-key": key, "aria-current": current ? "true" : undefined, onclick },
|
|
11
|
+
h("span", { class: "label" }, label),
|
|
12
|
+
extra,
|
|
13
|
+
count == null ? null : h("span", { class: "count" }, String(count)),
|
|
14
|
+
),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const group = (title, ...children) => h("section", { class: "group" }, h("h2", { class: "group-title" }, title), ...children);
|
|
19
|
+
|
|
20
|
+
/** Rebuilds the filter list from the latest status. `onChange` runs after a filter is picked. */
|
|
21
|
+
export function renderSidebar(onChange) {
|
|
22
|
+
const status = state.status;
|
|
23
|
+
if (!status) return;
|
|
24
|
+
const { scope } = state;
|
|
25
|
+
const projectTotal = status.projects.reduce((sum, p) => sum + p.count, 0);
|
|
26
|
+
const pick = (patch) => () => {
|
|
27
|
+
Object.assign(state, patch);
|
|
28
|
+
onChange();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const scopes = h(
|
|
32
|
+
"ul",
|
|
33
|
+
{ class: "nav" },
|
|
34
|
+
navItem("scope-all", "All", status.total, scope.kind === "all", pick({ scope: { kind: "all", project: null } })),
|
|
35
|
+
navItem("scope-global", "Global", status.total - projectTotal, scope.kind === "global", pick({ scope: { kind: "global", project: null } })),
|
|
36
|
+
...status.projects.map((p) =>
|
|
37
|
+
navItem(
|
|
38
|
+
`scope-p-${p.project}`,
|
|
39
|
+
p.project,
|
|
40
|
+
p.count,
|
|
41
|
+
scope.kind === "project" && scope.project === p.project,
|
|
42
|
+
pick({ scope: { kind: "project", project: p.project } }),
|
|
43
|
+
p.project === status.currentProject ? h("span", { class: "here", title: "The project this was started in" }, "here") : null,
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Empty built-in categories are hidden to keep the list short, unless one is selected.
|
|
49
|
+
const shown = status.categories.filter((c) => c.count > 0 || c.name === state.category);
|
|
50
|
+
const categories = h(
|
|
51
|
+
"ul",
|
|
52
|
+
{ class: "nav" },
|
|
53
|
+
navItem("cat-all", "All", null, state.category === "", pick({ category: "" })),
|
|
54
|
+
...shown.map((c) => navItem(`cat-${c.name}`, c.name, c.count, state.category === c.name, pick({ category: c.name }))),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const importance = h(
|
|
58
|
+
"div",
|
|
59
|
+
{ class: "segmented", role: "group", "aria-label": "Minimum importance" },
|
|
60
|
+
...[
|
|
61
|
+
[0, "Any"],
|
|
62
|
+
[3, "3+"],
|
|
63
|
+
[4, "4+"],
|
|
64
|
+
[5, "5"],
|
|
65
|
+
].map(([value, label]) =>
|
|
66
|
+
h("button", { type: "button", "data-key": `imp-${value}`, "aria-pressed": state.minImportance === value ? "true" : "false", onclick: pick({ minImportance: value }) }, label),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const pinned = h(
|
|
71
|
+
"label",
|
|
72
|
+
{ class: "check-row" },
|
|
73
|
+
h("input", { type: "checkbox", "data-key": "pinned-only", checked: state.pinned, onchange: (e) => pick({ pinned: e.target.checked })() }),
|
|
74
|
+
"Pinned only",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
clear($("#filters")).append(group("Scope", scopes), group("Category", categories), group("Importance", h("div", { class: "nav" }, importance)), h("div", { class: "group" }, pinned));
|
|
78
|
+
|
|
79
|
+
const e = status.embedder;
|
|
80
|
+
clear($("#embedder")).append(
|
|
81
|
+
e ? `${e.provider}/${e.model}` : "no embedder",
|
|
82
|
+
h("br"),
|
|
83
|
+
`${status.vectors} of ${status.total} indexed`,
|
|
84
|
+
);
|
|
85
|
+
}
|
package/ui/js/state.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const KEY = "memi.ui";
|
|
2
|
+
const MODES = ["hybrid", "semantic", "text"];
|
|
3
|
+
const ORDERS = ["recent", "oldest", "importance"];
|
|
4
|
+
|
|
5
|
+
function loadPrefs() {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(localStorage.getItem(KEY) ?? "{}");
|
|
8
|
+
} catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const prefs = loadPrefs();
|
|
14
|
+
|
|
15
|
+
export const PAGE = 50;
|
|
16
|
+
|
|
17
|
+
export const state = {
|
|
18
|
+
status: null,
|
|
19
|
+
items: [],
|
|
20
|
+
total: 0,
|
|
21
|
+
modeUsed: null,
|
|
22
|
+
note: null,
|
|
23
|
+
error: null,
|
|
24
|
+
loading: true,
|
|
25
|
+
|
|
26
|
+
query: "",
|
|
27
|
+
mode: MODES.includes(prefs.mode) ? prefs.mode : "hybrid",
|
|
28
|
+
order: ORDERS.includes(prefs.order) ? prefs.order : "recent",
|
|
29
|
+
scope: { kind: "all", project: null },
|
|
30
|
+
category: "",
|
|
31
|
+
minImportance: 0,
|
|
32
|
+
pinned: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Search mode and sort order are remembered. Filters start fresh each visit. */
|
|
36
|
+
export function savePrefs() {
|
|
37
|
+
try {
|
|
38
|
+
localStorage.setItem(KEY, JSON.stringify({ mode: state.mode, order: state.order }));
|
|
39
|
+
} catch {
|
|
40
|
+
// Storage can be blocked. The page works without it.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Turns the current filters into API query parameters. */
|
|
45
|
+
export function queryParams(offset = 0) {
|
|
46
|
+
const { scope } = state;
|
|
47
|
+
return {
|
|
48
|
+
q: state.query,
|
|
49
|
+
mode: state.mode,
|
|
50
|
+
order: state.order,
|
|
51
|
+
scope: scope.kind === "all" ? "all" : scope.kind === "global" ? "global" : "project",
|
|
52
|
+
project: scope.kind === "project" ? scope.project : undefined,
|
|
53
|
+
category: state.category,
|
|
54
|
+
minImportance: state.minImportance || undefined,
|
|
55
|
+
pinned: state.pinned ? "true" : undefined,
|
|
56
|
+
limit: PAGE,
|
|
57
|
+
offset,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const hasFilters = () => state.scope.kind !== "all" || state.category !== "" || state.minImportance > 0 || state.pinned;
|
package/ui/js/toast.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { $, h } from "./dom.js";
|
|
2
|
+
import { icon } from "./icons.js";
|
|
3
|
+
|
|
4
|
+
const ICONS = { info: "info", success: "check", error: "triangle-alert" };
|
|
5
|
+
|
|
6
|
+
export function toast(message, { kind = "info", ms } = {}) {
|
|
7
|
+
const el = h("div", { class: `toast ${kind}`, role: kind === "error" ? "alert" : undefined }, icon(ICONS[kind] ?? "info"), h("span", {}, message));
|
|
8
|
+
$("#toasts").append(el);
|
|
9
|
+
setTimeout(() => el.remove(), ms ?? (kind === "error" ? 7000 : 4000));
|
|
10
|
+
}
|