@nordbyte/nordrelay 0.4.1 → 0.5.1

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 (57) hide show
  1. package/.env.example +155 -64
  2. package/README.md +81 -65
  3. package/dist/access-control.js +126 -115
  4. package/dist/agent-updates.js +62 -9
  5. package/dist/bot-rendering.js +838 -0
  6. package/dist/bot-ui.js +1 -0
  7. package/dist/bot.js +342 -2498
  8. package/dist/channel-actions.js +8 -8
  9. package/dist/channel-runtime.js +89 -0
  10. package/dist/config-metadata.js +238 -0
  11. package/dist/config.js +0 -58
  12. package/dist/index.js +8 -0
  13. package/dist/operations.js +63 -9
  14. package/dist/relay-artifact-service.js +126 -0
  15. package/dist/relay-external-activity-monitor.js +216 -0
  16. package/dist/relay-queue-service.js +66 -0
  17. package/dist/relay-runtime-types.js +1 -0
  18. package/dist/relay-runtime.js +96 -354
  19. package/dist/settings-service.js +2 -117
  20. package/dist/support-bundle.js +205 -0
  21. package/dist/telegram-access-commands.js +123 -0
  22. package/dist/telegram-access-middleware.js +129 -0
  23. package/dist/telegram-agent-commands.js +212 -0
  24. package/dist/telegram-artifact-commands.js +139 -0
  25. package/dist/telegram-channel-runtime.js +132 -0
  26. package/dist/telegram-command-menu.js +55 -0
  27. package/dist/telegram-command-types.js +1 -0
  28. package/dist/telegram-diagnostics-command.js +102 -0
  29. package/dist/telegram-general-commands.js +52 -0
  30. package/dist/telegram-operational-commands.js +153 -0
  31. package/dist/telegram-output.js +216 -0
  32. package/dist/telegram-preference-commands.js +198 -0
  33. package/dist/telegram-queue-commands.js +278 -0
  34. package/dist/telegram-support-command.js +53 -0
  35. package/dist/telegram-update-commands.js +93 -0
  36. package/dist/user-management.js +708 -0
  37. package/dist/web-api-contract.js +104 -0
  38. package/dist/web-api-types.js +1 -0
  39. package/dist/web-dashboard-access-routes.js +163 -0
  40. package/dist/web-dashboard-artifact-routes.js +65 -0
  41. package/dist/web-dashboard-assets.js +35 -2
  42. package/dist/web-dashboard-http.js +143 -0
  43. package/dist/web-dashboard-pages.js +257 -0
  44. package/dist/web-dashboard-runtime-routes.js +92 -0
  45. package/dist/web-dashboard-session-routes.js +209 -0
  46. package/dist/web-dashboard-ui.js +14 -14
  47. package/dist/web-dashboard.js +330 -707
  48. package/dist/webui-assets/dashboard.css +989 -0
  49. package/dist/webui-assets/dashboard.js +1750 -0
  50. package/dist/zip-writer.js +83 -0
  51. package/package.json +13 -4
  52. package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
  53. package/plugins/nordrelay/commands/remote.md +1 -1
  54. package/plugins/nordrelay/scripts/nordrelay.mjs +227 -78
  55. package/plugins/nordrelay/skills/telegram-remote/SKILL.md +1 -1
  56. package/dist/web-dashboard-client.js +0 -275
  57. package/dist/web-dashboard-style.js +0 -9
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { agentLabel } from "./agent.js";
@@ -7,9 +7,16 @@ import { resolveClaudeCodeCli } from "./claude-code-cli.js";
7
7
  import { resolveCodexCli } from "./codex-cli.js";
8
8
  import { resolveHermesCli } from "./hermes-cli.js";
9
9
  import { resolveOpenClawCli } from "./openclaw-cli.js";
