@aliyunrds/ctxdb 1.0.8-beta.3 → 1.0.8-beta.4

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 CHANGED
@@ -17,9 +17,42 @@ Config and logs live under `~/.ctxdb/`. `~/.ctxdb/ctxdb.json` is one file, but r
17
17
 
18
18
  ## Install
19
19
 
20
+ ### Public Device Flow login (requires the enabled Core/API phase 2 service)
21
+
22
+ ```sh
23
+ ctxdb login --agent qoder --server https://<core-host> --no-browser
24
+ # Complete the displayed user code through the console/independent POP test client.
25
+ ctxdb auth status --agent qoder --json
26
+ ctxdb setup --agent qoder
27
+ ctxdb logout --agent qoder --json
28
+ ```
29
+
30
+ Public login uses Core Device Flow only; it never calls POP, starts a localhost callback,
31
+ or uses the internal login server. `--device-code` explicitly selects the same default flow.
32
+ Device Flow always uses `default`, following the current Member permissions and resource ACLs.
33
+ `login` does not accept `--scope`; creation and approval do not send a scope selector.
34
+ Login does not install hooks/skills. Run setup separately for each selected Agent.
35
+
36
+ Each profile contains only an `oauth_credential_ref`; AT/RT live in owner-only files under
37
+ `~/.ctxdb/oauth-credentials/`. `CTXDB_CONFIG_PATH` can select an isolated configuration file;
38
+ the OAuth directory is its sibling. CLI, Hooks and OpenCode share a cross-process, single-flight
39
+ refresh implementation. A lost refresh response or crashed refresh requires a new login, not
40
+ a retry of the old RT. Managed failure/logout never falls back to an API key or another profile.
41
+ Re-login replaces only the selected profile; logout reports remote-revoke failure separately.
42
+ AT/RT must not be copied into Agent/Skill configuration or handed to a sandbox.
43
+
44
+ The optional sibling `device-environments.json` (`version: 1`, `environments: [{core_url,
45
+ authorization_url}]`) maps a Core origin to a deployment-approved HTTPS console page.
46
+ CLI appends only `user_code` to that locally configured URL. There is no server discovery or
47
+ invented default console page. Without a mapping, the code can be used with the independent POP
48
+ test client. This phase never opens a browser automatically; `--no-browser` is supported.
49
+
50
+ POSIX uses private files/directories; Windows validates an owner-only ACL using local PowerShell.
51
+ Native Windows acceptance remains separate from the macOS local test record.
52
+
20
53
  ### Internal browser-login distribution
21
54
 
22
- The same source also builds `@ali/ctxdb-internal`. That artifact alone
55
+ The same source also builds `@ali/ctxdb-internal`. Its existing prototype
23
56
  adds `ctxdb login --server <url>`, `ctxdb login --device-code`, `ctxdb logout`,
24
57
  and `ctxdb auth status`. The default login uses a browser loopback callback;
25
58
  `--device-code` supports remote or headless terminals. Both methods discover a
@@ -36,14 +69,14 @@ credential overrides the API key and DATA URL in the existing v2
36
69
  three while `ctxdb logout` rejects the two credential overrides, so command
37
70
  output cannot disagree with subsequent DATA requests. Re-login replaces the
38
71
  active connection and revokes the previous key after setup succeeds. The
39
- public `@aliyunrds/ctxdb` build does not contain or advertise these commands and
40
- continues to use `ctxdb setup` and the v2 config exactly as before. Build the
72
+ public `@aliyunrds/ctxdb` build uses the distinct Device Flow implementation above and
73
+ continues to support API-key setup. It never reads the internal credential store. Build the
41
74
  internal staging package with `pnpm build:internal`.
42
75
 
43
76
  The distinction is a compile-time distribution manifest, not a runtime
44
77
  environment-variable or file-existence check. The `public` manifest disables
45
- `interactiveLogin` and `managedCredentials`; the `internal` manifest enables
46
- both. The CLI hooks and the separately bundled OpenCode plugin receive the
78
+ internal-protocol flags `interactiveLogin` and `managedCredentials`, and enables `deviceFlow`;
79
+ the `internal` manifest enables the former two and disables `deviceFlow`. The CLI hooks and the separately bundled OpenCode plugin receive the
47
80
  same manifest, so installing a public build on a machine that happens to have
48
81
  `~/.ctxdb/credentials.json` does not make that build consume the file.
49
82
 
@@ -607,7 +640,7 @@ Field reference:
607
640
 
