@jpetit/toolkit 3.0.23 → 3.1.2

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 (61) 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/lib/ai.ts +60 -4
  15. package/lib/cleaner.ts +24 -13
  16. package/lib/compilers/base.ts +70 -14
  17. package/lib/compilers/clojure.ts +21 -10
  18. package/lib/compilers/gcc.ts +4 -33
  19. package/lib/compilers/ghc.ts +4 -40
  20. package/lib/compilers/gxx.ts +4 -33
  21. package/lib/compilers/index.ts +9 -0
  22. package/lib/compilers/java.ts +105 -0
  23. package/lib/compilers/python3.ts +44 -37
  24. package/lib/compilers/run-clojure.ts +101 -0
  25. package/lib/compilers/run-haskell.ts +26 -22
  26. package/lib/compilers/run-python.ts +29 -35
  27. package/lib/compilers/rust.ts +39 -0
  28. package/lib/create-with-jutgeai.ts +407 -0
  29. package/lib/create-with-template.ts +55 -0
  30. package/lib/data.ts +6 -0
  31. package/lib/doctor.ts +86 -6
  32. package/lib/generate.ts +132 -290
  33. package/lib/helpers.ts +48 -0
  34. package/lib/inspector.ts +253 -0
  35. package/lib/jutge_api_client.ts +4631 -0
  36. package/lib/maker.ts +202 -289
  37. package/lib/settings.ts +26 -17
  38. package/lib/tui.ts +25 -15
  39. package/lib/types.ts +40 -5
  40. package/lib/upload.ts +216 -0
  41. package/lib/utils.ts +82 -14
  42. package/lib/versions.ts +46 -0
  43. package/package.json +50 -11
  44. package/toolkit/about.ts +43 -0
  45. package/toolkit/ai.ts +44 -18
  46. package/toolkit/check.ts +16 -0
  47. package/toolkit/clean.ts +16 -26
  48. package/toolkit/compilers.ts +4 -4
  49. package/toolkit/config.ts +91 -0
  50. package/toolkit/create.ts +30 -58
  51. package/toolkit/doctor.ts +15 -11
  52. package/toolkit/generate.ts +195 -98
  53. package/toolkit/index.ts +32 -21
  54. package/toolkit/make.ts +12 -48
  55. package/toolkit/upgrade.ts +9 -0
  56. package/toolkit/upload.ts +19 -0
  57. package/toolkit/create-jutge-ai.ts +0 -101
  58. package/toolkit/create-template.ts +0 -55
  59. package/toolkit/create-wizard.ts +0 -6
  60. package/toolkit/init.ts +0 -56
  61. package/toolkit/verify.ts +0 -19
@@ -1,116 +1,213 @@
1
- import tui from '../lib/tui'
2
- import { Maker, newMaker, type MakerOptions } from '../lib/maker'
3
- import { guessUserEmail, guessUserName, nothing, readText, writeText, writeYaml } from '../lib/utils'
4
- import { Command } from '@commander-js/extra-typings'
5
- import { languageNames } from '../lib/data'
1
+ import { Argument, Command } from '@commander-js/extra-typings'
6
2
  import { join } from 'path'
