@jutge.org/toolkit 4.4.6 → 4.4.9

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.
@@ -170,6 +170,12 @@ This will run the solution against all test cases and compare outputs.
170
170
 
171
171
  ### Generating Additional Content
172
172
 
173
+ **Generate a statement from a solution:**
174
+
175
+ ```bash
176
+ jtk generate statement cc en "Add a cute story about Joan."
177
+ ```
178
+
173
179
  **Add statement translations:**
174
180
 
175
181
  ```bash
package/docs/jutge-ai.md CHANGED
@@ -61,7 +61,7 @@ See https://platform.openai.com/docs/pricing for the pricing of the OpenAI model
61
61
 
62
62
  ### Recommendation
63
63
 
64
- Try to use `gpt-4.1-nano` or `gpt-4.1-mini` for the quickest results. If you need more reliable results, use `gpt-5-nano` or `gpt-5-mini`.
64
+ Try to use `gpt-4.1-nano` or `gpt-4.1-mini` for the quickest results. If you need more reliable results, use `gpt-5-nano` or `gpt-5-mini`. As could be expected, the larger the model, the more reliable the results and the slower the generation.
65
65
 
66
66
  # Jutge<sup>AI</sup> costs
67
67
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jutge.org/toolkit",
3
3
  "description": "Toolkit to prepare problems for Jutge.org",
4
- "version": "4.4.6",
4
+ "version": "4.4.9",
5
5
  "homepage": "https://jutge.org",
6
6
  "author": {
7
7
  "name": "Jutge.org",
@@ -65,6 +65,7 @@
65
65
  "@dicebear/core": "^9.3.1",
66
66
  "@eslint/js": "^9.39.2",
67
67
  "@inquirer/prompts": "^8.2.0",
68
+ "adm-zip": "^0.5.16",
68
69
  "archiver": "^7.0.1",
69
70
  "boxen": "^8.0.1",
70
71
  "bun-types": "^1.3.7",
@@ -94,6 +95,7 @@
94
95
  "zod-validation-error": "^5.0.0"
95
96
  },
96
97
  "devDependencies": {
98
+ "@types/adm-zip": "^0.5.0",
97
99
  "@types/archiver": "^7.0.0",
98
100
  "@types/image-size": "^0.8.0",
99
101
  "@types/node": "^25.1.0",
package/toolkit/clean.ts CHANGED
@@ -2,20 +2,21 @@ import { Command, Option } from '@commander-js/extra-typings'
2
2
  import { cleanDirectory } from '../lib/cleaner'
3
3
  import tui from '../lib/tui'
4
4
  import { findRealDirectories } from '../lib/helpers'
5
+ import { resolve } from 'path'
5
6
 
6
7
  export const cleanCmd = new Command('clean')
7
8
  .description('Clean disposable files')
8
9
 
9
- .option('-d, --directories <directories...>', 'problem directories', ['.'])
10
+ .option('-d, --directory <directory>', 'problem directory', '.')
10
11
  .option('-a, --all', 'clean all disposable files (including generated statement and correct files', false)
11
12
  .addOption(new Option('-f, --force', 'force removal').conflicts('dryRun'))
12
13
  .addOption(new Option('-n, --dry-run', 'show but do not remove files').conflicts('force'))
13
14
 
14
- .action(async ({ directories, all, force, dryRun }) => {
15
+ .action(async ({ directory, all, force, dryRun }) => {
15
16
  const isForce = force || false // default to dry-run if neither option is specified
16
17
 
17
- await tui.section(`Cleaning generated files`, async () => {
18
- const realDirectories = await findRealDirectories(directories)
18
+ await tui.section(`Cleaning disposable files in ${tui.hyperlink(resolve(directory))}`, async () => {
19
+ const realDirectories = await findRealDirectories([directory])
19
20
  for (const directory of realDirectories) {
20
21
  await tui.section(`Cleaning directory ${tui.hyperlink(directory)}`, async () => {
21
22
  await cleanDirectory(isForce, all, directory)
@@ -15,10 +15,10 @@ convertCmd
15
15
  .command('transform-at-signs')
16
16
  .description('Transform @ signs to lstinline')
17
17
 
18
- .option('-d, --directories <directories...>', 'problem directories', ['.'])
18
+ .option('-d, --directory <directory>', 'problem directory', '.')
19
19
 
20
- .action(async ({ directories }) => {
21
- const realDirectories = await findRealDirectories(directories)
20
+ .action(async ({ directory }) => {
21
+ const realDirectories = await findRealDirectories([directory])
22
22
 
23
23
  for (const realDirectory of realDirectories) {
24
24
  await tui.section(`Processing ${realDirectory}`, async () => {
@@ -0,0 +1,16 @@
1
+ import { Command } from '@commander-js/extra-typings'
2
+ import { downloadProblem } from '../lib/download'
3
+
4
+ export const downloadCmd = new Command('download')
5
+ .summary('Download a problem from Jutge.org')
6
+
7
+ .argument('<problem_nm>', 'problem to download')
8
+ .option('-d, --directory <path>', 'output directory (default: <problem_nm>.pbm)')
9
+
10
+ .action(async (problem_nm, { directory }) => {
11
+ const outDir = directory ?? `${problem_nm}.pbm`
12
+ if (!outDir.endsWith('.pbm')) {
13
+ throw new Error('Output directory must end with .pbm')
14
+ }
15
+ await downloadProblem(problem_nm, outDir)
16
+ })
@@ -0,0 +1,219 @@
1
+ import { Command } from '@commander-js/extra-typings'
2
+ import type { Option, Argument } from '@commander-js/extra-typings'
3
+ import { confirm, input, select } from '@inquirer/prompts'
4
+
5
+ type CommandUnknownOpts = Command<unknown[], Record<string, unknown>, Record<string, unknown>>
6
+
7
+ function getVisibleSubcommands(cmd: CommandUnknownOpts): CommandUnknownOpts[] {
8
+ const help = cmd.createHelp()
9
+ return help.visibleCommands(cmd).filter((c) => c.name() !== 'help')
10
+ }
11
+
12
+ function getVisibleOptions(cmd: CommandUnknownOpts): Option[] {
13
+ return cmd.options.filter((o) => !o.hidden && o.long !== 'help' && o.long !== 'version' && !o.negate)
14
+ }
15
+
16
+ function getVisibleArguments(cmd: CommandUnknownOpts): Argument[] {
17
+ return [...cmd.registeredArguments]
18
+ }
19
+
20
+ function commandSummary(cmd: CommandUnknownOpts): string {
21
+ const summary = (cmd as CommandUnknownOpts & { summary?: () => string }).summary?.()
22
+ if (summary) return summary
23
+ const desc = cmd.description()
24
+ return desc ? desc.split('\n')[0]!.trim() : cmd.name()
25
+ }
26
+
27
+ async function chooseCommand(
28
+ program: CommandUnknownOpts,
29
+ path: string[],
30
+ ): Promise<{ path: string[]; cmd: CommandUnknownOpts }> {
31
+ const current = path.length === 0 ? program : resolveCommand(program, path)!
32
+ const subcommands = getVisibleSubcommands(current)
33
+
34
+ if (subcommands.length === 0) {
35
+ return { path, cmd: current }
36
+ }
37
+
38
+ const choices = subcommands.map((c) => ({
39
+ name: `${c.name()} — ${commandSummary(c)}`,
40
+ value: c.name(),
41
+ }))
42
+
43
+ const chosen = await select({
44
+ message: path.length === 0 ? 'What do you want to do?' : `Choose ${current.name()} subcommand:`,
45
+ choices: [...choices, { name: '← Back', value: '__back__' }],
46
+ })
47
+
48
+ if (chosen === '__back__') {
49
+ if (path.length === 0) return { path: [], cmd: program }
50
+ return chooseCommand(program, path.slice(0, -1))
51
+ }
52
+
53
+
54
+ const newPath = [...path, chosen]
55
+ const nextCmd = resolveCommand(program, newPath)!
56
+ const nextSubs = getVisibleSubcommands(nextCmd)
57
+ if (nextSubs.length > 0) {
58
+ return chooseCommand(program, newPath)
59
+ }
60
+ return { path: newPath, cmd: nextCmd }
61
+ }
62
+
63
+ function resolveCommand(program: CommandUnknownOpts, path: string[]): CommandUnknownOpts | null {
64
+ let current: CommandUnknownOpts = program
65
+ for (const name of path) {
66
+ const sub = current.commands.find((c) => c.name() === name)
67
+ if (!sub) return null
68
+ current = sub as CommandUnknownOpts
69
+ }
70
+ return current
71
+ }
72
+
73
+ async function promptForArgument(arg: Argument, existing: string[]): Promise<string | string[]> {
74
+ const name = arg.name()
75
+ const desc = arg.description || name
76
+ const defaultVal = arg.defaultValue
77
+ const choices = arg.argChoices
78
+ const variadic = arg.variadic
79
+ const required = arg.required
80
+
81
+ const message = desc + (required ? '' : ' (optional)')
82
+
83
+ if (choices && choices.length > 0) {
84
+ const value = await select({
85
+ message,
86
+ choices: choices.map((c) => ({ name: c, value: c })),
87
+ default: defaultVal != null ? (Array.isArray(defaultVal) ? defaultVal[0] : defaultVal) : undefined,
88
+ })
89
+ return value
90
+ }
91
+
92
+ const defaultStr =
93
+ defaultVal != null
94
+ ? (Array.isArray(defaultVal) ? defaultVal.join(' ') : String(defaultVal))
95
+ : required
96
+ ? undefined
97
+ : ''
98
+
99
+ const raw = await input({
100
+ message,
101
+ default: defaultStr,
102
+ validate: (v) => (required && !v.trim() ? 'This argument is required' : true),
103
+ })
104
+
105
+ if (variadic && raw.includes(' ')) {
106
+ return raw.trim().split(/\s+/).filter(Boolean)
107
+ }
108
+ return raw.trim() || (defaultStr as string)
109
+ }
110
+
111
+ async function promptForOption(opt: Option): Promise<{ name: string; value: unknown } | null> {
112
+ const attr = opt.attributeName()
113
+ const desc = opt.description || opt.long || opt.flags
114
+ const defaultVal = opt.defaultValue
115
+ const choices = opt.argChoices
116
+ const isBool = opt.isBoolean()
117
+
118
+ if (isBool) {
119
+ const value = await confirm({
120
+ message: desc,
121
+ default: defaultVal === true,
122
+ })
123
+ return { name: attr, value }
124
+ }
125
+
126
+ if (choices && choices.length > 0) {
127
+ const value = await select({
128
+ message: desc,
129
+ choices: choices.map((c) => ({ name: c, value: c })),
130
+ default: defaultVal != null ? String(defaultVal) : undefined,
131
+ })
132
+ return { name: attr, value }
133
+ }
134
+
135
+ const defaultStr = defaultVal != null ? String(defaultVal) : ''
136
+ const value = await input({
137
+ message: desc + (opt.required ? '' : ' (optional)'),
138
+ default: defaultStr,
139
+ })
140
+ if (value === '' && !opt.required) return null
141
+ return { name: attr, value }
142
+ }
143
+
144
+ function buildArgv(
145
+ path: string[],
146
+ cmd: CommandUnknownOpts,
147
+ argValues: (string | string[])[],
148
+ optionValues: Record<string, unknown>,
149
+ ): string[] {
150
+ const argv: string[] = [...path]
151
+
152
+ const positionals: string[] = []
153
+ for (const v of argValues) {
154
+ if (Array.isArray(v)) positionals.push(...v)
155
+ else if (v !== '') positionals.push(v)
156
+ }
157
+ argv.push(...positionals)
158
+
159
+ for (const opt of getVisibleOptions(cmd)) {
160
+ const attr = opt.attributeName()
161
+ const val = optionValues[attr]
162
+ if (val === undefined) continue
163
+ if (opt.isBoolean()) {
164
+ if (val === true) {
165
+ argv.push(opt.long!.startsWith('--') ? opt.long! : `--${opt.long}`)
166
+ } else if (val === false) {
167
+ const negateOpt = cmd.options.find((o) => o.negate && o.attributeName() === attr)
168
+ if (negateOpt?.long) {
169
+ argv.push(negateOpt.long.startsWith('--') ? negateOpt.long : `--${negateOpt.long}`)
170
+ }
171
+ }
172
+ } else {
173
+ if (val !== '' && val != null) {
174
+ const long = opt.long!.startsWith('--') ? opt.long! : `--${opt.long}`
175
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
176
+ argv.push(long, String(val))
177
+ }
178
+ }
179
+ }
180
+
181
+ return argv
182
+ }
183
+
184
+ export const dummiesCmd = new Command('for-dummies')
185
+ .alias('interactive')
186
+ .summary('Interactive menu for all toolkit tasks')
187
+ .description(
188
+ 'Run a guided flow of menus and prompts to perform any toolkit task. Help and defaults are taken from the command definitions.',
189
+ )
190
+ .action(async function (this: CommandUnknownOpts) {
191
+ const program = this.parent
192
+ if (!program) {
193
+ throw new Error('Dummies command must be run under the main program')
194
+ }
195
+
196
+ const { path, cmd } = await chooseCommand(program as CommandUnknownOpts, [])
197
+
198
+ if (path.length === 0) {
199
+ return
200
+ }
201
+
202
+ const args = getVisibleArguments(cmd)
203
+ const opts = getVisibleOptions(cmd)
204
+
205
+ const argValues: (string | string[])[] = []
206
+ for (const arg of args) {
207
+ const v = await promptForArgument(arg, argValues.flat(1))
208
+ argValues.push(v)
209
+ }
210
+
211
+ const optionValues: Record<string, unknown> = {}
212
+ for (const opt of opts) {
213
+ const result = await promptForOption(opt)
214
+ if (result) optionValues[result.name] = result.value
215
+ }
216
+
217
+ const argv = buildArgv(path, cmd, argValues, optionValues)
218
+ await program.parseAsync(argv, { from: 'user' })
219
+ })
@@ -8,6 +8,7 @@ import {
8
8
  addAlternativeSolution,
9
9
  addMainFile,
10
10
  addStatementTranslation,
11
+ generateStatementFromSolution,
11
12
  generateTestCasesGenerator,
12
13
  } from '../lib/generate'
13
14
  import { newProblem } from '../lib/problem'
@@ -51,8 +52,8 @@ The original statement will be used as the source text for translation.
51
52
 
52
53
  Provide one or more target language from the following list:
53
54
  ${Object.entries(languageNames)
54
- .map(([key, name]) => ` - ${key}: ${name}`)
55
- .join('\n')}
55
+ .map(([key, name]) => ` - ${key}: ${name}`)
56
+ .join('\n')}
56
57
 
57
58
  The added translations will be saved in the problem directory overwrite possible existing files.`,
