@aident-ai/cli 0.0.6 → 0.1.0-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 +47 -73
- package/dist/cli.mjs +354 -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.1.0-rc.1";
|
|
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,9 @@ async function runWhoami(format) {
|
|
|
861
998
|
process.exitCode = 1;
|
|
862
999
|
return;
|
|
863
1000
|
}
|
|
1001
|
+
await fetchCatalog(client);
|
|
864
1002
|
const result = await callWithRefresh(client, (c) => c.exec("account", "auth status", {}));
|
|
865
|
-
if (format === "json") {
|
|
1003
|
+
if (parsed.format === "json") {
|
|
866
1004
|
logInfo(JSON.stringify(result.body));
|
|
867
1005
|
if (!result.body.success)
|
|
868
1006
|
process.exitCode = 1;
|
|
@@ -874,7 +1012,7 @@ async function runWhoami(format) {
|
|
|
874
1012
|
return;
|
|
875
1013
|
}
|
|
876
1014
|
const err = result.body.error;
|
|
877
|
-
logErr(`${colors.red}
|
|
1015
|
+
logErr(`${colors.red}x ${err?.code ?? "error"}:${colors.reset} ${err?.message ?? "Unable to fetch user info."}`);
|
|
878
1016
|
process.exitCode = 1;
|
|
879
1017
|
}
|
|
880
1018
|
async function runConfigCmd(parsed) {
|
|
@@ -887,8 +1025,9 @@ async function runConfigCmd(parsed) {
|
|
|
887
1025
|
}
|
|
888
1026
|
logInfo(`${colors.bold}Aident config${colors.reset} ${colors.dim}(${getConfigFile()})${colors.reset}`);
|
|
889
1027
|
if (Object.keys(config).length === 0) {
|
|
890
|
-
logInfo(` ${colors.dim}(empty
|
|
1028
|
+
logInfo(` ${colors.dim}(empty, using defaults)${colors.reset}`);
|
|
891
1029
|
logInfo(` ${colors.dim}baseUrl${colors.reset} ${colors.dim}default: ${DEFAULT_BASE_URL}${colors.reset}`);
|
|
1030
|
+
logInfo(` ${colors.dim}packages${colors.reset} ${colors.dim}default: loadout${colors.reset}`);
|
|
892
1031
|
return;
|
|
893
1032
|
}
|
|
894
1033
|
for (const [key, val] of Object.entries(config)) {
|
|
@@ -934,14 +1073,18 @@ async function runConfigCmd(parsed) {
|
|
|
934
1073
|
process.exitCode = 1;
|
|
935
1074
|
return;
|
|
936
1075
|
}
|
|
937
|
-
const value = key === "baseUrl" ? normalizeBaseUrl(rawValue) : rawValue;
|
|
938
|
-
await setConfigValue(key, value);
|
|
939
|
-
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
940
1076
|
if (key === "baseUrl") {
|
|
1077
|
+
const value = normalizeBaseUrl(rawValue);
|
|
1078
|
+
await setConfigValue(key, value);
|
|
1079
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
941
1080
|
const creds = await readCredentials();
|
|
942
1081
|
if (creds && creds.base_url !== value) {
|
|
943
1082
|
logInfo(`${colors.yellow}Note:${colors.reset} existing credentials are for ${creds.base_url}. Run \`aident login\` to authenticate against ${value}.`);
|
|
944
1083
|
}
|
|
1084
|
+
} else {
|
|
1085
|
+
const value = normalizeCliPackages(rawValue);
|
|
1086
|
+
await setConfigValue(key, value);
|
|
1087
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${formatConfigValue(value)}`);
|
|
945
1088
|
}
|
|
946
1089
|
return;
|
|
947
1090
|
}
|
|
@@ -960,6 +1103,54 @@ async function runConfigCmd(parsed) {
|
|
|
960
1103
|
logErr("Usage: aident config [show | get <key> | set <key> <value> | unset <key> | path]");
|
|
961
1104
|
process.exitCode = 1;
|
|
962
1105
|
}
|
|
1106
|
+
async function runPackagesCmd(parsed) {
|
|
1107
|
+
const sub = parsed.positional[1] ?? "list";
|
|
1108
|
+
if (sub === "list" || sub === "show") {
|
|
1109
|
+
const packages = await resolveDefaultPackages();
|
|
1110
|
+
if (parsed.format === "json") {
|
|
1111
|
+
logInfo(JSON.stringify({ packages, defaultPackage: "loadout" }));
|
|
1112
|
+
} else {
|
|
1113
|
+
logInfo(`${colors.bold}Aident packages${colors.reset}`);
|
|
1114
|
+
logInfo(` ${formatCliPackages(packages)}`);
|
|
1115
|
+
logInfo("");
|
|
1116
|
+
logInfo(`Loadout is always enabled. Add-ons: ${CLI_PACKAGES.filter((p) => p !== "loadout").join(", ")}`);
|
|
1117
|
+
}
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (sub === "add" || sub === "enable") {
|
|
1121
|
+
const packageName = parsed.positional[2];
|
|
1122
|
+
if (!packageName) {
|
|
1123
|
+
logErr("Usage: aident packages add <playbook|intern>");
|
|
1124
|
+
process.exitCode = 1;
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const packages = normalizeCliPackages([...await readConfiguredPackages(), parseCliPackage(packageName)]);
|
|
1128
|
+
await setConfigValue("packages", packages);
|
|
1129
|
+
logInfo(`${colors.green}Enabled packages:${colors.reset} ${formatCliPackages(packages)}`);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (sub === "remove" || sub === "disable") {
|
|
1133
|
+
const packageName = parsed.positional[2];
|
|
1134
|
+
if (!packageName) {
|
|
1135
|
+
logErr("Usage: aident packages remove <playbook|intern>");
|
|
1136
|
+
process.exitCode = 1;
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const target = parseCliPackage(packageName);
|
|
1140
|
+
if (target === "loadout") {
|
|
1141
|
+
logErr("Loadout is the default package and cannot be removed.");
|
|
1142
|
+
process.exitCode = 1;
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const packages = normalizeCliPackages((await readConfiguredPackages()).filter((p) => p !== target));
|
|
1146
|
+
await setConfigValue("packages", packages);
|
|
1147
|
+
logInfo(`${colors.green}Enabled packages:${colors.reset} ${formatCliPackages(packages)}`);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
logErr(`Unknown packages subcommand: ${sub}`);
|
|
1151
|
+
logErr("Usage: aident packages [list | add <playbook|intern> | remove <playbook|intern>]");
|
|
1152
|
+
process.exitCode = 1;
|
|
1153
|
+
}
|
|
963
1154
|
async function runDoctorCmd(format) {
|
|
964
1155
|
const baseUrl = await resolveDefaultBaseUrl();
|
|
965
1156
|
const report = await runDoctor({ baseUrl, configFile: getConfigFile(), credentialsFile: getCredentialsFile() });
|
|
@@ -972,7 +1163,7 @@ async function runDoctorCmd(format) {
|
|
|
972
1163
|
logInfo(`${colors.bold}Aident CLI doctor${colors.reset}`);
|
|
973
1164
|
logInfo("");
|
|
974
1165
|
for (const c of report.checks) {
|
|
975
|
-
const mark = c.ok ? `${colors.green}
|
|
1166
|
+
const mark = c.ok ? `${colors.green}ok${colors.reset}` : `${colors.red}x${colors.reset}`;
|
|
976
1167
|
logInfo(` ${mark} ${c.name.padEnd(22)} ${colors.dim}${c.detail}${colors.reset}`);
|
|
977
1168
|
}
|
|
978
1169
|
logInfo("");
|
|
@@ -1009,8 +1200,8 @@ async function runSetup() {
|
|
|
1009
1200
|
logInfo("");
|
|
1010
1201
|
logInfo(`Done. Try ${colors.cyan}aident --help${colors.reset} or ${colors.cyan}aident doctor${colors.reset}.`);
|
|
1011
1202
|
}
|
|
1012
|
-
async function runCatalog(
|
|
1013
|
-
const client = await getAuthenticatedClient();
|
|
1203
|
+
async function runCatalog(parsed) {
|
|
1204
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
1014
1205
|
if (!client) {
|
|
1015
1206
|
process.exitCode = 1;
|
|
1016
1207
|
return;
|
|
@@ -1018,10 +1209,10 @@ async function runCatalog(format) {
|
|
|
1018
1209
|
const catalog = await fetchCatalog(client);
|
|
1019
1210
|
if (!catalog)
|
|
1020
1211
|
return;
|
|
1021
|
-
logInfo(JSON.stringify(catalog, null, format === "json" ? 0 : 2));
|
|
1212
|
+
logInfo(JSON.stringify(catalog, null, parsed.format === "json" ? 0 : 2));
|
|
1022
1213
|
}
|
|
1023
1214
|
async function runCommand(parsed) {
|
|
1024
|
-
const client = await getAuthenticatedClient();
|
|
1215
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
1025
1216
|
if (!client) {
|
|
1026
1217
|
if (parsed.format === "json") {
|
|
1027
1218
|
logInfo(JSON.stringify({ success: false, error: { code: "not-authenticated", message: "Run `aident login` first." } }));
|
|
@@ -1097,17 +1288,33 @@ function emitResult(result, format, resolved) {
|
|
|
1097
1288
|
return;
|
|
1098
1289
|
}
|
|
1099
1290
|
if (body.success) {
|
|
1100
|
-
logInfo(`${colors.green}
|
|
1291
|
+
logInfo(`${colors.green}ok${colors.reset} ${resolved.domain} ${resolved.command}`);
|
|
1101
1292
|
if (body.data !== undefined)
|
|
1102
1293
|
logInfo(JSON.stringify(body.data, null, 2));
|
|
1103
1294
|
if (body.meta)
|
|
1104
1295
|
logInfo(`${colors.cyan}meta:${colors.reset} ${JSON.stringify(body.meta)}`);
|
|
1105
1296
|
} else {
|
|
1106
|
-
logErr(`${colors.red}
|
|
1297
|
+
logErr(`${colors.red}x ${body.error?.code ?? "error"}:${colors.reset} ${body.error?.message ?? "Unknown error"}`);
|
|
1107
1298
|
process.exitCode = 1;
|
|
1108
1299
|
}
|
|
1109
1300
|
}
|
|
1110
|
-
async function
|
|
1301
|
+
async function getRequestedPackages(parsed) {
|
|
1302
|
+
const rawPackage = parsed.flags["package"] ?? process.env.AIDENT_PACKAGE;
|
|
1303
|
+
if (rawPackage !== undefined && rawPackage !== null && rawPackage !== false) {
|
|
1304
|
+
if (rawPackage !== true)
|
|
1305
|
+
return [parseCliPackage(rawPackage)];
|
|
1306
|
+
throw new Error("--package requires a value: loadout, playbook, or intern");
|
|
1307
|
+
}
|
|
1308
|
+
const rawPackages = parsed.flags["packages"] ?? process.env.AIDENT_PACKAGES;
|
|
1309
|
+
if (rawPackages !== undefined && rawPackages !== null && rawPackages !== false) {
|
|
1310
|
+
if (rawPackages !== true)
|
|
1311
|
+
return normalizeCliPackages(rawPackages);
|
|
1312
|
+
throw new Error("--packages requires a comma-separated package list");
|
|
1313
|
+
}
|
|
1314
|
+
return resolveDefaultPackages();
|
|
1315
|
+
}
|
|
1316
|
+
async function getAuthenticatedClient(packages) {
|
|
1317
|
+
const activePackages = packages ?? await resolveDefaultPackages();
|
|
1111
1318
|
const envToken = process.env.AIDENT_TOKEN;
|
|
1112
1319
|
const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
|
|
1113
1320
|
if (envToken) {
|
|
@@ -1116,7 +1323,7 @@ async function getAuthenticatedClient() {
|
|
|
1116
1323
|
base_url: normalizeBaseUrl(baseUrl),
|
|
1117
1324
|
client_id: "",
|
|
1118
1325
|
access_token: envToken
|
|
1119
|
-
});
|
|
1326
|
+
}, "env", activePackages);
|
|
1120
1327
|
}
|
|
1121
1328
|
let creds = await readCredentials();
|
|
1122
1329
|
if (!creds)
|
|
@@ -1131,12 +1338,13 @@ async function getAuthenticatedClient() {
|
|
|
1131
1338
|
creds = refreshed;
|
|
1132
1339
|
}
|
|
1133
1340
|
}
|
|
1134
|
-
return new CliClient(creds);
|
|
1341
|
+
return new CliClient(creds, "stored", activePackages);
|
|
1135
1342
|
}
|
|
1136
1343
|
var catalogCache = null;
|
|
1137
1344
|
async function fetchCatalog(client) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1345
|
+
const packagesKey = client.activePackages.join(",");
|
|
1346
|
+
if (catalogCache && catalogCache.packagesKey === packagesKey)
|
|
1347
|
+
return catalogCache.catalog;
|
|
1140
1348
|
const res = await callWithRefresh(client, (c) => c.getCatalog());
|
|
1141
1349
|
if (res.status === 401) {
|
|
1142
1350
|
logErr(`Not authenticated (HTTP ${res.status}). Run \`aident login\`.`);
|
|
@@ -1148,8 +1356,15 @@ async function fetchCatalog(client) {
|
|
|
1148
1356
|
process.exitCode = 1;
|
|
1149
1357
|
return null;
|
|
1150
1358
|
}
|
|
1151
|
-
catalogCache = res.body;
|
|
1152
|
-
return catalogCache;
|
|
1359
|
+
catalogCache = { packagesKey, catalog: res.body };
|
|
1360
|
+
return catalogCache.catalog;
|
|
1361
|
+
}
|
|
1362
|
+
function formatConfigValue(value) {
|
|
1363
|
+
return Array.isArray(value) ? value.join(",") : String(value);
|
|
1364
|
+
}
|
|
1365
|
+
async function readConfiguredPackages() {
|
|
1366
|
+
const config = await readConfig();
|
|
1367
|
+
return normalizeCliPackages(config.packages);
|
|
1153
1368
|
}
|
|
1154
1369
|
function isCommandCatalog(body) {
|
|
1155
1370
|
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.1.0-rc.1",
|
|
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:*",
|