@lumy-pack/syncpoint 0.0.7 → 0.0.9

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Vincent K. Kelvin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -73,6 +73,7 @@ The fastest and easiest way to get started. AI automatically generates your conf
73
73
  ```
74
74
 
75
75
  **Tip:** If you don't have Claude Code, use `--print` to get the prompt for any LLM:
76
+
76
77
  ```bash
77
78
  npx @lumy-pack/syncpoint wizard --print
78
79
  ```
@@ -100,8 +101,8 @@ If you prefer to manually edit your configuration file, use this approach.
100
101
  - ~/.gitconfig
101
102
  - ~/.ssh/config
102
103
  exclude:
103
- - "**/*.swp"
104
- filename: "{hostname}_{datetime}"
104
+ - '**/*.swp'
105
+ filename: '{hostname}_{datetime}'
105
106
  ```
106
107
 
107
108
  3. **Create your first backup**
@@ -137,6 +138,7 @@ npx @lumy-pack/syncpoint provision my-dev-setup
137
138
  Initialize the syncpoint directory structure and create default configuration.
138
139
 
139
140
  **What it does:**
141
+
140
142
  - Creates `~/.syncpoint/` directory structure
141
143
  - Sets up subdirectories: `backups/`, `templates/`, `scripts/`, `logs/`
142
144
  - Generates default `config.yml`
@@ -155,6 +157,7 @@ npx @lumy-pack/syncpoint init
155
157
  Interactive LLM-powered wizard to generate personalized `config.yml` based on your home directory.
156
158
 
157
159
  **What it does:**
160
+
158
161
  1. Scans your home directory for common configuration files
159
162
  2. Categorizes files (shell configs, git, SSH, application configs)
160
163
  3. Invokes Claude Code to generate customized backup configuration
@@ -164,8 +167,8 @@ Interactive LLM-powered wizard to generate personalized `config.yml` based on yo
164
167
 
165
168
  **Options:**
166
169
 
167
- | Option | Description |
168
- |--------|-------------|
170
+ | Option | Description |
171
+ | ------------- | ----------------------------------------------------------------- |
169
172
  | `-p, --print` | Print prompt for manual LLM usage instead of invoking Claude Code |
170
173
 
171
174
  **Usage:**
@@ -179,10 +182,12 @@ npx @lumy-pack/syncpoint wizard --print
179
182
  ```
180
183
 
181
184
  **Requirements:**
185
+
182
186
  - Claude Code CLI must be installed for default mode
183
187
  - Use `--print` mode if Claude Code is not available
184
188
 
185
189
  **Validation:**
190
+
186
191
  - Automatic AJV schema validation
187
192
  - Retry loop with error feedback to LLM
188
193
  - Session resume preserves conversation context
@@ -194,6 +199,7 @@ npx @lumy-pack/syncpoint wizard --print
194
199
  Interactive LLM-powered wizard to create custom provisioning templates.
195
200
 
196
201
  **What it does:**
202
+
197
203
  1. Guides you through defining provisioning requirements
198
204
  2. Invokes Claude Code to generate template YAML
199
205
  3. Validates template structure with automatic retry (max 3 attempts)
@@ -202,8 +208,8 @@ Interactive LLM-powered wizard to create custom provisioning templates.
202
208
 
203
209
  **Options:**
204
210
 
205
- | Option | Description |
206
- |--------|-------------|
211
+ | Option | Description |
212
+ | ------------- | ----------------------------------------------------------------- |
207
213
  | `-p, --print` | Print prompt for manual LLM usage instead of invoking Claude Code |
208
214
 
209
215
  **Usage:**
@@ -220,6 +226,7 @@ npx @lumy-pack/syncpoint create-template --print
220
226
  ```
221
227
 
222
228
  **Template Fields:**
229
+
223
230
  - `name` (required) — Template name
224
231
  - `description` (optional) — Template description
225
232
  - `steps` (required) — Array of provisioning steps
@@ -227,6 +234,7 @@ npx @lumy-pack/syncpoint create-template --print
227
234
  - `sudo` (optional) — Whether sudo privilege is required
228
235
 
229
236
  **Step Fields:**
237
+
230
238
  - `name` (required) — Step name
231
239
  - `command` (required) — Shell command to execute
232
240
  - `description` (optional) — Step description
@@ -240,6 +248,7 @@ npx @lumy-pack/syncpoint create-template --print
240
248
  Create a compressed backup archive of your configuration files.
241
249
 
242
250
  **What it does:**
251
+
243
252
  1. Scans configured target files and directories
244
253
  2. Applies glob patterns and exclusions
245
254
  3. Warns about large files (>10MB) and sensitive files (SSH keys, certificates)
@@ -249,10 +258,10 @@ Create a compressed backup archive of your configuration files.
249
258
 
250
259
  **Options:**
251
260
 
252
- | Option | Description |
253
- |--------|-------------|
254
- | `--dry-run` | Preview files to be backed up without creating archive |
255
- | `--tag <name>` | Add custom tag to backup filename |
261
+ | Option | Description |
262
+ | -------------- | ------------------------------------------------------ |
263
+ | `--dry-run` | Preview files to be backed up without creating archive |
264
+ | `--tag <name>` | Add custom tag to backup filename |
256
265
 
257
266
  **Usage:**
258
267
 
@@ -278,6 +287,7 @@ Backups are saved to `~/.syncpoint/backups/` (or custom destination) with filena
278
287
  Restore configuration files from a backup archive.
279
288
 
280
289
  **What it does:**
290
+
281
291
  1. Lists available backups (if no filename provided)
282
292
  2. Generates restore plan by comparing file hashes:
283
293
  - `create` — File doesn't exist locally
@@ -289,8 +299,8 @@ Restore configuration files from a backup archive.
289
299
 
290
300
  **Options:**
291
301
 
292
- | Option | Description |
293
- |--------|-------------|
302
+ | Option | Description |
303
+ | ----------- | -------------------------------------------- |
294
304
  | `--dry-run` | Show restore plan without actually restoring |
295
305
 
296
306
  **Usage:**
@@ -307,6 +317,7 @@ npx @lumy-pack/syncpoint restore --dry-run
307
317
  ```
308
318
 
309
319
  **Safety Features:**
320
+
310
321
  - Automatic safety backup before any file is overwritten
311
322
  - Hash-based comparison to skip identical files
312
323
  - Symlink validation to prevent directory traversal attacks
@@ -318,6 +329,7 @@ npx @lumy-pack/syncpoint restore --dry-run
318
329
  Run template-based machine provisioning to install software and configure your system.
319
330
 
320
331
  **What it does:**
332
+
321
333
  1. Loads template YAML from `~/.syncpoint/templates/` (by name) or from a custom path (with `--file`)
322
334
  2. Validates template structure and security
323
335
  3. Checks for sudo requirement (prompts if needed)
@@ -328,11 +340,11 @@ Run template-based machine provisioning to install software and configure your s
328
340
 
329
341
  **Options:**
330
342
 
331
- | Option | Description |
332
- |--------|-------------|
343
+ | Option | Description |
344
+ | ------------------- | ---------------------------------------------------- |
333
345
  | `-f, --file <path>` | Path to template file (alternative to template name) |
334
- | `--dry-run` | Show execution plan without running commands |
335
- | `--skip-restore` | Skip automatic config restore after provisioning |
346
+ | `--dry-run` | Show execution plan without running commands |
347
+ | `--skip-restore` | Skip automatic config restore after provisioning |
336
348
 
337
349
  **Usage:**
338
350
 
@@ -357,12 +369,14 @@ npx @lumy-pack/syncpoint provision -f ./template.yml --dry-run --skip-restore
357
369
  ```
