@corvio/admin-cli 0.1.0-beta.7 → 0.1.0-beta.8
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/dist/cli.js +1 -1
- package/dist/core.js +147 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
writeResult,
|
|
25
25
|
} from "./core.js";
|
|
26
26
|
|
|
27
|
-
const VERSION = "0.1.0-beta.
|
|
27
|
+
const VERSION = "0.1.0-beta.8";
|
|
28
28
|
const DEFAULT_API_BASE = "https://api.corvio.ai/v1";
|
|
29
29
|
const USER_AGENT = `corvio-admin-cli/${VERSION}`;
|
|
30
30
|
const GLOBAL_OPTIONS = Object.freeze(["api-base", "json", "no-input", "help", "version"]);
|
package/dist/core.js
CHANGED
|
@@ -97,6 +97,8 @@ export function parseArgv(argv) {
|
|
|
97
97
|
"force",
|
|
98
98
|
"once",
|
|
99
99
|
"include-revoked",
|
|
100
|
+
"recover-credential",
|
|
101
|
+
"no-listen",
|
|
100
102
|
"start-new-chain",
|
|
101
103
|
"help",
|
|
102
104
|
"version",
|
|
@@ -556,6 +558,14 @@ export function adminCredentialPath(env = process.env) {
|
|
|
556
558
|
return join(configRoot(env), "admin-credentials.json");
|
|
557
559
|
}
|
|
558
560
|
|
|
561
|
+
export function agentCredentialPath(env = process.env) {
|
|
562
|
+
return join(configRoot(env), "agent-credentials.json");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function conversationRoutingPath(env = process.env) {
|
|
566
|
+
return join(configRoot(env), "conversation-routing.json");
|
|
567
|
+
}
|
|
568
|
+
|
|
559
569
|
export async function readWorkspaceCredential(env = process.env) {
|
|
560
570
|
const environmentKey = String(env.CORVIO_API_KEY || "").trim();
|
|
561
571
|
if (environmentKey) {
|
|
@@ -588,21 +598,72 @@ export async function readWorkspaceCredential(env = process.env) {
|
|
|
588
598
|
return { key, source: "device_link", profile: profileName, metadata };
|
|
589
599
|
}
|
|
590
600
|
|
|
591
|
-
export function readAgentCredential(env = process.env) {
|
|
601
|
+
export async function readAgentCredential(env = process.env, { required = true } = {}) {
|
|
592
602
|
const key = String(env.CORVIO_AGENT_API_KEY || "").trim();
|
|
593
|
-
if (
|
|
603
|
+
if (key) {
|
|
604
|
+
if (!key.startsWith("cvg_")) {
|
|
605
|
+
throw new CliError("CORVIO_AGENT_API_KEY must be an Agent cvg_ key.", {
|
|
606
|
+
code: "invalid_agent_key",
|
|
607
|
+
exitCode: EXIT.AUTH,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return { key, source: "environment", profile: null, metadata: null };
|
|
611
|
+
}
|
|
612
|
+
const path = agentCredentialPath(env);
|
|
613
|
+
let parsed;
|
|
614
|
+
try {
|
|
615
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
616
|
+
} catch (cause) {
|
|
617
|
+
if (cause?.code === "ENOENT" && !required) return null;
|
|
618
|
+
if (cause?.code === "ENOENT") {
|
|
619
|
+
throw new CliError(
|
|
620
|
+
"No Agent credential found. Run `corvio agents connect` or set CORVIO_AGENT_API_KEY.",
|
|
621
|
+
{ code: "agent_auth_required", exitCode: EXIT.AUTH },
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
throw new CliError("Stored Agent credentials are unreadable.", {
|
|
625
|
+
code: "agent_credential_store_invalid",
|
|
626
|
+
exitCode: EXIT.AUTH,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
const storedKey = String(parsed?.api_key || "").trim();
|
|
630
|
+
if (!storedKey.startsWith("cvg_")) {
|
|
594
631
|
throw new CliError(
|
|
595
|
-
"
|
|
596
|
-
{ code: "
|
|
632
|
+
"Stored Agent credentials are invalid; run `corvio agents connect --recover-credential`.",
|
|
633
|
+
{ code: "agent_credential_store_invalid", exitCode: EXIT.AUTH },
|
|
597
634
|
);
|
|
598
635
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
636
|
+
return { key: storedKey, source: "secure_store", profile: "default", metadata: parsed };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export async function storeAgentCredential(payload, env = process.env) {
|
|
640
|
+
const secret = String(payload?.secret || "").trim();
|
|
641
|
+
if (!secret.startsWith("cvg_")) {
|
|
642
|
+
throw new CliError("Agent connection did not return a valid cvg_ credential.", {
|
|
643
|
+
code: "agent_credential_invalid",
|
|
602
644
|
exitCode: EXIT.AUTH,
|
|
603
645
|
});
|
|
604
646
|
}
|
|
605
|
-
|
|
647
|
+
const path = agentCredentialPath(env);
|
|
648
|
+
const root = dirname(path);
|
|
649
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
650
|
+
await chmod(root, 0o700).catch(() => {});
|
|
651
|
+
const document = {
|
|
652
|
+
version: 1,
|
|
653
|
+
api_key: secret,
|
|
654
|
+
agent_id: String(payload.agent_id || ""),
|
|
655
|
+
display_name: String(payload.display_name || ""),
|
|
656
|
+
provider: String(payload.provider || "custom"),
|
|
657
|
+
display_prefix: String(payload.display_prefix || "cvg"),
|
|
658
|
+
created_at: payload.created_at || new Date().toISOString(),
|
|
659
|
+
source: "agent_connect",
|
|
660
|
+
};
|
|
661
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
662
|
+
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
663
|
+
await chmod(temporary, 0o600);
|
|
664
|
+
await rename(temporary, path);
|
|
665
|
+
await chmod(path, 0o600);
|
|
666
|
+
return { path, profile: "default", metadata: document };
|
|
606
667
|
}
|
|
607
668
|
|
|
608
669
|
export async function storeWorkspaceCredential(payload, env = process.env) {
|
|
@@ -642,6 +703,7 @@ export async function readWorkspaceContext(env = process.env) {
|
|
|
642
703
|
workspaceId,
|
|
643
704
|
workspaceName: String(parsed.active_workspace_name || "").trim() || null,
|
|
644
705
|
source: "stored",
|
|
706
|
+
routingPreference: parsed.routing_preference || null,
|
|
645
707
|
};
|
|
646
708
|
} catch (cause) {
|
|
647
709
|
if (cause?.code === "ENOENT") return null;
|
|
@@ -652,7 +714,7 @@ export async function readWorkspaceContext(env = process.env) {
|
|
|
652
714
|
}
|
|
653
715
|
}
|
|
654
716
|
|
|
655
|
-
export async function storeWorkspaceContext({ workspaceId, workspaceName = null }, env = process.env) {
|
|
717
|
+
export async function storeWorkspaceContext({ workspaceId, workspaceName = null, routingPreference = null }, env = process.env) {
|
|
656
718
|
const normalizedId = String(workspaceId || "").trim();
|
|
657
719
|
if (!normalizedId) throw new CliError("Workspace ID is required.", { code: "workspace_selection_required" });
|
|
658
720
|
const path = workspaceContextPath(env);
|
|
@@ -663,6 +725,13 @@ export async function storeWorkspaceContext({ workspaceId, workspaceName = null
|
|
|
663
725
|
version: 1,
|
|
664
726
|
active_workspace_id: normalizedId,
|
|
665
727
|
active_workspace_name: String(workspaceName || "").trim() || null,
|
|
728
|
+
routing_preference: routingPreference || {
|
|
729
|
+
workspace_id: normalizedId,
|
|
730
|
+
provenance: "user_confirmed_cli",
|
|
731
|
+
confidence: 1,
|
|
732
|
+
scope: "global",
|
|
733
|
+
expires_at: null,
|
|
734
|
+
},
|
|
666
735
|
updated_at: new Date().toISOString(),
|
|
667
736
|
};
|
|
668
737
|
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
@@ -673,6 +742,75 @@ export async function storeWorkspaceContext({ workspaceId, workspaceName = null
|
|
|
673
742
|
return { path, workspaceId: normalizedId };
|
|
674
743
|
}
|
|
675
744
|
|
|
745
|
+
export async function readConversationRoutingReceipt(conversationId, env = process.env) {
|
|
746
|
+
const normalizedId = String(conversationId || "").trim();
|
|
747
|
+
if (!normalizedId) return null;
|
|
748
|
+
const path = conversationRoutingPath(env);
|
|
749
|
+
try {
|
|
750
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
751
|
+
const receipt = parsed?.receipts?.[normalizedId];
|
|
752
|
+
const workspaceId = String(receipt?.workspace_id || "").trim();
|
|
753
|
+
if (!workspaceId) return null;
|
|
754
|
+
return {
|
|
755
|
+
conversationId: normalizedId,
|
|
756
|
+
workspaceId,
|
|
757
|
+
agentId: String(receipt.agent_id || "").trim() || null,
|
|
758
|
+
apiBase: String(receipt.api_base || "").trim() || null,
|
|
759
|
+
recordedAt: String(receipt.recorded_at || "").trim() || null,
|
|
760
|
+
};
|
|
761
|
+
} catch (cause) {
|
|
762
|
+
if (cause?.code === "ENOENT") return null;
|
|
763
|
+
throw new CliError("Stored Corvio conversation routing receipts are unreadable.", {
|
|
764
|
+
code: "conversation_routing_store_invalid",
|
|
765
|
+
exitCode: EXIT.AUTH,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export async function storeConversationRoutingReceipt({ conversationId, workspaceId, agentId = null, apiBase = null }, env = process.env) {
|
|
771
|
+
const normalizedConversationId = String(conversationId || "").trim();
|
|
772
|
+
const normalizedWorkspaceId = String(workspaceId || "").trim();
|
|
773
|
+
if (!normalizedConversationId || !normalizedWorkspaceId) {
|
|
774
|
+
throw new CliError("Conversation and Workspace IDs are required for routing continuity.", {
|
|
775
|
+
code: "conversation_routing_receipt_invalid",
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
const path = conversationRoutingPath(env);
|
|
779
|
+
const root = dirname(path);
|
|
780
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
781
|
+
await chmod(root, 0o700).catch(() => {});
|
|
782
|
+
let existing = {};
|
|
783
|
+
try {
|
|
784
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
785
|
+
if (parsed?.receipts && typeof parsed.receipts === "object" && !Array.isArray(parsed.receipts)) {
|
|
786
|
+
existing = parsed.receipts;
|
|
787
|
+
}
|
|
788
|
+
} catch (cause) {
|
|
789
|
+
if (cause?.code !== "ENOENT") {
|
|
790
|
+
throw new CliError("Stored Corvio conversation routing receipts are unreadable.", {
|
|
791
|
+
code: "conversation_routing_store_invalid",
|
|
792
|
+
exitCode: EXIT.AUTH,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const ordered = Object.entries({
|
|
797
|
+
...existing,
|
|
798
|
+
[normalizedConversationId]: {
|
|
799
|
+
workspace_id: normalizedWorkspaceId,
|
|
800
|
+
agent_id: String(agentId || "").trim() || null,
|
|
801
|
+
api_base: String(apiBase || "").trim() || null,
|
|
802
|
+
recorded_at: new Date().toISOString(),
|
|
803
|
+
},
|
|
804
|
+
}).sort((left, right) => String(right[1]?.recorded_at || "").localeCompare(String(left[1]?.recorded_at || "")));
|
|
805
|
+
const document = { version: 1, receipts: Object.fromEntries(ordered.slice(0, 500)) };
|
|
806
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
807
|
+
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
808
|
+
await chmod(temporary, 0o600);
|
|
809
|
+
await rename(temporary, path);
|
|
810
|
+
await chmod(path, 0o600);
|
|
811
|
+
return { path, conversationId: normalizedConversationId, workspaceId: normalizedWorkspaceId };
|
|
812
|
+
}
|
|
813
|
+
|
|
676
814
|
export async function clearWorkspaceContext(env = process.env) {
|
|
677
815
|
const path = workspaceContextPath(env);
|
|
678
816
|
await rm(path, { force: true });
|