@garyr/pt-cli 0.32.0 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -119,12 +119,18 @@ pt init ./new-project --file my-template.json --yes
119
119
  An official agent skill is included in this repository: [`skills/agency-pt-operator/SKILL.md`](skills/agency-pt-operator/SKILL.md).
120
120
 
121
121
  Equipping your agent with this skill allows it to automatically use `pt-cli` to lay down standardized boilerplate and capture new architectures you develop together.
122
+
122
123
  ## Warning
123
124
 
124
125
  **⚠️ Beta Software Warning**: This project is in beta stage. While stable for most use cases, please make regular backups of your template configurations before running updates or major changes.
125
126
 
126
127
  **💾 Backup Paths**:
128
+
127
129
  - **Linux/macOS**: `~/.pt/` or `/home/username/.pt/` and `/Users/username/.pt/`
128
130
  - **Windows**: `%USERPROFILE%\.pt\` (typically `C:\Users\Username\.pt\`)
129
131
 
130
- Always back up these directories before installing new versions or making significant changes.
132
+ Always back up these directories before installing new versions or making significant changes.
133
+
134
+ ## Example Templates
135
+
136
+ There are a couple of [example templates](https://github.com/search?q=topic%3Atemplate-project+org%3Agaryritchie&type=Repositories) for you to try. These include utilities written in python to help streamline common file management tasks.
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { loadConfig, getTemplateNames, CONFIG_PATH } from '../config.js';
2
+ import { loadConfig, getTemplateNames, getConfigPath } from '../config.js';
3
3
  export function configCommand(templateName, options = {}) {
4
4
  const config = loadConfig();
5
5
  if (options.json) {
@@ -22,7 +22,7 @@ export function configCommand(templateName, options = {}) {
22
22
  return;
23
23
  }
24
24
  const names = getTemplateNames(config);
25
- console.log(chalk.cyan('Config Location:'), CONFIG_PATH);
25
+ console.log(chalk.cyan('Config Location:'), getConfigPath());
26
26
  console.log(chalk.cyan('\nLearned Templates:'));
27
27
  if (names.length === 0) {
28
28
  console.log(chalk.gray(' (none)'));
package/dist/config.js CHANGED
@@ -3,16 +3,20 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
5
  import chalk from 'chalk';
6
- export const HOME_DIR = path.join(os.homedir(), '.pt');
7
- export const CONFIG_PATH = path.join(HOME_DIR, 'config.yaml');
6
+ export function getHomeDir() {
7
+ return path.join(os.homedir(), '.pt');
8
+ }
9
+ export function getConfigPath() {
10
+ return path.join(getHomeDir(), 'config.yaml');
11
+ }
8
12
  export function ensureConfigDir() {
9
- if (!fs.existsSync(HOME_DIR)) {
10
- fs.mkdirSync(HOME_DIR, { recursive: true });
13
+ if (!fs.existsSync(getHomeDir())) {
14
+ fs.mkdirSync(getHomeDir(), { recursive: true });
11
15
  }
12
16
  }
13
17
  export function loadConfig() {
14
18
  ensureConfigDir();
15
- if (!fs.existsSync(CONFIG_PATH)) {
19
+ if (!fs.existsSync(getConfigPath())) {
16
20
  const defaultConfig = {
17
21
  version: '3.0',
18
22
  templates: {},
@@ -25,7 +29,7 @@ export function loadConfig() {
25
29
  return defaultConfig;
26
30
  }
27
31
  try {
28
- const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
32
+ const content = fs.readFileSync(getConfigPath(), 'utf-8');
29
33
  if (!content.trim()) {
30
34
  throw new Error("Config file is empty");
31
35
  }
@@ -81,7 +85,7 @@ export function loadConfig() {
81
85
  const error = err;
82
86
  console.error(chalk.red(`\nError loading config: ${error.message}`));
83
87
  // If we have a backup, maybe suggest using it
84
- const backupPath = CONFIG_PATH + '.bak';
88
+ const backupPath = getConfigPath() + '.bak';
85
89
  if (fs.existsSync(backupPath)) {
86
90
  console.error(chalk.yellow(`A backup exists at ${backupPath}. You may want to restore it.`));
87
91
  }
@@ -113,17 +117,17 @@ export function saveConfig(config) {
113
117
  }
114
118
  }
115
119
  const content = YAML.stringify(config);
116
- const tempPath = CONFIG_PATH + '.tmp';
117
- const backupPath = CONFIG_PATH + '.bak';
120
+ const tempPath = getConfigPath() + '.tmp';
121
+ const backupPath = getConfigPath() + '.bak';
118
122
  try {
119
123
  // 1. Create a backup of the current valid config if it exists
120
- if (fs.existsSync(CONFIG_PATH)) {
121
- fs.copyFileSync(CONFIG_PATH, backupPath);
124
+ if (fs.existsSync(getConfigPath())) {
125
+ fs.copyFileSync(getConfigPath(), backupPath);
122
126
  }
123
127
  // 2. Write to a temporary file first (atomic save)
124
128
  fs.writeFileSync(tempPath, content);
125
129
  // 3. Rename temp file to actual config path
126
- fs.renameSync(tempPath, CONFIG_PATH);
130
+ fs.renameSync(tempPath, getConfigPath());
127
131
  }
128
132
  catch (err) {
129
133
  const error = err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garyr/pt-cli",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "description": "Project Template CLI - Learn structures and initialize projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agency-pt-operator
3
- description: Specialist in using pt-cli to scaffold project templates, capture boilerplate, and maintain standardized directory structures. Includes knowledge of default_post_config, global variables, and automatic variable detection.
3
+ description: Specialist in using pt-cli to scaffold project templates, capture boilerplate, and maintain standardized directory structures. Includes knowledge of default_post_config, global variables, automatic variable detection, JSON template configs, and portable template workflows.
4
4
  ---
5
5
 
6
6
  # `pt-cli` Operator Skill
@@ -14,26 +14,30 @@ As an agent equipped with this skill, you have the ability to rapidly scaffold,
14
14
  - Run `pt config` to view available templates, their post-config tasks, and default post-config tasks.
15
15
 
16
16
  2. **Scaffolding (`pt init`):**
17
- When a matching template exists, initialize it using the non-interactive flags.
17
+ When a matching template exists, initialize it using the non-interactive flags. URL targets (GitHub, Gitea, etc.) are automatically translated to tarball downloads.
18
18
  - **Command:** `pt init <template_name> <destination_path> --yes`
19
19
  - If the template requires variables, pass them: `pt init <template_name> <destination_path> --yes --vars key1=value1,key2=value2`
20
20
  - **Direct JSON scaffolding:** To scaffold from a JSON template file without registering it in `config.yaml`:
21
21
  `pt init <destination_path> --file <json_path> --yes`
22
22
  - *Never* run `pt init` without `--yes`, as interactive prompts will block you.
23
+ - **Dry-run:** Preview what would be created without making changes: `pt init <template_name> <destination_path> --yes --dry-run`
24
+ - **Skip post-config:** Skip running post-config tasks: `pt init <template_name> <destination_path> --yes --skip-post-config`
23
25
  - Note any errors from auto-executed post-config tasks (like `npm install` failing) and correct them if necessary.
24
26
 
25
27
  3. **Capturing Knowledge (`pt learn`):**
26
- If you spend time establishing a new, complex directory structure or configuration (e.g., a specific flavor of an Express backend with testing hooks), save it!
28
+ If you spend time establishing a new, complex directory structure or configuration (e.g., a specific flavor of an Express backend with testing hooks), save it! Remote URLs (GitHub, Gitea, etc.) are automatically translated to tarball downloads.
27
29
  - **Command:** `pt learn <source_path> --name <template_name> --desc "<Description>" --yes`
28
- - **Remote Templates:** You can also learn from a remote Git repository or archive URL directly! Pass the HTTP/HTTPS URL as the `<source_path>`:
30
+ - **Update existing template:** Update an existing template with new structure/files: `pt update <template_name> <source_path> --yes`
31
+ - **Remote Templates:** Learn from a remote Git repository or archive URL directly! Pass the HTTP/HTTPS URL as the `<source_path>`:
29
32
  `pt learn https://github.com/username/my-template --name my_template --desc "Description" --yes`
