om-memory-system 4.14.0-next.106 → 4.15.0-next.116

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.
Files changed (59) hide show
  1. package/README.md +106 -149
  2. package/dist/adapters/claude-code/hook-command.d.ts +3 -1
  3. package/dist/adapters/claude-code/hook-command.js +14 -0
  4. package/dist/adapters/claude-code/plugin-self-update.d.ts +27 -0
  5. package/dist/adapters/claude-code/plugin-self-update.js +103 -0
  6. package/dist/adapters/opencode/profile-learning.js +6 -25
  7. package/dist/cli/index.js +22 -0
  8. package/dist/cli/profile-catch-up-command.js +12 -0
  9. package/dist/cli/profile-cleanup-categories-command.d.ts +3 -0
  10. package/dist/cli/profile-cleanup-categories-command.js +92 -0
  11. package/dist/cli/profile-normalise-categories-command.d.ts +2 -0
  12. package/dist/cli/profile-normalise-categories-command.js +85 -0
  13. package/dist/core/extraction.d.ts +15 -0
  14. package/dist/core/extraction.js +36 -0
  15. package/dist/core/profile-analysis.js +9 -5
  16. package/dist/importer/claude-import.js +1 -0
  17. package/dist/importer/import-runs.js +9 -3
  18. package/dist/importer/import-sessions.d.ts +5 -0
  19. package/dist/importer/import-sessions.js +15 -3
  20. package/dist/importer/importer.d.ts +1 -0
  21. package/dist/importer/importer.js +1 -0
  22. package/dist/importer/opencode-import.d.ts +1 -0
  23. package/dist/importer/opencode-import.js +1 -0
  24. package/dist/importer/profile-import.d.ts +2 -0
  25. package/dist/importer/profile-import.js +4 -0
  26. package/dist/importer/run-import.d.ts +2 -0
  27. package/dist/importer/run-import.js +11 -2
  28. package/dist/importer/web-import-group.d.ts +57 -0
  29. package/dist/importer/web-import-group.js +355 -0
  30. package/dist/importer/web-import-jobs.d.ts +12 -6
  31. package/dist/importer/web-import-jobs.js +48 -2
  32. package/dist/importer/web-import-profile-estimate.d.ts +14 -0
  33. package/dist/importer/web-import-profile-estimate.js +37 -0
  34. package/dist/services/api-handlers.d.ts +7 -0
  35. package/dist/services/api-handlers.js +72 -0
  36. package/dist/services/turso/cross-process-write-lock.js +55 -18
  37. package/dist/services/user-profile/category-cleanup-job.d.ts +30 -0
  38. package/dist/services/user-profile/category-cleanup-job.js +50 -0
  39. package/dist/services/user-profile/category-cleanup-store.d.ts +8 -0
  40. package/dist/services/user-profile/category-cleanup-store.js +35 -0
  41. package/dist/services/user-profile/category-cleanup.d.ts +74 -0
  42. package/dist/services/user-profile/category-cleanup.js +310 -0
  43. package/dist/services/user-profile/category-groups.d.ts +60 -0
  44. package/dist/services/user-profile/category-groups.js +171 -0
  45. package/dist/services/user-profile/profile-candidate-matching.d.ts +19 -0
  46. package/dist/services/user-profile/profile-candidate-matching.js +67 -0
  47. package/dist/services/user-profile/profile-category.d.ts +13 -0
  48. package/dist/services/user-profile/profile-category.js +43 -0
  49. package/dist/services/user-profile/user-profile-manager.d.ts +9 -1
  50. package/dist/services/user-profile/user-profile-manager.js +46 -65
  51. package/dist/services/web-server.js +17 -2
  52. package/dist/services/web-update.d.ts +7 -2
  53. package/dist/services/web-update.js +20 -7
  54. package/dist/web/assets/index-B46sRbG_.js +127 -0
  55. package/dist/web/assets/index-BktRr9cC.css +2 -0
  56. package/dist/web/index.html +2 -2
  57. package/package.json +1 -1
  58. package/dist/web/assets/index-BH1QAU9V.js +0 -125
  59. package/dist/web/assets/index-xoKkyBBV.css +0 -2