58
59
  )
@@ -71,6 +72,31 @@ The added translations will be saved in the problem directory overwrite possible
71
72
  })
72
73
  })
73
74
 
75
+ generateCmd
76
+ .command('statement')
77
+ .summary('Generate a statement from a solution using JutgeAI')
78
+ .description(
79
+ `Generate a problem statement from a solution using JutgeAI
80
+
81
+ Use this command to create a statement file from an existing solution.
82
+ The AI infers the problem (input/output, task) from the solution code and writes a problem statement.
83
+
84
+ Provide the programming language of the solution to use and the target language for the statement.
85
+ An optional prompt can guide the statement generation (e.g. "Focus on edge cases" or "Assume the problem is for beginners").
86
+
87
+ The result is written to statement.<lang>.tex in the problem directory.`,
88
+ )
89
+ .addArgument(new Argument('<proglang>', 'solution to use (e.g. cc, py)').choices(proglangKeys))
90
+ .addArgument(new Argument('<language>', 'statement language').choices(languageKeys))
91
+ .option('-d, --directory <path>', 'problem directory', '.')
92
+ .option('-m, --model <model>', 'AI model to use', settings.defaultModel)
93
+ .argument('[prompt]', 'optional prompt to guide statement generation', '')
94
+ .action(async (proglang, language, prompt, { directory, model }) => {
95
+ const jutge = await getLoggedInJutgeClient()
96
+ const problem = await newProblem(directory)
97
+ await generateStatementFromSolution(jutge, model, problem, proglang, language, (prompt ?? '').trim() || undefined)
98
+ })
99
+
74
100
  generateCmd
