@aliyunrds/ctxdb 1.0.8-beta.4 → 1.0.9-beta.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 +215 -38
- package/dist/{chunk-SVO6H62S.js → chunk-2U7G6UXE.js} +287 -217
- package/dist/{chunk-OEZM2ZYA.js → chunk-3MEBQDGU.js} +5 -0
- package/dist/{chunk-5DGRNP55.js → chunk-7TPYIT63.js} +1 -1
- package/dist/{chunk-6MEWI73Y.js → chunk-FD3IPF6R.js} +2 -2
- package/dist/{chunk-76AXXLVE.js → chunk-N5JGS5EC.js} +2 -2
- package/dist/{chunk-ZW7YXNJ6.js → chunk-T5ZJEPR4.js} +286 -11
- package/dist/{chunk-O6MKQ2I3.js → chunk-U4DRH4NZ.js} +1 -1
- package/dist/{chunk-WJTV7JNR.js → chunk-V7IZ2UYJ.js} +3 -3
- package/dist/cli/main.js +4161 -1925
- package/dist/hooks/hermes-post-llm-call.js +3 -3
- package/dist/hooks/hermes-pre-llm-call.js +6 -6
- package/dist/hooks/session-start.js +5 -5
- package/dist/hooks/stop.js +3 -3
- package/dist/hooks/user-prompt-submit.js +5 -5
- package/dist/opencode/index.js +713 -293
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +14 -3
- package/dist/setup/skills/contextdb-memory/SKILL.md +10 -3
- package/dist/workers/version-check.js +1 -1
- package/package.json +2 -2
|
@@ -4,16 +4,11 @@ import {
|
|
|
4
4
|
} from "./chunk-5ITT5GSP.js";
|
|
5
5
|
|
|
6
6
|
// src/lib/credentials.ts
|
|
7
|
+
import { selectDataApiCredential } from "@aliyunrds/ctxdb-shared";
|
|
7
8
|
function apiKeyCredential(apiKey) {
|
|
8
9
|
return { type: "api-key", value: apiKey };
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
const accessToken = env.CTXDB_ACCESS_TOKEN?.trim();
|
|
12
|
-
if (accessToken) return { type: "access-token", value: accessToken };
|
|
13
|
-
if (env.CTXDB_API_KEY) return apiKeyCredential(env.CTXDB_API_KEY);
|
|
14
|
-
if (cfg.oauthCredential) return { type: "oauth-session", profile: cfg.oauthCredential };
|
|
15
|
-
return cfg.apiKey ? apiKeyCredential(cfg.apiKey) : null;
|
|
16
|
-
}
|
|
11
|
+
var resolveDataApiCredential = selectDataApiCredential;
|
|
17
12
|
function isDataApiReady(cfg, env = process.env) {
|
|
18
13
|
return Boolean(cfg.baseUrl && resolveDataApiCredential(cfg, env));
|
|
19
14
|
}
|
|
@@ -84,7 +79,8 @@ var PACKAGE_NAME = PACKAGE_IDENTITY.name;
|
|
|
84
79
|
var PACKAGE_VERSION = PACKAGE_IDENTITY.version;
|
|
85
80
|
|
|
86
81
|
// src/lib/http-client.ts
|
|
87
|
-
import {
|
|
82
|
+
import { createRequestAuthorizationProvider } from "@aliyunrds/ctxdb-shared";
|
|
83
|
+
import "@aliyunrds/ctxdb-shared";
|
|
88
84
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
89
85
|
var CtxdbError = class extends Error {
|
|
90
86
|
constructor(message) {
|
|
@@ -178,7 +174,7 @@ var HttpClient = class {
|
|
|
178
174
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
179
175
|
this.credential = credential;
|
|
180
176
|
this.apiKey = opts.apiKey ?? "";
|
|
181
|
-
this.authorizationProvider = opts.authorizationProvider ?? (credential
|
|
177
|
+
this.authorizationProvider = opts.authorizationProvider ?? createRequestAuthorizationProvider(credential, this.baseUrl);
|
|
182
178
|
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
183
179
|
this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
|
|
184
180
|
this.extraHeaders = { ...opts.extraHeaders ?? {} };
|
|
@@ -201,9 +197,10 @@ var HttpClient = class {
|
|
|
201
197
|
return h;
|
|
202
198
|
}
|
|
203
199
|
async doRequest(method, path, init = {}) {
|
|
200
|
+
const effectiveTimeout = init.timeoutMs ?? this.timeoutMs, started = Date.now();
|
|
204
201
|
let authorization;
|
|
205
202
|
if (this.authorizationProvider) {
|
|
206
|
-
authorization = await this.authorizationProvider.resolve();
|
|
203
|
+
authorization = await this.authorizationProvider.resolve({ timeoutMs: effectiveTimeout });
|
|
207
204
|
} else {
|
|
208
205
|
const credential = this.credential;
|
|
209
206
|
if (!credential || credential.type === "oauth-session") throw new CtxdbError("DATA API credential is required");
|
|
@@ -216,7 +213,7 @@ var HttpClient = class {
|
|
|
216
213
|
}
|
|
217
214
|
const safeText = (value) => {
|
|
218
215
|
const text = String(value);
|
|
219
|
-
return
|
|
216
|
+
return text.split(authorization.value).join("[REDACTED]");
|
|
220
217
|
};
|
|
221
218
|
let url = `${authorization.baseUrl.replace(/\/+$/, "")}${path}`;
|
|
222
219
|
if (init.params) {
|
|
@@ -227,7 +224,6 @@ var HttpClient = class {
|
|
|
227
224
|
const s = qs.toString();
|
|
228
225
|
if (s) url = `${url}?${s}`;
|
|
229
226
|
}
|
|
230
|
-
const effectiveTimeout = init.timeoutMs ?? this.timeoutMs;
|
|
231
227
|
const dbg = isDebug();
|
|
232
228
|
let t0 = 0;
|
|
233
229
|
if (dbg) {
|
|
@@ -235,7 +231,7 @@ var HttpClient = class {
|
|
|
235
231
|
debug("http", `\u2192 ${method} ${url} (timeout=${effectiveTimeout}ms)`);
|
|
236
232
|
}
|
|
237
233
|
const controller = new AbortController();
|
|
238
|
-
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
234
|
+
const timer = setTimeout(() => controller.abort(), Math.max(1, effectiveTimeout - (Date.now() - started)));
|
|
239
235
|
try {
|
|
240
236
|
let resp;
|
|
241
237
|
try {
|
|
@@ -264,7 +260,8 @@ var HttpClient = class {
|
|
|
264
260
|
}
|
|
265
261
|
let text;
|
|
266
262
|
try {
|
|
267
|
-
|
|
263
|
+
const bodyText = await resp.text();
|
|
264
|
+
text = authorization.scheme === "Bearer" || !resp.ok ? safeText(bodyText) : bodyText;
|
|
268
265
|
} catch (err) {
|
|
269
266
|
if (err?.name === "AbortError") {
|
|
270
267
|
if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
|
|
@@ -459,7 +456,8 @@ function agentHomeDir(agent, home = homedir2()) {
|
|
|
459
456
|
var AGENT_VARIANT_HOMES = {
|
|
460
457
|
qoder: [".qoder-cn"],
|
|
461
458
|
qoderwork: [".qoderworkcn"],
|
|
462
|
-
qwenwork: [".qwenworkcn"]
|
|
459
|
+
qwenwork: [".qwenworkcn"],
|
|
460
|
+
workbuddy: [".workbuddy"]
|
|
463
461
|
};
|
|
464
462
|
function agentHomeDirs(agent, home = homedir2()) {
|
|
465
463
|
const primary = agentHomeDir(agent, home);
|
|
@@ -605,9 +603,10 @@ function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.e
|
|
|
605
603
|
}
|
|
606
604
|
|
|
607
605
|
// src/lib/config.ts
|
|
608
|
-
import {
|
|
609
|
-
import {
|
|
610
|
-
import {
|
|
606
|
+
import { publicAuthenticationFromConfig } from "@aliyunrds/ctxdb-shared";
|
|
607
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
608
|
+
import { homedir as homedir3 } from "os";
|
|
609
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
611
610
|
|
|
612
611
|
// src/lib/secure-file.ts
|
|
613
612
|
import {
|
|
@@ -707,146 +706,39 @@ function secureAtomicWrite(target, content, options = {}) {
|
|
|
707
706
|
// src/lib/secrets.ts
|
|
708
707
|
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
709
708
|
var PROCESS_FINGERPRINT_KEY = randomBytes2(32);
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
readFileSync as readFileSync2,
|
|
718
|
-
statSync as statSync2,
|
|
719
|
-
unlinkSync as unlinkSync2,
|
|
720
|
-
writeFileSync as writeFileSync2
|
|
721
|
-
} from "fs";
|
|
722
|
-
import { homedir as homedir3 } from "os";
|
|
723
|
-
import { dirname as dirname4, join as join4 } from "path";
|
|
724
|
-
import { setTimeout as delay } from "timers/promises";
|
|
725
|
-
|
|
726
|
-
// src/credentials/types.ts
|
|
727
|
-
var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
|
|
728
|
-
|
|
729
|
-
// src/credentials/local-credential-provider.ts
|
|
730
|
-
var DOCUMENT_VERSION = 1;
|
|
731
|
-
var STALE_LOCK_MS = 10 * 60 * 1e3;
|
|
732
|
-
function defaultCredentialsPath() {
|
|
733
|
-
return join4(homedir3(), ".ctxdb", "credentials.json");
|
|
709
|
+
function maskApiKey(apiKey) {
|
|
710
|
+
if (!apiKey) return "";
|
|
711
|
+
const payload = apiKey.startsWith("ctxdb-") ? apiKey.slice("ctxdb-".length) : null;
|
|
712
|
+
if (payload && payload.length > 12) return `ctxdb-xxxxxx${payload.slice(-6)}`;
|
|
713
|
+
if (payload !== null) return "x".repeat(apiKey.length);
|
|
714
|
+
if (apiKey.length <= 12) return "x".repeat(apiKey.length);
|
|
715
|
+
return `xxxxxx${apiKey.slice(-6)}`;
|
|
734
716
|
}
|
|
735
|
-
function
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
function normalizeOrigin(value, field) {
|
|
739
|
-
if (typeof value !== "string" || !value) {
|
|
740
|
-
throw new Error(`credentials: ${field} must be a non-empty URL`);
|
|
741
|
-
}
|
|
742
|
-
let url;
|
|
743
|
-
try {
|
|
744
|
-
url = new URL(value);
|
|
745
|
-
} catch {
|
|
746
|
-
throw new Error(`credentials: ${field} must be a valid URL`);
|
|
747
|
-
}
|
|
748
|
-
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
749
|
-
throw new Error(`credentials: ${field} must be an HTTP(S) origin`);
|
|
750
|
-
}
|
|
751
|
-
return url.toString().replace(/\/$/, "");
|
|
752
|
-
}
|
|
753
|
-
function parseRecord(value) {
|
|
754
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
755
|
-
throw new Error("credentials: record must be an object");
|
|
756
|
-
}
|
|
757
|
-
const raw = value;
|
|
758
|
-
if (raw.kind !== "api-key") {
|
|
759
|
-
throw new Error(`credentials: unsupported record kind ${String(raw.kind)}`);
|
|
760
|
-
}
|
|
761
|
-
const payload = raw.payload;
|
|
762
|
-
const metadata = raw.metadata;
|
|
763
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
764
|
-
throw new Error("credentials: api-key payload must be an object");
|
|
765
|
-
}
|
|
766
|
-
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
767
|
-
throw new Error("credentials: api-key metadata must be an object");
|
|
768
|
-
}
|
|
769
|
-
const body = payload;
|
|
770
|
-
const meta = metadata;
|
|
771
|
-
if (typeof body.api_key !== "string" || !body.api_key) {
|
|
772
|
-
throw new Error("credentials: api_key must be a non-empty string");
|
|
773
|
-
}
|
|
774
|
-
if (meta.authorization_method !== "browser-loopback" && meta.authorization_method !== "device-code") {
|
|
775
|
-
throw new Error("credentials: authorization_method is invalid");
|
|
776
|
-
}
|
|
777
|
-
if (typeof meta.issued_at !== "string" || !Number.isFinite(Date.parse(meta.issued_at))) {
|
|
778
|
-
throw new Error("credentials: issued_at is invalid");
|
|
779
|
-
}
|
|
780
|
-
return {
|
|
781
|
-
kind: "api-key",
|
|
782
|
-
payload: {
|
|
783
|
-
apiKey: body.api_key,
|
|
784
|
-
baseUrl: normalizeOrigin(body.base_url, "base_url"),
|
|
785
|
-
loginServer: normalizeOrigin(body.login_server, "login_server")
|
|
786
|
-
},
|
|
787
|
-
metadata: {
|
|
788
|
-
authorizationMethod: meta.authorization_method,
|
|
789
|
-
issuedAt: meta.issued_at
|
|
790
|
-
}
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
function parseDocument(text) {
|
|
794
|
-
let raw;
|
|
795
|
-
try {
|
|
796
|
-
raw = JSON.parse(text);
|
|
797
|
-
} catch {
|
|
798
|
-
throw new Error("credentials: credentials.json is not valid JSON");
|
|
799
|
-
}
|
|
800
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
801
|
-
throw new Error("credentials: document must be an object");
|
|
802
|
-
}
|
|
803
|
-
const document = raw;
|
|
804
|
-
if (document.version !== DOCUMENT_VERSION) {
|
|
805
|
-
throw new Error(`credentials: unsupported document version ${String(document.version)}`);
|
|
806
|
-
}
|
|
807
|
-
if (!document.records || typeof document.records !== "object" || Array.isArray(document.records)) {
|
|
808
|
-
throw new Error("credentials: records must be an object");
|
|
809
|
-
}
|
|
810
|
-
const records = document.records;
|
|
811
|
-
const unknown = Object.keys(records).filter((key) => key !== ACTIVE_CONTEXTDB_CREDENTIAL);
|
|
812
|
-
if (unknown.length > 0) {
|
|
813
|
-
throw new Error(`credentials: unsupported record key ${unknown[0]}`);
|
|
814
|
-
}
|
|
815
|
-
const active = records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
816
|
-
return {
|
|
817
|
-
version: DOCUMENT_VERSION,
|
|
818
|
-
records: active === void 0 ? {} : { [ACTIVE_CONTEXTDB_CREDENTIAL]: parseRecord(active) }
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
function assertOwnerOnly(path) {
|
|
822
|
-
if (process.platform === "win32" || !existsSync3(path)) return;
|
|
823
|
-
const mode = statSync2(path).mode & 511;
|
|
824
|
-
if ((mode & 63) !== 0) {
|
|
825
|
-
throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
function readDocument(path) {
|
|
829
|
-
if (!existsSync3(path)) return emptyDocument();
|
|
830
|
-
assertOwnerOnly(path);
|
|
831
|
-
return parseDocument(readFileSync2(path, "utf8"));
|
|
832
|
-
}
|
|
833
|
-
function readActiveCredentialSync(path = defaultCredentialsPath()) {
|
|
834
|
-
return readDocument(path).records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
717
|
+
function credentialFingerprint(apiKey, processKey = PROCESS_FINGERPRINT_KEY) {
|
|
718
|
+
if (!apiKey) return "";
|
|
719
|
+
return createHmac("sha256", processKey).update(apiKey).digest("hex").slice(0, 12);
|
|
835
720
|
}
|
|
836
721
|
|
|
722
|
+
// src/credentials/local-credential-provider.ts
|
|
723
|
+
import { LocalCredentialProvider, defaultCredentialsPath, readActiveCredentialSync, withAuthorizationLock, internalCandidate } from "@aliyunrds/ctxdb-shared";
|
|
724
|
+
|
|
837
725
|
// src/lib/config.ts
|
|
838
726
|
import {
|
|
839
727
|
resolveDebugPolicy,
|
|
840
|
-
oauthProfile
|
|
728
|
+
oauthProfile,
|
|
729
|
+
validateLoginChoice,
|
|
730
|
+
authSnapshot,
|
|
731
|
+
assertAuthSnapshot,
|
|
732
|
+
withAuthConfigLock
|
|
841
733
|
} from "@aliyunrds/ctxdb-shared";
|
|
842
734
|
function defaultConfigPath(env = process.env) {
|
|
843
|
-
return env.CTXDB_CONFIG_PATH ||
|
|
735
|
+
return env.CTXDB_CONFIG_PATH || join4(homedir3(), ".ctxdb", "ctxdb.json");
|
|
844
736
|
}
|
|
845
|
-
var DEFAULT_CONFIG_PATH =
|
|
737
|
+
var DEFAULT_CONFIG_PATH = join4(homedir3(), ".ctxdb", "ctxdb.json");
|
|
846
738
|
function configDir() {
|
|
847
|
-
return
|
|
739
|
+
return join4(homedir3(), ".ctxdb");
|
|
848
740
|
}
|
|
849
|
-
var DEFAULT_BASE_URL = "https://
|
|
741
|
+
var DEFAULT_BASE_URL = "https://api.cn-hangzhou.agentcontext.aliyuncs.com";
|
|
850
742
|
var DEFAULT_USER_ID = "default";
|
|
851
743
|
var DEFAULT_TOP_K = 5;
|
|
852
744
|
var DEFAULT_THRESHOLD = 0.4;
|
|
@@ -878,9 +770,9 @@ function coerceKbCatalogInjection(v) {
|
|
|
878
770
|
return DEFAULT_KB_CATALOG_INJECTION;
|
|
879
771
|
}
|
|
880
772
|
function readRaw(path) {
|
|
881
|
-
if (!
|
|
773
|
+
if (!existsSync3(path)) return {};
|
|
882
774
|
try {
|
|
883
|
-
const parsed = JSON.parse(
|
|
775
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
884
776
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
885
777
|
return parsed;
|
|
886
778
|
}
|
|
@@ -888,6 +780,99 @@ function readRaw(path) {
|
|
|
888
780
|
}
|
|
889
781
|
return {};
|
|
890
782
|
}
|
|
783
|
+
function asRawObject(value) {
|
|
784
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
785
|
+
}
|
|
786
|
+
function explicitAgentRaw(raw, agent) {
|
|
787
|
+
const agents = asRawObject(raw.agents);
|
|
788
|
+
return agents ? asRawObject(agents[agent]) : null;
|
|
789
|
+
}
|
|
790
|
+
function explicitString(raw, key) {
|
|
791
|
+
const value = raw?.[key];
|
|
792
|
+
return typeof value === "string" && value ? value : null;
|
|
793
|
+
}
|
|
794
|
+
function inspectConfig(options = {}) {
|
|
795
|
+
const path = options.path ?? defaultConfigPath();
|
|
796
|
+
const env = options.env ?? process.env;
|
|
797
|
+
if (!existsSync3(path)) {
|
|
798
|
+
return {
|
|
799
|
+
path,
|
|
800
|
+
state: "missing",
|
|
801
|
+
profiles: diagnosticProfiles({}, env),
|
|
802
|
+
unknownProfiles: [],
|
|
803
|
+
safeMessage: null
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
let parsed;
|
|
807
|
+
try {
|
|
808
|
+
parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
809
|
+
} catch {
|
|
810
|
+
return {
|
|
811
|
+
path,
|
|
812
|
+
state: "malformed-json",
|
|
813
|
+
profiles: diagnosticProfiles({}, env),
|
|
814
|
+
unknownProfiles: [],
|
|
815
|
+
safeMessage: "Configuration contains malformed JSON and requires confirmation before repair."
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
const raw = asRawObject(parsed);
|
|
819
|
+
if (!raw || !isV2Schema(raw)) {
|
|
820
|
+
return {
|
|
821
|
+
path,
|
|
822
|
+
state: "unsupported-schema",
|
|
823
|
+
profiles: diagnosticProfiles({}, env),
|
|
824
|
+
unknownProfiles: [],
|
|
825
|
+
safeMessage: "Configuration schema is unsupported and requires confirmation before repair."
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
const agents = asRawObject(raw.agents) ?? {};
|
|
829
|
+
const known = /* @__PURE__ */ new Set(["default", ...SUPPORTED_AGENTS]);
|
|
830
|
+
return {
|
|
831
|
+
path,
|
|
832
|
+
state: "valid-v2",
|
|
833
|
+
profiles: diagnosticProfiles(raw, env),
|
|
834
|
+
unknownProfiles: Object.keys(agents).filter((name) => !known.has(name)),
|
|
835
|
+
safeMessage: null
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function diagnosticProfiles(raw, env) {
|
|
839
|
+
const profiles = ["default", ...SUPPORTED_AGENTS];
|
|
840
|
+
return profiles.map((agent) => {
|
|
841
|
+
const explicit = explicitAgentRaw(raw, agent);
|
|
842
|
+
const inherited = agent === "default" ? null : explicitAgentRaw(raw, "default");
|
|
843
|
+
const explicitKey = explicitString(explicit, "api_key");
|
|
844
|
+
const inheritedKey = explicitString(inherited, "api_key");
|
|
845
|
+
const apiKey = env.CTXDB_API_KEY || explicitKey || inheritedKey;
|
|
846
|
+
const credentialSource = env.CTXDB_API_KEY ? "environment" : explicitKey ? "profile" : inheritedKey ? "default" : "none";
|
|
847
|
+
const credentialProfile = credentialSource === "profile" ? agent : credentialSource === "default" ? "default" : null;
|
|
848
|
+
const explicitBase = explicitString(explicit, "base_url");
|
|
849
|
+
const inheritedBase = explicitString(inherited, "base_url");
|
|
850
|
+
const baseUrl = (env.CTXDB_BASE_URL || explicitBase || inheritedBase || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
851
|
+
const baseUrlSource = env.CTXDB_BASE_URL ? "environment" : explicitBase ? "profile" : inheritedBase ? "default" : "built-in";
|
|
852
|
+
const explicitUser = explicitString(explicit, "user_id");
|
|
853
|
+
const inheritedUser = explicitString(inherited, "user_id");
|
|
854
|
+
const userId = env.CTXDB_USER_ID || explicitUser || inheritedUser || DEFAULT_USER_ID;
|
|
855
|
+
const userIdSource = env.CTXDB_USER_ID ? "environment" : explicitUser ? "profile" : inheritedUser ? "default" : "built-in";
|
|
856
|
+
return {
|
|
857
|
+
agent,
|
|
858
|
+
explicitProfile: explicit !== null,
|
|
859
|
+
explicitFields: explicit ? Object.keys(explicit).sort() : [],
|
|
860
|
+
credential: {
|
|
861
|
+
source: credentialSource,
|
|
862
|
+
profile: credentialProfile,
|
|
863
|
+
masked: apiKey ? maskApiKey(apiKey) : null,
|
|
864
|
+
fingerprint: apiKey ? credentialFingerprint(apiKey) : null
|
|
865
|
+
},
|
|
866
|
+
effective: {
|
|
867
|
+
baseUrl,
|
|
868
|
+
baseUrlSource,
|
|
869
|
+
userId,
|
|
870
|
+
userIdSource,
|
|
871
|
+
complete: Boolean(apiKey && baseUrl)
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
});
|
|
875
|
+
}
|
|
891
876
|
function agentRawFromFile(raw, agent) {
|
|
892
877
|
const agents = raw.agents;
|
|
893
878
|
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
@@ -955,20 +940,32 @@ function load(options = {}) {
|
|
|
955
940
|
}
|
|
956
941
|
const cfg2 = configFromDisk({});
|
|
957
942
|
const managed2 = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
|
|
958
|
-
options.credentialsPath ??
|
|
943
|
+
options.credentialsPath ?? join4(dirname4(path), "credentials.json")
|
|
959
944
|
);
|
|
960
945
|
if (managed2) {
|
|
961
946
|
cfg2.apiKey = managed2.payload.apiKey;
|
|
962
|
-
cfg2.baseUrl = managed2.
|
|
947
|
+
cfg2.baseUrl = managed2.metadata.baseUrl;
|
|
963
948
|
}
|
|
964
|
-
|
|
949
|
+
applyEnv(cfg2, env);
|
|
950
|
+
if (managed2) cfg2.authContext = {
|
|
951
|
+
configPath: path,
|
|
952
|
+
agent,
|
|
953
|
+
baseUrl: cfg2.baseUrl,
|
|
954
|
+
inheritDefaultKey: true,
|
|
955
|
+
managedCredentials: true,
|
|
956
|
+
credentialsPath: options.credentialsPath,
|
|
957
|
+
...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
|
|
958
|
+
};
|
|
959
|
+
return cfg2;
|
|
965
960
|
}
|
|
966
|
-
const
|
|
961
|
+
const explicit = agentRawFromFile(raw, agent);
|
|
962
|
+
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) validateLoginChoice(explicit);
|
|
963
|
+
const agentRaw = { ...explicit };
|
|
967
964
|
if (agent !== "default") {
|
|
968
965
|
const defaultRaw = agentRawFromFile(raw, "default");
|
|
969
966
|
if (Object.keys(defaultRaw).length > 0) {
|
|
970
967
|
for (const [k, v] of Object.entries(defaultRaw)) {
|
|
971
|
-
if (k !== "oauth_credential_ref" && !(k in agentRaw)) {
|
|
968
|
+
if (k !== "credential_ref" && k !== "oauth_credential_ref" && k !== "access_credential" && !(k in agentRaw)) {
|
|
972
969
|
agentRaw[k] = v;
|
|
973
970
|
}
|
|
974
971
|
}
|
|
@@ -976,23 +973,62 @@ function load(options = {}) {
|
|
|
976
973
|
}
|
|
977
974
|
const cfg = configFromDisk(agentRaw);
|
|
978
975
|
const managed = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
|
|
979
|
-
options.credentialsPath ??
|
|
976
|
+
options.credentialsPath ?? join4(dirname4(path), "credentials.json")
|
|
980
977
|
);
|
|
981
978
|
if (managed) {
|
|
982
979
|
cfg.apiKey = managed.payload.apiKey;
|
|
983
|
-
cfg.baseUrl = managed.
|
|
980
|
+
cfg.baseUrl = managed.metadata.baseUrl;
|
|
984
981
|
}
|
|
982
|
+
const environmentCredential = !!(env.CTXDB_API_KEY || env.CTXDB_ACCESS_TOKEN?.trim());
|
|
983
|
+
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
|
|
984
|
+
cfg.credentialRef = explicit.credential_ref;
|
|
985
|
+
const stored = environmentCredential ? void 0 : publicAuthenticationFromConfig({ ...raw, version: 2, agents: raw.agents ?? {} }, path, agent, env.CTXDB_BASE_URL);
|
|
986
|
+
if (stored) {
|
|
987
|
+
cfg.baseUrl = stored.baseUrl;
|
|
988
|
+
cfg.apiKey = stored.apiKey ?? null;
|
|
989
|
+
cfg.oauthCredential = stored.oauthCredential;
|
|
990
|
+
} else if (cfg.credentialRef) cfg.apiKey = null;
|
|
991
|
+
if (environmentCredential && explicit.oauth_credential_ref) cfg.oauthCredential = oauthProfile(explicit.oauth_credential_ref, path, agent, cfg.baseUrl);
|
|
992
|
+
}
|
|
993
|
+
const persistedApiKey = cfg.apiKey;
|
|
985
994
|
applyEnv(cfg, env);
|
|
986
995
|
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
|
|
987
|
-
|
|
996
|
+
const authRaw = { ...raw, version: 2, agents: raw.agents ?? {} };
|
|
997
|
+
if (Object.hasOwn(explicit, "access_credential")) {
|
|
998
|
+
cfg.accessCredential = explicit.access_credential;
|
|
999
|
+
if (!env.CTXDB_API_KEY) cfg.apiKey = null;
|
|
1000
|
+
}
|
|
1001
|
+
if (env.CTXDB_API_KEY) {
|
|
1002
|
+
cfg.environmentApiKey = env.CTXDB_API_KEY;
|
|
1003
|
+
cfg.persistedApiKey = cfg.accessCredential !== void 0 || cfg.oauthCredential ? null : persistedApiKey;
|
|
1004
|
+
}
|
|
1005
|
+
cfg.authSnapshot = authSnapshot(authRaw, agent);
|
|
1006
|
+
cfg.authAgent = agent;
|
|
1007
|
+
cfg.authContext = {
|
|
1008
|
+
configPath: path,
|
|
1009
|
+
agent,
|
|
1010
|
+
baseUrl: cfg.baseUrl,
|
|
1011
|
+
inheritDefaultKey: true,
|
|
1012
|
+
...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
|
|
1013
|
+
};
|
|
988
1014
|
}
|
|
1015
|
+
if (managed) cfg.authContext = {
|
|
1016
|
+
configPath: path,
|
|
1017
|
+
agent,
|
|
1018
|
+
baseUrl: cfg.baseUrl,
|
|
1019
|
+
inheritDefaultKey: true,
|
|
1020
|
+
managedCredentials: true,
|
|
1021
|
+
credentialsPath: options.credentialsPath,
|
|
1022
|
+
...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
|
|
1023
|
+
};
|
|
989
1024
|
return cfg;
|
|
990
1025
|
}
|
|
991
1026
|
function configToDisk(cfg) {
|
|
992
1027
|
return {
|
|
993
|
-
...cfg.
|
|
994
|
-
|
|
995
|
-
|
|
1028
|
+
...cfg.credentialRef ? { credential_ref: cfg.credentialRef } : {},
|
|
1029
|
+
...cfg.accessCredential !== void 0 ? { access_credential: cfg.accessCredential } : cfg.oauthCredential ? { oauth_credential_ref: cfg.oauthCredential.reference } : {},
|
|
1030
|
+
api_key: cfg.credentialRef || cfg.accessCredential !== void 0 || cfg.oauthCredential ? null : cfg.environmentApiKey && cfg.apiKey === cfg.environmentApiKey ? cfg.persistedApiKey ?? null : cfg.apiKey,
|
|
1031
|
+
...!cfg.credentialRef && cfg.accessCredential === void 0 && !cfg.oauthCredential && !cfg.authContext?.managedCredentials ? { base_url: cfg.baseUrl } : {},
|
|
996
1032
|
user_id: cfg.userId,
|
|
997
1033
|
agent_id: cfg.agentId,
|
|
998
1034
|
app_id: cfg.appId,
|
|
@@ -1009,53 +1045,85 @@ function configToDisk(cfg) {
|
|
|
1009
1045
|
}
|
|
1010
1046
|
function removeAgent(agent, path, options = {}) {
|
|
1011
1047
|
const target = path ?? defaultConfigPath();
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
const raw = readRaw(target);
|
|
1016
|
-
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
1017
|
-
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
1018
|
-
}
|
|
1019
|
-
const agents = { ...raw.agents };
|
|
1020
|
-
if (!(agent in agents)) {
|
|
1021
|
-
return {
|
|
1022
|
-
removed: false,
|
|
1023
|
-
remainingAgents: Object.keys(agents),
|
|
1024
|
-
fileDeleted: false
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
delete agents[agent];
|
|
1028
|
-
const remaining = Object.keys(agents);
|
|
1029
|
-
if (remaining.length === 0 && !options.keepEmptyShell) {
|
|
1030
|
-
try {
|
|
1031
|
-
unlinkSync3(target);
|
|
1032
|
-
return { removed: true, remainingAgents: [], fileDeleted: true };
|
|
1033
|
-
} catch {
|
|
1048
|
+
return withAuthConfigLock(target, () => {
|
|
1049
|
+
if (!existsSync3(target)) {
|
|
1050
|
+
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
1034
1051
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1052
|
+
const raw = readRaw(target);
|
|
1053
|
+
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
1054
|
+
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
1055
|
+
}
|
|
1056
|
+
const agents = { ...raw.agents };
|
|
1057
|
+
if (!(agent in agents)) {
|
|
1058
|
+
return {
|
|
1059
|
+
removed: false,
|
|
1060
|
+
remainingAgents: Object.keys(agents),
|
|
1061
|
+
fileDeleted: false
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
delete agents[agent];
|
|
1065
|
+
const remaining = Object.keys(agents);
|
|
1066
|
+
if (remaining.length === 0 && !options.keepEmptyShell && !Object.keys(asRawObject(raw.logins) ?? {}).length) {
|
|
1067
|
+
try {
|
|
1068
|
+
unlinkSync2(target);
|
|
1069
|
+
return { removed: true, remainingAgents: [], fileDeleted: true };
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
const onDisk = { ...raw, version: 2, agents };
|
|
1074
|
+
delete onDisk.default_agent;
|
|
1075
|
+
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
1076
|
+
return { removed: true, remainingAgents: remaining, fileDeleted: false };
|
|
1077
|
+
});
|
|
1040
1078
|
}
|
|
1041
1079
|
function save(cfg, path, options = {}) {
|
|
1042
1080
|
const target = path ?? defaultConfigPath();
|
|
1043
1081
|
const agent = resolveConfigAgent(options);
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
...validRaw,
|
|
1051
|
-
version: 2,
|
|
1052
|
-
agents: {
|
|
1053
|
-
...existingAgents,
|
|
1054
|
-
[agent]: serialized
|
|
1082
|
+
withAuthConfigLock(target, () => {
|
|
1083
|
+
const raw = readRaw(target);
|
|
1084
|
+
const validRaw = isV2Schema(raw) ? raw : {};
|
|
1085
|
+
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
|
|
1086
|
+
const authRaw = { ...validRaw, version: 2, agents: validRaw.agents ?? {} };
|
|
1087
|
+
if (cfg.authSnapshot && cfg.authAgent === agent) assertAuthSnapshot(authRaw, agent, cfg.authSnapshot);
|
|
1055
1088
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1089
|
+
const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
|
|
1090
|
+
const serialized = configToDisk(cfg);
|
|
1091
|
+
if (options.omitApiKey) delete serialized.api_key;
|
|
1092
|
+
const previous = asRawObject(existingAgents[agent]) ?? {};
|
|
1093
|
+
const next = { ...previous, ...serialized };
|
|
1094
|
+
if (cfg.credentialRef) {
|
|
1095
|
+
delete next.access_credential;
|
|
1096
|
+
delete next.oauth_credential_ref;
|
|
1097
|
+
delete next.api_key;
|
|
1098
|
+
} else if (cfg.accessCredential !== void 0) {
|
|
1099
|
+
delete next.credential_ref;
|
|
1100
|
+
delete next.oauth_credential_ref;
|
|
1101
|
+
delete next.api_key;
|
|
1102
|
+
} else if (cfg.oauthCredential) {
|
|
1103
|
+
delete next.access_credential;
|
|
1104
|
+
next.api_key = null;
|
|
1105
|
+
} else {
|
|
1106
|
+
delete next.access_credential;
|
|
1107
|
+
delete next.oauth_credential_ref;
|
|
1108
|
+
}
|
|
1109
|
+
if (options.omitApiKey) {
|
|
1110
|
+
delete next.api_key;
|
|
1111
|
+
for (const field of ["access_credential", "credential_ref", "oauth_credential_ref"]) {
|
|
1112
|
+
if (Object.hasOwn(previous, field)) next[field] = previous[field];
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (next.credential_ref || Object.hasOwn(next, "access_credential") || next.oauth_credential_ref || cfg.authContext?.managedCredentials) delete next.base_url;
|
|
1116
|
+
const onDisk = {
|
|
1117
|
+
...validRaw,
|
|
1118
|
+
version: 2,
|
|
1119
|
+
agents: {
|
|
1120
|
+
...existingAgents,
|
|
1121
|
+
[agent]: next
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
delete onDisk.default_agent;
|
|
1125
|
+
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
1126
|
+
});
|
|
1059
1127
|
}
|
|
1060
1128
|
function configuredAgents(path) {
|
|
1061
1129
|
const target = path ?? defaultConfigPath();
|
|
@@ -1075,21 +1143,23 @@ function hasConfiguredAgent(agent, path) {
|
|
|
1075
1143
|
}
|
|
1076
1144
|
function updateConfiguredDebug(agent, configured, path) {
|
|
1077
1145
|
const target = path ?? defaultConfigPath();
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1146
|
+
return withAuthConfigLock(target, () => {
|
|
1147
|
+
const raw = readRaw(target);
|
|
1148
|
+
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
const agents = { ...raw.agents };
|
|
1152
|
+
const profile = agents[agent];
|
|
1153
|
+
if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
agents[agent] = { ...profile, debug: configured };
|
|
1157
|
+
secureAtomicWrite(
|
|
1158
|
+
target,
|
|
1159
|
+
JSON.stringify({ ...raw, agents }, null, 2) + "\n"
|
|
1160
|
+
);
|
|
1161
|
+
return true;
|
|
1162
|
+
});
|
|
1093
1163
|
}
|
|
1094
1164
|
function writeInstalledPkgVersion(version, path) {
|
|
1095
1165
|
const target = path ?? defaultConfigPath();
|
|
@@ -1120,11 +1190,11 @@ export {
|
|
|
1120
1190
|
detectInstalledAgents,
|
|
1121
1191
|
agentFromEnv,
|
|
1122
1192
|
agentFromArgvWithFallback,
|
|
1123
|
-
secureAtomicWrite,
|
|
1124
1193
|
defaultConfigPath,
|
|
1125
1194
|
configDir,
|
|
1126
1195
|
DEFAULT_BASE_URL,
|
|
1127
1196
|
DEFAULT_USER_ID,
|
|
1197
|
+
inspectConfig,
|
|
1128
1198
|
load,
|
|
1129
1199
|
removeAgent,
|
|
1130
1200
|
save,
|