@jpetit/toolkit 3.0.23 → 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.
- package/assets/prompts/creators/create-solution.tpl.txt +10 -0
- package/assets/prompts/creators/create-statement.tpl.txt +21 -0
- package/assets/prompts/creators/create-translation.tpl.txt +5 -0
- package/assets/prompts/creators/private-test-cases.txt +6 -0
- package/assets/prompts/creators/sample-test-cases.txt +6 -0
- package/assets/prompts/creators/system-prompt.txt +2 -0
- package/assets/prompts/examples/statement-coda.tex +7 -0
- package/assets/prompts/examples/statement.tex +19 -0
- package/assets/prompts/generators/efficiency.md +41 -0
- package/assets/prompts/generators/hard.md +47 -0
- package/assets/prompts/generators/random.md +39 -0
- package/assets/prompts/proglangs/cc.md +3 -0
- package/assets/prompts/proglangs/py.md +40 -0
- package/package.json +48 -11
- package/toolkit/index.ts +32 -21
- package/lib/ai.ts +0 -144
- package/lib/cleaner.ts +0 -66
- package/lib/compilers/base.ts +0 -103
- package/lib/compilers/clojure.ts +0 -76
- package/lib/compilers/gcc.ts +0 -68
- package/lib/compilers/ghc.ts +0 -75
- package/lib/compilers/gxx.ts +0 -68
- package/lib/compilers/index.ts +0 -72
- package/lib/compilers/python3.ts +0 -105
- package/lib/compilers/run-haskell.ts +0 -113
- package/lib/compilers/run-python.ts +0 -109
- package/lib/data.ts +0 -19
- package/lib/doctor.ts +0 -158
- package/lib/generate.ts +0 -329
- package/lib/maker.ts +0 -700
- package/lib/settings.ts +0 -42
- package/lib/tui.ts +0 -142
- package/lib/types.ts +0 -20
- package/lib/utils.ts +0 -133
- package/toolkit/ai.ts +0 -30
- package/toolkit/clean.ts +0 -37
- package/toolkit/compilers.ts +0 -29
- package/toolkit/create-jutge-ai.ts +0 -101
- package/toolkit/create-template.ts +0 -55
- package/toolkit/create-wizard.ts +0 -6
- package/toolkit/create.ts +0 -65
- package/toolkit/doctor.ts +0 -18
- package/toolkit/generate.ts +0 -116
- package/toolkit/init.ts +0 -56
- package/toolkit/make.ts +0 -118
- package/toolkit/verify.ts +0 -19
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Now, write a solution in {{proglang}} to solve this problem
|
|
2
|
+
|
|
3
|
+
It must be written in an idiomatic way and only include relevant comments.
|
|
4
|
+
The code must be efficient and handle all edge cases.
|
|
5
|
+
The code must read from standard input and write to standard output.
|
|
6
|
+
The code must be well written and easy to understand to novices.
|
|
7
|
+
The code does not have to check the preconditions stated in the problem statement.
|
|
8
|
+
Do not use any non-standard libraries.
|
|
9
|
+
|
|
10
|
+
{{proglangPrompt}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
You are to write the statement of a programming problem in {{language}}.
|
|
2
|
+
|
|
3
|
+
The statement must be written in LaTeX, using a few predefined macros.
|
|
4
|
+
Use LaTeX math syntax for formulas and variables.
|
|
5
|
+
Use dollars for inline maths and use \[ and \] for display maths.
|
|
6
|
+
Do not add input/output test cases in the statement.
|
|
7
|
+
Separate paragraphs by a blank line and \medskip macro.
|
|
8
|
+
|
|
9
|
+
Write in the style of programming contests like the ACM ICPC or Jutge.org.
|
|
10
|
+
|
|
11
|
+
Here is an example for an unrelated problem in English, follow its structure and macros:
|
|
12
|
+
|
|
13
|
+
{{latexExample}}
|
|
14
|
+
|
|
15
|
+
The title for the new problem is:
|
|
16
|
+
|
|
17
|
+
{{title}}
|
|
18
|
+
|
|
19
|
+
Here is the description of this problem and some additional instructions:
|
|
20
|
+
|
|
21
|
+
{{description}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Now, write a private test case file to check the correctness (not the efficiency) of the program.
|
|
2
|
+
|
|
3
|
+
Only the input should be given, the output will be computed from it.
|
|
4
|
+
Private test cases must be written in plain text format and in the same format that the program reads.
|
|
5
|
+
Private test cases should cover edge cases.
|
|
6
|
+
Limit the number of test cases to a dozen.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Now, write a sample test case file to illustrate the input that the program must read according to the problem statement.
|
|
2
|
+
|
|
3
|
+
Only the input should be given, the output will be computed from it.
|
|
4
|
+
Sample test cases must be written in plain text format and in the same format that the program reads.
|
|
5
|
+
Sample test cases should be relatively small but cover interesting cases but not all edge cases.
|
|
6
|
+
If the problem statement features some examples, include them as part of the sample test cases.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
\Problem{Which one is missing?}
|
|
2
|
+
|
|
3
|
+
\Statement
|
|
4
|
+
|
|
5
|
+
Johny has a list of objects, labeled between 1 and $n$, but he lost one of them. He wants to know which one is missing.
|
|
6
|
+
|
|
7
|
+
\medskip
|
|
8
|
+
|
|
9
|
+
Write a program that reads sequences with all the numbers between 1 and $n$ but one, and tells which one is missing.
|
|
10
|
+
|
|
11
|
+
\Input
|
|
12
|
+
|
|
13
|
+
Input consists of several sequences.
|
|
14
|
+
Every sequence begins with a number $n$ between~1 and~$10^4$ followed by $n - 1$ natural numbers.
|
|
15
|
+
Every number between 1 and $n$ appears exactly once, except one of them, which is missing.
|
|
16
|
+
|
|
17
|
+
\Output
|
|
18
|
+
|
|
19
|
+
For every sequence, print the missing number.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Task: Generate Efficiency Input Test Cases
|
|
2
|
+
|
|
3
|
+
Write a program in Python that generates efficiency input test cases according to the specification of the given problem statement.
|
|
4
|
+
|
|
5
|
+
## Important:
|
|
6
|
+
|
|
7
|
+
- The program should not read any data and should write to standard output.
|
|
8
|
+
|
|
9
|
+
- Its main program should be as follows:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
num_cases = int(sys.argv[1])
|
|
14
|
+
generate_efficiency_test_cases(num_cases)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Do not change the main program.
|
|
18
|
+
|
|
19
|
+
- Add a docstring to `generate_efficiency_test_cases` explaining what the program does and what categories of random test cases it generates.
|
|
20
|
+
|
|
21
|
+
- Add inline comments explaining each test case category.
|
|
22
|
+
|
|
23
|
+
- Efficiency test cases are those that are specifically designed to challenge the performance and efficiency of the algorithms solving the problem, but not their correctness.
|
|
24
|
+
|
|
25
|
+
- Efficiency test cases should match the problem constraints.
|
|
26
|
+
|
|
27
|
+
- The number of test cases to generate is given as the first command-line argument in the `num_cases` variable.
|
|
28
|
+
|
|
29
|
+
- Do not generate random test cases.
|
|
30
|
+
|
|
31
|
+
- Use type hints when necessary. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
32
|
+
|
|
33
|
+
- Do not use any non-standard libraries.
|
|
34
|
+
|
|
35
|
+
- Ensure that the generated Python code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
36
|
+
|
|
37
|
+
- Only provide the code for the program, without any additional explanations or text.
|
|
38
|
+
|
|
39
|
+
## Problem statement
|
|
40
|
+
|
|
41
|
+
{{statement}}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Task: Generate Hard Input Test Cases
|
|
2
|
+
|
|
3
|
+
Write a program in Python that generates hard input test cases according to the specification of the given problem statement.
|
|
4
|
+
|
|
5
|
+
## Important:
|
|
6
|
+
|
|
7
|
+
- The program should not read any data and should write to standard output.
|
|
8
|
+
|
|
9
|
+
- Its main program should be as follows:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
generate_hard_test_cases()
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- Do not change the main program.
|
|
17
|
+
|
|
18
|
+
- Add a docstring to `generate_hard_test_cases` explaining what the program does and what categories of random test cases it generates.
|
|
19
|
+
|
|
20
|
+
- Add inline comments explaining each test case category.
|
|
21
|
+
|
|
22
|
+
- Hard cases are those that are specifically designed to challenge the correctness of the algorithms solving the problem, but not its efficiency.
|
|
23
|
+
|
|
24
|
+
- Hard test cases should include:
|
|
25
|
+
- Edge cases that are not covered by typical random test cases.
|
|
26
|
+
- Corner cases that exploit known weaknesses in common algorithms for the problem.
|
|
27
|
+
- Cases that require careful handling of special conditions or constraints.
|
|
28
|
+
- Common pitfalls that might cause incorrect solutions.
|
|
29
|
+
- Degenerate cases that test the robustness of the solution.
|
|
30
|
+
|
|
31
|
+
- But the hard test cases should match the problem constraints.
|
|
32
|
+
|
|
33
|
+
- Do not generate random test cases.
|
|
34
|
+
|
|
35
|
+
- Do not generate test cases to test performance or efficiency.
|
|
36
|
+
|
|
37
|
+
- Use type hints when necessary. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
38
|
+
|
|
39
|
+
- Do not use any non-standard libraries.
|
|
40
|
+
|
|
41
|
+
- Ensure that the generated Python code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
42
|
+
|
|
43
|
+
- Only provide the code for the program, without any additional explanations or text.
|
|
44
|
+
|
|
45
|
+
## Problem statement
|
|
46
|
+
|
|
47
|
+
{{statement}}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Task: Generate Random Input Test Cases
|
|
2
|
+
|
|
3
|
+
Write a program in Python that generates random input test cases according to the specification of the given problem statement.
|
|
4
|
+
|
|
5
|
+
## Important:
|
|
6
|
+
|
|
7
|
+
- The program should not read any data and should write to standard output.
|
|
8
|
+
|
|
9
|
+
- Its main program should be as follows:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
num_cases = int(sys.argv[1])
|
|
14
|
+
seed = int(sys.argv[2]) if len(sys.argv) > 2 else 42
|
|
15
|
+
random.seed(seed)
|
|
16
|
+
generate_random_test_cases(num_cases)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Do not change the main program.
|
|
20
|
+
|
|
21
|
+
- Add a docstring to `generate_random_test_cases` explaining what the program does and what categories of random test cases it generates.
|
|
22
|
+
|
|
23
|
+
- Add inline comments explaining each test case category.
|
|
24
|
+
|
|
25
|
+
- The generated random test cases should match the problem constraints.
|
|
26
|
+
|
|
27
|
+
- The number of test cases to generate is given as the first command-line argument in the `num_cases` variable.
|
|
28
|
+
|
|
29
|
+
- Use type hints when necessary. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
30
|
+
|
|
31
|
+
- Do not use any non-standard libraries.
|
|
32
|
+
|
|
33
|
+
- Ensure that the generated Python code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
34
|
+
|
|
35
|
+
- Only provide the code for the program, without any additional explanations or text.
|
|
36
|
+
|
|
37
|
+
## Problem statement
|
|
38
|
+
|
|
39
|
+
{{statement}}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Use f-strings for string formatting.
|
|
2
|
+
|
|
3
|
+
Use type hints for function definitions. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
4
|
+
|
|
5
|
+
Additionally, ensure that the generated Python3 code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
6
|
+
|
|
7
|
+
Important: Do not use `input()`, `sys.stdin()`, or `EOFError`` to read input as data can be placed freely across many lines. Instead, use the yogi library:
|
|
8
|
+
|
|
9
|
+
`yogi` provides three functions for reading typed input:
|
|
10
|
+
|
|
11
|
+
**`read(type)`** - Returns the next token as `int`, `float`, or `str`. Raises exception if input ends or type mismatches.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from yogi import read
|
|
15
|
+
x = read(int) # reads and returns one integer
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**`scan(type)`** - Like `read()`, but returns `None` instead of raising exceptions when input ends or type mismatches.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from yogi import scan
|
|
22
|
+
s = 0
|
|
23
|
+
x = scan(int) # returns int or None
|
|
24
|
+
while x is not None:
|
|
25
|
+
s += x
|
|
26
|
+
x = scan(int) # returns int or None
|
|
27
|
+
print(s)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**`tokens(type)`** - Iterator that yields tokens of specified type until input ends. Raises exception on type mismatch.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from yogi import tokens
|
|
34
|
+
s = 0
|
|
35
|
+
for x in tokens(int): # iterates over all integers in the input
|
|
36
|
+
s += x
|
|
37
|
+
print(s)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
In yogi functions, `type` can be `int`, `float`, or `str`.
|
package/package.json
CHANGED
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jpetit/toolkit",
|
|
3
3
|
"description": "Toolkit to prepare problems for Jutge.org",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.1.1",
|
|
5
|
+
"homepage": "https://jutge.org",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Jutge.org",
|
|
8
|
+
"email": "info@jutge.org",
|
|
9
|
+
"url": "https://jutge.org"
|
|
10
|
+
},
|
|
11
|
+
"contributors": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Jordi Petit",
|
|
14
|
+
"url": "https://github.com/jordi-petit"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "Pau Fernández",
|
|
18
|
+
"url": "https://github.com/pauek"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/jutge-org/jutge-toolkit.git"
|
|
24
|
+
},
|
|
5
25
|
"publishConfig": {
|
|
6
26
|
"access": "public"
|
|
7
27
|
},
|
|
8
28
|
"module": "index.ts",
|
|
9
29
|
"type": "module",
|
|
10
|
-
"bin":
|
|
30
|
+
"bin": {
|
|
31
|
+
"jutge-toolkit": "toolkit/index.ts",
|
|
32
|
+
"jtk": "toolkit/index.ts"
|
|
33
|
+
},
|
|
11
34
|
"scripts": {
|
|
12
35
|
"lint": "eslint .",
|
|
13
36
|
"format": "bun prettier --write .",
|
|
14
|
-
"depcheck": "bunx depcheck"
|
|
37
|
+
"depcheck": "bunx depcheck",
|
|
38
|
+
"update-jutge-client": "(cd lib ; rm -f jutge_api_client.ts ; https --download https://api.jutge.org/clients/download/typescript)"
|
|
15
39
|
},
|
|
16
40
|
"files": [
|
|
17
|
-
"lib",
|
|
18
|
-
"toolkit",
|
|
19
41
|
"assets"
|
|
20
42
|
],
|
|
21
43
|
"dependencies": {
|
|
22
44
|
"@commander-js/extra-typings": "^14.0.0",
|
|
23
45
|
"@eslint/js": "^9.39.2",
|
|
24
46
|
"@inquirer/prompts": "^8.1.0",
|
|
47
|
+
"archiver": "^7.0.1",
|
|
25
48
|
"boxen": "^8.0.1",
|
|
26
49
|
"bun-types": "^1.3.5",
|
|
27
50
|
"chalk": "^5.6.2",
|
|
@@ -30,35 +53,49 @@
|
|
|
30
53
|
"eslint": "^9.39.2",
|
|
31
54
|
"execa": "^9.6.1",
|
|
32
55
|
"gpt-tokenizer": "^3.4.0",
|
|
56
|
+
"handlebars": "^4.7.8",
|
|
33
57
|
"human-id": "^4.1.3",
|
|
34
58
|
"image-size": "^2.0.2",
|
|
35
|
-
"
|
|
36
|
-
"jdenticon": "^3.3.0",
|
|
59
|
+
"inquirer-checkbox-plus-plus": "^1.1.1",
|
|
37
60
|
"marked": "^17.0.1",
|
|
38
61
|
"marked-terminal": "^7.3.0",
|
|
39
62
|
"multi-llm-ts": "^4.6.2",
|
|
40
63
|
"nanoid": "^5.1.6",
|
|
64
|
+
"openai": "^6.15.0",
|
|
41
65
|
"ora": "^9.0.0",
|
|
42
66
|
"prettier": "^3.7.4",
|
|
43
67
|
"pretty-bytes": "^7.1.0",
|
|
44
68
|
"pretty-ms": "^9.3.0",
|
|
45
69
|
"radash": "^12.1.1",
|
|
46
|
-
"
|
|
70
|
+
"semver": "^7.7.3",
|
|
71
|
+
"sharp": "^0.34.5",
|
|
47
72
|
"terminal-image": "^4.1.0",
|
|
48
73
|
"terminal-link": "^5.0.0",
|
|
49
|
-
"typescript-eslint": "^8.
|
|
74
|
+
"typescript-eslint": "^8.51.0",
|
|
50
75
|
"yaml": "^2.8.2",
|
|
51
|
-
"zod": "^4.
|
|
76
|
+
"zod": "^4.3.4",
|
|
77
|
+
"zod-package-json": "^2.1.0",
|
|
78
|
+
"zod-validation-error": "^5.0.0"
|
|
52
79
|
},
|
|
53
80
|
"devDependencies": {
|
|
81
|
+
"@types/archiver": "^7.0.0",
|
|
54
82
|
"@types/image-size": "^0.8.0",
|
|
55
83
|
"@types/marked": "^6.0.0",
|
|
56
84
|
"@types/marked-terminal": "^6.1.1",
|
|
57
85
|
"@types/node": "^25.0.3",
|
|
58
86
|
"@types/ora": "^3.2.0",
|
|
59
|
-
"@types/
|
|
87
|
+
"@types/semver": "^7.7.1"
|
|
60
88
|
},
|
|
61
89
|
"peerDependencies": {
|
|
62
90
|
"typescript": "^5.9.3"
|
|
91
|
+
},
|
|
92
|
+
"prettier": {
|
|
93
|
+
"semi": false,
|
|
94
|
+
"singleQuote": true,
|
|
95
|
+
"trailingComma": "all",
|
|
96
|
+
"tabWidth": 4,
|
|
97
|
+
"useTabs": false,
|
|
98
|
+
"arrowParens": "always",
|
|
99
|
+
"printWidth": 120
|
|
63
100
|
}
|
|
64
101
|
}
|
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 {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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(
|
|
24
|
-
program.addCommand(
|
|
25
|
-
program.addCommand(
|
|
26
|
-
program.addCommand(
|
|
27
|
-
program.addCommand(
|
|
28
|
-
program.addCommand(
|
|
29
|
-
program.addCommand(
|
|
30
|
-
program.addCommand(
|
|
31
|
-
program.addCommand(
|
|
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
|
-
|
|
44
|
-
if (showTrace) console.error(error)
|
|
55
|
+
if (settings.developer) console.error(error)
|
|
45
56
|
}
|
|
46
57
|
} else {
|
|
47
58
|
console.error(error)
|
package/lib/ai.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { encode } from 'gpt-tokenizer'
|
|
2
|
-
import { estimateCost } from 'gpt-tokenizer/model/gpt-5'
|
|
3
|
-
import { igniteModel, LlmModel, loadModels, logger, Message } from 'multi-llm-ts'
|
|
4
|
-
|
|
5
|
-
// do not log anything from multi-llm-ts
|
|
6
|
-
logger.disable()
|
|
7
|
-
|
|
8
|
-
export async function complete(model: string, systemPrompt: string, userPrompt: string): Promise<string> {
|
|
9
|
-
const parts = model.split('/')
|
|
10
|
-
const providerName = parts[0]!
|
|
11
|
-
const modelName = parts[1]!
|
|
12
|
-
|
|
13
|
-
const config = { apiKey: process.env[keys[providerName]!] || '' }
|
|
14
|
-
|
|
15
|
-
const models = await loadModels(providerName, config)
|
|
16
|
-
// console.log(models)
|
|
17
|
-
const chat = models!.chat.find((m) => m.id === modelName)!
|
|
18
|
-
|
|
19
|
-
const bot = igniteModel(providerName, chat, config)
|
|
20
|
-
const messages = [new Message('system', systemPrompt), new Message('user', userPrompt)]
|
|
21
|
-
const response = await bot.complete(messages)
|
|
22
|
-
return response.content!
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type ModelInfo = Record<string, Record<string, string[]>>
|
|
26
|
-
|
|
27
|
-
export async function listModels(): Promise<ModelInfo> {
|
|
28
|
-
const result: ModelInfo = {}
|
|
29
|
-
const providers = Object.keys(keys).sort()
|
|
30
|
-
for (const providerName of providers) {
|
|
31
|
-
if (providerName === 'ollama') continue // Ollama models are local, skip for now
|
|
32
|
-
const config = { apiKey: process.env[keys[providerName]!] || '' }
|
|
33
|
-
const models = await loadModels(providerName, config)
|
|
34
|
-
if (models === null) continue
|
|
35
|
-
result[providerName] = {}
|
|
36
|
-
for (const modelType in models) {
|
|
37
|
-
result[providerName][modelType] = []
|
|
38
|
-
for (const model of models.chat) {
|
|
39
|
-
result[providerName][modelType].push(model.id)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return result
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class ChatBot {
|
|
47
|
-
private model: string
|
|
48
|
-
private bot: LlmModel | null = null
|
|
49
|
-
private messages: Message[]
|
|
50
|
-
public totalInputTokens: number = 0
|
|
51
|
-
public totalOutputTokens: number = 0
|
|
52
|
-
public totalInputCost: number = 0
|
|
53
|
-
public totalOutputCost: number = 0
|
|
54
|
-
|
|
55
|
-
constructor(model: string, systemPrompt: string) {
|
|
56
|
-
this.model = model
|
|
57
|
-
this.messages = [new Message('system', systemPrompt)]
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async init() {
|
|
61
|
-
const parts = this.model.split('/')
|
|
62
|
-
const providerName = parts[0]!
|
|
63
|
-
const modelName = parts[1]!
|
|
64
|
-
const config = { apiKey: process.env[keys[providerName]!] || '' }
|
|
65
|
-
const models = await loadModels(providerName, config)
|
|
66
|
-
const chat = models!.chat.find((m: any) => m.id === modelName)!
|
|
67
|
-
this.bot = igniteModel(providerName, chat, config)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async complete(userPrompt: string): Promise<string> {
|
|
71
|
-
if (!this.bot) {
|
|
72
|
-
throw new Error(`Model '${this.model}' could not be initialized`)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this.messages.push(new Message('user', userPrompt))
|
|
76
|
-
const response = await this.bot.complete(this.messages)
|
|
77
|
-
this.messages.push(new Message('assistant', response.content))
|
|
78
|
-
|
|
79
|
-
const inputTokens = encode(userPrompt).length
|
|
80
|
-
const outputTokens = encode(response.content!).length
|
|
81
|
-
const inputCost = estimateCost(inputTokens)
|
|
82
|
-
const outputCost = estimateCost(outputTokens)
|
|
83
|
-
|
|
84
|
-
this.totalInputTokens += inputTokens
|
|
85
|
-
this.totalOutputTokens += outputTokens
|
|
86
|
-
this.totalInputCost += inputCost.main!.input!
|
|
87
|
-
this.totalOutputCost += outputCost.main!.output!
|
|
88
|
-
|
|
89
|
-
return response.content!
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
forgetLastInteraction() {
|
|
93
|
-
if (this.messages.length > 2) {
|
|
94
|
-
this.messages.pop()
|
|
95
|
-
this.messages.pop()
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
modelInformation(): any {
|
|
100
|
-
if (!this.bot) {
|
|
101
|
-
throw new Error(`Model '${this.model}' has not been initialized`)
|
|
102
|
-
}
|
|
103
|
-
return this.bot.model
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export type PowerEstimation = {
|
|
108
|
-
wattHours: number
|
|
109
|
-
joules: number
|
|
110
|
-
co2Grams: number
|
|
111
|
-
trees: number
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function estimatePowerConsumption(inputTokens: number, outputTokens: number): PowerEstimation {
|
|
115
|
-
// Written by Claude.ai
|
|
116
|
-
|
|
117
|
-
// Very rough estimates based on public data
|
|
118
|
-
// One tree absorbs approximately:
|
|
119
|
-
// - 22 kg (22,000g) of CO2 per year on average
|
|
120
|
-
// - 1 ton (1,000,000g) over its lifetime (~40 years)
|
|
121
|
-
|
|
122
|
-
const wattsPerToken = 0.0003 // ~0.3 milliwatts per token
|
|
123
|
-
const totalTokens = inputTokens + outputTokens
|
|
124
|
-
const co2PerTree = 22000
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
wattHours: (totalTokens * wattsPerToken) / 3600, // Convert to Wh
|
|
128
|
-
joules: totalTokens * wattsPerToken,
|
|
129
|
-
co2Grams: ((totalTokens * wattsPerToken) / 3600) * 0.5, // Rough CO2 estimate
|
|
130
|
-
trees: (((totalTokens * wattsPerToken) / 3600) * 0.5) / co2PerTree,
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const keys: Record<string, string> = {
|
|
135
|
-
google: 'GEMINI_API_KEY',
|
|
136
|
-
openai: 'OPENAI_API_KEY',
|
|
137
|
-
ollama: '',
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function cleanMardownCodeString(s: string): string {
|
|
141
|
-
const pattern = /^\n*```\w*\s*(.*?)\s*```\n*$/s
|
|
142
|
-
const clean = s.replace(pattern, '$1')
|
|
143
|
-
return clean
|
|
144
|
-
}
|
package/lib/cleaner.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import tui from './tui'
|
|
2
|
-
import { confirm } from '@inquirer/prompts'
|
|
3
|
-
import { readdir, rm } from 'fs/promises'
|
|
4
|
-
import { join } from 'path'
|
|
5
|
-
|
|
6
|
-
export async function cleanFiles(force: boolean, directory: string): Promise<void> {
|
|
7
|
-
const patterns = [
|
|
8
|
-
'\\.exe$',
|
|
9
|
-
'\\.cor$',
|
|
10
|
-
'\\.out$',
|
|
11
|
-
'\\.pyc$',
|
|
12
|
-
'\\.class$',
|
|
13
|
-
'\\.o$',
|
|
14
|
-
'\\.ho$',
|
|
15
|
-
'~$',
|
|
16
|
-
'^problem\\.[a-z][a-z]\\.ps$',
|
|
17
|
-
'^problem\\.[a-z][a-z]\\.md$',
|
|
18
|
-
'^problem\\.[a-z][a-z]\\.pdf$',
|
|
19
|
-
'^problem\\.[a-z][a-z]\\.txt$',
|
|
20
|
-
'^problem\\.[a-z][a-z]\\.html$',
|
|
21
|
-
'^a\\.out$',
|
|
22
|
-
'^__pycache__$',
|
|
23
|
-
]
|
|
24
|
-
const pattern = new RegExp(patterns.join('|'))
|
|
25
|
-
|
|
26
|
-
const entries = await readdir(directory, { withFileTypes: true })
|
|
27
|
-
|
|
28
|
-
const removalList: string[] = []
|
|
29
|
-
for (const entry of entries) {
|
|
30
|
-
const fullPath = join(directory, entry.name)
|
|
31
|
-
if (pattern.test(entry.name)) {
|
|
32
|
-
removalList.push(fullPath)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (removalList.length === 0) {
|
|
37
|
-
tui.success('No files/directories to remove')
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
tui.warning(`The following ${removalList.length} files/directories will be removed:`)
|
|
42
|
-
for (const elem of removalList.sort()) {
|
|
43
|
-
tui.print(elem)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!force) {
|
|
47
|
-
console.log()
|
|
48
|
-
const conformation = await confirm({
|
|
49
|
-
message: `Remove ${removalList.length} files/directories?`,
|
|
50
|
-
default: false,
|
|
51
|
-
})
|
|
52
|
-
if (!conformation) return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let removalCount = 0
|
|
56
|
-
for (const elem of removalList) {
|
|
57
|
-
try {
|
|
58
|
-
await rm(elem, { recursive: true, force: true })
|
|
59
|
-
removalCount++
|
|
60
|
-
} catch (error) {
|
|
61
|
-
tui.error(`Could not remove file/directory ${elem}`)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
tui.success(`Removed ${removalCount} files/directories`)
|
|
66
|
-
}
|