@aliyunrds/ctxdb 1.0.8-beta.2 → 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/dist/cli/main.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  uploadFile,
17
17
  uploadText,
18
18
  validateLocalFile
19
- } from "../chunk-CULTDVI2.js";
19
+ } from "../chunk-ZW7YXNJ6.js";
20
20
  import {
21
21
  AGENT_CHOICES,
22
22
  CtxdbError,
@@ -32,27 +32,30 @@ import {
32
32
  agentHomeDirs,
33
33
  agentPlatformSupport,
34
34
  configuredAgents,
35
+ defaultConfigPath,
35
36
  detectInstalledAgents,
36
37
  hasConfiguredAgent,
37
38
  inspectAgentHomes,
38
39
  isAgentSlug,
39
40
  isBuiltinAgent,
40
- isComplete,
41
+ isDataApiReady,
41
42
  load,
42
43
  removeAgent,
44
+ resolveDataApiCredential,
43
45
  save,
46
+ secureAtomicWrite,
44
47
  updateConfiguredDebug,
45
48
  writeInstalledPkgVersion
46
- } from "../chunk-DMS5YHIK.js";
49
+ } from "../chunk-SVO6H62S.js";
47
50
  import {
48
51
  beginUpdateNotification,
49
52
  completeUpdateNotification,
50
53
  runSelfUpdate
51
- } from "../chunk-ULAWTBBC.js";
54
+ } from "../chunk-OEZM2ZYA.js";
52
55
  import {
53
56
  DISTRIBUTION_MANIFEST
54
- } from "../chunk-R67JELM7.js";
55
- import "../chunk-VIG4SYLU.js";
57
+ } from "../chunk-5ITT5GSP.js";
58
+ import "../chunk-PI2SUS3M.js";
56
59
 
57
60
  // src/cli/util.ts
