@lumy-pack/syncpoint 0.0.10 → 0.0.12

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.mjs CHANGED
@@ -87,9 +87,6 @@ var init_wizard_template = __esm({
87
87
  }
88
88
  });
89
89
 
90
- // src/cli.ts
91
- import { Command } from "commander";
92
-
93
90
  // ../shared/src/respond.ts
94
91
  function respond(command, data, startTime, version) {
95
92
  const response = {
@@ -119,6 +116,9 @@ function respondError(command, code, message, startTime, version, details) {
119
116
  process.exitCode = 1;
120
117
  }
121
118
 
119
+ // src/cli.ts
120
+ import { Command } from "commander";
121
+
122
122
  // src/commands/Backup.tsx
123
123
  import { Box, Static, Text as Text2, useApp } from "ink";
124
124
  import { render } from "ink";
@@ -148,6 +148,7 @@ var ProgressBar = ({
148
148
  // src/core/backup.ts
149
149
  import { readdir } from "fs/promises";
150
150
  import { basename, join as join5 } from "path";
151
+ import { filter } from "@winglet/common-utils";
151
152
  import fg from "fast-glob";
152
153
 
153
154
  // src/constants.ts
@@ -451,6 +452,10 @@ function isValidPattern(pattern) {
451
452
  // src/core/metadata.ts
452
453
  import { createHash } from "crypto";
453
454
  import { lstat, readFile, readlink } from "fs/promises";
455
+ import { isString } from "@winglet/common-utils";
456
+
457
+ // src/schemas/metadata.schema.ts
458
+ import { map } from "@winglet/common-utils";
454
459
 
455
460
  // assets/schemas/metadata.schema.json
456
461
  var metadata_schema_default = {
@@ -595,14 +600,15 @@ var validate2 = ajv.compile(metadata_schema_default);
595
600
  function validateMetadata(data) {
596
601
  const valid = validate2(data);
597
602
  if (valid) return { valid: true };
598
- const errors = validate2.errors?.map(
603
+ const errors = validate2.errors ? map(
604
+ validate2.errors,
599
605
  (e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
600
- );
606
+ ) : void 0;
601
607
  return { valid: false, errors };
602
608
  }
603
609
 
604
610
  // src/version.ts
605
- var VERSION = "0.0.10";
611
+ var VERSION = "0.0.12";
606
612
 
607
613
  // src/core/metadata.ts
608
614
  var METADATA_VERSION = "1.0.0";
@@ -626,7 +632,7 @@ function createMetadata(files, config) {
626
632
  };
627
633
  }
628
634
  function parseMetadata(data) {
629
- const str = typeof data === "string" ? data : data.toString("utf-8");
635
+ const str = isString(data) ? data : data.toString("utf-8");
630
636
  const parsed = JSON.parse(str);
631
637
  const result = validateMetadata(parsed);
632
638
  if (!result.valid) {
@@ -780,7 +786,8 @@ async function scanTargets(config) {
780
786
  "**/Library/**",
781
787
  "**/.cache/**",
782
788
  "**/node_modules/**",
783
- ...config.backup.exclude.filter(
789
+ ...filter(
790
+ config.backup.exclude,
784
791
  (p) => detectPatternType(p) === "glob"
785
792
  )
786
793
  ]
@@ -951,8 +958,12 @@ async function createBackup(config, options = {}) {
951
958
  // src/core/config.ts
952
959
  import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
953
960
  import { join as join7 } from "path";
961
+ import { isArray, map as map3 } from "@winglet/common-utils";
954
962
  import YAML from "yaml";
955
963
 
964
+ // src/schemas/config.schema.ts
965
+ import { map as map2 } from "@winglet/common-utils";
966
+
956
967
  // assets/schemas/config.schema.json
957
968
  var config_schema_default = {
958
969
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -1022,9 +1033,10 @@ var validate3 = ajv.compile(config_schema_default);
1022
1033
  function validateConfig(data) {
1023
1034
  const valid = validate3(data);
1024
1035
  if (valid) return { valid: true };
1025
- const errors = validate3.errors?.map(
1036
+ const errors = validate3.errors ? map2(
1037
+ validate3.errors,
1026
1038
  (e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
1027
- );
1039
+ ) : void 0;
1028
1040
  return { valid: false, errors };
1029
1041
  }
1030
1042
 
@@ -1032,7 +1044,7 @@ function validateConfig(data) {
1032
1044
  init_assets();
1033
1045
  function stripDangerousKeys(obj) {
1034
1046
  if (obj === null || typeof obj !== "object") return obj;
1035
- if (Array.isArray(obj)) return obj.map(stripDangerousKeys);
1047
+ if (isArray(obj)) return map3(obj, stripDangerousKeys);
1036
1048
  const cleaned = {};
1037
1049
  for (const [key, value] of Object.entries(obj)) {
1038
1050
  if (["__proto__", "constructor", "prototype"].includes(key)) continue;
@@ -1102,6 +1114,8 @@ var SyncpointErrorCode = {
1102
1114
  PROVISION_FAILED: "PROVISION_FAILED",
1103
1115
  MISSING_ARGUMENT: "MISSING_ARGUMENT",
1104
1116
  INVALID_ARGUMENT: "INVALID_ARGUMENT",
1117
+ LINK_FAILED: "LINK_FAILED",
1118
+ UNLINK_FAILED: "UNLINK_FAILED",
1105
1119
  UNKNOWN: "UNKNOWN"
1106
1120
  };
1107
1121
  function classifyError(err) {
@@ -1118,6 +1132,12 @@ function classifyError(err) {
1118
1132
  if (msg.includes("Template file not found")) {
1119
1133
  return SyncpointErrorCode.TEMPLATE_NOT_FOUND;
1120
1134
  }
1135
+ if (msg.includes("not a symlink") || msg.includes('"syncpoint link"')) {
1136
+ return SyncpointErrorCode.UNLINK_FAILED;
1137
+ }
1138
+ if (msg.includes("destination is not set") || msg.includes("cross-device") || msg.includes("EXDEV")) {
1139
+ return SyncpointErrorCode.LINK_FAILED;
1140
+ }
1121
1141
  return SyncpointErrorCode.UNKNOWN;
1122
1142
  }
1123
1143
 
@@ -1267,7 +1287,13 @@ var COMMANDS = {
1267
1287
  required: false
1268
1288
  }
1269
1289
  ],
1270
- options: [{ flag: "--delete <filename>", description: "Delete item by filename", type: "string" }],
1290
+ options: [
1291
+ {
1292
+ flag: "--delete <filename>",
1293
+ description: "Delete item by filename",
1294
+ type: "string"
1295
+ }
1296
+ ],
1271
1297
  examples: [
1272
1298
  "npx @lumy-pack/syncpoint list",
1273
1299
  "npx @lumy-pack/syncpoint list backups",
@@ -1279,7 +1305,11 @@ var COMMANDS = {
1279
1305
  description: "Show ~/.syncpoint/ status summary and manage cleanup",
1280
1306
  usage: "npx @lumy-pack/syncpoint status [options]",
1281
1307
  options: [
1282
- { flag: "--cleanup", description: "Enter interactive cleanup mode", type: "boolean" }
1308
+ {
1309
+ flag: "--cleanup",
1310
+ description: "Enter interactive cleanup mode",
1311
+ type: "boolean"
1312
+ }
1283
1313
  ],
1284
1314
  examples: [
1285
1315
  "npx @lumy-pack/syncpoint status",
@@ -1302,6 +1332,38 @@ var COMMANDS = {
1302
1332
  "npx @lumy-pack/syncpoint migrate --dry-run"
1303
1333
  ]
1304
1334
  },
1335
+ link: {
1336
+ name: "link",
1337
+ description: "Move ~/.syncpoint to backup destination and create a symlink",
1338
+ usage: "npx @lumy-pack/syncpoint link [options]",
1339
+ options: [
1340
+ {
1341
+ flag: "-r, --ref <path>",
1342
+ description: "Adopt <path>/.syncpoint as ~/.syncpoint via symlink",
1343
+ type: "string"
1344
+ }
1345
+ ],
1346
+ examples: [
1347
+ "npx @lumy-pack/syncpoint link",
1348
+ "npx @lumy-pack/syncpoint link --ref ~/Dropbox"
1349
+ ]
1350
+ },
1351
+ unlink: {
1352
+ name: "unlink",
1353
+ description: "Remove the symlink and restore ~/.syncpoint from the destination copy",
1354
+ usage: "npx @lumy-pack/syncpoint unlink [options]",
1355
+ options: [
1356
+ {
1357
+ flag: "--clean",
1358
+ description: "Also remove the destination copy after restoring",
1359
+ type: "boolean"
1360
+ }
1361
+ ],
1362
+ examples: [
1363
+ "npx @lumy-pack/syncpoint unlink",
1364
+ "npx @lumy-pack/syncpoint unlink --clean"
1365
+ ]
1366
+ },
1305
1367
  help: {
1306
1368
  name: "help",
1307
1369
  description: "Display help information",
@@ -1524,6 +1586,9 @@ import Spinner from "ink-spinner";
1524
1586
  import { useEffect as useEffect2, useState as useState2 } from "react";
1525
1587
  init_wizard_template();
1526
1588
 
1589
+ // src/schemas/template.schema.ts
1590
+ import { map as map4 } from "@winglet/common-utils";
1591
+
1527
1592
  // assets/schemas/template.schema.json
1528
1593
  var template_schema_default = {
1529
1594
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -1592,9 +1657,10 @@ var validate4 = ajv.compile(template_schema_default);
1592
1657
  function validateTemplate(data) {
1593
1658
  const valid = validate4(data);
1594
1659
  if (valid) return { valid: true };
1595
- const errors = validate4.errors?.map(
1660
+ const errors = validate4.errors ? map4(
1661
+ validate4.errors,
1596
1662
  (e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
1597
- );
1663
+ ) : void 0;
1598
1664
  return { valid: false, errors };
1599
1665
  }
1600
1666
 
@@ -1604,19 +1670,19 @@ init_assets();
1604
1670
  // src/utils/claude-code-runner.ts
1605
1671
  import { spawn } from "child_process";
1606
1672
  async function isClaudeCodeAvailable() {
1607
- return new Promise((resolve2) => {
1673
+ return new Promise((resolve3) => {
1608
1674
  const child = spawn("which", ["claude"], { shell: true });
1609
1675
  child.on("close", (code) => {
1610
- resolve2(code === 0);
1676
+ resolve3(code === 0);
1611
1677
  });
1612
1678
  child.on("error", () => {
1613
- resolve2(false);
1679
+ resolve3(false);
1614
1680
  });
1615
1681
  });
1616
1682
  }
1617
1683
  async function invokeClaudeCode(prompt, options) {
1618
1684
  const timeout = options?.timeout ?? 12e4;
1619
- return await new Promise((resolve2, reject) => {
1685
+ return await new Promise((resolve3, reject) => {
1620
1686
  const args = ["--permission-mode", "acceptEdits", "--model", "sonnet"];
1621
1687
  if (options?.sessionId) {
1622
1688
  args.push("--session", options.sessionId);
@@ -1641,13 +1707,13 @@ async function invokeClaudeCode(prompt, options) {
1641
1707
  child.on("close", (code) => {
1642
1708
  clearTimeout(timer);
1643
1709
  if (code === 0) {
1644
- resolve2({
1710
+ resolve3({
1645
1711
  success: true,
1646
1712
  output: stdout,
1647
1713
  sessionId: options?.sessionId
1648
1714
  });
1649
1715
  } else {
1650
- resolve2({
1716
+ resolve3({
1651
1717
  success: false,
1652
1718
  output: stdout,
1653
1719
  error: stderr || `Process exited with code ${code}`
@@ -1667,7 +1733,7 @@ async function resumeClaudeCodeSession(sessionId, prompt, options) {
1667
1733
  });
1668
1734
  }
1669
1735
  async function invokeClaudeCodeInteractive(prompt) {
1670
- return await new Promise((resolve2, reject) => {
1736
+ return await new Promise((resolve3, reject) => {
1671
1737
  const initialMessage = `${prompt}
1672
1738
 
1673
1739
  IMPORTANT INSTRUCTIONS:
@@ -1691,7 +1757,7 @@ Start by asking the user about their backup priorities for the home directory st
1691
1757
  // Share stdin/stdout/stderr with parent process
1692
1758
  });
1693
1759
  child.on("close", (code) => {
1694
- resolve2({
1760
+ resolve3({
1695
1761
  success: code === 0,
1696
1762
  output: ""
1697
1763
  // No captured output in interactive mode
@@ -1704,11 +1770,12 @@ Start by asking the user about their backup priorities for the home directory st
1704
1770
  }
1705
1771
 
1706
1772
  // src/utils/error-formatter.ts
1773
+ import { map as map5 } from "@winglet/common-utils";
1707
1774
  function formatValidationErrors(errors) {
1708
1775
  if (errors.length === 0) {
1709
1776
  return "No validation errors.";
1710
1777
  }
1711
- const formattedErrors = errors.map((error, index) => {
1778
+ const formattedErrors = map5(errors, (error, index) => {
1712
1779
  return `${index + 1}. ${error}`;
1713
1780
  });
1714
1781
  return `Validation failed with ${errors.length} error(s):
@@ -2247,11 +2314,10 @@ function registerInitCommand(program2) {
2247
2314
  });
2248
2315
  }
2249
2316
 
2250
- // src/commands/List.tsx
2251
- import { unlinkSync } from "fs";
2252
- import { Box as Box6, Text as Text8, useApp as useApp4, useInput as useInput2 } from "ink";
2317
+ // src/commands/Link.tsx
2318
+ import { lstat as lstat3 } from "fs/promises";
2319
+ import { Box as Box5, Text as Text7, useApp as useApp4 } from "ink";
2253
2320
  import { render as render5 } from "ink";
2254
- import SelectInput from "ink-select-input";
2255
2321
  import { useEffect as useEffect4, useState as useState5 } from "react";
2256
2322
 
2257
2323
  // src/components/Confirm.tsx
@@ -2291,9 +2357,298 @@ var Confirm = ({
2291
2357
  ] });
2292
2358
  };
2293
2359
 
2360
+ // src/core/link.ts
2361
+ import {
2362
+ cp,
2363
+ lstat as lstat2,
2364
+ readlink as readlink2,
2365
+ rename,
2366
+ rm as rm2,
2367
+ stat as stat2,
2368
+ symlink,
2369
+ unlink
2370
+ } from "fs/promises";
2371
+ import { basename as basename2, join as join10, resolve as resolve2 } from "path";
2372
+ async function linkSyncpoint(destination, _options = {}) {
2373
+ const appDir = getAppDir();
2374
+ const expandedDest = expandTilde(destination);
2375
+ const targetDir = join10(expandedDest, ".syncpoint");
2376
+ let wasAlreadyLinked = false;
2377
+ let lstats;
2378
+ try {
2379
+ lstats = await lstat2(appDir);
2380
+ } catch {
2381
+ throw new Error(`${appDir} does not exist. Run "syncpoint init" first.`);
2382
+ }
2383
+ if (lstats.isSymbolicLink()) {
2384
+ wasAlreadyLinked = true;
2385
+ const existingTarget = await readlink2(appDir);
2386
+ await unlink(appDir);
2387
+ if (existingTarget !== targetDir) {
2388
+ await rename(existingTarget, appDir);
2389
+ } else {
2390
+ await symlink(targetDir, appDir);
2391
+ return { appDir, targetDir, wasAlreadyLinked };
2392
+ }
2393
+ }
2394
+ try {
2395
+ await rename(appDir, targetDir);
2396
+ } catch (err) {
2397
+ const isExdev = err instanceof Error && (err.message.includes("EXDEV") || err.message.includes("cross-device"));
2398
+ if (isExdev) {
2399
+ throw new Error(
2400
+ `Cannot move ${appDir} to ${targetDir}: source and destination are on different filesystems. Use a destination on the same filesystem, or manually copy the directory.`
2401
+ );
2402
+ }
2403
+ throw err;
2404
+ }
2405
+ await symlink(targetDir, appDir);
2406
+ return { appDir, targetDir, wasAlreadyLinked };
2407
+ }
2408
+ async function linkSyncpointByRef(refPath) {
2409
+ const appDir = getAppDir();
2410
+ const absoluteRef = resolve2(expandTilde(refPath));
2411
+ const targetDir = basename2(absoluteRef) === ".syncpoint" ? absoluteRef : join10(absoluteRef, ".syncpoint");
2412
+ let refStats;
2413
+ try {
2414
+ refStats = await stat2(targetDir);
2415
+ } catch {
2416
+ throw new Error(`Reference syncpoint path does not exist: ${targetDir}`);
2417
+ }
2418
+ if (!refStats.isDirectory()) {
2419
+ throw new Error(
2420
+ `Reference syncpoint path is not a directory: ${targetDir}`
2421
+ );
2422
+ }
2423
+ try {
2424
+ const existing = await lstat2(appDir);
2425
+ if (existing.isSymbolicLink()) {
2426
+ await unlink(appDir);
2427
+ } else {
2428
+ await rm2(appDir, { recursive: true, force: true });
2429
+ }
2430
+ } catch {
2431
+ }
2432
+ await symlink(targetDir, appDir);
2433
+ return { appDir, targetDir, wasAlreadyLinked: false };
2434
+ }
2435
+ async function unlinkSyncpoint(options = {}) {
2436
+ const appDir = getAppDir();
2437
+ let lstats;
2438
+ try {
2439
+ lstats = await lstat2(appDir);
2440
+ } catch {
2441
+ throw new Error(`${appDir} does not exist.`);
2442
+ }
2443
+ if (!lstats.isSymbolicLink()) {
2444
+ throw new Error(`${appDir} is not a symlink. Run "syncpoint link" first.`);
2445
+ }
2446
+ const targetDir = await readlink2(appDir);
2447
+ await unlink(appDir);
2448
+ await cp(targetDir, appDir, { recursive: true });
2449
+ if (options.clean) {
2450
+ await rm2(targetDir, { recursive: true, force: true });
2451
+ }
2452
+ return { appDir, targetDir, cleaned: options.clean ?? false };
2453
+ }
2454
+
2455
+ // src/commands/Link.tsx
2456
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2457
+ var LinkView = ({ refPath, yes }) => {
2458
+ const { exit } = useApp4();
2459
+ const [phase, setPhase] = useState5("checking");
2460
+ const [result, setResult] = useState5(null);
2461
+ const [error, setError] = useState5(null);
2462
+ const [existingType, setExistingType] = useState5("directory");
2463
+ useEffect4(() => {
2464
+ (async () => {
2465
+ try {
2466
+ if (!refPath) {
2467
+ setPhase("linking");
2468
+ return;
2469
+ }
2470
+ const appDir = getAppDir();
2471
+ try {
2472
+ const stats = await lstat3(appDir);
2473
+ setExistingType(stats.isSymbolicLink() ? "symlink" : "directory");
2474
+ if (yes) {
2475
+ setPhase("linking");
2476
+ } else {
2477
+ setPhase("confirming");
2478
+ }
2479
+ } catch {
2480
+ setPhase("linking");
2481
+ }
2482
+ } catch (err) {
2483
+ setError(err instanceof Error ? err.message : String(err));
2484
+ setPhase("error");
2485
+ setTimeout(() => exit(), 100);
2486
+ }
2487
+ })();
2488
+ }, []);
2489
+ useEffect4(() => {
2490
+ if (phase !== "linking") return;
2491
+ (async () => {
2492
+ try {
2493
+ let linkResult;
2494
+ if (refPath) {
2495
+ linkResult = await linkSyncpointByRef(refPath);
2496
+ } else {
2497
+ const config = await loadConfig();
2498
+ const destination = config.backup.destination;
2499
+ if (!destination) {
2500
+ throw new Error(
2501
+ 'backup.destination is not set in config.yml. Set it before running "syncpoint link".'
2502
+ );
2503
+ }
2504
+ linkResult = await linkSyncpoint(destination);
2505
+ }
2506
+ setResult(linkResult);
2507
+ setPhase("done");
2508
+ setTimeout(() => exit(), 100);
2509
+ } catch (err) {
2510
+ setError(err instanceof Error ? err.message : String(err));
2511
+ setPhase("error");
2512
+ setTimeout(() => exit(), 100);
2513
+ }
2514
+ })();
2515
+ }, [phase]);
2516
+ if (phase === "error" || error) {
2517
+ return /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
2518
+ "\u2717 Link failed: ",
2519
+ error
2520
+ ] }) });
2521
+ }
2522
+ if (phase === "confirming") {
2523
+ return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
2524
+ /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
2525
+ "\u26A0 ~/.syncpoint already exists as a ",
2526
+ existingType,
2527
+ "."
2528
+ ] }),
2529
+ /* @__PURE__ */ jsx7(
2530
+ Confirm,
2531
+ {
2532
+ message: "Overwrite with symlink?",
2533
+ defaultYes: false,
2534
+ onConfirm: (confirmed) => {
2535
+ if (confirmed) {
2536
+ setPhase("linking");
2537
+ } else {
2538
+ setError("Aborted.");
2539
+ setPhase("error");
2540
+ setTimeout(() => exit(), 100);
2541
+ }
2542
+ }
2543
+ }
2544
+ )
2545
+ ] });
2546
+ }
2547
+ if (phase === "checking" || phase === "linking") {
2548
+ return /* @__PURE__ */ jsx7(Box5, { children: /* @__PURE__ */ jsx7(Text7, { children: "\u25B8 Linking ~/.syncpoint to destination..." }) });
2549
+ }
2550
+ return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
2551
+ /* @__PURE__ */ jsx7(Text7, { color: "green", bold: true, children: "\u2713 Link complete" }),
2552
+ result && /* @__PURE__ */ jsxs7(Fragment2, { children: [
2553
+ /* @__PURE__ */ jsxs7(Text7, { children: [
2554
+ " ",
2555
+ "From: ",
2556
+ contractTilde(result.appDir)
2557
+ ] }),
2558
+ /* @__PURE__ */ jsxs7(Text7, { children: [
2559
+ " ",
2560
+ "To: ",
2561
+ contractTilde(result.targetDir)
2562
+ ] }),
2563
+ result.wasAlreadyLinked && /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
2564
+ " ",
2565
+ "(previous link was removed and re-linked)"
2566
+ ] })
2567
+ ] })
2568
+ ] });
2569
+ };
2570
+ function registerLinkCommand(program2) {
2571
+ const cmdInfo = COMMANDS.link;
2572
+ const cmd = program2.command("link").description(cmdInfo.description);
2573
+ cmd.option(
2574
+ "-r, --ref <path>",
2575
+ "Adopt <path>/.syncpoint as ~/.syncpoint via symlink"
2576
+ );
2577
+ cmd.action(async (opts) => {
2578
+ const globalOpts = program2.opts();
2579
+ const startTime = Date.now();
2580
+ if (globalOpts.json) {
2581
+ try {
2582
+ if (opts.ref) {
2583
+ const appDir = getAppDir();
2584
+ let appDirExists = false;
2585
+ try {
2586
+ await lstat3(appDir);
2587
+ appDirExists = true;
2588
+ } catch {
2589
+ }
2590
+ if (appDirExists && !globalOpts.yes) {
2591
+ respondError(
2592
+ "link",
2593
+ SyncpointErrorCode.LINK_FAILED,
2594
+ "~/.syncpoint already exists. Use --yes to overwrite.",
2595
+ startTime,
2596
+ VERSION
2597
+ );
2598
+ return;
2599
+ }
2600
+ const linkResult = await linkSyncpointByRef(opts.ref);
2601
+ respond(
2602
+ "link",
2603
+ {
2604
+ appDir: linkResult.appDir,
2605
+ targetDir: linkResult.targetDir,
2606
+ wasAlreadyLinked: linkResult.wasAlreadyLinked
2607
+ },
2608
+ startTime,
2609
+ VERSION
2610
+ );
2611
+ } else {
2612
+ const config = await loadConfig();
2613
+ const destination = config.backup.destination;
2614
+ if (!destination) {
2615
+ throw new Error("backup.destination is not set in config.yml.");
2616
+ }
2617
+ const linkResult = await linkSyncpoint(destination);
2618
+ respond(
2619
+ "link",
2620
+ {
2621
+ appDir: linkResult.appDir,
2622
+ targetDir: linkResult.targetDir,
2623
+ wasAlreadyLinked: linkResult.wasAlreadyLinked
2624
+ },
2625
+ startTime,
2626
+ VERSION
2627
+ );
2628
+ }
2629
+ } catch (error) {
2630
+ const code = classifyError(error);
2631
+ respondError("link", code, error.message, startTime, VERSION);
2632
+ }
2633
+ return;
2634
+ }
2635
+ const { waitUntilExit } = render5(
2636
+ /* @__PURE__ */ jsx7(LinkView, { refPath: opts.ref, yes: globalOpts.yes ?? false })
2637
+ );
2638
+ await waitUntilExit();
2639
+ });
2640
+ }
2641
+
2642
+ // src/commands/List.tsx
2643
+ import { unlinkSync } from "fs";
2644
+ import { Box as Box7, Text as Text9, useApp as useApp5, useInput as useInput2 } from "ink";
2645
+ import { render as render6 } from "ink";
2646
+ import SelectInput from "ink-select-input";
2647
+ import { useEffect as useEffect5, useState as useState6 } from "react";
2648
+
2294
2649
  // src/components/Table.tsx
2295
- import { Box as Box5, Text as Text7 } from "ink";
2296
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2650
+ import { Box as Box6, Text as Text8 } from "ink";
2651
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2297
2652
  var Table = ({
2298
2653
  headers,
2299
2654
  rows,
@@ -2310,13 +2665,13 @@ var Table = ({
2310
2665
  return text.padEnd(width);
2311
2666
  };
2312
2667
  const separator = widths.map((w) => "\u2500".repeat(w)).join(" ");
2313
- return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
2314
- /* @__PURE__ */ jsx7(Text7, { children: headers.map((h, i) => /* @__PURE__ */ jsxs7(Text7, { bold: true, children: [
2668
+ return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2669
+ /* @__PURE__ */ jsx8(Text8, { children: headers.map((h, i) => /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
2315
2670
  padCell(h, widths[i]),
2316
2671
  i < headers.length - 1 ? " " : ""
2317
2672
  ] }, i)) }),
2318
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: separator }),
2319
- rows.map((row, rowIdx) => /* @__PURE__ */ jsx7(Text7, { children: row.map((cell, colIdx) => /* @__PURE__ */ jsxs7(Text7, { children: [
2673
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: separator }),
2674
+ rows.map((row, rowIdx) => /* @__PURE__ */ jsx8(Text8, { children: row.map((cell, colIdx) => /* @__PURE__ */ jsxs8(Text8, { children: [
2320
2675
  padCell(cell, widths[colIdx]),
2321
2676
  colIdx < row.length - 1 ? " " : ""
2322
2677
  ] }, colIdx)) }, rowIdx))
@@ -2326,7 +2681,8 @@ var Table = ({
2326
2681
  // src/core/provision.ts
2327
2682
  import { exec } from "child_process";
2328
2683
  import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
2329
- import { join as join10 } from "path";
2684
+ import { join as join11 } from "path";
2685
+ import { filter as filter2, isTruthy } from "@winglet/common-utils";
2330
2686
  import YAML3 from "yaml";
2331
2687
  var REMOTE_SCRIPT_PATTERNS = [
2332
2688
  /curl\s.*\|\s*(ba)?sh/,
@@ -2366,7 +2722,7 @@ async function listTemplates() {
2366
2722
  if (!entry.isFile() || !entry.name.endsWith(".yml") && !entry.name.endsWith(".yaml")) {
2367
2723
  continue;
2368
2724
  }
2369
- const fullPath = join10(templatesDir, entry.name);
2725
+ const fullPath = join11(templatesDir, entry.name);
2370
2726
  try {
2371
2727
  const config = await loadTemplate(fullPath);
2372
2728
  templates.push({
@@ -2381,7 +2737,7 @@ async function listTemplates() {
2381
2737
  return templates;
2382
2738
  }
2383
2739
  function execAsync(command) {
2384
- return new Promise((resolve2, reject) => {
2740
+ return new Promise((resolve3, reject) => {
2385
2741
  exec(
2386
2742
  command,
2387
2743
  { shell: "/bin/bash", timeout: 3e5 },
@@ -2394,7 +2750,7 @@ function execAsync(command) {
2394
2750
  })
2395
2751
  );
2396
2752
  } else {
2397
- resolve2({
2753
+ resolve3({
2398
2754
  stdout: stdout?.toString() ?? "",
2399
2755
  stderr: stderr?.toString() ?? ""
2400
2756
  });
@@ -2435,7 +2791,7 @@ async function executeStep(step) {
2435
2791
  }
2436
2792
  try {
2437
2793
  const { stdout, stderr } = await execAsync(step.command);
2438
- const output = [stdout, stderr].filter(Boolean).join("\n").trim();
2794
+ const output = filter2([stdout, stderr], isTruthy).join("\n").trim();
2439
2795
  return {
2440
2796
  name: step.name,
2441
2797
  status: "success",
@@ -2446,7 +2802,7 @@ async function executeStep(step) {
2446
2802
  const error = err instanceof Error ? err : new Error(String(err));
2447
2803
  const stdout = err?.stdout ?? "";
2448
2804
  const stderr = err?.stderr ?? "";
2449
- const errorOutput = [stdout, stderr, error.message].filter(Boolean).join("\n").trim();
2805
+ const errorOutput = filter2([stdout, stderr, error.message], isTruthy).join("\n").trim();
2450
2806
  return {
2451
2807
  name: step.name,
2452
2808
  status: "failed",
@@ -2484,8 +2840,9 @@ async function* runProvision(templatePath, options = {}) {
2484
2840
  }
2485
2841
 
2486
2842
  // src/core/restore.ts
2487
- import { copyFile, lstat as lstat2, readdir as readdir3, stat as stat2 } from "fs/promises";
2488
- import { dirname as dirname2, join as join11 } from "path";
2843
+ import { copyFile, lstat as lstat4, readdir as readdir3, stat as stat3 } from "fs/promises";
2844
+ import { dirname as dirname2, join as join12 } from "path";
2845
+ import { forEach } from "@winglet/common-utils";
2489
2846
  async function getBackupList(config) {
2490
2847
  const backupDir = config?.backup.destination ? resolveTargetPath(config.backup.destination) : getSubDir(BACKUPS_DIR);
2491
2848
  const exists = await fileExists(backupDir);
@@ -2494,8 +2851,8 @@ async function getBackupList(config) {
2494
2851
  const backups = [];
2495
2852
  for (const entry of entries) {
2496
2853
  if (!entry.isFile() || !entry.name.endsWith(".tar.gz")) continue;
2497
- const fullPath = join11(backupDir, entry.name);
2498
- const fileStat = await stat2(fullPath);
2854
+ const fullPath = join12(backupDir, entry.name);
2855
+ const fileStat = await stat3(fullPath);
2499
2856
  let hostname;
2500
2857
  let fileCount;
2501
2858
  try {
@@ -2543,7 +2900,7 @@ This may not be a valid syncpoint backup.`
2543
2900
  continue;
2544
2901
  }
2545
2902
  const currentHash = await computeFileHash(absPath);
2546
- const currentStat = await stat2(absPath);
2903
+ const currentStat = await stat3(absPath);
2547
2904
  if (currentHash === file.hash) {
2548
2905
  actions.push({
2549
2906
  path: file.path,
@@ -2569,7 +2926,7 @@ async function createSafetyBackup(filePaths) {
2569
2926
  const filename = `_pre-restore_${formatDatetime(now)}.tar.gz`;
2570
2927
  const backupDir = getSubDir(BACKUPS_DIR);
2571
2928
  await ensureDir(backupDir);
2572
- const archivePath = join11(backupDir, filename);
2929
+ const archivePath = join12(backupDir, filename);
2573
2930
  const files = [];
2574
2931
  for (const fp of filePaths) {
2575
2932
  const absPath = resolveTargetPath(fp);
@@ -2590,21 +2947,32 @@ async function restoreBackup(archivePath, options = {}) {
2590
2947
  const plan = await getRestorePlan(archivePath);
2591
2948
  const restoredFiles = [];
2592
2949
  const skippedFiles = [];
2593
- const overwritePaths = plan.actions.filter((a) => a.action === "overwrite").map((a) => a.path);
2950
+ const overwritePaths = [];
2951
+ forEach(plan.actions, (a) => {
2952
+ if (a.action === "overwrite") overwritePaths.push(a.path);
2953
+ });
2594
2954
  let safetyBackupPath;
2595
2955
  if (overwritePaths.length > 0 && !options.dryRun) {
2596
2956
  safetyBackupPath = await createSafetyBackup(overwritePaths);
2597
2957
  }
2598
2958
  if (options.dryRun) {
2959
+ const restoredFiles2 = [];
2960
+ forEach(plan.actions, (a) => {
2961
+ if (a.action !== "skip") restoredFiles2.push(a.path);
2962
+ });
2963
+ const skippedFiles2 = [];
2964
+ forEach(plan.actions, (a) => {
2965
+ if (a.action === "skip") skippedFiles2.push(a.path);
2966
+ });
2599
2967
  return {
2600
- restoredFiles: plan.actions.filter((a) => a.action !== "skip").map((a) => a.path),
2601
- skippedFiles: plan.actions.filter((a) => a.action === "skip").map((a) => a.path),
2968
+ restoredFiles: restoredFiles2,
2969
+ skippedFiles: skippedFiles2,
2602
2970
  safetyBackupPath
2603
2971
  };
2604
2972
  }
2605
- const { mkdtemp: mkdtemp2, rm: rm2 } = await import("fs/promises");
2973
+ const { mkdtemp: mkdtemp2, rm: rm3 } = await import("fs/promises");
2606
2974
  const { tmpdir: tmpdir2 } = await import("os");
2607
- const tmpDir = await mkdtemp2(join11(tmpdir2(), "syncpoint-restore-"));
2975
+ const tmpDir = await mkdtemp2(join12(tmpdir2(), "syncpoint-restore-"));
2608
2976
  try {
2609
2977
  await extractArchive(archivePath, tmpDir);
2610
2978
  for (const action of plan.actions) {
@@ -2613,7 +2981,7 @@ async function restoreBackup(archivePath, options = {}) {
2613
2981
  continue;
2614
2982
  }
2615
2983
  const archiveName = action.path.startsWith("~/") ? action.path.slice(2) : action.path;
2616
- const extractedPath = join11(tmpDir, archiveName);
2984
+ const extractedPath = join12(tmpDir, archiveName);
2617
2985
  const destPath = resolveTargetPath(action.path);
2618
2986
  const extractedExists = await fileExists(extractedPath);
2619
2987
  if (!extractedExists) {
@@ -2623,7 +2991,7 @@ async function restoreBackup(archivePath, options = {}) {
2623
2991
  }
2624
2992
  await ensureDir(dirname2(destPath));
2625
2993
  try {
2626
- const destStat = await lstat2(destPath);
2994
+ const destStat = await lstat4(destPath);
2627
2995
  if (destStat.isSymbolicLink()) {
2628
2996
  logger.warn(`Skipping symlink target: ${action.path}`);
2629
2997
  skippedFiles.push(action.path);
@@ -2636,45 +3004,45 @@ async function restoreBackup(archivePath, options = {}) {
2636
3004
  restoredFiles.push(action.path);
2637
3005
  }
2638
3006
  } finally {
2639
- await rm2(tmpDir, { recursive: true, force: true });
3007
+ await rm3(tmpDir, { recursive: true, force: true });
2640
3008
  }
2641
3009
  return { restoredFiles, skippedFiles, safetyBackupPath };
2642
3010
  }
2643
3011
 
2644
3012
  // src/commands/List.tsx
2645
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3013
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2646
3014
  var MenuItem = ({ isSelected = false, label }) => {
2647
3015
  if (label === "Delete") {
2648
- return /* @__PURE__ */ jsx8(Text8, { bold: isSelected, color: "red", children: label });
3016
+ return /* @__PURE__ */ jsx9(Text9, { bold: isSelected, color: "red", children: label });
2649
3017
  }
2650
3018
  const match = label.match(/^(.+?)\s+\((\d+)\)$/);
2651
3019
  if (match) {
2652
3020
  const [, name, count] = match;
2653
- return /* @__PURE__ */ jsxs8(Text8, { children: [
2654
- /* @__PURE__ */ jsx8(Text8, { bold: isSelected, children: name }),
3021
+ return /* @__PURE__ */ jsxs9(Text9, { children: [
3022
+ /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: name }),
2655
3023
  " ",
2656
- /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
3024
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2657
3025
  "(",
2658
3026
  count,
2659
3027
  ")"
2660
3028
  ] })
2661
3029
  ] });
2662
3030
  }
2663
- return /* @__PURE__ */ jsx8(Text8, { bold: isSelected, children: label });
3031
+ return /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: label });
2664
3032
  };
2665
3033
  var ListView = ({ type, deleteIndex }) => {
2666
- const { exit } = useApp4();
2667
- const [phase, setPhase] = useState5("loading");
2668
- const [backups, setBackups] = useState5([]);
2669
- const [templates, setTemplates] = useState5([]);
2670
- const [selectedTemplate, setSelectedTemplate] = useState5(
3034
+ const { exit } = useApp5();
3035
+ const [phase, setPhase] = useState6("loading");
3036
+ const [backups, setBackups] = useState6([]);
3037
+ const [templates, setTemplates] = useState6([]);
3038
+ const [selectedTemplate, setSelectedTemplate] = useState6(
2671
3039
  null
2672
3040
  );
2673
- const [selectedBackup, setSelectedBackup] = useState5(null);
2674
- const [deleteTarget, setDeleteTarget] = useState5(null);
2675
- const [error, setError] = useState5(null);
2676
- const [backupDir, setBackupDir] = useState5(getSubDir("backups"));
2677
- const [successMessage, setSuccessMessage] = useState5(null);
3041
+ const [selectedBackup, setSelectedBackup] = useState6(null);
3042
+ const [deleteTarget, setDeleteTarget] = useState6(null);
3043
+ const [error, setError] = useState6(null);
3044
+ const [backupDir, setBackupDir] = useState6(getSubDir("backups"));
3045
+ const [successMessage, setSuccessMessage] = useState6(null);
2678
3046
  useInput2((_input, key) => {
2679
3047
  if (!key.escape) return;
2680
3048
  switch (phase) {
@@ -2698,7 +3066,7 @@ var ListView = ({ type, deleteIndex }) => {
2698
3066
  break;
2699
3067
  }
2700
3068
  });
2701
- useEffect4(() => {
3069
+ useEffect5(() => {
2702
3070
  (async () => {
2703
3071
  try {
2704
3072
  const config = await loadConfig();
@@ -2790,16 +3158,16 @@ var ListView = ({ type, deleteIndex }) => {
2790
3158
  }
2791
3159
  };
2792
3160
  if (phase === "error" || error) {
2793
- return /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
3161
+ return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
2794
3162
  "\u2717 ",
2795
3163
  error
2796
3164
  ] }) });
2797
3165
  }
2798
3166
  if (phase === "loading") {
2799
- return /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "Loading..." });
3167
+ return /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Loading..." });
2800
3168
  }
2801
3169
  if (phase === "deleting" && deleteTarget) {
2802
- return /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx8(
3170
+ return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx9(
2803
3171
  Confirm,
2804
3172
  {
2805
3173
  message: `Delete ${deleteTarget.name}?`,
@@ -2809,7 +3177,7 @@ var ListView = ({ type, deleteIndex }) => {
2809
3177
  ) });
2810
3178
  }
2811
3179
  if (phase === "done" && deleteTarget) {
2812
- return /* @__PURE__ */ jsxs8(Text8, { color: "green", children: [
3180
+ return /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
2813
3181
  "\u2713 ",
2814
3182
  deleteTarget.name,
2815
3183
  " deleted"
@@ -2841,8 +3209,8 @@ var ListView = ({ type, deleteIndex }) => {
2841
3209
  setPhase("template-list");
2842
3210
  }
2843
3211
  };
2844
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2845
- /* @__PURE__ */ jsx8(
3212
+ return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3213
+ /* @__PURE__ */ jsx9(
2846
3214
  SelectInput,
2847
3215
  {
2848
3216
  items: menuItems,
@@ -2850,9 +3218,9 @@ var ListView = ({ type, deleteIndex }) => {
2850
3218
  itemComponent: MenuItem
2851
3219
  }
2852
3220
  ),
2853
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
3221
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2854
3222
  "Press ",
2855
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "ESC" }),
3223
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
2856
3224
  " to exit"
2857
3225
  ] }) })
2858
3226
  ] });
@@ -2869,10 +3237,10 @@ var ListView = ({ type, deleteIndex }) => {
2869
3237
  setPhase("backup-detail");
2870
3238
  }
2871
3239
  };
2872
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2873
- successMessage && /* @__PURE__ */ jsx8(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "green", children: successMessage }) }),
2874
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "\u25B8 Backups" }),
2875
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: backups.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " No backups found." }) : /* @__PURE__ */ jsx8(
3240
+ return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3241
+ successMessage && /* @__PURE__ */ jsx9(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "green", children: successMessage }) }),
3242
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "\u25B8 Backups" }),
3243
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: backups.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " No backups found." }) : /* @__PURE__ */ jsx9(
2876
3244
  SelectInput,
2877
3245
  {
2878
3246
  items,
@@ -2880,9 +3248,9 @@ var ListView = ({ type, deleteIndex }) => {
2880
3248
  itemComponent: MenuItem
2881
3249
  }
2882
3250
  ) }),
2883
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
3251
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2884
3252
  "Press ",
2885
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "ESC" }),
3253
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
2886
3254
  " to go back"
2887
3255
  ] }) })
2888
3256
  ] });
@@ -2899,9 +3267,9 @@ var ListView = ({ type, deleteIndex }) => {
2899
3267
  setPhase("template-detail");
2900
3268
  }
2901
3269
  };
2902
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2903
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "\u25B8 Templates" }),
2904
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: templates.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " No templates found." }) : /* @__PURE__ */ jsx8(
3270
+ return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3271
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "\u25B8 Templates" }),
3272
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: templates.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " No templates found." }) : /* @__PURE__ */ jsx9(
2905
3273
  SelectInput,
2906
3274
  {
2907
3275
  items,
@@ -2909,9 +3277,9 @@ var ListView = ({ type, deleteIndex }) => {
2909
3277
  itemComponent: MenuItem
2910
3278
  }
2911
3279
  ) }),
2912
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
3280
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2913
3281
  "Press ",
2914
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "ESC" }),
3282
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
2915
3283
  " to go back"
2916
3284
  ] }) })
2917
3285
  ] });
@@ -2939,20 +3307,20 @@ var ListView = ({ type, deleteIndex }) => {
2939
3307
  goBackToBackupList();
2940
3308
  }
2941
3309
  };
2942
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2943
- /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
3310
+ return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3311
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
2944
3312
  "\u25B8 ",
2945
3313
  selectedBackup.filename.replace(".tar.gz", "")
2946
3314
  ] }),
2947
- /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: sections.map((section, idx) => {
3315
+ /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: sections.map((section, idx) => {
2948
3316
  const labelWidth = Math.max(...sections.map((s) => s.label.length)) + 1;
2949
- return /* @__PURE__ */ jsxs8(Box6, { children: [
2950
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: section.label.padEnd(labelWidth) }),
2951
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
2952
- /* @__PURE__ */ jsx8(Text8, { children: section.value })
3317
+ return /* @__PURE__ */ jsxs9(Box7, { children: [
3318
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: section.label.padEnd(labelWidth) }),
3319
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
3320
+ /* @__PURE__ */ jsx9(Text9, { children: section.value })
2953
3321
  ] }, idx);
2954
3322
  }) }),
2955
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(
3323
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(
2956
3324
  SelectInput,
2957
3325
  {
2958
3326
  items: actionItems,
@@ -2960,9 +3328,9 @@ var ListView = ({ type, deleteIndex }) => {
2960
3328
  itemComponent: MenuItem
2961
3329
  }
2962
3330
  ) }),
2963
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
3331
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2964
3332
  "Press ",
2965
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "ESC" }),
3333
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
2966
3334
  " to go back"
2967
3335
  ] }) })
2968
3336
  ] });
@@ -2982,19 +3350,19 @@ var ListView = ({ type, deleteIndex }) => {
2982
3350
  }
2983
3351
  };
2984
3352
  const labelWidth = Math.max(...sections.map((s) => s.label.length)) + 1;
2985
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2986
- /* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
3353
+ return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3354
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
2987
3355
  "\u25B8 ",
2988
3356
  selectedTemplate.name
2989
3357
  ] }),
2990
- /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: sections.map((section, idx) => /* @__PURE__ */ jsxs8(Box6, { children: [
2991
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: section.label.padEnd(labelWidth) }),
2992
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
2993
- /* @__PURE__ */ jsx8(Text8, { children: section.value })
3358
+ /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: sections.map((section, idx) => /* @__PURE__ */ jsxs9(Box7, { children: [
3359
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: section.label.padEnd(labelWidth) }),
3360
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
3361
+ /* @__PURE__ */ jsx9(Text9, { children: section.value })
2994
3362
  ] }, idx)) }),
2995
- t.steps.length > 0 && /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", marginTop: 1, children: [
2996
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " Provisioning Steps" }),
2997
- /* @__PURE__ */ jsx8(Box6, { marginLeft: 2, children: /* @__PURE__ */ jsx8(
3363
+ t.steps.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: 1, children: [
3364
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " Provisioning Steps" }),
3365
+ /* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(
2998
3366
  Table,
2999
3367
  {
3000
3368
  headers: ["#", "Step", "Description"],
@@ -3006,7 +3374,7 @@ var ListView = ({ type, deleteIndex }) => {
3006
3374
  }
3007
3375
  ) })
3008
3376
  ] }),
3009
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(
3377
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(
3010
3378
  SelectInput,
3011
3379
  {
3012
3380
  items: actionItems,
@@ -3014,9 +3382,9 @@ var ListView = ({ type, deleteIndex }) => {
3014
3382
  itemComponent: MenuItem
3015
3383
  }
3016
3384
  ) }),
3017
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
3385
+ /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
3018
3386
  "Press ",
3019
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "ESC" }),
3387
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "ESC" }),
3020
3388
  " to go back"
3021
3389
  ] }) })
3022
3390
  ] });
@@ -3085,26 +3453,27 @@ function registerListCommand(program2) {
3085
3453
  }
3086
3454
  const deleteIndex = opts.delete ? parseInt(opts.delete, 10) : void 0;
3087
3455
  if (deleteIndex !== void 0 && isNaN(deleteIndex)) {
3088
- const { waitUntilExit: waitUntilExit2 } = render5(
3089
- /* @__PURE__ */ jsx8(ListView, { type, deleteIndex: void 0 })
3456
+ const { waitUntilExit: waitUntilExit2 } = render6(
3457
+ /* @__PURE__ */ jsx9(ListView, { type, deleteIndex: void 0 })
3090
3458
  );
3091
3459
  await waitUntilExit2();
3092
3460
  return;
3093
3461
  }
3094
- const { waitUntilExit } = render5(
3095
- /* @__PURE__ */ jsx8(ListView, { type, deleteIndex })
3462
+ const { waitUntilExit } = render6(
3463
+ /* @__PURE__ */ jsx9(ListView, { type, deleteIndex })
3096
3464
  );
3097
3465
  await waitUntilExit();
3098
3466
  });
3099
3467
  }
3100
3468
 
3101
3469
  // src/commands/Migrate.tsx
3102
- import { Box as Box7, Text as Text9, useApp as useApp5 } from "ink";
3103
- import { render as render6 } from "ink";
3104
- import { useEffect as useEffect5, useState as useState6 } from "react";
3470
+ import { Box as Box8, Text as Text10, useApp as useApp6 } from "ink";
3471
+ import { render as render7 } from "ink";
3472
+ import { useEffect as useEffect6, useState as useState7 } from "react";
3105
3473
 
3106
3474
  // src/core/migrate.ts
3107
3475
  import { copyFile as copyFile2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
3476
+ import { filter as filter3, map as map6 } from "@winglet/common-utils";
3108
3477
  import YAML4 from "yaml";
3109
3478
  init_assets();
3110
3479
  function extractSchemaPaths(schema, prefix = []) {
@@ -3154,22 +3523,23 @@ function diffConfigFields(userData) {
3154
3523
  const templateData = YAML4.parse(readAsset("config.default.yml"));
3155
3524
  const templatePaths = extractDataPaths(templateData);
3156
3525
  const userPaths = extractDataPaths(userData);
3157
- const schemaKeys = new Set(schemaPaths.map(pathKey));
3158
- const userKeys = new Set(userPaths.map(pathKey));
3526
+ const schemaKeys = new Set(map6(schemaPaths, pathKey));
3527
+ const userKeys = new Set(map6(userPaths, pathKey));
3159
3528
  const isEditorDirective = (p) => p.length === 1 && p[0] === "yaml-language-server";
3160
3529
  return {
3161
3530
  // Fields present in template with defaults AND valid in schema, but missing from user
3162
- added: templatePaths.filter((p) => {
3531
+ added: filter3(templatePaths, (p) => {
3163
3532
  if (isEditorDirective(p)) return false;
3164
3533
  const key = pathKey(p);
3165
3534
  return schemaKeys.has(key) && !userKeys.has(key);
3166
3535
  }),
3167
3536
  // Fields in user but not in schema (truly deprecated)
3168
- removed: userPaths.filter(
3537
+ removed: filter3(
3538
+ userPaths,
3169
3539
  (p) => !isEditorDirective(p) && !schemaKeys.has(pathKey(p))
3170
3540
  ),
3171
3541
  // Fields in user AND in schema (preserve user values)
3172
- existing: userPaths.filter((p) => schemaKeys.has(pathKey(p)))
3542
+ existing: filter3(userPaths, (p) => schemaKeys.has(pathKey(p)))
3173
3543
  };
3174
3544
  }
3175
3545
  function buildMigratedDocument(templateText, userData, diff) {
@@ -3216,15 +3586,15 @@ Run "syncpoint init" first.`
3216
3586
  return {
3217
3587
  added: [],
3218
3588
  deprecated: [],
3219
- preserved: diff.existing.map(pathKey),
3589
+ preserved: map6(diff.existing, pathKey),
3220
3590
  backupPath: "",
3221
3591
  migrated: false
3222
3592
  };
3223
3593
  }
3224
3594
  const result = {
3225
- added: diff.added.map(pathKey),
3226
- deprecated: diff.removed.map(pathKey),
3227
- preserved: diff.existing.map(pathKey),
3595
+ added: map6(diff.added, pathKey),
3596
+ deprecated: map6(diff.removed, pathKey),
3597
+ preserved: map6(diff.existing, pathKey),
3228
3598
  backupPath: "",
3229
3599
  migrated: false
3230
3600
  };
@@ -3249,13 +3619,13 @@ ${(validation.errors ?? []).join("\n")}`
3249
3619
  }
3250
3620
 
3251
3621
  // src/commands/Migrate.tsx
3252
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3622
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3253
3623
  var MigrateView = ({ dryRun }) => {
3254
- const { exit } = useApp5();
3255
- const [result, setResult] = useState6(null);
3256
- const [error, setError] = useState6(null);
3257
- const [loading, setLoading] = useState6(true);
3258
- useEffect5(() => {
3624
+ const { exit } = useApp6();
3625
+ const [result, setResult] = useState7(null);
3626
+ const [error, setError] = useState7(null);
3627
+ const [loading, setLoading] = useState7(true);
3628
+ useEffect6(() => {
3259
3629
  (async () => {
3260
3630
  try {
3261
3631
  const res = await migrateConfig({ dryRun });
@@ -3270,54 +3640,54 @@ var MigrateView = ({ dryRun }) => {
3270
3640
  })();
3271
3641
  }, []);
3272
3642
  if (error) {
3273
- return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
3643
+ return /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
3274
3644
  "\u2717 ",
3275
3645
  error
3276
3646
  ] }) });
3277
3647
  }
3278
3648
  if (loading) {
3279
- return /* @__PURE__ */ jsx9(Text9, { children: "Analyzing config..." });
3649
+ return /* @__PURE__ */ jsx10(Text10, { children: "Analyzing config..." });
3280
3650
  }
3281
3651
  if (!result) return null;
3282
3652
  if (!result.migrated && result.added.length === 0 && result.deprecated.length === 0) {
3283
- return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713 Config is already up to date." }) });
3653
+ return /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713 Config is already up to date." }) });
3284
3654
  }
3285
- return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3286
- dryRun && /* @__PURE__ */ jsx9(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", bold: true, children: "[dry-run] Preview only \u2014 no changes written." }) }),
3287
- result.added.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
3288
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "New fields (added with defaults):" }),
3289
- result.added.map((field, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
3655
+ return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
3656
+ dryRun && /* @__PURE__ */ jsx10(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "yellow", bold: true, children: "[dry-run] Preview only \u2014 no changes written." }) }),
3657
+ result.added.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
3658
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "New fields (added with defaults):" }),
3659
+ result.added.map((field, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
3290
3660
  " ",
3291
- /* @__PURE__ */ jsx9(Text9, { color: "green", children: "+" }),
3661
+ /* @__PURE__ */ jsx10(Text10, { color: "green", children: "+" }),
3292
3662
  " ",
3293
3663
  field
3294
3664
  ] }, i))
3295
3665
  ] }),
3296
- result.deprecated.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: result.added.length > 0 ? 1 : 0, children: [
3297
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "Deprecated fields (commented out):" }),
3298
- result.deprecated.map((field, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
3666
+ result.deprecated.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: result.added.length > 0 ? 1 : 0, children: [
3667
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "Deprecated fields (commented out):" }),
3668
+ result.deprecated.map((field, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
3299
3669
  " ",
3300
- /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "~" }),
3670
+ /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "~" }),
3301
3671
  " ",
3302
3672
  field
3303
3673
  ] }, i))
3304
3674
  ] }),
3305
- result.preserved.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: 1, children: [
3306
- /* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
3675
+ result.preserved.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
3676
+ /* @__PURE__ */ jsxs10(Text10, { bold: true, children: [
3307
3677
  "Preserved fields (",
3308
3678
  result.preserved.length,
3309
3679
  "):"
3310
3680
  ] }),
3311
- result.preserved.map((field, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
3681
+ result.preserved.map((field, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
3312
3682
  " ",
3313
- /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u2022" }),
3683
+ /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "\u2022" }),
3314
3684
  " ",
3315
3685
  field
3316
3686
  ] }, i))
3317
3687
  ] }),
3318
- result.migrated && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: 1, children: [
3319
- /* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713 Migration complete." }),
3320
- result.backupPath && /* @__PURE__ */ jsxs9(Text9, { children: [
3688
+ result.migrated && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
3689
+ /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713 Migration complete." }),
3690
+ result.backupPath && /* @__PURE__ */ jsxs10(Text10, { children: [
3321
3691
  " ",
3322
3692
  "Backup saved to: ",
3323
3693
  result.backupPath
@@ -3339,50 +3709,50 @@ function registerMigrateCommand(program2) {
3339
3709
  }
3340
3710
  return;
3341
3711
  }
3342
- const { waitUntilExit } = render6(
3343
- /* @__PURE__ */ jsx9(MigrateView, { dryRun: opts.dryRun ?? false })
3712
+ const { waitUntilExit } = render7(
3713
+ /* @__PURE__ */ jsx10(MigrateView, { dryRun: opts.dryRun ?? false })
3344
3714
  );
3345
3715
  await waitUntilExit();
3346
3716
  });
3347
3717
  }
3348
3718
 
3349
3719
  // src/commands/Provision.tsx
3350
- import { Box as Box9, Text as Text11, useApp as useApp6 } from "ink";
3351
- import { render as render7 } from "ink";
3352
- import { useEffect as useEffect6, useState as useState7 } from "react";
3720
+ import { Box as Box10, Text as Text12, useApp as useApp7 } from "ink";
3721
+ import { render as render8 } from "ink";
3722
+ import { useEffect as useEffect7, useState as useState8 } from "react";
3353
3723
 
3354
3724
  // src/components/StepRunner.tsx
3355
- import { Box as Box8, Static as Static2, Text as Text10 } from "ink";
3725
+ import { Box as Box9, Static as Static2, Text as Text11 } from "ink";
3356
3726
  import Spinner2 from "ink-spinner";
3357
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3727
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3358
3728
  var StepIcon = ({ status }) => {
3359
3729
  switch (status) {
3360
3730
  case "success":
3361
- return /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713" });
3731
+ return /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" });
3362
3732
  case "running":
3363
- return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: /* @__PURE__ */ jsx10(Spinner2, { type: "dots" }) });
3733
+ return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }) });
3364
3734
  case "skipped":
3365
- return /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "\u23ED" });
3735
+ return /* @__PURE__ */ jsx11(Text11, { color: "blue", children: "\u23ED" });
3366
3736
  case "failed":
3367
- return /* @__PURE__ */ jsx10(Text10, { color: "red", children: "\u2717" });
3737
+ return /* @__PURE__ */ jsx11(Text11, { color: "red", children: "\u2717" });
3368
3738
  case "pending":
3369
3739
  default:
3370
- return /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "\u25CB" });
3740
+ return /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "\u25CB" });
3371
3741
  }
3372
3742
  };
3373
3743
  var StepStatusText = ({ step }) => {
3374
3744
  switch (step.status) {
3375
3745
  case "success":
3376
- return /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
3746
+ return /* @__PURE__ */ jsxs11(Text11, { color: "green", children: [
3377
3747
  "Done",
3378
3748
  step.duration != null ? ` (${Math.round(step.duration / 1e3)}s)` : ""
3379
3749
  ] });
3380
3750
  case "running":
3381
- return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "Running..." });
3751
+ return /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "Running..." });
3382
3752
  case "skipped":
3383
- return /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "Skipped (already installed)" });
3753
+ return /* @__PURE__ */ jsx11(Text11, { color: "blue", children: "Skipped (already installed)" });
3384
3754
  case "failed":
3385
- return /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
3755
+ return /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
3386
3756
  "Failed",
3387
3757
  step.error ? `: ${step.error}` : ""
3388
3758
  ] });
@@ -3396,13 +3766,13 @@ var StepItemView = ({
3396
3766
  index,
3397
3767
  total,
3398
3768
  isLast
3399
- }) => /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginBottom: isLast ? 0 : 1, children: [
3400
- /* @__PURE__ */ jsxs10(Text10, { children: [
3769
+ }) => /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: isLast ? 0 : 1, children: [
3770
+ /* @__PURE__ */ jsxs11(Text11, { children: [
3401
3771
  " ",
3402
- /* @__PURE__ */ jsx10(StepIcon, { status: step.status }),
3403
- /* @__PURE__ */ jsxs10(Text10, { children: [
3772
+ /* @__PURE__ */ jsx11(StepIcon, { status: step.status }),
3773
+ /* @__PURE__ */ jsxs11(Text11, { children: [
3404
3774
  " ",
3405
- /* @__PURE__ */ jsxs10(Text10, { bold: true, children: [
3775
+ /* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
3406
3776
  "Step ",
3407
3777
  index + 1,
3408
3778
  "/",
@@ -3412,13 +3782,13 @@ var StepItemView = ({
3412
3782
  step.name
3413
3783
  ] })
3414
3784
  ] }),
3415
- step.output && step.status !== "pending" && /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
3785
+ step.output && step.status !== "pending" && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
3416
3786
  " ",
3417
3787
  step.output
3418
3788
  ] }),
3419
- /* @__PURE__ */ jsxs10(Text10, { children: [
3789
+ /* @__PURE__ */ jsxs11(Text11, { children: [
3420
3790
  " ",
3421
- /* @__PURE__ */ jsx10(StepStatusText, { step })
3791
+ /* @__PURE__ */ jsx11(StepStatusText, { step })
3422
3792
  ] })
3423
3793
  ] });
3424
3794
  var StepRunner = ({ steps, total }) => {
@@ -3433,8 +3803,8 @@ var StepRunner = ({ steps, total }) => {
3433
3803
  }
3434
3804
  });
3435
3805
  const lastIdx = steps.length - 1;
3436
- return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
3437
- /* @__PURE__ */ jsx10(Static2, { items: completedSteps, children: (item) => /* @__PURE__ */ jsx10(
3806
+ return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3807
+ /* @__PURE__ */ jsx11(Static2, { items: completedSteps, children: (item) => /* @__PURE__ */ jsx11(
3438
3808
  StepItemView,
3439
3809
  {
3440
3810
  step: item,
@@ -3444,7 +3814,7 @@ var StepRunner = ({ steps, total }) => {
3444
3814
  },
3445
3815
  item.idx
3446
3816
  ) }),
3447
- activeSteps.map((item) => /* @__PURE__ */ jsx10(
3817
+ activeSteps.map((item) => /* @__PURE__ */ jsx11(
3448
3818
  StepItemView,
3449
3819
  {
3450
3820
  step: item,
@@ -3490,26 +3860,26 @@ ${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
3490
3860
  }
3491
3861
 
3492
3862
  // src/commands/Provision.tsx
3493
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3863
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3494
3864
  var ProvisionView = ({
3495
3865
  template,
3496
3866
  templatePath,
3497
3867
  options
3498
3868
  }) => {
3499
- const { exit } = useApp6();
3500
- const [phase, setPhase] = useState7(
3869
+ const { exit } = useApp7();
3870
+ const [phase, setPhase] = useState8(
3501
3871
  options.dryRun ? "done" : "running"
3502
3872
  );
3503
- const [steps, setSteps] = useState7(
3873
+ const [steps, setSteps] = useState8(
3504
3874
  template.steps.map((s) => ({
3505
3875
  name: s.name,
3506
3876
  status: "pending",
3507
3877
  output: s.description
3508
3878
  }))
3509
3879
  );
3510
- const [currentStep, setCurrentStep] = useState7(0);
3511
- const [error, setError] = useState7(null);
3512
- useEffect6(() => {
3880
+ const [currentStep, setCurrentStep] = useState8(0);
3881
+ const [error, setError] = useState8(null);
3882
+ useEffect7(() => {
3513
3883
  if (options.dryRun) {
3514
3884
  setTimeout(() => exit(), 100);
3515
3885
  return;
@@ -3551,7 +3921,7 @@ var ProvisionView = ({
3551
3921
  })();
3552
3922
  }, []);
3553
3923
  if (phase === "error" || error) {
3554
- return /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
3924
+ return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
3555
3925
  "\u2717 ",
3556
3926
  error
3557
3927
  ] }) });
@@ -3559,27 +3929,27 @@ var ProvisionView = ({
3559
3929
  const successCount = steps.filter((s) => s.status === "success").length;
3560
3930
  const skippedCount = steps.filter((s) => s.status === "skipped").length;
3561
3931
  const failedCount = steps.filter((s) => s.status === "failed").length;
3562
- return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3563
- /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: 1, children: [
3564
- /* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
3932
+ return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3933
+ /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
3934
+ /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
3565
3935
  "\u25B8 ",
3566
3936
  template.name
3567
3937
  ] }),
3568
- template.description && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
3938
+ template.description && /* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
3569
3939
  " ",
3570
3940
  template.description
3571
3941
  ] })
3572
3942
  ] }),
3573
- options.dryRun && phase === "done" && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3574
- /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "(dry-run) Showing execution plan only" }),
3575
- template.sudo && /* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
3943
+ options.dryRun && phase === "done" && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3944
+ /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "(dry-run) Showing execution plan only" }),
3945
+ template.sudo && /* @__PURE__ */ jsxs12(Text12, { color: "yellow", children: [
3576
3946
  " ",
3577
3947
  "\u26A0 This template requires sudo privileges (will prompt on actual run)"
3578
3948
  ] }),
3579
- /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", marginTop: 1, children: template.steps.map((step, idx) => /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: 1, children: [
3580
- /* @__PURE__ */ jsxs11(Text11, { children: [
3949
+ /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", marginTop: 1, children: template.steps.map((step, idx) => /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
3950
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3581
3951
  " ",
3582
- /* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
3952
+ /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
3583
3953
  "Step ",
3584
3954
  idx + 1,
3585
3955
  "/",
@@ -3588,18 +3958,18 @@ var ProvisionView = ({
3588
3958
  " ",
3589
3959
  step.name
3590
3960
  ] }),
3591
- step.description && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
3961
+ step.description && /* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
3592
3962
  " ",
3593
3963
  step.description
3594
3964
  ] }),
3595
- step.skip_if && /* @__PURE__ */ jsxs11(Text11, { color: "blue", children: [
3965
+ step.skip_if && /* @__PURE__ */ jsxs12(Text12, { color: "blue", children: [
3596
3966
  " ",
3597
3967
  "Skip condition: ",
3598
3968
  step.skip_if
3599
3969
  ] })
3600
3970
  ] }, idx)) })
3601
3971
  ] }),
3602
- (phase === "running" || phase === "done" && !options.dryRun) && /* @__PURE__ */ jsx11(
3972
+ (phase === "running" || phase === "done" && !options.dryRun) && /* @__PURE__ */ jsx12(
3603
3973
  StepRunner,
3604
3974
  {
3605
3975
  steps,
@@ -3607,34 +3977,34 @@ var ProvisionView = ({
3607
3977
  total: template.steps.length
3608
3978
  }
3609
3979
  ),
3610
- phase === "done" && !options.dryRun && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: 1, children: [
3611
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
3980
+ phase === "done" && !options.dryRun && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
3981
+ /* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
3612
3982
  " ",
3613
3983
  "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
3614
3984
  ] }),
3615
- /* @__PURE__ */ jsxs11(Text11, { children: [
3985
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3616
3986
  " ",
3617
3987
  "Result: ",
3618
- /* @__PURE__ */ jsxs11(Text11, { color: "green", children: [
3988
+ /* @__PURE__ */ jsxs12(Text12, { color: "green", children: [
3619
3989
  successCount,
3620
3990
  " succeeded"
3621
3991
  ] }),
3622
3992
  " \xB7",
3623
3993
  " ",
3624
- /* @__PURE__ */ jsxs11(Text11, { color: "blue", children: [
3994
+ /* @__PURE__ */ jsxs12(Text12, { color: "blue", children: [
3625
3995
  skippedCount,
3626
3996
  " skipped"
3627
3997
  ] }),
3628
3998
  " \xB7",
3629
3999
  " ",
3630
- /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
4000
+ /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
3631
4001
  failedCount,
3632
4002
  " failed"
3633
4003
  ] })
3634
4004
  ] }),
3635
- template.backup && !options.skipRestore && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: 1, children: [
3636
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "\u25B8 Proceeding with config file restore..." }),
3637
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
4005
+ template.backup && !options.skipRestore && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
4006
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Proceeding with config file restore..." }),
4007
+ /* @__PURE__ */ jsxs12(Text12, { color: "gray", children: [
3638
4008
  " ",
3639
4009
  "Backup link: ",
3640
4010
  template.backup
@@ -3727,8 +4097,8 @@ function registerProvisionCommand(program2) {
3727
4097
  }
3728
4098
  return;
3729
4099
  }
3730
- const { waitUntilExit } = render7(
3731
- /* @__PURE__ */ jsx11(
4100
+ const { waitUntilExit } = render8(
4101
+ /* @__PURE__ */ jsx12(
3732
4102
  ProvisionView,
3733
4103
  {
3734
4104
  template: tmpl,
@@ -3746,21 +4116,21 @@ function registerProvisionCommand(program2) {
3746
4116
  }
3747
4117
 
3748
4118
  // src/commands/Restore.tsx
3749
- import { Box as Box10, Text as Text12, useApp as useApp7 } from "ink";
3750
- import { render as render8 } from "ink";
4119
+ import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
4120
+ import { render as render9 } from "ink";
3751
4121
  import SelectInput2 from "ink-select-input";
3752
- import { useEffect as useEffect7, useState as useState8 } from "react";
3753
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
4122
+ import { useEffect as useEffect8, useState as useState9 } from "react";
4123
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
3754
4124
  var RestoreView = ({ filename, options }) => {
3755
- const { exit } = useApp7();
3756
- const [phase, setPhase] = useState8("loading");
3757
- const [backups, setBackups] = useState8([]);
3758
- const [selectedPath, setSelectedPath] = useState8(null);
3759
- const [plan, setPlan] = useState8(null);
3760
- const [result, setResult] = useState8(null);
3761
- const [safetyDone, setSafetyDone] = useState8(false);
3762
- const [error, setError] = useState8(null);
3763
- useEffect7(() => {
4125
+ const { exit } = useApp8();
4126
+ const [phase, setPhase] = useState9("loading");
4127
+ const [backups, setBackups] = useState9([]);
4128
+ const [selectedPath, setSelectedPath] = useState9(null);
4129
+ const [plan, setPlan] = useState9(null);
4130
+ const [result, setResult] = useState9(null);
4131
+ const [safetyDone, setSafetyDone] = useState9(false);
4132
+ const [error, setError] = useState9(null);
4133
+ useEffect8(() => {
3764
4134
  (async () => {
3765
4135
  try {
3766
4136
  const config = await loadConfig();
@@ -3794,7 +4164,7 @@ var RestoreView = ({ filename, options }) => {
3794
4164
  }
3795
4165
  })();
3796
4166
  }, []);
3797
- useEffect7(() => {
4167
+ useEffect8(() => {
3798
4168
  if (phase !== "planning" || !selectedPath) return;
3799
4169
  (async () => {
3800
4170
  try {
@@ -3842,7 +4212,7 @@ var RestoreView = ({ filename, options }) => {
3842
4212
  }
3843
4213
  };
3844
4214
  if (phase === "error" || error) {
3845
- return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
4215
+ return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
3846
4216
  "\u2717 ",
3847
4217
  error
3848
4218
  ] }) });
@@ -3853,30 +4223,30 @@ var RestoreView = ({ filename, options }) => {
3853
4223
  }));
3854
4224
  const currentHostname = getHostname();
3855
4225
  const isRemoteBackup = plan?.metadata.hostname && plan.metadata.hostname !== currentHostname;
3856
- return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3857
- phase === "loading" && /* @__PURE__ */ jsx12(Text12, { children: "\u25B8 Loading backup list..." }),
3858
- phase === "selecting" && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3859
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Select backup" }),
3860
- /* @__PURE__ */ jsx12(SelectInput2, { items: selectItems, onSelect: handleSelect })
4226
+ return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4227
+ phase === "loading" && /* @__PURE__ */ jsx13(Text13, { children: "\u25B8 Loading backup list..." }),
4228
+ phase === "selecting" && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4229
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Select backup" }),
4230
+ /* @__PURE__ */ jsx13(SelectInput2, { items: selectItems, onSelect: handleSelect })
3861
4231
  ] }),
3862
- (phase === "planning" || phase === "confirming" || phase === "restoring" || phase === "done" && plan) && plan && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3863
- /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
3864
- /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
4232
+ (phase === "planning" || phase === "confirming" || phase === "restoring" || phase === "done" && plan) && plan && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4233
+ /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginBottom: 1, children: [
4234
+ /* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
3865
4235
  "\u25B8 Metadata (",
3866
4236
  plan.metadata.config.filename ?? "",
3867
4237
  ")"
3868
4238
  ] }),
3869
- /* @__PURE__ */ jsxs12(Text12, { children: [
4239
+ /* @__PURE__ */ jsxs13(Text13, { children: [
3870
4240
  " ",
3871
4241
  "Host: ",
3872
4242
  plan.metadata.hostname
3873
4243
  ] }),
3874
- /* @__PURE__ */ jsxs12(Text12, { children: [
4244
+ /* @__PURE__ */ jsxs13(Text13, { children: [
3875
4245
  " ",
3876
4246
  "Created: ",
3877
4247
  plan.metadata.createdAt
3878
4248
  ] }),
3879
- /* @__PURE__ */ jsxs12(Text12, { children: [
4249
+ /* @__PURE__ */ jsxs13(Text13, { children: [
3880
4250
  " ",
3881
4251
  "Files: ",
3882
4252
  plan.metadata.summary.fileCount,
@@ -3884,15 +4254,15 @@ var RestoreView = ({ filename, options }) => {
3884
4254
  formatBytes(plan.metadata.summary.totalSize),
3885
4255
  ")"
3886
4256
  ] }),
3887
- isRemoteBackup && /* @__PURE__ */ jsxs12(Text12, { color: "yellow", children: [
4257
+ isRemoteBackup && /* @__PURE__ */ jsxs13(Text13, { color: "yellow", children: [
3888
4258
  " ",
3889
4259
  "\u26A0 This backup was created on a different machine (",
3890
4260
  plan.metadata.hostname,
3891
4261
  ")"
3892
4262
  ] })
3893
4263
  ] }),
3894
- /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
3895
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Restore plan:" }),
4264
+ /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginBottom: 1, children: [
4265
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Restore plan:" }),
3896
4266
  plan.actions.map((action, idx) => {
3897
4267
  let icon;
3898
4268
  let color;
@@ -3914,45 +4284,45 @@ var RestoreView = ({ filename, options }) => {
3914
4284
  label = "(not present)";
3915
4285
  break;
3916
4286
  }
3917
- return /* @__PURE__ */ jsxs12(Text12, { children: [
4287
+ return /* @__PURE__ */ jsxs13(Text13, { children: [
3918
4288
  " ",
3919
- /* @__PURE__ */ jsx12(Text12, { color, children: icon.padEnd(8) }),
4289
+ /* @__PURE__ */ jsx13(Text13, { color, children: icon.padEnd(8) }),
3920
4290
  " ",
3921
4291
  contractTilde(action.path),
3922
4292
  " ",
3923
- /* @__PURE__ */ jsx12(Text12, { color: "gray", children: label })
4293
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: label })
3924
4294
  ] }, idx);
3925
4295
  })
3926
4296
  ] }),
3927
- options.dryRun && phase === "done" && /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "(dry-run) No actual restore was performed" })
4297
+ options.dryRun && phase === "done" && /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "(dry-run) No actual restore was performed" })
3928
4298
  ] }),
3929
- phase === "confirming" && /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsx12(Confirm, { message: "Proceed with restore?", onConfirm: handleConfirm }) }),
3930
- phase === "restoring" && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3931
- safetyDone && /* @__PURE__ */ jsxs12(Text12, { children: [
3932
- /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
4299
+ phase === "confirming" && /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsx13(Confirm, { message: "Proceed with restore?", onConfirm: handleConfirm }) }),
4300
+ phase === "restoring" && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4301
+ safetyDone && /* @__PURE__ */ jsxs13(Text13, { children: [
4302
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
3933
4303
  " Safety backup of current files complete"
3934
4304
  ] }),
3935
- /* @__PURE__ */ jsx12(Text12, { children: "\u25B8 Restoring..." })
4305
+ /* @__PURE__ */ jsx13(Text13, { children: "\u25B8 Restoring..." })
3936
4306
  ] }),
3937
- phase === "done" && result && !options.dryRun && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
3938
- safetyDone && /* @__PURE__ */ jsxs12(Text12, { children: [
3939
- /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
4307
+ phase === "done" && result && !options.dryRun && /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
4308
+ safetyDone && /* @__PURE__ */ jsxs13(Text13, { children: [
4309
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
3940
4310
  " Safety backup of current files complete"
3941
4311
  ] }),
3942
- /* @__PURE__ */ jsx12(Text12, { color: "green", bold: true, children: "\u2713 Restore complete" }),
3943
- /* @__PURE__ */ jsxs12(Text12, { children: [
4312
+ /* @__PURE__ */ jsx13(Text13, { color: "green", bold: true, children: "\u2713 Restore complete" }),
4313
+ /* @__PURE__ */ jsxs13(Text13, { children: [
3944
4314
  " ",
3945
4315
  "Restored: ",
3946
4316
  result.restoredFiles.length,
3947
4317
  " files"
3948
4318
  ] }),
3949
- /* @__PURE__ */ jsxs12(Text12, { children: [
4319
+ /* @__PURE__ */ jsxs13(Text13, { children: [
3950
4320
  " ",
3951
4321
  "Skipped: ",
3952
4322
  result.skippedFiles.length,
3953
4323
  " files"
3954
4324
  ] }),
3955
- result.safetyBackupPath && /* @__PURE__ */ jsxs12(Text12, { children: [
4325
+ result.safetyBackupPath && /* @__PURE__ */ jsxs13(Text13, { children: [
3956
4326
  " ",
3957
4327
  "Safety backup: ",
3958
4328
  contractTilde(result.safetyBackupPath)
@@ -4008,8 +4378,8 @@ function registerRestoreCommand(program2) {
4008
4378
  }
4009
4379
  return;
4010
4380
  }
4011
- const { waitUntilExit } = render8(
4012
- /* @__PURE__ */ jsx12(RestoreView, { filename, options: { dryRun: opts.dryRun } })
4381
+ const { waitUntilExit } = render9(
4382
+ /* @__PURE__ */ jsx13(RestoreView, { filename, options: { dryRun: opts.dryRun } })
4013
4383
  );
4014
4384
  await waitUntilExit();
4015
4385
  });
@@ -4017,12 +4387,12 @@ function registerRestoreCommand(program2) {
4017
4387
 
4018
4388
  // src/commands/Status.tsx
4019
4389
  import { readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
4020
- import { join as join12 } from "path";
4021
- import { Box as Box11, Text as Text13, useApp as useApp8, useInput as useInput3 } from "ink";
4022
- import { render as render9 } from "ink";
4390
+ import { join as join13 } from "path";
4391
+ import { Box as Box12, Text as Text14, useApp as useApp9, useInput as useInput3 } from "ink";
4392
+ import { render as render10 } from "ink";
4023
4393
  import SelectInput3 from "ink-select-input";
4024
- import { useEffect as useEffect8, useState as useState9 } from "react";
4025
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
4394
+ import { useEffect as useEffect9, useState as useState10 } from "react";
4395
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
4026
4396
  function getDirStats(dirPath) {
4027
4397
  try {
4028
4398
  const entries = readdirSync(dirPath);
@@ -4030,9 +4400,9 @@ function getDirStats(dirPath) {
4030
4400
  let count = 0;
4031
4401
  for (const entry of entries) {
4032
4402
  try {
4033
- const stat4 = statSync(join12(dirPath, entry));
4034
- if (stat4.isFile()) {
4035
- totalSize += stat4.size;
4403
+ const stat5 = statSync(join13(dirPath, entry));
4404
+ if (stat5.isFile()) {
4405
+ totalSize += stat5.size;
4036
4406
  count++;
4037
4407
  }
4038
4408
  } catch {
@@ -4044,34 +4414,34 @@ function getDirStats(dirPath) {
4044
4414
  }
4045
4415
  }
4046
4416
  var DisplayActionItem = ({ isSelected = false, label }) => {
4047
- return /* @__PURE__ */ jsx13(Text13, { bold: isSelected, children: label });
4417
+ return /* @__PURE__ */ jsx14(Text14, { bold: isSelected, children: label });
4048
4418
  };
4049
4419
  var CleanupActionItem = ({ isSelected = false, label }) => {
4050
4420
  if (label === "Cancel" || label === "Select specific backups to delete") {
4051
- return /* @__PURE__ */ jsx13(Text13, { bold: isSelected, children: label });
4421
+ return /* @__PURE__ */ jsx14(Text14, { bold: isSelected, children: label });
4052
4422
  }
4053
4423
  const parts = label.split(/\s{2,}/);
4054
4424
  if (parts.length === 2) {
4055
- return /* @__PURE__ */ jsxs13(Text13, { bold: isSelected, children: [
4425
+ return /* @__PURE__ */ jsxs14(Text14, { bold: isSelected, children: [
4056
4426
  parts[0],
4057
4427
  " ",
4058
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: parts[1] })
4428
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: parts[1] })
4059
4429
  ] });
4060
4430
  }
4061
- return /* @__PURE__ */ jsx13(Text13, { bold: isSelected, children: label });
4431
+ return /* @__PURE__ */ jsx14(Text14, { bold: isSelected, children: label });
4062
4432
  };
4063
4433
  var StatusView = ({ cleanup }) => {
4064
- const { exit } = useApp8();
4065
- const [phase, setPhase] = useState9("loading");
4066
- const [status, setStatus] = useState9(null);
4067
- const [backups, setBackups] = useState9([]);
4068
- const [cleanupAction, setCleanupAction] = useState9(null);
4069
- const [cleanupMessage, setCleanupMessage] = useState9("");
4070
- const [error, setError] = useState9(null);
4071
- const [selectedForDeletion, setSelectedForDeletion] = useState9(
4434
+ const { exit } = useApp9();
4435
+ const [phase, setPhase] = useState10("loading");
4436
+ const [status, setStatus] = useState10(null);
4437
+ const [backups, setBackups] = useState10([]);
4438
+ const [cleanupAction, setCleanupAction] = useState10(null);
4439
+ const [cleanupMessage, setCleanupMessage] = useState10("");
4440
+ const [error, setError] = useState10(null);
4441
+ const [selectedForDeletion, setSelectedForDeletion] = useState10(
4072
4442
  []
4073
4443
  );
4074
- const [backupDir, setBackupDir] = useState9(getSubDir("backups"));
4444
+ const [backupDir, setBackupDir] = useState10(getSubDir("backups"));
4075
4445
  useInput3((_input, key) => {
4076
4446
  if (!key.escape) return;
4077
4447
  if (phase === "display") {
@@ -4083,7 +4453,7 @@ var StatusView = ({ cleanup }) => {
4083
4453
  setPhase("cleanup");
4084
4454
  }
4085
4455
  });
4086
- useEffect8(() => {
4456
+ useEffect9(() => {
4087
4457
  (async () => {
4088
4458
  try {
4089
4459
  const config = await loadConfig();
@@ -4182,7 +4552,7 @@ var StatusView = ({ cleanup }) => {
4182
4552
  try {
4183
4553
  const entries = readdirSync(logsDir);
4184
4554
  for (const entry of entries) {
4185
- const logPath = join12(logsDir, entry);
4555
+ const logPath = join13(logsDir, entry);
4186
4556
  if (!isInsideDir(logPath, logsDir))
4187
4557
  throw new Error(
4188
4558
  `Refusing to delete file outside logs directory: ${logPath}`
@@ -4232,26 +4602,26 @@ var StatusView = ({ cleanup }) => {
4232
4602
  }
4233
4603
  };
4234
4604
  if (phase === "error" || error) {
4235
- return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
4605
+ return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs14(Text14, { color: "red", children: [
4236
4606
  "\u2717 ",
4237
4607
  error
4238
4608
  ] }) });
4239
4609
  }
4240
4610
  if (phase === "loading") {
4241
- return /* @__PURE__ */ jsx13(Text13, { color: "cyan", children: "Loading..." });
4611
+ return /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "Loading..." });
4242
4612
  }
4243
4613
  if (!status) return null;
4244
4614
  const totalCount = status.backups.count + status.templates.count + status.scripts.count + status.logs.count;
4245
4615
  const totalSize = status.backups.totalSize + status.templates.totalSize + status.scripts.totalSize + status.logs.totalSize;
4246
- const statusDisplay = /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4247
- /* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
4616
+ const statusDisplay = /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4617
+ /* @__PURE__ */ jsxs14(Text14, { bold: true, children: [
4248
4618
  "\u25B8 ",
4249
4619
  APP_NAME,
4250
4620
  " status \u2014 ~/.",
4251
4621
  APP_NAME,
4252
4622
  "/"
4253
4623
  ] }),
4254
- /* @__PURE__ */ jsx13(Box11, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx13(
4624
+ /* @__PURE__ */ jsx14(Box12, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx14(
4255
4625
  Table,
4256
4626
  {
4257
4627
  headers: ["Directory", "Count", "Size"],
@@ -4280,15 +4650,15 @@ var StatusView = ({ cleanup }) => {
4280
4650
  ]
4281
4651
  }
4282
4652
  ) }),
4283
- status.lastBackup && /* @__PURE__ */ jsxs13(Box11, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
4284
- /* @__PURE__ */ jsxs13(Text13, { children: [
4653
+ status.lastBackup && /* @__PURE__ */ jsxs14(Box12, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
4654
+ /* @__PURE__ */ jsxs14(Text14, { children: [
4285
4655
  "Latest backup: ",
4286
4656
  formatDate(status.lastBackup),
4287
4657
  " (",
4288
4658
  formatRelativeTime(status.lastBackup),
4289
4659
  ")"
4290
4660
  ] }),
4291
- status.oldestBackup && /* @__PURE__ */ jsxs13(Text13, { children: [
4661
+ status.oldestBackup && /* @__PURE__ */ jsxs14(Text14, { children: [
4292
4662
  "Oldest backup: ",
4293
4663
  formatDate(status.oldestBackup),
4294
4664
  " (",
@@ -4297,18 +4667,18 @@ var StatusView = ({ cleanup }) => {
4297
4667
  ] })
4298
4668
  ] })
4299
4669
  ] });
4300
- const escHint = (action) => /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
4670
+ const escHint = (action) => /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
4301
4671
  "Press ",
4302
- /* @__PURE__ */ jsx13(Text13, { bold: true, children: "ESC" }),
4672
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "ESC" }),
4303
4673
  " to ",
4304
4674
  action
4305
4675
  ] }) });
4306
4676
  if (phase === "display") {
4307
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4677
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4308
4678
  statusDisplay,
4309
- /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
4310
- /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Actions" }),
4311
- /* @__PURE__ */ jsx13(
4679
+ /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
4680
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "\u25B8 Actions" }),
4681
+ /* @__PURE__ */ jsx14(
4312
4682
  SelectInput3,
4313
4683
  {
4314
4684
  items: [
@@ -4356,11 +4726,11 @@ var StatusView = ({ cleanup }) => {
4356
4726
  value: "cancel"
4357
4727
  }
4358
4728
  ];
4359
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4729
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4360
4730
  statusDisplay,
4361
- /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
4362
- /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Cleanup options" }),
4363
- /* @__PURE__ */ jsx13(
4731
+ /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
4732
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "\u25B8 Cleanup options" }),
4733
+ /* @__PURE__ */ jsx14(
4364
4734
  SelectInput3,
4365
4735
  {
4366
4736
  items: cleanupItems,
@@ -4386,26 +4756,26 @@ var StatusView = ({ cleanup }) => {
4386
4756
  value: "done"
4387
4757
  }
4388
4758
  ];
4389
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4759
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4390
4760
  statusDisplay,
4391
- /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
4392
- /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Select backups to delete" }),
4393
- selectedForDeletion.length > 0 && /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
4761
+ /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
4762
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "\u25B8 Select backups to delete" }),
4763
+ selectedForDeletion.length > 0 && /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
4394
4764
  " ",
4395
4765
  selectedForDeletion.length,
4396
4766
  " backup(s) selected (",
4397
4767
  formatBytes(selectedForDeletion.reduce((s, b) => s + b.size, 0)),
4398
4768
  ")"
4399
4769
  ] }),
4400
- /* @__PURE__ */ jsx13(SelectInput3, { items: selectItems, onSelect: handleSelectBackup })
4770
+ /* @__PURE__ */ jsx14(SelectInput3, { items: selectItems, onSelect: handleSelectBackup })
4401
4771
  ] }),
4402
4772
  escHint("go back")
4403
4773
  ] });
4404
4774
  }
4405
4775
  if (phase === "confirming") {
4406
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
4407
- /* @__PURE__ */ jsx13(Text13, { children: cleanupMessage }),
4408
- /* @__PURE__ */ jsx13(
4776
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4777
+ /* @__PURE__ */ jsx14(Text14, { children: cleanupMessage }),
4778
+ /* @__PURE__ */ jsx14(
4409
4779
  Confirm,
4410
4780
  {
4411
4781
  message: "Proceed?",
@@ -4416,7 +4786,7 @@ var StatusView = ({ cleanup }) => {
4416
4786
  ] });
4417
4787
  }
4418
4788
  if (phase === "done") {
4419
- return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713 Cleanup complete" }) });
4789
+ return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx14(Text14, { color: "green", children: "\u2713 Cleanup complete" }) });
4420
4790
  }
4421
4791
  return null;
4422
4792
  };
@@ -4450,7 +4820,93 @@ function registerStatusCommand(program2) {
4450
4820
  }
4451
4821
  return;
4452
4822
  }
4453
- const { waitUntilExit } = render9(/* @__PURE__ */ jsx13(StatusView, { cleanup: opts.cleanup }));
4823
+ const { waitUntilExit } = render10(/* @__PURE__ */ jsx14(StatusView, { cleanup: opts.cleanup }));
4824
+ await waitUntilExit();
4825
+ });
4826
+ }
4827
+
4828
+ // src/commands/Unlink.tsx
4829
+ import { Box as Box13, Text as Text15, useApp as useApp10 } from "ink";
4830
+ import { render as render11 } from "ink";
4831
+ import { useEffect as useEffect10, useState as useState11 } from "react";
4832
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
4833
+ var UnlinkView = ({ clean }) => {
4834
+ const { exit } = useApp10();
4835
+ const [phase, setPhase] = useState11("unlinking");
4836
+ const [result, setResult] = useState11(null);
4837
+ const [error, setError] = useState11(null);
4838
+ useEffect10(() => {
4839
+ (async () => {
4840
+ try {
4841
+ const unlinkResult = await unlinkSyncpoint({ clean });
4842
+ setResult(unlinkResult);
4843
+ setPhase("done");
4844
+ setTimeout(() => exit(), 100);
4845
+ } catch (err) {
4846
+ setError(err instanceof Error ? err.message : String(err));
4847
+ setPhase("error");
4848
+ setTimeout(() => exit(), 100);
4849
+ }
4850
+ })();
4851
+ }, []);
4852
+ if (phase === "error" || error) {
4853
+ return /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
4854
+ "\u2717 Unlink failed: ",
4855
+ error
4856
+ ] }) });
4857
+ }
4858
+ if (phase === "unlinking") {
4859
+ return /* @__PURE__ */ jsx15(Box13, { children: /* @__PURE__ */ jsx15(Text15, { children: "\u25B8 Restoring ~/.syncpoint from destination..." }) });
4860
+ }
4861
+ return /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
4862
+ /* @__PURE__ */ jsx15(Text15, { color: "green", bold: true, children: "\u2713 Unlink complete" }),
4863
+ result && /* @__PURE__ */ jsxs15(Fragment3, { children: [
4864
+ /* @__PURE__ */ jsxs15(Text15, { children: [
4865
+ " ",
4866
+ "Restored: ",
4867
+ contractTilde(result.appDir)
4868
+ ] }),
4869
+ /* @__PURE__ */ jsxs15(Text15, { children: [
4870
+ " ",
4871
+ "Source: ",
4872
+ contractTilde(result.targetDir)
4873
+ ] }),
4874
+ result.cleaned && /* @__PURE__ */ jsxs15(Text15, { color: "yellow", children: [
4875
+ " ",
4876
+ "Destination copy removed (--clean)"
4877
+ ] })
4878
+ ] })
4879
+ ] });
4880
+ };
4881
+ function registerUnlinkCommand(program2) {
4882
+ const cmdInfo = COMMANDS.unlink;
4883
+ const cmd = program2.command("unlink").description(cmdInfo.description);
4884
+ cmdInfo.options?.forEach((opt) => {
4885
+ cmd.option(opt.flag, opt.description);
4886
+ });
4887
+ cmd.action(async (opts) => {
4888
+ const globalOpts = program2.opts();
4889
+ const startTime = Date.now();
4890
+ if (globalOpts.json) {
4891
+ try {
4892
+ const unlinkResult = await unlinkSyncpoint({ clean: opts.clean });
4893
+ respond(
4894
+ "unlink",
4895
+ {
4896
+ appDir: unlinkResult.appDir,
4897
+ targetDir: unlinkResult.targetDir,
4898
+ cleaned: unlinkResult.cleaned
4899
+ },
4900
+ startTime,
4901
+ VERSION
4902
+ );
4903
+ } catch (error) {
4904
+ const code = classifyError(error);
4905
+ respondError("unlink", code, error.message, startTime, VERSION);
4906
+ }
4907
+ return;
4908
+ }
4909
+ const { waitUntilExit } = render11(/* @__PURE__ */ jsx15(UnlinkView, { clean: opts.clean ?? false }));
4454
4910
  await waitUntilExit();
4455
4911
  });
4456
4912
  }
@@ -4459,15 +4915,15 @@ function registerStatusCommand(program2) {
4459
4915
  import {
4460
4916
  copyFile as copyFile3,
4461
4917
  readFile as readFile6,
4462
- rename,
4463
- unlink,
4918
+ rename as rename2,
4919
+ unlink as unlink2,
4464
4920
  writeFile as writeFile5
4465
4921
  } from "fs/promises";
4466
- import { join as join14 } from "path";
4467
- import { Box as Box12, Text as Text14, useApp as useApp9 } from "ink";
4468
- import { render as render10 } from "ink";
4922
+ import { join as join15 } from "path";
4923
+ import { Box as Box14, Text as Text16, useApp as useApp11 } from "ink";
4924
+ import { render as render12 } from "ink";
4469
4925
  import Spinner3 from "ink-spinner";
4470
- import { useEffect as useEffect9, useState as useState10 } from "react";
4926
+ import { useEffect as useEffect11, useState as useState12 } from "react";
4471
4927
 
4472
4928
  // src/prompts/wizard-config.ts
4473
4929
  function generateConfigWizardPrompt(variables) {
@@ -4511,8 +4967,8 @@ ${variables.defaultConfig}
4511
4967
  init_assets();
4512
4968
 
4513
4969
  // src/utils/file-scanner.ts
4514
- import { stat as stat3 } from "fs/promises";
4515
- import { join as join13 } from "path";
4970
+ import { stat as stat4 } from "fs/promises";
4971
+ import { join as join14 } from "path";
4516
4972
  import glob from "fast-glob";
4517
4973
  var FILE_CATEGORIES = {
4518
4974
  shell: {
@@ -4591,8 +5047,8 @@ async function scanHomeDirectory(options) {
4591
5047
  const validFiles = [];
4592
5048
  for (const file of files) {
4593
5049
  try {
4594
- const fullPath = join13(homeDir, file);
4595
- await stat3(fullPath);
5050
+ const fullPath = join14(homeDir, file);
5051
+ await stat4(fullPath);
4596
5052
  validFiles.push(file);
4597
5053
  categorizedFiles.add(file);
4598
5054
  } catch {
@@ -4626,8 +5082,8 @@ async function scanHomeDirectory(options) {
4626
5082
  if (categorizedFiles.has(file)) continue;
4627
5083
  if (totalFiles >= maxFiles) break;
4628
5084
  try {
4629
- const fullPath = join13(homeDir, file);
4630
- await stat3(fullPath);
5085
+ const fullPath = join14(homeDir, file);
5086
+ await stat4(fullPath);
4631
5087
  uncategorizedFiles.push(file);
4632
5088
  totalFiles++;
4633
5089
  } catch {
@@ -4651,7 +5107,7 @@ async function scanHomeDirectory(options) {
4651
5107
  }
4652
5108
 
4653
5109
  // src/commands/Wizard.tsx
4654
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
5110
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
4655
5111
  var MAX_RETRIES2 = 3;
4656
5112
  async function restoreBackup2(configPath) {
4657
5113
  const bakPath = `${configPath}.bak`;
@@ -4701,21 +5157,21 @@ ${formatValidationErrors(validation.errors || [])}`
4701
5157
  }
4702
5158
  }
4703
5159
  var WizardView = ({ printMode }) => {
4704
- const { exit } = useApp9();
4705
- const [phase, setPhase] = useState10("init");
4706
- const [message, setMessage] = useState10("");
4707
- const [error, setError] = useState10(null);
4708
- const [prompt, setPrompt] = useState10("");
4709
- const [sessionId, setSessionId] = useState10(void 0);
4710
- const [attemptNumber, setAttemptNumber] = useState10(1);
4711
- useEffect9(() => {
5160
+ const { exit } = useApp11();
5161
+ const [phase, setPhase] = useState12("init");
5162
+ const [message, setMessage] = useState12("");
5163
+ const [error, setError] = useState12(null);
5164
+ const [prompt, setPrompt] = useState12("");
5165
+ const [sessionId, setSessionId] = useState12(void 0);
5166
+ const [attemptNumber, setAttemptNumber] = useState12(1);
5167
+ useEffect11(() => {
4712
5168
  (async () => {
4713
5169
  try {
4714
- const configPath = join14(getAppDir(), CONFIG_FILENAME);
5170
+ const configPath = join15(getAppDir(), CONFIG_FILENAME);
4715
5171
  if (await fileExists(configPath)) {
4716
5172
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4717
5173
  const bakPath = `${configPath}.${timestamp2}.bak`;
4718
- await rename(configPath, bakPath);
5174
+ await rename2(configPath, bakPath);
4719
5175
  setMessage(`Backed up existing config to ${bakPath}`);
4720
5176
  }
4721
5177
  setPhase("scanning");
@@ -4781,9 +5237,9 @@ var WizardView = ({ printMode }) => {
4781
5237
  await writeFile5(tmpPath, yamlContent, "utf-8");
4782
5238
  const verification = validateConfig(parseYAML(yamlContent));
4783
5239
  if (verification.valid) {
4784
- await rename(tmpPath, configPath);
5240
+ await rename2(tmpPath, configPath);
4785
5241
  } else {
4786
- await unlink(tmpPath);
5242
+ await unlink2(tmpPath);
4787
5243
  throw new Error("Final validation failed");
4788
5244
  }
4789
5245
  setPhase("done");
@@ -4822,37 +5278,37 @@ ${formatValidationErrors(validation.errors || [])}`
4822
5278
  }
4823
5279
  }
4824
5280
  if (error) {
4825
- return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs14(Text14, { color: "red", children: [
5281
+ return /* @__PURE__ */ jsx16(Box14, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
4826
5282
  "\u2717 ",
4827
5283
  error
4828
5284
  ] }) });
4829
5285
  }
4830
5286
  if (printMode && phase === "done") {
4831
- return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4832
- /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Config Wizard Prompt (Copy and paste to your LLM):" }),
4833
- /* @__PURE__ */ jsx14(Box12, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "\u2500".repeat(60) }) }),
4834
- /* @__PURE__ */ jsx14(Text14, { children: prompt }),
4835
- /* @__PURE__ */ jsx14(Box12, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "\u2500".repeat(60) }) }),
4836
- /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "After getting the YAML response, save it to ~/.syncpoint/config.yml" })
5287
+ return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
5288
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Config Wizard Prompt (Copy and paste to your LLM):" }),
5289
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2500".repeat(60) }) }),
5290
+ /* @__PURE__ */ jsx16(Text16, { children: prompt }),
5291
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2500".repeat(60) }) }),
5292
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "After getting the YAML response, save it to ~/.syncpoint/config.yml" })
4837
5293
  ] });
4838
5294
  }
4839
5295
  if (phase === "done") {
4840
- return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4841
- /* @__PURE__ */ jsx14(Text14, { color: "green", children: message }),
4842
- /* @__PURE__ */ jsxs14(Box12, { marginTop: 1, children: [
4843
- /* @__PURE__ */ jsx14(Text14, { children: "Next steps:" }),
4844
- /* @__PURE__ */ jsx14(Text14, { children: " 1. Review your config: ~/.syncpoint/config.yml" }),
4845
- /* @__PURE__ */ jsx14(Text14, { children: " 2. Run: syncpoint backup" })
5296
+ return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
5297
+ /* @__PURE__ */ jsx16(Text16, { color: "green", children: message }),
5298
+ /* @__PURE__ */ jsxs16(Box14, { marginTop: 1, children: [
5299
+ /* @__PURE__ */ jsx16(Text16, { children: "Next steps:" }),
5300
+ /* @__PURE__ */ jsx16(Text16, { children: " 1. Review your config: ~/.syncpoint/config.yml" }),
5301
+ /* @__PURE__ */ jsx16(Text16, { children: " 2. Run: syncpoint backup" })
4846
5302
  ] })
4847
5303
  ] });
4848
5304
  }
4849
- return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4850
- /* @__PURE__ */ jsxs14(Text14, { children: [
4851
- /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: /* @__PURE__ */ jsx14(Spinner3, { type: "dots" }) }),
5305
+ return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
5306
+ /* @__PURE__ */ jsxs16(Text16, { children: [
5307
+ /* @__PURE__ */ jsx16(Text16, { color: "cyan", children: /* @__PURE__ */ jsx16(Spinner3, { type: "dots" }) }),
4852
5308
  " ",
4853
5309
  message
4854
5310
  ] }),
4855
- attemptNumber > 1 && /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
5311
+ attemptNumber > 1 && /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
4856
5312
  "Attempt ",
4857
5313
  attemptNumber,
4858
5314
  "/",
@@ -4889,17 +5345,17 @@ function registerWizardCommand(program2) {
4889
5345
  return;
4890
5346
  }
4891
5347
  if (opts.print) {
4892
- const { waitUntilExit } = render10(/* @__PURE__ */ jsx14(WizardView, { printMode: true }));
5348
+ const { waitUntilExit } = render12(/* @__PURE__ */ jsx16(WizardView, { printMode: true }));
4893
5349
  await waitUntilExit();
4894
5350
  return;
4895
5351
  }
4896
- const configPath = join14(getAppDir(), CONFIG_FILENAME);
5352
+ const configPath = join15(getAppDir(), CONFIG_FILENAME);
4897
5353
  try {
4898
5354
  if (await fileExists(configPath)) {
4899
5355
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4900
5356
  const bakPath = `${configPath}.${timestamp2}.bak`;
4901
5357
  console.log(`\u{1F4CB} Backing up existing config to ${bakPath}`);
4902
- await rename(configPath, bakPath);
5358
+ await rename2(configPath, bakPath);
4903
5359
  }
4904
5360
  if (!await isClaudeCodeAvailable()) {
4905
5361
  throw new Error(
@@ -4934,15 +5390,37 @@ registerCreateTemplateCommand(program);
4934
5390
  registerListCommand(program);
4935
5391
  registerMigrateCommand(program);
4936
5392
  registerStatusCommand(program);
5393
+ registerLinkCommand(program);
5394
+ registerUnlinkCommand(program);
4937
5395
  registerHelpCommand(program);
4938
5396
  if (process.argv.includes("--describe")) {
4939
5397
  const startTime = Date.now();
4940
5398
  const globalOptions = [
4941
- { flag: "--json", description: "Output structured JSON to stdout", type: "boolean" },
4942
- { flag: "--yes", description: "Skip confirmation prompts (non-interactive mode)", type: "boolean" },
4943
- { flag: "--describe", description: "Print CLI schema as JSON and exit", type: "boolean" },
4944
- { flag: "-V, --version", description: "Output the version number", type: "boolean" },
4945
- { flag: "-h, --help", description: "Display help for command", type: "boolean" }
5399
+ {
5400
+ flag: "--json",
5401
+ description: "Output structured JSON to stdout",
5402
+ type: "boolean"
5403
+ },
5404
+ {
5405
+ flag: "--yes",
5406
+ description: "Skip confirmation prompts (non-interactive mode)",
5407
+ type: "boolean"
5408
+ },
5409
+ {
5410
+ flag: "--describe",
5411
+ description: "Print CLI schema as JSON and exit",
5412
+ type: "boolean"
5413
+ },
5414
+ {
5415
+ flag: "-V, --version",
5416
+ description: "Output the version number",
5417
+ type: "boolean"
5418
+ },
5419
+ {
5420
+ flag: "-h, --help",
5421
+ description: "Display help for command",
5422
+ type: "boolean"
5423
+ }
4946
5424
  ];
4947
5425
  respond(
4948
5426
  "describe",
@@ -4960,7 +5438,13 @@ if (process.argv.includes("--describe")) {
4960
5438
  }
4961
5439
  program.parseAsync(process.argv).catch((error) => {
4962
5440
  if (process.argv.includes("--json")) {
4963
- respondError("unknown", SyncpointErrorCode.UNKNOWN, error.message, Date.now(), VERSION);
5441
+ respondError(
5442
+ "unknown",
5443
+ SyncpointErrorCode.UNKNOWN,
5444
+ error.message,
5445
+ Date.now(),
5446
+ VERSION
5447
+ );
4964
5448
  process.exit(1);
4965
5449
  }
4966
5450
  console.error("Fatal error:", error.message);