@lumy-pack/syncpoint 0.0.4 → 0.0.5

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
@@ -336,23 +336,11 @@ function isValidPattern(pattern) {
336
336
  import { createHash } from "crypto";
337
337
  import { lstat, readFile, readlink } from "fs/promises";
338
338
 
339
- // src/schemas/ajv.ts
340
- import Ajv from "ajv";
341
- import addFormats from "ajv-formats";
342
- var ajv = new Ajv({ allErrors: true });
343
- addFormats(ajv);
344
- ajv.addKeyword({
345
- keyword: "validPattern",
346
- type: "string",
347
- validate: function validate(schema, data) {
348
- if (!schema) return true;
349
- return isValidPattern(data);
350
- },
351
- errors: true
352
- });
353
-
354
- // src/schemas/metadata.schema.ts
355
- var metadataSchema = {
339
+ // assets/schemas/metadata.schema.json
340
+ var metadata_schema_default = {
341
+ $schema: "http://json-schema.org/draft-07/schema#",
342
+ title: "Syncpoint Backup Metadata",
343
+ description: "Metadata stored inside backup archives as _metadata.json",
356
344
  type: "object",
357
345
  required: [
358
346
  "version",
@@ -365,57 +353,129 @@ var metadataSchema = {
365
353
  "summary"
366
354
  ],
367
355
  properties: {
368
- version: { type: "string" },
369
- toolVersion: { type: "string" },
370
- createdAt: { type: "string" },
371
- hostname: { type: "string" },
356
+ version: {
357
+ type: "string",
358
+ description: "Metadata schema version."
359
+ },
360
+ toolVersion: {
361
+ type: "string",
362
+ description: "Syncpoint tool version used to create the backup."
363
+ },
364
+ createdAt: {
365
+ type: "string",
366
+ description: "ISO 8601 timestamp of backup creation."
367
+ },
368
+ hostname: {
369
+ type: "string",
370
+ description: "Hostname of the machine where the backup was created."
371
+ },
372
372
  system: {
373
373
  type: "object",
374
+ description: "System information at backup time.",
374
375
  required: ["platform", "release", "arch"],
375
376
  properties: {
376
- platform: { type: "string" },
377
- release: { type: "string" },
378
- arch: { type: "string" }
377
+ platform: {
378
+ type: "string",
379
+ description: "Operating system platform (e.g. darwin, linux)."
380
+ },
381
+ release: {
382
+ type: "string",
383
+ description: "OS kernel release version."
384
+ },
385
+ arch: {
386
+ type: "string",
387
+ description: "CPU architecture (e.g. arm64, x64)."
388
+ }
379
389
  },
380
390
  additionalProperties: false
381
391
  },
382
392
  config: {
383
393
  type: "object",
394
+ description: "Backup configuration snapshot.",
384
395
  required: ["filename"],
385
396
  properties: {
386
- filename: { type: "string" },
387
- destination: { type: "string" }
397
+ filename: {
398
+ type: "string",
399
+ description: "Filename pattern used for the backup."
400
+ },
401
+ destination: {
402
+ type: "string",
403
+ description: "Custom destination path, if configured."
404
+ }
388
405
  },
389
406
  additionalProperties: false
390
407
  },
391
408
  files: {
392
409
  type: "array",
410
+ description: "List of files included in the backup.",
393
411
  items: {
394
412
  type: "object",
395
413
  required: ["path", "absolutePath", "size", "hash"],
396
414
  properties: {
397
- path: { type: "string" },
398
- absolutePath: { type: "string" },
399
- size: { type: "number", minimum: 0 },
400
- hash: { type: "string" },
401
- type: { type: "string" }
415
+ path: {
416
+ type: "string",
417
+ description: "Display path (e.g. ~/.zshrc)."
418
+ },
419
+ absolutePath: {
420
+ type: "string",
421
+ description: "Full filesystem path."
422
+ },
423
+ size: {
424
+ type: "number",
425
+ description: "File size in bytes.",
426
+ minimum: 0
427
+ },
428
+ hash: {
429
+ type: "string",
430
+ description: "SHA-256 hash of file contents."
431
+ },
432
+ type: {
433
+ type: "string",
434
+ description: "File type (e.g. symlink, directory)."
435
+ }
402
436
  },
403
437
  additionalProperties: false
404
438
  }
405
439
  },
406
440
  summary: {
407
441
  type: "object",
442
+ description: "Backup summary statistics.",
408
443
  required: ["fileCount", "totalSize"],
409
444
  properties: {
410
- fileCount: { type: "integer", minimum: 0 },
411
- totalSize: { type: "number", minimum: 0 }
445
+ fileCount: {
446
+ type: "integer",
447
+ description: "Total number of files in the backup.",
448
+ minimum: 0
449
+ },
450
+ totalSize: {
451
+ type: "number",
452
+ description: "Total size of all files in bytes.",
453
+ minimum: 0
454
+ }
412
455
  },
413
456
  additionalProperties: false
414
457
  }
415
458
  },
416
459
  additionalProperties: false
417
460
  };
418
- var validate2 = ajv.compile(metadataSchema);
461
+
462
+ // src/schemas/ajv.ts
463
+ import Ajv from "ajv";
464
+ import addFormats from "ajv-formats";
465
+ var ajv = new Ajv({ allErrors: true });
466
+ addFormats(ajv);
467
+ ajv.addKeyword({
468
+ keyword: "validPattern",
469
+ type: "string",
470
+ validate: function validate(schema, data) {
471
+ if (!schema) return true;
472
+ return isValidPattern(data);
473
+ },
474
+ errors: true
475
+ });
476
+
477
+ // src/schemas/metadata.schema.ts
478
+ var validate2 = ajv.compile(metadata_schema_default);
419
479
  function validateMetadata(data) {
420
480
  const valid = validate2(data);
421
481
  if (valid) return { valid: true };
@@ -426,7 +486,7 @@ function validateMetadata(data) {
426
486
  }
427
487
 
428
488
  // src/version.ts
429
- var VERSION = "0.0.4";
489
+ var VERSION = "0.0.5";
430
490
 
431
491
  // src/core/metadata.ts
432
492
  var METADATA_VERSION = "1.0.0";
@@ -611,6 +671,10 @@ async function scanTargets(config) {
611
671
  });
612
672
  for (const match of allFiles) {
613
673
  if (regex.test(match) && !isExcluded(match)) {
674
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
675
+ logger.warn(`Sensitive file excluded: ${match}`);
676
+ continue;
677
+ }
614
678
  const entry = await collectFileInfo(match, match);
615
679
  found.push(entry);
616
680
  }
@@ -639,6 +703,10 @@ async function scanTargets(config) {
639
703
  });
640
704
  for (const match of matches) {
641
705
  if (!isExcluded(match)) {
706
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
707
+ logger.warn(`Sensitive file excluded: ${match}`);
708
+ continue;
709
+ }
642
710
  const entry = await collectFileInfo(match, match);
643
711
  found.push(entry);
644
712
  }
@@ -672,6 +740,10 @@ async function scanTargets(config) {
672
740
  });
673
741
  for (const match of matches) {
674
742
  if (!isExcluded(match)) {
743
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(match)) {
744
+ logger.warn(`Sensitive file excluded: ${match}`);
745
+ continue;
746
+ }
675
747
  const entry = await collectFileInfo(match, match);
676
748
  found.push(entry);
677
749
  }
@@ -683,15 +755,16 @@ async function scanTargets(config) {
683
755
  if (isExcluded(absPath)) {
684
756
  continue;
685
757
  }
758
+ if (!config.backup.includeSensitiveFiles && isSensitiveFile(absPath)) {
759
+ logger.warn(`Sensitive file excluded: ${target}`);
760
+ continue;
761
+ }
686
762
  const entry = await collectFileInfo(absPath, absPath);
687
763
  if (entry.size > LARGE_FILE_THRESHOLD) {
688
764
  logger.warn(
689
765
  `Large file (>${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB): ${target}`
690
766
  );
691
767
  }
692
- if (isSensitiveFile(absPath)) {
693
- logger.warn(`Sensitive file detected: ${target}`);
694
- }
695
768
  found.push(entry);
696
769
  }
697
770
  }
@@ -764,46 +837,78 @@ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
764
837
  import { join as join7 } from "path";
765
838
  import YAML from "yaml";
