@aident-ai/cli 0.1.7 → 0.1.8-rc.0

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.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/dist/cli.mjs +139 -41
  3. 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
- logInfo(`Opening browser for Aident login...`);
149
- logInfo(`If the browser does not open, visit: ${authorizeUrlString}`);
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
- logInfo(`Opening browser for Aident login...`);
178
- logInfo(`If the browser does not open, visit: ${authorizeUrlString}`);
178
+ writeInfo(`Opening browser for Aident login...`);
179
+ writeInfo(`If the browser does not open, visit: ${authorizeUrlString}`);
179
180
  openBrowser(authorizeUrlString);
180
- logInfo("After approving, paste the 8-digit verification code shown on the Aident page below.");
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
- logInfo(`Login window open signal not received within ${LOGIN_OPEN_TIMEOUT_MS / 1000}s; still waiting for login to finish.`);
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
- logInfo(LOGIN_OPENED_MESSAGE);
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.7";
601
+ var VERSION = "0.1.8-rc.0";
600
602
 
601
603
  // src/catalogCache.ts
602
604
  var CACHE_TTL_MS = 5 * 60 * 1000;
@@ -6449,7 +6451,7 @@ var LOCAL_HELP_COMMANDS = [
6449
6451
  { command: "config get <key>", description: "Read a single value" },
6450
6452
  { command: "packages add <playbook|intern>", description: "Enable an add-on package" },
6451
6453
  { command: "doctor", description: "Validate installation" },
6452
- { command: "setup", description: "Interactive setup wizard" },
6454
+ { command: "setup", description: "Install Aident globally, authenticate, and verify Loadout" },
6453
6455
  { command: "integrations migrate-local", description: "Plan migration from local MCP configs to Loadout" },
6454
6456
  { command: "<domain> <command> [--flag value ...] [--json]", description: "Run a catalog command after login" }
6455
6457
  ];
@@ -6467,7 +6469,7 @@ function getLocalHelp(version) {
6467
6469
  "aident config get <key>",
6468
6470
  "aident packages add <playbook|intern>",
6469
6471
  "aident doctor",
6470
- "aident setup",
6472
+ "aident setup [--client-name <name>]",
6471
6473
  "aident integrations migrate-local",
6472
6474
  "aident <domain> <command> [--flag value ...] [--json]"
6473
6475
  ],
@@ -9432,32 +9434,128 @@ function formatAidentUpdateReport(report) {
9432
9434
  `);
9433
9435
  }
9434
9436
  async function runSetup(parsed) {
9435
- logInfo(`${colors.bold}Aident CLI setup${colors.reset}`);
9436
- logInfo("");
9437
- const current = await resolveDefaultBaseUrl();
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}`);
9437
+ if (parsed.isHelp) {
9438
+ logInfo(renderSetupHelp());
9439
+ return;
9443
9440
  }