58
61
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -275,6 +278,8 @@ function formatStatusResult(r) {
275
278
  ["base_url", r.base_url],
276
279
  ["user_id", r.user_id],
277
280
  ["api_key", r.api_key_set ? "set" : "not set"],
281
+ ["access_token", r.access_token_set ? "set" : "not set"],
282
+ ["credential_type", r.credential_type ?? "none"],
278
283
  ["auto_capture", r.auto_capture],
279
284
  ["auto_recall", r.auto_recall],
280
285
  ["top_k", r.top_k],
@@ -323,15 +328,16 @@ function agentFromFlags(flags) {
323
328
  function buildContext(args) {
324
329
  const agent = agentFromFlags(args?.flags ?? {});
325
330
  const cfg = load({ agent });
326
- if (!isComplete(cfg)) {
327
- const hint = agent === "default" ? "run `ctxdb setup --api-key=<key> --base-url=<url>` or set CTXDB_API_KEY / CTXDB_BASE_URL env vars" : `run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID env vars`;
331
+ const credential = resolveDataApiCredential(cfg);
332
+ if (!cfg.baseUrl || !credential) {
333
+ const hint = agent === "default" ? "run `ctxdb setup --api-key=<key> --base-url=<url>` or set CTXDB_ACCESS_TOKEN / CTXDB_API_KEY / CTXDB_BASE_URL env vars" : `run \`ctxdb setup --agent ${agent}\` or set CTXDB_ACCESS_TOKEN / CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID env vars`;
328
334
  process.stderr.write(`config incomplete for agent ${agent}: ${hint}
329
335
  `);
330
336
  process.exit(2);
331
337
  }
332
338
  const client = new HttpClient({
333
339
  baseUrl: cfg.baseUrl,
334
- apiKey: cfg.apiKey
340
+ credential
335
341
  });
336
342
  return { cfg, client, agent };
337
343
  }
@@ -468,11 +474,14 @@ async function runSetup(options) {
468
474
  }
469
475
  const cfg = load({ agent });
470
476
  const persistedCredentialConfig = options.persistCredential === false ? load({ agent, env: {}, managedCredentials: false }) : null;
471
- if (options.apiKey) cfg.apiKey = options.apiKey;
477
+ if (options.apiKey) {
478
+ cfg.apiKey = options.apiKey;
479
+ cfg.oauthCredential = void 0;
480
+ }
472
481
  if (options.baseUrl) cfg.baseUrl = options.baseUrl.replace(/\/+$/, "");
473
482
  else if (!cfg.baseUrl) cfg.baseUrl = DEFAULT_BASE_URL;
474
483
  cfg.userId = options.userId ?? cfg.userId ?? DEFAULT_USER_ID;
475
- if (!cfg.apiKey) {
484
+ if (!isDataApiReady(cfg)) {
476
485
  steps.push({
477
486
  step: "write-config",
478
487
  ok: false,
@@ -492,7 +501,7 @@ async function runSetup(options) {
492
501
  ok: true,
493
502
  detail: `${agent}: ${cfg.baseUrl} (user_id=${cfg.userId})`
494
503
  });
495
- if (isBuiltinAgent(agent)) {
504
+ if (isBuiltinAgent(agent) && !cfg.oauthCredential) {
496
505
  const hasDefault = configuredAgents().includes("default");
497
506
  if (!hasDefault) {
498
507
  save(persistedCfg, void 0, { agent: "default", omitApiKey });
@@ -716,7 +725,7 @@ async function runSetup(options) {
716
725
  }
717
726
  }
718
727
  if (validate) {
719
- if (!cfg.apiKey || !cfg.baseUrl) {
728
+ if (!isDataApiReady(cfg)) {
720
729
  steps.push({
721
730
  step: "validate-ping",
722
731
  ok: false,
@@ -724,7 +733,10 @@ async function runSetup(options) {
724
733
  });
725
734
  } else {
726
735
  try {
727
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
736
+ const client = new HttpClient({
737
+ baseUrl: cfg.baseUrl,
738
+ credential: resolveDataApiCredential(cfg)
739
+ });
728
740
  await client.get("/v1/ping/");
729
741
  steps.push({ step: "validate-ping", ok: true });
730
742
  } catch (err) {
@@ -811,8 +823,8 @@ async function runSetupAll(options = {}, dependencies = {}) {
811
823
  }
812
824
  for (const agent of detection.selected) {
813
825
  const existing = configSnapshot.get(agent);
814
- const apiKey = options.apiKey ?? existing.apiKey;
815
- if (!apiKey) {
826
+ const apiKey = options.apiKey ?? (existing.oauthCredential ? void 0 : existing.apiKey ?? void 0);
827
+ if (!apiKey && !isDataApiReady(existing)) {
816
828
  agents[agent] = missingApiKeyResult(agent);
817
829
  continue;
818
830
  }
@@ -2090,12 +2102,13 @@ FLAGS
2090
2102
  `;
2091
2103
  async function inspectStatus(agent) {
2092
2104
  const cfg = load({ agent });
2093
- const complete = isComplete(cfg);
2105
+ const credential = resolveDataApiCredential(cfg);
2106
+ const complete = Boolean(cfg.baseUrl && credential);
2094
2107
  let connected = false;
2095
2108
  let pingError = null;
2096
- if (complete) {
2109
+ if (complete && credential) {
2097
2110
  try {
2098
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
2111
+ const client = new HttpClient({ baseUrl: cfg.baseUrl, credential });
2099
2112
  await client.get("/v1/ping/");
2100
2113
  connected = true;
2101
2114
  } catch (err) {
@@ -2110,6 +2123,8 @@ async function inspectStatus(agent) {
2110
2123
  base_url: cfg.baseUrl,
2111
2124
  user_id: cfg.userId,
2112
2125
  api_key_set: Boolean(cfg.apiKey),
2126
+ access_token_set: Boolean(process.env.CTXDB_ACCESS_TOKEN?.trim()),
2127
+ credential_type: credential?.type ?? null,
2113
2128
  auto_capture: cfg.autoCapture,
2114
2129
  auto_recall: cfg.autoRecall,
2115
2130
  top_k: cfg.topK,
@@ -2192,17 +2207,217 @@ ${hint}
2192
2207
  async function ping(args) {
2193
2208
  const agent = agentFromFlags(args.flags);
2194
2209
  const cfg = load({ agent });
2195
- if (!isComplete(cfg)) {
2210
+ const credential = resolveDataApiCredential(cfg);
2211
+ if (!cfg.baseUrl || !credential) {
2196
2212
  fail(
2197
- `config incomplete for agent ${agent} \u2014 run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID`
2213
+ `config incomplete for agent ${agent} \u2014 run \`ctxdb setup --agent ${agent}\` or set CTXDB_ACCESS_TOKEN / CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID`
2198
2214
  );
2199
2215
  }
2200
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
2216
+ const client = new HttpClient({ baseUrl: cfg.baseUrl, credential });
2201
2217
  const resp = await client.get("/v1/ping/");
2202
2218
  printResult(resp, !!args.flags.json);
2203
2219
  return 0;
2204
2220
  }
2205
2221
 
2222
+ // src/cli/device-auth-cli.ts
2223
+ import { dirname as dirname3, join as join2 } from "path";
2224
+ import { CoreDeviceClient, normalizeCoreOrigin as normalizeCoreOrigin2, OAuthSessionStore } from "@aliyunrds/ctxdb-shared";
2225
+
2226
+ // src/lib/oauth-login-config.ts
2227
+ import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, unlinkSync as unlinkSync2 } from "fs";
2228
+ import { dirname as dirname2 } from "path";
2229
+ import { setTimeout as delay } from "timers/promises";
2230
+ import { normalizeCoreOrigin, oauthProfile } from "@aliyunrds/ctxdb-shared";
2231
+ function read(path) {
2232
+ if (!existsSync2(path)) return { version: 2, agents: {} };
2233
+ let raw;
2234
+ try {
2235
+ raw = JSON.parse(readFileSync2(path, "utf8"));
2236
+ } catch {
2237
+ throw new Error("Invalid ctxdb configuration; login did not overwrite it.");
2238
+ }
2239
+ if (!raw || raw.version !== 2 || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
2240
+ throw new Error("Unsupported ctxdb configuration; login did not overwrite it.");
2241
+ }
2242
+ return raw;
2243
+ }
2244
+ function selectedOAuthProfile(path, agent) {
2245
+ const section = read(path).agents[agent];
2246
+ return oauthProfile(section?.oauth_credential_ref, path, agent, section?.base_url);
2247
+ }
2248
+ async function commitOAuthProfile(path, agent, reference, baseUrl) {
2249
+ oauthProfile(reference, path, agent, baseUrl);
2250
+ mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
2251
+ const lock = path + ".oauth-config.lock", deadline = Date.now() + 1e4;
2252
+ let fd;
2253
+ while (true) {
2254
+ try {
2255
+ fd = openSync(lock, "wx", 384);
2256
+ break;
2257
+ } catch (error) {
2258
+ if (error.code !== "EEXIST") throw new Error("Cannot lock OAuth profile configuration.");
2259
+ if (Date.now() >= deadline) throw new Error("OAuth profile configuration is locked; recover a crashed login's config lock before retrying.");
2260
+ await delay(25);
2261
+ }
2262
+ }
2263
+ try {
2264
+ const raw = read(path), section = raw.agents[agent] ?? {};
2265
+ if (typeof section !== "object" || Array.isArray(section)) throw new Error("Invalid agent profile; login did not overwrite it.");
2266
+ const previous = oauthProfile(section.oauth_credential_ref, path, agent, section.base_url);
2267
+ raw.agents[agent] = { ...section, oauth_credential_ref: reference, base_url: normalizeCoreOrigin(baseUrl) };
2268
+ delete raw.agents[agent].api_key;
2269
+ secureAtomicWrite(path, JSON.stringify(raw, null, 2) + "\n");
2270
+ return previous;
2271
+ } finally {
2272
+ closeSync(fd);
2273
+ unlinkSync2(lock);
2274
+ }
2275
+ }
2276
+ function consoleAuthorizationBase(path, core) {
2277
+ if (!existsSync2(path)) return void 0;
2278
+ try {
2279
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
2280
+ if (raw.version !== 1 || !Array.isArray(raw.environments) || raw.environments.length > 32) throw new Error();
2281
+ const matches = raw.environments.filter((entry) => normalizeCoreOrigin(entry.core_url) === core);
2282
+ if (matches.length > 1) throw new Error();
2283
+ if (!matches.length) return void 0;
2284
+ const url = new URL(matches[0].authorization_url);
2285
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error();
2286
+ return url.toString();
2287
+ } catch {
2288
+ throw new Error("Invalid local device-environments.json. No authorization request was created.");
2289
+ }
2290
+ }
2291
+
2292
+ // src/cli/device-auth-cli.ts
2293
+ var usage = "ctxdb login [--agent <name>] [--server <core-origin>] [--device-code] [--no-browser] [--json]\n";
2294
+ function createPublicAuthCommands(dependencies = {}) {
2295
+ const env = () => dependencies.env ?? process.env;
2296
+ const stdout = dependencies.stdout ?? ((s) => {
2297
+ process.stdout.write(s);
2298
+ });
2299
+ const stderr = dependencies.stderr ?? ((s) => {
2300
+ process.stderr.write(s);
2301
+ });
2302
+ const client = dependencies.client ?? ((url) => new CoreDeviceClient(url));
2303
+ const output = (value, json) => stdout(json ? JSON.stringify(value) + "\n" : String(value) + "\n");
2304
+ function options(args, login = false) {
2305
+ const allowed = ["agent", "json", "help", "h", ...login ? ["server", "device-code", "no-browser"] : []];
2306
+ if (args.positional.length || Object.keys(args.flags).some((k) => !allowed.includes(k))) throw new Error("Unsupported Device login option.");
2307
+ for (const flag of ["json", "help", "h", "device-code", "no-browser"]) {
2308
+ if (args.flags[flag] !== void 0 && args.flags[flag] !== true) throw new Error("Invalid boolean Device login option.");
2309
+ }
2310
+ const agent = args.flags.agent ?? agentFromEnv(env());
2311
+ if (!isAgentSlug(agent)) throw new Error("Invalid agent profile.");
2312
+ const path = defaultConfigPath(env());
2313
+ return { agent, path, json: args.flags.json === true };
2314
+ }
2315
+ async function revoke(profile) {
2316
+ return new OAuthSessionStore(profile.directory).logout(profile.reference, profile.profile, client(profile.baseUrl));
2317
+ }
2318
+ return {
2319
+ async login(args) {
2320
+ if (args.flags.help || args.flags.h) {
2321
+ stdout(usage);
2322
+ return 0;
2323
+ }
2324
+ const { agent, path, json } = options(args, true);
2325
+ if (["CTXDB_API_KEY", "CTXDB_ACCESS_TOKEN", "CTXDB_BASE_URL"].some((k) => env()[k])) {
2326
+ throw new Error("Unset CTXDB_API_KEY, CTXDB_ACCESS_TOKEN and CTXDB_BASE_URL before managed login; use --server to select Core.");
2327
+ }
2328
+ if (args.flags.server !== void 0 && typeof args.flags.server !== "string") throw new Error("--server requires a Core origin.");
2329
+ const previous = selectedOAuthProfile(path, agent);
2330
+ const baseUrl = normalizeCoreOrigin2(args.flags.server ?? previous?.baseUrl ?? DEFAULT_BASE_URL);
2331
+ const consoleBase = consoleAuthorizationBase(join2(dirname3(path), "device-environments.json"), baseUrl);
2332
+ const connection = client(baseUrl), controller = new AbortController();
2333
+ const interrupt = () => controller.abort();
2334
+ process.once("SIGINT", interrupt);
2335
+ try {
2336
+ const session = await connection.login({ signal: controller.signal, onPrompt: (prompt) => {
2337
+ const url = consoleBase ? new URL(consoleBase) : void 0;
2338
+ url?.searchParams.set("user_code", prompt.userCode);
2339
+ const safe = {
2340
+ event: "device_authorization",
2341
+ user_code: prompt.userCode,
2342
+ request_id: prompt.requestId,
2343
+ expires_in: prompt.expiresIn,
2344
+ ...url ? { authorization_url: url.toString() } : {}
2345
+ };
2346
+ stderr(json ? JSON.stringify(safe) + "\n" : `User code: ${prompt.userCode}
2347
+ ${url ? url.toString() : "Console URL is not configured. Complete authorization with the independent POP test client."}
2348
+ `);
2349
+ } });
2350
+ const store = new OAuthSessionStore(join2(dirname3(path), "oauth-credentials"));
2351
+ const reference = store.saveLogin(agent, session);
2352
+ let replaced;
2353
+ try {
2354
+ replaced = await commitOAuthProfile(path, agent, reference, baseUrl);
2355
+ } catch (error) {
2356
+ await store.logout(reference, agent, connection);
2357
+ throw error;
2358
+ }
2359
+ if (replaced) {
2360
+ try {
2361
+ if (!(await revoke(replaced)).remoteRevoked) stderr("New login saved; old session could not be revoked remotely.\n");
2362
+ } catch {
2363
+ stderr("New login saved; old credential cleanup needs attention.\n");
2364
+ }
2365
+ }
2366
+ output(json ? {
2367
+ ok: true,
2368
+ agent,
2369
+ workspace_id: session.workspaceId,
2370
+ member_id: session.memberId,
2371
+ scope: session.scope,
2372
+ client_id: session.clientId,
2373
+ account_display: session.accountDisplay,
2374
+ authorization_method: "device-code"
2375
+ } : `Logged in for ${agent}: Workspace ${session.workspaceId}, Member ${session.memberId}, scope ${session.scope}. Run ctxdb setup --agent ${agent} separately.`, json);
2376
+ return 0;
2377
+ } finally {
2378
+ process.removeListener("SIGINT", interrupt);
2379
+ }
2380
+ },
2381
+ async status(args) {
2382
+ if (args.flags.help || args.flags.h) {
2383
+ stdout("ctxdb auth status [--agent <name>] [--json]\n");
2384
+ return 0;
2385
+ }
2386
+ const { agent, path, json } = options(args);
2387
+ const profile = selectedOAuthProfile(path, agent);
2388
+ const state = profile ? new OAuthSessionStore(profile.directory).status(profile.reference, agent) : { state: "missing" };
2389
+ const overridden = Boolean(env().CTXDB_ACCESS_TOKEN || env().CTXDB_API_KEY);
2390
+ const ok = state.state === "ready";
2391
+ output(json ? { ok, agent, ...state, environment_credential_override: overridden } : `${agent}: ${state.state}${overridden ? " (environment credential takes precedence for DATA requests)" : ""}`, json);
2392
+ return ok ? 0 : 1;
2393
+ },
2394
+ async logout(args) {
2395
+ if (args.flags.help || args.flags.h) {
2396
+ stdout("ctxdb logout [--agent <name>] [--json]\n");
2397
+ return 0;
2398
+ }
2399
+ const { agent, path, json } = options(args);
2400
+ const profile = selectedOAuthProfile(path, agent);
2401
+ if (!profile) {
2402
+ output(json ? { ok: true, agent, local_cleared: true, remote_revoked: false, status: "no_managed_session" } : `${agent}: no managed session.`, json);
2403
+ return 0;
2404
+ }
2405
+ const store = new OAuthSessionStore(profile.directory);
2406
+ if (store.status(profile.reference, agent).state === "logged_out") {
2407
+ output(json ? { ok: true, agent, local_cleared: true, remote_revoked: false, status: "already_logged_out" } : `${agent}: already logged out locally.`, json);
2408
+ return 0;
2409
+ }
2410
+ const result = await revoke(profile);
2411
+ output(json ? { ok: result.remoteRevoked, agent, local_cleared: true, remote_revoked: result.remoteRevoked } : `${agent}: local credential cleared; ${result.remoteRevoked ? "remote revoke completed" : "remote revoke failed or could not be confirmed"}.`, json);
2412
+ return result.remoteRevoked ? 0 : 1;
2413
+ }
2414
+ };
2415
+ }
2416
+ var commands = createPublicAuthCommands();
2417
+ var deviceLogin = commands.login;
2418
+ var deviceAuthStatus = commands.status;
2419
+ var deviceLogout = commands.logout;
2420
+
2206
2421
  // src/cli/setup-cli.ts
2207
2422
  var HELP = `ctxdb setup \u2014 configure an agent
2208
2423
 
@@ -2781,12 +2996,12 @@ function parseFileUpdate(args) {
2781
2996
  }
2782
2997
  fail(UPDATE_FILE_USAGE, 2);
2783
2998
  }
2784
- function shouldWaitForIngest(args, usage) {
2999
+ function shouldWaitForIngest(args, usage2) {
2785
3000
  const waitRequested = args.flags.wait !== void 0;
2786
3001
  const noWaitRequested = args.flags["no-wait"] !== void 0;
2787
3002
  if (waitRequested && noWaitRequested) {
2788
3003
  fail(`cannot use --wait and --no-wait together
2789
- ${usage}`, 2);
3004
+ ${usage2}`, 2);
2790
3005
  }
2791
3006
  return waitRequested;
2792
3007
  }
@@ -3396,130 +3611,130 @@ function stringFlag(args, name) {
3396
3611
  const value = args.flags[name];
3397
3612
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
3398
3613
  }
3399
- function requireIdFlag(args, name, usage) {
3614
+ function requireIdFlag(args, name, usage2) {
3400
3615
  const value = stringFlag(args, name);
3401
3616
  if (value === void 0) fail(`--${name} is required
3402
- ${usage}`, 2);
3617
+ ${usage2}`, 2);
3403
3618
  return value;
3404
3619
  }
3405
- function requireTemplateIdPositional(args, usage) {
3620
+ function requireTemplateIdPositional(args, usage2) {
3406
3621
  const templateId = args.positional[0];
3407
- if (!templateId || templateId.trim() === "") fail(usage, 2);
3622
+ if (!templateId || templateId.trim() === "") fail(usage2, 2);
3408
3623
  return templateId;
3409
3624
  }
3410
- function intFlag(args, name, usage) {
3625
+ function intFlag(args, name, usage2) {
3411
3626
  const raw = stringFlag(args, name);
3412
3627
  if (raw === void 0) return void 0;
3413
3628
  const n = Number(raw);
3414
3629
  if (!Number.isInteger(n) || n < 1) {
3415
3630
  fail(`invalid --${name}: ${raw} (expected a positive integer)
3416
- ${usage}`, 2);
3631
+ ${usage2}`, 2);
3417
3632
  }
3418
3633
  return n;
3419
3634
  }
3420
- function parseIdsFlag(args, usage) {
3635
+ function parseIdsFlag(args, usage2) {
3421
3636
  const raw = stringFlag(args, "ids");
3422
3637
  if (raw === void 0) fail(`--ids is required (comma-separated)
3423
- ${usage}`, 2);
3638
+ ${usage2}`, 2);
3424
3639
  const ids = raw.split(",").map((s) => s.trim()).filter(Boolean);
3425
3640
  if (ids.length === 0) fail(`--ids must contain at least one id
3426
- ${usage}`, 2);
3641
+ ${usage2}`, 2);
3427
3642
  if (ids.length > BATCH_MAX_ITEMS) {
3428
3643
  fail(`--ids accepts at most ${BATCH_MAX_ITEMS} items per request (got ${ids.length})
3429
- ${usage}`, 2);
3644
+ ${usage2}`, 2);
3430
3645
  }
3431
3646
  return ids;
3432
3647
  }
3433
- function parseResolutions(raw, usage) {
3648
+ function parseResolutions(raw, usage2) {
3434
3649
  let parsed;
3435
3650
  try {
3436
3651
  parsed = JSON.parse(raw);
3437
3652
  } catch {
3438
3653
  fail(`--resolutions-json is not valid JSON
3439
- ${usage}`, 2);
3654
+ ${usage2}`, 2);
3440
3655
  }
3441
3656
  if (!Array.isArray(parsed) || parsed.length === 0) {
3442
3657
  fail(`--resolutions-json must be a non-empty array
3443
- ${usage}`, 2);
3658
+ ${usage2}`, 2);
3444
3659
  }
3445
3660
  return parsed.map((entry, i) => {
3446
3661
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3447
3662
  fail(`--resolutions-json[${i}] must be an object
3448
- ${usage}`, 2);
3663
+ ${usage2}`, 2);
3449
3664
  }
3450
3665
  const e = entry;
3451
3666
  if (typeof e.index !== "number" || !Number.isInteger(e.index) || e.index < 0) {
3452
3667
  fail(`--resolutions-json[${i}].index must be a non-negative integer
3453
- ${usage}`, 2);
3668
+ ${usage2}`, 2);
3454
3669
  }
3455
3670
  if (typeof e.choice !== "string" || e.choice.trim() === "") {
3456
3671
  fail(`--resolutions-json[${i}].choice must be a non-empty string
3457
- ${usage}`, 2);
3672
+ ${usage2}`, 2);
3458
3673
  }
3459
3674
  const out = { index: e.index, choice: e.choice };
3460
3675
  if (e.comment !== void 0 && e.comment !== null) {
3461
3676
  if (typeof e.comment !== "string") {
3462
3677
  fail(`--resolutions-json[${i}].comment must be a string
3463
- ${usage}`, 2);
3678
+ ${usage2}`, 2);
3464
3679
  }
3465
3680
  out.comment = e.comment;
3466
3681
  }
3467
3682
  return out;
3468
3683
  });
3469
3684
  }
3470
- function buildTemplateUpsertBody(args, usage) {
3685
+ function buildTemplateUpsertBody(args, usage2) {
3471
3686
  const name = stringFlag(args, "name");
3472
3687
  if (name === void 0) fail(`--name is required
3473
- ${usage}`, 2);
3688
+ ${usage2}`, 2);
3474
3689
  const sectionsRaw = args.flags["sections-json"];
3475
3690
  if (typeof sectionsRaw !== "string" || sectionsRaw.trim() === "") {
3476
3691
  fail(`--sections-json is required
3477
- ${usage}`, 2);
3692
+ ${usage2}`, 2);
3478
3693
  }
3479
- const sections = parseSections(sectionsRaw, usage);
3694
+ const sections = parseSections(sectionsRaw, usage2);
3480
3695
  const body = { name, sections };
3481
3696
  if (args.flags["is-default"] === true) body.is_default = true;
3482
3697
  return body;
3483
3698
  }
3484
- function parseSections(raw, usage) {
3699
+ function parseSections(raw, usage2) {
3485
3700
  let parsed;
3486
3701
  try {
3487
3702
  parsed = JSON.parse(raw);
3488
3703
  } catch {
3489
3704
  fail(`--sections-json is not valid JSON
3490
- ${usage}`, 2);
3705
+ ${usage2}`, 2);
3491
3706
  }
3492
3707
  if (!Array.isArray(parsed) || parsed.length === 0) {
3493
3708
  fail(`--sections-json must be a non-empty array
3494
- ${usage}`, 2);
3709
+ ${usage2}`, 2);
3495
3710
  }
3496
3711
  if (parsed.length > TEMPLATE_SECTIONS_MAX) {
3497
3712
  fail(`--sections-json accepts at most ${TEMPLATE_SECTIONS_MAX} sections (got ${parsed.length})
3498
- ${usage}`, 2);
3713
+ ${usage2}`, 2);
3499
3714
  }
3500
3715
  return parsed.map((entry, i) => {
3501
3716
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3502
3717
  fail(`--sections-json[${i}] must be an object
3503
- ${usage}`, 2);
3718
+ ${usage2}`, 2);
3504
3719
  }
3505
3720
  const e = entry;
3506
3721
  if (typeof e.title !== "string" || e.title.trim() === "") {
3507
3722
  fail(`--sections-json[${i}].title must be a non-empty string
3508
- ${usage}`, 2);
3723
+ ${usage2}`, 2);
3509
3724
  }
3510
3725
  if (/[\r\n]/.test(e.title.trim())) {
3511
3726
  fail(`--sections-json[${i}].title must not contain line breaks
3512
- ${usage}`, 2);
3727
+ ${usage2}`, 2);
3513
3728
  }
3514
3729
  if (typeof e.guidance !== "string" || e.guidance.trim() === "") {
3515
3730
  fail(`--sections-json[${i}].guidance must be a non-empty string
3516
- ${usage}`, 2);
3731
+ ${usage2}`, 2);
3517
3732
  }
3518
3733
  const out = { title: e.title, guidance: e.guidance };
3519
3734
  if (e.level !== void 0 && e.level !== null) {
3520
3735
  if (typeof e.level !== "number" || !Number.isInteger(e.level) || e.level < 1 || e.level > 3) {
3521
3736
  fail(`--sections-json[${i}].level must be an integer in 1-3
3522
- ${usage}`, 2);
3737
+ ${usage2}`, 2);
3523
3738
  }
3524
3739
  out.level = e.level;
3525
3740
  }
@@ -3594,7 +3809,7 @@ function compactTemplateList(data) {
3594
3809
  }
3595
3810
 
3596
3811
  // src/cli/review-cli.ts
3597
- import { readFileSync as readFileSync2 } from "fs";
3812
+ import { readFileSync as readFileSync3 } from "fs";
3598
3813
 
3599
3814
  // src/lib/review.ts
3600
3815
  function unwrapReviewData(response, operation) {
@@ -3896,43 +4111,43 @@ function stringValue(args, name) {
3896
4111
  const value = args.flags[name];
3897
4112
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
3898
4113
  }
3899
- function requireId(args, flagName, positionalIndex, usage) {
4114
+ function requireId(args, flagName, positionalIndex, usage2) {
3900
4115
  const positional = args.positional[positionalIndex]?.trim();
3901
4116
  const value = positional !== void 0 && positional !== "" ? positional : stringValue(args, flagName);
3902
- if (value === void 0) fail(usage, 2);
4117
+ if (value === void 0) fail(usage2, 2);
3903
4118
  return value.trim();
3904
4119
  }
3905
- function requireEnumFlag(args, flagName, allowed, usage) {
4120
+ function requireEnumFlag(args, flagName, allowed, usage2) {
3906
4121
  const value = stringValue(args, flagName);
3907
- if (value === void 0) fail(usage, 2);
4122
+ if (value === void 0) fail(usage2, 2);
3908
4123
  if (!allowed.includes(value)) {
3909
4124
  fail(`${flagName} must be one of ${allowed.join("|")}
3910
- ${usage}`, 2);
4125
+ ${usage2}`, 2);
3911
4126
  }
3912
4127
  return value;
3913
4128
  }
3914
- function positiveIntFlag(args, flagName, usage) {
4129
+ function positiveIntFlag(args, flagName, usage2) {
3915
4130
  const value = args.flags[flagName];
3916
4131
  if (value === void 0) return void 0;
3917
- if (typeof value !== "string") fail(usage, 2);
4132
+ if (typeof value !== "string") fail(usage2, 2);
3918
4133
  const parsed = Number(value);
3919
4134
  if (!Number.isInteger(parsed) || parsed < 1) {
3920
4135
  fail(`${flagName} must be a positive integer
3921
- ${usage}`, 2);
4136
+ ${usage2}`, 2);
3922
4137
  }
3923
4138
  return parsed;
3924
4139
  }
3925
- function readPayloadInput(args, inlineFlag, fileFlag, usage) {
4140
+ function readPayloadInput(args, inlineFlag, fileFlag, usage2) {
3926
4141
  const inline = stringValue(args, inlineFlag);
3927
4142
  const filePath = stringValue(args, fileFlag);
3928
4143
  if (inline !== void 0 && filePath !== void 0) {
3929
4144
  fail(`cannot use --${inlineFlag} and --${fileFlag} together
3930
- ${usage}`, 2);
4145
+ ${usage2}`, 2);
3931
4146
  }
3932
4147
  if (inline !== void 0) return inline;
3933
4148
  if (filePath !== void 0) {
3934
4149
  try {
3935
- return readFileSync2(filePath, "utf-8");
4150
+ return readFileSync3(filePath, "utf-8");
3936
4151
  } catch (error) {
3937
4152
  fail(
3938
4153
  `cannot read --${fileFlag} ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
@@ -3940,17 +4155,17 @@ ${usage}`, 2);
3940
4155
  );
3941
4156
  }
3942
4157
  }
3943
- fail(usage, 2);
4158
+ fail(usage2, 2);
3944
4159
  }
3945
- function parseIdsFlag2(args, usage) {
4160
+ function parseIdsFlag2(args, usage2) {
3946
4161
  const raw = stringValue(args, "ids");
3947
- if (raw === void 0) fail(usage, 2);
4162
+ if (raw === void 0) fail(usage2, 2);
3948
4163
  const ids = parseIdList(raw);
3949
4164
  if (ids.length === 0) fail(`--ids must contain at least one id
3950
- ${usage}`, 2);
4165
+ ${usage2}`, 2);
3951
4166
  if (ids.length > BATCH_MAX_ITEMS2) {
3952
4167
  fail(`--ids accepts at most ${BATCH_MAX_ITEMS2} ids per request
3953
- ${usage}`, 2);
4168
+ ${usage2}`, 2);
3954
4169
  }
3955
4170
  return ids;
3956
4171
  }
