@tiens.nguyen/gu-cli 1.0.764 → 1.0.765
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/client-expert-run.mjs +108 -1
- package/client-expert-setup.mjs +48 -0
- package/package.json +1 -1
package/client-expert-run.mjs
CHANGED
|
@@ -46,6 +46,9 @@ import {
|
|
|
46
46
|
clientExpertAvailable,
|
|
47
47
|
mongoInstallPlan,
|
|
48
48
|
mongoDockerPlan,
|
|
49
|
+
buildMongoUri,
|
|
50
|
+
mongoShellPingCommand,
|
|
51
|
+
mongoAuthVerdict,
|
|
49
52
|
MONGO_DOCKER_NAME,
|
|
50
53
|
} from "./client-expert-setup.mjs";
|
|
51
54
|
|
|
@@ -299,6 +302,79 @@ export async function startMongoInDocker(ask, { platform = process.platform } =
|
|
|
299
302
|
return false;
|
|
300
303
|
}
|
|
301
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Does MongoDB accept THIS uri — credentials included? (task #15)
|
|
307
|
+
*
|
|
308
|
+
* Asks a mongo shell, because the shell is all that exists at this point in setup: the API
|
|
309
|
+
* package (and the mongodb driver inside it) is downloaded LATER. `mongosh` first, the legacy
|
|
310
|
+
* `mongo` shell second. When neither exists the answer is { ok: null, unverifiable: true } —
|
|
311
|
+
* honestly "cannot check", which is a different thing from "check failed": the seed step still
|
|
312
|
+
* verifies the URI for real before anything is written.
|
|
313
|
+
*/
|
|
314
|
+
export async function checkMongoConnection(uri) {
|
|
315
|
+
for (const shell of ["mongosh", "mongo"]) {
|
|
316
|
+
const has = await tryRun("sh", ["-c", `command -v ${shell} || true`]);
|
|
317
|
+
if (!has || !has.trim()) continue;
|
|
318
|
+
const out = await tryRun(shell, mongoShellPingCommand(uri), 12000);
|
|
319
|
+
return mongoAuthVerdict({ output: out });
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
ok: null,
|
|
323
|
+
unverifiable: true,
|
|
324
|
+
why: "no mongo shell on this machine to check with — it will be verified for real " +
|
|
325
|
+
"when the database is seeded",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Confirm WHO the MongoDB on this machine belongs to, and collect credentials when it is not
|
|
331
|
+
* the one setup would have created (task #15).
|
|
332
|
+
*
|
|
333
|
+
* THE CASE: something is already answering on 27017 — a MongoDB the OS had before gu — and it
|
|
334
|
+
* has its OWN username/password, which we do not have admin access to. Seeding into it with
|
|
335
|
+
* the default empty credentials then fails two steps later, after a 360-package install, with
|
|
336
|
+
* a message about "a permissions or connection-string problem". The question belongs at the
|
|
337
|
+
* mongo step, where the answer is still cheap to change.
|
|
338
|
+
*
|
|
339
|
+
* Returns { uri, custom } to continue with, or { exit: true } when the user declines — the
|
|
340
|
+
* caller ends setup with the `gu clear` → `gu` hint.
|
|
341
|
+
*/
|
|
342
|
+
export async function askMongoCredentials(ask) {
|
|
343
|
+
const { host, port } = mongoHostPort(LOCAL_MONGO_URI);
|
|
344
|
+
console.log(dim(` MongoDB is answering at ${host}:${port}.`));
|
|
345
|
+
console.log(dim(" If that is a MongoDB this machine already had, it may have its OWN"));
|
|
346
|
+
console.log(dim(" username and password — which setup does not know, and cannot change."));
|
|
347
|
+
const yes = await ask(" Use the default (empty username and password)? [Y/n]: ");
|
|
348
|
+
if (!String(yes ?? "").trim().toLowerCase().startsWith("n")) {
|
|
349
|
+
return { uri: LOCAL_MONGO_URI, custom: false };
|
|
350
|
+
}
|
|
351
|
+
// NO → the instance has its own credentials. Ask for them, and CHECK before any installing
|
|
352
|
+
// continues: a typo found here costs ten seconds; found at the seed step it costs the run.
|
|
353
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
354
|
+
const username = String(await ask(" MongoDB username: ") ?? "").trim();
|
|
355
|
+
const password = String(await ask(" MongoDB password: ") ?? "");
|
|
356
|
+
const uri = buildMongoUri({ username, password, host, port });
|
|
357
|
+
process.stdout.write(dim(" checking the connection…"));
|
|
358
|
+
const verdict = await checkMongoConnection(uri);
|
|
359
|
+
if (verdict.ok === true) {
|
|
360
|
+
console.log("\n " + cyan("✓ Connection OK") + dim(" — those credentials work"));
|
|
361
|
+
return { uri, custom: true };
|
|
362
|
+
}
|
|
363
|
+
if (verdict.unverifiable) {
|
|
364
|
+
console.log("\n " + yellow("⚠ could not verify: ") + dim(verdict.why));
|
|
365
|
+
const carryOn = await ask(" Continue with these credentials anyway? [Y/n]: ");
|
|
366
|
+
if (!String(carryOn ?? "").trim().toLowerCase().startsWith("n")) {
|
|
367
|
+
return { uri, custom: true, unverified: true };
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.log("\n " + red("✗ Check Connection failed") + dim(` — ${verdict.why}`));
|
|
371
|
+
}
|
|
372
|
+
const again = await ask(" Re-enter the username and password? [y/N]: ");
|
|
373
|
+
if (!String(again ?? "").trim().toLowerCase().startsWith("y")) return { exit: true };
|
|
374
|
+
}
|
|
375
|
+
return { exit: true };
|
|
376
|
+
}
|
|
377
|
+
|
|
302
378
|
/**
|
|
303
379
|
* Where the published API packages live. Overridable so a fork, a staging bucket or an
|
|
304
380
|
* air-gapped mirror does not need a code change.
|
|
@@ -616,6 +692,36 @@ export async function runClientExpertSetup({ ask }) {
|
|
|
616
692
|
}
|
|
617
693
|
|
|
618
694
|
console.log("\n " + cyan("✓ MongoDB is running") + dim(` (${mongo.how})`));
|
|
695
|
+
|
|
696
|
+
// --- whose MongoDB is it? (task #15) ---------------------------------------------------
|
|
697
|
+
// Something answering on 27017 is not necessarily a database setup can USE: an instance the
|
|
698
|
+
// OS already had may carry its own admin credentials, and seeding into it with the default
|
|
699
|
+
// empty pair fails two steps later, after the 360-package install, with a message pointing
|
|
700
|
+
// at the wrong thing. Confirm the credentials HERE, where changing the answer is cheap.
|
|
701
|
+
// A uri already in state — a resumed run — is reused silently, like every other answered
|
|
702
|
+
// question, and a URI forced via GONEXT_LOCAL_MONGO_URI is taken as deliberate.
|
|
703
|
+
let mongoUri = LOCAL_MONGO_URI;
|
|
704
|
+
if (typeof state.localMongoUri === "string" && state.localMongoUri) {
|
|
705
|
+
mongoUri = state.localMongoUri;
|
|
706
|
+
if (mongoUri !== LOCAL_MONGO_URI) {
|
|
707
|
+
console.log(dim(" using the MongoDB credentials from the previous run"));
|
|
708
|
+
}
|
|
709
|
+
} else if (!process.env.GONEXT_LOCAL_MONGO_URI) {
|
|
710
|
+
const credsChoice = await askMongoCredentials(ask);
|
|
711
|
+
if (credsChoice.exit) {
|
|
712
|
+
console.log("\n " + dim("No working MongoDB credentials — setup stops here."));
|
|
713
|
+
console.log(dim(" To start over: `gu clear`, then `gu` again.\n"));
|
|
714
|
+
state = recordStep(state, "mongo", "interrupted", {
|
|
715
|
+
reason: "declined at the MongoDB credentials check",
|
|
716
|
+
});
|
|
717
|
+
await saveState(state);
|
|
718
|
+
return { complete: false, stoppedAt: "mongo" };
|
|
719
|
+
}
|
|
720
|
+
mongoUri = credsChoice.uri;
|
|
721
|
+
if (credsChoice.custom) {
|
|
722
|
+
state = { ...state, localMongoUri: mongoUri };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
619
725
|
state = recordStep(state, "mongo", "done");
|
|
620
726
|
await saveState(state);
|
|
621
727
|
|
|
@@ -687,6 +793,7 @@ export async function runClientExpertSetup({ ask }) {
|
|
|
687
793
|
try {
|
|
688
794
|
creds = await seedDatabase({
|
|
689
795
|
apiDir,
|
|
796
|
+
mongoUri,
|
|
690
797
|
userId: state.localUserId,
|
|
691
798
|
workerKey: state.localWorkerKey,
|
|
692
799
|
});
|
|
@@ -713,7 +820,7 @@ export async function runClientExpertSetup({ ask }) {
|
|
|
713
820
|
let health = await probeLocalApi(LOCAL_API_PORT);
|
|
714
821
|
if (!health.ok) {
|
|
715
822
|
console.log("\n " + bold(`Starting the local API on port ${LOCAL_API_PORT}`));
|
|
716
|
-
await startLocalApi({ apiDir });
|
|
823
|
+
await startLocalApi({ apiDir, mongoUri });
|
|
717
824
|
health = await waitForApi({ port: LOCAL_API_PORT });
|
|
718
825
|
}
|
|
719
826
|
if (!health.ok) {
|
package/client-expert-setup.mjs
CHANGED
|
@@ -808,3 +808,51 @@ export async function promptForLicenseToken(ask, initialToken, {
|
|
|
808
808
|
}
|
|
809
809
|
return { token: "", result: null, declined: true };
|
|
810
810
|
}
|
|
811
|
+
|
|
812
|
+
/* ------------------------- MongoDB credentials (task #15) ------------------------------ */
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Build a mongodb:// URI from parts, with the credentials URL-encoded.
|
|
816
|
+
*
|
|
817
|
+
* Encoding is not decoration: a password of `p@ss/word#1` pasted verbatim into a URI breaks
|
|
818
|
+
* the parse in three different ways, and the failure surfaces two steps later as "MongoDB
|
|
819
|
+
* answered a moment ago, so this is usually a permissions problem" — a message that sends the
|
|
820
|
+
* user debugging the wrong thing entirely.
|
|
821
|
+
*/
|
|
822
|
+
export function buildMongoUri({ username = "", password = "", host = "127.0.0.1", port = 27017 } = {}) {
|
|
823
|
+
const user = String(username ?? "").trim();
|
|
824
|
+
if (!user) return `mongodb://${host}:${port}`;
|
|
825
|
+
const pass = String(password ?? "");
|
|
826
|
+
return `mongodb://${encodeURIComponent(user)}:${encodeURIComponent(pass)}@${host}:${port}`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/** The one-liner any mongo shell answers, credentialed or not. */
|
|
830
|
+
export function mongoShellPingCommand(uri) {
|
|
831
|
+
return ["--quiet", String(uri), "--eval", "db.runCommand({ping:1}).ok"];
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* What a mongo shell's ping output means for CREDENTIALS (task #15).
|
|
836
|
+
*
|
|
837
|
+
* → { ok } true when the ping answered, false when it definitely did not,
|
|
838
|
+
* { ok: null, unverifiable: true } when no shell exists on this machine to ask.
|
|
839
|
+
*
|
|
840
|
+
* A socket can prove a port answers; only something that speaks the wire protocol can prove a
|
|
841
|
+
* username/password pair is ACCEPTED — which is the whole question when the MongoDB on 27017
|
|
842
|
+
* is not one we installed. The shells are what is available before the API package (and its
|
|
843
|
+
* mongodb driver) is downloaded, so their absence means "cannot check", not "check failed" —
|
|
844
|
+
* and those two get different messages because they need different fixes.
|
|
845
|
+
*/
|
|
846
|
+
export function mongoAuthVerdict({ output } = {}) {
|
|
847
|
+
const out = String(output ?? "");
|
|
848
|
+
if (out.trim().endsWith("1")) return { ok: true, why: "" };
|
|
849
|
+
const looksLikeAuth =
|
|
850
|
+
/auth|AuthenticationFailed|SCRAM|not authorized|requires authentication/i.test(out);
|
|
851
|
+
return {
|
|
852
|
+
ok: false,
|
|
853
|
+
why: looksLikeAuth
|
|
854
|
+
? "the database rejected those credentials"
|
|
855
|
+
: "it answered, but the ping did not succeed",
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
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.765",
|
|
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",
|