@aident-ai/cli 0.0.5 → 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 +383 -141
- 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 = {};
|
|
@@ -676,6 +806,30 @@ function coerce(val) {
|
|
|
676
806
|
}
|
|
677
807
|
return val;
|
|
678
808
|
}
|
|
809
|
+
function coerceArgsToSchema(args, schema) {
|
|
810
|
+
const props = schema?.properties;
|
|
811
|
+
if (!props)
|
|
812
|
+
return args;
|
|
813
|
+
const out = { ...args };
|
|
814
|
+
for (const [key, prop] of Object.entries(props)) {
|
|
815
|
+
if (!(key in out))
|
|
816
|
+
continue;
|
|
817
|
+
const value = out[key];
|
|
818
|
+
const types = Array.isArray(prop.type) ? prop.type : prop.type ? [prop.type] : [];
|
|
819
|
+
if (types.includes("array") && !Array.isArray(value)) {
|
|
820
|
+
if (value === undefined || value === null || value === "") {
|
|
821
|
+
out[key] = [];
|
|
822
|
+
} else if (typeof value === "string" && value.includes(",")) {
|
|
823
|
+
out[key] = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
824
|
+
} else {
|
|
825
|
+
out[key] = [value];
|
|
826
|
+
}
|
|
827
|
+
} else if (types.includes("string") && !types.includes("number") && !types.includes("boolean") && (typeof value === "number" || typeof value === "boolean")) {
|
|
828
|
+
out[key] = String(value);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return out;
|
|
832
|
+
}
|
|
679
833
|
function resolveCommand(positional, knownCommands) {
|
|
680
834
|
if (positional.length === 0)
|
|
681
835
|
return null;
|
|
@@ -716,6 +870,8 @@ async function callWithRefresh(client, call, deps = defaultDeps) {
|
|
|
716
870
|
}
|
|
717
871
|
if (first.status !== 401)
|
|
718
872
|
return first;
|
|
873
|
+
if (!client.canRefresh)
|
|
874
|
+
return first;
|
|
719
875
|
const creds = await deps.readCredentials();
|
|
720
876
|
if (!creds)
|
|
721
877
|
return first;
|
|
@@ -745,7 +901,7 @@ async function main() {
|
|
|
745
901
|
return;
|
|
746
902
|
}
|
|
747
903
|
if (!first || parsed.isHelp && parsed.positional.length === 0) {
|
|
748
|
-
await runHelp(parsed
|
|
904
|
+
await runHelp(parsed);
|
|
749
905
|
return;
|
|
750
906
|
}
|
|
751
907
|
switch (first) {
|
|
@@ -756,7 +912,7 @@ async function main() {
|
|
|
756
912
|
await runLogout();
|
|
757
913
|
return;
|
|
758
914
|
case "whoami":
|
|
759
|
-
await runWhoami(parsed
|
|
915
|
+
await runWhoami(parsed);
|
|
760
916
|
return;
|
|
761
917
|
case "doctor":
|
|
762
918
|
await runDoctorCmd(parsed.format);
|
|
@@ -767,17 +923,21 @@ async function main() {
|
|
|
767
923
|
case "config":
|
|
768
924
|
await runConfigCmd(parsed);
|
|
769
925
|
return;
|
|
926
|
+
case "packages":
|
|
927
|
+
case "package":
|
|
928
|
+
await runPackagesCmd(parsed);
|
|
929
|
+
return;
|
|
770
930
|
case "catalog":
|
|
771
|
-
await runCatalog(parsed
|
|
931
|
+
await runCatalog(parsed);
|
|
772
932
|
return;
|
|
773
933
|
default:
|
|
774
934
|
await runCommand(parsed);
|
|
775
935
|
}
|
|
776
936
|
}
|
|
777
|
-
async function runHelp(
|
|
778
|
-
const client = await getAuthenticatedClient();
|
|
937
|
+
async function runHelp(parsed) {
|
|
938
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
779
939
|
if (!client) {
|
|
780
|
-
if (format === "json") {
|
|
940
|
+
if (parsed.format === "json") {
|
|
781
941
|
logInfo(JSON.stringify({ error: "not-authenticated", message: "Run `aident login` first." }));
|
|
782
942
|
} else {
|
|
783
943
|
logInfo(`${colors.bold}Aident CLI v${VERSION}${colors.reset}`);
|
|
@@ -791,6 +951,7 @@ async function runHelp(format) {
|
|
|
791
951
|
logInfo(" aident config show Print persistent config");
|
|
792
952
|
logInfo(" aident config set <key> <value> Persist a config value");
|
|
793
953
|
logInfo(" aident config get <key> Read a single value");
|
|
954
|
+
logInfo(" aident packages add <playbook|intern> Enable an add-on package");
|
|
794
955
|
logInfo(" aident doctor Validate installation");
|
|
795
956
|
logInfo(" aident setup Interactive setup wizard");
|
|
796
957
|
logInfo(" aident <domain> <command> [--flag value ...] [--json]");
|
|
@@ -800,7 +961,7 @@ async function runHelp(format) {
|
|
|
800
961
|
const catalog = await fetchCatalog(client);
|
|
801
962
|
if (!catalog)
|
|
802
963
|
return;
|
|
803
|
-
if (format === "json") {
|
|
964
|
+
if (parsed.format === "json") {
|
|
804
965
|
logInfo(JSON.stringify(catalog));
|
|
805
966
|
} else {
|
|
806
967
|
logInfo(renderHelp(catalog));
|
|
@@ -826,10 +987,10 @@ async function runLogout() {
|
|
|
826
987
|
await clearCredentials();
|
|
827
988
|
logInfo(`${colors.green}Signed out.${colors.reset}`);
|
|
828
989
|
}
|
|
829
|
-
async function runWhoami(
|
|
830
|
-
const client = await getAuthenticatedClient();
|
|
990
|
+
async function runWhoami(parsed) {
|
|
991
|
+
const client = await getAuthenticatedClient(["loadout"]);
|
|
831
992
|
if (!client) {
|
|
832
|
-
if (format === "json") {
|
|
993
|
+
if (parsed.format === "json") {
|
|
833
994
|
logInfo(JSON.stringify({ authenticated: false }));
|
|
834
995
|
} else {
|
|
835
996
|
logInfo("Not signed in. Run `aident login`.");
|
|
@@ -837,8 +998,11 @@ async function runWhoami(format) {
|
|
|
837
998
|
process.exitCode = 1;
|
|
838
999
|
return;
|
|
839
1000
|
}
|
|
840
|
-
const
|
|
841
|
-
if (
|
|
1001
|
+
const catalog = await fetchCatalog(client);
|
|
1002
|
+
if (!catalog)
|
|
1003
|
+
return;
|
|
1004
|
+
const result = await callWithRefresh(client, (c) => c.exec("account", "auth status", {}));
|
|
1005
|
+
if (parsed.format === "json") {
|
|
842
1006
|
logInfo(JSON.stringify(result.body));
|
|
843
1007
|
if (!result.body.success)
|
|
844
1008
|
process.exitCode = 1;
|
|
@@ -850,7 +1014,7 @@ async function runWhoami(format) {
|
|
|
850
1014
|
return;
|
|
851
1015
|
}
|
|
852
1016
|
const err = result.body.error;
|
|
853
|
-
logErr(`${colors.red}
|
|
1017
|
+
logErr(`${colors.red}x ${err?.code ?? "error"}:${colors.reset} ${err?.message ?? "Unable to fetch user info."}`);
|
|
854
1018
|
process.exitCode = 1;
|
|
855
1019
|
}
|
|
856
1020
|
async function runConfigCmd(parsed) {
|
|
@@ -863,8 +1027,9 @@ async function runConfigCmd(parsed) {
|
|
|
863
1027
|
}
|
|
864
1028
|
logInfo(`${colors.bold}Aident config${colors.reset} ${colors.dim}(${getConfigFile()})${colors.reset}`);
|
|
865
1029
|
if (Object.keys(config).length === 0) {
|
|
866
|
-
logInfo(` ${colors.dim}(empty
|
|
1030
|
+
logInfo(` ${colors.dim}(empty, using defaults)${colors.reset}`);
|
|
867
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}`);
|
|
868
1033
|
return;
|
|
869
1034
|
}
|
|
870
1035
|
for (const [key, val] of Object.entries(config)) {
|
|
@@ -910,14 +1075,18 @@ async function runConfigCmd(parsed) {
|
|
|
910
1075
|
process.exitCode = 1;
|
|
911
1076
|
return;
|
|
912
1077
|
}
|
|
913
|
-
const value = key === "baseUrl" ? normalizeBaseUrl(rawValue) : rawValue;
|
|
914
|
-
await setConfigValue(key, value);
|
|
915
|
-
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
916
1078
|
if (key === "baseUrl") {
|
|
1079
|
+
const value = normalizeBaseUrl(rawValue);
|
|
1080
|
+
await setConfigValue(key, value);
|
|
1081
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
917
1082
|
const creds = await readCredentials();
|
|
918
1083
|
if (creds && creds.base_url !== value) {
|
|
919
1084
|
logInfo(`${colors.yellow}Note:${colors.reset} existing credentials are for ${creds.base_url}. Run \`aident login\` to authenticate against ${value}.`);
|
|
920
1085
|
}
|
|
1086
|
+
} else {
|
|
1087
|
+
const value = normalizeCliPackages(rawValue);
|
|
1088
|
+
await setConfigValue(key, value);
|
|
1089
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${formatConfigValue(value)}`);
|
|
921
1090
|
}
|
|
922
1091
|
return;
|
|
923
1092
|
}
|
|
@@ -936,6 +1105,54 @@ async function runConfigCmd(parsed) {
|
|
|
936
1105
|
logErr("Usage: aident config [show | get <key> | set <key> <value> | unset <key> | path]");
|
|
937
1106
|
process.exitCode = 1;
|
|
938
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
|
+
}
|
|
939
1156
|
async function runDoctorCmd(format) {
|
|
940
1157
|
const baseUrl = await resolveDefaultBaseUrl();
|
|
941
1158
|
const report = await runDoctor({ baseUrl, configFile: getConfigFile(), credentialsFile: getCredentialsFile() });
|
|
@@ -948,7 +1165,7 @@ async function runDoctorCmd(format) {
|
|
|
948
1165
|
logInfo(`${colors.bold}Aident CLI doctor${colors.reset}`);
|
|
949
1166
|
logInfo("");
|
|
950
1167
|
for (const c of report.checks) {
|
|
951
|
-
const mark = c.ok ? `${colors.green}
|
|
1168
|
+
const mark = c.ok ? `${colors.green}ok${colors.reset}` : `${colors.red}x${colors.reset}`;
|
|
952
1169
|
logInfo(` ${mark} ${c.name.padEnd(22)} ${colors.dim}${c.detail}${colors.reset}`);
|
|
953
1170
|
}
|
|
954
1171
|
logInfo("");
|
|
@@ -985,8 +1202,8 @@ async function runSetup() {
|
|
|
985
1202
|
logInfo("");
|
|
986
1203
|
logInfo(`Done. Try ${colors.cyan}aident --help${colors.reset} or ${colors.cyan}aident doctor${colors.reset}.`);
|
|
987
1204
|
}
|
|
988
|
-
async function runCatalog(
|
|
989
|
-
const client = await getAuthenticatedClient();
|
|
1205
|
+
async function runCatalog(parsed) {
|
|
1206
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
990
1207
|
if (!client) {
|
|
991
1208
|
process.exitCode = 1;
|
|
992
1209
|
return;
|
|
@@ -994,10 +1211,10 @@ async function runCatalog(format) {
|
|
|
994
1211
|
const catalog = await fetchCatalog(client);
|
|
995
1212
|
if (!catalog)
|
|
996
1213
|
return;
|
|
997
|
-
logInfo(JSON.stringify(catalog, null, format === "json" ? 0 : 2));
|
|
1214
|
+
logInfo(JSON.stringify(catalog, null, parsed.format === "json" ? 0 : 2));
|
|
998
1215
|
}
|
|
999
1216
|
async function runCommand(parsed) {
|
|
1000
|
-
const client = await getAuthenticatedClient();
|
|
1217
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
1001
1218
|
if (!client) {
|
|
1002
1219
|
if (parsed.format === "json") {
|
|
1003
1220
|
logInfo(JSON.stringify({ success: false, error: { code: "not-authenticated", message: "Run `aident login` first." } }));
|
|
@@ -1042,12 +1259,12 @@ async function runCommand(parsed) {
|
|
|
1042
1259
|
logInfo(renderCommandHelp(catalog, resolved.domain, resolved.command));
|
|
1043
1260
|
return;
|
|
1044
1261
|
}
|
|
1045
|
-
|
|
1262
|
+
let args = { ...parsed.flags };
|
|
1046
1263
|
for (const reserved of RESERVED_CLI_FLAGS) {
|
|
1047
1264
|
delete args[reserved];
|
|
1048
1265
|
}
|
|
1266
|
+
const schema = cmdInfo.inputSchema;
|
|
1049
1267
|
if (resolved.remaining.length > 0) {
|
|
1050
|
-
const schema = cmdInfo.inputSchema;
|
|
1051
1268
|
const required = new Set(schema?.required ?? []);
|
|
1052
1269
|
const props = schema?.properties ?? {};
|
|
1053
1270
|
for (const key of Object.keys(props)) {
|
|
@@ -1060,6 +1277,7 @@ async function runCommand(parsed) {
|
|
|
1060
1277
|
args[key] = resolved.remaining.shift();
|
|
1061
1278
|
}
|
|
1062
1279
|
}
|
|
1280
|
+
args = coerceArgsToSchema(args, schema);
|
|
1063
1281
|
const result = await callWithRefresh(client, (c) => c.exec(resolved.domain, resolved.command, args));
|
|
1064
1282
|
emitResult(result, parsed.format, resolved);
|
|
1065
1283
|
}
|
|
@@ -1072,17 +1290,33 @@ function emitResult(result, format, resolved) {
|
|
|
1072
1290
|
return;
|
|
1073
1291
|
}
|
|
1074
1292
|
if (body.success) {
|
|
1075
|
-
logInfo(`${colors.green}
|
|
1293
|
+
logInfo(`${colors.green}ok${colors.reset} ${resolved.domain} ${resolved.command}`);
|
|
1076
1294
|
if (body.data !== undefined)
|
|
1077
1295
|
logInfo(JSON.stringify(body.data, null, 2));
|
|
1078
1296
|
if (body.meta)
|
|
1079
1297
|
logInfo(`${colors.cyan}meta:${colors.reset} ${JSON.stringify(body.meta)}`);
|
|
1080
1298
|
} else {
|
|
1081
|
-
logErr(`${colors.red}
|
|
1299
|
+
logErr(`${colors.red}x ${body.error?.code ?? "error"}:${colors.reset} ${body.error?.message ?? "Unknown error"}`);
|
|
1082
1300
|
process.exitCode = 1;
|
|
1083
1301
|
}
|
|
1084
1302
|
}
|
|
1085
|
-
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();
|
|
1086
1320
|
const envToken = process.env.AIDENT_TOKEN;
|
|
1087
1321
|
const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
|
|
1088
1322
|
if (envToken) {
|
|
@@ -1091,7 +1325,7 @@ async function getAuthenticatedClient() {
|
|
|
1091
1325
|
base_url: normalizeBaseUrl(baseUrl),
|
|
1092
1326
|
client_id: "",
|
|
1093
1327
|
access_token: envToken
|
|
1094
|
-
});
|
|
1328
|
+
}, "env", activePackages);
|
|
1095
1329
|
}
|
|
1096
1330
|
let creds = await readCredentials();
|
|
1097
1331
|
if (!creds)
|
|
@@ -1106,12 +1340,13 @@ async function getAuthenticatedClient() {
|
|
|
1106
1340
|
creds = refreshed;
|
|
1107
1341
|
}
|
|
1108
1342
|
}
|
|
1109
|
-
return new CliClient(creds);
|
|
1343
|
+
return new CliClient(creds, "stored", activePackages);
|
|
1110
1344
|
}
|
|
1111
1345
|
var catalogCache = null;
|
|
1112
1346
|
async function fetchCatalog(client) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1347
|
+
const packagesKey = client.activePackages.join(",");
|
|
1348
|
+
if (catalogCache && catalogCache.packagesKey === packagesKey)
|
|
1349
|
+
return catalogCache.catalog;
|
|
1115
1350
|
const res = await callWithRefresh(client, (c) => c.getCatalog());
|
|
1116
1351
|
if (res.status === 401) {
|
|
1117
1352
|
logErr(`Not authenticated (HTTP ${res.status}). Run \`aident login\`.`);
|
|
@@ -1123,8 +1358,15 @@ async function fetchCatalog(client) {
|
|
|
1123
1358
|
process.exitCode = 1;
|
|
1124
1359
|
return null;
|
|
1125
1360
|
}
|
|
1126
|
-
catalogCache = res.body;
|
|
1127
|
-
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);
|
|
1128
1370
|
}
|
|
1129
1371
|
function isCommandCatalog(body) {
|
|
1130
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:*",
|