@lumy-pack/syncpoint 0.0.1 → 0.0.2

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/index.mjs CHANGED
@@ -49,6 +49,14 @@ async function fileExists(filePath) {
49
49
  return false;
50
50
  }
51
51
  }
52
+ async function isDirectory(filePath) {
53
+ try {
54
+ const stats = await stat(filePath);
55
+ return stats.isDirectory();
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
52
60
 
53
61
  // src/constants.ts
54
62
  var APP_NAME = "syncpoint";
@@ -64,7 +72,6 @@ var LOGS_DIR = "logs";
64
72
  function getAppDir() {
65
73
  return join2(getHomeDir(), APP_DIR);
66
74
  }
67
- var APP_VERSION = "0.0.1";
68
75
  function getSubDir(sub) {
69
76
  return join2(getAppDir(), sub);
70
77
  }
@@ -72,8 +79,106 @@ function getSubDir(sub) {
72
79
  // src/schemas/ajv.ts
73
80
  import Ajv from "ajv";
74
81
  import addFormats from "ajv-formats";
82
+
83
+ // src/utils/pattern.ts
84
+ import micromatch from "micromatch";
85
+ function detectPatternType(pattern) {
86
+ if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 2) {
87
+ const inner = pattern.slice(1, -1);
88
+ const hasUnescapedSlash = /(?<!\\)\//.test(inner);
89
+ if (!hasUnescapedSlash) {
90
+ return "regex";
91
+ }
92
+ }
93
+ if (pattern.includes("*") || pattern.includes("?") || pattern.includes("{")) {
94
+ return "glob";
95
+ }
96
+ return "literal";
97
+ }
98
+ function parseRegexPattern(pattern) {
99
+ if (!pattern.startsWith("/") || !pattern.endsWith("/")) {
100
+ throw new Error(
101
+ `Invalid regex pattern format: ${pattern}. Must be enclosed in slashes like /pattern/`
102
+ );
103
+ }
104
+ const regexBody = pattern.slice(1, -1);
105
+ try {
106
+ return new RegExp(regexBody);
107
+ } catch (error) {
108
+ throw new Error(
109
+ `Invalid regex pattern: ${pattern}. ${error instanceof Error ? error.message : String(error)}`
110
+ );
111
+ }
112
+ }
113
+ function createExcludeMatcher(excludePatterns) {
114
+ if (excludePatterns.length === 0) {
115
+ return () => false;
116
+ }
117
+ const regexPatterns = [];
118
+ const globPatterns = [];
119
+ const literalPatterns = /* @__PURE__ */ new Set();
120
+ for (const pattern of excludePatterns) {
121
+ const type = detectPatternType(pattern);
122
+ if (type === "regex") {
123
+ try {
124
+ regexPatterns.push(parseRegexPattern(pattern));
125
+ } catch {
126
+ console.warn(`Skipping invalid regex pattern: ${pattern}`);
127
+ }
128
+ } else if (type === "glob") {
129
+ globPatterns.push(pattern);
130
+ } else {
131
+ literalPatterns.add(pattern);
132
+ }
133
+ }
134
+ return (filePath) => {
135
+ if (literalPatterns.has(filePath)) {
136
+ return true;
137
+ }
138
+ if (globPatterns.length > 0 && micromatch.isMatch(filePath, globPatterns, {
139
+ dot: true,
140
+ // Match dotfiles
141
+ matchBase: false
142
+ // Don't use basename matching, match full path
143
+ })) {
144
+ return true;
145
+ }
146
+ for (const regex of regexPatterns) {
147
+ if (regex.test(filePath)) {
148
+ return true;
149
+ }
150
+ }
151
+ return false;
152
+ };
153
+ }
154
+ function isValidPattern(pattern) {
155
+ if (typeof pattern !== "string" || pattern.length === 0) {
156
+ return false;
157
+ }
158
+ const type = detectPatternType(pattern);
159
+ if (type === "regex") {
160
+ try {
161
+ parseRegexPattern(pattern);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+ return true;
168
+ }
169
+
170
+ // src/schemas/ajv.ts
75
171
  var ajv = new Ajv({ allErrors: true });
76
172
  addFormats(ajv);
173
+ ajv.addKeyword({
174
+ keyword: "validPattern",
175
+ type: "string",
176
+ validate: function validate(schema, data) {
177
+ if (!schema) return true;
178
+ return isValidPattern(data);
179
+ },
180
+ errors: true
181
+ });
77
182
 
78
183
  // src/schemas/config.schema.ts
79
184
  var configSchema = {
@@ -86,11 +191,11 @@ var configSchema = {
86
191
  properties: {
87
192
  targets: {
88
193
  type: "array",
89
- items: { type: "string" }
194
+ items: { type: "string", validPattern: true }
90
195
  },
91
196
  exclude: {
92
197
  type: "array",
93
- items: { type: "string" }
198
+ items: { type: "string", validPattern: true }
94
199
  },
95
200
  filename: {
96
201
  type: "string",
@@ -114,11 +219,11 @@ var configSchema = {
114
219
  },
115
220
  additionalProperties: false
116
221
  };
117
- var validate = ajv.compile(configSchema);
222
+ var validate2 = ajv.compile(configSchema);
118
223
  function validateConfig(data) {
119
- const valid = validate(data);
224
+ const valid = validate2(data);
120
225
  if (valid) return { valid: true };
121
- const errors = validate.errors?.map(
226
+ const errors = validate2.errors?.map(
122
227
  (e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
123
228
  );
124
229
  return { valid: false, errors };
@@ -170,20 +275,16 @@ Run "syncpoint init" first.`
170
275
  const data = stripDangerousKeys(YAML.parse(raw));
171
276
  const result = validateConfig(data);
172
277
  if (!result.valid) {
173
- throw new Error(
174
- `Invalid config:
175
- ${(result.errors ?? []).join("\n")}`
176
- );
278
+ throw new Error(`Invalid config:
279
+ ${(result.errors ?? []).join("\n")}`);
177
280
  }
178
281
  return data;
179
282
  }
180
283
  async function saveConfig(config) {
181
284
  const result = validateConfig(config);
182
285
  if (!result.valid) {
183
- throw new Error(
184
- `Invalid config:
185
- ${(result.errors ?? []).join("\n")}`
186
- );
286
+ throw new Error(`Invalid config:
287
+ ${(result.errors ?? []).join("\n")}`);
187
288
  }
188
289
  const configPath = getConfigPath();
189
290
  await ensureDir(getAppDir());
@@ -223,11 +324,11 @@ async function initDefaultConfig() {
223
324
 
224
325
  // src/core/backup.ts
225
326
  import { readdir } from "fs/promises";
226
- import { join as join7, basename } from "path";
327
+ import { basename, join as join7 } from "path";
227
328
  import fg from "fast-glob";
228
329
 
229
330
  // src/utils/system.ts
230
- import { hostname as osHostname, platform, release, arch } from "os";
331
+ import { arch, hostname as osHostname, platform, release } from "os";
231
332
  function getHostname() {
232
333
  return osHostname();
233
334
  }
@@ -240,7 +341,7 @@ function getSystemInfo() {
240
341
  }
241
342
  function formatHostname(name) {
242
343
  const raw = name ?? getHostname();
243
- return raw.replace(/\s+/g, "-").replace(/\./g, "-").replace(/[^a-zA-Z0-9\-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
344
+ return raw.replace(/\s+/g, "-").replace(/\./g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
244
345
  }
245
346
 
246
347
  // src/utils/format.ts
@@ -332,7 +433,7 @@ var logger = {
332
433
 
333
434
  // src/core/metadata.ts
334
435
  import { createHash } from "crypto";
335
- import { readFile as readFile2, lstat } from "fs/promises";
436
+ import { lstat, readFile as readFile2 } from "fs/promises";
336
437
 
337
438
  // src/schemas/metadata.schema.ts
338
439
  var metadataSchema = {
@@ -398,23 +499,26 @@ var metadataSchema = {
398
499
  },
399
500
  additionalProperties: false
400
501
  };
401
- var validate2 = ajv.compile(metadataSchema);
502
+ var validate3 = ajv.compile(metadataSchema);
402
503
  function validateMetadata(data) {
403
- const valid = validate2(data);
504
+ const valid = validate3(data);
404
505
  if (valid) return { valid: true };
405
- const errors = validate2.errors?.map(
506
+ const errors = validate3.errors?.map(
406
507
  (e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
407
508
  );
408
509
  return { valid: false, errors };
409
510
  }
410
511
 
512
+ // src/version.ts
513
+ var VERSION = "0.0.2";
514
+
411
515
  // src/core/metadata.ts
412
516
  var METADATA_VERSION = "1.0.0";
413
517
  function createMetadata(files, config) {
414
518
  const totalSize = files.reduce((sum, f) => sum + f.size, 0);
415
519
  return {
416
520
  version: METADATA_VERSION,
417
- toolVersion: APP_VERSION,
521
+ toolVersion: VERSION,
418
522
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
419
523
  hostname: getHostname(),
420
524
  system: getSystemInfo(),
@@ -434,10 +538,8 @@ function parseMetadata(data) {
434
538
  const parsed = JSON.parse(str);
435
539
  const result = validateMetadata(parsed);
436
540
  if (!result.valid) {
437
- throw new Error(
438
- `Invalid metadata:
439
- ${(result.errors ?? []).join("\n")}`
440
- );
541
+ throw new Error(`Invalid metadata:
542
+ ${(result.errors ?? []).join("\n")}`);
441
543
  }
442
544
  return parsed;
443
545
  }
@@ -453,6 +555,9 @@ async function collectFileInfo(absolutePath, logicalPath) {
453
555
  type = "symlink";
454
556
  } else if (lstats.isDirectory()) {
455
557
  type = "directory";
558
+ throw new Error(
559
+ `Cannot collect file info for directory: ${logicalPath}. Directories should be converted to glob patterns before calling collectFileInfo().`
560
+ );
456
561
  }
457
562
  let hash;
458
563
  if (lstats.isSymbolicLink()) {
@@ -480,7 +585,10 @@ async function createArchive(files, outputPath) {
480
585
  const fileNames = [];
481
586
  for (const file of files) {
482
587
  const targetPath = join6(tmpDir, file.name);
483
- const parentDir = join6(tmpDir, file.name.split("/").slice(0, -1).join("/"));
588
+ const parentDir = join6(
589
+ tmpDir,
590
+ file.name.split("/").slice(0, -1).join("/")
591
+ );
484
592
  if (parentDir !== tmpDir) {
485
593
  await mkdir3(parentDir, { recursive: true });
486
594
  }
@@ -557,18 +665,48 @@ function isSensitiveFile(filePath) {
557
665
  async function scanTargets(config) {
558
666
  const found = [];
559
667
  const missing = [];
668
+ const isExcluded = createExcludeMatcher(config.backup.exclude);
560
669
  for (const target of config.backup.targets) {
561
670
  const expanded = expandTilde(target);
562
- if (expanded.includes("*") || expanded.includes("?") || expanded.includes("{")) {
671
+ const patternType = detectPatternType(expanded);
672
+ if (patternType === "regex") {
673
+ try {
674
+ const regex = parseRegexPattern(expanded);
675
+ const homeDir = expandTilde("~/");
676
+ const homeDirNormalized = homeDir.endsWith("/") ? homeDir : `${homeDir}/`;
677
+ const allFiles = await fg(`${homeDirNormalized}**`, {
678
+ dot: true,
679
+ absolute: true,
680
+ onlyFiles: true,
681
+ deep: 5
682
+ // Limit depth for performance
683
+ });
684
+ for (const match of allFiles) {
685
+ if (regex.test(match) && !isExcluded(match)) {
686
+ const entry = await collectFileInfo(match, match);
687
+ found.push(entry);
688
+ }
689
+ }
690
+ } catch (error) {
691
+ logger.warn(
692
+ `Invalid regex pattern "${target}": ${error instanceof Error ? error.message : String(error)}`
693
+ );
694
+ }
695
+ } else if (patternType === "glob") {
696
+ const globExcludes = config.backup.exclude.filter(
697
+ (p) => detectPatternType(p) === "glob"
698
+ );
563
699
  const matches = await fg(expanded, {
564
700
  dot: true,
565
701
  absolute: true,
566
- ignore: config.backup.exclude,
702
+ ignore: globExcludes,
567
703
  onlyFiles: true
568
704
  });
569
705
  for (const match of matches) {
570
- const entry = await collectFileInfo(match, match);
571
- found.push(entry);
706
+ if (!isExcluded(match)) {
707
+ const entry = await collectFileInfo(match, match);
708
+ found.push(entry);
709
+ }
572
710
  }
573
711
  } else {
574
712
  const absPath = resolveTargetPath(target);
@@ -577,16 +715,43 @@ async function scanTargets(config) {
577
715
  missing.push(target);
578
716
  continue;
579
717
  }
580
- const entry = await collectFileInfo(absPath, absPath);
581
- if (entry.size > LARGE_FILE_THRESHOLD) {
582
- logger.warn(
583
- `Large file (>${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB): ${target}`
718
+ const isDir = await isDirectory(absPath);
719
+ if (isDir) {
720
+ const dirGlob = `${expanded}/**/*`;
721
+ const globExcludes = config.backup.exclude.filter(
722
+ (p) => detectPatternType(p) === "glob"
584
723
  );
724
+ const matches = await fg(dirGlob, {
725
+ dot: true,
726
+ absolute: true,
727
+ ignore: globExcludes,
728
+ onlyFiles: true
729
+ // Only include files, not subdirectories
730
+ });
731
+ for (const match of matches) {
732
+ if (!isExcluded(match)) {
733
+ const entry = await collectFileInfo(match, match);
734
+ found.push(entry);
735
+ }
736
+ }
737
+ if (matches.length === 0) {
738
+ logger.warn(`Directory is empty or fully excluded: ${target}`);
739
+ }
740
+ } else {
741
+ if (isExcluded(absPath)) {
742
+ continue;
743
+ }
744
+ const entry = await collectFileInfo(absPath, absPath);
745
+ if (entry.size > LARGE_FILE_THRESHOLD) {
746
+ logger.warn(
747
+ `Large file (>${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB): ${target}`
748
+ );
749
+ }
750
+ if (isSensitiveFile(absPath)) {
751
+ logger.warn(`Sensitive file detected: ${target}`);
752
+ }
753
+ found.push(entry);
585
754
  }
586
- if (isSensitiveFile(absPath)) {
587
- logger.warn(`Sensitive file detected: ${target}`);
588
- }
589
- found.push(entry);
590
755
  }
591
756
  }
592
757
  return { found, missing };
@@ -612,8 +777,10 @@ async function collectScripts() {
612
777
  }
613
778
  async function createBackup(config, options = {}) {
614
779
  const { found, missing } = await scanTargets(config);
615
- for (const m of missing) {
616
- logger.warn(`File not found, skipping: ${m}`);
780
+ if (options.verbose && missing.length > 0) {
781
+ for (const m of missing) {
782
+ logger.warn(`File not found, skipping: ${m}`);
783
+ }
617
784
  }
618
785
  let allFiles = [...found];
619
786
  if (config.scripts.includeInBackup) {
@@ -652,7 +819,7 @@ async function createBackup(config, options = {}) {
652
819
 
653
820
  // src/core/restore.ts
654
821
  import { copyFile, lstat as lstat2, readdir as readdir2, stat as stat2 } from "fs/promises";
655
- import { join as join8, dirname as dirname2 } from "path";
822
+ import { dirname as dirname2, join as join8 } from "path";
656
823
  async function getBackupList(config) {
657
824
  const backupDir = config?.backup.destination ? resolveTargetPath(config.backup.destination) : getSubDir(BACKUPS_DIR);
658
825
  const exists = await fileExists(backupDir);
@@ -842,11 +1009,11 @@ var templateSchema = {
842
1009
  },
843
1010
  additionalProperties: false
844
1011
  };
845
- var validate3 = ajv.compile(templateSchema);
1012
+ var validate4 = ajv.compile(templateSchema);
846
1013
  function validateTemplate(data) {
847
- const valid = validate3(data);
1014
+ const valid = validate4(data);
848
1015
  if (valid) return { valid: true };
849
- const errors = validate3.errors?.map(
1016
+ const errors = validate4.errors?.map(
850
1017
  (e) => `${e.instancePath || "/"} ${e.message ?? "unknown error"}`
851
1018
  );
852
1019
  return { valid: false, errors };
@@ -930,7 +1097,9 @@ function execAsync(command) {
930
1097
  }
931
1098
  async function evaluateSkipIf(command, stepName) {
932
1099
  if (containsRemoteScriptPattern(command)) {
933
- throw new Error(`Blocked dangerous remote script pattern in skip_if: ${stepName}`);
1100
+ throw new Error(
1101
+ `Blocked dangerous remote script pattern in skip_if: ${stepName}`
1102
+ );
934
1103
  }
935
1104
  try {
936
1105
  await execAsync(command);
@@ -942,7 +1111,9 @@ async function evaluateSkipIf(command, stepName) {
942
1111
  async function executeStep(step) {
943
1112
  const startTime = Date.now();
944
1113
  if (containsRemoteScriptPattern(step.command)) {
945
- throw new Error(`Blocked dangerous remote script pattern in command: ${step.name}`);
1114
+ throw new Error(
1115
+ `Blocked dangerous remote script pattern in command: ${step.name}`
1116
+ );
946
1117
  }
947
1118
  if (step.skip_if) {
948
1119
  const shouldSkip = await evaluateSkipIf(step.skip_if, step.name);
@@ -996,9 +1167,7 @@ async function* runProvision(templatePath, options = {}) {
996
1167
  const result = await executeStep(step);
997
1168
  yield result;
998
1169
  if (result.status === "failed" && !step.continue_on_error) {
999
- logger.error(
1000
- `Step "${step.name}" failed. Stopping provisioning.`
1001
- );
1170
+ logger.error(`Step "${step.name}" failed. Stopping provisioning.`);
1002
1171
  return;
1003
1172
  }
1004
1173
  }
@@ -0,0 +1,9 @@
1
+ import type { FileStructure } from '../utils/file-scanner.js';
2
+ export interface ConfigPromptVariables {
3
+ fileStructure: FileStructure;
4
+ defaultConfig: string;
5
+ }
6
+ /**
7
+ * Generate the LLM prompt for config wizard
8
+ */
9
+ export declare function generateConfigWizardPrompt(variables: ConfigPromptVariables): string;
@@ -0,0 +1,7 @@
1
+ export interface TemplatePromptVariables {
2
+ exampleTemplate: string;
3
+ }
4
+ /**
5
+ * Generate the LLM prompt for template creation wizard
6
+ */
7
+ export declare function generateTemplateWizardPrompt(variables: TemplatePromptVariables): string;
@@ -1,3 +1,3 @@
1
- import Ajv from "ajv";
1
+ import Ajv from 'ajv';
2
2
  declare const ajv: Ajv;
3
3
  export { ajv };
@@ -0,0 +1,29 @@
1
+ export interface ClaudeCodeResult {
2
+ success: boolean;
3
+ output: string;
4
+ error?: string;
5
+ sessionId?: string;
6
+ }
7
+ /**
8
+ * Check if Claude Code CLI is available
9
+ */
10
+ export declare function isClaudeCodeAvailable(): Promise<boolean>;
11
+ /**
12
+ * Invoke Claude Code in edit mode with a prompt
13
+ */
14
+ export declare function invokeClaudeCode(prompt: string, options?: {
15
+ sessionId?: string;
16
+ timeout?: number;
17
+ }): Promise<ClaudeCodeResult>;
18
+ /**
19
+ * Resume a Claude Code session with additional context
20
+ */
21
+ export declare function resumeClaudeCodeSession(sessionId: string, prompt: string, options?: {
22
+ timeout?: number;
23
+ }): Promise<ClaudeCodeResult>;
24
+ /**
25
+ * Invoke Claude Code in interactive mode
26
+ * User can directly interact with Claude Code UI
27
+ * @param prompt - Context prompt with file structure
28
+ */
29
+ export declare function invokeClaudeCodeInteractive(prompt: string): Promise<ClaudeCodeResult>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Central command registry for syncpoint CLI
3
+ * This ensures consistency between custom help and Commander.js --help
4
+ */
5
+ export interface CommandOption {
6
+ flag: string;
7
+ description: string;
8
+ default?: string;
9
+ }
10
+ export interface CommandArgument {
11
+ name: string;
12
+ description: string;
13
+ required: boolean;
14
+ }
15
+ export interface CommandInfo {
16
+ name: string;
17
+ description: string;
18
+ usage: string;
19
+ arguments?: CommandArgument[];
20
+ options?: CommandOption[];
21
+ examples?: string[];
22
+ }
23
+ export declare const COMMANDS: Record<string, CommandInfo>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Format validation errors for human-readable feedback to LLM
3
+ */
4
+ export declare function formatValidationErrors(errors: string[]): string;
5
+ /**
6
+ * Create a retry prompt with validation error context
7
+ */
8
+ export declare function createRetryPrompt(originalPrompt: string, errors: string[], attemptNumber: number): string;
9
+ /**
10
+ * Format error for display in terminal UI
11
+ */
12
+ export declare function formatErrorForDisplay(error: string | Error): string;
13
+ /**
14
+ * Create user-friendly error messages
15
+ */
16
+ export declare function createUserFriendlyError(context: string, error: unknown): string;
@@ -0,0 +1,25 @@
1
+ export interface FileCategory {
2
+ category: string;
3
+ files: string[];
4
+ }
5
+ export interface FileStructure {
6
+ homeDir: string;
7
+ categories: FileCategory[];
8
+ totalFiles: number;
9
+ }
10
+ /**
11
+ * Scan home directory and categorize files for backup suggestions
12
+ */
13
+ export declare function scanHomeDirectory(options?: {
14
+ maxDepth?: number;
15
+ maxFiles?: number;
16
+ ignorePatterns?: string[];
17
+ }): Promise<FileStructure>;
18
+ /**
19
+ * Convert file structure to JSON format for LLM prompt
20
+ */
21
+ export declare function fileStructureToJSON(structure: FileStructure): string;
22
+ /**
23
+ * Get a list of recommended backup targets based on scan results
24
+ */
25
+ export declare function getRecommendedTargets(structure: FileStructure): string[];
@@ -19,4 +19,9 @@ export declare function ensureDir(dirPath: string): Promise<void>;
19
19
  * Check if a file exists.
20
20
  */
21
21
  export declare function fileExists(filePath: string): Promise<boolean>;
22
+ /**
23
+ * Check if a path is a directory.
24
+ * Returns false if path doesn't exist or is not a directory.
25
+ */
26
+ export declare function isDirectory(filePath: string): Promise<boolean>;
22
27
  export declare function isInsideDir(filePath: string, dir: string): boolean;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Pattern type classification for targets and excludes.
3
+ */
4
+ export type PatternType = 'literal' | 'glob' | 'regex';
5
+ /**
6
+ * Detect the type of a pattern string.
7
+ *
8
+ * Rules:
9
+ * - Regex: Starts and ends with `/`, and the content between doesn't contain unescaped `/`
10
+ * Example: `/\.conf$/`, `/test/` (simple)
11
+ * - Glob: Contains glob metacharacters: `*`, `?`, `{`
12
+ * Example: `*.conf`, `~/.config/{a,b}`
13
+ * - Literal: Everything else
14
+ * Example: `~/.zshrc`, `/usr/local/bin`
15
+ *
16
+ * Special handling for absolute paths vs regex:
17
+ * - `/usr/local/bin` is literal (multiple `/` inside, not at boundaries)
18
+ * - `/test/` is regex (starts and ends with `/`, single segment inside)
19
+ * - `/\.conf$/` is regex (starts and ends with `/`, no unescaped `/` inside)
20
+ */
21
+ export declare function detectPatternType(pattern: string): PatternType;
22
+ /**
23
+ * Parse a regex pattern string (e.g., `/\.conf$/`) into a RegExp object.
24
+ *
25
+ * @throws {Error} If the pattern is invalid regex syntax
26
+ */
27
+ export declare function parseRegexPattern(pattern: string): RegExp;
28
+ /**
29
+ * Create an optimized exclude matcher function.
30
+ *
31
+ * This function pre-compiles all patterns for efficient repeated matching.
32
+ * It handles three pattern types: glob, regex, and literal.
33
+ *
34
+ * @param excludePatterns - Array of exclude pattern strings
35
+ * @returns A function that tests if a file path should be excluded
36
+ */
37
+ export declare function createExcludeMatcher(excludePatterns: string[]): (filePath: string) => boolean;
38
+ /**
39
+ * Validate that a pattern string is well-formed.
40
+ *
41
+ * Used for schema validation at config load time.
42
+ *
43
+ * @param pattern - Pattern string to validate
44
+ * @returns true if valid, false otherwise
45
+ */
46
+ export declare function isValidPattern(pattern: string): boolean;
@@ -65,7 +65,7 @@ export interface RestorePlan {
65
65
  }
66
66
  export interface RestoreAction {
67
67
  path: string;
68
- action: "overwrite" | "skip" | "create";
68
+ action: 'overwrite' | 'skip' | 'create';
69
69
  currentSize?: number;
70
70
  backupSize?: number;
71
71
  reason: string;
@@ -76,7 +76,7 @@ export interface ProvisionResult {
76
76
  }
77
77
  export interface StepResult {
78
78
  name: string;
79
- status: "success" | "skipped" | "failed" | "running" | "pending";
79
+ status: 'success' | 'skipped' | 'failed' | 'running' | 'pending';
80
80
  duration?: number;
81
81
  error?: string;
82
82
  output?: string;
@@ -84,6 +84,7 @@ export interface StepResult {
84
84
  export interface BackupOptions {
85
85
  dryRun?: boolean;
86
86
  tag?: string;
87
+ verbose?: boolean;
87
88
  }
88
89
  export interface RestoreOptions {
89
90
  dryRun?: boolean;
@@ -121,7 +122,7 @@ export interface StatusInfo {
121
122
  oldestBackup?: Date;
122
123
  }
123
124
  export interface CleanupPolicy {
124
- type: "keep-recent" | "older-than" | "select";
125
+ type: 'keep-recent' | 'older-than' | 'select';
125
126
  count?: number;
126
127
  days?: number;
127
128
  indices?: number[];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Extract YAML content from LLM response
3
+ * Handles markdown code blocks and pure YAML
4
+ * Rejects plain text and scalar values
5
+ */
6
+ export declare function extractYAML(response: string): string | null;
7
+ /**
8
+ * Parse YAML string to object
9
+ */
10
+ export declare function parseYAML<T = unknown>(yamlString: string): T;
11
+ /**
12
+ * Extract and parse YAML from LLM response
13
+ */
14
+ export declare function extractAndParseYAML<T = unknown>(response: string): T | null;
15
+ /**
16
+ * Extract config-specific YAML (requires 'backup:' key)
17
+ * More strict validation for Syncpoint config files
18
+ */
19
+ export declare function extractConfigYAML(response: string): string | null;