@tiens.nguyen/gu-cli 1.0.777 → 1.0.778
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/cli-token.mjs +113 -19
- package/gu-repl.mjs +80 -13
- package/package.json +1 -1
package/cli-token.mjs
CHANGED
|
@@ -90,7 +90,10 @@ export function verifyCliToken(token, publicKeyB64) {
|
|
|
90
90
|
// Missing means OFF — tokens issued before the switch existed (#238) are in CLIs right now.
|
|
91
91
|
!(p.enforce === undefined || typeof p.enforce === "boolean") ||
|
|
92
92
|
// Missing is every token before #267; present, it must be an address.
|
|
93
|
-
!(p.email === undefined || isEmail(p.email))
|
|
93
|
+
!(p.email === undefined || isEmail(p.email)) ||
|
|
94
|
+
// #315: this computer's free time — missing = unknown, null = no limit (the owner's Mark OK).
|
|
95
|
+
!(p.freeUntil === undefined || p.freeUntil === null || isIso(p.freeUntil)) ||
|
|
96
|
+
!(p.freeAccounts === undefined || (Number.isInteger(p.freeAccounts) && p.freeAccounts >= 1))
|
|
94
97
|
) {
|
|
95
98
|
return { ok: false, why: "bad-payload" };
|
|
96
99
|
}
|
|
@@ -172,6 +175,13 @@ export async function claimTrial({ hostedBase, device, fetchImpl = fetch, timeou
|
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
/** Where a person who thinks a lock is wrong can write (#315 P7) — the address the website shows. */
|
|
179
|
+
export const SUPPORT_EMAIL = "support@getmarsic.cc";
|
|
180
|
+
|
|
181
|
+
/** The Cursor-style line (#315 W2): matter-of-fact, never "abuse", never the other account. */
|
|
182
|
+
export const FREE_USED_UP_MANY =
|
|
183
|
+
"Too many free accounts used on this computer. Its free time is used up — enter a Pro token to continue.";
|
|
184
|
+
|
|
175
185
|
/** Whole days left until an ISO date (rounded up; 0 when past). */
|
|
176
186
|
export function daysLeft(iso, now = new Date()) {
|
|
177
187
|
const ms = Date.parse(iso ?? "") - now.getTime();
|
|
@@ -179,9 +189,10 @@ export function daysLeft(iso, now = new Date()) {
|
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
/** The one line a first install sees instead of a token question (#305). */
|
|
182
|
-
export function trialStartedLine(endsAt, site = DEFAULT_SITE) {
|
|
192
|
+
export function trialStartedLine(endsAt, site = DEFAULT_SITE, now = new Date()) {
|
|
183
193
|
const accountUrl = `${String(site).replace(/^https?:\/\//, "")}/account`;
|
|
184
|
-
|
|
194
|
+
const n = daysLeft(endsAt, now);
|
|
195
|
+
return `Free trial on this computer: ${n} day${n === 1 ? "" : "s"}, until ${shortDate(endsAt)}. ` +
|
|
185
196
|
`Then get a free token for 1 computer at ${accountUrl} — gu token <paste> links it.`;
|
|
186
197
|
}
|
|
187
198
|
|
|
@@ -198,7 +209,7 @@ export function shortToken(token) {
|
|
|
198
209
|
* THE TOKEN QUESTION, WITH THE TRIAL AS ITS DEFAULT (#305/#76, placed by #314).
|
|
199
210
|
*
|
|
200
211
|
* Input token: gu1.eyJ2IjoxLCJz…Xk9aQw ← typed in: Enter takes it, or delete it
|
|
201
|
-
* This is a free trial token:
|
|
212
|
+
* This is a free trial token: 30 days left on this computer, until 30 Oct 2026. ← yellow, under
|
|
202
213
|
* For a free token for 1 computer, log in at gu.example/account and create one — or buy Pro …
|
|
203
214
|
*
|
|
204
215
|
* The trial is fetched first (it is the same one every time for this computer). Nothing is saved
|
|
@@ -226,6 +237,12 @@ export async function promptForToken({
|
|
|
226
237
|
publicKeyB64 = publicKey(),
|
|
227
238
|
required = false,
|
|
228
239
|
maxRounds = 1000, // a bound for tests; a person answers long before this
|
|
240
|
+
// #315: WHY the question is asked when a token exists but is no longer entitled (the computer's
|
|
241
|
+
// free time is used up, the trial ended…) — replaces the "needs a token" title, adds the way on.
|
|
242
|
+
why = "",
|
|
243
|
+
// #315: a saved token must also be ENTITLED on this computer. `accept(token)` → { ok } | { ok:false, reason }
|
|
244
|
+
// — the REPL renews it from this computer (to learn its free time) and runs the licence rule.
|
|
245
|
+
accept = null,
|
|
229
246
|
}) {
|
|
230
247
|
const accountUrl = `${String(site).replace(/^https?:\/\//, "")}/account`;
|
|
231
248
|
const dev = device === undefined ? await trialDevice().catch(() => null) : device;
|
|
@@ -236,16 +253,32 @@ export async function promptForToken({
|
|
|
236
253
|
trialOk = Boolean(trial.ok && verifyCliToken(trial.token, publicKeyB64).ok);
|
|
237
254
|
shown = trialOk ? shortToken(trial.token) : "";
|
|
238
255
|
hint = trialOk
|
|
239
|
-
? [`This is a free trial token:
|
|
256
|
+
? [`This is a free trial token: ${daysLeft(trial.endsAt)} days left on this computer, until ${shortDate(trial.endsAt)}.`, getOne, "Enter uses it — or delete it (one Backspace) and paste your own token."]
|
|
240
257
|
: [
|
|
241
258
|
...(trial.why === "used" && trial.endedAt ? [`The free trial on this computer ended on ${shortDate(trial.endedAt)}.`] : []),
|
|
242
259
|
...(trial.why === "unreachable" ? ["The gu service can't be reached right now — Enter on an empty line tries again."] : []),
|
|
243
260
|
getOne,
|
|
244
261
|
required ? "Paste it here, then Enter." : "Paste it here — or Enter to skip for now.",
|
|
245
262
|
];
|
|
263
|
+
if (why) {
|
|
264
|
+
const pricingUrl = `${String(site).replace(/^https?:\/\//, "")}/pricing`;
|
|
265
|
+
hint = [
|
|
266
|
+
// The reason (the title) already says what happened; a "trial ended on <date>" line beside it is
|
|
267
|
+
// noise — and wrong for a computer that went straight to a free account and never had one.
|
|
268
|
+
...hint.filter((l) => !/^For a free token|^Paste it here|^The free trial on this computer ended/.test(l)),
|
|
269
|
+
`Get Pro at ${pricingUrl} · Think this is wrong? ${SUPPORT_EMAIL}`,
|
|
270
|
+
...(trialOk ? [] : ["Paste a Pro token here, then Enter."]),
|
|
271
|
+
];
|
|
272
|
+
}
|
|
246
273
|
};
|
|
247
274
|
await offer();
|
|
248
|
-
log("This computer needs a gu token before the agent can start — one time only.", "title");
|
|
275
|
+
log(why || "This computer needs a gu token before the agent can start — one time only.", "title");
|
|
276
|
+
const entitled = async (token) => {
|
|
277
|
+
if (!accept) return true;
|
|
278
|
+
const r = await accept(token).catch(() => ({ ok: true })); // unknown = soft, like every other check here
|
|
279
|
+
if (!r.ok) log(`That token can't be used on this computer: ${r.reason}`, "warn");
|
|
280
|
+
return r.ok;
|
|
281
|
+
};
|
|
249
282
|
const ask1 = () => ask("Input token: ", { prefill: shown, hint, atomic: true, noCancel: required });
|
|
250
283
|
for (let round = 0; required ? round < maxRounds : round < 2; round++) {
|
|
251
284
|
const typed = String((await ask1()) ?? "").trim();
|
|
@@ -256,11 +289,13 @@ export async function promptForToken({
|
|
|
256
289
|
}
|
|
257
290
|
if (trialOk && (typed === shown || typed === trial.token)) {
|
|
258
291
|
await writeState(dir, { token: trial.token, lastAttemptAt: new Date().toISOString() });
|
|
259
|
-
|
|
292
|
+
if (!(await entitled(trial.token))) continue;
|
|
293
|
+
log(`✓ free trial token saved — ${daysLeft(trial.endsAt)} days left on this computer, until ${shortDate(trial.endsAt)}.`, "ok");
|
|
260
294
|
return { ok: true, kind: "trial" };
|
|
261
295
|
}
|
|
262
296
|
const saved = await saveTokenFromPaste(dir, typed, publicKeyB64);
|
|
263
297
|
if (saved.ok) {
|
|
298
|
+
if (!(await entitled(typed))) continue;
|
|
264
299
|
log("✓ token saved — it renews itself from now on.", "ok");
|
|
265
300
|
return { ok: true, kind: "pasted" };
|
|
266
301
|
}
|
|
@@ -435,6 +470,17 @@ export function barNotice({ token, publicKeyB64, mode, now = new Date(), attempt
|
|
|
435
470
|
const left = daysLeft(p.paidUntil, now);
|
|
436
471
|
return { kind: "trial", urgent: false, text: `trial · ${left} day${left === 1 ? "" : "s"} left · ${way}` };
|
|
437
472
|
}
|
|
473
|
+
// FREE TIME IS THE COMPUTER'S (#315): the days left on THIS computer, and — when more than one free
|
|
474
|
+
// account uses it — that they share them (W1). Yellow once it matters: used up, shared, or two weeks left.
|
|
475
|
+
if (p.plan === "free" && typeof p.freeUntil === "string") {
|
|
476
|
+
const left = daysLeft(p.freeUntil, now);
|
|
477
|
+
const shared = Number.isInteger(p.freeAccounts) && p.freeAccounts > 1;
|
|
478
|
+
if (left === 0) return { kind: "free-used", urgent: true, text: `free time on this computer is used up · Pro at ${pricingUrl}` };
|
|
479
|
+
const days = `${left} day${left === 1 ? "" : "s"}`;
|
|
480
|
+
return shared
|
|
481
|
+
? { kind: "free-shared", urgent: true, text: `More than one free account on this computer · they share ${days} of free time · Pro at ${pricingUrl}` }
|
|
482
|
+
: { kind: "free", urgent: left <= 14, text: `free · ${days} left on this computer · Pro at ${pricingUrl}` };
|
|
483
|
+
}
|
|
438
484
|
// SHORT AND CALM (#292: "make it simpler … seems so serious"): the fact, then the one thing to
|
|
439
485
|
// do. `urgent` is what the bar colours by — only these two need the user to act.
|
|
440
486
|
if (ended) return { kind: "plan-ended", urgent: true, text: `Pro ended ${shortDate(p.paidUntil)} · renew at ${pricingUrl}` };
|
|
@@ -550,7 +596,17 @@ export function tokenSummary(payload, now = new Date()) {
|
|
|
550
596
|
* TURNS — `gu login`, `gu token`, /model and every other command keep working, because the person has
|
|
551
597
|
* to be able to fix it from where they are.
|
|
552
598
|
*/
|
|
553
|
-
|
|
599
|
+
/** #315 O3: how long a token may go without being renewed online before gu asks to connect. */
|
|
600
|
+
export const MAX_OFFLINE_DAYS = { free: 7, trial: 7, pro: 30 };
|
|
601
|
+
/** #315 O2: how far the computer's clock may run behind the server's before a token reads as "from the future". */
|
|
602
|
+
export const CLOCK_SKEW_MS = 10 * 60_000;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* `deviceId` (#315 O1): THIS computer's hash, when the caller knows it. A free token checked on
|
|
606
|
+
* another computer — or never on any (a web-issued one) — says nothing about THIS computer's free
|
|
607
|
+
* time, so it needs one online check here first.
|
|
608
|
+
*/
|
|
609
|
+
export function enforcementDecision({ token, publicKeyB64, now = new Date(), site = DEFAULT_SITE, deviceId }) {
|
|
554
610
|
if (!publicKeyB64) return { enforced: false, allowed: true, why: "" };
|
|
555
611
|
const check = token ? verifyCliToken(token, publicKeyB64) : { ok: false, why: "missing" };
|
|
556
612
|
// An unreadable or missing token cannot say whether enforcement is on, and this must not be a way
|
|
@@ -564,17 +620,55 @@ export function enforcementDecision({ token, publicKeyB64, now = new Date(), sit
|
|
|
564
620
|
const pricingUrl = `${host}/pricing`;
|
|
565
621
|
const accountUrl = `${host}/account`;
|
|
566
622
|
const ended = p.paidUntil && Date.parse(p.paidUntil) <= now.getTime();
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
623
|
+
const blocked = (why) => ({ enforced: true, allowed: false, why });
|
|
624
|
+
// OFFLINE CANNOT UNLOCK ANYTHING (#315, the owner: "if the user cannot reach the internet or our
|
|
625
|
+
// API, it should still be blocked"). Three ways a token could otherwise outlive what it says:
|
|
626
|
+
// O2 — the clock set back: a token "issued in the future" is not trusted;
|
|
627
|
+
const issued = Date.parse(p.issuedAt);
|
|
628
|
+
if (issued - now.getTime() > CLOCK_SKEW_MS) {
|
|
629
|
+
return blocked(
|
|
630
|
+
`This computer's clock is behind (your gu token was issued ${shortDate(p.issuedAt)}). ` +
|
|
631
|
+
"Set the correct date and time, then start gu again."
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
// O3 — staying offline: a token must have been renewed online recently (Pro gets longer);
|
|
635
|
+
const paid = p.plan === "pro" && !ended;
|
|
636
|
+
const maxDays = paid ? MAX_OFFLINE_DAYS.pro : p.plan === "trial" ? MAX_OFFLINE_DAYS.trial : MAX_OFFLINE_DAYS.free;
|
|
637
|
+
if (now.getTime() - issued > maxDays * 86_400_000) {
|
|
638
|
+
return blocked(
|
|
639
|
+
`gu needs to check your token online — it was last checked ${shortDate(p.issuedAt)}. ` +
|
|
640
|
+
"Connect to the internet and start gu again."
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
// O1 — a free token not yet checked on THIS computer (see the free branch below).
|
|
644
|
+
// THE TRIAL (#305): entitled for its days; after them a free account gives THIS computer 3 months.
|
|
645
|
+
if (p.plan === "trial") {
|
|
646
|
+
if (ended) {
|
|
647
|
+
return blocked(
|
|
648
|
+
`The free trial on this computer ended on ${shortDate(p.paidUntil)}. A free gu account gives it 3 months ` +
|
|
649
|
+
`more — get your token at ${accountUrl} — or go Pro at ${pricingUrl}.`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
} else if (p.plan === "pro" && !ended) {
|
|
653
|
+
// Pro: the computer-count check below.
|
|
654
|
+
} else if (p.plan === "pro" && ended && p.freeUntil === undefined) {
|
|
655
|
+
// An old Pro token, issued while paid and not renewed since: it cannot know this computer's free time.
|
|
656
|
+
return blocked(`Your gu Pro plan ended on ${shortDate(p.paidUntil)}. Renew at ${pricingUrl} to keep using the agent.`);
|
|
657
|
+
} else {
|
|
658
|
+
// FREE (#315): the time belongs to the COMPUTER — 3 months, shared by every free account on it.
|
|
659
|
+
// O1: the token must have been renewed FROM THIS COMPUTER — a web-issued token (no device) or one
|
|
660
|
+
// copied from another computer says nothing about this one's free time. null = the owner marked
|
|
661
|
+
// this computer OK. undefined on a token renewed here = a server with no free-time store: allowed.
|
|
662
|
+
const notHere = !p.device || (deviceId && p.device !== deviceId);
|
|
663
|
+
if (notHere) return blocked("Connect to the internet once to activate this token on this computer.");
|
|
664
|
+
const fu = p.freeUntil;
|
|
665
|
+
if (fu !== undefined && fu !== null && Date.parse(fu) <= now.getTime()) {
|
|
666
|
+
return blocked(
|
|
667
|
+
Number.isInteger(p.freeAccounts) && p.freeAccounts > 1
|
|
668
|
+
? FREE_USED_UP_MANY
|
|
669
|
+
: `The free time on this computer ended on ${shortDate(fu)} — enter a Pro token to continue.`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
578
672
|
}
|
|
579
673
|
if (Number.isInteger(p.activeDevices) && p.activeDevices > p.deviceLimit) {
|
|
580
674
|
return {
|
package/gu-repl.mjs
CHANGED
|
@@ -2280,9 +2280,10 @@ async function refreshCliToken() {
|
|
|
2280
2280
|
}
|
|
2281
2281
|
if (!licenceDecided) {
|
|
2282
2282
|
licenceDecided = true;
|
|
2283
|
-
const d = ct.enforcementDecision({ token: state.token, publicKeyB64: pk, site });
|
|
2283
|
+
const d = ct.enforcementDecision({ token: state.token, publicKeyB64: pk, site, deviceId: await thisDeviceId(ct) });
|
|
2284
2284
|
licenceBlock = d.enforced && !d.allowed ? d.why : null;
|
|
2285
|
-
|
|
2285
|
+
// Private mode at a terminal: the start-up token row says it, and offers the way on (#315).
|
|
2286
|
+
if (licenceBlock && !(isClientExpert() && process.stdin.isTTY)) {
|
|
2286
2287
|
console.log("");
|
|
2287
2288
|
console.log(GUTTER + orange("gu cannot run turns on this account.") );
|
|
2288
2289
|
console.log(GUTTER + dim(" " + licenceBlock));
|
|
@@ -10008,32 +10009,98 @@ async function ensureTokenForTurn() {
|
|
|
10008
10009
|
* (Esc does nothing), one Backspace clears the default for a paste, and a valid token turns the
|
|
10009
10010
|
* row back into ">> ". → true once a token is saved.
|
|
10010
10011
|
*/
|
|
10011
|
-
async function askForCliToken(ct, dir) {
|
|
10012
|
+
async function askForCliToken(ct, dir, { why = "" } = {}) {
|
|
10012
10013
|
console.log("");
|
|
10013
|
-
const
|
|
10014
|
+
const hostedBase = await tokenHostedBase();
|
|
10015
|
+
const pk = ct.publicKey();
|
|
10016
|
+
const site = ct.siteUrl();
|
|
10017
|
+
const deviceId = await thisDeviceId(ct);
|
|
10014
10018
|
const r = await ct.promptForToken({
|
|
10015
10019
|
ask: (label, opts) => ask(label, opts),
|
|
10016
10020
|
dir,
|
|
10017
10021
|
required: true,
|
|
10018
|
-
hostedBase
|
|
10019
|
-
|
|
10020
|
-
|
|
10022
|
+
hostedBase,
|
|
10023
|
+
why,
|
|
10024
|
+
// #315: a token is only good if THIS computer may use it — renew it from here (the server then
|
|
10025
|
+
// says how much free time the computer has), save what came back, and run the licence rule.
|
|
10026
|
+
accept: async (token) => {
|
|
10027
|
+
const fresh = await renewFromThisComputer(ct, token, hostedBase);
|
|
10028
|
+
const use = fresh ?? token;
|
|
10029
|
+
if (fresh) await ct.writeState(dir, { token: fresh, lastAttemptAt: new Date().toISOString() });
|
|
10030
|
+
const d = ct.enforcementDecision({ token: use, publicKeyB64: pk, site, deviceId });
|
|
10031
|
+
return d.allowed ? { ok: true } : { ok: false, reason: d.why };
|
|
10032
|
+
},
|
|
10033
|
+
// Word-wrapped to the window: a long reason ("Too many free accounts used on this computer…")
|
|
10034
|
+
// otherwise breaks mid-word at 80 columns.
|
|
10035
|
+
log: (l, kind) => {
|
|
10036
|
+
const paint = kind === "title" || kind === "warn" ? yellow : kind === "ok" ? accent : dim;
|
|
10037
|
+
for (const row of questionHintRows([l], { width: process.stdout.columns || 80, gutter: GUTTER, paint })) console.log(row);
|
|
10038
|
+
},
|
|
10021
10039
|
});
|
|
10022
10040
|
console.log("");
|
|
10023
|
-
|
|
10041
|
+
if (r.ok) {
|
|
10042
|
+
licenceBlock = null; // the row only lets an entitled token through
|
|
10043
|
+
licenceDecided = true;
|
|
10044
|
+
}
|
|
10045
|
+
void refreshCliToken(); // the bar's line, now that there is a token to describe
|
|
10024
10046
|
return r.ok;
|
|
10025
10047
|
}
|
|
10026
10048
|
|
|
10027
|
-
/**
|
|
10049
|
+
/** This computer's hash (#315 O1) — the same id every renewal sends — or undefined when unknown. */
|
|
10050
|
+
const thisDeviceId = async (ct) => (await ct.thisDevice({ instanceId: myInstanceId || "" }).catch(() => null))?.id;
|
|
10051
|
+
|
|
10052
|
+
const tokenHostedBase = async () => {
|
|
10053
|
+
const { DEFAULT_API_BASE } = await import("./device-login.mjs");
|
|
10054
|
+
return String(process.env.GU_TOKEN_API_BASE ?? "").trim() || DEFAULT_API_BASE;
|
|
10055
|
+
};
|
|
10056
|
+
|
|
10057
|
+
/** A renewed token for THIS computer (so the server counts it and says its free time), or null. */
|
|
10058
|
+
async function renewFromThisComputer(ct, token, hostedBase) {
|
|
10059
|
+
const pk = ct.publicKey();
|
|
10060
|
+
const device = await ct.thisDevice({ instanceId: myInstanceId || "" }).catch(() => null);
|
|
10061
|
+
const r = await ct.renewToken({ mode: "expert", hostedBase, token, device }).catch(() => ({ ok: false }));
|
|
10062
|
+
return r.ok && ct.verifyCliToken(r.token, pk).ok ? r.token : null;
|
|
10063
|
+
}
|
|
10064
|
+
|
|
10065
|
+
/**
|
|
10066
|
+
* At the first ">>" in private mode (#78, #315): no usable token → ask for one; a token this
|
|
10067
|
+
* computer may no longer use (its free time is used up, the trial ended — enforcement is on by
|
|
10068
|
+
* default) → the same row, saying why, and only an ENTITLED token leads to ">>".
|
|
10069
|
+
*/
|
|
10028
10070
|
async function askTokenAtStart() {
|
|
10029
10071
|
if (!isClientExpert() || !process.stdin.isTTY) return;
|
|
10030
10072
|
const ct = await import("./cli-token.mjs");
|
|
10031
|
-
|
|
10073
|
+
const pk = ct.publicKey();
|
|
10074
|
+
if (!pk) return;
|
|
10075
|
+
// The background refresh started at launch renews and WRITES the token: let it finish, or it
|
|
10076
|
+
// could overwrite a token pasted here with the old one — and its answer is the freshest anyway.
|
|
10077
|
+
await startupTokenRefresh?.catch(() => undefined);
|
|
10032
10078
|
const dir = join(homedir(), ".gonext");
|
|
10033
10079
|
const state = await ct.readState(dir);
|
|
10034
|
-
if (
|
|
10035
|
-
|
|
10080
|
+
if (ct.turnNeedsToken({ privateMode: true, token: state.token })) {
|
|
10081
|
+
await askForCliToken(ct, dir);
|
|
10082
|
+
return;
|
|
10083
|
+
}
|
|
10084
|
+
const site = ct.siteUrl();
|
|
10085
|
+
const deviceId = await thisDeviceId(ct);
|
|
10086
|
+
let d = ct.enforcementDecision({ token: state.token, publicKeyB64: pk, site, deviceId });
|
|
10087
|
+
if (d.allowed) return;
|
|
10088
|
+
// Before locking, ask the server once more from THIS computer: a plan paid a minute ago, or the
|
|
10089
|
+
// owner's "Mark OK", arrives in a renewed token — never lock on a stale one.
|
|
10090
|
+
const fresh = await renewFromThisComputer(ct, state.token, await tokenHostedBase());
|
|
10091
|
+
if (fresh) {
|
|
10092
|
+
await ct.writeState(dir, { token: fresh, lastAttemptAt: new Date().toISOString() });
|
|
10093
|
+
d = ct.enforcementDecision({ token: fresh, publicKeyB64: pk, site, deviceId });
|
|
10094
|
+
if (d.allowed) {
|
|
10095
|
+
licenceBlock = null;
|
|
10096
|
+
licenceDecided = true;
|
|
10097
|
+
return;
|
|
10098
|
+
}
|
|
10099
|
+
}
|
|
10100
|
+
await askForCliToken(ct, dir, { why: d.why });
|
|
10036
10101
|
}
|
|
10102
|
+
/** The token refresh started at launch — askTokenAtStart waits for it (see there). */
|
|
10103
|
+
let startupTokenRefresh = null;
|
|
10037
10104
|
|
|
10038
10105
|
async function runAgentTurn(history, { attachJobId = "", ownJob = false } = {}) {
|
|
10039
10106
|
// #238: when the licence is ENFORCED and this account is not entitled, no turn is sent. Decided at
|
|
@@ -12122,7 +12189,7 @@ async function main() {
|
|
|
12122
12189
|
void refreshAvailableUpdates();
|
|
12123
12190
|
setInterval(() => void refreshAvailableUpdates(), UPDATE_CHECK_MS).unref();
|
|
12124
12191
|
// The gu token (#232): same arrangement — background, never delays the prompt, unref'd.
|
|
12125
|
-
|
|
12192
|
+
startupTokenRefresh = refreshCliToken();
|
|
12126
12193
|
setInterval(() => void refreshCliToken(), TOKEN_TICK_MS).unref();
|
|
12127
12194
|
// …and the other-agents count (task #195). Slower than the turn's own poll and skipped while
|
|
12128
12195
|
// a turn runs; unref'd so a pending timer is never the reason gu refuses to exit.
|
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.778",
|
|
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",
|