@inixiative/archive 0.6.0 → 0.8.0

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/src/cli.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env bun
2
2
  import { randomBytes } from 'node:crypto';
3
3
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { hostname } from 'node:os';
4
5
  import { join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
5
7
  import { parseArgs } from 'node:util';
6
8
  import {
7
9
  agentStatus,
@@ -14,21 +16,25 @@ import {
14
16
  writeAgents,
15
17
  } from './agents';
16
18
  import { importChatGPTThread } from './chatgpt';
17
- import {
18
- archiveRequest,
19
- kingdomFields,
20
- publishArchive,
21
- routingPreview,
22
- searchRemotes,
23
- syncArchives,
24
- } from './client';
19
+ import { archiveRequest, routingPreview, searchRemotes, syncArchives } from './client';
25
20
  import { collectSessions } from './collector';
26
21
  import { archiveDestinationSchema, connectDestination, readDestinations } from './config';
27
22
  import { importTranscriptFile } from './import-file';
28
- import { archiveKey } from './index';
29
- import { LocalArchiveStore } from './local';
23
+ import { pairWithKingdom, verifyKingdomDestination } from './kingdom';
30
24
  import { previewImports } from './preview';
25
+ import { ArchiveClient, DEFAULT_URL } from './remote';
31
26
  import { startArchiveServer } from './server';
27
+ import { ArchiveStore } from './store';
28
+
29
+ /** Applies pending schema migrations to the archive database. */
30
+ export function migrate(databaseUrl: string) {
31
+ const schema = fileURLToPath(new URL('../prisma/schema.prisma', import.meta.url));
32
+ const run = Bun.spawnSync(
33
+ [process.execPath, 'x', 'prisma', 'migrate', 'deploy', '--schema', schema],
34
+ { env: { ...process.env, DATABASE_URL: databaseUrl }, stdout: 'ignore', stderr: 'pipe' },
35
+ );
36
+ if (!run.success) throw new Error(`Archive migration failed: ${run.stderr.toString().trim()}`);
37
+ }
32
38
 
33
39
  export async function runCli(args = Bun.argv.slice(2)) {
34
40
  const { values: v, positionals } = parseArgs({
@@ -36,15 +42,14 @@ export async function runCli(args = Bun.argv.slice(2)) {
36
42
  allowPositionals: true,
37
43
  options: {
38
44
  home: { type: 'string', default: defaultHome() },
39
- store: { type: 'string' },
40
45
  config: { type: 'string' },
41
46
  url: { type: 'string' },
42
47
  kind: { type: 'string', default: 'archive' },
43
48
  'project-id': { type: 'string' },
44
- 'connection-id': { type: 'string' },
45
- 'owner-model': { type: 'string' },
46
- 'organization-id': { type: 'string' },
47
- 'space-id': { type: 'string' },
49
+ 'integration-id': { type: 'string' },
50
+ 'resource-id': { type: 'string' },
51
+ 'credential-file': { type: 'string' },
52
+ kingdom: { type: 'string' },
48
53
  'token-env': { type: 'string' },
49
54
  'token-file': { type: 'string' },
50
55
  file: { type: 'string' },
@@ -66,61 +71,97 @@ export async function runCli(args = Bun.argv.slice(2)) {
66
71
  watch: { type: 'boolean', default: false },
67
72
  sync: { type: 'boolean', default: false },
68
73
  remote: { type: 'boolean', default: false },
69
- destination: { type: 'string' },
70
74
  help: { type: 'boolean' },
71
75
  },
72
76
  });
73
77
  const home = resolve(v.home!),
74
- config = v.config ?? join(home, 'destinations.json');
75
- const storePath = v.store ?? join(home, 'archives.sqlite');
78
+ config = v.config ?? join(home, 'destinations.json'),
79
+ tokenFile = v['token-file'] ? resolve(v['token-file']) : join(home, 'server.token');
76
80
  const output = (value: unknown) => console.log(JSON.stringify(value, null, 2));
77
81
  const command = positionals[0];
78
82
  if (v.help || !command) {
79
83
  console.log(
80
- 'Archive: init | serve | connect (setup) | preview | import | collect | list | export | tag | routes | sync | search | agents\n' +
81
- 'connect --url HTTPS_URL --project-id ID (--token-env ENV | --token-file PATH) [--kind kingdom [--connection-id ID] [--owner-model M --organization-id UUID --space-id UUID]]\n' +
82
- 'import --file PATH --source codex|claude-code --project-id ID [--tag TAG]\n' +
83
- 'tag --id ID [--tag TAG ...] [--untag TAG ...]\n' +
84
+ 'Archive: init | up | down | serve | connect (setup) | preview | import | collect | list | export | tag | routes | sync | search | agents\n' +
85
+ 'up | down (the local Archive: bundled compose.yaml with Postgres, data in --home)\n' +
86
+ 'serve [--port 4700 --hostname 127.0.0.1] [--sync] (DATABASE_URL; token from ARCHIVE_SERVER_TOKEN or --token-file)\n' +
87
+ 'import --file PATH --source codex|claude-code|chatgpt --project-id ID [--tag TAG]\n' +
84
88
  'collect --directory HISTORY --source codex|claude-code --project-root EXACT_CWD [--project-root ...] [--worktrees] [--atlas] --project-id ID [--watch]\n' +
85
- 'sync [--watch] | routes | search --query TEXT [--remote]\n' +
86
- 'serve --home PATH [--port 4411 --hostname 127.0.0.1] [--sync]\n' +
89
+ 'list | export --id ID | tag --id ID [--tag TAG ...] [--untag TAG ...] | search --query TEXT [--remote]\n' +
90
+ 'pair --kingdom KINGDOM_API_URL [--name NAME] (pair this Archive with Kingdom; approve the review code there)\n' +
91
+ 'connect --url HTTPS_URL --project-id ID (--token-env ENV | --token-file PATH) (a remote Archive, for serve --sync)\n' +
92
+ 'connect --kind kingdom --url KINGDOM_API_URL --credential-file PATH --integration-id UUID --resource-id UUID --project-id ID (a hosted Archive through Kingdom)\n' +
93
+ 'sync | routes (DATABASE_URL: publish to, or preview, destinations once)\n' +
87
94
  'agents add-collector --name N --source codex|claude-code --project-id ID --project-root DIR [--project-root ...] [--directory HISTORY] [--worktrees] [--atlas]\n' +
88
- 'agents remove-collector --name N | serve on|off [--port P] | sync on|off | install | uninstall | status\n' +
89
- 'All commands accept --home PATH; integrations may use --store FILE --config FILE.',
95
+ 'agents remove-collector --name N | server --url URL | install | uninstall | status\n' +
96
+ `Data commands talk to the Archive server at --url (default ${DEFAULT_URL}) with the token in --token-file (default <home>/server.token) or ARCHIVE_TOKEN.`,
90
97
  );
91
98
  return;
92
99
  }
93
- if (command === 'init') {
100
+ const readToken = () =>
101
+ existsSync(tokenFile) ? readFileSync(tokenFile, 'utf8').trim() : undefined;
102
+ const init = () => {
94
103
  mkdirSync(home, { recursive: true, mode: 0o700 });
95
- const tokenFile = join(home, 'server.token');
96
104
  if (!existsSync(tokenFile))
97
- writeFileSync(tokenFile, randomBytes(32).toString('hex') + '\n', { mode: 0o600, flag: 'wx' });
105
+ writeFileSync(tokenFile, `${randomBytes(32).toString('hex')}\n`, { mode: 0o600, flag: 'wx' });
98
106
  chmodSync(tokenFile, 0o600);
99
- const store = new LocalArchiveStore(storePath);
100
- store.close();
101
- output({ initialized: true, home, store: storePath, tokenFile });
107
+ };
108
+ if (command === 'init') {
109
+ init();
110
+ output({ initialized: true, home, tokenFile });
102
111
  return;
103
112
  }
113
+ if (command === 'up' || command === 'down') {
114
+ // The machine's one local Archive: the bundled compose file, with its data in the home.
115
+ if (command === 'up') init();
116
+ const compose = fileURLToPath(new URL('../compose.yaml', import.meta.url));
117
+ const run = Bun.spawnSync(
118
+ [
119
+ 'docker',
120
+ 'compose',
121
+ '--project-name',
122
+ 'archive',
123
+ '--file',
124
+ compose,
125
+ ...(command === 'up' ? ['up', '--detach', '--wait'] : ['down']),
126
+ ],
127
+ {
128
+ env: {
129
+ ...process.env,
130
+ ARCHIVE_DATA_DIR: home,
131
+ // Compose requires the variable even to stop; down never needs the real token.
132
+ ARCHIVE_SERVER_TOKEN: readToken() ?? 'x'.repeat(32),
133
+ },
134
+ stdout: 'inherit',
135
+ stderr: 'inherit',
136
+ },
137
+ );
138
+ if (!run.success) throw new Error(`docker compose ${command} failed`);
139
+ output(command === 'up' ? { running: DEFAULT_URL, home } : { stopped: true });
140
+ return;
141
+ }
142
+ const databaseUrl = () => {
143
+ const url = process.env.DATABASE_URL;
144
+ if (!url) throw new Error('DATABASE_URL is required');
145
+ return url;
146
+ };
104
147
  if (command === 'serve') {
105
- const tokenFile = v['token-file'] ?? join(home, 'server.token');
106
- const token =
107
- process.env.ARCHIVE_SERVER_TOKEN ??
108
- (existsSync(tokenFile) ? readFileSync(tokenFile, 'utf8').trim() : '');
109
- const instance = startArchiveServer({
110
- store: storePath,
111
- token,
112
- port: Number(v.port ?? process.env.PORT ?? 4411),
148
+ const url = databaseUrl();
149
+ migrate(url);
150
+ const instance = await startArchiveServer({
151
+ databaseUrl: url,
152
+ token: process.env.ARCHIVE_SERVER_TOKEN ?? readToken() ?? '',
153
+ port: Number(v.port ?? process.env.PORT ?? 4700),
113
154
  hostname: v.hostname,
114
155
  });
115
- output({ listening: instance.server.url.href, store: storePath, sync: v.sync });
156
+ output({ listening: instance.server.url.href, sync: v.sync });
116
157
  let stopped = false;
117
- // One process owns the store: the server and its sync share it, as in a container.
158
+ // One process owns the database connection: the server and its sync share it.
118
159
  const sync = (async () => {
119
160
  while (v.sync && !stopped) {
120
161
  try {
121
162
  const results = await syncArchives(instance.store, readDestinations(config));
122
- const failed = results.filter((r) => r.status.startsWith('failed')).length;
123
- if (failed) console.error(JSON.stringify({ sync: 'failed', archives: failed }));
163
+ const failed = results.filter((r) => r.status.startsWith('failed'));
164
+ if (failed.length) console.error(JSON.stringify({ sync: 'failed', archives: failed }));
124
165
  } catch {
125
166
  console.error(JSON.stringify({ sync: 'unavailable' }));
126
167
  }
@@ -137,45 +178,61 @@ export async function runCli(args = Bun.argv.slice(2)) {
137
178
  process.once('SIGINT', stop);
138
179
  return;
139
180
  }
181
+ if (command === 'pair') {
182
+ if (!v.kingdom) throw new Error('pair requires --kingdom KINGDOM_API_URL');
183
+ const local = new ArchiveClient({
184
+ url: v.url ?? DEFAULT_URL,
185
+ token: process.env.ARCHIVE_TOKEN ?? readToken() ?? '',
186
+ });
187
+ output(
188
+ await pairWithKingdom({
189
+ kingdom: v.kingdom,
190
+ name: v.name ?? `${hostname()} Archive`,
191
+ sourceId: await local.sourceId(),
192
+ directory: join(home, 'kingdom'),
193
+ onReview: (review) => console.error(JSON.stringify(review)),
194
+ }),
195
+ );
196
+ return;
197
+ }
140
198
  if (command === 'connect' || command === 'setup') {
141
- const tokenFile = v['token-file'] && resolve(v['token-file']);
142
- const destination = archiveDestinationSchema.parse({
143
- projectId: v['project-id'],
144
- url: v.url,
145
- tokenEnv: v['token-env'] ?? (tokenFile ? undefined : 'ARCHIVE_TOKEN'),
146
- tokenFile,
147
- kind: v.kind,
148
- ...(v.kind === 'kingdom'
199
+ const destination = archiveDestinationSchema.parse(
200
+ v.kind === 'kingdom'
149
201
  ? {
150
- connectionId: v['connection-id'],
151
- ownerModel: v['owner-model'],
152
- organizationId: v['organization-id'],
153
- spaceId: v['space-id'],
202
+ kind: 'kingdom',
203
+ projectId: v['project-id'],
204
+ url: v.url,
205
+ credentialFile: v['credential-file'] && resolve(v['credential-file']),
206
+ integrationId: v['integration-id'],
207
+ resourceId: v['resource-id'],
154
208
  }
155
- : {}),
156
- });
209
+ : {
210
+ kind: 'archive',
211
+ projectId: v['project-id'],
212
+ url: v.url,
213
+ tokenEnv: v['token-env'] ?? (v['token-file'] ? undefined : 'ARCHIVE_TOKEN'),
214
+ tokenFile: v['token-file'] && resolve(v['token-file']),
215
+ },
216
+ );
157
217
  // Verify credentials and protocol before saving; no records uploaded by setup.
158
- const probe = await archiveRequest(destination, 'search', {
159
- query: '',
160
- budget: 16,
161
- limit: 1,
162
- ...(destination.kind === 'archive'
163
- ? { projectId: destination.projectId }
164
- : kingdomFields(destination)),
165
- });
166
- if (!Array.isArray(probe.data?.archives))
167
- throw new Error('Destination is not an Archive-compatible endpoint');
218
+ if (destination.kind === 'kingdom') await verifyKingdomDestination(destination);
219
+ else {
220
+ const probe = await archiveRequest(destination, 'search', {
221
+ query: '',
222
+ budget: 16,
223
+ limit: 1,
224
+ projectId: destination.projectId,
225
+ });
226
+ if (!Array.isArray(probe.data?.archives))
227
+ throw new Error('Destination is not an Archive-compatible endpoint');
228
+ }
168
229
  output(connectDestination(config, destination));
169
230
  return;
170
231
  }
171
232
  if (command === 'agents') {
172
233
  const file = join(home, 'agents.json');
173
234
  const current = readAgents(file);
174
- const [action, state] = positionals.slice(1);
175
- const toggle = () => {
176
- if (state !== 'on' && state !== 'off') throw new Error(`agents ${action} requires on|off`);
177
- return state === 'on';
178
- };
235
+ const action = positionals[1];
179
236
  const base = { collectors: [], ...current };
180
237
  if (action === 'add-collector') {
181
238
  if (!v.name || !v['project-id'] || !v['project-root'] || !v.source)
@@ -203,16 +260,10 @@ export async function runCli(args = Bun.argv.slice(2)) {
203
260
  collectors: base.collectors.filter((c) => c.name !== v.name),
204
261
  }),
205
262
  );
206
- } else if (action === 'serve') {
207
- const { serve: _, ...rest } = base;
208
- output(
209
- writeAgents(
210
- file,
211
- toggle() ? { ...rest, serve: v.port ? { port: Number(v.port) } : {} } : rest,
212
- ),
213
- );
214
- } else if (action === 'sync') output(writeAgents(file, { ...base, sync: toggle() }));
215
- else if (action === 'install') {
263
+ } else if (action === 'server') {
264
+ if (!v.url) throw new Error('agents server requires --url');
265
+ output(writeAgents(file, { ...base, server: { url: v.url } }));
266
+ } else if (action === 'install') {
216
267
  if (!current) throw new Error('No agents.json; declare agents first');
217
268
  output(installAgents(current, { home }));
218
269
  } else if (action === 'uninstall') output(uninstallAgents({ home }));
@@ -237,110 +288,118 @@ export async function runCli(args = Bun.argv.slice(2)) {
237
288
  if (results.some((r) => 'error' in r)) process.exitCode = 1;
238
289
  return;
239
290
  }
240
- const store = new LocalArchiveStore(storePath);
241
- try {
242
- if (command === 'import') {
243
- if (
244
- !v.file ||
245
- !['codex', 'claude-code', 'chatgpt'].includes(v.source ?? '') ||
246
- !v['project-id']
247
- )
248
- throw new Error(
249
- 'Import requires --file, --source codex|claude-code|chatgpt and --project-id',
250
- );
251
- const snapshot =
252
- v.source === 'chatgpt'
253
- ? importChatGPTThread(JSON.parse(readFileSync(v.file, 'utf8')), {
254
- sourceId: store.sourceId,
255
- projectId: v['project-id'],
256
- title: v.title,
257
- })
258
- : importTranscriptFile(v.file, {
259
- source: v.source as 'codex' | 'claude-code',
260
- sourceId: store.sourceId,
261
- projectId: v['project-id'],
262
- sessionId: v['session-id'],
263
- title: v.title,
264
- });
265
- snapshot.tags = [
266
- ...new Set([...(store.read(archiveKey(snapshot))?.snapshot.tags ?? []), ...(v.tag ?? [])]),
267
- ];
268
- output({
269
- ...store.capture(snapshot),
270
- entries: snapshot.entries.length,
271
- coverage: snapshot.coverage,
272
- });
273
- } else if (command === 'list') output(store.list());
274
- else if (command === 'routes') output(routingPreview(store, readDestinations(config)));
275
- else if (command === 'sync' || command === 'collect') {
276
- if (
277
- command === 'collect' &&
278
- (!v.directory ||
279
- !v['project-root'] ||
280
- !v['project-id'] ||
281
- !['codex', 'claude-code'].includes(v.source ?? ''))
282
- )
283
- throw new Error('Collect requires --directory, --source, --project-root and --project-id');
284
- let stopped = false;
285
- const stop = () => {
286
- stopped = true;
287
- };
288
- process.once('SIGINT', stop);
289
- process.once('SIGTERM', stop);
290
- do {
291
- if (command === 'collect') {
292
- const result = await collectSessions(store, {
293
- directory: v.directory!,
294
- source: v.source as 'codex' | 'claude-code',
295
- projectRoots: v['project-root']!,
296
- worktrees: v.worktrees,
297
- atlas: v.atlas,
298
- projectId: v['project-id']!,
299
- tags: v.tag,
300
- });
301
- output(result);
302
- if (!v.watch && result.failed) process.exitCode = 1;
303
- } else {
304
- const results = await syncArchives(store, readDestinations(config));
305
- output(results);
306
- if (!v.watch && results.some((r) => r.status.startsWith('failed'))) process.exitCode = 1;
307
- }
308
- if (v.watch && !stopped) await Bun.sleep(1000);
309
- // Short waits allow prompt shutdown; network requests have their own timeout.
310
- for (let i = 1; v.watch && !stopped && i < 30; i++) await Bun.sleep(1000);
311
- } while (v.watch && !stopped);
312
- process.removeListener('SIGINT', stop);
313
- process.removeListener('SIGTERM', stop);
314
- } else if (command === 'publish') {
315
- if (!v.id || !v.destination) throw new Error('Publish requires --id and --destination');
316
- output(
317
- await publishArchive(
318
- store,
319
- v.id,
320
- archiveDestinationSchema.parse(JSON.parse(readFileSync(v.destination, 'utf8'))),
321
- ),
322
- );
323
- } else if (command === 'search')
324
- output(store.search(v.id ? [v.id] : store.list().map((a) => a.id), v.query!));
325
- else if (command === 'export' || command === 'tag') {
326
- const archive = v.id ? store.read(v.id) : undefined;
327
- if (!archive) throw new Error('Valid --id required');
328
- if (command === 'export') output(archive.snapshot);
291
+ if (command === 'sync' || command === 'routes') {
292
+ const store = new ArchiveStore(databaseUrl());
293
+ try {
294
+ if (command === 'routes') output(await routingPreview(store, readDestinations(config)));
329
295
  else {
330
- if (!v.tag?.length && !v.untag?.length)
331
- throw new Error('Tag requires at least one --tag or --untag');
332
- output(store.tag(archive.id, { add: v.tag, remove: v.untag }));
296
+ const results = await syncArchives(store, readDestinations(config));
297
+ output(results);
298
+ if (results.some((r) => r.status.startsWith('failed'))) process.exitCode = 1;
333
299
  }
334
- } else throw new Error('Unknown command; use --help');
335
- } finally {
336
- store.close();
300
+ } finally {
301
+ await store.close();
302
+ }
303
+ return;
337
304
  }
305
+ const token = process.env.ARCHIVE_TOKEN ?? readToken();
306
+ if (!token) throw new Error('No Archive token: run `archive init` or set ARCHIVE_TOKEN');
307
+ const archive = new ArchiveClient({ url: v.url ?? DEFAULT_URL, token });
308
+ if (command === 'import') {
309
+ if (
310
+ !v.file ||
311
+ !['codex', 'claude-code', 'chatgpt'].includes(v.source ?? '') ||
312
+ !v['project-id']
313
+ )
314
+ throw new Error(
315
+ 'Import requires --file, --source codex|claude-code|chatgpt and --project-id',
316
+ );
317
+ const sourceId = await archive.sourceId();
318
+ const snapshot =
319
+ v.source === 'chatgpt'
320
+ ? importChatGPTThread(JSON.parse(readFileSync(v.file, 'utf8')), {
321
+ sourceId,
322
+ projectId: v['project-id'],
323
+ title: v.title,
324
+ })
325
+ : importTranscriptFile(v.file, {
326
+ source: v.source as 'codex' | 'claude-code',
327
+ sourceId,
328
+ projectId: v['project-id'],
329
+ sessionId: v['session-id'],
330
+ title: v.title,
331
+ });
332
+ snapshot.tags = [...new Set(v.tag ?? [])];
333
+ output({
334
+ ...(await archive.capture(snapshot)),
335
+ entries: snapshot.entries.length,
336
+ coverage: snapshot.coverage,
337
+ });
338
+ } else if (command === 'list') output(await archive.list());
339
+ else if (command === 'collect') {
340
+ if (
341
+ !v.directory ||
342
+ !v['project-root'] ||
343
+ !v['project-id'] ||
344
+ !['codex', 'claude-code'].includes(v.source ?? '')
345
+ )
346
+ throw new Error('Collect requires --directory, --source, --project-root and --project-id');
347
+ let stopped = false;
348
+ const stop = () => {
349
+ stopped = true;
350
+ };
351
+ process.once('SIGINT', stop);
352
+ process.once('SIGTERM', stop);
353
+ do {
354
+ try {
355
+ const result = await collectSessions(archive, {
356
+ directory: v.directory,
357
+ source: v.source as 'codex' | 'claude-code',
358
+ projectRoots: v['project-root'],
359
+ worktrees: v.worktrees,
360
+ atlas: v.atlas,
361
+ projectId: v['project-id'],
362
+ tags: v.tag,
363
+ });
364
+ output(result);
365
+ if (!v.watch && result.failed) process.exitCode = 1;
366
+ } catch (error) {
367
+ // The server may be starting; a watching collector retries.
368
+ if (!v.watch) throw error;
369
+ console.error(JSON.stringify({ collect: 'server unavailable' }));
370
+ }
371
+ // Short waits allow prompt shutdown; network requests have their own timeout.
372
+ for (let i = 0; v.watch && !stopped && i < 30; i++) await Bun.sleep(1000);
373
+ } while (v.watch && !stopped);
374
+ process.removeListener('SIGINT', stop);
375
+ process.removeListener('SIGTERM', stop);
376
+ } else if (command === 'search')
377
+ output(
378
+ await archive.search({
379
+ query: v.query,
380
+ limit: Number(v.limit) > 100 ? 100 : Number(v.limit),
381
+ }),
382
+ );
383
+ else if (command === 'export' || command === 'tag') {
384
+ if (!v.id) throw new Error('Valid --id required');
385
+ if (command === 'export') {
386
+ const read = await archive.read(v.id);
387
+ if (!read) throw new Error('Valid --id required');
388
+ output(read.snapshot);
389
+ } else {
390
+ if (!v.tag?.length && !v.untag?.length)
391
+ throw new Error('Tag requires at least one --tag or --untag');
392
+ output(await archive.tag(v.id, { add: v.tag, remove: v.untag }));
393
+ }
394
+ } else throw new Error('Unknown command; use --help');
338
395
  }
339
396
 
340
397
  if (import.meta.main)
341
- runCli().catch(() => {
398
+ runCli().catch((error) => {
342
399
  console.error(
343
- 'Archive command failed. Check arguments, destination access and local paths. Use --help for usage.',
400
+ process.env.ARCHIVE_DEBUG
401
+ ? error
402
+ : 'Archive command failed. Check arguments, server and destination access, and local paths. Use --help for usage. Set ARCHIVE_DEBUG=1 for details.',
344
403
  );
345
404
  process.exitCode = 1;
346
405
  });