358
370
 
359
371
  **Path Resolution:**
372
+
360
373
  - Supports absolute paths: `/path/to/template.yml`
361
374
  - Supports relative paths: `./template.yml`, `../templates/setup.yaml`
362
375
  - Supports tilde expansion: `~/templates/custom.yml`
363
376
  - Must have `.yml` or `.yaml` extension
364
377
 
365
378
  **Security:**
379
+
366
380
  - Blocks dangerous remote script patterns (`curl | bash`, `wget | sh`)
367
381
  - Sanitizes error output to mask sensitive paths and credentials
368
382
  - Validates all templates against schema
@@ -375,6 +389,7 @@ npx @lumy-pack/syncpoint provision -f ./template.yml --dry-run --skip-restore
375
389
  Browse and manage backups and templates interactively.
376
390
 
377
391
  **What it does:**
392
+
378
393
  - Displays interactive menu to browse backups or templates
379
394
  - Shows detailed metadata (size, date, file count, description)
380
395
  - Allows safe deletion of backups with confirmation
@@ -392,6 +407,7 @@ npx @lumy-pack/syncpoint list templates
392
407
  ```
393
408
 
394
409
  **Navigation:**
410
+
395
411
  - Use arrow keys to select items
396
412
  - Press Enter to view details
397
413
  - Press ESC to go back
@@ -404,6 +420,7 @@ npx @lumy-pack/syncpoint list templates
404
420
  Show status summary and manage cleanup of `~/.syncpoint/` directory.
405
421
 
406
422
  **What it does:**
423
+
407
424
  - Scans all subdirectories and calculates statistics
408
425
  - Displays file counts and total sizes
409
426
  - Shows backup timeline (newest and oldest)
@@ -411,8 +428,8 @@ Show status summary and manage cleanup of `~/.syncpoint/` directory.
411
428
 
412
429
  **Options:**
413
430
 
414
- | Option | Description |
415
- |--------|-------------|
431
+ | Option | Description |
432
+ | ----------- | ------------------------------ |
416
433
  | `--cleanup` | Enter interactive cleanup mode |
417
434
 
418
435
  **Usage:**
@@ -426,6 +443,7 @@ npx @lumy-pack/syncpoint status --cleanup
426
443
  ```
427
444
 
428
445
  **Cleanup Strategies:**
446
+
429
447
  - Keep only 5 most recent backups
430
448
  - Remove backups older than 30 days
431
449
  - Delete all log files
@@ -457,14 +475,14 @@ backup:
457
475
  # (Required) Patterns to exclude from backup
458
476
  # Supports glob and regex patterns
459
477
  exclude:
460
- - "**/*.swp"
461
- - "**/.DS_Store"
462
- - "**/node_modules"
478
+ - '**/*.swp'
479
+ - '**/.DS_Store'
480
+ - '**/node_modules'
463
481
  # Example regex: "/\\.bak$/" excludes all .bak files
464
482
 
465
483
  # (Required) Backup filename pattern
466
484
  # Available placeholders: {hostname}, {date}, {time}, {datetime}, {tag}
467
- filename: "{hostname}_{datetime}"
485
+ filename: '{hostname}_{datetime}'
468
486
 
469
487
  # (Optional) Custom backup destination
470
488
  # Default: ~/.syncpoint/backups/
@@ -478,13 +496,13 @@ scripts:
478
496
 
479
497
  ### Filename Placeholders
480
498
 
481
- | Placeholder | Example | Description |
482
- |-------------|---------|-------------|
483
- | `{hostname}` | `macbook-pro` | System hostname |
484
- | `{date}` | `2024-01-15` | Current date (YYYY-MM-DD) |
485
- | `{time}` | `14-30-00` | Current time (HH-MM-SS) |
486
- | `{datetime}` | `2024-01-15_14-30-00` | Combined date and time |
487
- | `{tag}` | `before-upgrade` | Custom tag from `--tag` option |
499
+ | Placeholder | Example | Description |
500
+ | ------------ | --------------------- | ------------------------------ |
501
+ | `{hostname}` | `macbook-pro` | System hostname |
502
+ | `{date}` | `2024-01-15` | Current date (YYYY-MM-DD) |
503
+ | `{time}` | `14-30-00` | Current time (HH-MM-SS) |
504
+ | `{datetime}` | `2024-01-15_14-30-00` | Combined date and time |
505
+ | `{tag}` | `before-upgrade` | Custom tag from `--tag` option |
488
506
 
489
507
  ### Pattern Types
490
508
 
@@ -495,6 +513,7 @@ Syncpoint supports three types of patterns for `targets` and `exclude` fields:
495
513
  Direct file or directory paths. Tilde (`~`) is automatically expanded to home directory.
496
514
 
497
515
  **Examples:**
516
+
498
517
  - `~/.zshrc` — Specific file in home directory
499
518
  - `/etc/hosts` — Absolute path
500
519
  - `~/.ssh/config` — Nested file
@@ -504,6 +523,7 @@ Direct file or directory paths. Tilde (`~`) is automatically expanded to home di
504
523
  Wildcard patterns for matching multiple files. Uses standard glob syntax.
505
524
 
506
525
  **Examples:**
526
+
507
527
  - `*.conf` — All .conf files in current directory
508
528
  - `~/.config/*.yml` — All .yml files in ~/.config/
509
529
  - `**/*.toml` — All .toml files recursively
@@ -518,17 +538,20 @@ Regular expressions for advanced pattern matching. Must be enclosed in forward s
518
538
  **Format:** `/pattern/` (e.g., `/\.conf$/`)
519
539
 
520
540
  **Examples:**
541
+
521
542
  - `/\.conf$/` — Files ending with .conf
522
543
  - `/\.toml$/` — Files ending with .toml
523
544
  - `/\.(bak|tmp)$/` — Files ending with .bak or .tmp
524
545
  - `/^\.config\//` — Files starting with .config/
525
546
 
526
547
  **Limitations:**
548
+
527
549
  - Regex targets scan home directory (`~/`) only
528
550
  - Maximum depth: 5 levels for performance