package/README.md CHANGED
@@ -1,191 +1,148 @@
1
+ <p align="center">
2
+ <img src=".github/screenshots/omms-logo.png" alt="OMMS logo" width="96" height="96" />
3
+ </p>
4
+
1
5
  # OMMS: Opinionated Modular Memory System
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
4
8
  [![npm downloads](https://img.shields.io/npm/dm/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
5
- [![license](https://img.shields.io/npm/l/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
9
+ [![MIT licence](https://img.shields.io/npm/l/om-memory-system.svg)](LICENSE.md)
10
+
11
+ OMMS gives AI coding agents long-term project memory. It captures technical
12
+ work, recalls relevant notes in later sessions and learns your working
13
+ preferences. One shared engine serves **OpenCode**, **Pi** and **Claude Code**,
14
+ so each host can retrieve memories captured by the others in the same project.
6
15
 
7
- ![OMMS banner](.github/banner.png)
16
+ Memories are stored locally, by default in `~/.omms/data`. Embeddings run
17
+ locally by default; the first use downloads a search model. Capture and profile
18
+ learning make additional model calls that can send conversation content to your
19
+ chosen provider and incur costs. Remote embeddings are optional. OMMS removes
20
+ text inside `<private>` tags before storage. See [Configuration](docs/configuration.md)
21
+ for model choices and privacy controls.
8
22
 
9
- OMMS gives your AI coding agent a long-term memory. As you work, it writes
10
- short notes about what was done and decided in each project: fixes, design
11
- choices, things that did not work. It brings the relevant notes back in later
12
- sessions, so the agent does not start from zero every time. It also learns how
13
- you like to work and keeps that as a user profile.
23
+ ## Set up
14
24
 
15
- It runs inside [OpenCode](https://opencode.ai) and the
25
+ Use Node.js **22.14 or later** for the terminal command and Claude Code hooks.
26
+ OpenCode v1 needs **1.18.29 or later**. OMMS also supports OpenCode v2 and the
16
27
  [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
17
- Both share one memory per project, so a note written in one is available in
18
- the other. Everything is stored locally on your machine.
19
28
 
20
- Claude Code is also supported, through a plugin of hooks. It needs Node.js and
21
- your own external API for capture. A global install is optional. See the
22
- [Claude Code adapter](docs/claude-code-adapter.md) guide.
29
+ > Automatic history import is enabled by default and makes model calls.
30
+ > To disable it before your first session, set `"autoBackfill": false` in
31
+ > `~/.config/omms/omms.jsonc`. See [Automatic import](docs/web-ui-memory.md#automatic-import).
23
32
 
24
- ## What it does
33
+ ### OpenCode
25
34
 
26
- - **Remembers automatically.** After each piece of work, a background model
27
- call summarises it into a memory. You do not have to ask.
28
- - **Recalls when relevant.** Matching memories are added to the agent's
29
- context: on every prompt in OpenCode v2 and Pi, at the start of a session in
30
- OpenCode v1.
31
- - **Learns your preferences.** A user profile of your habits builds up over
32
- time and follows you across projects.
33
- - **Imports your past sessions.** Pi and OpenCode import older history after
34
- startup. A command gives you a preview and manual control.
35
- - **Lets you look and edit.** A local web page shows every memory and your
36
- profile.
37
- - **Keeps private things private.** Text inside `<private>` tags is never
38
- stored.
35
+ Add the package to `~/.config/opencode/opencode.json`. Keep your existing settings.
36
+ On Windows, use `%USERPROFILE%\.config\opencode\opencode.json`.
39
37
 
40
- ![Project memory timeline](.github/screenshot-project-memory.png)
38
+ **OpenCode v2:**
41
39
 
42
- ![User profile viewer](.github/screenshot-user-profile.png)
40
+ ```json
41
+ { "plugins": ["om-memory-system"] }
42
+ ```
43
43
 
44
- ## Before you start
44
+ **OpenCode v1:**
45
45
 
46
- You need one of:
46
+ ```json
47
+ { "plugin": ["om-memory-system"] }
48
+ ```
47
49
 
48
- - **OpenCode** 1.18.29 or later (v1 plugin API) or OpenCode v2
49
- - **Pi coding agent**
50
- - **Claude Code**, through a plugin of hooks. Capture needs your own external API. See
51
- the [Claude Code adapter](docs/claude-code-adapter.md) guide for setup.
50
+ OpenCode v2 also supports:
52
51
 
53
- Nothing else is required. OMMS brings its own database. On first use it
54
- downloads a small embedding model (the part that makes memories searchable),
55
- so you need internet access once. The terminal import command also needs
56
- Node.js 22.14 or later.
52
+ ```bash
53
+ opencode plugin add om-memory-system
54
+ ```
57
55
 
58
- ## Set up
56
+ Restart OpenCode. The [OpenCode guide](docs/opencode-adapter.md#installation)
57
+ covers plugin management and its additional `cli.json` configuration.
59
58
 
60
- ### 1. Install
59
+ ### Pi
60
+
61
+ ```bash
62
+ pi install npm:om-memory-system
63
+ ```
61
64
 
62
- **OpenCode.** Add `om-memory-system` to `~/.config/opencode/opencode.json`
63
- (on Windows, `%USERPROFILE%\.config\opencode\opencode.json`):
65
+ Restart Pi. See the [Pi guide](docs/pi-adapter.md).
64
66
 
65
- ```jsonc
66
- // OpenCode v2
67
- { "plugins": ["om-memory-system"] }
67
+ ### Claude Code
68
68
 
69
- // OpenCode v1
70
- { "plugin": ["om-memory-system"] }
71
- ```
69
+ Install the OMMS plugin using the [Claude Code guide](docs/claude-code-adapter.md#installation).
70
+ Capture and profile learning require a configured external API; retrieval and
71
+ manual memory operations remain available without it. A global npm install is optional.
72
72
 
73
- On OpenCode v2 you can run `opencode plugin add om-memory-system` instead.
74
- Restart OpenCode.
73
+ ### Model and web app
75
74
 
76
- **Pi.** Run:
75
+ With no model settings, OpenCode and Pi use the session model. You can choose a
76
+ host model or configure an external API under **Settings**. Claude Code uses
77
+ only the external API. See [Choosing the model](docs/configuration.md#choosing-the-model)
78
+ for the selection order and credentials.
77
79
 
78
- ```bash
79
- pi install npm:om-memory-system
80
- ```
80
+ Start a host session, then open [http://127.0.0.1:4747](http://127.0.0.1:4747).
81
+ Each host starts the shared web app when needed, unless it is disabled. Browse
82
+ captured notes on **Project memories**, or ask your agent to search memory for
83
+ an earlier decision.
81
84
 
82
- Restart Pi. You can install OMMS in both agents; they share the same memory.
85
+ ### Terminal command (optional)
83
86
 
84
- **Terminal command (optional).** A global install puts the `om-memory-system`
85
- command on your `PATH`. You do not need it. OMMS runs the newest copy it finds on
86
- the machine, so updating one host is enough. A global install from 4.3 or earlier
87
- needs one update. See
88
- [Updating and upgrading](docs/upgrading.md#one-update-updates-every-host).
89
- To install it:
87
+ A global install puts `om-memory-system` on your `PATH`:
90
88
 
91
89
  ```bash
92
- npm i -g om-memory-system # or: bun add -g om-memory-system
90
+ npm install -g om-memory-system
93
91
  om-memory-system --version
94
92
  ```
95
93
 
96
- ### 2. Choose which model writes memories (optional)
97
-
98
- With no settings, OMMS uses the model of the session you are working in. To
99
- use a different one, add it to `~/.config/omms/omms.jsonc`. If you have no
100
- config file yet, OMMS creates this one with comments on first start.
101
-
102
- ```jsonc
103
- {
104
- // A model you are already signed in to in OpenCode or Pi.
105
- // Use "inherit" to always follow the session's model.
106
- "opencodeProvider": "openai",
107
- "opencodeModel": "gpt-5.6-luna",
108
- "piProvider": "openai-codex",
109
- "piModel": "gpt-5.6-luna",
110
- }
111
- ```
94
+ See the [CLI reference](docs/cli.md) for manual memory operations and history imports.
112
95
 
113
- You can also use any OpenAI-compatible or Anthropic API with your own key, set
114
- up on the Settings page's **External API** card, and choose `"external"` as a
115
- host's model. See [Configuration](docs/configuration.md#choosing-the-model).
96
+ ## Inside OMMS
116
97
 
117
- ### 3. Check it works
98
+ These screenshots use synthetic examples and configuration. All visible prompts,
99
+ code snippets and outcomes are made up for the demonstration.
118
100
 
119
- Work normally for a few turns, then open `http://127.0.0.1:4747` in your
120
- browser. OpenCode or the login web app serves this page. New memories appear
121
- on the timeline. You can also ask the agent: "search memory for what we changed today".
101
+ ### Project memories
122
102
 
123
- ## Import your past history
103
+ Browse captured notes alongside example user input and technical outcomes.
124
104
 
125
- Older sessions import automatically after a host starts. This makes model
126
- calls. To turn it off, set `"autoBackfill": false` in
127
- `~/.config/omms/omms.jsonc` before you start the agent. For a manual import or
128
- a custom source, preview it inside the agent:
105
+ ![Project memories with synthetic English prompts, code and outcomes](.github/screenshots/project-memories.png)
129
106
 
130
- ```text
131
- /memory-import-opencode-history --dry-run
132
- /memory-import-pi-history --dry-run
133
- ```
107
+ ### Memory
108
+
109
+ Review history imports, manage profile learning and set memory limits. This page
110
+ also helps resolve missing project folders.
111
+
112
+ ![Memory page with synthetic import and profile examples](.github/screenshots/memory.png)
113
+
114
+ ### Settings
115
+
116
+ Choose models and manage access. Diagnostics show capture outcomes and component health.
134
117
 
135
- The preview shows how many model calls a real import needs. It uses the
136
- session's model; add `--model provider/id` for a cheaper one. There is also a
137
- terminal version, `npx om-memory-system`, that uses an API key. See the
138
- [OpenCode](docs/opencode-history-import.md) and [Pi](docs/pi-history-import.md)
139
- import guides, and the [CLI reference](docs/cli.md).
140
-
141
- ## Keeping OMMS up to date
142
-
143
- Pi shows a notice when a new version is out; update with
144
- `pi update npm:om-memory-system`. On OpenCode v2, the footer shows when a new version is out;
145
- run `opencode plugin update om-memory-system`. Restart the agent afterwards.
146
- [UPDATES.md](UPDATES.md) explains how updates work on every host.
147
- To stay on one version, install it with the number, for example
148
- `pi install npm:om-memory-system@3.1.1` or
149
- `opencode plugin add om-memory-system@3.1.1`; a pinned install is never
150
- updated. Coming from `opencode-mem`? Your memories move over automatically, with a
151
- backup first. See [Updating and upgrading](docs/upgrading.md) and
152
- [CHANGELOG.md](CHANGELOG.md).
118
+ ![Settings page with synthetic model configuration](.github/screenshots/settings.png)
153
119
 
154
120
  ## Documentation
155
121
 
156
- | Read this | To learn about |
157
- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
158
- | [Using memory day to day](docs/using-memory.md) | How capture and recall work, the `memory` tool, the user profile |
159
- | [Configuration](docs/configuration.md) | Settings, choosing the model, embeddings, troubleshooting |
160
- | [Web UI](docs/web-ui.md) | The memory explorer, opening it on a network safely |
161
- | [Settings page](docs/web-ui-settings.md) | Every card and control on the web Settings page |
162
- | [Moving projects](docs/moving-projects.md) | Nested repositories, moved folders, backup and restore |
163
- | [Updating and upgrading](docs/upgrading.md) | Updates, pinning a version, older stores |
164
- | [OpenCode adapter](docs/opencode-adapter.md) | How the OpenCode plugin hooks in |
165
- | [Pi adapter](docs/pi-adapter.md) | How the Pi extension hooks in |
166
- | [Claude Code adapter](docs/claude-code-adapter.md) | Installing the Claude Code plugin, hooks, the `memory` command |
167
- | [OpenCode history import](docs/opencode-history-import.md) | Importing past OpenCode sessions |
168
- | [Pi history import](docs/pi-history-import.md) | Importing past Pi sessions, moving machines |
169
- | [Claude Code history import](docs/claude-code-history-import.md) | Importing past Claude Code transcripts |
170
- | [CLI reference](docs/cli.md) | The `om-memory-system` terminal command |
171
- | [Migrating from opencode-mem](docs/omms-migration.md) | What changes when upgrading from the original plugin |
172
- | [For developers](docs/developers.md) | Building, testing, the public `tags` export, architecture |
173
- | [Contributing](CONTRIBUTING.md) | How to set up, test, and send a pull request |
174
-
175
- ## About this fork
176
-
177
- OMMS is [`cmdaltctr/omms`](https://github.com/cmdaltctr/omms), a fork of
178
- [`tickernelz/opencode-mem`](https://github.com/tickernelz/opencode-mem),
179
- published on npm as `om-memory-system`. The fork runs the memory engine as a
180
- shared core for both OpenCode and Pi (see [shared core](docs/shared-core.md)).
181
- Existing OpenCode memories and settings keep working.
182
-
183
- ## License and links
184
-
185
- MIT License. See [LICENSE.md](LICENSE.md).
186
-
187
- - Repository: https://github.com/cmdaltctr/omms
188
- - Upstream: https://github.com/tickernelz/opencode-mem
189
- - Issues: https://github.com/cmdaltctr/omms/issues
190
-
191
- Inspired by [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory).
122
+ | Guide | Covers |
123
+ | ------------------------------------------- | ------------------------------------------------------------ |
124
+ | [Using memory](docs/using-memory.md) | Daily use, capture, recall and the user profile |
125
+ | [Web UI](docs/web-ui.md) | Starting the app, browsing memories and safe network access |
126
+ | [Memory page](docs/web-ui-memory.md) | History imports, profile actions, limits and missing folders |
127
+ | [Settings page](docs/web-ui-settings.md) | Models, credentials, diagnostics and profile identities |
128
+ | [Configuration](docs/configuration.md) | Config files, model selection, privacy and embeddings |
129
+ | [Moving projects](docs/moving-projects.md) | Folder moves, backup and restore |
130
+ | [Updating and upgrading](docs/upgrading.md) | Updates, version pinning and older stores |
131
+ | [CLI reference](docs/cli.md) | Terminal commands |
132
+ | [For developers](docs/developers.md) | Building, testing and architecture |
133
+ | [Contributing](CONTRIBUTING.md) | Development workflow and pull requests |
134
+
135
+ History import guides: [OpenCode](docs/opencode-history-import.md),
136
+ [Pi](docs/pi-history-import.md) and [Claude Code](docs/claude-code-history-import.md).
137
+ See [UPDATES.md](UPDATES.md) for host update commands and
138
+ [CHANGELOG.md](CHANGELOG.md) for release changes. Existing `opencode-mem` users
139
+ can follow the [migration guide](docs/omms-migration.md).
140
+
141
+ ## Licence
142
+
143
+ MIT. See [LICENSE.md](LICENSE.md) for copyright and attribution, and
144
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for bundled dependencies.
145
+
146
+ [Repository](https://github.com/cmdaltctr/omms) ·
147
+ [Issues](https://github.com/cmdaltctr/omms/issues) ·
148
+ [Documentation index](docs/README.md)
@@ -4,4 +4,6 @@ import { type ClaudeStatusOptions } from "./status.js";
4
4
  * `om-memory-system claude-hook <event>`. Always returns 0: a hook must never
5
5
  * block or break a Claude Code session, whatever goes wrong.
6
6
  */
7
- export declare function runClaudeHookCommand(argv: string[], io?: ClaudeHookOptions & ClaudeStatusOptions): Promise<number>;
7
+ export declare function runClaudeHookCommand(argv: string[], io?: ClaudeHookOptions & ClaudeStatusOptions & {
8
+ skipSelfUpdate?: boolean;
9
+ }): Promise<number>;
@@ -9,6 +9,18 @@ async function printStatus(io) {
9
9
  const status = await buildClaudeStatus({ ...inputs, fetch: io.fetch ?? globalThis.fetch });
10
10
  (io.writeStdout ?? ((text) => process.stdout.write(text)))(`${JSON.stringify(status)}\n`);
11
11
  }
12
+ /** Update the Claude plugin in the background when npm has a newer release. Never throws. */
13
+ async function selfUpdate(io) {
14
+ try {
15
+ const { startPluginSelfUpdate, pluginSelfUpdateDeps } = await import("./plugin-self-update.js");
16
+ const log = io.log ??
17
+ (async (message, data) => (await import("../../services/logger.js")).log(message, data));
18
+ await startPluginSelfUpdate(pluginSelfUpdateDeps(log));
19
+ }
20
+ catch {
21
+ /* The memory hook already ran; a failed update check changes nothing. */
22
+ }
23
+ }
12
24
  /**
13
25
  * `om-memory-system claude-hook <event>`. Always returns 0: a hook must never
14
26
  * block or break a Claude Code session, whatever goes wrong.
@@ -22,6 +34,8 @@ export async function runClaudeHookCommand(argv, io = {}) {
22
34
  }
23
35
  if (isHookEvent(event)) {
24
36
  await runClaudeHook(event, io);
37
+ if (event === "session-start" && !io.skipSelfUpdate)
38
+ await selfUpdate(io);
25
39
  return 0;
26
40
  }
27
41
  // Only a known name reaches the log; argv is not trusted text.
@@ -0,0 +1,27 @@
1
+ /** A second session does not start another update for the same release within this time. */
2
+ export declare const SELF_UPDATE_RETRY_MS: number;
3
+ export type PluginSelfUpdateCode = "started" | "current" | "recent" | "disabled" | "no-plugin" | "unreadable-plugin" | "unreachable" | "spawn-failed";
4
+ export interface PluginSelfUpdateDeps {
5
+ /** False when `OMMS_DISABLE_UPDATE_CHECK` is `1`. */
6
+ enabled: boolean;
7
+ /** `CLAUDE_PLUGIN_ROOT`; undefined outside a Claude plugin. */
8
+ pluginRoot: string | undefined;
9
+ readPluginVersion: (root: string) => string;
10
+ fetch: typeof fetch;
11
+ now: () => number;
12
+ readMarker: () => {
13
+ version: string;
14
+ startedAt: number;
15
+ } | null;
16
+ writeMarker: (value: {
17
+ version: string;
18
+ startedAt: number;
19
+ }) => void;
20
+ /** Start a process that outlives this hook. */
21
+ spawnDetached: (command: string, args: string[]) => void;
22
+ log: (message: string, data: Record<string, unknown>) => void | Promise<void>;
23
+ }
24
+ /** Start a background plugin update when npm has a newer release. Never throws. */
25
+ export declare function startPluginSelfUpdate(deps: PluginSelfUpdateDeps): Promise<PluginSelfUpdateCode>;
26
+ /** Production inputs: the real plugin manifest, npm, and a marker file in ~/.omms. */
27
+ export declare function pluginSelfUpdateDeps(log: PluginSelfUpdateDeps["log"], env?: NodeJS.ProcessEnv): PluginSelfUpdateDeps;
@@ -0,0 +1,103 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { availableUpdate, latestNpmVersion } from "../../services/update-check.js";
6
+ // Claude Code installs the OMMS plugin from the `stable` branch, but its own
7
+ // update pass runs at its own time and can leave a new session on the old
8
+ // plugin. At session start OMMS compares the plugin with npm `latest` and, when
9
+ // it is older, runs Claude Code's own update commands in the background. The
10
+ // new copy loads in the next session or after /reload-plugins.
11
+ /** A second session does not start another update for the same release within this time. */
12
+ export const SELF_UPDATE_RETRY_MS = 30 * 60 * 1000;
13
+ /** Node script that refreshes the marketplace, then updates the plugin. Run detached. */
14
+ const UPDATE_SCRIPT = `
15
+ const { spawnSync } = require("node:child_process");
16
+ const shell = process.platform === "win32";
17
+ for (const args of [["plugin", "marketplace", "update", "omms"], ["plugin", "update", "omms@omms"]]) {
18
+ const run = spawnSync("claude", args, { stdio: "ignore", shell, timeout: 120000 });
19
+ if (run.status !== 0) process.exit(1);
20
+ }
21
+ `;
22
+ /** Start a background plugin update when npm has a newer release. Never throws. */
23
+ export async function startPluginSelfUpdate(deps) {
24
+ const code = await decide(deps);
25
+ if (code.code === "started" || code.code === "spawn-failed") {
26
+ await deps.log("Claude plugin self-update", {
27
+ code: code.code,
28
+ plugin: code.plugin,
29
+ latest: code.latest,
30
+ });
31
+ }
32
+ return code.code;
33
+ }
34
+ async function decide(deps) {
35
+ if (!deps.enabled)
36
+ return { code: "disabled" };
37
+ if (!deps.pluginRoot)
38
+ return { code: "no-plugin" };
39
+ let plugin;
40
+ try {
41
+ plugin = deps.readPluginVersion(deps.pluginRoot);
42
+ }
43
+ catch {
44
+ return { code: "unreadable-plugin" };
45
+ }
46
+ const latest = await latestNpmVersion(deps.fetch);
47
+ if (latest === null)
48
+ return { code: "unreachable" };
49
+ const newer = availableUpdate(plugin, latest);
50
+ if (!newer)
51
+ return { code: "current" };
52
+ const marker = deps.readMarker();
53
+ const now = deps.now();
54
+ if (marker?.version === newer && now - marker.startedAt <= SELF_UPDATE_RETRY_MS) {
55
+ return { code: "recent" };
56
+ }
57
+ // Write first, so a session that starts during the update does not start another.
58
+ deps.writeMarker({ version: newer, startedAt: now });
59
+ try {
60
+ deps.spawnDetached(process.execPath, ["-e", UPDATE_SCRIPT]);
61
+ }
62
+ catch {
63
+ return { code: "spawn-failed", plugin, latest: newer };
64
+ }
65
+ return { code: "started", plugin, latest: newer };
66
+ }
67
+ /** Production inputs: the real plugin manifest, npm, and a marker file in ~/.omms. */
68
+ export function pluginSelfUpdateDeps(log, env = process.env) {
69
+ const markerPath = join(homedir(), ".omms", "claude-plugin-update.json");
70
+ return {
71
+ enabled: env.OMMS_DISABLE_UPDATE_CHECK !== "1",
72
+ pluginRoot: env.CLAUDE_PLUGIN_ROOT || undefined,
73
+ readPluginVersion: (root) => {
74
+ const manifest = JSON.parse(readFileSync(join(root, ".claude-plugin", "plugin.json"), "utf8"));
75
+ if (typeof manifest.version !== "string")
76
+ throw new Error("no version");
77
+ return manifest.version;
78
+ },
79
+ fetch: globalThis.fetch,
80
+ now: Date.now,
81
+ readMarker: () => {
82
+ try {
83
+ const value = JSON.parse(readFileSync(markerPath, "utf8"));
84
+ return typeof value.version === "string" && typeof value.startedAt === "number"
85
+ ? { version: value.version, startedAt: value.startedAt }
86
+ : null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ },
92
+ writeMarker: (value) => {
93
+ mkdirSync(dirname(markerPath), { recursive: true });
94
+ writeFileSync(markerPath, JSON.stringify(value));
95
+ },
96
+ spawnDetached: (command, args) => {
97
+ const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
98
+ child.on("error", () => { });
99
+ child.unref();
100
+ },
101
+ log,
102
+ };
103
+ }
@@ -246,30 +246,7 @@ function generateChangeSummary(oldProfile, newProfile) {
246
246
  return changes.length > 0 ? changes.join(", ") : "Profile refinement";
247
247
  }
248
248
  function buildCategorySummary(profileData) {
249
- const prefCats = [...new Set(profileData.preferences.map((p) => p.category))];
250
- const patCats = [...new Set(profileData.patterns.map((p) => p.category))];
251
- const catParts = [];
252
- if (prefCats.length > 0) {
253
- const catCounts = prefCats
254
- .map((cat) => {
255
- const cnt = profileData.preferences.filter((p) => p.category === cat).length;
256
- return `${cat} (${cnt})`;
257
- })
258
- .join(", ");
259
- catParts.push(`Preference categories: ${catCounts}`);
260
- }
261
- if (patCats.length > 0) {
262
- const catCounts = patCats
263
- .map((cat) => {
264
- const cnt = profileData.patterns.filter((p) => p.category === cat).length;
265
- return `${cat} (${cnt})`;
266
- })
267
- .join(", ");
268
- catParts.push(`Pattern categories: ${catCounts}`);
269
- }
270
- const catSection = catParts.length > 0
271
- ? `## Existing Categories\nUse these exact category names when your observation fits:\n\n${catParts.join("\n")}\n`
272
- : "";
249
+ const catSection = buildCategoryList(profileData);
273
250
  const prefCount = profileData.preferences.length;
274
251
  const patCount = profileData.patterns.length;
275
252
  const wfCount = profileData.workflows.length;
@@ -350,7 +327,7 @@ CRITICAL: Only output observations grounded in the RECENT PROMPTS above. Write d
350
327
  }
351
328
  return truncate(base);
352
329
  }
353
- import { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX, } from "../../core/extraction.js";
330
+ import { buildCategoryList, createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX, WORKFLOW_PROMPT_RULES, } from "../../core/extraction.js";
354
331
  export { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX };
355
332
  export function createUserProfileToolSchema(existingProfile) {
356
333
  return {
@@ -523,6 +500,8 @@ CRITICAL: Detect the language used by the user in their prompts. You MUST output
523
500
 
524
501
  CRITICAL: All JSON string values MUST escape double quotes with backslash. Do NOT use unescaped quotation marks inside string values.
525
502
 
503
+ ${WORKFLOW_PROMPT_RULES}
504
+
526
505
  Use the update_user_profile tool to save the ${existingProfile ? "updated" : "new"} profile.`;
527
506
  const { z } = await import("zod");
528
507
  const schema = createUserProfileAnalysisSchema(z);
@@ -580,6 +559,8 @@ CRITICAL: Detect the language used by the user in their prompts. You MUST output
580
559
 
581
560
  CRITICAL: All JSON string values MUST escape double quotes with backslash. Do NOT use unescaped quotation marks inside string values.
582
561
 
562
+ ${WORKFLOW_PROMPT_RULES}
563
+
583
564
  Use the update_user_profile tool to save the ${existingProfile ? "updated" : "new"} profile.`;
584
565
  const toolSchema = createUserProfileToolSchema(Boolean(existingProfile));
585
566
  const result = await provider.executeToolCall(systemPrompt, context, toolSchema, `user-profile-${Date.now()}`);
package/dist/cli/index.js CHANGED
@@ -18,6 +18,8 @@ Commands:
18
18
  web update Update the global install and restart the web app
19
19
  memory <mode> [options] Search, add, list, or forget memories (run with --help)
20
20
  profile-catch-up Analyse every prompt waiting for profile learning (run with --help)
21
+ profile-normalise-categories Preview or confirm profile category renames (run with --help)
22
+ profile-cleanup-categories Group category names by meaning; preview, apply or undo (run with --help)
21
23
  claude-hook <event> Run a Claude Code hook (used by the Claude Code plugin)
22
24
 
23
25
  Options:
@@ -67,6 +69,14 @@ export async function runCli(argv) {
67
69
  const { runProfileCatchUpCommand } = await import("./profile-catch-up-command.js");
68
70
  return await runProfileCatchUpCommand(argv.slice(1));
69
71
  }
72
+ if (argv[0] === "profile-normalise-categories") {
73
+ const { runProfileNormaliseCategoriesCommand } = await import("./profile-normalise-categories-command.js");
74
+ return await runProfileNormaliseCategoriesCommand(argv.slice(1));
75
+ }
76
+ if (argv[0] === "profile-cleanup-categories") {
77
+ const { runProfileCleanupCategoriesCommand } = await import("./profile-cleanup-categories-command.js");
78
+ return await runProfileCleanupCategoriesCommand(argv.slice(1));
79
+ }
70
80
  if (argv[0] === "web") {
71
81
  try {
72
82
  const { runWebCommand } = await import("./web-command.js");
@@ -107,6 +117,18 @@ export async function runCli(argv) {
107
117
  model = `${selected.provider}/${selected.modelId}`;
108
118
  console.log(`Import model: ${model}`);
109
119
  }
120
+ // Profile merging buffers findings (capped at 50 per type) while the embedding service is
121
+ // cold, and a profile-only import never embeds through the memory store. Warm it first.
122
+ if (!args.dryRun && !args.skipProfile) {
123
+ try {
124
+ const { memoryClient } = await import("../services/client.js");
125
+ await memoryClient.warmup();
126
+ }
127
+ catch (error) {
128
+ console.warn(`Embedding warm-up failed: ${error instanceof Error ? error.message : String(error)}. ` +
129
+ "Profile findings may be held back until it works.");
130
+ }
131
+ }
110
132
  const { formatHistoryImportReport, historyImportFailed, runHistoryImport } = await import("../importer/run-import.js");
111
133
  const report = await runHistoryImport(host, args, {
112
134
  cwd: process.cwd(),
@@ -75,6 +75,18 @@ export async function runProfileCatchUpCommand(argv, print = console.log) {
75
75
  const { selectImportModel } = await import("../importer/model-selection.js");
76
76
  const selected = selectImportModel(flags.model);
77
77
  print(`Profile model: ${selected.provider}/${selected.modelId}`);
78
+ // A cold embedding service makes profile merging buffer findings (capped at 50 per type).
79
+ // A backlog that needs no model call never merges, so it does not start the service.
80
+ if (preview.calls > 0) {
81
+ try {
82
+ const { memoryClient } = await import("../services/client.js");
83
+ await memoryClient.warmup();
84
+ }
85
+ catch (error) {
86
+ print(`Embedding warm-up failed: ${error instanceof Error ? error.message : String(error)}. ` +
87
+ "Profile findings may be held back until it works.");
88
+ }
89
+ }
78
90
  const { drainProfileBacklog } = await import("../importer/profile-backlog.js");
79
91
  const { catchUpLeaseHooks } = await import("../importer/profile-catch-up.js");
80
92
  const hooks = await catchUpLeaseHooks();
@@ -0,0 +1,3 @@
1
+ import type { CleanupDeps } from "../services/user-profile/category-cleanup.js";
2
+ /** Preview, apply or undo a meaning-based cleanup of the current user's profile categories. */
3
+ export declare function runProfileCleanupCategoriesCommand(argv: string[], print?: (line: string) => void, deps?: CleanupDeps): Promise<number>;