crewly 1.11.4 → 1.11.6

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 (75) hide show
  1. package/dist/backend/backend/src/constants.d.ts +22 -1
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +22 -1
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts +90 -0
  6. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
  7. package/dist/backend/backend/src/services/backup/backup-archive.service.js +309 -0
  8. package/dist/backend/backend/src/services/backup/backup-archive.service.js.map +1 -0
  9. package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
  10. package/dist/backend/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
  11. package/dist/backend/backend/src/services/backup/backup-cloud.client.js +134 -0
  12. package/dist/backend/backend/src/services/backup/backup-cloud.client.js.map +1 -0
  13. package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts +78 -0
  14. package/dist/backend/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
  15. package/dist/backend/backend/src/services/backup/backup-restore.service.js +358 -0
  16. package/dist/backend/backend/src/services/backup/backup-restore.service.js.map +1 -0
  17. package/dist/backend/backend/src/services/backup/backup.types.d.ts +163 -0
  18. package/dist/backend/backend/src/services/backup/backup.types.d.ts.map +1 -0
  19. package/dist/backend/backend/src/services/backup/backup.types.js +13 -0
  20. package/dist/backend/backend/src/services/backup/backup.types.js.map +1 -0
  21. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +29 -2
  22. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
  23. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +97 -13
  24. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
  25. package/dist/cli/backend/src/constants.d.ts +22 -1
  26. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  27. package/dist/cli/backend/src/constants.js +22 -1
  28. package/dist/cli/backend/src/constants.js.map +1 -1
  29. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts +70 -0
  30. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.d.ts.map +1 -0
  31. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js +427 -0
  32. package/dist/cli/backend/src/controllers/cloud/cloud-google-auth.controller.js.map +1 -0
  33. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts +90 -0
  34. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts.map +1 -0
  35. package/dist/cli/backend/src/services/backup/backup-archive.service.js +309 -0
  36. package/dist/cli/backend/src/services/backup/backup-archive.service.js.map +1 -0
  37. package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts +75 -0
  38. package/dist/cli/backend/src/services/backup/backup-cloud.client.d.ts.map +1 -0
  39. package/dist/cli/backend/src/services/backup/backup-cloud.client.js +134 -0
  40. package/dist/cli/backend/src/services/backup/backup-cloud.client.js.map +1 -0
  41. package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts +78 -0
  42. package/dist/cli/backend/src/services/backup/backup-restore.service.d.ts.map +1 -0
  43. package/dist/cli/backend/src/services/backup/backup-restore.service.js +358 -0
  44. package/dist/cli/backend/src/services/backup/backup-restore.service.js.map +1 -0
  45. package/dist/cli/backend/src/services/backup/backup.types.d.ts +163 -0
  46. package/dist/cli/backend/src/services/backup/backup.types.d.ts.map +1 -0
  47. package/dist/cli/backend/src/services/backup/backup.types.js +13 -0
  48. package/dist/cli/backend/src/services/backup/backup.types.js.map +1 -0
  49. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts +410 -0
  50. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -0
  51. package/dist/cli/backend/src/services/cloud/cloud-client.service.js +863 -0
  52. package/dist/cli/backend/src/services/cloud/cloud-client.service.js.map +1 -0
  53. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts +292 -0
  54. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -0
  55. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js +1093 -0
  56. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js.map +1 -0
  57. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +328 -0
  58. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -0
  59. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +171 -0
  60. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -0
  61. package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts +89 -0
  62. package/dist/cli/backend/src/services/cloud/device-identity.service.d.ts.map +1 -0
  63. package/dist/cli/backend/src/services/cloud/device-identity.service.js +148 -0
  64. package/dist/cli/backend/src/services/cloud/device-identity.service.js.map +1 -0
  65. package/dist/cli/backend/src/services/user/user-identity.service.d.ts +86 -0
  66. package/dist/cli/backend/src/services/user/user-identity.service.d.ts.map +1 -0
  67. package/dist/cli/backend/src/services/user/user-identity.service.js +190 -0
  68. package/dist/cli/backend/src/services/user/user-identity.service.js.map +1 -0
  69. package/dist/cli/cli/src/commands/backup.d.ts +31 -0
  70. package/dist/cli/cli/src/commands/backup.d.ts.map +1 -0
  71. package/dist/cli/cli/src/commands/backup.js +280 -0
  72. package/dist/cli/cli/src/commands/backup.js.map +1 -0
  73. package/dist/cli/cli/src/index.js +10 -0
  74. package/dist/cli/cli/src/index.js.map +1 -1
  75. package/package.json +1 -1
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Backup Archive Service (P0)
3
+ *
4
+ * Builds a portable workspace backup: a single `.tar.gz` containing a
5
+ * top-level `manifest.json`, the captured `CREWLY_HOME` globals (under
6
+ * `home/`), each project's `.crewly/` tree (under `projects/<id>/`), and
7
+ * optionally `chat.db`.
8
+ *
9
+ * Runs locally with no Cloud dependency — `crewly backup create`. The Cloud
10
+ * upload/park step (Pro-gated) is a later phase and consumes the archive this
11
+ * service produces. See specs/2026-06-07-workspace-backup.md.
12
+ *
13
+ * @module services/backup/backup-archive.service
14
+ */
15
+ import { createHash } from 'node:crypto';
16
+ import { createReadStream } from 'node:fs';
17
+ import * as fs from 'node:fs/promises';
18
+ import * as os from 'node:os';
19
+ import * as path from 'node:path';
20
+ import { execFile } from 'node:child_process';
21
+ import { promisify } from 'node:util';
22
+ import { create as tarCreate } from 'tar';
23
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
24
+ import { safeReadJson } from '../../utils/file-io.utils.js';
25
+ import { LoggerService } from '../core/logger.service.js';
26
+ import { BACKUP_SCHEMA_VERSION, } from './backup.types.js';
27
+ const execFileAsync = promisify(execFile);
28
+ /**
29
+ * Top-level CREWLY_HOME entries that are MACHINE-SPECIFIC, secret, runtime, or
30
+ * would cause recursion — never captured. chat.db is handled separately via
31
+ * the SQLite online backup API, so it's excluded from the generic file walk.
32
+ */
33
+ const GLOBAL_EXCLUDE_TOPLEVEL = new Set([
34
+ 'device.json',
35
+ 'cloud',
36
+ 'credentials',
37
+ 'telegram-credentials.json',
38
+ 'runtime.json',
39
+ '.orchestrator-state',
40
+ 'session-state.json',
41
+ 'logs',
42
+ 'teams-backup.json',
43
+ 'teams-backup-history',
44
+ 'backups', // our own output dir
45
+ 'chat.db',
46
+ 'chat.db-wal',
47
+ 'chat.db-shm',
48
+ ]);
49
+ /** Directory names excluded at ANY depth (runtime/session/log noise). */
50
+ const EXCLUDE_DIR_ANYWHERE = new Set(['sessions', 'logs', '.orchestrator-state']);
51
+ /** True for files excluded at any depth (ephemeral session logs). */
52
+ function isExcludedFile(name) {
53
+ return name.endsWith('.jsonl');
54
+ }
55
+ /**
56
+ * Service that builds workspace backup archives.
57
+ */
58
+ export class BackupArchiveService {
59
+ logger;
60
+ constructor(logger) {
61
+ this.logger = logger ?? LoggerService.getInstance().createComponentLogger('BackupArchive');
62
+ }
63
+ /**
64
+ * Build a workspace backup archive.
65
+ *
66
+ * Captures CREWLY_HOME globals (exclude-based, so new data domains are
67
+ * picked up automatically), each project's `.crewly/` tree (walked from
68
+ * projects.json) with git provenance, and chat.db (unless excluded). Stages
69
+ * everything into a temp dir, writes the manifest, and tars it to `outPath`.
70
+ *
71
+ * @param options - Build options (createdAt is required — no clock in lib code)
72
+ * @returns The archive path, manifest, and total captured bytes
73
+ * @throws If CREWLY_HOME does not exist or the tar write fails
74
+ */
75
+ async createArchive(options) {
76
+ const home = options.homePath ?? getCrewlyHomePath();
77
+ const homeStat = await fs.stat(home).catch(() => null);
78
+ if (!homeStat?.isDirectory()) {
79
+ throw new Error(`CREWLY_HOME not found or not a directory: ${home}`);
80
+ }
81
+ const outPath = options.outPath ??
82
+ path.join(home, 'backups', `workspace-${options.createdAt.replace(/[:.]/g, '-')}.tar.gz`);
83
+ const staging = await fs.mkdtemp(path.join(os.tmpdir(), 'crewly-backup-'));
84
+ try {
85
+ let totalBytes = 0;
86
+ // 1) Global CREWLY_HOME files → staging/home/<rel>
87
+ const global = [];
88
+ for (const rel of await this.collectGlobalFiles(home)) {
89
+ const entry = await this.stageFile(home, rel, staging, path.join('home', rel));
90
+ global.push(entry);
91
+ totalBytes += entry.bytes;
92
+ }
93
+ // 2) Per-project .crewly trees → staging/projects/<id>/<rel>
94
+ const projects = await this.collectProjects(home, staging);
95
+ for (const p of projects)
96
+ for (const f of p.files)
97
+ totalBytes += f.bytes;
98
+ // 3) chat.db via SQLite online backup → staging/chat.db
99
+ const chatDb = options.excludeChatDb
100
+ ? { included: false, skippedReason: 'excluded by option' }
101
+ : await this.captureChatDb(home, staging);
102
+ if (chatDb.bytes)
103
+ totalBytes += chatDb.bytes;
104
+ // 4) Manifest
105
+ const manifest = {
106
+ schemaVersion: BACKUP_SCHEMA_VERSION,
107
+ crewlyVersion: this.readCrewlyVersion(),
108
+ createdAt: options.createdAt,
109
+ sourceDeviceId: options.sourceDeviceId ?? null,
110
+ sourceDeviceName: options.sourceDeviceName ?? null,
111
+ sourceHomePath: home,
112
+ ownerSub: options.ownerSub ?? null,
113
+ global,
114
+ projects,
115
+ chatDb,
116
+ crypto: { mode: 'none' },
117
+ };
118
+ await fs.writeFile(path.join(staging, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
119
+ // 5) tar.gz the staging dir
120
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
121
+ await tarCreate({ gzip: true, file: outPath, cwd: staging }, ['.']);
122
+ this.logger.info('Workspace backup created', {
123
+ outPath,
124
+ globalFiles: global.length,
125
+ projects: projects.length,
126
+ chatDb: chatDb.included,
127
+ totalBytes,
128
+ });
129
+ return { archivePath: outPath, manifest, totalBytes };
130
+ }
131
+ finally {
132
+ await fs.rm(staging, { recursive: true, force: true }).catch(() => undefined);
133
+ }
134
+ }
135
+ /**
136
+ * Recursively collect capturable global files under CREWLY_HOME (relative
137
+ * paths). Exclude-based so new data domains are captured automatically.
138
+ *
139
+ * @param home - CREWLY_HOME absolute path
140
+ * @returns Relative file paths (POSIX-style) to capture
141
+ */
142
+ async collectGlobalFiles(home) {
143
+ const out = [];
144
+ const walk = async (absDir, rel, isTop) => {
145
+ const entries = await fs.readdir(absDir, { withFileTypes: true }).catch(() => []);
146
+ for (const e of entries) {
147
+ if (isTop && GLOBAL_EXCLUDE_TOPLEVEL.has(e.name))
148
+ continue;
149
+ if (e.isDirectory() && EXCLUDE_DIR_ANYWHERE.has(e.name))
150
+ continue;
151
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
152
+ if (e.isDirectory()) {
153
+ await walk(path.join(absDir, e.name), childRel, false);
154
+ }
155
+ else if (e.isFile() && !isExcludedFile(e.name)) {
156
+ out.push(childRel);
157
+ }
158
+ }
159
+ };
160
+ await walk(home, '', true);
161
+ return out;
162
+ }
163
+ /**
164
+ * Walk projects.json and capture each project's `.crewly/` tree + git
165
+ * provenance into staging/projects/<id>/.
166
+ *
167
+ * @param home - CREWLY_HOME absolute path
168
+ * @param staging - staging dir root
169
+ * @returns Project entries for the manifest
170
+ */
171
+ async collectProjects(home, staging) {
172
+ const projects = await safeReadJson(path.join(home, 'projects.json'), []);
173
+ const result = [];
174
+ for (const proj of projects) {
175
+ const crewlyDir = path.join(proj.path, '.crewly');
176
+ const dirStat = await fs.stat(crewlyDir).catch(() => null);
177
+ if (!dirStat?.isDirectory())
178
+ continue; // project gone / never initialized
179
+ const files = [];
180
+ const walk = async (absDir, rel) => {
181
+ const entries = await fs.readdir(absDir, { withFileTypes: true }).catch(() => []);
182
+ for (const e of entries) {
183
+ if (e.isDirectory() && EXCLUDE_DIR_ANYWHERE.has(e.name))
184
+ continue;
185
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
186
+ if (e.isDirectory())
187
+ await walk(path.join(absDir, e.name), childRel);
188
+ else if (e.isFile() && !isExcludedFile(e.name)) {
189
+ const archivePath = `projects/${proj.id}/.crewly/${childRel}`;
190
+ files.push(await this.stageFile(crewlyDir, childRel, staging, archivePath));
191
+ }
192
+ }
193
+ };
194
+ await walk(crewlyDir, '');
195
+ result.push({
196
+ id: proj.id,
197
+ name: proj.name,
198
+ sourcePath: proj.path,
199
+ git: await this.readGitProvenance(proj.path),
200
+ files,
201
+ });
202
+ }
203
+ return result;
204
+ }
205
+ /**
206
+ * Read `origin` remote + HEAD commit for a project dir (best-effort).
207
+ *
208
+ * @param projectPath - Absolute project path
209
+ * @returns Git provenance (nulls when not a repo / unavailable)
210
+ */
211
+ async readGitProvenance(projectPath) {
212
+ const run = async (args) => {
213
+ try {
214
+ const { stdout } = await execFileAsync('git', ['-C', projectPath, ...args], { timeout: 5000 });
215
+ const v = stdout.trim();
216
+ return v.length > 0 ? v : null;
217
+ }
218
+ catch {
219
+ return null;
220
+ }
221
+ };
222
+ return {
223
+ remote: await run(['remote', 'get-url', 'origin']),
224
+ commit: await run(['rev-parse', 'HEAD']),
225
+ };
226
+ }
227
+ /**
228
+ * Capture chat.db via the SQLite online backup API (consistent snapshot of a
229
+ * possibly-live DB). Degrades gracefully when chat.db is absent or the native
230
+ * module can't load — the backup just omits it with a recorded reason.
231
+ *
232
+ * @param home - CREWLY_HOME absolute path
233
+ * @param staging - staging dir root
234
+ * @returns chat.db manifest record
235
+ */
236
+ async captureChatDb(home, staging) {
237
+ const dbPath = path.join(home, 'chat.db');
238
+ if (!(await fs.stat(dbPath).catch(() => null))?.isFile()) {
239
+ return { included: false, skippedReason: 'chat.db not present' };
240
+ }
241
+ const dest = path.join(staging, 'chat.db');
242
+ try {
243
+ const mod = (await import('better-sqlite3'));
244
+ const Database = mod.default;
245
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
246
+ try {
247
+ await db.backup(dest);
248
+ }
249
+ finally {
250
+ db.close();
251
+ }
252
+ }
253
+ catch (err) {
254
+ const reason = err instanceof Error ? err.message : String(err);
255
+ this.logger.warn('chat.db capture skipped', { reason });
256
+ return { included: false, skippedReason: `sqlite backup failed: ${reason}` };
257
+ }
258
+ const { sha256, bytes } = await hashAndSize(dest);
259
+ return { included: true, sha256, bytes };
260
+ }
261
+ /**
262
+ * Copy a source file into the staging archive layout, returning its manifest
263
+ * entry (archive-relative path + sha256 + size).
264
+ *
265
+ * @param srcRoot - Root the relative path is resolved against
266
+ * @param rel - Source path relative to srcRoot (POSIX-style)
267
+ * @param staging - staging dir root
268
+ * @param archivePath - Destination path inside the archive
269
+ * @returns Manifest file entry
270
+ */
271
+ async stageFile(srcRoot, rel, staging, archivePath) {
272
+ const src = path.join(srcRoot, ...rel.split('/'));
273
+ const dest = path.join(staging, ...archivePath.split('/'));
274
+ await fs.mkdir(path.dirname(dest), { recursive: true });
275
+ await fs.copyFile(src, dest);
276
+ const { sha256, bytes } = await hashAndSize(dest);
277
+ return { path: archivePath, sha256, bytes };
278
+ }
279
+ /**
280
+ * Read the running Crewly version (best-effort). Uses npm_package_version —
281
+ * the same source as tracing.service.ts — which works under both the ESM
282
+ * runtime and the CJS test runner (no import.meta / __dirname).
283
+ *
284
+ * @returns Version string or 'unknown'
285
+ */
286
+ readCrewlyVersion() {
287
+ return process.env.npm_package_version ?? 'unknown';
288
+ }
289
+ }
290
+ /**
291
+ * Stream a file through SHA-256 and return the hex digest + byte size.
292
+ *
293
+ * @param filePath - File to hash
294
+ * @returns sha256 (hex) and byte size
295
+ */
296
+ async function hashAndSize(filePath) {
297
+ return new Promise((resolve, reject) => {
298
+ const hash = createHash('sha256');
299
+ let bytes = 0;
300
+ const stream = createReadStream(filePath);
301
+ stream.on('data', (chunk) => {
302
+ bytes += chunk.length;
303
+ hash.update(chunk);
304
+ });
305
+ stream.on('error', reject);
306
+ stream.on('end', () => resolve({ sha256: hash.digest('hex'), bytes }));
307
+ });
308
+ }
309
+ //# sourceMappingURL=backup-archive.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-archive.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/backup/backup-archive.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Backup Cloud Client (P3)
3
+ *
4
+ * OSS-side HTTP client for the Pro-gated cloud backup service
5
+ * (`/api/cloud/backup`). Used by `crewly backup push/pull/list` to park an
6
+ * archive in the cloud and pull it back on another machine.
7
+ *
8
+ * Transport-only: it streams files to/from the cloud and surfaces a friendly
9
+ * upgrade error on 402. Auth (access token) + base URL come from
10
+ * CloudClientService; both are injected so this is testable without the cloud.
11
+ *
12
+ * @module services/backup/backup-cloud.client
13
+ */
14
+ /** Thrown when the account isn't Pro (HTTP 402). */
15
+ export declare class BackupNotProError extends Error {
16
+ readonly code = "upgrade_required";
17
+ constructor(message?: string);
18
+ }
19
+ /** Thrown on any other cloud backup HTTP failure. */
20
+ export declare class BackupCloudError extends Error {
21
+ readonly status?: number | undefined;
22
+ constructor(message: string, status?: number | undefined);
23
+ }
24
+ /** A cloud snapshot as returned by the service (client-facing view). */
25
+ export interface CloudBackupItem {
26
+ backupId: string;
27
+ deviceName: string | null;
28
+ deviceId: string | null;
29
+ sizeBytes: number;
30
+ sha256: string | null;
31
+ cryptoMode: 'sse' | 'none';
32
+ createdAt: string;
33
+ }
34
+ /** Metadata sent with an upload. */
35
+ export interface PushMeta {
36
+ backupId?: string;
37
+ deviceName?: string | null;
38
+ deviceId?: string | null;
39
+ sha256?: string | null;
40
+ }
41
+ /** HTTP client for the cloud backup endpoints. */
42
+ export declare class BackupCloudClient {
43
+ private readonly opts;
44
+ private readonly fetchImpl;
45
+ constructor(opts: {
46
+ baseUrl: string;
47
+ token: string;
48
+ fetchImpl?: typeof fetch;
49
+ });
50
+ private url;
51
+ private authHeaders;
52
+ /** List this account's cloud snapshots (newest first). */
53
+ list(): Promise<CloudBackupItem[]>;
54
+ /**
55
+ * Upload an archive to the cloud. Streams the file with its Content-Length so
56
+ * the service can enforce quota before accepting the blob.
57
+ *
58
+ * @param archivePath - Local archive (.tar.gz)
59
+ * @param meta - Optional backupId + device/sha metadata
60
+ * @returns The created snapshot record
61
+ */
62
+ push(archivePath: string, meta?: PushMeta): Promise<CloudBackupItem>;
63
+ /**
64
+ * Download a snapshot to `destPath` (streamed).
65
+ *
66
+ * @param backupId - Snapshot id
67
+ * @param destPath - Local file to write
68
+ */
69
+ pull(backupId: string, destPath: string): Promise<void>;
70
+ /** Delete a cloud snapshot. */
71
+ remove(backupId: string): Promise<void>;
72
+ /** Map non-2xx responses to typed errors (402 → upgrade). */
73
+ private assertOk;
74
+ }
75
+ //# sourceMappingURL=backup-cloud.client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-cloud.client.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/backup/backup-cloud.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,oDAAoD;AACpD,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,sBAAsB;gBACvB,OAAO,SAA+E;CAInG;AAED,qDAAqD;AACrD,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,MAAM,CAAC,EAAE,MAAM;gBAD/B,OAAO,EAAE,MAAM,EACC,MAAM,CAAC,EAAE,MAAM,YAAA;CAKlC;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oCAAoC;AACpC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,kDAAkD;AAClD,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAGtB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;KAAE;IAKrF,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,WAAW;IAInB,0DAA0D;IACpD,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAOxC;;;;;;;OAOG;IACG,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,QAAa,GAAG,OAAO,CAAC,eAAe,CAAC;IAsB9E;;;;;OAKG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7D,+BAA+B;IACzB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7C,6DAA6D;YAC/C,QAAQ;CAoBvB"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Backup Cloud Client (P3)
3
+ *
4
+ * OSS-side HTTP client for the Pro-gated cloud backup service
5
+ * (`/api/cloud/backup`). Used by `crewly backup push/pull/list` to park an
6
+ * archive in the cloud and pull it back on another machine.
7
+ *
8
+ * Transport-only: it streams files to/from the cloud and surfaces a friendly
9
+ * upgrade error on 402. Auth (access token) + base URL come from
10
+ * CloudClientService; both are injected so this is testable without the cloud.
11
+ *
12
+ * @module services/backup/backup-cloud.client
13
+ */
14
+ import { createReadStream, createWriteStream } from 'node:fs';
15
+ import * as fs from 'node:fs/promises';
16
+ import * as path from 'node:path';
17
+ import { Readable } from 'node:stream';
18
+ import { pipeline } from 'node:stream/promises';
19
+ /** Thrown when the account isn't Pro (HTTP 402). */
20
+ export class BackupNotProError extends Error {
21
+ code = 'upgrade_required';
22
+ constructor(message = 'Cloud backup requires a Pro plan. Upgrade at https://crewlyai.com/pricing.') {
23
+ super(message);
24
+ this.name = 'BackupNotProError';
25
+ }
26
+ }
27
+ /** Thrown on any other cloud backup HTTP failure. */
28
+ export class BackupCloudError extends Error {
29
+ status;
30
+ constructor(message, status) {
31
+ super(message);
32
+ this.status = status;
33
+ this.name = 'BackupCloudError';
34
+ }
35
+ }
36
+ /** HTTP client for the cloud backup endpoints. */
37
+ export class BackupCloudClient {
38
+ opts;
39
+ fetchImpl;
40
+ constructor(opts) {
41
+ this.opts = opts;
42
+ this.fetchImpl = opts.fetchImpl ?? fetch;
43
+ }
44
+ url(suffix) {
45
+ return `${this.opts.baseUrl.replace(/\/$/, '')}/api/cloud/backup${suffix}`;
46
+ }
47
+ authHeaders(extra = {}) {
48
+ return { Authorization: `Bearer ${this.opts.token}`, ...extra };
49
+ }
50
+ /** List this account's cloud snapshots (newest first). */
51
+ async list() {
52
+ const res = await this.fetchImpl(this.url(''), { headers: this.authHeaders() });
53
+ await this.assertOk(res);
54
+ const json = (await res.json());
55
+ return json.data?.backups ?? [];
56
+ }
57
+ /**
58
+ * Upload an archive to the cloud. Streams the file with its Content-Length so
59
+ * the service can enforce quota before accepting the blob.
60
+ *
61
+ * @param archivePath - Local archive (.tar.gz)
62
+ * @param meta - Optional backupId + device/sha metadata
63
+ * @returns The created snapshot record
64
+ */
65
+ async push(archivePath, meta = {}) {
66
+ const stat = await fs.stat(archivePath);
67
+ const query = meta.backupId ? `?backupId=${encodeURIComponent(meta.backupId)}` : '';
68
+ const headers = this.authHeaders({
69
+ 'Content-Type': 'application/gzip',
70
+ 'Content-Length': String(stat.size),
71
+ ...(meta.deviceName ? { 'X-Device-Name': meta.deviceName } : {}),
72
+ ...(meta.deviceId ? { 'X-Device-Id': meta.deviceId } : {}),
73
+ ...(meta.sha256 ? { 'X-Backup-Sha256': meta.sha256 } : {}),
74
+ });
75
+ const res = await this.fetchImpl(this.url(query), {
76
+ method: 'POST',
77
+ headers,
78
+ body: createReadStream(archivePath),
79
+ // Node fetch requires this for a streaming request body.
80
+ duplex: 'half',
81
+ });
82
+ await this.assertOk(res);
83
+ const json = (await res.json());
84
+ return json.data;
85
+ }
86
+ /**
87
+ * Download a snapshot to `destPath` (streamed).
88
+ *
89
+ * @param backupId - Snapshot id
90
+ * @param destPath - Local file to write
91
+ */
92
+ async pull(backupId, destPath) {
93
+ const res = await this.fetchImpl(this.url(`/${encodeURIComponent(backupId)}`), { headers: this.authHeaders() });
94
+ await this.assertOk(res);
95
+ if (!res.body)
96
+ throw new BackupCloudError('Empty download response from cloud.');
97
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
98
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(destPath));
99
+ }
100
+ /** Delete a cloud snapshot. */
101
+ async remove(backupId) {
102
+ const res = await this.fetchImpl(this.url(`/${encodeURIComponent(backupId)}`), {
103
+ method: 'DELETE',
104
+ headers: this.authHeaders(),
105
+ });
106
+ await this.assertOk(res);
107
+ }
108
+ /** Map non-2xx responses to typed errors (402 → upgrade). */
109
+ async assertOk(res) {
110
+ if (res.ok)
111
+ return;
112
+ if (res.status === 402) {
113
+ let msg;
114
+ try {
115
+ msg = (await res.json()).error;
116
+ }
117
+ catch {
118
+ /* ignore */
119
+ }
120
+ throw new BackupNotProError(msg);
121
+ }
122
+ let message = `Cloud backup request failed (HTTP ${res.status}).`;
123
+ try {
124
+ const j = (await res.json());
125
+ if (j.error)
126
+ message = j.error;
127
+ }
128
+ catch {
129
+ /* non-JSON body */
130
+ }
131
+ throw new BackupCloudError(message, res.status);
132
+ }
133
+ }
134
+ //# sourceMappingURL=backup-cloud.client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-cloud.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/backup/backup-cloud.client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Backup Restore Service (P1)
3
+ *
4
+ * Restores a workspace archive (produced by BackupArchiveService) onto this
5
+ * machine to resume work after a machine dies / is replaced.
6
+ *
7
+ * Safety model:
8
+ * - `preview()` is non-destructive (dry-run) — reports the plan + conflicts.
9
+ * - `restore()` snapshots the CURRENT CREWLY_HOME first (rollback), refuses
10
+ * on overlapping ids unless mode='overwrite', then applies and — on any
11
+ * failure — rolls back from the snapshot.
12
+ * - device.json is NEVER in the archive, so the target keeps its own identity.
13
+ * - cron `nextRunAt` is reset to null so the scheduler recomputes from
14
+ * `cronExpression` (no stale fires); runtime/session state is wiped so
15
+ * agents start clean.
16
+ *
17
+ * See specs/2026-06-07-workspace-backup.md.
18
+ *
19
+ * @module services/backup/backup-restore.service
20
+ */
21
+ import { type ComponentLogger } from '../core/logger.service.js';
22
+ import { type RestoreOptions, type RestorePlan, type RestoreResult } from './backup.types.js';
23
+ /** Thrown by restore() when mode='abort' and the target has overlapping data. */
24
+ export declare class RestoreConflictError extends Error {
25
+ readonly plan: RestorePlan;
26
+ constructor(message: string, plan: RestorePlan);
27
+ }
28
+ /**
29
+ * Service that restores workspace backup archives.
30
+ */
31
+ export declare class BackupRestoreService {
32
+ private readonly logger;
33
+ constructor(logger?: ComponentLogger);
34
+ /**
35
+ * Non-destructive restore plan (dry-run): what would be created/overwritten,
36
+ * id conflicts, resolved project paths, and what's regenerated/discarded.
37
+ *
38
+ * @param options - Restore options (mode/pathMap affect conflict + path resolution)
39
+ * @returns The plan; never writes to disk
40
+ */
41
+ preview(options: RestoreOptions): Promise<RestorePlan>;
42
+ /**
43
+ * Restore the archive onto this machine.
44
+ *
45
+ * @param options - Restore options
46
+ * @returns Summary of what was restored
47
+ * @throws RestoreConflictError when mode='abort' and ids overlap
48
+ * @throws Error on integrity failure or unsupported schema (no changes applied)
49
+ */
50
+ restore(options: RestoreOptions): Promise<RestoreResult>;
51
+ /** Extract the archive to a fresh temp dir; returns its path. */
52
+ private extract;
53
+ /** Read + minimally validate the manifest from an extracted archive. */
54
+ private readManifest;
55
+ /** Verify every recorded file's sha256 against the extracted bytes. */
56
+ private verifyChecksums;
57
+ /** Build the dry-run plan (conflicts, resolved project paths, warnings). */
58
+ private buildPlan;
59
+ /** Copy every manifest global file from temp/home/* into CREWLY_HOME. */
60
+ private applyGlobals;
61
+ /** Copy each resolved project's `.crewly/` from the archive to its target path. */
62
+ private applyProjects;
63
+ /** Atomically swap chat.db into place (temp copy → rename). */
64
+ private applyChatDb;
65
+ /** Rewrite projects.json `path` entries using the resolved plan mapping. */
66
+ private rewriteProjectsJson;
67
+ /** Reset cron `nextRunAt` so the scheduler recomputes from cronExpression. */
68
+ private resetCron;
69
+ /** Remove runtime/session state so agents start clean on the restored machine. */
70
+ private wipeRuntime;
71
+ /** Recursively copy a directory tree, skipping top-level names in `skipTop`. */
72
+ private copyTree;
73
+ /** Recursive copy without the top-level skip filter. */
74
+ private copyTreeRec;
75
+ private pathExists;
76
+ private readJson;
77
+ }
78
+ //# sourceMappingURL=backup-restore.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-restore.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/backup/backup-restore.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAC;AAQ3B,iFAAiF;AACjF,qBAAa,oBAAqB,SAAQ,KAAK;aAG3B,IAAI,EAAE,WAAW;gBADjC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,WAAW;CAKpC;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAE7B,MAAM,CAAC,EAAE,eAAe;IAIpC;;;;;;OAMG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAW5D;;;;;;;OAOG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA4D9D,iEAAiE;YACnD,OAAO;IASrB,wEAAwE;YAC1D,YAAY;IAe1B,uEAAuE;YACzD,eAAe;IAiB7B,4EAA4E;YAC9D,SAAS;IAqDvB,yEAAyE;YAC3D,YAAY;IAc1B,mFAAmF;YACrE,aAAa;IAmB3B,+DAA+D;YACjD,WAAW;IAezB,4EAA4E;YAC9D,mBAAmB;IAgBjC,8EAA8E;YAChE,SAAS;IAgBvB,kFAAkF;YACpE,WAAW;IAQzB,gFAAgF;YAClE,QAAQ;IAYtB,wDAAwD;YAC1C,WAAW;YAUX,UAAU;YAIV,QAAQ;CAOvB"}