@tiens.nguyen/gu-cli 1.0.881 → 1.0.883

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tien Nguyen Song
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,157 +1,222 @@
1
- # @tiens.nguyen/gu-cli
2
-
3
- **gu** is a terminal coding agent for the folder you are in, plus the local worker that runs
4
- agent / OCR / PDF / embedding jobs on your machine (Ollama, MLX or any OpenAI-compatible server).
1
+ <h1 align="center">gu — the coding agent in your terminal</h1>
2
+
3
+ <p align="center">
4
+ <strong>It reads your repo, makes the change, and proves it works.</strong><br>
5
+ Your models — Ollama, MLX or any OpenAI-compatible provider. Your files stay in your folder.<br>
6
+ <strong>Free for 30 days. No account, no sign-in, no card.</strong>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@tiens.nguyen/gu-cli"><img alt="npm" src="https://img.shields.io/npm/v/@tiens.nguyen/gu-cli?color=6366f1&label=npm"></a>
11
+ <a href="https://www.npmjs.com/package/@tiens.nguyen/gu-cli"><img alt="downloads" src="https://img.shields.io/npm/dm/@tiens.nguyen/gu-cli?color=6366f1"></a>
12
+ <img alt="node" src="https://img.shields.io/node/v/@tiens.nguyen/gu-cli?color=6366f1">
13
+ <img alt="platforms" src="https://img.shields.io/badge/macOS%20%C2%B7%20Linux%20%C2%B7%20Windows-supported-6366f1">
14
+ <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@tiens.nguyen/gu-cli?color=6366f1"></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://gucli.com">Website</a> ·
19
+ <a href="https://gucli.com/docs">Docs</a> ·
20
+ <a href="https://gucli.com/pricing">Pricing</a> ·
21
+ <a href="https://gucli.com/support">Support</a>
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="https://gucli.com/videos/gu-design-web.mp4">
26
+ <img alt="gu building a web page from one request (click to watch)" src="https://gucli.com/videos/gu-design-web.jpg" width="760">
27
+ </a>
28
+ <br><sub>One request: gu plans, writes the files and checks the result. Click to watch.</sub>
29
+ </p>
30
+
31
+ ## Install
5
32
 
6
33
  ```sh
7
34
  npm install -g @tiens.nguyen/gu-cli
8
- gu # open the REPL in the current folder
35
+ cd your-project
36
+ gu
9
37
  ```
10
38
 
