@jutge.org/toolkit 4.3.1 → 4.4.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/dist/index.js +445 -406
- package/docs/getting-started-guide.md +12 -1
- package/package.json +1 -1
- package/toolkit/completion.ts +202 -0
- package/toolkit/generate.ts +6 -7
- package/toolkit/index.ts +6 -1
- package/toolkit/lint.ts +64 -0
- package/toolkit/submit.ts +3 -2
- package/toolkit/ai.ts +0 -56
|
@@ -61,6 +61,16 @@ Before we dive into configuration, it's important to know how to get help:
|
|
|
61
61
|
jtk about
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
5. **Enable shell completion (Bash, Zsh, Fish, PowerShell):**
|
|
65
|
+
|
|
66
|
+
Completions are available for commands, options, and values (e.g. language codes, compiler ids, template names). To enable them:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
jtk completion install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
After upgrading the toolkit with `jtk upgrade`, run `jtk completion install` again to refresh the completion script.
|
|
73
|
+
|
|
64
74
|
### Configuration
|
|
65
75
|
|
|
66
76
|
Before using the toolkit, you should configure it with your preferences:
|
|
@@ -230,6 +240,7 @@ Here's a quick reference of the most commonly used commands:
|
|
|
230
240
|
jtk --help # Show help
|
|
231
241
|
jtk --version # Show version
|
|
232
242
|
jtk upgrade # Upgrade to latest version
|
|
243
|
+
jtk completion install # Install/update shell completions
|
|
233
244
|
jtk about # Show information about the toolkit
|
|
234
245
|
|
|
235
246
|
jtk config show # Show configuration
|
|
@@ -263,7 +274,7 @@ jtk doctor # Check system dependencies
|
|
|
263
274
|
|
|
264
275
|
## Tips for Success
|
|
265
276
|
|
|
266
|
-
1. **Keep the toolkit updated:** Regularly run `jtk upgrade` to get the latest features and fixes.
|
|
277
|
+
1. **Keep the toolkit updated:** Regularly run `jtk upgrade` to get the latest features and fixes. After upgrading, run `jtk completion install` to update shell completions.
|
|
267
278
|
|
|
268
279
|
2. **Start simple:** Begin with a basic problem and gradually explore more features.
|
|
269
280
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import tui from '../lib/tui'
|
|
5
|
+
import { complete } from '../lib/completion'
|
|
6
|
+
|
|
7
|
+
/** Hidden command used by completion scripts to get candidates */
|
|
8
|
+
export const completeInternalCmd = new Command('_complete')
|
|
9
|
+
.description('Internal: output completion candidates (used by shell completion scripts)')
|
|
10
|
+
.argument('<shell>', 'bash|zsh|fish|powershell')
|
|
11
|
+
.argument('<cword>', 'index of word being completed', (v) => parseInt(v, 10))
|
|
12
|
+
.argument('[words...]', 'command line words')
|
|
13
|
+
.action(async (shell: string, cword: number, words: string[]) => {
|
|
14
|
+
const result = await complete(words, cword)
|
|
15
|
+
for (const w of result.words) {
|
|
16
|
+
process.stdout.write(w + '\n')
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function bashScript(): string {
|
|
21
|
+
return `# Bash completion for jtk/jutge-toolkit
|
|
22
|
+
_jtk_completion() {
|
|
23
|
+
local cur words cword
|
|
24
|
+
_get_comp_words_by_ref -n : cur words cword
|
|
25
|
+
local reply
|
|
26
|
+
reply=($(jtk _complete bash "$cword" -- "\${words[@]}" 2>/dev/null))
|
|
27
|
+
COMPREPLY=($(compgen -W "\${reply[*]}" -- "$cur"))
|
|
28
|
+
}
|
|
29
|
+
complete -o default -F _jtk_completion jtk jutge-toolkit 2>/dev/null || true
|
|
30
|
+
`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function zshScript(): string {
|
|
34
|
+
return `# Zsh completion for jtk/jutge-toolkit
|
|
35
|
+
_jtk_completion() {
|
|
36
|
+
local cword=$((CURRENT - 1))
|
|
37
|
+
local -a reply
|
|
38
|
+
reply=("\${(@f)"$(jtk _complete zsh "$cword" -- "\${words[@]}" 2>/dev/null)"}")
|
|
39
|
+
_describe 'jtk' reply
|
|
40
|
+
}
|
|
41
|
+
compdef _jtk_completion jtk jutge-toolkit
|
|
42
|
+
`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fishScript(): string {
|
|
46
|
+
return `# Fish completion for jtk/jutge-toolkit
|
|
47
|
+
function __jtk_completion
|
|
48
|
+
set -l tokens (commandline -o)
|
|
49
|
+
set -l cword (math (count $tokens) - 1)
|
|
50
|
+
jtk _complete fish $cword -- $tokens 2>/dev/null
|
|
51
|
+
end
|
|
52
|
+
complete -c jtk -a '(__jtk_completion)'
|
|
53
|
+
complete -c jutge-toolkit -a '(__jtk_completion)'
|
|
54
|
+
`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function powershellScript(): string {
|
|
58
|
+
return `# PowerShell completion for jtk/jutge-toolkit
|
|
59
|
+
Register-ArgumentCompleter -CommandName jtk,jutge-toolkit -ScriptBlock {
|
|
60
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
61
|
+
$words = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
|
|
62
|
+
$cword = [Math]::Max(0, $words.Count - 1)
|
|
63
|
+
$all = $words + $wordToComplete
|
|
64
|
+
$reply = jtk _complete powershell $cword -- @all 2>$null
|
|
65
|
+
$reply | Where-Object { $_ -like "$wordToComplete*" }
|
|
66
|
+
}
|
|
67
|
+
`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const completionCmd = new Command('completion')
|
|
71
|
+
.description('Generate and install shell completion scripts for jtk')
|
|
72
|
+
.action(() => {
|
|
73
|
+
completionCmd.help()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
completionCmd
|
|
77
|
+
.command('bash')
|
|
78
|
+
.description('Output Bash completion script')
|
|
79
|
+
.action(() => {
|
|
80
|
+
console.log(bashScript())
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
completionCmd
|
|
84
|
+
.command('zsh')
|
|
85
|
+
.description('Output Zsh completion script')
|
|
86
|
+
.action(() => {
|
|
87
|
+
console.log(zshScript())
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
completionCmd
|
|
91
|
+
.command('fish')
|
|
92
|
+
.description('Output Fish completion script')
|
|
93
|
+
.action(() => {
|
|
94
|
+
console.log(fishScript())
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
completionCmd
|
|
98
|
+
.command('powershell')
|
|
99
|
+
.description('Output PowerShell completion script')
|
|
100
|
+
.action(() => {
|
|
101
|
+
console.log(powershellScript())
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
completionCmd
|
|
105
|
+
.command('install [shell]')
|
|
106
|
+
.description('Install completion script for the current shell (or specify bash|zsh|fish|powershell)')
|
|
107
|
+
.action(async (shellArg?: string) => {
|
|
108
|
+
const shell = shellArg || detectShell()
|
|
109
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || ''
|
|
110
|
+
if (!homedir) {
|
|
111
|
+
tui.print('Could not determine home directory (set HOME or USERPROFILE).')
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
const xdgData = process.env.XDG_DATA_HOME || join(homedir, '.local', 'share')
|
|
115
|
+
|
|
116
|
+
const writeScript = async (filePath: string, script: string): Promise<'created' | 'updated'> => {
|
|
117
|
+
let existing: string | null = null
|
|
118
|
+
try {
|
|
119
|
+
existing = await readFile(filePath, 'utf-8')
|
|
120
|
+
} catch {
|
|
121
|
+
// file does not exist
|
|
122
|
+
}
|
|
123
|
+
await writeFile(filePath, script, 'utf-8')
|
|
124
|
+
return existing !== null && existing !== script ? 'updated' : 'created'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (shell === 'bash') {
|
|
128
|
+
const dir = join(xdgData, 'bash-completion', 'completions')
|
|
129
|
+
const filePath = join(dir, 'jtk')
|
|
130
|
+
await mkdir(dir, { recursive: true })
|
|
131
|
+
const status = await writeScript(filePath, bashScript())
|
|
132
|
+
tui.success(
|
|
133
|
+
status === 'created' ? `Created ${tui.hyperlink(filePath)}` : `Updated ${tui.hyperlink(filePath)}`,
|
|
134
|
+
)
|
|
135
|
+
tui.print('Restart your shell or run: source "' + filePath + '"')
|
|
136
|
+
} else if (shell === 'zsh') {
|
|
137
|
+
const dir = join(homedir, '.zsh')
|
|
138
|
+
const filePath = join(dir, '_jtk')
|
|
139
|
+
await mkdir(dir, { recursive: true })
|
|
140
|
+
const status = await writeScript(filePath, zshScript())
|
|
141
|
+
tui.success(
|
|
142
|
+
status === 'created' ? `Created ${tui.hyperlink(filePath)}` : `Updated ${tui.hyperlink(filePath)}`,
|
|
143
|
+
)
|
|
144
|
+
const zshrc = join(homedir, '.zshrc')
|
|
145
|
+
let zshrcContent: string | null = null
|
|
146
|
+
try {
|
|
147
|
+
zshrcContent = await readFile(zshrc, 'utf-8')
|
|
148
|
+
} catch {
|
|
149
|
+
/* .zshrc may not exist */
|
|
150
|
+
}
|
|
151
|
+
const lines: string[] = []
|
|
152
|
+
if (!zshrcContent?.includes(dir)) {
|
|
153
|
+
lines.push(`fpath=("${dir}" $fpath)`)
|
|
154
|
+
}
|
|
155
|
+
if (!zshrcContent?.includes('compinit')) {
|
|
156
|
+
lines.push('autoload -Uz compinit && compinit')
|
|
157
|
+
}
|
|
158
|
+
if (lines.length > 0) {
|
|
159
|
+
const toAppend = `\n# jtk completion\n${lines.join('\n')}\n`
|
|
160
|
+
await writeFile(zshrc, (zshrcContent ?? '') + toAppend, 'utf-8')
|
|
161
|
+
const added =
|
|
162
|
+
lines.length === 2
|
|
163
|
+
? 'fpath and compinit'
|
|
164
|
+
: lines.length === 1 && lines[0]!.includes('fpath')
|
|
165
|
+
? 'fpath'
|
|
166
|
+
: 'compinit'
|
|
167
|
+
tui.success(`Updated ${tui.hyperlink(zshrc)} (added ${added})`)
|
|
168
|
+
}
|
|
169
|
+
tui.print('Restart your shell to use completions.')
|
|
170
|
+
} else if (shell === 'fish') {
|
|
171
|
+
const dir = join(homedir, '.config', 'fish', 'completions')
|
|
172
|
+
const filePath = join(dir, 'jtk.fish')
|
|
173
|
+
await mkdir(dir, { recursive: true })
|
|
174
|
+
const status = await writeScript(filePath, fishScript())
|
|
175
|
+
tui.success(
|
|
176
|
+
status === 'created' ? `Created ${tui.hyperlink(filePath)}` : `Updated ${tui.hyperlink(filePath)}`,
|
|
177
|
+
)
|
|
178
|
+
tui.print('Restart your shell to use completions.')
|
|
179
|
+
} else if (shell === 'powershell') {
|
|
180
|
+
const dir = join(homedir, '.config', 'powershell')
|
|
181
|
+
await mkdir(dir, { recursive: true })
|
|
182
|
+
const filePath = join(dir, 'jtk-completion.ps1')
|
|
183
|
+
const status = await writeScript(filePath, powershellScript())
|
|
184
|
+
tui.success(
|
|
185
|
+
status === 'created' ? `Created ${tui.hyperlink(filePath)}` : `Updated ${tui.hyperlink(filePath)}`,
|
|
186
|
+
)
|
|
187
|
+
tui.print('Load it in this session: . "' + filePath.replace(/'/g, "''") + '"')
|
|
188
|
+
tui.print('To load automatically, add the line above to your $PROFILE.')
|
|
189
|
+
} else {
|
|
190
|
+
tui.print(`Unknown or unsupported shell: ${shell}`)
|
|
191
|
+
tui.print('Use: jtk completion install bash|zsh|fish|powershell')
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
function detectShell(): string {
|
|
196
|
+
const shell = process.env.SHELL || ''
|
|
197
|
+
if (shell.includes('fish')) return 'fish'
|
|
198
|
+
if (shell.includes('zsh')) return 'zsh'
|
|
199
|
+
if (shell.includes('bash')) return 'bash'
|
|
200
|
+
if (process.env.PSModulePath) return 'powershell'
|
|
201
|
+
return 'bash'
|
|
202
|
+
}
|
package/toolkit/generate.ts
CHANGED
|
@@ -51,8 +51,8 @@ The original statement will be used as the source text for translation.
|
|
|
51
51
|
|
|
52
52
|
Provide one or more target language from the following list:
|
|
53
53
|
${Object.entries(languageNames)
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
55
|
+
.join('\n')}
|
|
56
56
|
|
|
57
57
|
The added translations will be saved in the problem directory overwrite possible existing files.`,
|
|
58
58
|
)
|
|
@@ -82,8 +82,8 @@ The golden solution will be used as a reference for generating the alternatives.
|
|
|
82
82
|
|
|
83
83
|
Provide one or more target programming languages from the following list:
|
|
84
84
|
${Object.entries(languageNames)
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
86
|
+
.join('\n')}
|
|
87
87
|
|
|
88
88
|
The added solutions will be saved in the problem directory overwrite possible existing files.`,
|
|
89
89
|
)
|
|
@@ -117,8 +117,8 @@ The main file for the golden solution will be used as a reference for generating
|
|
|
117
117
|
|
|
118
118
|
Provide one or more target programming languages from the following list:
|
|
119
119
|
${Object.entries(languageNames)
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
121
|
+
.join('\n')}
|
|
122
122
|
|
|
123
123
|
The added main files will be saved in the problem directory overwrite possible existing files.`,
|
|
124
124
|
)
|
|
@@ -191,7 +191,6 @@ The new image will be saved as award.png in the problem directory, overriding an
|
|
|
191
191
|
imagePrompt = 'A colorful award on a white background'
|
|
192
192
|
}
|
|
193
193
|
await tui.section('Generating award image', async () => {
|
|
194
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call -- generated API client
|
|
195
194
|
const download = await jutge.instructor.jutgeai.createImage({
|
|
196
195
|
model,
|
|
197
196
|
label: 'award',
|
package/toolkit/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { submitCmd } from './submit'
|
|
|
22
22
|
import { askCmd } from './ask'
|
|
23
23
|
import { convertCmd } from './convert'
|
|
24
24
|
import { stageCmd } from './stage'
|
|
25
|
+
import { lintCmd } from './lint'
|
|
26
|
+
import { completeInternalCmd, completionCmd } from './completion'
|
|
25
27
|
|
|
26
28
|
program.name(Object.keys(packageJson.bin as Record<string, string>)[0] as string)
|
|
27
29
|
program.alias(Object.keys(packageJson.bin as Record<string, string>)[1] as string)
|
|
@@ -37,6 +39,7 @@ program.addCommand(cleanCmd)
|
|
|
37
39
|
program.addCommand(cloneCmd)
|
|
38
40
|
program.addCommand(generateCmd)
|
|
39
41
|
program.addCommand(verifyCmd)
|
|
42
|
+
program.addCommand(lintCmd)
|
|
40
43
|
program.addCommand(submitCmd)
|
|
41
44
|
program.addCommand(convertCmd)
|
|
42
45
|
program.addCommand(stageCmd)
|
|
@@ -47,12 +50,14 @@ if (settings.developer) {
|
|
|
47
50
|
}
|
|
48
51
|
program.addCommand(configCmd)
|
|
49
52
|
program.addCommand(upgradeCmd)
|
|
53
|
+
program.addCommand(completionCmd)
|
|
50
54
|
program.addCommand(aboutCmd)
|
|
51
55
|
program.addCommand(askCmd)
|
|
56
|
+
program.addCommand(completeInternalCmd, { hidden: true })
|
|
52
57
|
|
|
53
58
|
try {
|
|
54
59
|
await program.parseAsync()
|
|
55
|
-
process.exit(0)
|
|
60
|
+
process.exit(process.exitCode ?? 0)
|
|
56
61
|
} catch (error) {
|
|
57
62
|
console.log()
|
|
58
63
|
console.error('An error occurred:')
|
package/toolkit/lint.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { lintDirectories, type LintIssue } from '../lib/lint'
|
|
4
|
+
|
|
5
|
+
function formatIssue(issue: LintIssue): string {
|
|
6
|
+
const prefix = issue.severity === 'error' ? chalk.red('error') : chalk.yellow('warning')
|
|
7
|
+
const path = issue.path ? chalk.gray(` (${issue.path})`) : ''
|
|
8
|
+
return ` ${prefix} ${issue.code}: ${issue.message}${path}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const lintCmd = new Command('lint')
|
|
12
|
+
.summary('Lint a problem directory')
|
|
13
|
+
.description(
|
|
14
|
+
'Check problem.yml/handler.yml schema, required files present, naming conventions, statement structure, sample vs public test consistency, etc.',
|
|
15
|
+
)
|
|
16
|
+
.argument('[directories...]', 'problem directories to lint (default: current directory)')
|
|
17
|
+
.option('-d, --directory <path>', 'problem directory when no arguments given', '.')
|
|
18
|
+
.action(async (directories: string[], { directory }) => {
|
|
19
|
+
const dirs = directories.length > 0 ? directories : [directory]
|
|
20
|
+
const results = await lintDirectories(dirs)
|
|
21
|
+
|
|
22
|
+
if (results.length === 0) {
|
|
23
|
+
console.log(chalk.yellow('No problem directories found (looked for handler.yml in the given path(s)).'))
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let hasError = false
|
|
28
|
+
let hasWarning = false
|
|
29
|
+
|
|
30
|
+
for (const result of results) {
|
|
31
|
+
const errors = result.issues.filter((i) => i.severity === 'error')
|
|
32
|
+
const warnings = result.issues.filter((i) => i.severity === 'warning')
|
|
33
|
+
if (errors.length > 0) hasError = true
|
|
34
|
+
if (warnings.length > 0) hasWarning = true
|
|
35
|
+
|
|
36
|
+
const dirLabel = result.directory === dirs[0] && results.length === 1 ? result.directory : result.directory
|
|
37
|
+
if (result.issues.length === 0) {
|
|
38
|
+
console.log(chalk.green('✓'), dirLabel, chalk.gray('— no issues'))
|
|
39
|
+
} else {
|
|
40
|
+
console.log()
|
|
41
|
+
console.log(chalk.bold(dirLabel))
|
|
42
|
+
for (const issue of result.issues) {
|
|
43
|
+
console.log(formatIssue(issue))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (results.length > 1) {
|
|
49
|
+
console.log()
|
|
50
|
+
const totalErrors = results.reduce((s, r) => s + r.issues.filter((i) => i.severity === 'error').length, 0)
|
|
51
|
+
const totalWarnings = results.reduce((s, r) => s + r.issues.filter((i) => i.severity === 'warning').length, 0)
|
|
52
|
+
if (totalErrors > 0 || totalWarnings > 0) {
|
|
53
|
+
console.log(
|
|
54
|
+
chalk.gray(
|
|
55
|
+
`Total: ${totalErrors} error(s), ${totalWarnings} warning(s) across ${results.length} problem(s)`,
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (hasError) {
|
|
62
|
+
process.exitCode = 1
|
|
63
|
+
}
|
|
64
|
+
})
|
package/toolkit/submit.ts
CHANGED
|
@@ -10,9 +10,10 @@ export const submitCmd = new Command('submit')
|
|
|
10
10
|
.option('-c, --compiler <id>', 'compiler to use (default: auto-detect from file extension)', 'auto')
|
|
11
11
|
.option('-l, --language <code>', 'language code (ca, es, en, ...)', 'en')
|
|
12
12
|
.option('-n, --no-wait', 'do not wait for submissions to be judged')
|
|
13
|
+
.option('--no-browser', 'do not open the submission URL in the browser (only print URL and/or wait for verdict in terminal)')
|
|
13
14
|
.option('-a, --annotation <annotation>', "annotation for the submission (default: 'jtk-submit-<nanoid16>')")
|
|
14
15
|
|
|
15
|
-
.action(async (programs, { directory, compiler, language, wait, annotation }) => {
|
|
16
|
+
.action(async (programs, { directory, compiler, language, wait, browser, annotation }) => {
|
|
16
17
|
const annotationValue = annotation ?? `jtk-submit-${nanoid16()}`
|
|
17
|
-
await submitInDirectory(directory, language, programs, !wait, compiler, annotationValue)
|
|
18
|
+
await submitInDirectory(directory, language, programs, !wait, browser, compiler, annotationValue)
|
|
18
19
|
})
|
package/toolkit/ai.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Command } from '@commander-js/extra-typings'
|
|
2
|
-
import sharp from 'sharp'
|
|
3
|
-
import z from 'zod'
|
|
4
|
-
import { complete, generateImage, listModels } from '../lib/ai.ts'
|
|
5
|
-
import { settings } from '../lib/settings.ts'
|
|
6
|
-
import tui from '../lib/tui.ts'
|
|
7
|
-
import { convertStringToItsType } from '../lib/utils.ts'
|
|
8
|
-
|
|
9
|
-
export const aiCmd = new Command('ai')
|
|
10
|
-
.description('Query AI models')
|
|
11
|
-
|
|
12
|
-
.action(() => {
|
|
13
|
-
aiCmd.help()
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
aiCmd
|
|
17
|
-
.command('models')
|
|
18
|
-
.description('Show available AI models')
|
|
19
|
-
|
|
20
|
-
.action(async () => {
|
|
21
|
-
const models = await listModels()
|
|
22
|
-
tui.yaml(models)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
aiCmd
|
|
26
|
-
.command('complete')
|
|
27
|
-
.description('Complete a prompt using an AI model')
|
|
28
|
-
|
|
29
|
-
.argument('<prompt>', 'the user prompt to complete')
|
|
30
|
-
.option('-s, --system-prompt <system>', 'the system prompt to use', 'You are a helpful assistant.')
|
|
31
|
-
.option('-m, --model <model>', 'the AI model to use', settings.defaultModel)
|
|
32
|
-
|
|
33
|
-
.action(async (prompt, { model, systemPrompt }) => {
|
|
34
|
-
prompt = prompt.trim()
|
|
35
|
-
systemPrompt = systemPrompt.trim()
|
|
36
|
-
const answer = await complete(model, systemPrompt, prompt)
|
|
37
|
-
tui.print(answer)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
// TODO: generate with different aspect ratios
|
|
41
|
-
aiCmd
|
|
42
|
-
.command('image')
|
|
43
|
-
.description('Generate a square image using an AI model')
|
|
44
|
-
|
|
45
|
-
.argument('<prompt>', 'description of the image to generate')
|
|
46
|
-
.option('-m, --model <model>', 'the graphic AI model to use', 'openai/dall-e-3')
|
|
47
|
-
.option('-s, --size <size>', 'the size of the image (in pixels)', '1024')
|
|
48
|
-
.option('-o, --output <path>', 'the output image path', 'image.png')
|
|
49
|
-
|
|
50
|
-
.action(async (prompt, { model, size, output }) => {
|
|
51
|
-
const sizeInt = z.int().min(16).max(2048).parse(convertStringToItsType(size))
|
|
52
|
-
const image = await generateImage(model, prompt)
|
|
53
|
-
await sharp(image).resize(sizeInt, sizeInt).toFile(output)
|
|
54
|
-
tui.success(`Generated image saved to ${output}`)
|
|
55
|
-
await tui.image(output, 20, 10)
|
|
56
|
-
})
|