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