75
101
  .command('solutions')
76
102
  .summary('Generate alternative solutions using JutgeAI')
@@ -82,8 +108,8 @@ The golden solution will be used as a reference for generating the alternatives.
82
108
 
83
109
  Provide one or more target programming languages from the following list:
84
110
  ${Object.entries(languageNames)
85
- .map(([key, name]) => ` - ${key}: ${name}`)
86
- .join('\n')}
111
+ .map(([key, name]) => ` - ${key}: ${name}`)
112
+ .join('\n')}
87
113
 
88
114
  The added solutions will be saved in the problem directory overwrite possible existing files.`,
89
115
  )
@@ -117,8 +143,8 @@ The main file for the golden solution will be used as a reference for generating
117
143
 
118
144
  Provide one or more target programming languages from the following list:
119
145
  ${Object.entries(languageNames)
120
- .map(([key, name]) => ` - ${key}: ${name}`)
121
- .join('\n')}
146
+ .map(([key, name]) => ` - ${key}: ${name}`)
147
+ .join('\n')}
122
148
 
123
149
  The added main files will be saved in the problem directory overwrite possible existing files.`,
124
150
  )
package/toolkit/index.ts CHANGED
@@ -11,6 +11,7 @@ import { cleanCmd } from './clean'
11
11
  import { compilersCmd } from './compilers'