766
839
 
767
- // src/schemas/config.schema.ts
768
- var configSchema = {
840
+ // assets/schemas/config.schema.json
841
+ var config_schema_default = {
842
+ $schema: "http://json-schema.org/draft-07/schema#",
843
+ title: "Syncpoint Config",
844
+ description: "Configuration for syncpoint backup tool",
769
845
  type: "object",
770
- required: ["backup"],
846
+ required: [
847
+ "backup"
848
+ ],
771
849
  properties: {
772
850
  backup: {
773
851
  type: "object",
774
- required: ["targets", "exclude", "filename"],
852
+ description: "Backup configuration",
853
+ required: [
854
+ "targets",
855
+ "exclude",
856
+ "filename"
857
+ ],
775
858
  properties: {
776
859
  targets: {
777
860
  type: "array",
778
- items: { type: "string", validPattern: true }
861
+ description: "List of files/directories to backup. Supports literal paths (e.g. ~/.zshrc), glob patterns (e.g. ~/.config/*.conf), and regex patterns (e.g. /\\.conf$/).",
862
+ items: {
863
+ type: "string",
864
+ validPattern: true
865
+ }
779
866
  },
780
867
  exclude: {
781
868
  type: "array",
782
- items: { type: "string", validPattern: true }
869
+ description: "List of patterns to exclude from backup. Supports glob (e.g. **/*.swp) and regex (e.g. /\\.bak$/) patterns.",
870
+ items: {
871
+ type: "string",
872
+ validPattern: true
873
+ }
783
874
  },
784
875
  filename: {
785
876
  type: "string",
877
+ description: "Backup archive filename pattern. Available variables: {hostname}, {datetime}.",
786
878
  minLength: 1
787
879
  },
788
880
  destination: {
789
- type: "string"
881
+ type: "string",
882
+ description: "Backup archive destination path. Default: ~/.syncpoint/backups/"
883
+ },
884
+ includeSensitiveFiles: {
885
+ type: "boolean",
886
+ description: "Include sensitive files (SSH keys, certificates, etc.) in backup. When false (default), files matching sensitive patterns (id_rsa, id_ed25519, *.pem, *.key) are automatically excluded."
790
887
  }
791
888
  },
792
889
  additionalProperties: false
793
890
  },
794
891
  scripts: {
795
892
  type: "object",
893
+ description: "Scripts configuration",
796
894
  properties: {
797
895
  includeInBackup: {
798
- type: "boolean"
896
+ type: "boolean",
897
+ description: "Whether to include scripts/ directory in backup. Default: true."
799
898
  }
800
899
  },
801
900
  additionalProperties: false
901
+ },
902
+ "yaml-language-server": {
903
+ type: "string",
904
+ description: "Editor directive for schema association; ignored at runtime."
802
905
  }
803
906
  },
804
907
  additionalProperties: false
805
908
  };
806
- var validate3 = ajv.compile(configSchema);
909
+
910
+ // src/schemas/config.schema.ts
911
+ var validate3 = ajv.compile(config_schema_default);
807
912
  function validateConfig(data) {
808
913
  const valid = validate3(data);
809
914
  if (valid) return { valid: true };
@@ -1051,6 +1156,21 @@ var COMMANDS = {
1051
1156
  "npx @lumy-pack/syncpoint status --cleanup"
1052
1157
  ]
1053
1158
  },
1159
+ migrate: {
1160
+ name: "migrate",
1161
+ description: "Migrate config.yml to match the current schema",
1162
+ usage: "npx @lumy-pack/syncpoint migrate [options]",
1163
+ options: [
1164
+ {
1165
+ flag: "--dry-run",
1166
+ description: "Preview changes without writing"
1167
+ }
1168
+ ],
1169
+ examples: [
1170
+ "npx @lumy-pack/syncpoint migrate",
1171
+ "npx @lumy-pack/syncpoint migrate --dry-run"
1172
+ ]
1173
+ },
1054
1174
  help: {
1055
1175
  name: "help",
1056
1176
  description: "Display help information",
@@ -1206,27 +1326,61 @@ import { render as render2 } from "ink";
1206
1326
  import { join as join8 } from "path";
1207
1327
  import { writeFile as writeFile3 } from "fs/promises";
1208
1328
 
1209
- // src/schemas/template.schema.ts
1210
- var templateSchema = {
1329
+ // assets/schemas/template.schema.json
1330
+ var template_schema_default = {
1331
+ $schema: "http://json-schema.org/draft-07/schema#",
1332
+ title: "Syncpoint Template",
1333
+ description: "Provisioning template for syncpoint",
1211
1334
  type: "object",
1212
1335
  required: ["name", "steps"],
1213
1336
  properties: {
1214
- name: { type: "string", minLength: 1 },
1215
- description: { type: "string" },
1216
- backup: { type: "string" },
1217
- sudo: { type: "boolean" },
1337
+ name: {
1338
+ type: "string",
1339
+ description: "Template name.",
1340
+ minLength: 1
1341
+ },
1342
+ description: {
1343
+ type: "string",
1344
+ description: "Template description."
1345
+ },
1346
+ backup: {
1347
+ type: "string",
1348
+ description: "Backup name to restore automatically after provisioning."
1349
+ },
1350
+ sudo: {
1351
+ type: "boolean",
1352
+ description: "Whether sudo privilege is required. If true, requests sudo authentication before execution."
1353
+ },
1218
1354
  steps: {
1219
1355
  type: "array",
1356
+ description: "List of provisioning steps. At least 1 step required.",
1220
1357
  minItems: 1,
1221
1358
  items: {
1222
1359
  type: "object",
1223
1360
  required: ["name", "command"],
1224
1361
  properties: {
1225
- name: { type: "string", minLength: 1 },
1226
- description: { type: "string" },
1227
- command: { type: "string", minLength: 1 },
1228
- skip_if: { type: "string" },
1229
- continue_on_error: { type: "boolean" }
1362
+ name: {
1363
+ type: "string",
1364
+ description: "Step name.",
1365
+ minLength: 1
1366
+ },
1367
+ description: {
1368
+ type: "string",
1369
+ description: "Step description."
1370
+ },
1371
+ command: {
1372
+ type: "string",
1373
+ description: "Shell command to execute.",
1374
+ minLength: 1
1375
+ },
1376
+ skip_if: {
1377
+ type: "string",
1378
+ description: "Skip this step if this command exits with code 0."
1379
+ },
1380
+ continue_on_error: {
1381
+ type: "boolean",
1382
+ description: "Continue to next step even if this fails. Default: false."
1383
+ }
1230
1384
  },
1231
1385
  additionalProperties: false
1232
1386
  }
@@ -1234,7 +1388,9 @@ var templateSchema = {
1234
1388
  },
1235
1389
  additionalProperties: false
1236
1390
  };
1237
- var validate4 = ajv.compile(templateSchema);
1391
+
1392
+ // src/schemas/template.schema.ts
1393
+ var validate4 = ajv.compile(template_schema_default);
1238
1394
  function validateTemplate(data) {
1239
1395
  const valid = validate4(data);
1240
1396
  if (valid) return { valid: true };
@@ -1804,9 +1960,9 @@ var InitView = () => {
1804
1960
  setSteps([...completed]);
1805
1961
  const exampleTemplatePath = join9(getSubDir(TEMPLATES_DIR), "example.yml");
1806
1962
  if (!await fileExists(exampleTemplatePath)) {
1807
- const { writeFile: writeFile5 } = await import("fs/promises");
1963
+ const { writeFile: writeFile6 } = await import("fs/promises");
1808
1964
  const exampleYaml = readAsset("template.example.yml");
1809
- await writeFile5(exampleTemplatePath, exampleYaml, "utf-8");
1965
+ await writeFile6(exampleTemplatePath, exampleYaml, "utf-8");
1810
1966
  completed.push({ name: `Created templates/example.yml`, done: true });
1811
1967
  setSteps([...completed]);
1812
1968
  }
@@ -2631,11 +2787,246 @@ function registerListCommand(program2) {
2631
2787
  });
2632
2788
  }
2633
2789
 
2634
- // src/commands/Provision.tsx
2790
+ // src/commands/Migrate.tsx
2635
2791
  import { useState as useState6, useEffect as useEffect5 } from "react";
2636
- import { Text as Text10, Box as Box8, useApp as useApp5 } from "ink";
2792
+ import { Text as Text9, Box as Box7, useApp as useApp5 } from "ink";
2637
2793
  import { render as render6 } from "ink";
2638
2794
 
2795
+ // src/core/migrate.ts
2796
+ import { copyFile as copyFile2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2797
+ import YAML4 from "yaml";
2798
+ function extractSchemaPaths(schema, prefix = []) {
2799
+ const paths = [];
2800
+ const properties = schema.properties;
2801
+ if (!properties) return paths;
2802
+ for (const [key, value] of Object.entries(properties)) {
2803
+ const currentPath = [...prefix, key];
2804
+ if (value.type === "object" && value.properties) {
2805
+ paths.push(
2806
+ ...extractSchemaPaths(value, currentPath)
2807
+ );
2808
+ } else {
2809
+ paths.push(currentPath);
2810
+ }
2811
+ }
2812
+ return paths;
2813
+ }
2814
+ function extractDataPaths(data, prefix = []) {
2815
+ const paths = [];
2816
+ if (!data || typeof data !== "object" || Array.isArray(data)) return paths;
2817
+ for (const [key, value] of Object.entries(data)) {
2818
+ const currentPath = [...prefix, key];
2819
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2820
+ paths.push(...extractDataPaths(value, currentPath));
2821
+ } else {
2822
+ paths.push(currentPath);
2823
+ }
2824
+ }
2825
+ return paths;
2826
+ }
2827
+ function pathKey(path) {
2828
+ return path.join(".");
2829
+ }
2830
+ function getNestedValue(obj, path) {
2831
+ let current = obj;
2832
+ for (const key of path) {
2833
+ if (!current || typeof current !== "object") return void 0;
2834
+ current = current[key];
2835
+ }
2836
+ return current;
2837
+ }
2838
+ function diffConfigFields(userData) {
2839
+ const schemaPaths = extractSchemaPaths(
2840
+ config_schema_default
2841
+ );
2842
+ const templateData = YAML4.parse(readAsset("config.default.yml"));
2843
+ const templatePaths = extractDataPaths(templateData);
2844
+ const userPaths = extractDataPaths(userData);
2845
+ const schemaKeys = new Set(schemaPaths.map(pathKey));
2846
+ const userKeys = new Set(userPaths.map(pathKey));
2847
+ const isEditorDirective = (p) => p.length === 1 && p[0] === "yaml-language-server";
2848
+ return {
2849
+ // Fields present in template with defaults AND valid in schema, but missing from user
2850
+ added: templatePaths.filter((p) => {
2851
+ if (isEditorDirective(p)) return false;
2852
+ const key = pathKey(p);
2853
+ return schemaKeys.has(key) && !userKeys.has(key);
2854
+ }),
2855
+ // Fields in user but not in schema (truly deprecated)
2856
+ removed: userPaths.filter(
2857
+ (p) => !isEditorDirective(p) && !schemaKeys.has(pathKey(p))
2858
+ ),
2859
+ // Fields in user AND in schema (preserve user values)
2860
+ existing: userPaths.filter((p) => schemaKeys.has(pathKey(p)))
2861
+ };
2862
+ }
2863
+ function buildMigratedDocument(templateText, userData, diff) {
2864
+ const doc = YAML4.parseDocument(templateText);
2865
+ for (const path of diff.existing) {
2866
+ const userValue = getNestedValue(userData, path);
2867
+ if (userValue === void 0) continue;
2868
+ const node = doc.getIn(path, true);
2869
+ if (YAML4.isScalar(node)) {
2870
+ node.value = userValue;
2871
+ } else {
2872
+ doc.setIn(path, userValue);
2873
+ }
2874
+ }
2875
+ let output = doc.toString();
2876
+ if (diff.removed.length > 0) {
2877
+ const lines = [
2878
+ "",
2879
+ "# [deprecated] The following fields are no longer in the current schema.",
2880
+ "# They have been preserved as comments for reference."
2881
+ ];
2882
+ for (const path of diff.removed) {
2883
+ const value = getNestedValue(userData, path);
2884
+ const valueStr = typeof value === "object" ? JSON.stringify(value) : String(value);
2885
+ lines.push(`# [deprecated] ${path.join(".")}: ${valueStr}`);
2886
+ }
2887
+ output += lines.join("\n") + "\n";
2888
+ }
2889
+ return output;
2890
+ }
2891
+ async function migrateConfig(options) {
2892
+ const configPath = getConfigPath();
2893
+ if (!await fileExists(configPath)) {
2894
+ throw new Error(
2895
+ `Config file not found: ${configPath}
2896
+ Run "syncpoint init" first.`
2897
+ );
2898
+ }
2899
+ const userText = await readFile5(configPath, "utf-8");
2900
+ const userData = YAML4.parse(userText);
2901
+ const templateText = readAsset("config.default.yml");
2902
+ const diff = diffConfigFields(userData);
2903
+ if (diff.added.length === 0 && diff.removed.length === 0) {
2904
+ return {
2905
+ added: [],
2906
+ deprecated: [],
2907
+ preserved: diff.existing.map(pathKey),
2908
+ backupPath: "",
2909
+ migrated: false
2910
+ };
2911
+ }
2912
+ const result = {
2913
+ added: diff.added.map(pathKey),
2914
+ deprecated: diff.removed.map(pathKey),
2915
+ preserved: diff.existing.map(pathKey),
2916
+ backupPath: "",
2917
+ migrated: false
2918
+ };
2919
+ if (!options?.dryRun) {
2920
+ const migratedText = buildMigratedDocument(templateText, userData, diff);
2921
+ const backupPath = configPath + ".bak";
2922
+ await copyFile2(configPath, backupPath);
2923
+ result.backupPath = backupPath;
2924
+ await writeFile4(configPath, migratedText, "utf-8");
2925
+ const migrated = YAML4.parse(migratedText);
2926
+ const validation = validateConfig(migrated);
2927
+ if (!validation.valid) {
2928
+ await copyFile2(backupPath, configPath);
2929
+ throw new Error(
2930
+ `Migration produced invalid config (restored from backup):
2931
+ ${(validation.errors ?? []).join("\n")}`
2932
+ );
2933
+ }
2934
+ result.migrated = true;
2935
+ }
2936
+ return result;
2937
+ }
2938
+
2939
+ // src/commands/Migrate.tsx
2940
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2941
+ var MigrateView = ({ dryRun }) => {
2942
+ const { exit } = useApp5();
2943
+ const [result, setResult] = useState6(null);
2944
+ const [error, setError] = useState6(null);
2945
+ const [loading, setLoading] = useState6(true);
2946
+ useEffect5(() => {
2947
+ (async () => {
2948
+ try {
2949
+ const res = await migrateConfig({ dryRun });
2950
+ setResult(res);
2951
+ setLoading(false);
2952
+ setTimeout(() => exit(), 100);
2953
+ } catch (err) {
2954
+ setError(err instanceof Error ? err.message : String(err));
2955
+ setLoading(false);
2956
+ exit();
2957
+ }
2958
+ })();
2959
+ }, []);
2960
+ if (error) {
2961
+ return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
2962
+ "\u2717 ",
2963
+ error
2964
+ ] }) });
2965
+ }
2966
+ if (loading) {
2967
+ return /* @__PURE__ */ jsx9(Text9, { children: "Analyzing config..." });
2968
+ }
2969
+ if (!result) return null;
2970
+ if (!result.migrated && result.added.length === 0 && result.deprecated.length === 0) {
2971
+ return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713 Config is already up to date." }) });
2972
+ }
2973
+ return /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
2974
+ dryRun && /* @__PURE__ */ jsx9(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", bold: true, children: "[dry-run] Preview only \u2014 no changes written." }) }),
2975
+ result.added.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", children: [
2976
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "New fields (added with defaults):" }),
2977
+ result.added.map((field, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
2978
+ " ",
2979
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: "+" }),
2980
+ " ",
2981
+ field
2982
+ ] }, i))
2983
+ ] }),
2984
+ result.deprecated.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: result.added.length > 0 ? 1 : 0, children: [
2985
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "Deprecated fields (commented out):" }),
2986
+ result.deprecated.map((field, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
2987
+ " ",
2988
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "~" }),
2989
+ " ",
2990
+ field
2991
+ ] }, i))
2992
+ ] }),
2993
+ result.preserved.length > 0 && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: 1, children: [
2994
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
2995
+ "Preserved fields (",
2996
+ result.preserved.length,
2997
+ "):"
2998
+ ] }),
2999
+ result.preserved.map((field, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
3000
+ " ",
3001
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u2022" }),
3002
+ " ",
3003
+ field
3004
+ ] }, i))
3005
+ ] }),
3006
+ result.migrated && /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginTop: 1, children: [
3007
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713 Migration complete." }),
3008
+ result.backupPath && /* @__PURE__ */ jsxs9(Text9, { children: [
3009
+ " ",
3010
+ "Backup saved to: ",
3011
+ result.backupPath
3012
+ ] })
3013
+ ] })
3014
+ ] });
3015
+ };
3016
+ function registerMigrateCommand(program2) {
3017
+ program2.command("migrate").description("Migrate config.yml to match the current schema").option("--dry-run", "Preview changes without writing").action(async (opts) => {
3018
+ const { waitUntilExit } = render6(
3019
+ /* @__PURE__ */ jsx9(MigrateView, { dryRun: opts.dryRun ?? false })
3020
+ );
3021
+ await waitUntilExit();
3022
+ });
3023
+ }
3024
+
3025
+ // src/commands/Provision.tsx
3026
+ import { useState as useState7, useEffect as useEffect6 } from "react";
3027
+ import { Text as Text11, Box as Box9, useApp as useApp6 } from "ink";
3028
+ import { render as render7 } from "ink";
3029
+
2639
3030
  // src/utils/sudo.ts
