@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/dist/cli/main.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  uploadFile,
17
17
  uploadText,
18
18
  validateLocalFile
19
- } from "../chunk-3NJ37TEY.js";
19
+ } from "../chunk-ZW7YXNJ6.js";
20
20
  import {
21
21
  AGENT_CHOICES,
22
22
  CtxdbError,
@@ -31,29 +31,31 @@ import {
31
31
  agentHomeDir,
32
32
  agentHomeDirs,
33
33
  agentPlatformSupport,
34
- apiKeyCredential,
35
34
  configuredAgents,
35
+ defaultConfigPath,
36
36
  detectInstalledAgents,
37
37
  hasConfiguredAgent,
38
38
  inspectAgentHomes,
39
39
  isAgentSlug,
40
40
  isBuiltinAgent,
41
+ isDataApiReady,
41
42
  load,
42
43
  removeAgent,
43
44
  resolveDataApiCredential,
44
45
  save,
46
+ secureAtomicWrite,
45
47
  updateConfiguredDebug,
46
48
  writeInstalledPkgVersion
47
- } from "../chunk-BUK4SZC2.js";
49
+ } from "../chunk-SVO6H62S.js";
48
50
  import {
49
51
  beginUpdateNotification,
50
52
  completeUpdateNotification,
51
53
  runSelfUpdate
52
- } from "../chunk-ULAWTBBC.js";
54
+ } from "../chunk-OEZM2ZYA.js";
53
55
  import {
54
56
  DISTRIBUTION_MANIFEST
55
- } from "../chunk-R67JELM7.js";
56
- import "../chunk-VIG4SYLU.js";
57
+ } from "../chunk-5ITT5GSP.js";
58
+ import "../chunk-PI2SUS3M.js";
57
59
 
58
60
  // src/cli/util.ts
59
61
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -472,11 +474,14 @@ async function runSetup(options) {
472
474
  }
473
475
  const cfg = load({ agent });
474
476
  const persistedCredentialConfig = options.persistCredential === false ? load({ agent, env: {}, managedCredentials: false }) : null;
475
- if (options.apiKey) cfg.apiKey = options.apiKey;
477
+ if (options.apiKey) {
478
+ cfg.apiKey = options.apiKey;
479
+ cfg.oauthCredential = void 0;
480
+ }
476
481
  if (options.baseUrl) cfg.baseUrl = options.baseUrl.replace(/\/+$/, "");
477
482
  else if (!cfg.baseUrl) cfg.baseUrl = DEFAULT_BASE_URL;
478
483
  cfg.userId = options.userId ?? cfg.userId ?? DEFAULT_USER_ID;
