@tiens.nguyen/gu-cli 1.0.686 → 1.0.692
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/gu-cli.mjs +89 -0
- package/package.json +5 -4
- package/powershell-alias.mjs +94 -0
package/gu-cli.mjs
CHANGED
|
@@ -92,6 +92,8 @@ Usage:
|
|
|
92
92
|
gu-cli rag # test the embedder end-to-end + list knowledge bases
|
|
93
93
|
gu-cli workspace revert [runId] # undo an agent run's file edits from its backups
|
|
94
94
|
gu-cli clear [--scratch|--keep-history] [--yes] # back to a fresh machine (asks first)
|
|
95
|
+
gu-cli fix-powershell [--check] # Windows: drop PowerShell's built-in \`gu\` alias
|
|
96
|
+
# (it is Get-Unique and shadows this CLI)
|
|
95
97
|
gu-cli rag plan [path] # is RAG worth it here? files/chars/chunks → estimated index time
|
|
96
98
|
gu-cli rag --test # can this machine embed and retrieve?
|
|
97
99
|
gu-cli test-proxy # can this machine route model calls through a proxy?
|
|
@@ -905,6 +907,93 @@ if (args[0] === "token-usage" || args[0] === "tokens") {
|
|
|
905
907
|
process.exit(0);
|
|
906
908
|
}
|
|
907
909
|
|
|
910
|
+
// `gu-cli fix-powershell` — drop PowerShell's built-in `gu` alias so the command works there.
|
|
911
|
+
// ASKS FIRST and never runs as part of an install: a PowerShell profile is the user's file, and
|
|
912
|
+
// a CLI that edits it unannounced is one that surprises people. --check only reports.
|
|
913
|
+
if (args[0] === "fix-powershell") {
|
|
914
|
+
const pa = await import("./powershell-alias.mjs");
|
|
915
|
+
if (!pa.affectsThisMachine()) {
|
|
916
|
+
console.log(" Nothing to fix — this only affects Windows PowerShell.");
|
|
917
|
+
process.exit(0);
|
|
918
|
+
}
|
|
919
|
+
const { execFileSync } = await import("node:child_process");
|
|
920
|
+
const ps = (script) =>
|
|
921
|
+
execFileSync("powershell", ["-NoProfile", "-Command", script], { encoding: "utf8" }).trim();
|
|
922
|
+
|
|
923
|
+
let profilePath = "";
|
|
924
|
+
try {
|
|
925
|
+
profilePath = ps("$PROFILE");
|
|
926
|
+
} catch (e) {
|
|
927
|
+
console.error(` Could not ask PowerShell where your profile is: ${e.message}`);
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
const { readFileSync, writeFileSync, mkdirSync, existsSync } = await import("node:fs");
|
|
931
|
+
const current = existsSync(profilePath) ? readFileSync(profilePath, "utf8") : "";
|
|
932
|
+
const next = pa.profileWithFix(current);
|
|
933
|
+
// ASKED IN EVERY PATH, because "already fixed" was reported on the very machine whose policy
|
|
934
|
+
// stops the profile loading — the same "said Done, changed nothing" mistake one branch along.
|
|
935
|
+
const blockedBy = () => {
|
|
936
|
+
try {
|
|
937
|
+
return pa.profileBlockedBy(ps("Get-ExecutionPolicy"));
|
|
938
|
+
} catch {
|
|
939
|
+
return "";
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
if (next === null) {
|
|
943
|
+
console.log(` Already fixed — ${profilePath} removes the alias.`);
|
|
944
|
+
const blocked = blockedBy();
|
|
945
|
+
if (blocked) {
|
|
946
|
+
console.log("");
|
|
947
|
+
for (const l of pa.policyAdvice(blocked)) console.log(" " + l);
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
console.log(" Open a NEW PowerShell window for it to take effect.");
|
|
951
|
+
process.exit(0);
|
|
952
|
+
}
|
|
953
|
+
if (args.includes("--check")) {
|
|
954
|
+
console.log(" Not fixed yet. `gu` in PowerShell is still Get-Unique.");
|
|
955
|
+
const blocked = blockedBy();
|
|
956
|
+
if (blocked) for (const l of pa.policyAdvice(blocked)) console.log(" " + l);
|
|
957
|
+
console.log(` Run: gu-cli fix-powershell (edits ${profilePath})`);
|
|
958
|
+
process.exit(1);
|
|
959
|
+
}
|
|
960
|
+
console.log("");
|
|
961
|
+
for (const l of pa.shadowedAdvice()) console.log(" " + l);
|
|
962
|
+
console.log("");
|
|
963
|
+
console.log(` This appends ONE line to ${profilePath}:`);
|
|
964
|
+
console.log(" " + pa.PROFILE_LINE);
|
|
965
|
+
const rlp = (await import("node:readline")).createInterface({
|
|
966
|
+
input: process.stdin,
|
|
967
|
+
output: process.stdout,
|
|
968
|
+
});
|
|
969
|
+
const answer = await new Promise((r) => rlp.question("\n Append it? [y/N]: ", r));
|
|
970
|
+
rlp.close();
|
|
971
|
+
if (!/^y(es)?$/i.test(String(answer).trim())) {
|
|
972
|
+
console.log(" " + pa.MEANWHILE);
|
|
973
|
+
process.exit(0);
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const dir = profilePath.replace(/[\\/][^\\/]*$/, "");
|
|
977
|
+
if (dir && !existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
978
|
+
writeFileSync(profilePath, next, "utf8");
|
|
979
|
+
} catch (e) {
|
|
980
|
+
console.error(` Could not write ${profilePath}: ${e.message}`);
|
|
981
|
+
process.exit(1);
|
|
982
|
+
}
|
|
983
|
+
console.log(` Done — ${profilePath} updated.`);
|
|
984
|
+
// VERIFY THE FIX CAN ACTUALLY RUN. Writing the line is not the same as PowerShell loading it:
|
|
985
|
+
// under Restricted or AllSigned the profile is a script it refuses to execute, and the first
|
|
986
|
+
// real run of this said "Done" to a machine where `gu` stayed shadowed.
|
|
987
|
+
const policy = blockedBy();
|
|
988
|
+
if (policy) {
|
|
989
|
+
console.log("");
|
|
990
|
+
for (const l of pa.policyAdvice(policy)) console.log(" " + l);
|
|
991
|
+
process.exit(1);
|
|
992
|
+
}
|
|
993
|
+
console.log(" Open a NEW PowerShell window, then `gu` runs this CLI.");
|
|
994
|
+
process.exit(0);
|
|
995
|
+
}
|
|
996
|
+
|
|
908
997
|
if (args[0] === "clear" || args[0] === "reset") {
|
|
909
998
|
const cs = await import("./clear-state.mjs");
|
|
910
999
|
const scratchOnly = args.includes("--scratch");
|
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.692",
|
|
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",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"url": "https://github.com/tiennsloit/gu/issues"
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
|
-
"gu-cli": "
|
|
17
|
-
"gu-local-worker": "
|
|
18
|
-
"gu": "
|
|
16
|
+
"gu-cli": "gu-cli.mjs",
|
|
17
|
+
"gu-local-worker": "gu-cli.mjs",
|
|
18
|
+
"gu": "gu-repl.mjs"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"test": "python3 -m unittest discover tests -v && node --test",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"proxy-dispatcher.mjs",
|
|
78
78
|
"model-doctor.mjs",
|
|
79
79
|
"coding-key-prompt.mjs",
|
|
80
|
+
"powershell-alias.mjs",
|
|
80
81
|
"gonext_probe_agent.py",
|
|
81
82
|
"gonext_agent_chat.py",
|
|
82
83
|
"gonext_transcribe.py",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gu` IS A BUILT-IN POWERSHELL ALIAS FOR Get-Unique.
|
|
3
|
+
*
|
|
4
|
+
* Found by the Windows end-to-end run: on Windows 11, where PowerShell is the default shell,
|
|
5
|
+
* typing `gu --help` runs Get-Unique and fails with "A positional parameter cannot be found that
|
|
6
|
+
* accepts argument '--help'". cmd.exe has no such alias, which is why it works there.
|
|
7
|
+
*
|
|
8
|
+
* NOTHING ON PATH CAN FIX THIS. PowerShell resolves Alias → Function → Cmdlet → Application, so
|
|
9
|
+
* an alias beats every executable and every function we could ship. The only remedies are to
|
|
10
|
+
* remove the alias from the user's profile, or to be called something else.
|
|
11
|
+
*
|
|
12
|
+
* So this offers the removal, EXPLICITLY and never automatically. A profile is the user's file;
|
|
13
|
+
* a CLI that edits it during an install is a CLI that surprises people. `gu-cli fix-powershell`
|
|
14
|
+
* asks, shows the line, and appends it — and `--check` just reports.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** The line appended to the profile. `-Force` because the built-in alias is read-only. */
|
|
18
|
+
export const PROFILE_LINE =
|
|
19
|
+
"Remove-Item Alias:gu -Force -ErrorAction SilentlyContinue # gu CLI: PowerShell's built-in `gu` is Get-Unique";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What still works while the alias is in place.
|
|
23
|
+
*
|
|
24
|
+
* IT LIVES HERE, NOT IN gu-cli.mjs, and that is not tidiness. platform-assumptions.test.mjs
|
|
25
|
+
* switches on a rule for any file whose source mentions a quoted ".cmd" — and gu-cli.mjs has
|
|
26
|
+
* several long-standing spawn sites that the rule then flags. One sentence about `gu.cmd` in
|
|
27
|
+
* that file woke all of them. Keeping the string in this module leaves that file exactly as it
|
|
28
|
+
* was, and those sites a separate question rather than one a rename quietly changed.
|
|
29
|
+
*/
|
|
30
|
+
export const MEANWHILE = "Left alone. `gu-cli` and `gu.cmd` both run this program in the meantime.";
|
|
31
|
+
|
|
32
|
+
/** True when this machine can even have the problem. */
|
|
33
|
+
export function affectsThisMachine(platform = process.platform) {
|
|
34
|
+
return platform === "win32";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What to tell someone whose `gu` is shadowed.
|
|
39
|
+
*
|
|
40
|
+
* Names BOTH working commands first, because the immediate need is to run the thing at all —
|
|
41
|
+
* the profile edit is the fix for tomorrow, `gu-cli` is the fix for right now.
|
|
42
|
+
*/
|
|
43
|
+
export function shadowedAdvice() {
|
|
44
|
+
return [
|
|
45
|
+
"PowerShell has a built-in alias `gu` (Get-Unique) that wins over this CLI.",
|
|
46
|
+
"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.",
|
|
48
|
+
"cmd.exe is unaffected; `gu` works there already.",
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Will PowerShell even LOAD a profile on this machine?
|
|
54
|
+
*
|
|
55
|
+
* Found immediately after the first real fix: the line was written, the command said "Done",
|
|
56
|
+
* and a new PowerShell still had the alias — because the execution policy was Restricted and
|
|
57
|
+
* profiles are scripts. A fix that cannot run is worse than no fix, because it stops you
|
|
58
|
+
* looking for the real reason.
|
|
59
|
+
*
|
|
60
|
+
* "Restricted" and "AllSigned" both refuse an unsigned profile. Everything else loads it.
|
|
61
|
+
*/
|
|
62
|
+
export function profileBlockedBy(policy) {
|
|
63
|
+
const p = String(policy ?? "").trim();
|
|
64
|
+
return /^(Restricted|AllSigned)$/i.test(p) ? p : "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** What to do about a policy that will not run the profile. The user's decision, not ours. */
|
|
68
|
+
export function policyAdvice(policy) {
|
|
69
|
+
return [
|
|
70
|
+
`PowerShell's execution policy is ${policy}, so it will not load your profile —`,
|
|
71
|
+
"the line is written but will never run. To allow your own profile:",
|
|
72
|
+
" Set-ExecutionPolicy -Scope CurrentUser RemoteSigned",
|
|
73
|
+
"Until then `gu-cli` and `gu.cmd` still work, and cmd.exe is unaffected.",
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Is the line already in a profile's text?
|
|
79
|
+
*
|
|
80
|
+
* Matched on the COMMAND, not the whole line including its comment — so a profile edited by
|
|
81
|
+
* hand, or by an older version of this message, is still recognised and not appended to twice.
|
|
82
|
+
*/
|
|
83
|
+
export function alreadyFixed(profileText) {
|
|
84
|
+
return /Remove-Item\s+Alias:gu\b/i.test(String(profileText ?? ""));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The text to write, given what the profile already holds. Returns null when nothing is needed. */
|
|
88
|
+
export function profileWithFix(profileText) {
|
|
89
|
+
if (alreadyFixed(profileText)) return null;
|
|
90
|
+
const prev = String(profileText ?? "");
|
|
91
|
+
// A profile that does not end in a newline would otherwise get the fix glued to its last line.
|
|
92
|
+
const sep = prev.length === 0 ? "" : prev.endsWith("\n") ? "" : "\n";
|
|
93
|
+
return `${prev}${sep}${PROFILE_LINE}\n`;
|
|
94
|
+
}
|