@tiens.nguyen/gu-cli 1.0.719 → 1.0.722
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/add-server.mjs +207 -0
- package/agent-model-command.mjs +3 -1
- package/coding-provider-setup.mjs +23 -0
- package/gonext_agent_chat.py +15 -13
- package/gu-repl.mjs +608 -831
- package/package.json +3 -1
- package/server-keys.mjs +97 -0
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.722",
|
|
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",
|
|
@@ -83,6 +83,8 @@
|
|
|
83
83
|
"proxy-dispatcher.mjs",
|
|
84
84
|
"model-doctor.mjs",
|
|
85
85
|
"coding-key-prompt.mjs",
|
|
86
|
+
"server-keys.mjs",
|
|
87
|
+
"add-server.mjs",
|
|
86
88
|
"powershell-alias.mjs",
|
|
87
89
|
"install-path.mjs",
|
|
88
90
|
"gonext_probe_agent.py",
|
package/server-keys.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A KEY BELONGS TO A SERVER URL, NOT TO A MODEL (task #245) — the terminal's decisions, pure.
|
|
3
|
+
*
|
|
4
|
+
* The rules the user set:
|
|
5
|
+
* · a server URL owns its keys (many, named) and its models (at most MAX_MODELS_PER_SERVER);
|
|
6
|
+
* · the first key chosen for a server becomes its DEFAULT for every later terminal / project;
|
|
7
|
+
* · picking another key in a folder applies to that folder only, PER ROLE (coding/agent/embed);
|
|
8
|
+
* · the switch picker loads the current server's keys only;
|
|
9
|
+
* · deleting the first key entered for a server warns — its model list was loaded with it.
|
|
10
|
+
*
|
|
11
|
+
* The API owns resolution (resolveKeyForRole in api/src/app.ts); this module only decides what
|
|
12
|
+
* the terminal shows and asks, so /model, /agent-model and /embed-model cannot drift apart.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The three roles, the folder-choice field each one's pick lives in, and how to say it. */
|
|
16
|
+
export const KEY_ROLES = {
|
|
17
|
+
coding: { field: "codingKeyId", label: "coding model" },
|
|
18
|
+
agent: { field: "agentKeyId", label: "agent model" },
|
|
19
|
+
embed: { field: "embedKeyId", label: "embedding model" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function roleOf(role) {
|
|
23
|
+
return KEY_ROLES[role] ?? KEY_ROLES.coding;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The key a folder is actually on, as the API will resolve it — for the "(in use)" mark and the
|
|
28
|
+
* bar's tail. Mirrors resolveCodingApiKey: the folder's pick (one of THIS server's keys) → the
|
|
29
|
+
* server's default → its first key → any of its keys → a key tied to no server.
|
|
30
|
+
* `keys` is GET /coding-keys?backendId=… (this server's keys, then unbound ones).
|
|
31
|
+
*/
|
|
32
|
+
export function keyInUse(keys, backendId, folderKeyId = "") {
|
|
33
|
+
const list = Array.isArray(keys) ? keys : [];
|
|
34
|
+
const own = list.filter((k) => k && k.backendId === backendId);
|
|
35
|
+
const loose = list.filter((k) => k && !k.backendId);
|
|
36
|
+
return (
|
|
37
|
+
(folderKeyId && own.find((k) => k.id === folderKeyId)) ||
|
|
38
|
+
own.find((k) => k.default) ||
|
|
39
|
+
own.find((k) => k.first) ||
|
|
40
|
+
own[0] ||
|
|
41
|
+
loose[0] ||
|
|
42
|
+
null
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What a picker row says about a key besides its name and tail. Order: most specific first. */
|
|
47
|
+
export function keyTags(k, { folderKeyId = "", inUseId = "" } = {}) {
|
|
48
|
+
const tags = [];
|
|
49
|
+
if (k.id === folderKeyId) tags.push("this folder");
|
|
50
|
+
else if (k.id === inUseId) tags.push("in use");
|
|
51
|
+
if (k.default) tags.push("default");
|
|
52
|
+
if (k.first) tags.push("first key");
|
|
53
|
+
if (!k.backendId) tags.push("not tied to a server yet");
|
|
54
|
+
return tags;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* After a key is picked: WHERE does the choice apply? The first key chosen for a server already
|
|
59
|
+
* became its default (the API stamps it), so this is only asked when the pick differs from the
|
|
60
|
+
* server's default. Returns the rows to offer, in order; `scope` is what each one means.
|
|
61
|
+
*/
|
|
62
|
+
export function scopeChoices({ host = "the server", role = "coding" } = {}) {
|
|
63
|
+
return [
|
|
64
|
+
{ scope: "folder", label: `just this folder's ${roleOf(role).label}` },
|
|
65
|
+
{ scope: "default", label: `make it the default for ${host} — every folder that has not picked its own` },
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The warnings before a key is deleted, from DELETE /coding-keys/:id?dryRun=1. Irreversible, so
|
|
71
|
+
* everything that changes is said first. The FIRST key gets its own line, as asked: "if later
|
|
72
|
+
* user delete that key we need to warn the user that it was the first key that user input for
|
|
73
|
+
* that server url".
|
|
74
|
+
*/
|
|
75
|
+
export function deleteWarnings(preview, { host = "this server", isFolderPick = false, roleLabel = "", lastOfServer = false } = {}) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
// No preview (an older API, or it could not be reached) still gets the warnings the terminal
|
|
78
|
+
// can work out itself — this folder's pick, the server's last key.
|
|
79
|
+
preview = preview && typeof preview === "object" ? preview : {};
|
|
80
|
+
if (preview.wasFirst) {
|
|
81
|
+
lines.push(`This was the FIRST key you entered for ${host} — its model list was loaded with it.`);
|
|
82
|
+
}
|
|
83
|
+
if (preview.wasDefault) {
|
|
84
|
+
lines.push(`It is ${host}'s default key — folders without their own pick fall back to another key of ${host}, or none.`);
|
|
85
|
+
}
|
|
86
|
+
if (isFolderPick) {
|
|
87
|
+
lines.push(`This folder uses it${roleLabel ? ` for the ${roleLabel}` : ""} — it will fall back to ${host}'s default.`);
|
|
88
|
+
}
|
|
89
|
+
if (lastOfServer) {
|
|
90
|
+
lines.push(`It is the only key stored for ${host} — every model there will have none until you add another.`);
|
|
91
|
+
}
|
|
92
|
+
const others = Number(preview.folders ?? 0) - (isFolderPick ? 1 : 0);
|
|
93
|
+
if (others > 0) {
|
|
94
|
+
lines.push(`${others} other folder${others === 1 ? "" : "s"} picked it${Array.isArray(preview.roles) && preview.roles.length ? ` (${preview.roles.join(", ")})` : ""}.`);
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|