479
- if (!cfg.apiKey) {
484
+ if (!isDataApiReady(cfg)) {
480
485
  steps.push({
481
486
  step: "write-config",
482
487
  ok: false,
@@ -496,7 +501,7 @@ async function runSetup(options) {
496
501
  ok: true,
497
502
  detail: `${agent}: ${cfg.baseUrl} (user_id=${cfg.userId})`
498
503
  });
499
- if (isBuiltinAgent(agent)) {
504
+ if (isBuiltinAgent(agent) && !cfg.oauthCredential) {
500
505
  const hasDefault = configuredAgents().includes("default");
501
506
  if (!hasDefault) {
502
507
  save(persistedCfg, void 0, { agent: "default", omitApiKey });
@@ -720,7 +725,7 @@ async function runSetup(options) {
720
725
  }
721
726
  }
722
727
  if (validate) {
723
- if (!cfg.apiKey || !cfg.baseUrl) {
728
+ if (!isDataApiReady(cfg)) {
724
729
  steps.push({
725
730
  step: "validate-ping",
726
731
  ok: false,
@@ -730,7 +735,7 @@ async function runSetup(options) {
730
735
  try {
731
736
  const client = new HttpClient({
732
737
  baseUrl: cfg.baseUrl,
733
- credential: apiKeyCredential(cfg.apiKey)
738
+ credential: resolveDataApiCredential(cfg)
734
739
  });
735
740
  await client.get("/v1/ping/");
736
741
  steps.push({ step: "validate-ping", ok: true });
@@ -818,8 +823,8 @@ async function runSetupAll(options = {}, dependencies = {}) {
818
823
  }
819
824
  for (const agent of detection.selected) {
820
825
  const existing = configSnapshot.get(agent);
821
- const apiKey = options.apiKey ?? existing.apiKey;
822
- if (!apiKey) {
826
+ const apiKey = options.apiKey ?? (existing.oauthCredential ? void 0 : existing.apiKey ?? void 0);
827
+ if (!apiKey && !isDataApiReady(existing)) {
823
828
  agents[agent] = missingApiKeyResult(agent);
824
829
  continue;
825
830
  }
@@ -2214,6 +2219,205 @@ async function ping(args) {
2214
2219
  return 0;
2215
2220
  }
2216
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
+
2217
2421
  // src/cli/setup-cli.ts
2218
2422
  var HELP = `ctxdb setup \u2014 configure an agent
2219
2423
 
@@ -2792,12 +2996,12 @@ function parseFileUpdate(args) {
2792
2996
  }
2793
2997
  fail(UPDATE_FILE_USAGE, 2);
2794
2998
  }
2795
- function shouldWaitForIngest(args, usage) {
2999
+ function shouldWaitForIngest(args, usage2) {
2796
3000
  const waitRequested = args.flags.wait !== void 0;
2797
3001
  const noWaitRequested = args.flags["no-wait"] !== void 0;
2798
3002
  if (waitRequested && noWaitRequested) {
2799
3003
  fail(`cannot use --wait and --no-wait together
2800
- ${usage}`, 2);
3004
+ ${usage2}`, 2);
2801
3005
  }
2802
3006
  return waitRequested;
2803
3007
  }
@@ -3407,130 +3611,130 @@ function stringFlag(args, name) {
3407
3611
  const value = args.flags[name];
3408
3612
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
3409
3613
  }
3410
- function requireIdFlag(args, name, usage) {
3614
+ function requireIdFlag(args, name, usage2) {
3411
3615
  const value = stringFlag(args, name);
3412
3616
  if (value === void 0) fail(`--${name} is required
3413
- ${usage}`, 2);
3617
+ ${usage2}`, 2);
3414
3618
  return value;
3415
3619
  }
3416
- function requireTemplateIdPositional(args, usage) {
3620
+ function requireTemplateIdPositional(args, usage2) {
3417
3621
  const templateId = args.positional[0];
3418
- if (!templateId || templateId.trim() === "") fail(usage, 2);
3622
+ if (!templateId || templateId.trim() === "") fail(usage2, 2);
3419
3623
  return templateId;
3420
3624
  }
3421
- function intFlag(args, name, usage) {
3625
+ function intFlag(args, name, usage2) {
3422
3626
  const raw = stringFlag(args, name);
3423
3627
  if (raw === void 0) return void 0;
3424
3628
  const n = Number(raw);
3425
3629
  if (!Number.isInteger(n) || n < 1) {
3426
3630
  fail(`invalid --${name}: ${raw} (expected a positive integer)
3427
- ${usage}`, 2);
3631
+ ${usage2}`, 2);
3428
3632
  }
3429
3633
  return n;
3430
3634
  }
3431
- function parseIdsFlag(args, usage) {
3635
+ function parseIdsFlag(args, usage2) {
3432
3636
  const raw = stringFlag(args, "ids");
3433
3637
  if (raw === void 0) fail(`--ids is required (comma-separated)
3434
- ${usage}`, 2);
3638
+ ${usage2}`, 2);
3435
3639
  const ids = raw.split(",").map((s) => s.trim()).filter(Boolean);
3436
3640
  if (ids.length === 0) fail(`--ids must contain at least one id
3437
- ${usage}`, 2);
3641
+ ${usage2}`, 2);
3438
3642
  if (ids.length > BATCH_MAX_ITEMS) {
3439
3643
  fail(`--ids accepts at most ${BATCH_MAX_ITEMS} items per request (got ${ids.length})
3440
- ${usage}`, 2);
3644
+ ${usage2}`, 2);
3441
3645
  }
3442
3646
  return ids;
3443
3647
  }
3444
- function parseResolutions(raw, usage) {
3648
+ function parseResolutions(raw, usage2) {
3445
3649
  let parsed;
3446
3650
  try {
3447
3651
  parsed = JSON.parse(raw);
3448
3652
  } catch {
3449
3653
  fail(`--resolutions-json is not valid JSON
3450
- ${usage}`, 2);
3654
+ ${usage2}`, 2);
3451
3655
  }
3452
3656
  if (!Array.isArray(parsed) || parsed.length === 0) {
3453
3657
  fail(`--resolutions-json must be a non-empty array
3454
- ${usage}`, 2);
3658
+ ${usage2}`, 2);
3455
3659
  }
3456
3660
  return parsed.map((entry, i) => {
3457
3661
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3458
3662
  fail(`--resolutions-json[${i}] must be an object
3459
- ${usage}`, 2);
3663
+ ${usage2}`, 2);
3460
3664
  }
3461
3665
  const e = entry;
3462
3666
  if (typeof e.index !== "number" || !Number.isInteger(e.index) || e.index < 0) {
3463
3667
  fail(`--resolutions-json[${i}].index must be a non-negative integer
3464
- ${usage}`, 2);
3668
+ ${usage2}`, 2);
3465
3669
  }
3466
3670
  if (typeof e.choice !== "string" || e.choice.trim() === "") {
3467
3671
  fail(`--resolutions-json[${i}].choice must be a non-empty string
3468
- ${usage}`, 2);
3672
+ ${usage2}`, 2);
3469
3673
  }
3470
3674
  const out = { index: e.index, choice: e.choice };
3471
3675
  if (e.comment !== void 0 && e.comment !== null) {
3472
3676
  if (typeof e.comment !== "string") {
3473
3677
  fail(`--resolutions-json[${i}].comment must be a string
3474
- ${usage}`, 2);
3678
+ ${usage2}`, 2);
3475
3679
  }
3476
3680
  out.comment = e.comment;
3477
3681
  }
3478
3682
  return out;
3479
3683
  });
3480
3684
  }
3481
- function buildTemplateUpsertBody(args, usage) {
3685
+ function buildTemplateUpsertBody(args, usage2) {
3482
3686
  const name = stringFlag(args, "name");
3483
3687
  if (name === void 0) fail(`--name is required
3484
- ${usage}`, 2);
3688
+ ${usage2}`, 2);
3485
3689
  const sectionsRaw = args.flags["sections-json"];
3486
3690
  if (typeof sectionsRaw !== "string" || sectionsRaw.trim() === "") {
3487
3691
  fail(`--sections-json is required
3488
- ${usage}`, 2);
3692
+ ${usage2}`, 2);
3489
3693
  }
3490
- const sections = parseSections(sectionsRaw, usage);
3694
+ const sections = parseSections(sectionsRaw, usage2);
3491
3695
  const body = { name, sections };
3492
3696
  if (args.flags["is-default"] === true) body.is_default = true;
3493
3697
  return body;
3494
3698
  }
3495
- function parseSections(raw, usage) {
3699
+ function parseSections(raw, usage2) {
3496
3700
  let parsed;
3497
3701
  try {
3498
3702
  parsed = JSON.parse(raw);
3499
3703
  } catch {
3500
3704
  fail(`--sections-json is not valid JSON
3501
- ${usage}`, 2);
3705
+ ${usage2}`, 2);
3502
3706
  }
3503
3707
  if (!Array.isArray(parsed) || parsed.length === 0) {
3504
3708
  fail(`--sections-json must be a non-empty array
3505
- ${usage}`, 2);
3709
+ ${usage2}`, 2);
3506
3710
  }
3507
3711
  if (parsed.length > TEMPLATE_SECTIONS_MAX) {
3508
3712
  fail(`--sections-json accepts at most ${TEMPLATE_SECTIONS_MAX} sections (got ${parsed.length})
3509
- ${usage}`, 2);
3713
+ ${usage2}`, 2);
3510
3714
  }
3511
3715
  return parsed.map((entry, i) => {
3512
3716
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3513
3717
  fail(`--sections-json[${i}] must be an object
3514
- ${usage}`, 2);
3718
+ ${usage2}`, 2);
3515
3719
  }
3516
3720
  const e = entry;
3517
3721
  if (typeof e.title !== "string" || e.title.trim() === "") {
3518
3722
  fail(`--sections-json[${i}].title must be a non-empty string
3519
- ${usage}`, 2);
3723
+ ${usage2}`, 2);
3520
3724
  }
3521
3725
  if (/[\r\n]/.test(e.title.trim())) {
3522
3726
  fail(`--sections-json[${i}].title must not contain line breaks
3523
- ${usage}`, 2);
3727
+ ${usage2}`, 2);
3524
3728
  }
3525
3729
  if (typeof e.guidance !== "string" || e.guidance.trim() === "") {
3526
3730
  fail(`--sections-json[${i}].guidance must be a non-empty string
3527
- ${usage}`, 2);
3731
+ ${usage2}`, 2);
3528
3732
  }
3529
3733
  const out = { title: e.title, guidance: e.guidance };
3530
3734
  if (e.level !== void 0 && e.level !== null) {
3531
3735
  if (typeof e.level !== "number" || !Number.isInteger(e.level) || e.level < 1 || e.level > 3) {
3532
3736
  fail(`--sections-json[${i}].level must be an integer in 1-3
3533
- ${usage}`, 2);
3737
+ ${usage2}`, 2);
3534
3738
  }
3535
3739
  out.level = e.level;
3536
3740
  }
@@ -3605,7 +3809,7 @@ function compactTemplateList(data) {
3605
3809
  }
3606
3810
 
3607
3811
  // src/cli/review-cli.ts
3608
- import { readFileSync as readFileSync2 } from "fs";
3812
+ import { readFileSync as readFileSync3 } from "fs";
3609
3813
 
3610
3814
  // src/lib/review.ts
3611
3815
  function unwrapReviewData(response, operation) {
@@ -3907,43 +4111,43 @@ function stringValue(args, name) {
3907
4111
  const value = args.flags[name];
3908
4112
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
3909
4113
  }
3910
- function requireId(args, flagName, positionalIndex, usage) {
4114
+ function requireId(args, flagName, positionalIndex, usage2) {
3911
4115
  const positional = args.positional[positionalIndex]?.trim();
3912
4116
  const value = positional !== void 0 && positional !== "" ? positional : stringValue(args, flagName);
3913
- if (value === void 0) fail(usage, 2);
4117
+ if (value === void 0) fail(usage2, 2);
3914
4118
  return value.trim();
3915
4119
  }
3916
- function requireEnumFlag(args, flagName, allowed, usage) {
4120
+ function requireEnumFlag(args, flagName, allowed, usage2) {
3917
4121
  const value = stringValue(args, flagName);
3918
- if (value === void 0) fail(usage, 2);
4122
+ if (value === void 0) fail(usage2, 2);
3919
4123
  if (!allowed.includes(value)) {
3920
4124
  fail(`${flagName} must be one of ${allowed.join("|")}
3921
- ${usage}`, 2);
4125
+ ${usage2}`, 2);
3922
4126
  }
3923
4127
  return value;
3924
4128
  }
3925
- function positiveIntFlag(args, flagName, usage) {
4129
+ function positiveIntFlag(args, flagName, usage2) {
3926
4130
  const value = args.flags[flagName];
3927
4131
  if (value === void 0) return void 0;
3928
- if (typeof value !== "string") fail(usage, 2);
4132
+ if (typeof value !== "string") fail(usage2, 2);
3929
4133
  const parsed = Number(value);
3930
4134
  if (!Number.isInteger(parsed) || parsed < 1) {
3931
4135
  fail(`${flagName} must be a positive integer
3932
- ${usage}`, 2);
4136
+ ${usage2}`, 2);
3933
4137
  }
3934
4138
  return parsed;
3935
4139
  }
3936
- function readPayloadInput(args, inlineFlag, fileFlag, usage) {
4140
+ function readPayloadInput(args, inlineFlag, fileFlag, usage2) {
3937
4141
  const inline = stringValue(args, inlineFlag);
3938
4142
  const filePath = stringValue(args, fileFlag);
3939
4143
  if (inline !== void 0 && filePath !== void 0) {
3940
4144
  fail(`cannot use --${inlineFlag} and --${fileFlag} together
3941
- ${usage}`, 2);
4145
+ ${usage2}`, 2);
3942
4146
  }
3943
4147
  if (inline !== void 0) return inline;
3944
4148
  if (filePath !== void 0) {
3945
4149
  try {
3946
- return readFileSync2(filePath, "utf-8");
4150
+ return readFileSync3(filePath, "utf-8");
3947
4151
  } catch (error) {
3948
4152
  fail(
3949
4153
  `cannot read --${fileFlag} ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
@@ -3951,17 +4155,17 @@ ${usage}`, 2);
3951
4155
  );
3952
4156
  }
3953
4157
  }
3954
- fail(usage, 2);
4158
+ fail(usage2, 2);
3955
4159
  }
3956
- function parseIdsFlag2(args, usage) {
4160
+ function parseIdsFlag2(args, usage2) {
3957
4161
  const raw = stringValue(args, "ids");
3958
- if (raw === void 0) fail(usage, 2);
4162
+ if (raw === void 0) fail(usage2, 2);
3959
4163
  const ids = parseIdList(raw);
3960
4164
  if (ids.length === 0) fail(`--ids must contain at least one id
3961
- ${usage}`, 2);
4165
+ ${usage2}`, 2);
3962
4166
  if (ids.length > BATCH_MAX_ITEMS2) {
3963
4167
  fail(`--ids accepts at most ${BATCH_MAX_ITEMS2} ids per request
3964
- ${usage}`, 2);
4168
+ ${usage2}`, 2);
3965
4169
  }
3966
4170
  return ids;
3967
4171
  }
@@ -4045,7 +4249,7 @@ ${REVIEW_ACTION_USAGE}`, 2);
4045
4249
  if (editedContent !== void 0) body.edited_content = editedContent;
4046
4250
  else if (editedContentFile !== void 0) {
4047
4251
  try {
4048
- body.edited_content = readFileSync2(editedContentFile, "utf-8");
4252
+ body.edited_content = readFileSync3(editedContentFile, "utf-8");
4049
4253
  } catch (error) {
4050
4254
  fail(
4051
4255
  `cannot read --edited-content-file ${editedContentFile}: ${error instanceof Error ? error.message : String(error)}`,
@@ -4126,28 +4330,28 @@ async function reviewTemplateList(args) {
4126
4330
  printResult(out, !!args.flags.json);
4127
4331
  return 0;
4128
4332
  }
4129
- function buildTemplateUpsertBody2(args, usage, requireNameAndSections) {
4333
+ function buildTemplateUpsertBody2(args, usage2, requireNameAndSections) {
4130
4334
  const body = {};
4131
4335
  const name = stringValue(args, "name");
4132
4336
  if (name !== void 0) {
4133
4337
  if (name.length > TEMPLATE_MAX_NAME_LENGTH) {
4134
4338
  fail(`--name must be at most ${TEMPLATE_MAX_NAME_LENGTH} characters
4135
- ${usage}`, 2);
4339
+ ${usage2}`, 2);
4136
4340
  }
4137
4341
  body.name = name;
4138
4342
  }
