@telepath-computer/stash 0.2.4 → 0.3.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.
package/dist/cli-main.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { existsSync } from "node:fs";
2
- import { access, constants, readFile } from "node:fs/promises";
2
+ import { access, constants, readFile, rm } from "node:fs/promises";
3
3
  import { basename, delimiter, join, resolve } from "node:path";
4
4
  import { Daemon, UnsupportedPlatformError } from "@rupertsworld/daemon";
5
5
  import { Command, Option } from "commander";
6
6
  import { input, password } from "@inquirer/prompts";
7
7
  import { addBackgroundStash, getBackgroundStashes, getProviderConfig, readGlobalConfig, removeBackgroundStash, setProviderConfig, writeGlobalConfig, } from "./global-config.js";
8
+ import { readLocalConfig, writeLocalConfig } from "./local-config.js";
8
9
  import { runDaemon } from "./daemon.js";
10
+ import { needsMigration } from "./migrations.js";
9
11
  import { providers } from "./providers/index.js";
10
12
  import { Stash } from "./stash.js";
11
13
  import { createColors } from "./ui/color.js";
@@ -15,6 +17,13 @@ import { SyncRenderer } from "./ui/sync-renderer.js";
15
17
  import { watch as watchStash } from "./watch.js";
16
18
  const SERVICE_NAME = "stash-background";
17
19
  const SERVICE_DESCRIPTION = "Stash background sync";
