@tiens.nguyen/gu-cli 1.0.802 → 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 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
- ## Agent chat mode
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
- Select **Agent** (instead of Chat) under the composer in the web app. Your
8
- free-form prompt is handed to a [smolagents](https://github.com/huggingface/smolagents)
9
- agent running on your local MLX/Ollama model. The agent can call tools (v1:
10
- `http_request`) and streams its thinking steps + final answer directly into the
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
- Requires smolagents in the worker's Python environment:
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
- - `certifi` supplies a trusted CA bundle so the agent's `http_request` tool can
20
- verify HTTPS certificates on macOS (where Python's default bundle may be
21
- missing). The worker falls back to the system bundle if certifi is absent.
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
- The agent script is `gonext_agent_chat.py` (reads `{messages, agentBaseURL,
28
- agentApiKey, agentModelId, tools, maxSteps}` on stdin; emits NDJSON
29
- `{"type":"step"/"final","text":"..."}` lines on stdout).
52
+ ### Slash commands
30
53
 
31
- ## API Check / HTTP probe (Tools & Agents modes)
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
- The worker can run Postman-style HTTP probes queued from the web app
34
- ("Tools" = `tool_only`, "Agents" = `agentic`). The worker always performs the
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
- - **Tools (`tool_only`)** — no extra setup. The selected local model writes a
40
- one-line health summary of the measured result.
41
- - **Agents (`agentic`)** — a smolagents agent (running on the selected local
42
- model) produces the summary. Requires `pip install smolagents`.
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
- The agent talks to your local MLX OpenAI-compatible server (no cloud calls).
45
- The agent only summarizes; the worker's measurement stays the source of truth,
46
- so if smolagents or the model is unavailable the probe still returns the
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
- ### Env vars
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
- GONEXT_PROBE_PYTHON Python executable for smolagents scripts
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
- // BOTH FIELDS. With a direct agentModelUrl set — the Client Expert shape — the API reads
8601
- // agentModelName; agentModel is the legacy mirror. Writing one leaves the two disagreeing.
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.802",
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",
@@ -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 —