10
- import { getAgentUpdateLogPath, getConnectorHome } from "./operations.js";
10
+ import { getAgentUpdateLogPath, getConnectorHome, resolveNpmSpawnCommand } from "./operations.js";
11
11
  import { resolvePiCli } from "./pi-cli.js";
12
12
  import { redactText } from "./redaction.js";
13
+ const AGENT_INSTALL_PACKAGES = {
14
+ codex: "@openai/codex",
15
+ pi: "@earendil-works/pi-coding-agent",
16
+ hermes: "hermes-agent",
17
+ openclaw: "openclaw",
18
+ "claude-code": "@anthropic-ai/claude-code",
19
+ };
13
20
  export class AgentUpdateManager {
14
21
  options;
15
22
  jobs = new Map();
@@ -34,6 +41,9 @@ export class AgentUpdateManager {
34
41
  }
35
42
  readLog(id) {
36
43
  const job = this.requireJob(id);
44
+ if (job.logDeletedAt) {
45
+ return { job: this.snapshot(job), plain: `Update log was deleted at ${job.logDeletedAt}.` };
46
+ }
37
47
  try {
38
48
  return { job: this.snapshot(job), plain: redactText(readFileSync(job.logPath, "utf8")) };
39
49
  }
@@ -44,12 +54,23 @@ export class AgentUpdateManager {
44
54
  };
45
55
  }
46
56
  }