4139
4343
  const hasSectionsInput = Object.hasOwn(args.flags, "sections") || Object.hasOwn(args.flags, "sections-file");
4140
4344
  if (hasSectionsInput) {
4141
- const raw = readPayloadInput(args, "sections", "sections-file", usage);
4345
+ const raw = readPayloadInput(args, "sections", "sections-file", usage2);
4142
4346
  try {
4143
4347
  body.sections = parseTemplateSections(raw);
4144
4348
  } catch (error) {
4145
4349
  fail(`${error instanceof Error ? error.message : String(error)}
4146
- ${usage}`, 2);
4350
+ ${usage2}`, 2);
4147
4351
  }
4148
4352
  }
4149
4353
  if (requireNameAndSections && (body.name === void 0 || body.sections === void 0)) {
4150
- fail(usage, 2);
4354
+ fail(usage2, 2);
4151
4355
  }
4152
4356
  return body;
4153
4357
  }
@@ -4310,7 +4514,12 @@ USAGE
4310
4514
  ctxdb <command> [args] [--flags]
4311
4515
 
4312
4516
  COMMANDS
4313
- 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]
4314
4523
  [--api-key=K] [--base-url=URL] [--user-id=ID]
4315
4524
  [--no-install-skill] [--no-validate] [--json]
4316
4525
  Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
@@ -4499,6 +4708,7 @@ CONFIG: ~/.ctxdb/ctxdb.json (agents.default / ${SUPPORTED_AGENTS.map((agent) =>
4499
4708
  LOGS: ~/.ctxdb/logs/ctxdb.log, ~/.ctxdb/logs/recall-trace.jsonl
4500
4709
  `;
4501
4710
  var ROUTES = {
4711
+ ...DISTRIBUTION_MANIFEST.capabilities.deviceFlow ? { login: deviceLogin, logout: deviceLogout, auth: { status: deviceAuthStatus } } : {},
4502
4712
  setup,
4503
4713
  status,
4504
4714
  ping,
@@ -4603,14 +4813,14 @@ async function dispatch(argv, routes = ROUTES, help = HELP5) {
4603
4813
  return dispatchGroup([first], route, rest);
4604
4814
  }
4605
4815
  async function dispatchGroup(path, group, argv) {
4606
- const usage = `usage: ctxdb ${path.join(" ")} <${Object.keys(group).join("|")}> ...
4816
+ const usage2 = `usage: ctxdb ${path.join(" ")} <${Object.keys(group).join("|")}> ...
4607
4817
  `;
4608
4818
  if (argv.length === 0) {
4609
- process.stderr.write(usage);
4819
+ process.stderr.write(usage2);
4610
4820
  return 2;
4611
4821
  }
4612
4822
  if (argv[0] === "-h" || argv[0] === "--help") {
4613
- process.stdout.write(usage);
4823
+ process.stdout.write(usage2);
4614
4824
  return 0;
4615
4825
  }
4616
4826
  const [sub, ...subRest] = argv;
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  captureParsedMessages
4
- } from "../chunk-ZFS6OMWE.js";
5
- import "../chunk-EI63DQX3.js";
4
+ } from "../chunk-76AXXLVE.js";
5
+ import "../chunk-5DGRNP55.js";
6
6
  import {
7
7
  HttpClient,
8
8
  agentFromArgvWithFallback,
@@ -10,12 +10,12 @@ import {
10
10
  load,
11
11
  resolveDataApiCredential,
12
12
  setDebug
13
- } from "../chunk-BUK4SZC2.js";
13
+ } from "../chunk-SVO6H62S.js";
14
14
  import {
15
15
  shouldSkipHooks
16
16
  } from "../chunk-5ML4VSXN.js";
17
- import "../chunk-R67JELM7.js";
18
- import "../chunk-VIG4SYLU.js";
17
+ import "../chunk-5ITT5GSP.js";
18
+ import "../chunk-PI2SUS3M.js";
19
19
 
20
20
  // src/hooks/hermes-post-llm-call.ts
21
21
  import { pathToFileURL } from "url";