practicode 0.1.7 → 0.1.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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +28 -8
- package/SECURITY.md +11 -0
- package/assets/i18n/en.json +13 -4
- package/assets/i18n/es.json +13 -4
- package/assets/i18n/ja.json +13 -4
- package/assets/i18n/ko.json +13 -4
- package/assets/i18n/zh.json +13 -4
- package/docs/ARCHITECTURE.md +10 -5
- package/docs/MAINTAINING.md +5 -0
- package/package.json +7 -2
- package/src/ai.rs +103 -4
- package/src/core.rs +63 -4
- package/src/lib.rs +2 -6
- package/src/tui/commands.rs +14 -0
- package/src/tui/editor.rs +180 -0
- package/src/tui/problem_view.rs +138 -0
- package/src/tui/settings_panel.rs +319 -0
- package/src/tui.rs +423 -398
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|

|
|
7
|
+

|
|
8
|
+

|
|
7
9
|

|
|
10
|
+
[Socket.dev package health](https://socket.dev/npm/package/practicode)
|
|
8
11
|
|
|
9
12
|

|
|
10
13
|
|
|
@@ -34,6 +37,8 @@ npm install -g practicode
|
|
|
34
37
|
practicode
|
|
35
38
|
```
|
|
36
39
|
|
|
40
|
+
The npm package has a `postinstall` step that runs `cargo build --release --locked` from the package root so the Rust TUI binary is ready. Set `PRACTICODE_SKIP_BUILD=1` to skip that install-time build; the `practicode` launcher will try the same locked Cargo build on first run if the binary is missing.
|
|
41
|
+
|
|
37
42
|
### Cargo
|
|
38
43
|
|
|
39
44
|
```bash
|
|
@@ -60,9 +65,10 @@ practicode --help
|
|
|
60
65
|
|
|
61
66
|
## Daily Loop
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
On first run, practicode opens a setup panel. After that, the code editor starts focused.
|
|
64
69
|
|
|
65
70
|
```text
|
|
71
|
+
first run: review /profile setup
|
|
66
72
|
write code
|
|
67
73
|
Esc, then /
|
|
68
74
|
choose /run
|
|
@@ -71,6 +77,8 @@ choose /next when it passes
|
|
|
71
77
|
|
|
72
78
|
Typing `/` outside the editor opens the command palette. Use `up/down` to move, `Enter` to run or complete the selected command, and `Esc` to cancel. Press `?` for in-app help or `Ctrl+C` to quit.
|
|
73
79
|
|
|
80
|
+
Hints, failed cases, answers, and user profile panels are copy-friendly: drag in the output pane to use your terminal's normal text selection/copy behavior. When the code editor is visible in the right pane, mouse focus is enabled for that editor. Use `Esc`, `e`, or `/code` to return from output to code.
|
|
81
|
+
|
|
74
82
|
Submissions are saved as you type under `submissions/<problem-id>/solution.<ext>`.
|
|
75
83
|
|
|
76
84
|
## CLI Flags
|
|
@@ -88,17 +96,19 @@ Submissions are saved as you type under `submissions/<problem-id>/solution.<ext>
|
|
|
88
96
|
| `/run` | Judge the current submission |
|
|
89
97
|
| `/code` | Return to the code editor |
|
|
90
98
|
| `/next` | Open the next unsolved problem, or ask AI only when none remain |
|
|
91
|
-
| `/generate easy string problem` | Ask AI to create a new problem
|
|
99
|
+
| `/generate easy string problem` | Ask AI to create a new problem in the background |
|
|
92
100
|
| `/back` | Go back through problem history |
|
|
93
101
|
| `/problems` | Browse problems with `up/down` or `j/k`, open with `Enter` |
|
|
94
102
|
| `/open 2` | Open by number, id, or slug |
|
|
95
103
|
| `/answer` | Show the reference answer |
|
|
96
104
|
| `/hint` | Ask the selected AI for a concise hint |
|
|
97
105
|
| `/hint explain my bug` | Ask the selected AI about the current problem and submission |
|
|
98
|
-
| `/profile` | Show your current
|
|
106
|
+
| `/profile` | Show your current user profile |
|
|
99
107
|
| `/difficulty auto` | Set difficulty preference: `auto`, `easy`, `medium`, `hard` |
|
|
100
108
|
| `/topics arrays, strings` | Set preferred topics for future problems |
|
|
101
109
|
| `/avoid dp, graph` | Set topics to avoid in future problems |
|
|
110
|
+
| `/generate-languages python, rust` | Limit generated answer languages, or use `all` |
|
|
111
|
+
| `/generate-ui ko, en` | Limit generated problem text languages, or use `all` |
|
|
102
112
|
| `/provider codex` | Set AI provider and show local CLI/daemon status |
|
|
103
113
|
| `/model auto` | Use the provider default model for `/hint` and AI-backed `/next` |
|
|
104
114
|
| `/language python` | Set code language: `python`, `ts`, `java`, `rust` |
|
|
@@ -111,13 +121,17 @@ Older command names such as `/prev`, `/list`, `/giveup`, and `/lang` still work
|
|
|
111
121
|
|
|
112
122
|
The default UI language is English. Switch it any time with `/ui ko`, `/ui ja`, `/ui zh`, or `/ui es`.
|
|
113
123
|
|
|
114
|
-
Your
|
|
124
|
+
Your user profile is saved in `.practicode/problem-state.json`. It keeps UI language, code language, theme, preferred difficulty, preferred topics, topics to avoid, and generation language scope. `auto` difficulty follows gradual progression; a fixed difficulty asks local selection and AI generation to prefer that level.
|
|
125
|
+
|
|
126
|
+
Inside `/profile`, use `up/down` to move and `Space` or `Enter` to cycle common settings or enable/disable generated answer/UI languages. Use slash commands for free-form lists such as `/topics arrays, strings`.
|
|
115
127
|
|
|
116
128
|
## Problem Flow
|
|
117
129
|
|
|
118
130
|
`/next` is local-first: it opens the next unsolved local problem before generating anything. When no unsolved problem remains, it asks the selected AI provider to create one.
|
|
119
131
|
|
|
120
|
-
Use `/generate <request>` when you explicitly want to create a new problem
|
|
132
|
+
Use `/generate <request>` when you explicitly want to create a new problem in the background while you keep solving the current one. The generated problem stays local and `/next` will pick it up later.
|
|
133
|
+
|
|
134
|
+
If `/next` has to generate because no local problem remains, it runs in the foreground. Editing and commands are paused so state cannot change halfway through that AI task. Press `Space` for the warmup timing drill, or `q`/`Ctrl+C` to quit.
|
|
121
135
|
|
|
122
136
|
```text
|
|
123
137
|
/generate a slightly harder string problem
|
|
@@ -164,18 +178,24 @@ cargo install --force practicode
|
|
|
164
178
|
|
|
165
179
|
- `/run` executes your local submission as a normal process. practicode runs it from `.practicode/build/<problem-id>/run`, but this is not an OS sandbox. Only run code you trust.
|
|
166
180
|
- `/hint` sends the current problem and submission to the selected AI provider CLI.
|
|
167
|
-
- AI-backed `/next` can run a custom shell command from `settings.ai_next_command`; save only commands you trust.
|
|
181
|
+
- AI-backed `/next` and `/generate` can run a custom shell command from `settings.ai_next_command`; save only commands you trust.
|
|
182
|
+
- npm installs run the package `postinstall` script described above. It only invokes Cargo with the checked-in lockfile from this package root; it does not read local `.env`/`.npmrc` files or contact the configured AI provider.
|
|
183
|
+
- npm releases are published from GitHub Actions with registry signatures and provenance enabled in `package.json`. The release workflow is also prepared for npm Trusted Publishing/OIDC; maintainers should prefer that over long-lived publish tokens when the package setting is enabled on npm.
|
|
168
184
|
- Local `.env`, `.npmrc`, `.practicode/`, `problems/`, and `submissions/` are ignored by git. Do not commit tokens, private prompts, or answer keys.
|
|
169
185
|
|
|
170
186
|
## Development Checks
|
|
171
187
|
|
|
172
188
|
```bash
|
|
189
|
+
cargo fmt --check
|
|
173
190
|
cargo test
|
|
191
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
174
192
|
cargo run -- --smoke
|
|
175
|
-
cargo audit
|
|
193
|
+
cargo audit --deny warnings
|
|
194
|
+
npm pack --dry-run
|
|
195
|
+
npm run smoke
|
|
176
196
|
```
|
|
177
197
|
|
|
178
|
-
This repo has no npm dependencies or lockfile today, so `npm audit` and `pnpm audit` are not applicable until a matching lockfile is added.
|
|
198
|
+
CI runs the Rust audit gate with `cargo audit --deny warnings`; release publishing stops before crates.io/npm publish if that audit fails. This repo has no npm dependencies or lockfile today, so `npm audit` and `pnpm audit` are not applicable until a matching lockfile is added.
|
|
179
199
|
|
|
180
200
|
## Contributing
|
|
181
201
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting
|
|
4
|
+
|
|
5
|
+
Report vulnerabilities through GitHub Security Advisories for this repository when available. If that is not available, open a minimal public issue asking for a private security contact and avoid posting exploit details.
|
|
6
|
+
|
|
7
|
+
Do not include tokens, private prompts, `.env`, `.npmrc`, `.practicode/`, `problems/`, or `submissions/` contents in public reports.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Security-sensitive areas include npm install scripts, release publishing, command execution, local judging, AI provider prompts, update checks, and local user data handling.
|
package/assets/i18n/en.json
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"palette_hint": "up/down select | Enter run | Esc cancel",
|
|
16
16
|
"hint_command": "Enter submit | Esc cancel",
|
|
17
17
|
"hint_list": "up/down move | Enter open | Esc close",
|
|
18
|
-
"hint_output": "Esc/
|
|
18
|
+
"hint_output": "Esc/e edit | drag select to copy | / command",
|
|
19
|
+
"hint_settings": "up/down move | Space/Enter toggle | Esc close",
|
|
19
20
|
"hint_code": "Esc then / command",
|
|
20
21
|
"hint_idle": "/ command | ? help",
|
|
22
|
+
"hint_busy": "Working | q quit",
|
|
23
|
+
"hint_busy_next": "Space warmup | q quit",
|
|
21
24
|
"help_title": "Help",
|
|
22
25
|
"daily_loop": "Daily loop",
|
|
23
26
|
"keys": "Keys",
|
|
@@ -26,17 +29,19 @@
|
|
|
26
29
|
"cmd_code": "Return to the code editor",
|
|
27
30
|
"cmd_edit": "Return to the code editor",
|
|
28
31
|
"cmd_next": "Open the next problem",
|
|
29
|
-
"cmd_generate": "Generate a new problem
|
|
32
|
+
"cmd_generate": "Generate a new problem in the background",
|
|
30
33
|
"cmd_prev": "Open the previous problem",
|
|
31
34
|
"cmd_list": "Browse problems",
|
|
32
35
|
"cmd_open": "Open by number, id, or slug",
|
|
33
36
|
"cmd_giveup": "Show the reference answer",
|
|
34
37
|
"cmd_hint": "Ask for a hint about the current problem",
|
|
35
38
|
"cmd_ai": "Ask AI about the current problem and code",
|
|
36
|
-
"cmd_profile": "Show
|
|
39
|
+
"cmd_profile": "Show user profile",
|
|
37
40
|
"cmd_difficulty": "Set preferred difficulty",
|
|
38
41
|
"cmd_topics": "Set preferred topics",
|
|
39
42
|
"cmd_avoid": "Set topics to avoid",
|
|
43
|
+
"cmd_generate_languages": "Set answer languages for generated problems",
|
|
44
|
+
"cmd_generate_ui": "Set UI languages for generated problems",
|
|
40
45
|
"cmd_provider": "Set AI provider",
|
|
41
46
|
"cmd_model": "Set AI model",
|
|
42
47
|
"cmd_model_auto": "Use provider default model",
|
|
@@ -57,6 +62,10 @@
|
|
|
57
62
|
"update_check_failed": "Could not check for updates.",
|
|
58
63
|
"generating_next": "Generating next problem",
|
|
59
64
|
"already_busy": "Already busy.",
|
|
65
|
+
"busy_warmup": "Warmup: press Space when * hits the center.",
|
|
66
|
+
"busy_commands_paused": "Commands are paused until generation finishes.",
|
|
67
|
+
"hits": "Hits",
|
|
68
|
+
"misses": "Misses",
|
|
60
69
|
"run_pass_next": "Next: /next",
|
|
61
|
-
"run_fail_next": "Fix: press Esc or
|
|
70
|
+
"run_fail_next": "Fix: press Esc or e to edit, then /run"
|
|
62
71
|
}
|
package/assets/i18n/es.json
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"palette_hint": "arriba/abajo elegir | Enter ejecutar | Esc cancelar",
|
|
16
16
|
"hint_command": "Enter ejecutar | Esc cancelar",
|
|
17
17
|
"hint_list": "arriba/abajo mover | Enter abrir | Esc cerrar",
|
|
18
|
-
"hint_output": "Esc/
|
|
18
|
+
"hint_output": "Esc/e editar | arrastra para copiar | / comando",
|
|
19
|
+
"hint_settings": "arriba/abajo mover | Space/Enter alternar | Esc cerrar",
|
|
19
20
|
"hint_code": "Esc y luego / comando",
|
|
20
21
|
"hint_idle": "/ comando | ? ayuda",
|
|
22
|
+
"hint_busy": "Trabajando | q salir",
|
|
23
|
+
"hint_busy_next": "Space calentamiento | q salir",
|
|
21
24
|
"help_title": "Ayuda",
|
|
22
25
|
"daily_loop": "Flujo diario",
|
|
23
26
|
"keys": "Teclas",
|
|
@@ -26,17 +29,19 @@
|
|
|
26
29
|
"cmd_code": "Volver al editor de codigo",
|
|
27
30
|
"cmd_edit": "Volver al editor de codigo",
|
|
28
31
|
"cmd_next": "Abrir el siguiente problema",
|
|
29
|
-
"cmd_generate": "Generar un problema nuevo
|
|
32
|
+
"cmd_generate": "Generar un problema nuevo en segundo plano",
|
|
30
33
|
"cmd_prev": "Abrir el problema anterior",
|
|
31
34
|
"cmd_list": "Abrir la lista de problemas",
|
|
32
35
|
"cmd_open": "Abrir por numero, id o slug",
|
|
33
36
|
"cmd_giveup": "Mostrar la respuesta de referencia",
|
|
34
37
|
"cmd_hint": "Pedir una pista para el problema actual",
|
|
35
38
|
"cmd_ai": "Preguntar a AI sobre el problema y codigo actuales",
|
|
36
|
-
"cmd_profile": "Mostrar perfil de
|
|
39
|
+
"cmd_profile": "Mostrar perfil de usuario",
|
|
37
40
|
"cmd_difficulty": "Configurar dificultad preferida",
|
|
38
41
|
"cmd_topics": "Configurar temas preferidos",
|
|
39
42
|
"cmd_avoid": "Configurar temas a evitar",
|
|
43
|
+
"cmd_generate_languages": "Configurar lenguajes de respuesta generados",
|
|
44
|
+
"cmd_generate_ui": "Configurar idiomas de UI generados",
|
|
40
45
|
"cmd_provider": "Configurar AI provider",
|
|
41
46
|
"cmd_model": "Configurar AI model",
|
|
42
47
|
"cmd_model_auto": "Usar el modelo predeterminado del provider",
|
|
@@ -57,6 +62,10 @@
|
|
|
57
62
|
"update_check_failed": "No se pudo comprobar actualizaciones.",
|
|
58
63
|
"generating_next": "Generando el siguiente problema",
|
|
59
64
|
"already_busy": "Ya hay una tarea en curso.",
|
|
65
|
+
"busy_warmup": "Calentamiento: pulsa Space cuando * llegue al centro.",
|
|
66
|
+
"busy_commands_paused": "Los comandos quedan pausados hasta que termine la generacion.",
|
|
67
|
+
"hits": "Aciertos",
|
|
68
|
+
"misses": "Fallos",
|
|
60
69
|
"run_pass_next": "Siguiente: /next",
|
|
61
|
-
"run_fail_next": "Corrige: pulsa Esc o
|
|
70
|
+
"run_fail_next": "Corrige: pulsa Esc o e para editar, luego /run"
|
|
62
71
|
}
|
package/assets/i18n/ja.json
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"palette_hint": "上下 選択 | Enter 実行 | Esc キャンセル",
|
|
16
16
|
"hint_command": "Enter 実行 | Esc キャンセル",
|
|
17
17
|
"hint_list": "上下 移動 | Enter 開く | Esc 閉じる",
|
|
18
|
-
"hint_output": "Esc
|
|
18
|
+
"hint_output": "Esc/e 編集 | ドラッグ選択でコピー | / コマンド",
|
|
19
|
+
"hint_settings": "上下 移動 | Space/Enter 切替 | Esc 閉じる",
|
|
19
20
|
"hint_code": "Esc の後 / コマンド",
|
|
20
21
|
"hint_idle": "/ コマンド | ? ヘルプ",
|
|
22
|
+
"hint_busy": "処理中 | q 終了",
|
|
23
|
+
"hint_busy_next": "Space ウォームアップ | q 終了",
|
|
21
24
|
"help_title": "ヘルプ",
|
|
22
25
|
"daily_loop": "基本フロー",
|
|
23
26
|
"keys": "キー",
|
|
@@ -26,17 +29,19 @@
|
|
|
26
29
|
"cmd_code": "コードエディタに戻る",
|
|
27
30
|
"cmd_edit": "コードエディタに戻る",
|
|
28
31
|
"cmd_next": "次の問題を開く",
|
|
29
|
-
"cmd_generate": "
|
|
32
|
+
"cmd_generate": "新しい問題をバックグラウンド生成",
|
|
30
33
|
"cmd_prev": "前の問題を開く",
|
|
31
34
|
"cmd_list": "問題一覧を開く",
|
|
32
35
|
"cmd_open": "番号、id、slug で問題を開く",
|
|
33
36
|
"cmd_giveup": "解答を見る",
|
|
34
37
|
"cmd_hint": "現在の問題のヒントを依頼",
|
|
35
38
|
"cmd_ai": "現在の問題とコードについて AI に質問",
|
|
36
|
-
"cmd_profile": "
|
|
39
|
+
"cmd_profile": "ユーザープロファイルを表示",
|
|
37
40
|
"cmd_difficulty": "希望難易度を設定",
|
|
38
41
|
"cmd_topics": "希望トピックを設定",
|
|
39
42
|
"cmd_avoid": "避けるトピックを設定",
|
|
43
|
+
"cmd_generate_languages": "生成問題の解答言語を設定",
|
|
44
|
+
"cmd_generate_ui": "生成問題の UI 言語を設定",
|
|
40
45
|
"cmd_provider": "AI provider を設定",
|
|
41
46
|
"cmd_model": "AI model を設定",
|
|
42
47
|
"cmd_model_auto": "provider の既定モデルを使用",
|
|
@@ -57,6 +62,10 @@
|
|
|
57
62
|
"update_check_failed": "更新を確認できませんでした。",
|
|
58
63
|
"generating_next": "次の問題を生成中",
|
|
59
64
|
"already_busy": "すでに処理中です。",
|
|
65
|
+
"busy_warmup": "ウォームアップ: * が中央に来たら Space。",
|
|
66
|
+
"busy_commands_paused": "生成が終わるまでコマンドは一時停止します。",
|
|
67
|
+
"hits": "成功",
|
|
68
|
+
"misses": "ミス",
|
|
60
69
|
"run_pass_next": "次: /next",
|
|
61
|
-
"run_fail_next": "修正: Esc
|
|
70
|
+
"run_fail_next": "修正: Esc または e で編集し、/run"
|
|
62
71
|
}
|
package/assets/i18n/ko.json
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"palette_hint": "위/아래 선택 | Enter 실행 | Esc 취소",
|
|
16
16
|
"hint_command": "Enter 실행 | Esc 취소",
|
|
17
17
|
"hint_list": "위/아래 이동 | Enter 열기 | Esc 닫기",
|
|
18
|
-
"hint_output": "Esc
|
|
18
|
+
"hint_output": "Esc/e 편집 | 드래그 선택으로 복사 | / 명령",
|
|
19
|
+
"hint_settings": "위/아래 이동 | Space/Enter 토글 | Esc 닫기",
|
|
19
20
|
"hint_code": "Esc 후 / 명령",
|
|
20
21
|
"hint_idle": "/ 명령 | ? 도움말",
|
|
22
|
+
"hint_busy": "작업 중 | q 종료",
|
|
23
|
+
"hint_busy_next": "Space 워밍업 | q 종료",
|
|
21
24
|
"help_title": "도움말",
|
|
22
25
|
"daily_loop": "기본 흐름",
|
|
23
26
|
"keys": "키",
|
|
@@ -26,17 +29,19 @@
|
|
|
26
29
|
"cmd_code": "코드 편집기로 돌아가기",
|
|
27
30
|
"cmd_edit": "코드 편집기로 돌아가기",
|
|
28
31
|
"cmd_next": "다음 문제 열기",
|
|
29
|
-
"cmd_generate": "새 문제를
|
|
32
|
+
"cmd_generate": "새 문제를 백그라운드에서 생성",
|
|
30
33
|
"cmd_prev": "이전 문제 열기",
|
|
31
34
|
"cmd_list": "문제 목록 열기",
|
|
32
35
|
"cmd_open": "번호, id, slug로 문제 열기",
|
|
33
36
|
"cmd_giveup": "정답 보기",
|
|
34
37
|
"cmd_hint": "현재 문제 힌트 요청",
|
|
35
38
|
"cmd_ai": "현재 문제와 코드에 대해 AI에게 질문",
|
|
36
|
-
"cmd_profile": "
|
|
39
|
+
"cmd_profile": "사용자 프로필 보기",
|
|
37
40
|
"cmd_difficulty": "선호 난이도 설정",
|
|
38
41
|
"cmd_topics": "선호 주제 설정",
|
|
39
42
|
"cmd_avoid": "피할 주제 설정",
|
|
43
|
+
"cmd_generate_languages": "생성 문제의 정답 언어 설정",
|
|
44
|
+
"cmd_generate_ui": "생성 문제의 UI 언어 설정",
|
|
40
45
|
"cmd_provider": "AI provider 설정",
|
|
41
46
|
"cmd_model": "AI model 설정",
|
|
42
47
|
"cmd_model_auto": "provider 기본 모델 사용",
|
|
@@ -57,6 +62,10 @@
|
|
|
57
62
|
"update_check_failed": "업데이트를 확인할 수 없습니다.",
|
|
58
63
|
"generating_next": "다음 문제 생성 중",
|
|
59
64
|
"already_busy": "이미 작업 중입니다.",
|
|
65
|
+
"busy_warmup": "워밍업: *가 가운데에 왔을 때 Space를 누르세요.",
|
|
66
|
+
"busy_commands_paused": "생성이 끝날 때까지 명령은 잠시 멈춥니다.",
|
|
67
|
+
"hits": "성공",
|
|
68
|
+
"misses": "실패",
|
|
60
69
|
"run_pass_next": "다음: /next",
|
|
61
|
-
"run_fail_next": "수정: Esc
|
|
70
|
+
"run_fail_next": "수정: Esc 또는 e로 편집한 뒤 /run"
|
|
62
71
|
}
|
package/assets/i18n/zh.json
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
"palette_hint": "上下 选择 | Enter 执行 | Esc 取消",
|
|
16
16
|
"hint_command": "Enter 执行 | Esc 取消",
|
|
17
17
|
"hint_list": "上下 移动 | Enter 打开 | Esc 关闭",
|
|
18
|
-
"hint_output": "Esc
|
|
18
|
+
"hint_output": "Esc/e 编辑 | 拖拽选择复制 | / 命令",
|
|
19
|
+
"hint_settings": "上下移动 | Space/Enter 切换 | Esc 关闭",
|
|
19
20
|
"hint_code": "Esc 后输入 / 命令",
|
|
20
21
|
"hint_idle": "/ 命令 | ? 帮助",
|
|
22
|
+
"hint_busy": "处理中 | q 退出",
|
|
23
|
+
"hint_busy_next": "Space 热身 | q 退出",
|
|
21
24
|
"help_title": "帮助",
|
|
22
25
|
"daily_loop": "日常流程",
|
|
23
26
|
"keys": "按键",
|
|
@@ -26,17 +29,19 @@
|
|
|
26
29
|
"cmd_code": "回到代码编辑器",
|
|
27
30
|
"cmd_edit": "回到代码编辑器",
|
|
28
31
|
"cmd_next": "打开下一题",
|
|
29
|
-
"cmd_generate": "
|
|
32
|
+
"cmd_generate": "后台生成新题",
|
|
30
33
|
"cmd_prev": "打开上一题",
|
|
31
34
|
"cmd_list": "打开题目列表",
|
|
32
35
|
"cmd_open": "按编号、id 或 slug 打开题目",
|
|
33
36
|
"cmd_giveup": "显示参考答案",
|
|
34
37
|
"cmd_hint": "请求当前题目的提示",
|
|
35
38
|
"cmd_ai": "向 AI 询问当前题目和代码",
|
|
36
|
-
"cmd_profile": "
|
|
39
|
+
"cmd_profile": "显示用户配置",
|
|
37
40
|
"cmd_difficulty": "设置偏好难度",
|
|
38
41
|
"cmd_topics": "设置偏好主题",
|
|
39
42
|
"cmd_avoid": "设置要避开的主题",
|
|
43
|
+
"cmd_generate_languages": "设置生成题目的答案语言",
|
|
44
|
+
"cmd_generate_ui": "设置生成题目的 UI 语言",
|
|
40
45
|
"cmd_provider": "设置 AI provider",
|
|
41
46
|
"cmd_model": "设置 AI model",
|
|
42
47
|
"cmd_model_auto": "使用 provider 默认模型",
|
|
@@ -57,6 +62,10 @@
|
|
|
57
62
|
"update_check_failed": "无法检查更新。",
|
|
58
63
|
"generating_next": "正在生成下一题",
|
|
59
64
|
"already_busy": "已有任务正在运行。",
|
|
65
|
+
"busy_warmup": "热身:当 * 到达中间时按 Space。",
|
|
66
|
+
"busy_commands_paused": "生成完成前命令会暂停。",
|
|
67
|
+
"hits": "命中",
|
|
68
|
+
"misses": "失误",
|
|
60
69
|
"run_pass_next": "下一步: /next",
|
|
61
|
-
"run_fail_next": "修复: 按 Esc
|
|
70
|
+
"run_fail_next": "修复: 按 Esc 或 e 编辑,然后 /run"
|
|
62
71
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -5,19 +5,24 @@ Practicode is local-first: user data stays under `.practicode/`, `problems/`, an
|
|
|
5
5
|
## Source Layout
|
|
6
6
|
|
|
7
7
|
- `src/core.rs` owns problem data, state loading/saving, judging, and file generation.
|
|
8
|
-
- `src/core/profile.rs` owns
|
|
9
|
-
- `src/tui.rs` owns Ratatui
|
|
8
|
+
- `src/core/profile.rs` owns user-profile defaults and normalization.
|
|
9
|
+
- `src/tui.rs` owns the Ratatui app shell, event routing, and workflow orchestration.
|
|
10
10
|
- `src/tui/commands.rs` owns the command palette catalog.
|
|
11
|
-
- `src/
|
|
11
|
+
- `src/tui/editor.rs` owns the in-terminal code editor state.
|
|
12
|
+
- `src/tui/problem_view.rs` owns problem-statement rendering.
|
|
13
|
+
- `src/tui/settings_panel.rs` owns `/profile` setup-panel rendering and keyboard toggles.
|
|
14
|
+
- `src/ai.rs` owns provider commands, daemon/model checks, and AI prompts for foreground `/next` generation and background `/generate` prefetch.
|
|
12
15
|
- `src/update.rs` owns update checks.
|
|
13
16
|
- `src/text.rs` owns terminal text editing and markdown/plain rendering helpers.
|
|
14
17
|
|
|
15
18
|
## Extension Rules
|
|
16
19
|
|
|
17
|
-
- Add domain logic under the owning module first; keep `tui.rs` as orchestration
|
|
20
|
+
- Add domain logic under the owning module first; keep `tui.rs` as orchestration, not a catch-all.
|
|
18
21
|
- Add user-visible commands in `src/tui/commands.rs`, then route behavior in `PracticodeApp::handle_command`.
|
|
19
|
-
- Add persisted profile settings to `Settings`, normalize them in `normalize_settings`, and cover old-state compatibility with tests.
|
|
22
|
+
- Add persisted user profile settings to `Settings`, normalize them in `normalize_settings`, and cover old-state compatibility with tests.
|
|
20
23
|
- Keep provider-specific behavior in `src/ai.rs`; TUI should ask for status or start tasks, not know provider internals.
|
|
24
|
+
- Keep foreground and background generation flows separate: `/next` may block when no local problem exists, while `/generate` must preserve the current problem and user profile state.
|
|
25
|
+
- Keep output panes copy-friendly. Mouse capture should be enabled for the visible code editor, but disabled while output, hints, answers, lists, or settings panels are shown so terminal drag selection keeps working.
|
|
21
26
|
- Keep local user data backwards-compatible. Missing fields should default cleanly.
|
|
22
27
|
|
|
23
28
|
## Release
|
package/docs/MAINTAINING.md
CHANGED
|
@@ -45,11 +45,16 @@ Verify publication:
|
|
|
45
45
|
```bash
|
|
46
46
|
gh run list --limit 5
|
|
47
47
|
npm view practicode version
|
|
48
|
+
npm view practicode dist.signatures dist.attestations --json
|
|
48
49
|
cargo search practicode --limit 1
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
Do not print or commit tokens. Local `.env` and `.npmrc` are ignored; GitHub Actions uses `NPM_TOKEN` and `CRATES_TOKEN` repository secrets.
|
|
52
53
|
|
|
54
|
+
For npm supply-chain posture, keep `publishConfig.provenance` enabled and keep the release job's `id-token: write` permission. When the npm package's Trusted Publisher setting is configured for this repository and `.github/workflows/release.yml`, remove the long-lived `NPM_TOKEN` dependency from the npm publish steps and disallow token publishing in the npm package settings.
|
|
55
|
+
|
|
56
|
+
Socket.dev indexes the npm package page at <https://socket.dev/npm/package/practicode>. It may lag behind npm immediately after a release; verify npm first with `npm view practicode version`, then re-check Socket after indexing catches up. If Socket flags the npm `postinstall` script, confirm it still only runs the locked Cargo build documented in the README.
|
|
57
|
+
|
|
53
58
|
## Documentation Ownership
|
|
54
59
|
|
|
55
60
|
| File | Audience |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "practicode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Local-first coding-test practice in a Rust terminal UI with optional AI help.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"url": "git+https://github.com/baba9811/practicode.git"
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://github.com/baba9811/practicode#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/baba9811/practicode/issues"
|
|
13
|
+
},
|
|
11
14
|
"bin": {
|
|
12
15
|
"practicode": "bin/practicode.js"
|
|
13
16
|
},
|
|
@@ -27,6 +30,7 @@
|
|
|
27
30
|
"Cargo.toml",
|
|
28
31
|
"LICENSE",
|
|
29
32
|
"README.md",
|
|
33
|
+
"SECURITY.md",
|
|
30
34
|
"THIRD_PARTY_LICENSES.md"
|
|
31
35
|
],
|
|
32
36
|
"keywords": [
|
|
@@ -41,6 +45,7 @@
|
|
|
41
45
|
"node": ">=18"
|
|
42
46
|
},
|
|
43
47
|
"publishConfig": {
|
|
44
|
-
"access": "public"
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
45
50
|
}
|
|
46
51
|
}
|
package/src/ai.rs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use crate::{
|
|
2
2
|
core::{
|
|
3
|
-
AppState, PROBLEM_NOTES_PATH, Problem, Settings,
|
|
4
|
-
render_problem,
|
|
3
|
+
AppState, LANGUAGES, PROBLEM_NOTES_PATH, Problem, Settings, UI_LANGUAGES,
|
|
4
|
+
ensure_submission, normalize_ai_provider, render_problem,
|
|
5
5
|
},
|
|
6
6
|
process::{run_capture, sh_quote, shell_process, unique_temp_path, which},
|
|
7
7
|
};
|
|
@@ -78,6 +78,38 @@ pub fn run_ai_next(root: &Path, state: &AppState, force: bool, request: &str) ->
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
pub fn run_ai_generate(root: &Path, state: &AppState, request: &str) -> String {
|
|
82
|
+
let provider = normalize_ai_provider(&state.settings.ai_provider);
|
|
83
|
+
let command = if state.settings.next_ai_command().trim().is_empty() {
|
|
84
|
+
default_ai_generate_command(root, &state.settings, request)
|
|
85
|
+
} else {
|
|
86
|
+
state.settings.next_ai_command().to_string()
|
|
87
|
+
};
|
|
88
|
+
let mut process = shell_process(&command);
|
|
89
|
+
process
|
|
90
|
+
.current_dir(root)
|
|
91
|
+
.env("PRACTICODE_NEXT_REQUEST", request)
|
|
92
|
+
.env("PRACTICODE_GENERATE_BACKGROUND", "1")
|
|
93
|
+
.env("PRACTICODE_AI_PROVIDER", &provider)
|
|
94
|
+
.env("PRACTICODE_AI_MODEL", &state.settings.ai_model);
|
|
95
|
+
match run_capture(&mut process, "", Duration::from_secs(900)) {
|
|
96
|
+
Ok(run) if run.code == Some(0) => {
|
|
97
|
+
let output = output_text(&run.stdout, &run.stderr);
|
|
98
|
+
format!("{provider} background generation finished\n{output}")
|
|
99
|
+
.trim()
|
|
100
|
+
.to_string()
|
|
101
|
+
}
|
|
102
|
+
Ok(run) => {
|
|
103
|
+
let output = output_text(&run.stdout, &run.stderr);
|
|
104
|
+
format!(
|
|
105
|
+
"{provider} background generation failed ({})\n{output}",
|
|
106
|
+
run.code.unwrap_or(-1)
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
Err(error) => format!("{provider} background generation failed\n{error}"),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
pub fn default_ai_next_command(root: &Path, settings: &Settings, request: &str) -> String {
|
|
82
114
|
match normalize_ai_provider(&settings.ai_provider).as_str() {
|
|
83
115
|
"claude" => default_claude_next_command(root, settings, request),
|
|
@@ -85,6 +117,13 @@ pub fn default_ai_next_command(root: &Path, settings: &Settings, request: &str)
|
|
|
85
117
|
}
|
|
86
118
|
}
|
|
87
119
|
|
|
120
|
+
pub fn default_ai_generate_command(root: &Path, settings: &Settings, request: &str) -> String {
|
|
121
|
+
match normalize_ai_provider(&settings.ai_provider).as_str() {
|
|
122
|
+
"claude" => default_claude_generate_command(root, settings, request),
|
|
123
|
+
_ => default_codex_generate_command(root, settings, request),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
88
127
|
pub fn provider_status(provider: &str) -> String {
|
|
89
128
|
match normalize_ai_provider(provider).as_str() {
|
|
90
129
|
"claude" => {
|
|
@@ -128,7 +167,7 @@ pub fn default_ai_next_prompt(request: &str) -> String {
|
|
|
128
167
|
|
|
129
168
|
pub fn default_ai_next_prompt_with_settings(settings: &Settings, request: &str) -> String {
|
|
130
169
|
format!(
|
|
131
|
-
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}.
|
|
170
|
+
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. User profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; code language: {}; UI language: {}; generated answer languages: {}; generated UI languages: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, problems/INDEX.md, and .practicode/problem-state.json. Do not include the answer in the problem statement.",
|
|
132
171
|
if request.is_empty() {
|
|
133
172
|
"(none)"
|
|
134
173
|
} else {
|
|
@@ -138,7 +177,27 @@ pub fn default_ai_next_prompt_with_settings(settings: &Settings, request: &str)
|
|
|
138
177
|
list_or_none(&settings.topics),
|
|
139
178
|
list_or_none(&settings.avoid_topics),
|
|
140
179
|
settings.language,
|
|
141
|
-
settings.ui_language
|
|
180
|
+
settings.ui_language,
|
|
181
|
+
list_or_all(&settings.generate_languages, LANGUAGES),
|
|
182
|
+
list_or_all(&settings.generate_ui_languages, UI_LANGUAGES)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pub fn default_ai_generate_prompt_with_settings(settings: &Settings, request: &str) -> String {
|
|
187
|
+
format!(
|
|
188
|
+
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem for later use. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. User profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; current code language: {}; current UI language: {}; generated answer languages: {}; generated UI languages: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, and problems/INDEX.md. Preserve .practicode/problem-state.json current_problem, history, solved, and settings; do not switch the current problem. Do not include the answer in the problem statement.",
|
|
189
|
+
if request.is_empty() {
|
|
190
|
+
"(none)"
|
|
191
|
+
} else {
|
|
192
|
+
request
|
|
193
|
+
},
|
|
194
|
+
settings.difficulty,
|
|
195
|
+
list_or_none(&settings.topics),
|
|
196
|
+
list_or_none(&settings.avoid_topics),
|
|
197
|
+
settings.language,
|
|
198
|
+
settings.ui_language,
|
|
199
|
+
list_or_all(&settings.generate_languages, LANGUAGES),
|
|
200
|
+
list_or_all(&settings.generate_ui_languages, UI_LANGUAGES)
|
|
142
201
|
)
|
|
143
202
|
}
|
|
144
203
|
|
|
@@ -319,6 +378,22 @@ fn default_codex_next_command(root: &Path, settings: &Settings, request: &str) -
|
|
|
319
378
|
format!("{start}; {exec}")
|
|
320
379
|
}
|
|
321
380
|
|
|
381
|
+
fn default_codex_generate_command(root: &Path, settings: &Settings, request: &str) -> String {
|
|
382
|
+
let start = "if [ -x \"$HOME/.codex/packages/standalone/current/codex\" ]; then codex app-server daemon start >/dev/null 2>&1 || true; fi";
|
|
383
|
+
let mut exec = format!(
|
|
384
|
+
"codex exec --ephemeral --cd {} --sandbox workspace-write",
|
|
385
|
+
sh_quote(&root.display().to_string())
|
|
386
|
+
);
|
|
387
|
+
if let Some(model) = settings.model_arg() {
|
|
388
|
+
exec.push_str(&format!(" --model {}", sh_quote(model)));
|
|
389
|
+
}
|
|
390
|
+
exec.push(' ');
|
|
391
|
+
exec.push_str(&sh_quote(&default_ai_generate_prompt_with_settings(
|
|
392
|
+
settings, request,
|
|
393
|
+
)));
|
|
394
|
+
format!("{start}; {exec}")
|
|
395
|
+
}
|
|
396
|
+
|
|
322
397
|
fn codex_daemon_path() -> Option<PathBuf> {
|
|
323
398
|
env::var_os("HOME").map(|home| {
|
|
324
399
|
PathBuf::from(home)
|
|
@@ -343,6 +418,22 @@ fn default_claude_next_command(root: &Path, settings: &Settings, request: &str)
|
|
|
343
418
|
)
|
|
344
419
|
}
|
|
345
420
|
|
|
421
|
+
fn default_claude_generate_command(root: &Path, settings: &Settings, request: &str) -> String {
|
|
422
|
+
let mut claude = "claude --permission-mode acceptEdits".to_string();
|
|
423
|
+
if let Some(model) = settings.model_arg() {
|
|
424
|
+
claude.push_str(&format!(" --model {}", sh_quote(model)));
|
|
425
|
+
}
|
|
426
|
+
claude.push_str(" -p ");
|
|
427
|
+
claude.push_str(&sh_quote(&default_ai_generate_prompt_with_settings(
|
|
428
|
+
settings, request,
|
|
429
|
+
)));
|
|
430
|
+
format!(
|
|
431
|
+
"claude daemon status >/dev/null 2>&1 || true; cd {}; {}",
|
|
432
|
+
sh_quote(&root.display().to_string()),
|
|
433
|
+
claude
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
|
|
346
437
|
fn output_text(stdout: &str, stderr: &str) -> String {
|
|
347
438
|
[stdout.trim(), stderr.trim()]
|
|
348
439
|
.into_iter()
|
|
@@ -359,6 +450,14 @@ fn list_or_none(values: &[String]) -> String {
|
|
|
359
450
|
}
|
|
360
451
|
}
|
|
361
452
|
|
|
453
|
+
fn list_or_all(values: &[String], all: &[&str]) -> String {
|
|
454
|
+
if values.is_empty() {
|
|
455
|
+
all.join(", ")
|
|
456
|
+
} else {
|
|
457
|
+
values.join(", ")
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
362
461
|
#[cfg(test)]
|
|
363
462
|
mod tests {
|
|
364
463
|
use super::parse_model_list;
|