608
641
  Env-var overrides apply to the selected agent config (env wins): `CTXDB_AGENT` / `CTXDB_ACCESS_TOKEN` / `CTXDB_API_KEY` / `CTXDB_BASE_URL` / `CTXDB_USER_ID` / `CTXDB_AGENT_ID` / `CTXDB_APP_ID`. The internal login command additionally reads `CTXDB_LOGIN_SERVER` as its provider-neutral login endpoint. The last two default to unset — the request body omits `agent_id`/`app_id` entirely; set them only when you want per-agent / per-app scope isolation on the server.
609
642
 
610
- Downstream Agent hosts can set `CTXDB_ACCESS_TOKEN` to send `Authorization: Bearer <token>` to the Data API. It is runtime-only, never written to `~/.ctxdb/ctxdb.json`, and takes precedence over an API key for CLI, process hooks, and the in-process OpenCode plugin; a rejected token does not fall back to the API key. `ctxdb setup` remains API-key-only and never persists or validates an access token.
643
+ Downstream Agent hosts can set `CTXDB_ACCESS_TOKEN` to send `Authorization: Bearer <token>` to the Data API. It is runtime-only, never written to `~/.ctxdb/ctxdb.json`, and takes precedence over a managed session or API key for CLI, process hooks, and OpenCode; a rejected token does not fall back to another credential. `ctxdb setup` can use a managed reference or an environment AT for validation, but never persists the AT. An environment-only credential must remain available to the Agent runtime after setup.
611
644
 
612
645
  Hook/plugin escape hatch: set `CTXDB_SKIP_HOOKS=TRUE` on the agent process to make all ctxdb hook entrypoints exit immediately before reading config or calling the API. Direct `ctxdb memory` / `ctxdb kb` / `ctxdb setup` CLI commands are unchanged.
613
646
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  configDir
4
- } from "./chunk-BUK4SZC2.js";
4
+ } from "./chunk-SVO6H62S.js";
5
5
 
6
6
  // src/lib/circuit.ts
7
7
  import { statSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, readFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  define_CTXDB_DISTRIBUTION_MANIFEST_default
4
- } from "./chunk-VIG4SYLU.js";
4
+ } from "./chunk-PI2SUS3M.js";
5
5
 
6
6
  // src/lib/distribution-capabilities.ts
7
7
  var PUBLIC_DISTRIBUTION = {
@@ -10,7 +10,8 @@ var PUBLIC_DISTRIBUTION = {
10
10
  packageRegistry: null,
11
11
  capabilities: {
12
12
  interactiveLogin: false,
13
- managedCredentials: false
13
+ managedCredentials: false,
14
+ deviceFlow: true
14
15
  }
15
16
  };
16
17
  var DISTRIBUTION_MANIFEST = typeof define_CTXDB_DISTRIBUTION_MANIFEST_default === "undefined" ? PUBLIC_DISTRIBUTION : define_CTXDB_DISTRIBUTION_MANIFEST_default;
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "./chunk-7OX6UVPB.js";
5
+ } from "./chunk-WJTV7JNR.js";
6
6
  import {
7
7
  debug
8
- } from "./chunk-BUK4SZC2.js";
8
+ } from "./chunk-SVO6H62S.js";
9
9
 
10
10
  // src/lib/warmup-recall.ts
11
11
  import { execSync } from "child_process";
@@ -4,13 +4,13 @@ import {
4
4
  isConnectionError,
5
5
  resetCircuit,
6
6
  tripCircuit
7
- } from "./chunk-EI63DQX3.js";
7
+ } from "./chunk-5DGRNP55.js";
8
8
  import {
9
9
  CtxdbError,
10
10
  debug,
11
11
  isDataApiReady,
12
12
  isDebug
13
- } from "./chunk-BUK4SZC2.js";
13
+ } from "./chunk-SVO6H62S.js";
14
14
 
15
15
  // src/lib/capture-orchestrator.ts
16
16
  import {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "./chunk-7OX6UVPB.js";
5
+ } from "./chunk-WJTV7JNR.js";
6
6
 
7
7
  // src/lib/user-prompt-submit-compose.ts
8
8
  async function composeUserPromptSubmit(cfg, agent, client, prompt, sessionId = null) {
@@ -1,7 +1,7 @@
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/self-update.ts
7
7
  import { spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // <define:__CTXDB_DISTRIBUTION_MANIFEST__>
4
- var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false } };
4
+ var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false, deviceFlow: true } };
5
5
 
