@rynx-ai/daemon 0.1.11-beta.2 → 0.1.11-beta.21

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 (88) hide show
  1. package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
  2. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
  3. package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
  4. package/bundled-plugins/plugins/lark/dist/host-adapters.js +20 -18
  5. package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
  6. package/bundled-plugins/plugins/lark/dist/runtime.js +6046 -6022
  7. package/bundled-plugins/plugins/lark/dist/settings.js +6006 -5988
  8. package/bundled-plugins/plugins/lark/package.json +1 -1
  9. package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
  10. package/bundled-plugins/plugins/lark/ui/index.html +2 -2
  11. package/dist/agent-file.js +0 -1
  12. package/dist/browser-artifact-management.d.ts +4 -1
  13. package/dist/browser-artifact-management.js +22 -11
  14. package/dist/browser-artifacts.d.ts +1 -0
  15. package/dist/browser-artifacts.js +1 -0
  16. package/dist/chrome-for-testing-runtime.d.ts +27 -0
  17. package/dist/chrome-for-testing-runtime.js +194 -0
  18. package/dist/chrome-for-testing-store.d.ts +2 -0
  19. package/dist/chrome-for-testing-store.js +37 -6
  20. package/dist/control-deps.d.ts +6 -2
  21. package/dist/control-deps.js +87 -4
  22. package/dist/control-link-store.js +4 -3
  23. package/dist/daemon-owner.js +4 -7
  24. package/dist/daemon-server.d.ts +4 -1
  25. package/dist/daemon-server.js +158 -45
  26. package/dist/db.d.ts +2 -2
  27. package/dist/db.js +55 -20
  28. package/dist/entry-path.d.ts +3 -0
  29. package/dist/entry-path.js +12 -0
  30. package/dist/index-daemon.js +3 -87
  31. package/dist/lifecycle.d.ts +13 -0
  32. package/dist/lifecycle.js +19 -0
  33. package/dist/maintenance-lease.d.ts +39 -0
  34. package/dist/maintenance-lease.js +250 -0
  35. package/dist/migrations/cleanup-legacy-sessions.js +4 -3
  36. package/dist/plugin-cli.d.ts +10 -0
  37. package/dist/plugin-cli.js +19 -2
  38. package/dist/plugin-host-rpc.d.ts +35 -6
  39. package/dist/plugin-host-rpc.js +222 -59
  40. package/dist/plugin-installer.js +62 -2
  41. package/dist/plugin-management-service.js +0 -2
  42. package/dist/plugin-marketplace.js +2 -1
  43. package/dist/plugin-store.js +2 -4
  44. package/dist/plugin-supervisor.d.ts +5 -0
  45. package/dist/plugin-supervisor.js +218 -33
  46. package/dist/pm2.d.ts +11 -2
  47. package/dist/pm2.js +116 -19
  48. package/dist/process.d.ts +12 -0
  49. package/dist/process.js +91 -0
  50. package/dist/remote-runtime-access-store.d.ts +2 -2
  51. package/dist/remote-runtime-access-store.js +6 -9
  52. package/dist/remote-runtime-target-store.d.ts +2 -2
  53. package/dist/remote-runtime-target-store.js +6 -6
  54. package/dist/session-emulator-binding-store.js +4 -6
  55. package/dist/session-fork-store.js +24 -8
  56. package/dist/session-launch-operations.d.ts +10 -1
  57. package/dist/session-launch-operations.js +21 -11
  58. package/dist/session-log-store.js +2 -1
  59. package/dist/session-meta-store.d.ts +5 -0
  60. package/dist/session-meta-store.js +25 -2
  61. package/dist/session-portal-grant-store.d.ts +29 -0
  62. package/dist/session-portal-grant-store.js +85 -0
  63. package/dist/session-resource-store.js +3 -2
  64. package/dist/setup-service.d.ts +21 -0
  65. package/dist/setup-service.js +39 -0
  66. package/dist/skills-catalog.d.ts +11 -2
  67. package/dist/skills-catalog.js +307 -10
  68. package/dist/sqlite.d.ts +22 -0
  69. package/dist/sqlite.js +57 -0
  70. package/dist/system-dependencies.d.ts +22 -0
  71. package/dist/system-dependencies.js +79 -0
  72. package/package.json +37 -21
  73. package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
  74. package/dist/browser-cli-args.d.ts +0 -20
  75. package/dist/browser-cli-args.js +0 -100
  76. package/dist/cli.d.ts +0 -2
  77. package/dist/cli.js +0 -550
  78. package/dist/control-client.d.ts +0 -122
  79. package/dist/control-client.js +0 -855
  80. package/dist/control-endpoint.d.ts +0 -14
  81. package/dist/control-endpoint.js +0 -124
  82. package/dist/desktop-browser-host-client.d.ts +0 -35
  83. package/dist/desktop-browser-host-client.js +0 -415
  84. package/dist/setup.d.ts +0 -20
  85. package/dist/setup.js +0 -386
  86. package/dist/update.d.ts +0 -25
  87. package/dist/update.js +0 -133
  88. /package/bundled-plugins/plugins/lark/ui/assets/{index-D9N-AkLX.js → index-D8Amoe6a.js} +0 -0
