@aident-ai/cli 0.1.7 → 0.1.8-rc.1
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/README.md +5 -0
- package/dist/cli.mjs +165 -62
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,11 @@ aident update --check
|
|
|
28
28
|
aident --help
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
`aident setup` installs or updates the verified Aident Skill globally, migrates recognized clean project copies that
|
|
32
|
+
could shadow it, reuses or opens Aident authentication, verifies Loadout access, and records completion. Add
|
|
33
|
+
`--client-name <name>` to record the agent client and `--json` for a single machine-readable report; a configured
|
|
34
|
+
`AIDENT_TOKEN` makes the flow non-interactive.
|
|
35
|
+
|
|
31
36
|
Use `--oob` for browserless auth environments:
|
|
32
37
|
|
|
33
38
|
```bash
|
package/dist/cli.mjs
CHANGED
|
@@ -45,7 +45,7 @@ function logErr(text) {
|
|
|
45
45
|
// src/prompt.ts
|
|
46
46
|
function readLine(prompt, options = {}) {
|
|
47
47
|
return new Promise((resolve, reject) => {
|
|
48
|
-
process.stdout.write(prompt);
|
|
48
|
+
(options.output ?? process.stdout).write(prompt);
|
|
49
49
|
let data = "";
|
|
50
50
|
let timeout;
|
|
51
51
|
const cleanup = () => {
|
|
@@ -86,13 +86,14 @@ var LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS = 5 * 60 * 1000;
|
|
|
86
86
|
var LOGIN_OPENED_MESSAGE = `User opened the Aident login window. They have ${LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS / 60000} minutes to finish; if it fails, run \`aident login --oob\`, share the URL, and ask for the verification code.`;
|
|
87
87
|
async function login(options) {
|
|
88
88
|
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
89
|
+
const writeInfo = options.format === "json" ? logErr : logInfo;
|
|
89
90
|
if (options.oob)
|
|
90
|
-
return loginOob(baseUrl);
|
|
91
|
+
return loginOob(baseUrl, writeInfo, options.format === "json" ? process.stderr : process.stdout);
|
|
91
92
|
try {
|
|
92
|
-
return await loginLoopback(baseUrl);
|
|
93
|
+
return await loginLoopback(baseUrl, writeInfo);
|
|
93
94
|
} catch (err) {
|
|
94
95
|
logErr(`Loopback OAuth failed (${err instanceof Error ? err.message : String(err)}). Falling back to OOB flow.`);
|
|
95
|
-
return loginOob(baseUrl);
|
|
96
|
+
return loginOob(baseUrl, writeInfo, options.format === "json" ? process.stderr : process.stdout);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
async function refreshToken(creds) {
|
|
@@ -127,11 +128,11 @@ async function logout(creds) {
|
|
|
127
128
|
return;
|
|
128
129
|
});
|
|
129
130
|
}
|
|
130
|
-
async function loginLoopback(baseUrl) {
|
|
131
|
+
async function loginLoopback(baseUrl, writeInfo) {
|
|
131
132
|
const verifier = base64UrlEncode(randomBytes(48));
|
|
132
133
|
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
133
134
|
const state = base64UrlEncode(randomBytes(16));
|
|
134
|
-
const { server, port, codePromise, setExpectedOpenRedirectUrl } = await startCallbackServer(state);
|
|
135
|
+
const { server, port, codePromise, setExpectedOpenRedirectUrl } = await startCallbackServer(state, writeInfo);
|
|
135
136
|
const redirectUri = `http://${LOOPBACK_HOST}:${port}/callback`;
|
|
136
137
|
const clientId = await registerClient(baseUrl, [redirectUri]);
|
|
137
138
|
const authorizeUrl = new URL(`${baseUrl}/api/mcp/oauth/authorize`);
|
|
@@ -145,8 +146,8 @@ async function loginLoopback(baseUrl) {
|
|
|
145
146
|
const authorizeUrlString = authorizeUrl.toString();
|
|
146
147
|
setExpectedOpenRedirectUrl(authorizeUrlString);
|
|
147
148
|
const loginOpenUrl = buildLoginOpenUrl(port, state, authorizeUrlString);
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
writeInfo(`Opening browser for Aident login...`);
|
|
150
|
+
writeInfo(`If the browser does not open, visit: ${authorizeUrlString}`);
|
|
150
151
|
openBrowser(shouldUseLoopbackBrowserHandoff() ? loginOpenUrl : authorizeUrlString);
|
|
151
152
|
let code;
|
|
152
153
|
try {
|
|
@@ -157,7 +158,7 @@ async function loginLoopback(baseUrl) {
|
|
|
157
158
|
const tok = await exchangeCode(baseUrl, clientId, code, redirectUri, verifier);
|
|
158
159
|
return buildCreds(baseUrl, clientId, tok);
|
|
159
160
|
}
|
|
160
|
-
async function loginOob(baseUrl) {
|
|
161
|
+
async function loginOob(baseUrl, writeInfo, promptOutput) {
|
|
161
162
|
const redirectUri = `${baseUrl}/mcp/oob`;
|
|
162
163
|
const verifier = base64UrlEncode(randomBytes(48));
|
|
163
164
|
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
@@ -174,11 +175,12 @@ async function loginOob(baseUrl) {
|
|
|
174
175
|
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
175
176
|
authorizeUrl.searchParams.set("state", state);
|
|
176
177
|
const authorizeUrlString = authorizeUrl.toString();
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
writeInfo(`Opening browser for Aident login...`);
|
|
179
|
+
writeInfo(`If the browser does not open, visit: ${authorizeUrlString}`);
|
|
179
180
|
openBrowser(authorizeUrlString);
|
|
180
|
-
|
|
181
|
+
writeInfo("After approving, paste the 8-digit verification code shown on the Aident page below.");
|
|
181
182
|
const pastedInput = await readLine("Paste 8-digit code here: ", {
|
|
183
|
+
output: promptOutput,
|
|
182
184
|
timeoutMs: LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS,
|
|
183
185
|
timeoutMessage: `Verification code not received within ${LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS / 1000}s; aborting.`
|
|
184
186
|
});
|
|
@@ -288,7 +290,7 @@ function updateCredsFromToken(creds, tok) {
|
|
|
288
290
|
expires_at: expiresAt
|
|
289
291
|
};
|
|
290
292
|
}
|
|
291
|
-
function startCallbackServer(expectedState) {
|
|
293
|
+
function startCallbackServer(expectedState, writeInfo = logInfo) {
|
|
292
294
|
return new Promise((resolve, reject) => {
|
|
293
295
|
let codeResolver = () => {
|
|
294
296
|
return;
|
|
@@ -310,7 +312,7 @@ function startCallbackServer(expectedState) {
|
|
|
310
312
|
};
|
|
311
313
|
const openTimeout = setTimeout(() => {
|
|
312
314
|
if (!opened) {
|
|
313
|
-
|
|
315
|
+
writeInfo(`Login window open signal not received within ${LOGIN_OPEN_TIMEOUT_MS / 1000}s; still waiting for login to finish.`);
|
|
314
316
|
}
|
|
315
317
|
}, LOGIN_OPEN_TIMEOUT_MS);
|
|
316
318
|
scheduleCompleteTimeout(LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS, `Authorization callback not received within ${LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS / 1000}s; aborting.`);
|
|
@@ -328,7 +330,7 @@ function startCallbackServer(expectedState) {
|
|
|
328
330
|
return;
|
|
329
331
|
opened = true;
|
|
330
332
|
clearTimeout(openTimeout);
|
|
331
|
-
|
|
333
|
+
writeInfo(LOGIN_OPENED_MESSAGE);
|
|
332
334
|
const timeoutMs = LOGIN_COMPLETE_TIMEOUT_AFTER_OPENED_MS;
|
|
333
335
|
scheduleCompleteTimeout(timeoutMs, `Login page opened but did not complete within ${timeoutMs / 1000}s; aborting.`);
|
|
334
336
|
};
|
|
@@ -596,7 +598,7 @@ function normalizeBaseUrl(url) {
|
|
|
596
598
|
}
|
|
597
599
|
|
|
598
600
|
// src/version.ts
|
|
599
|
-
var VERSION = "0.1.
|
|
601
|
+
var VERSION = "0.1.8-rc.1";
|
|
600
602
|
|
|
601
603
|
// src/catalogCache.ts
|
|
602
604
|
var CACHE_TTL_MS = 5 * 60 * 1000;
|
|
@@ -6177,6 +6179,30 @@ function mergeCatalogs(catalogs, packages, onCommand) {
|
|
|
6177
6179
|
// src/credentials.ts
|
|
6178
6180
|
import { mkdir as mkdir5, readFile as readFile6, rm, writeFile as writeFile5 } from "node:fs/promises";
|
|
6179
6181
|
import { join as join7 } from "node:path";
|
|
6182
|
+
|
|
6183
|
+
// ../web/shared/loadout/AidentCredentialOrigin.mjs
|
|
6184
|
+
function getSharedAidentOrigin(value) {
|
|
6185
|
+
try {
|
|
6186
|
+
const url = new URL(value);
|
|
6187
|
+
if (url.protocol !== "https:" || url.port !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.username !== "" || url.password !== "" || url.hostname !== "aident.ai" && !url.hostname.endsWith(".aident.ai")) {
|
|
6188
|
+
return null;
|
|
6189
|
+
}
|
|
6190
|
+
return url.origin;
|
|
6191
|
+
} catch {
|
|
6192
|
+
return null;
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6195
|
+
function resolveAidentCredentialBaseUrl(credentialsBaseUrl, requestedBaseUrl) {
|
|
6196
|
+
const credentialsUrl = credentialsBaseUrl.replace(/\/+$/, "");
|
|
6197
|
+
const requestedUrl = requestedBaseUrl.replace(/\/+$/, "");
|
|
6198
|
+
if (credentialsUrl === requestedUrl)
|
|
6199
|
+
return requestedUrl;
|
|
6200
|
+
if (!getSharedAidentOrigin(credentialsUrl))
|
|
6201
|
+
return null;
|
|
6202
|
+
return getSharedAidentOrigin(requestedUrl);
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6205
|
+
// src/credentials.ts
|
|
6180
6206
|
var REFRESH_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
6181
6207
|
function getCredentialsFile() {
|
|
6182
6208
|
return join7(getAidentDir(), "credentials.json");
|
|
@@ -6200,16 +6226,8 @@ async function clearCredentials() {
|
|
|
6200
6226
|
await rm(getCredentialsFile(), { force: true });
|
|
6201
6227
|
}
|
|
6202
6228
|
function credentialsForBaseUrl(creds, baseUrl) {
|
|
6203
|
-
const
|
|
6204
|
-
|
|
6205
|
-
if (credentialsBaseUrl === requestedBaseUrl)
|
|
6206
|
-
return { ...creds, base_url: requestedBaseUrl };
|
|
6207
|
-
if (!getSharedAidentBaseUrl(credentialsBaseUrl))
|
|
6208
|
-
return null;
|
|
6209
|
-
const sharedBaseUrl = getSharedAidentBaseUrl(requestedBaseUrl);
|
|
6210
|
-
if (!sharedBaseUrl)
|
|
6211
|
-
return null;
|
|
6212
|
-
return { ...creds, base_url: sharedBaseUrl };
|
|
6229
|
+
const resolvedBaseUrl = resolveAidentCredentialBaseUrl(creds.base_url, baseUrl);
|
|
6230
|
+
return resolvedBaseUrl ? { ...creds, base_url: resolvedBaseUrl } : null;
|
|
6213
6231
|
}
|
|
6214
6232
|
async function clearCredentialsForIncompatibleBaseUrl(baseUrl) {
|
|
6215
6233
|
const creds = await readCredentials();
|
|
@@ -6226,17 +6244,6 @@ function isExpired(creds) {
|
|
|
6226
6244
|
return false;
|
|
6227
6245
|
return Date.now() >= expiresAt - REFRESH_WINDOW_MS;
|
|
6228
6246
|
}
|
|
6229
|
-
function getSharedAidentBaseUrl(baseUrl) {
|
|
6230
|
-
try {
|
|
6231
|
-
const url = new URL(baseUrl);
|
|
6232
|
-
if (url.protocol !== "https:" || url.port !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.username !== "" || url.password !== "" || url.hostname !== "aident.ai" && !url.hostname.endsWith(".aident.ai")) {
|
|
6233
|
-
return null;
|
|
6234
|
-
}
|
|
6235
|
-
return url.origin;
|
|
6236
|
-
} catch {
|
|
6237
|
-
return null;
|
|
6238
|
-
}
|
|
6239
|
-
}
|
|
6240
6247
|
|
|
6241
6248
|
// src/doctor.ts
|
|
6242
6249
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -6449,7 +6456,7 @@ var LOCAL_HELP_COMMANDS = [
|
|
|
6449
6456
|
{ command: "config get <key>", description: "Read a single value" },
|
|
6450
6457
|
{ command: "packages add <playbook|intern>", description: "Enable an add-on package" },
|
|
6451
6458
|
{ command: "doctor", description: "Validate installation" },
|
|
6452
|
-
{ command: "setup", description: "
|
|
6459
|
+
{ command: "setup", description: "Install Aident globally, authenticate, and verify Loadout" },
|
|
6453
6460
|
{ command: "integrations migrate-local", description: "Plan migration from local MCP configs to Loadout" },
|
|
6454
6461
|
{ command: "<domain> <command> [--flag value ...] [--json]", description: "Run a catalog command after login" }
|
|
6455
6462
|
];
|
|
@@ -6467,7 +6474,7 @@ function getLocalHelp(version) {
|
|
|
6467
6474
|
"aident config get <key>",
|
|
6468
6475
|
"aident packages add <playbook|intern>",
|
|
6469
6476
|
"aident doctor",
|
|
6470
|
-
"aident setup",
|
|
6477
|
+
"aident setup [--client-name <name>]",
|
|
6471
6478
|
"aident integrations migrate-local",
|
|
6472
6479
|
"aident <domain> <command> [--flag value ...] [--json]"
|
|
6473
6480
|
],
|
|
@@ -9432,32 +9439,128 @@ function formatAidentUpdateReport(report) {
|
|
|
9432
9439
|
`);
|
|
9433
9440
|
}
|
|
9434
9441
|
async function runSetup(parsed) {
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
const input = (await readLine(`Base URL [${current}]: `)).trim();
|
|
9439
|
-
const chosen = input ? normalizeBaseUrl(input) : current;
|
|
9440
|
-
if (chosen !== current) {
|
|
9441
|
-
await setConfigValue("baseUrl", chosen);
|
|
9442
|
-
logInfo(`${colors.green}Saved${colors.reset} baseUrl = ${chosen}`);
|
|
9442
|
+
if (parsed.isHelp) {
|
|
9443
|
+
logInfo(renderSetupHelp());
|
|
9444
|
+
return;
|
|
9443
9445
|
}
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
await
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9446
|
+
const report = { schemaVersion: 1, success: false };
|
|
9447
|
+
let errorCode = "invalid-input";
|
|
9448
|
+
try {
|
|
9449
|
+
const options = await getSetupOptions(parsed);
|
|
9450
|
+
errorCode = "setup-failed";
|
|
9451
|
+
report.baseUrl = options.baseUrl;
|
|
9452
|
+
report.clientName = options.clientName;
|
|
9453
|
+
if (options.persistBaseUrl)
|
|
9454
|
+
await setConfigValue("baseUrl", options.baseUrl);
|
|
9455
|
+
const update = await runAidentUpdate({
|
|
9456
|
+
baseUrl: options.baseUrl,
|
|
9457
|
+
currentVersion: VERSION,
|
|
9458
|
+
mode: "apply" /* Apply */,
|
|
9459
|
+
includeCli: true,
|
|
9460
|
+
includeSkill: true,
|
|
9461
|
+
includeProject: true
|
|
9462
|
+
});
|
|
9463
|
+
recordLoadoutUpdateTelemetry(options.baseUrl, getLoadoutUpdateCompletionTelemetry(update));
|
|
9464
|
+
report.update = update;
|
|
9465
|
+
if (!update.success) {
|
|
9466
|
+
throw new Error(update.errors?.[0]?.message ?? update.skill?.detail ?? "Aident installation was incomplete");
|
|
9467
|
+
}
|
|
9468
|
+
const skillVersion = update.skill?.targetVersion;
|
|
9469
|
+
let credentials = await readCredentials();
|
|
9470
|
+
const hasEnvironmentToken = !!process.env.AIDENT_TOKEN?.trim();
|
|
9471
|
+
if ((!credentials || !credentialsForBaseUrl(credentials, options.baseUrl)) && !hasEnvironmentToken) {
|
|
9472
|
+
credentials = await login({ baseUrl: options.baseUrl, format: parsed.format, oob: options.oob });
|
|
9473
|
+
await writeCredentials(credentials);
|
|
9474
|
+
}
|
|
9475
|
+
const client = await getAuthenticatedClient(["loadout"]);
|
|
9476
|
+
if (!client)
|
|
9477
|
+
throw new Error("Aident credentials could not be loaded after sign-in");
|
|
9478
|
+
const catalog = await fetchCatalog(client);
|
|
9479
|
+
if (!catalog)
|
|
9480
|
+
throw new Error("Aident Loadout command catalog is unavailable");
|
|
9481
|
+
const identity = await callWithRefresh(client, (activeClient) => activeClient.exec("account", "auth status", {}));
|
|
9482
|
+
if (!identity.body.success) {
|
|
9483
|
+
throw new Error(identity.body.error?.message ?? "Aident account verification failed");
|
|
9484
|
+
}
|
|
9485
|
+
report.authenticated = true;
|
|
9486
|
+
report.account = identity.body.data;
|
|
9487
|
+
const doctor = await runDoctor({
|
|
9488
|
+
baseUrl: options.baseUrl,
|
|
9489
|
+
configFile: getConfigFile(),
|
|
9490
|
+
credentialsFile: getCredentialsFile()
|
|
9491
|
+
});
|
|
9492
|
+
report.doctor = doctor;
|
|
9493
|
+
if (!doctor.ok)
|
|
9494
|
+
throw new Error("Aident installation verification failed");
|
|
9495
|
+
const completion = await callWithRefresh(client, (activeClient) => activeClient.exec("loadout", "setup complete", {
|
|
9496
|
+
clientName: options.clientName,
|
|
9497
|
+
...skillVersion ? { skillVersion } : {}
|
|
9498
|
+
}));
|
|
9499
|
+
if (!completion.body.success || !completion.body.data?.recorded) {
|
|
9500
|
+
throw new Error(completion.body.error?.message ?? "Aident setup completion was not recorded");
|
|
9456
9501
|
}
|
|
9502
|
+
report.completion = completion.body.data;
|
|
9503
|
+
report.success = true;
|
|
9504
|
+
await maybeOfferLocalIntegrationMigration(parsed);
|
|
9505
|
+
emitSetupReport(parsed.format, report);
|
|
9506
|
+
} catch (error) {
|
|
9507
|
+
report.error = {
|
|
9508
|
+
code: errorCode,
|
|
9509
|
+
message: error instanceof Error ? error.message : String(error)
|
|
9510
|
+
};
|
|
9511
|
+
emitSetupReport(parsed.format, report);
|
|
9512
|
+
process.exitCode = 1;
|
|
9457
9513
|
}
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9514
|
+
}
|
|
9515
|
+
async function getSetupOptions(parsed) {
|
|
9516
|
+
if (parsed.positional.length !== 1)
|
|
9517
|
+
throw new Error("Usage: aident setup [flags]");
|
|
9518
|
+
const allowedFlags = new Set(["base-url", "client-name", "oob"]);
|
|
9519
|
+
const unknownFlag = Object.keys(parsed.flags).find((flag) => !allowedFlags.has(flag));
|
|
9520
|
+
if (unknownFlag)
|
|
9521
|
+
throw new Error(`Unknown setup flag: --${unknownFlag}`);
|
|
9522
|
+
const rawBaseUrl = getOptionalStringFlag(parsed.flags, "base-url");
|
|
9523
|
+
const clientName = getOptionalStringFlag(parsed.flags, "client-name") ?? "Aident CLI";
|
|
9524
|
+
if (clientName.length > 160)
|
|
9525
|
+
throw new Error("--client-name must be at most 160 characters");
|
|
9526
|
+
return {
|
|
9527
|
+
baseUrl: rawBaseUrl ? normalizeBaseUrl(rawBaseUrl) : await resolveDefaultBaseUrl(),
|
|
9528
|
+
clientName,
|
|
9529
|
+
oob: getBooleanUpdateFlag(parsed.flags, "oob"),
|
|
9530
|
+
persistBaseUrl: !!rawBaseUrl
|
|
9531
|
+
};
|
|
9532
|
+
}
|
|
9533
|
+
function getOptionalStringFlag(flags, name) {
|
|
9534
|
+
const value = flags[name];
|
|
9535
|
+
if (value === undefined)
|
|
9536
|
+
return;
|
|
9537
|
+
if (typeof value !== "string" || !value.trim())
|
|
9538
|
+
throw new Error(`--${name} requires a value`);
|
|
9539
|
+
return value.trim();
|
|
9540
|
+
}
|
|
9541
|
+
function emitSetupReport(format, report) {
|
|
9542
|
+
if (format === "json") {
|
|
9543
|
+
logInfo(JSON.stringify(report));
|
|
9544
|
+
return;
|
|
9545
|
+
}
|
|
9546
|
+
if (report.success) {
|
|
9547
|
+
logInfo(`${colors.green}Aident Loadout setup is complete.${colors.reset}`);
|
|
9548
|
+
return;
|
|
9549
|
+
}
|
|
9550
|
+
const error = report.error;
|
|
9551
|
+
logErr(`${colors.red}Aident Loadout setup failed:${colors.reset} ${error?.message ?? "Unknown error"}`);
|
|
9552
|
+
}
|
|
9553
|
+
function renderSetupHelp() {
|
|
9554
|
+
return [
|
|
9555
|
+
`${colors.bold}AIDENT SETUP${colors.reset}`,
|
|
9556
|
+
"",
|
|
9557
|
+
"USAGE:",
|
|
9558
|
+
" aident setup [--client-name <name>] [--base-url <url>] [--oob] [--json]",
|
|
9559
|
+
"",
|
|
9560
|
+
"The setup command updates the CLI, installs or reconciles the verified Aident Skill globally, and opens sign-in when needed.",
|
|
9561
|
+
"It verifies Loadout access and records completion. AIDENT_TOKEN enables non-interactive authentication."
|
|
9562
|
+
].join(`
|
|
9563
|
+
`);
|
|
9461
9564
|
}
|
|
9462
9565
|
async function maybeOfferLocalIntegrationMigration(parsed) {
|
|
9463
9566
|
if (parsed.format !== "tui")
|
package/package.json
CHANGED