6
6
  export {
7
7
  define_CTXDB_DISTRIBUTION_MANIFEST_default
@@ -1,7 +1,7 @@
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
7
  function apiKeyCredential(apiKey) {
@@ -10,6 +10,8 @@ function apiKeyCredential(apiKey) {
10
10
  function resolveDataApiCredential(cfg, env = process.env) {
11
11
  const accessToken = env.CTXDB_ACCESS_TOKEN?.trim();
12
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 };
13
15
  return cfg.apiKey ? apiKeyCredential(cfg.apiKey) : null;
14
16
  }
15
17
  function isDataApiReady(cfg, env = process.env) {
@@ -82,6 +84,7 @@ var PACKAGE_NAME = PACKAGE_IDENTITY.name;
82
84
  var PACKAGE_VERSION = PACKAGE_IDENTITY.version;
83
85
 
84
86
  // src/lib/http-client.ts
87
+ import { OAuthProfileProvider } from "@aliyunrds/ctxdb-shared";
85
88
  var DEFAULT_TIMEOUT_MS = 3e4;
86
89
  var CtxdbError = class extends Error {
87
90
  constructor(message) {
@@ -175,7 +178,7 @@ var HttpClient = class {
175
178
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
176
179
  this.credential = credential;
177
180
  this.apiKey = opts.apiKey ?? "";
178
- this.authorizationProvider = opts.authorizationProvider;
181
+ this.authorizationProvider = opts.authorizationProvider ?? (credential?.type === "oauth-session" ? new OAuthProfileProvider(credential.profile) : void 0);
179
182
  this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
180
183
  this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
181
184
  this.extraHeaders = { ...opts.extraHeaders ?? {} };
@@ -190,6 +193,10 @@ var HttpClient = class {
190
193
  ...this.extraHeaders,
191
194
  ...requestHeaders
192
195
  };
196
+ if (authorization.scheme === "Bearer") {
197
+ for (const key of Object.keys(h)) if (key.toLowerCase() === "authorization") delete h[key];
198
+ h.Authorization = `Bearer ${authorization.value}`;
199
+ }
193
200
  if (contentType) h["Content-Type"] = contentType;
194
201
  return h;
195
202
  }
@@ -199,7 +206,7 @@ var HttpClient = class {
199
206
  authorization = await this.authorizationProvider.resolve();
200
207
  } else {
201
208
  const credential = this.credential;
202
- if (!credential) throw new CtxdbError("DATA API credential is required");
209
+ if (!credential || credential.type === "oauth-session") throw new CtxdbError("DATA API credential is required");
203
210
  authorization = {
204
211
  scheme: credential.type === "access-token" ? "Bearer" : "Token",
205
212
  value: credential.value,
@@ -207,6 +214,10 @@ var HttpClient = class {
207
214
  expiresAt: null
208
215
  };
209
216
  }
217
+ const safeText = (value) => {
218
+ const text = String(value);
219
+ return authorization.scheme === "Bearer" ? text.split(authorization.value).join("[REDACTED]") : text;
220
+ };
210
221
  let url = `${authorization.baseUrl.replace(/\/+$/, "")}${path}`;
211
222
  if (init.params) {
212
223
  const qs = new URLSearchParams();
@@ -236,15 +247,16 @@ var HttpClient = class {
236
247
  init.headers
237
248
  ),
238
249
  body: init.body,
239
- signal: controller.signal
250
+ signal: controller.signal,
251
+ ...authorization.scheme === "Bearer" ? { redirect: "error" } : {}
240
252
  });
241
253
  } catch (err) {
242
254
  if (err?.name === "AbortError") {
243
255
  if (dbg) debug("http", `\u2717 ${method} ${path} timeout after ${Date.now() - t0}ms`);
244
256
  throw new CtxdbError(`request timeout after ${effectiveTimeout}ms: ${url}`);
245
257
  }
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}`);
258
+ if (dbg) debug("http", `\u2717 ${method} ${path} network error after ${Date.now() - t0}ms: ${safeText(err?.message ?? err)}`);
259
+ throw new CtxdbError(`network error contacting ${url}: ${safeText(err?.message ?? err)}`);
248
260
  }
249
261
  if (resp.status === 204) {
250
262
  if (dbg) debug("http", `\u2190 ${method} ${path} 204 (${Date.now() - t0}ms)`);
@@ -252,14 +264,14 @@ var HttpClient = class {
252
264
  }
253
265
  let text;
254
266
  try {
255
- text = await resp.text();
267
+ text = safeText(await resp.text());
256
268
  } catch (err) {
257
269
  if (err?.name === "AbortError") {
258
270
  if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
259
271
  throw new CtxdbError(`request timeout after ${effectiveTimeout}ms (body read): ${url}`);
260
272
  }
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}`);
273
+ if (dbg) debug("http", `\u2717 ${method} ${path} body-read error after ${Date.now() - t0}ms: ${safeText(err?.message ?? err)}`);
274
+ throw new CtxdbError(`network error reading body from ${url}: ${safeText(err?.message ?? err)}`);
263
275
  }
264
276
  if (!resp.ok) {
265
277
  const parsedError = parseErrorResponse(text, `HTTP ${resp.status}`);
@@ -824,10 +836,11 @@ function readActiveCredentialSync(path = defaultCredentialsPath()) {
824
836
 
825
837
  // src/lib/config.ts
826
838
  import {
827
- resolveDebugPolicy
839
+ resolveDebugPolicy,
840
+ oauthProfile
828
841
  } from "@aliyunrds/ctxdb-shared";
829
- function defaultConfigPath() {
830
- return join5(homedir4(), ".ctxdb", "ctxdb.json");
842
+ function defaultConfigPath(env = process.env) {
843
+ return env.CTXDB_CONFIG_PATH || join5(homedir4(), ".ctxdb", "ctxdb.json");
831
844
  }
832
845
  var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
833
846
  function configDir() {
@@ -928,8 +941,8 @@ function applyEnv(cfg, env) {
928
941
  return applyDebugPolicy(cfg);
929
942
  }
930
943
  function load(options = {}) {
931
- const path = options.path ?? defaultConfigPath();
932
944
  const env = options.env ?? process.env;
945
+ const path = options.path ?? defaultConfigPath(env);
933
946
  const raw = readRaw(path);
934
947
  const agent = resolveConfigAgent({ ...options, path, env }, raw);
935
948
  if (!isV2Schema(raw)) {
@@ -955,7 +968,7 @@ function load(options = {}) {
955
968
  const defaultRaw = agentRawFromFile(raw, "default");
956
969
  if (Object.keys(defaultRaw).length > 0) {
957
970
  for (const [k, v] of Object.entries(defaultRaw)) {
958
- if (!(k in agentRaw)) {
971
+ if (k !== "oauth_credential_ref" && !(k in agentRaw)) {
959
972
  agentRaw[k] = v;
960
973
  }
961
974
  }
@@ -969,11 +982,16 @@ function load(options = {}) {
969
982
  cfg.apiKey = managed.payload.apiKey;
970
983
  cfg.baseUrl = managed.payload.baseUrl;
971
984
  }
972
- return applyEnv(cfg, env);
985
+ applyEnv(cfg, env);
986
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
987
+ cfg.oauthCredential = oauthProfile(agentRaw.oauth_credential_ref, path, agent, cfg.baseUrl);
988
+ }
989
+ return cfg;
973
990
  }
974
991
  function configToDisk(cfg) {
975
992
  return {
976
- api_key: cfg.apiKey,
993
+ ...cfg.oauthCredential ? { oauth_credential_ref: cfg.oauthCredential.reference } : {},
994
+ api_key: cfg.oauthCredential ? null : cfg.apiKey,
977
995
  base_url: cfg.baseUrl,
978
996
  user_id: cfg.userId,
979
997
  agent_id: cfg.agentId,
@@ -1081,7 +1099,6 @@ function writeInstalledPkgVersion(version, path) {
1081
1099
  }
1082
1100
 
1083
1101
  export {
1084
- apiKeyCredential,
1085
1102
  resolveDataApiCredential,
1086
1103
  isDataApiReady,
1087
1104
  setDebug,
@@ -1103,6 +1120,8 @@ export {
1103
1120
  detectInstalledAgents,
1104
1121
  agentFromEnv,
1105
1122
  agentFromArgvWithFallback,
1123
+ secureAtomicWrite,
1124
+ defaultConfigPath,
1106
1125
  configDir,
1107
1126
  DEFAULT_BASE_URL,
1108
1127
  DEFAULT_USER_ID,
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  listKnowledgeBases
4
- } from "./chunk-3NJ37TEY.js";
4
+ } from "./chunk-ZW7YXNJ6.js";
5
5
  import {
6
6
  isConnectionError,
7
7
  resetCircuit,
8
8
  tripCircuit
9
- } from "./chunk-EI63DQX3.js";
9
+ } from "./chunk-5DGRNP55.js";
10
10
  import {
11
11
  CtxdbError,
12
12
  isDataApiReady,
13
13
  resolveDataApiCredential
14
- } from "./chunk-BUK4SZC2.js";
14
+ } from "./chunk-SVO6H62S.js";
15
15
 
16
16
  // src/lib/kb-catalog.ts
17
17
  function sanitizeKeyEntities(raw) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CtxdbError
4
- } from "./chunk-BUK4SZC2.js";
4
+ } from "./chunk-SVO6H62S.js";
5
5
 
6
6
  // src/lib/kb.ts
7
7
  import {