@jpetit/toolkit 3.0.22 → 3.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.
Files changed (46) hide show
  1. package/assets/prompts/creators/create-solution.tpl.txt +10 -0
  2. package/assets/prompts/creators/create-statement.tpl.txt +21 -0
  3. package/assets/prompts/creators/create-translation.tpl.txt +5 -0
  4. package/assets/prompts/creators/private-test-cases.txt +6 -0
  5. package/assets/prompts/creators/sample-test-cases.txt +6 -0
  6. package/assets/prompts/creators/system-prompt.txt +2 -0
  7. package/assets/prompts/examples/statement-coda.tex +7 -0
  8. package/assets/prompts/examples/statement.tex +19 -0
  9. package/assets/prompts/generators/efficiency.md +41 -0
  10. package/assets/prompts/generators/hard.md +47 -0
  11. package/assets/prompts/generators/random.md +39 -0
  12. package/assets/prompts/proglangs/cc.md +3 -0
  13. package/assets/prompts/proglangs/py.md +40 -0
  14. package/package.json +48 -11
  15. package/toolkit/index.ts +32 -21
  16. package/lib/ai.ts +0 -144
  17. package/lib/cleaner.ts +0 -66
  18. package/lib/compilers/base.ts +0 -103
  19. package/lib/compilers/clojure.ts +0 -76
  20. package/lib/compilers/gcc.ts +0 -68
  21. package/lib/compilers/ghc.ts +0 -75
  22. package/lib/compilers/gxx.ts +0 -68
  23. package/lib/compilers/index.ts +0 -72
  24. package/lib/compilers/python3.ts +0 -105
  25. package/lib/compilers/run-haskell.ts +0 -113
  26. package/lib/compilers/run-python.ts +0 -109
  27. package/lib/data.ts +0 -19
  28. package/lib/doctor.ts +0 -158
  29. package/lib/generate.ts +0 -329
  30. package/lib/maker.ts +0 -700
  31. package/lib/settings.ts +0 -42
  32. package/lib/tui.ts +0 -142
  33. package/lib/types.ts +0 -20
  34. package/lib/utils.ts +0 -133
  35. package/toolkit/ai.ts +0 -30
  36. package/toolkit/clean.ts +0 -37
  37. package/toolkit/compilers.ts +0 -29
  38. package/toolkit/create-jutge-ai.ts +0 -101
  39. package/toolkit/create-template.ts +0 -55
  40. package/toolkit/create-wizard.ts +0 -6
  41. package/toolkit/create.ts +0 -65
  42. package/toolkit/doctor.ts +0 -18
  43. package/toolkit/generate.ts +0 -116
  44. package/toolkit/init.ts +0 -56
  45. package/toolkit/make.ts +0 -118
  46. package/toolkit/verify.ts +0 -19