529
551
  - No unescaped forward slashes in pattern body
530
552
 
531
553
  **When to use regex:**
554
+
532
555
  - Complex extension matching: `/\.(conf|toml|yaml)$/`
533
556
  - Pattern-based exclusions: `/\.(bak|tmp|cache)$/`
534
557
  - Path prefix/suffix matching
@@ -547,12 +570,12 @@ backup:
547
570
  - ~/Documents/notes
548
571
 
549
572
  exclude:
550
- - "**/*.swp"
551
- - "**/*.tmp"
552
- - "**/.DS_Store"
553
- - "**/*cache*"
573
+ - '**/*.swp'
574
+ - '**/*.tmp'
575
+ - '**/.DS_Store'
576
+ - '**/*cache*'
554
577
 
555
- filename: "{hostname}_{date}_{tag}"
578
+ filename: '{hostname}_{date}_{tag}'
556
579
  destination: ~/Dropbox/backups
557
580
 
558
581
  scripts:
@@ -582,10 +605,10 @@ sudo: boolean
582
605
 
583
606
  # (Required) List of provisioning steps
584
607
  steps:
585
- - name: string # (Required) Step name
586
- description: string # (Optional) Step description
587
- command: string # (Required) Shell command to execute
588
- skip_if: string # (Optional) Skip if this command succeeds
608
+ - name: string # (Required) Step name
609
+ description: string # (Optional) Step description
610
+ command: string # (Required) Shell command to execute
611
+ skip_if: string # (Optional) Skip if this command succeeds
589
612
  continue_on_error: boolean # (Optional) Continue on failure (default: false)
590
613
  ```
591
614
 
@@ -671,6 +694,7 @@ After initialization, syncpoint creates the following structure:
671
694
  ### Backup Archive Contents
672
695
 
673
696
  Each backup archive contains:
697
+
674
698
  - Your configuration files in their relative paths
675
699
  - `_metadata.json` with backup information:
676
700
  - File hashes for comparison
@@ -810,6 +834,7 @@ Syncpoint includes multiple security layers to protect your data and system:
810
834
  **Claude Code CLI not found**
811
835
 
812
836
  If you see "Claude Code CLI not found" error:
837
+
813
838
  1. Install Claude Code CLI: Visit [claude.ai/code](https://claude.ai/code) for installation instructions
814
839
  2. Or use `--print` mode to get the prompt and use it with your preferred LLM
815
840
  3. Verify installation: `claude --version`
@@ -817,6 +842,7 @@ If you see "Claude Code CLI not found" error:
817
842
  **Validation errors after LLM generation**
818
843
 
819
844
  The wizard automatically retries up to 3 times when validation fails:
845
+
820
846
  - Each retry includes error feedback to help the LLM correct the issues
821
847
  - If all retries fail, check the validation error messages
822
848
  - Common issues:
@@ -827,6 +853,7 @@ The wizard automatically retries up to 3 times when validation fails:
827
853
  **Print mode usage**
828
854
 
829
855
  Use `--print` mode when Claude Code is not available:
856
+
830
857
  ```bash
831
858
  # Get the prompt
832
859
  npx @lumy-pack/syncpoint wizard --print > prompt.txt
@@ -838,6 +865,7 @@ npx @lumy-pack/syncpoint wizard --print > prompt.txt
838
865
  **Session context lost**
839
866
 
840
867
  The wizard preserves session context across retries using Claude Code's session management. If context is lost:
868
+
841
869
  - The wizard will start a new session on the next retry
842
870
  - Manual intervention may be needed after 3 failed attempts
843
871
 
@@ -846,6 +874,7 @@ The wizard preserves session context across retries using Claude Code's session
846
874
  **Permission errors**
847
875
 
848
876
  If you encounter permission errors:
877
+
849
878
  - Ensure you have write access to `~/.syncpoint/`
850
879
  - Check file permissions: `ls -la ~/.syncpoint/`
851
880
  - Run with appropriate permissions (avoid unnecessary sudo)
@@ -853,6 +882,7 @@ If you encounter permission errors:
853
882
  **Large file warnings**
854
883
 
855
884
  Files larger than 10MB trigger warnings:
885
+
856
886
  - Consider excluding large files using `exclude` patterns
857
887
  - Review if these files should be in version control instead
858
888
  - Compress large files before backing up
@@ -860,6 +890,7 @@ Files larger than 10MB trigger warnings:
860
890
  **Backup restore conflicts**
861
891
 
862
892
  If restore shows many "overwrite" actions:
893
+
863
894
  - Use `--dry-run` to preview changes first
864
895
  - Automatic safety backup is created before overwrite
865
896
  - Review the safety backup in `~/.syncpoint/backups/` tagged with `_pre-restore_`
@@ -3,18 +3,12 @@
3
3
  "title": "Syncpoint Config",
4
4
  "description": "Configuration for syncpoint backup tool",
5
5
  "type": "object",
6
- "required": [
7
- "backup"
8
- ],
6
+ "required": ["backup"],
9
7
  "properties": {
10
8
  "backup": {
11
9
  "type": "object",
12
10
  "description": "Backup configuration",
13
- "required": [
14
- "targets",
15
- "exclude",
16
- "filename"
17
- ],
11
+ "required": ["targets", "exclude", "filename"],
18
12
  "properties": {
19
13
  "targets": {
20
14
  "type": "array",
@@ -65,4 +59,4 @@
65
59
  }
66
60
  },
67
61
  "additionalProperties": false
68
- }
62
+ }
package/dist/cli.mjs CHANGED
@@ -486,7 +486,7 @@ function validateMetadata(data) {
486
486
  }
487
487
 
488
488
  // src/version.ts
489
- var VERSION = "0.0.7";
489
+ var VERSION = "0.0.8";
490
490
 
491
491
  // src/core/metadata.ts
492
492
  var METADATA_VERSION = "1.0.0";