47
- start(agentId, context = {}) {
57
+ deleteLog(id) {
58
+ const job = this.requireJob(id);
59
+ if (job.status === "running") {
60
+ throw new Error("Cannot delete the update log while the update job is still running.");
61
+ }
62
+ const snapshot = this.snapshot(job);
63
+ rmSync(job.logPath, { force: true });
64
+ this.jobs.delete(id);
65
+ this.persistJobs();
66
+ return snapshot;
67
+ }
68
+ start(agentId, context = {}, operation = "update") {
48
69
  const running = [...this.jobs.values()].find((job) => job.agentId === agentId && job.status === "running");
49
70
  if (running) {
50
- throw new Error(`${agentLabel(agentId)} update is already running.`);
71
+ throw new Error(`${agentLabel(agentId)} update/install job is already running.`);
51
72
  }
52
- const plan = resolveAgentUpdatePlan(agentId, { ...context, env: context.env ?? this.options.env });
73
+ const plan = resolveAgentUpdatePlan(agentId, { ...context, env: context.env ?? this.options.env }, operation);
53
74
  const now = new Date().toISOString();
54
75
  const id = `${agentId.replace(/[^a-z0-9]/gi, "")}-${Date.now().toString(36)}`;
55
76
  const logPath = path.join(this.home, "updates", `${id}.log`);
@@ -58,6 +79,7 @@ export class AgentUpdateManager {
58
79
  id,
59
80
  agentId,
60
81
  agentLabel: plan.agentLabel,
82
+ operation,
61
83
  status: "running",
62
84
  method: plan.method,
63
85
  command: plan.command,
@@ -77,7 +99,7 @@ export class AgentUpdateManager {
77
99
  this.jobs.set(id, job);
78
100
  this.persistJobs();
79
101
  this.append(job, [
80
- `[${now}] Starting ${job.agentLabel} update`,
102
+ `[${now}] Starting ${job.agentLabel} ${job.operation}`,
81
103
  `Method: ${job.method}`,
82
104
  `Command: ${[job.command, ...job.args].join(" ")}`,
83
105
  `Working directory: ${job.cwd}`,
@@ -101,7 +123,7 @@ export class AgentUpdateManager {
101
123
  if (job.status !== "running") {
102
124
  return;
103
125
  }
104
- this.finish(job, code === 0 ? "completed" : "failed", code, signal, code === 0 ? undefined : `Update exited with code ${code ?? "unknown"}`);
126
+ this.finish(job, code === 0 ? "completed" : "failed", code, signal, code === 0 ? undefined : `${capitalize(job.operation)} exited with code ${code ?? "unknown"}`);
105
127
  });
106
128
  this.emit(job);
107
129
  return this.snapshot(job);
@@ -164,7 +186,7 @@ export class AgentUpdateManager {
164
186
  job.finishedAt = new Date().toISOString();
165
187
  job.updatedAt = job.finishedAt;
166
188
  job.child = undefined;
167
- this.append(job, `\n[${job.finishedAt}] ${job.agentLabel} update ${status}${error ? `: ${job.error}` : ""}\n`);
189
+ this.append(job, `\n[${job.finishedAt}] ${job.agentLabel} ${job.operation} ${status}${error ? `: ${job.error}` : ""}\n`);
168
190
  }
169
191
  emit(job) {
170
192
  this.options.onUpdate?.(this.snapshot(job));
@@ -181,12 +203,17 @@ export class AgentUpdateManager {
181
203
  const parsed = JSON.parse(readFileSync(this.manifestPath, "utf8"));
182
204
  let changed = false;
183
205
  for (const snapshot of parsed) {
206
+ if (snapshot.logDeletedAt) {
207
+ changed = true;
208
+ continue;
209
+ }
184
210
  const staleRunning = snapshot.status === "running" && !isProcessRunning(snapshot.ownerPid);
185
211
  if (staleRunning) {
186
212
  changed = true;
187
213
  }
188
214
  const job = {
189
215
  ...snapshot,
216
+ operation: snapshot.operation ?? "update",
190
217
  status: staleRunning ? "failed" : snapshot.status,
191
218
  canInput: false,
192
219
  needsInput: false,
@@ -236,7 +263,10 @@ function isProcessRunning(pid) {
236
263
  return false;
237
264
  }
238
265
  }
239
- export function resolveAgentUpdatePlan(agentId, context = {}) {
266
+ export function resolveAgentUpdatePlan(agentId, context = {}, operation = "update") {
267
+ if (operation === "install") {
268
+ return resolveAgentInstallPlan(agentId, context);
269
+ }
240
270
  const env = context.env ?? process.env;
241
271
  switch (agentId) {
242
272
  case "codex": {
@@ -276,10 +306,30 @@ export function resolveAgentUpdatePlan(agentId, context = {}) {
276
306
  }
277
307
  }
278
308
  }
309
+ function resolveAgentInstallPlan(agentId, context = {}) {
310
+ const env = context.env ?? process.env;
311
+ const npm = resolveNpmSpawnCommand(env);
312
+ if (!npm) {
313
+ throw new Error(`Cannot install ${agentLabel(agentId)} because npm was not found on PATH.`);
314
+ }
315
+ const packageName = AGENT_INSTALL_PACKAGES[agentId];
316
+ return {
317
+ agentId,
318
+ agentLabel: agentLabel(agentId),
319
+ operation: "install",
320
+ method: `npm install -g ${packageName}@latest`,
321
+ command: npm.command,
322
+ args: [...npm.argsPrefix, "install", "-g", `${packageName}@latest`],
323
+ cwd: env.HOME || os.homedir(),
324
+ summary: `Installs the latest ${agentLabel(agentId)} CLI with npm. Restart NordRelay after installation if the agent was previously unavailable.`,
325
+ interactive: true,
326
+ };
327
+ }
279
328
  function plan(agentId, method, command, args, summary, env) {
280
329
  return {
281
330
  agentId,
282
331
  agentLabel: agentLabel(agentId),
332
+ operation: "update",
283
333
  method,
284
334
  command,
285
335
  args,
@@ -292,3 +342,6 @@ function looksLikePrompt(text) {
292
342
  const tail = text.split(/\r?\n/).slice(-4).join("\n");
293
343
  return /\b(y\/n|yes\/no|continue|proceed|confirm|password|passphrase|token|api key|enter|select)\b|[?>]\s*$/i.test(tail);
294
344
  }
345
+ function capitalize(value) {
346
+ return value.charAt(0).toUpperCase() + value.slice(1);
347
+ }