12
12
  import { configCmd } from './config'
13
13
  import { cloneCmd } from './clone'
14
+ import { downloadCmd } from './download'
14
15
  import { doctorCmd } from './doctor'
15
16
  import { generateCmd } from './generate'
16
17
  import { makeCmd } from './make'
@@ -24,6 +25,7 @@ import { convertCmd } from './convert'
24
25
  import { stageCmd } from './stage'
25
26
  import { lintCmd } from './lint'
26
27
  import { completeInternalCmd, completionCmd } from './completion'
28
+ import { dummiesCmd } from './dummies'
27
29
 
28
30
  program.name(Object.keys(packageJson.bin as Record<string, string>)[0] as string)
29
31
  program.alias(Object.keys(packageJson.bin as Record<string, string>)[1] as string)
@@ -37,6 +39,7 @@ program.addCommand(uploadCmd)
37
39
  program.addCommand(cmdShare)
38
40
  program.addCommand(cleanCmd)
39
41
  program.addCommand(cloneCmd)
42
+ program.addCommand(downloadCmd)
40
43
  program.addCommand(generateCmd)
41
44
  program.addCommand(verifyCmd)
42
45
  program.addCommand(lintCmd)
@@ -54,6 +57,7 @@ program.addCommand(completionCmd)
54
57
  program.addCommand(aboutCmd)
