@aliyunrds/ctxdb 1.0.6 → 1.0.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DISTRIBUTION_MANIFEST
4
+ } from "./chunk-R67JELM7.js";
2
5
 
3
6
  // src/lib/logger.ts
4
7
  import { appendFileSync, mkdirSync } from "fs";
@@ -43,21 +46,27 @@ ${s}`;
43
46
  import { readFileSync } from "fs";
44
47
  import { dirname as dirname2, join as join2 } from "path";
45
48
  import { fileURLToPath } from "url";
46
- function findPackageVersion() {
49
+ var PACKAGE_NAMES = /* @__PURE__ */ new Set([
50
+ "@aliyunrds/ctxdb",
51
+ "@ali/ctxdb-internal"
52
+ ]);
53
+ function findPackageIdentity() {
47
54
  let dir = dirname2(fileURLToPath(import.meta.url));
48
55
  for (let i = 0; i < 5; i++) {
49
56
  try {
50
57
  const pkg = JSON.parse(readFileSync(join2(dir, "package.json"), "utf-8"));
51
- if (pkg.name === "@aliyunrds/ctxdb" && typeof pkg.version === "string") {
52
- return pkg.version;
58
+ if (PACKAGE_NAMES.has(pkg.name) && typeof pkg.version === "string") {
59
+ return { name: pkg.name, version: pkg.version };
53
60
  }
54
61
  } catch {
55
62
  }
56
63
  dir = dirname2(dir);
57
64
  }
58
- return "0.0.0";
65
+ return { name: "@aliyunrds/ctxdb", version: "0.0.0" };
59
66
  }
60
- var PACKAGE_VERSION = findPackageVersion();
67
+ var PACKAGE_IDENTITY = findPackageIdentity();
68
+ var PACKAGE_NAME = PACKAGE_IDENTITY.name;
69
+ var PACKAGE_VERSION = PACKAGE_IDENTITY.version;
61
70
 
62
71
  // src/lib/http-client.ts
63
72
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -143,18 +152,23 @@ var HttpClient = class {
143
152
  userAgent;
144
153
  extraHeaders;
145
154
  fetchImpl;
155
+ authorizationProvider;
146
156
  constructor(opts) {
147
157
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
148
- this.apiKey = opts.apiKey;
158
+ if (!opts.apiKey && !opts.authorizationProvider) {
159
+ throw new Error("HttpClient requires apiKey or authorizationProvider");
160
+ }
161
+ this.apiKey = opts.apiKey ?? "";
162
+ this.authorizationProvider = opts.authorizationProvider;
149
163
  this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
150
164
  this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
151
165
  this.extraHeaders = { ...opts.extraHeaders ?? {} };
152
166
  const f = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
153
167
  this.fetchImpl = f;
154
168
  }
155
- buildHeaders(contentType, requestHeaders = {}) {
169
+ async buildHeaders(authorization, contentType, requestHeaders = {}) {
156
170
  const h = {
157
- Authorization: `Token ${this.apiKey}`,
171
+ Authorization: `${authorization.scheme} ${authorization.value}`,
158
172
  "User-Agent": this.userAgent,
159
173
  Connection: "close",
160
174
  ...this.extraHeaders,
@@ -164,7 +178,13 @@ var HttpClient = class {
164
178
  return h;
165
179
  }
166
180
  async doRequest(method, path, init = {}) {
167
- let url = `${this.baseUrl}${path}`;
181
+ const authorization = this.authorizationProvider ? await this.authorizationProvider.resolve() : {
182
+ scheme: "Token",
183
+ value: this.apiKey,
184
+ baseUrl: this.baseUrl,
185
+ expiresAt: null
186
+ };
187
+ let url = `${authorization.baseUrl.replace(/\/+$/, "")}${path}`;
168
188
  if (init.params) {
169
189
  const qs = new URLSearchParams();
170
190
  for (const [k, v] of Object.entries(init.params)) {
@@ -187,7 +207,11 @@ var HttpClient = class {
187
207
  try {
188
208
  resp = await this.fetchImpl(url, {
189
209
  method,
190
- headers: this.buildHeaders(init.contentType, init.headers),
210
+ headers: await this.buildHeaders(
211
+ authorization,
212
+ init.contentType,
213
+ init.headers
214
+ ),
191
215
  body: init.body,
192
216
  signal: controller.signal
193
217
  });
@@ -248,15 +272,42 @@ var HttpClient = class {
248
272
  headers: options.headers
249
273
  });
250
274
  }
251
- putJson(path, body) {
275
+ putJson(path, body, params, options = {}) {
252
276
  return this.doRequest("PUT", path, {
253
277
  body: JSON.stringify(body),
254
- contentType: "application/json"
278
+ contentType: "application/json",
279
+ params,
280
+ timeoutMs: options.timeoutMs,
281
+ headers: options.headers
282
+ });
283
+ }
284
+ patchJson(path, body, params, options = {}) {
285
+ return this.doRequest("PATCH", path, {
286
+ body: JSON.stringify(body),
287
+ contentType: "application/json",
288
+ params,
289
+ timeoutMs: options.timeoutMs,
290
+ headers: options.headers
255
291
  });
256
292
  }
257
293
  delete(path, params) {
258
294
  return this.doRequest("DELETE", path, { params });
259
295
  }
296
+ /**
297
+ * DELETE with a JSON body. ContextDB batch deletes use the
298
+ * `DELETE /v1/{resource}/batch` shape with an `{"ids": [...]}` body
299
+ * (server contract) — the plain `delete()` above only supports query
300
+ * params.
301
+ */
302
+ deleteJson(path, body, params, options = {}) {
303
+ return this.doRequest("DELETE", path, {
304
+ body: JSON.stringify(body),
305
+ contentType: "application/json",
306
+ params,
307
+ timeoutMs: options.timeoutMs,
308
+ headers: options.headers
309
+ });
310
+ }
260
311
  /**
261
312
  * POST multipart/form-data using runtime-native FormData.
262
313
  *
@@ -266,14 +317,7 @@ var HttpClient = class {
266
317
  * boundary itself.
267
318
  */
268
319
  postMultipart(path, fields = {}, files = {}, options = {}) {
269
- const fd = new FormData();
270
- for (const [name, value] of Object.entries(fields)) {
271
- fd.append(name, value);
272
- }
273
- for (const [name, part] of Object.entries(files)) {
274
- const blob = part.content instanceof Blob ? part.content : new Blob([part.content], { type: part.mimeType });
275
- fd.append(name, blob, part.filename);
276
- }
320
+ const fd = buildMultipartForm(fields, files);
277
321
  return this.doRequest("POST", path, {
278
322
  body: fd,
279
323
  params: options.params,
@@ -281,7 +325,28 @@ var HttpClient = class {
281
325
  headers: options.headers
282
326
  });
283
327
  }
328
+ /** PUT multipart/form-data with the same runtime-owned boundary as POST. */
329
+ putMultipart(path, fields = {}, files = {}, options = {}) {
330
+ const fd = buildMultipartForm(fields, files);
331
+ return this.doRequest("PUT", path, {
332
+ body: fd,
333
+ params: options.params,
334
+ timeoutMs: options.timeoutMs,
335
+ headers: options.headers
336
+ });
337
+ }
284
338
  };
339
+ function buildMultipartForm(fields, files) {
340
+ const fd = new FormData();
341
+ for (const [name, value] of Object.entries(fields)) {
342
+ fd.append(name, value);
343
+ }
344
+ for (const [name, part] of Object.entries(files)) {
345
+ const blob = part.content instanceof Blob ? part.content : new Blob([part.content], { type: part.mimeType });
346
+ fd.append(name, blob, part.filename);
347
+ }
348
+ return fd;
349
+ }
285
350
  function maybeJson(text) {
286
351
  try {
287
352
  return JSON.parse(text);
@@ -326,7 +391,7 @@ function parseErrorResponse(text, fallback) {
326
391
  import { homedir as homedir2 } from "os";
327
392
  import { delimiter, join as join3, sep } from "path";
328
393
  import { accessSync, constants, existsSync, statSync } from "fs";
329
- var SUPPORTED_AGENTS = ["qoder", "qoderwork", "qwenwork", "codex", "claude", "opencode", "hermes"];
394
+ var SUPPORTED_AGENTS = ["qoder", "qoderwork", "qwenwork", "codex", "claude", "opencode", "hermes", "workbuddy"];
330
395
  function isBuiltinAgent(v) {
331
396
  return typeof v === "string" && SUPPORTED_AGENTS.includes(v);
332
397
  }
@@ -350,6 +415,8 @@ function agentHomeDir(agent, home = homedir2()) {
350
415
  return join3(home, ".config", "opencode");
351
416
  case "hermes":
352
417
  return join3(home, ".hermes");
418
+ case "workbuddy":
419
+ return join3(home, ".workbuddy");
353
420
  }
354
421
  }
355
422
  var AGENT_VARIANT_HOMES = {
@@ -501,9 +568,9 @@ function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.e
501
568
  }
502
569
 
503
570
  // src/lib/config.ts
504
- import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
505
- import { homedir as homedir3 } from "os";
506
- import { join as join4 } from "path";
571
+ import { readFileSync as readFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
572
+ import { homedir as homedir4 } from "os";
573
+ import { dirname as dirname5, join as join5 } from "path";
507
574
 
508
575
  // src/lib/secure-file.ts
509
576
  import {
@@ -604,16 +671,142 @@ function secureAtomicWrite(target, content, options = {}) {
604
671
  import { createHmac, randomBytes as randomBytes2 } from "crypto";
605
672
  var PROCESS_FINGERPRINT_KEY = randomBytes2(32);
606
673
 
674
+ // src/credentials/local-credential-provider.ts
675
+ import {
676
+ closeSync as closeSync2,
677
+ existsSync as existsSync3,
678
+ mkdirSync as mkdirSync3,
679
+ openSync as openSync2,
680
+ readFileSync as readFileSync2,
681
+ statSync as statSync2,
682
+ unlinkSync as unlinkSync2,
683
+ writeFileSync as writeFileSync2
684
+ } from "fs";
685
+ import { homedir as homedir3 } from "os";
686
+ import { dirname as dirname4, join as join4 } from "path";
687
+ import { setTimeout as delay } from "timers/promises";
688
+
689
+ // src/credentials/types.ts
690
+ var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
691
+
692
+ // src/credentials/local-credential-provider.ts
693
+ var DOCUMENT_VERSION = 1;
694
+ var STALE_LOCK_MS = 10 * 60 * 1e3;
695
+ function defaultCredentialsPath() {
696
+ return join4(homedir3(), ".ctxdb", "credentials.json");
697
+ }
698
+ function emptyDocument() {
699
+ return { version: DOCUMENT_VERSION, records: {} };
700
+ }
701
+ function normalizeOrigin(value, field) {
702
+ if (typeof value !== "string" || !value) {
703
+ throw new Error(`credentials: ${field} must be a non-empty URL`);
704
+ }
705
+ let url;
706
+ try {
707
+ url = new URL(value);
708
+ } catch {
709
+ throw new Error(`credentials: ${field} must be a valid URL`);
710
+ }
711
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
712
+ throw new Error(`credentials: ${field} must be an HTTP(S) origin`);
713
+ }
714
+ return url.toString().replace(/\/$/, "");
715
+ }
716
+ function parseRecord(value) {
717
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
718
+ throw new Error("credentials: record must be an object");
719
+ }
720
+ const raw = value;
721
+ if (raw.kind !== "api-key") {
722
+ throw new Error(`credentials: unsupported record kind ${String(raw.kind)}`);
723
+ }
724
+ const payload = raw.payload;
725
+ const metadata = raw.metadata;
726
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
727
+ throw new Error("credentials: api-key payload must be an object");
728
+ }
729
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
730
+ throw new Error("credentials: api-key metadata must be an object");
731
+ }
732
+ const body = payload;
733
+ const meta = metadata;
734
+ if (typeof body.api_key !== "string" || !body.api_key) {
735
+ throw new Error("credentials: api_key must be a non-empty string");
736
+ }
737
+ if (meta.authorization_method !== "browser-loopback" && meta.authorization_method !== "device-code") {
738
+ throw new Error("credentials: authorization_method is invalid");
739
+ }
740
+ if (typeof meta.issued_at !== "string" || !Number.isFinite(Date.parse(meta.issued_at))) {
741
+ throw new Error("credentials: issued_at is invalid");
742
+ }
743
+ return {
744
+ kind: "api-key",
745
+ payload: {
746
+ apiKey: body.api_key,
747
+ baseUrl: normalizeOrigin(body.base_url, "base_url"),
748
+ loginServer: normalizeOrigin(body.login_server, "login_server")
749
+ },
750
+ metadata: {
751
+ authorizationMethod: meta.authorization_method,
752
+ issuedAt: meta.issued_at
753
+ }
754
+ };
755
+ }
756
+ function parseDocument(text) {
757
+ let raw;
758
+ try {
759
+ raw = JSON.parse(text);
760
+ } catch {
761
+ throw new Error("credentials: credentials.json is not valid JSON");
762
+ }
763
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
764
+ throw new Error("credentials: document must be an object");
765
+ }
766
+ const document = raw;
767
+ if (document.version !== DOCUMENT_VERSION) {
768
+ throw new Error(`credentials: unsupported document version ${String(document.version)}`);
769
+ }
770
+ if (!document.records || typeof document.records !== "object" || Array.isArray(document.records)) {
771
+ throw new Error("credentials: records must be an object");
772
+ }
773
+ const records = document.records;
774
+ const unknown = Object.keys(records).filter((key) => key !== ACTIVE_CONTEXTDB_CREDENTIAL);
775
+ if (unknown.length > 0) {
776
+ throw new Error(`credentials: unsupported record key ${unknown[0]}`);
777
+ }
778
+ const active = records[ACTIVE_CONTEXTDB_CREDENTIAL];
779
+ return {
780
+ version: DOCUMENT_VERSION,
781
+ records: active === void 0 ? {} : { [ACTIVE_CONTEXTDB_CREDENTIAL]: parseRecord(active) }
782
+ };
783
+ }
784
+ function assertOwnerOnly(path) {
785
+ if (process.platform === "win32" || !existsSync3(path)) return;
786
+ const mode = statSync2(path).mode & 511;
787
+ if ((mode & 63) !== 0) {
788
+ throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
789
+ }
790
+ }
791
+ function readDocument(path) {
792
+ if (!existsSync3(path)) return emptyDocument();
793
+ assertOwnerOnly(path);
794
+ return parseDocument(readFileSync2(path, "utf8"));
795
+ }
796
+ function readActiveCredentialSync(path = defaultCredentialsPath()) {
797
+ return readDocument(path).records[ACTIVE_CONTEXTDB_CREDENTIAL];
798
+ }
799
+
607
800
  // src/lib/config.ts
608
801
  import {
609
802
  resolveDebugPolicy
610
803
  } from "@aliyunrds/ctxdb-shared";
611
804
  function defaultConfigPath() {
612
- return join4(homedir3(), ".ctxdb", "ctxdb.json");
805
+ return join5(homedir4(), ".ctxdb", "ctxdb.json");
613
806
  }
614
- var DEFAULT_CONFIG_PATH = join4(homedir3(), ".ctxdb", "ctxdb.json");
807
+ var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
615
808
  function configDir() {
616
- return join4(homedir3(), ".ctxdb");
809
+ return join5(homedir4(), ".ctxdb");
617
810
  }
618
811
  var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
619
812
  var DEFAULT_USER_ID = "default";
@@ -650,9 +843,9 @@ function coerceKbCatalogInjection(v) {
650
843
  return DEFAULT_KB_CATALOG_INJECTION;
651
844
  }
652
845
  function readRaw(path) {
653
- if (!existsSync3(path)) return {};
846
+ if (!existsSync4(path)) return {};
654
847
  try {
655
- const parsed = JSON.parse(readFileSync2(path, "utf-8"));
848
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
656
849
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
657
850
  return parsed;
658
851
  }
@@ -725,7 +918,15 @@ function load(options = {}) {
725
918
  );
726
919
  } catch {
727
920
  }
728
- return applyEnv(configFromDisk({}), env);
921
+ const cfg2 = configFromDisk({});
922
+ const managed2 = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
923
+ options.credentialsPath ?? join5(dirname5(path), "credentials.json")
924
+ );
925
+ if (managed2) {
926
+ cfg2.apiKey = managed2.payload.apiKey;
927
+ cfg2.baseUrl = managed2.payload.baseUrl;
928
+ }
929
+ return applyEnv(cfg2, env);
729
930
  }
730
931
  const agentRaw = agentRawFromFile(raw, agent);
731
932
  if (agent !== "default") {
@@ -738,7 +939,15 @@ function load(options = {}) {
738
939
  }
739
940
  }
740
941
  }
741
- return applyEnv(configFromDisk(agentRaw), env);
942
+ const cfg = configFromDisk(agentRaw);
943
+ const managed = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
944
+ options.credentialsPath ?? join5(dirname5(path), "credentials.json")
945
+ );
946
+ if (managed) {
947
+ cfg.apiKey = managed.payload.apiKey;
948
+ cfg.baseUrl = managed.payload.baseUrl;
949
+ }
950
+ return applyEnv(cfg, env);
742
951
  }
743
952
  function configToDisk(cfg) {
744
953
  return {
@@ -760,7 +969,7 @@ function configToDisk(cfg) {
760
969
  }
761
970
  function removeAgent(agent, path, options = {}) {
762
971
  const target = path ?? defaultConfigPath();
763
- if (!existsSync3(target)) {
972
+ if (!existsSync4(target)) {
764
973
  return { removed: false, remainingAgents: [], fileDeleted: false };
765
974
  }
766
975
  const raw = readRaw(target);
@@ -779,7 +988,7 @@ function removeAgent(agent, path, options = {}) {
779
988
  const remaining = Object.keys(agents);
780
989
  if (remaining.length === 0 && !options.keepEmptyShell) {
781
990
  try {
782
- unlinkSync2(target);
991
+ unlinkSync3(target);
783
992
  return { removed: true, remainingAgents: [], fileDeleted: true };
784
993
  } catch {
785
994
  }
@@ -795,12 +1004,14 @@ function save(cfg, path, options = {}) {
795
1004
  const raw = readRaw(target);
796
1005
  const validRaw = isV2Schema(raw) ? raw : {};
797
1006
  const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
1007
+ const serialized = configToDisk(cfg);
1008
+ if (options.omitApiKey) delete serialized.api_key;
798
1009
  const onDisk = {
799
1010
  ...validRaw,
800
1011
  version: 2,
801
1012
  agents: {
802
1013
  ...existingAgents,
803
- [agent]: configToDisk(cfg)
1014
+ [agent]: serialized
804
1015
  }
805
1016
  };
806
1017
  delete onDisk.default_agent;
@@ -851,6 +1062,7 @@ export {
851
1062
  setDebug,
852
1063
  isDebug,
853
1064
  debug,
1065
+ PACKAGE_NAME,
854
1066
  PACKAGE_VERSION,
855
1067
  CtxdbError,
856
1068
  HttpClient,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "./chunk-VHNHVMCA.js";
5
+ } from "./chunk-WSMZOFZQ.js";
6
6
 
7
7
  // src/lib/user-prompt-submit-compose.ts
8
8
  async function composeUserPromptSubmit(cfg, agent, client, prompt, sessionId = null) {