@roamcode.ai/server 1.0.0 → 1.0.2

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 (99) hide show
  1. package/dist/index.d.ts +2406 -82
  2. package/dist/index.js +8324 -44
  3. package/dist/managed-update-helper.js +631 -26
  4. package/dist/mcp-send.js +76 -90
  5. package/dist/start.d.ts +591 -9
  6. package/dist/start.js +7629 -353
  7. package/package.json +3 -3
  8. package/dist/auth.d.ts +0 -63
  9. package/dist/auth.js +0 -133
  10. package/dist/claude-auth-service.d.ts +0 -76
  11. package/dist/claude-auth-service.js +0 -217
  12. package/dist/claude-latest-service.d.ts +0 -34
  13. package/dist/claude-latest-service.js +0 -61
  14. package/dist/config.d.ts +0 -78
  15. package/dist/config.js +0 -59
  16. package/dist/data-dir.d.ts +0 -42
  17. package/dist/data-dir.js +0 -70
  18. package/dist/diag.d.ts +0 -43
  19. package/dist/diag.js +0 -83
  20. package/dist/fs-service.d.ts +0 -90
  21. package/dist/fs-service.js +0 -290
  22. package/dist/managed-runtime.d.ts +0 -51
  23. package/dist/managed-runtime.js +0 -411
  24. package/dist/managed-update-helper.d.ts +0 -2
  25. package/dist/mcp-send.d.ts +0 -33
  26. package/dist/origin-check.d.ts +0 -37
  27. package/dist/origin-check.js +0 -101
  28. package/dist/pane-status.d.ts +0 -61
  29. package/dist/pane-status.js +0 -145
  30. package/dist/providers/claude-metadata-service.d.ts +0 -58
  31. package/dist/providers/claude-metadata-service.js +0 -352
  32. package/dist/providers/claude-provider.d.ts +0 -11
  33. package/dist/providers/claude-provider.js +0 -166
  34. package/dist/providers/codex-activity.d.ts +0 -21
  35. package/dist/providers/codex-activity.js +0 -122
  36. package/dist/providers/codex-app-server-client.d.ts +0 -90
  37. package/dist/providers/codex-app-server-client.js +0 -485
  38. package/dist/providers/codex-latest-service.d.ts +0 -50
  39. package/dist/providers/codex-latest-service.js +0 -174
  40. package/dist/providers/codex-metadata-service.d.ts +0 -161
  41. package/dist/providers/codex-metadata-service.js +0 -686
  42. package/dist/providers/codex-profile-client.d.ts +0 -16
  43. package/dist/providers/codex-profile-client.js +0 -52
  44. package/dist/providers/codex-profile-security.d.ts +0 -23
  45. package/dist/providers/codex-profile-security.js +0 -161
  46. package/dist/providers/codex-provider.d.ts +0 -15
  47. package/dist/providers/codex-provider.js +0 -174
  48. package/dist/providers/codex-thread-coordinator.d.ts +0 -18
  49. package/dist/providers/codex-thread-coordinator.js +0 -93
  50. package/dist/providers/codex-thread-persistence.d.ts +0 -9
  51. package/dist/providers/codex-thread-persistence.js +0 -45
  52. package/dist/providers/codex-thread-resolver.d.ts +0 -59
  53. package/dist/providers/codex-thread-resolver.js +0 -322
  54. package/dist/providers/options.d.ts +0 -7
  55. package/dist/providers/options.js +0 -155
  56. package/dist/providers/provider-artifacts.d.ts +0 -3
  57. package/dist/providers/provider-artifacts.js +0 -30
  58. package/dist/providers/registry.d.ts +0 -7
  59. package/dist/providers/registry.js +0 -23
  60. package/dist/providers/types.d.ts +0 -95
  61. package/dist/providers/types.js +0 -8
  62. package/dist/push-dispatch.d.ts +0 -81
  63. package/dist/push-dispatch.js +0 -100
  64. package/dist/push-store.d.ts +0 -25
  65. package/dist/push-store.js +0 -79
  66. package/dist/rate-limit.d.ts +0 -52
  67. package/dist/rate-limit.js +0 -72
  68. package/dist/server-config.d.ts +0 -60
  69. package/dist/server-config.js +0 -77
  70. package/dist/service-install.d.ts +0 -60
  71. package/dist/service-install.js +0 -221
  72. package/dist/session-defaults.d.ts +0 -26
  73. package/dist/session-defaults.js +0 -60
  74. package/dist/session-store.d.ts +0 -81
  75. package/dist/session-store.js +0 -654
  76. package/dist/static-routes.d.ts +0 -101
  77. package/dist/static-routes.js +0 -188
  78. package/dist/terminal-capability.d.ts +0 -5
  79. package/dist/terminal-capability.js +0 -27
  80. package/dist/terminal-manager.d.ts +0 -224
  81. package/dist/terminal-manager.js +0 -917
  82. package/dist/terminal-process.d.ts +0 -85
  83. package/dist/terminal-process.js +0 -238
  84. package/dist/terminal-shared.d.ts +0 -36
  85. package/dist/terminal-shared.js +0 -43
  86. package/dist/tmux-list.d.ts +0 -11
  87. package/dist/tmux-list.js +0 -39
  88. package/dist/transport.d.ts +0 -123
  89. package/dist/transport.js +0 -1559
  90. package/dist/updater.d.ts +0 -161
  91. package/dist/updater.js +0 -451
  92. package/dist/usage-service.d.ts +0 -118
  93. package/dist/usage-service.js +0 -173
  94. package/dist/vapid.d.ts +0 -17
  95. package/dist/vapid.js +0 -31
  96. package/dist/web-push-send.d.ts +0 -20
  97. package/dist/web-push-send.js +0 -21
  98. package/dist/ws-ticket.d.ts +0 -47
  99. package/dist/ws-ticket.js +0 -62