55
58
  program.addCommand(askCmd)
56
59
  program.addCommand(completeInternalCmd, { hidden: true })
60
+ program.addCommand(dummiesCmd)
57
61
 
58
62
  try {
59
63
  await program.parseAsync()
package/toolkit/lint.ts CHANGED
@@ -47,19 +47,17 @@ export async function printLintResults(results: LintResult[], directories: strin
47
47
  export const lintCmd = new Command('lint')
48
48
  .summary('Lint a problem directory')
49
49
 
50
- .argument('[directories...]', 'problem directories to lint (default: current directory)')
51
- .option('-d, --directory <path>', 'problem directory when no arguments given', '.')
50
+ .argument('[directory]', 'problem directory to lint', '.')
52
51
 
53
- .action(async (directories: string[], { directory }) => {
54
- const dirs = directories.length > 0 ? directories : [directory]
55
- const results = await lintDirectories(dirs)
52
+ .action(async (directory: string) => {
53
+ const results = await lintDirectories([directory])
56
54
 
57
55
  if (results.length === 0) {
58
56
  tui.warning('No problem directories found (looked for handler.yml in the given path(s)).')
59
57
  return
60
58
  }
61
59
 
62
- const { hasError } = await printLintResults(results, dirs)
60
+ const { hasError } = await printLintResults(results, [directory])
63
61
  if (hasError) {
64
62
  process.exitCode = 1
65
63
  }
package/toolkit/make.ts CHANGED
@@ -16,13 +16,13 @@ export const makeCmd = new Command('make')
16
16
  .description('Make problem')
17
17
 
18
18
  .argument('[tasks...]', 'tasks to make: all|info|exe|cor|pdf|txt|md|html', ['all'])
19
- .option('-d, --directories <directories...>', 'problem directories', ['.'])
19
+ .option('-d, --directory <directory>', 'problem directory', '.')
20
20
  .option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
21
21
  .option('-e, --only-errors', 'only show errors at the final summary', false)
22
22
  .option('-p, --problem_nm <problem_nm>', 'problem nm', 'DRAFT')
23
23
  .option('-w, --watch', 'watch for changes and rebuild incrementally (under development)', false)
24
24
 
25
- .action(async (tasks, { directories, ignoreErrors, onlyErrors, problem_nm, watch }) => {
25
+ .action(async (tasks, { directory, ignoreErrors, onlyErrors, problem_nm, watch }) => {
26
26
  if (watch) {
27
27
  tasks = ['all']
28
28
  }
@@ -41,7 +41,7 @@ export const makeCmd = new Command('make')
41
41
 
42
42
  const errors: Record<string, string> = {} // directory -> error message
43
43
 
44
- const realDirectories = await findRealDirectories(directories)
44
+ const realDirectories = await findRealDirectories([directory])
45
45
 
46
46
  if (watch && realDirectories.length > 1) {
47
47
  tui.warning('With --watch only the first directory is watched')
package/toolkit/quiz.ts CHANGED
@@ -15,10 +15,10 @@ quizCmd
15
15
  .command('validate')
16
16
  .description('Validate a quiz problem')
17
17
 
18
- .option('-d, --directories <directories...>', 'problem directories', ['.'])
18
+ .option('-d, --directory <directory>', 'problem directory', '.')
19
19
 
20
- .action(async ({ directories }) => {
21
- const realDirectories = await findRealDirectories(directories)
20
+ .action(async ({ directory }) => {
21
+ const realDirectories = await findRealDirectories([directory])
22
22
  for (const directory of realDirectories) {
23
23
  await tui.section(`Validating quiz in directory ${tui.hyperlink(directory)}`, async () => {
24
24
  await validateQuiz(directory)
package/toolkit/share.ts CHANGED
@@ -31,17 +31,17 @@ Finally, it updates problem.yml file with the current sharing settings and shows
31
31
  .action(async function () {
32
32
  const opts = this.opts()
33
33
 
34
- let passcode: string | false | undefined
35
- if (this.getOptionValueSource('passcode') !== 'default') {
36
- const raw = opts.passcode
37
- if (raw === false) {
38
- passcode = false
39
- } else if (raw === true || raw === undefined || raw === '') {
34
+ let passcode: string | undefined | false
35
+ if (this.getOptionValueSource('passcode') === undefined) {
36
+ passcode = undefined
37
+ } else if (this.getOptionValueSource('passcode') === 'cli') {
38
+ if (opts.passcode === true) {
40
39
  passcode = await password({ message: 'Passcode:' })
41
40
  } else {
42
- passcode = raw
41
+ passcode = opts.passcode
43
42
  }
44
43
  }
44
+
45
45
  const testcases = this.getOptionValueSource('testcases') !== 'default' ? opts.testcases : undefined
46
46
  const solutions = this.getOptionValueSource('solutions') !== 'default' ? opts.solutions : undefined
47
47
 
package/docs/windows.md DELETED
@@ -1,106 +0,0 @@
1
- # Notes for Windows
2
-
3
- ## Installation
4
-
5
- - Use PowerShell as terminal. Remember to reopen the terminal after the installation of each tool.
6
-
7
- - Bun is a JavaScript runtime like Node.js but faster and lighter.
8
- It is required to run the toolkit on Windows. Install `bun` from https://bun.sh/. It is easy.
9
-
10
- - Install the toolkit using `bun`:
11
-
12
- ```sh
13
- bun install --global "@jutge.org/toolkit"
14
- ```
15
-
16
- The first time may take a while as `bun` needs to download and compile some dependencies. If it fails with a `mkdir` error, just try again, it seems to be a transient error.
17
-
18
- - Check that the installation was successful:
19
-
20
- ```sh
21
- jtk
22
- ```
23
-
24
- It should show the help message. You can always add a `--help` flag to any command to get more information.
25
-
26
- - Check the tools required by the toolkit:
27
-
28
- ```sh
29
- jtk doctor
30
- ```
31
-
32
- It should print information about the installed tools. If any tool is missing, consider installing it and try again. Depending on your workflow, some dependencies may not be necessary.
33
-
34
- ## Upgrade
35
-
36
- Try to use the latest version of the toolkit. Upgrade the toolkit to the latest version with the following command:
37
-
38
- ```powershell
39
- jtk upgrade
40
- ```
41
-
42
- Check the version after upgrading:
43
-
44
- ```powershell
45
- jtk --version
46
- ```
47
-
48
- ## Dependencies
49
-
50
- - LaTeX: A LaTeX distribution is required to compile problem statements and get their PDFs. It is not necessary but strongly recommended.
51
-
52
- For Windows, install MiKTeX from https://miktex.org/download. During installation, select the option to install missing packages on-the-fly.
53
-
54
- - Pandoc: Pandoc with Lua support is required to convert problem statements to Markdown, Text and HTML. It is not necessary but recommended.
55
-
56
- Install it easily using the Windows Package Manager (`winget`):
57
-
58
- ```powershell
59
- winget install --id JohnMacFarlane.Pandoc
60
- ```
61
-
62
- - Python 3: You only need Python 3 if you plan to use Python scripts in your problems.
63
-
64
- Install Python from https://www.python.org/downloads/windows/. Make sure to check the option to add Python to the system PATH during installation. The toolkit uses `python3` command to run Python scripts.
65
-
66
- - C/C++ Compiler: You only need a C/C++ compiler if you plan to use C/C++ programs in your problems. The toolkit uses `gcc` and `g++` commands to compile C and C++ programs, respectively.
67
-
68
- We suggest using [w64devkit](https://github.com/skeeto/w64devkit), a portable C and C++ development kit for Windows. Here are the steps to install it:
69
- 1. **Download** the latest `.exe` file from https://github.com/skeeto/w64devkit/releases.
70
-
71
- 2. **Extract** by double-clicking the downloaded file and choosing a destination (e.g., `C:\w64devkit`).
72
-
73
- 3. **Run** `w64devkit.exe` from the extracted folder to open a terminal with gcc and g++ available.
74
-
75
- 4. **Test** by typing `gcc --version` in the terminal.
76
-
77
- 5. **Compile programs:**
78
-
79
- ```bash
80
- gcc myprogram.c -o myprogram.exe
81
- g++ myprogram.cpp -o myprogram.exe
82
- ```
83
-
84
- Other options are to install [MinGW-w64](http://mingw-w64.org/doku.php) or use the compiler provided by [MSYS2](https://www.msys2.org/).
85
-
86
- - Java: You only need Java if you plan to use Java programs in your problems. The toolkit uses the `java` and `javac` commands to run and compile Java programs, respectively.
87
-
88
- Install the Java Runtime Environment (JRE) from https://www.java.com/en/download/manual.jsp. Make sure to download the Windows version.
89
-
90
- - Likewise, you may need to install Rust, Haskell and Clojure if you plan to use these languages in your problems. If you know how to install them on Windows, please consider contributing to the documentation.
91
-
92
- ## Miscellaneous tips
93
-
94
- - Open a file with its associated application with `start filename.extension`.
95
-
96
- - Show environment variables with `echo $env:VARIABLE`.
97
-
98
- - Set environment temporarly variables with `$env:VARIABLE=value`.
99
-
100
- - Set environment variables permanently with `[System.Environment]::SetEnvironmentVariable('VARIABLE', 'value', 'User')`.
101
-
102
- - Console font doesn't support Unicode: The default console font (Raster Fonts) doesn't support many Unicode characters. Change to a font like "Consolas", "Lucida Console", or "Cascadia Code" in your PowerShell window properties.
103
-
104
- ```
105
-
106
- ```