7
- import { cleanMardownCodeString, complete } from '../lib/ai'
8
- import { getTitleFromStatement } from '../lib/generate'
9
- import { writeFile } from 'fs/promises'
10
- import * as jdenticon from 'jdenticon'
11
-
12
- type Options = {
13
- directory: string
14
- verbose: boolean
15
- model: string
16
- }
17
-
18
- export const generate = new Command('generate')
19
- .description('Generate additional components')
20
- // Add common options here
21
- .option('-d, --directory <path>', 'problem directory', process.cwd()) // TODO: use '.' when bun fixes it
22
- .option('-v, --verbose', 'verbose output (TODO)')
3
+ import sharp from 'sharp'
4
+ import {
5
+ addAlternativeSolution,
6
+ addMainFile,
7
+ addStatementTranslation,
8
+ generateTestCasesGenerator,
9
+ } from '../lib/generate'
10
+ import { complete, generateImage } from '../lib/ai'
11
+ import { languageKeys, languageNames, proglangKeys } from '../lib/data'
12
+ import { newInspector } from '../lib/inspector'
13
+ import tui from '../lib/tui'
14
+ import { writeText } from '../lib/utils'
15
+
16
+ export const generateCmd = new Command('generate')
17
+ .description('Generate elements using JutgeAI')
18
+
19
+ .action(() => {
20
+ generateCmd.help()
21
+ })
22
+
23
+ generateCmd
24
+ .command('translations')
25
+ .summary('Generate statement translations using JutgeAI')
26
+ .description(
27
+ `Generate statement translations using JutgeAI
28
+
29
+ Use this command to add translations of the problem statement into different languages.
30
+ The original statement will be used as the source text for translation.
31
+
32
+ Provide one or more target language from the following list:
33
+ ${Object.entries(languageNames)
34
+ .map(([key, name]) => ` - ${key}: ${name}`)
35
+ .join('\n')}
36
+
37
+ The added translations will be saved in the problem directory overwrite possible existing files.`,
38
+ )
39
+
40
+ .addArgument(new Argument('<languages...>', 'languages to add').choices(languageKeys))
41
+ .option('-d, --directory <path>', 'problem directory', '.')
23
42
  .option(
24
43
  '-m, --model <model>',
25
- 'the AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
44
+ 'AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
26
45
  'google/gemini-2.5-flash-lite',
27
46
  )
28
47
 