30
33
  - Explain to the user that you've captured this template for future use.
31
34
  - **Automatic Variable Detection:** `pt learn` and `pt update` automatically scan text files (at root and one level deep) for `{{ variable_name }}` placeholders. You can add these to files (e.g., `README.md`, `.makerc`) and run `pt update <template> . --yes` to have them registered as template variables without manual configuration.
32
- - **JSON Template Config:** If the source directory contains a `.pt-template.json` or `template.json` file, `pt learn` will auto-detect name, description, variables, folders, copy_files, post_config, and post_copy from it — skipping the corresponding interactive prompts.
35
+ - **JSON Template Config:** If the source directory contains a `.pt-template.json` or `template.json` file, `pt learn` will auto-detect name, description, variables, folders, copy_files, post_config, and post_copy from it — skipping the corresponding interactive prompts. JSON config takes precedence over `.info.md` and shell scripts.
36
+ - **JSON Output:** Output template structure as JSON for sharing instead of saving: `pt learn <source_path> --json`
33
37
 
34
38
  4. **Template Maintenance (`pt rm`):**
35
39
  If a template is obsolete or requested for deletion, use `pt rm`.
36
- - **Command:** `pt rm <template_name> --yes`
40
+ - **Command:** `pt rm <template_name> --yes` or `pt remove <template_name> --yes`
37
41
 
38
42
  ## Template Sharing & Portability
39
43
 
@@ -41,7 +45,7 @@ Templates are designed to be fully portable. There are two approaches to sharing
41
45
 
42
46
  ### 1. Directory-based Sharing (recommended for complete templates)
43
47
 
44
- Place a `.pt-template.json` at the root of your template directory. This file can include all template metadata:
48
+ Place a `.pt-template.json` or `template.json` at the root of your template directory. This file can include all template metadata:
45
49
 
46
50
  ```json
47
51
  {
@@ -56,9 +60,9 @@ Place a `.pt-template.json` at the root of your template directory. This file ca
56
60
  }
57
61
  ```
58
62
 
59
- When someone runs `pt learn /path/to/shared-dir --yes`, all metadata is auto-detected from this file.
63
+ When someone runs `pt learn /path/to/shared-dir --yes`, all metadata is auto-detected from this file. The priority order is: `.pt-template.json` > `template.json` > `.info.md` > `post_config.sh`/`.bat`.
60
64
 
61
- **Priority order:** `.pt-template.json` > `template.json` > `.info.md` > `post_config.sh`/`.bat`
65
+ JSON config files also take precedence over shell scripts for post_config tasks.
62
66
 
63
67
  ### 2. JSON Export/Import (for config-only sharing)
64
68
 
@@ -80,6 +84,11 @@ The complete portable template workflow:
80
84
  2. Copy `.pt-template.json` into the template source directory
81
85
  3. Share the directory (zip, git repo, etc.)
82
86
  4. Recipient: `pt learn /path/to/shared-dir --yes` — auto-detects everything
87
+ 5. Or scaffold directly without registering: `pt init ./new-project --file .pt-template.json --yes`
88
+
89
+ ### JSON Output for Sharing
90
+
91
+ You can output a template as JSON for sharing without saving it: `pt learn <source_path> --json`
83
92
 
84
93
  ## Default Post-Config
85
94
 
@@ -101,7 +110,7 @@ Global variables are defined in `~/.pt/config.yaml` under `variables`. They act
101
110
  - **Purpose:** They ensure that common metadata fields (like `author`, `license`, `project_version`) are consistently offered for inclusion in every new template you create.
102
111
  - **Inheritance:** When you `learn` a new project structure, these global variables are automatically merged with any detected variables (`{{ var }}`) and offered as part of the new template's variable list.
103
112
  - **Localization:** Once a template is saved, its variables are "stamped" in. Subsequent changes to global variables in the config will **not** retroactively affect old templates, ensuring stability and portability.
104
- - **Management:** Use `pt variables --set key=value` to update your global defaults. For bulk updates, use `pt variables --set --json '...'`.
113
+ - **Management:** Use `pt variables --set key=value` to update your global defaults. For bulk updates, use `pt variables --set --json '...'`. To delete a global variable, use `pt variables --delete key`.
105
114
 
106
115
  ## Workflow Optimization