@@ -4034,7 +4249,7 @@ ${REVIEW_ACTION_USAGE}`, 2);
4034
4249
  if (editedContent !== void 0) body.edited_content = editedContent;
4035
4250
  else if (editedContentFile !== void 0) {
4036
4251
  try {
4037
- body.edited_content = readFileSync2(editedContentFile, "utf-8");
4252
+ body.edited_content = readFileSync3(editedContentFile, "utf-8");
4038
4253
  } catch (error) {
4039
4254
  fail(
4040
4255
  `cannot read --edited-content-file ${editedContentFile}: ${error instanceof Error ? error.message : String(error)}`,
@@ -4115,28 +4330,28 @@ async function reviewTemplateList(args) {
4115
4330
  printResult(out, !!args.flags.json);
4116
4331
  return 0;
4117
4332
  }
4118
- function buildTemplateUpsertBody2(args, usage, requireNameAndSections) {
4333
+ function buildTemplateUpsertBody2(args, usage2, requireNameAndSections) {
4119
4334
  const body = {};
4120
4335
  const name = stringValue(args, "name");
4121
4336
  if (name !== void 0) {
4122
4337
  if (name.length > TEMPLATE_MAX_NAME_LENGTH) {
4123
4338
  fail(`--name must be at most ${TEMPLATE_MAX_NAME_LENGTH} characters
