@aident-ai/cli 0.0.5 → 0.1.0-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 +381 -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.1.0-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") && typeof value === "number") {
|
|
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,9 @@ async function runWhoami(format) {
|
|
|
837
998
|
process.exitCode = 1;
|
|
838
999
|
return;
|
|
839
1000
|
}
|
|
840
|
-
|
|
841
|
-
|
|
1001
|
+
await fetchCatalog(client);
|
|
1002
|
+
const result = await callWithRefresh(client, (c) => c.exec("account", "auth status", {}));
|
|
1003
|
+
if (parsed.format === "json") {
|
|
842
1004
|
logInfo(JSON.stringify(result.body));
|
|
843
1005
|
if (!result.body.success)
|
|
844
1006
|
process.exitCode = 1;
|
|
@@ -850,7 +1012,7 @@ async function runWhoami(format) {
|
|
|
850
1012
|
return;
|
|
851
1013
|
}
|
|
852
1014
|
const err = result.body.error;
|
|
853
|
-
logErr(`${colors.red}
|
|
1015
|
+
logErr(`${colors.red}x ${err?.code ?? "error"}:${colors.reset} ${err?.message ?? "Unable to fetch user info."}`);
|
|
854
1016
|
process.exitCode = 1;
|
|
855
1017
|
}
|
|
856
1018
|
async function runConfigCmd(parsed) {
|
|
@@ -863,8 +1025,9 @@ async function runConfigCmd(parsed) {
|
|
|
863
1025
|
}
|
|
864
1026
|
logInfo(`${colors.bold}Aident config${colors.reset} ${colors.dim}(${getConfigFile()})${colors.reset}`);
|
|
865
1027
|
if (Object.keys(config).length === 0) {
|
|
866
|
-
logInfo(` ${colors.dim}(empty
|
|
1028
|
+
logInfo(` ${colors.dim}(empty, using defaults)${colors.reset}`);
|
|
867
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}`);
|
|
868
1031
|
return;
|
|
869
1032
|
}
|
|
870
1033
|
for (const [key, val] of Object.entries(config)) {
|
|
@@ -910,14 +1073,18 @@ async function runConfigCmd(parsed) {
|
|
|
910
1073
|
process.exitCode = 1;
|
|
911
1074
|
return;
|
|
912
1075
|
}
|
|
913
|
-
const value = key === "baseUrl" ? normalizeBaseUrl(rawValue) : rawValue;
|
|
914
|
-
await setConfigValue(key, value);
|
|
915
|
-
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
916
1076
|
if (key === "baseUrl") {
|
|
1077
|
+
const value = normalizeBaseUrl(rawValue);
|
|
1078
|
+
await setConfigValue(key, value);
|
|
1079
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
917
1080
|
const creds = await readCredentials();
|
|
918
1081
|
if (creds && creds.base_url !== value) {
|
|
919
1082
|
logInfo(`${colors.yellow}Note:${colors.reset} existing credentials are for ${creds.base_url}. Run \`aident login\` to authenticate against ${value}.`);
|
|
920
1083
|
}
|
|
1084
|
+
} else {
|
|
1085
|
+
const value = normalizeCliPackages(rawValue);
|
|
1086
|
+
await setConfigValue(key, value);
|
|
1087
|
+
logInfo(`${colors.green}Set${colors.reset} ${key} = ${formatConfigValue(value)}`);
|
|
921
1088
|
}
|
|
922
1089
|
return;
|
|
923
1090
|
}
|
|
@@ -936,6 +1103,54 @@ async function runConfigCmd(parsed) {
|
|
|
936
1103
|
logErr("Usage: aident config [show | get <key> | set <key> <value> | unset <key> | path]");
|
|
937
1104
|
process.exitCode = 1;
|
|
938
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
|
+
}
|
|
939
1154
|
async function runDoctorCmd(format) {
|
|
940
1155
|
const baseUrl = await resolveDefaultBaseUrl();
|
|
941
1156
|
const report = await runDoctor({ baseUrl, configFile: getConfigFile(), credentialsFile: getCredentialsFile() });
|
|
@@ -948,7 +1163,7 @@ async function runDoctorCmd(format) {
|
|
|
948
1163
|
logInfo(`${colors.bold}Aident CLI doctor${colors.reset}`);
|
|
949
1164
|
logInfo("");
|
|
950
1165
|
for (const c of report.checks) {
|
|
951
|
-
const mark = c.ok ? `${colors.green}
|
|
1166
|
+
const mark = c.ok ? `${colors.green}ok${colors.reset}` : `${colors.red}x${colors.reset}`;
|
|
952
1167
|
logInfo(` ${mark} ${c.name.padEnd(22)} ${colors.dim}${c.detail}${colors.reset}`);
|
|
953
1168
|
}
|
|
954
1169
|
logInfo("");
|
|
@@ -985,8 +1200,8 @@ async function runSetup() {
|
|
|
985
1200
|
logInfo("");
|
|
986
1201
|
logInfo(`Done. Try ${colors.cyan}aident --help${colors.reset} or ${colors.cyan}aident doctor${colors.reset}.`);
|
|
987
1202
|
}
|
|
988
|
-
async function runCatalog(
|
|
989
|
-
const client = await getAuthenticatedClient();
|
|
1203
|
+
async function runCatalog(parsed) {
|
|
1204
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
990
1205
|
if (!client) {
|
|
991
1206
|
process.exitCode = 1;
|
|
992
1207
|
return;
|
|
@@ -994,10 +1209,10 @@ async function runCatalog(format) {
|
|
|
994
1209
|
const catalog = await fetchCatalog(client);
|
|
995
1210
|
if (!catalog)
|
|
996
1211
|
return;
|
|
997
|
-
logInfo(JSON.stringify(catalog, null, format === "json" ? 0 : 2));
|
|
1212
|
+
logInfo(JSON.stringify(catalog, null, parsed.format === "json" ? 0 : 2));
|
|
998
1213
|
}
|
|
999
1214
|
async function runCommand(parsed) {
|
|
1000
|
-
const client = await getAuthenticatedClient();
|
|
1215
|
+
const client = await getAuthenticatedClient(await getRequestedPackages(parsed));
|
|
1001
1216
|
if (!client) {
|
|
1002
1217
|
if (parsed.format === "json") {
|
|
1003
1218
|
logInfo(JSON.stringify({ success: false, error: { code: "not-authenticated", message: "Run `aident login` first." } }));
|
|
@@ -1042,12 +1257,12 @@ async function runCommand(parsed) {
|
|
|
1042
1257
|
logInfo(renderCommandHelp(catalog, resolved.domain, resolved.command));
|
|
1043
1258
|
return;
|
|
1044
1259
|
}
|
|
1045
|
-
|
|
1260
|
+
let args = { ...parsed.flags };
|
|
1046
1261
|
for (const reserved of RESERVED_CLI_FLAGS) {
|
|
1047
1262
|
delete args[reserved];
|
|
1048
1263
|
}
|
|
1264
|
+
const schema = cmdInfo.inputSchema;
|
|
1049
1265
|
if (resolved.remaining.length > 0) {
|
|
1050
|
-
const schema = cmdInfo.inputSchema;
|
|
1051
1266
|
const required = new Set(schema?.required ?? []);
|
|
1052
1267
|
const props = schema?.properties ?? {};
|
|
1053
1268
|
for (const key of Object.keys(props)) {
|
|
@@ -1060,6 +1275,7 @@ async function runCommand(parsed) {
|
|
|
1060
1275
|
args[key] = resolved.remaining.shift();
|
|
1061
1276
|
}
|
|
1062
1277
|
}
|
|
1278
|
+
args = coerceArgsToSchema(args, schema);
|
|
1063
1279
|
const result = await callWithRefresh(client, (c) => c.exec(resolved.domain, resolved.command, args));
|
|
1064
1280
|
emitResult(result, parsed.format, resolved);
|
|
1065
1281
|
}
|
|
@@ -1072,17 +1288,33 @@ function emitResult(result, format, resolved) {
|
|
|
1072
1288
|
return;
|
|
1073
1289
|
}
|
|
1074
1290
|
if (body.success) {
|
|
1075
|
-
logInfo(`${colors.green}
|
|
1291
|
+
logInfo(`${colors.green}ok${colors.reset} ${resolved.domain} ${resolved.command}`);
|
|
1076
1292
|
if (body.data !== undefined)
|
|
1077
1293
|
logInfo(JSON.stringify(body.data, null, 2));
|
|
1078
1294
|
if (body.meta)
|
|
1079
1295
|
logInfo(`${colors.cyan}meta:${colors.reset} ${JSON.stringify(body.meta)}`);
|
|
1080
1296
|
} else {
|
|
1081
|
-
logErr(`${colors.red}
|
|
1297
|
+
logErr(`${colors.red}x ${body.error?.code ?? "error"}:${colors.reset} ${body.error?.message ?? "Unknown error"}`);
|
|
1082
1298
|
process.exitCode = 1;
|
|
1083
1299
|
}
|
|
1084
1300
|
}
|
|
1085
|
-
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();
|
|
1086
1318
|
const envToken = process.env.AIDENT_TOKEN;
|
|
1087
1319
|
const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
|
|
1088
1320
|
if (envToken) {
|
|
@@ -1091,7 +1323,7 @@ async function getAuthenticatedClient() {
|
|
|
1091
1323
|
base_url: normalizeBaseUrl(baseUrl),
|
|
1092
1324
|
client_id: "",
|
|
1093
1325
|
access_token: envToken
|
|
1094
|
-
});
|
|
1326
|
+
}, "env", activePackages);
|
|
1095
1327
|
}
|
|
1096
1328
|
let creds = await readCredentials();
|
|
1097
1329
|
if (!creds)
|
|
@@ -1106,12 +1338,13 @@ async function getAuthenticatedClient() {
|
|
|
1106
1338
|
creds = refreshed;
|
|
1107
1339
|
}
|
|
1108
1340
|
}
|
|
1109
|
-
return new CliClient(creds);
|
|
1341
|
+
return new CliClient(creds, "stored", activePackages);
|
|
1110
1342
|
}
|
|
1111
1343
|
var catalogCache = null;
|
|
1112
1344
|
async function fetchCatalog(client) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1345
|
+
const packagesKey = client.activePackages.join(",");
|
|
1346
|
+
if (catalogCache && catalogCache.packagesKey === packagesKey)
|
|
1347
|
+
return catalogCache.catalog;
|
|
1115
1348
|
const res = await callWithRefresh(client, (c) => c.getCatalog());
|
|
1116
1349
|
if (res.status === 401) {
|
|
1117
1350
|
logErr(`Not authenticated (HTTP ${res.status}). Run \`aident login\`.`);
|
|
@@ -1123,8 +1356,15 @@ async function fetchCatalog(client) {
|
|
|
1123
1356
|
process.exitCode = 1;
|
|
1124
1357
|
return null;
|
|
1125
1358
|
}
|
|
1126
|
-
catalogCache = res.body;
|
|
1127
|
-
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);
|
|
1128
1368
|
}
|
|
1129
1369
|
function isCommandCatalog(body) {
|
|
1130
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.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:*",
|