@garyr/pt-cli 1.0.1 → 1.1.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 +41 -14
- package/dist/commands/completionCommand.js +396 -0
- package/dist/commands/learnCommand.js +28 -7
- package/dist/commands/template-utils.js +17 -7
- package/dist/commands/updateCommand.js +56 -25
- package/dist/config.js +3 -0
- package/dist/index.js +9 -1
- package/doc/usage.md +38 -0
- package/package.json +4 -1
- package/src/commands/completionCommand.ts +404 -0
- package/src/commands/learnCommand.ts +29 -7
- package/src/commands/template-utils.ts +17 -8
- package/src/commands/updateCommand.ts +125 -91
- package/src/config.ts +3 -0
- package/src/index.ts +10 -1
- package/tests/completion.test.ts +210 -0
- package/tests/config-utils.test.ts +1 -1
- package/tests/learn.test.ts +5 -4
- package/tests/update.test.ts +63 -9
package/README.md
CHANGED
|
@@ -32,20 +32,27 @@ graph LR
|
|
|
32
32
|
<!-- TOC -->
|
|
33
33
|
|
|
34
34
|
- [pt - Project Template CLI](#pt---project-template-cli)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
- [Why pt-cli?](#why-pt-cli)
|
|
36
|
+
- [Core Benefits \& Uses](#core-benefits--uses)
|
|
37
|
+
- [🚀 Low-Friction Templating](#-low-friction-templating)
|
|
38
|
+
- [🧠 Reduces Cognitive Load](#-reduces-cognitive-load)
|
|
39
|
+
- [📦 Sharing is Caring](#-sharing-is-caring)
|
|
40
|
+
- [🤖 Agentic and API Friendly](#-agentic-and-api-friendly)
|
|
41
|
+
- [Features at a Glance](#features-at-a-glance)
|
|
42
|
+
- [Quick Start](#quick-start)
|
|
43
|
+
- [Installation](#installation)
|
|
44
|
+
- [Basic Commands](#basic-commands)
|
|
45
|
+
- [Shell Completions](#shell-completions)
|
|
46
|
+
- [Agent Integration](#agent-integration)
|
|
47
|
+
- [Documentation](#documentation)
|
|
48
|
+
- [Development](#development)
|
|
49
|
+
- [Where are the Templates?](#where-are-the-templates)
|
|
50
|
+
- [1.0 Release \& API Stability](#10-release--api-stability)
|
|
51
|
+
- [🔒 Stability Guarantee (1.x series)](#-stability-guarantee-1x-series)
|
|
52
|
+
- [📦 Versioning Policy](#-versioning-policy)
|
|
53
|
+
- [📋 What's Locked in 1.0](#-whats-locked-in-10)
|
|
54
|
+
- [📖 Migration from 0.x to 1.0](#-migration-from-0x-to-10)
|
|
55
|
+
- [Documentation](#documentation-1)
|
|
49
56
|
|
|
50
57
|
<!-- /TOC -->
|
|
51
58
|
|
|
@@ -124,6 +131,25 @@ pt init ./new-project --file my-template.json --yes
|
|
|
124
131
|
|
|
125
132
|
```
|
|
126
133
|
|
|
134
|
+
### Shell Completions
|
|
135
|
+
|
|
136
|
+
Generate and install tab completions:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Bash
|
|
140
|
+
pt completion bash > /etc/bash_completion.d/pt
|
|
141
|
+
# Or user-local: pt completion bash > ~/.local/share/bash-completion/completions/pt
|
|
142
|
+
|
|
143
|
+
# Zsh
|
|
144
|
+
pt completion zsh > ~/.zsh/completions/_pt
|
|
145
|
+
# Add to ~/.zshrc: fpath=(~/.zsh/completions $fpath)
|
|
146
|
+
|
|
147
|
+
# Fish
|
|
148
|
+
pt completion fish > ~/.config/fish/completions/pt.fish
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Template names auto-complete dynamically for `pt init`, `pt update`, `pt config`, and `pt remove`.
|
|
152
|
+
|
|
127
153
|
## Agent Integration
|
|
128
154
|
|
|
129
155
|
`pt-cli` is fully compatible with AI agents. By utilizing non-interactive flags (`--yes`, `--vars`, `--name`, `--desc`), agents can autonomously scaffold and learn projects without hanging on interactive terminal prompts.
|
|
@@ -191,6 +217,7 @@ pt variables [--set] [--delete] [--json]
|
|
|
191
217
|
pt default-post-config [--set --json]
|
|
192
218
|
pt ignore [patterns] [--set]
|
|
193
219
|
pt security-response <response>
|
|
220
|
+
pt completion <shell>
|
|
194
221
|
```
|
|
195
222
|
|
|
196
223
|
**Config Schema (`~/.pt/config.yaml` v3.0):**
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import YAML from 'yaml';
|
|
3
|
+
import { getConfigPath } from '../config.js';
|
|
4
|
+
export function getTemplatesForCompletion() {
|
|
5
|
+
try {
|
|
6
|
+
const configPath = getConfigPath();
|
|
7
|
+
if (!fs.existsSync(configPath)) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
11
|
+
if (!content.trim()) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const parsed = YAML.parse(content);
|
|
15
|
+
if (!parsed || !parsed.templates || typeof parsed.templates !== 'object') {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return Object.keys(parsed.templates);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function generateBashCompletion() {
|
|
25
|
+
return `# Bash completion for pt
|
|
26
|
+
_pt_completions() {
|
|
27
|
+
local cur prev words cword
|
|
28
|
+
if declare -F _init_completion >/dev/null 2>&1; then
|
|
29
|
+
_init_completion || return
|
|
30
|
+
else
|
|
31
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
32
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
33
|
+
words=("\${COMP_WORDS[@]}")
|
|
34
|
+
cword=$COMP_CWORD
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
local commands="learn update init config ignore variables default-post-config add remove rm security-response completion"
|
|
38
|
+
|
|
39
|
+
# Complete top-level command or global flags
|
|
40
|
+
if [[ $cword -eq 1 ]]; then
|
|
41
|
+
if [[ "$cur" == -* ]]; then
|
|
42
|
+
COMPREPLY=( $(compgen -W "-v --version -h --help" -- "$cur") )
|
|
43
|
+
else
|
|
44
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
45
|
+
fi
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
local cmd="\${words[1]}"
|
|
50
|
+
|
|
51
|
+
case "$cmd" in
|
|
52
|
+
learn)
|
|
53
|
+
if [[ "$cur" == -* ]]; then
|
|
54
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --name --desc --json --allow-untrusted -h --help" -- "$cur") )
|
|
55
|
+
fi
|
|
56
|
+
;;
|
|
57
|
+
update)
|
|
58
|
+
if [[ "$cur" == -* ]]; then
|
|
59
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --desc --no-diff -h --help" -- "$cur") )
|
|
60
|
+
elif [[ $cword -eq 2 ]]; then
|
|
61
|
+
local templates
|
|
62
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
63
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
64
|
+
fi
|
|
65
|
+
;;
|
|
66
|
+
init)
|
|
67
|
+
if [[ "$cur" == -* ]]; then
|
|
68
|
+
COMPREPLY=( $(compgen -W "-f --file --skip-post-config --dry-run -y --yes --vars -h --help" -- "$cur") )
|
|
69
|
+
elif [[ $cword -eq 2 ]]; then
|
|
70
|
+
local templates
|
|
71
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
72
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
73
|
+
fi
|
|
74
|
+
;;
|
|
75
|
+
config)
|
|
76
|
+
if [[ "$cur" == -* ]]; then
|
|
77
|
+
COMPREPLY=( $(compgen -W "--json -h --help" -- "$cur") )
|
|
78
|
+
elif [[ $cword -eq 2 ]]; then
|
|
79
|
+
local templates
|
|
80
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
81
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
82
|
+
fi
|
|
83
|
+
;;
|
|
84
|
+
ignore)
|
|
85
|
+
if [[ "$cur" == -* ]]; then
|
|
86
|
+
COMPREPLY=( $(compgen -W "--set -h --help" -- "$cur") )
|
|
87
|
+
fi
|
|
88
|
+
;;
|
|
89
|
+
variables)
|
|
90
|
+
if [[ "$cur" == -* ]]; then
|
|
91
|
+
COMPREPLY=( $(compgen -W "--set --json --delete -h --help" -- "$cur") )
|
|
92
|
+
fi
|
|
93
|
+
;;
|
|
94
|
+
default-post-config)
|
|
95
|
+
if [[ "$cur" == -* ]]; then
|
|
96
|
+
COMPREPLY=( $(compgen -W "--set --json -h --help" -- "$cur") )
|
|
97
|
+
fi
|
|
98
|
+
;;
|
|
99
|
+
add)
|
|
100
|
+
if [[ "$cur" == -* ]]; then
|
|
101
|
+
COMPREPLY=( $(compgen -W "-f --file -h --help" -- "$cur") )
|
|
102
|
+
fi
|
|
103
|
+
;;
|
|
104
|
+
remove|rm)
|
|
105
|
+
if [[ "$cur" == -* ]]; then
|
|
106
|
+
COMPREPLY=( $(compgen -W "-y --yes -h --help" -- "$cur") )
|
|
107
|
+
elif [[ $cword -eq 2 ]]; then
|
|
108
|
+
local templates
|
|
109
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
110
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
111
|
+
fi
|
|
112
|
+
;;
|
|
113
|
+
completion)
|
|
114
|
+
if [[ "$cur" == -* ]]; then
|
|
115
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
116
|
+
elif [[ $cword -eq 2 ]]; then
|
|
117
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
118
|
+
fi
|
|
119
|
+
;;
|
|
120
|
+
security-response)
|
|
121
|
+
if [[ "$cur" == -* ]]; then
|
|
122
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
123
|
+
fi
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
complete -F _pt_completions pt
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
export function generateZshCompletion() {
|
|
132
|
+
return `#compdef pt
|
|
133
|
+
|
|
134
|
+
_pt_templates() {
|
|
135
|
+
local -a templates
|
|
136
|
+
templates=(\${(f)"$(pt completion --templates 2>/dev/null)"})
|
|
137
|
+
if [[ \${#templates[@]} -gt 0 ]]; then
|
|
138
|
+
_describe -t templates 'template' templates
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_pt() {
|
|
143
|
+
local context state state_policy
|
|
144
|
+
typeset -A opt_args
|
|
145
|
+
|
|
146
|
+
_arguments -C \\
|
|
147
|
+
'(-v --version)'{-v,--version}'[output the version number]' \\
|
|
148
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
149
|
+
'1: :->command' \\
|
|
150
|
+
'*:: :->args'
|
|
151
|
+
|
|
152
|
+
case $state in
|
|
153
|
+
command)
|
|
154
|
+
local -a commands
|
|
155
|
+
commands=(
|
|
156
|
+
'learn:Learn a project structure from an existing directory'
|
|
157
|
+
'update:Update an existing template with new structure/files'
|
|
158
|
+
'init:Initialize a new project from a learned template'
|
|
159
|
+
'config:Show current config location and list templates, or export a specific template'
|
|
160
|
+
'ignore:View or set global ignore patterns (comma-separated)'
|
|
161
|
+
'variables:View or set global variables (comma-separated key=value)'
|
|
162
|
+
'default-post-config:View or set default post-config tasks'
|
|
163
|
+
'add:Import/add a template from a JSON string or file'
|
|
164
|
+
'remove:Remove a learned template from the config'
|
|
165
|
+
'rm:Remove a learned template from the config'
|
|
166
|
+
'security-response:Handle security response from GUI'
|
|
167
|
+
'completion:Generate shell completion script'
|
|
168
|
+
)
|
|
169
|
+
_describe -t commands 'pt command' commands
|
|
170
|
+
;;
|
|
171
|
+
args)
|
|
172
|
+
case $words[1] in
|
|
173
|
+
learn)
|
|
174
|
+
_arguments \\
|
|
175
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
176
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
177
|
+
'--name=[Template name]:name:' \\
|
|
178
|
+
'--desc=[Template description]:description:' \\
|
|
179
|
+
'--json[Output template structure as JSON for sharing instead of saving]' \\
|
|
180
|
+
'--allow-untrusted[Bypass the trusted-source check for remote URLs]' \\
|
|
181
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
182
|
+
'1:path:_files -/'
|
|
183
|
+
;;
|
|
184
|
+
update)
|
|
185
|
+
_arguments \\
|
|
186
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
187
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
188
|
+
'--desc=[Template description]:description:' \\
|
|
189
|
+
'--no-diff[Disable additive mode, show full list]' \\
|
|
190
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
191
|
+
'1:template:_pt_templates' \\
|
|
192
|
+
'2:sourcePath:_files -/'
|
|
193
|
+
;;
|
|
194
|
+
init)
|
|
195
|
+
_arguments \\
|
|
196
|
+
'(-f --file)'{-f,--file}'[Initialize directly from a JSON template file]:file:_files' \\
|
|
197
|
+
'--skip-post-config[Skip running post-config tasks]' \\
|
|
198
|
+
'--dry-run[Show what would be created without making changes]' \\
|
|
199
|
+
'(-y --yes)'{-y,--yes}'[Automatically answer yes to prompts]' \\
|
|
200
|
+
'--vars=[Comma-separated key=value variables]:variables:' \\
|
|
201
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
202
|
+
'1:template:_pt_templates' \\
|
|
203
|
+
'2:destPath:_files -/'
|
|
204
|
+
;;
|
|
205
|
+
config)
|
|
206
|
+
_arguments \\
|
|
207
|
+
'--json[Output config or specific template as JSON]' \\
|
|
208
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
209
|
+
'1:template:_pt_templates'
|
|
210
|
+
;;
|
|
211
|
+
ignore)
|
|
212
|
+
_arguments \\
|
|
213
|
+
'--set[Set the ignore patterns to the provided value]' \\
|
|
214
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
215
|
+
'1:patterns:'
|
|
216
|
+
;;
|
|
217
|
+
variables)
|
|
218
|
+
_arguments \\
|
|
219
|
+
'--set[Set the variables to the provided pairs]' \\
|
|
220
|
+
'--json=[Set variables via JSON string or file]:data:' \\
|
|
221
|
+
'--delete=[Delete a specific global variable]:key:' \\
|
|
222
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
223
|
+
'1:pairs:'
|
|
224
|
+
;;
|
|
225
|
+
default-post-config)
|
|
226
|
+
_arguments \\
|
|
227
|
+
'--set[Set the default post-config tasks via JSON]' \\
|
|
228
|
+
'--json=[JSON string or file containing tasks array]:data:' \\
|
|
229
|
+
'(-h --help)'{-h,--help}'[display help for command]'
|
|
230
|
+
;;
|
|
231
|
+
add)
|
|
232
|
+
_arguments \\
|
|
233
|
+
'(-f --file)'{-f,--file}'[Path to JSON file containing template data]:file:_files' \\
|
|
234
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
235
|
+
'1:name:' \\
|
|
236
|
+
'2:json:'
|
|
237
|
+
;;
|
|
238
|
+
remove|rm)
|
|
239
|
+
_arguments \\
|
|
240
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm removal]' \\
|
|
241
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
242
|
+
'1:template:_pt_templates'
|
|
243
|
+
;;
|
|
244
|
+
security-response)
|
|
245
|
+
_arguments \\
|
|
246
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
247
|
+
'1:response:'
|
|
248
|
+
;;
|
|
249
|
+
completion)
|
|
250
|
+
_arguments \\
|
|
251
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
252
|
+
'1:shell:(bash zsh fish)'
|
|
253
|
+
;;
|
|
254
|
+
esac
|
|
255
|
+
;;
|
|
256
|
+
esac
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if [[ "$(basename -- "$0")" != "_pt" ]]; then
|
|
260
|
+
compdef _pt pt 2>/dev/null || true
|
|
261
|
+
fi
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
export function generateFishCompletion() {
|
|
265
|
+
return `# Fish completion for pt
|
|
266
|
+
|
|
267
|
+
function __fish_pt_needs_command
|
|
268
|
+
set -l cmd (commandline -opc)
|
|
269
|
+
if [ (count $cmd) -eq 1 ]
|
|
270
|
+
return 0
|
|
271
|
+
end
|
|
272
|
+
return 1
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
function __fish_pt_using_command
|
|
276
|
+
set -l cmd (commandline -opc)
|
|
277
|
+
if [ (count $cmd) -gt 1 ]
|
|
278
|
+
if [ "$argv[1]" = "$cmd[2]" ]
|
|
279
|
+
return 0
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
return 1
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
function __fish_pt_templates
|
|
286
|
+
pt completion --templates 2>/dev/null
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Global options
|
|
290
|
+
complete -c pt -n '__fish_pt_needs_command' -s v -l version -d 'output the version number'
|
|
291
|
+
complete -c pt -n '__fish_pt_needs_command' -s h -l help -d 'display help for command'
|
|
292
|
+
|
|
293
|
+
# Commands
|
|
294
|
+
complete -c pt -n '__fish_pt_needs_command' -a learn -d 'Learn a project structure from an existing directory'
|
|
295
|
+
complete -c pt -n '__fish_pt_needs_command' -a update -d 'Update an existing template with new structure/files'
|
|
296
|
+
complete -c pt -n '__fish_pt_needs_command' -a init -d 'Initialize a new project from a learned template'
|
|
297
|
+
complete -c pt -n '__fish_pt_needs_command' -a config -d 'Show current config location and list templates, or export a specific template'
|
|
298
|
+
complete -c pt -n '__fish_pt_needs_command' -a ignore -d 'View or set global ignore patterns (comma-separated)'
|
|
299
|
+
complete -c pt -n '__fish_pt_needs_command' -a variables -d 'View or set global variables (comma-separated key=value)'
|
|
300
|
+
complete -c pt -n '__fish_pt_needs_command' -a default-post-config -d 'View or set default post-config tasks'
|
|
301
|
+
complete -c pt -n '__fish_pt_needs_command' -a add -d 'Import/add a template from a JSON string or file'
|
|
302
|
+
complete -c pt -n '__fish_pt_needs_command' -a remove -d 'Remove a learned template from the config'
|
|
303
|
+
complete -c pt -n '__fish_pt_needs_command' -a rm -d 'Remove a learned template from the config'
|
|
304
|
+
complete -c pt -n '__fish_pt_needs_command' -a security-response -d 'Handle security response from GUI'
|
|
305
|
+
complete -c pt -n '__fish_pt_needs_command' -a completion -d 'Generate shell completion script'
|
|
306
|
+
|
|
307
|
+
# learn
|
|
308
|
+
complete -c pt -n '__fish_pt_using_command learn' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
309
|
+
complete -c pt -n '__fish_pt_using_command learn' -s y -l yes -d 'Automatically confirm prompts'
|
|
310
|
+
complete -c pt -n '__fish_pt_using_command learn' -l name -d 'Template name (skip prompt)'
|
|
311
|
+
complete -c pt -n '__fish_pt_using_command learn' -l desc -d 'Template description (skip prompt)'
|
|
312
|
+
complete -c pt -n '__fish_pt_using_command learn' -l json -d 'Output template structure as JSON for sharing instead of saving'
|
|
313
|
+
complete -c pt -n '__fish_pt_using_command learn' -l allow-untrusted -d 'Bypass the trusted-source check for remote URLs'
|
|
314
|
+
|
|
315
|
+
# update
|
|
316
|
+
complete -c pt -n '__fish_pt_using_command update' -a '(__fish_pt_templates)' -d 'Template name'
|
|
317
|
+
complete -c pt -n '__fish_pt_using_command update' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
318
|
+
complete -c pt -n '__fish_pt_using_command update' -s y -l yes -d 'Automatically confirm prompts'
|
|
319
|
+
complete -c pt -n '__fish_pt_using_command update' -l desc -d 'Template description (skip prompt)'
|
|
320
|
+
complete -c pt -n '__fish_pt_using_command update' -l no-diff -d 'Disable additive mode, show full list'
|
|
321
|
+
|
|
322
|
+
# init
|
|
323
|
+
complete -c pt -n '__fish_pt_using_command init' -a '(__fish_pt_templates)' -d 'Template name'
|
|
324
|
+
complete -c pt -n '__fish_pt_using_command init' -s f -l file -d 'Initialize directly from a JSON template file without adding it to local config'
|
|
325
|
+
complete -c pt -n '__fish_pt_using_command init' -l skip-post-config -d 'Skip running post-config tasks'
|
|
326
|
+
complete -c pt -n '__fish_pt_using_command init' -l dry-run -d 'Show what would be created without making changes'
|
|
327
|
+
complete -c pt -n '__fish_pt_using_command init' -s y -l yes -d 'Automatically answer yes to prompts'
|
|
328
|
+
complete -c pt -n '__fish_pt_using_command init' -l vars -d 'Comma-separated key=value variables'
|
|
329
|
+
|
|
330
|
+
# config
|
|
331
|
+
complete -c pt -n '__fish_pt_using_command config' -a '(__fish_pt_templates)' -d 'Template name'
|
|
332
|
+
complete -c pt -n '__fish_pt_using_command config' -l json -d 'Output config or specific template as JSON'
|
|
333
|
+
|
|
334
|
+
# ignore
|
|
335
|
+
complete -c pt -n '__fish_pt_using_command ignore' -l set -d 'Set the ignore patterns to the provided value'
|
|
336
|
+
|
|
337
|
+
# variables
|
|
338
|
+
complete -c pt -n '__fish_pt_using_command variables' -l set -d 'Set the variables to the provided pairs'
|
|
339
|
+
complete -c pt -n '__fish_pt_using_command variables' -l json -d 'Set variables via JSON string or file'
|
|
340
|
+
complete -c pt -n '__fish_pt_using_command variables' -l delete -d 'Delete a specific global variable'
|
|
341
|
+
|
|
342
|
+
# default-post-config
|
|
343
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l set -d 'Set the default post-config tasks via JSON'
|
|
344
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l json -d 'JSON string or file containing tasks array'
|
|
345
|
+
|
|
346
|
+
# add
|
|
347
|
+
complete -c pt -n '__fish_pt_using_command add' -s f -l file -d 'Path to JSON file containing template data'
|
|
348
|
+
|
|
349
|
+
# remove / rm
|
|
350
|
+
complete -c pt -n '__fish_pt_using_command remove' -a '(__fish_pt_templates)' -d 'Template name'
|
|
351
|
+
complete -c pt -n '__fish_pt_using_command remove' -s y -l yes -d 'Automatically confirm removal'
|
|
352
|
+
complete -c pt -n '__fish_pt_using_command rm' -a '(__fish_pt_templates)' -d 'Template name'
|
|
353
|
+
complete -c pt -n '__fish_pt_using_command rm' -s y -l yes -d 'Automatically confirm removal'
|
|
354
|
+
|
|
355
|
+
# completion
|
|
356
|
+
complete -c pt -n '__fish_pt_using_command completion' -a 'bash zsh fish' -d 'Shell'
|
|
357
|
+
`;
|
|
358
|
+
}
|
|
359
|
+
export function generateShellScript(shell) {
|
|
360
|
+
switch (shell.toLowerCase()) {
|
|
361
|
+
case 'bash':
|
|
362
|
+
return generateBashCompletion();
|
|
363
|
+
case 'zsh':
|
|
364
|
+
return generateZshCompletion();
|
|
365
|
+
case 'fish':
|
|
366
|
+
return generateFishCompletion();
|
|
367
|
+
default: {
|
|
368
|
+
const supported = ['bash', 'zsh', 'fish'];
|
|
369
|
+
throw new Error(`Unsupported shell: ${shell}. Supported: ${supported.join(', ')}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
export function generateCompletion(shell) {
|
|
374
|
+
return generateShellScript(shell);
|
|
375
|
+
}
|
|
376
|
+
export async function completionCommand(shellArg, options) {
|
|
377
|
+
if (options?.templates || shellArg === '--templates' || shellArg === '_templates') {
|
|
378
|
+
const templates = getTemplatesForCompletion();
|
|
379
|
+
if (templates.length > 0) {
|
|
380
|
+
console.log(templates.join('\n'));
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (!shellArg) {
|
|
385
|
+
console.error('Error: Please specify a shell (bash, zsh, fish).');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
const script = generateCompletion(shellArg);
|
|
390
|
+
console.log(script);
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
console.error(err.message || String(err));
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -238,13 +238,19 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
238
238
|
printNoNewFolders();
|
|
239
239
|
folders = existingTemplate.folders;
|
|
240
240
|
}
|
|
241
|
+
// selectedStructure should include ALL folders (existing + added) in additive mode
|
|
242
|
+
// so that the final filter doesn't drop user-selected new folders
|
|
243
|
+
selectedStructure = [
|
|
244
|
+
...new Set([
|
|
245
|
+
...existingTemplate.folders?.map((f) => f.name) || [],
|
|
246
|
+
...folders.filter(f => !existingTemplate.folders?.some(ef => ef.name === f.name)).map(f => f.name),
|
|
247
|
+
]),
|
|
248
|
+
];
|
|
241
249
|
// New files
|
|
242
250
|
const newFiles = rootFiles.filter((f) => !existingTemplate.copy_files?.some((cf) => cf.src === f));
|
|
243
251
|
printNewFiles(newFiles.length, newFiles);
|
|
244
252
|
const addedFiles = await promptNewFiles(newFiles, options);
|
|
245
253
|
selectedFiles = [...(existingTemplate.copy_files?.filter((cf) => !rootDirs.includes(cf.src)).map((cf) => cf.src) || []), ...addedFiles];
|
|
246
|
-
// Structure
|
|
247
|
-
selectedStructure = existingTemplate.folders?.map((f) => f.name) || [];
|
|
248
254
|
// Seed selectedFolders from existing copy_files directory entries
|
|
249
255
|
selectedFolders = (existingTemplate.copy_files || [])
|
|
250
256
|
.filter((f) => rootDirs.includes(f.src))
|
|
@@ -266,7 +272,14 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
266
272
|
}
|
|
267
273
|
// --- COPY FILES ---
|
|
268
274
|
const existingCopyFiles = isUpdate ? config.templates[updateTemplate].copy_files || [] : [];
|
|
269
|
-
|
|
275
|
+
// If JSON template config has copy_files, use those directly (for new templates)
|
|
276
|
+
let copy_files;
|
|
277
|
+
if (!isUpdate && fileTemplateConfig.copy_files && Array.isArray(fileTemplateConfig.copy_files)) {
|
|
278
|
+
copy_files = [...fileTemplateConfig.copy_files];
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
copy_files = buildCopyFiles(selectedFiles, selectedFolders, existingCopyFiles);
|
|
282
|
+
}
|
|
270
283
|
const templateConfig = {
|
|
271
284
|
description: description,
|
|
272
285
|
templateRoot: resolvedPath,
|
|
@@ -308,8 +321,18 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
308
321
|
const detectedExecutables = rootFiles
|
|
309
322
|
.filter(file => isExecutable(path.join(resolvedPath, file), file))
|
|
310
323
|
.filter(file => !shouldExcludeFile(file));
|
|
324
|
+
// In additive mode (isUpdate), only add executables that were explicitly selected by the user
|
|
325
|
+
let selectedExecutables = [];
|
|
326
|
+
if (isUpdate) {
|
|
327
|
+
// In additive mode, only include executables that are in selectedFiles (user explicitly chose them)
|
|
328
|
+
selectedExecutables = detectedExecutables.filter(exec => selectedFiles.includes(exec));
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// In new template mode, include all detected executables (original behavior)
|
|
332
|
+
selectedExecutables = detectedExecutables;
|
|
333
|
+
}
|
|
311
334
|
const existingPostCopy = isUpdate ? config.templates[updateTemplate].post_copy || [] : [];
|
|
312
|
-
const post_copy = mergePostCopyFiles(existingPostCopy, fileTemplateConfig.post_copy,
|
|
335
|
+
const post_copy = mergePostCopyFiles(existingPostCopy, fileTemplateConfig.post_copy, selectedExecutables);
|
|
313
336
|
if (post_copy.length > 0) {
|
|
314
337
|
templateConfig.post_copy = post_copy;
|
|
315
338
|
const postCopySrcs = post_copy.map(f => f.src);
|
|
@@ -318,9 +341,7 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
318
341
|
// --- OUTPUT ---
|
|
319
342
|
if (options.json) {
|
|
320
343
|
const output = { name: targetName, ...templateConfig };
|
|
321
|
-
process.stdout.write(JSON.stringify(output, null, 2) + '\n'
|
|
322
|
-
process.exit(0);
|
|
323
|
-
});
|
|
344
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
324
345
|
return;
|
|
325
346
|
}
|
|
326
347
|
config.templates[targetName] = templateConfig;
|
|
@@ -153,7 +153,9 @@ export function getRootEntries(dirPath, ignorePatterns) {
|
|
|
153
153
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
|
154
154
|
.filter(e => !shouldExclude(dirPath, path.join(dirPath, e.name), ignorePatterns))
|
|
155
155
|
.filter(e => !shouldIgnore(e.name, e.name, ignorePatterns));
|
|
156
|
-
const files = entries.filter(e => e.isFile())
|
|
156
|
+
const files = entries.filter(e => e.isFile())
|
|
157
|
+
.map(e => e.name)
|
|
158
|
+
.filter(fileName => !shouldExcludeFile(fileName));
|
|
157
159
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
158
160
|
return { files, dirs };
|
|
159
161
|
}
|
|
@@ -204,12 +206,21 @@ export function mergePostConfigTasks(existingTasks, jsonTasks, detectedTasks) {
|
|
|
204
206
|
if (jsonTasks && Array.isArray(jsonTasks)) {
|
|
205
207
|
return [...jsonTasks];
|
|
206
208
|
}
|
|
207
|
-
|
|
209
|
+
// Merge detected tasks with existing, avoiding duplicates
|
|
210
|
+
const merged = [...existingTasks];
|
|
211
|
+
for (const dt of detectedTasks) {
|
|
212
|
+
const exists = merged.some(et => et.command === dt.command && et.script === dt.script);
|
|
213
|
+
if (!exists) {
|
|
214
|
+
merged.push(dt);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return merged;
|
|
208
218
|
}
|
|
209
219
|
/**
|
|
210
220
|
* Merge post_copy files from existing, JSON file, and detected executables
|
|
221
|
+
* Only adds executables that were explicitly selected by the user
|
|
211
222
|
*/
|
|
212
|
-
export function mergePostCopyFiles(existingPostCopy, jsonPostCopy,
|
|
223
|
+
export function mergePostCopyFiles(existingPostCopy, jsonPostCopy, selectedExecutables) {
|
|
213
224
|
let post_copy = [...existingPostCopy];
|
|
214
225
|
if (jsonPostCopy && Array.isArray(jsonPostCopy)) {
|
|
215
226
|
for (const pc of jsonPostCopy) {
|
|
@@ -218,10 +229,9 @@ export function mergePostCopyFiles(existingPostCopy, jsonPostCopy, detectedExecu
|
|
|
218
229
|
}
|
|
219
230
|
}
|
|
220
231
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
for (const file of newExecutables) {
|
|
232
|
+
// Only add executables that were explicitly selected by the user
|
|
233
|
+
for (const file of selectedExecutables) {
|
|
234
|
+
if (!post_copy.some(existing => existing.src === file)) {
|
|
225
235
|
post_copy.push({ src: file, dest: file });
|
|
226
236
|
}
|
|
227
237
|
}
|