107
116
 
@@ -109,3 +118,25 @@ Global variables are defined in `~/.pt/config.yaml` under `variables`. They act
109
118
  1. Identify the closest matching template (`pt config`).
110
119
  2. Scaffold it non-interactively (`pt init ... --yes`).
111
120
  3. Make the specific manual code/configuration changes requested by the user on top of that scaffolded base.
121
+ 4. If the scaffold is close but needs updates, use `pt update <template_name> . --yes` to update the saved template with your changes.
122
+
123
+ ## Additional Notes
124
+
125
+ - JSON variables take precedence over other variable sources during `pt update`.
126
+ - Optional whitespace is allowed around template variables in the substitute function.
127
+ - Trailing `.git` is automatically stripped from repository URLs.
128
+ - Atomic saves, backups, and safe initialization logic prevent data loss.
129
+ - Platform-specific post-config scripts (`.sh`/`.bat`) are executed automatically based on the OS.
130
+
131
+ ## CLI Reference
132
+
133
+ | Command | Description |
134
+ |---------|-------------|
135
+ | `pt learn <path>` | Learn a project structure from an existing directory |
136
+ | `pt update <template> [path]` | Update an existing template with new structure/files |
137
+ | `pt init [template] [dest]` | Initialize a new project from a learned template |
138
+ | `pt config [template]` | Show current config location and list templates, or export a specific template |
139
+ | `pt variables [pairs]` | View or set global variables (comma-separated key=value) |
140
+ | `pt default-post-config` | View or set default post-config tasks |
141
+ | `pt add <name> [json]` | Import/add a template from a JSON string or file |
142
+ | `pt remove <template>` / `pt rm <template>` | Remove a learned template from the config |
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { loadConfig, getTemplateNames, CONFIG_PATH } from '../config.js';
2
+ import { loadConfig, getTemplateNames, getConfigPath } from '../config.js';
3
3
 
