@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.
@@ -1,17 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DISTRIBUTION_MANIFEST
4
- } from "./chunk-R67JELM7.js";
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
- function resolveDataApiCredential(cfg, env = process.env) {
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
- text = await resp.text();
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 { readFileSync as readFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
597
- import { homedir as homedir4 } from "os";
598
- import { dirname as dirname5, join as join5 } from "path";
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
- // src/credentials/local-credential-provider.ts
700
- import {
701
- closeSync as closeSync2,
702
- existsSync as existsSync3,
703
- mkdirSync as mkdirSync3,
704
- openSync as openSync2,
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 readActiveCredentialSync(path = defaultCredentialsPath()) {
822
- return readDocument(path).records[ACTIVE_CONTEXTDB_CREDENTIAL];
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 join5(homedir4(), ".ctxdb", "ctxdb.json");
733
+ function defaultConfigPath(env = process.env) {
734
+ return env.CTXDB_CONFIG_PATH || join4(homedir3(), ".ctxdb", "ctxdb.json");
831
735
  }
832
- var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
736
+ var DEFAULT_CONFIG_PATH = join4(homedir3(), ".ctxdb", "ctxdb.json");
833
737
  function configDir() {
834
- return join5(homedir4(), ".ctxdb");
738
+ return join4(homedir3(), ".ctxdb");
835
739
  }
836
- var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
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 (!existsSync4(path)) return {};
772
+ if (!existsSync3(path)) return {};
869
773
  try {
870
- const parsed = JSON.parse(readFileSync3(path, "utf-8"));
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 ?? join5(dirname5(path), "credentials.json")
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.payload.baseUrl;
946
+ cfg2.baseUrl = managed2.metadata.baseUrl;
950
947
  }
951
- return applyEnv(cfg2, env);
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 agentRaw = agentRawFromFile(raw, agent);
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 ?? join5(dirname5(path), "credentials.json")
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.payload.baseUrl;
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
- return applyEnv(cfg, env);
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
- api_key: cfg.apiKey,
977
- base_url: cfg.baseUrl,
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
- if (!existsSync4(target)) {
995
- return { removed: false, remainingAgents: [], fileDeleted: false };
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
- const onDisk = { ...raw, version: 2, agents };
1019
- delete onDisk.default_agent;
1020
- secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
1021
- return { removed: true, remainingAgents: remaining, fileDeleted: false };
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
- const raw = readRaw(target);
1027
- const validRaw = isV2Schema(raw) ? raw : {};
1028
- const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
1029
- const serialized = configToDisk(cfg);
1030
- if (options.omitApiKey) delete serialized.api_key;
1031
- const onDisk = {
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
- delete onDisk.default_agent;
1040
- secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
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
- const raw = readRaw(target);
1061
- if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
1062
- return false;
1063
- }
1064
- const agents = { ...raw.agents };
1065
- const profile = agents[agent];
1066
- if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
1067
- return false;
1068
- }
1069
- agents[agent] = { ...profile, debug: configured };
1070
- secureAtomicWrite(
1071
- target,
1072
- JSON.stringify({ ...raw, agents }, null, 2) + "\n"
1073
- );
1074
- return true;
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,