@@ -1,290 +0,0 @@
1
- import { readdir, readFile, stat, realpath, open, mkdir, unlink } from "node:fs/promises";
2
- import { constants } from "node:fs";
3
- import { resolve, join, sep, basename } from "node:path";
4
- export class FsError extends Error {
5
- code;
6
- constructor(code, message) {
7
- super(message);
8
- this.name = "FsError";
9
- this.code = code;
10
- }
11
- }
12
- /** GET /fs/search walk bounds. Depth 5 / 400 dirs keeps the worst case (a huge home dir) to a bounded,
13
- * sub-second readdir sweep; 30 results is more than a picker list ever shows. */
14
- export const SEARCH_MAX_DEPTH = 5;
15
- export const SEARCH_MAX_DIRS = 400;
16
- export const SEARCH_MAX_RESULTS = 30;
17
- /** Extensions rendered inline as images (lowercased, no dot). */
18
- const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif"]);
19
- function isImagePath(name) {
20
- const dot = name.lastIndexOf(".");
21
- if (dot === -1)
22
- return false;
23
- return IMAGE_EXTENSIONS.has(name.slice(dot + 1).toLowerCase());
24
- }
25
- export class FsService {
26
- root;
27
- constructor(opts) {
28
- this.root = resolve(opts.root);
29
- }
30
- /** Resolve a target (absolute or relative to root) and confine it to root. */
31
- resolveWithinRoot(target) {
32
- const resolved = resolve(this.root, target);
33
- if (resolved !== this.root && !resolved.startsWith(this.root + sep)) {
34
- throw new FsError("forbidden", `path is outside the allowed root: ${target}`);
35
- }
36
- return resolved;
37
- }
38
- /** Resolve real paths so a symlink inside root cannot point outside it. Missing -> not-found. */
39
- async realWithinRoot(resolvedPath) {
40
- let realRoot;
41
- let realTarget;
42
- try {
43
- realRoot = await realpath(this.root);
44
- }
45
- catch {
46
- realRoot = this.root;
47
- }
48
- try {
49
- realTarget = await realpath(resolvedPath);
50
- }
51
- catch {
52
- throw new FsError("not-found", `not found: ${resolvedPath}`);
53
- }
54
- if (realTarget !== realRoot && !realTarget.startsWith(realRoot + sep)) {
55
- throw new FsError("forbidden", `path resolves outside the allowed root`);
56
- }
57
- return realTarget;
58
- }
59
- async listDirectory(target) {
60
- const dir = this.resolveWithinRoot(target);
61
- const realDir = await this.realWithinRoot(dir);
62
- const dirStat = await stat(realDir);
63
- if (!dirStat.isDirectory())
64
- throw new Error(`not a directory: ${target}`);
65
- const dirents = await readdir(realDir, { withFileTypes: true });
66
- const entries = [];
67
- for (const d of dirents) {
68
- const full = join(dir, d.name);
69
- const isDirectory = d.isDirectory();
70
- let isGitRepo = false;
71
- let gitBranch;
72
- if (isDirectory) {
73
- gitBranch = await this.readGitBranch(full);
74
- isGitRepo = gitBranch !== undefined;
75
- }
76
- entries.push({ name: d.name, path: full, isDirectory, isGitRepo, gitBranch });
77
- }
78
- // Directories first, then files; each group name-sorted.
79
- entries.sort((a, b) => {
80
- if (a.isDirectory !== b.isDirectory)
81
- return a.isDirectory ? -1 : 1;
82
- return a.name.localeCompare(b.name);
83
- });
84
- const parent = dir === this.root ? undefined : resolve(dir, "..");
85
- return { path: dir, parent, entries };
86
- }
87
- /** Read .git/HEAD cheaply; return the branch name or undefined if not a repo. */
88
- async readGitBranch(dirPath) {
89
- try {
90
- const head = await readFile(join(dirPath, ".git", "HEAD"), "utf8");
91
- const m = /^ref:\s+refs\/heads\/(.+)\s*$/.exec(head.trim());
92
- if (m)
93
- return m[1];
94
- // Detached HEAD: return the short commit.
95
- return head.trim().slice(0, 8);
96
- }
97
- catch {
98
- return undefined;
99
- }
100
- }
101
- async readFileForDownload(target) {
102
- const file = this.resolveWithinRoot(target);
103
- const real = await this.realWithinRoot(file);
104
- const data = await readFile(real);
105
- return { filename: basename(file), data };
106
- }
107
- /**
108
- * Validate that `target` is a real in-root file and describe it for an attachment frame WITHOUT
109
- * reading its bytes. Reuses the same resolveWithinRoot + realpath defense as readFileForDownload so
110
- * a traversal/symlink-escape path throws FsError("forbidden") and a missing path FsError("not-found").
111
- */
112
- async describeForAttachment(target) {
113
- const file = this.resolveWithinRoot(target);
114
- await this.realWithinRoot(file);
115
- const name = basename(file);
116
- return { name, isImage: isImagePath(name) };
117
- }
118
- async writeUploadedFile(targetDir, filename, data) {
119
- if (filename.includes("/") || filename.includes("\\") || filename.includes(sep)) {
120
- throw new Error(`invalid upload filename (no path separators allowed): ${filename}`);
121
- }
122
- const dir = this.resolveWithinRoot(targetDir);
123
- // Realpath the TARGET DIR (the file does not exist yet) so a symlinked dir cannot escape root.
124
- await this.realWithinRoot(dir);
125
- const dest = this.resolveWithinRoot(join(dir, filename));
126
- // Refuse to write THROUGH a symlink at the destination (a plain writeFile follows symlinks): a
127
- // symlink named `filename` — creatable by the running claude or a prior upload — could otherwise
128
- // redirect the write outside fsRoot. O_NOFOLLOW makes open() fail (ELOOP) if the final component is a
129
- // symlink — ATOMICALLY, with no check-then-write (TOCTOU) gap a concurrent swap could exploit. A
130
- // regular file is still overwritten (O_TRUNC) — a legit re-upload.
131
- const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW;
132
- let fh;
133
- try {
134
- fh = await open(dest, flags, 0o600);
135
- }
136
- catch (err) {
137
- if (err.code === "ELOOP") {
138
- throw new Error(`refusing to write through a symlink: ${filename}`);
139
- }
140
- throw err;
141
- }
142
- try {
143
- await fh.writeFile(data);
144
- }
145
- finally {
146
- await fh.close();
147
- }
148
- return { path: dest };
149
- }
150
- /** Ensure a directory (confined to root) exists, creating parents as needed; returns its absolute path. */
151
- async ensureDirWithinRoot(target) {
152
- const dir = this.resolveWithinRoot(target);
153
- await mkdir(dir, { recursive: true });
154
- return dir;
155
- }
156
- /**
157
- * POST /fs/mkdir: create ONE new directory (recursive:false — the picker creates a single folder under
158
- * an existing parent; a missing parent is a client bug, not something to silently paper over). Same
159
- * confinement as listDirectory: resolveWithinRoot on the target + realpath on the PARENT (the target
160
- * itself doesn't exist yet), so a symlinked parent can't smuggle the create outside root.
161
- * Errors: "exists" (→409) when the path is already taken, "not-found" (→404) when the parent is
162
- * missing, "forbidden" (→403) on any escape.
163
- */
164
- async makeDirectory(target) {
165
- const dir = this.resolveWithinRoot(target);
166
- if (dir === this.root)
167
- throw new FsError("exists", `directory already exists: ${target}`);
168
- await this.realWithinRoot(resolve(dir, "..")); // parent must be a real, in-root location
169
- try {
170
- await mkdir(dir, { recursive: false });
171
- }
172
- catch (err) {
173
- const code = err.code;
174
- if (code === "EEXIST")
175
- throw new FsError("exists", `directory already exists: ${target}`);
176
- if (code === "ENOENT")
177
- throw new FsError("not-found", `parent directory does not exist: ${target}`);
178
- throw err;
179
- }
180
- return { path: dir };
181
- }
182
- /**
183
- * GET /fs/search: case-insensitive SUBSTRING match on DIRECTORY names under `base` (default root),
184
- * for the project picker's "type to find your repo" flow. Breadth-first — so results come back
185
- * shallowest-first without a sort — bounded by {@link SEARCH_MAX_DEPTH} / {@link SEARCH_MAX_DIRS} /
186
- * {@link SEARCH_MAX_RESULTS} so a giant tree can never wedge the request. Dot-entries and node_modules
187
- * are skipped (never a project root; node_modules alone would blow the dir budget). Unreadable dirs are
188
- * skipped, not fatal. Same fsRoot confinement as every other fs route; entries are reported by their
189
- * in-root path (symlinked children are NOT re-resolved — mirroring listDirectory).
190
- */
191
- async searchDirectories(query, base) {
192
- const start = this.resolveWithinRoot(base ?? this.root);
193
- const realStart = await this.realWithinRoot(start); // must exist + confinement (throws not-found/forbidden)
194
- if (!(await stat(realStart)).isDirectory()) {
195
- throw new FsError("not-found", `not a directory: ${base ?? this.root}`);
196
- }
197
- const needle = query.toLowerCase();
198
- const results = [];
199
- const queue = [{ dir: start, depth: 0 }];
200
- let visited = 0;
201
- while (queue.length > 0 && visited < SEARCH_MAX_DIRS && results.length < SEARCH_MAX_RESULTS) {
202
- const { dir, depth } = queue.shift();
203
- visited += 1;
204
- let dirents;
205
- try {
206
- dirents = await readdir(dir, { withFileTypes: true });
207
- }
208
- catch {
209
- continue; // permission denied / vanished mid-walk — skip, never fail the search
210
- }
211
- // Name-sorted so results are deterministic within a depth level (BFS handles the between-levels order).
212
- const dirs = dirents.filter((d) => d.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
213
- for (const d of dirs) {
214
- if (d.name.startsWith(".") || d.name === "node_modules")
215
- continue;
216
- const full = join(dir, d.name);
217
- if (d.name.toLowerCase().includes(needle)) {
218
- results.push({ path: full, name: d.name, isGitRepo: (await this.readGitBranch(full)) !== undefined });
219
- if (results.length >= SEARCH_MAX_RESULTS)
220
- break;
221
- }
222
- // Children found while reading a depth-d dir sit at depth d+1; only descend while that stays <= max.
223
- if (depth + 1 < SEARCH_MAX_DEPTH)
224
- queue.push({ dir: full, depth: depth + 1 });
225
- }
226
- }
227
- return results;
228
- }
229
- /** Prune expired files inside EACH immediate subdirectory of `base` (confined to root). Used to age out
230
- * the terminal shared-files folders (one per session) in a single sweep — including ORPHANED folders
231
- * whose session no longer exists (the old per-live-session sweep leaked those forever). Best-effort:
232
- * returns the total files removed; a missing base / unreadable entry is skipped, not thrown. */
233
- async pruneChildDirsOlderThan(base, maxAgeMs, now = Date.now()) {
234
- let dir;
235
- try {
236
- dir = this.resolveWithinRoot(base);
237
- }
238
- catch {
239
- return 0;
240
- }
241
- let entries;
242
- try {
243
- entries = await readdir(dir, { withFileTypes: true });
244
- }
245
- catch {
246
- return 0; // base doesn't exist yet (nothing uploaded)
247
- }
248
- let removed = 0;
249
- for (const e of entries) {
250
- if (!e.isDirectory())
251
- continue;
252
- removed += await this.pruneOlderThan(join(dir, e.name), maxAgeMs, now);
253
- }
254
- return removed;
255
- }
256
- /** Delete top-level regular files in `target` (confined to root) whose mtime is older than `maxAgeMs`.
257
- * Best-effort — returns how many were removed; a missing dir / unreadable entry is skipped, not thrown.
258
- * Subdirectories are left untouched. Used to give terminal shared-files uploads a bounded lifetime. */
259
- async pruneOlderThan(target, maxAgeMs, now = Date.now()) {
260
- let dir;
261
- try {
262
- dir = this.resolveWithinRoot(target);
263
- }
264
- catch {
265
- return 0;
266
- }
267
- let names;
268
- try {
269
- names = await readdir(dir);
270
- }
271
- catch {
272
- return 0; // dir doesn't exist (nothing uploaded yet)
273
- }
274
- let removed = 0;
275
- for (const name of names) {
276
- const p = join(dir, name);
277
- try {
278
- const s = await stat(p);
279
- if (s.isFile() && now - s.mtimeMs > maxAgeMs) {
280
- await unlink(p);
281
- removed += 1;
282
- }
283
- }
284
- catch {
285
- /* skip an entry we can't stat/remove */
286
- }
287
- }
288
- return removed;
289
- }
290
- }
@@ -1,51 +0,0 @@
1
- export interface ManagedPaths {
2
- root: string;
3
- releases: string;
4
- staging: string;
5
- current: string;
6
- previous: string;
7
- bin: string;
8
- launcher: string;
9
- lock: string;
10
- }
11
- export declare function resolveInstallRoot(env?: NodeJS.ProcessEnv, home?: string): string;
12
- export declare function managedPaths(root: string): ManagedPaths;
13
- export declare function isStableVersion(value: string): boolean;
14
- export declare function compareVersions(a: string, b: string): number;
15
- export declare function renderManagedLauncher(root: string, nodePath: string): string;
16
- export declare function writeManagedLauncher(root: string, nodePath?: string): string;
17
- export declare function readActiveVersion(root: string): string | undefined;
18
- export declare function readPreviousVersion(root: string): string | undefined;
19
- export interface ManagedInstallStatus {
20
- operationId: string;
21
- state: "starting" | "downloading" | "installing" | "verifying" | "activating" | "restarting" | "done" | "failed";
22
- phase?: string;
23
- target?: string;
24
- fromVersion?: string;
25
- error?: string;
26
- log?: string;
27
- updatedAt: number;
28
- }
29
- export interface ManagedInstallOptions {
30
- version: string;
31
- installRoot: string;
32
- dataDir: string;
33
- operationId?: string;
34
- expectedIntegrity?: string;
35
- expectedIntegrities?: Record<string, string>;
36
- nodePath?: string;
37
- npmCommand?: string;
38
- restart?: boolean;
39
- rollback?: boolean;
40
- now?: () => number;
41
- onStatus?: (status: ManagedInstallStatus) => void;
42
- }
43
- export interface ManagedInstallResult {
44
- version: string;
45
- previousVersion?: string;
46
- releaseDir: string;
47
- launcherPath: string;
48
- }
49
- export declare function installManagedRelease(opts: ManagedInstallOptions): Promise<ManagedInstallResult>;
50
- export declare function writeManagedStatus(dataDir: string, status: ManagedInstallStatus): void;
51
- export declare function readManagedStatus(dataDir: string): ManagedInstallStatus | undefined;