@jpetit/toolkit 3.1.1 → 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.
- package/lib/ai.ts +200 -0
- package/lib/cleaner.ts +77 -0
- package/lib/compilers/base.ts +159 -0
- package/lib/compilers/clojure.ts +87 -0
- package/lib/compilers/gcc.ts +39 -0
- package/lib/compilers/ghc.ts +39 -0
- package/lib/compilers/gxx.ts +39 -0
- package/lib/compilers/index.ts +81 -0
- package/lib/compilers/java.ts +105 -0
- package/lib/compilers/python3.ts +112 -0
- package/lib/compilers/run-clojure.ts +101 -0
- package/lib/compilers/run-haskell.ts +117 -0
- package/lib/compilers/run-python.ts +103 -0
- package/lib/compilers/rust.ts +39 -0
- package/lib/create-with-jutgeai.ts +407 -0
- package/lib/create-with-template.ts +55 -0
- package/lib/data.ts +25 -0
- package/lib/doctor.ts +238 -0
- package/lib/generate.ts +171 -0
- package/lib/helpers.ts +48 -0
- package/lib/inspector.ts +253 -0
- package/lib/jutge_api_client.ts +4631 -0
- package/lib/maker.ts +613 -0
- package/lib/settings.ts +51 -0
- package/lib/tui.ts +152 -0
- package/lib/types.ts +55 -0
- package/lib/upload.ts +216 -0
- package/lib/utils.ts +201 -0
- package/lib/versions.ts +46 -0
- package/package.json +4 -2
- package/toolkit/about.ts +43 -0
- package/toolkit/ai.ts +56 -0
- package/toolkit/check.ts +16 -0
- package/toolkit/clean.ts +27 -0
- package/toolkit/compilers.ts +29 -0
- package/toolkit/config.ts +91 -0
- package/toolkit/create.ts +37 -0
- package/toolkit/doctor.ts +22 -0
- package/toolkit/generate.ts +213 -0
- package/toolkit/make.ts +82 -0
- package/toolkit/upgrade.ts +9 -0
- package/toolkit/upload.ts +19 -0
package/toolkit/about.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { nothing, projectDir, readJson } from '../lib/utils.ts'
|
|
4
|
+
import { join } from 'path'
|
|
5
|
+
import { PackageJson } from 'zod-package-json'
|
|
6
|
+
import tui from '../lib/tui.ts'
|
|
7
|
+
|
|
8
|
+
const packageJson = PackageJson.parse(await readJson(join(projectDir(), 'package.json')))
|
|
9
|
+
|
|
10
|
+
export const aboutCmd = new Command('about')
|
|
11
|
+
.description('Get information about jutge-toolkit')
|
|
12
|
+
|
|
13
|
+
.action(async () => {
|
|
14
|
+
await nothing()
|
|
15
|
+
tui.print(chalk.bold(`jutge-toolkit ${packageJson.version}`))
|
|
16
|
+
tui.print(packageJson.description!)
|
|
17
|
+
tui.print('')
|
|
18
|
+
tui.url(packageJson.homepage!)
|
|
19
|
+
tui.print('')
|
|
20
|
+
tui.print('Author:')
|
|
21
|
+
showperson(packageJson.author!)
|
|
22
|
+
tui.print('')
|
|
23
|
+
tui.print('Contributors:')
|
|
24
|
+
for (const contributor of packageJson.contributors!) {
|
|
25
|
+
showperson(contributor)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
function showperson(person: string | { name: string; email?: string; url?: string }) {
|
|
30
|
+
let line = ' - '
|
|
31
|
+
if (typeof person === 'string') {
|
|
32
|
+
line += person
|
|
33
|
+
} else {
|
|
34
|
+
line += person.name
|
|
35
|
+
if (person.email) {
|
|
36
|
+
line += ` <${tui.link('mailto://' + person.email, person.email)}>`
|
|
37
|
+
}
|
|
38
|
+
if (person.url) {
|
|
39
|
+
line += ` (${tui.link(person.url)})`
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
tui.print(line)
|
|
43
|
+
}
|
package/toolkit/ai.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
})
|
package/toolkit/check.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { newMaker } from '../lib/maker'
|
|
3
|
+
|
|
4
|
+
export const checkCmd = new Command('check')
|
|
5
|
+
.alias('verify')
|
|
6
|
+
.description('Check candidate programs')
|
|
7
|
+
|
|
8
|
+
.argument('<programs...>', 'source programs to check')
|
|
9
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
10
|
+
|
|
11
|
+
.action(async (programs, { directory }) => {
|
|
12
|
+
const maker = await newMaker(directory)
|
|
13
|
+
for (const program of programs) {
|
|
14
|
+
await maker.checkCandidate(program)
|
|
15
|
+
}
|
|
16
|
+
})
|
package/toolkit/clean.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command, Option } from '@commander-js/extra-typings'
|
|
2
|
+
import { cleanDirectory } from '../lib/cleaner'
|
|
3
|
+
import tui from '../lib/tui'
|
|
4
|
+
import { findRealDirectories } from '../lib/helpers'
|
|
5
|
+
|
|
6
|
+
// TODO: usefindRealDirectories from lib/helpers.ts
|
|
7
|
+
|
|
8
|
+
export const cleanCmd = new Command('clean')
|
|
9
|
+
.description('Clean generated files')
|
|
10
|
+
|
|
11
|
+
.option('-d, --directories <directories...>', 'problem directories', ['.'])
|
|
12
|
+
.option('-a, --all', 'clean all generated files (including generated statement and correct files', false)
|
|
13
|
+
.addOption(new Option('-f, --force', 'force removal').conflicts('dryRun'))
|
|
14
|
+
.addOption(new Option('-n, --dry-run', 'show but do not remove files').conflicts('force'))
|
|
15
|
+
|
|
16
|
+
.action(async ({ directories, all, force, dryRun }) => {
|
|
17
|
+
const isForce = force || false // default to dry-run if neither option is specified
|
|
18
|
+
|
|
19
|
+
await tui.section(`Cleaning generated files`, async () => {
|
|
20
|
+
const realDirectories = await findRealDirectories(directories)
|
|
21
|
+
for (const directory of realDirectories) {
|
|
22
|
+
await tui.section(`Cleaning directory ${tui.hyperlink(directory)}`, async () => {
|
|
23
|
+
await cleanDirectory(isForce, all, directory)
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getAvailableCompilers, getCompilersInfo, getDefinedCompilerIds } from '../lib/compilers'
|
|
2
|
+
import { Command } from '@commander-js/extra-typings'
|
|
3
|
+
|
|
4
|
+
export const compilersCmd = new Command('compilers')
|
|
5
|
+
.description('Query compiler information')
|
|
6
|
+
// default action is to list all compilers because of older compatibility
|
|
7
|
+
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const info = await getCompilersInfo()
|
|
10
|
+
console.dir(info)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
compilersCmd
|
|
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
|
+
compilersCmd
|
|
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
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { confirm } from '@inquirer/prompts'
|
|
3
|
+
import { editor } from '@inquirer/prompts'
|
|
4
|
+
import YAML from 'yaml'
|
|
5
|
+
import { ZodError } from 'zod'
|
|
6
|
+
import { fromError } from 'zod-validation-error'
|
|
7
|
+
import { configPath, loadSettings, saveSettings, settings } from '../lib/settings'
|
|
8
|
+
import { Settings } from '../lib/types.ts'
|
|
9
|
+
import tui from '../lib/tui.ts'
|
|
10
|
+
import { convertStringToItsType } from '../lib/utils.ts'
|
|
11
|
+
|
|
12
|
+
export const configCmd = new Command('config')
|
|
13
|
+
.summary('Manage configuration')
|
|
14
|
+
.description(
|
|
15
|
+
`Manage configuration
|
|
16
|
+
|
|
17
|
+
The actual configuration file is stored at ${configPath()}`,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
.action(() => {
|
|
21
|
+
configCmd.help()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
configCmd
|
|
25
|
+
.command('show')
|
|
26
|
+
.alias('list')
|
|
27
|
+
.description('Show configuration options')
|
|
28
|
+
|
|
29
|
+
.action(async () => {
|
|
30
|
+
const settings = await loadSettings()
|
|
31
|
+
tui.yaml(settings)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
configCmd
|
|
35
|
+
.command('get <key>')
|
|
36
|
+
.description('Get the value of a configuration option')
|
|
37
|
+
|
|
38
|
+
.action((key: string) => {
|
|
39
|
+
if (!(key in settings)) {
|
|
40
|
+
throw new Error(`Configuration key ${key} does not exist`)
|
|
41
|
+
}
|
|
42
|
+
console.log((settings as any)[key])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
configCmd
|
|
46
|
+
.command('set <key> <value>')
|
|
47
|
+
.description('Set the value of a configuration option')
|
|
48
|
+
|
|
49
|
+
.action(async (key: string, value: string) => {
|
|
50
|
+
if (!(key in settings)) {
|
|
51
|
+
throw new Error(`Configuration key ${key} does not exist`)
|
|
52
|
+
}
|
|
53
|
+
const convertedValue = convertStringToItsType(value)
|
|
54
|
+
const newSettings = Settings.parse({ ...settings, [key]: convertedValue })
|
|
55
|
+
await saveSettings(newSettings)
|
|
56
|
+
tui.success(`Configuration key ${key} updated successfully`)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
configCmd
|
|
60
|
+
.command('edit')
|
|
61
|
+
.description('Open an editor (uses $EDITOR or $VISUAL) to modify the configuration options')
|
|
62
|
+
|
|
63
|
+
.action(async () => {
|
|
64
|
+
let data = YAML.stringify(settings, null, 4)
|
|
65
|
+
while (true) {
|
|
66
|
+
const newData = await editor({
|
|
67
|
+
message: 'Edit configuration',
|
|
68
|
+
default: data,
|
|
69
|
+
postfix: '.yml',
|
|
70
|
+
waitForUserInput: false,
|
|
71
|
+
})
|
|
72
|
+
try {
|
|
73
|
+
const newSettings = Settings.parse(YAML.parse(newData))
|
|
74
|
+
await saveSettings(newSettings)
|
|
75
|
+
tui.success('Configuration options updated successfully')
|
|
76
|
+
return
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof ZodError) {
|
|
79
|
+
console.error(fromError(error).toString())
|
|
80
|
+
} else {
|
|
81
|
+
console.error(error)
|
|
82
|
+
}
|
|
83
|
+
const again = await confirm({ message: 'Edit again?', default: true })
|
|
84
|
+
if (!again) {
|
|
85
|
+
tui.warning('No changes made to the configuration options')
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
data = newData
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { createProblemWithJutgeAI } from '../lib/create-with-jutgeai'
|
|
3
|
+
import { createProblemWithTemplate } from '../lib/create-with-template'
|
|
4
|
+
import { settings } from '../lib/settings'
|
|
5
|
+
|
|
6
|
+
export const createCmd = new Command('create')
|
|
7
|
+
.alias('new')
|
|
8
|
+
.description('Create a new problem')
|
|
9
|
+
|
|
10
|
+
.action(() => {
|
|
11
|
+
createCmd.help()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
createCmd
|
|
15
|
+
.command('with-template')
|
|
16
|
+
.description('Create a problem with a template')
|
|
17
|
+
|
|
18
|
+
.argument('[template]', 'template to use (empty to interactive selection)')
|
|
19
|
+
.option('-d, --directory <path>', 'output directory', 'new-problem.pbm')
|
|
20
|
+
|
|
21
|
+
.action(async (template, { directory }) => {
|
|
22
|
+
await createProblemWithTemplate(directory, template)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
createCmd
|
|
26
|
+
.command('with-ai')
|
|
27
|
+
.description('Create a problem with JutgeAI')
|
|
28
|
+
|
|
29
|
+
.option('-d, --directory <path>', 'output directory', 'new-problem.pbm')
|
|
30
|
+
.option('-i, --input <path>', 'input specification file')
|
|
31
|
+
.option('-o, --output <path>', 'output specification file')
|
|
32
|
+
.option('-n, --do-not-ask', 'do not ask interactively if --input given', false)
|
|
33
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
34
|
+
|
|
35
|
+
.action(async ({ input, output, directory, model, doNotAsk }) => {
|
|
36
|
+
await createProblemWithJutgeAI(model, directory, input, output, doNotAsk)
|
|
37
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
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 doctorCmd = new Command('doctor')
|
|
6
|
+
.description('Diagnose status of the environment')
|
|
7
|
+
|
|
8
|
+
.action(async () => {
|
|
9
|
+
await tui.section('Perform checks', async () => {
|
|
10
|
+
await tui.section('Checking Python3 installation', doc.checkPython3)
|
|
11
|
+
await tui.section('Checking C/C++ installation', doc.checkGCC)
|
|
12
|
+
await tui.section('Checking Haskell installation', doc.checkHaskell)
|
|
13
|
+
await tui.section('Checking Clojure installation', doc.checkClojure)
|
|
14
|
+
await tui.section('Checking Java installation', doc.checkJava)
|
|
15
|
+
await tui.section('Checking Rust installation', doc.checkRust)
|
|
16
|
+
await tui.section('Checking XeLaTeX installation', doc.checkXeLaTeX)
|
|
17
|
+
await tui.section('Checking Pandoc installation', doc.checkPandoc)
|
|
18
|
+
await tui.section('Checking ImageMagick installation', doc.checkImageMagick)
|
|
19
|
+
await tui.section('Checking AI models', doc.checkAIEnvVars)
|
|
20
|
+
await tui.section('Checking terminal', doc.checkTerminal)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Argument, Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { join } from 'path'
|
|
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', '.')
|
|
42
|
+
.option(
|
|
43
|
+
'-m, --model <model>',
|
|
44
|
+
'AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
|
|
45
|
+
'google/gemini-2.5-flash-lite',
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
.action(async (languages, { directory, model }) => {
|
|
49
|
+
const inspector = await newInspector(directory)
|
|
50
|
+
await tui.section('Generating statement translations', async () => {
|
|
51
|
+
for (const language of languages) {
|
|
52
|
+
await addStatementTranslation(model, inspector, language)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
generateCmd
|
|
58
|
+
.command('solutions')
|
|
59
|
+
.summary('Generate alternative solutions using JutgeAI')
|
|
60
|
+
.description(
|
|
61
|
+
`Generate alternative solutions using JutgeAI
|
|
62
|
+
|
|
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
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
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)
|
|
120
|
+
}
|
|
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.
|
|
163
|
+
|
|
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
|
+
})
|
|
213
|
+
})
|
package/toolkit/make.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
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'
|
|
5
|
+
import tui from '../lib/tui'
|
|
6
|
+
import { nothing, projectDir } from '../lib/utils'
|
|
7
|
+
|
|
8
|
+
export const makeCmd = new Command('make')
|
|
9
|
+
.alias('build')
|
|
10
|
+
.description('Make problem elements')
|
|
11
|
+
|
|
12
|
+
.argument('[tasks...]', 'tasks to run: all, inspection, executables, corrects, pdf, text', ['all'])
|
|
13
|
+
.option('-d, --directories <directories...>', 'problem directories', ['.'])
|
|
14
|
+
.option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
|
|
15
|
+
.option('-e, --only-errors', 'only show errors at the final summary', false)
|
|
16
|
+
|
|
17
|
+
.action(async (tasks, { directories, ignoreErrors, onlyErrors }) => {
|
|
18
|
+
console.log()
|
|
19
|
+
await tui.image(join(projectDir(), 'assets', 'images', 'jutge-toolkit.png'), 8, 4)
|
|
20
|
+
|
|
21
|
+
const errors: Record<string, string> = {} // directory -> error message
|
|
22
|
+
|
|
23
|
+
const realDirectories = await findRealDirectories(directories)
|
|
24
|
+
// console.log(realDirectories)
|
|
25
|
+
|
|
26
|
+
for (const directory of realDirectories) {
|
|
27
|
+
try {
|
|
28
|
+
tui.title(`Making problem in directory ${tui.hyperlink(directory, resolve(directory))}`)
|
|
29
|
+
|
|
30
|
+
const maker = await newMaker(directory)
|
|
31
|
+
|
|
32
|
+
// If tasks include 'all', run makeProblem
|
|
33
|
+
if (tasks.includes('all')) {
|
|
34
|
+
await maker.makeProblem()
|
|
35
|
+
} else {
|
|
36
|
+
// Run specific tasks
|
|
37
|
+
if (tasks.includes('inspection')) {
|
|
38
|
+
// already done in maker initialization
|
|
39
|
+
}
|
|
40
|
+
if (tasks.includes('executables')) {
|
|
41
|
+
await maker.makeExecutables()
|
|
42
|
+
}
|
|
43
|
+
if (tasks.includes('corrects')) {
|
|
44
|
+
await maker.makeCorrects()
|
|
45
|
+
}
|
|
46
|
+
if (tasks.includes('pdf')) {
|
|
47
|
+
await maker.makePdfs()
|
|
48
|
+
}
|
|
49
|
+
if (tasks.includes('text')) {
|
|
50
|
+
await maker.makeTexts()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
55
|
+
|
|
56
|
+
if (ignoreErrors) {
|
|
57
|
+
errors[directory] = errorMessage
|
|
58
|
+
tui.error(`Error: ${errorMessage}`)
|
|
59
|
+
} else {
|
|
60
|
+
throw error
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
console.log()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
tui.title('Summary')
|
|
67
|
+
await tui.section('', async () => {
|
|
68
|
+
await nothing()
|
|
69
|
+
if (realDirectories.length === 0) {
|
|
70
|
+
tui.warning('No problem directories found')
|
|
71
|
+
}
|
|
72
|
+
for (const directory of realDirectories) {
|
|
73
|
+
if (errors[directory]) {
|
|
74
|
+
tui.directory(directory)
|
|
75
|
+
tui.error(` ${errors[directory]}`)
|
|
76
|
+
} else if (!onlyErrors) {
|
|
77
|
+
tui.directory(directory)
|
|
78
|
+
tui.success(` No errors found`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -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
|
+
})
|