9444
- logInfo("");
9445
- const existing = await readCredentials();
9446
- if (existing && credentialsForBaseUrl(existing, chosen)) {
9447
- logInfo(`${colors.green}Already signed in${colors.reset} to ${chosen}.`);
9448
- } else {
9449
- const proceed = (await readLine(`Open browser to authenticate now? [Y/n]: `)).trim().toLowerCase();
9450
- if (proceed === "" || proceed === "y" || proceed === "yes") {
9451
- const creds = await login({ baseUrl: chosen });
9452
- await writeCredentials(creds);
9453
- logInfo(`${colors.green}Signed in to ${creds.base_url}${colors.reset}`);
9454
- } else {
9455
- logInfo("Skipped login. Run `aident login` whenever you want.");
9441
+ const report = { schemaVersion: 1, success: false };
9442
+ let errorCode = "invalid-input";
9443
+ try {
9444
+ const options = await getSetupOptions(parsed);
9445
+ errorCode = "setup-failed";
9446
+ report.baseUrl = options.baseUrl;
9447
+ report.clientName = options.clientName;
9448
+ if (options.persistBaseUrl)
9449
+ await setConfigValue("baseUrl", options.baseUrl);
9450
+ const update = await runAidentUpdate({
9451
+ baseUrl: options.baseUrl,
9452
+ currentVersion: VERSION,
9453
+ mode: "apply" /* Apply */,
9454
+ includeCli: true,
9455
+ includeSkill: true,
9456
+ includeProject: true
9457
+ });
9458
+ recordLoadoutUpdateTelemetry(options.baseUrl, getLoadoutUpdateCompletionTelemetry(update));
9459
+ report.update = update;
9460
+ if (!update.success) {
9461
+ throw new Error(update.errors?.[0]?.message ?? update.skill?.detail ?? "Aident installation was incomplete");
9462
+ }
9463
+ const skillVersion = update.skill?.targetVersion;
9464
+ let credentials = await readCredentials();
9465
+ const hasEnvironmentToken = !!process.env.AIDENT_TOKEN?.trim();
9466
+ if ((!credentials || !credentialsForBaseUrl(credentials, options.baseUrl)) && !hasEnvironmentToken) {
9467
+ credentials = await login({ baseUrl: options.baseUrl, format: parsed.format, oob: options.oob });
9468
+ await writeCredentials(credentials);
9469
+ }
9470
+ const client = await getAuthenticatedClient(["loadout"]);
9471
+ if (!client)
9472
+ throw new Error("Aident credentials could not be loaded after sign-in");
9473
+ const catalog = await fetchCatalog(client);
9474
+ if (!catalog)
9475
+ throw new Error("Aident Loadout command catalog is unavailable");
9476
+ const identity = await callWithRefresh(client, (activeClient) => activeClient.exec("account", "auth status", {}));
9477
+ if (!identity.body.success) {
9478
+ throw new Error(identity.body.error?.message ?? "Aident account verification failed");
9479
+ }
9480
+ report.authenticated = true;
9481
+ report.account = identity.body.data;
9482
+ const doctor = await runDoctor({
9483
+ baseUrl: options.baseUrl,
9484
+ configFile: getConfigFile(),
9485
+ credentialsFile: getCredentialsFile()
9486
+ });
9487
+ report.doctor = doctor;
9488
+ if (!doctor.ok)
9489
+ throw new Error("Aident installation verification failed");
9490
+ const completion = await callWithRefresh(client, (activeClient) => activeClient.exec("loadout", "setup complete", {
9491
+ clientName: options.clientName,
9492
+ ...skillVersion ? { skillVersion } : {}
9493
+ }));
9494
+ if (!completion.body.success || !completion.body.data?.recorded) {
9495
+ throw new Error(completion.body.error?.message ?? "Aident setup completion was not recorded");
9456
9496
  }
9497
+ report.completion = completion.body.data;
9498
+ report.success = true;
9499
+ await maybeOfferLocalIntegrationMigration(parsed);
9500
+ emitSetupReport(parsed.format, report);
9501
+ } catch (error) {
9502
+ report.error = {
9503
+ code: errorCode,
9504
+ message: error instanceof Error ? error.message : String(error)
9505
+ };
9506
+ emitSetupReport(parsed.format, report);
9507
+ process.exitCode = 1;
9457
9508
  }
9458
- await maybeOfferLocalIntegrationMigration(parsed);
9459
- logInfo("");
9460
- logInfo(`Done. Try ${colors.cyan}aident --help${colors.reset} or ${colors.cyan}aident doctor${colors.reset}.`);
9509
+ }
9510
+ async function getSetupOptions(parsed) {
9511
+ if (parsed.positional.length !== 1)
9512
+ throw new Error("Usage: aident setup [flags]");
9513
+ const allowedFlags = new Set(["base-url", "client-name", "oob"]);
9514
+ const unknownFlag = Object.keys(parsed.flags).find((flag) => !allowedFlags.has(flag));
9515
+ if (unknownFlag)
9516
+ throw new Error(`Unknown setup flag: --${unknownFlag}`);
9517
+ const rawBaseUrl = getOptionalStringFlag(parsed.flags, "base-url");
9518
+ const clientName = getOptionalStringFlag(parsed.flags, "client-name") ?? "Aident CLI";
9519
+ if (clientName.length > 160)
9520
+ throw new Error("--client-name must be at most 160 characters");
9521
+ return {
9522
+ baseUrl: rawBaseUrl ? normalizeBaseUrl(rawBaseUrl) : await resolveDefaultBaseUrl(),
9523
+ clientName,
9524
+ oob: getBooleanUpdateFlag(parsed.flags, "oob"),
9525
+ persistBaseUrl: !!rawBaseUrl
9526
+ };
9527
+ }
9528
+ function getOptionalStringFlag(flags, name) {
9529
+ const value = flags[name];
9530
+ if (value === undefined)
9531
+ return;
9532
+ if (typeof value !== "string" || !value.trim())
9533
+ throw new Error(`--${name} requires a value`);
9534
+ return value.trim();
9535
+ }
9536
+ function emitSetupReport(format, report) {
9537
+ if (format === "json") {
9538
+ logInfo(JSON.stringify(report));
9539
+ return;
9540
+ }
9541
+ if (report.success) {
9542
+ logInfo(`${colors.green}Aident Loadout setup is complete.${colors.reset}`);
9543
+ return;
9544
+ }
9545
+ const error = report.error;
9546
+ logErr(`${colors.red}Aident Loadout setup failed:${colors.reset} ${error?.message ?? "Unknown error"}`);
9547
+ }
9548
+ function renderSetupHelp() {
9549
+ return [
9550
+ `${colors.bold}AIDENT SETUP${colors.reset}`,
9551
+ "",
9552
+ "USAGE:",
9553
+ " aident setup [--client-name <name>] [--base-url <url>] [--oob] [--json]",
9554
+ "",
9555
+ "The setup command updates the CLI, installs or reconciles the verified Aident Skill globally, and opens sign-in when needed.",
9556
+ "It verifies Loadout access and records completion. AIDENT_TOKEN enables non-interactive authentication."
9557
+ ].join(`
9558
+ `);
9461
9559
  }
9462
9560
  async function maybeOfferLocalIntegrationMigration(parsed) {
9463
9561
  if (parsed.format !== "tui")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aident-ai/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8-rc.0",
4
4
  "description": "Aident CLI — umbrella access to Loadout integrations, Playbook automation, Intern tools, and the Aident platform.",
5
5
  "homepage": "https://aident.ai",
6
6
  "repository": {