20
+ const STASH_CONFIG_KEYS = new Set(["allow-git"]);
21
+ function isStashConfigKey(key) {
22
+ return STASH_CONFIG_KEYS.has(key);
23
+ }
24
+ function isStashDirectory(dir) {
25
+ return existsSync(join(dir, ".stash"));
26
+ }
18
27
  function getProvider(name) {
19
28
  const provider = providers[name];
20
29
  if (!provider) {
@@ -37,12 +46,32 @@ function writeLine(stream, text) {
37
46
  function isUnsupportedPlatformError(error) {
38
47
  return error instanceof UnsupportedPlatformError;
39
48
  }
40
- export async function resolveStashCommand() {
41
- const argvPath = process.argv[1];
42
- if (argvPath && basename(argvPath) === "stash") {
43
- const candidate = resolve(argvPath);
49
+ async function bounceDaemonIfMigrationNeeded(dir, getService) {
50
+ if (!isStashDirectory(dir) || !needsMigration(dir)) {
51
+ return false;
52
+ }
53
+ try {
54
+ const service = await getService();
55
+ const status = await service.status();
56
+ if (!status.running) {
57
+ return false;
58
+ }
59
+ await service.uninstall();
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ export async function resolveServiceLaunch(argv = process.argv) {
67
+ const invokedPath = argv[1];
68
+ if (invokedPath) {
69
+ const candidate = resolve(invokedPath);
44
70
  if (await isExecutable(candidate)) {
45
- return candidate;
71
+ return { command: candidate, args: ["daemon"] };
72
+ }
73
+ if (existsSync(candidate)) {
74
+ return { command: process.execPath, args: [candidate, "daemon"] };
46
75
  }
47
76
  }
48
77
  for (const pathEntry of (process.env.PATH ?? "").split(delimiter)) {
@@ -51,10 +80,10 @@ export async function resolveStashCommand() {
51
80
  }
52
81
  const candidate = join(pathEntry, "stash");
53
82
  if (await isExecutable(candidate)) {
54
- return candidate;
83
+ return { command: candidate, args: ["daemon"] };
55
84
  }
56
85
  }
57
- throw new Error("Could not find the `stash` binary on PATH");
86
+ throw new Error("Could not resolve a command to run `stash daemon`");
58
87
  }
59
88
  async function promptField(field) {
60
89
  if (field.secret) {
@@ -84,23 +113,30 @@ async function readBackgroundStatus(dir) {
84
113
  }
85
114
  return JSON.parse(await readFile(statusPath, "utf8"));
86
115
  }
87
- async function warnIfServiceUnavailable(serviceHandle, stderr) {
88
- if (!serviceHandle) {
116
+ function renderGitWarning(stdout) {
117
+ const { dim, yellow } = createColors(stdout);
118
+ writeLine(stdout, `${yellow("Warning:")} ${dim("This directory contains .git. Stash will not sync until you either:")}`);
119
+ writeLine(stdout, dim(" - remove .git, or"));
120
+ writeLine(stdout, dim(' - run `stash config set allow-git true` (see "Using stash with git" in the README)'));
121
+ }
122
+ async function warnIfGitSyncBlocked(dir, stdout) {
123
+ if (!existsSync(join(dir, ".git"))) {
89
124
  return;
90
125
  }
91
- try {
92
- const status = await serviceHandle.status();
93
- if (!status.installed) {
94
- writeLine(stderr, "warning: background service is not installed");
95
- }
126
+ const localConfig = await readLocalConfig(dir);
127
+ if (localConfig["allow-git"] === true) {
128
+ return;
96
129
  }
97
- catch (error) {
98
- if (isUnsupportedPlatformError(error)) {
99
- writeLine(stderr, "warning: service install is not supported on this platform yet; run `stash background watch` manually");
100
- return;
101
- }
102
- throw error;
130
+ renderGitWarning(stdout);
131
+ }
132
+ function parseConfigValue(key, value) {
133
+ if (value === "true") {
134
+ return true;
135
+ }
136
+ if (value === "false") {
137
+ return false;
103
138
  }
139
+ throw new Error(`Invalid value for ${key}: expected true or false`);
104
140
  }
105
141
  async function runSetup(providerName, valuesFromCli, deps, stdout) {
106
142
  const provider = getProvider(providerName);
@@ -110,22 +146,8 @@ async function runSetup(providerName, valuesFromCli, deps, stdout) {
110
146
  await deps.writeGlobalConfig(globalConfig);
111
147
  writeLine(stdout, `Configured ${providerName}.`);
112
148
  }
113
- async function runInit(cwd, readConfig, stdout) {
114
- const dir = cwd();
115
- const alreadyInitialized = existsSync(join(dir, ".stash"));
116
- await Stash.init(dir, await readConfig());
117
- writeLine(stdout, alreadyInitialized ? "Already initialized." : "Initialized stash.");
118
- }
119
- async function registerBackgroundStash(dir, readConfig, writeConfig, serviceHandle, stderr) {
120
- const globalConfig = await readConfig();
121
- const stash = await Stash.load(dir, globalConfig);
122
- if (Object.keys(stash.connections).length === 0) {
123
- writeLine(stderr, "warning: this stash won't sync until a provider is connected");
124
- }
125
- await writeConfig(addBackgroundStash(globalConfig, dir));
126
- await warnIfServiceUnavailable(serviceHandle, stderr);
127
- }
128
- async function runConnect(providerName, valuesFromCli, deps, stdout, stderr) {
149
+ async function runConnect(providerName, valuesFromCli, deps, stdout) {
150
+ const { dim, green } = createColors(stdout);
129
151
  const provider = getProvider(providerName);
130
152
  let globalConfig = await deps.readGlobalConfig();
131
153
  const setupValues = await collectFields(provider.spec.setup, valuesFromCli, getProviderConfig(globalConfig, providerName));
@@ -135,14 +157,47 @@ async function runConnect(providerName, valuesFromCli, deps, stdout, stderr) {
135
157
  const stash = await Stash.init(dir, globalConfig);
136
158
  const connectValues = await collectFields(provider.spec.connect, valuesFromCli);
137
159
  await stash.connect(providerName, connectValues);
138
- if (valuesFromCli.background === true) {
139
- await registerBackgroundStash(dir, deps.readGlobalConfig, deps.writeGlobalConfig, deps.service, stderr);
140
- }
160
+ globalConfig = await deps.readGlobalConfig();
161
+ await deps.writeGlobalConfig(addBackgroundStash(globalConfig, resolve(dir)));
141
162
  writeLine(stdout, `Connected ${providerName}.`);
163
+ await warnIfGitSyncBlocked(dir, stdout);
164
+ if (!deps.getService) {
165
+ return;
166
+ }
167
+ try {
168
+ const status = await (await deps.getService()).status();
169
+ if (status.running) {
170
+ writeLine(stdout, `${green("Background sync is on")} ${dim("·")} ${dim("This stash is now syncing automatically")}`);
171
+ }
172
+ }
173
+ catch (error) {
174
+ if (error instanceof Error &&
175
+ error.message === "Could not resolve a command to run `stash daemon`") {
176
+ return;
177
+ }
178
+ if (!isUnsupportedPlatformError(error)) {
179
+ throw error;
180
+ }
181
+ }
142
182
  }
143
- async function runDisconnect(providerName, cwd, readConfig, stdout) {
144
- const stash = await Stash.load(cwd(), await readConfig());
183
+ async function runDisconnect(providerName, cwd, readConfig, writeConfig, stdout) {
184
+ const dir = cwd();
185
+ const globalConfig = await readConfig();
186
+ const stash = await Stash.load(dir, globalConfig);
187
+ if (!providerName) {
188
+ for (const name of Object.keys(stash.connections)) {
189
+ await stash.disconnect(name);
190
+ }
191
+ await writeConfig(removeBackgroundStash(globalConfig, resolve(dir)));
192
+ await rm(join(dir, ".stash"), { recursive: true, force: true });
193
+ writeLine(stdout, "Disconnected stash.");
194
+ return;
195
+ }
145
196
  await stash.disconnect(providerName);
197
+ if (Object.keys(stash.connections).length === 0) {
198
+ await writeConfig(removeBackgroundStash(globalConfig, resolve(dir)));
199
+ await rm(join(dir, ".stash"), { recursive: true, force: true });
200
+ }
146
201
  writeLine(stdout, `Disconnected ${providerName}.`);
147
202
  }
148
203
  async function runSync(cwd, readConfig) {
@@ -188,14 +243,116 @@ function formatChangeParts(status) {
188
243
  parts.push(`${status.deleted.length} deleted`);
189
244
  return parts;
190
245
  }
191
- async function runStatus(cwd, readConfig, stdout) {
192
- const { dim, green, yellow } = createColors(stdout);
193
- const stash = await Stash.load(cwd(), await readConfig());
246
+ function formatStashCount(count) {
247
+ return `${count} ${count === 1 ? "stash" : "stashes"}`;
248
+ }
249
+ async function runStatusAll(readConfig, serviceHandle, stdout) {
250
+ const { dim, green, red } = createColors(stdout);
251
+ const globalConfig = await readConfig();
252
+ const stashes = getBackgroundStashes(globalConfig);
253
+ if (stashes.length === 0) {
254
+ writeLine(stdout, "No stashes connected yet — run `stash connect <provider>` in a directory to add one");
255
+ return;
256
+ }
257
+ try {
258
+ const current = await serviceHandle.status();
259
+ if (current.running) {
260
+ writeLine(stdout, `${green("Background sync is on")} ${dim("·")} ${dim(`watching ${formatStashCount(stashes.length)}`)}`);
261
+ }
262
+ else {
263
+ writeLine(stdout, red("Background sync is off"));
264
+ writeLine(stdout, dim(`Run \`stash start\` to resume syncing ${formatStashCount(stashes.length)}`));
265
+ }
266
+ }
267
+ catch (error) {
268
+ if (isUnsupportedPlatformError(error)) {
269
+ writeLine(stdout, red("Background sync is not supported on this platform"));
270
+ }
271
+ else {
272
+ throw error;
273
+ }
274
+ }
275
+ writeLine(stdout, "");
276
+ for (const dir of stashes) {
277
+ const title = basename(dir);
278
+ if (!existsSync(dir)) {
279
+ writeLine(stdout, `${red("✗")} ${title}`);
280
+ writeLine(stdout, dim(` ${dir}`));
281
+ writeLine(stdout, dim(" Directory not found"));
282
+ continue;
283
+ }
284
+ if (!isStashDirectory(dir)) {
285
+ writeLine(stdout, `${red("✗")} ${title}`);
286
+ writeLine(stdout, dim(` ${dir}`));
287
+ writeLine(stdout, dim(" Not a stash"));
288
+ continue;
289
+ }
290
+ const stash = await Stash.load(dir, globalConfig);
291
+ const persistedStatus = await readBackgroundStatus(dir);
292
+ const localStatus = stash.status();
293
+ const connectionNames = Object.keys(stash.connections);
294
+ if (persistedStatus?.kind === "error") {
295
+ writeLine(stdout, `${red("✗")} ${title}`);
296
+ writeLine(stdout, dim(` ${dir}`));
297
+ for (const providerName of connectionNames) {
298
+ const conn = stash.connections[providerName];
299
+ const label = conn.repo ?? Object.values(conn).join(", ");
300
+ writeLine(stdout, ` ${providerName} ${label} ${dim("·")} ${red(persistedStatus.error ?? "unknown error")}`);
301
+ }
302
+ continue;
303
+ }
304
+ writeLine(stdout, `${green("●")} ${title}`);
305
+ writeLine(stdout, dim(` ${dir}`));
306
+ for (const providerName of connectionNames) {
307
+ const conn = stash.connections[providerName];
308
+ const label = conn.repo ?? Object.values(conn).join(", ");
309
+ if (persistedStatus === null || localStatus.lastSync === null) {
310
+ writeLine(stdout, ` ${providerName} ${label} ${dim("·")} ${dim("Waiting for first sync")}`);
311
+ continue;
312
+ }
313
+ const parts = formatChangeParts(localStatus);
314
+ if (parts.length > 0) {
315
+ writeLine(stdout, ` ${providerName} ${label} ${dim("·")} Local changes: ${parts.join(", ")} ${dim("·")} ${dim(`synced ${formatTimeAgo(localStatus.lastSync)}`)}`);
316
+ }
317
+ else {
318
+ writeLine(stdout, ` ${providerName} ${label} ${dim("·")} ${dim(`Up to date · synced ${formatTimeAgo(localStatus.lastSync)}`)}`);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ async function runStatus(cwd, readConfig, serviceHandle, stdout) {
324
+ const { dim, green, yellow, red } = createColors(stdout);
325
+ const dir = cwd();
326
+ if (!isStashDirectory(dir)) {
327
+ throw new Error("Not in a stash directory — run `stash status --all` to view all stashes");
328
+ }
329
+ const globalConfig = await readConfig();
330
+ const stash = await Stash.load(dir, globalConfig);
194
331
  const connectionNames = Object.keys(stash.connections);
195
332
  if (connectionNames.length === 0) {
196
- writeLine(stdout, dim("no connections — run `stash connect <provider>` to get started"));
333
+ writeLine(stdout, dim("No connections — run `stash connect <provider>` to get started"));
197
334
  return;
198
335
  }
336
+ if (getBackgroundStashes(globalConfig).includes(resolve(dir))) {
337
+ try {
338
+ const current = await serviceHandle.status();
339
+ if (current.running) {
340
+ writeLine(stdout, `${green("Background sync is on")} ${dim("·")} ${dim("Use `stash status --all` to view all stashes")}`);
341
+ }
342
+ else {
343
+ writeLine(stdout, `${red("Background sync is off")} ${dim("·")} ${dim("Run `stash start` to keep connected stashes in sync")}`);
344
+ }
345
+ }
346
+ catch (error) {
347
+ if (isUnsupportedPlatformError(error)) {
348
+ writeLine(stdout, `${red("Background sync is not supported on this platform")}`);
349
+ }
350
+ else {
351
+ throw error;
352
+ }
353
+ }
354
+ writeLine(stdout, "");
355
+ }
199
356
  const status = stash.status();
200
357
  const parts = formatChangeParts(status);
201
358
  for (const name of connectionNames) {
@@ -206,118 +363,102 @@ async function runStatus(cwd, readConfig, stdout) {
206
363
  if (status.lastSync) {
207
364
  const ago = formatTimeAgo(status.lastSync);
208
365
  if (parts.length > 0) {
209
- writeLine(stdout, ` ${parts.join(", ")} ${dim("·")} ${dim(`synced ${ago}`)}`);
366
+ writeLine(stdout, ` Local changes: ${parts.join(", ")} ${dim("·")} ${dim(`synced ${ago}`)}`);
210
367
  }
211
368
  else {
212
- writeLine(stdout, dim(` up to date · synced ${ago}`));
369
+ writeLine(stdout, dim(` Up to date · synced ${ago}`));
213
370
  }
214
371
  }
215
372
  else if (parts.length > 0) {
216
- writeLine(stdout, ` ${parts.join(", ")} ${dim("·")} ${dim("never synced")}`);
373
+ writeLine(stdout, ` Local changes: ${parts.join(", ")} ${dim("·")} ${dim("Never synced")}`);
217
374
  }
218
375
  else {
219
- writeLine(stdout, dim(" never synced"));
376
+ writeLine(stdout, dim(" Never synced"));
220
377
  }
221
378
  }
222
379
  }
223
- async function runBackgroundInstall(serviceHandle, stdout) {
224
- await serviceHandle.install();
225
- writeLine(stdout, "Installed background service.");
226
- }
227
- async function runBackgroundUninstall(serviceHandle, stdout) {
228
- await serviceHandle.uninstall();
229
- writeLine(stdout, "Uninstalled background service.");
230
- }
231
- async function runBackgroundAdd(dirArg, cwd, readConfig, writeConfig, serviceHandle, stdout, stderr) {
232
- const dir = resolve(dirArg ?? cwd());
233
- await registerBackgroundStash(dir, readConfig, writeConfig, serviceHandle, stderr);
234
- writeLine(stdout, `Registered ${dir} for background sync.`);
235
- }
236
- async function runBackgroundRemove(dirArg, cwd, readConfig, writeConfig, stdout) {
237
- const dir = resolve(dirArg ?? cwd());
238
- const globalConfig = await readConfig();
239
- await writeConfig(removeBackgroundStash(globalConfig, dir));
240
- writeLine(stdout, `Removed ${dir} from background sync.`);
241
- }
242
- async function runBackgroundStatus(readConfig, serviceHandle, stdout) {
380
+ async function runStart(serviceHandle, readConfig, stdout) {
243
381
  const { dim, green, red } = createColors(stdout);
244
- let serviceRunning = false;
245
- let serviceMessage = null;
246
382
  try {
247
383
  const current = await serviceHandle.status();
248
- if (!current.installed) {
249
- serviceMessage = dim("service not installed run `stash background install`");
250
- }
251
- else if (current.running) {
252
- serviceRunning = true;
253
- }
254
- else {
255
- serviceMessage = red("background service is stopped — run `stash background install` to restart");
384
+ if (current.installed && current.running) {
385
+ writeLine(stdout, green("Background sync is already running"));
386
+ return;
256
387
  }
257
388
  }
258
389
  catch (error) {
259
390
  if (isUnsupportedPlatformError(error)) {
260
- serviceMessage = dim("service install not supported on this platform — run `stash background watch` manually");
261
- }
262
- else {
263
- throw error;
391
+ writeLine(stdout, red("Background sync is not supported on this platform"));
392
+ process.exitCode = 1;
393
+ return;
264
394
  }
395
+ throw error;
265
396
  }
266
- const globalConfig = await readConfig();
267
- const stashes = getBackgroundStashes(globalConfig);
268
- if (serviceRunning) {
269
- writeLine(stdout, dim("stash is syncing in the background"));
397
+ await serviceHandle.install();
398
+ const count = getBackgroundStashes(await readConfig()).length;
399
+ writeLine(stdout, green("Background sync is on"));
400
+ writeLine(stdout, dim(`Watching ${formatStashCount(count)} · starts on startup`));
401
+ }
402
+ async function runStop(serviceHandle, readConfig, stdout) {
403
+ const { dim, red } = createColors(stdout);
404
+ try {
405
+ const current = await serviceHandle.status();
406
+ if (!current.installed && !current.running) {
407
+ writeLine(stdout, red("Background sync is not running"));
408
+ return;
409
+ }
270
410
  }
271
- else if (serviceMessage) {
272
- writeLine(stdout, serviceMessage);
411
+ catch (error) {
412
+ if (isUnsupportedPlatformError(error)) {
413
+ writeLine(stdout, red("Background sync is not running"));
414
+ return;
415
+ }
416
+ throw error;
273
417
  }
274
- if (stashes.length === 0) {
275
- writeLine(stdout, dim("\nno stashes registered — run `stash background add` to add one"));
276
- return;
418
+ await serviceHandle.uninstall();
419
+ const count = getBackgroundStashes(await readConfig()).length;
420
+ writeLine(stdout, red("Background sync is off"));
421
+ writeLine(stdout, dim(`Run \`stash start\` to resume syncing ${formatStashCount(count)}`));
422
+ }
423
+ async function ensureInitializedStash(cwd, readConfig) {
424
+ const dir = cwd();
425
+ await Stash.load(dir, await readConfig());
426
+ return dir;
427
+ }
428
+ async function runConfigSet(key, value, cwd, readConfig, stdout) {
429
+ if (!isStashConfigKey(key)) {
430
+ throw new Error(`Unknown config key: ${key}`);
277
431
  }
278
- writeLine(stdout, "");
279
- for (const dir of stashes) {
280
- if (!existsSync(dir)) {
281
- writeLine(stdout, `${red("✗")} ${dir}`);
282
- writeLine(stdout, dim(" directory not found"));
283
- continue;
284
- }
285
- const status = await readBackgroundStatus(dir);
286
- if (!status) {
287
- writeLine(stdout, `${dim("○")} ${dir}`);
288
- writeLine(stdout, dim(" waiting for first sync"));
289
- continue;
290
- }
291
- if (status.kind === "error") {
292
- writeLine(stdout, `${red("✗")} ${dir}`);
293
- const ago = status.lastSync ? formatTimeAgo(new Date(status.lastSync)) : null;
294
- const errorMsg = status.error ?? "unknown error";
295
- writeLine(stdout, ago ? ` ${red(errorMsg)} ${dim("·")} ${dim(`synced ${ago}`)}` : ` ${red(errorMsg)}`);
296
- continue;
297
- }
298
- const ago = status.lastSync ? formatTimeAgo(new Date(status.lastSync)) : null;
299
- if (status.summary) {
300
- writeLine(stdout, `${green("●")} ${dir}`);
301
- writeLine(stdout, ` ${status.summary} ${dim("·")} ${dim(`synced ${ago ?? "unknown"}`)}`);
302
- }
303
- else {
304
- writeLine(stdout, `${green("●")} ${dir}`);
305
- writeLine(stdout, dim(` up to date · synced ${ago ?? "unknown"}`));
306
- }
432
+ const dir = await ensureInitializedStash(cwd, readConfig);
433
+ const currentConfig = await readLocalConfig(dir);
434
+ const nextConfig = {
435
+ ...currentConfig,
436
+ [key]: parseConfigValue(key, value),
437
+ };
438
+ await writeLocalConfig(dir, nextConfig);
439
+ writeLine(stdout, `${key}=${String(nextConfig[key])}`);
440
+ }
441
+ async function runConfigGet(key, cwd, readConfig, stdout) {
442
+ if (!isStashConfigKey(key)) {
443
+ throw new Error(`Unknown config key: ${key}`);
307
444
  }
445
+ const dir = await ensureInitializedStash(cwd, readConfig);
446
+ const localConfig = await readLocalConfig(dir);
447
+ const value = localConfig[key];
448
+ writeLine(stdout, value === undefined ? "" : String(value));
308
449
  }
309
450
  function addFieldOptions(command, fields) {
310
451
  for (const field of fields) {
311
452
  command.addOption(new Option(`--${field.name} <value>`, field.label));
312
453
  }
313
454
  }
314
- async function createDefaultService() {
315
- const command = await resolveStashCommand();
455
+ async function createDefaultService(argv = process.argv) {
456
+ const launch = await resolveServiceLaunch(argv);
316
457
  return new Daemon({
317
458
  name: SERVICE_NAME,
318
459
  description: SERVICE_DESCRIPTION,
319
- command,
320
- args: ["background", "watch"],
460
+ command: launch.command,
461
+ args: launch.args,
321
462
  env: process.env.PATH ? { PATH: process.env.PATH } : undefined,
322
463
  });
323
464
  }
@@ -326,10 +467,25 @@ export async function main(argv = process.argv, deps = {}) {
326
467
  const readConfig = deps.readGlobalConfig ?? readGlobalConfig;
327
468
  const writeConfig = deps.writeGlobalConfig ?? writeGlobalConfig;
328
469
  const serviceHandle = deps.service;
329
- const getService = async () => serviceHandle ?? (await createDefaultService());
470
+ const getService = async () => serviceHandle ?? (await createDefaultService(argv));
330
471
  const runDaemonCommand = deps.runDaemon ?? runDaemon;
331
472
  const stdout = deps.stdout ?? process.stdout;
332
473
  const stderr = deps.stderr ?? process.stderr;
474
+ const dirsToCheck = [cwd()];
475
+ try {
476
+ const config = await readConfig();
477
+ dirsToCheck.push(...getBackgroundStashes(config));
478
+ }
479
+ catch {
480
+ // Global config unreadable — skip migration check.
481
+ }
482
+ let daemonBounced = false;
483
+ for (const dir of dirsToCheck) {
484
+ if (await bounceDaemonIfMigrationNeeded(dir, getService)) {
485
+ daemonBounced = true;
486
+ break;
487
+ }
488
+ }
333
489
  const program = new Command()
334
490
  .name("stash")
335
491
  .description("Conflict-free synced folders")
@@ -354,30 +510,22 @@ export async function main(argv = process.argv, deps = {}) {
354
510
  .command("connect")
355
511
  .description("Connect this stash to a provider")
356
512
  .argument("<provider>", "Provider name")
357
- .option("--background", "Register this stash for background syncing")
358
513
  .allowUnknownOption(true)
359
514
  .action(async (providerName, _opts, command) => {
360
515
  const opts = command.opts();
361
- const svc = opts.background === true ? await getService() : undefined;
362
516
  await runConnect(providerName, opts, {
363
517
  cwd,
364
518
  readGlobalConfig: readConfig,
365
519
  writeGlobalConfig: writeConfig,
366
- service: svc,
367
- }, stdout, stderr);
520
+ getService,
521
+ }, stdout);
368
522
  });
369
523
  program
370
524
  .command("disconnect")
371
525
  .description("Disconnect provider from this stash")
372
- .argument("<provider>", "Provider name")
526
+ .argument("[provider]", "Provider name")
373
527
  .action(async (providerName) => {
374
- await runDisconnect(providerName, cwd, readConfig, stdout);
375
- });
376
- program
377
- .command("init")
378
- .description("Initialize the current directory as a stash")
379
- .action(() => {
380
- return runInit(cwd, readConfig, stdout);
528
+ await runDisconnect(providerName, cwd, readConfig, writeConfig, stdout);
381
529
  });
382
530
  program
383
531
  .command("sync")
@@ -390,35 +538,37 @@ export async function main(argv = process.argv, deps = {}) {
390
538
  program
391
539
  .command("status")
392
540
  .description("Show local stash status")
393
- .action(() => runStatus(cwd, readConfig, stdout));
394
- const backgroundCommand = program.command("background").description("Manage background syncing");
395
- backgroundCommand
396
- .command("install")
397
- .description("Install the background service")
398
- .action(async () => runBackgroundInstall(await getService(), stdout));
399
- backgroundCommand
400
- .command("uninstall")
401
- .description("Remove the background service")
402
- .action(async () => runBackgroundUninstall(await getService(), stdout));
403
- backgroundCommand
404
- .command("add")
405
- .description("Register a stash for background syncing")
406
- .argument("[dir]", "Stash directory")
407
- .action(async (dirArg) => {
408
- const svc = await getService().catch(() => undefined);
409
- await runBackgroundAdd(dirArg, cwd, readConfig, writeConfig, svc, stdout, stderr);
541
+ .option("--all", "Show all connected stashes")
542
+ .action(async (_opts, command) => {
543
+ const opts = command.opts();
544
+ if (opts.all) {
545
+ await runStatusAll(readConfig, await getService(), stdout);
546
+ return;
547
+ }
548
+ await runStatus(cwd, readConfig, await getService(), stdout);
410
549
  });
411
- backgroundCommand
412
- .command("remove")
413
- .description("Unregister a stash from background syncing")
414
- .argument("[dir]", "Stash directory")
415
- .action((dirArg) => runBackgroundRemove(dirArg, cwd, readConfig, writeConfig, stdout));
416
- backgroundCommand
417
- .command("status")
418
- .description("Show background service and stash status")
419
- .action(async () => runBackgroundStatus(readConfig, await getService(), stdout));
420
- backgroundCommand
421
- .command("watch", { hidden: true })
550
+ const configCommand = program.command("config").description("Manage stash config");
551
+ configCommand
552
+ .command("set")
553
+ .description("Set a stash config value")
554
+ .argument("<key>", "Config key")
555
+ .argument("<value>", "Config value")
556
+ .action((key, value) => runConfigSet(key, value, cwd, readConfig, stdout));
557
+ configCommand
558
+ .command("get")
559
+ .description("Get a stash config value")
560
+ .argument("<key>", "Config key")
561
+ .action((key) => runConfigGet(key, cwd, readConfig, stdout));
562
+ program
563
+ .command("start")
564
+ .description("Start background sync")
565
+ .action(async () => runStart(await getService(), readConfig, stdout));
566
+ program
567
+ .command("stop")
568
+ .description("Stop background sync")
569
+ .action(async () => runStop(await getService(), readConfig, stdout));
570
+ program
571
+ .command("daemon", { hidden: true })
422
572
  .description("Run the background daemon")
423
573
  .action(() => runDaemonCommand());
424
574
  const commandName = argv[2];
@@ -439,5 +589,14 @@ export async function main(argv = process.argv, deps = {}) {
439
589
  return;
440
590
  }
441
591
  await program.parseAsync(argv);
592
+ const skipRestart = commandName === "stop" || commandName === "daemon";
593
+ if (daemonBounced && !skipRestart) {
594
+ try {
595
+ await (await getService()).install();
596
+ }
597
+ catch {
598
+ // Best-effort restart — service may be unsupported or unavailable.
599
+ }
600
+ }
442
601
  }
443
602
  //# sourceMappingURL=cli-main.js.map