@tiens.nguyen/gu-cli 1.0.801 → 1.0.803
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/README.md +136 -38
- package/gu-repl.mjs +38 -4
- package/package.json +1 -1
- package/terminal-layout.mjs +11 -0
package/README.md
CHANGED
|
@@ -1,52 +1,150 @@
|
|
|
1
1
|
# @tiens.nguyen/gu-cli
|
|
2
|
-
Run:
|
|
3
|
-
GONEXT_API_BASE=... GONEXT_WORKER_KEY=... npx -y --package @tiens.nguyen/gu-cli gu-cli
|
|
4
2
|
|
|
5
|
-
|
|
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).
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
chat thread.
|
|
6
|
+
```sh
|
|
7
|
+
npm install -g @tiens.nguyen/gu-cli
|
|
8
|
+
gu # open the REPL in the current folder
|
|
9
|
+
```
|
|
12
10
|
|
|
13
|
-
|
|
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 runs `fix-path` for you; if
|
|
13
|
+
npm's global bin folder is not on your PATH afterwards, run `gu-cli fix-path`.
|
|
14
|
+
On Windows, PowerShell's built-in `gu` alias (Get-Unique) shadows this CLI — run
|
|
15
|
+
`gu-cli fix-powershell` to drop it.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
1. `gu login` — browser sign-in; writes `~/.gonext/worker.env`.
|
|
20
|
+
(Client Expert machines can instead paste a token from the Account page with `gu-cli token <token>`.)
|
|
21
|
+
2. `gu doctor` — set up and check the models on this machine
|
|
22
|
+
(`gu doctor --client` installs only the agent dependencies when the models live on another machine).
|
|
23
|
+
3. `gu` in a project folder, then type a request. Conversation history is saved per folder in
|
|
24
|
+
`~/.gonext/sessions` and resumed next time; `/new` starts a fresh one and archives the old one.
|
|
25
|
+
|
|
26
|
+
A first install asks how this machine runs (hosted client / MLX host / private). Pass
|
|
27
|
+
`--license <pro token>` to choose a pro license up front; without it a first install is private,
|
|
28
|
+
meaning the whole stack runs on this machine.
|
|
29
|
+
|
|
30
|
+
## The `gu` REPL
|
|
14
31
|
|
|
15
|
-
```sh
|
|
16
|
-
pip install smolagents certifi
|
|
17
32
|
```
|
|
33
|
+
gu [-v|--verbose] [--log] [--license <pro token>]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `-v, --verbose` shows RAG index-file activity in the bottom bar.
|
|
37
|
+
- `--log` logs this run in detail to `~/.gonext/worker.log`. Without it only starts, stops and errors are logged.
|
|
38
|
+
- Ctrl-C stops following a turn.
|
|
39
|
+
|
|
40
|
+
### Three models, three commands
|
|
41
|
+
|
|
42
|
+
| Command | Model it switches |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `/model` | the CODING model (the one that writes code), for this session |
|
|
45
|
+
| `/agent-model` | the AGENT model (the one that talks) — saved on this machine |
|
|
46
|
+
| `/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 |
|
|
18
47
|
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- Agent mode is blocked for cloud models (the API returns 400). Select a local
|
|
23
|
-
MLX or Ollama model first.
|
|
24
|
-
- Tool steps appear in the collapsible reasoning (`<think>`) area; the final
|
|
25
|
-
answer is the message body — no new UI needed.
|
|
48
|
+
`/agent-model` saves the model's server URL (`agentModelUrl`) together with its name, so picking a
|
|
49
|
+
model from another provider (for example OpenAI) can no longer leave chat pointed at the old
|
|
50
|
+
server. After picking, `gu doctor` shows the URL and model that are actually in use.
|
|
26
51
|
|
|
27
|
-
|
|
28
|
-
agentApiKey, agentModelId, tools, maxSteps}` on stdin; emits NDJSON
|
|
29
|
-
`{"type":"step"/"final","text":"..."}` lines on stdout).
|
|
52
|
+
### Slash commands
|
|
30
53
|
|
|
31
|
-
|
|
54
|
+
| Command | What it does |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `/model`, `/agent-model`, `/embed-model` | switch models (see above) |
|
|
57
|
+
| `/context-window [tokens\|auto]` | tell gu how big the coding model's context is (hosted models cannot be probed) |
|
|
58
|
+
| `/doctor` | check every configured model answers, especially the embedder |
|
|
59
|
+
| `/tokens` | break down the last prompt sent to the coding model — how much of it was RAG |
|
|
60
|
+
| `/prices [update]` | price and cache tables, and what the last turn and this session cost |
|
|
61
|
+
| `/rag` | where this folder's RAG knowledge base lives: local, cloud, or both |
|
|
62
|
+
| `/s3 [test]` | set up your own S3 bucket (RAG index, generated PDFs/downloads) and check the AWS keys |
|
|
63
|
+
| `/proxy [show\|set\|on\|off\|clear\|test]` | route model calls through an HTTP proxy (local calls always go direct) |
|
|
64
|
+
| `/test-auto` | toggle auto-test (verify and fix until it passes) for this folder |
|
|
65
|
+
| `/undo [runId]` | put back the files the last stopped turn changed, from its backups (asks first) |
|
|
66
|
+
| `/server` | pick a deployment server (host/user) for this session |
|
|
67
|
+
| `/host-mode` | undo client mode — run agent turns on this machine again |
|
|
68
|
+
| `/save-settings [name\|path]`, `/load-settings [name\|path]` | move providers, models and key names (no secrets) to another machine |
|
|
69
|
+
| `/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) |
|
|
70
|
+
| `/hooks [list\|approve\|unapprove]` | commands gu runs on onStart, onAsk, onToolDone, onTurnEnd |
|
|
71
|
+
| `/mcp [list\|tools\|add\|remove\|enable\|disable\|approve\|allow\|disallow\|reconnect]` | manage MCP servers and which of their tools run without asking |
|
|
72
|
+
| `/switch-skill [claude\|gu]` | use Claude Code's (`.claude`) or gu's own (`.gonext`) skills, MCP servers and hooks for this folder |
|
|
73
|
+
| `/clear-logs` | show what logs, RAG indexes and old API versions take on disk, then ask what to clear |
|
|
74
|
+
| `/new` | start a new conversation in this folder (the old one is archived) |
|
|
75
|
+
| `/help`, `/exit` (`/quit`) | list commands / quit |
|
|
32
76
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
actual HTTP request itself (Node `fetch`, so TLS works), measures the
|
|
36
|
-
status/latency/headers/body, and classifies it (2xx/3xx/4xx/5xx/timeout/
|
|
37
|
-
network_error).
|
|
77
|
+
Each skill in `~/.gonext/skills/<name>/SKILL.md` (and the folder's `.gonext/skills`) is also a
|
|
78
|
+
slash command of its own. Run `/help` in the REPL for the list your version offers.
|
|
38
79
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
## Shell commands
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
gu status worker + a table of the running model servers
|
|
84
|
+
gu start | stop | restart run the polling daemon in the background (stop --models: model servers too)
|
|
85
|
+
gu logs [--lines N] [-f] tail the daemon log; gu logs <port|pid|chat|embed> [-f] tails a model server's log
|
|
86
|
+
gu doctor [--client] set up models here
|
|
87
|
+
gu rag test the embedder end-to-end and list knowledge bases
|
|
88
|
+
gu rag plan [path] is RAG worth it here? files / chars / chunks -> estimated index time
|
|
89
|
+
gu rag --test can this machine embed and retrieve?
|
|
90
|
+
gu api <start|stop|restart|status|upgrade> the local API (Local / Client Expert only)
|
|
91
|
+
gu workspace revert [runId] undo an agent run's file edits from its backups
|
|
92
|
+
gu test-proxy can this machine route model calls through a proxy?
|
|
93
|
+
gu mongo migrate-from-docker macOS: move a Docker MongoDB to native (Homebrew)
|
|
94
|
+
gu clear [--scratch|--keep-history] [--yes] back to a fresh machine (asks first)
|
|
95
|
+
gu fix-path | fix-powershell repair PATH / drop PowerShell's `gu` alias
|
|
96
|
+
```
|
|
43
97
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
measured result with a note.
|
|
98
|
+
`gu-cli` accepts the same commands, plus `login [--api-base <url>]`, `set <workerKey> [--api-base <url>]`,
|
|
99
|
+
`token [<token>]`, `ws-ping-test` and `simulate-chat [text...]`.
|
|
100
|
+
`gu api restart` runs the installed version; an upgrade changes the files, not a process that is already serving.
|
|
48
101
|
|
|
49
|
-
|
|
102
|
+
## Files and settings
|
|
103
|
+
|
|
104
|
+
| Path | Contents |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `~/.gonext/worker.env` | sign-in: worker key and API base |
|
|
107
|
+
| `~/.gonext/sessions/` | per-folder conversation history (`archive/` holds old conversations) |
|
|
108
|
+
| `~/.gonext/worker.log` | worker log (detailed with `--log`) |
|
|
109
|
+
| `~/.gonext/local-api.log` | the local API's log |
|
|
110
|
+
| `~/.gonext/mcp.json` | MCP servers and their allow rules |
|
|
111
|
+
| `~/.gonext/skills/`, `.gonext/skills` | skills (global / this folder) |
|
|
112
|
+
|
|
113
|
+
## Worker modes
|
|
114
|
+
|
|
115
|
+
The background worker polls the API for jobs queued from the web app and runs them on this machine.
|
|
116
|
+
|
|
117
|
+
### Agent chat
|
|
118
|
+
|
|
119
|
+
Choosing **Agent** under the composer in the web app hands your prompt to a
|
|
120
|
+
[smolagents](https://github.com/huggingface/smolagents) agent running on your local model. It
|
|
121
|
+
needs `pip install smolagents certifi` in the worker's Python environment (`certifi` gives
|
|
122
|
+
HTTPS a trusted CA bundle on macOS). Agent mode is blocked for cloud models; pick a local
|
|
123
|
+
MLX or Ollama model first. The script is `gonext_agent_chat.py`.
|
|
124
|
+
|
|
125
|
+
### API check / HTTP probe
|
|
126
|
+
|
|
127
|
+
- **Tools (`tool_only`)**: the worker runs the HTTP request itself, measures status, latency, headers and
|
|
128
|
+
body, classifies it (2xx/3xx/4xx/5xx/timeout/network_error), and the local model writes a one-line summary.
|
|
129
|
+
- **Agents (`agentic`)**: a smolagents agent writes the summary; the measurement stays the source of truth.
|
|
130
|
+
|
|
131
|
+
### Environment variables
|
|
132
|
+
|
|
133
|
+
| Variable | Meaning |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `GONEXT_API_BASE`, `GONEXT_WORKER_KEY` | API URL and worker key (normally from `~/.gonext/worker.env`) |
|
|
136
|
+
| `GONEXT_PROBE_PYTHON` | Python for smolagents scripts (default: `GONEXT_MLX_LM_PYTHON` or `python3`) |
|
|
137
|
+
| `GONEXT_MLX_LM_PYTHON` | Python for the MLX LM native probe (default `python3`) |
|
|
138
|
+
| `GONEXT_OCR_MODEL_PATH` | local GLM-OCR-bf16 directory (default `~/mlx-models/GLM-OCR-bf16`) |
|
|
139
|
+
| `GONEXT_OCR_DEBUG=1` | print raw OCR stdout/stderr snippets |
|
|
140
|
+
| `GONEXT_SIMULATE_TEXT` | default reply body for `simulate-chat` |
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
npm test # python unittest + node --test
|
|
146
|
+
npm run preflight # checks before a release
|
|
147
|
+
npm run publish:org # npm publish --access public
|
|
148
|
+
```
|
|
50
149
|
|
|
51
|
-
|
|
52
|
-
(default: GONEXT_MLX_LM_PYTHON or python3)
|
|
150
|
+
Source and issues: https://github.com/tiennsloit/gu
|
package/gu-repl.mjs
CHANGED
|
@@ -111,6 +111,7 @@ import {
|
|
|
111
111
|
bulletKind,
|
|
112
112
|
workspaceScopeWarning,
|
|
113
113
|
isLocalApiBase,
|
|
114
|
+
apiVersionSuffix,
|
|
114
115
|
modeRow,
|
|
115
116
|
nothingToAnswerWith,
|
|
116
117
|
NO_MODEL_LINES,
|
|
@@ -8583,6 +8584,18 @@ async function chooseAgentModel() {
|
|
|
8583
8584
|
console.log(dim(` already using ${current} — nothing changed.\n`));
|
|
8584
8585
|
return;
|
|
8585
8586
|
}
|
|
8587
|
+
if (rows[idx].missing) {
|
|
8588
|
+
// The one row agentModelRows injects WITHOUT the host listing it (#108): picking it re-affirms
|
|
8589
|
+
// a name the server no longer has, which fails later as a confused agent, not here. The user
|
|
8590
|
+
// can still insist — it may be a list outage — but the consequence is said BEFORE the write.
|
|
8591
|
+
console.log(GUTTER + yellow(`${chosen} is not on ${listHost || "the server"} any more — it may have been removed there.`));
|
|
8592
|
+
console.log(GUTTER + yellow("As the agent model it would fail on your next question, not now."));
|
|
8593
|
+
const go = await pickFromList([`use it anyway`, `pick something else`], 1);
|
|
8594
|
+
if (go !== 0) {
|
|
8595
|
+
console.log(dim(" (kept)\n"));
|
|
8596
|
+
return;
|
|
8597
|
+
}
|
|
8598
|
+
}
|
|
8586
8599
|
if (rows[idx].embedder) {
|
|
8587
8600
|
// Not a refusal — it is their box and their call — but the consequence has to be said BEFORE
|
|
8588
8601
|
// the write, because an embedder as the chat model does not fail here. It fails as an agent
|
|
@@ -8597,9 +8610,17 @@ async function chooseAgentModel() {
|
|
|
8597
8610
|
}
|
|
8598
8611
|
|
|
8599
8612
|
const md = await import("./model-doctor.mjs");
|
|
8600
|
-
|
|
8601
|
-
//
|
|
8613
|
+
const ps2 = await import("./coding-provider-setup.mjs");
|
|
8614
|
+
// THREE FIELDS — NAME **AND** SERVER (#108). This picker once wrote only agentModelName +
|
|
8615
|
+
// agentModel, leaving agentModelUrl wherever it was: choosing an OpenAI model while the stored
|
|
8616
|
+
// URL still said the shared Ollama box meant every turn went to that (dead) host and died as an
|
|
8617
|
+
// nginx 502, with the valid OpenAI key never touched — the key is chosen by MATCHING the URL.
|
|
8618
|
+
// The URL saved is the one this very list came from (agentListSource reads settings.agentModelUrl),
|
|
8619
|
+
// normalised to the /v1 root so it matches the provider row app.ts resolves the key against —
|
|
8620
|
+
// the same shape the add-server flow's saveAgentModel stores. Name and server can no longer diverge.
|
|
8621
|
+
const agentUrl = ps2.ensureV1Root(String(settings?.agentModelUrl ?? source.url ?? ""));
|
|
8602
8622
|
const saved = await md.saveWorkerSettings(apiBase, workerKey, {
|
|
8623
|
+
agentModelUrl: agentUrl,
|
|
8603
8624
|
agentModelName: chosen,
|
|
8604
8625
|
agentModel: chosen,
|
|
8605
8626
|
});
|
|
@@ -8611,7 +8632,7 @@ async function chooseAgentModel() {
|
|
|
8611
8632
|
console.log(GUTTER + dim("`gu api restart` to pick up the current version.\n"));
|
|
8612
8633
|
return;
|
|
8613
8634
|
}
|
|
8614
|
-
console.log(accent(` ✓ agent model → ${chosen}`));
|
|
8635
|
+
console.log(accent(` ✓ agent model → ${chosen}`) + (listHost ? dim(` · ${listHost}`) : ""));
|
|
8615
8636
|
console.log(dim(" takes effect on your next question.\n"));
|
|
8616
8637
|
}
|
|
8617
8638
|
|
|
@@ -12795,8 +12816,21 @@ async function main() {
|
|
|
12795
12816
|
agentUrl: p.agentBaseURL,
|
|
12796
12817
|
servers: await (await import("./model-doctor.mjs")).discoverModelServers(),
|
|
12797
12818
|
});
|
|
12819
|
+
// Task #18: in client expert / private mode the API is a SEPARATE build running on this
|
|
12820
|
+
// machine — `gu-cli api upgrade` moves it without touching the CLI, so `v<cli>` alone
|
|
12821
|
+
// does not say what is answering. Ask its /api/health for the running version and show
|
|
12822
|
+
// both. Best-effort and only in that mode: a client machine's API is the hosted service,
|
|
12823
|
+
// whose version the user cannot act on, and an unreachable local API must not hold up or
|
|
12824
|
+
// break the banner.
|
|
12825
|
+
let apiBannerVersion = "";
|
|
12826
|
+
if (isClientExpert()) {
|
|
12827
|
+
try {
|
|
12828
|
+
const ceApi = await import("./client-expert-api.mjs");
|
|
12829
|
+
apiBannerVersion = String((await ceApi.probeLocalApi()).body?.version ?? "").trim();
|
|
12830
|
+
} catch { /* absent beats guessed — no suffix */ }
|
|
12831
|
+
}
|
|
12798
12832
|
const statusRows = [
|
|
12799
|
-
bold(accent("GU AGENT")) + dim(` v${REPL_VERSION}`),
|
|
12833
|
+
bold(accent("GU AGENT")) + dim(` v${REPL_VERSION}${apiVersionSuffix(apiBannerVersion)}`),
|
|
12800
12834
|
// Task #134: a client machine OVERRIDES the account's agent model locally (the
|
|
12801
12835
|
// account value points at the model host's own server). Show what will actually be
|
|
12802
12836
|
// used, not a value this machine can't reach.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.803",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/terminal-layout.mjs
CHANGED
|
@@ -2320,6 +2320,17 @@ export function isLocalApiBase(apiBase) {
|
|
|
2320
2320
|
* entire distinction — go unmentioned. Reported live on a fully set-up Ubuntu box that said
|
|
2321
2321
|
* plain "client".
|
|
2322
2322
|
*/
|
|
2323
|
+
/**
|
|
2324
|
+
* The banner's API-version suffix for client expert / private mode (task #18). There the API
|
|
2325
|
+
* runs on THIS machine and its build can differ from the CLI's — an `api upgrade` without a gu
|
|
2326
|
+
* update is the common way — so the banner shows both, next to `v<cli>`. An empty version means
|
|
2327
|
+
* the running build did not report one (or is unreachable), and absent beats guessed: no suffix.
|
|
2328
|
+
*/
|
|
2329
|
+
export function apiVersionSuffix(version) {
|
|
2330
|
+
const v = String(version ?? "").trim();
|
|
2331
|
+
return v ? ` · api v${v}` : "";
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2323
2334
|
export function modeRow({ clientExpert, clientMode, modelUrls = [], coderWhere }) {
|
|
2324
2335
|
if (clientExpert) {
|
|
2325
2336
|
// "models remote" WAS A CONSTANT (task #333): a box running its own Ollama on 127.0.0.1 —
|