@aident-ai/cli 0.1.2 → 0.1.3-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 +14 -3
- package/dist/cli.mjs +590 -205
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,14 @@ Use `--oob` for browserless auth environments:
|
|
|
33
33
|
aident login --oob
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
To revoke the stored OAuth token and remove `~/.aident/credentials.json`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
aident logout
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When using `AIDENT_TOKEN`, unset it in your shell after running `aident logout`.
|
|
43
|
+
|
|
36
44
|
## Packages
|
|
37
45
|
|
|
38
46
|
Loadout is always enabled by default. Add Playbook only when an agent needs to create, execute, or manage playbooks.
|
|
@@ -50,9 +58,9 @@ aident --packages playbook playbooks execute --playbookId pb_123 --json
|
|
|
50
58
|
|
|
51
59
|
```bash
|
|
52
60
|
aident capabilities search --query "send email"
|
|
53
|
-
aident capabilities get --name "gmail_tools
|
|
54
|
-
aident capabilities execute --name "gmail_tools
|
|
55
|
-
aident vault status --integrationId github_tools
|
|
61
|
+
aident capabilities get --name "composio:gmail_tools:gmail_send_email"
|
|
62
|
+
aident capabilities execute --name "composio:gmail_tools:gmail_send_email" --input '{"to":"user@example.com"}'
|
|
63
|
+
aident vault status --integrationId composio:github_tools
|
|
56
64
|
aident integrations migrate-local --json
|
|
57
65
|
aident integrations migrate-local --apply --integrationIds github_tools,slack_tools --json
|
|
58
66
|
aident audit recent --limit 20
|
|
@@ -86,6 +94,9 @@ Environment overrides:
|
|
|
86
94
|
| `AIDENT_PACKAGE` | Focus one package for one invocation. |
|
|
87
95
|
| `AIDENT_PACKAGES` | Enable add-on packages for one invocation, e.g. `playbook`. |
|
|
88
96
|
|
|
97
|
+
OAuth credentials are shared across HTTPS Aident hosts under `aident.ai`, so changing `baseUrl` or `AIDENT_BASE_URL`
|
|
98
|
+
between production, RC, staging, or preview hosts does not require another login. Custom hosts remain isolated.
|
|
99
|
+
|
|
89
100
|
## How It Works
|
|
90
101
|
|
|
91
102
|
Discovery fetches `/api/openapi/{package}.json` and reads the package command catalog embedded in the OpenAPI document.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
5
|
+
|
|
3
6
|
// src/auth.ts
|
|
4
7
|
import { spawn } from "node:child_process";
|
|
5
8
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -444,16 +447,160 @@ function escapeHtml(s) {
|
|
|
444
447
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
445
448
|
}
|
|
446
449
|
|
|
450
|
+
// src/catalogCache.ts
|
|
451
|
+
import crypto from "node:crypto";
|
|
452
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
453
|
+
import { join as join2 } from "node:path";
|
|
454
|
+
|
|
455
|
+
// src/config.ts
|
|
456
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
457
|
+
import { homedir } from "node:os";
|
|
458
|
+
import { join } from "node:path";
|
|
459
|
+
|
|
460
|
+
// src/packages.ts
|
|
461
|
+
var CLI_PACKAGES = ["loadout", "playbook", "intern"];
|
|
462
|
+
var DEFAULT_CLI_PACKAGES = ["loadout"];
|
|
463
|
+
function parseCliPackage(value) {
|
|
464
|
+
if (typeof value !== "string")
|
|
465
|
+
throw new Error(`Unsupported Aident package: ${String(value)}`);
|
|
466
|
+
const normalized = value.trim().toLowerCase();
|
|
467
|
+
if (!CLI_PACKAGES.includes(normalized))
|
|
468
|
+
throw new Error(`Unsupported Aident package: ${value}`);
|
|
469
|
+
return normalized;
|
|
470
|
+
}
|
|
471
|
+
function normalizeCliPackages(value) {
|
|
472
|
+
const rawPackages = value === undefined || value === null || value === "" ? [] : Array.isArray(value) ? value : String(value).split(/[,\s]+/);
|
|
473
|
+
const packages = ["loadout"];
|
|
474
|
+
for (const rawPackage of rawPackages) {
|
|
475
|
+
if (rawPackage === undefined || rawPackage === null || rawPackage === "")
|
|
476
|
+
continue;
|
|
477
|
+
const cliPackage = parseCliPackage(rawPackage);
|
|
478
|
+
if (!packages.includes(cliPackage))
|
|
479
|
+
packages.push(cliPackage);
|
|
480
|
+
}
|
|
481
|
+
return packages;
|
|
482
|
+
}
|
|
483
|
+
function formatCliPackages(packages) {
|
|
484
|
+
return packages.join(",");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/config.ts
|
|
488
|
+
function getAidentDir() {
|
|
489
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
490
|
+
return join(home, ".aident");
|
|
491
|
+
}
|
|
492
|
+
function getConfigFile() {
|
|
493
|
+
return join(getAidentDir(), "config.json");
|
|
494
|
+
}
|
|
495
|
+
var DEFAULT_BASE_URL = "https://loadout.aident.ai";
|
|
496
|
+
var CONFIG_KEYS = ["baseUrl", "packages"];
|
|
497
|
+
async function readConfig() {
|
|
498
|
+
try {
|
|
499
|
+
const text = await readFile(getConfigFile(), "utf-8");
|
|
500
|
+
const parsed = JSON.parse(text);
|
|
501
|
+
if (!parsed || typeof parsed !== "object")
|
|
502
|
+
return {};
|
|
503
|
+
return parsed;
|
|
504
|
+
} catch {
|
|
505
|
+
return {};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async function writeConfig(config) {
|
|
509
|
+
await mkdir(getAidentDir(), { recursive: true, mode: 448 });
|
|
510
|
+
await writeFile(getConfigFile(), JSON.stringify(config, null, 2) + `
|
|
511
|
+
`, { mode: 420 });
|
|
512
|
+
}
|
|
513
|
+
async function setConfigValue(key, value) {
|
|
514
|
+
const config = await readConfig();
|
|
515
|
+
config[key] = value;
|
|
516
|
+
await writeConfig(config);
|
|
517
|
+
}
|
|
518
|
+
async function unsetConfigValue(key) {
|
|
519
|
+
const config = await readConfig();
|
|
520
|
+
delete config[key];
|
|
521
|
+
await writeConfig(config);
|
|
522
|
+
}
|
|
523
|
+
async function resolveDefaultBaseUrl() {
|
|
524
|
+
const env = process.env.AIDENT_BASE_URL?.trim();
|
|
525
|
+
if (env)
|
|
526
|
+
return env;
|
|
527
|
+
const config = await readConfig();
|
|
528
|
+
if (typeof config.baseUrl === "string" && config.baseUrl.trim() !== "") {
|
|
529
|
+
return config.baseUrl.trim();
|
|
530
|
+
}
|
|
531
|
+
return DEFAULT_BASE_URL;
|
|
532
|
+
}
|
|
533
|
+
async function resolveDefaultPackages() {
|
|
534
|
+
const env = process.env.AIDENT_PACKAGES?.trim() || process.env.AIDENT_PACKAGE?.trim();
|
|
535
|
+
if (env)
|
|
536
|
+
return normalizeCliPackages(env);
|
|
537
|
+
const config = await readConfig();
|
|
538
|
+
return normalizeCliPackages(config.packages ?? DEFAULT_CLI_PACKAGES);
|
|
539
|
+
}
|
|
540
|
+
function isKnownConfigKey(key) {
|
|
541
|
+
return CONFIG_KEYS.includes(key);
|
|
542
|
+
}
|
|
543
|
+
function normalizeBaseUrl(url) {
|
|
544
|
+
let trimmed = url.trim();
|
|
545
|
+
if (!/^https?:\/\//i.test(trimmed))
|
|
546
|
+
trimmed = `https://${trimmed}`;
|
|
547
|
+
return trimmed.replace(/\/+$/, "");
|
|
548
|
+
}
|
|
549
|
+
|
|
447
550
|
// src/version.ts
|
|
448
|
-
var VERSION = "0.1.
|
|
551
|
+
var VERSION = "0.1.3-rc.1";
|
|
552
|
+
|
|
553
|
+
// src/catalogCache.ts
|
|
554
|
+
var CACHE_TTL_MS = 5 * 60 * 1000;
|
|
555
|
+
async function genReadCachedCatalog(params) {
|
|
556
|
+
if (process.env.AIDENT_CLI_DISABLE_CATALOG_CACHE === "1")
|
|
557
|
+
return null;
|
|
558
|
+
try {
|
|
559
|
+
const cached = JSON.parse(await readFile2(getCacheFile(params), "utf8"));
|
|
560
|
+
if (cached.credentialFingerprint !== getCredentialFingerprint(params.accessToken) || Date.now() - cached.cachedAt > CACHE_TTL_MS || !isCommandCatalog(cached.catalog))
|
|
561
|
+
return null;
|
|
562
|
+
return cached.catalog;
|
|
563
|
+
} catch {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
async function genWriteCachedCatalog(params, catalog) {
|
|
568
|
+
if (process.env.AIDENT_CLI_DISABLE_CATALOG_CACHE === "1")
|
|
569
|
+
return;
|
|
570
|
+
try {
|
|
571
|
+
const cacheDir = join2(getAidentDir(), "cache");
|
|
572
|
+
await mkdir2(cacheDir, { recursive: true, mode: 448 });
|
|
573
|
+
await writeFile2(getCacheFile(params), JSON.stringify({
|
|
574
|
+
cachedAt: Date.now(),
|
|
575
|
+
catalog,
|
|
576
|
+
credentialFingerprint: getCredentialFingerprint(params.accessToken)
|
|
577
|
+
}), { mode: 384 });
|
|
578
|
+
} catch {}
|
|
579
|
+
}
|
|
580
|
+
function getCacheFile(params) {
|
|
581
|
+
const key = crypto.createHash("sha256").update([params.baseUrl, params.packageName, params.installedSkillVersion ?? "", VERSION].join("\x00")).digest("hex").slice(0, 16);
|
|
582
|
+
return join2(getAidentDir(), "cache", `catalog-${key}.json`);
|
|
583
|
+
}
|
|
584
|
+
function getCredentialFingerprint(accessToken) {
|
|
585
|
+
return crypto.createHash("sha256").update(accessToken).digest("hex").slice(0, 16);
|
|
586
|
+
}
|
|
587
|
+
function isCommandCatalog(value) {
|
|
588
|
+
if (!value || typeof value !== "object")
|
|
589
|
+
return false;
|
|
590
|
+
const catalog = value;
|
|
591
|
+
return Array.isArray(catalog.packages) && Array.isArray(catalog.domains) && Array.isArray(catalog.commands);
|
|
592
|
+
}
|
|
449
593
|
|
|
450
594
|
// src/client.ts
|
|
595
|
+
var CLI_CATALOG_DURATION_HEADER = "x-aident-cli-catalog-duration-ms";
|
|
596
|
+
|
|
451
597
|
class CliClient {
|
|
452
598
|
creds;
|
|
453
599
|
credentialSource;
|
|
454
600
|
packages;
|
|
455
601
|
installedSkillVersion;
|
|
456
602
|
commandOperations = new Map;
|
|
603
|
+
catalogDurationMs = null;
|
|
457
604
|
constructor(creds, credentialSource = "stored", packages, installedSkillVersion = null) {
|
|
458
605
|
this.creds = creds;
|
|
459
606
|
this.credentialSource = credentialSource;
|
|
@@ -475,6 +622,14 @@ class CliClient {
|
|
|
475
622
|
this.creds = creds;
|
|
476
623
|
}
|
|
477
624
|
async getCatalog() {
|
|
625
|
+
const startedAt = Date.now();
|
|
626
|
+
try {
|
|
627
|
+
return await this.fetchCatalog();
|
|
628
|
+
} finally {
|
|
629
|
+
this.catalogDurationMs = Date.now() - startedAt;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
async fetchCatalog() {
|
|
478
633
|
if (this.packages.length === 1) {
|
|
479
634
|
const packageName = this.packages[0];
|
|
480
635
|
const result = await this.fetchOpenApiCatalog(packageName);
|
|
@@ -503,14 +658,37 @@ class CliClient {
|
|
|
503
658
|
}
|
|
504
659
|
};
|
|
505
660
|
}
|
|
506
|
-
return this.fetchJson("POST", this.operationPath(operation.packageName, operation.operationId), args);
|
|
661
|
+
return this.fetchJson("POST", this.operationPath(operation.packageName, operation.operationId), args, this.takeCatalogTimingHeaders());
|
|
507
662
|
}
|
|
508
663
|
async execOperation(packageName, operationId, args) {
|
|
509
|
-
return this.fetchJson("POST", this.operationPath(packageName, operationId), args);
|
|
664
|
+
return this.fetchJson("POST", this.operationPath(packageName, operationId), args, this.takeCatalogTimingHeaders());
|
|
665
|
+
}
|
|
666
|
+
takeCatalogTimingHeaders() {
|
|
667
|
+
if (this.catalogDurationMs === null)
|
|
668
|
+
return;
|
|
669
|
+
const headers = { [CLI_CATALOG_DURATION_HEADER]: String(this.catalogDurationMs) };
|
|
670
|
+
this.catalogDurationMs = null;
|
|
671
|
+
return headers;
|
|
510
672
|
}
|
|
511
673
|
async fetchOpenApiCatalog(packageName) {
|
|
674
|
+
const cacheParams = {
|
|
675
|
+
accessToken: this.creds.access_token,
|
|
676
|
+
baseUrl: this.baseUrl,
|
|
677
|
+
packageName,
|
|
678
|
+
installedSkillVersion: this.installedSkillVersion
|
|
679
|
+
};
|
|
680
|
+
const cached = await genReadCachedCatalog(cacheParams);
|
|
681
|
+
if (cached)
|
|
682
|
+
return { status: 200, body: cached };
|
|
512
683
|
const headers = packageName === "loadout" && this.installedSkillVersion ? { "x-aident-skill-version": this.installedSkillVersion } : undefined;
|
|
513
|
-
const
|
|
684
|
+
const path = this.catalogPath(packageName);
|
|
685
|
+
let result;
|
|
686
|
+
try {
|
|
687
|
+
result = await this.fetchJson("GET", path, undefined, headers);
|
|
688
|
+
} catch (error) {
|
|
689
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
690
|
+
throw new Error(`Unable to reach the Aident ${packageName} command catalog at ${this.baseUrl}${path}. Allow outbound HTTPS access to ${this.baseUrl} and retry. (${detail})`);
|
|
691
|
+
}
|
|
514
692
|
const catalog = result.body["x-aident-command-catalog"];
|
|
515
693
|
if (result.status !== 200 || !catalog) {
|
|
516
694
|
return {
|
|
@@ -521,6 +699,7 @@ class CliClient {
|
|
|
521
699
|
if (packageName === "loadout" && result.body["x-aident-loadout-skill"]) {
|
|
522
700
|
catalog.loadoutSkill = result.body["x-aident-loadout-skill"];
|
|
523
701
|
}
|
|
702
|
+
await genWriteCachedCatalog(cacheParams, catalog);
|
|
524
703
|
return { status: result.status, body: catalog };
|
|
525
704
|
}
|
|
526
705
|
catalogPath(packageName) {
|
|
@@ -603,111 +782,16 @@ function mergeCatalogs(catalogs, packages, onCommand) {
|
|
|
603
782
|
};
|
|
604
783
|
}
|
|
605
784
|
|
|
606
|
-
// src/config.ts
|
|
607
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
608
|
-
import { homedir } from "node:os";
|
|
609
|
-
import { join } from "node:path";
|
|
610
|
-
|
|
611
|
-
// src/packages.ts
|
|
612
|
-
var CLI_PACKAGES = ["loadout", "playbook", "intern"];
|
|
613
|
-
var DEFAULT_CLI_PACKAGES = ["loadout"];
|
|
614
|
-
function parseCliPackage(value) {
|
|
615
|
-
if (typeof value !== "string")
|
|
616
|
-
throw new Error(`Unsupported Aident package: ${String(value)}`);
|
|
617
|
-
const normalized = value.trim().toLowerCase();
|
|
618
|
-
if (!CLI_PACKAGES.includes(normalized))
|
|
619
|
-
throw new Error(`Unsupported Aident package: ${value}`);
|
|
620
|
-
return normalized;
|
|
621
|
-
}
|
|
622
|
-
function normalizeCliPackages(value) {
|
|
623
|
-
const rawPackages = value === undefined || value === null || value === "" ? [] : Array.isArray(value) ? value : String(value).split(/[,\s]+/);
|
|
624
|
-
const packages = ["loadout"];
|
|
625
|
-
for (const rawPackage of rawPackages) {
|
|
626
|
-
if (rawPackage === undefined || rawPackage === null || rawPackage === "")
|
|
627
|
-
continue;
|
|
628
|
-
const cliPackage = parseCliPackage(rawPackage);
|
|
629
|
-
if (!packages.includes(cliPackage))
|
|
630
|
-
packages.push(cliPackage);
|
|
631
|
-
}
|
|
632
|
-
return packages;
|
|
633
|
-
}
|
|
634
|
-
function formatCliPackages(packages) {
|
|
635
|
-
return packages.join(",");
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// src/config.ts
|
|
639
|
-
function getAidentDir() {
|
|
640
|
-
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
641
|
-
return join(home, ".aident");
|
|
642
|
-
}
|
|
643
|
-
function getConfigFile() {
|
|
644
|
-
return join(getAidentDir(), "config.json");
|
|
645
|
-
}
|
|
646
|
-
var DEFAULT_BASE_URL = "https://loadout.aident.ai";
|
|
647
|
-
var CONFIG_KEYS = ["baseUrl", "packages"];
|
|
648
|
-
async function readConfig() {
|
|
649
|
-
try {
|
|
650
|
-
const text = await readFile(getConfigFile(), "utf-8");
|
|
651
|
-
const parsed = JSON.parse(text);
|
|
652
|
-
if (!parsed || typeof parsed !== "object")
|
|
653
|
-
return {};
|
|
654
|
-
return parsed;
|
|
655
|
-
} catch {
|
|
656
|
-
return {};
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
async function writeConfig(config) {
|
|
660
|
-
await mkdir(getAidentDir(), { recursive: true, mode: 448 });
|
|
661
|
-
await writeFile(getConfigFile(), JSON.stringify(config, null, 2) + `
|
|
662
|
-
`, { mode: 420 });
|
|
663
|
-
}
|
|
664
|
-
async function setConfigValue(key, value) {
|
|
665
|
-
const config = await readConfig();
|
|
666
|
-
config[key] = value;
|
|
667
|
-
await writeConfig(config);
|
|
668
|
-
}
|
|
669
|
-
async function unsetConfigValue(key) {
|
|
670
|
-
const config = await readConfig();
|
|
671
|
-
delete config[key];
|
|
672
|
-
await writeConfig(config);
|
|
673
|
-
}
|
|
674
|
-
async function resolveDefaultBaseUrl() {
|
|
675
|
-
const env = process.env.AIDENT_BASE_URL?.trim();
|
|
676
|
-
if (env)
|
|
677
|
-
return env;
|
|
678
|
-
const config = await readConfig();
|
|
679
|
-
if (typeof config.baseUrl === "string" && config.baseUrl.trim() !== "") {
|
|
680
|
-
return config.baseUrl.trim();
|
|
681
|
-
}
|
|
682
|
-
return DEFAULT_BASE_URL;
|
|
683
|
-
}
|
|
684
|
-
async function resolveDefaultPackages() {
|
|
685
|
-
const env = process.env.AIDENT_PACKAGES?.trim() || process.env.AIDENT_PACKAGE?.trim();
|
|
686
|
-
if (env)
|
|
687
|
-
return normalizeCliPackages(env);
|
|
688
|
-
const config = await readConfig();
|
|
689
|
-
return normalizeCliPackages(config.packages ?? DEFAULT_CLI_PACKAGES);
|
|
690
|
-
}
|
|
691
|
-
function isKnownConfigKey(key) {
|
|
692
|
-
return CONFIG_KEYS.includes(key);
|
|
693
|
-
}
|
|
694
|
-
function normalizeBaseUrl(url) {
|
|
695
|
-
let trimmed = url.trim();
|
|
696
|
-
if (!/^https?:\/\//i.test(trimmed))
|
|
697
|
-
trimmed = `https://${trimmed}`;
|
|
698
|
-
return trimmed.replace(/\/+$/, "");
|
|
699
|
-
}
|
|
700
|
-
|
|
701
785
|
// src/credentials.ts
|
|
702
|
-
import { mkdir as
|
|
703
|
-
import { join as
|
|
786
|
+
import { mkdir as mkdir3, readFile as readFile3, rm, writeFile as writeFile3 } from "node:fs/promises";
|
|
787
|
+
import { join as join3 } from "node:path";
|
|
704
788
|
var REFRESH_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
705
789
|
function getCredentialsFile() {
|
|
706
|
-
return
|
|
790
|
+
return join3(getAidentDir(), "credentials.json");
|
|
707
791
|
}
|
|
708
792
|
async function readCredentials() {
|
|
709
793
|
try {
|
|
710
|
-
const content = await
|
|
794
|
+
const content = await readFile3(getCredentialsFile(), "utf-8");
|
|
711
795
|
const parsed = JSON.parse(content);
|
|
712
796
|
if (!parsed.access_token || !parsed.base_url)
|
|
713
797
|
return null;
|
|
@@ -717,12 +801,31 @@ async function readCredentials() {
|
|
|
717
801
|
}
|
|
718
802
|
}
|
|
719
803
|
async function writeCredentials(creds) {
|
|
720
|
-
await
|
|
721
|
-
await
|
|
804
|
+
await mkdir3(getAidentDir(), { recursive: true, mode: 448 });
|
|
805
|
+
await writeFile3(getCredentialsFile(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
722
806
|
}
|
|
723
807
|
async function clearCredentials() {
|
|
724
808
|
await rm(getCredentialsFile(), { force: true });
|
|
725
809
|
}
|
|
810
|
+
function credentialsForBaseUrl(creds, baseUrl) {
|
|
811
|
+
const credentialsBaseUrl = creds.base_url.replace(/\/+$/, "");
|
|
812
|
+
const requestedBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
813
|
+
if (credentialsBaseUrl === requestedBaseUrl)
|
|
814
|
+
return { ...creds, base_url: requestedBaseUrl };
|
|
815
|
+
if (!getSharedAidentBaseUrl(credentialsBaseUrl))
|
|
816
|
+
return null;
|
|
817
|
+
const sharedBaseUrl = getSharedAidentBaseUrl(requestedBaseUrl);
|
|
818
|
+
if (!sharedBaseUrl)
|
|
819
|
+
return null;
|
|
820
|
+
return { ...creds, base_url: sharedBaseUrl };
|
|
821
|
+
}
|
|
822
|
+
async function clearCredentialsForIncompatibleBaseUrl(baseUrl) {
|
|
823
|
+
const creds = await readCredentials();
|
|
824
|
+
if (!creds || credentialsForBaseUrl(creds, baseUrl))
|
|
825
|
+
return null;
|
|
826
|
+
await clearCredentials();
|
|
827
|
+
return creds.base_url;
|
|
828
|
+
}
|
|
726
829
|
function isExpired(creds) {
|
|
727
830
|
if (!creds.expires_at)
|
|
728
831
|
return false;
|
|
@@ -731,11 +834,23 @@ function isExpired(creds) {
|
|
|
731
834
|
return false;
|
|
732
835
|
return Date.now() >= expiresAt - REFRESH_WINDOW_MS;
|
|
733
836
|
}
|
|
837
|
+
function getSharedAidentBaseUrl(baseUrl) {
|
|
838
|
+
try {
|
|
839
|
+
const url = new URL(baseUrl);
|
|
840
|
+
if (url.protocol !== "https:" || url.port !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "" || url.username !== "" || url.password !== "" || url.hostname !== "aident.ai" && !url.hostname.endsWith(".aident.ai")) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
return url.origin;
|
|
844
|
+
} catch {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
734
848
|
|
|
735
849
|
// src/loadoutSkill.ts
|
|
736
|
-
import {
|
|
850
|
+
import { existsSync } from "node:fs";
|
|
851
|
+
import { readFile as readFile4, realpath } from "node:fs/promises";
|
|
737
852
|
import { homedir as homedir2 } from "node:os";
|
|
738
|
-
import { join as
|
|
853
|
+
import { dirname, join as join4, resolve } from "node:path";
|
|
739
854
|
async function fetchLoadoutSkillMetadata(baseUrl) {
|
|
740
855
|
const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/.well-known/loadout-skill.json`, {
|
|
741
856
|
method: "GET",
|
|
@@ -756,34 +871,104 @@ function isLocalIntegrationMigrationPromptEnabled(metadata) {
|
|
|
756
871
|
return metadata?.localIntegrationMigrationPromptEnabled === true;
|
|
757
872
|
}
|
|
758
873
|
function getInstalledLoadoutSkillCandidates(cwd = process.cwd(), home = homedir2()) {
|
|
759
|
-
const skillFile =
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
874
|
+
const skillFile = join4("skills", "aident-skill", "SKILL.md");
|
|
875
|
+
const candidates = [];
|
|
876
|
+
const paths = new Set;
|
|
877
|
+
const add = (agent, scope, root, folder = "") => {
|
|
878
|
+
const path = join4(root, folder, skillFile);
|
|
879
|
+
if (paths.has(path))
|
|
880
|
+
return;
|
|
881
|
+
paths.add(path);
|
|
882
|
+
candidates.push({ agent, scope, path });
|
|
883
|
+
};
|
|
884
|
+
for (const root of getProjectRoots(cwd)) {
|
|
885
|
+
add("shared" /* Shared */, "project" /* Project */, root, ".agents");
|
|
886
|
+
add("claude-code" /* ClaudeCode */, "project" /* Project */, root, ".claude");
|
|
887
|
+
add("cursor" /* Cursor */, "project" /* Project */, root, ".cursor");
|
|
888
|
+
add("gemini" /* Gemini */, "project" /* Project */, root, ".gemini");
|
|
889
|
+
add("workbuddy" /* WorkBuddy */, "project" /* Project */, root, ".workbuddy");
|
|
890
|
+
}
|
|
891
|
+
add("shared" /* Shared */, "global" /* Global */, home, ".agents");
|
|
892
|
+
add("shared" /* Shared */, "global" /* Global */, home, join4(".config", "agents"));
|
|
893
|
+
add("claude-code" /* ClaudeCode */, "global" /* Global */, home, ".claude");
|
|
894
|
+
add("codex" /* Codex */, "global" /* Global */, home, ".codex");
|
|
895
|
+
add("cursor" /* Cursor */, "global" /* Global */, home, ".cursor");
|
|
896
|
+
add("gemini" /* Gemini */, "global" /* Global */, home, ".gemini");
|
|
897
|
+
add("workbuddy" /* WorkBuddy */, "global" /* Global */, home, ".workbuddy");
|
|
898
|
+
add("codex" /* Codex */, "admin" /* Admin */, "/etc/codex");
|
|
899
|
+
return candidates;
|
|
769
900
|
}
|
|
770
901
|
function formatLoadoutSkillWarnings(metadata) {
|
|
771
902
|
const warnings = metadata?.notices.filter((notice) => notice.severity === "warning") ?? [];
|
|
772
903
|
return warnings.map((notice) => `${colors.yellow}Warning:${colors.reset} ${notice.message}`);
|
|
773
904
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
905
|
+
function isLoadoutSkillVersionBehind(installed, current) {
|
|
906
|
+
const installedParts = parseSkillVersion(installed);
|
|
907
|
+
const currentParts = parseSkillVersion(current);
|
|
908
|
+
if (!installedParts || !currentParts)
|
|
909
|
+
return false;
|
|
910
|
+
for (let i = 0;i < installedParts.length; i++) {
|
|
911
|
+
if (installedParts[i] !== currentParts[i])
|
|
912
|
+
return installedParts[i] < currentParts[i];
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
async function scanInstalledLoadoutSkills(candidates = getInstalledLoadoutSkillCandidates()) {
|
|
917
|
+
const matches = await Promise.all(candidates.map(async (candidate) => {
|
|
777
918
|
try {
|
|
778
|
-
content = await
|
|
919
|
+
const [content, resolvedPath] = await Promise.all([
|
|
920
|
+
readFile4(candidate.path, "utf-8"),
|
|
921
|
+
realpath(candidate.path)
|
|
922
|
+
]);
|
|
923
|
+
const match = /^version:\s*(\d+\.\d+\.\d+)\s*$/m.exec(content);
|
|
924
|
+
return match ? { candidate, resolvedPath, version: match[1] } : null;
|
|
779
925
|
} catch {
|
|
780
|
-
|
|
926
|
+
return null;
|
|
781
927
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
928
|
+
}));
|
|
929
|
+
const installed = [];
|
|
930
|
+
const resolvedPaths = new Set;
|
|
931
|
+
for (const match of matches) {
|
|
932
|
+
if (!match || resolvedPaths.has(match.resolvedPath))
|
|
933
|
+
continue;
|
|
934
|
+
resolvedPaths.add(match.resolvedPath);
|
|
935
|
+
installed.push({ ...match.candidate, version: match.version });
|
|
936
|
+
}
|
|
937
|
+
return installed;
|
|
938
|
+
}
|
|
939
|
+
async function findInstalledLoadoutSkillVersion(candidates = getInstalledLoadoutSkillCandidates()) {
|
|
940
|
+
const installed = await scanInstalledLoadoutSkills(candidates);
|
|
941
|
+
return installed.reduce((oldest, skill) => !oldest || isLoadoutSkillVersionBehind(skill.version, oldest) ? skill.version : oldest, null);
|
|
942
|
+
}
|
|
943
|
+
function formatLoadoutAgentHost(agent) {
|
|
944
|
+
switch (agent) {
|
|
945
|
+
case "claude-code" /* ClaudeCode */:
|
|
946
|
+
return "Claude Code";
|
|
947
|
+
case "codex" /* Codex */:
|
|
948
|
+
return "Codex";
|
|
949
|
+
case "cursor" /* Cursor */:
|
|
950
|
+
return "Cursor";
|
|
951
|
+
case "gemini" /* Gemini */:
|
|
952
|
+
return "Gemini";
|
|
953
|
+
case "workbuddy" /* WorkBuddy */:
|
|
954
|
+
return "WorkBuddy";
|
|
955
|
+
case "shared" /* Shared */:
|
|
956
|
+
return "Shared agents";
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function getProjectRoots(cwd) {
|
|
960
|
+
const start = resolve(cwd);
|
|
961
|
+
const roots = [];
|
|
962
|
+
let current = start;
|
|
963
|
+
while (true) {
|
|
964
|
+
roots.push(current);
|
|
965
|
+
if (existsSync(join4(current, ".git")))
|
|
966
|
+
return roots;
|
|
967
|
+
const parent = dirname(current);
|
|
968
|
+
if (parent === current)
|
|
969
|
+
return [start];
|
|
970
|
+
current = parent;
|
|
785
971
|
}
|
|
786
|
-
return null;
|
|
787
972
|
}
|
|
788
973
|
function isLoadoutSkillNotice(value) {
|
|
789
974
|
if (!value || typeof value !== "object")
|
|
@@ -791,27 +976,56 @@ function isLoadoutSkillNotice(value) {
|
|
|
791
976
|
const body = value;
|
|
792
977
|
return typeof body.id === "string" && (body.severity === "info" || body.severity === "warning") && typeof body.message === "string";
|
|
793
978
|
}
|
|
979
|
+
function parseSkillVersion(version) {
|
|
980
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
|
|
981
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
982
|
+
}
|
|
794
983
|
|
|
795
984
|
// src/doctor.ts
|
|
796
|
-
async function runDoctor(opts) {
|
|
985
|
+
async function runDoctor(opts, skillCandidates = getInstalledLoadoutSkillCandidates()) {
|
|
797
986
|
const checks = [];
|
|
798
987
|
checks.push(checkNodeVersion());
|
|
799
988
|
checks.push(await checkConfig());
|
|
800
989
|
checks.push(await checkCredentials(opts.baseUrl));
|
|
801
990
|
checks.push(await checkServerReachable(opts.baseUrl));
|
|
802
|
-
checks.push(await
|
|
991
|
+
checks.push(...await checkLoadoutSkills(opts.baseUrl, skillCandidates));
|
|
803
992
|
const ok = checks.every((c) => c.ok);
|
|
804
993
|
return { ok, checks, ...opts };
|
|
805
994
|
}
|
|
806
|
-
async function
|
|
807
|
-
const metadata = await
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
995
|
+
async function checkLoadoutSkills(baseUrl, candidates) {
|
|
996
|
+
const [metadata, installedSkills] = await Promise.all([
|
|
997
|
+
fetchLoadoutSkillMetadata(baseUrl),
|
|
998
|
+
scanInstalledLoadoutSkills(candidates)
|
|
999
|
+
]);
|
|
1000
|
+
if (installedSkills.length === 0) {
|
|
1001
|
+
return [
|
|
1002
|
+
{
|
|
1003
|
+
name: "Loadout skills",
|
|
1004
|
+
ok: true,
|
|
1005
|
+
detail: metadata ? `none found; latest v${metadata.skillVersion}; ${metadata.updatePrompt}` : "none found; freshness metadata unavailable"
|
|
1006
|
+
}
|
|
1007
|
+
];
|
|
1008
|
+
}
|
|
1009
|
+
return installedSkills.map((skill) => {
|
|
1010
|
+
const name = `Loadout skill (${formatLoadoutAgentHost(skill.agent)})`;
|
|
1011
|
+
const location = `${skill.scope}; ${skill.path}`;
|
|
1012
|
+
if (!metadata) {
|
|
1013
|
+
return { name, ok: true, detail: `installed v${skill.version}; latest unavailable; ${location}` };
|
|
1014
|
+
}
|
|
1015
|
+
if (isLoadoutSkillVersionBehind(skill.version, metadata.skillVersion)) {
|
|
1016
|
+
return {
|
|
1017
|
+
name,
|
|
1018
|
+
ok: true,
|
|
1019
|
+
warning: true,
|
|
1020
|
+
detail: `installed v${skill.version}; latest v${metadata.skillVersion}; ${location}; ${metadata.updatePrompt}`
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
name,
|
|
1025
|
+
ok: true,
|
|
1026
|
+
detail: skill.version === metadata.skillVersion ? `installed v${skill.version} (latest); ${location}` : `installed v${skill.version}; published v${metadata.skillVersion}; ${location}`
|
|
1027
|
+
};
|
|
1028
|
+
});
|
|
815
1029
|
}
|
|
816
1030
|
function checkNodeVersion() {
|
|
817
1031
|
const version = process.versions.node;
|
|
@@ -840,14 +1054,14 @@ async function checkCredentials(baseUrl) {
|
|
|
840
1054
|
if (!creds) {
|
|
841
1055
|
return { name: "Authenticated", ok: false, detail: "no credentials — run `aident login`" };
|
|
842
1056
|
}
|
|
843
|
-
if (creds
|
|
1057
|
+
if (!credentialsForBaseUrl(creds, baseUrl)) {
|
|
844
1058
|
return {
|
|
845
1059
|
name: "Authenticated",
|
|
846
1060
|
ok: false,
|
|
847
1061
|
detail: `credentials are for ${creds.base_url} but config baseUrl is ${baseUrl} — run \`aident login\``
|
|
848
1062
|
};
|
|
849
1063
|
}
|
|
850
|
-
return { name: "Authenticated", ok: true, detail: `signed in to ${
|
|
1064
|
+
return { name: "Authenticated", ok: true, detail: `signed in to ${baseUrl}` };
|
|
851
1065
|
}
|
|
852
1066
|
async function checkServerReachable(baseUrl) {
|
|
853
1067
|
try {
|
|
@@ -879,7 +1093,7 @@ async function checkServerReachable(baseUrl) {
|
|
|
879
1093
|
// src/help.ts
|
|
880
1094
|
var LOCAL_HELP_COMMANDS = [
|
|
881
1095
|
{ command: "login", description: "Authenticate with Aident" },
|
|
882
|
-
{ command: "logout", description: "
|
|
1096
|
+
{ command: "logout", description: "Sign out and remove local credentials" },
|
|
883
1097
|
{ command: "whoami", description: "Show current user" },
|
|
884
1098
|
{ command: "config show", description: "Print persistent config" },
|
|
885
1099
|
{ command: "config set <key> <value>", description: "Persist a config value" },
|
|
@@ -932,7 +1146,7 @@ function renderHelp(catalog) {
|
|
|
932
1146
|
lines.push("USAGE:");
|
|
933
1147
|
lines.push(" aident <domain> <command> [--flag value ...] [--json]");
|
|
934
1148
|
lines.push(" aident login Authenticate with Aident");
|
|
935
|
-
lines.push(" aident logout
|
|
1149
|
+
lines.push(" aident logout Sign out and remove local credentials");
|
|
936
1150
|
lines.push(" aident whoami Show current user");
|
|
937
1151
|
lines.push(" aident <domain> help List commands in a domain");
|
|
938
1152
|
lines.push(" aident <domain> <command> help Show input schema and examples");
|
|
@@ -972,6 +1186,18 @@ function renderLoginHelp() {
|
|
|
972
1186
|
].join(`
|
|
973
1187
|
`);
|
|
974
1188
|
}
|
|
1189
|
+
function renderLogoutHelp() {
|
|
1190
|
+
return [
|
|
1191
|
+
`${colors.bold}AIDENT LOGOUT${colors.reset}`,
|
|
1192
|
+
"",
|
|
1193
|
+
"USAGE:",
|
|
1194
|
+
" aident logout",
|
|
1195
|
+
"",
|
|
1196
|
+
"Revokes the stored OAuth token and removes ~/.aident/credentials.json.",
|
|
1197
|
+
"If AIDENT_TOKEN is set, unset it in your shell to finish signing out."
|
|
1198
|
+
].join(`
|
|
1199
|
+
`);
|
|
1200
|
+
}
|
|
975
1201
|
function renderDomainHelp(catalog, domain) {
|
|
976
1202
|
const cmds = catalog.commands.filter((c) => c.domain === domain);
|
|
977
1203
|
if (cmds.length === 0)
|
|
@@ -987,20 +1213,27 @@ function renderDomainHelp(catalog, domain) {
|
|
|
987
1213
|
lines.push("");
|
|
988
1214
|
lines.push("COMMANDS:");
|
|
989
1215
|
for (const c of cmds) {
|
|
990
|
-
|
|
1216
|
+
if (c.command === domain && c.cliActionSubcommands?.length) {
|
|
1217
|
+
for (const subcommand of c.cliActionSubcommands) {
|
|
1218
|
+
lines.push(` ${subcommand.padEnd(28)} ${c.description}`);
|
|
1219
|
+
}
|
|
1220
|
+
} else {
|
|
1221
|
+
lines.push(` ${c.command.padEnd(28)} ${c.description}`);
|
|
1222
|
+
}
|
|
991
1223
|
}
|
|
992
1224
|
lines.push("");
|
|
993
1225
|
lines.push(`Run "aident ${display} <command> help" for input schema and examples.`);
|
|
994
1226
|
return lines.join(`
|
|
995
1227
|
`);
|
|
996
1228
|
}
|
|
997
|
-
function renderCommandHelp(catalog, domain, command) {
|
|
1229
|
+
function renderCommandHelp(catalog, domain, command, subcommand) {
|
|
998
1230
|
const cmd = catalog.commands.find((c) => c.domain === domain && c.command === command);
|
|
999
1231
|
if (!cmd)
|
|
1000
1232
|
return `Unknown command: ${domain} ${command}`;
|
|
1001
1233
|
const display = domain.startsWith("admin:") ? `admin ${domain.replace("admin:", "")}` : domain;
|
|
1234
|
+
const commandSuffix = subcommand ? ` ${subcommand}` : domain === command ? "" : ` ${command}`;
|
|
1002
1235
|
const lines = [];
|
|
1003
|
-
lines.push(`${colors.bold}AIDENT ${display.toUpperCase()}
|
|
1236
|
+
lines.push(`${colors.bold}AIDENT ${display.toUpperCase()}${commandSuffix.toUpperCase()}${colors.reset}`);
|
|
1004
1237
|
lines.push("");
|
|
1005
1238
|
lines.push(cmd.description);
|
|
1006
1239
|
if (cmd.longDescription) {
|
|
@@ -1013,10 +1246,16 @@ function renderCommandHelp(catalog, domain, command) {
|
|
|
1013
1246
|
lines.push("");
|
|
1014
1247
|
lines.push("INPUT:");
|
|
1015
1248
|
for (const [key, val] of Object.entries(schema.properties)) {
|
|
1249
|
+
if (key === "action" && cmd.cliActionSubcommands?.length)
|
|
1250
|
+
continue;
|
|
1016
1251
|
const tag = required.has(key) ? "" : " (optional)";
|
|
1017
1252
|
const type = val.type ? ` ${colors.dim}<${val.type}>${colors.reset}` : "";
|
|
1018
1253
|
lines.push(` --${key.padEnd(24)}${type} ${val.description ?? ""}${tag}`);
|
|
1019
1254
|
}
|
|
1255
|
+
if (Object.values(schema.properties).some((val) => val.type === "string")) {
|
|
1256
|
+
lines.push("");
|
|
1257
|
+
lines.push(` ${colors.dim}String flags also accept --<flag>-file <path> to read the value from a local file.${colors.reset}`);
|
|
1258
|
+
}
|
|
1020
1259
|
}
|
|
1021
1260
|
if (cmd.outputDescription) {
|
|
1022
1261
|
lines.push("");
|
|
@@ -1025,10 +1264,13 @@ function renderCommandHelp(catalog, domain, command) {
|
|
|
1025
1264
|
if (cmd.examples && cmd.examples.length > 0) {
|
|
1026
1265
|
lines.push("");
|
|
1027
1266
|
lines.push("EXAMPLES:");
|
|
1028
|
-
|
|
1267
|
+
const examples = subcommand ? cmd.examples.filter((example) => example.args.action === subcommand) : cmd.examples;
|
|
1268
|
+
for (const ex of examples) {
|
|
1029
1269
|
lines.push(` ${colors.dim}# ${ex.description}${colors.reset}`);
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1270
|
+
const action = typeof ex.args.action === "string" && cmd.cliActionSubcommands?.includes(ex.args.action) ? ex.args.action : undefined;
|
|
1271
|
+
const argStr = Object.entries(ex.args).filter(([key]) => key !== "action" || !action).map(([k, v]) => `--${k} ${typeof v === "string" ? `"${v}"` : JSON.stringify(v)}`).join(" ");
|
|
1272
|
+
const exampleSuffix = action ? ` ${action}` : commandSuffix;
|
|
1273
|
+
lines.push(` aident ${display}${exampleSuffix} ${argStr}`);
|
|
1032
1274
|
lines.push("");
|
|
1033
1275
|
}
|
|
1034
1276
|
}
|
|
@@ -1038,9 +1280,9 @@ function renderCommandHelp(catalog, domain, command) {
|
|
|
1038
1280
|
|
|
1039
1281
|
// src/localIntegrationMigration.ts
|
|
1040
1282
|
import { createHash as createHash2 } from "node:crypto";
|
|
1041
|
-
import { readFile as
|
|
1283
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1042
1284
|
import { homedir as homedir3 } from "node:os";
|
|
1043
|
-
import { basename, join as
|
|
1285
|
+
import { basename, join as join5 } from "node:path";
|
|
1044
1286
|
var LOCAL_INTEGRATION_MIGRATION_PROMPTED_AT_KEY = "localIntegrationMigrationPromptedAt";
|
|
1045
1287
|
var LOCAL_INTEGRATION_MIGRATION_SKIPPED_AT_KEY = "localIntegrationMigrationSkippedAt";
|
|
1046
1288
|
var LOCAL_INTEGRATION_MIGRATION_COMPLETED_AT_KEY = "localIntegrationMigrationCompletedAt";
|
|
@@ -1154,44 +1396,44 @@ function formatLocalIntegrationMigrationPlan(plan) {
|
|
|
1154
1396
|
}
|
|
1155
1397
|
function localConfigFiles(cwd, homeDir) {
|
|
1156
1398
|
return [
|
|
1157
|
-
{ path:
|
|
1399
|
+
{ path: join5(cwd, ".mcp.json"), source: "mcp_config", kind: "json" },
|
|
1158
1400
|
{
|
|
1159
|
-
path:
|
|
1401
|
+
path: join5(cwd, ".cursor", "mcp.json"),
|
|
1160
1402
|
source: "mcp_config",
|
|
1161
1403
|
kind: "json"
|
|
1162
1404
|
},
|
|
1163
1405
|
{
|
|
1164
|
-
path:
|
|
1406
|
+
path: join5(cwd, ".vscode", "mcp.json"),
|
|
1165
1407
|
source: "mcp_config",
|
|
1166
1408
|
kind: "json"
|
|
1167
1409
|
},
|
|
1168
1410
|
{
|
|
1169
|
-
path:
|
|
1411
|
+
path: join5(homeDir, ".claude.json"),
|
|
1170
1412
|
source: "agent_config",
|
|
1171
1413
|
kind: "json"
|
|
1172
1414
|
},
|
|
1173
1415
|
{
|
|
1174
|
-
path:
|
|
1416
|
+
path: join5(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
1175
1417
|
source: "agent_config",
|
|
1176
1418
|
kind: "json"
|
|
1177
1419
|
},
|
|
1178
1420
|
{
|
|
1179
|
-
path:
|
|
1421
|
+
path: join5(homeDir, "AppData", "Roaming", "Claude", "claude_desktop_config.json"),
|
|
1180
1422
|
source: "agent_config",
|
|
1181
1423
|
kind: "json"
|
|
1182
1424
|
},
|
|
1183
1425
|
{
|
|
1184
|
-
path:
|
|
1426
|
+
path: join5(homeDir, ".config", "Claude", "claude_desktop_config.json"),
|
|
1185
1427
|
source: "agent_config",
|
|
1186
1428
|
kind: "json"
|
|
1187
1429
|
},
|
|
1188
1430
|
{
|
|
1189
|
-
path:
|
|
1431
|
+
path: join5(homeDir, ".cursor", "mcp.json"),
|
|
1190
1432
|
source: "mcp_config",
|
|
1191
1433
|
kind: "json"
|
|
1192
1434
|
},
|
|
1193
1435
|
{
|
|
1194
|
-
path:
|
|
1436
|
+
path: join5(homeDir, ".aident", "config.json"),
|
|
1195
1437
|
source: "aident_cli_config",
|
|
1196
1438
|
kind: "aident-config"
|
|
1197
1439
|
}
|
|
@@ -1199,7 +1441,7 @@ function localConfigFiles(cwd, homeDir) {
|
|
|
1199
1441
|
}
|
|
1200
1442
|
async function readOptionalText(path) {
|
|
1201
1443
|
try {
|
|
1202
|
-
return await
|
|
1444
|
+
return await readFile5(path, "utf-8");
|
|
1203
1445
|
} catch {
|
|
1204
1446
|
return null;
|
|
1205
1447
|
}
|
|
@@ -1554,6 +1796,38 @@ function coerce(val) {
|
|
|
1554
1796
|
}
|
|
1555
1797
|
return val;
|
|
1556
1798
|
}
|
|
1799
|
+
async function applyFileValueFlags(flags, schema, readFile6) {
|
|
1800
|
+
const props = schema?.properties ?? {};
|
|
1801
|
+
const out = { ...flags };
|
|
1802
|
+
for (const key of Object.keys(flags)) {
|
|
1803
|
+
if (!key.endsWith("-file") || key in props)
|
|
1804
|
+
continue;
|
|
1805
|
+
const base = key.slice(0, -"-file".length);
|
|
1806
|
+
const prop = base ? props[base] : undefined;
|
|
1807
|
+
const types = Array.isArray(prop?.type) ? prop.type : prop?.type ? [prop.type] : [];
|
|
1808
|
+
if (!types.includes("string"))
|
|
1809
|
+
continue;
|
|
1810
|
+
if (base in out) {
|
|
1811
|
+
return { error: { code: "invalid-input", message: `Pass either --${base} or --${key}, not both.` } };
|
|
1812
|
+
}
|
|
1813
|
+
const path = out[key];
|
|
1814
|
+
if (typeof path !== "string" || !path.trim()) {
|
|
1815
|
+
return { error: { code: "invalid-input", message: `--${key} requires a file path.` } };
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
out[base] = await readFile6(path);
|
|
1819
|
+
} catch (error) {
|
|
1820
|
+
return {
|
|
1821
|
+
error: {
|
|
1822
|
+
code: "invalid-input",
|
|
1823
|
+
message: `Unable to read --${key} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
delete out[key];
|
|
1828
|
+
}
|
|
1829
|
+
return { flags: out };
|
|
1830
|
+
}
|
|
1557
1831
|
function coerceArgsToSchema(args, schema) {
|
|
1558
1832
|
const props = schema?.properties;
|
|
1559
1833
|
if (!props)
|
|
@@ -1578,6 +1852,74 @@ function coerceArgsToSchema(args, schema) {
|
|
|
1578
1852
|
}
|
|
1579
1853
|
return out;
|
|
1580
1854
|
}
|
|
1855
|
+
function shouldRejectRemainingArgs(domain, command) {
|
|
1856
|
+
return domain === "audit" && command === "audit" || domain === "billing" && command === "billing" || domain === "vault" && command === "vault";
|
|
1857
|
+
}
|
|
1858
|
+
function parseJsonObjectArg(value) {
|
|
1859
|
+
const trimmed = value.trim();
|
|
1860
|
+
if (!trimmed.startsWith("{"))
|
|
1861
|
+
return null;
|
|
1862
|
+
try {
|
|
1863
|
+
const parsed = JSON.parse(trimmed);
|
|
1864
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1865
|
+
return { error: { code: "invalid-input", message: "JSON command input must be an object." } };
|
|
1866
|
+
}
|
|
1867
|
+
return { args: parsed };
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
return {
|
|
1870
|
+
error: {
|
|
1871
|
+
code: "invalid-input",
|
|
1872
|
+
message: `Invalid JSON command input: ${error instanceof Error ? error.message : String(error)}`
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
function buildCommandArgs(params) {
|
|
1878
|
+
const remaining = [...params.remaining];
|
|
1879
|
+
const positionalJson = remaining.length === 1 ? parseJsonObjectArg(remaining[0]) : null;
|
|
1880
|
+
if (positionalJson?.error)
|
|
1881
|
+
return positionalJson;
|
|
1882
|
+
const args = { ...positionalJson?.args ?? {}, ...params.flags };
|
|
1883
|
+
const props = params.schema?.properties ?? {};
|
|
1884
|
+
for (const reserved of RESERVED_CLI_FLAGS) {
|
|
1885
|
+
if (reserved === "version" && props.version !== undefined)
|
|
1886
|
+
continue;
|
|
1887
|
+
delete args[reserved];
|
|
1888
|
+
}
|
|
1889
|
+
if (positionalJson?.args)
|
|
1890
|
+
remaining.pop();
|
|
1891
|
+
if (params.subcommand) {
|
|
1892
|
+
if (args.action !== undefined) {
|
|
1893
|
+
return {
|
|
1894
|
+
error: {
|
|
1895
|
+
code: "invalid-input",
|
|
1896
|
+
message: `Do not combine ${params.domain} ${params.subcommand} with --action.`
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
args.action = params.subcommand;
|
|
1901
|
+
}
|
|
1902
|
+
const required = new Set(params.schema?.required ?? []);
|
|
1903
|
+
for (const key of Object.keys(props)) {
|
|
1904
|
+
if (remaining.length === 0)
|
|
1905
|
+
break;
|
|
1906
|
+
if (!required.has(key))
|
|
1907
|
+
continue;
|
|
1908
|
+
if (key in args)
|
|
1909
|
+
continue;
|
|
1910
|
+
args[key] = remaining.shift();
|
|
1911
|
+
}
|
|
1912
|
+
if (remaining.length > 0 && shouldRejectRemainingArgs(params.domain, params.command)) {
|
|
1913
|
+
const commandLabel = params.subcommand ? `${params.domain} ${params.subcommand}` : params.domain === params.command ? params.domain : `${params.domain} ${params.command}`;
|
|
1914
|
+
return {
|
|
1915
|
+
error: {
|
|
1916
|
+
code: "invalid-input",
|
|
1917
|
+
message: `Unexpected arguments for ${commandLabel}: ${remaining.join(" ")}`
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
return { args: coerceArgsToSchema(args, params.schema) };
|
|
1922
|
+
}
|
|
1581
1923
|
function resolveCommand(positional, knownCommands) {
|
|
1582
1924
|
if (positional.length === 0)
|
|
1583
1925
|
return null;
|
|
@@ -1587,16 +1929,32 @@ function resolveCommand(positional, knownCommands) {
|
|
|
1587
1929
|
domain = `admin:${positional[1]}`;
|
|
1588
1930
|
consumed = 2;
|
|
1589
1931
|
}
|
|
1590
|
-
const remaining = positional.slice(consumed);
|
|
1591
|
-
if (remaining.length === 0)
|
|
1592
|
-
return { domain, command: "", remaining: [] };
|
|
1593
1932
|
const domainCommands = knownCommands.filter((c) => c.domain === domain);
|
|
1933
|
+
const remaining = positional.slice(consumed);
|
|
1934
|
+
if (remaining.length === 0) {
|
|
1935
|
+
const selfCommand2 = domainCommands.find((candidate) => candidate.command === domain);
|
|
1936
|
+
return {
|
|
1937
|
+
domain,
|
|
1938
|
+
command: selfCommand2?.cliActionSubcommands?.length ? "" : selfCommand2?.command ?? "",
|
|
1939
|
+
remaining: []
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1594
1942
|
for (let len = Math.min(remaining.length, 5);len >= 1; len--) {
|
|
1595
1943
|
const candidate = remaining.slice(0, len).join(" ");
|
|
1596
1944
|
if (domainCommands.some((c) => c.command === candidate)) {
|
|
1597
1945
|
return { domain, command: candidate, remaining: remaining.slice(len) };
|
|
1598
1946
|
}
|
|
1599
1947
|
}
|
|
1948
|
+
const selfCommand = domainCommands.find((candidate) => candidate.command === domain);
|
|
1949
|
+
const subcommand = remaining[0];
|
|
1950
|
+
if (selfCommand?.cliActionSubcommands?.includes(subcommand)) {
|
|
1951
|
+
return {
|
|
1952
|
+
domain,
|
|
1953
|
+
command: selfCommand.command,
|
|
1954
|
+
subcommand,
|
|
1955
|
+
remaining: remaining.slice(1)
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1600
1958
|
return { domain, command: remaining[0], remaining: remaining.slice(1) };
|
|
1601
1959
|
}
|
|
1602
1960
|
|
|
@@ -1623,11 +1981,17 @@ async function callWithRefresh(client, call, deps = defaultDeps) {
|
|
|
1623
1981
|
const creds = await deps.readCredentials();
|
|
1624
1982
|
if (!creds)
|
|
1625
1983
|
return first;
|
|
1626
|
-
const
|
|
1984
|
+
const activeCredentials = credentialsForBaseUrl(creds, client.baseUrl);
|
|
1985
|
+
if (!activeCredentials)
|
|
1986
|
+
return first;
|
|
1987
|
+
const refreshed = await deps.refreshToken(activeCredentials);
|
|
1627
1988
|
if (!refreshed)
|
|
1628
1989
|
return first;
|
|
1629
|
-
|
|
1630
|
-
|
|
1990
|
+
const updatedCredentials = credentialsForBaseUrl(refreshed, client.baseUrl);
|
|
1991
|
+
if (!updatedCredentials)
|
|
1992
|
+
return first;
|
|
1993
|
+
await deps.writeCredentials(updatedCredentials);
|
|
1994
|
+
client.updateCredentials(updatedCredentials);
|
|
1631
1995
|
const retried = await call(client);
|
|
1632
1996
|
if (retried.status === 426) {
|
|
1633
1997
|
deps.onUpgradeRequired(extractUpgradeMessage(retried.body));
|
|
@@ -1643,8 +2007,7 @@ function extractUpgradeMessage(body) {
|
|
|
1643
2007
|
var LOADOUT_CAPABILITIES_SEARCH_OPERATION = "loadout_capabilities_search";
|
|
1644
2008
|
var LOADOUT_CAPABILITIES_EXECUTE_OPERATION = "loadout_capabilities_execute";
|
|
1645
2009
|
var LOADOUT_CAPABILITIES_FEEDBACK_OPERATION = "loadout_capabilities_feedback";
|
|
1646
|
-
var
|
|
1647
|
-
var LOADOUT_VAULT_CONNECT_OPERATION = "loadout_vault_connect";
|
|
2010
|
+
var LOADOUT_VAULT_OPERATION = "loadout_vault";
|
|
1648
2011
|
var LOADOUT_LOCAL_MIGRATION_EVENT_OPERATION = "loadout_vault_local_migration_event";
|
|
1649
2012
|
var LOCAL_MIGRATION_SEARCH_BATCH_SIZE = 10;
|
|
1650
2013
|
var LOCAL_MIGRATION_SEARCH_RESULT_MAX_DEPTH = 4;
|
|
@@ -1675,7 +2038,7 @@ async function main() {
|
|
|
1675
2038
|
await runLogin(parsed);
|
|
1676
2039
|
return;
|
|
1677
2040
|
case "logout":
|
|
1678
|
-
await runLogout();
|
|
2041
|
+
await runLogout(parsed);
|
|
1679
2042
|
return;
|
|
1680
2043
|
case "whoami":
|
|
1681
2044
|
await runWhoami(parsed);
|
|
@@ -1739,14 +2102,24 @@ async function runLogin(parsed) {
|
|
|
1739
2102
|
logInfo(`${colors.green}Signed in to ${creds.base_url}${colors.reset}`);
|
|
1740
2103
|
await setConfigValue("baseUrl", creds.base_url);
|
|
1741
2104
|
}
|
|
1742
|
-
async function runLogout() {
|
|
2105
|
+
async function runLogout(parsed) {
|
|
2106
|
+
if (parsed.isHelp) {
|
|
2107
|
+
logInfo(renderLogoutHelp());
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
const envToken = process.env.AIDENT_TOKEN;
|
|
1743
2111
|
const creds = await readCredentials();
|
|
1744
2112
|
if (!creds) {
|
|
1745
|
-
|
|
2113
|
+
await clearCredentials();
|
|
2114
|
+
logInfo(envToken ? "AIDENT_TOKEN is set. Unset it in your shell to sign out." : "Not signed in.");
|
|
1746
2115
|
return;
|
|
1747
2116
|
}
|
|
1748
2117
|
await logout(creds);
|
|
1749
2118
|
await clearCredentials();
|
|
2119
|
+
if (envToken) {
|
|
2120
|
+
logInfo(`${colors.green}Stored credentials removed.${colors.reset} AIDENT_TOKEN is still set; unset it in your shell to sign out.`);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
1750
2123
|
logInfo(`${colors.green}Signed out.${colors.reset}`);
|
|
1751
2124
|
}
|
|
1752
2125
|
async function runWhoami(parsed) {
|
|
@@ -1841,9 +2214,9 @@ async function runConfigCmd(parsed) {
|
|
|
1841
2214
|
const value = normalizeBaseUrl(rawValue);
|
|
1842
2215
|
await setConfigValue(key, value);
|
|
1843
2216
|
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
1846
|
-
logInfo(`${colors.yellow}
|
|
2217
|
+
const clearedCredentialsBaseUrl = await clearCredentialsForIncompatibleBaseUrl(value);
|
|
2218
|
+
if (clearedCredentialsBaseUrl) {
|
|
2219
|
+
logInfo(`${colors.yellow}Cleared credentials for ${clearedCredentialsBaseUrl}.${colors.reset} Run \`aident login\` to authenticate against ${value}.`);
|
|
1847
2220
|
}
|
|
1848
2221
|
} else {
|
|
1849
2222
|
const value = normalizeCliPackages(rawValue);
|
|
@@ -1861,6 +2234,13 @@ async function runConfigCmd(parsed) {
|
|
|
1861
2234
|
}
|
|
1862
2235
|
await unsetConfigValue(key);
|
|
1863
2236
|
logInfo(`${colors.green}Unset${colors.reset} ${key}`);
|
|
2237
|
+
if (key === "baseUrl") {
|
|
2238
|
+
const baseUrl = await resolveDefaultBaseUrl();
|
|
2239
|
+
const clearedCredentialsBaseUrl = await clearCredentialsForIncompatibleBaseUrl(baseUrl);
|
|
2240
|
+
if (clearedCredentialsBaseUrl) {
|
|
2241
|
+
logInfo(`${colors.yellow}Cleared credentials for ${clearedCredentialsBaseUrl}.${colors.reset} Run \`aident login\` to authenticate against ${baseUrl}.`);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
1864
2244
|
return;
|
|
1865
2245
|
}
|
|
1866
2246
|
logErr(`Unknown config subcommand: ${sub}`);
|
|
@@ -1927,7 +2307,7 @@ async function runDoctorCmd(format) {
|
|
|
1927
2307
|
logInfo(`${colors.bold}Aident CLI doctor${colors.reset}`);
|
|
1928
2308
|
logInfo("");
|
|
1929
2309
|
for (const c of report.checks) {
|
|
1930
|
-
const mark = c.ok ? `${colors.
|
|
2310
|
+
const mark = !c.ok ? `${colors.red}x${colors.reset}` : c.warning ? `${colors.yellow}warning${colors.reset}` : `${colors.green}ok${colors.reset}`;
|
|
1931
2311
|
logInfo(` ${mark} ${c.name.padEnd(22)} ${colors.dim}${c.detail}${colors.reset}`);
|
|
1932
2312
|
}
|
|
1933
2313
|
logInfo("");
|
|
@@ -1949,7 +2329,7 @@ async function runSetup(parsed) {
|
|
|
1949
2329
|
}
|
|
1950
2330
|
logInfo("");
|
|
1951
2331
|
const existing = await readCredentials();
|
|
1952
|
-
if (existing && existing
|
|
2332
|
+
if (existing && credentialsForBaseUrl(existing, chosen)) {
|
|
1953
2333
|
logInfo(`${colors.green}Already signed in${colors.reset} to ${chosen}.`);
|
|
1954
2334
|
} else {
|
|
1955
2335
|
const proceed = (await readLine(`Open browser to authenticate now? [Y/n]: `)).trim().toLowerCase();
|
|
@@ -2113,7 +2493,8 @@ async function genFetchLoadoutIntegrations(client, queries) {
|
|
|
2113
2493
|
async function genEnrichLoadoutIntegrationsWithVaultStatus(client, integrations) {
|
|
2114
2494
|
if (integrations.length === 0)
|
|
2115
2495
|
return integrations;
|
|
2116
|
-
const result = await callWithRefresh(client, (c) => c.execOperation("loadout",
|
|
2496
|
+
const result = await callWithRefresh(client, (c) => c.execOperation("loadout", LOADOUT_VAULT_OPERATION, {
|
|
2497
|
+
action: "status",
|
|
2117
2498
|
integrationIds: integrations.map((integration) => integration.id)
|
|
2118
2499
|
}));
|
|
2119
2500
|
if (!result.body.success)
|
|
@@ -2137,7 +2518,7 @@ async function genApplyLocalIntegrationMigration(client, plan, selectedIntegrati
|
|
|
2137
2518
|
connectResults.push({ integrationId, skipped: "not-connectable" });
|
|
2138
2519
|
continue;
|
|
2139
2520
|
}
|
|
2140
|
-
const result = await callWithRefresh(client, (c) => c.execOperation("loadout",
|
|
2521
|
+
const result = await callWithRefresh(client, (c) => c.execOperation("loadout", LOADOUT_VAULT_OPERATION, { action: "connect", integrationId }));
|
|
2141
2522
|
const entry = { integrationId, result: result.body };
|
|
2142
2523
|
if (result.body.success) {
|
|
2143
2524
|
entry.validation = await genValidateLocalIntegrationConnection(client, integrationId);
|
|
@@ -2162,7 +2543,7 @@ async function tryRecordLocalMigrationEvent(client, action, properties = {}) {
|
|
|
2162
2543
|
}
|
|
2163
2544
|
}
|
|
2164
2545
|
async function genValidateLocalIntegrationConnection(client, integrationId) {
|
|
2165
|
-
const result = await callWithRefresh(client, (c) => c.execOperation("loadout",
|
|
2546
|
+
const result = await callWithRefresh(client, (c) => c.execOperation("loadout", LOADOUT_VAULT_OPERATION, { action: "status", integrationId }));
|
|
2166
2547
|
if (!result.body.success) {
|
|
2167
2548
|
return {
|
|
2168
2549
|
success: false,
|
|
@@ -2315,7 +2696,7 @@ async function trySubmitLocalMigrationFeedback(client, integrationId, comment) {
|
|
|
2315
2696
|
}
|
|
2316
2697
|
function formatLocalMigrationValidationFeedback(entry) {
|
|
2317
2698
|
const validation = entry.validation;
|
|
2318
|
-
const validationMethod = validation?.providerCapabilityName ? "read_only_capability" : "
|
|
2699
|
+
const validationMethod = validation?.providerCapabilityName ? "read_only_capability" : "vault";
|
|
2319
2700
|
const parts = [
|
|
2320
2701
|
"Local integration migration validation failed.",
|
|
2321
2702
|
`integrationId=${entry.integrationId}`,
|
|
@@ -2585,29 +2966,28 @@ async function runCommand(parsed) {
|
|
|
2585
2966
|
return;
|
|
2586
2967
|
}
|
|
2587
2968
|
if (parsed.isHelp) {
|
|
2588
|
-
logInfo(renderCommandHelp(catalog, resolved.domain, resolved.command));
|
|
2969
|
+
logInfo(renderCommandHelp(catalog, resolved.domain, resolved.command, resolved.subcommand));
|
|
2589
2970
|
return;
|
|
2590
2971
|
}
|
|
2591
|
-
let args = { ...parsed.flags };
|
|
2592
|
-
for (const reserved of RESERVED_CLI_FLAGS) {
|
|
2593
|
-
delete args[reserved];
|
|
2594
|
-
}
|
|
2595
2972
|
const schema = cmdInfo.inputSchema;
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
if (resolved.remaining.length === 0)
|
|
2601
|
-
break;
|
|
2602
|
-
if (!required.has(key))
|
|
2603
|
-
continue;
|
|
2604
|
-
if (key in args)
|
|
2605
|
-
continue;
|
|
2606
|
-
args[key] = resolved.remaining.shift();
|
|
2607
|
-
}
|
|
2973
|
+
const fileFlags = await applyFileValueFlags(parsed.flags, schema, (path) => readFile6(path, "utf8"));
|
|
2974
|
+
if (fileFlags.error) {
|
|
2975
|
+
emitLocalCommandResult(parsed.format, false, undefined, fileFlags.error.code, fileFlags.error.message);
|
|
2976
|
+
return;
|
|
2608
2977
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2978
|
+
const builtArgs = buildCommandArgs({
|
|
2979
|
+
flags: fileFlags.flags,
|
|
2980
|
+
subcommand: resolved.subcommand,
|
|
2981
|
+
remaining: resolved.remaining,
|
|
2982
|
+
schema,
|
|
2983
|
+
domain: resolved.domain,
|
|
2984
|
+
command: resolved.command
|
|
2985
|
+
});
|
|
2986
|
+
if (builtArgs.error) {
|
|
2987
|
+
emitLocalCommandResult(parsed.format, false, undefined, builtArgs.error.code, builtArgs.error.message);
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
const result = await callWithRefresh(client, (c) => c.exec(resolved.domain, resolved.command, builtArgs.args ?? {}));
|
|
2611
2991
|
emitResult(result, parsed.format, resolved);
|
|
2612
2992
|
}
|
|
2613
2993
|
function emitResult(result, format, resolved) {
|
|
@@ -2619,7 +2999,8 @@ function emitResult(result, format, resolved) {
|
|
|
2619
2999
|
return;
|
|
2620
3000
|
}
|
|
2621
3001
|
if (body.success) {
|
|
2622
|
-
|
|
3002
|
+
const commandLabel = resolved.subcommand ? `${resolved.domain} ${resolved.subcommand}` : resolved.domain === resolved.command ? resolved.domain : `${resolved.domain} ${resolved.command}`;
|
|
3003
|
+
logInfo(`${colors.green}ok${colors.reset} ${commandLabel}`);
|
|
2623
3004
|
if (body.data !== undefined)
|
|
2624
3005
|
logInfo(JSON.stringify(body.data, null, 2));
|
|
2625
3006
|
if (body.meta)
|
|
@@ -2650,9 +3031,9 @@ async function getAuthenticatedClient(packages) {
|
|
|
2650
3031
|
const envToken = process.env.AIDENT_TOKEN;
|
|
2651
3032
|
const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
|
|
2652
3033
|
if (envToken) {
|
|
2653
|
-
const
|
|
3034
|
+
const baseUrl2 = envBaseUrl || (await readCredentials())?.base_url || await resolveDefaultBaseUrl();
|
|
2654
3035
|
return new CliClient({
|
|
2655
|
-
base_url: normalizeBaseUrl(
|
|
3036
|
+
base_url: normalizeBaseUrl(baseUrl2),
|
|
2656
3037
|
client_id: "",
|
|
2657
3038
|
access_token: envToken
|
|
2658
3039
|
}, "env", activePackages, installedSkillVersion);
|
|
@@ -2660,9 +3041,13 @@ async function getAuthenticatedClient(packages) {
|
|
|
2660
3041
|
let creds = await readCredentials();
|
|
2661
3042
|
if (!creds)
|
|
2662
3043
|
return null;
|
|
2663
|
-
|
|
2664
|
-
|
|
3044
|
+
const baseUrl = normalizeBaseUrl(envBaseUrl || await resolveDefaultBaseUrl());
|
|
3045
|
+
const activeCredentials = credentialsForBaseUrl(creds, baseUrl);
|
|
3046
|
+
if (!activeCredentials) {
|
|
3047
|
+
logErr(`${colors.yellow}Warning:${colors.reset} ${baseUrl} does not match the host your token was issued for (${creds.base_url}). Run \`aident login --base-url ${baseUrl}\` to authenticate against the new host.`);
|
|
3048
|
+
return null;
|
|
2665
3049
|
}
|
|
3050
|
+
creds = activeCredentials;
|
|
2666
3051
|
if (isExpired(creds)) {
|
|
2667
3052
|
const refreshed = await refreshToken(creds);
|
|
2668
3053
|
if (refreshed) {
|
|
@@ -2685,7 +3070,7 @@ async function fetchCatalog(client, options = {}) {
|
|
|
2685
3070
|
}
|
|
2686
3071
|
return null;
|
|
2687
3072
|
}
|
|
2688
|
-
if (res.status !== 200 || !
|
|
3073
|
+
if (res.status !== 200 || !isCommandCatalog2(res.body)) {
|
|
2689
3074
|
logErr(`Failed to fetch command catalog (HTTP ${res.status}): ${JSON.stringify(res.body)}`);
|
|
2690
3075
|
process.exitCode = 1;
|
|
2691
3076
|
return null;
|
|
@@ -2710,7 +3095,7 @@ async function readConfiguredPackages() {
|
|
|
2710
3095
|
const config = await readConfig();
|
|
2711
3096
|
return normalizeCliPackages(config.packages);
|
|
2712
3097
|
}
|
|
2713
|
-
function
|
|
3098
|
+
function isCommandCatalog2(body) {
|
|
2714
3099
|
if (!body || typeof body !== "object")
|
|
2715
3100
|
return false;
|
|
2716
3101
|
const b = body;
|
package/package.json
CHANGED