@aident-ai/cli 0.0.6 → 0.0.7-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.
- package/README.md +47 -73
- package/dist/cli.mjs +356 -139
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -1,119 +1,93 @@
|
|
|
1
1
|
# @aident-ai/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agent-friendly access to Aident Loadout by default, with Playbook and Intern available as packages.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
aident login
|
|
7
7
|
aident capabilities search --query "send email"
|
|
8
|
+
aident packages add playbook
|
|
8
9
|
aident playbooks list --json
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
The CLI is the only npm CLI package Aident ships. It is a thin wrapper around the public OpenAPI package APIs under
|
|
13
|
+
`/api/openapi/...`, so CLI behavior stays aligned with API and MCP behavior.
|
|
12
14
|
|
|
13
15
|
## Install
|
|
14
16
|
|
|
15
|
-
### One-line install (recommended)
|
|
16
|
-
|
|
17
|
-
**macOS / Linux:**
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
curl -fsSL https://app.aident.ai/cli/install.sh | bash
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Windows (PowerShell, with Git Bash installed):**
|
|
24
|
-
|
|
25
|
-
```powershell
|
|
26
|
-
& "C:\Program Files\Git\bin\bash.exe" -c 'curl -fsSL https://app.aident.ai/cli/install.sh | bash'
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
The installer requires Node ≥ 18. If you don't have it, install via [nodejs.org](https://nodejs.org) or a version manager (`brew install node`, `nvm install --lts`, `fnm install --lts`) and re-run.
|
|
30
|
-
|
|
31
|
-
### Manual install
|
|
32
|
-
|
|
33
17
|
```bash
|
|
34
|
-
# One-off (no install)
|
|
35
18
|
npx -y @aident-ai/cli <command>
|
|
36
|
-
|
|
37
|
-
# Global
|
|
38
19
|
npm install -g @aident-ai/cli
|
|
39
20
|
```
|
|
40
21
|
|
|
41
|
-
## Get
|
|
22
|
+
## Get Started
|
|
42
23
|
|
|
43
24
|
```bash
|
|
44
|
-
aident setup
|
|
45
|
-
aident doctor
|
|
46
|
-
aident --help
|
|
25
|
+
aident setup
|
|
26
|
+
aident doctor
|
|
27
|
+
aident --help
|
|
47
28
|
```
|
|
48
29
|
|
|
49
|
-
|
|
30
|
+
Use `--oob` for browserless auth environments:
|
|
50
31
|
|
|
51
32
|
```bash
|
|
52
33
|
aident login --oob
|
|
53
34
|
```
|
|
54
35
|
|
|
55
|
-
|
|
36
|
+
## Packages
|
|
56
37
|
|
|
57
|
-
|
|
38
|
+
Loadout is always enabled by default. Add Playbook only when an agent needs to create, execute, or manage playbooks.
|
|
39
|
+
Intern is admin-gated and should be used only by authenticated admin users.
|
|
58
40
|
|
|
59
41
|
```bash
|
|
60
|
-
aident
|
|
61
|
-
aident
|
|
62
|
-
aident
|
|
63
|
-
aident playbooks list --json
|
|
64
|
-
aident playbooks execute --playbookId pb_123
|
|
65
|
-
aident integrations status # see connected integrations
|
|
66
|
-
aident logout # revoke and clear creds
|
|
42
|
+
aident --help
|
|
43
|
+
aident packages add playbook
|
|
44
|
+
aident packages list
|
|
45
|
+
aident --package playbook playbooks list --json
|
|
46
|
+
aident --packages playbook playbooks execute --playbookId pb_123 --json
|
|
67
47
|
```
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
## Configuration
|
|
72
|
-
|
|
73
|
-
The CLI persists settings to `~/.aident/config.json` and credentials to `~/.aident/credentials.json` — the same `~/.aident/` directory used by the Aident skill, the desktop sandbox app, and any other Aident tooling.
|
|
49
|
+
## Common Loadout Commands
|
|
74
50
|
|
|
75
51
|
```bash
|
|
76
|
-
aident
|
|
77
|
-
aident
|
|
78
|
-
aident
|
|
79
|
-
aident
|
|
80
|
-
aident
|
|
52
|
+
aident capabilities search --query "send email"
|
|
53
|
+
aident capabilities get --name "gmail.send_email"
|
|
54
|
+
aident capabilities execute --name "gmail.send_email" --input '{"to":"user@example.com"}'
|
|
55
|
+
aident vault status --integrationId github_tools
|
|
56
|
+
aident audit recent --limit 20
|
|
81
57
|
```
|
|
82
58
|
|
|
83
|
-
|
|
84
|
-
| --------- | ----------------------- | ---------------------------------------------------------- |
|
|
85
|
-
| `baseUrl` | `https://app.aident.ai` | API host used for new logins. Save once, reuse everywhere. |
|
|
86
|
-
|
|
87
|
-
### Resolution order for the API base URL
|
|
59
|
+
## Configuration
|
|
88
60
|
|
|
89
|
-
|
|
90
|
-
2. `aident login --base-url <url>` (only at login time)
|
|
91
|
-
3. `~/.aident/config.json` `baseUrl`
|
|
92
|
-
4. Active credentials file (after login carries its own host)
|
|
93
|
-
5. Default `https://app.aident.ai`
|
|
61
|
+
Settings live in `~/.aident/config.json`; OAuth credentials live in `~/.aident/credentials.json`.
|
|
94
62
|
|
|
95
|
-
|
|
63
|
+
| Key | Default | Purpose |
|
|
64
|
+
| ---------- | ----------------------- | ----------------------------------------------------- |
|
|
65
|
+
| `baseUrl` | `https://app.aident.ai` | API host used for logins and command execution. |
|
|
66
|
+
| `packages` | `["loadout"]` | Enabled packages. Loadout is always included locally. |
|
|
96
67
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
68
|
+
```bash
|
|
69
|
+
aident config show
|
|
70
|
+
aident config set baseUrl https://app.aident.ai
|
|
71
|
+
aident config set packages playbook
|
|
72
|
+
aident config unset packages
|
|
73
|
+
```
|
|
101
74
|
|
|
102
|
-
|
|
75
|
+
Environment overrides:
|
|
103
76
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
| `~/.aident/sandbox/` | Sandbox daemon | Daemon PID, status, logs |
|
|
77
|
+
| Variable | Purpose |
|
|
78
|
+
| ----------------- | ----------------------------------------------------------- |
|
|
79
|
+
| `AIDENT_TOKEN` | Use this Bearer token directly; skips the credentials file. |
|
|
80
|
+
| `AIDENT_BASE_URL` | Override the API host for one invocation. |
|
|
81
|
+
| `AIDENT_PACKAGE` | Focus one package for one invocation. |
|
|
82
|
+
| `AIDENT_PACKAGES` | Enable add-on packages for one invocation, e.g. `playbook`. |
|
|
111
83
|
|
|
112
|
-
|
|
84
|
+
## How It Works
|
|
113
85
|
|
|
114
|
-
|
|
86
|
+
Discovery fetches `/api/openapi/{package}.json` and reads the package command catalog embedded in the OpenAPI document.
|
|
87
|
+
Execution calls `/api/openapi/{package}/{operationId}` with the command arguments as JSON.
|
|
115
88
|
|
|
116
|
-
|
|
89
|
+
When multiple packages are enabled, the CLI fetches each package schema, merges the catalogs locally, and routes each
|
|
90
|
+
command back to the package operation that contributed it. Unknown commands are not guessed across packages.
|
|
117
91
|
|
|
118
92
|
## License
|
|
119
93
|
|
package/dist/cli.mjs
CHANGED
|
@@ -5,100 +5,6 @@ import { spawn } from "node:child_process";
|
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
|
|
8
|
-
// src/credentials.ts
|
|
9
|
-
import { mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
10
|
-
import { join as join2 } from "node:path";
|
|
11
|
-
|
|
12
|
-
// src/config.ts
|
|
13
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
function getAidentDir() {
|
|
17
|
-
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
18
|
-
return join(home, ".aident");
|
|
19
|
-
}
|
|
20
|
-
function getConfigFile() {
|
|
21
|
-
return join(getAidentDir(), "config.json");
|
|
22
|
-
}
|
|
23
|
-
var DEFAULT_BASE_URL = "https://app.aident.ai";
|
|
24
|
-
var CONFIG_KEYS = ["baseUrl"];
|
|
25
|
-
async function readConfig() {
|
|
26
|
-
try {
|
|
27
|
-
const text = await readFile(getConfigFile(), "utf-8");
|
|
28
|
-
const parsed = JSON.parse(text);
|
|
29
|
-
if (!parsed || typeof parsed !== "object")
|
|
30
|
-
return {};
|
|
31
|
-
return parsed;
|
|
32
|
-
} catch {
|
|
33
|
-
return {};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function writeConfig(config) {
|
|
37
|
-
await mkdir(getAidentDir(), { recursive: true, mode: 448 });
|
|
38
|
-
await writeFile(getConfigFile(), JSON.stringify(config, null, 2) + `
|
|
39
|
-
`, { mode: 420 });
|
|
40
|
-
}
|
|
41
|
-
async function setConfigValue(key, value) {
|
|
42
|
-
const config = await readConfig();
|
|
43
|
-
config[key] = value;
|
|
44
|
-
await writeConfig(config);
|
|
45
|
-
}
|
|
46
|
-
async function unsetConfigValue(key) {
|
|
47
|
-
const config = await readConfig();
|
|
48
|
-
delete config[key];
|
|
49
|
-
await writeConfig(config);
|
|
50
|
-
}
|
|
51
|
-
async function resolveDefaultBaseUrl() {
|
|
52
|
-
const env = process.env.AIDENT_BASE_URL?.trim();
|
|
53
|
-
if (env)
|
|
54
|
-
return env;
|
|
55
|
-
const config = await readConfig();
|
|
56
|
-
if (typeof config.baseUrl === "string" && config.baseUrl.trim() !== "") {
|
|
57
|
-
return config.baseUrl.trim();
|
|
58
|
-
}
|
|
59
|
-
return DEFAULT_BASE_URL;
|
|
60
|
-
}
|
|
61
|
-
function isKnownConfigKey(key) {
|
|
62
|
-
return CONFIG_KEYS.includes(key);
|
|
63
|
-
}
|
|
64
|
-
function normalizeBaseUrl(url) {
|
|
65
|
-
let trimmed = url.trim();
|
|
66
|
-
if (!/^https?:\/\//i.test(trimmed))
|
|
67
|
-
trimmed = `https://${trimmed}`;
|
|
68
|
-
return trimmed.replace(/\/+$/, "");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// src/credentials.ts
|
|
72
|
-
function getCredentialsFile() {
|
|
73
|
-
return join2(getAidentDir(), "credentials.json");
|
|
74
|
-
}
|
|
75
|
-
async function readCredentials() {
|
|
76
|
-
try {
|
|
77
|
-
const content = await readFile2(getCredentialsFile(), "utf-8");
|
|
78
|
-
const parsed = JSON.parse(content);
|
|
79
|
-
if (!parsed.access_token || !parsed.base_url)
|
|
80
|
-
return null;
|
|
81
|
-
return parsed;
|
|
82
|
-
} catch {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async function writeCredentials(creds) {
|
|
87
|
-
await mkdir2(getAidentDir(), { recursive: true, mode: 448 });
|
|
88
|
-
await writeFile2(getCredentialsFile(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
89
|
-
}
|
|
90
|
-
async function clearCredentials() {
|
|
91
|
-
await rm(getCredentialsFile(), { force: true });
|
|
92
|
-
}
|
|
93
|
-
function isExpired(creds) {
|
|
94
|
-
if (!creds.expires_at)
|
|
95
|
-
return false;
|
|
96
|
-
const expiresAt = Date.parse(creds.expires_at);
|
|
97
|
-
if (Number.isNaN(expiresAt))
|
|
98
|
-
return false;
|
|
99
|
-
return Date.now() >= expiresAt - 60000;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
8
|
// src/output.ts
|
|
103
9
|
var isTTY = process.stdout.isTTY === true;
|
|
104
10
|
var colors = {
|
|
@@ -218,7 +124,7 @@ async function loginOob(baseUrl) {
|
|
|
218
124
|
const verifier = base64UrlEncode(randomBytes(48));
|
|
219
125
|
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
220
126
|
const state = base64UrlEncode(randomBytes(16));
|
|
221
|
-
const clientId = await
|
|
127
|
+
const clientId = await registerClient(baseUrl, [redirectUri]);
|
|
222
128
|
const authorizeUrl = new URL(`${baseUrl}/api/mcp/oauth/authorize`);
|
|
223
129
|
authorizeUrl.searchParams.set("response_type", "code");
|
|
224
130
|
authorizeUrl.searchParams.set("client_id", clientId);
|
|
@@ -255,12 +161,6 @@ function looksLikeAccessToken(value) {
|
|
|
255
161
|
return false;
|
|
256
162
|
return /^[A-Za-z0-9._-]+$/.test(value);
|
|
257
163
|
}
|
|
258
|
-
async function reuseOrRegisterClient(baseUrl, redirectUris) {
|
|
259
|
-
const existing = await readCredentials();
|
|
260
|
-
if (existing && existing.client_id && existing.base_url === baseUrl)
|
|
261
|
-
return existing.client_id;
|
|
262
|
-
return registerClient(baseUrl, redirectUris);
|
|
263
|
-
}
|
|
264
164
|
async function registerClient(baseUrl, redirectUris) {
|
|
265
165
|
const res = await fetch(`${baseUrl}/api/mcp/oauth/register`, {
|
|
266
166
|
method: "POST",
|
|
@@ -402,25 +302,91 @@ function escapeHtml(s) {
|
|
|
402
302
|
}
|
|
403
303
|
|
|
404
304
|
// src/version.ts
|
|
405
|
-
var VERSION = "0.0.
|
|
305
|
+
var VERSION = "0.0.7-rc.0";
|
|
406
306
|
|
|
407
307
|
// src/client.ts
|
|
408
308
|
class CliClient {
|
|
409
309
|
creds;
|
|
410
|
-
|
|
310
|
+
credentialSource;
|
|
311
|
+
packages;
|
|
312
|
+
commandOperations = new Map;
|
|
313
|
+
constructor(creds, credentialSource = "stored", packages) {
|
|
411
314
|
this.creds = creds;
|
|
315
|
+
this.credentialSource = credentialSource;
|
|
316
|
+
if (packages.length === 0)
|
|
317
|
+
throw new Error("CliClient packages must not be empty");
|
|
318
|
+
this.packages = [...packages];
|
|
412
319
|
}
|
|
413
320
|
get baseUrl() {
|
|
414
321
|
return this.creds.base_url.replace(/\/+$/, "");
|
|
415
322
|
}
|
|
323
|
+
get canRefresh() {
|
|
324
|
+
return this.credentialSource === "stored";
|
|
325
|
+
}
|
|
326
|
+
get activePackages() {
|
|
327
|
+
return this.packages;
|
|
328
|
+
}
|
|
416
329
|
updateCredentials(creds) {
|
|
417
330
|
this.creds = creds;
|
|
418
331
|
}
|
|
419
332
|
async getCatalog() {
|
|
420
|
-
|
|
333
|
+
if (this.packages.length === 1) {
|
|
334
|
+
const packageName = this.packages[0];
|
|
335
|
+
const result = await this.fetchOpenApiCatalog(packageName);
|
|
336
|
+
if (result.status === 200)
|
|
337
|
+
this.indexCatalogCommands(result.body, packageName);
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
const results = await Promise.all(this.packages.map((packageName) => this.fetchOpenApiCatalog(packageName)));
|
|
341
|
+
const failed = results.find((result) => result.status !== 200);
|
|
342
|
+
if (failed)
|
|
343
|
+
return failed;
|
|
344
|
+
const catalog = mergeCatalogs(results.map((result) => result.body), this.packages, (domain, command, packageName, operationId) => this.commandOperations.set(commandKey(domain, command), { packageName, operationId }));
|
|
345
|
+
return { status: 200, body: catalog };
|
|
421
346
|
}
|
|
422
347
|
async exec(domain, command, args) {
|
|
423
|
-
|
|
348
|
+
const operation = this.resolveOperation(domain, command);
|
|
349
|
+
if (operation === null) {
|
|
350
|
+
return {
|
|
351
|
+
status: 404,
|
|
352
|
+
body: {
|
|
353
|
+
success: false,
|
|
354
|
+
error: {
|
|
355
|
+
code: "unknown-command",
|
|
356
|
+
message: `Unknown command: ${domain} ${command}. Run \`aident --help\` to refresh the command catalog.`
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return this.fetchJson("POST", this.operationPath(operation.packageName, operation.operationId), args);
|
|
362
|
+
}
|
|
363
|
+
async fetchOpenApiCatalog(packageName) {
|
|
364
|
+
const result = await this.fetchJson("GET", this.catalogPath(packageName));
|
|
365
|
+
const catalog = result.body["x-aident-command-catalog"];
|
|
366
|
+
if (result.status !== 200 || !catalog) {
|
|
367
|
+
return {
|
|
368
|
+
status: result.status,
|
|
369
|
+
body: invalidResponseFallback(JSON.stringify(result.body))
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return { status: result.status, body: catalog };
|
|
373
|
+
}
|
|
374
|
+
catalogPath(packageName) {
|
|
375
|
+
return `/api/openapi/${encodeURIComponent(packageName)}.json`;
|
|
376
|
+
}
|
|
377
|
+
operationPath(packageName, operationId) {
|
|
378
|
+
return `/api/openapi/${encodeURIComponent(packageName)}/${encodeURIComponent(operationId)}`;
|
|
379
|
+
}
|
|
380
|
+
resolveOperation(domain, command) {
|
|
381
|
+
return this.commandOperations.get(commandKey(domain, command)) ?? null;
|
|
382
|
+
}
|
|
383
|
+
indexCatalogCommands(catalog, packageName) {
|
|
384
|
+
for (const command of catalog.commands) {
|
|
385
|
+
this.commandOperations.set(commandKey(command.domain, command.command), {
|
|
386
|
+
packageName,
|
|
387
|
+
operationId: command.operationId
|
|
388
|
+
});
|
|
389
|
+
}
|
|
424
390
|
}
|
|
425
391
|
async fetchJson(method, path, body) {
|
|
426
392
|
const headers = {
|
|
@@ -445,6 +411,170 @@ class CliClient {
|
|
|
445
411
|
function invalidResponseFallback(text) {
|
|
446
412
|
return { success: false, error: { code: "invalid-response", message: text } };
|
|
447
413
|
}
|
|
414
|
+
function commandKey(domain, command) {
|
|
415
|
+
return `${domain}\x00${command}`;
|
|
416
|
+
}
|
|
417
|
+
function mergeCatalogs(catalogs, packages, onCommand) {
|
|
418
|
+
const packageMap = new Map;
|
|
419
|
+
const domainMap = new Map;
|
|
420
|
+
const commandMap = new Map;
|
|
421
|
+
catalogs.forEach((catalog, index) => {
|
|
422
|
+
const packageName = packages[index];
|
|
423
|
+
for (const pkg of catalog.packages) {
|
|
424
|
+
packageMap.set(pkg.name, {
|
|
425
|
+
...pkg,
|
|
426
|
+
domainCount: (packageMap.get(pkg.name)?.domainCount ?? 0) + pkg.domainCount
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
for (const domain of catalog.domains) {
|
|
430
|
+
const existing = domainMap.get(domain.name);
|
|
431
|
+
domainMap.set(domain.name, {
|
|
432
|
+
...domain,
|
|
433
|
+
commandCount: (existing?.commandCount ?? 0) + domain.commandCount
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
for (const command of catalog.commands) {
|
|
437
|
+
const key = commandKey(command.domain, command.command);
|
|
438
|
+
if (!commandMap.has(key)) {
|
|
439
|
+
commandMap.set(key, command);
|
|
440
|
+
onCommand(command.domain, command.command, packageName, command.operationId);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return {
|
|
445
|
+
packages: [...packageMap.values()],
|
|
446
|
+
domains: [...domainMap.values()],
|
|
447
|
+
commands: [...commandMap.values()]
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/config.ts
|
|
452
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
453
|
+
import { homedir } from "node:os";
|
|
454
|
+
import { join } from "node:path";
|
|
455
|
+
|
|
456
|
+
// src/packages.ts
|
|
457
|
+
var CLI_PACKAGES = ["loadout", "playbook", "intern"];
|
|
458
|
+
var DEFAULT_CLI_PACKAGES = ["loadout"];
|
|
459
|
+
function parseCliPackage(value) {
|
|
460
|
+
if (typeof value !== "string")
|
|
461
|
+
throw new Error(`Unsupported Aident package: ${String(value)}`);
|
|
462
|
+
const normalized = value.trim().toLowerCase();
|
|
463
|
+
if (!CLI_PACKAGES.includes(normalized))
|
|
464
|
+
throw new Error(`Unsupported Aident package: ${value}`);
|
|
465
|
+
return normalized;
|
|
466
|
+
}
|
|
467
|
+
function normalizeCliPackages(value) {
|
|
468
|
+
const rawPackages = value === undefined || value === null || value === "" ? [] : Array.isArray(value) ? value : String(value).split(/[,\s]+/);
|
|
469
|
+
const packages = ["loadout"];
|
|
470
|
+
for (const rawPackage of rawPackages) {
|
|
471
|
+
if (rawPackage === undefined || rawPackage === null || rawPackage === "")
|
|
472
|
+
continue;
|
|
473
|
+
const cliPackage = parseCliPackage(rawPackage);
|
|
474
|
+
if (!packages.includes(cliPackage))
|
|
475
|
+
packages.push(cliPackage);
|
|
476
|
+
}
|
|
477
|
+
return packages;
|
|
478
|
+
}
|
|
479
|
+
function formatCliPackages(packages) {
|
|
480
|
+
return packages.join(",");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/config.ts
|
|
484
|
+
function getAidentDir() {
|
|
485
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
486
|
+
return join(home, ".aident");
|
|
487
|
+
}
|
|
488
|
+
function getConfigFile() {
|
|
489
|
+
return join(getAidentDir(), "config.json");
|
|
490
|
+
}
|
|
491
|
+
var DEFAULT_BASE_URL = "https://app.aident.ai";
|
|
492
|
+
var CONFIG_KEYS = ["baseUrl", "packages"];
|
|
493
|
+
async function readConfig() {
|
|
494
|
+
try {
|
|
495
|
+
const text = await readFile(getConfigFile(), "utf-8");
|
|
496
|
+
const parsed = JSON.parse(text);
|
|
497
|
+
if (!parsed || typeof parsed !== "object")
|
|
498
|
+
return {};
|
|
499
|
+
return parsed;
|
|
500
|
+
} catch {
|
|
501
|
+
return {};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function writeConfig(config) {
|
|
505
|
+
await mkdir(getAidentDir(), { recursive: true, mode: 448 });
|
|
506
|
+
await writeFile(getConfigFile(), JSON.stringify(config, null, 2) + `
|
|
507
|
+
`, { mode: 420 });
|
|
508
|
+
}
|
|
509
|
+
async function setConfigValue(key, value) {
|
|
510
|
+
const config = await readConfig();
|
|
511
|
+
config[key] = value;
|
|
512
|
+
await writeConfig(config);
|
|
513
|
+
}
|
|
514
|
+
async function unsetConfigValue(key) {
|
|
515
|
+
const config = await readConfig();
|
|
516
|
+
delete config[key];
|
|
517
|
+
await writeConfig(config);
|
|
518
|
+
}
|
|
519
|
+
async function resolveDefaultBaseUrl() {
|
|
520
|
+
const env = process.env.AIDENT_BASE_URL?.trim();
|
|
521
|
+
if (env)
|
|
522
|
+
return env;
|
|
523
|
+
const config = await readConfig();
|
|
524
|
+
if (typeof config.baseUrl === "string" && config.baseUrl.trim() !== "") {
|
|
525
|
+
return config.baseUrl.trim();
|
|
526
|
+
}
|
|
527
|
+
return DEFAULT_BASE_URL;
|
|
528
|
+
}
|
|
529
|
+
async function resolveDefaultPackages() {
|
|
530
|
+
const env = process.env.AIDENT_PACKAGES?.trim() || process.env.AIDENT_PACKAGE?.trim();
|
|
531
|
+
if (env)
|
|
532
|
+
return normalizeCliPackages(env);
|
|
533
|
+
const config = await readConfig();
|
|
534
|
+
return normalizeCliPackages(config.packages ?? DEFAULT_CLI_PACKAGES);
|
|
535
|
+
}
|
|
536
|
+
function isKnownConfigKey(key) {
|
|
537
|
+
return CONFIG_KEYS.includes(key);
|
|
538
|
+
}
|
|
539
|
+
function normalizeBaseUrl(url) {
|
|
540
|
+
let trimmed = url.trim();
|
|
541
|
+
if (!/^https?:\/\//i.test(trimmed))
|
|
542
|
+
trimmed = `https://${trimmed}`;
|
|
543
|
+
return trimmed.replace(/\/+$/, "");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/credentials.ts
|
|
547
|
+
import { mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
548
|
+
import { join as join2 } from "node:path";
|
|
549
|
+
function getCredentialsFile() {
|
|
550
|
+
return join2(getAidentDir(), "credentials.json");
|
|
551
|
+
}
|
|
552
|
+
async function readCredentials() {
|
|
553
|
+
try {
|
|
554
|
+
const content = await readFile2(getCredentialsFile(), "utf-8");
|
|
555
|
+
const parsed = JSON.parse(content);
|
|
556
|
+
if (!parsed.access_token || !parsed.base_url)
|
|
557
|
+
return null;
|
|
558
|
+
return parsed;
|
|
559
|
+
} catch {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function writeCredentials(creds) {
|
|
564
|
+
await mkdir2(getAidentDir(), { recursive: true, mode: 448 });
|
|
565
|
+
await writeFile2(getCredentialsFile(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
566
|
+
}
|
|
567
|
+
async function clearCredentials() {
|
|
568
|
+
await rm(getCredentialsFile(), { force: true });
|
|
569
|
+
}
|
|
570
|
+
function isExpired(creds) {
|
|
571
|
+
if (!creds.expires_at)
|
|
572
|
+
return false;
|
|
573
|
+
const expiresAt = Date.parse(creds.expires_at);
|
|
574
|
+
if (Number.isNaN(expiresAt))
|
|
575
|
+
return false;
|
|
576
|
+
return Date.now() >= expiresAt - 60000;
|
|
577
|
+
}
|
|
448
578
|
|
|
449
579
|
// src/doctor.ts
|
|
450
580
|
async function runDoctor(opts) {
|
|
@@ -494,7 +624,7 @@ async function checkCredentials(baseUrl) {
|
|
|
494
624
|
}
|
|
495
625
|
async function checkServerReachable(baseUrl) {
|
|
496
626
|
try {
|
|
497
|
-
const res = await fetch(`${baseUrl}/api/
|
|
627
|
+
const res = await fetch(`${baseUrl}/api/openapi/loadout.json`, {
|
|
498
628
|
method: "GET",
|
|
499
629
|
headers: { "User-Agent": `@aident-ai/cli/${VERSION}` }
|
|
500
630
|
});
|
|
@@ -533,7 +663,7 @@ function renderHelp(catalog) {
|
|
|
533
663
|
lines.push(" aident <domain> <command> help Show input schema and examples");
|
|
534
664
|
lines.push("");
|
|
535
665
|
if (catalog.packages.length > 0) {
|
|
536
|
-
lines.push("
|
|
666
|
+
lines.push("COMMAND GROUPS:");
|
|
537
667
|
for (const pkg of catalog.packages) {
|
|
538
668
|
lines.push(` ${pkg.name.padEnd(10)} ${pkg.description}`);
|
|
539
669
|
}
|
|
@@ -612,7 +742,7 @@ function renderCommandHelp(catalog, domain, command) {
|
|
|
612
742
|
}
|
|
613
743
|
|
|
614
744
|
// src/parser.ts
|
|
615
|
-
var RESERVED_CLI_FLAGS = ["base-url", "json", "tui", "help", "oob", "version"];
|
|
745
|
+
var RESERVED_CLI_FLAGS = ["base-url", "json", "tui", "help", "oob", "version", "package", "packages"];
|
|
616
746
|
function parseArgs(argv) {
|
|
617
747
|
const positional = [];
|
|
618
748
|
const flags = {};
|
|
@@ -694,7 +824,7 @@ function coerceArgsToSchema(args, schema) {
|
|
|
694
824
|
} else {
|
|
695
825
|
out[key] = [value];
|
|
696
826
|
}
|
|
697
|
-
} else if (types.includes("string") && !types.includes("number") && typeof value === "number") {
|
|
827
|
+
} else if (types.includes("string") && !types.includes("number") && !types.includes("boolean") && (typeof value === "number" || typeof value === "boolean")) {
|
|
698
828
|
out[key] = String(value);
|
|
699
829
|
}
|
|
700
830
|
}
|
|
@@ -740,6 +870,8 @@ async function callWithRefresh(client, call, deps = defaultDeps) {
|
|
|
740
870
|
}
|
|
741
871
|
if (first.status !== 401)
|
|
742
872
|
return first;
|
|
873
|
+
if (!client.canRefresh)
|
|
874
|
+
return first;
|
|
743
875
|
const creds = await deps.readCredentials();
|
|
744
876
|
if (!creds)
|
|
745
877
|
return first;
|
|
@@ -769,7 +901,7 @@ async function main() {
|
|
|
769
901
|
return;
|
|
770
902
|
}
|
|
771
903
|
if (!first || parsed.isHelp && parsed.positional.length === 0) {
|
|
772
|
-
await runHelp(parsed
|
|
904
|
+
await runHelp(parsed);
|
|
773
905
|
return;
|
|
774
906
|
}
|
|
775
907
|
switch (first) {
|
|
@@ -780,7 +912,7 @@ async function main() {
|
|
|
780
912
|
await runLogout();
|
|
781
913
|
return;
|
|
782
914
|
case "whoami":
|
|
783
|
-
await runWhoami(parsed
|
|
915
|
+
await runWhoami(parsed);
|
|
784
916
|
return;
|
|
785
917
|
case "doctor":
|
|
786
918
|
await runDoctorCmd(parsed.format);
|
|
@@ -791,17 +923,21 @@ async function main() {
|
|
|
791
923
|
case "config":
|
|
792
924
|
await runConfigCmd(parsed);
|
|
793
925
|
return;
|
|
926
|
+
case "packages":
|
|
927
|
+
case "package":
|
|
928
|
+
await runPackagesCmd(parsed);
|
|
929
|
+
return;
|
|
794
930
|
case "catalog":
|
|
795
|
-
await runCatalog(parsed
|
|
931
|
+
await runCatalog(parsed);
|
|
796
932
|
return;
|
|
797
933
|
default:
|
|
798
934
|
await runCommand(parsed);
|
|
799
935
|
}
|
|
800
936
|
}
|
|
801
|
-
async function runHelp(
|
|
802
|
-
const client = await getAuthenticatedClient();
|
|
937
|
+
async function runHelp(parsed) {
|
|
938
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
803
939
|
if (!client) {
|
|
804
|
-
if (format === "json") {
|
|
940
|
+
if (parsed.format === "json") {
|
|
805
941
|
logInfo(JSON.stringify({ error: "not-authenticated", message: "Run `aident login` first." }));
|
|
806
942
|
} else {
|
|
807
943
|
logInfo(`${colors.bold}Aident CLI v${VERSION}${colors.reset}`);
|
|
@@ -815,6 +951,7 @@ async function runHelp(format) {
|
|
|
815
951
|
logInfo(" aident config show Print persistent config");
|
|
816
952
|
logInfo(" aident config set <key> <value> Persist a config value");
|
|
817
953
|
logInfo(" aident config get <key> Read a single value");
|
|
954
|
+
logInfo(" aident packages add <playbook|intern> Enable an add-on package");
|
|
818
955
|
logInfo(" aident doctor Validate installation");
|
|
819
956
|
logInfo(" aident setup Interactive setup wizard");
|
|
820
957
|
logInfo(" aident <domain> <command> [--flag value ...] [--json]");
|
|
@@ -824,7 +961,7 @@ async function runHelp(format) {
|
|
|
824
961
|
const catalog = await fetchCatalog(client);
|
|
825
962
|
if (!catalog)
|
|
826
963
|
return;
|
|
827
|
-
if (format === "json") {
|
|
964
|
+
if (parsed.format === "json") {
|
|
828
965
|
logInfo(JSON.stringify(catalog));
|
|
829
966
|
} else {
|
|
830
967
|
logInfo(renderHelp(catalog));
|
|
@@ -850,10 +987,10 @@ async function runLogout() {
|
|
|
850
987
|
await clearCredentials();
|
|
851
988
|
logInfo(`${colors.green}Signed out.${colors.reset}`);
|
|
852
989
|
}
|
|
853
|
-
async function runWhoami(
|
|
854
|
-
const client = await getAuthenticatedClient();
|
|
990
|
+
async function runWhoami(parsed) {
|
|
991
|
+
const client = await getAuthenticatedClient(["loadout"]);
|
|
855
992
|
if (!client) {
|
|
856
|
-
if (format === "json") {
|
|
993
|
+
if (parsed.format === "json") {
|
|
857
994
|
logInfo(JSON.stringify({ authenticated: false }));
|
|
858
995
|
} else {
|
|
859
996
|
logInfo("Not signed in. Run `aident login`.");
|
|
@@ -861,8 +998,11 @@ async function runWhoami(format) {
|
|
|
861
998
|
process.exitCode = 1;
|
|
862
999
|
return;
|
|
863
1000
|
}
|
|
1001
|
+
const catalog = await fetchCatalog(client);
|
|
1002
|
+
if (!catalog)
|
|
1003
|
+
return;
|
|
864
1004
|
const result = await callWithRefresh(client, (c) => c.exec("account", "auth status", {}));
|
|
865
|
-
if (format === "json") {
|
|
1005
|
+
if (parsed.format === "json") {
|
|
866
1006
|
logInfo(JSON.stringify(result.body));
|
|
867
1007
|
if (!result.body.success)
|
|
868
1008
|
process.exitCode = 1;
|
|
@@ -874,7 +1014,7 @@ async function runWhoami(format) {
|
|
|
874
1014
|
return;
|
|
875
1015
|
}
|
|
876
1016
|
const err = result.body.error;
|
|
877
|
-
logErr(`${colors.red}
|
|
1017
|
+
logErr(`${colors.red}x ${err?.code ?? "error"}:${colors.reset} ${err?.message ?? "Unable to fetch user info."}`);
|
|
878
1018
|
process.exitCode = 1;
|
|
879
1019
|
}
|
|
880
1020
|
async function runConfigCmd(parsed) {
|
|
@@ -887,8 +1027,9 @@ async function runConfigCmd(parsed) {
|
|
|
887
1027
|
}
|
|
888
1028
|
logInfo(`${colors.bold}Aident config${colors.reset} ${colors.dim}(${getConfigFile()})${colors.reset}`);
|
|
889
1029
|
if (Object.keys(config).length === 0) {
|
|
890
|
-
logInfo(` ${colors.dim}(empty
|
|
1030
|
+
logInfo(` ${colors.dim}(empty, using defaults)${colors.reset}`);
|
|
891
1031
|
logInfo(` ${colors.dim}baseUrl${colors.reset} ${colors.dim}default: ${DEFAULT_BASE_URL}${colors.reset}`);
|
|
1032
|
+
logInfo(` ${colors.dim}packages${colors.reset} ${colors.dim}default: loadout${colors.reset}`);
|
|
892
1033
|
return;
|
|
893
1034
|
}
|
|
894
1035
|
for (const [key, val] of Object.entries(config)) {
|
|
@@ -934,14 +1075,18 @@ async function runConfigCmd(parsed) {
|
|
|
934
1075
|
process.exitCode = 1;
|
|
935
1076
|
return;
|
|
936
1077
|
}
|
|
937
|
-
const value = key === "baseUrl" ? normalizeBaseUrl(rawValue) : rawValue;
|
|
938
|
-
await setConfigValue(key, value);
|
|
939
|
-
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
940
1078
|
if (key === "baseUrl") {
|
|
1079
|
+
const value = normalizeBaseUrl(rawValue);
|
|
1080
|
+
await setConfigValue(key, value);
|
|
1081
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
941
1082
|
const creds = await readCredentials();
|
|
942
1083
|
if (creds && creds.base_url !== value) {
|
|
943
1084
|
logInfo(`${colors.yellow}Note:${colors.reset} existing credentials are for ${creds.base_url}. Run \`aident login\` to authenticate against ${value}.`);
|
|
944
1085
|
}
|
|
1086
|
+
} else {
|
|
1087
|
+
const value = normalizeCliPackages(rawValue);
|
|
1088
|
+
await setConfigValue(key, value);
|
|
1089
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${formatConfigValue(value)}`);
|
|
945
1090
|
}
|
|
946
1091
|
return;
|
|
947
1092
|
}
|
|
@@ -960,6 +1105,54 @@ async function runConfigCmd(parsed) {
|
|
|
960
1105
|
logErr("Usage: aident config [show | get <key> | set <key> <value> | unset <key> | path]");
|
|
961
1106
|
process.exitCode = 1;
|
|
962
1107
|
}
|
|
1108
|
+
async function runPackagesCmd(parsed) {
|
|
1109
|
+
const sub = parsed.positional[1] ?? "list";
|
|
1110
|
+
if (sub === "list" || sub === "show") {
|
|
1111
|
+
const packages = await resolveDefaultPackages();
|
|
1112
|
+
if (parsed.format === "json") {
|
|
1113
|
+
logInfo(JSON.stringify({ packages, defaultPackage: "loadout" }));
|
|
1114
|
+
} else {
|
|
1115
|
+
logInfo(`${colors.bold}Aident packages${colors.reset}`);
|
|
1116
|
+
logInfo(` ${formatCliPackages(packages)}`);
|
|
1117
|
+
logInfo("");
|
|
1118
|
+
logInfo(`Loadout is always enabled. Add-ons: ${CLI_PACKAGES.filter((p) => p !== "loadout").join(", ")}`);
|
|
1119
|
+
}
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
if (sub === "add" || sub === "enable") {
|
|
1123
|
+
const packageName = parsed.positional[2];
|
|
1124
|
+
if (!packageName) {
|
|
1125
|
+
logErr("Usage: aident packages add <playbook|intern>");
|
|
1126
|
+
process.exitCode = 1;
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const packages = normalizeCliPackages([...await readConfiguredPackages(), parseCliPackage(packageName)]);
|
|
1130
|
+
await setConfigValue("packages", packages);
|
|
1131
|
+
logInfo(`${colors.green}Enabled packages:${colors.reset} ${formatCliPackages(packages)}`);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (sub === "remove" || sub === "disable") {
|
|
1135
|
+
const packageName = parsed.positional[2];
|
|
1136
|
+
if (!packageName) {
|
|
1137
|
+
logErr("Usage: aident packages remove <playbook|intern>");
|
|
1138
|
+
process.exitCode = 1;
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const target = parseCliPackage(packageName);
|
|
1142
|
+
if (target === "loadout") {
|
|
1143
|
+
logErr("Loadout is the default package and cannot be removed.");
|
|
1144
|
+
process.exitCode = 1;
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const packages = normalizeCliPackages((await readConfiguredPackages()).filter((p) => p !== target));
|
|
1148
|
+
await setConfigValue("packages", packages);
|
|
1149
|
+
logInfo(`${colors.green}Enabled packages:${colors.reset} ${formatCliPackages(packages)}`);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
logErr(`Unknown packages subcommand: ${sub}`);
|
|
1153
|
+
logErr("Usage: aident packages [list | add <playbook|intern> | remove <playbook|intern>]");
|
|
1154
|
+
process.exitCode = 1;
|
|
1155
|
+
}
|
|
963
1156
|
async function runDoctorCmd(format) {
|
|
964
1157
|
const baseUrl = await resolveDefaultBaseUrl();
|
|
965
1158
|
const report = await runDoctor({ baseUrl, configFile: getConfigFile(), credentialsFile: getCredentialsFile() });
|
|
@@ -972,7 +1165,7 @@ async function runDoctorCmd(format) {
|
|
|
972
1165
|
logInfo(`${colors.bold}Aident CLI doctor${colors.reset}`);
|
|
973
1166
|
logInfo("");
|
|
974
1167
|
for (const c of report.checks) {
|
|
975
|
-
const mark = c.ok ? `${colors.green}
|
|
1168
|
+
const mark = c.ok ? `${colors.green}ok${colors.reset}` : `${colors.red}x${colors.reset}`;
|
|
976
1169
|
logInfo(` ${mark} ${c.name.padEnd(22)} ${colors.dim}${c.detail}${colors.reset}`);
|
|
977
1170
|
}
|
|
978
1171
|
logInfo("");
|
|
@@ -1009,8 +1202,8 @@ async function runSetup() {
|
|
|
1009
1202
|
logInfo("");
|
|
1010
1203
|
logInfo(`Done. Try ${colors.cyan}aident --help${colors.reset} or ${colors.cyan}aident doctor${colors.reset}.`);
|
|
1011
1204
|
}
|
|
1012
|
-
async function runCatalog(
|
|
1013
|
-
const client = await getAuthenticatedClient();
|
|
1205
|
+
async function runCatalog(parsed) {
|
|
1206
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
1014
1207
|
if (!client) {
|
|
1015
1208
|
process.exitCode = 1;
|
|
1016
1209
|
return;
|
|
@@ -1018,10 +1211,10 @@ async function runCatalog(format) {
|
|
|
1018
1211
|
const catalog = await fetchCatalog(client);
|
|
1019
1212
|
if (!catalog)
|
|
1020
1213
|
return;
|
|
1021
|
-
logInfo(JSON.stringify(catalog, null, format === "json" ? 0 : 2));
|
|
1214
|
+
logInfo(JSON.stringify(catalog, null, parsed.format === "json" ? 0 : 2));
|
|
1022
1215
|
}
|
|
1023
1216
|
async function runCommand(parsed) {
|
|
1024
|
-
const client = await getAuthenticatedClient();
|
|
1217
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
1025
1218
|
if (!client) {
|
|
1026
1219
|
if (parsed.format === "json") {
|
|
1027
1220
|
logInfo(JSON.stringify({ success: false, error: { code: "not-authenticated", message: "Run `aident login` first." } }));
|
|
@@ -1097,17 +1290,33 @@ function emitResult(result, format, resolved) {
|
|
|
1097
1290
|
return;
|
|
1098
1291
|
}
|
|
1099
1292
|
if (body.success) {
|
|
1100
|
-
logInfo(`${colors.green}
|
|
1293
|
+
logInfo(`${colors.green}ok${colors.reset} ${resolved.domain} ${resolved.command}`);
|
|
1101
1294
|
if (body.data !== undefined)
|
|
1102
1295
|
logInfo(JSON.stringify(body.data, null, 2));
|
|
1103
1296
|
if (body.meta)
|
|
1104
1297
|
logInfo(`${colors.cyan}meta:${colors.reset} ${JSON.stringify(body.meta)}`);
|
|
1105
1298
|
} else {
|
|
1106
|
-
logErr(`${colors.red}
|
|
1299
|
+
logErr(`${colors.red}x ${body.error?.code ?? "error"}:${colors.reset} ${body.error?.message ?? "Unknown error"}`);
|
|
1107
1300
|
process.exitCode = 1;
|
|
1108
1301
|
}
|
|
1109
1302
|
}
|
|
1110
|
-
async function
|
|
1303
|
+
async function getRequestedPackages(parsed) {
|
|
1304
|
+
const rawPackage = parsed.flags["package"] ?? process.env.AIDENT_PACKAGE;
|
|
1305
|
+
if (rawPackage !== undefined && rawPackage !== null && rawPackage !== false) {
|
|
1306
|
+
if (rawPackage !== true)
|
|
1307
|
+
return [parseCliPackage(rawPackage)];
|
|
1308
|
+
throw new Error("--package requires a value: loadout, playbook, or intern");
|
|
1309
|
+
}
|
|
1310
|
+
const rawPackages = parsed.flags["packages"] ?? process.env.AIDENT_PACKAGES;
|
|
1311
|
+
if (rawPackages !== undefined && rawPackages !== null && rawPackages !== false) {
|
|
1312
|
+
if (rawPackages !== true)
|
|
1313
|
+
return normalizeCliPackages(rawPackages);
|
|
1314
|
+
throw new Error("--packages requires a comma-separated package list");
|
|
1315
|
+
}
|
|
1316
|
+
return resolveDefaultPackages();
|
|
1317
|
+
}
|
|
1318
|
+
async function getAuthenticatedClient(packages) {
|
|
1319
|
+
const activePackages = packages ?? await resolveDefaultPackages();
|
|
1111
1320
|
const envToken = process.env.AIDENT_TOKEN;
|
|
1112
1321
|
const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
|
|
1113
1322
|
if (envToken) {
|
|
@@ -1116,7 +1325,7 @@ async function getAuthenticatedClient() {
|
|
|
1116
1325
|
base_url: normalizeBaseUrl(baseUrl),
|
|
1117
1326
|
client_id: "",
|
|
1118
1327
|
access_token: envToken
|
|
1119
|
-
});
|
|
1328
|
+
}, "env", activePackages);
|
|
1120
1329
|
}
|
|
1121
1330
|
let creds = await readCredentials();
|
|
1122
1331
|
if (!creds)
|
|
@@ -1131,12 +1340,13 @@ async function getAuthenticatedClient() {
|
|
|
1131
1340
|
creds = refreshed;
|
|
1132
1341
|
}
|
|
1133
1342
|
}
|
|
1134
|
-
return new CliClient(creds);
|
|
1343
|
+
return new CliClient(creds, "stored", activePackages);
|
|
1135
1344
|
}
|
|
1136
1345
|
var catalogCache = null;
|
|
1137
1346
|
async function fetchCatalog(client) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1347
|
+
const packagesKey = client.activePackages.join(",");
|
|
1348
|
+
if (catalogCache && catalogCache.packagesKey === packagesKey)
|
|
1349
|
+
return catalogCache.catalog;
|
|
1140
1350
|
const res = await callWithRefresh(client, (c) => c.getCatalog());
|
|
1141
1351
|
if (res.status === 401) {
|
|
1142
1352
|
logErr(`Not authenticated (HTTP ${res.status}). Run \`aident login\`.`);
|
|
@@ -1148,8 +1358,15 @@ async function fetchCatalog(client) {
|
|
|
1148
1358
|
process.exitCode = 1;
|
|
1149
1359
|
return null;
|
|
1150
1360
|
}
|
|
1151
|
-
catalogCache = res.body;
|
|
1152
|
-
return catalogCache;
|
|
1361
|
+
catalogCache = { packagesKey, catalog: res.body };
|
|
1362
|
+
return catalogCache.catalog;
|
|
1363
|
+
}
|
|
1364
|
+
function formatConfigValue(value) {
|
|
1365
|
+
return Array.isArray(value) ? value.join(",") : String(value);
|
|
1366
|
+
}
|
|
1367
|
+
async function readConfiguredPackages() {
|
|
1368
|
+
const config = await readConfig();
|
|
1369
|
+
return normalizeCliPackages(config.packages);
|
|
1153
1370
|
}
|
|
1154
1371
|
function isCommandCatalog(body) {
|
|
1155
1372
|
if (!body || typeof body !== "object")
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aident-ai/cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Aident CLI —
|
|
3
|
+
"version": "0.0.7-rc.0",
|
|
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": {
|
|
7
7
|
"type": "git",
|
|
@@ -28,14 +28,16 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "bun run scripts/build.ts",
|
|
30
30
|
"dev": "bun run src/cli.ts",
|
|
31
|
-
"test": "bun test",
|
|
31
|
+
"test": "bun test src/__tests__",
|
|
32
|
+
"test:e2e": "bun test integration",
|
|
32
33
|
"typecheck": "tsc --noEmit",
|
|
33
34
|
"format": "pnpm exec prettier . --write",
|
|
34
|
-
"lint": "pnpm exec eslint ./src --cache --cache-location ../../.eslintcache",
|
|
35
|
+
"lint": "pnpm exec eslint ./src ./integration --cache --cache-location ../../.eslintcache",
|
|
35
36
|
"prepublishOnly": "pnpm run build"
|
|
36
37
|
},
|
|
37
38
|
"imports": {
|
|
38
|
-
"~cli/*": "./src/*"
|
|
39
|
+
"~cli/*": "./src/*",
|
|
40
|
+
"~e2e/*": "./integration/*"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@aident/eslint-config": "workspace:*",
|