package/dist/setup.js DELETED
@@ -1,386 +0,0 @@
1
- /**
2
- * `rynx setup` / `rynx doctor` — bootstrap + health check for the daemon home
3
- * (`~/.rynx`). Replaces the old repo-bound `scripts/setup.sh`: config lives in
4
- * `{RYNX_HOME}/config.json` and the CLI ships as a global binary, so this belongs
5
- * here where it can also read the SQLite store directly. The interactive UI is
6
- * rendered with `@clack/prompts`.
7
- *
8
- * rynx setup interactive, step-by-step wizard that writes ~/.rynx/config.json
9
- * (falls back to writing defaults when stdin isn't a TTY)
10
- * rynx doctor read-only diagnostics (creates nothing; non-zero exit on failure)
11
- */
12
- import { spawnSync } from "node:child_process";
13
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
- import { join } from "node:path";
15
- import * as p from "@clack/prompts";
16
- import { AGENT_RUNTIME_IDS, getRuntimeProfile, listAgentSpecs, loadConfig, resolveRuntimeHome, rynxAgentsDir, rynxConfigFile, rynxHome, } from "@rynx-ai/core";
17
- import { resolveChromeForTestingRelease } from "./chrome-for-testing-release-resolver.js";
18
- import { createChromeForTestingArtifactStore, isChromeForTestingHostSupported, } from "./chrome-for-testing-store.js";
19
- import { ensureBundledOfficialPlugins } from "./bundled-plugins.js";
20
- import { dbPath } from "./db.js";
21
- import { listPlugins } from "./plugin-store.js";
22
- import { formatControlUrl } from "./pm2.js";
23
- const MIN_NODE_MAJOR = 20;
24
- export function controlListenModeFromHost(host) {
25
- const normalized = typeof host === "string" ? host.trim().replace(/^\[|\]$/g, "") : "";
26
- return normalized === "" ||
27
- normalized === "127.0.0.1" ||
28
- normalized === "::1" ||
29
- normalized.toLowerCase() === "localhost"
30
- ? "loopback"
31
- : "host";
32
- }
33
- export function controlHostForListenMode(mode) {
34
- return mode === "host" ? "0.0.0.0" : "127.0.0.1";
35
- }
36
- export function managedBrowserSetupPlan(listenMode, browser) {
37
- if (!browser.supported)
38
- return { kind: "unsupported" };
39
- if (browser.installed)
40
- return { kind: "ready" };
41
- return { kind: "offer", installByDefault: listenMode === "host" };
42
- }
43
- // ── rynx setup — interactive wizard ─────────────────────────────────────────
44
- /** Walk the user through config.json step by step. Returns a process exit code. */
45
- export async function runSetup() {
46
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
47
- // Non-interactive (CI / piped): never block on a prompt — just ensure defaults.
48
- if (!existsSync(rynxConfigFile()))
49
- writeConfig({ HOST: "127.0.0.1", PORT: 3000, LOG_LEVEL: "info" });
50
- const browser = inspectManagedBrowser();
51
- const bundledMessage = await setupBundledPlugins();
52
- console.log(`非交互环境:已确保 ${rynxConfigFile()} 存在(默认值)。` +
53
- "在终端(TTY)下重跑 `rynx setup` 可分步配置,或直接编辑该文件。" +
54
- (browser.supported && !browser.installed
55
- ? "\nSession Browser 尚未安装;需要时运行 `rynx browser install`。"
56
- : "") +
57
- bundledMessage);
58
- return 0;
59
- }
60
- p.intro("rynx setup · 配置 ~/.rynx");
61
- const cfg = readConfig();
62
- // Runtimes: show status, offer login for any logged-out codex/traex, pick default.
63
- let states = AGENT_RUNTIME_IDS.map(runtimeState);
64
- p.note(states.map((s) => `${statusGlyph(s)} ${s.id.padEnd(7)} ${runtimeHint(s)}`).join("\n"), "运行时");
65
- for (const s of states) {
66
- if (s.installed && s.login === "logged-out" && s.id !== "claude") {
67
- const yes = await p.confirm({ message: `现在登录 ${s.id}(${s.profile.loginCommand})?`, initialValue: false });
68
- if (p.isCancel(yes))
69
- return bail();
70
- if (yes)
71
- spawnSync(s.bin, ["login"], { stdio: "inherit" });
72
- }
73
- }
74
- const claude = states.find((s) => s.id === "claude");
75
- if (claude?.installed && claude.login === "logged-out") {
76
- p.log.info("claude 登录:运行 `claude /login`,或设置 ANTHROPIC_API_KEY 后重来");
77
- }
78
- states = AGENT_RUNTIME_IDS.map(runtimeState); // re-probe after any login
79
- const installed = states.filter((s) => s.installed);
80
- if (installed.length === 0) {
81
- p.log.warn("没有检测到运行时;装好 codex / traex / claude 之一后可设默认。");
82
- }
83
- else {
84
- const current = typeof cfg.AGENT_RUNTIME === "string" ? cfg.AGENT_RUNTIME : undefined;
85
- const initial = current ?? installed.find((s) => s.login === "logged-in")?.id ?? installed[0].id;
86
- const runtime = await p.select({
87
- message: "默认运行时 AGENT_RUNTIME",
88
- initialValue: initial,
89
- options: installed.map((s) => ({ value: s.id, label: s.id, hint: runtimeHint(s) })),
90
- });
91
- if (p.isCancel(runtime))
92
- return bail();
93
- cfg.AGENT_RUNTIME = runtime;
94
- }
95
- // HTTP port.
96
- const port = await p.text({
97
- message: "HTTP 端口 PORT",
98
- placeholder: "3000",
99
- initialValue: String(cfg.PORT ?? 3000),
100
- validate: (v) => {
101
- const n = Number(v);
102
- if (!Number.isInteger(n) || n <= 0 || n > 65535)
103
- return "请输入 1–65535 之间的端口";
104
- return undefined;
105
- },
106
- });
107
- if (p.isCancel(port))
108
- return bail();
109
- cfg.PORT = Number(port);
110
- const listenMode = await p.select({
111
- message: "控制台访问范围 HOST",
112
- initialValue: controlListenModeFromHost(cfg.HOST),
113
- options: [
114
- {
115
- value: "loopback",
116
- label: "仅本机(Loopback,推荐)",
117
- hint: "绑定 127.0.0.1",
118
- },
119
- {
120
- value: "host",
121
- label: "网络访问(Host)",
122
- hint: "绑定 0.0.0.0,可信网络可通过机器 IP 访问",
123
- },
124
- ],
125
- });
126
- if (p.isCancel(listenMode))
127
- return bail();
128
- cfg.HOST = controlHostForListenMode(listenMode);
129
- cfg.LOG_LEVEL = cfg.LOG_LEVEL ?? "info";
130
- const browserBeforeSetup = inspectManagedBrowser();
131
- const browserPlan = managedBrowserSetupPlan(listenMode, {
132
- supported: browserBeforeSetup.supported,
133
- installed: Boolean(browserBeforeSetup.installed),
134
- });
135
- let installBrowser = false;
136
- if (browserPlan.kind === "ready") {
137
- p.log.success(`Session Browser 已就绪 · Chrome for Testing ${browserBeforeSetup.installed.version}`);
138
- }
139
- else if (browserPlan.kind === "unsupported") {
140
- p.log.info(`当前平台 ${process.platform}-${process.arch} 不支持 managed Chrome for Testing;跳过 Browser 安装。`);
141
- }
142
- else {
143
- p.note([
144
- "Session Browser 让 agent 在当前 Runtime 上打开和操作网页。",
145
- listenMode === "host"
146
- ? "网络访问模式下,外部浏览器不能提供 Desktop Browser Host,因此推荐安装无头 Browser。"
147
- : "桌面 App 可使用内置 Browser;独立 daemon 或远端 Runtime 仍需要无头 Browser。",
148
- "安装会从 Chrome for Testing 官方源下载浏览器,不会改动系统浏览器。",
149
- ...(process.platform === "linux"
150
- ? ["Linux 的动态库和字体依赖仍需由系统或主机镜像提供。"]
151
- : []),
152
- ].join("\n"), "Session Browser(可选)");
153
- const choice = await p.confirm({
154
- message: "现在安装 managed Chrome for Testing?",
155
- initialValue: browserPlan.installByDefault,
156
- });
157
- if (p.isCancel(choice))
158
- return bail();
159
- installBrowser = choice;
160
- }
161
- writeConfig(cfg);
162
- p.log.success(`已写入 ${rynxConfigFile()}(chmod 600)`);
163
- p.log.message(`HOST=${cfg.HOST} PORT=${cfg.PORT} AGENT_RUNTIME=${cfg.AGENT_RUNTIME ?? "(默认)"}`);
164
- let browserReady = browserPlan.kind === "ready";
165
- if (installBrowser) {
166
- const spinner = p.spinner();
167
- spinner.start("准备安装 Stable Chrome for Testing");
168
- try {
169
- const release = await resolveChromeForTestingRelease({
170
- kind: "channel",
171
- channel: "Stable",
172
- });
173
- const installedBrowser = await createChromeForTestingArtifactStore().installResolved(release, { onProgress: (message) => spinner.message(message) });
174
- browserReady = true;
175
- spinner.stop(`Session Browser 已就绪 · Chrome for Testing ${installedBrowser.version}`);
176
- }
177
- catch (error) {
178
- spinner.error("Session Browser 安装失败");
179
- p.log.warn(`${error instanceof Error ? error.message : String(error)}\n` +
180
- "配置已保存;修复网络或系统依赖后运行 `rynx browser install` 重试。");
181
- }
182
- }
183
- else if (browserPlan.kind === "offer") {
184
- p.log.info("已跳过 Browser 安装;需要时运行 `rynx browser install`。");
185
- }
186
- const bundledMessage = await setupBundledPlugins();
187
- if (bundledMessage)
188
- p.log.info(bundledMessage.trim());
189
- const url = formatControlUrl(String(cfg.HOST ?? "127.0.0.1"), Number(cfg.PORT ?? 3000));
190
- p.note([
191
- `启动常驻服务 rynx start`,
192
- `打开控制台 ${url}`,
193
- browserReady
194
- ? "Session Browser 已就绪"
195
- : "启用 Browser rynx browser install",
196
- `新建 agent rynx agent add <id>`,
197
- `随时体检 rynx doctor`,
198
- ].join("\n"), "下一步");
199
- p.outro("配置完成 🎉");
200
- return 0;
201
- }
202
- async function setupBundledPlugins() {
203
- try {
204
- const result = await ensureBundledOfficialPlugins();
205
- if (result.status === "unavailable")
206
- return "";
207
- const changed = [...result.installed, ...result.updated];
208
- if (changed.length > 0) {
209
- return `\n已准备官方插件: ${changed.join(", ")}。`;
210
- }
211
- if (result.unchanged.length > 0) {
212
- return `\n官方插件已就绪: ${result.unchanged.join(", ")}。`;
213
- }
214
- return "";
215
- }
216
- catch (error) {
217
- return `\n官方插件准备失败: ${error instanceof Error ? error.message : String(error)}`;
218
- }
219
- }
220
- // ── rynx doctor — read-only health check ────────────────────────────────────
221
- /** Read-only diagnostics. Returns a process exit code (0 ok, 1 on any failure). */
222
- export async function runDoctor() {
223
- let warn = 0;
224
- let fail = 0;
225
- const home = rynxHome();
226
- p.intro("rynx doctor · 只读体检");
227
- p.note([
228
- `RYNX_HOME ${home}`,
229
- `config ${rynxConfigFile()}`,
230
- `db ${dbPath()}`,
231
- `agents ${rynxAgentsDir()}`,
232
- `logs ${join(home, "logs")}`,
233
- ].join("\n"), "路径");
234
- const major = Number(process.versions.node.split(".")[0]);
235
- if (major >= MIN_NODE_MAJOR)
236
- p.log.success(`Node.js v${process.versions.node}(要求 ≥ ${MIN_NODE_MAJOR})`);
237
- else
238
- (p.log.error(`Node.js v${process.versions.node} 过低,要求 ≥ ${MIN_NODE_MAJOR}`), fail++);
239
- if (existsSync(rynxConfigFile()))
240
- p.log.success(`config.json 已存在`);
241
- else
242
- (p.log.warn("缺少 config.json —— 运行 `rynx setup` 生成"), warn++);
243
- try {
244
- const config = loadConfig();
245
- p.log.info(`HOST=${config.HOST} PORT=${config.PORT} LOG_LEVEL=${config.LOG_LEVEL} AGENT_RUNTIME=${config.AGENT_RUNTIME}`);
246
- }
247
- catch (error) {
248
- p.log.error(`配置解析失败:${error.message}`);
249
- fail++;
250
- }
251
- let anyReady = false;
252
- for (const s of AGENT_RUNTIME_IDS.map(runtimeState)) {
253
- if (!s.installed)
254
- p.log.info(`${s.id}:未安装(${s.bin} 不在 PATH)`);
255
- else if (s.login === "logged-in")
256
- (p.log.success(`${s.id}:已登录`), (anyReady = true));
257
- else if (s.login === "logged-out")
258
- (p.log.warn(`${s.id}:未登录 —— 运行 \`${s.profile.loginCommand}\``), warn++);
259
- else
260
- (p.log.warn(`${s.id}:已安装,登录状态待确认(若报鉴权错误,执行 \`${s.profile.loginCommand}\`)`), (anyReady = true));
261
- }
262
- if (!anyReady)
263
- (p.log.error("没有就绪的运行时 —— 至少登录一个:codex login / traex login / claude /login"), fail++);
264
- if (!existsSync(dbPath())) {
265
- p.log.info("rynx.db 尚未创建(首次 `rynx start` 时生成)");
266
- }
267
- else {
268
- const plugins = listPlugins();
269
- p.log.info(`插件:${plugins.length} 个${plugins.length ? `(${plugins.map((pl) => pl.name).join(", ")})` : ""}`);
270
- }
271
- p.log.info(`agent:${(await listAgentSpecs()).length} 个(${rynxAgentsDir()})`);
272
- const browser = inspectManagedBrowser();
273
- if (!browser.supported) {
274
- p.log.info(`Session Browser:当前平台 ${process.platform}-${process.arch} 不受支持`);
275
- }
276
- else if (browser.installed) {
277
- p.log.success(`Session Browser:Chrome for Testing ${browser.installed.version} 已安装`);
278
- if (process.platform === "linux") {
279
- p.log.info("Linux Browser 动态库和字体依赖由系统或主机镜像提供");
280
- }
281
- }
282
- else if (browser.error) {
283
- p.log.warn(`Session Browser 安装无效:${browser.error} —— 运行 \`rynx browser install\` 修复`);
284
- warn++;
285
- }
286
- else {
287
- p.log.warn("Session Browser 未安装 —— 运行 `rynx browser install`");
288
- warn++;
289
- }
290
- if (fail > 0) {
291
- p.outro(`体检发现 ${fail} 个失败项(警告 ${warn})—— 处理后重跑 rynx doctor`);
292
- return 1;
293
- }
294
- p.outro(warn > 0 ? `基本就绪,有 ${warn} 个警告` : "全部通过 ✓");
295
- return 0;
296
- }
297
- function inspectManagedBrowser() {
298
- if (!isChromeForTestingHostSupported())
299
- return { supported: false };
300
- try {
301
- return {
302
- supported: true,
303
- installed: createChromeForTestingArtifactStore().readActive(),
304
- };
305
- }
306
- catch (error) {
307
- return {
308
- supported: true,
309
- installed: undefined,
310
- error: error instanceof Error ? error.message : String(error),
311
- };
312
- }
313
- }
314
- function runtimeState(id) {
315
- const profile = getRuntimeProfile(id);
316
- const installed = onPath(profile.defaultBinary);
317
- const login = !installed
318
- ? "logged-out"
319
- : id === "claude"
320
- ? claudeAuthState(profile)
321
- : cliLoginState(profile.defaultBinary);
322
- return { id, bin: profile.defaultBinary, profile, installed, login };
323
- }
324
- function statusGlyph(s) {
325
- if (!s.installed)
326
- return "○";
327
- return s.login === "logged-in" ? "●" : "◍";
328
- }
329
- function runtimeHint(s) {
330
- if (!s.installed)
331
- return `未安装(${s.bin} 不在 PATH)`;
332
- if (s.login === "logged-in")
333
- return "已登录";
334
- if (s.login === "logged-out")
335
- return "已安装,未登录";
336
- return "已安装,状态待确认";
337
- }
338
- /** Is `bin` runnable on PATH? Probe with `--version` (ENOENT ⇒ not installed). */
339
- function onPath(bin) {
340
- const result = spawnSync(bin, ["--version"], { stdio: "ignore", timeout: 8000 });
341
- return !result.error && result.status === 0;
342
- }
343
- /** Parse `<bin> login status` (codex/traex) into a tri-state. */
344
- function cliLoginState(bin) {
345
- const result = spawnSync(bin, ["login", "status"], { encoding: "utf8", timeout: 12000 });
346
- const out = `${result.stdout ?? ""}${result.stderr ?? ""}`;
347
- if (/Logged in using/i.test(out))
348
- return "logged-in";
349
- if (/not logged in|logged out/i.test(out))
350
- return "logged-out";
351
- return "unknown";
352
- }
353
- /** Claude has no `login status`; auth is an API-key env var or the `~/.claude` config dir. */
354
- function claudeAuthState(profile) {
355
- if (process.env.ANTHROPIC_API_KEY?.trim() || process.env.ANTHROPIC_AUTH_TOKEN?.trim())
356
- return "logged-in";
357
- return existsSync(resolveRuntimeHome(profile)) ? "unknown" : "logged-out";
358
- }
359
- function readConfig() {
360
- const file = rynxConfigFile();
361
- if (!existsSync(file))
362
- return {};
363
- try {
364
- const parsed = JSON.parse(readFileSync(file, "utf8"));
365
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
366
- }
367
- catch {
368
- return {};
369
- }
370
- }
371
- function writeConfig(cfg) {
372
- const file = rynxConfigFile();
373
- mkdirSync(rynxHome(), { recursive: true });
374
- writeFileSync(file, `${JSON.stringify(cfg, null, 2)}\n`);
375
- try {
376
- chmodSync(file, 0o600);
377
- }
378
- catch {
379
- /* best-effort on platforms without POSIX modes */
380
- }
381
- }
382
- /** User pressed Ctrl-C mid-wizard: print a cancel note and exit cleanly. */
383
- function bail() {
384
- p.cancel("已取消,未保存改动。");
385
- return 0;
386
- }
package/dist/update.d.ts DELETED
@@ -1,25 +0,0 @@
1
- export interface UpdateOptions {
2
- check?: boolean;
3
- /** With `check`, emit a typed {@link UpdateCheck} as JSON instead of a human line. */
4
- json?: boolean;
5
- version?: string;
6
- }
7
- export type UpdateCheck = {
8
- status: "up_to_date";
9
- current: string;
10
- } | {
11
- status: "behind";
12
- current: string;
13
- latest: string;
14
- } | {
15
- status: "error";
16
- detail: string;
17
- };
18
- /** Is `latest` a newer release than `current`? Numeric major.minor.patch (prerelease ignored). */
19
- export declare function isNewer(latest: string, current: string): boolean;
20
- /** Typed `--check` result a channel consumes via `--check --json`. */
21
- export declare function buildUpdateCheck(current: string, latest: string | null): UpdateCheck;
22
- /** Format the one-line, human-readable status emitted by a bare `--check`. */
23
- export declare function checkStatusLine(current: string, latest: string | null): string;
24
- /** Run `rynx update`. Returns a process exit code. */
25
- export declare function runUpdate(opts: UpdateOptions): Promise<number>;
package/dist/update.js DELETED
@@ -1,133 +0,0 @@
1
- /**
2
- * `rynx update` — upgrade the globally-installed daemon npm package and restart.
3
- *
4
- * The daemon ships as a global npm package (`@rynx-ai/daemon`, bin `rynx`), so
5
- * updating = `npm install -g <pkg>@latest` + restart.
6
- *
7
- * rynx update [version] upgrade (to a version, or latest) + restart
8
- * rynx update --check report status only (human one-liner)
9
- * rynx update --check --json report status only (typed UpdateCheck JSON)
10
- */
11
- import { spawnSync } from "node:child_process";
12
- import { createRequire } from "node:module";
13
- import { dirname, sep } from "node:path";
14
- import { loadConfig } from "@rynx-ai/core";
15
- import { formatControlUrl, startDaemon } from "./pm2.js";
16
- /** This daemon package's name + installed version (read from its own package.json). */
17
- function selfPackage() {
18
- const require = createRequire(import.meta.url);
19
- const pkg = require("../package.json");
20
- return { name: pkg.name ?? "@rynx-ai/daemon", version: pkg.version ?? "0.0.0" };
21
- }
22
- /** This package's root directory (the dir holding its package.json). */
23
- function packageRoot() {
24
- const require = createRequire(import.meta.url);
25
- return dirname(require.resolve("../package.json"));
26
- }
27
- /**
28
- * True when running from a working checkout / linked dev build rather than an
29
- * npm install. We refuse to `npm install -g` over it — that would clobber the
30
- * dev build. An installed package always lives under a `node_modules` directory;
31
- * a checkout (e.g. `packages/daemon`) does not.
32
- */
33
- function isLocalDevInstall() {
34
- return !packageRoot().split(sep).includes("node_modules");
35
- }
36
- /** Latest published version from the configured npm registry, or null on failure. */
37
- function latestVersion(name) {
38
- const result = spawnSync("npm", ["view", name, "version"], { encoding: "utf8", timeout: 60_000 });
39
- if (result.error || result.status !== 0)
40
- return null;
41
- return result.stdout.trim() || null;
42
- }
43
- /** Is `latest` a newer release than `current`? Numeric major.minor.patch (prerelease ignored). */
44
- export function isNewer(latest, current) {
45
- const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n) || 0);
46
- const a = parse(latest);
47
- const b = parse(current);
48
- for (let i = 0; i < 3; i++) {
49
- const x = a[i] ?? 0;
50
- const y = b[i] ?? 0;
51
- if (x !== y)
52
- return x > y;
53
- }
54
- return false;
55
- }
56
- /** Typed `--check` result a channel consumes via `--check --json`. */
57
- export function buildUpdateCheck(current, latest) {
58
- if (!latest)
59
- return { status: "error", detail: "could not resolve latest version" };
60
- return isNewer(latest, current)
61
- ? { status: "behind", current, latest }
62
- : { status: "up_to_date", current };
63
- }
64
- /** Format the one-line, human-readable status emitted by a bare `--check`. */
65
- export function checkStatusLine(current, latest) {
66
- const check = buildUpdateCheck(current, latest);
67
- switch (check.status) {
68
- case "behind":
69
- return `behind ${check.current} ${check.latest}`;
70
- case "up_to_date":
71
- return `up_to_date ${check.current}`;
72
- case "error":
73
- return `error ${check.detail}`;
74
- }
75
- }
76
- function npmInstallGlobal(name, version) {
77
- const result = spawnSync("npm", ["install", "-g", `${name}@${version}`, "--no-audit", "--no-fund"], { stdio: "inherit", timeout: 600_000 });
78
- return !result.error && result.status === 0;
79
- }
80
- /** Poll `/health` until the restarted daemon answers (or give up). */
81
- async function verifyHealthy() {
82
- const config = loadConfig();
83
- const url = `${formatControlUrl(config.HOST, config.PORT)}health`;
84
- for (let attempt = 0; attempt < 10; attempt++) {
85
- await new Promise((r) => setTimeout(r, 1000));
86
- try {
87
- const res = await fetch(url);
88
- if (res.ok)
89
- return true;
90
- }
91
- catch {
92
- /* not back up yet */
93
- }
94
- }
95
- return false;
96
- }
97
- /** Run `rynx update`. Returns a process exit code. */
98
- export async function runUpdate(opts) {
99
- const { name, version: current } = selfPackage();
100
- const latest = latestVersion(name);
101
- if (opts.check) {
102
- console.log(opts.json ? JSON.stringify(buildUpdateCheck(current, latest)) : checkStatusLine(current, latest));
103
- return 0;
104
- }
105
- // Never `npm install -g` over a working checkout / linked dev build.
106
- if (isLocalDevInstall()) {
107
- console.error(`update: ${name} is a local dev install — update your checkout (e.g. pnpm build), not npm`);
108
- return 1;
109
- }
110
- const target = opts.version ?? latest;
111
- if (!target) {
112
- console.error(`update: could not resolve a target version for ${name} (registry unreachable?)`);
113
- return 1;
114
- }
115
- if (!opts.version && latest && !isNewer(latest, current)) {
116
- console.log(`Already on the latest ${name}@${current}.`);
117
- return 0;
118
- }
119
- console.log(`Updating ${name}: ${current} → ${target} …`);
120
- if (!npmInstallGlobal(name, target)) {
121
- console.error("update: npm install failed");
122
- return 1;
123
- }
124
- startDaemon({ force: true });
125
- if (!(await verifyHealthy())) {
126
- console.error(`update: ${name}@${target} did not come back up — rolling back to ${current}`);
127
- npmInstallGlobal(name, current);
128
- startDaemon({ force: true });
129
- return 1;
130
- }
131
- console.log(`Updated ${name} → ${target} and restarted.`);
132
- return 0;
133
- }