@stemtrooper/learningcode 0.2.1 → 0.3.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/LICENSE +1 -29
- package/NOTICE +42 -0
- package/README.md +199 -121
- package/extensions/banner.ts +69 -32
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 TLC (The Learning Curve)
|
|
4
|
-
|
|
5
|
-
learningcode is a distribution wrapper. It does not contain source copied from
|
|
6
|
-
Pi; it depends on @earendil-works/pi-coding-agent at runtime.
|
|
7
|
-
|
|
8
|
-
That dependency is MIT licensed:
|
|
9
|
-
|
|
10
|
-
MIT License
|
|
11
|
-
Copyright (c) Mario Zechner (and contributors)
|
|
12
|
-
|
|
13
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
-
in the Software without restriction, including without limitation the rights
|
|
16
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
-
furnished to do so, subject to the following conditions:
|
|
19
|
-
|
|
20
|
-
The above copyright notice and this permission notice shall be included in all
|
|
21
|
-
copies or substantial portions of the Software.
|
|
22
|
-
|
|
23
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
-
SOFTWARE.
|
|
30
|
-
|
|
31
|
-
The MIT licence terms below govern learningcode itself.
|
|
3
|
+
Copyright (c) 2026 TLC (The Learning Curve)
|
|
32
4
|
|
|
33
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
34
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/NOTICE
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
learningcode
|
|
2
|
+
Copyright (c) 2026 TLC (The Learning Curve)
|
|
3
|
+
|
|
4
|
+
This product includes software developed by third parties.
|
|
5
|
+
|
|
6
|
+
--------------------------------------------------------------------------------
|
|
7
|
+
Pi — https://github.com/earendil-works/pi
|
|
8
|
+
MIT License
|
|
9
|
+
Copyright (c) Mario Zechner (and contributors)
|
|
10
|
+
|
|
11
|
+
learningcode is a distribution wrapper and contains no source copied from Pi. It
|
|
12
|
+
depends on @earendil-works/pi-coding-agent at runtime, and on
|
|
13
|
+
@earendil-works/pi-tui. Both are MIT licensed:
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
|
|
33
|
+
Pi's own NOTICE credits Ratatui (MIT, Florian Dehau and Ratatui Developers).
|
|
34
|
+
|
|
35
|
+
--------------------------------------------------------------------------------
|
|
36
|
+
Trademarks
|
|
37
|
+
|
|
38
|
+
"The Learning Curve", TLC and Spark are trademarks of The Learning Curve.
|
|
39
|
+
|
|
40
|
+
learningcode is not affiliated with, endorsed by, or connected to Pi, OpenCode,
|
|
41
|
+
Anomaly, or any model provider reachable through this client. Model names
|
|
42
|
+
belonging to their respective owners are used only to identify what is available.
|
package/README.md
CHANGED
|
@@ -1,55 +1,140 @@
|
|
|
1
1
|
# learningcode
|
|
2
2
|
|
|
3
|
-
A terminal coding agent for
|
|
4
|
-
inference
|
|
5
|
-
and weekly quota.
|
|
3
|
+
A terminal coding agent for **The Learning Curve** students, running on TLC's own
|
|
4
|
+
inference through **TLC-Spark**.
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
`learningcode` is a purpose-built client for the TLC-Spark token plan. It is not a
|
|
7
|
+
general-purpose AI coding tool: the model, the quota and the seat limits all belong
|
|
8
|
+
to TLC, and every token you spend comes out of your course allocation.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## You will need a TLC-Spark token
|
|
13
|
+
|
|
14
|
+
`learningcode` cannot reach a model on its own. It connects to the TLC-Spark
|
|
15
|
+
gateway, which checks who you are and whether you are allowed to use it right now.
|
|
16
|
+
|
|
17
|
+
**Ask your teacher for a Spark API token.** In the Spark bench, go to
|
|
18
|
+
**Issue / rotate token**. The token is shown **once** and cannot be retrieved
|
|
19
|
+
afterwards, so copy it immediately. It looks like `spark_live_…`.
|
|
20
|
+
|
|
21
|
+
Without a token, `learningcode` will not start a conversation. That is by design:
|
|
22
|
+
there is no shared school key and no fallback to somebody else's account.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
| | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| **Node.js** | **22.19.0 or newer** (24.x LTS recommended) |
|
|
31
|
+
| **Disk** | about 120 MB |
|
|
32
|
+
| **Network** | must reach `spark.learning.com.my` |
|
|
33
|
+
| **Token** | your personal `spark_live_…` from the Spark bench |
|
|
34
|
+
| **Terminal** | 51+ columns for the banner; 99+ for the large one |
|
|
35
|
+
|
|
36
|
+
Node 22.19 is the floor because the underlying agent runtime requires it. If you
|
|
37
|
+
install Node and `npm i` only *warns*, check `node --version` — an older Node
|
|
38
|
+
produces a confusing error later rather than at install time.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
### Windows (PowerShell)
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
node --version
|
|
48
|
+
npm install -g @stemtrooper/learningcode
|
|
49
|
+
learningcode --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If PowerShell refuses to run the command because script execution is disabled:
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### macOS / Linux
|
|
8
59
|
|
|
9
60
|
```bash
|
|
10
|
-
|
|
11
|
-
learningcode
|
|
61
|
+
node --version
|
|
62
|
+
npm install -g @stemtrooper/learningcode
|
|
63
|
+
learningcode --version
|
|
12
64
|
```
|
|
13
65
|
|
|
14
|
-
|
|
15
|
-
caches it with `0600`, writes the TLC-Spark provider config, and hands off to
|
|
16
|
-
Pi with the model pinned.
|
|
66
|
+
### Termux (Android)
|
|
17
67
|
|
|
68
|
+
No root and no `proot-distro` needed.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pkg install nodejs-lts npm
|
|
72
|
+
node --version
|
|
73
|
+
npm install -g @stemtrooper/learningcode
|
|
74
|
+
learningcode --version
|
|
18
75
|
```
|
|
19
|
-
|
|
20
|
-
|
|
76
|
+
|
|
77
|
+
Install `npm` explicitly: Termux stopped bundling it with Node at 25.3.0.
|
|
78
|
+
Prefer `nodejs-lts` (24.x) over `nodejs` (26.x).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## First run
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd ~/your-project
|
|
86
|
+
learningcode
|
|
21
87
|
```
|
|
22
88
|
|
|
23
|
-
|
|
89
|
+
It asks for your Spark token, stores it with owner-only permissions in
|
|
90
|
+
`~/.learningcode/agent/spark-token`, and writes its configuration. The token is
|
|
91
|
+
only ever read from your own machine.
|
|
24
92
|
|
|
25
|
-
|
|
26
|
-
|
|
93
|
+
You now have an agent that can read files, run shell commands, and edit code in
|
|
94
|
+
the directory you started it from. **It runs those commands on your machine, as
|
|
95
|
+
you.** Nothing runs on a microcontroller, and Spark never executes anything on
|
|
96
|
+
your behalf.
|
|
27
97
|
|
|
28
|
-
|
|
29
|
-
|---|---|
|
|
30
|
-
| `/quota` | Tokens used against today's limit, and whether AI is enabled for your account |
|
|
31
|
-
| `/seats` | Live seat queue, so a student can see why a turn is waiting |
|
|
98
|
+
---
|
|
32
99
|
|
|
33
|
-
|
|
34
|
-
teacher has switched AI off for your account.
|
|
100
|
+
## Using it
|
|
35
101
|
|
|
36
|
-
|
|
102
|
+
Type a request in plain language.
|
|
37
103
|
|
|
38
|
-
|
|
104
|
+
```
|
|
105
|
+
write a function that reads notes.txt and returns the average resistance
|
|
106
|
+
why does my ESP32 reset when I power the servo
|
|
107
|
+
explain this error: "Brown detector failed"
|
|
108
|
+
```
|
|
39
109
|
|
|
40
|
-
|
|
41
|
-
starts Pi with `--no-skills`, since skills are pure prompt-token spend.
|
|
42
|
-
- **One active generation per student.** Subagents and parallel tool calls would
|
|
43
|
-
only earn `429`s. Pi is serial by default and this wrapper does not turn that off.
|
|
44
|
-
- **250K tokens per week.** Frugality is the product, so the budget is visible
|
|
45
|
-
instead of mysterious.
|
|
110
|
+
Useful commands, typed inside the session:
|
|
46
111
|
|
|
47
|
-
|
|
48
|
-
|
|
112
|
+
| Command | What it does |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `/help` | list everything available |
|
|
115
|
+
| `/quota` | your token spend today |
|
|
116
|
+
| `/seats` | how many seats are free |
|
|
117
|
+
| `/model` | switch model |
|
|
118
|
+
| `/hotkeys` | keyboard shortcuts |
|
|
49
119
|
|
|
50
|
-
|
|
120
|
+
`/quota` and `/seats` talk to Spark directly. They are the two commands a stock
|
|
121
|
+
AI coding tool does not have, because those endpoints are not part of any public
|
|
122
|
+
API.
|
|
51
123
|
|
|
52
|
-
|
|
124
|
+
One-off, non-interactive:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
learningcode -p "explain main.py"
|
|
128
|
+
learningcode -c # continue your last session
|
|
129
|
+
learningcode --mode json # machine-readable output
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## What it looks like
|
|
135
|
+
|
|
136
|
+
The TLC brand theme and banner ship with the package, so there is nothing to
|
|
137
|
+
configure.
|
|
53
138
|
|
|
54
139
|
```
|
|
55
140
|
██╗ ███████╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
@@ -58,128 +143,121 @@ The LEARNINGCODE block banner replaces Pi's header at startup:
|
|
|
58
143
|
██║ ██╔══╝ ██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║██║ ██║ ██║██║ ██║██╔══╝
|
|
59
144
|
███████╗███████╗██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗
|
|
60
145
|
╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
|
61
|
-
═════════════════════════════════════════════════════════════════════════════════════════════════
|
|
62
146
|
The Learning Curve · Sarawak
|
|
63
|
-
/help commands · /quota today's spend · /hotkeys keys
|
|
64
147
|
```
|
|
65
148
|
|
|
66
|
-
|
|
67
|
-
|
|
149
|
+
On a narrower terminal it steps down to a condensed banner, then to the wordmark,
|
|
150
|
+
rather than drawing art that would be clipped.
|
|
68
151
|
|
|
69
|
-
|
|
152
|
+
A footer shows your remaining quota whenever you are connected to Spark:
|
|
70
153
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
the art.
|
|
154
|
+
```
|
|
155
|
+
█████░░░░░ 50% 125k / 250k today
|
|
156
|
+
```
|
|
75
157
|
|
|
76
|
-
|
|
158
|
+
---
|
|
77
159
|
|
|
78
|
-
##
|
|
160
|
+
## Troubleshooting
|
|
79
161
|
|
|
80
|
-
|
|
81
|
-
|
|
162
|
+
**`Node 22.19.0 or newer is required`**
|
|
163
|
+
Upgrade Node, then reinstall: `npm i -g @stemtrooper/learningcode`.
|
|
82
164
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
165
|
+
**`No Spark API token configured`**
|
|
166
|
+
You do not have a token yet, or it is not cached. Run `learningcode --login` to
|
|
167
|
+
enter a new one. If you never had one, ask your teacher.
|
|
168
|
+
|
|
169
|
+
**`Token rejected (401)`**
|
|
170
|
+
The token is wrong, or it was rotated and the old one no longer works.
|
|
171
|
+
`learningcode --login`.
|
|
87
172
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
moves the key into Pi's `auth.json` under `opencode-go` alone, which leaves Zen
|
|
92
|
-
credential-less so it never registers. Set `LEARNINGCODE_ALLOW_ZEN=1` to opt in.
|
|
173
|
+
**`AI off — ask your teacher` / `AI disabled for your account`**
|
|
174
|
+
Spark has AI switched off for your account — usually a timetable window or a
|
|
175
|
+
policy setting. This is not something you can fix.
|
|
93
176
|
|
|
94
|
-
|
|
177
|
+
**`You already have a generation running`**
|
|
178
|
+
Spark allows one active generation per student. Stop the running one first.
|
|
95
179
|
|
|
96
|
-
|
|
97
|
-
|
|
180
|
+
**`Every seat is taken` / queue position**
|
|
181
|
+
All seats are busy. `learningcode` prints your position; try again shortly.
|
|
98
182
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
| `tlc-dark` | `#29c8f2` | `TLC_BLACKBGND.png` |
|
|
102
|
-
| `tlc-light` | `#0dacd6` | `TLC_WHITEBGND.png` |
|
|
183
|
+
**The banner looks like plain text**
|
|
184
|
+
Your terminal is narrower than 51 columns. Widen it.
|
|
103
185
|
|
|
104
|
-
|
|
105
|
-
cyans because the darker one has to hold contrast on a white field, which maps
|
|
106
|
-
exactly onto Pi's light/dark split.
|
|
186
|
+
---
|
|
107
187
|
|
|
108
|
-
|
|
109
|
-
|
|
188
|
+
## Configuration
|
|
189
|
+
|
|
190
|
+
Everything lives in `~/.learningcode/agent/`. Nothing is uploaded anywhere except
|
|
191
|
+
to Spark, and no telemetry is sent.
|
|
192
|
+
|
|
193
|
+
| Variable | Purpose |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `LEARNINGCODE_DIR` | config location (default `~/.learningcode/agent`) |
|
|
196
|
+
| `LEARNINGCODE_SPARK_BASE_URL` | point at a different Spark deployment |
|
|
197
|
+
| `LEARNINGCODE_TOKEN` | supply a token without using the cached file |
|
|
198
|
+
| `LEARNINGCODE_THEME` | `tlc-dark` (default), `tlc-light`, or any Pi theme |
|
|
199
|
+
| `LEARNINGCODE_PI_FLAGS` | extra flags passed to the agent on every launch |
|
|
110
200
|
|
|
111
201
|
```bash
|
|
112
|
-
learningcode --
|
|
202
|
+
learningcode --show-config # show resolved paths and settings
|
|
203
|
+
learningcode --login # enter a new token
|
|
204
|
+
learningcode --list-models # every reachable model
|
|
205
|
+
learningcode --theme dark # use the upstream theme instead of TLC's
|
|
113
206
|
```
|
|
114
207
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
ramp. Semantic colours (red, green, yellow) are left alone, because those mean
|
|
118
|
-
error, success and warning rather than anything about the brand. The thinking ramp
|
|
119
|
-
runs grey to cyan and keeps red at the top, where it still means "this is getting
|
|
120
|
-
expensive".
|
|
121
|
-
|
|
122
|
-
The banner does **not** follow the theme. It uses the brand cyan directly, the way
|
|
123
|
-
Pi's own logo does, because a wordmark that changes colour with whatever theme is
|
|
124
|
-
active stops being a wordmark. It picks the right one of the two cyans from the
|
|
125
|
-
terminal's colour mode.
|
|
208
|
+
**Your edits are kept.** If you change the theme or the model configuration,
|
|
209
|
+
upgrading `learningcode` will not overwrite your copy.
|
|
126
210
|
|
|
127
|
-
|
|
211
|
+
---
|
|
128
212
|
|
|
129
|
-
|
|
213
|
+
## For educators
|
|
130
214
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
```
|
|
215
|
+
<details>
|
|
216
|
+
<summary>Running a local Spark, and other model providers</summary>
|
|
134
217
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
student is waiting on. When AI is switched off for an account the footer says so
|
|
139
|
-
instead of showing a bar.
|
|
218
|
+
**Local bench.** `learningcode --base-url http://localhost:3000/v1` points the
|
|
219
|
+
client at a Spark running on your own machine, for demos without spending course
|
|
220
|
+
quota.
|
|
140
221
|
|
|
141
|
-
|
|
142
|
-
|
|
222
|
+
**Other providers.** Any `--model` other than `tlc-spark/…` skips the Spark token
|
|
223
|
+
and health check entirely:
|
|
143
224
|
|
|
144
|
-
|
|
225
|
+
```bash
|
|
226
|
+
export OPENCODE_API_KEY=...
|
|
227
|
+
learningcode --model opencode-go/glm-5.3-flash
|
|
228
|
+
```
|
|
145
229
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
230
|
+
This uses **your own** provider account and **your own** credits, with no Spark
|
|
231
|
+
quota, seat limit or audit trail. `learningcode` deliberately excludes OpenCode
|
|
232
|
+
Zen, which bills per token, so a stale entry cannot silently spend money. It is
|
|
233
|
+
still not something to hand to students.
|
|
149
234
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
| `maxTokensField` | `"max_tokens"` | `v1.ts` reads `body.max_tokens` only |
|
|
154
|
-
| `supportsReasoningEffort` | `false` | not forwarded to the runtime |
|
|
155
|
-
| `supportsStore` | `false` | not implemented |
|
|
235
|
+
**Seat awareness.** Spark enforces one active generation per student, so the
|
|
236
|
+
client runs strictly serially with subagents off. Expect a 429 rather than
|
|
237
|
+
parallel fan-out.
|
|
156
238
|
|
|
157
|
-
|
|
239
|
+
</details>
|
|
158
240
|
|
|
159
|
-
|
|
160
|
-
|---|---|
|
|
161
|
-
| `LEARNINGCODE_DIR` | agent directory (default `~/.learningcode/agent`) |
|
|
162
|
-
| `LEARNINGCODE_SPARK_BASE_URL` | point at another Spark deployment |
|
|
163
|
-
| `LEARNINGCODE_TOKEN` | Spark token, skips the cached file |
|
|
164
|
-
| `LEARNINGCODE_PI_FLAGS` | extra flags appended to every launch |
|
|
241
|
+
---
|
|
165
242
|
|
|
166
|
-
|
|
167
|
-
learningcode --show-config # resolved paths, model, token prefix
|
|
168
|
-
learningcode --base-url http://localhost:3000/v1 # local bench
|
|
169
|
-
learningcode --login # re-enter a rotated token
|
|
170
|
-
learningcode -c # continue last session
|
|
171
|
-
learningcode -p "explain main.py"
|
|
172
|
-
learningcode --mode json # machine-readable event stream
|
|
173
|
-
```
|
|
243
|
+
## How it works
|
|
174
244
|
|
|
175
|
-
|
|
245
|
+
`learningcode` is a thin wrapper around [Pi](https://github.com/earendil-works/pi),
|
|
246
|
+
an open-source coding agent by Mario Zechner. It adds the TLC-Spark provider
|
|
247
|
+
configuration, the TLC theme and banner, and the quota and seat commands.
|
|
176
248
|
|
|
177
|
-
|
|
249
|
+
The underlying model is **Qwen3.8-27B**, served through TLC-Spark. Every turn is a
|
|
250
|
+
separate request, takes a seat, and spends quota — an agent session that reads ten
|
|
251
|
+
files costs roughly eleven turns. That is why the footer exists.
|
|
178
252
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
them mid-term. Bump it deliberately, after testing against a live pod.
|
|
253
|
+
Pi is pinned to an exact version on purpose: the runtime ships breaking changes
|
|
254
|
+
often, and a student's install must not change underneath them mid-term.
|
|
182
255
|
|
|
183
256
|
## Licence
|
|
184
257
|
|
|
185
|
-
MIT.
|
|
258
|
+
MIT. `learningcode` is distributed under the MIT licence and depends on Pi, which
|
|
259
|
+
is also MIT. See [LICENSE](LICENSE).
|
|
260
|
+
|
|
261
|
+
Not affiliated with, endorsed by, or connected to Pi, OpenCode, Anomaly, or any of
|
|
262
|
+
the model providers reachable through this client. "The Learning Curve", TLC and
|
|
263
|
+
Spark are trademarks of The Learning Curve.
|
package/extensions/banner.ts
CHANGED
|
@@ -5,22 +5,25 @@ import { foregroundAnsi, rgbColor } from "@earendil-works/pi-tui";
|
|
|
5
5
|
* LEARNINGCODE startup banner.
|
|
6
6
|
*
|
|
7
7
|
* Pi lets an extension replace the whole header, which is the supported way to
|
|
8
|
-
* brand a fork without touching Pi's internals.
|
|
9
|
-
* logo plus key hints; we trade that for the TLC banner and a line of slash
|
|
10
|
-
* commands, so nothing is claimed about keybindings we cannot read.
|
|
8
|
+
* brand a fork without touching Pi's internals.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Three tiers, because the full face does not fit everywhere. A phone will never
|
|
11
|
+
* have 97 columns, and clipping box characters looks like a rendering bug rather
|
|
12
|
+
* than a design, so the banner steps down instead:
|
|
15
13
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* >= 99 columns figlet "ANSI Shadow", 97 wide, 6 rows
|
|
15
|
+
* >= 51 columns condensed 4 row face, 47 wide
|
|
16
|
+
* below that the wordmark
|
|
17
|
+
*
|
|
18
|
+
* The full art is stored verbatim rather than rebuilt from a glyph map. That face
|
|
19
|
+
* only aligns because every row keeps its exact offset, the top row flush left
|
|
20
|
+
* and the rest flush too; a one-space drift makes the whole banner look broken.
|
|
21
|
+
* A test asserts those offsets.
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
* Only the parts of Pi's Theme this file touches, to keep the runtime import
|
|
23
|
-
*
|
|
25
|
+
* Only the parts of Pi's Theme this file touches, to keep the runtime import down
|
|
26
|
+
* to the two colour helpers actually needed.
|
|
24
27
|
*/
|
|
25
28
|
type ThemeLike = {
|
|
26
29
|
fg(token: string, text: string): string;
|
|
@@ -45,14 +48,20 @@ type HeaderComponent = { render(width: number): string[] };
|
|
|
45
48
|
*
|
|
46
49
|
* Note this must not go through `theme.fg()`. That resolves theme *tokens*, and
|
|
47
50
|
* `Theme.tokenAnsi` throws `Unknown theme color: #29c8f2` for anything that is
|
|
48
|
-
* not one, so a raw hex there crashes the header at render time.
|
|
49
|
-
* takes a colour value directly, and unlike a hand-rolled escape
|
|
50
|
-
* degrades to 256 colour on terminals without truecolour support.
|
|
51
|
+
* not one, so a raw hex there crashes the header at render time.
|
|
52
|
+
* `foregroundAnsi` takes a colour value directly, and unlike a hand-rolled escape
|
|
53
|
+
* it still degrades to 256 colour on terminals without truecolour support.
|
|
51
54
|
*/
|
|
52
55
|
const CYAN_DARK_BG = rgbColor(0x29, 0xc8, 0xf2);
|
|
53
56
|
const CYAN_LIGHT_BG = rgbColor(0x0d, 0xac, 0xd6);
|
|
54
57
|
const RESET = "\x1b[0m";
|
|
55
58
|
|
|
59
|
+
const WORD = "LEARNINGCODE";
|
|
60
|
+
const PAD = " ";
|
|
61
|
+
const TAGLINE = "The Learning Curve · Sarawak";
|
|
62
|
+
const HINTS = "/help commands · /quota today's spend · /hotkeys keys";
|
|
63
|
+
|
|
64
|
+
/** figlet "ANSI Shadow", verbatim. 97 columns, 6 rows. */
|
|
56
65
|
const ART: readonly string[] = [
|
|
57
66
|
"██╗ ███████╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗",
|
|
58
67
|
"██║ ██╔════╝██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
|
|
@@ -62,26 +71,47 @@ const ART: readonly string[] = [
|
|
|
62
71
|
"╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
|
|
63
72
|
];
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Condensed fallback for narrow terminals. Three columns per glyph, which is
|
|
76
|
+
* the most a twelve letter word can be compressed before the letterforms stop
|
|
77
|
+
* reading: an N needs four columns to show its diagonal, and dropping it to
|
|
78
|
+
* three turns the letter into a filled block.
|
|
79
|
+
*/
|
|
80
|
+
const CONDENSED_GLYPHS: Record<string, readonly string[]> = {
|
|
81
|
+
L: ["█ ", "█ ", "█ ", "███"],
|
|
82
|
+
E: ["███", "█ ", "███", "███"],
|
|
83
|
+
A: [" █ ", "█ █", "███", "█ █"],
|
|
84
|
+
R: ["███", "█ █", "███", "█ █"],
|
|
85
|
+
N: ["█ █", "███", "█ █", "█ █"],
|
|
86
|
+
I: ["███", " █ ", " █ ", "███"],
|
|
87
|
+
G: ["███", "█ ", "█ █", "███"],
|
|
88
|
+
C: ["███", "█ ", "█ ", "███"],
|
|
89
|
+
O: ["███", "█ █", "█ █", "███"],
|
|
90
|
+
D: ["███", "█ █", "█ █", "███"],
|
|
91
|
+
};
|
|
67
92
|
|
|
68
|
-
const
|
|
93
|
+
const condensedArt = (): string[] => {
|
|
94
|
+
const rows = CONDENSED_GLYPHS[WORD[0]].length;
|
|
95
|
+
return Array.from({ length: rows }, (_, row) =>
|
|
96
|
+
[...WORD].map((letter) => CONDENSED_GLYPHS[letter][row]).join(" ").trimEnd(),
|
|
97
|
+
);
|
|
98
|
+
};
|
|
69
99
|
|
|
70
100
|
/** A copy, so a caller mutating the result cannot corrupt later renders. */
|
|
71
101
|
export const bannerArt = (): string[] => [...ART];
|
|
72
102
|
|
|
103
|
+
export const condensedArt_ = condensedArt;
|
|
104
|
+
|
|
73
105
|
export const bannerWidth = (): number => Math.max(...ART.map((line) => line.length));
|
|
106
|
+
export const condensedWidth = (): number => Math.max(...condensedArt().map((line) => line.length));
|
|
74
107
|
|
|
75
|
-
/**
|
|
76
|
-
* The art is 97 columns, which does not fit the classic 80 column terminal.
|
|
77
|
-
* Below the art plus its indent we show a wordmark instead, because clipped box
|
|
78
|
-
* characters look like a rendering bug rather than a design.
|
|
79
|
-
*/
|
|
108
|
+
/** Art plus its indent. Derived, never guessed. */
|
|
80
109
|
const MIN_FULL_WIDTH = bannerWidth() + PAD.length;
|
|
110
|
+
const MIN_CONDENSED_WIDTH = condensedWidth() + PAD.length;
|
|
81
111
|
|
|
82
112
|
export function createBanner(theme: ThemeLike): HeaderComponent {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
113
|
+
const full = bannerArt();
|
|
114
|
+
const small = condensedArt();
|
|
85
115
|
|
|
86
116
|
// Two different things, easy to swap by accident:
|
|
87
117
|
// appearance light or dark, decides which brand cyan is readable
|
|
@@ -95,16 +125,23 @@ export function createBanner(theme: ThemeLike): HeaderComponent {
|
|
|
95
125
|
|
|
96
126
|
return {
|
|
97
127
|
render(width: number): string[] {
|
|
98
|
-
if (width
|
|
99
|
-
|
|
128
|
+
if (width >= MIN_FULL_WIDTH) {
|
|
129
|
+
const lines = ["", ...full.map((line) => ink(PAD + line))];
|
|
130
|
+
lines.push(theme.fg("border", PAD + "═".repeat(bannerWidth())));
|
|
131
|
+
lines.push(theme.fg("muted", PAD + TAGLINE));
|
|
132
|
+
lines.push(theme.fg("dim", PAD + HINTS));
|
|
133
|
+
lines.push("");
|
|
134
|
+
return lines;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (width >= MIN_CONDENSED_WIDTH) {
|
|
138
|
+
const lines = ["", ...small.map((line) => ink(PAD + line))];
|
|
139
|
+
lines.push(theme.fg("muted", PAD + TAGLINE));
|
|
140
|
+
lines.push("");
|
|
141
|
+
return lines;
|
|
100
142
|
}
|
|
101
143
|
|
|
102
|
-
|
|
103
|
-
lines.push(theme.fg("border", PAD + "═".repeat(artWidth)));
|
|
104
|
-
lines.push(theme.fg("muted", PAD + TAGLINE));
|
|
105
|
-
lines.push(theme.fg("dim", PAD + HINTS));
|
|
106
|
-
lines.push("");
|
|
107
|
-
return lines;
|
|
144
|
+
return ["", ink(PAD + "learningcode"), theme.fg("muted", PAD + TAGLINE)];
|
|
108
145
|
},
|
|
109
146
|
};
|
|
110
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stemtrooper/learningcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "TLC Spark coding agent for students: Pi wired to the Spark OpenAI-compatible endpoint with per-student tokens, quota and seat-queue awareness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"lib",
|
|
14
14
|
"extensions",
|
|
15
15
|
"README.md",
|
|
16
|
-
"LICENSE"
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"NOTICE"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
|
19
20
|
"start": "node ./bin/learningcode.mjs",
|