11
- The package installs three commands: `gu` (the terminal REPL), `gu-cli` and `gu-local-worker`
12
- (the same worker CLI). Requires Node.js 18 or newer. The install tries to put npm's global bin
13
- folder on your PATH, but npm 11 skips install scripts it was not told to allow (it prints
14
- "install scripts not yet covered by allowScripts"). If `gu` is "command not found" afterwards, run
15
- `npx @tiens.nguyen/gu-cli fix-path` (works even when `gu-cli` itself is not found). `gu-cli fix-path`
16
- also finds what an old install left behind - broken `gu` links, a second gu from another npm, a
17
- shell-profile PATH line pinned to a versioned Homebrew folder - and asks before fixing each
18
- (`--check` only reports). `gu support` sends the support team a request with gu's logs attached.
19
- On Windows, PowerShell's built-in `gu` alias (Get-Unique) shadows this CLI — run
20
- `gu-cli fix-powershell` to drop it.
39
+ **Free to start, no account needed.** The first run creates a free 30-day token for this
40
+ computer automatically. Press Enter to use it. It also asks how this machine runs and which model
41
+ provider to use. Then type what you want done.
42
+
43
+ > **`gu: command not found`?** Run `npx @tiens.nguyen/gu-cli fix-path`. See [Troubleshooting](#troubleshooting).
44
+
45
+ ## What it does
46
+
47
+ - **Writes the code, in your terminal.** Ask in plain words; gu reads the repo, edits the files,
48
+ runs the build and the tests, and tells you what it changed.
49
+ - **Your models.** Run [Ollama](https://ollama.com) or MLX on your own machine, or bring any
50
+ OpenAI-compatible provider. Switch at any time with `/model`.
51
+ - **It stays in the repo.** Every edit is a file you can read, diff and revert, and `/undo` puts
52
+ back what a stopped turn changed. No hidden workspace.
53
+ - **Knows your codebase.** Local RAG indexes the folder so the model finds the right files.
54
+ - **Extensible.** [MCP](https://modelcontextprotocol.io) servers, skills (`SKILL.md`), and hooks.
55
+ Already set up for Claude Code? `/switch-skill claude` reuses its `.claude` folder.
56
+ - **Tests in a real browser.** The `browser` tool drives Chrome to check the web pages it builds.
57
+ - **Private by default.** Logs hold no prompts, code or keys unless you run `gu --log`.
21
58
 
22
59
  ## Quick start
23
60
 
24
- 1. `gu login` — browser sign-in; writes `~/.gonext/worker.env`.
25
- (Client Expert machines can instead paste a token from the Account page with `gu-cli token <token>`.)
26
- 2. `gu doctor` — set up and check the models on this machine
27
- (`gu doctor --client` installs only the agent dependencies when the models live on another machine).
28
- 3. `gu` in a project folder, then type a request. Conversation history is saved per folder in
29
- `~/.gonext/sessions` and resumed next time; `/new` starts a fresh one and archives the old one.
61
+ 1. **Start gu.** Run `gu` in a project folder. Your free 30-day token is created on the spot:
62
+ no sign-up, no browser, no card.
63
+ 2. **Check your models.** `gu doctor` sets up and tests the models on this machine.
64
+ (`gu doctor --client` installs only the agent's dependencies, when the models run elsewhere.)
65
+ 3. **Ask.** Type what you want done. Conversations are saved per folder and resumed next time;
66
+ `/new` starts a fresh one.
30
67
 
31
- A first install asks how this machine runs (hosted client / MLX host / private). Pass
32
- `--license <pro token>` to choose a pro license up front; without it a first install is private,
33
- meaning the whole stack runs on this machine.
68
+ ## Models
34
69
 
35
- ## The `gu` REPL
70
+ gu uses up to three models, each with its own command:
36
71
 
37
- ```
38
- gu [-v|--verbose] [--log] [--name <text>] [--license <pro token>]
39
- ```
72
+ | Command | Model | Saved |
73
+ | --- | --- | --- |
74
+ | `/model` | **coding**: writes the code | this session (`/quick-models` keeps 2 to flip with ← / →) |
75
+ | `/agent-model` | **agent**: talks to you and plans | this machine |
76
+ | `/embed-model` | **embedding**: RAG (`nomic-embed-text`, `qwen3-embedding`), local or remote | this machine; re-indexes what was indexed |
40
77
 
41
- - `-v, --verbose` shows RAG index-file activity in the bottom bar.
42
- - `--name <text>` draws `<text>` instead of the gu logo in the startup banner (this run only). Letters, digits, space, `-` and `_` become block art; anything else, or a name over 12 characters, is shown as plain text.
43
- - `--log` logs this run in detail to `~/.gonext/worker.log`. Without it only starts, stops and errors are logged.
44
- - Ctrl-C stops following a turn.
78
+ Run `/doctor` any time to check that each one answers.
45
79
 
46
- ### Three models, three commands
80
+ ## Using gu
47
81
 
48
- | Command | Model it switches |
82
+ ```
83
+ gu [-v|--verbose] [--log] [--color] [--name <text>] [--license <pro token>]
84
+ ```
85
+
86
+ | Flag | Effect |
49
87
  | --- | --- |
50
- | `/model` | the CODING model (the one that writes code), for this session |
51
- | `/agent-model` | the AGENT model (the one that talks) — saved on this machine |
52
- | `/embed-model` | the RAG EMBEDDING model (nomic-embed-text / qwen3-embedding) and its server; re-indexes what is already indexed; supports a remote URL + API key |
88
+ | `-v`, `--verbose` | show RAG index activity in the bottom bar |
89
+ | `--log` | log this run in detail to `~/.gonext/worker.log` (default: starts, stops and errors only) |
90
+ | `--color` | colour the live thought and the thinking spinner |
91
+ | `--name <text>` | show `<text>` instead of the gu logo in the banner (this run only) |
92
+ | `--license <token>` | choose a Pro licence on first install |
53
93
 
54
- `/agent-model` saves the model's server URL (`agentModelUrl`) together with its name, so picking a
55
- model from another provider (for example OpenAI) can no longer leave chat pointed at the old
56
- server. After picking, `gu doctor` shows the URL and model that are actually in use.
94
+ Ctrl-C stops the running turn. Type `/` at the prompt to see every command; Tab completes.
57
95
 
58
- ### Slash commands
96
+ <details>
97
+ <summary><strong>All slash commands</strong></summary>
59
98
 
60
99
  | Command | What it does |
61
100
  | --- | --- |
62
- | `/model`, `/agent-model`, `/embed-model` | switch models (see above) |
63
- | `/context-window [tokens\|auto]` | tell gu how big the coding model's context is (hosted models cannot be probed) |
64
- | `/doctor` | check every configured model answers, especially the embedder |
65
- | `/browser [allow\|forget\|install\|cleanup]` | the agent's web browser (UI tests): it asks before first use, then works in your Chrome through the gu extension or, with nothing installed, in gu's own Chrome window; `allow`/`forget` stop or restart the question for this folder; guide: [browser-extension/README.md](browser-extension/README.md) |
66
- | `/tokens` | break down the last prompt sent to the coding model — how much of it was RAG |
67
- | `/prices [update]` | price and cache tables, and what the last turn and this session cost |
68
- | `/rag` | where this folder's RAG knowledge base lives: local, cloud, or both |
69
- | `/s3 [test]` | set up your own S3 bucket (RAG index, generated PDFs/downloads) and check the AWS keys |
70
- | `/proxy [show\|set\|on\|off\|clear\|test]` | route model calls through an HTTP proxy (local calls always go direct) |
71
- | `/test-auto` | toggle auto-test (verify and fix until it passes) for this folder |
101
+ | `/model`, `/quick-models`, `/agent-model`, `/embed-model` | switch models (see [Models](#models)) |
102
+ | `/context-window [tokens\|auto]` | tell gu how big the coding model's context is (hosted models can't be probed) |
103
+ | `/doctor` | check that every configured model answers, especially the embedder |
104
+ | `/new` | start a new conversation in this folder (the old one is archived) |
72
105
  | `/undo [runId]` | put back the files the last stopped turn changed, from its backups (asks first) |
106
+ | `/test-auto` | toggle auto-test for this folder: verify and fix until it passes |
107
+ | `/browser [allow\|forget\|install\|cleanup]` | the agent's web browser, through the gu Chrome extension or gu's own Chrome window ([guide](browser-extension/README.md)) |
108
+ | `/shells` | long commands the agent left running in the background: status, output, stop one |
109
+ | `/rag` | where this folder's RAG index lives: local, cloud or both |
110
+ | `/tokens` | break down the last prompt sent to the coding model, including how much was RAG |
111
+ | `/prices [update]` | price tables, and what the last turn and this session cost |
112
+ | `/budget` | your API provider budgets and balances (Pro) |
113
+ | `/mcp [list\|tools\|add\|remove\|enable\|disable\|approve\|allow\|disallow\|reconnect]` | MCP servers, and which of their tools run without asking |
114
+ | `/hooks [list\|approve\|unapprove]` | commands gu runs on `onStart`, `onAsk`, `onToolDone`, `onTurnEnd` |
115
+ | `/switch-skill [claude\|gu]` | use Claude Code's (`.claude`) or gu's (`.gonext`) skills, MCP servers and hooks in this folder |
116
+ | `/skills-copy-to-claude [--dry-run] [name…]` | copy gu's skills into Claude Code's folders |
117
+ | `/proxy [show\|set\|on\|off\|clear\|test]` | route model calls through an HTTP proxy (local calls go direct) |
118
+ | `/s3 [test]` | use your own S3 bucket for the RAG index and generated files |
73
119
  | `/server` | pick a deployment server (host/user) for this session |
74
- | `/host-mode` | undo client mode — run agent turns on this machine again |
75
- | `/save-settings [name\|path]`, `/load-settings [name\|path]` | move providers, models and key names (no secrets) to another machine |
76
- | `/save-preset [name]`, `/open-presets [name]`, `/use-presets [name]` | presets: your whole setup, keys included, to copy to another computer, edit, and apply (backs up first) |
77
- | `/hooks [list\|approve\|unapprove]` | commands gu runs on onStart, onAsk, onToolDone, onTurnEnd |
78
- | `/mcp [list\|tools\|add\|remove\|enable\|disable\|approve\|allow\|disallow\|reconnect]` | manage MCP servers and which of their tools run without asking |
79
- | `/switch-skill [claude\|gu]` | use Claude Code's (`.claude`) or gu's own (`.gonext`) skills, MCP servers and hooks for this folder |
120
+ | `/host-mode` | undo client mode: run agent turns on this machine again |
121
+ | `/save-settings`, `/load-settings` | move providers, models and key *names* (no secrets) to another machine |
122
+ | `/save-preset`, `/open-presets`, `/use-presets` | your whole setup, keys included, to copy, edit and apply (backs up first) |
80
123
  | `/clear-logs` | show what logs, RAG indexes and old API versions take on disk, then ask what to clear |
81
- | `/new` | start a new conversation in this folder (the old one is archived) |
82
- | `/help`, `/exit` (`/quit`) | list commands / quit |
124
+ | `/voucher CODE` | use a voucher code: adds free days to this computer's trial or your free account |
125
+ | `/support` | send the support team a request with gu's logs attached (secrets masked; shows what goes first) |
126
+ | `/help`, `/exit` | list commands / quit |
83
127
 
84
- Each skill in `~/.gonext/skills/<name>/SKILL.md` (and the folder's `.gonext/skills`) is also a
85
- slash command of its own. Run `/help` in the REPL for the list your version offers.
128
+ Each skill in `~/.gonext/skills/<name>/SKILL.md` (or the folder's `.gonext/skills`) is a slash command too.
86
129
 
87
- ## Shell commands
130
+ </details>
131
+
132
+ <details>
133
+ <summary><strong>Shell commands</strong></summary>
88
134
 
89
135
  ```
90
- gu status worker + a table of the running model servers
91
- gu start | stop | restart run the polling daemon in the background (stop --models: model servers too)
92
- gu logs [--lines N] [-f] tail the daemon log; gu logs <port|pid|chat|embed> [-f] tails a model server's log
93
- gu doctor [--client] set up models here
94
- gu rag test the embedder end-to-end and list knowledge bases
95
- gu rag plan [path] is RAG worth it here? files / chars / chunks -> estimated index time
96
- gu rag --test can this machine embed and retrieve?
97
- gu api <start|stop|restart|status|upgrade> the local API (Local / Client Expert only)
136
+ gu status worker status and the running model servers
137
+ gu start | stop | restart the background worker (stop --models: model servers too)
138
+ gu logs [--lines N] [-f] tail the worker log; gu logs <port|pid|chat|embed> for a model server
139
+ gu doctor [--client] set up and check models on this machine
140
+ gu rag [plan [path] | --test] test the embedder, list knowledge bases, estimate index time
141
+ gu api <start|stop|restart|status|upgrade> the local API (private machines)
98
142
  gu workspace revert [runId] undo an agent run's file edits from its backups
99
- gu test-proxy can this machine route model calls through a proxy?
100
- gu mongo migrate-from-docker macOS: move a Docker MongoDB to native (Homebrew)
143
+ gu support send a support request with masked logs
144
+ gu test-proxy check model calls through a proxy
145
+ gu mongo migrate-from-docker macOS: move a Docker MongoDB to Homebrew
101
146
  gu clear [--scratch|--keep-history] [--yes] back to a fresh machine (asks first)
102
- gu fix-path | fix-powershell repair PATH / drop PowerShell's `gu` alias
147
+ gu fix-path | fix-powershell repair PATH / remove PowerShell's `gu` alias
103
148
  ```
104
149
 
105
- `gu-cli` accepts the same commands, plus `login [--api-base <url>]`, `set <workerKey> [--api-base <url>]`,
106
- `token [<token>]`, `ws-ping-test` and `simulate-chat [text...]`.
107
- `gu api restart` runs the installed version; an upgrade changes the files, not a process that is already serving.
150
+ `gu-cli` takes the same commands, plus `login [--api-base <url>]`, `set <workerKey>`,
151
+ `token [<token>]`, `ws-ping-test` and `simulate-chat [text…]`.
152
+
153
+ </details>
108
154
 
109
- ## Files and settings
155
+ ## Where gu keeps things
110
156
 
111
157
  | Path | Contents |
112
158
  | --- | --- |
113
- | `~/.gonext/worker.env` | sign-in: worker key and API base |
114
- | `~/.gonext/sessions/` | per-folder conversation history (`archive/` holds old conversations) |
115
- | `~/.gonext/worker.log` | worker log (detailed with `--log`) |
116
- | `~/.gonext/local-api.log` | the local API's log |
159
+ | `~/.gonext/worker.env` | sign-in: worker key and API address |
160
+ | `~/.gonext/sessions/` | conversation history per folder (`archive/` holds old ones) |
161
+ | `~/.gonext/worker.log`, `~/.gonext/local-api.log` | logs |
117
162
  | `~/.gonext/mcp.json` | MCP servers and their allow rules |
118
- | `~/.gonext/skills/`, `.gonext/skills` | skills (global / this folder) |
163
+ | `~/.gonext/skills/`, `<folder>/.gonext/skills/` | skills: everywhere / this folder only |
164
+
165
+ ## Plans
166
+
167
+ | | How long | What you need |
168
+ | --- | --- | --- |
169
+ | **Free trial** | 30 days, starts on the first run | nothing: the token is created for you |
170
+ | **Free plan** | 3 more months on that computer | a free account at [gucli.com](https://gucli.com) |
171
+ | **Pro** | while subscribed | more computers, plus provider budgets |
172
+
173
+ Got a **voucher code**? Type `/voucher CODE` in gu (or enter it on your
174
+ [account page](https://gucli.com/account)) for extra free days on top.
119
175
 
120
- ## Worker modes
176
+ Free time belongs to the computer: reinstalling doesn't restart it. Already have a token? Paste
177
+ it at the first-run question instead, or later with `gu-cli token <token>`. Current prices: [gucli.com/pricing](https://gucli.com/pricing).
121
178
 
122
- The background worker polls the API for jobs queued from the web app and runs them on this machine.
179
+ ## Troubleshooting
123
180
 
124
- ### Agent chat
181
+ **`gu: command not found` after install.** npm 11 skips install scripts it wasn't told to allow,
182
+ so the step that adds npm's bin folder to your PATH may not have run. Run
183
+ `npx @tiens.nguyen/gu-cli fix-path`. It also finds what an old install left behind (broken links,
184
+ a second gu from another npm, a PATH line pinned to an old Homebrew folder) and asks before
185
+ fixing each one. `--check` only reports.
125
186
 
126
- Choosing **Agent** under the composer in the web app hands your prompt to a
127
- [smolagents](https://github.com/huggingface/smolagents) agent running on your local model. It
128
- needs `pip install smolagents certifi` in the worker's Python environment (`certifi` gives
129
- HTTPS a trusted CA bundle on macOS). Agent mode is blocked for cloud models; pick a local
130
- MLX or Ollama model first. The script is `gonext_agent_chat.py`.
187
+ **Windows: `gu` runs `Get-Unique`.** PowerShell has a built-in `gu` alias. Run `gu-cli fix-powershell`.
131
188
 
132
- ### API check / HTTP probe
189
+ **Node version.** Node.js 18+ runs the CLI. A private machine (API and database on this
190
+ computer) needs **Node.js 20.19+**.
133
191
 
134
- - **Tools (`tool_only`)**: the worker runs the HTTP request itself, measures status, latency, headers and
135
- body, classifies it (2xx/3xx/4xx/5xx/timeout/network_error), and the local model writes a one-line summary.
136
- - **Agents (`agentic`)**: a smolagents agent writes the summary; the measurement stays the source of truth.
192
+ **A change in a model or setting seems ignored.** `gu doctor` shows the model and URL actually in use.
137
193
 
138
- ### Environment variables
194
+ **Still stuck?** Run `gu support` (or `/support` in gu, or click **? support** in the footer). It
195
+ sends the team your request with gu's logs attached, secrets masked, and shows you what goes
196
+ before sending.
197
+
198
+ <details>
199
+ <summary><strong>Advanced: worker jobs and environment variables</strong></summary>
200
+
201
+ The background worker also runs jobs queued from the [web app](https://gucli.com) on this machine:
202
+
203
+ - **Agent chat**: choosing **Agent** in the web app runs a
204
+ [smolagents](https://github.com/huggingface/smolagents) agent on your local model
205
+ (`pip install smolagents certifi`). Cloud models are not allowed here; pick a local MLX or Ollama model.
206
+ - **API checks**: the worker makes the HTTP request itself, measures status, latency and body,
207
+ and a local model writes a one-line summary.
139
208
 
140
209
  | Variable | Meaning |
141
210
  | --- | --- |
142
- | `GONEXT_API_BASE`, `GONEXT_WORKER_KEY` | API URL and worker key (normally from `~/.gonext/worker.env`) |
143
- | `GONEXT_PROBE_PYTHON` | Python for smolagents scripts (default: `GONEXT_MLX_LM_PYTHON` or `python3`) |
144
- | `GONEXT_MLX_LM_PYTHON` | Python for the MLX LM native probe (default `python3`) |
145
- | `GONEXT_OCR_MODEL_PATH` | local GLM-OCR-bf16 directory (default `~/mlx-models/GLM-OCR-bf16`) |
146
- | `GONEXT_OCR_DEBUG=1` | print raw OCR stdout/stderr snippets |
147
- | `GONEXT_SIMULATE_TEXT` | default reply body for `simulate-chat` |
211
+ | `GONEXT_API_BASE`, `GONEXT_WORKER_KEY` | API address and worker key (normally from `~/.gonext/worker.env`) |
212
+ | `GONEXT_PROBE_PYTHON` | Python for agent scripts (default: `GONEXT_MLX_LM_PYTHON` or `python3`) |
213
+ | `GONEXT_MLX_LM_PYTHON` | Python for the MLX probe (default `python3`) |
214
+ | `GONEXT_OCR_MODEL_PATH` | local GLM-OCR directory (default `~/mlx-models/GLM-OCR-bf16`) |
215
+ | `GONEXT_OCR_DEBUG=1` | print raw OCR output |
216
+ | `GONEXT_SIMULATE_TEXT` | default reply for `simulate-chat` |
148
217
 
149
- ## Development
218
+ </details>
150
219
 
151
- ```sh
152
- npm test # python unittest + node --test
153
- npm run preflight # checks before a release
154
- npm run publish:org # npm publish --access public
155
- ```
220
+ ## License
156
221
 
157
- Source and issues: https://github.com/tiennsloit/gu
222
+ [MIT](./LICENSE) © Tien Nguyen Song
package/cli-token.mjs CHANGED
@@ -533,6 +533,35 @@ export async function renewToken({ mode, apiBase, hostedBase, token, workerKey,
533
533
  }
534
534
  }
535
535
 
536
+ /**
537
+ * /voucher CODE (#425): redeem a marketing code with this computer's token as the credential — a
538
+ * trial (no account) or an account. Returns { ok: true, days, token } — the token already carries the
539
+ * extra days — or { ok: false, why, error } where error is the service's own sentence. Never throws.
540
+ */
541
+ export async function redeemVoucher({ mode, apiBase, hostedBase, token, device, code, fetchImpl = fetch, timeoutMs = 8000 }) {
542
+ const base = String(mode === "expert" ? hostedBase : apiBase || hostedBase).replace(/\/+$/, "");
543
+ if (!token) return { ok: false, why: "no-token", error: "This computer has no gu token yet — restart gu to get the free trial, then try again." };
544
+ if (!base) return { ok: false, why: "no-api", error: "No gu service address is set on this computer." };
545
+ try {
546
+ const res = await fetchImpl(`${base}/api/billing/cli-token/voucher`, {
547
+ method: "POST",
548
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
549
+ body: JSON.stringify({ code: String(code ?? ""), ...(device ? { device } : {}) }),
550
+ signal: AbortSignal.timeout(timeoutMs),
551
+ });
552
+ const data = await res.json().catch(() => null);
553
+ if (!res.ok) {
554
+ const error = typeof data?.error === "string" && data.error
555
+ ? data.error
556
+ : res.status === 404 ? "This gu service does not take vouchers yet." : `The gu service answered HTTP ${res.status}.`;
557
+ return { ok: false, why: typeof data?.why === "string" ? data.why : `http-${res.status}`, error };
558
+ }
559
+ return { ok: true, days: Number(data?.days ?? 0), token: typeof data?.token === "string" ? data.token : "" };
560
+ } catch (e) {
561
+ return { ok: false, why: "network", error: e?.name === "TimeoutError" ? "The gu service did not answer in time. Try again." : "Can't reach the gu service right now." };
562
+ }
563
+ }
564
+
536
565
  // ---------------------------------------------------------------------------
537
566
  // WHAT THE BAR SAYS
538
567
  // ---------------------------------------------------------------------------
package/gu-repl.mjs CHANGED
@@ -1041,6 +1041,7 @@ const COMMANDS = [
1041
1041
  { name: "/shells", desc: "long commands the agent left running in the background — status, runtime, command, output; stop one" },
1042
1042
  { name: "/budget", desc: "short view of your API provider budgets and balances (Pro) — the same items as the web /providers page" },
1043
1043
  { name: "/tokens", desc: "break down the last prompt sent to the coding model — how much of it was RAG" },
1044
+ { name: "/voucher", usage: "/voucher CODE", desc: "use a voucher code — it adds free days to this computer's trial or your free account" },
1044
1045
  { name: "/support", desc: "send the gu support team a request with gu's logs attached (secrets masked) — shows what goes first, then asks" },
1045
1046
  { name: "/clear-logs", desc: "show what gu's logs, RAG indexes and old API versions take on disk, then ask what to clear — the logs first" },
1046
1047
  { name: "/prices", usage: "/prices [update]", desc: "the price table and the cache table, and what the last turn and this session cost — \"update\" applies gu's newest published tables" },
@@ -8397,6 +8398,54 @@ async function noteTurnCost(inputTokens, breakdown, coder = null) {
8397
8398
  * use marked; what the last turn and this session cost; and, when gu has published newer tables,
8398
8399
  * that they are waiting. `/prices update` applies them — the same as clicking the notice.
8399
8400
  */
8401
+ /**
8402
+ * /voucher CODE (#425) — a marketing code that adds free days. Works on the free trial (no account)
8403
+ * and on a free account; the service answers with a fresh token that already carries the days, so
8404
+ * the bar and the licence see them at once — no restart.
8405
+ */
8406
+ async function runVoucherCommand(line = "/voucher") {
8407
+ const code = line.trim().slice("/voucher".length).trim();
8408
+ console.log("");
8409
+ if (!code) {
8410
+ console.log(GUTTER + "Type the code after the command: " + accent("/voucher CODE") + "\n");
8411
+ return;
8412
+ }
8413
+ const ct = await import("./cli-token.mjs");
8414
+ const pk = ct.publicKey();
8415
+ const dir = join(homedir(), ".gonext");
8416
+ const state = await ct.readState(dir);
8417
+ const { DEFAULT_API_BASE } = await import("./device-login.mjs");
8418
+ const device = await ct.thisDevice({ instanceId: myInstanceId || "" }).catch(() => null);
8419
+ const r = await withPendingStatus("checking the voucher", () =>
8420
+ ct.redeemVoucher({
8421
+ mode: isClientExpert() ? "expert" : "client",
8422
+ apiBase,
8423
+ hostedBase: String(process.env.GU_TOKEN_API_BASE ?? "").trim() || DEFAULT_API_BASE,
8424
+ token: state.token,
8425
+ device,
8426
+ code,
8427
+ })
8428
+ );
8429
+ if (!r.ok) {
8430
+ console.log(GUTTER + yellow(r.error) + "\n");
8431
+ return;
8432
+ }
8433
+ // Kept only if it checks out here, like every renewal.
8434
+ const check = r.token && pk ? ct.verifyCliToken(r.token, pk) : { ok: false };
8435
+ if (check.ok) {
8436
+ await ct.writeState(dir, { token: r.token, lastAttemptAt: new Date().toISOString() }).catch(() => {});
8437
+ const d = ct.enforcementDecision({ token: r.token, publicKeyB64: pk, site: ct.siteUrl(), deviceId: await thisDeviceId(ct) });
8438
+ licenceBlock = d.enforced && !d.allowed ? d.why : null;
8439
+ tokenNotice = ct.barNotice({ token: r.token, publicKeyB64: pk, mode: isClientExpert() ? "expert" : "client", now: new Date(), attempted: true, site: ct.siteUrl() });
8440
+ }
8441
+ const p = check.ok ? check.payload : null;
8442
+ const until = p?.plan === "trial" ? p.paidUntil : p?.plan === "free" && typeof p.freeUntil === "string" ? p.freeUntil : null;
8443
+ console.log(GUTTER + accent(`✓ +${r.days} free days added.`) + (until ? ` Free on this computer until ${ct.shortDate(until)}.` : ""));
8444
+ if (!check.ok) console.log(GUTTER + dim("gu picks the days up at its next token refresh (within the hour)."));
8445
+ console.log("");
8446
+ if (barPinned()) paintBar();
8447
+ }
8448
+
8400
8449
  async function runPricesCommand(line = "/prices") {
8401
8450
  const mp = await import("./model-prices.mjs");
8402
8451
  console.log("");
@@ -15080,6 +15129,10 @@ async function main() {
15080
15129
  await runPricesCommand(line);
15081
15130
  continue;
15082
15131
  }
15132
+ if (line === "/voucher" || line.startsWith("/voucher ")) {
15133
+ await runVoucherCommand(line);
15134
+ continue;
15135
+ }
15083
15136
  if (line === "/clear-logs") {
15084
15137
  await runClearLogs();
15085
15138
  continue;
package/package.json CHANGED
@@ -1,17 +1,28 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gu-cli",
3
- "version": "1.0.881",
4
- "description": "Gu CLI — the gu terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
5
- "type": "module",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/tiennsloit/gu.git"
10
- },
11
- "homepage": "https://github.com/tiennsloit/gu#readme",
3
+ "version": "1.0.883",
4
+ "description": "gu: the coding agent in your terminal. It reads your repo, makes the change and runs the tests, with your own models (Ollama, MLX or any OpenAI-compatible provider). macOS, Linux and Windows.",
5
+ "keywords": [
6
+ "ai",
7
+ "coding-agent",
8
+ "cli",
9
+ "terminal",
10
+ "llm",
11
+ "ollama",
12
+ "mlx",
13
+ "openai",
14
+ "agent",
15
+ "mcp",
16
+ "rag",
17
+ "code-assistant"
18
+ ],
19
+ "homepage": "https://gucli.com",
12
20
  "bugs": {
13
- "url": "https://github.com/tiennsloit/gu/issues"
21
+ "url": "https://gucli.com/support"
14
22
  },
23
+ "license": "MIT",
24
+ "author": "Tien Nguyen Song",
25
+ "type": "module",
15
26
  "bin": {
16
27
  "gu-cli": "gu-cli.mjs",
17
28
  "gu-local-worker": "gu-cli.mjs",