29
- generate
30
- .command('translations')
31
- .description('Generate statement translations')
32
- .argument('<languages...>', 'languages to generate')
33
-
34
- .action(async (languages, options, command) => {
35
- const parentOptions = (command.parent?.opts() || {}) as any as Options
36
- const maker = await newMaker(parentOptions)
37
-
38
- await tui.section('Generating translations', async () => {
48
+ .action(async (languages, { directory, model }) => {
49
+ const inspector = await newInspector(directory)
50
+ await tui.section('Generating statement translations', async () => {
39
51
  for (const language of languages) {
40
- await generateTranslation(parentOptions.model, maker, language)
52
+ await addStatementTranslation(model, inspector, language)
41
53
  }
42
54
  })
43
55
  })
44
56
 
45
- generate
46
- .command('award')
47
- .description('Generate random award')
48
-
49
- .action(async (options, command) => {
50
- const parentOptions = (command.parent?.opts() || {}) as any as Options
51
- const maker = await newMaker(parentOptions)
52
-
53
- await tui.section('Generating award', async () => {
54
- const png = jdenticon.toPng(maker.directory, 200)
55
- await writeFile(join(maker.directory, 'award.png'), png)
56
- await writeText(join(maker.directory, 'award.html'), '<b>Well done!</b>\n')
57
- tui.success(
58
- 'Award generated: ' +
59
- tui.hyperlink(maker.directory, 'award.png') +
60
- ' and ' +
61
- tui.hyperlink(maker.directory, 'award.html'),
62
- )
63
- await tui.image(join(maker.directory, 'award.png'), 6, 3)
64
- })
65
- })
66
-
67
- generate
57
+ generateCmd
68
58
  .command('solutions')
69
- .description('Generate solutions TODO')
70
- .argument('<proglangs...>', 'programming languages to generate')
59
+ .summary('Generate alternative solutions using JutgeAI')
60
+ .description(
61
+ `Generate alternative solutions using JutgeAI
71
62
 
72
- .action(async (proglangs, options, command) => {
73
- await nothing()
74
- throw new Error('Not implemented yet')
63
+ Use this command to add alternative solutions for the problem in different programming languages.
64
+ The golden solution will be used as a reference for generating the alternatives.
65
+
66
+ Provide one or more target programming languages from the following list:
67
+ ${Object.entries(languageNames)
68
+ .map(([key, name]) => ` - ${key}: ${name}`)
69
+ .join('\n')}
70
+
71
+ The added solutions will be saved in the problem directory overwrite possible existing files.`,
72
+ )
73
+
74
+ .addArgument(new Argument('<proglangs...>', 'proglangs to add').choices(proglangKeys))
75
+ .option('-d, --directory <path>', 'problem directory', '.')
76
+ .option(
77
+ '-m, --model <model>',
78
+ 'AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
79
+ 'google/gemini-2.5-flash-lite',
80
+ )
81
+
82
+ .action(async (proglangs, { directory, model }) => {
83
+ const inspector = await newInspector(directory)
84
+ for (const proglang of proglangs) {
85
+ await addAlternativeSolution(model, inspector, proglang)
86
+ }
75
87
  })
76
88
 
77
- async function generateTranslation(model: string, maker: Maker, language: string) {
78
- const original = maker.originalLanguage
79
- if (!original) {
80
- throw new Error('Original language not set, cannot generate translations')
81
- }
82
- if (language === original) {
83
- throw new Error(`Language ${language} is the original language`)
84
- }
85
- if (maker.languages.includes(language)) {
86
- throw new Error(`Language ${language} already exists`)
87
- }
88
-
89
- const statememt = await readText(join(maker.directory, `problem.${original}.tex`))
90
-
91
- await tui.section(`Translating from ${languageNames[original]} to ${languageNames[language]}`, async () => {
92
- const prompt = `
93
- Translate the given problem statement to ${languageNames[language]}.
94
-
95
- The translation must be accurate and use proper technical terminology.
96
- Maintain the LaTeX formatting and macros.
97
- The texts that the program must read and write should not be translated.
98
- `
99
-
100
- tui.action(`Generating translation with ${model}`)
101
-
102
- const answer = cleanMardownCodeString(await complete(model, prompt, statememt))
103
-
104
- const info = {
105
- title: getTitleFromStatement(answer) || 'Error translating title',
106
- translator: (await guessUserName()) || 'Unknown',
107
- translator_email: (await guessUserEmail()) || 'unknown',
108
- model,
89
+ generateCmd
90
+ .command('mains')
91
+ .summary('Generate main files using JutgeAI')
92
+ .description(
93
+ `Generate main files using JutgeAI
94
+
95
+ Main files are the entry point for problems that ask users to implement specific functions or classes.
96
+
97
+ Use this command to add main files for the problem in different programming languages.
98
+ The main file for the golden solution will be used as a reference for generating the main files.
99
+
100
+ Provide one or more target programming languages from the following list:
101
+ ${Object.entries(languageNames)
102
+ .map(([key, name]) => ` - ${key}: ${name}`)
103
+ .join('\n')}
104
+
105
+ The added main files will be saved in the problem directory overwrite possible existing files.`,
106
+ )
107
+
108
+ .addArgument(new Argument('<proglangs...>', 'proglangs to add').choices(proglangKeys))
109
+ .option('-d, --directory <path>', 'problem directory', '.')
110
+ .option(
111
+ '-m, --model <model>',
112
+ 'AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
113
+ 'google/gemini-2.5-flash-lite',
114
+ )
115
+
116
+ .action(async (proglangs, { directory, model }) => {
117
+ const inspector = await newInspector(directory)
118
+ for (const proglang of proglangs) {
119
+ await addMainFile(model, inspector, proglang)
109
120
  }
110
- await writeText(join(maker.directory, `problem.${language}.tex`), answer)
111
- await writeYaml(join(maker.directory, `problem.${language}.yml`), info)
121
+ })
122
+
123
+ generateCmd
124
+ .command('award.png')
125
+ .summary('Generate award.png using JutgeAI')
126
+ .description(
127
+ `Generate award.png using AI
128
+
129
+ Use this command to add an award image for the problem.
130
+ Awards are shown to users when they solve the problem.
131
+ They help to motivate users and make the platform more engaging.
132
+
133
+ Provide an interesting prompt to customize the image content. For example:
134
+ - "A golden trophy with a blue ribbon on a wooden base."
135
+ - "A star made of sparkling diamonds on a black background."
136
+ - "A minimalist image with a white background using Van Gogh style."
137
+
138
+ The new image will be saved as award.png in the problem directory, overriding any existing file.`,
139
+ )
140
+
141
+ .option('-d, --directory <path>', 'problem directory', '.')
142
+ .option('-m, --model <model>', 'graphic AI model to use', 'openai/dall-e-3')
143
+ .argument('[prompt]', 'prompt to generate the image', 'A colorful image on a white background. ')
144
+
145
+ .action(async (prompt, { directory, model }) => {
146
+ const output = join(directory, 'award.png')
147
+ const inspector = await newInspector(directory)
148
+ const image = await generateImage(model, prompt)
149
+ await sharp(image).resize(512, 512).toFile(output)
150
+ await tui.image(output, 20, 10)
151
+ tui.success(`Added ${output}`)
152
+ })
153
+
154
+ generateCmd
155
+ .command('award.html')
156
+ .summary('Generate award.html using JutgeAI')
157
+ .description(
158
+ `Generate award.html using JutgeAI
159
+
160
+ Use this command to add an award message for the problem.
161
+ Awards are shown to users when they solve the problem.
162
+ They help to motivate users and make the platform more engaging.
112
163
 
113
- tui.success(`Translation completed: files problem.${language}.tex and problem.${language}.yml created`)
114
- tui.warning(`Please review the generated translation`)
164
+ Provide an interesting prompt to customize the message content. For example:
165
+ - "A short encouraging message after having solved a challenge or a problem."
166
+ - "A congratulatory message for completing a difficult task."
167
+ - "A motivational quote to inspire further learning."
168
+
169
+ The new message will be saved as award.html in the problem directory, overriding any existing file.`,
170
+ )
171
+
172
+ .option('-d, --directory <path>', 'problem directory', '.')
173
+ .argument(
174
+ '[prompt]',
175
+ 'prompt to generate the award message',
176
+ 'Only provide a short encouraging message after having solved a challenge or a problem. Nothing else!',
177
+ )
178
+ .option('-m, --model <model>', 'AI model to use', 'google/gemini-2.5-flash-lite')
179
+
180
+ .action(async (prompt, { directory, model }) => {
181
+ const output = join(directory, 'award.html')
182
+ const inspector = await newInspector(directory)
183
+ const message = await complete(model, '', prompt)
184
+ tui.print(message)
185
+ await writeText(output, message)
186
+ tui.success(`Added ${output}`)
187
+ })
188
+
189
+ generateCmd
190
+ .command('generator')
191
+ .summary('Generate test cases generators using JutgeAI')
192
+
193
+ .option('--random', 'add a random test case generator')
194
+ .option('--hard', 'add a hard test case generator')
195
+ .option('--efficiency', 'add an efficiency test case generator')
196
+ .option('--all', 'add all three test case generators')
197
+
198
+ .option('-d, --directory <path>', 'problem directory', '.')
199
+ .option('-o, --output <path>', 'output file', 'generator-{{type}}.py')
200
+ .option(
201
+ '-m, --model <model>',
202
+ 'AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
203
+ 'google/gemini-2.5-flash-lite',
204
+ )
205
+
206
+ .action(async ({ efficiency, hard, random, all, directory, model, output }) => {
207
+ const inspector = await newInspector(directory)
208
+ await tui.section('Generating test cases generators', async () => {
209
+ if (all || random) await generateTestCasesGenerator(model, inspector, output, 'random')
210
+ if (all || hard) await generateTestCasesGenerator(model, inspector, output, 'hard')
211
+ if (all || efficiency) await generateTestCasesGenerator(model, inspector, output, 'efficiency')
212
+ })
115
213
  })
116
- }
package/toolkit/index.ts CHANGED
@@ -1,34 +1,44 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { program } from '@commander-js/extra-typings'
4
- import { confirm } from '@inquirer/prompts'
5
4
  import { join } from 'path'
6
- import { ai } from './ai'
7
- import { clean } from './clean'
8
- import { compilers } from './compilers'
9
- import { create } from './create'
10
- import { doctor } from './doctor'
11
- import { init } from './init'
12
- import { make } from './make'
13
- import { verify } from './verify'
14
- import { generate } from './generate'
5
+ import { ZodError } from 'zod'
6
+ import { fromError } from 'zod-validation-error'
7
+ import { settings } from '../lib/settings'
15
8
  import { projectDir, readJson } from '../lib/utils'
9
+ import { aboutCmd } from './about'
10
+ import { aiCmd } from './ai'
11
+ import { checkCmd } from './check'
12
+ import { cleanCmd } from './clean'
13
+ import { compilersCmd } from './compilers'
14
+ import { configCmd } from './config'
15
+ import { createCmd } from './create'
16
+ import { doctorCmd } from './doctor'
17
+ import { generateCmd } from './generate'
18
+ import { makeCmd } from './make'
19
+ import { upgradeCmd } from './upgrade'
20
+ import { uploadCmd } from './upload'
16
21
 
17
22
  const packageJson = await readJson(join(projectDir(), 'package.json'))
18
23
 
19
24
  program.name('jutge-toolkit')
25
+ program.alias('jtk')
20
26
  program.version(packageJson.version as string)
21
27
  program.description(packageJson.description as string)
28
+ program.helpCommand('help [command]', 'Display help for command') // To get the message with uppercase :-)
22
29
 
23
- program.addCommand(init)
24
- program.addCommand(create)
25
- program.addCommand(make)
26
- program.addCommand(verify)
27
- program.addCommand(generate)
28
- program.addCommand(clean)
29
- program.addCommand(compilers)
30
- program.addCommand(doctor)
31
- program.addCommand(ai)
30
+ program.addCommand(configCmd)
31
+ program.addCommand(createCmd)
32
+ program.addCommand(makeCmd)
33
+ program.addCommand(checkCmd)
34
+ program.addCommand(generateCmd)
35
+ program.addCommand(cleanCmd)
36
+ program.addCommand(uploadCmd)
37
+ program.addCommand(compilersCmd)
38
+ program.addCommand(doctorCmd)
39
+ program.addCommand(aiCmd)
40
+ program.addCommand(upgradeCmd)
41
+ program.addCommand(aboutCmd)
32
42
 
33
43
  try {
34
44
  await program.parseAsync()
@@ -38,10 +48,11 @@ try {
38
48
  if (error instanceof Error) {
39
49
  if (error.name === 'ExitPromptError') {
40
50
  console.error('Operation cancelled by the user')
51
+ } else if (error instanceof ZodError) {
52
+ console.error(fromError(error).toString())
41
53
  } else {
42
54
  console.error(error.message)
43
- const showTrace = await confirm({ message: 'Show stack trace?', default: false })
44
- if (showTrace) console.error(error)
55
+ if (settings.developer) console.error(error)
45
56
  }
46
57
  } else {
47
58
  console.error(error)
package/toolkit/make.ts CHANGED
@@ -1,59 +1,23 @@
1
+ import { Command } from '@commander-js/extra-typings'
2
+ import { join, resolve } from 'path'
3
+ import { findRealDirectories } from '../lib/helpers'
4
+ import { newMaker } from '../lib/maker'
1
5
  import tui from '../lib/tui'
2
- import { newMaker, type MakerOptions } from '../lib/maker'
3
6
  import { nothing, projectDir } from '../lib/utils'
4
- import { Command } from '@commander-js/extra-typings'
5
- import { join, normalize, resolve } from 'path'
6
- import { languageNames } from '../lib/data'
7
- import { exists } from 'fs/promises'
8
-
9
- /*
10
- Find real problem directories from a list of directories. A real problem directory is one that contains a handler.yml file.
11
- If a directory does not contain a handler.yml file, check if it contains subdirectories for each language that contain a handler.yml file.
12
- If so, add those subdirectories to the list of real problem directories.
13
- */
14
- async function findRealDirectories(directories: string[]): Promise<string[]> {
15
- const realDirectories: string[] = []
16
- for (let dir of directories) {
17
- if (dir === '.') {
18
- dir = normalize(resolve(process.cwd()))
19
- }
20
- if (await exists(join(dir, 'handler.yml'))) {
21
- realDirectories.push(dir)
22
- } else {
23
- for (const language of Object.keys(languageNames).sort()) {
24
- const child = join(dir, language, 'handler.yml')
25
- if (await exists(child)) {
26
- realDirectories.push(resolve(dir, language))
27
- }
28
- }
29
- }
30
- }
31
- return realDirectories
32
- }
33
-
34
- type TaskType = 'all' | 'inspection' | 'executables' | 'corrects' | 'pdf' | 'text'
35
7
 
36
- interface MakeOptions extends MakerOptions {
37
- directories: string[]
38
- tasks: TaskType[]
39
- ignoreErrors: boolean
40
- onlyErrors: boolean
41
- verbose?: boolean
42
- }
8
+ export const makeCmd = new Command('make')
9
+ .alias('build')
10
+ .description('Make problem elements')
43
11
 
44
- export const make = new Command('make')
45
- .description('Make problem components')
46
- .argument('[directories...]', 'problem directories', ['.'])
12
+ .argument('[tasks...]', 'tasks to run: all, inspection, executables, corrects, pdf, text', ['all'])
13
+ .option('-d, --directories <directories...>', 'problem directories', ['.'])
47
14
  .option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
48
15
  .option('-e, --only-errors', 'only show errors at the final summary', false)
49
- .option('-t, --tasks <tasks...>', 'tasks to run: all, inspection, executables, corrects, pdf, text', ['all'])
50
- .option('-v, --verbose', 'verbose output (TODO)', false)
51
16
 
52
- .action(async (directories, options) => {
17
+ .action(async (tasks, { directories, ignoreErrors, onlyErrors }) => {
53
18
  console.log()
54
19
  await tui.image(join(projectDir(), 'assets', 'images', 'jutge-toolkit.png'), 8, 4)
55
20
 
56
- const { tasks, ignoreErrors, ...makerOptions } = options as MakeOptions
57
21
  const errors: Record<string, string> = {} // directory -> error message
58
22
 
59
23
  const realDirectories = await findRealDirectories(directories)
@@ -63,7 +27,7 @@ export const make = new Command('make')
63
27
  try {
64
28
  tui.title(`Making problem in directory ${tui.hyperlink(directory, resolve(directory))}`)
65
29
 
66
- const maker = await newMaker({ ...makerOptions, directory })
30
+ const maker = await newMaker(directory)
67
31
 
68
32
  // If tasks include 'all', run makeProblem
69
33
  if (tasks.includes('all')) {
@@ -109,7 +73,7 @@ export const make = new Command('make')
109
73
  if (errors[directory]) {
110
74
  tui.directory(directory)
111
75
  tui.error(` ${errors[directory]}`)
112
- } else if (!options.onlyErrors) {
76
+ } else if (!onlyErrors) {
113
77
  tui.directory(directory)
114
78
  tui.success(` No errors found`)
115
79
  }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@commander-js/extra-typings'
2
+ import { upgrade } from 'lib/versions'
3
+
4
+ export const upgradeCmd = new Command('upgrade')
5
+ .description('Upgrade Jutge Toolkit to the latest version')
6
+
7
+ .action(async () => {
8
+ await upgrade()
9
+ })
@@ -0,0 +1,19 @@
1
+ import { Command } from '@commander-js/extra-typings'
2
+ import { uploadProblem } from '../lib/upload'
3
+
4
+ export const uploadCmd = new Command('upload')
5
+ .alias('push')
6
+ .summary('Upload problem to Jutge.org')
7
+ .description(
8
+ `Upload problem to Jutge.org
9
+
10
+ If problem.yml exists, the problem will be updated at Jutge.org using that information.
11
+ If problem.yml does not exist, a new problem will be created at Jutge.org and problem.yml will be generated.
12
+ `,
13
+ )
14
+
15
+ .option('-d, --directory <directory>', 'problem directory', '.')
16
+
17
+ .action(async ({ directory }) => {
18
+ await uploadProblem(directory)
19
+ })
@@ -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
- }