om-memory-system 4.2.0 → 4.3.0-next.60
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 +3 -2
- package/dist/adapters/opencode/tui-status.d.ts +28 -0
- package/dist/adapters/opencode/tui-status.js +89 -0
- package/dist/services/web-autostart.d.ts +9 -0
- package/dist/services/web-autostart.js +67 -1
- package/dist/web/assets/{index-RCrvx7Dv.js → index-D2Gueurd.js} +4 -4
- package/dist/web/index.html +1 -1
- package/opencode/tui.tsx +32 -0
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -137,8 +137,9 @@ import guides, and the [CLI reference](docs/cli.md).
|
|
|
137
137
|
## Keeping OMMS up to date
|
|
138
138
|
|
|
139
139
|
Pi shows a notice when a new version is out; update with
|
|
140
|
-
`pi update npm:om-memory-system`. On OpenCode v2,
|
|
141
|
-
|
|
140
|
+
`pi update npm:om-memory-system`. On OpenCode v2, the footer shows when a new version is out;
|
|
141
|
+
run `opencode plugin update om-memory-system`. Restart the agent afterwards.
|
|
142
|
+
[UPDATES.md](UPDATES.md) explains how updates work on every host.
|
|
142
143
|
To stay on one version, install it with the number, for example
|
|
143
144
|
`pi install npm:om-memory-system@3.1.1` or
|
|
144
145
|
`opencode plugin add om-memory-system@3.1.1`; a pinned install is never
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** What the OpenCode footer shows: the web app's state and a newer release, if any. */
|
|
2
|
+
export interface TuiStatusState {
|
|
3
|
+
web: "warming" | "connected" | "offline";
|
|
4
|
+
update: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function tuiStatusText(state: TuiStatusState): string;
|
|
7
|
+
/** The `latest` version on npm, or null when the registry cannot be reached. */
|
|
8
|
+
export declare function latestNpmVersion(fetchFn: typeof fetch, timeoutMs?: number): Promise<string | null>;
|
|
9
|
+
/** A newer release than `current`, ignoring prereleases; null otherwise. */
|
|
10
|
+
export declare function availableUpdate(current: string, latest: string | null): string | null;
|
|
11
|
+
export interface TuiStatusDeps {
|
|
12
|
+
currentVersion: string;
|
|
13
|
+
healthUrl: string;
|
|
14
|
+
fetch: typeof fetch;
|
|
15
|
+
setText: (text: string) => void;
|
|
16
|
+
/** Shown once per start when a newer release is on npm. */
|
|
17
|
+
notify: (message: string) => void;
|
|
18
|
+
checkUpdates?: boolean;
|
|
19
|
+
healthIntervalMs?: number;
|
|
20
|
+
updateIntervalMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Keep the footer text current: the web app is polled, and npm is asked for a
|
|
24
|
+
* newer release at start and then every few hours. Returns a stop function.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startTuiStatus(deps: TuiStatusDeps): () => void;
|
|
27
|
+
/** Wire the status to this install: its version, the configured web app port, and global fetch. */
|
|
28
|
+
export declare function startInstalledTuiStatus(callbacks: Pick<TuiStatusDeps, "setText" | "notify">): Promise<() => void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isOlderVersion } from "../../services/version-compare.js";
|
|
2
|
+
export function tuiStatusText(state) {
|
|
3
|
+
const web = state.web === "offline" ? "omms:web app off" : `omms:${state.web}`;
|
|
4
|
+
return state.update ? `${web} · ${state.update} available` : web;
|
|
5
|
+
}
|
|
6
|
+
/** The `latest` version on npm, or null when the registry cannot be reached. */
|
|
7
|
+
export async function latestNpmVersion(fetchFn, timeoutMs = 5_000) {
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetchFn("https://registry.npmjs.org/om-memory-system/latest", {
|
|
10
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
11
|
+
headers: { accept: "application/json" },
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
return null;
|
|
15
|
+
const body = (await response.json());
|
|
16
|
+
return typeof body.version === "string" ? body.version : null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** A newer release than `current`, ignoring prereleases; null otherwise. */
|
|
23
|
+
export function availableUpdate(current, latest) {
|
|
24
|
+
if (!latest || latest.includes("-"))
|
|
25
|
+
return null;
|
|
26
|
+
return isOlderVersion(current, latest) ? latest : null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Keep the footer text current: the web app is polled, and npm is asked for a
|
|
30
|
+
* newer release at start and then every few hours. Returns a stop function.
|
|
31
|
+
*/
|
|
32
|
+
export function startTuiStatus(deps) {
|
|
33
|
+
const state = { web: "warming", update: null };
|
|
34
|
+
let stopped = false;
|
|
35
|
+
let notified = null;
|
|
36
|
+
const show = () => {
|
|
37
|
+
if (!stopped)
|
|
38
|
+
deps.setText(tuiStatusText(state));
|
|
39
|
+
};
|
|
40
|
+
const checkHealth = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const response = await deps.fetch(deps.healthUrl, { signal: AbortSignal.timeout(3_000) });
|
|
43
|
+
state.web = response.ok ? "connected" : "offline";
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
state.web = "offline";
|
|
47
|
+
}
|
|
48
|
+
show();
|
|
49
|
+
};
|
|
50
|
+
const checkUpdate = async () => {
|
|
51
|
+
const update = availableUpdate(deps.currentVersion, await latestNpmVersion(deps.fetch));
|
|
52
|
+
state.update = update;
|
|
53
|
+
show();
|
|
54
|
+
if (update && notified !== update && !stopped) {
|
|
55
|
+
notified = update;
|
|
56
|
+
deps.notify(`om-memory-system ${update} is available (running ${deps.currentVersion}). Run: opencode plugin update om-memory-system, then restart OpenCode.`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
show();
|
|
60
|
+
void checkHealth();
|
|
61
|
+
if (deps.checkUpdates !== false)
|
|
62
|
+
void checkUpdate();
|
|
63
|
+
const timers = [setInterval(() => void checkHealth(), deps.healthIntervalMs ?? 30_000)];
|
|
64
|
+
if (deps.checkUpdates !== false) {
|
|
65
|
+
timers.push(setInterval(() => void checkUpdate(), deps.updateIntervalMs ?? 6 * 3_600_000));
|
|
66
|
+
}
|
|
67
|
+
for (const timer of timers)
|
|
68
|
+
timer.unref?.();
|
|
69
|
+
return () => {
|
|
70
|
+
stopped = true;
|
|
71
|
+
for (const timer of timers)
|
|
72
|
+
clearInterval(timer);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Wire the status to this install: its version, the configured web app port, and global fetch. */
|
|
76
|
+
export async function startInstalledTuiStatus(callbacks) {
|
|
77
|
+
const [{ CONFIG }, { packageVersion }] = await Promise.all([
|
|
78
|
+
import("../../config.js"),
|
|
79
|
+
import("../../services/package-version.js"),
|
|
80
|
+
]);
|
|
81
|
+
const host = CONFIG.webServerHost === "0.0.0.0" ? "127.0.0.1" : CONFIG.webServerHost;
|
|
82
|
+
return startTuiStatus({
|
|
83
|
+
...callbacks,
|
|
84
|
+
currentVersion: packageVersion(),
|
|
85
|
+
healthUrl: `http://${host.includes(":") ? `[${host}]` : host}:${CONFIG.webServerPort}/api/health`,
|
|
86
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
87
|
+
checkUpdates: process.env.OMMS_DISABLE_UPDATE_CHECK !== "1",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -18,6 +18,15 @@ export interface WebAutostartOptions {
|
|
|
18
18
|
}
|
|
19
19
|
/** OpenCode's executable is not a JavaScript runtime. */
|
|
20
20
|
export declare function resolveWebRuntime(execPath?: string, platform?: NodeJS.Platform): string | null;
|
|
21
|
+
/** The package folder a written login item runs, read back from its command line. */
|
|
22
|
+
export declare function itemPackageRoot(file: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* The newest OMMS copy among this one, the global install beside the runtime,
|
|
25
|
+
* and the copy the item already runs. A host starts OMMS from its own cache,
|
|
26
|
+
* which can be older than the global install; that copy must not downgrade
|
|
27
|
+
* the login item.
|
|
28
|
+
*/
|
|
29
|
+
export declare function preferredPackageRoot(candidates: readonly (string | null | undefined)[]): string | null;
|
|
21
30
|
/** Read the login item's state without changing any system files. */
|
|
22
31
|
export declare function webAutostartStatus(options?: WebAutostartOptions): WebAutostartStatus;
|
|
23
32
|
/** Install or update only the fixed-name OMMS item. `start` launches it now. */
|
|
@@ -6,6 +6,7 @@ import { basename, dirname, join, delimiter, resolve } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { CONFIG } from "../config.js";
|
|
8
8
|
import { log } from "./logger.js";
|
|
9
|
+
import { compareVersions } from "./version-compare.js";
|
|
9
10
|
const MARKER = "OMMS login item";
|
|
10
11
|
const NAME = "io.github.cmdaltctr.omms.web";
|
|
11
12
|
function executable(name) {
|
|
@@ -69,6 +70,67 @@ function packageRoot() {
|
|
|
69
70
|
dir = parent;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
function packageVersionAt(root) {
|
|
74
|
+
try {
|
|
75
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
76
|
+
if (pkg.name !== "om-memory-system" || !existsSync(join(root, "dist", "cli", "index.js"))) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** The package folder a written login item runs, read back from its command line. */
|
|
86
|
+
export function itemPackageRoot(file) {
|
|
87
|
+
try {
|
|
88
|
+
const content = readFileSync(file, "utf8");
|
|
89
|
+
const match = /([^"<>\r\n]*?)[\\/]dist[\\/]cli[\\/]index\.js/.exec(content);
|
|
90
|
+
const path = match?.[1]?.replace(/^.*?<string>/, "").trim();
|
|
91
|
+
if (!path)
|
|
92
|
+
return null;
|
|
93
|
+
// Only plists escape XML. Decode ampersands last to preserve literal entity text.
|
|
94
|
+
return content.includes("<plist")
|
|
95
|
+
? path
|
|
96
|
+
.replaceAll(""", '"')
|
|
97
|
+
.replaceAll("'", "'")
|
|
98
|
+
.replaceAll("<", "<")
|
|
99
|
+
.replaceAll(">", ">")
|
|
100
|
+
.replaceAll("&", "&")
|
|
101
|
+
: path;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The newest OMMS copy among this one, the global install beside the runtime,
|
|
109
|
+
* and the copy the item already runs. A host starts OMMS from its own cache,
|
|
110
|
+
* which can be older than the global install; that copy must not downgrade
|
|
111
|
+
* the login item.
|
|
112
|
+
*/
|
|
113
|
+
export function preferredPackageRoot(candidates) {
|
|
114
|
+
let best = null;
|
|
115
|
+
for (const root of candidates) {
|
|
116
|
+
if (!root)
|
|
117
|
+
continue;
|
|
118
|
+
const version = packageVersionAt(root);
|
|
119
|
+
if (!version || compareVersions(version, version) === null)
|
|
120
|
+
continue;
|
|
121
|
+
if (!best || (compareVersions(version, best.version) ?? 0) > 0)
|
|
122
|
+
best = { root, version };
|
|
123
|
+
}
|
|
124
|
+
return best?.root ?? null;
|
|
125
|
+
}
|
|
126
|
+
function globalPackageRoot(runtime, platform) {
|
|
127
|
+
if (!runtime)
|
|
128
|
+
return null;
|
|
129
|
+
const prefix = dirname(dirname(runtime));
|
|
130
|
+
return platform === "win32"
|
|
131
|
+
? join(dirname(runtime), "node_modules", "om-memory-system")
|
|
132
|
+
: join(prefix, "lib", "node_modules", "om-memory-system");
|
|
133
|
+
}
|
|
72
134
|
function itemPath(home, platform) {
|
|
73
135
|
if (platform === "darwin")
|
|
74
136
|
return join(home, "Library", "LaunchAgents", `${NAME}.plist`);
|
|
@@ -83,7 +145,11 @@ function details(options) {
|
|
|
83
145
|
const platform = options.platform ?? process.platform;
|
|
84
146
|
const path = itemPath(home, platform);
|
|
85
147
|
const runtime = options.runtime === undefined ? resolveWebRuntime() : options.runtime;
|
|
86
|
-
const
|
|
148
|
+
const own = options.packageRoot === undefined ? packageRoot() : options.packageRoot;
|
|
149
|
+
const current = path && existsSync(path) ? itemPackageRoot(path) : null;
|
|
150
|
+
const root = options.packageRoot === undefined
|
|
151
|
+
? preferredPackageRoot([own, globalPackageRoot(runtime ?? null, platform), current])
|
|
152
|
+
: own;
|
|
87
153
|
const supported = platform !== "linux" || (options.systemctlAvailable ?? Boolean(executable("systemctl")));
|
|
88
154
|
return { home, platform, path, runtime, root, supported };
|
|
89
155
|
}
|