@tiens.nguyen/gu-cli 1.0.862 → 1.0.863
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 +21 -6
- package/gu-cli.mjs +83 -67
- package/gu-repl.mjs +10 -5
- package/install-path.mjs +1 -1
- package/package.json +1 -1
- package/powershell-alias.mjs +62 -4
package/cli-token.mjs
CHANGED
|
@@ -275,6 +275,8 @@ export async function promptForToken({
|
|
|
275
275
|
why = "",
|
|
276
276
|
// #209: the block is the computer LIMIT (not a missing/expired plan) — a Pro token is not the way out.
|
|
277
277
|
overLimit = false,
|
|
278
|
+
// #219: with overLimit, the ONE short yellow line (the long `why` is shown only on request).
|
|
279
|
+
short = "",
|
|
278
280
|
// #315: a saved token must also be ENTITLED on this computer. `accept(token)` → { ok } | { ok:false, reason }
|
|
279
281
|
// — the REPL renews it from this computer (to learn its free time) and runs the licence rule.
|
|
280
282
|
accept = null,
|
|
@@ -283,6 +285,7 @@ export async function promptForToken({
|
|
|
283
285
|
const dev = device === undefined ? await trialDevice().catch(() => null) : device;
|
|
284
286
|
const getOne = `For a free token for 1 computer, log in at ${accountUrl} and create one — or buy Pro there for more computers.`;
|
|
285
287
|
let trial, trialOk, shown, hint;
|
|
288
|
+
let details = []; // #219: the over-limit detail, printed only when the person asks ("?" or an empty Enter)
|
|
286
289
|
const offer = async () => {
|
|
287
290
|
trial = dev ? await claim({ hostedBase, device: dev }) : { ok: false, why: "no-device" };
|
|
288
291
|
trialOk = Boolean(trial.ok && verifyCliToken(trial.token, publicKeyB64).ok);
|
|
@@ -305,12 +308,16 @@ export async function promptForToken({
|
|
|
305
308
|
];
|
|
306
309
|
if (why) {
|
|
307
310
|
const pricingUrl = `${String(site).replace(/^https?:\/\//, "")}/pricing`;
|
|
311
|
+
if (overLimit) {
|
|
312
|
+
details = [
|
|
313
|
+
why,
|
|
314
|
+
`Remove the computers you no longer use at ${accountUrl}, then start gu again — or Pro allows more computers: ${pricingUrl}`,
|
|
315
|
+
`Think this is wrong? ${SUPPORT_EMAIL}`,
|
|
316
|
+
required ? "/exit or Ctrl+C quits gu." : "Enter to skip for now.",
|
|
317
|
+
];
|
|
318
|
+
}
|
|
308
319
|
hint = overLimit
|
|
309
|
-
? [
|
|
310
|
-
`Remove the computers you no longer use at ${accountUrl}, then start gu again — or Pro allows more computers: ${pricingUrl}`,
|
|
311
|
-
`Think this is wrong? ${SUPPORT_EMAIL}`,
|
|
312
|
-
required ? "/exit or Ctrl+C quits gu." : "Enter to skip for now.",
|
|
313
|
-
]
|
|
320
|
+
? [] // #219: one yellow line only — the rest is behind "?"
|
|
314
321
|
: [
|
|
315
322
|
// The reason (the title) already says what happened; a "trial ended on <date>" line beside it is
|
|
316
323
|
// noise — and wrong for a computer that went straight to a free account and never had one.
|
|
@@ -321,7 +328,7 @@ export async function promptForToken({
|
|
|
321
328
|
}
|
|
322
329
|
};
|
|
323
330
|
await offer();
|
|
324
|
-
log(why || "This computer needs a gu token before the agent can start — one time only.", "title");
|
|
331
|
+
log(overLimit && why ? `${short || why} Type ? for details.` : why || "This computer needs a gu token before the agent can start — one time only.", "title");
|
|
325
332
|
const entitled = async (token) => {
|
|
326
333
|
if (!accept) return true;
|
|
327
334
|
const r = await accept(token).catch(() => ({ ok: true })); // unknown = soft, like every other check here
|
|
@@ -334,6 +341,12 @@ export async function promptForToken({
|
|
|
334
341
|
// A way OUT of a required question (task #17): "/exit" is not a token, and treating it as one
|
|
335
342
|
// ("That token was not saved…" and the same question again) left no way to leave gu at all.
|
|
336
343
|
if (/^\/(exit|quit)$/i.test(typed)) return { ok: false, why: "quit" };
|
|
344
|
+
// #219: the over-limit detail, on request only — "?" or an empty Enter, then the same question again.
|
|
345
|
+
if (details.length && (typed === "?" || (!typed && required))) {
|
|
346
|
+
for (const l of details) log(l, "info");
|
|
347
|
+
if (!required) round--; // "?" does not use up one of the two tries
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
337
350
|
if (!typed) {
|
|
338
351
|
if (!required) return { ok: false, why: "skipped" };
|
|
339
352
|
if (!trialOk) await offer(); // no default: maybe the service is back, or the trial exists now
|
|
@@ -754,6 +767,8 @@ export function enforcementDecision({ token, publicKeyB64, now = new Date(), sit
|
|
|
754
767
|
enforced: true,
|
|
755
768
|
allowed: false,
|
|
756
769
|
reason: "over-limit",
|
|
770
|
+
// #219: the ONE short line shown first (the long `why` is the detail, shown on request).
|
|
771
|
+
short: `${planName(p.plan)} allows ${computers(p.deviceLimit)}; this account is on ${p.activeDevices}.`,
|
|
757
772
|
why:
|
|
758
773
|
`This account has used gu on ${computers(p.activeDevices)} in the last 30 days, and the ` +
|
|
759
774
|
`${planName(p.plan)} allows ${p.deviceLimit}. Sign out of one, or remove it at ${accountUrl}.`,
|
package/gu-cli.mjs
CHANGED
|
@@ -1159,81 +1159,97 @@ if (args[0] === "fix-powershell") {
|
|
|
1159
1159
|
process.exit(0);
|
|
1160
1160
|
}
|
|
1161
1161
|
const { execFileSync } = await import("node:child_process");
|
|
1162
|
-
const
|
|
1163
|
-
execFileSync(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
const
|
|
1173
|
-
const current = existsSync(profilePath) ? readFileSync(profilePath, "utf8") : "";
|
|
1174
|
-
const next = pa.profileWithFix(current);
|
|
1175
|
-
// ASKED IN EVERY PATH, because "already fixed" was reported on the very machine whose policy
|
|
1176
|
-
// stops the profile loading — the same "said Done, changed nothing" mistake one branch along.
|
|
1177
|
-
const blockedBy = () => {
|
|
1162
|
+
const runPs = (host, script) =>
|
|
1163
|
+
execFileSync(host, ["-NoProfile", "-Command", script], {
|
|
1164
|
+
encoding: "utf8",
|
|
1165
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1166
|
+
}).trim();
|
|
1167
|
+
|
|
1168
|
+
// EVERY installed PowerShell has its OWN profile: Windows PowerShell 5.1 (`powershell`) and
|
|
1169
|
+
// PowerShell 7 (`pwsh`, VS Code's usual terminal) read different files. Asking only
|
|
1170
|
+
// `powershell` wrote the fix where a pwsh terminal never looked.
|
|
1171
|
+
const found = [];
|
|
1172
|
+
for (const host of pa.POWERSHELL_HOSTS) {
|
|
1178
1173
|
try {
|
|
1179
|
-
|
|
1174
|
+
found.push({ host, profilePath: runPs(host, "$PROFILE.CurrentUserAllHosts") });
|
|
1180
1175
|
} catch {
|
|
1181
|
-
|
|
1176
|
+
/* that PowerShell is not installed */
|
|
1182
1177
|
}
|
|
1183
|
-
};
|
|
1184
|
-
if (next === null) {
|
|
1185
|
-
console.log(` Already fixed — ${profilePath} removes the alias.`);
|
|
1186
|
-
const blocked = blockedBy();
|
|
1187
|
-
if (blocked) {
|
|
1188
|
-
console.log("");
|
|
1189
|
-
for (const l of pa.policyAdvice(blocked)) console.log(" " + l);
|
|
1190
|
-
process.exit(1);
|
|
1191
|
-
}
|
|
1192
|
-
console.log(" Open a NEW PowerShell window for it to take effect.");
|
|
1193
|
-
process.exit(0);
|
|
1194
1178
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (blocked) for (const l of pa.policyAdvice(blocked)) console.log(" " + l);
|
|
1199
|
-
console.log(` Run: gu-cli fix-powershell (edits ${profilePath})`);
|
|
1200
|
-
process.exit(1);
|
|
1201
|
-
}
|
|
1202
|
-
console.log("");
|
|
1203
|
-
for (const l of pa.shadowedAdvice()) console.log(" " + l);
|
|
1204
|
-
console.log("");
|
|
1205
|
-
console.log(` This appends ONE line to ${profilePath}:`);
|
|
1206
|
-
console.log(" " + pa.PROFILE_LINE);
|
|
1207
|
-
const rlp = (await import("node:readline")).createInterface({
|
|
1208
|
-
input: process.stdin,
|
|
1209
|
-
output: process.stdout,
|
|
1210
|
-
});
|
|
1211
|
-
const answer = await new Promise((r) => rlp.question("\n Append it? [y/N]: ", r));
|
|
1212
|
-
rlp.close();
|
|
1213
|
-
if (!/^y(es)?$/i.test(String(answer).trim())) {
|
|
1214
|
-
console.log(" " + pa.MEANWHILE);
|
|
1215
|
-
process.exit(0);
|
|
1216
|
-
}
|
|
1217
|
-
try {
|
|
1218
|
-
const dir = profilePath.replace(/[\\/][^\\/]*$/, "");
|
|
1219
|
-
if (dir && !existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1220
|
-
writeFileSync(profilePath, next, "utf8");
|
|
1221
|
-
} catch (e) {
|
|
1222
|
-
console.error(` Could not write ${profilePath}: ${e.message}`);
|
|
1179
|
+
const targets = pa.uniqueProfiles(found);
|
|
1180
|
+
if (targets.length === 0) {
|
|
1181
|
+
console.error(" Could not ask PowerShell where your profile is (neither `powershell` nor `pwsh` answered).");
|
|
1223
1182
|
process.exit(1);
|
|
1224
1183
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1184
|
+
const { readFileSync, writeFileSync, mkdirSync, existsSync } = await import("node:fs");
|
|
1185
|
+
const checkOnly = args.includes("--check");
|
|
1186
|
+
let rlp = null;
|
|
1187
|
+
let worst = 0;
|
|
1188
|
+
|
|
1189
|
+
for (const { host, profilePath } of targets) {
|
|
1231
1190
|
console.log("");
|
|
1232
|
-
|
|
1233
|
-
|
|
1191
|
+
console.log(` ${pa.hostLabel(host)} — ${profilePath}`);
|
|
1192
|
+
const current = existsSync(profilePath) ? readFileSync(profilePath, "utf8") : "";
|
|
1193
|
+
const next = pa.profileWithFix(current);
|
|
1194
|
+
// ASKED IN EVERY PATH, because "already fixed" was reported on the very machine whose policy
|
|
1195
|
+
// stops the profile loading — the same "said Done, changed nothing" mistake one branch along.
|
|
1196
|
+
const blockedBy = () => {
|
|
1197
|
+
try {
|
|
1198
|
+
return pa.profileBlockedBy(runPs(host, "Get-ExecutionPolicy"));
|
|
1199
|
+
} catch {
|
|
1200
|
+
return "";
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
if (next === null) {
|
|
1204
|
+
console.log(" Already fixed — the profile removes the alias.");
|
|
1205
|
+
const blocked = blockedBy();
|
|
1206
|
+
if (blocked) {
|
|
1207
|
+
for (const l of pa.policyAdvice(blocked)) console.log(" " + l);
|
|
1208
|
+
worst = 1;
|
|
1209
|
+
}
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (checkOnly) {
|
|
1213
|
+
console.log(" Not fixed yet. `gu` in this PowerShell is still Get-Unique.");
|
|
1214
|
+
const blocked = blockedBy();
|
|
1215
|
+
if (blocked) for (const l of pa.policyAdvice(blocked)) console.log(" " + l);
|
|
1216
|
+
worst = 1;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
for (const l of pa.shadowedAdvice()) console.log(" " + l);
|
|
1220
|
+
console.log(` This appends ONE line to ${profilePath}:`);
|
|
1221
|
+
console.log(" " + pa.PROFILE_LINE);
|
|
1222
|
+
rlp ??= (await import("node:readline")).createInterface({
|
|
1223
|
+
input: process.stdin,
|
|
1224
|
+
output: process.stdout,
|
|
1225
|
+
});
|
|
1226
|
+
const answer = await new Promise((r) => rlp.question("\n Append it? [y/N]: ", r));
|
|
1227
|
+
if (!/^y(es)?$/i.test(String(answer).trim())) {
|
|
1228
|
+
console.log(" " + pa.MEANWHILE);
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
const dir = profilePath.replace(/[\\/][^\\/]*$/, "");
|
|
1233
|
+
if (dir && !existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1234
|
+
writeFileSync(profilePath, next, "utf8");
|
|
1235
|
+
} catch (e) {
|
|
1236
|
+
console.error(` Could not write ${profilePath}: ${e.message}`);
|
|
1237
|
+
worst = 1;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
console.log(` Done — ${profilePath} updated.`);
|
|
1241
|
+
// VERIFY THE FIX CAN ACTUALLY RUN. Writing the line is not the same as PowerShell loading it:
|
|
1242
|
+
// under Restricted or AllSigned the profile is a script it refuses to execute, and the first
|
|
1243
|
+
// real run of this said "Done" to a machine where `gu` stayed shadowed.
|
|
1244
|
+
const policy = blockedBy();
|
|
1245
|
+
if (policy) {
|
|
1246
|
+
for (const l of pa.policyAdvice(policy)) console.log(" " + l);
|
|
1247
|
+
worst = 1;
|
|
1248
|
+
}
|
|
1234
1249
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1250
|
+
rlp?.close();
|
|
1251
|
+
if (worst === 0 && !checkOnly) console.log("\n Open a NEW PowerShell window (in VS Code too), then `gu` runs this CLI.");
|
|
1252
|
+
process.exit(worst);
|
|
1237
1253
|
}
|
|
1238
1254
|
|
|
1239
1255
|
if (args[0] === "support") {
|
package/gu-repl.mjs
CHANGED
|
@@ -2488,6 +2488,8 @@ async function refreshAvailableUpdates() {
|
|
|
2488
2488
|
* paid to see the warning go.
|
|
2489
2489
|
*/
|
|
2490
2490
|
let tokenNotice = null;
|
|
2491
|
+
/** #219: true while the token question is on screen — the bar must not repeat its warning. */
|
|
2492
|
+
let tokenQuestionOpen = false;
|
|
2491
2493
|
let tokenBusy = false;
|
|
2492
2494
|
let startupRenewDone = false;
|
|
2493
2495
|
/**
|
|
@@ -3008,7 +3010,7 @@ function zoneNotice(width) {
|
|
|
3008
3010
|
}
|
|
3009
3011
|
if (priceUpdate) items.push({ key: "prices", clickable: true, paint: accent, text: `${priceUpdateText()} (click)` });
|
|
3010
3012
|
if (limitsUpdate) items.push({ key: "limits", clickable: true, paint: accent, text: `${limitsUpdate.label} (click)` });
|
|
3011
|
-
if (tokenNotice?.text) items.push({ key: "token", paint: tokenNotice.urgent ? yellow : dim, text: tokenNotice.text });
|
|
3013
|
+
if (tokenNotice?.text && !(tokenQuestionOpen && tokenNotice.kind === "over-limit")) items.push({ key: "token", paint: tokenNotice.urgent ? yellow : dim, text: tokenNotice.text });
|
|
3012
3014
|
const agents = agentsIndicator(otherAgents);
|
|
3013
3015
|
if (agents) items.push({ key: "agents", clickable: true, paint: accent, text: `${agents} running (click)` });
|
|
3014
3016
|
const shells = shellsNotice(shellEntries, { cwd: process.cwd() });
|
|
@@ -11780,13 +11782,15 @@ async function ensureTokenForTurn() {
|
|
|
11780
11782
|
* (Esc does nothing), one Backspace clears the default for a paste, and a valid token turns the
|
|
11781
11783
|
* row back into ">> ". → true once a token is saved.
|
|
11782
11784
|
*/
|
|
11783
|
-
async function askForCliToken(ct, dir, { why = "", overLimit = false } = {}) {
|
|
11785
|
+
async function askForCliToken(ct, dir, { why = "", overLimit = false, short = "" } = {}) {
|
|
11784
11786
|
console.log("");
|
|
11785
11787
|
tokenPromptActive = true; // Ctrl+C now quits gu (see onInterrupt) — task #17
|
|
11788
|
+
tokenQuestionOpen = true;
|
|
11786
11789
|
try {
|
|
11787
|
-
return await askForCliTokenInner(ct, dir, why, overLimit);
|
|
11790
|
+
return await askForCliTokenInner(ct, dir, why, overLimit, short);
|
|
11788
11791
|
} finally {
|
|
11789
11792
|
tokenPromptActive = false;
|
|
11793
|
+
tokenQuestionOpen = false;
|
|
11790
11794
|
}
|
|
11791
11795
|
}
|
|
11792
11796
|
|
|
@@ -11804,7 +11808,7 @@ function quitFromTokenPrompt() {
|
|
|
11804
11808
|
process.exit(0);
|
|
11805
11809
|
}
|
|
11806
11810
|
|
|
11807
|
-
async function askForCliTokenInner(ct, dir, why, overLimit = false) {
|
|
11811
|
+
async function askForCliTokenInner(ct, dir, why, overLimit = false, short = "") {
|
|
11808
11812
|
const hostedBase = await tokenHostedBase();
|
|
11809
11813
|
const pk = ct.publicKey();
|
|
11810
11814
|
const site = ct.siteUrl();
|
|
@@ -11816,6 +11820,7 @@ async function askForCliTokenInner(ct, dir, why, overLimit = false) {
|
|
|
11816
11820
|
hostedBase,
|
|
11817
11821
|
why,
|
|
11818
11822
|
overLimit, // #209: the computer limit, not a missing plan — no "paste a Pro token"
|
|
11823
|
+
short, // #219: the one short yellow line; the rest is behind "?"
|
|
11819
11824
|
// #315: a token is only good if THIS computer may use it — renew it from here (the server then
|
|
11820
11825
|
// says how much free time the computer has), save what came back, and run the licence rule.
|
|
11821
11826
|
accept: async (token) => {
|
|
@@ -11893,7 +11898,7 @@ async function askTokenAtStart() {
|
|
|
11893
11898
|
return;
|
|
11894
11899
|
}
|
|
11895
11900
|
}
|
|
11896
|
-
await askForCliToken(ct, dir, { why: d.why, overLimit: d.reason === "over-limit" });
|
|
11901
|
+
await askForCliToken(ct, dir, { why: d.why, overLimit: d.reason === "over-limit", short: d.short ?? "" });
|
|
11897
11902
|
}
|
|
11898
11903
|
/** The token refresh started at launch — askTokenAtStart waits for it (see there). */
|
|
11899
11904
|
let startupTokenRefresh = null;
|
package/install-path.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* - NEVER fail the install. Every path through this file ends in exit 0; npm treats a
|
|
22
22
|
* postinstall failure as a failed install and the user gets nothing.
|
|
23
23
|
* - NEVER run on Windows (PATH there lives in the registry, and `gu`'s PowerShell
|
|
24
|
-
* alias problem is
|
|
24
|
+
* alias problem is install.ps1's job (asks once) and `gu-cli fix-powershell`'s, both interactive).
|
|
25
25
|
* - NEVER touch an rc file twice: the marker pair makes re-runs no-ops.
|
|
26
26
|
* - Under `sudo npm i -g`, write to the REAL user's home (SUDO_USER) and chown the
|
|
27
27
|
* file back, or the user's next login shell reads a root-owned rc file.
|
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.863",
|
|
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/powershell-alias.mjs
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
* an alias beats every executable and every function we could ship. The only remedies are to
|
|
10
10
|
* remove the alias from the user's profile, or to be called something else.
|
|
11
11
|
*
|
|
12
|
-
* So this offers the removal, EXPLICITLY and never
|
|
13
|
-
* a CLI that edits it
|
|
14
|
-
* asks,
|
|
12
|
+
* So this offers the removal, EXPLICITLY and never silently. A profile is the user's file;
|
|
13
|
+
* a CLI that edits it unannounced is a CLI that surprises people. The Windows installer
|
|
14
|
+
* (web-v2/public/install.ps1) asks ONCE, default Yes, for every PowerShell on the machine, so a
|
|
15
|
+
* new user never meets the alias; `gu-cli fix-powershell` is the same fix for an existing install
|
|
16
|
+
* (it asks, shows the line, and appends it — `--check` just reports). Both write the ALL-HOSTS
|
|
17
|
+
* profile, which VS Code's terminal reads too.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
/** The line appended to the profile. `-Force` because the built-in alias is read-only. */
|
|
@@ -29,6 +32,61 @@ export const PROFILE_LINE =
|
|
|
29
32
|
*/
|
|
30
33
|
export const MEANWHILE = "Left alone. `gu-cli` and `gu.cmd` both run this program in the meantime.";
|
|
31
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The PowerShell hosts that can be installed side by side on Windows 11.
|
|
37
|
+
* `powershell` = Windows PowerShell 5.1 (built in); `pwsh` = PowerShell 7 (the default in many
|
|
38
|
+
* VS Code terminals). Each has ITS OWN profile and execution policy — fixing one leaves the other
|
|
39
|
+
* shadowed (issue #218).
|
|
40
|
+
*/
|
|
41
|
+
export const POWERSHELL_HOSTS = ["powershell", "pwsh"];
|
|
42
|
+
|
|
43
|
+
/** The one PowerShell command that reports where a host's profiles are and its policy (3 lines). */
|
|
44
|
+
export const HOST_PROBE =
|
|
45
|
+
"$PROFILE.CurrentUserAllHosts; $PROFILE.CurrentUserCurrentHost; Get-ExecutionPolicy";
|
|
46
|
+
|
|
47
|
+
/** Parse HOST_PROBE output: { allHosts, currentHost, policy } (empty strings when missing). */
|
|
48
|
+
export function parseHostProbe(out) {
|
|
49
|
+
const [allHosts = "", currentHost = "", policy = ""] = String(out ?? "")
|
|
50
|
+
.split(/\r?\n/)
|
|
51
|
+
.map((s) => s.trim())
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
return { allHosts, currentHost, policy };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Is ONE host fixed, and can its profile run? Fixed if either of its profile files (all-hosts or
|
|
58
|
+
* this-host, the latter where older versions wrote) already drops the alias.
|
|
59
|
+
*/
|
|
60
|
+
export function hostStatus({ allHostsText, currentHostText, policy }) {
|
|
61
|
+
return {
|
|
62
|
+
fixed: alreadyFixed(allHostsText) || alreadyFixed(currentHostText),
|
|
63
|
+
blocked: profileBlockedBy(policy),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A human name for a host command. */
|
|
68
|
+
export function hostLabel(host) {
|
|
69
|
+
return host === "pwsh" ? "PowerShell 7 (pwsh)" : "Windows PowerShell";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The profiles to fix, one per distinct file. `found` is [{host, profilePath}] in host order;
|
|
74
|
+
* entries with an empty path are dropped, and two hosts that share a file are fixed once.
|
|
75
|
+
*/
|
|
76
|
+
export function uniqueProfiles(found) {
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const f of found ?? []) {
|
|
80
|
+
const p = String(f?.profilePath ?? "").trim();
|
|
81
|
+
if (!p) continue;
|
|
82
|
+
const key = p.toLowerCase();
|
|
83
|
+
if (seen.has(key)) continue;
|
|
84
|
+
seen.add(key);
|
|
85
|
+
out.push({ host: f.host, profilePath: p });
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
32
90
|
/** True when this machine can even have the problem. */
|
|
33
91
|
export function affectsThisMachine(platform = process.platform) {
|
|
34
92
|
return platform === "win32";
|
|
@@ -44,7 +102,7 @@ export function shadowedAdvice() {
|
|
|
44
102
|
return [
|
|
45
103
|
"PowerShell has a built-in alias `gu` (Get-Unique) that wins over this CLI.",
|
|
46
104
|
"Right now: use `gu-cli` — or `gu.cmd`, which is this program.",
|
|
47
|
-
"Permanently: run `gu-cli fix-powershell` to drop the alias from your PowerShell profile.",
|
|
105
|
+
"Permanently: run `gu-cli fix-powershell` to drop the alias from your PowerShell profile(s) —\n Windows PowerShell and PowerShell 7 (pwsh, the VS Code default) each have their own.",
|
|
48
106
|
"cmd.exe is unaffected; `gu` works there already.",
|
|
49
107
|
];
|
|
50
108
|
}
|