@@ -843,18 +843,12 @@ var config_schema_default = {
843
843
  title: "Syncpoint Config",
844
844
  description: "Configuration for syncpoint backup tool",
845
845
  type: "object",
846
- required: [
847
- "backup"
848
- ],
846
+ required: ["backup"],
849
847
  properties: {
850
848
  backup: {
851
849
  type: "object",
852
850
  description: "Backup configuration",
853
- required: [
854
- "targets",
855
- "exclude",
856
- "filename"
857
- ],
851
+ required: ["targets", "exclude", "filename"],
858
852
  properties: {
859
853
  targets: {
860
854
  type: "array",
@@ -1210,7 +1204,11 @@ var BackupView = ({ options }) => {
1210
1204
  for (const f of foundFiles) {
1211
1205
  if (seen.has(f.absolutePath)) continue;
1212
1206
  seen.add(f.absolutePath);
1213
- deduped.push({ id: `found-${f.absolutePath}`, type: "found", file: f });
1207
+ deduped.push({
1208
+ id: `found-${f.absolutePath}`,
1209
+ type: "found",
1210
+ file: f
1211
+ });
1214
1212
  }
1215
1213
  for (const p of missingFiles) {
1216
1214
  if (seen.has(p)) continue;
@@ -1334,21 +1332,72 @@ function registerBackupCommand(program2) {
1334
1332
  cmdInfo.options?.forEach((opt) => {
1335
1333
  cmd.option(opt.flag, opt.description);
1336
1334
  });
1337
- cmd.action(async (opts) => {
1338
- const { waitUntilExit } = render(
1339
- /* @__PURE__ */ jsx2(BackupView, { options: { dryRun: opts.dryRun, tag: opts.tag, verbose: opts.verbose } })
1340
- );
1341
- await waitUntilExit();
1342
- });
1335
+ cmd.action(
1336
+ async (opts) => {
1337
+ const { waitUntilExit } = render(
1338
+ /* @__PURE__ */ jsx2(
1339
+ BackupView,
1340
+ {
1341
+ options: {
1342
+ dryRun: opts.dryRun,
1343
+ tag: opts.tag,
1344
+ verbose: opts.verbose
1345
+ }
1346
+ }
1347
+ )
1348
+ );
1349
+ await waitUntilExit();
1350
+ }
1351
+ );
1343
1352
  }
1344
1353
 
1345
1354
  // src/commands/CreateTemplate.tsx
1346
- import { useState as useState2, useEffect as useEffect2 } from "react";
1347
- import { Text as Text3, Box as Box2, useApp as useApp2 } from "ink";
1348
- import Spinner from "ink-spinner";
1349
- import { render as render2 } from "ink";
1350
- import { join as join8 } from "path";
1351
1355
  import { writeFile as writeFile3 } from "fs/promises";
1356
+ import { join as join8 } from "path";
1357
+ import { Box as Box2, Text as Text3, useApp as useApp2 } from "ink";
1358
+ import { render as render2 } from "ink";
1359
+ import Spinner from "ink-spinner";
1360
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1361
+
1362
+ // src/prompts/wizard-template.ts
1363
+ function generateTemplateWizardPrompt(variables) {
1364
+ return `You are a Syncpoint provisioning template assistant. Your role is to help users create automated environment setup templates.
1365
+
1366
+ **Input:**
1367
+ 1. User's provisioning requirements (described in natural language)
1368
+ 2. Example template structure (YAML)
1369
+
1370
+ **Your Task:**
1371
+ 1. Ask clarifying questions to understand the provisioning workflow:
1372
+ - What software/tools need to be installed?
1373
+ - What dependencies should be checked?
1374
+ - Are there any configuration steps after installation?
1375
+ - Should any steps require sudo privileges?
1376
+ - Should any steps be conditional (skip_if)?
1377
+ 2. Based on user responses, generate a complete provision template
1378
+
1379
+ **Output Requirements:**
1380
+ - Pure YAML format only (no markdown, no code blocks, no explanations)
1381
+ - Must be valid according to Syncpoint template schema
1382
+ - Required fields:
1383
+ - \`name\`: Template name
1384
+ - \`steps\`: Array of provisioning steps (minimum 1)
1385
+ - Each step must include:
1386
+ - \`name\`: Step name (required)
1387
+ - \`command\`: Shell command to execute (required)
1388
+ - \`description\`: Step description (optional)
1389
+ - \`skip_if\`: Condition to skip step (optional)
1390
+ - \`continue_on_error\`: Whether to continue on failure (optional, default: false)
1391
+ - Optional template fields:
1392
+ - \`description\`: Template description
1393
+ - \`backup\`: Backup name to restore after provisioning
1394
+ - \`sudo\`: Whether sudo is required (boolean)
1395
+
1396
+ **Example Template:**
1397
+ ${variables.exampleTemplate}
1398
+
1399
+ Begin by asking the user to describe their provisioning needs.`;
1400
+ }
1352
1401
 
1353
1402
  // assets/schemas/template.schema.json
1354
1403
  var template_schema_default = {
@@ -1424,46 +1473,6 @@ function validateTemplate(data) {
1424
1473
  return { valid: false, errors };
1425
1474
  }
1426
1475
 
1427
- // src/prompts/wizard-template.ts
1428
- function generateTemplateWizardPrompt(variables) {
1429
- return `You are a Syncpoint provisioning template assistant. Your role is to help users create automated environment setup templates.
1430
-
1431
- **Input:**
1432
- 1. User's provisioning requirements (described in natural language)
1433
- 2. Example template structure (YAML)
1434
-
1435
- **Your Task:**
1436
- 1. Ask clarifying questions to understand the provisioning workflow:
1437
- - What software/tools need to be installed?
1438
- - What dependencies should be checked?
1439
- - Are there any configuration steps after installation?
1440
- - Should any steps require sudo privileges?
1441
- - Should any steps be conditional (skip_if)?
1442
- 2. Based on user responses, generate a complete provision template
1443
-
1444
- **Output Requirements:**
1445
- - Pure YAML format only (no markdown, no code blocks, no explanations)
1446
- - Must be valid according to Syncpoint template schema
1447
- - Required fields:
1448
- - \`name\`: Template name
1449
- - \`steps\`: Array of provisioning steps (minimum 1)
1450
- - Each step must include:
1451
- - \`name\`: Step name (required)
1452
- - \`command\`: Shell command to execute (required)
1453
- - \`description\`: Step description (optional)
1454
- - \`skip_if\`: Condition to skip step (optional)
1455
- - \`continue_on_error\`: Whether to continue on failure (optional, default: false)
1456
- - Optional template fields:
1457
- - \`description\`: Template description
1458
- - \`backup\`: Backup name to restore after provisioning
1459
- - \`sudo\`: Whether sudo is required (boolean)
1460
-
1461
- **Example Template:**
1462
- ${variables.exampleTemplate}
1463
-
1464
- Begin by asking the user to describe their provisioning needs.`;
1465
- }
1466
-
1467
1476
  // src/utils/claude-code-runner.ts
1468
1477
  import { spawn } from "child_process";
1469
1478
  async function isClaudeCodeAvailable() {
@@ -1566,6 +1575,39 @@ Start by asking the user about their backup priorities for the home directory st
1566
1575
  });
1567
1576
  }
1568
1577
 
1578
+ // src/utils/error-formatter.ts
1579
+ function formatValidationErrors(errors) {
1580
+ if (errors.length === 0) {
1581
+ return "No validation errors.";
1582
+ }
1583
+ const formattedErrors = errors.map((error, index) => {
1584
+ return `${index + 1}. ${error}`;
1585
+ });
1586
+ return `Validation failed with ${errors.length} error(s):
1587
+
1588
+ ${formattedErrors.join("\n")}`;
1589
+ }
1590
+ function createRetryPrompt(originalPrompt, errors, attemptNumber) {
1591
+ const errorSummary = formatValidationErrors(errors);
1592
+ return `${originalPrompt}
1593
+
1594
+ ---
1595
+
1596
+ **VALIDATION FAILED (Attempt ${attemptNumber})**
1597
+
1598
+ The previously generated YAML configuration did not pass validation:
1599
+
1600
+ ${errorSummary}
1601
+
1602
+ Please analyze these errors and generate a corrected YAML configuration that addresses all validation issues.
1603
+
1604
+ Remember:
1605
+ - Output pure YAML only (no markdown, no code blocks, no explanations)
1606
+ - Ensure all required fields are present
1607
+ - Follow the correct schema structure
1608
+ - Validate pattern syntax for targets and exclude arrays`;
1609
+ }
1610
+
1569
1611
  // src/utils/yaml-parser.ts
1570
1612
  import YAML2 from "yaml";
1571
1613
  function isStructuredYAML(parsed) {
@@ -1603,39 +1645,6 @@ function parseYAML(yamlString) {
1603
1645
  return YAML2.parse(yamlString);
1604
1646
  }
1605
1647
 
1606
- // src/utils/error-formatter.ts
1607
- function formatValidationErrors(errors) {
1608
- if (errors.length === 0) {
1609
- return "No validation errors.";
1610
- }
1611
- const formattedErrors = errors.map((error, index) => {
1612
- return `${index + 1}. ${error}`;
1613
- });
1614
- return `Validation failed with ${errors.length} error(s):
1615
-
1616
- ${formattedErrors.join("\n")}`;
1617
- }
1618
- function createRetryPrompt(originalPrompt, errors, attemptNumber) {
1619
- const errorSummary = formatValidationErrors(errors);
1620
- return `${originalPrompt}
1621
-
1622
- ---
1623
-
1624
- **VALIDATION FAILED (Attempt ${attemptNumber})**
1625
-
1626
- The previously generated YAML configuration did not pass validation:
1627
-
1628
- ${errorSummary}
1629
-
1630
- Please analyze these errors and generate a corrected YAML configuration that addresses all validation issues.
1631
-
1632
- Remember:
1633
- - Output pure YAML only (no markdown, no code blocks, no explanations)
1634
- - Ensure all required fields are present
1635
- - Follow the correct schema structure
1636
- - Validate pattern syntax for targets and exclude arrays`;
1637
- }
1638
-
1639
1648
  // src/commands/CreateTemplate.tsx
1640
1649
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1641
1650
  var MAX_RETRIES = 3;
@@ -1685,7 +1694,9 @@ var CreateTemplateView = ({
1685
1694
  while (currentAttempt <= MAX_RETRIES) {
1686
1695
  try {
1687
1696
  setPhase("llm-invoke");
1688
- setMessage(`Generating template... (Attempt ${currentAttempt}/${MAX_RETRIES})`);
1697
+ setMessage(
1698
+ `Generating template... (Attempt ${currentAttempt}/${MAX_RETRIES})`
1699
+ );
1689
1700
  const result = currentSessionId ? await resumeClaudeCodeSession(currentSessionId, currentPrompt) : await invokeClaudeCode(currentPrompt);
1690
1701
  if (!result.success) {
1691
1702
  throw new Error(result.error || "Failed to invoke Claude Code");
@@ -1762,8 +1773,11 @@ ${formatValidationErrors(validation.errors || [])}`
1762
1773
  /* @__PURE__ */ jsx3(Text3, { color: "green", children: message }),
1763
1774
  /* @__PURE__ */ jsxs3(Box2, { marginTop: 1, children: [
1764
1775
  /* @__PURE__ */ jsx3(Text3, { children: "Next steps:" }),
1765
- /* @__PURE__ */ jsx3(Text3, { children: " 1. Review your template: syncpoint list templates" }),
1766
- /* @__PURE__ */ jsx3(Text3, { children: " 2. Run provisioning: syncpoint provision <template-name>" })
1776
+ /* @__PURE__ */ jsx3(Text3, { children: " 1. Review your template: syncpoint list templates" }),
1777
+ /* @__PURE__ */ jsxs3(Text3, { children: [
1778
+ " ",
1779
+ "2. Run provisioning: syncpoint provision <template-name>"
1780
+ ] })
1767
1781
  ] })
1768
1782
  ] });
1769
1783
  }
@@ -1784,7 +1798,13 @@ ${formatValidationErrors(validation.errors || [])}`
1784
1798
  function registerCreateTemplateCommand(program2) {
1785
1799
  program2.command("create-template [name]").description("Interactive wizard to create a provisioning template").option("-p, --print", "Print prompt instead of invoking Claude Code").action(async (name, opts) => {
1786
1800
  const { waitUntilExit } = render2(
1787
- /* @__PURE__ */ jsx3(CreateTemplateView, { printMode: opts.print || false, templateName: name })
1801
+ /* @__PURE__ */ jsx3(
1802
+ CreateTemplateView,
1803
+ {
1804
+ printMode: opts.print || false,
1805
+ templateName: name
1806
+ }
1807
+ )
1788
1808
  );
1789
1809
  await waitUntilExit();
1790
1810
  });
@@ -1935,10 +1955,10 @@ function registerHelpCommand(program2) {
1935
1955
  }
1936
1956
 
1937
1957
  // src/commands/Init.tsx
1938
- import { useState as useState3, useEffect as useEffect3 } from "react";
1939
- import { Text as Text5, Box as Box4, useApp as useApp3 } from "ink";
1940
- import { render as render4 } from "ink";
1941
1958
  import { join as join9 } from "path";
1959
+ import { Box as Box4, Text as Text5, useApp as useApp3 } from "ink";
1960
+ import { render as render4 } from "ink";
1961
+ import { useEffect as useEffect3, useState as useState3 } from "react";
1942
1962
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1943
1963
  var InitView = () => {
1944
1964
  const { exit } = useApp3();
@@ -1980,9 +2000,15 @@ var InitView = () => {
1980
2000
  setSteps([...completed]);
1981
2001
  }
1982
2002
  await initDefaultConfig();
1983
- completed.push({ name: `Created ${CONFIG_FILENAME} (defaults)`, done: true });
2003
+ completed.push({
2004
+ name: `Created ${CONFIG_FILENAME} (defaults)`,
2005
+ done: true
2006
+ });
1984
2007
  setSteps([...completed]);
1985
- const exampleTemplatePath = join9(getSubDir(TEMPLATES_DIR), "example.yml");
2008
+ const exampleTemplatePath = join9(
2009
+ getSubDir(TEMPLATES_DIR),
2010
+ "example.yml"
2011
+ );
1986
2012
  if (!await fileExists(exampleTemplatePath)) {
1987
2013
  const { writeFile: writeFile6 } = await import("fs/promises");
1988
2014
  const exampleYaml = readAsset("template.example.yml");
@@ -2033,7 +2059,9 @@ var InitView = () => {
2033
2059
  ] });
2034
2060
  };
2035
2061
  function registerInitCommand(program2) {
2036
- program2.command("init").description(`Initialize ~/.${APP_NAME}/ directory structure and default config`).action(async () => {
2062
+ program2.command("init").description(
2063
+ `Initialize ~/.${APP_NAME}/ directory structure and default config`
2064
+ ).action(async () => {
2037
2065
  const { waitUntilExit } = render4(/* @__PURE__ */ jsx5(InitView, {}));
2038
2066
  await waitUntilExit();
2039
2067
  });
@@ -2084,7 +2112,7 @@ var Confirm = ({
2084
2112
  };
2085
2113
 
2086
2114
  // src/components/Table.tsx
2087
- import { Text as Text7, Box as Box5 } from "ink";
2115
+ import { Box as Box5, Text as Text7 } from "ink";
2088
2116
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2089
2117
  var Table = ({
2090
2118
  headers,
@@ -2466,6 +2494,7 @@ var ListView = ({ type, deleteIndex }) => {
2466
2494
  const [deleteTarget, setDeleteTarget] = useState5(null);
2467
2495
  const [error, setError] = useState5(null);
2468
2496
  const [backupDir, setBackupDir] = useState5(getSubDir("backups"));
2497
+ const [successMessage, setSuccessMessage] = useState5(null);
2469
2498
  useInput2((_input, key) => {
2470
2499
  if (!key.escape) return;
2471
2500
  switch (phase) {
@@ -2476,6 +2505,7 @@ var ListView = ({ type, deleteIndex }) => {
2476
2505
  case "template-list":
2477
2506
  setSelectedBackup(null);
2478
2507
  setSelectedTemplate(null);
2508
+ setSuccessMessage(null);
2479
2509
  setPhase("main-menu");
2480
2510
  break;
2481
2511
  case "backup-detail":
@@ -2541,15 +2571,30 @@ var ListView = ({ type, deleteIndex }) => {
2541
2571
  setSelectedTemplate(null);
2542
2572
  setPhase("template-list");
2543
2573
  };
2574
+ const reloadBackups = async () => {
2575
+ try {
2576
+ const config = await loadConfig();
2577
+ const list2 = await getBackupList(config);
2578
+ setBackups(list2);
2579
+ } catch {
2580
+ }
2581
+ };
2544
2582
  const handleDeleteConfirm = (yes) => {
2545
2583
  if (yes && deleteTarget) {
2546
2584
  try {
2547
2585
  if (!isInsideDir(deleteTarget.path, backupDir)) {
2548
- throw new Error(`Refusing to delete file outside backups directory: ${deleteTarget.path}`);
2586
+ throw new Error(
2587
+ `Refusing to delete file outside backups directory: ${deleteTarget.path}`
2588
+ );
2549
2589
  }
2550
2590
  unlinkSync(deleteTarget.path);
2551
- setPhase("done");
2552
- setTimeout(() => exit(), 100);
2591
+ const deletedName = deleteTarget.name;
2592
+ setBackups((prev) => prev.filter((b) => b.path !== deleteTarget.path));
2593
+ setDeleteTarget(null);
2594
+ setSelectedBackup(null);
2595
+ setSuccessMessage(`\u2713 ${deletedName} deleted`);
2596
+ setPhase("backup-list");
2597
+ void reloadBackups();
2553
2598
  } catch (err) {
2554
2599
  setError(err instanceof Error ? err.message : String(err));
2555
2600
  setPhase("error");
@@ -2645,6 +2690,7 @@ var ListView = ({ type, deleteIndex }) => {
2645
2690
  }
2646
2691
  };
2647
2692
  return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
2693
+ successMessage && /* @__PURE__ */ jsx8(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "green", children: successMessage }) }),
2648
2694
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: "\u25B8 Backups" }),
2649
2695
  /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: backups.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " No backups found." }) : /* @__PURE__ */ jsx8(
2650
2696
  SelectInput,
@@ -2812,9 +2858,9 @@ function registerListCommand(program2) {
2812
2858
  }
2813
2859
 
2814
2860
  // src/commands/Migrate.tsx
2815
- import { useState as useState6, useEffect as useEffect5 } from "react";
2816
- import { Text as Text9, Box as Box7, useApp as useApp5 } from "ink";
2861
+ import { Box as Box7, Text as Text9, useApp as useApp5 } from "ink";
2817
2862
  import { render as render6 } from "ink";
2863
+ import { useEffect as useEffect5, useState as useState6 } from "react";
2818
2864
 
2819
2865
  // src/core/migrate.ts
2820
2866
  import { copyFile as copyFile2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
@@ -3047,44 +3093,12 @@ function registerMigrateCommand(program2) {
3047
3093
  }
3048
3094
 
3049
3095
  // src/commands/Provision.tsx
3050
- import { useState as useState7, useEffect as useEffect6 } from "react";
3051
- import { Text as Text11, Box as Box9, useApp as useApp6 } from "ink";
3096
+ import { Box as Box9, Text as Text11, useApp as useApp6 } from "ink";
3052
3097
  import { render as render7 } from "ink";
3053
-
3054
- // src/utils/sudo.ts
3055
- import { execSync } from "child_process";
3056
- import pc2 from "picocolors";
3057
- function isSudoCached() {
3058
- try {
3059
- execSync("sudo -n true", { stdio: "ignore" });
3060
- return true;
3061
- } catch {
3062
- return false;
3063
- }
3064
- }
3065
- function ensureSudo(templateName) {
3066
- if (isSudoCached()) return;
3067
- console.log(
3068
- `
3069
- ${pc2.yellow("\u26A0")} Template ${pc2.bold(templateName)} requires ${pc2.bold("sudo")} privileges.`
3070
- );
3071
- console.log(
3072
- pc2.gray(" Some provisioning steps need elevated permissions to execute.")
3073
- );
3074
- console.log(pc2.gray(" You will be prompted for your password.\n"));
3075
- try {
3076
- execSync("sudo -v", { stdio: "inherit", timeout: 6e4 });
3077
- } catch {
3078
- console.error(
3079
- `
3080
- ${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
3081
- );
3082
- process.exit(1);
3083
- }
3084
- }
3098
+ import { useEffect as useEffect6, useState as useState7 } from "react";
3085
3099
 
3086
3100
  // src/components/StepRunner.tsx
3087
- import { Text as Text10, Box as Box8, Static as Static2 } from "ink";
3101
+ import { Box as Box8, Static as Static2, Text as Text10 } from "ink";
3088
3102
  import Spinner2 from "ink-spinner";
3089
3103
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3090
3104
  var StepIcon = ({ status }) => {
@@ -3153,10 +3167,7 @@ var StepItemView = ({
3153
3167
  /* @__PURE__ */ jsx10(StepStatusText, { step })
3154
3168
  ] })
3155
3169
  ] });
3156
- var StepRunner = ({
3157
- steps,
3158
- total
3159
- }) => {
3170
+ var StepRunner = ({ steps, total }) => {
3160
3171
  const completedSteps = [];
3161
3172
  const activeSteps = [];
3162
3173
  steps.forEach((step, idx) => {
@@ -3192,6 +3203,38 @@ var StepRunner = ({
3192
3203
  ] });
3193
3204
  };
3194
3205
 
3206
+ // src/utils/sudo.ts
3207
+ import { execSync } from "child_process";
3208
+ import pc2 from "picocolors";
3209
+ function isSudoCached() {
3210
+ try {
3211
+ execSync("sudo -n true", { stdio: "ignore" });
3212
+ return true;
3213
+ } catch {
3214
+ return false;
3215
+ }
3216
+ }
3217
+ function ensureSudo(templateName) {
3218
+ if (isSudoCached()) return;
3219
+ console.log(
3220
+ `
3221
+ ${pc2.yellow("\u26A0")} Template ${pc2.bold(templateName)} requires ${pc2.bold("sudo")} privileges.`
3222
+ );
3223
+ console.log(
3224
+ pc2.gray(" Some provisioning steps need elevated permissions to execute.")
3225
+ );
3226
+ console.log(pc2.gray(" You will be prompted for your password.\n"));
3227
+ try {
3228
+ execSync("sudo -v", { stdio: "inherit", timeout: 6e4 });
3229
+ } catch {
3230
+ console.error(
3231
+ `
3232
+ ${pc2.red("\u2717")} Sudo authentication failed or was cancelled. Aborting.`
3233
+ );
3234
+ process.exit(1);
3235
+ }
3236
+ }
3237
+
3195
3238
  // src/commands/Provision.tsx
3196
3239
  import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3197
3240
  var ProvisionView = ({
@@ -3200,7 +3243,9 @@ var ProvisionView = ({
3200
3243
  options
3201
3244
  }) => {
3202
3245
  const { exit } = useApp6();
3203
- const [phase, setPhase] = useState7(options.dryRun ? "done" : "running");
3246
+ const [phase, setPhase] = useState7(
3247
+ options.dryRun ? "done" : "running"
3248
+ );
3204
3249
  const [steps, setSteps] = useState7(
3205
3250
  template.steps.map((s) => ({
3206
3251
  name: s.name,
@@ -3345,7 +3390,11 @@ var ProvisionView = ({
3345
3390
  ] });
3346
3391
  };
3347
3392
  function registerProvisionCommand(program2) {
3348
- program2.command("provision [template]").description("Run template-based machine provisioning").option("--dry-run", "Show plan without execution", false).option("--skip-restore", "Skip automatic restore after template completion", false).option("-f, --file <path>", "Path to template file").action(
3393
+ program2.command("provision [template]").description("Run template-based machine provisioning").option("--dry-run", "Show plan without execution", false).option(
3394
+ "--skip-restore",
3395
+ "Skip automatic restore after template completion",
3396
+ false
3397
+ ).option("-f, --file <path>", "Path to template file").action(
3349
3398
  async (templateName, opts) => {
3350
3399
  let templatePath;
3351
3400
  if (opts.file) {
@@ -3355,7 +3404,9 @@ function registerProvisionCommand(program2) {
3355
3404
  process.exit(1);
3356
3405
  }
3357
3406
  if (!templatePath.endsWith(".yml") && !templatePath.endsWith(".yaml")) {
3358
- console.error(`Template file must have .yml or .yaml extension: ${opts.file}`);
3407
+ console.error(
3408
+ `Template file must have .yml or .yaml extension: ${opts.file}`
3409
+ );
3359
3410
  process.exit(1);
3360
3411
  }
3361
3412
  } else if (templateName) {
@@ -3369,7 +3420,9 @@ function registerProvisionCommand(program2) {
3369
3420
  }
3370
3421
  templatePath = match.path;
3371
3422
  } else {
3372
- console.error("Error: Either <template> name or --file option must be provided");
3423
+ console.error(
3424
+ "Error: Either <template> name or --file option must be provided"
3425
+ );
3373
3426
  console.error("Usage: syncpoint provision <template> [options]");
3374
3427
  console.error(" syncpoint provision --file <path> [options]");
3375
3428
  process.exit(1);
@@ -3397,10 +3450,10 @@ function registerProvisionCommand(program2) {
3397
3450
  }
3398
3451
 
3399
3452
  // src/commands/Restore.tsx
3400
- import { useState as useState8, useEffect as useEffect7 } from "react";
3401
- import { Text as Text12, Box as Box10, useApp as useApp7 } from "ink";
3402
- import SelectInput2 from "ink-select-input";
3453
+ import { Box as Box10, Text as Text12, useApp as useApp7 } from "ink";
3403
3454
  import { render as render8 } from "ink";
3455
+ import SelectInput2 from "ink-select-input";
3456
+ import { useEffect as useEffect7, useState as useState8 } from "react";
3404
3457
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3405
3458
  var RestoreView = ({ filename, options }) => {
3406
3459
  const { exit } = useApp7();
@@ -3614,13 +3667,7 @@ var RestoreView = ({ filename, options }) => {
3614
3667
  function registerRestoreCommand(program2) {
3615
3668
  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) => {
3616
3669
  const { waitUntilExit } = render8(
3617
- /* @__PURE__ */ jsx12(
3618
- RestoreView,
3619
- {
3620
- filename,
3621
- options: { dryRun: opts.dryRun }
3622
- }
3623
- )
3670
+ /* @__PURE__ */ jsx12(RestoreView, { filename, options: { dryRun: opts.dryRun } })
3624
3671
  );
3625
3672
  await waitUntilExit();
3626
3673
  });
@@ -3771,7 +3818,10 @@ var StatusView = ({ cleanup }) => {
3771
3818
  if (cleanupAction === "keep-recent-5") {
3772
3819
  const toDelete = backups.slice(5);
3773
3820
  for (const b of toDelete) {
3774
- if (!isInsideDir(b.path, backupDir)) throw new Error(`Refusing to delete file outside backups directory: ${b.path}`);
3821
+ if (!isInsideDir(b.path, backupDir))
3822
+ throw new Error(
3823
+ `Refusing to delete file outside backups directory: ${b.path}`
3824
+ );
3775
3825
  unlinkSync2(b.path);
3776
3826
  }
3777
3827
  } else if (cleanupAction === "older-than-30") {
@@ -3779,7 +3829,10 @@ var StatusView = ({ cleanup }) => {
3779
3829
  cutoff.setDate(cutoff.getDate() - 30);
3780
3830
  const toDelete = backups.filter((b) => b.createdAt < cutoff);
3781
3831
  for (const b of toDelete) {
3782
- if (!isInsideDir(b.path, backupDir)) throw new Error(`Refusing to delete file outside backups directory: ${b.path}`);
3832
+ if (!isInsideDir(b.path, backupDir))
3833
+ throw new Error(
3834
+ `Refusing to delete file outside backups directory: ${b.path}`
3835
+ );
3783
3836
  unlinkSync2(b.path);
3784
3837
  }
3785
3838
  } else if (cleanupAction === "delete-logs") {
@@ -3788,7 +3841,10 @@ var StatusView = ({ cleanup }) => {
3788
3841
  const entries = readdirSync(logsDir);
3789
3842
  for (const entry of entries) {
3790
3843
  const logPath = join12(logsDir, entry);
3791
- if (!isInsideDir(logPath, logsDir)) throw new Error(`Refusing to delete file outside logs directory: ${logPath}`);
3844
+ if (!isInsideDir(logPath, logsDir))
3845
+ throw new Error(
3846
+ `Refusing to delete file outside logs directory: ${logPath}`
3847
+ );
3792
3848
  try {
3793
3849
  if (statSync(logPath).isFile()) {
3794
3850
  unlinkSync2(logPath);
@@ -3800,7 +3856,10 @@ var StatusView = ({ cleanup }) => {
3800
3856
  }
3801
3857
  } else if (cleanupAction === "select-specific") {
3802
3858
  for (const b of selectedForDeletion) {
3803
- if (!isInsideDir(b.path, backupDir)) throw new Error(`Refusing to delete file outside backups directory: ${b.path}`);
3859
+ if (!isInsideDir(b.path, backupDir))
3860
+ throw new Error(
3861
+ `Refusing to delete file outside backups directory: ${b.path}`
3862
+ );
3804
3863
  unlinkSync2(b.path);
3805
3864
  }
3806
3865
  }
@@ -4027,7 +4086,13 @@ function registerStatusCommand(program2) {
4027
4086
  }
4028
4087
 
4029
4088
  // src/commands/Wizard.tsx
4030
- import { copyFile as copyFile3, readFile as readFile6, rename, unlink, writeFile as writeFile5 } from "fs/promises";
4089
+ import {
4090
+ copyFile as copyFile3,
4091
+ readFile as readFile6,
4092
+ rename,
4093
+ unlink,
4094
+ writeFile as writeFile5
4095
+ } from "fs/promises";
4031
4096
  import { join as join14 } from "path";
4032
4097
  import { Box as Box12, Text as Text14, useApp as useApp9 } from "ink";
4033
4098
  import { render as render10 } from "ink";
@@ -1,2 +1,2 @@
1
- import { Command } from "commander";
1
+ import { Command } from 'commander';
2
2
  export declare function registerCreateTemplateCommand(program: Command): void;
@@ -1,2 +1,2 @@
1
- import { Command } from "commander";
1
+ import { Command } from 'commander';
2
2
  export declare function registerInitCommand(program: Command): void;
@@ -1,2 +1,2 @@
1
- import { Command } from "commander";
1
+ import { Command } from 'commander';
2
2
  export declare function registerProvisionCommand(program: Command): void;
@@ -1,2 +1,2 @@
1
- import { Command } from "commander";
1
+ import { Command } from 'commander';
2
2
  export declare function registerRestoreCommand(program: Command): void;
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React from 'react';
2
2
  interface ProgressBarProps {
3
3
  percent: number;
4
4
  width?: number;
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import type { StepResult } from "../utils/types.js";
1
+ import React from 'react';
2
+ import type { StepResult } from '../utils/types.js';
3
3
  interface StepRunnerProps {
4
4
  steps: StepResult[];
5
5
  currentStep: number;
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React from 'react';
2
2
  interface TableProps {
3
3
  headers: string[];
4
4
  rows: string[][];
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React from 'react';
2
2
  export interface ViewerSection {
3
3
  label: string;
4
4
  value: string;
package/dist/index.cjs CHANGED
@@ -132,18 +132,12 @@ var config_schema_default = {
132
132
  title: "Syncpoint Config",
133
133
  description: "Configuration for syncpoint backup tool",
134
134
  type: "object",
135
- required: [
136
- "backup"
137
- ],
135
+ required: ["backup"],
138
136
  properties: {
139
137
  backup: {
140
138
  type: "object",
141
139
  description: "Backup configuration",
142
- required: [
143
- "targets",
144
- "exclude",
145
- "filename"
146
- ],
140
+ required: ["targets", "exclude", "filename"],
147
141
  properties: {
148
142
  targets: {
149
143
  type: "array",
@@ -652,7 +646,7 @@ function validateMetadata(data) {
652
646
  }
653
647
 
654
648
  // src/version.ts
655
- var VERSION = "0.0.7";
649
+ var VERSION = "0.0.8";
656
650
 
657
651
  // src/core/metadata.ts
658
652
  var METADATA_VERSION = "1.0.0";
package/dist/index.mjs CHANGED
@@ -82,18 +82,12 @@ var config_schema_default = {
82
82
  title: "Syncpoint Config",
83
83
  description: "Configuration for syncpoint backup tool",
84
84
  type: "object",
85
- required: [
86
- "backup"
87
- ],
85
+ required: ["backup"],
88
86
  properties: {
89
87
  backup: {
90
88
  type: "object",
91
89
  description: "Backup configuration",
92
- required: [
93
- "targets",
94
- "exclude",
95
- "filename"
96
- ],
90
+ required: ["targets", "exclude", "filename"],
97
91
  properties: {
98
92
  targets: {
99
93
  type: "array",
@@ -602,7 +596,7 @@ function validateMetadata(data) {
602
596
  }
603
597
 
604
598
  // src/version.ts
605
- var VERSION = "0.0.7";
599
+ var VERSION = "0.0.8";
606
600
 
607
601
  // src/core/metadata.ts
608
602
  var METADATA_VERSION = "1.0.0";
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current package version from package.json
3
3
  * Automatically synchronized during build process
4
4
  */
5
- export declare const VERSION = "0.0.7";
5
+ export declare const VERSION = "0.0.8";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumy-pack/syncpoint",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "CLI tool for project synchronization and scaffolding",
5
5
  "keywords": [
6
6
  "cli",
@@ -31,9 +31,10 @@
31
31
  "main": "dist/index.cjs",
32
32
  "module": "dist/index.mjs",
33
33
  "types": "dist/index.d.ts",
34
- "bin": "./dist/cli.mjs",
34
+ "bin": "dist/cli.mjs",
35
35
  "files": [
36
36
  "dist",
37
+ "!dist/tsconfig.tsbuildinfo",
37
38
  "assets",
38
39
  "README.md"
39
40
  ],
@@ -55,6 +56,7 @@
55
56
  "version:patch": "yarn version patch"
56
57
  },
57
58
  "dependencies": {
59
+ "@lumy-pack/shared": "0.0.1",
58
60
  "ajv": "^8.0.0",
59
61
  "ajv-formats": "^3.0.0",
60
62
  "commander": "^12.1.0",