package/lib/settings.ts DELETED
@@ -1,42 +0,0 @@
1
- import { exists } from 'fs/promises'
2
- import envPaths from 'env-paths'
3
- import { mkdir } from 'fs/promises'
4
- import { join } from 'path'
5
- import { z } from 'zod'
6
- import { readJson, writeJson } from './utils'
7
-
8
- const ZSettings = z.object({
9
- name: z.string().min(1).default('John Doe'),
10
- email: z.string().email().default('john.doe@example.com'),
11
- notifications: z.boolean().default(true),
12
- })
13
-
14
- export type Settings = z.infer<typeof ZSettings>
15
-
16
- export const paths = envPaths('jutge', { suffix: 'toolkit' })
17
-
18
- function configPath() {
19
- return join(paths.config, 'config.json')
20
- }
21
-
22
- export async function initializePaths() {
23
- await mkdir(paths.config, { recursive: true })
24
- await mkdir(paths.data, { recursive: true })
25
- await mkdir(paths.cache, { recursive: true })
26
- await mkdir(paths.log, { recursive: true })
27
- await mkdir(paths.temp, { recursive: true })
28
- }
29
-
30
- export async function saveSettings(settings: Settings) {
31
- await initializePaths()
32
- return await writeJson(configPath(), settings)
33
- }
34
-
35
- export async function loadSettings(): Promise<Settings> {
36
- const data = await readJson(configPath())
37
- return ZSettings.parse(data)
38
- }
39
-
40
- export async function settingsExist(): Promise<boolean> {
41
- return await exists(configPath())
42
- }
package/lib/tui.ts DELETED
@@ -1,142 +0,0 @@
1
- import boxen from 'boxen'
2
- import chalk from 'chalk'
3
- import { marked } from 'marked'
4
- import { markedTerminal } from 'marked-terminal'
5
- import { resolve } from 'path'
6
- import terminalImage from 'terminal-image'
7
- import terminalLink from 'terminal-link'
8
- import YAML from 'yaml'
9
-
10
- let indentation = 0
11
-
12
- const symbols = ['', '▶', '◆', '●', '■']
13
-
14
- function sectionStart(text: string) {
15
- if (indentation === 0) {
16
- console.log()
17
- if (text) {
18
- console.log(chalk.blue(boxen(text, { padding: { left: 1, right: 1 }, width: process.stdout.columns })))
19
- }
20
- } else {
21
- if (text) {
22
- console.log(chalk.blue(`${symbols[indentation]} ${text}`))
23
- }
24
- }
25
- ++indentation
26
- console.group()
27
- }
28
-
29
- function sectionEnd() {
30
- --indentation
31
- console.groupEnd()
32
- }
33
-
34
- function sectionReset() {
35
- while (indentation > 0) {
36
- --indentation
37
- sectionEnd()
38
- }
39
- }
40
-
41
- async function section<T>(text: string, fn: () => Promise<T>): Promise<T> {
42
- try {
43
- sectionStart(text)
44
- const result = await fn()
45
- return result
46
- } finally {
47
- sectionEnd()
48
- }
49
- }
50
-
51
- function title(text: string) {
52
- console.log(
53
- chalk.blue.bold(
54
- boxen(text, { padding: { left: 1, right: 1 }, width: process.stdout.columns, borderStyle: 'double' }),
55
- ),
56
- )
57
- }
58
-
59
- // TODO: use them anywhere
60
- type CommandOptions = {
61
- italic?: boolean
62
- }
63
-
64
- function command(text: string, options: CommandOptions = {}) {
65
- if (options.italic) {
66
- console.log(chalk.magenta.italic(`❯ ${text}`))
67
- } else {
68
- console.log(chalk.magenta(`❯ ${text}`))
69
- }
70
- }
71
-
72
- function directory(text: string) {
73
- console.log(chalk.magenta('◳ ' + hyperlink(process.cwd(), text)))
74
- }
75
-
76
- function warning(text: string) {
77
- console.log(chalk.hex('#FFA500')(text))
78
- }
79
-
80
- function error(text: string) {
81
- console.log(chalk.red(text))
82
- }
83
-
84
- function success(text: string) {
85
- console.log(chalk.green(text))
86
- }
87
-
88
- function action(text: string) {
89
- console.log(chalk.blue(`${text}...`))
90
- }
91
-
92
- function print(text: string): void {
93
- const lines = text.split('\n')
94
- for (const line of lines) {
95
- console.log(line)
96
- }
97
- }
98
-
99
- async function markdown(content: string): Promise<void> {
100
- // @ts-expect-error: i don't know why types are not working here but seems to work at runtime
101
- marked.use(markedTerminal())
102
- const output = await marked.parse(content)
103
- console.log(output)
104
- }
105
-
106
- function yaml(content: any): void {
107
- const output = YAML.stringify(content, null, 2).trim()
108
- print(output)
109
- }
110
-
111
- function json(content: any): void {
112
- const output = JSON.stringify(content, null, 2)
113
- print(output)
114
- }
115
-
116
- async function image(path: string, width: number, height: number): Promise<void> {
117
- console.log(await terminalImage.file(path, { width, height }))
118
- }
119
-
120
- function hyperlink(dir: string, path: string): string {
121
- return terminalLink(path, 'file://' + resolve(dir, path))
122
- }
123
-
124
- export default {
125
- sectionStart,
126
- sectionEnd,
127
- sectionReset,
128
- section,
129
- title,
130
- command,
131
- directory,
132
- warning,
133
- error,
134
- success,
135
- action,
136
- print,
137
- markdown,
138
- yaml,
139
- json,
140
- image,
141
- hyperlink,
142
- }
package/lib/types.ts DELETED
@@ -1,20 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- export const ZHandler = z.object({
4
- handler: z.enum(['std', 'graphic']).default('std'),
5
- solution: z.string().default('C++'),
6
- source_modifier: z.enum(['none', 'no_main', 'structs']).default('none'),
7
- compilers: z.enum(['RunPython', 'RunHaskell', 'RunClojure', 'GHC']).nullable().default(null),
8
- })
9
-
10
- export type Handler = z.infer<typeof ZHandler>
11
-
12
- export const ZScores = z.array(
13
- z.object({
14
- part: z.string(),
15
- prefix: z.string(),
16
- points: z.number().min(0),
17
- }),
18
- )
19
-
20
- export type Scores = z.infer<typeof ZScores>
package/lib/utils.ts DELETED
@@ -1,133 +0,0 @@
1
- import { execa } from 'execa'
2
- import { readFileSync } from 'fs'
3
- import { readFile, stat, writeFile } from 'fs/promises'
4
- import humanId from 'human-id'
5
- import { customAlphabet } from 'nanoid'
6
- import { dirname, join } from 'path'
7
- import { fileURLToPath } from 'url'
8
- import YAML from 'yaml'
9
-
10
- export async function nothing(): Promise<void> {
11
- // Do nothing: just a way to ignore lint warnings
12
- }
13
-
14
- export async function readText(path: string): Promise<string> {
15
- const content = await readFile(path, 'utf8')
16
- return content
17
- }
18
-
19
- export async function readBytes(path: string): Promise<Uint8Array> {
20
- const content = await readFile(path)
21
- return content
22
- }
23
-
24
- export async function readYaml(path: string): Promise<any> {
25
- const content = await readText(path)
26
- return YAML.parse(content)
27
- }
28
-
29
- export async function readJson(path: string): Promise<any> {
30
- const content = await readText(path)
31
- return JSON.parse(content)
32
- }
33
-
34
- export async function writeText(path: string, content: string): Promise<void> {
35
- await writeFile(path, content)
36
- }
37
-
38
- export async function concatText(
39
- inputPaths: string[],
40
- outputPath: string,
41
- separator: string = '\n\n\n',
42
- ): Promise<void> {
43
- let content = ''
44
- for (const inputPath of inputPaths) {
45
- content += (await readText(inputPath)) + separator
46
- }
47
- await writeText(outputPath, content)
48
- }
49
-
50
- export async function writeYaml(path: string, data: any): Promise<void> {
51
- const content = YAML.stringify(data, null, 4)
52
- await writeText(path, content)
53
- }
54
-
55
- export async function writeJson(path: string, data: any): Promise<void> {
56
- const content = JSON.stringify(data, null, 4)
57
- await writeText(path, content)
58
- }
59
-
60
- export async function fileSize(path: string): Promise<number> {
61
- const stats = await stat(path)
62
- return stats.size
63
- }
64
-
65
- export async function filesAreEqual(path1: string, path2: string): Promise<boolean> {
66
- const bytes1 = await readBytes(path1)
67
- const bytes2 = await readBytes(path2)
68
- return bytes1.length === bytes2.length && bytes1.every((b, i) => b === bytes2[i])
69
- }
70
-
71
- export async function isDirectory(path: string): Promise<boolean> {
72
- try {
73
- const stats = await stat(path)
74
- return stats.isDirectory()
75
- } catch (e) {
76
- return false
77
- }
78
- }
79
-
80
- // Guess user name from git config
81
- export async function guessUserName(): Promise<string | null> {
82
- try {
83
- const { stdout } = await execa`git config user.name`
84
- const name = stdout.trim()
85
- if (name === '') return null
86
- return name
87
- } catch (e) {
88
- return null
89
- }
90
- }
91
-
92
- // Guess user email from git config
93
- export async function guessUserEmail(): Promise<string | null> {
94
- try {
95
- const { stdout } = await execa`git config user.email`
96
- const email = stdout.trim()
97
- if (email === '') return null
98
- return email
99
- } catch (e) {
100
- return null
101
- }
102
- }
103
-
104
- export function nanoid16(): string {
105
- const alphabet = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
106
- const nanoid = customAlphabet(alphabet, 16)
107
- return nanoid()
108
- }
109
-
110
- export function nanoid12(): string {
111
- const alphabet = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
112
- const nanoid = customAlphabet(alphabet, 12)
113
- return nanoid()
114
- }
115
-
116
- export function nanoid8(): string {
117
- const alphabet = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
118
- const nanoid = customAlphabet(alphabet, 8)
119
- return nanoid()
120
- }
121
-
122
- export function humanid(): string {
123
- return humanId({
124
- separator: '-',
125
- capitalize: false,
126
- })
127
- }
128
-
129
- export function projectDir(): string {
130
- const __filename = fileURLToPath(import.meta.url)
131
- const __dirname = dirname(__filename)
132
- return join(__dirname, '..')
133
- }
package/toolkit/ai.ts DELETED
@@ -1,30 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings'
2
- import { complete, listModels } from '../lib/ai.ts'
3
-
4
- export const ai = new Command('ai').description('Query AI models')
5
-
6
- ai.command('complete')
7
- .description('Complete a prompt using an AI model')
8
-
9
- .argument('prompt', 'the user prompt to complete')
10
- .option('-s, --system-prompt <system>', 'the system prompt to use', 'You are a helpful assistant.')
11
- .option(
12
- '-m, --model <model>',
13
- 'the AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
14
- 'google/gemini-2.5-flash-lite',
15
- )
16
-
17
- .action(async (prompt, { model, systemPrompt }) => {
18
- if (!prompt) prompt = 'Who are you?'
19
- if (!systemPrompt) systemPrompt = 'You are a helpful assistant.'
20
-
21
- const answer = await complete(model, systemPrompt, prompt)
22
- console.log(answer)
23
- })
24
-
25
- ai.command('models')
26
- .description('Show available AI models')
27
- .action(async () => {
28
- const models = await listModels()
29
- console.dir(models, { depth: null })
30
- })
package/toolkit/clean.ts DELETED
@@ -1,37 +0,0 @@
1
- import { cleanFiles } from '../lib/cleaner'
2
- import tui from '../lib/tui'
3
- import { Command, Option } from '@commander-js/extra-typings'
4
- import { resolve } from 'path'
5
-
6
- export const clean = new Command('clean')
7
- .description('Clean generated files')
8
-
9
- .argument('[directories...]', 'problem directories', ['.'])
10
- .option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
11
- .addOption(new Option('-f, --force', 'force removal').conflicts('dryRun'))
12
- .addOption(new Option('-n, --dry-run', 'show but do not remove files').conflicts('force'))
13
-
14
- .action(async (directories, { ignoreErrors, force }) => {
15
- // Default to dry-run if neither option is specified
16
- const isForce = force || false
17
- await tui.section('Cleaning generated files', async () => {
18
- for (const directory of directories) {
19
- try {
20
- await tui.section(
21
- `Cleaning directory ${tui.hyperlink(directory, resolve(directory))}`,
22
- async () => {
23
- await cleanFiles(isForce, directory)
24
- },
25
- )
26
- } catch (error) {
27
- const errorMessage = error instanceof Error ? error.message : String(error)
28
-
29
- if (ignoreErrors) {
30
- tui.error(`Error: ${errorMessage}`)
31
- } else {
32
- throw error
33
- }
34
- }
35
- }
36
- })
37
- })
@@ -1,29 +0,0 @@
1
- import { getAvailableCompilers, getCompilersInfo, getDefinedCompilerIds } from '../lib/compilers'
2
- import { Command } from '@commander-js/extra-typings'
3
-
4
- export const compilers = new Command('compilers')
5
- .description('Query compiler information')
6
- // default action is to list all compilers
7
-
8
- .action(async () => {
9
- const info = await getCompilersInfo()
10
- console.dir(info)
11
- })
12
-
13
- compilers
14
- .command('list-defined')
15
- .description('List all defined compiler names')
16
-
17
- .action(async () => {
18
- const items = await getDefinedCompilerIds()
19
- console.dir(items)
20
- })
21
-
22
- compilers
23
- .command('list-available')
24
- .description('List all available compiler names')
25
-
26
- .action(async () => {
27
- const items = await getAvailableCompilers()
28
- console.dir(items)
29
- })
@@ -1,101 +0,0 @@
1
- import { generateProblemWithJutgeAI, type ProblemData } from '../lib/generate'
2
- import { guessUserEmail, guessUserName } from '../lib/utils'
3
- import { checkbox, input, select } from '@inquirer/prompts'
4
- import tui from '../lib/tui'
5
- import humanId from 'human-id'
6
- import slug from 'slug'
7
-
8
- const proglangsChoices = [
9
- { name: 'C', value: 'c' },
10
- { name: 'C++', value: 'cpp' },
11
- { name: 'Python3', value: 'py' },
12
- { name: 'Haskell', value: 'hs' },
13
- { name: 'Clojure', value: 'clj' },
14
- ]
15
-
16
- const problemTypesChoices = [
17
- { name: 'Standard (read input, write output)', value: 'Standard' },
18
- { name: 'Function (use functions)', value: 'Function' },
19
- { name: 'Graphic', value: 'Graphic' },
20
- ]
21
-
22
- const languagesChoices = [
23
- { name: 'English', value: 'en' },
24
- { name: 'Catalan', value: 'ca' },
25
- { name: 'Spanish', value: 'es' },
26
- { name: 'French', value: 'fr' },
27
- { name: 'German', value: 'de' },
28
- ]
29
-
30
- const modelsChoices = [
31
- { name: 'Google - Gemini 2.5 flash lite', value: 'google/gemini-2.5-flash-lite' },
32
- { name: 'Google - Gemini 2.5 flash', value: 'google/gemini-2.5-flash' },
33
- { name: 'Google - Gemini 2.5', value: 'google/gemini-2.5' },
34
- { name: 'OpenAI - GPT-5 Nano', value: 'openai/gpt-5-nano' },
35
- { name: 'OpenAI - GPT-5 Mini', value: 'openai/gpt-5-mini' },
36
- { name: 'OpenAI - GPT-5', value: 'openai/gpt-5' },
37
- { name: 'Ollama - GPT OSS', value: 'ollama/gpt-oss' },
38
- ]
39
-
40
- async function getInitialData(outputDir: string): Promise<ProblemData> {
41
- const author = (await guessUserName()) || 'John Doe'
42
- const email = (await guessUserEmail()) || 'john.doe@example.com'
43
- const title = 'The ' + humanId({ separator: ' ', capitalize: false })
44
- const folder = slug(`The ${title}.pbm`)
45
-
46
- const data = {
47
- title,
48
- description: '',
49
- author,
50
- email,
51
- type: 'std',
52
- proglangs: ['cpp', 'py'],
53
- languages: ['en', 'ca', 'es'],
54
- model: modelsChoices[0]!.value,
55
- outputDir,
56
- }
57
-
58
- return data
59
- }
60
-
61
- async function updateDataFromUser(data: ProblemData) {
62
- data.author = await input({ message: 'Author:', default: data.author })
63
- data.email = await input({ message: 'Author email:', default: data.email })
64
- data.title = await input({ message: 'Problem title:', default: data.title })
65
- data.description = await input({ message: 'Problem description:', default: data.description })
66
-
67
- const checkedProglangsChoices = proglangsChoices.map((choice) => ({
68
- ...choice,
69
- checked: data.proglangs.includes(choice.value),
70
- }))
71
- data.proglangs = await checkbox({
72
- message: 'Select programming languages for solutions:',
73
- choices: checkedProglangsChoices,
74
- required: true,
75
- })
76
-
77
- const checkedLanguagesChoices = languagesChoices.map((choice) => ({
78
- ...choice,
79
- checked: data.languages.includes(choice.value),
80
- }))
81
- data.languages = await checkbox({
82
- message: 'Select languages for statements:',
83
- choices: checkedLanguagesChoices,
84
- required: true,
85
- })
86
-
87
- data.model = await select({
88
- message: 'Select an AI model:',
89
- choices: modelsChoices,
90
- default: data.model || modelsChoices[0]!.value,
91
- })
92
- }
93
-
94
- export async function createProblemWithJutgeAI(outputDir: string): Promise<void> {
95
- tui.warning('JutgeAI can only create std problems for now.')
96
-
97
- const data = await getInitialData(outputDir)
98
- await updateDataFromUser(data)
99
- console.log(data)
100
- await generateProblemWithJutgeAI(data)
101
- }
@@ -1,55 +0,0 @@
1
- import tui from '../lib/tui'
2
- import { isDirectory, projectDir, readText } from '../lib/utils'
3
- import { confirm, select, Separator } from '@inquirer/prompts'
4
- import { cp, glob } from 'fs/promises'
5
- import { join } from 'path'
6
- import { title } from 'radash'
7
-
8
- async function chooseTemplate(): Promise<string> {
9
- // build the choices array
10
- const choices = []
11
- const path = join(projectDir(), 'assets', 'problems')
12
- const glob1 = glob('*', { cwd: path })
13
- for await (const entry1 of glob1) {
14
- if (await isDirectory(join(path, entry1))) {
15
- choices.push(new Separator(`◇ ${title(entry1)}:`))
16
- const glob2 = glob('*', { cwd: join(path, entry1) })
17
- for await (const entry2 of glob2) {
18
- if (await isDirectory(join(path, entry1, entry2))) {
19
- const readme = await readText(join(path, entry1, entry2, 'README.md'))
20
- const name = // Extract title from README.md
21
- ' ' +
22
- readme
23
- .split('\n')
24
- .filter((line) => line.startsWith('#'))[0]
25
- ?.replace('#', '')
26
- .trim() || 'No description'
27
- choices.push({ name, value: join(entry1, entry2) })
28
- }
29
- }
30
- }
31
- }
32
-
33
- while (true) {
34
- const template = await select({ choices, message: 'Select a problem template:', loop: false })
35
-
36
- const readme = await readText(join(path, template, 'README.md'))
37
- await tui.markdown(readme)
38
- console.log()
39
-
40
- const confirmation = await confirm({
41
- message: `Use this template?`,
42
- default: true,
43
- })
44
- if (confirmation) return template
45
- }
46
- }
47
-
48
- export async function createProblemWithTemplate(outputDir: string): Promise<void> {
49
- const template = await chooseTemplate()
50
-
51
- const source = join(projectDir(), 'assets', 'problems', template)
52
- tui.action(`Creating new problem from template ${template}`)
53
- await cp(source, outputDir, { recursive: true })
54
- tui.success(`Problem created at ${outputDir}`)
55
- }
@@ -1,6 +0,0 @@
1
- import { nothing } from '../lib/utils'
2
-
3
- export async function createProblemWithWizard(outputDir: string): Promise<void> {
4
- await nothing()
5
- throw new Error('Wizard method is not yet implemented.')
6
- }
package/toolkit/create.ts DELETED
@@ -1,65 +0,0 @@
1
- import tui from '../lib/tui'
2
- import { Command } from '@commander-js/extra-typings'
3
- import { confirm, input, select } from '@inquirer/prompts'
4
- import { exists, mkdir, rm } from 'fs/promises'
5
- import { normalize } from 'path'
6
- import { createProblemWithJutgeAI } from './create-jutge-ai'
7
- import { createProblemWithTemplate } from './create-template'
8
- import { createProblemWithWizard } from './create-wizard'
9
-
10
- async function selectMethod(): Promise<'template' | 'wizard' | 'jutgeAI'> {
11
- return await select({
12
- message: 'Method to create a new problem:',
13
- choices: [
14
- { name: 'Use a template', value: 'template' },
15
- { name: 'Use the wizard', value: 'wizard' },
16
- { name: 'Use JutgeAI', value: 'jutgeAI' },
17
- ],
18
- })
19
- }
20
-
21
- async function selectOutputDir(): Promise<string> {
22
- let dir = 'my-new-problem.pbm'
23
- while (true) {
24
- dir = await input({
25
- message: 'Output directory for the new problem:',
26
- default: dir,
27
- })
28
- dir = normalize(dir)
29
- if (await exists(dir)) {
30
- tui.error(`Directory ${dir} already exists.`)
31
- const remove = await confirm({
32
- message: 'Remove it?',
33
- default: false,
34
- })
35
- if (!remove) continue
36
- tui.action(`Removing directory ${dir}`)
37
- await rm(dir, { recursive: true, force: true })
38
- tui.success(`Removed directory ${dir}`)
39
- }
40
- if (!dir.endsWith('.pbm')) {
41
- tui.warning("The output directory must end with the '.pbm' extension. Please try again.")
42
- dir += '.pbm'
43
- continue
44
- }
45
- await mkdir(dir, { recursive: true })
46
- tui.success(`Created directory ${dir}`)
47
- return dir
48
- }
49
- }
50
-
51
- export const create = new Command('create')
52
- .description('Create a new problem')
53
-
54
- .action(async () => {
55
- tui.title('Create new problem')
56
- const outputDir = await selectOutputDir()
57
- const method = await selectMethod()
58
- if (method === 'template') {
59
- await createProblemWithTemplate(outputDir)
60
- } else if (method === 'wizard') {
61
- await createProblemWithWizard(outputDir)
62
- } else if (method === 'jutgeAI') {
63
- await createProblemWithJutgeAI(outputDir)
64
- }
65
- })
package/toolkit/doctor.ts DELETED
@@ -1,18 +0,0 @@
1
- import * as doc from '../lib/doctor'
2
- import { Command } from '@commander-js/extra-typings'
3
- import tui from '../lib/tui'
4
-
5
- export const doctor = new Command('doctor')
6
- .description('Diagnose and fix common issues with the project setup')
7
-
8
- .action(async () => {
9
- tui.title('Doctor')
10
-
11
- await tui.section('Checking Python3 installation', doc.checkPython3)
12
- await tui.section('Checking C/C++ installation', doc.checkGCC)
13
- await tui.section('Checking XeLaTeX installation', doc.checkXeLaTeX)
14
- await tui.section('Checking Pandoc installation', doc.checkPandoc)
15
- await tui.section('Checking ImageMagick installation', doc.checkImageMagick)
16
- await tui.section('Checking AI models', doc.checkEnvVars)
17
- await tui.section('Checking terminal', doc.checkTerminal)
18
- })