@tiens.nguyen/gu-cli 1.0.882 → 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/README.md +21 -9
- package/cli-token.mjs +29 -0
- package/gu-repl.mjs +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
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
|
|
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>
|
|
6
7
|
</p>
|
|
7
8
|
|
|
8
9
|
<p align="center">
|
|
@@ -35,8 +36,9 @@ cd your-project
|
|
|
35
36
|
gu
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
40
42
|
|
|
41
43
|
> **`gu: command not found`?** Run `npx @tiens.nguyen/gu-cli fix-path`. See [Troubleshooting](#troubleshooting).
|
|
42
44
|
|
|
@@ -56,12 +58,12 @@ gu token (a free trial is offered). After that, type what you want done.
|
|
|
56
58
|
|
|
57
59
|
## Quick start
|
|
58
60
|
|
|
59
|
-
1. **
|
|
60
|
-
|
|
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.
|
|
61
63
|
2. **Check your models.** `gu doctor` sets up and tests the models on this machine.
|
|
62
64
|
(`gu doctor --client` installs only the agent's dependencies, when the models run elsewhere.)
|
|
63
|
-
3. **Ask.**
|
|
64
|
-
|
|
65
|
+
3. **Ask.** Type what you want done. Conversations are saved per folder and resumed next time;
|
|
66
|
+
`/new` starts a fresh one.
|
|
65
67
|
|
|
66
68
|
## Models
|
|
67
69
|
|
|
@@ -119,6 +121,7 @@ Ctrl-C stops the running turn. Type `/` at the prompt to see every command; Tab
|
|
|
119
121
|
| `/save-settings`, `/load-settings` | move providers, models and key *names* (no secrets) to another machine |
|
|
120
122
|
| `/save-preset`, `/open-presets`, `/use-presets` | your whole setup, keys included, to copy, edit and apply (backs up first) |
|
|
121
123
|
| `/clear-logs` | show what logs, RAG indexes and old API versions take on disk, then ask what to clear |
|
|
124
|
+
| `/voucher CODE` | use a voucher code: adds free days to this computer's trial or your free account |
|
|
122
125
|
| `/support` | send the support team a request with gu's logs attached (secrets masked; shows what goes first) |
|
|
123
126
|
| `/help`, `/exit` | list commands / quit |
|
|
124
127
|
|
|
@@ -161,8 +164,17 @@ gu fix-path | fix-powershell repair PATH / remove PowerShell's `gu` alias
|
|
|
161
164
|
|
|
162
165
|
## Plans
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
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.
|
|
175
|
+
|
|
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).
|
|
166
178
|
|
|
167
179
|
## Troubleshooting
|
|
168
180
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.883",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"ai",
|