2640
3031
  import { execSync } from "child_process";
2641
3032
  import pc2 from "picocolors";
@@ -2669,37 +3060,37 @@ ${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
2669
3060
  }
2670
3061
 
2671
3062
  // src/components/StepRunner.tsx
2672
- import { Text as Text9, Box as Box7 } from "ink";
3063
+ import { Text as Text10, Box as Box8 } from "ink";
2673
3064
  import Spinner2 from "ink-spinner";
2674
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3065
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2675
3066
  var StepIcon = ({ status }) => {
2676
3067
  switch (status) {
2677
3068
  case "success":
2678
- return /* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713" });
3069
+ return /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713" });
2679
3070
  case "running":
2680
- return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }) });
3071
+ return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: /* @__PURE__ */ jsx10(Spinner2, { type: "dots" }) });
2681
3072
  case "skipped":
2682
- return /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u23ED" });
3073
+ return /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "\u23ED" });
2683
3074
  case "failed":
2684
- return /* @__PURE__ */ jsx9(Text9, { color: "red", children: "\u2717" });
3075
+ return /* @__PURE__ */ jsx10(Text10, { color: "red", children: "\u2717" });
2685
3076
  case "pending":
2686
3077
  default:
2687
- return /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "\u25CB" });
3078
+ return /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "\u25CB" });
2688
3079
  }