4124
- ${usage}`, 2);
4339
+ ${usage2}`, 2);
4125
4340
  }
4126
4341
  body.name = name;
4127
4342
  }
4128
4343
  const hasSectionsInput = Object.hasOwn(args.flags, "sections") || Object.hasOwn(args.flags, "sections-file");
4129
4344
  if (hasSectionsInput) {
4130
- const raw = readPayloadInput(args, "sections", "sections-file", usage);
4345
+ const raw = readPayloadInput(args, "sections", "sections-file", usage2);
4131
4346
  try {
4132
4347
  body.sections = parseTemplateSections(raw);
4133
4348
  } catch (error) {
4134
4349
  fail(`${error instanceof Error ? error.message : String(error)}
4135
- ${usage}`, 2);
4350
+ ${usage2}`, 2);
4136
4351
  }
4137
4352
  }
4138
4353
  if (requireNameAndSections && (body.name === void 0 || body.sections === void 0)) {
4139
- fail(usage, 2);
4354
+ fail(usage2, 2);
4140
4355
  }
4141
4356
  return body;
4142
4357
  }
@@ -4299,7 +4514,12 @@ USAGE
4299
4514
  ctxdb <command> [args] [--flags]
4300
4515
 
4301
4516
  COMMANDS
4302
- setup [--agent <${SUPPORTED_AGENT_CHOICES}> | --all]
4517
+ ${DISTRIBUTION_MANIFEST.capabilities.deviceFlow ? ` login [--agent <name>] [--scope read_only|read_write|default] [--server <core-origin>]
4518
+ [--device-code] [--no-browser] [--json]
4519
+ Device Flow login only; run setup separately.
4520
+ auth status [--agent <name>] [--json]
4521
+ logout [--agent <name>] [--json]
4522
+ ` : ""} setup [--agent <${SUPPORTED_AGENT_CHOICES}> | --all]
4303
4523
  [--api-key=K] [--base-url=URL] [--user-id=ID]
4304
4524
  [--no-install-skill] [--no-validate] [--json]
4305
4525
  Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
@@ -4474,6 +4694,7 @@ AGENT RESOLUTION (when --agent is omitted):
4474
4694
  ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
4475
4695
  CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
4476
4696
  CTXDB_AGENT_ID CTXDB_APP_ID
4697
+ CTXDB_ACCESS_TOKEN Runtime-only Data API Bearer credential; takes precedence over API key
4477
4698
 
4478
4699
  UPDATE CHECK ENV VARS:
4479
4700
  CTXDB_DISABLE_UPDATE_CHECK=1
@@ -4487,6 +4708,7 @@ CONFIG: ~/.ctxdb/ctxdb.json (agents.default / ${SUPPORTED_AGENTS.map((agent) =>
4487
4708
  LOGS: ~/.ctxdb/logs/ctxdb.log, ~/.ctxdb/logs/recall-trace.jsonl
4488
4709
  `;
4489
4710
  var ROUTES = {
4711
+ ...DISTRIBUTION_MANIFEST.capabilities.deviceFlow ? { login: deviceLogin, logout: deviceLogout, auth: { status: deviceAuthStatus } } : {},
4490
4712
  setup,
4491
4713
  status,
4492
4714
  ping,
@@ -4591,14 +4813,14 @@ async function dispatch(argv, routes = ROUTES, help = HELP5) {
4591
4813
  return dispatchGroup([first], route, rest);
4592
4814
  }
4593
4815
  async function dispatchGroup(path, group, argv) {
4594
- const usage = `usage: ctxdb ${path.join(" ")} <${Object.keys(group).join("|")}> ...
4816
+ const usage2 = `usage: ctxdb ${path.join(" ")} <${Object.keys(group).join("|")}> ...
4595
4817
  `;
4596
4818
  if (argv.length === 0) {
4597
- process.stderr.write(usage);
4819
+ process.stderr.write(usage2);
4598
4820
  return 2;
4599
4821
  }
4600
4822
  if (argv[0] === "-h" || argv[0] === "--help") {
4601
- process.stdout.write(usage);
4823
+ process.stdout.write(usage2);
4602
4824
  return 0;
4603
4825
  }
4604
4826
  const [sub, ...subRest] = argv;