@aliyunrds/ctxdb 1.0.8-beta.4 → 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.
@@ -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
- function resolveDataApiCredential(cfg, env = process.env) {
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 { OAuthProfileProvider } from "@aliyunrds/ctxdb-shared";
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?.type === "oauth-session" ? new OAuthProfileProvider(credential.profile) : void 0);
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 authorization.scheme === "Bearer" ? text.split(authorization.value).join("[REDACTED]") : text;
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
- text = safeText(await resp.text());
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`);
@@ -605,9 +602,10 @@ function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.e
605
602
  }
606
603
 
607
604
  // src/lib/config.ts
608
- import { readFileSync as readFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
609
- import { homedir as homedir4 } from "os";
610
- 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";
611
609
 
612
610
  // src/lib/secure-file.ts
613
611
  import {
@@ -707,146 +705,39 @@ function secureAtomicWrite(target, content, options = {}) {
707
705
  // src/lib/secrets.ts
708
706
  import { createHmac, randomBytes as randomBytes2 } from "crypto";
709
707
  var PROCESS_FINGERPRINT_KEY = randomBytes2(32);
710
-
711
- // src/credentials/local-credential-provider.ts
712
- import {
713
- closeSync as closeSync2,
714
- existsSync as existsSync3,
715
- mkdirSync as mkdirSync3,
716
- openSync as openSync2,
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");
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)}`;
734
715
  }
735
- function emptyDocument() {
736
- return { version: DOCUMENT_VERSION, records: {} };
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];
716
+ function credentialFingerprint(apiKey, processKey = PROCESS_FINGERPRINT_KEY) {
717
+ if (!apiKey) return "";
718
+ return createHmac("sha256", processKey).update(apiKey).digest("hex").slice(0, 12);
835
719
  }
836
720
 
721
+ // src/credentials/local-credential-provider.ts
722
+ import { LocalCredentialProvider, defaultCredentialsPath, readActiveCredentialSync, withAuthorizationLock, internalCandidate } from "@aliyunrds/ctxdb-shared";
723
+
837
724
  // src/lib/config.ts
838
725
  import {
839
726
  resolveDebugPolicy,
840
- oauthProfile
727
+ oauthProfile,
728
+ validateLoginChoice,
729
+ authSnapshot,
730
+ assertAuthSnapshot,
731
+ withAuthConfigLock
841
732
  } from "@aliyunrds/ctxdb-shared";
842
733
  function defaultConfigPath(env = process.env) {
843
- return env.CTXDB_CONFIG_PATH || join5(homedir4(), ".ctxdb", "ctxdb.json");
734
+ return env.CTXDB_CONFIG_PATH || join4(homedir3(), ".ctxdb", "ctxdb.json");
844
735
  }
845
- var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
736
+ var DEFAULT_CONFIG_PATH = join4(homedir3(), ".ctxdb", "ctxdb.json");
846
737
  function configDir() {
847
- return join5(homedir4(), ".ctxdb");
738
+ return join4(homedir3(), ".ctxdb");
848
739
  }
849
- var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
740
+ var DEFAULT_BASE_URL = "https://api.cn-hangzhou.agentcontext.aliyuncs.com";
850
741
  var DEFAULT_USER_ID = "default";
851
742
  var DEFAULT_TOP_K = 5;
852
743
  var DEFAULT_THRESHOLD = 0.4;
@@ -878,9 +769,9 @@ function coerceKbCatalogInjection(v) {
878
769
  return DEFAULT_KB_CATALOG_INJECTION;
879
770
  }
880
771
  function readRaw(path) {
881
- if (!existsSync4(path)) return {};
772
+ if (!existsSync3(path)) return {};
882
773
  try {
883
- const parsed = JSON.parse(readFileSync3(path, "utf-8"));
774
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
884
775
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
885
776
  return parsed;
886
777
  }
@@ -888,6 +779,99 @@ function readRaw(path) {
888
779
  }
889
780
  return {};
890
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
+ }
891
875
  function agentRawFromFile(raw, agent) {
892
876
  const agents = raw.agents;
893
877
  if (agents && typeof agents === "object" && !Array.isArray(agents)) {
@@ -955,20 +939,32 @@ function load(options = {}) {
955
939
  }
956
940
  const cfg2 = configFromDisk({});
957
941
  const managed2 = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
958
- options.credentialsPath ?? join5(dirname5(path), "credentials.json")
942
+ options.credentialsPath ?? join4(dirname4(path), "credentials.json")
959
943
  );
960
944
  if (managed2) {
961
945
  cfg2.apiKey = managed2.payload.apiKey;
962
- cfg2.baseUrl = managed2.payload.baseUrl;
946
+ cfg2.baseUrl = managed2.metadata.baseUrl;
963
947
  }
964
- 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;
965
959
  }