2689
3080
  };
2690
3081
  var StepStatusText = ({ step }) => {
2691
3082
  switch (step.status) {
2692
3083
  case "success":
2693
- return /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
3084
+ return /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
2694
3085
  "Done",
2695
3086
  step.duration != null ? ` (${Math.round(step.duration / 1e3)}s)` : ""
2696
3087
  ] });
2697
3088
  case "running":
2698
- return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Running..." });
3089
+ return /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "Running..." });
2699
3090
  case "skipped":
2700
- return /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "Skipped (already installed)" });
3091
+ return /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "Skipped (already installed)" });
2701
3092
  case "failed":
2702
- return /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
3093
+ return /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
2703
3094
  "Failed",
2704
3095
  step.error ? `: ${step.error}` : ""
2705
3096
  ] });
@@ -2712,13 +3103,13 @@ var StepRunner = ({
2712
3103
  steps,
2713
3104
  total
2714
3105
  }) => {
2715
- return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", children: steps.map((step, idx) => /* @__PURE__ */ jsxs9(Box7, { flexDirection: "column", marginBottom: idx < steps.length - 1 ? 1 : 0, children: [
2716
- /* @__PURE__ */ jsxs9(Text9, { children: [
3106
+ return /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: steps.map((step, idx) => /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginBottom: idx < steps.length - 1 ? 1 : 0, children: [
3107
+ /* @__PURE__ */ jsxs10(Text10, { children: [
2717
3108
  " ",
2718
- /* @__PURE__ */ jsx9(StepIcon, { status: step.status }),
2719
- /* @__PURE__ */ jsxs9(Text9, { children: [
3109
+ /* @__PURE__ */ jsx10(StepIcon, { status: step.status }),
3110
+ /* @__PURE__ */ jsxs10(Text10, { children: [
2720
3111
  " ",
2721
- /* @__PURE__ */ jsxs9(Text9, { bold: true, children: [
3112
+ /* @__PURE__ */ jsxs10(Text10, { bold: true, children: [
2722
3113
  "Step ",
2723
3114
  idx + 1,
2724
3115
  "/",
@@ -2728,36 +3119,36 @@ var StepRunner = ({
2728
3119
  step.name
2729
3120
  ] })
2730
3121
  ] }),
2731
- step.output && step.status !== "pending" && /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
3122
+ step.output && step.status !== "pending" && /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
2732
3123
  " ",
2733
3124
  step.output
2734
3125
  ] }),
2735
- /* @__PURE__ */ jsxs9(Text9, { children: [
3126
+ /* @__PURE__ */ jsxs10(Text10, { children: [
2736
3127
  " ",
2737
- /* @__PURE__ */ jsx9(StepStatusText, { step })
3128
+ /* @__PURE__ */ jsx10(StepStatusText, { step })
2738
3129
  ] })
2739
3130
  ] }, idx)) });
2740
3131
  };
2741
3132
 
2742
3133
  // src/commands/Provision.tsx
2743
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3134
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2744
3135
  var ProvisionView = ({
2745
3136
  template,
2746
3137
  templatePath,
2747
3138
  options
2748
3139
  }) => {
2749
- const { exit } = useApp5();
2750
- const [phase, setPhase] = useState6(options.dryRun ? "done" : "running");
2751
- const [steps, setSteps] = useState6(
3140
+ const { exit } = useApp6();
3141
+ const [phase, setPhase] = useState7(options.dryRun ? "done" : "running");
3142
+ const [steps, setSteps] = useState7(
2752
3143
  template.steps.map((s) => ({
2753
3144
  name: s.name,
2754
3145
  status: "pending",
2755
3146
  output: s.description
2756
3147
  }))
2757
3148
  );
2758
- const [currentStep, setCurrentStep] = useState6(0);
2759
- const [error, setError] = useState6(null);
2760
- useEffect5(() => {
3149
+ const [currentStep, setCurrentStep] = useState7(0);
3150
+ const [error, setError] = useState7(null);
3151
+ useEffect6(() => {
2761
3152
  if (options.dryRun) {
2762
3153
  setTimeout(() => exit(), 100);
2763
3154
  return;
@@ -2799,7 +3190,7 @@ var ProvisionView = ({
2799
3190
  })();
2800
3191
  }, []);
2801
3192
  if (phase === "error" || error) {
2802
- return /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
3193
+ return /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
2803
3194
  "\u2717 ",
2804
3195
  error
2805
3196
  ] }) });
@@ -2807,27 +3198,27 @@ var ProvisionView = ({
2807
3198
  const successCount = steps.filter((s) => s.status === "success").length;
2808
3199
  const skippedCount = steps.filter((s) => s.status === "skipped").length;
2809
3200
  const failedCount = steps.filter((s) => s.status === "failed").length;
2810
- return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
2811
- /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginBottom: 1, children: [
2812
- /* @__PURE__ */ jsxs10(Text10, { bold: true, children: [
3201
+ return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3202
+ /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: 1, children: [
3203
+ /* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
2813
3204
  "\u25B8 ",
2814
3205
  template.name
2815
3206
  ] }),
2816
- template.description && /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
3207
+ template.description && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
2817
3208
  " ",
2818
3209
  template.description
2819
3210
  ] })
2820
3211
  ] }),
2821
- options.dryRun && phase === "done" && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
2822
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "(dry-run) Showing execution plan only" }),
2823
- template.sudo && /* @__PURE__ */ jsxs10(Text10, { color: "yellow", children: [
3212
+ options.dryRun && phase === "done" && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3213
+ /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "(dry-run) Showing execution plan only" }),
3214
+ template.sudo && /* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
2824
3215
  " ",
2825
3216
  "\u26A0 This template requires sudo privileges (will prompt on actual run)"
2826
3217
  ] }),
2827
- /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", marginTop: 1, children: template.steps.map((step, idx) => /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginBottom: 1, children: [
2828
- /* @__PURE__ */ jsxs10(Text10, { children: [
3218
+ /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", marginTop: 1, children: template.steps.map((step, idx) => /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: 1, children: [
3219
+ /* @__PURE__ */ jsxs11(Text11, { children: [
2829
3220
  " ",
2830
- /* @__PURE__ */ jsxs10(Text10, { bold: true, children: [
3221
+ /* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
2831
3222
  "Step ",
2832
3223
  idx + 1,
2833
3224
  "/",
@@ -2836,18 +3227,18 @@ var ProvisionView = ({
2836
3227
  " ",
2837
3228
  step.name
2838
3229
  ] }),
2839
- step.description && /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
3230
+ step.description && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
2840
3231
  " ",
2841
3232
  step.description
2842
3233
  ] }),
2843
- step.skip_if && /* @__PURE__ */ jsxs10(Text10, { color: "blue", children: [
3234
+ step.skip_if && /* @__PURE__ */ jsxs11(Text11, { color: "blue", children: [
2844
3235
  " ",
2845
3236
  "Skip condition: ",
2846
3237
  step.skip_if
2847
3238
  ] })
2848
3239
  ] }, idx)) })
2849
3240
  ] }),
2850
- (phase === "running" || phase === "done" && !options.dryRun) && /* @__PURE__ */ jsx10(
3241
+ (phase === "running" || phase === "done" && !options.dryRun) && /* @__PURE__ */ jsx11(
2851
3242
  StepRunner,
2852
3243
  {
2853
3244
  steps,
@@ -2855,34 +3246,34 @@ var ProvisionView = ({
2855
3246
  total: template.steps.length
2856
3247
  }
2857
3248
  ),
2858
- phase === "done" && !options.dryRun && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
2859
- /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
3249
+ phase === "done" && !options.dryRun && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: 1, children: [
3250
+ /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
2860
3251
  " ",
2861
3252
  "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
2862
3253
  ] }),
2863
- /* @__PURE__ */ jsxs10(Text10, { children: [
3254
+ /* @__PURE__ */ jsxs11(Text11, { children: [
2864
3255
  " ",
2865
3256
  "Result: ",
2866
- /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
3257
+ /* @__PURE__ */ jsxs11(Text11, { color: "green", children: [
2867
3258
  successCount,
2868
3259
  " succeeded"
2869
3260
  ] }),
2870
3261
  " \xB7",
2871
3262
  " ",
2872
- /* @__PURE__ */ jsxs10(Text10, { color: "blue", children: [
3263
+ /* @__PURE__ */ jsxs11(Text11, { color: "blue", children: [
2873
3264
  skippedCount,
2874
3265
  " skipped"
2875
3266
  ] }),
2876
3267
  " \xB7",
2877
3268
  " ",
2878
- /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
3269
+ /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
2879
3270
  failedCount,
2880
3271
  " failed"
2881
3272
  ] })
2882
3273
  ] }),
2883
- template.backup && !options.skipRestore && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
2884
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: "\u25B8 Proceeding with config file restore..." }),
2885
- /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
3274
+ template.backup && !options.skipRestore && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: 1, children: [
3275
+ /* @__PURE__ */ jsx11(Text11, { bold: true, children: "\u25B8 Proceeding with config file restore..." }),
3276
+ /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
2886
3277
  " ",
2887
3278
  "Backup link: ",
2888
3279
  template.backup
@@ -2925,8 +3316,8 @@ function registerProvisionCommand(program2) {
2925
3316
  if (tmpl.sudo && !opts.dryRun) {
2926
3317
  ensureSudo(tmpl.name);
2927
3318
  }
2928
- const { waitUntilExit } = render6(
2929
- /* @__PURE__ */ jsx10(
3319
+ const { waitUntilExit } = render7(
3320
+ /* @__PURE__ */ jsx11(
2930
3321
  ProvisionView,
2931
3322
  {
2932
3323
  template: tmpl,
@@ -2944,21 +3335,21 @@ function registerProvisionCommand(program2) {
2944
3335
  }
2945
3336
 
2946
3337
  // src/commands/Restore.tsx
2947
- import { useState as useState7, useEffect as useEffect6 } from "react";
2948
- import { Text as Text11, Box as Box9, useApp as useApp6 } from "ink";
3338
+ import { useState as useState8, useEffect as useEffect7 } from "react";
3339
+ import { Text as Text12, Box as Box10, useApp as useApp7 } from "ink";
2949
3340
  import SelectInput2 from "ink-select-input";
2950
- import { render as render7 } from "ink";
2951
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3341
+ import { render as render8 } from "ink";
3342
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
2952
3343
  var RestoreView = ({ filename, options }) => {
2953
- const { exit } = useApp6();
2954
- const [phase, setPhase] = useState7("loading");
2955
- const [backups, setBackups] = useState7([]);
2956
- const [selectedPath, setSelectedPath] = useState7(null);
2957
- const [plan, setPlan] = useState7(null);
2958
- const [result, setResult] = useState7(null);
2959
- const [safetyDone, setSafetyDone] = useState7(false);
2960
- const [error, setError] = useState7(null);
2961
- useEffect6(() => {
3344
+ const { exit } = useApp7();
3345
+ const [phase, setPhase] = useState8("loading");
3346
+ const [backups, setBackups] = useState8([]);
3347
+ const [selectedPath, setSelectedPath] = useState8(null);
3348
+ const [plan, setPlan] = useState8(null);
3349
+ const [result, setResult] = useState8(null);
3350
+ const [safetyDone, setSafetyDone] = useState8(false);
3351
+ const [error, setError] = useState8(null);
3352
+ useEffect7(() => {
2962
3353
  (async () => {
2963
3354
  try {
2964
3355
  const config = await loadConfig();
@@ -2992,7 +3383,7 @@ var RestoreView = ({ filename, options }) => {
2992
3383
  }
2993
3384
  })();
2994
3385
  }, []);
2995
- useEffect6(() => {
3386
+ useEffect7(() => {
2996
3387
  if (phase !== "planning" || !selectedPath) return;
2997
3388
  (async () => {
2998
3389
  try {
@@ -3040,7 +3431,7 @@ var RestoreView = ({ filename, options }) => {
3040
3431
  }
3041
3432
  };
3042
3433
  if (phase === "error" || error) {
3043
- return /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
3434
+ return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
3044
3435
  "\u2717 ",
3045
3436
  error
3046
3437
  ] }) });
@@ -3051,30 +3442,30 @@ var RestoreView = ({ filename, options }) => {
3051
3442
  }));
3052
3443
  const currentHostname = getHostname();
3053
3444
  const isRemoteBackup = plan?.metadata.hostname && plan.metadata.hostname !== currentHostname;
3054
- return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3055
- phase === "loading" && /* @__PURE__ */ jsx11(Text11, { children: "\u25B8 Loading backup list..." }),
3056
- phase === "selecting" && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3057
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "\u25B8 Select backup" }),
3058
- /* @__PURE__ */ jsx11(SelectInput2, { items: selectItems, onSelect: handleSelect })
3445
+ return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3446
+ phase === "loading" && /* @__PURE__ */ jsx12(Text12, { children: "\u25B8 Loading backup list..." }),
3447
+ phase === "selecting" && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3448
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Select backup" }),
3449
+ /* @__PURE__ */ jsx12(SelectInput2, { items: selectItems, onSelect: handleSelect })
3059
3450
  ] }),
3060
- (phase === "planning" || phase === "confirming" || phase === "restoring" || phase === "done" && plan) && plan && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3061
- /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: 1, children: [
3062
- /* @__PURE__ */ jsxs11(Text11, { bold: true, children: [
3451
+ (phase === "planning" || phase === "confirming" || phase === "restoring" || phase === "done" && plan) && plan && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3452
+ /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
3453
+ /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
3063
3454
  "\u25B8 Metadata (",
3064
3455
  plan.metadata.config.filename ?? "",
3065
3456
  ")"
3066
3457
  ] }),
3067
- /* @__PURE__ */ jsxs11(Text11, { children: [
3458
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3068
3459
  " ",
3069
3460
  "Host: ",
3070
3461
  plan.metadata.hostname
3071
3462
  ] }),
3072
- /* @__PURE__ */ jsxs11(Text11, { children: [
3463
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3073
3464
  " ",
3074
3465
  "Created: ",
3075
3466
  plan.metadata.createdAt
3076
3467
  ] }),
3077
- /* @__PURE__ */ jsxs11(Text11, { children: [
3468
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3078
3469
  " ",
3079
3470
  "Files: ",
3080
3471
  plan.metadata.summary.fileCount,
@@ -3082,15 +3473,15 @@ var RestoreView = ({ filename, options }) => {
3082
3473
  formatBytes(plan.metadata.summary.totalSize),
3083
3474
  ")"
3084
3475
  ] }),
3085
- isRemoteBackup && /* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
3476
+ isRemoteBackup && /* @__PURE__ */ jsxs12(Text12, { color: "yellow", children: [
3086
3477
  " ",
3087
3478
  "\u26A0 This backup was created on a different machine (",
3088
3479
  plan.metadata.hostname,
3089
3480
  ")"
3090
3481
  ] })
3091
3482
  ] }),
3092
- /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginBottom: 1, children: [
3093
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "\u25B8 Restore plan:" }),
3483
+ /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginBottom: 1, children: [
3484
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Restore plan:" }),
3094
3485
  plan.actions.map((action, idx) => {
3095
3486
  let icon;
3096
3487
  let color;
@@ -3112,45 +3503,45 @@ var RestoreView = ({ filename, options }) => {
3112
3503
  label = "(not present)";
3113
3504
  break;
3114
3505
  }
3115
- return /* @__PURE__ */ jsxs11(Text11, { children: [
3506
+ return /* @__PURE__ */ jsxs12(Text12, { children: [
3116
3507
  " ",
3117
- /* @__PURE__ */ jsx11(Text11, { color, children: icon.padEnd(8) }),
3508
+ /* @__PURE__ */ jsx12(Text12, { color, children: icon.padEnd(8) }),
3118
3509
  " ",
3119
3510
  contractTilde(action.path),
3120
3511
  " ",
3121
- /* @__PURE__ */ jsx11(Text11, { color: "gray", children: label })
3512
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: label })
3122
3513
  ] }, idx);
3123
3514
  })
3124
3515
  ] }),
3125
- options.dryRun && phase === "done" && /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "(dry-run) No actual restore was performed" })
3516
+ options.dryRun && phase === "done" && /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "(dry-run) No actual restore was performed" })
3126
3517
  ] }),
3127
- phase === "confirming" && /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx11(Confirm, { message: "Proceed with restore?", onConfirm: handleConfirm }) }),
3128
- phase === "restoring" && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
3129
- safetyDone && /* @__PURE__ */ jsxs11(Text11, { children: [
3130
- /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" }),
3518
+ phase === "confirming" && /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsx12(Confirm, { message: "Proceed with restore?", onConfirm: handleConfirm }) }),
3519
+ phase === "restoring" && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3520
+ safetyDone && /* @__PURE__ */ jsxs12(Text12, { children: [
3521
+ /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
3131
3522
  " Safety backup of current files complete"
3132
3523
  ] }),
3133
- /* @__PURE__ */ jsx11(Text11, { children: "\u25B8 Restoring..." })
3524
+ /* @__PURE__ */ jsx12(Text12, { children: "\u25B8 Restoring..." })
3134
3525
  ] }),
3135
- phase === "done" && result && !options.dryRun && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: 1, children: [
3136
- safetyDone && /* @__PURE__ */ jsxs11(Text11, { children: [
3137
- /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" }),
3526
+ phase === "done" && result && !options.dryRun && /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
3527
+ safetyDone && /* @__PURE__ */ jsxs12(Text12, { children: [
3528
+ /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
3138
3529
  " Safety backup of current files complete"
3139
3530
  ] }),
3140
- /* @__PURE__ */ jsx11(Text11, { color: "green", bold: true, children: "\u2713 Restore complete" }),
3141
- /* @__PURE__ */ jsxs11(Text11, { children: [
3531
+ /* @__PURE__ */ jsx12(Text12, { color: "green", bold: true, children: "\u2713 Restore complete" }),
3532
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3142
3533
  " ",
3143
3534
  "Restored: ",
3144
3535
  result.restoredFiles.length,
3145
3536
  " files"
3146
3537
  ] }),
3147
- /* @__PURE__ */ jsxs11(Text11, { children: [
3538
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3148
3539
  " ",
3149
3540
  "Skipped: ",
3150
3541
  result.skippedFiles.length,
3151
3542
  " files"
3152
3543
  ] }),
3153
- result.safetyBackupPath && /* @__PURE__ */ jsxs11(Text11, { children: [
3544
+ result.safetyBackupPath && /* @__PURE__ */ jsxs12(Text12, { children: [
3154
3545
  " ",
3155
3546
  "Safety backup: ",
3156
3547
  contractTilde(result.safetyBackupPath)
@@ -3160,8 +3551,8 @@ var RestoreView = ({ filename, options }) => {
3160
3551
  };
3161
3552
  function registerRestoreCommand(program2) {
3162
3553
  program2.command("restore [filename]").description("Restore config files from a backup").option("--dry-run", "Show planned changes without actual restore", false).action(async (filename, opts) => {
3163
- const { waitUntilExit } = render7(
3164
- /* @__PURE__ */ jsx11(
3554
+ const { waitUntilExit } = render8(
3555
+ /* @__PURE__ */ jsx12(
3165
3556
  RestoreView,
3166
3557
  {
3167
3558
  filename,
@@ -3176,11 +3567,11 @@ function registerRestoreCommand(program2) {
3176
3567
  // src/commands/Status.tsx
3177
3568
  import { readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
3178
3569
  import { join as join12 } from "path";
3179
- import { Box as Box10, Text as Text12, useApp as useApp7, useInput as useInput3 } from "ink";
3180
- import { render as render8 } from "ink";
3570
+ import { Box as Box11, Text as Text13, useApp as useApp8, useInput as useInput3 } from "ink";
3571
+ import { render as render9 } from "ink";
3181
3572
  import SelectInput3 from "ink-select-input";
3182
- import { useEffect as useEffect7, useState as useState8 } from "react";
3183
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3573
+ import { useEffect as useEffect8, useState as useState9 } from "react";
3574
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
3184
3575
  function getDirStats(dirPath) {
3185
3576
  try {
3186
3577
  const entries = readdirSync(dirPath);
@@ -3202,34 +3593,34 @@ function getDirStats(dirPath) {
3202
3593
  }
3203
3594
  }
3204
3595
  var DisplayActionItem = ({ isSelected = false, label }) => {
3205
- return /* @__PURE__ */ jsx12(Text12, { bold: isSelected, children: label });
3596
+ return /* @__PURE__ */ jsx13(Text13, { bold: isSelected, children: label });
3206
3597
  };
3207
3598
  var CleanupActionItem = ({ isSelected = false, label }) => {
3208
3599
  if (label === "Cancel" || label === "Select specific backups to delete") {
3209
- return /* @__PURE__ */ jsx12(Text12, { bold: isSelected, children: label });
3600
+ return /* @__PURE__ */ jsx13(Text13, { bold: isSelected, children: label });
3210
3601
  }
3211
3602
  const parts = label.split(/\s{2,}/);
3212
3603
  if (parts.length === 2) {
3213
- return /* @__PURE__ */ jsxs12(Text12, { bold: isSelected, children: [
3604
+ return /* @__PURE__ */ jsxs13(Text13, { bold: isSelected, children: [
3214
3605
  parts[0],
3215
3606
  " ",
3216
- /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: parts[1] })
3607
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: parts[1] })
3217
3608
  ] });
3218
3609
  }
3219
- return /* @__PURE__ */ jsx12(Text12, { bold: isSelected, children: label });
3610
+ return /* @__PURE__ */ jsx13(Text13, { bold: isSelected, children: label });
3220
3611
  };
3221
3612
  var StatusView = ({ cleanup }) => {
3222
- const { exit } = useApp7();
3223
- const [phase, setPhase] = useState8("loading");
3224
- const [status, setStatus] = useState8(null);
3225
- const [backups, setBackups] = useState8([]);
3226
- const [cleanupAction, setCleanupAction] = useState8(null);
3227
- const [cleanupMessage, setCleanupMessage] = useState8("");
3228
- const [error, setError] = useState8(null);
3229
- const [selectedForDeletion, setSelectedForDeletion] = useState8(
3613
+ const { exit } = useApp8();
3614
+ const [phase, setPhase] = useState9("loading");
3615
+ const [status, setStatus] = useState9(null);
3616
+ const [backups, setBackups] = useState9([]);
3617
+ const [cleanupAction, setCleanupAction] = useState9(null);
3618
+ const [cleanupMessage, setCleanupMessage] = useState9("");
3619
+ const [error, setError] = useState9(null);
3620
+ const [selectedForDeletion, setSelectedForDeletion] = useState9(
3230
3621
  []
3231
3622
  );
3232
- const [backupDir, setBackupDir] = useState8(getSubDir("backups"));
3623
+ const [backupDir, setBackupDir] = useState9(getSubDir("backups"));
3233
3624
  useInput3((_input, key) => {
3234
3625
  if (!key.escape) return;
3235
3626
  if (phase === "display") {
@@ -3241,7 +3632,7 @@ var StatusView = ({ cleanup }) => {
3241
3632
  setPhase("cleanup");
3242
3633
  }
3243
3634
  });
3244
- useEffect7(() => {
3635
+ useEffect8(() => {
3245
3636
  (async () => {
3246
3637
  try {
3247
3638
  const config = await loadConfig();
@@ -3378,26 +3769,26 @@ var StatusView = ({ cleanup }) => {
3378
3769
  }
3379
3770
  };
3380
3771
  if (phase === "error" || error) {
3381
- return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
3772
+ return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
3382
3773
  "\u2717 ",
3383
3774
  error
3384
3775
  ] }) });
3385
3776
  }
3386
3777
  if (phase === "loading") {
3387
- return /* @__PURE__ */ jsx12(Text12, { color: "cyan", children: "Loading..." });
3778
+ return /* @__PURE__ */ jsx13(Text13, { color: "cyan", children: "Loading..." });
3388
3779
  }
3389
3780
  if (!status) return null;
3390
3781
  const totalCount = status.backups.count + status.templates.count + status.scripts.count + status.logs.count;
3391
3782
  const totalSize = status.backups.totalSize + status.templates.totalSize + status.scripts.totalSize + status.logs.totalSize;
3392
- const statusDisplay = /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3393
- /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
3783
+ const statusDisplay = /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3784
+ /* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
3394
3785
  "\u25B8 ",
3395
3786
  APP_NAME,
3396
3787
  " status \u2014 ~/.",
3397
3788
  APP_NAME,
3398
3789
  "/"
3399
3790
  ] }),
3400
- /* @__PURE__ */ jsx12(Box10, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx12(
3791
+ /* @__PURE__ */ jsx13(Box11, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsx13(
3401
3792
  Table,
3402
3793
  {
3403
3794
  headers: ["Directory", "Count", "Size"],
@@ -3426,15 +3817,15 @@ var StatusView = ({ cleanup }) => {
3426
3817
  ]
3427
3818
  }
3428
3819
  ) }),
3429
- status.lastBackup && /* @__PURE__ */ jsxs12(Box10, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
3430
- /* @__PURE__ */ jsxs12(Text12, { children: [
3820
+ status.lastBackup && /* @__PURE__ */ jsxs13(Box11, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
3821
+ /* @__PURE__ */ jsxs13(Text13, { children: [
3431
3822
  "Latest backup: ",
3432
3823
  formatDate(status.lastBackup),
3433
3824
  " (",
3434
3825
  formatRelativeTime(status.lastBackup),
3435
3826
  ")"
3436
3827
  ] }),
3437
- status.oldestBackup && /* @__PURE__ */ jsxs12(Text12, { children: [
3828
+ status.oldestBackup && /* @__PURE__ */ jsxs13(Text13, { children: [
3438
3829
  "Oldest backup: ",
3439
3830
  formatDate(status.oldestBackup),
3440
3831
  " (",
@@ -3443,18 +3834,18 @@ var StatusView = ({ cleanup }) => {
3443
3834
  ] })
3444
3835
  ] })
3445
3836
  ] });
3446
- const escHint = (action) => /* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3837
+ const escHint = (action) => /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
3447
3838
  "Press ",
3448
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "ESC" }),
3839
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "ESC" }),
3449
3840
  " to ",
3450
3841
  action
3451
3842
  ] }) });
3452
3843
  if (phase === "display") {
3453
- return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3844
+ return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3454
3845
  statusDisplay,
3455
- /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
3456
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Actions" }),
3457
- /* @__PURE__ */ jsx12(
3846
+ /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
3847
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Actions" }),
3848
+ /* @__PURE__ */ jsx13(
3458
3849
  SelectInput3,
3459
3850
  {
3460
3851
  items: [
@@ -3502,11 +3893,11 @@ var StatusView = ({ cleanup }) => {
3502
3893
  value: "cancel"
3503
3894
  }
3504
3895
  ];
3505
- return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3896
+ return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3506
3897
  statusDisplay,
3507
- /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
3508
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Cleanup options" }),
3509
- /* @__PURE__ */ jsx12(
3898
+ /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
3899
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Cleanup options" }),
3900
+ /* @__PURE__ */ jsx13(
3510
3901
  SelectInput3,
3511
3902
  {
3512
3903
  items: cleanupItems,
@@ -3532,26 +3923,26 @@ var StatusView = ({ cleanup }) => {
3532
3923
  value: "done"
3533
3924
  }
3534
3925
  ];
3535
- return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3926
+ return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3536
3927
  statusDisplay,
3537
- /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", marginTop: 1, children: [
3538
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "\u25B8 Select backups to delete" }),
3539
- selectedForDeletion.length > 0 && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3928
+ /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", marginTop: 1, children: [
3929
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: "\u25B8 Select backups to delete" }),
3930
+ selectedForDeletion.length > 0 && /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
3540
3931
  " ",
3541
3932
  selectedForDeletion.length,
3542
3933
  " backup(s) selected (",
3543
3934
  formatBytes(selectedForDeletion.reduce((s, b) => s + b.size, 0)),
3544
3935
  ")"
3545
3936
  ] }),
3546
- /* @__PURE__ */ jsx12(SelectInput3, { items: selectItems, onSelect: handleSelectBackup })
3937
+ /* @__PURE__ */ jsx13(SelectInput3, { items: selectItems, onSelect: handleSelectBackup })
3547
3938
  ] }),
3548
3939
  escHint("go back")
3549
3940
  ] });
3550
3941
  }
3551
3942
  if (phase === "confirming") {
3552
- return /* @__PURE__ */ jsxs12(Box10, { flexDirection: "column", children: [
3553
- /* @__PURE__ */ jsx12(Text12, { children: cleanupMessage }),
3554
- /* @__PURE__ */ jsx12(
3943
+ return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3944
+ /* @__PURE__ */ jsx13(Text13, { children: cleanupMessage }),
3945
+ /* @__PURE__ */ jsx13(
3555
3946
  Confirm,
3556
3947
  {
3557
3948
  message: "Proceed?",
@@ -3562,24 +3953,24 @@ var StatusView = ({ cleanup }) => {
3562
3953
  ] });
3563
3954
  }
3564
3955
  if (phase === "done") {
3565
- return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713 Cleanup complete" }) });
3956
+ return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713 Cleanup complete" }) });
3566
3957
  }
3567
3958
  return null;
3568
3959
  };
3569
3960
  function registerStatusCommand(program2) {
3570
3961
  program2.command("status").description(`Show ~/.${APP_NAME}/ status summary`).option("--cleanup", "Interactive cleanup mode", false).action(async (opts) => {
3571
- const { waitUntilExit } = render8(/* @__PURE__ */ jsx12(StatusView, { cleanup: opts.cleanup }));
3962
+ const { waitUntilExit } = render9(/* @__PURE__ */ jsx13(StatusView, { cleanup: opts.cleanup }));
3572
3963
  await waitUntilExit();
3573
3964
  });
3574
3965
  }
3575
3966
 
3576
3967
  // src/commands/Wizard.tsx
3577
- import { copyFile as copyFile2, readFile as readFile5, rename, unlink, writeFile as writeFile4 } from "fs/promises";
3968
+ import { copyFile as copyFile3, readFile as readFile6, rename, unlink, writeFile as writeFile5 } from "fs/promises";
3578
3969
  import { join as join14 } from "path";
3579
- import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
3580
- import { render as render9 } from "ink";
3970
+ import { Box as Box12, Text as Text14, useApp as useApp9 } from "ink";
3971
+ import { render as render10 } from "ink";
3581
3972
  import Spinner3 from "ink-spinner";
3582
- import { useEffect as useEffect8, useState as useState9 } from "react";
3973
+ import { useEffect as useEffect9, useState as useState10 } from "react";
3583
3974
 
3584
3975
  // src/prompts/wizard-config.ts
3585
3976
  function generateConfigWizardPrompt(variables) {
@@ -3760,12 +4151,12 @@ async function scanHomeDirectory(options) {
3760
4151
  }
3761
4152
 
3762
4153
  // src/commands/Wizard.tsx
3763
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
4154
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
3764
4155
  var MAX_RETRIES2 = 3;
3765
4156
  async function restoreBackup2(configPath) {
3766
4157
  const bakPath = `${configPath}.bak`;
3767
4158
  if (await fileExists(bakPath)) {
3768
- await copyFile2(bakPath, configPath);
4159
+ await copyFile3(bakPath, configPath);
3769
4160
  }
3770
4161
  }
3771
4162
  async function runScanPhase() {
@@ -3791,7 +4182,7 @@ async function runValidationPhase(configPath) {
3791
4182
  console.log("\u26A0\uFE0F Config file was not created. Restored backup.");
3792
4183
  return;
3793
4184
  }
3794
- const content = await readFile5(configPath, "utf-8");
4185
+ const content = await readFile6(configPath, "utf-8");
3795
4186
  const parsed = parseYAML(content);
3796
4187
  const validation = validateConfig(parsed);
3797
4188
  if (!validation.valid) {
@@ -3810,14 +4201,14 @@ ${formatValidationErrors(validation.errors || [])}`
3810
4201
  }
3811
4202
  }
3812
4203
  var WizardView = ({ printMode }) => {
3813
- const { exit } = useApp8();
3814
- const [phase, setPhase] = useState9("init");
3815
- const [message, setMessage] = useState9("");
3816
- const [error, setError] = useState9(null);
3817
- const [prompt, setPrompt] = useState9("");
3818
- const [sessionId, setSessionId] = useState9(void 0);
3819
- const [attemptNumber, setAttemptNumber] = useState9(1);
3820
- useEffect8(() => {
4204
+ const { exit } = useApp9();
4205
+ const [phase, setPhase] = useState10("init");
4206
+ const [message, setMessage] = useState10("");
4207
+ const [error, setError] = useState10(null);
4208
+ const [prompt, setPrompt] = useState10("");
4209
+ const [sessionId, setSessionId] = useState10(void 0);
4210
+ const [attemptNumber, setAttemptNumber] = useState10(1);
4211
+ useEffect9(() => {
3821
4212
  (async () => {
3822
4213
  try {
3823
4214
  const configPath = join14(getAppDir(), CONFIG_FILENAME);
@@ -3887,7 +4278,7 @@ var WizardView = ({ printMode }) => {
3887
4278
  setPhase("writing");
3888
4279
  setMessage("Writing config.yml...");
3889
4280
  const tmpPath = `${configPath}.tmp`;
3890
- await writeFile4(tmpPath, yamlContent, "utf-8");
4281
+ await writeFile5(tmpPath, yamlContent, "utf-8");
3891
4282
  const verification = validateConfig(parseYAML(yamlContent));
3892
4283
  if (verification.valid) {
3893
4284
  await rename(tmpPath, configPath);
@@ -3931,37 +4322,37 @@ ${formatValidationErrors(validation.errors || [])}`
3931
4322
  }
3932
4323
  }
3933
4324
  if (error) {
3934
- return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
4325
+ return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs14(Text14, { color: "red", children: [
3935
4326
  "\u2717 ",
3936
4327
  error
3937
4328
  ] }) });
3938
4329
  }
3939
4330
  if (printMode && phase === "done") {
3940
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3941
- /* @__PURE__ */ jsx13(Text13, { bold: true, children: "Config Wizard Prompt (Copy and paste to your LLM):" }),
3942
- /* @__PURE__ */ jsx13(Box11, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "\u2500".repeat(60) }) }),
3943
- /* @__PURE__ */ jsx13(Text13, { children: prompt }),
3944
- /* @__PURE__ */ jsx13(Box11, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "\u2500".repeat(60) }) }),
3945
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "After getting the YAML response, save it to ~/.syncpoint/config.yml" })
4331
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4332
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Config Wizard Prompt (Copy and paste to your LLM):" }),
4333
+ /* @__PURE__ */ jsx14(Box12, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "\u2500".repeat(60) }) }),
4334
+ /* @__PURE__ */ jsx14(Text14, { children: prompt }),
4335
+ /* @__PURE__ */ jsx14(Box12, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "\u2500".repeat(60) }) }),
4336
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "After getting the YAML response, save it to ~/.syncpoint/config.yml" })
3946
4337
  ] });
3947
4338
  }
3948
4339
  if (phase === "done") {
3949
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3950
- /* @__PURE__ */ jsx13(Text13, { color: "green", children: message }),
3951
- /* @__PURE__ */ jsxs13(Box11, { marginTop: 1, children: [
3952
- /* @__PURE__ */ jsx13(Text13, { children: "Next steps:" }),
3953
- /* @__PURE__ */ jsx13(Text13, { children: " 1. Review your config: ~/.syncpoint/config.yml" }),
3954
- /* @__PURE__ */ jsx13(Text13, { children: " 2. Run: syncpoint backup" })
4340
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4341
+ /* @__PURE__ */ jsx14(Text14, { color: "green", children: message }),
4342
+ /* @__PURE__ */ jsxs14(Box12, { marginTop: 1, children: [
4343
+ /* @__PURE__ */ jsx14(Text14, { children: "Next steps:" }),
4344
+ /* @__PURE__ */ jsx14(Text14, { children: " 1. Review your config: ~/.syncpoint/config.yml" }),
4345
+ /* @__PURE__ */ jsx14(Text14, { children: " 2. Run: syncpoint backup" })
3955
4346
  ] })
3956
4347
  ] });
3957
4348
  }
3958
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", children: [
3959
- /* @__PURE__ */ jsxs13(Text13, { children: [
3960
- /* @__PURE__ */ jsx13(Text13, { color: "cyan", children: /* @__PURE__ */ jsx13(Spinner3, { type: "dots" }) }),
4349
+ return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
4350
+ /* @__PURE__ */ jsxs14(Text14, { children: [
4351
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: /* @__PURE__ */ jsx14(Spinner3, { type: "dots" }) }),
3961
4352
  " ",
3962
4353
  message
3963
4354
  ] }),
3964
- attemptNumber > 1 && /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
4355
+ attemptNumber > 1 && /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
3965
4356
  "Attempt ",
3966
4357
  attemptNumber,
3967
4358
  "/",
@@ -3977,7 +4368,7 @@ function registerWizardCommand(program2) {
3977
4368
  });
3978
4369
  cmd.action(async (opts) => {
3979
4370
  if (opts.print) {
3980
- const { waitUntilExit } = render9(/* @__PURE__ */ jsx13(WizardView, { printMode: true }));
4371
+ const { waitUntilExit } = render10(/* @__PURE__ */ jsx14(WizardView, { printMode: true }));
3981
4372
  await waitUntilExit();
3982
4373
  return;
3983
4374
  }
@@ -4020,6 +4411,7 @@ registerRestoreCommand(program);
4020
4411
  registerProvisionCommand(program);
4021
4412
  registerCreateTemplateCommand(program);
4022
4413
  registerListCommand(program);
4414
+ registerMigrateCommand(program);
4023
4415
  registerStatusCommand(program);
4024
4416
  registerHelpCommand(program);
4025
4417
  program.parseAsync(process.argv).catch((error) => {