@garyr/pt-cli 1.0.1 → 1.1.0
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 +21 -0
- package/dist/commands/completionCommand.js +408 -0
- package/dist/index.js +8 -0
- package/doc/usage.md +38 -0
- package/package.json +4 -1
- package/src/commands/completionCommand.ts +415 -0
- package/src/index.ts +9 -0
- package/tests/completion.test.ts +210 -0
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ graph LR
|
|
|
42
42
|
- [Quick Start](#quick-start)
|
|
43
43
|
- [Installation](#installation)
|
|
44
44
|
- [Basic Commands](#basic-commands)
|
|
45
|
+
- [Shell Completions](#shell-completions)
|
|
45
46
|
- [Agent Integration](#agent-integration)
|
|
46
47
|
- [Documentation](#documentation)
|
|
47
48
|
- [Development](#development)
|
|
@@ -124,6 +125,25 @@ pt init ./new-project --file my-template.json --yes
|
|
|
124
125
|
|
|
125
126
|
```
|
|
126
127
|
|
|
128
|
+
### Shell Completions
|
|
129
|
+
|
|
130
|
+
Generate and install tab completions:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Bash
|
|
134
|
+
pt completion bash > /etc/bash_completion.d/pt
|
|
135
|
+
# Or user-local: pt completion bash > ~/.local/share/bash-completion/completions/pt
|
|
136
|
+
|
|
137
|
+
# Zsh
|
|
138
|
+
pt completion zsh > ~/.zsh/completions/_pt
|
|
139
|
+
# Add to ~/.zshrc: fpath=(~/.zsh/completions $fpath)
|
|
140
|
+
|
|
141
|
+
# Fish
|
|
142
|
+
pt completion fish > ~/.config/fish/completions/pt.fish
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Template names auto-complete dynamically for `pt init`, `pt update`, `pt config`, and `pt remove`.
|
|
146
|
+
|
|
127
147
|
## Agent Integration
|
|
128
148
|
|
|
129
149
|
`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 +211,7 @@ pt variables [--set] [--delete] [--json]
|
|
|
191
211
|
pt default-post-config [--set --json]
|
|
192
212
|
pt ignore [patterns] [--set]
|
|
193
213
|
pt security-response <response>
|
|
214
|
+
pt completion <shell>
|
|
194
215
|
```
|
|
195
216
|
|
|
196
217
|
**Config Schema (`~/.pt/config.yaml` v3.0):**
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
import { getConfigPath } from '../config.js';
|
|
6
|
+
export function getTemplatesForCompletion() {
|
|
7
|
+
try {
|
|
8
|
+
const configPath = getConfigPath();
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
13
|
+
if (!content.trim()) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const parsed = YAML.parse(content);
|
|
17
|
+
if (!parsed || !parsed.templates || typeof parsed.templates !== 'object') {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
return Object.keys(parsed.templates);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function generateBashCompletion() {
|
|
27
|
+
return `# Bash completion for pt
|
|
28
|
+
_pt_completions() {
|
|
29
|
+
local cur prev words cword
|
|
30
|
+
if declare -F _init_completion >/dev/null 2>&1; then
|
|
31
|
+
_init_completion || return
|
|
32
|
+
else
|
|
33
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
34
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
35
|
+
words=("\${COMP_WORDS[@]}")
|
|
36
|
+
cword=$COMP_CWORD
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
local commands="learn update init config ignore variables default-post-config add remove rm security-response completion"
|
|
40
|
+
|
|
41
|
+
# Complete top-level command or global flags
|
|
42
|
+
if [[ $cword -eq 1 ]]; then
|
|
43
|
+
if [[ "$cur" == -* ]]; then
|
|
44
|
+
COMPREPLY=( $(compgen -W "-v --version -h --help" -- "$cur") )
|
|
45
|
+
else
|
|
46
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
47
|
+
fi
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
local cmd="\${words[1]}"
|
|
52
|
+
|
|
53
|
+
case "$cmd" in
|
|
54
|
+
learn)
|
|
55
|
+
if [[ "$cur" == -* ]]; then
|
|
56
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --name --desc --json --allow-untrusted -h --help" -- "$cur") )
|
|
57
|
+
fi
|
|
58
|
+
;;
|
|
59
|
+
update)
|
|
60
|
+
if [[ "$cur" == -* ]]; then
|
|
61
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --desc --no-diff -h --help" -- "$cur") )
|
|
62
|
+
elif [[ $cword -eq 2 ]]; then
|
|
63
|
+
local templates
|
|
64
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
65
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
66
|
+
fi
|
|
67
|
+
;;
|
|
68
|
+
init)
|
|
69
|
+
if [[ "$cur" == -* ]]; then
|
|
70
|
+
COMPREPLY=( $(compgen -W "-f --file --skip-post-config --dry-run -y --yes --vars -h --help" -- "$cur") )
|
|
71
|
+
elif [[ $cword -eq 2 ]]; then
|
|
72
|
+
local templates
|
|
73
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
74
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
75
|
+
fi
|
|
76
|
+
;;
|
|
77
|
+
config)
|
|
78
|
+
if [[ "$cur" == -* ]]; then
|
|
79
|
+
COMPREPLY=( $(compgen -W "--json -h --help" -- "$cur") )
|
|
80
|
+
elif [[ $cword -eq 2 ]]; then
|
|
81
|
+
local templates
|
|
82
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
83
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
84
|
+
fi
|
|
85
|
+
;;
|
|
86
|
+
ignore)
|
|
87
|
+
if [[ "$cur" == -* ]]; then
|
|
88
|
+
COMPREPLY=( $(compgen -W "--set -h --help" -- "$cur") )
|
|
89
|
+
fi
|
|
90
|
+
;;
|
|
91
|
+
variables)
|
|
92
|
+
if [[ "$cur" == -* ]]; then
|
|
93
|
+
COMPREPLY=( $(compgen -W "--set --json --delete -h --help" -- "$cur") )
|
|
94
|
+
fi
|
|
95
|
+
;;
|
|
96
|
+
default-post-config)
|
|
97
|
+
if [[ "$cur" == -* ]]; then
|
|
98
|
+
COMPREPLY=( $(compgen -W "--set --json -h --help" -- "$cur") )
|
|
99
|
+
fi
|
|
100
|
+
;;
|
|
101
|
+
add)
|
|
102
|
+
if [[ "$cur" == -* ]]; then
|
|
103
|
+
COMPREPLY=( $(compgen -W "-f --file -h --help" -- "$cur") )
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
remove|rm)
|
|
107
|
+
if [[ "$cur" == -* ]]; then
|
|
108
|
+
COMPREPLY=( $(compgen -W "-y --yes -h --help" -- "$cur") )
|
|
109
|
+
elif [[ $cword -eq 2 ]]; then
|
|
110
|
+
local templates
|
|
111
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
112
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
113
|
+
fi
|
|
114
|
+
;;
|
|
115
|
+
completion)
|
|
116
|
+
if [[ "$cur" == -* ]]; then
|
|
117
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
118
|
+
elif [[ $cword -eq 2 ]]; then
|
|
119
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
120
|
+
fi
|
|
121
|
+
;;
|
|
122
|
+
security-response)
|
|
123
|
+
if [[ "$cur" == -* ]]; then
|
|
124
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
125
|
+
fi
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
complete -F _pt_completions pt
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
export function generateZshCompletion() {
|
|
134
|
+
return `#compdef pt
|
|
135
|
+
|
|
136
|
+
_pt_templates() {
|
|
137
|
+
local -a templates
|
|
138
|
+
templates=(\${(f)"$(pt completion --templates 2>/dev/null)"})
|
|
139
|
+
if [[ \${#templates[@]} -gt 0 ]]; then
|
|
140
|
+
_describe -t templates 'template' templates
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_pt() {
|
|
145
|
+
local context state state_policy
|
|
146
|
+
typeset -A opt_args
|
|
147
|
+
|
|
148
|
+
_arguments -C \\
|
|
149
|
+
'(-v --version)'{-v,--version}'[output the version number]' \\
|
|
150
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
151
|
+
'1: :->command' \\
|
|
152
|
+
'*:: :->args'
|
|
153
|
+
|
|
154
|
+
case $state in
|
|
155
|
+
command)
|
|
156
|
+
local -a commands
|
|
157
|
+
commands=(
|
|
158
|
+
'learn:Learn a project structure from an existing directory'
|
|
159
|
+
'update:Update an existing template with new structure/files'
|
|
160
|
+
'init:Initialize a new project from a learned template'
|
|
161
|
+
'config:Show current config location and list templates, or export a specific template'
|
|
162
|
+
'ignore:View or set global ignore patterns (comma-separated)'
|
|
163
|
+
'variables:View or set global variables (comma-separated key=value)'
|
|
164
|
+
'default-post-config:View or set default post-config tasks'
|
|
165
|
+
'add:Import/add a template from a JSON string or file'
|
|
166
|
+
'remove:Remove a learned template from the config'
|
|
167
|
+
'rm:Remove a learned template from the config'
|
|
168
|
+
'security-response:Handle security response from GUI'
|
|
169
|
+
'completion:Generate shell completion script'
|
|
170
|
+
)
|
|
171
|
+
_describe -t commands 'pt command' commands
|
|
172
|
+
;;
|
|
173
|
+
args)
|
|
174
|
+
case $words[1] in
|
|
175
|
+
learn)
|
|
176
|
+
_arguments \\
|
|
177
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
178
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
179
|
+
'--name=[Template name]:name:' \\
|
|
180
|
+
'--desc=[Template description]:description:' \\
|
|
181
|
+
'--json[Output template structure as JSON for sharing instead of saving]' \\
|
|
182
|
+
'--allow-untrusted[Bypass the trusted-source check for remote URLs]' \\
|
|
183
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
184
|
+
'1:path:_files -/'
|
|
185
|
+
;;
|
|
186
|
+
update)
|
|
187
|
+
_arguments \\
|
|
188
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
189
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
190
|
+
'--desc=[Template description]:description:' \\
|
|
191
|
+
'--no-diff[Disable additive mode, show full list]' \\
|
|
192
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
193
|
+
'1:template:_pt_templates' \\
|
|
194
|
+
'2:sourcePath:_files -/'
|
|
195
|
+
;;
|
|
196
|
+
init)
|
|
197
|
+
_arguments \\
|
|
198
|
+
'(-f --file)'{-f,--file}'[Initialize directly from a JSON template file]:file:_files' \\
|
|
199
|
+
'--skip-post-config[Skip running post-config tasks]' \\
|
|
200
|
+
'--dry-run[Show what would be created without making changes]' \\
|
|
201
|
+
'(-y --yes)'{-y,--yes}'[Automatically answer yes to prompts]' \\
|
|
202
|
+
'--vars=[Comma-separated key=value variables]:variables:' \\
|
|
203
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
204
|
+
'1:template:_pt_templates' \\
|
|
205
|
+
'2:destPath:_files -/'
|
|
206
|
+
;;
|
|
207
|
+
config)
|
|
208
|
+
_arguments \\
|
|
209
|
+
'--json[Output config or specific template as JSON]' \\
|
|
210
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
211
|
+
'1:template:_pt_templates'
|
|
212
|
+
;;
|
|
213
|
+
ignore)
|
|
214
|
+
_arguments \\
|
|
215
|
+
'--set[Set the ignore patterns to the provided value]' \\
|
|
216
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
217
|
+
'1:patterns:'
|
|
218
|
+
;;
|
|
219
|
+
variables)
|
|
220
|
+
_arguments \\
|
|
221
|
+
'--set[Set the variables to the provided pairs]' \\
|
|
222
|
+
'--json=[Set variables via JSON string or file]:data:' \\
|
|
223
|
+
'--delete=[Delete a specific global variable]:key:' \\
|
|
224
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
225
|
+
'1:pairs:'
|
|
226
|
+
;;
|
|
227
|
+
default-post-config)
|
|
228
|
+
_arguments \\
|
|
229
|
+
'--set[Set the default post-config tasks via JSON]' \\
|
|
230
|
+
'--json=[JSON string or file containing tasks array]:data:' \\
|
|
231
|
+
'(-h --help)'{-h,--help}'[display help for command]'
|
|
232
|
+
;;
|
|
233
|
+
add)
|
|
234
|
+
_arguments \\
|
|
235
|
+
'(-f --file)'{-f,--file}'[Path to JSON file containing template data]:file:_files' \\
|
|
236
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
237
|
+
'1:name:' \\
|
|
238
|
+
'2:json:'
|
|
239
|
+
;;
|
|
240
|
+
remove|rm)
|
|
241
|
+
_arguments \\
|
|
242
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm removal]' \\
|
|
243
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
244
|
+
'1:template:_pt_templates'
|
|
245
|
+
;;
|
|
246
|
+
security-response)
|
|
247
|
+
_arguments \\
|
|
248
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
249
|
+
'1:response:'
|
|
250
|
+
;;
|
|
251
|
+
completion)
|
|
252
|
+
_arguments \\
|
|
253
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
254
|
+
'1:shell:(bash zsh fish)'
|
|
255
|
+
;;
|
|
256
|
+
esac
|
|
257
|
+
;;
|
|
258
|
+
esac
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if [[ "$(basename -- "$0")" != "_pt" ]]; then
|
|
262
|
+
compdef _pt pt 2>/dev/null || true
|
|
263
|
+
fi
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
export function generateFishCompletion() {
|
|
267
|
+
return `# Fish completion for pt
|
|
268
|
+
|
|
269
|
+
function __fish_pt_needs_command
|
|
270
|
+
set -l cmd (commandline -opc)
|
|
271
|
+
if [ (count $cmd) -eq 1 ]
|
|
272
|
+
return 0
|
|
273
|
+
end
|
|
274
|
+
return 1
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
function __fish_pt_using_command
|
|
278
|
+
set -l cmd (commandline -opc)
|
|
279
|
+
if [ (count $cmd) -gt 1 ]
|
|
280
|
+
if [ "$argv[1]" = "$cmd[2]" ]
|
|
281
|
+
return 0
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
return 1
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
function __fish_pt_templates
|
|
288
|
+
pt completion --templates 2>/dev/null
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Global options
|
|
292
|
+
complete -c pt -n '__fish_pt_needs_command' -s v -l version -d 'output the version number'
|
|
293
|
+
complete -c pt -n '__fish_pt_needs_command' -s h -l help -d 'display help for command'
|
|
294
|
+
|
|
295
|
+
# Commands
|
|
296
|
+
complete -c pt -n '__fish_pt_needs_command' -a learn -d 'Learn a project structure from an existing directory'
|
|
297
|
+
complete -c pt -n '__fish_pt_needs_command' -a update -d 'Update an existing template with new structure/files'
|
|
298
|
+
complete -c pt -n '__fish_pt_needs_command' -a init -d 'Initialize a new project from a learned template'
|
|
299
|
+
complete -c pt -n '__fish_pt_needs_command' -a config -d 'Show current config location and list templates, or export a specific template'
|
|
300
|
+
complete -c pt -n '__fish_pt_needs_command' -a ignore -d 'View or set global ignore patterns (comma-separated)'
|
|
301
|
+
complete -c pt -n '__fish_pt_needs_command' -a variables -d 'View or set global variables (comma-separated key=value)'
|
|
302
|
+
complete -c pt -n '__fish_pt_needs_command' -a default-post-config -d 'View or set default post-config tasks'
|
|
303
|
+
complete -c pt -n '__fish_pt_needs_command' -a add -d 'Import/add a template from a JSON string or file'
|
|
304
|
+
complete -c pt -n '__fish_pt_needs_command' -a remove -d 'Remove a learned template from the config'
|
|
305
|
+
complete -c pt -n '__fish_pt_needs_command' -a rm -d 'Remove a learned template from the config'
|
|
306
|
+
complete -c pt -n '__fish_pt_needs_command' -a security-response -d 'Handle security response from GUI'
|
|
307
|
+
complete -c pt -n '__fish_pt_needs_command' -a completion -d 'Generate shell completion script'
|
|
308
|
+
|
|
309
|
+
# learn
|
|
310
|
+
complete -c pt -n '__fish_pt_using_command learn' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
311
|
+
complete -c pt -n '__fish_pt_using_command learn' -s y -l yes -d 'Automatically confirm prompts'
|
|
312
|
+
complete -c pt -n '__fish_pt_using_command learn' -l name -d 'Template name (skip prompt)'
|
|
313
|
+
complete -c pt -n '__fish_pt_using_command learn' -l desc -d 'Template description (skip prompt)'
|
|
314
|
+
complete -c pt -n '__fish_pt_using_command learn' -l json -d 'Output template structure as JSON for sharing instead of saving'
|
|
315
|
+
complete -c pt -n '__fish_pt_using_command learn' -l allow-untrusted -d 'Bypass the trusted-source check for remote URLs'
|
|
316
|
+
|
|
317
|
+
# update
|
|
318
|
+
complete -c pt -n '__fish_pt_using_command update' -a '(__fish_pt_templates)' -d 'Template name'
|
|
319
|
+
complete -c pt -n '__fish_pt_using_command update' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
320
|
+
complete -c pt -n '__fish_pt_using_command update' -s y -l yes -d 'Automatically confirm prompts'
|
|
321
|
+
complete -c pt -n '__fish_pt_using_command update' -l desc -d 'Template description (skip prompt)'
|
|
322
|
+
complete -c pt -n '__fish_pt_using_command update' -l no-diff -d 'Disable additive mode, show full list'
|
|
323
|
+
|
|
324
|
+
# init
|
|
325
|
+
complete -c pt -n '__fish_pt_using_command init' -a '(__fish_pt_templates)' -d 'Template name'
|
|
326
|
+
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'
|
|
327
|
+
complete -c pt -n '__fish_pt_using_command init' -l skip-post-config -d 'Skip running post-config tasks'
|
|
328
|
+
complete -c pt -n '__fish_pt_using_command init' -l dry-run -d 'Show what would be created without making changes'
|
|
329
|
+
complete -c pt -n '__fish_pt_using_command init' -s y -l yes -d 'Automatically answer yes to prompts'
|
|
330
|
+
complete -c pt -n '__fish_pt_using_command init' -l vars -d 'Comma-separated key=value variables'
|
|
331
|
+
|
|
332
|
+
# config
|
|
333
|
+
complete -c pt -n '__fish_pt_using_command config' -a '(__fish_pt_templates)' -d 'Template name'
|
|
334
|
+
complete -c pt -n '__fish_pt_using_command config' -l json -d 'Output config or specific template as JSON'
|
|
335
|
+
|
|
336
|
+
# ignore
|
|
337
|
+
complete -c pt -n '__fish_pt_using_command ignore' -l set -d 'Set the ignore patterns to the provided value'
|
|
338
|
+
|
|
339
|
+
# variables
|
|
340
|
+
complete -c pt -n '__fish_pt_using_command variables' -l set -d 'Set the variables to the provided pairs'
|
|
341
|
+
complete -c pt -n '__fish_pt_using_command variables' -l json -d 'Set variables via JSON string or file'
|
|
342
|
+
complete -c pt -n '__fish_pt_using_command variables' -l delete -d 'Delete a specific global variable'
|
|
343
|
+
|
|
344
|
+
# default-post-config
|
|
345
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l set -d 'Set the default post-config tasks via JSON'
|
|
346
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l json -d 'JSON string or file containing tasks array'
|
|
347
|
+
|
|
348
|
+
# add
|
|
349
|
+
complete -c pt -n '__fish_pt_using_command add' -s f -l file -d 'Path to JSON file containing template data'
|
|
350
|
+
|
|
351
|
+
# remove / rm
|
|
352
|
+
complete -c pt -n '__fish_pt_using_command remove' -a '(__fish_pt_templates)' -d 'Template name'
|
|
353
|
+
complete -c pt -n '__fish_pt_using_command remove' -s y -l yes -d 'Automatically confirm removal'
|
|
354
|
+
complete -c pt -n '__fish_pt_using_command rm' -a '(__fish_pt_templates)' -d 'Template name'
|
|
355
|
+
complete -c pt -n '__fish_pt_using_command rm' -s y -l yes -d 'Automatically confirm removal'
|
|
356
|
+
|
|
357
|
+
# completion
|
|
358
|
+
complete -c pt -n '__fish_pt_using_command completion' -a 'bash zsh fish' -d 'Shell'
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
export function generateShellScript(shell) {
|
|
362
|
+
switch (shell.toLowerCase()) {
|
|
363
|
+
case 'bash':
|
|
364
|
+
return generateBashCompletion();
|
|
365
|
+
case 'zsh':
|
|
366
|
+
return generateZshCompletion();
|
|
367
|
+
case 'fish':
|
|
368
|
+
return generateFishCompletion();
|
|
369
|
+
default: {
|
|
370
|
+
const supported = ['bash', 'zsh', 'fish'];
|
|
371
|
+
throw new Error(`Unsupported shell: ${shell}. Supported: ${supported.join(', ')}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
export function generateCompletion(shell) {
|
|
376
|
+
return generateShellScript(shell);
|
|
377
|
+
}
|
|
378
|
+
export async function completionCommand(shellArg, options) {
|
|
379
|
+
if (options?.templates || shellArg === '--templates' || shellArg === '_templates') {
|
|
380
|
+
const templates = getTemplatesForCompletion();
|
|
381
|
+
if (templates.length > 0) {
|
|
382
|
+
console.log(templates.join('\n'));
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (!shellArg) {
|
|
387
|
+
console.error('Error: Please specify a shell (bash, zsh, fish).');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const script = generateCompletion(shellArg);
|
|
392
|
+
console.log(script);
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.error(err.message || String(err));
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Allow direct execution via tsx src/commands/completionCommand.ts <shell>
|
|
400
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
401
|
+
const shell = process.argv[2];
|
|
402
|
+
if (shell === '--templates' || shell === '_templates') {
|
|
403
|
+
completionCommand(shell, { templates: true });
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
completionCommand(shell);
|
|
407
|
+
}
|
|
408
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { addCommand } from './commands/addCommand.js';
|
|
|
12
12
|
import { removeCommand } from './commands/removeCommand.js';
|
|
13
13
|
import { defaultPostConfigCommand } from './commands/defaultPostConfigCommand.js';
|
|
14
14
|
import { securityResponseCommand } from './commands/securityResponseCommand.js';
|
|
15
|
+
import { completionCommand } from './commands/completionCommand.js';
|
|
15
16
|
import pkg from '../package.json' with { type: 'json' };
|
|
16
17
|
const program = new Command();
|
|
17
18
|
program
|
|
@@ -114,4 +115,11 @@ program
|
|
|
114
115
|
.action(async (response) => {
|
|
115
116
|
await securityResponseCommand(response);
|
|
116
117
|
});
|
|
118
|
+
program
|
|
119
|
+
.command('completion [shell]')
|
|
120
|
+
.description('Generate shell completion script')
|
|
121
|
+
.option('--templates', 'Internal helper to list template names for completion')
|
|
122
|
+
.action(async (shellArg, options) => {
|
|
123
|
+
await completionCommand(shellArg, options);
|
|
124
|
+
});
|
|
117
125
|
program.parse(process.argv);
|
package/doc/usage.md
CHANGED
|
@@ -305,3 +305,41 @@ To see your entire configuration (including all templates) in JSON format:
|
|
|
305
305
|
```bash
|
|
306
306
|
pt config --json
|
|
307
307
|
```
|
|
308
|
+
|
|
309
|
+
## Shell Completions
|
|
310
|
+
|
|
311
|
+
`pt` can generate tab-completion scripts for Bash, Zsh, and Fish shells.
|
|
312
|
+
|
|
313
|
+
### Installation
|
|
314
|
+
|
|
315
|
+
#### Bash
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
pt completion bash > /etc/bash_completion.d/pt
|
|
319
|
+
# Or user-local:
|
|
320
|
+
pt completion bash > ~/.local/share/bash-completion/completions/pt
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### Zsh
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
pt completion zsh > ~/.zsh/completions/_pt
|
|
327
|
+
# Add to ~/.zshrc:
|
|
328
|
+
# fpath=(~/.zsh/completions $fpath)
|
|
329
|
+
# autoload -U compinit && compinit
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### Fish
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
pt completion fish > ~/.config/fish/completions/pt.fish
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Dynamic Completion
|
|
339
|
+
|
|
340
|
+
Template names auto-complete dynamically from your local `~/.pt/config.yaml` for:
|
|
341
|
+
- `pt init <TAB>`
|
|
342
|
+
- `pt update <TAB>`
|
|
343
|
+
- `pt config <TAB>`
|
|
344
|
+
- `pt remove <TAB>` (and `pt rm <TAB>`)
|
|
345
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garyr/pt-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Project Template CLI - Learn structures and initialize projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"dev": "tsx src/index.ts",
|
|
14
14
|
"test": "node --import tsx --test tests/**/*.test.ts",
|
|
15
15
|
"test:sequential": "node --import tsx --test tests/config.test.ts && node --import tsx --test tests/init.test.ts && node --import tsx --test tests/learn.test.ts && node --import tsx --test tests/substitute.test.ts && node --import tsx --test tests/config-utils.test.ts",
|
|
16
|
+
"completion:bash": "tsx src/commands/completionCommand.ts bash",
|
|
17
|
+
"completion:zsh": "tsx src/commands/completionCommand.ts zsh",
|
|
18
|
+
"completion:fish": "tsx src/commands/completionCommand.ts fish",
|
|
16
19
|
"prepublishOnly": "npm run build",
|
|
17
20
|
"build:linux": "bun build ./src/index.ts --compile --minify --target=bun-linux-x64 --outfile ../BUILD/pt-cli/pt-linux",
|
|
18
21
|
"build:macos": "bun build ./src/index.ts --compile --minify --target=bun-darwin-x64 --outfile ../BUILD/pt-cli/pt-macos",
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
import { getConfigPath } from '../config.js';
|
|
6
|
+
|
|
7
|
+
export function getTemplatesForCompletion(): string[] {
|
|
8
|
+
try {
|
|
9
|
+
const configPath = getConfigPath();
|
|
10
|
+
if (!fs.existsSync(configPath)) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
14
|
+
if (!content.trim()) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const parsed = YAML.parse(content);
|
|
18
|
+
if (!parsed || !parsed.templates || typeof parsed.templates !== 'object') {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return Object.keys(parsed.templates);
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function generateBashCompletion(): string {
|
|
28
|
+
return `# Bash completion for pt
|
|
29
|
+
_pt_completions() {
|
|
30
|
+
local cur prev words cword
|
|
31
|
+
if declare -F _init_completion >/dev/null 2>&1; then
|
|
32
|
+
_init_completion || return
|
|
33
|
+
else
|
|
34
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
35
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
36
|
+
words=("\${COMP_WORDS[@]}")
|
|
37
|
+
cword=$COMP_CWORD
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
local commands="learn update init config ignore variables default-post-config add remove rm security-response completion"
|
|
41
|
+
|
|
42
|
+
# Complete top-level command or global flags
|
|
43
|
+
if [[ $cword -eq 1 ]]; then
|
|
44
|
+
if [[ "$cur" == -* ]]; then
|
|
45
|
+
COMPREPLY=( $(compgen -W "-v --version -h --help" -- "$cur") )
|
|
46
|
+
else
|
|
47
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
48
|
+
fi
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
local cmd="\${words[1]}"
|
|
53
|
+
|
|
54
|
+
case "$cmd" in
|
|
55
|
+
learn)
|
|
56
|
+
if [[ "$cur" == -* ]]; then
|
|
57
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --name --desc --json --allow-untrusted -h --help" -- "$cur") )
|
|
58
|
+
fi
|
|
59
|
+
;;
|
|
60
|
+
update)
|
|
61
|
+
if [[ "$cur" == -* ]]; then
|
|
62
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --desc --no-diff -h --help" -- "$cur") )
|
|
63
|
+
elif [[ $cword -eq 2 ]]; then
|
|
64
|
+
local templates
|
|
65
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
66
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
67
|
+
fi
|
|
68
|
+
;;
|
|
69
|
+
init)
|
|
70
|
+
if [[ "$cur" == -* ]]; then
|
|
71
|
+
COMPREPLY=( $(compgen -W "-f --file --skip-post-config --dry-run -y --yes --vars -h --help" -- "$cur") )
|
|
72
|
+
elif [[ $cword -eq 2 ]]; then
|
|
73
|
+
local templates
|
|
74
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
75
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
76
|
+
fi
|
|
77
|
+
;;
|
|
78
|
+
config)
|
|
79
|
+
if [[ "$cur" == -* ]]; then
|
|
80
|
+
COMPREPLY=( $(compgen -W "--json -h --help" -- "$cur") )
|
|
81
|
+
elif [[ $cword -eq 2 ]]; then
|
|
82
|
+
local templates
|
|
83
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
84
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
85
|
+
fi
|
|
86
|
+
;;
|
|
87
|
+
ignore)
|
|
88
|
+
if [[ "$cur" == -* ]]; then
|
|
89
|
+
COMPREPLY=( $(compgen -W "--set -h --help" -- "$cur") )
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
variables)
|
|
93
|
+
if [[ "$cur" == -* ]]; then
|
|
94
|
+
COMPREPLY=( $(compgen -W "--set --json --delete -h --help" -- "$cur") )
|
|
95
|
+
fi
|
|
96
|
+
;;
|
|
97
|
+
default-post-config)
|
|
98
|
+
if [[ "$cur" == -* ]]; then
|
|
99
|
+
COMPREPLY=( $(compgen -W "--set --json -h --help" -- "$cur") )
|
|
100
|
+
fi
|
|
101
|
+
;;
|
|
102
|
+
add)
|
|
103
|
+
if [[ "$cur" == -* ]]; then
|
|
104
|
+
COMPREPLY=( $(compgen -W "-f --file -h --help" -- "$cur") )
|
|
105
|
+
fi
|
|
106
|
+
;;
|
|
107
|
+
remove|rm)
|
|
108
|
+
if [[ "$cur" == -* ]]; then
|
|
109
|
+
COMPREPLY=( $(compgen -W "-y --yes -h --help" -- "$cur") )
|
|
110
|
+
elif [[ $cword -eq 2 ]]; then
|
|
111
|
+
local templates
|
|
112
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
113
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
114
|
+
fi
|
|
115
|
+
;;
|
|
116
|
+
completion)
|
|
117
|
+
if [[ "$cur" == -* ]]; then
|
|
118
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
119
|
+
elif [[ $cword -eq 2 ]]; then
|
|
120
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
121
|
+
fi
|
|
122
|
+
;;
|
|
123
|
+
security-response)
|
|
124
|
+
if [[ "$cur" == -* ]]; then
|
|
125
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
126
|
+
fi
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
complete -F _pt_completions pt
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function generateZshCompletion(): string {
|
|
136
|
+
return `#compdef pt
|
|
137
|
+
|
|
138
|
+
_pt_templates() {
|
|
139
|
+
local -a templates
|
|
140
|
+
templates=(\${(f)"$(pt completion --templates 2>/dev/null)"})
|
|
141
|
+
if [[ \${#templates[@]} -gt 0 ]]; then
|
|
142
|
+
_describe -t templates 'template' templates
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_pt() {
|
|
147
|
+
local context state state_policy
|
|
148
|
+
typeset -A opt_args
|
|
149
|
+
|
|
150
|
+
_arguments -C \\
|
|
151
|
+
'(-v --version)'{-v,--version}'[output the version number]' \\
|
|
152
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
153
|
+
'1: :->command' \\
|
|
154
|
+
'*:: :->args'
|
|
155
|
+
|
|
156
|
+
case $state in
|
|
157
|
+
command)
|
|
158
|
+
local -a commands
|
|
159
|
+
commands=(
|
|
160
|
+
'learn:Learn a project structure from an existing directory'
|
|
161
|
+
'update:Update an existing template with new structure/files'
|
|
162
|
+
'init:Initialize a new project from a learned template'
|
|
163
|
+
'config:Show current config location and list templates, or export a specific template'
|
|
164
|
+
'ignore:View or set global ignore patterns (comma-separated)'
|
|
165
|
+
'variables:View or set global variables (comma-separated key=value)'
|
|
166
|
+
'default-post-config:View or set default post-config tasks'
|
|
167
|
+
'add:Import/add a template from a JSON string or file'
|
|
168
|
+
'remove:Remove a learned template from the config'
|
|
169
|
+
'rm:Remove a learned template from the config'
|
|
170
|
+
'security-response:Handle security response from GUI'
|
|
171
|
+
'completion:Generate shell completion script'
|
|
172
|
+
)
|
|
173
|
+
_describe -t commands 'pt command' commands
|
|
174
|
+
;;
|
|
175
|
+
args)
|
|
176
|
+
case $words[1] in
|
|
177
|
+
learn)
|
|
178
|
+
_arguments \\
|
|
179
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
180
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
181
|
+
'--name=[Template name]:name:' \\
|
|
182
|
+
'--desc=[Template description]:description:' \\
|
|
183
|
+
'--json[Output template structure as JSON for sharing instead of saving]' \\
|
|
184
|
+
'--allow-untrusted[Bypass the trusted-source check for remote URLs]' \\
|
|
185
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
186
|
+
'1:path:_files -/'
|
|
187
|
+
;;
|
|
188
|
+
update)
|
|
189
|
+
_arguments \\
|
|
190
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
191
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
192
|
+
'--desc=[Template description]:description:' \\
|
|
193
|
+
'--no-diff[Disable additive mode, show full list]' \\
|
|
194
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
195
|
+
'1:template:_pt_templates' \\
|
|
196
|
+
'2:sourcePath:_files -/'
|
|
197
|
+
;;
|
|
198
|
+
init)
|
|
199
|
+
_arguments \\
|
|
200
|
+
'(-f --file)'{-f,--file}'[Initialize directly from a JSON template file]:file:_files' \\
|
|
201
|
+
'--skip-post-config[Skip running post-config tasks]' \\
|
|
202
|
+
'--dry-run[Show what would be created without making changes]' \\
|
|
203
|
+
'(-y --yes)'{-y,--yes}'[Automatically answer yes to prompts]' \\
|
|
204
|
+
'--vars=[Comma-separated key=value variables]:variables:' \\
|
|
205
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
206
|
+
'1:template:_pt_templates' \\
|
|
207
|
+
'2:destPath:_files -/'
|
|
208
|
+
;;
|
|
209
|
+
config)
|
|
210
|
+
_arguments \\
|
|
211
|
+
'--json[Output config or specific template as JSON]' \\
|
|
212
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
213
|
+
'1:template:_pt_templates'
|
|
214
|
+
;;
|
|
215
|
+
ignore)
|
|
216
|
+
_arguments \\
|
|
217
|
+
'--set[Set the ignore patterns to the provided value]' \\
|
|
218
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
219
|
+
'1:patterns:'
|
|
220
|
+
;;
|
|
221
|
+
variables)
|
|
222
|
+
_arguments \\
|
|
223
|
+
'--set[Set the variables to the provided pairs]' \\
|
|
224
|
+
'--json=[Set variables via JSON string or file]:data:' \\
|
|
225
|
+
'--delete=[Delete a specific global variable]:key:' \\
|
|
226
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
227
|
+
'1:pairs:'
|
|
228
|
+
;;
|
|
229
|
+
default-post-config)
|
|
230
|
+
_arguments \\
|
|
231
|
+
'--set[Set the default post-config tasks via JSON]' \\
|
|
232
|
+
'--json=[JSON string or file containing tasks array]:data:' \\
|
|
233
|
+
'(-h --help)'{-h,--help}'[display help for command]'
|
|
234
|
+
;;
|
|
235
|
+
add)
|
|
236
|
+
_arguments \\
|
|
237
|
+
'(-f --file)'{-f,--file}'[Path to JSON file containing template data]:file:_files' \\
|
|
238
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
239
|
+
'1:name:' \\
|
|
240
|
+
'2:json:'
|
|
241
|
+
;;
|
|
242
|
+
remove|rm)
|
|
243
|
+
_arguments \\
|
|
244
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm removal]' \\
|
|
245
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
246
|
+
'1:template:_pt_templates'
|
|
247
|
+
;;
|
|
248
|
+
security-response)
|
|
249
|
+
_arguments \\
|
|
250
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
251
|
+
'1:response:'
|
|
252
|
+
;;
|
|
253
|
+
completion)
|
|
254
|
+
_arguments \\
|
|
255
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
256
|
+
'1:shell:(bash zsh fish)'
|
|
257
|
+
;;
|
|
258
|
+
esac
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if [[ "$(basename -- "$0")" != "_pt" ]]; then
|
|
264
|
+
compdef _pt pt 2>/dev/null || true
|
|
265
|
+
fi
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function generateFishCompletion(): string {
|
|
270
|
+
return `# Fish completion for pt
|
|
271
|
+
|
|
272
|
+
function __fish_pt_needs_command
|
|
273
|
+
set -l cmd (commandline -opc)
|
|
274
|
+
if [ (count $cmd) -eq 1 ]
|
|
275
|
+
return 0
|
|
276
|
+
end
|
|
277
|
+
return 1
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
function __fish_pt_using_command
|
|
281
|
+
set -l cmd (commandline -opc)
|
|
282
|
+
if [ (count $cmd) -gt 1 ]
|
|
283
|
+
if [ "$argv[1]" = "$cmd[2]" ]
|
|
284
|
+
return 0
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
return 1
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
function __fish_pt_templates
|
|
291
|
+
pt completion --templates 2>/dev/null
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Global options
|
|
295
|
+
complete -c pt -n '__fish_pt_needs_command' -s v -l version -d 'output the version number'
|
|
296
|
+
complete -c pt -n '__fish_pt_needs_command' -s h -l help -d 'display help for command'
|
|
297
|
+
|
|
298
|
+
# Commands
|
|
299
|
+
complete -c pt -n '__fish_pt_needs_command' -a learn -d 'Learn a project structure from an existing directory'
|
|
300
|
+
complete -c pt -n '__fish_pt_needs_command' -a update -d 'Update an existing template with new structure/files'
|
|
301
|
+
complete -c pt -n '__fish_pt_needs_command' -a init -d 'Initialize a new project from a learned template'
|
|
302
|
+
complete -c pt -n '__fish_pt_needs_command' -a config -d 'Show current config location and list templates, or export a specific template'
|
|
303
|
+
complete -c pt -n '__fish_pt_needs_command' -a ignore -d 'View or set global ignore patterns (comma-separated)'
|
|
304
|
+
complete -c pt -n '__fish_pt_needs_command' -a variables -d 'View or set global variables (comma-separated key=value)'
|
|
305
|
+
complete -c pt -n '__fish_pt_needs_command' -a default-post-config -d 'View or set default post-config tasks'
|
|
306
|
+
complete -c pt -n '__fish_pt_needs_command' -a add -d 'Import/add a template from a JSON string or file'
|
|
307
|
+
complete -c pt -n '__fish_pt_needs_command' -a remove -d 'Remove a learned template from the config'
|
|
308
|
+
complete -c pt -n '__fish_pt_needs_command' -a rm -d 'Remove a learned template from the config'
|
|
309
|
+
complete -c pt -n '__fish_pt_needs_command' -a security-response -d 'Handle security response from GUI'
|
|
310
|
+
complete -c pt -n '__fish_pt_needs_command' -a completion -d 'Generate shell completion script'
|
|
311
|
+
|
|
312
|
+
# learn
|
|
313
|
+
complete -c pt -n '__fish_pt_using_command learn' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
314
|
+
complete -c pt -n '__fish_pt_using_command learn' -s y -l yes -d 'Automatically confirm prompts'
|
|
315
|
+
complete -c pt -n '__fish_pt_using_command learn' -l name -d 'Template name (skip prompt)'
|
|
316
|
+
complete -c pt -n '__fish_pt_using_command learn' -l desc -d 'Template description (skip prompt)'
|
|
317
|
+
complete -c pt -n '__fish_pt_using_command learn' -l json -d 'Output template structure as JSON for sharing instead of saving'
|
|
318
|
+
complete -c pt -n '__fish_pt_using_command learn' -l allow-untrusted -d 'Bypass the trusted-source check for remote URLs'
|
|
319
|
+
|
|
320
|
+
# update
|
|
321
|
+
complete -c pt -n '__fish_pt_using_command update' -a '(__fish_pt_templates)' -d 'Template name'
|
|
322
|
+
complete -c pt -n '__fish_pt_using_command update' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
323
|
+
complete -c pt -n '__fish_pt_using_command update' -s y -l yes -d 'Automatically confirm prompts'
|
|
324
|
+
complete -c pt -n '__fish_pt_using_command update' -l desc -d 'Template description (skip prompt)'
|
|
325
|
+
complete -c pt -n '__fish_pt_using_command update' -l no-diff -d 'Disable additive mode, show full list'
|
|
326
|
+
|
|
327
|
+
# init
|
|
328
|
+
complete -c pt -n '__fish_pt_using_command init' -a '(__fish_pt_templates)' -d 'Template name'
|
|
329
|
+
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'
|
|
330
|
+
complete -c pt -n '__fish_pt_using_command init' -l skip-post-config -d 'Skip running post-config tasks'
|
|
331
|
+
complete -c pt -n '__fish_pt_using_command init' -l dry-run -d 'Show what would be created without making changes'
|
|
332
|
+
complete -c pt -n '__fish_pt_using_command init' -s y -l yes -d 'Automatically answer yes to prompts'
|
|
333
|
+
complete -c pt -n '__fish_pt_using_command init' -l vars -d 'Comma-separated key=value variables'
|
|
334
|
+
|
|
335
|
+
# config
|
|
336
|
+
complete -c pt -n '__fish_pt_using_command config' -a '(__fish_pt_templates)' -d 'Template name'
|
|
337
|
+
complete -c pt -n '__fish_pt_using_command config' -l json -d 'Output config or specific template as JSON'
|
|
338
|
+
|
|
339
|
+
# ignore
|
|
340
|
+
complete -c pt -n '__fish_pt_using_command ignore' -l set -d 'Set the ignore patterns to the provided value'
|
|
341
|
+
|
|
342
|
+
# variables
|
|
343
|
+
complete -c pt -n '__fish_pt_using_command variables' -l set -d 'Set the variables to the provided pairs'
|
|
344
|
+
complete -c pt -n '__fish_pt_using_command variables' -l json -d 'Set variables via JSON string or file'
|
|
345
|
+
complete -c pt -n '__fish_pt_using_command variables' -l delete -d 'Delete a specific global variable'
|
|
346
|
+
|
|
347
|
+
# default-post-config
|
|
348
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l set -d 'Set the default post-config tasks via JSON'
|
|
349
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l json -d 'JSON string or file containing tasks array'
|
|
350
|
+
|
|
351
|
+
# add
|
|
352
|
+
complete -c pt -n '__fish_pt_using_command add' -s f -l file -d 'Path to JSON file containing template data'
|
|
353
|
+
|
|
354
|
+
# remove / rm
|
|
355
|
+
complete -c pt -n '__fish_pt_using_command remove' -a '(__fish_pt_templates)' -d 'Template name'
|
|
356
|
+
complete -c pt -n '__fish_pt_using_command remove' -s y -l yes -d 'Automatically confirm removal'
|
|
357
|
+
complete -c pt -n '__fish_pt_using_command rm' -a '(__fish_pt_templates)' -d 'Template name'
|
|
358
|
+
complete -c pt -n '__fish_pt_using_command rm' -s y -l yes -d 'Automatically confirm removal'
|
|
359
|
+
|
|
360
|
+
# completion
|
|
361
|
+
complete -c pt -n '__fish_pt_using_command completion' -a 'bash zsh fish' -d 'Shell'
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function generateShellScript(shell: string): string {
|
|
366
|
+
switch (shell.toLowerCase()) {
|
|
367
|
+
case 'bash':
|
|
368
|
+
return generateBashCompletion();
|
|
369
|
+
case 'zsh':
|
|
370
|
+
return generateZshCompletion();
|
|
371
|
+
case 'fish':
|
|
372
|
+
return generateFishCompletion();
|
|
373
|
+
default: {
|
|
374
|
+
const supported = ['bash', 'zsh', 'fish'];
|
|
375
|
+
throw new Error(`Unsupported shell: ${shell}. Supported: ${supported.join(', ')}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function generateCompletion(shell: string): string {
|
|
381
|
+
return generateShellScript(shell);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function completionCommand(shellArg?: string, options?: { templates?: boolean }): Promise<void> {
|
|
385
|
+
if (options?.templates || shellArg === '--templates' || shellArg === '_templates') {
|
|
386
|
+
const templates = getTemplatesForCompletion();
|
|
387
|
+
if (templates.length > 0) {
|
|
388
|
+
console.log(templates.join('\n'));
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!shellArg) {
|
|
394
|
+
console.error('Error: Please specify a shell (bash, zsh, fish).');
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const script = generateCompletion(shellArg);
|
|
400
|
+
console.log(script);
|
|
401
|
+
} catch (err: any) {
|
|
402
|
+
console.error(err.message || String(err));
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Allow direct execution via tsx src/commands/completionCommand.ts <shell>
|
|
408
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
409
|
+
const shell = process.argv[2];
|
|
410
|
+
if (shell === '--templates' || shell === '_templates') {
|
|
411
|
+
completionCommand(shell, { templates: true });
|
|
412
|
+
} else {
|
|
413
|
+
completionCommand(shell);
|
|
414
|
+
}
|
|
415
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { addCommand } from './commands/addCommand.js';
|
|
|
16
16
|
import { removeCommand } from './commands/removeCommand.js';
|
|
17
17
|
import { defaultPostConfigCommand } from './commands/defaultPostConfigCommand.js';
|
|
18
18
|
import { securityResponseCommand } from './commands/securityResponseCommand.js';
|
|
19
|
+
import { completionCommand } from './commands/completionCommand.js';
|
|
19
20
|
|
|
20
21
|
import pkg from '../package.json' with { type: 'json' };
|
|
21
22
|
|
|
@@ -129,4 +130,12 @@ program
|
|
|
129
130
|
await securityResponseCommand(response);
|
|
130
131
|
});
|
|
131
132
|
|
|
133
|
+
program
|
|
134
|
+
.command('completion [shell]')
|
|
135
|
+
.description('Generate shell completion script')
|
|
136
|
+
.option('--templates', 'Internal helper to list template names for completion')
|
|
137
|
+
.action(async (shellArg: string | undefined, options) => {
|
|
138
|
+
await completionCommand(shellArg, options);
|
|
139
|
+
});
|
|
140
|
+
|
|
132
141
|
program.parse(process.argv);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import YAML from 'yaml';
|
|
7
|
+
|
|
8
|
+
// Force a temporary home directory for testing before importing anything from the CLI
|
|
9
|
+
const testHome = path.join(process.cwd(), '.test-home-completion');
|
|
10
|
+
process.env.HOME = testHome;
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
generateCompletion,
|
|
14
|
+
generateBashCompletion,
|
|
15
|
+
generateZshCompletion,
|
|
16
|
+
generateFishCompletion,
|
|
17
|
+
getTemplatesForCompletion,
|
|
18
|
+
completionCommand,
|
|
19
|
+
} from '../src/commands/completionCommand.js';
|
|
20
|
+
import { getConfigPath, ensureConfigDir } from '../src/config.js';
|
|
21
|
+
|
|
22
|
+
describe('Shell Completion Generation', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
ensureConfigDir();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (fs.existsSync(testHome)) {
|
|
29
|
+
fs.rmSync(testHome, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('generateCompletion throws on unsupported shell', () => {
|
|
34
|
+
assert.throws(
|
|
35
|
+
() => generateCompletion('powershell'),
|
|
36
|
+
/Unsupported shell: powershell\. Supported: bash, zsh, fish/
|
|
37
|
+
);
|
|
38
|
+
assert.throws(
|
|
39
|
+
() => generateCompletion('elvish'),
|
|
40
|
+
/Unsupported shell: elvish\. Supported: bash, zsh, fish/
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('generateBashCompletion generates valid bash script with commands and flags', () => {
|
|
45
|
+
const script = generateBashCompletion();
|
|
46
|
+
assert.ok(script.includes('_pt_completions()'), 'Must define _pt_completions');
|
|
47
|
+
assert.ok(script.includes('complete -F _pt_completions pt'), 'Must register complete -F');
|
|
48
|
+
assert.ok(script.includes('pt completion --templates'), 'Must include template completion call');
|
|
49
|
+
|
|
50
|
+
const expectedCommands = [
|
|
51
|
+
'learn',
|
|
52
|
+
'update',
|
|
53
|
+
'init',
|
|
54
|
+
'config',
|
|
55
|
+
'ignore',
|
|
56
|
+
'variables',
|
|
57
|
+
'default-post-config',
|
|
58
|
+
'add',
|
|
59
|
+
'remove',
|
|
60
|
+
'rm',
|
|
61
|
+
'security-response',
|
|
62
|
+
'completion',
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const cmd of expectedCommands) {
|
|
66
|
+
assert.ok(script.includes(cmd), `Bash completion must include command ${cmd}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Verify bash script parses cleanly and registers completion
|
|
70
|
+
try {
|
|
71
|
+
const tempScriptPath = path.join(testHome, 'pt-completion.bash');
|
|
72
|
+
fs.writeFileSync(tempScriptPath, script, 'utf-8');
|
|
73
|
+
const verifyOutput = execSync(`bash -c "source '${tempScriptPath}' && complete -p pt"`, { encoding: 'utf-8' });
|
|
74
|
+
assert.ok(verifyOutput.includes('_pt_completions pt'), 'complete -p pt should return registered function');
|
|
75
|
+
} catch (e: any) {
|
|
76
|
+
if (e.status !== undefined) throw e;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('generateZshCompletion generates valid zsh script with commands and flags', () => {
|
|
81
|
+
const script = generateZshCompletion();
|
|
82
|
+
assert.ok(script.includes('#compdef pt'), 'Must include #compdef pt header');
|
|
83
|
+
assert.ok(script.includes('_pt()'), 'Must define _pt function');
|
|
84
|
+
assert.ok(script.includes('_pt_templates()'), 'Must define _pt_templates function');
|
|
85
|
+
assert.ok(script.includes('pt completion --templates'), 'Must call pt completion --templates');
|
|
86
|
+
|
|
87
|
+
const expectedCommands = [
|
|
88
|
+
'learn',
|
|
89
|
+
'update',
|
|
90
|
+
'init',
|
|
91
|
+
'config',
|
|
92
|
+
'ignore',
|
|
93
|
+
'variables',
|
|
94
|
+
'default-post-config',
|
|
95
|
+
'add',
|
|
96
|
+
'remove',
|
|
97
|
+
'rm',
|
|
98
|
+
'security-response',
|
|
99
|
+
'completion',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const cmd of expectedCommands) {
|
|
103
|
+
assert.ok(script.includes(cmd), `Zsh completion must include command ${cmd}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('generateFishCompletion generates valid fish script with commands and flags', () => {
|
|
108
|
+
const script = generateFishCompletion();
|
|
109
|
+
assert.ok(script.includes('complete -c pt'), 'Must include complete -c pt');
|
|
110
|
+
assert.ok(script.includes('__fish_pt_templates'), 'Must define __fish_pt_templates function');
|
|
111
|
+
assert.ok(script.includes('pt completion --templates'), 'Must call pt completion --templates');
|
|
112
|
+
|
|
113
|
+
const expectedCommands = [
|
|
114
|
+
'learn',
|
|
115
|
+
'update',
|
|
116
|
+
'init',
|
|
117
|
+
'config',
|
|
118
|
+
'ignore',
|
|
119
|
+
'variables',
|
|
120
|
+
'default-post-config',
|
|
121
|
+
'add',
|
|
122
|
+
'remove',
|
|
123
|
+
'rm',
|
|
124
|
+
'security-response',
|
|
125
|
+
'completion',
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
for (const cmd of expectedCommands) {
|
|
129
|
+
assert.ok(script.includes(cmd), `Fish completion must include command ${cmd}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('getTemplatesForCompletion returns template names when config exists', () => {
|
|
134
|
+
ensureConfigDir();
|
|
135
|
+
const configPath = getConfigPath();
|
|
136
|
+
const testConfig = {
|
|
137
|
+
version: '3.0',
|
|
138
|
+
templates: {
|
|
139
|
+
'web-app': { description: 'Web Application', folders: [] },
|
|
140
|
+
'python-cli': { description: 'Python CLI', folders: [] },
|
|
141
|
+
'godot-game': { description: 'Godot Game', folders: [] },
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
fs.writeFileSync(configPath, YAML.stringify(testConfig), 'utf-8');
|
|
145
|
+
|
|
146
|
+
const templates = getTemplatesForCompletion();
|
|
147
|
+
assert.deepStrictEqual(templates, ['web-app', 'python-cli', 'godot-game']);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('getTemplatesForCompletion returns empty array when config does not exist', () => {
|
|
151
|
+
const configPath = getConfigPath();
|
|
152
|
+
if (fs.existsSync(configPath)) {
|
|
153
|
+
fs.unlinkSync(configPath);
|
|
154
|
+
}
|
|
155
|
+
const templates = getTemplatesForCompletion();
|
|
156
|
+
assert.deepStrictEqual(templates, []);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('getTemplatesForCompletion returns empty array when config is empty or invalid', () => {
|
|
160
|
+
ensureConfigDir();
|
|
161
|
+
const configPath = getConfigPath();
|
|
162
|
+
fs.writeFileSync(configPath, '', 'utf-8');
|
|
163
|
+
assert.deepStrictEqual(getTemplatesForCompletion(), []);
|
|
164
|
+
|
|
165
|
+
fs.writeFileSync(configPath, ':::invalid yaml:::', 'utf-8');
|
|
166
|
+
assert.deepStrictEqual(getTemplatesForCompletion(), []);
|
|
167
|
+
|
|
168
|
+
fs.writeFileSync(configPath, YAML.stringify({ version: '3.0' }), 'utf-8');
|
|
169
|
+
assert.deepStrictEqual(getTemplatesForCompletion(), []);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('completionCommand with --templates option outputs newline-separated template names', async () => {
|
|
173
|
+
ensureConfigDir();
|
|
174
|
+
const configPath = getConfigPath();
|
|
175
|
+
const testConfig = {
|
|
176
|
+
version: '3.0',
|
|
177
|
+
templates: {
|
|
178
|
+
'alpha-template': { description: 'Alpha', folders: [] },
|
|
179
|
+
'beta-template': { description: 'Beta', folders: [] },
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
fs.writeFileSync(configPath, YAML.stringify(testConfig), 'utf-8');
|
|
183
|
+
|
|
184
|
+
const logged: string[] = [];
|
|
185
|
+
const origLog = console.log;
|
|
186
|
+
console.log = (msg: any) => logged.push(String(msg));
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await completionCommand(undefined, { templates: true });
|
|
190
|
+
assert.strictEqual(logged.length, 1);
|
|
191
|
+
assert.strictEqual(logged[0], 'alpha-template\nbeta-template');
|
|
192
|
+
} finally {
|
|
193
|
+
console.log = origLog;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('completionCommand with valid shell outputs script', async () => {
|
|
198
|
+
const logged: string[] = [];
|
|
199
|
+
const origLog = console.log;
|
|
200
|
+
console.log = (msg: any) => logged.push(String(msg));
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await completionCommand('bash');
|
|
204
|
+
assert.strictEqual(logged.length, 1);
|
|
205
|
+
assert.ok(logged[0].includes('_pt_completions()'));
|
|
206
|
+
} finally {
|
|
207
|
+
console.log = origLog;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|