@aident-ai/cli 0.1.2 → 0.1.3-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 +14 -3
- package/dist/cli.mjs +588 -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.0";
|
|
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,72 @@ function coerceArgsToSchema(args, schema) {
|
|
|
1578
1852
|
}
|
|
1579
1853
|
return out;
|
|
1580
1854
|
}
|
|
1855
|
+
function shouldRejectRemainingArgs(domain, command) {
|
|
1856
|
+
return domain === "audit" && command === "audit" || 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
|
+
for (const reserved of RESERVED_CLI_FLAGS) {
|
|
1884
|
+
delete args[reserved];
|
|
1885
|
+
}
|
|
1886
|
+
if (positionalJson?.args)
|
|
1887
|
+
remaining.pop();
|
|
1888
|
+
if (params.subcommand) {
|
|
1889
|
+
if (args.action !== undefined) {
|
|
1890
|
+
return {
|
|
1891
|
+
error: {
|
|
1892
|
+
code: "invalid-input",
|
|
1893
|
+
message: `Do not combine ${params.domain} ${params.subcommand} with --action.`
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
args.action = params.subcommand;
|
|
1898
|
+
}
|
|
1899
|
+
const required = new Set(params.schema?.required ?? []);
|
|
1900
|
+
const props = params.schema?.properties ?? {};
|
|
1901
|
+
for (const key of Object.keys(props)) {
|
|
1902
|
+
if (remaining.length === 0)
|
|
1903
|
+
break;
|
|
1904
|
+
if (!required.has(key))
|
|
1905
|
+
continue;
|
|
1906
|
+
if (key in args)
|
|
1907
|
+
continue;
|
|
1908
|
+
args[key] = remaining.shift();
|
|
1909
|
+
}
|
|
1910
|
+
if (remaining.length > 0 && shouldRejectRemainingArgs(params.domain, params.command)) {
|
|
1911
|
+
const commandLabel = params.subcommand ? `${params.domain} ${params.subcommand}` : params.domain === params.command ? params.domain : `${params.domain} ${params.command}`;
|
|
1912
|
+
return {
|
|
1913
|
+
error: {
|
|
1914
|
+
code: "invalid-input",
|
|
1915
|
+
message: `Unexpected arguments for ${commandLabel}: ${remaining.join(" ")}`
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
return { args: coerceArgsToSchema(args, params.schema) };
|
|
1920
|
+
}
|
|
1581
1921
|
function resolveCommand(positional, knownCommands) {
|
|
1582
1922
|
if (positional.length === 0)
|
|
1583
1923
|
return null;
|
|
@@ -1587,16 +1927,32 @@ function resolveCommand(positional, knownCommands) {
|
|
|
1587
1927
|
domain = `admin:${positional[1]}`;
|
|
1588
1928
|
consumed = 2;
|
|
1589
1929
|
}
|
|
1590
|
-
const remaining = positional.slice(consumed);
|
|
1591
|
-
if (remaining.length === 0)
|
|
1592
|
-
return { domain, command: "", remaining: [] };
|
|
1593
1930
|
const domainCommands = knownCommands.filter((c) => c.domain === domain);
|
|
1931
|
+
const remaining = positional.slice(consumed);
|
|
1932
|
+
if (remaining.length === 0) {
|
|
1933
|
+
const selfCommand2 = domainCommands.find((candidate) => candidate.command === domain);
|
|
1934
|
+
return {
|
|
1935
|
+
domain,
|
|
1936
|
+
command: selfCommand2?.cliActionSubcommands?.length ? "" : selfCommand2?.command ?? "",
|
|
1937
|
+
remaining: []
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1594
1940
|
for (let len = Math.min(remaining.length, 5);len >= 1; len--) {
|
|
1595
1941
|
const candidate = remaining.slice(0, len).join(" ");
|
|
1596
1942
|
if (domainCommands.some((c) => c.command === candidate)) {
|
|
1597
1943
|
return { domain, command: candidate, remaining: remaining.slice(len) };
|
|
1598
1944
|
}
|
|
1599
1945
|
}
|
|
1946
|
+
const selfCommand = domainCommands.find((candidate) => candidate.command === domain);
|
|
1947
|
+
const subcommand = remaining[0];
|
|
1948
|
+
if (selfCommand?.cliActionSubcommands?.includes(subcommand)) {
|
|
1949
|
+
return {
|
|
1950
|
+
domain,
|
|
1951
|
+
command: selfCommand.command,
|
|
1952
|
+
subcommand,
|
|
1953
|
+
remaining: remaining.slice(1)
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1600
1956
|
return { domain, command: remaining[0], remaining: remaining.slice(1) };
|
|
1601
1957
|
}
|
|
1602
1958
|
|
|
@@ -1623,11 +1979,17 @@ async function callWithRefresh(client, call, deps = defaultDeps) {
|
|
|
1623
1979
|
const creds = await deps.readCredentials();
|
|
1624
1980
|
if (!creds)
|
|
1625
1981
|
return first;
|
|
1626
|
-
const
|
|
1982
|
+
const activeCredentials = credentialsForBaseUrl(creds, client.baseUrl);
|
|
1983
|
+
if (!activeCredentials)
|
|
1984
|
+
return first;
|
|
1985
|
+
const refreshed = await deps.refreshToken(activeCredentials);
|
|
1627
1986
|
if (!refreshed)
|
|
1628
1987
|
return first;
|
|
1629
|
-
|
|
1630
|
-
|
|
1988
|
+
const updatedCredentials = credentialsForBaseUrl(refreshed, client.baseUrl);
|
|
1989
|
+
if (!updatedCredentials)
|
|
1990
|
+
return first;
|
|
1991
|
+
await deps.writeCredentials(updatedCredentials);
|
|
1992
|
+
client.updateCredentials(updatedCredentials);
|
|
1631
1993
|
const retried = await call(client);
|
|
1632
1994
|
if (retried.status === 426) {
|
|
1633
1995
|
deps.onUpgradeRequired(extractUpgradeMessage(retried.body));
|
|
@@ -1643,8 +2005,7 @@ function extractUpgradeMessage(body) {
|
|
|
1643
2005
|
var LOADOUT_CAPABILITIES_SEARCH_OPERATION = "loadout_capabilities_search";
|
|
1644
2006
|
var LOADOUT_CAPABILITIES_EXECUTE_OPERATION = "loadout_capabilities_execute";
|
|
1645
2007
|
var LOADOUT_CAPABILITIES_FEEDBACK_OPERATION = "loadout_capabilities_feedback";
|
|
1646
|
-
var
|
|
1647
|
-
var LOADOUT_VAULT_CONNECT_OPERATION = "loadout_vault_connect";
|
|
2008
|
+
var LOADOUT_VAULT_OPERATION = "loadout_vault";
|
|
1648
2009
|
var LOADOUT_LOCAL_MIGRATION_EVENT_OPERATION = "loadout_vault_local_migration_event";
|
|
1649
2010
|
var LOCAL_MIGRATION_SEARCH_BATCH_SIZE = 10;
|
|
1650
2011
|
var LOCAL_MIGRATION_SEARCH_RESULT_MAX_DEPTH = 4;
|
|
@@ -1675,7 +2036,7 @@ async function main() {
|
|
|
1675
2036
|
await runLogin(parsed);
|
|
1676
2037
|
return;
|
|
1677
2038
|
case "logout":
|
|
1678
|
-
await runLogout();
|
|
2039
|
+
await runLogout(parsed);
|
|
1679
2040
|
return;
|
|
1680
2041
|
case "whoami":
|
|
1681
2042
|
await runWhoami(parsed);
|
|
@@ -1739,14 +2100,24 @@ async function runLogin(parsed) {
|
|
|
1739
2100
|
logInfo(`${colors.green}Signed in to ${creds.base_url}${colors.reset}`);
|
|
1740
2101
|
await setConfigValue("baseUrl", creds.base_url);
|
|
1741
2102
|
}
|
|
1742
|
-
async function runLogout() {
|
|
2103
|
+
async function runLogout(parsed) {
|
|
2104
|
+
if (parsed.isHelp) {
|
|
2105
|
+
logInfo(renderLogoutHelp());
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
const envToken = process.env.AIDENT_TOKEN;
|
|
1743
2109
|
const creds = await readCredentials();
|
|
1744
2110
|
if (!creds) {
|
|
1745
|
-
|
|
2111
|
+
await clearCredentials();
|
|
2112
|
+
logInfo(envToken ? "AIDENT_TOKEN is set. Unset it in your shell to sign out." : "Not signed in.");
|
|
1746
2113
|
return;
|
|
1747
2114
|
}
|
|
1748
2115
|
await logout(creds);
|
|
1749
2116
|
await clearCredentials();
|
|
2117
|
+
if (envToken) {
|
|
2118
|
+
logInfo(`${colors.green}Stored credentials removed.${colors.reset} AIDENT_TOKEN is still set; unset it in your shell to sign out.`);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
1750
2121
|
logInfo(`${colors.green}Signed out.${colors.reset}`);
|
|
1751
2122
|
}
|
|
1752
2123
|
async function runWhoami(parsed) {
|
|
@@ -1841,9 +2212,9 @@ async function runConfigCmd(parsed) {
|
|
|
1841
2212
|
const value = normalizeBaseUrl(rawValue);
|
|
1842
2213
|
await setConfigValue(key, value);
|
|
1843
2214
|
logInfo(`${colors.green}Set${colors.reset} ${key} = ${value}`);
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
1846
|
-
logInfo(`${colors.yellow}
|
|
2215
|
+
const clearedCredentialsBaseUrl = await clearCredentialsForIncompatibleBaseUrl(value);
|
|
2216
|
+
if (clearedCredentialsBaseUrl) {
|
|
2217
|
+
logInfo(`${colors.yellow}Cleared credentials for ${clearedCredentialsBaseUrl}.${colors.reset} Run \`aident login\` to authenticate against ${value}.`);
|
|
1847
2218
|
}
|
|
1848
2219
|
} else {
|
|
1849
2220
|
const value = normalizeCliPackages(rawValue);
|
|
@@ -1861,6 +2232,13 @@ async function runConfigCmd(parsed) {
|
|
|
1861
2232
|
}
|
|
1862
2233
|
await unsetConfigValue(key);
|
|
1863
2234
|
logInfo(`${colors.green}Unset${colors.reset} ${key}`);
|
|
2235
|
+
if (key === "baseUrl") {
|
|
2236
|
+
const baseUrl = await resolveDefaultBaseUrl();
|
|
2237
|
+
const clearedCredentialsBaseUrl = await clearCredentialsForIncompatibleBaseUrl(baseUrl);
|
|
2238
|
+
if (clearedCredentialsBaseUrl) {
|
|
2239
|
+
logInfo(`${colors.yellow}Cleared credentials for ${clearedCredentialsBaseUrl}.${colors.reset} Run \`aident login\` to authenticate against ${baseUrl}.`);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
1864
2242
|
return;
|
|
1865
2243
|
}
|
|
1866
2244
|
logErr(`Unknown config subcommand: ${sub}`);
|
|
@@ -1927,7 +2305,7 @@ async function runDoctorCmd(format) {
|
|
|
1927
2305
|
logInfo(`${colors.bold}Aident CLI doctor${colors.reset}`);
|
|
1928
2306
|
logInfo("");
|
|
1929
2307
|
for (const c of report.checks) {
|
|
1930
|
-
const mark = c.ok ? `${colors.
|
|
2308
|
+
const mark = !c.ok ? `${colors.red}x${colors.reset}` : c.warning ? `${colors.yellow}warning${colors.reset}` : `${colors.green}ok${colors.reset}`;
|
|
1931
2309
|
logInfo(` ${mark} ${c.name.padEnd(22)} ${colors.dim}${c.detail}${colors.reset}`);
|
|
1932
2310
|
}
|
|
1933
2311
|
logInfo("");
|
|
@@ -1949,7 +2327,7 @@ async function runSetup(parsed) {
|
|
|
1949
2327
|
}
|
|
1950
2328
|
logInfo("");
|
|
1951
2329
|
const existing = await readCredentials();
|
|
1952
|
-
if (existing && existing
|
|
2330
|
+
if (existing && credentialsForBaseUrl(existing, chosen)) {
|
|
1953
2331
|
logInfo(`${colors.green}Already signed in${colors.reset} to ${chosen}.`);
|
|
1954
2332
|
} else {
|
|
1955
2333
|
const proceed = (await readLine(`Open browser to authenticate now? [Y/n]: `)).trim().toLowerCase();
|
|
@@ -2113,7 +2491,8 @@ async function genFetchLoadoutIntegrations(client, queries) {
|
|
|
2113
2491
|
async function genEnrichLoadoutIntegrationsWithVaultStatus(client, integrations) {
|
|
2114
2492
|
if (integrations.length === 0)
|
|
2115
2493
|
return integrations;
|
|
2116
|
-
const result = await callWithRefresh(client, (c) => c.execOperation("loadout",
|
|
2494
|
+
const result = await callWithRefresh(client, (c) => c.execOperation("loadout", LOADOUT_VAULT_OPERATION, {
|
|
2495
|
+
action: "status",
|
|
2117
2496
|
integrationIds: integrations.map((integration) => integration.id)
|
|
2118
2497
|
}));
|
|
2119
2498
|
if (!result.body.success)
|
|
@@ -2137,7 +2516,7 @@ async function genApplyLocalIntegrationMigration(client, plan, selectedIntegrati
|
|
|
2137
2516
|
connectResults.push({ integrationId, skipped: "not-connectable" });
|
|
2138
2517
|
continue;
|
|
2139
2518
|
}
|
|
2140
|
-
const result = await callWithRefresh(client, (c) => c.execOperation("loadout",
|
|
2519
|
+
const result = await callWithRefresh(client, (c) => c.execOperation("loadout", LOADOUT_VAULT_OPERATION, { action: "connect", integrationId }));
|
|
2141
2520
|
const entry = { integrationId, result: result.body };
|
|
2142
2521
|
if (result.body.success) {
|
|
2143
2522
|
entry.validation = await genValidateLocalIntegrationConnection(client, integrationId);
|
|
@@ -2162,7 +2541,7 @@ async function tryRecordLocalMigrationEvent(client, action, properties = {}) {
|
|
|
2162
2541
|
}
|
|
2163
2542
|
}
|
|
2164
2543
|
async function genValidateLocalIntegrationConnection(client, integrationId) {
|
|
2165
|
-
const result = await callWithRefresh(client, (c) => c.execOperation("loadout",
|
|
2544
|
+
const result = await callWithRefresh(client, (c) => c.execOperation("loadout", LOADOUT_VAULT_OPERATION, { action: "status", integrationId }));
|
|
2166
2545
|
if (!result.body.success) {
|
|
2167
2546
|
return {
|
|
2168
2547
|
success: false,
|
|
@@ -2315,7 +2694,7 @@ async function trySubmitLocalMigrationFeedback(client, integrationId, comment) {
|
|
|
2315
2694
|
}
|
|
2316
2695
|
function formatLocalMigrationValidationFeedback(entry) {
|
|
2317
2696
|
const validation = entry.validation;
|
|
2318
|
-
const validationMethod = validation?.providerCapabilityName ? "read_only_capability" : "
|
|
2697
|
+
const validationMethod = validation?.providerCapabilityName ? "read_only_capability" : "vault";
|
|
2319
2698
|
const parts = [
|
|
2320
2699
|
"Local integration migration validation failed.",
|
|
2321
2700
|
`integrationId=${entry.integrationId}`,
|
|
@@ -2585,29 +2964,28 @@ async function runCommand(parsed) {
|
|
|
2585
2964
|
return;
|
|
2586
2965
|
}
|
|
2587
2966
|
if (parsed.isHelp) {
|
|
2588
|
-
logInfo(renderCommandHelp(catalog, resolved.domain, resolved.command));
|
|
2967
|
+
logInfo(renderCommandHelp(catalog, resolved.domain, resolved.command, resolved.subcommand));
|
|
2589
2968
|
return;
|
|
2590
2969
|
}
|
|
2591
|
-
let args = { ...parsed.flags };
|
|
2592
|
-
for (const reserved of RESERVED_CLI_FLAGS) {
|
|
2593
|
-
delete args[reserved];
|
|
2594
|
-
}
|
|
2595
2970
|
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
|
-
}
|
|
2971
|
+
const fileFlags = await applyFileValueFlags(parsed.flags, schema, (path) => readFile6(path, "utf8"));
|
|
2972
|
+
if (fileFlags.error) {
|
|
2973
|
+
emitLocalCommandResult(parsed.format, false, undefined, fileFlags.error.code, fileFlags.error.message);
|
|
2974
|
+
return;
|
|
2608
2975
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2976
|
+
const builtArgs = buildCommandArgs({
|
|
2977
|
+
flags: fileFlags.flags,
|
|
2978
|
+
subcommand: resolved.subcommand,
|
|
2979
|
+
remaining: resolved.remaining,
|
|
2980
|
+
schema,
|
|
2981
|
+
domain: resolved.domain,
|
|
2982
|
+
command: resolved.command
|
|
2983
|
+
});
|
|
2984
|
+
if (builtArgs.error) {
|
|
2985
|
+
emitLocalCommandResult(parsed.format, false, undefined, builtArgs.error.code, builtArgs.error.message);
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
const result = await callWithRefresh(client, (c) => c.exec(resolved.domain, resolved.command, builtArgs.args ?? {}));
|
|
2611
2989
|
emitResult(result, parsed.format, resolved);
|
|
2612
2990
|
}
|
|
2613
2991
|
function emitResult(result, format, resolved) {
|
|
@@ -2619,7 +2997,8 @@ function emitResult(result, format, resolved) {
|
|
|
2619
2997
|
return;
|
|
2620
2998
|
}
|
|
2621
2999
|
if (body.success) {
|
|
2622
|
-
|
|
3000
|
+
const commandLabel = resolved.subcommand ? `${resolved.domain} ${resolved.subcommand}` : resolved.domain === resolved.command ? resolved.domain : `${resolved.domain} ${resolved.command}`;
|
|
3001
|
+
logInfo(`${colors.green}ok${colors.reset} ${commandLabel}`);
|
|
2623
3002
|
if (body.data !== undefined)
|
|
2624
3003
|
logInfo(JSON.stringify(body.data, null, 2));
|
|
2625
3004
|
if (body.meta)
|
|
@@ -2650,9 +3029,9 @@ async function getAuthenticatedClient(packages) {
|
|
|
2650
3029
|
const envToken = process.env.AIDENT_TOKEN;
|
|
2651
3030
|
const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
|
|
2652
3031
|
if (envToken) {
|
|
2653
|
-
const
|
|
3032
|
+
const baseUrl2 = envBaseUrl || (await readCredentials())?.base_url || await resolveDefaultBaseUrl();
|
|
2654
3033
|
return new CliClient({
|
|
2655
|
-
base_url: normalizeBaseUrl(
|
|
3034
|
+
base_url: normalizeBaseUrl(baseUrl2),
|
|
2656
3035
|
client_id: "",
|
|
2657
3036
|
access_token: envToken
|
|
2658
3037
|
}, "env", activePackages, installedSkillVersion);
|
|
@@ -2660,9 +3039,13 @@ async function getAuthenticatedClient(packages) {
|
|
|
2660
3039
|
let creds = await readCredentials();
|
|
2661
3040
|
if (!creds)
|
|
2662
3041
|
return null;
|
|
2663
|
-
|
|
2664
|
-
|
|
3042
|
+
const baseUrl = normalizeBaseUrl(envBaseUrl || await resolveDefaultBaseUrl());
|
|
3043
|
+
const activeCredentials = credentialsForBaseUrl(creds, baseUrl);
|
|
3044
|
+
if (!activeCredentials) {
|
|
3045
|
+
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.`);
|
|
3046
|
+
return null;
|
|
2665
3047
|
}
|
|
3048
|
+
creds = activeCredentials;
|
|
2666
3049
|
if (isExpired(creds)) {
|
|
2667
3050
|
const refreshed = await refreshToken(creds);
|
|
2668
3051
|
if (refreshed) {
|
|
@@ -2685,7 +3068,7 @@ async function fetchCatalog(client, options = {}) {
|
|
|
2685
3068
|
}
|
|
2686
3069
|
return null;
|
|
2687
3070
|
}
|
|
2688
|
-
if (res.status !== 200 || !
|
|
3071
|
+
if (res.status !== 200 || !isCommandCatalog2(res.body)) {
|
|
2689
3072
|
logErr(`Failed to fetch command catalog (HTTP ${res.status}): ${JSON.stringify(res.body)}`);
|
|
2690
3073
|
process.exitCode = 1;
|
|
2691
3074
|
return null;
|
|
@@ -2710,7 +3093,7 @@ async function readConfiguredPackages() {
|
|
|
2710
3093
|
const config = await readConfig();
|
|
2711
3094
|
return normalizeCliPackages(config.packages);
|
|
2712
3095
|
}
|
|
2713
|
-
function
|
|
3096
|
+
function isCommandCatalog2(body) {
|
|
2714
3097
|
if (!body || typeof body !== "object")
|
|
2715
3098
|
return false;
|
|
2716
3099
|
const b = body;
|
package/package.json
CHANGED