4
4
  export interface ConfigOptions {
5
5
  json?: boolean;
@@ -28,7 +28,7 @@ export function configCommand(templateName: string | undefined, options: ConfigO
28
28
 
29
29
  const names = getTemplateNames(config);
30
30
 
31
- console.log(chalk.cyan('Config Location:'), CONFIG_PATH);
31
+ console.log(chalk.cyan('Config Location:'), getConfigPath());
32
32
  console.log(chalk.cyan('\nLearned Templates:'));
33
33
  if (names.length === 0) {
34
34
  console.log(chalk.gray(' (none)'));
package/src/config.ts CHANGED
@@ -4,8 +4,13 @@ import path from 'path';
4
4
  import os from 'os';
5
5
  import chalk from 'chalk';
6
6
 
7
- export const HOME_DIR = path.join(os.homedir(), '.pt');
8
- export const CONFIG_PATH = path.join(HOME_DIR, 'config.yaml');
7
+ export function getHomeDir(): string {
8
+ return path.join(os.homedir(), '.pt');
9
+ }
10
+
11
+ export function getConfigPath(): string {
12
+ return path.join(getHomeDir(), 'config.yaml');
13
+ }
9
14
 
10
15
  export interface FolderNode {
11
16
  name: string;
@@ -63,14 +68,14 @@ export interface PtConfig {
63
68
  }
64
69
 
65
70
  export function ensureConfigDir() {
66
- if (!fs.existsSync(HOME_DIR)) {
67
- fs.mkdirSync(HOME_DIR, { recursive: true });
71
+ if (!fs.existsSync(getHomeDir())) {
72
+ fs.mkdirSync(getHomeDir(), { recursive: true });
68
73
  }
69
74
  }
70
75
 
71
76
  export function loadConfig(): PtConfig {
72
77
  ensureConfigDir();
73
- if (!fs.existsSync(CONFIG_PATH)) {
78
+ if (!fs.existsSync(getConfigPath())) {
74
79
  const defaultConfig: PtConfig = {
75
80
  version: '3.0',
76
81
  templates: {},
@@ -84,7 +89,7 @@ export function loadConfig(): PtConfig {
84
89
  }
85
90
 
86
91
  try {
87
- const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
92
+ const content = fs.readFileSync(getConfigPath(), 'utf-8');
88
93
  if (!content.trim()) {
89
94
  throw new Error("Config file is empty");
90
95
  }
@@ -142,7 +147,7 @@ export function loadConfig(): PtConfig {
142
147
  const error = err as Error;
143
148
  console.error(chalk.red(`\nError loading config: ${error.message}`));
144
149
  // If we have a backup, maybe suggest using it
145
- const backupPath = CONFIG_PATH + '.bak';
150
+ const backupPath = getConfigPath() + '.bak';
146
151
  if (fs.existsSync(backupPath)) {
147
152
  console.error(chalk.yellow(`A backup exists at ${backupPath}. You may want to restore it.`));
148
153
  }
@@ -175,20 +180,20 @@ export function saveConfig(config: PtConfig) {
175
180
  }
176
181
 
177
182
  const content = YAML.stringify(config);
178
- const tempPath = CONFIG_PATH + '.tmp';
179
- const backupPath = CONFIG_PATH + '.bak';
183
+ const tempPath = getConfigPath() + '.tmp';
184
+ const backupPath = getConfigPath() + '.bak';
180
185
 
181
186
  try {
182
187
  // 1. Create a backup of the current valid config if it exists
183
- if (fs.existsSync(CONFIG_PATH)) {
184
- fs.copyFileSync(CONFIG_PATH, backupPath);
188
+ if (fs.existsSync(getConfigPath())) {
189
+ fs.copyFileSync(getConfigPath(), backupPath);
185
190
  }
186
191
 
187
192
  // 2. Write to a temporary file first (atomic save)
188
193
  fs.writeFileSync(tempPath, content);
189
194
 
190
195
  // 3. Rename temp file to actual config path
191
- fs.renameSync(tempPath, CONFIG_PATH);
196
+ fs.renameSync(tempPath, getConfigPath());
192
197
  } catch (err) {
193
198
  const error = err as Error;
194
199
  console.error(chalk.red(`\nFailed to save config: ${error.message}`));
@@ -0,0 +1,4 @@
1
+ // Save original HOME before modifying
2
+ const originalHome = process
3
+ .env
4
+ .HOME;
@@ -16,7 +16,7 @@ import {
16
16
  shouldExcludeFile,
17
17
  sanitizePath,
18
18
  DEFAULT_EXCLUDES,
19
- HOME_DIR,
19
+ getHomeDir,
20
20
  PtConfig,
21
21
  } from '../src/config.js';
22
22
 
@@ -31,30 +31,30 @@ after(() => {
31
31
 
32
32
  test('ensureConfigDir creates dir when it does not exist', () => {
33
33
  // Make sure the dir does NOT exist before the test
34
- if (fs.existsSync(HOME_DIR)) {
35
- fs.rmSync(HOME_DIR, { recursive: true, force: true });
34
+ if (fs.existsSync(getHomeDir())) {
35
+ fs.rmSync(getHomeDir(), { recursive: true, force: true });
36
36
  }
37
- assert.ok(!fs.existsSync(HOME_DIR), 'Precondition: HOME_DIR should not exist');
37
+ assert.ok(!fs.existsSync(getHomeDir()), 'Precondition: getHomeDir() should not exist');
38
38
 
39
39
  ensureConfigDir();
40
40
 
41
- assert.ok(fs.existsSync(HOME_DIR), 'HOME_DIR should be created');
42
- assert.ok(fs.statSync(HOME_DIR).isDirectory(), 'HOME_DIR should be a directory');
41
+ assert.ok(fs.existsSync(getHomeDir()), 'getHomeDir() should be created');
42
+ assert.ok(fs.statSync(getHomeDir()).isDirectory(), 'getHomeDir() should be a directory');
43
43
  });
44
44
 
45
45
  test('ensureConfigDir does nothing when dir already exists', () => {
46
46
  // Ensure directory exists first
47
- if (!fs.existsSync(HOME_DIR)) {
48
- fs.mkdirSync(HOME_DIR, { recursive: true });
47
+ if (!fs.existsSync(getHomeDir())) {
48
+ fs.mkdirSync(getHomeDir(), { recursive: true });
49
49
  }
50
50
  // Place a marker file inside to prove the dir is not recreated
51
- const markerPath = path.join(HOME_DIR, '.marker');
51
+ const markerPath = path.join(getHomeDir(), '.marker');
52
52
  fs.writeFileSync(markerPath, 'exists');
53
53
 
54
54
  ensureConfigDir();
55
55
 
56
- assert.ok(fs.existsSync(HOME_DIR), 'HOME_DIR should still exist');
57
- assert.ok(fs.existsSync(markerPath), 'Marker file inside HOME_DIR should still exist');
56
+ assert.ok(fs.existsSync(getHomeDir()), 'getHomeDir() should still exist');
57
+ assert.ok(fs.existsSync(markerPath), 'Marker file inside getHomeDir() should still exist');
58
58
 
59
59
  // Clean up marker
60
60
  fs.unlinkSync(markerPath);
@@ -1,17 +1,15 @@
1
- import { test, before, after } from 'node:test';
1
+ import { test, after } from 'node:test';
2
2
  import assert from 'node:assert';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
- import os from 'os';
6
5
 
7
6
  // Force a temporary home directory for testing before importing anything from the CLI
8
7
  const testHome = path.join(process.cwd(), '.test-home-config');
9
8
 
10
- // Mock os.homedir() to return the test directory (Node.js doesn't use HOME env var on Linux)
11
- const originalHomedir = os.homedir;
12
- os.homedir = () => testHome;
9
+ // Set HOME environment variable before importing (needed for dynamic getters)
10
+ process.env.HOME = testHome;
13
11
 
14
- import { normalizeVariable, saveConfig, loadConfig, PtConfig, CONFIG_PATH, HOME_DIR } from '../src/config.js';
12
+ import { normalizeVariable, saveConfig, loadConfig, PtConfig, getConfigPath, getHomeDir } from '../src/config.js';
15
13
 
16
14
  test('normalizeVariable key ordering', () => {
17
15
  const variableInput = {
@@ -32,15 +30,15 @@ test('normalizeVariable key ordering', () => {
32
30
  test('saveConfig and loadConfig roundtrip', () => {
33
31
  // Save the current config to restore later
34
32
  let savedConfig: PtConfig | null = null;
35
- if (fs.existsSync(CONFIG_PATH)) {
33
+ if (fs.existsSync(getConfigPath())) {
36
34
  savedConfig = loadConfig();
37
35
  }
38
36
 
39
37
  // Clean up any existing test files
40
- if (fs.existsSync(CONFIG_PATH)) {
41
- fs.unlinkSync(CONFIG_PATH);
38
+ if (fs.existsSync(getConfigPath())) {
39
+ fs.unlinkSync(getConfigPath());
42
40
  }
43
- const backupPath = CONFIG_PATH + '.bak';
41
+ const backupPath = getConfigPath() + '.bak';
44
42
  if (fs.existsSync(backupPath)) {
45
43
  fs.unlinkSync(backupPath);
46
44
  }
@@ -68,11 +66,11 @@ test('saveConfig and loadConfig roundtrip', () => {
68
66
  // Save the config
69
67
  saveConfig(testConfig);
70
68
 
71
- // Assert CONFIG_PATH exists and has been created
72
- assert.ok(fs.existsSync(CONFIG_PATH), 'Config file should be created');
69
+ // Assert getConfigPath() exists and has been created
70
+ assert.ok(fs.existsSync(getConfigPath()), 'Config file should be created');
73
71
 
74
72
  // Verify key order in YAML string
75
- const yamlContent = fs.readFileSync(CONFIG_PATH, 'utf-8');
73
+ const yamlContent = fs.readFileSync(getConfigPath(), 'utf-8');
76
74
  assert.ok(yamlContent.includes('name: myVar'), 'Should contain myVar variable');
77
75
 
78
76
  // Load the config back
@@ -96,8 +94,8 @@ test('saveConfig and loadConfig roundtrip', () => {
96
94
  assert.ok(backupContent.includes("version: 1.0.0") || backupContent.includes("version: '1.0.0'"), 'Backup should contain previous version');
97
95
 
98
96
  // Clean up
99
- if (fs.existsSync(CONFIG_PATH)) {
100
- fs.unlinkSync(CONFIG_PATH);
97
+ if (fs.existsSync(getConfigPath())) {
98
+ fs.unlinkSync(getConfigPath());
101
99
  }
102
100
  if (fs.existsSync(backupPath)) {
103
101
  fs.unlinkSync(backupPath);
@@ -109,7 +107,7 @@ test('saveConfig and loadConfig roundtrip', () => {
109
107
  }
110
108
 
111
109
  if (fs.existsSync(testHome)) {
112
- fs.rmdirSync(testHome);
110
+ fs.rmSync(testHome, { recursive: true, force: true });
113
111
  }
114
112
  });
115
113
 
@@ -148,15 +146,15 @@ test('normalizeVariable with all fields', () => {
148
146
  test('saveConfig creates backup on overwrite', () => {
149
147
  // Save the current config to restore later
150
148
  let savedConfig: PtConfig | null = null;
151
- if (fs.existsSync(CONFIG_PATH)) {
149
+ if (fs.existsSync(getConfigPath())) {
152
150
  savedConfig = loadConfig();
153
151
  }
154
152
 
155
153
  // Clean up any existing test files
156
- if (fs.existsSync(CONFIG_PATH)) {
157
- fs.unlinkSync(CONFIG_PATH);
154
+ if (fs.existsSync(getConfigPath())) {
155
+ fs.unlinkSync(getConfigPath());
158
156
  }
159
- const backupPath = CONFIG_PATH + '.bak';
157
+ const backupPath = getConfigPath() + '.bak';
160
158
  if (fs.existsSync(backupPath)) {
161
159
  fs.unlinkSync(backupPath);
162
160
  }
@@ -172,7 +170,7 @@ test('saveConfig creates backup on overwrite', () => {
172
170
 
173
171
  // Save first config
174
172
  saveConfig(config1);
175
- assert.ok(fs.existsSync(CONFIG_PATH), 'Config file should be created');
173
+ assert.ok(fs.existsSync(getConfigPath()), 'Config file should be created');
176
174
 
177
175
  // Save second config (should create backup)
178
176
  saveConfig(config2);
@@ -183,8 +181,8 @@ test('saveConfig creates backup on overwrite', () => {
183
181
  assert.ok(backupContent.includes("version: 1.0.0") || backupContent.includes("version: '1.0.0'"), 'Backup should contain previous version');
184
182
 
185
183
  // Clean up
186
- if (fs.existsSync(CONFIG_PATH)) {
187
- fs.unlinkSync(CONFIG_PATH);
184
+ if (fs.existsSync(getConfigPath())) {
185
+ fs.unlinkSync(getConfigPath());
188
186
  }
189
187
  if (fs.existsSync(backupPath)) {
190
188
  fs.unlinkSync(backupPath);
@@ -196,22 +194,22 @@ test('saveConfig creates backup on overwrite', () => {
196
194
  }
197
195
 
198
196
  if (fs.existsSync(testHome)) {
199
- fs.rmdirSync(testHome);
197
+ fs.rmSync(testHome, { recursive: true, force: true });
200
198
  }
201
199
  });
202
200
 
203
201
  test('saveConfig atomic write behavior', () => {
204
202
  // Save the current config to restore later
205
203
  let savedConfig: PtConfig | null = null;
206
- if (fs.existsSync(CONFIG_PATH)) {
204
+ if (fs.existsSync(getConfigPath())) {
207
205
  savedConfig = loadConfig();
208
206
  }
209
207
 
210
208
  // Clean up any existing test files
211
- if (fs.existsSync(CONFIG_PATH)) {
212
- fs.unlinkSync(CONFIG_PATH);
209
+ if (fs.existsSync(getConfigPath())) {
210
+ fs.unlinkSync(getConfigPath());
213
211
  }
214
- const backupPath = CONFIG_PATH + '.bak';
212
+ const backupPath = getConfigPath() + '.bak';
215
213
  if (fs.existsSync(backupPath)) {
216
214
  fs.unlinkSync(backupPath);
217
215
  }
@@ -225,15 +223,15 @@ test('saveConfig atomic write behavior', () => {
225
223
  saveConfig(config);
226
224
 
227
225
  // Verify config exists
228
- assert.ok(fs.existsSync(CONFIG_PATH), 'Config file should be created');
226
+ assert.ok(fs.existsSync(getConfigPath()), 'Config file should be created');
229
227
 
230
228
  // Verify no temp file remains
231
- const tempPath = CONFIG_PATH + '.tmp';
229
+ const tempPath = getConfigPath() + '.tmp';
232
230
  assert.ok(!fs.existsSync(tempPath), 'Temp file should not remain');
233
231
 
234
232
  // Clean up
235
- if (fs.existsSync(CONFIG_PATH)) {
236
- fs.unlinkSync(CONFIG_PATH);
233
+ if (fs.existsSync(getConfigPath())) {
234
+ fs.unlinkSync(getConfigPath());
237
235
  }
238
236
  if (fs.existsSync(backupPath)) {
239
237
  fs.unlinkSync(backupPath);
@@ -245,22 +243,22 @@ test('saveConfig atomic write behavior', () => {
245
243
  }
246
244
 
247
245
  if (fs.existsSync(testHome)) {
248
- fs.rmdirSync(testHome);
246
+ fs.rmSync(testHome, { recursive: true, force: true });
249
247
  }
250
248
  });
251
249
 
252
250
  test('loadConfig returns default config when file missing', () => {
253
251
  // Save the current config to restore later
254
252
  let savedConfig: PtConfig | null = null;
255
- if (fs.existsSync(CONFIG_PATH)) {
253
+ if (fs.existsSync(getConfigPath())) {
256
254
  savedConfig = loadConfig();
257
255
  }
258
256
 
259
257
  // Clean up any existing test files
260
- if (fs.existsSync(CONFIG_PATH)) {
261
- fs.unlinkSync(CONFIG_PATH);
258
+ if (fs.existsSync(getConfigPath())) {
259
+ fs.unlinkSync(getConfigPath());
262
260
  }
263
- const backupPath = CONFIG_PATH + '.bak';
261
+ const backupPath = getConfigPath() + '.bak';
264
262
  if (fs.existsSync(backupPath)) {
265
263
  fs.unlinkSync(backupPath);
266
264
  }
@@ -275,7 +273,7 @@ test('loadConfig returns default config when file missing', () => {
275
273
 
276
274
  // Clean up
277
275
  if (fs.existsSync(testHome)) {
278
- fs.rmdirSync(testHome);
276
+ fs.rmSync(testHome, { recursive: true, force: true });
279
277
  }
280
278
 
281
279
  // Restore the original config if it existed
@@ -287,15 +285,15 @@ test('loadConfig returns default config when file missing', () => {
287
285
  test('saveConfig with variables normalizes key order', () => {
288
286
  // Save the current config to restore later
289
287
  let savedConfig: PtConfig | null = null;
290
- if (fs.existsSync(CONFIG_PATH)) {
288
+ if (fs.existsSync(getConfigPath())) {
291
289
  savedConfig = loadConfig();
292
290
  }
293
291
 
294
292
  // Clean up any existing test files
295
- if (fs.existsSync(CONFIG_PATH)) {
296
- fs.unlinkSync(CONFIG_PATH);
293
+ if (fs.existsSync(getConfigPath())) {
294
+ fs.unlinkSync(getConfigPath());
297
295
  }
298
- const backupPath = CONFIG_PATH + '.bak';
296
+ const backupPath = getConfigPath() + '.bak';
299
297
  if (fs.existsSync(backupPath)) {
300
298
  fs.unlinkSync(backupPath);
301
299
  }
@@ -336,8 +334,8 @@ test('saveConfig with variables normalizes key order', () => {
336
334
  assert.strictEqual(vars[1].name, 'var2', 'Second variable name should be var2');
337
335
 
338
336
  // Clean up
339
- if (fs.existsSync(CONFIG_PATH)) {
340
- fs.unlinkSync(CONFIG_PATH);
337
+ if (fs.existsSync(getConfigPath())) {
338
+ fs.unlinkSync(getConfigPath());
341
339
  }
342
340
  if (fs.existsSync(backupPath)) {
343
341
  fs.unlinkSync(backupPath);
@@ -349,28 +347,33 @@ test('saveConfig with variables normalizes key order', () => {
349
347
  }
350
348
 
351
349
  if (fs.existsSync(testHome)) {
352
- fs.rmdirSync(testHome);
350
+ fs.rmSync(testHome, { recursive: true, force: true });
353
351
  }
354
352
  });
355
353
 
356
354
  test('saveConfig with empty config file', () => {
357
355
  // Save the current config to restore later
358
356
  let savedConfig: PtConfig | null = null;
359
- if (fs.existsSync(CONFIG_PATH)) {
357
+ if (fs.existsSync(getConfigPath())) {
360
358
  savedConfig = loadConfig();
361
359
  }
362
360
 
363
361
  // Clean up any existing test files
364
- if (fs.existsSync(CONFIG_PATH)) {
365
- fs.unlinkSync(CONFIG_PATH);
362
+ if (fs.existsSync(getConfigPath())) {
363
+ fs.unlinkSync(getConfigPath());
366
364
  }
367
- const backupPath = CONFIG_PATH + '.bak';
365
+ const backupPath = getConfigPath() + '.bak';
368
366
  if (fs.existsSync(backupPath)) {
369
367
  fs.unlinkSync(backupPath);
370
368
  }
371
369
 
370
+ // Ensure the config directory exists
371
+ if (!fs.existsSync(getHomeDir())) {
372
+ fs.mkdirSync(getHomeDir(), { recursive: true });
373
+ }
374
+
372
375
  // Create empty config file
373
- fs.writeFileSync(CONFIG_PATH, '');
376
+ fs.writeFileSync(getConfigPath(), '');
374
377
 
375
378
  // loadConfig calls process.exit(1) on error, so we need to mock it
376
379
  // to prevent the test runner from dying
@@ -394,8 +397,8 @@ test('saveConfig with empty config file', () => {
394
397
  }
395
398
 
396
399
  // Clean up
397
- if (fs.existsSync(CONFIG_PATH)) {
398
- fs.unlinkSync(CONFIG_PATH);
400
+ if (fs.existsSync(getConfigPath())) {
401
+ fs.unlinkSync(getConfigPath());
399
402
  }
400
403
  if (fs.existsSync(backupPath)) {
401
404
  fs.unlinkSync(backupPath);
@@ -407,7 +410,7 @@ test('saveConfig with empty config file', () => {
407
410
  }
408
411
 
409
412
  if (fs.existsSync(testHome)) {
410
- fs.rmdirSync(testHome);
413
+ fs.rmSync(testHome, { recursive: true, force: true });
411
414
  }
412
415
  });
413
416
 
@@ -429,15 +432,15 @@ test('normalizeVariable preserves field order', () => {
429
432
  test('saveConfig handles multiple templates', () => {
430
433
  // Save the current config to restore later
431
434
  let savedConfig: PtConfig | null = null;
432
- if (fs.existsSync(CONFIG_PATH)) {
435
+ if (fs.existsSync(getConfigPath())) {
433
436
  savedConfig = loadConfig();
434
437
  }
435
438
 
436
439
  // Clean up any existing test files
437
- if (fs.existsSync(CONFIG_PATH)) {
438
- fs.unlinkSync(CONFIG_PATH);
440
+ if (fs.existsSync(getConfigPath())) {
441
+ fs.unlinkSync(getConfigPath());
439
442
  }
440
- const backupPath = CONFIG_PATH + '.bak';
443
+ const backupPath = getConfigPath() + '.bak';
441
444
  if (fs.existsSync(backupPath)) {
442
445
  fs.unlinkSync(backupPath);
443
446
  }
@@ -466,8 +469,8 @@ test('saveConfig handles multiple templates', () => {
466
469
  assert.strictEqual(loaded.templates['template2'].description, 'Template 2');
467
470
 
468
471
  // Clean up
469
- if (fs.existsSync(CONFIG_PATH)) {
470
- fs.unlinkSync(CONFIG_PATH);
472
+ if (fs.existsSync(getConfigPath())) {
473
+ fs.unlinkSync(getConfigPath());
471
474
  }
472
475
  if (fs.existsSync(backupPath)) {
473
476
  fs.unlinkSync(backupPath);
@@ -479,22 +482,22 @@ test('saveConfig handles multiple templates', () => {
479
482
  }
480
483
 
481
484
  if (fs.existsSync(testHome)) {
482
- fs.rmdirSync(testHome);
485
+ fs.rmSync(testHome, { recursive: true, force: true });
483
486
  }
484
487
  });
485
488
 
486
489
  test('saveConfig handles templates with variables', () => {
487
490
  // Save the current config to restore later
488
491
  let savedConfig: PtConfig | null = null;
489
- if (fs.existsSync(CONFIG_PATH)) {
492
+ if (fs.existsSync(getConfigPath())) {
490
493
  savedConfig = loadConfig();
491
494
  }
492
495
 
493
496
  // Clean up any existing test files
494
- if (fs.existsSync(CONFIG_PATH)) {
495
- fs.unlinkSync(CONFIG_PATH);
497
+ if (fs.existsSync(getConfigPath())) {
498
+ fs.unlinkSync(getConfigPath());
496
499
  }
497
- const backupPath = CONFIG_PATH + '.bak';
500
+ const backupPath = getConfigPath() + '.bak';
498
501
  if (fs.existsSync(backupPath)) {
499
502
  fs.unlinkSync(backupPath);
500
503
  }
@@ -532,8 +535,8 @@ test('saveConfig handles templates with variables', () => {
532
535
  assert.strictEqual(template.variables?.[0].required, true, 'Variable should be required');
533
536
 
534
537
  // Clean up
535
- if (fs.existsSync(CONFIG_PATH)) {
536
- fs.unlinkSync(CONFIG_PATH);
538
+ if (fs.existsSync(getConfigPath())) {
539
+ fs.unlinkSync(getConfigPath());
537
540
  }
538
541
  if (fs.existsSync(backupPath)) {
539
542
  fs.unlinkSync(backupPath);
@@ -545,26 +548,31 @@ test('saveConfig handles templates with variables', () => {
545
548
  }
546
549
 
547
550
  if (fs.existsSync(testHome)) {
548
- fs.rmdirSync(testHome);
551
+ fs.rmSync(testHome, { recursive: true, force: true });
549
552
  }
550
553
  });
551
554
 
552
555
  test('loadConfig handles migration from v2.0', () => {
553
556
  // Save the current config to restore later
554
557
  let savedConfig: PtConfig | null = null;
555
- if (fs.existsSync(CONFIG_PATH)) {
558
+ if (fs.existsSync(getConfigPath())) {
556
559
  savedConfig = loadConfig();
557
560
  }
558
561
 
559
562
  // Clean up any existing test files
560
- if (fs.existsSync(CONFIG_PATH)) {
561
- fs.unlinkSync(CONFIG_PATH);
563
+ if (fs.existsSync(getConfigPath())) {
564
+ fs.unlinkSync(getConfigPath());
562
565
  }
563
- const backupPath = CONFIG_PATH + '.bak';
566
+ const backupPath = getConfigPath() + '.bak';
564
567
  if (fs.existsSync(backupPath)) {
565
568
  fs.unlinkSync(backupPath);
566
569
  }
567
570
 
571
+ // Ensure the config directory exists
572
+ if (!fs.existsSync(getHomeDir())) {
573
+ fs.mkdirSync(getHomeDir(), { recursive: true });
574
+ }
575
+
568
576
  // Create a v2.0 config
569
577
  const v2Config = {
570
578
  version: '2.0',
@@ -577,7 +585,7 @@ test('loadConfig handles migration from v2.0', () => {
577
585
  }
578
586
  };
579
587
 
580
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(v2Config));
588
+ fs.writeFileSync(getConfigPath(), JSON.stringify(v2Config));
581
589
 
582
590
  // Load config - should migrate to v3.0
583
591
  const config = loadConfig();
@@ -588,8 +596,8 @@ test('loadConfig handles migration from v2.0', () => {
588
596
  assert.ok(!config.templates['test-template'].type, 'Type should be removed');
589
597
 
590
598
  // Clean up
591
- if (fs.existsSync(CONFIG_PATH)) {
592
- fs.unlinkSync(CONFIG_PATH);
599
+ if (fs.existsSync(getConfigPath())) {
600
+ fs.unlinkSync(getConfigPath());
593
601
  }
594
602
  if (fs.existsSync(backupPath)) {
595
603
  fs.unlinkSync(backupPath);
@@ -601,26 +609,31 @@ test('loadConfig handles migration from v2.0', () => {
601
609
  }
602
610
 
603
611
  if (fs.existsSync(testHome)) {
604
- fs.rmdirSync(testHome);
612
+ fs.rmSync(testHome, { recursive: true, force: true });
605
613
  }
606
614
  });
607
615
 
608
616
  test('loadConfig handles migration from v2.0 with global_post_config', () => {
609
617
  // Save the current config to restore later
610
618
  let savedConfig: PtConfig | null = null;
611
- if (fs.existsSync(CONFIG_PATH)) {
619
+ if (fs.existsSync(getConfigPath())) {
612
620
  savedConfig = loadConfig();
613
621
  }
614
622
 
615
623
  // Clean up any existing test files
616
- if (fs.existsSync(CONFIG_PATH)) {
617
- fs.unlinkSync(CONFIG_PATH);
624
+ if (fs.existsSync(getConfigPath())) {
625
+ fs.unlinkSync(getConfigPath());
618
626
  }
619
- const backupPath = CONFIG_PATH + '.bak';
627
+ const backupPath = getConfigPath() + '.bak';
620
628
  if (fs.existsSync(backupPath)) {
621
629
  fs.unlinkSync(backupPath);
622
630
  }
623
631
 
632
+ // Ensure the config directory exists
633
+ if (!fs.existsSync(getHomeDir())) {
634
+ fs.mkdirSync(getHomeDir(), { recursive: true });
635
+ }
636
+
624
637
  // Create a v2.0 config with global_post_config
625
638
  const v2Config = {
626
639
  version: '2.0',
@@ -633,7 +646,7 @@ test('loadConfig handles migration from v2.0 with global_post_config', () => {
633
646
  ]
634
647
  };
635
648
 
636
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(v2Config));
649
+ fs.writeFileSync(getConfigPath(), JSON.stringify(v2Config));
637
650
 
638
651
  // Load config - should migrate to v3.0
639
652
  const config = loadConfig();
@@ -649,8 +662,8 @@ test('loadConfig handles migration from v2.0 with global_post_config', () => {
649
662
  assert.ok(!config.global_post_config, 'global_post_config should be removed');
650
663
 
651
664
  // Clean up
652
- if (fs.existsSync(CONFIG_PATH)) {
653
- fs.unlinkSync(CONFIG_PATH);
665
+ if (fs.existsSync(getConfigPath())) {
666
+ fs.unlinkSync(getConfigPath());
654
667
  }
655
668
  if (fs.existsSync(backupPath)) {
656
669
  fs.unlinkSync(backupPath);
@@ -662,26 +675,31 @@ test('loadConfig handles migration from v2.0 with global_post_config', () => {
662
675
  }
663
676
 
664
677
  if (fs.existsSync(testHome)) {
665
- fs.rmdirSync(testHome);
678
+ fs.rmSync(testHome, { recursive: true, force: true });
666
679
  }
667
680
  });
668
681
 
669
682
  test('loadConfig handles migration from v2.0 with variables', () => {
670
683
  // Save the current config to restore later
671
684
  let savedConfig: PtConfig | null = null;
672
- if (fs.existsSync(CONFIG_PATH)) {
685
+ if (fs.existsSync(getConfigPath())) {
673
686
  savedConfig = loadConfig();
674
687
  }
675
688
 
676
689
  // Clean up any existing test files
677
- if (fs.existsSync(CONFIG_PATH)) {
678
- fs.unlinkSync(CONFIG_PATH);
690
+ if (fs.existsSync(getConfigPath())) {
691
+ fs.unlinkSync(getConfigPath());
679
692
  }
680
- const backupPath = CONFIG_PATH + '.bak';
693
+ const backupPath = getConfigPath() + '.bak';
681
694
  if (fs.existsSync(backupPath)) {
682
695
  fs.unlinkSync(backupPath);
683
696
  }
684
697
 
698
+ // Ensure the config directory exists
699
+ if (!fs.existsSync(getHomeDir())) {
700
+ fs.mkdirSync(getHomeDir(), { recursive: true });
701
+ }
702
+
685
703
  // Create a v2.0 config with variables as Record<string, string>
686
704
  const v2Config = {
687
705
  version: '2.0',
@@ -692,7 +710,7 @@ test('loadConfig handles migration from v2.0 with variables', () => {
692
710
  }
693
711
  };
694
712
 
695
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(v2Config));
713
+ fs.writeFileSync(getConfigPath(), JSON.stringify(v2Config));
696
714
 
697
715
  // Load config - should migrate to v3.0
698
716
  const config = loadConfig();
@@ -706,8 +724,8 @@ test('loadConfig handles migration from v2.0 with variables', () => {
706
724
  assert.strictEqual(config.variables[1].default, 'web', 'Second variable default should be web');
707
725
 
708
726
  // Clean up
709
- if (fs.existsSync(CONFIG_PATH)) {
710
- fs.unlinkSync(CONFIG_PATH);
727
+ if (fs.existsSync(getConfigPath())) {
728
+ fs.unlinkSync(getConfigPath());
711
729
  }
712
730
  if (fs.existsSync(backupPath)) {
713
731
  fs.unlinkSync(backupPath);
@@ -719,7 +737,7 @@ test('loadConfig handles migration from v2.0 with variables', () => {
719
737
  }
720
738
 
721
739
  if (fs.existsSync(testHome)) {
722
- fs.rmdirSync(testHome);
740
+ fs.rmSync(testHome, { recursive: true, force: true });
723
741
  }
724
742
  });
725
743
 
@@ -737,7 +755,7 @@ test('loadConfig handles existing config file', () => {
737
755
  test('saveConfig preserves existing config when not deleting', () => {
738
756
  // Save the current config to restore later
739
757
  let savedConfig: PtConfig | null = null;
740
- if (fs.existsSync(CONFIG_PATH)) {
758
+ if (fs.existsSync(getConfigPath())) {
741
759
  savedConfig = loadConfig();
742
760
  }
743
761
 
@@ -768,11 +786,8 @@ test('saveConfig preserves existing config when not deleting', () => {
768
786
 
769
787
  // Cleanup: restore original os.homedir function and clean up test directory
770
788
  after(() => {
771
- // Restore original os.homedir function
772
- os.homedir = originalHomedir;
773
-
774
789
  // Clean up test home directory if it exists
775
790
  if (fs.existsSync(testHome)) {
776
- fs.rmdirSync(testHome, { recursive: true });
791
+ fs.rmSync(testHome, { recursive: true, force: true });
777
792
  }
778
793
  });
@@ -8,7 +8,7 @@ const testHome = path.join(process.cwd(), '.test-home-init');
8
8
  process.env.HOME = testHome;
9
9
 
10
10
  import { init } from '../src/commands/initCommand.js';
11
- import { loadConfig, saveConfig, PtConfig, CONFIG_PATH } from '../src/config.js';
11
+ import { loadConfig, saveConfig, PtConfig, getConfigPath } from '../src/config.js';
12
12
 
13
13
  // Helper to clean up test directories
14
14
  function cleanup(...paths: string[]) {
@@ -9,7 +9,7 @@ const testHome = path.join(process.cwd(), '.test-home-learn');
9
9
  process.env.HOME = testHome;
10
10
 
11
11
  import { learn } from '../src/commands/learnCommand.js';
12
- import { loadConfig, saveConfig, PtConfig, CONFIG_PATH } from '../src/config.js';
12
+ import { loadConfig, saveConfig, PtConfig, getConfigPath } from '../src/config.js';
13
13
 
14
14
  // ---------------------------------------------------------------------------
15
15
  // Helpers
@@ -31,8 +31,8 @@ function rmrf(p: string) {
31
31
 
32
32
  /** Ensure a clean test config state (no leftover config files). */
33
33
  function cleanConfig() {
34
- if (fs.existsSync(CONFIG_PATH)) fs.unlinkSync(CONFIG_PATH);
35
- const bak = CONFIG_PATH + '.bak';
34
+ if (fs.existsSync(getConfigPath())) fs.unlinkSync(getConfigPath());
35
+ const bak = getConfigPath() + '.bak';
36
36
  if (fs.existsSync(bak)) fs.unlinkSync(bak);
37
37
  }
38
38