966
- const agentRaw = agentRawFromFile(raw, agent);
960
+ const explicit = agentRawFromFile(raw, agent);
961
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) validateLoginChoice(explicit);
962
+ const agentRaw = { ...explicit };
967
963
  if (agent !== "default") {
968
964
  const defaultRaw = agentRawFromFile(raw, "default");
969
965
  if (Object.keys(defaultRaw).length > 0) {
970
966
  for (const [k, v] of Object.entries(defaultRaw)) {
971
- if (k !== "oauth_credential_ref" && !(k in agentRaw)) {
967
+ if (k !== "credential_ref" && k !== "oauth_credential_ref" && k !== "access_credential" && !(k in agentRaw)) {
972
968
  agentRaw[k] = v;
973
969
  }
974
970
  }
@@ -976,23 +972,62 @@ function load(options = {}) {
976
972
  }
977
973
  const cfg = configFromDisk(agentRaw);
978
974
  const managed = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
979
- options.credentialsPath ?? join5(dirname5(path), "credentials.json")
975
+ options.credentialsPath ?? join4(dirname4(path), "credentials.json")
980
976
  );
981
977
  if (managed) {
982
978
  cfg.apiKey = managed.payload.apiKey;
983
- cfg.baseUrl = managed.payload.baseUrl;
979
+ cfg.baseUrl = managed.metadata.baseUrl;
984
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);
991
+ }
992
+ const persistedApiKey = cfg.apiKey;
985
993
  applyEnv(cfg, env);
986
994
  if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
987
- cfg.oauthCredential = oauthProfile(agentRaw.oauth_credential_ref, path, agent, cfg.baseUrl);
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
+ };
988
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
+ };
989
1023
  return cfg;
990
1024
  }
991
1025
  function configToDisk(cfg) {
992
1026
  return {
993
- ...cfg.oauthCredential ? { oauth_credential_ref: cfg.oauthCredential.reference } : {},
994
- api_key: cfg.oauthCredential ? null : cfg.apiKey,
995
- 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 } : {},
996
1031
  user_id: cfg.userId,
997
1032
  agent_id: cfg.agentId,
998
1033
  app_id: cfg.appId,
@@ -1009,53 +1044,85 @@ function configToDisk(cfg) {
1009
1044
  }
1010
1045
  function removeAgent(agent, path, options = {}) {
1011
1046
  const target = path ?? defaultConfigPath();
1012
- if (!existsSync4(target)) {
1013
- return { removed: false, remainingAgents: [], fileDeleted: false };
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 {
1047
+ return withAuthConfigLock(target, () => {
1048
+ if (!existsSync3(target)) {
1049
+ return { removed: false, remainingAgents: [], fileDeleted: false };
1034
1050
  }
1035
- }
1036
- const onDisk = { ...raw, version: 2, agents };
1037
- delete onDisk.default_agent;
1038
- secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
1039
- 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
+ });
1040
1077
  }
1041
1078
  function save(cfg, path, options = {}) {
1042
1079
  const target = path ?? defaultConfigPath();
1043
1080
  const agent = resolveConfigAgent(options);
1044
- const raw = readRaw(target);
1045
- const validRaw = isV2Schema(raw) ? raw : {};
1046
- const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
1047
- const serialized = configToDisk(cfg);
1048
- if (options.omitApiKey) delete serialized.api_key;
1049
- const onDisk = {
1050
- ...validRaw,
1051
- version: 2,
1052
- agents: {
1053
- ...existingAgents,
1054
- [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);
1055
1087
  }
1056
- };
1057
- delete onDisk.default_agent;
1058
- 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
+ });
1059
1126
  }
1060
1127
  function configuredAgents(path) {
1061
1128
  const target = path ?? defaultConfigPath();
@@ -1075,21 +1142,23 @@ function hasConfiguredAgent(agent, path) {
1075
1142
  }
1076
1143
  function updateConfiguredDebug(agent, configured, path) {
1077
1144
  const target = path ?? defaultConfigPath();
1078
- const raw = readRaw(target);
1079
- if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
1080
- return false;
1081
- }
1082
- const agents = { ...raw.agents };
1083
- const profile = agents[agent];
1084
- if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
1085
- return false;
1086
- }
1087
- agents[agent] = { ...profile, debug: configured };
1088
- secureAtomicWrite(
1089
- target,
1090
- JSON.stringify({ ...raw, agents }, null, 2) + "\n"
1091
- );
1092
- 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
+ });
1093
1162
  }
1094
1163
  function writeInstalledPkgVersion(version, path) {
1095
1164
  const target = path ?? defaultConfigPath();
@@ -1120,11 +1189,11 @@ export {
1120
1189
  detectInstalledAgents,
1121
1190
  agentFromEnv,
1122
1191
  agentFromArgvWithFallback,
1123
- secureAtomicWrite,
1124
1192
  defaultConfigPath,
1125
1193
  configDir,
1126
1194
  DEFAULT_BASE_URL,
1127
1195
  DEFAULT_USER_ID,
1196
+ inspectConfig,
1128
1197
  load,
1129
1198
  removeAgent,
1130
1199
  save,
@@ -418,6 +418,11 @@ function isCiEnvironment(env = process.env) {
418
418
  function shouldUseUpdateNotifier(argv, options = {}) {
419
419
  const env = options.env ?? process.env;
420
420
  if (options.installMethod !== "npm") return false;
421
+ if (argv.length === 0 || argv[0] === "help") return false;
422
+ for (const argument of argv) {
423
+ if (argument === "--") break;
424
+ if (argument === "-h" || argument === "--help") return false;
425
+ }
421
426
  if (env.CTXDB_DISABLE_UPDATE_CHECK === "1") return false;
422
427
  if (isCiEnvironment(env)) return false;
423
428
  if (argv.some((argument) => argument === "--json" || argument.startsWith("--json="))) {