@datarails-ai/dr-cli 1.6.140 → 1.6.142

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
@@ -300,6 +300,29 @@ interactively instead. An API-key file is different: see below.
300
300
  > the operator's own login expires. Use `--as <operator-email>` to pick the
301
301
  > operator when several are stored, or `--user-id <n>` to skip the lookup.
302
302
 
303
+ ### Tenant pin (one server, one account)
304
+
305
+ A launcher that opens each customer in its own session can confine `dr` to that
306
+ customer through the environment. With none of these set, `dr` behaves as usual.
307
+ A variable that is set but empty still counts as set, so leave unused ones unset.
308
+
309
+ ```
310
+ DR_PIN_SERVER=<key or URL> # required whenever any DR_PIN_* variable is set
311
+ DR_PIN_ACCOUNT=<email> # the login email; required unless org mode
312
+ DR_PIN_ORG_MODE=1 # server only, no account, read-only commands
313
+ ```
314
+
315
+ While a pin is set, `dr` refuses: a `--server` / `--account` that differs from
316
+ the pin (compared after canonicalising, so `-sX`, `--server=X` and the URL form
317
+ are covered); a missing `--server`, or a missing `--account` in customer mode;
318
+ `dr logout`, and `dr login-as` to anyone but the pinned account (or with
319
+ `--user-id`); a host override (`--app-space-host`, `DR_APP_SPACE_HOST`,
320
+ `--connectors-url`); in org mode, any command the scopes map does not prove
321
+ read-only, plus the few read-scoped commands with side effects
322
+ (`devices request-logs`, `desk smtp test`, `dashboards prepare`,
323
+ `storyboards prepare`). A pin that is set but malformed refuses every command
324
+ (`--help` and `--version` still print). A refusal exits 3, like a scope denial.
325
+
303
326
  ### API keys (headless and CI)
304
327
 
305
328
  A user-bound API key is a durable credential `dr` exchanges itself for a
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@datarails-ai/dr-cli",
11
- version: "1.6.140",
11
+ version: "1.6.142",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -40,7 +40,7 @@ var package_default = {
40
40
  postinstall: "node scripts/postinstall.mjs"
41
41
  },
42
42
  dependencies: {
43
- "@datarails-ai/dr-appspace-cli": "0.12.14",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.15",
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "@napi-rs/keyring": "^1.3.0",
46
46
  commander: "^12.1.0",
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-16T10:39:49.051Z";
72
+ var BUILD_TIME = "2026-10-19T11:06:59.748Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -4282,6 +4282,575 @@ function isENOENT(err) {
4282
4282
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
4283
4283
  }
4284
4284
 
4285
+ // src/scopes/matcher.ts
4286
+ var SCOPE_RE = /^(\*|[a-z][a-z0-9-]*):(\*|read|write|destructive)$/;
4287
+ var ACTION_RANK = new Map(
4288
+ ["read", "write", "destructive"].map((action, rank) => [action, rank])
4289
+ );
4290
+ function actionSatisfies(granted, wanted) {
4291
+ if (granted === "*" || granted === wanted) return true;
4292
+ const g = ACTION_RANK.get(granted);
4293
+ const w = ACTION_RANK.get(wanted);
4294
+ if (g === void 0 || w === void 0) return false;
4295
+ return g > w;
4296
+ }
4297
+ var WHITESPACE = "\\t\\n\\v\\f\\r \\x1c-\\x1f\\x85\\xa0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff";
4298
+ var TRIM_RE = new RegExp(`^[${WHITESPACE}]+|[${WHITESPACE}]+$`, "g");
4299
+ var SPLIT_RE = new RegExp(`[${WHITESPACE}]+`);
4300
+ function trimShared(value) {
4301
+ return value.replace(TRIM_RE, "");
4302
+ }
4303
+ function splitScopeString(value) {
4304
+ return value.split(SPLIT_RE).filter((part) => part !== "");
4305
+ }
4306
+ function parseScope(value) {
4307
+ if (typeof value !== "string") return null;
4308
+ const m = SCOPE_RE.exec(trimShared(value).toLowerCase());
4309
+ return m ? [m[1], m[2]] : null;
4310
+ }
4311
+ function parsed(scopes) {
4312
+ const list28 = scopes ?? [];
4313
+ const entries = typeof list28 === "string" ? splitScopeString(list28) : Array.isArray(list28) ? list28 : [];
4314
+ return entries.map(parseScope).filter((p) => p !== null);
4315
+ }
4316
+ function scopeEntries(scopes) {
4317
+ return parsed(scopes);
4318
+ }
4319
+ function isAgentScoped(scopes) {
4320
+ return parsed(scopes).length > 0;
4321
+ }
4322
+ function isAllowed(scopes, resource, action) {
4323
+ const entries = parsed(scopes);
4324
+ if (entries.length === 0) return true;
4325
+ const res = trimShared(resource).toLowerCase();
4326
+ const act = trimShared(action).toLowerCase();
4327
+ return entries.some(([r, a]) => (r === "*" || r === res) && actionSatisfies(a, act));
4328
+ }
4329
+ function isEntryAllowed(scopes, entry) {
4330
+ return entry.requires.every((r) => isAllowed(scopes, r.resource, r.action));
4331
+ }
4332
+
4333
+ // src/scopes/scopesMap.ts
4334
+ import { readFileSync } from "fs";
4335
+ import * as path3 from "path";
4336
+ import { fileURLToPath } from "url";
4337
+ function isDeniedEntry(entry) {
4338
+ return entry !== void 0 && Object.prototype.hasOwnProperty.call(entry, "denied");
4339
+ }
4340
+ var ACTIONS = /* @__PURE__ */ new Set(["read", "write", "destructive"]);
4341
+ var RESOURCE_RE = /^[a-z][a-z0-9-]*$/;
4342
+ function isValidResourceName(v) {
4343
+ return RESOURCE_RE.test(v);
4344
+ }
4345
+ function isAction(v) {
4346
+ return typeof v === "string" && ACTIONS.has(v);
4347
+ }
4348
+ function isResource(v) {
4349
+ return typeof v === "string" && isValidResourceName(v);
4350
+ }
4351
+ function parseRequirement(v) {
4352
+ if (typeof v !== "object" || v === null) return null;
4353
+ const o = v;
4354
+ if (!isResource(o.resource) || !isAction(o.action)) return null;
4355
+ return { resource: o.resource, action: o.action };
4356
+ }
4357
+ function parseDenial(v) {
4358
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return null;
4359
+ const o = v;
4360
+ if (typeof o.reason !== "string" || o.reason.trim() === "") return null;
4361
+ if (typeof o.until !== "string" || o.until.trim() === "") return null;
4362
+ return { reason: o.reason, until: o.until };
4363
+ }
4364
+ function parseEntry(v) {
4365
+ if (typeof v !== "object" || v === null) return null;
4366
+ const o = v;
4367
+ if (Object.prototype.hasOwnProperty.call(o, "denied")) {
4368
+ if (Object.prototype.hasOwnProperty.call(o, "requires")) return null;
4369
+ const denied = parseDenial(o.denied);
4370
+ if (denied === null) return null;
4371
+ return { denied };
4372
+ }
4373
+ if (!Array.isArray(o.requires)) return null;
4374
+ const requires = [];
4375
+ for (const raw of o.requires) {
4376
+ const req = parseRequirement(raw);
4377
+ if (req === null) return null;
4378
+ requires.push(req);
4379
+ }
4380
+ return { requires };
4381
+ }
4382
+ function emptyTable() {
4383
+ return /* @__PURE__ */ Object.create(null);
4384
+ }
4385
+ function parseStringMap(v) {
4386
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
4387
+ const out = emptyTable();
4388
+ for (const [k, val] of Object.entries(v)) {
4389
+ if (typeof val === "string") out[k] = val;
4390
+ }
4391
+ return out;
4392
+ }
4393
+ function parseResources(v) {
4394
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
4395
+ const out = emptyTable();
4396
+ for (const [resource, val] of Object.entries(v)) {
4397
+ if (!isResource(resource)) continue;
4398
+ if (typeof val !== "object" || val === null || Array.isArray(val)) continue;
4399
+ const o = val;
4400
+ if (typeof o.product !== "string" || o.product === "") continue;
4401
+ if (typeof o.productName !== "string" || o.productName === "") continue;
4402
+ out[resource] = { product: o.product, productName: o.productName };
4403
+ }
4404
+ return out;
4405
+ }
4406
+ function parsePendingCommands(v, pending) {
4407
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
4408
+ const out = emptyTable();
4409
+ for (const [group, val] of Object.entries(v)) {
4410
+ if (pending === void 0 || !Object.prototype.hasOwnProperty.call(pending, group)) continue;
4411
+ if (!Array.isArray(val)) continue;
4412
+ if (!val.every((p) => typeof p === "string" && p.split(" ")[0] === group)) continue;
4413
+ out[group] = val;
4414
+ }
4415
+ return out;
4416
+ }
4417
+ function parseScopesMap(text2) {
4418
+ let raw;
4419
+ try {
4420
+ raw = JSON.parse(text2);
4421
+ } catch {
4422
+ return void 0;
4423
+ }
4424
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
4425
+ const o = raw;
4426
+ if (o.version !== 1 || o.cliVersion !== void 0 && typeof o.cliVersion !== "string" || typeof o.commands !== "object" || o.commands === null || Array.isArray(o.commands))
4427
+ return void 0;
4428
+ const commands2 = emptyTable();
4429
+ for (const [k, v] of Object.entries(o.commands)) {
4430
+ const entry = parseEntry(v);
4431
+ if (entry === null) return void 0;
4432
+ commands2[k] = entry;
4433
+ }
4434
+ const map = { version: 1, commands: commands2 };
4435
+ if (typeof o.cliVersion === "string") map.cliVersion = o.cliVersion;
4436
+ if (typeof o._contract === "string") map._contract = o._contract;
4437
+ const aliases = parseStringMap(o.aliases);
4438
+ if (aliases !== void 0) map.aliases = aliases;
4439
+ const pending = parseStringMap(o.pending);
4440
+ if (pending !== void 0) map.pending = pending;
4441
+ const pendingCommands = parsePendingCommands(o.pendingCommands, pending);
4442
+ if (pendingCommands !== void 0) map.pendingCommands = pendingCommands;
4443
+ const resources = parseResources(o.resources);
4444
+ if (resources !== void 0) map.resources = resources;
4445
+ const deferred = new Set(Object.values(pendingCommands ?? {}).flat());
4446
+ for (const cmdPath of Object.keys(commands2)) {
4447
+ if (deferred.has(cmdPath)) return void 0;
4448
+ if (pending !== void 0 && Object.prototype.hasOwnProperty.call(pending, cmdPath.split(" ")[0]))
4449
+ return void 0;
4450
+ }
4451
+ return map;
4452
+ }
4453
+ function loadScopesMap(here = path3.dirname(fileURLToPath(import.meta.url))) {
4454
+ let text2;
4455
+ try {
4456
+ text2 = readFileSync(path3.join(here, "scopes-map.json"), "utf8");
4457
+ } catch {
4458
+ return void 0;
4459
+ }
4460
+ return parseScopesMap(text2);
4461
+ }
4462
+ function scopesMapKey(cmd) {
4463
+ const names = [];
4464
+ for (let c = cmd; c !== null && c.parent !== null; c = c.parent)
4465
+ names.unshift(c.name());
4466
+ return names.join(" ");
4467
+ }
4468
+ function commandPathOf(argv, root) {
4469
+ const names = [];
4470
+ let cmd = root;
4471
+ for (let i = 0; i < argv.length; i += 1) {
4472
+ const token = argv[i];
4473
+ if (token === "--") break;
4474
+ if (token.startsWith("-") && token !== "-") {
4475
+ if (!token.startsWith("--") && token.length > 2) continue;
4476
+ if (token.includes("=")) continue;
4477
+ if (takesValue(token, cmd)) i += 1;
4478
+ continue;
4479
+ }
4480
+ const child2 = childNamed(cmd, token);
4481
+ if (child2 === void 0) break;
4482
+ names.push(child2.name());
4483
+ cmd = child2;
4484
+ }
4485
+ return names.join(" ");
4486
+ }
4487
+ function childNamed(cmd, token) {
4488
+ return cmd.commands.find((c) => c.name() === token || c.aliases().includes(token));
4489
+ }
4490
+ function takesValue(flag, cmd) {
4491
+ for (let c = cmd; c !== null; c = c.parent) {
4492
+ for (const opt of c.options) {
4493
+ if (opt.short !== flag && opt.long !== flag) continue;
4494
+ return opt.required || opt.optional;
4495
+ }
4496
+ }
4497
+ return declaredWithValueBelow(flag, cmd);
4498
+ }
4499
+ function declaredWithValueBelow(flag, cmd) {
4500
+ for (const child2 of cmd.commands) {
4501
+ for (const opt of child2.options) {
4502
+ if ((opt.short === flag || opt.long === flag) && (opt.required || opt.optional)) return true;
4503
+ }
4504
+ if (declaredWithValueBelow(flag, child2)) return true;
4505
+ }
4506
+ return false;
4507
+ }
4508
+
4509
+ // src/scopes/filter.ts
4510
+ var SCOPE_EXIT_CODE = 3;
4511
+ var ScopeFilterError = class extends Error {
4512
+ constructor(command, message) {
4513
+ super(message);
4514
+ this.command = command;
4515
+ }
4516
+ command;
4517
+ exitCode = SCOPE_EXIT_CODE;
4518
+ };
4519
+ var ScopeHiddenError = class extends ScopeFilterError {
4520
+ constructor(command, required2) {
4521
+ super(
4522
+ command,
4523
+ `'dr ${command}' is not available under this identity's credential scope (needs ${required2.join(", ")}); run 'dr whoami' to see the scopes this identity carries`
4524
+ );
4525
+ this.required = required2;
4526
+ this.name = "ScopeHiddenError";
4527
+ }
4528
+ required;
4529
+ };
4530
+ var ScopeUnavailableError = class extends ScopeFilterError {
4531
+ constructor(command, reason, until) {
4532
+ super(
4533
+ command,
4534
+ `'dr ${command}' is unavailable to any agent-scoped credential (one whose scopes claim carries at least one resource:action entry): ${reason} (this is not a missing scope \u2014 no scope grants it; ${until} lifts the restriction)`
4535
+ );
4536
+ this.reason = reason;
4537
+ this.until = until;
4538
+ this.name = "ScopeUnavailableError";
4539
+ }
4540
+ reason;
4541
+ until;
4542
+ };
4543
+ var ScopeGroupHiddenError = class extends ScopeFilterError {
4544
+ constructor(command, required2, tickets) {
4545
+ super(
4546
+ command,
4547
+ `'dr ${command}' has no subcommand available to this identity.` + (required2.length > 0 ? ` Its subcommands need ${required2.join(", ")} \u2014 run 'dr whoami' to see the scopes this identity carries.` : "") + (tickets.length > 0 ? ` Some are closed to any agent-scoped credential whatever its scopes (${tickets.join(", ")}).` : "")
4548
+ );
4549
+ this.required = required2;
4550
+ this.tickets = tickets;
4551
+ this.name = "ScopeGroupHiddenError";
4552
+ }
4553
+ required;
4554
+ tickets;
4555
+ };
4556
+ function hide(cmd) {
4557
+ cmd._hidden = true;
4558
+ }
4559
+ function refuseHelp(cmd, err) {
4560
+ cmd.outputHelp = () => {
4561
+ throw err;
4562
+ };
4563
+ }
4564
+ function leafAnswer(leaf, scopes, map) {
4565
+ const key = scopesMapKey(leaf);
4566
+ if (!Object.prototype.hasOwnProperty.call(map.commands, key)) return void 0;
4567
+ const entry = map.commands[key];
4568
+ if (entry === void 0) return void 0;
4569
+ if (isDeniedEntry(entry))
4570
+ return {
4571
+ err: new ScopeUnavailableError(key, entry.denied.reason, entry.denied.until),
4572
+ required: [],
4573
+ denial: entry.denied
4574
+ };
4575
+ if (isEntryAllowed(scopes, entry)) return void 0;
4576
+ const required2 = entry.requires.map((r) => `${r.resource}:${r.action}`);
4577
+ return { err: new ScopeHiddenError(key, required2), required: required2, denial: void 0 };
4578
+ }
4579
+ var intercepted = /* @__PURE__ */ new WeakMap();
4580
+ function intercept(cmd, err, armed) {
4581
+ intercepted.set(cmd, err);
4582
+ refuseHelp(cmd, err);
4583
+ cmd.action(() => {
4584
+ throw err;
4585
+ });
4586
+ const parent = cmd.parent;
4587
+ if (parent === null || armed.has(parent)) return;
4588
+ armed.add(parent);
4589
+ parent.hook("preSubcommand", (_parent, sub) => {
4590
+ const denial = intercepted.get(sub);
4591
+ if (denial !== void 0) throw denial;
4592
+ });
4593
+ }
4594
+ function interceptGroup(group, err) {
4595
+ group.allowExcessArguments(true);
4596
+ group.allowUnknownOption(true);
4597
+ group.helpCommand(true);
4598
+ refuseHelp(group, err);
4599
+ group.action(() => {
4600
+ throw err;
4601
+ });
4602
+ }
4603
+ function groupAnswer(group, below) {
4604
+ const key = scopesMapKey(group);
4605
+ const [mark] = below.denials;
4606
+ if (mark !== void 0 && below.denials.length === 1 && below.required.length === 0)
4607
+ return new ScopeUnavailableError(key, mark.reason, mark.until);
4608
+ if (below.required.length === 0 && below.denials.length === 0) return void 0;
4609
+ return new ScopeGroupHiddenError(
4610
+ key,
4611
+ below.required,
4612
+ below.denials.map((d) => d.until).filter((t, i, all) => all.indexOf(t) === i)
4613
+ );
4614
+ }
4615
+ function filterBelow(node, scopes, map, armed) {
4616
+ const here = { count: 0, answer: void 0, required: [], denials: [] };
4617
+ let allAnswered = node.commands.length > 0;
4618
+ for (const child2 of node.commands) {
4619
+ if (child2.commands.length > 0) {
4620
+ const below = filterBelow(child2, scopes, map, armed);
4621
+ here.count += below.count;
4622
+ if (below.answer === void 0) {
4623
+ allAnswered = false;
4624
+ continue;
4625
+ }
4626
+ interceptGroup(child2, below.answer);
4627
+ hide(child2);
4628
+ addRequired(here, below.required);
4629
+ addDenials(here, below.denials);
4630
+ continue;
4631
+ }
4632
+ const answer = leafAnswer(child2, scopes, map);
4633
+ if (answer === void 0) {
4634
+ allAnswered = false;
4635
+ continue;
4636
+ }
4637
+ intercept(child2, answer.err, armed);
4638
+ hide(child2);
4639
+ here.count += 1;
4640
+ addRequired(here, answer.required);
4641
+ if (answer.denial !== void 0) addDenials(here, [answer.denial]);
4642
+ }
4643
+ if (allAnswered) here.answer = groupAnswer(node, here);
4644
+ return here;
4645
+ }
4646
+ function addRequired(into, required2) {
4647
+ for (const r of required2) if (!into.required.includes(r)) into.required.push(r);
4648
+ }
4649
+ function addDenials(into, denials) {
4650
+ for (const d of denials)
4651
+ if (!into.denials.some((x) => x.reason === d.reason && x.until === d.until))
4652
+ into.denials.push(d);
4653
+ }
4654
+ function applyScopeFilter(root, scopes, map) {
4655
+ if (map === void 0 || !isAgentScoped(scopes)) return 0;
4656
+ return filterBelow(root, scopes, map, /* @__PURE__ */ new Set()).count;
4657
+ }
4658
+
4659
+ // src/commands/_tenantPin.ts
4660
+ var SERVER_VAR = "DR_PIN_SERVER";
4661
+ var ACCOUNT_VAR = "DR_PIN_ACCOUNT";
4662
+ var ORG_MODE_VAR = "DR_PIN_ORG_MODE";
4663
+ var TenantPinError = class extends Error {
4664
+ /** "You cannot do this", the same exit code a scope denial uses (spec §8). */
4665
+ exitCode = SCOPE_EXIT_CODE;
4666
+ constructor(message) {
4667
+ super(`tenant pin: ${message}`);
4668
+ this.name = "TenantPinError";
4669
+ }
4670
+ };
4671
+ var ORG_MODE_UNSCOPED_ALLOWED = /* @__PURE__ */ new Set([
4672
+ "login",
4673
+ "whoami",
4674
+ "users me",
4675
+ // Local only: skills, the CLI's own update, the Help Center, MCP registration, and
4676
+ // app-space scaffolding never write to the tenant.
4677
+ "skill list",
4678
+ "skill print",
4679
+ "skill install",
4680
+ "skill init-project",
4681
+ "update",
4682
+ "kb search",
4683
+ "kb get",
4684
+ "kb categories",
4685
+ "kb sections",
4686
+ "kb articles-in-section",
4687
+ "kb recent",
4688
+ "kb attachments",
4689
+ "kb article-images",
4690
+ "mcp register",
4691
+ "mcp unregister",
4692
+ "mcp list",
4693
+ "mcp serve",
4694
+ "app-space scaffold",
4695
+ "app-space contract",
4696
+ "app-space eject"
4697
+ ]);
4698
+ var ORG_MODE_READ_SCOPED_REFUSED = /* @__PURE__ */ new Set([
4699
+ "devices request-logs",
4700
+ "desk smtp test",
4701
+ "dashboards prepare",
4702
+ "storyboards prepare"
4703
+ ]);
4704
+ var LOCAL_COMMANDS_WITH_TARGET_FLAGS = /* @__PURE__ */ new Set([
4705
+ "app-space scaffold"
4706
+ ]);
4707
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+$/;
4708
+ function readTenantPin(env = process.env) {
4709
+ const rawServer = env[SERVER_VAR];
4710
+ const rawAccount = env[ACCOUNT_VAR];
4711
+ const rawOrgMode = env[ORG_MODE_VAR];
4712
+ if (rawServer === void 0 && rawAccount === void 0 && rawOrgMode === void 0) {
4713
+ return void 0;
4714
+ }
4715
+ if (rawServer === void 0 || rawServer.trim() === "") {
4716
+ throw new TenantPinError(`${SERVER_VAR} is required when any DR_PIN_* variable is set`);
4717
+ }
4718
+ let server;
4719
+ try {
4720
+ server = resolve(rawServer).key;
4721
+ } catch {
4722
+ throw new TenantPinError(`${SERVER_VAR} is not a known server key or Datarails URL`);
4723
+ }
4724
+ if (rawOrgMode !== void 0 && rawOrgMode !== "1") {
4725
+ throw new TenantPinError(`${ORG_MODE_VAR} must be 1 when set`);
4726
+ }
4727
+ if (rawOrgMode === "1") {
4728
+ if (rawAccount !== void 0) {
4729
+ throw new TenantPinError(
4730
+ `${ORG_MODE_VAR}=1 pins a server only; ${ACCOUNT_VAR} must not be set with it`
4731
+ );
4732
+ }
4733
+ return { kind: "org", server };
4734
+ }
4735
+ const account = normaliseAccount(rawAccount);
4736
+ if (account === void 0 || !EMAIL_RE.test(account)) {
4737
+ throw new TenantPinError(`${ACCOUNT_VAR} must be the login email (or set ${ORG_MODE_VAR}=1)`);
4738
+ }
4739
+ return { kind: "customer", server, account };
4740
+ }
4741
+ function describePin(pin) {
4742
+ return pin.kind === "customer" ? `${pin.server} as ${pin.account}` : `${pin.server} (org mode)`;
4743
+ }
4744
+ function assertFlagsWithinPin(pin, serverFlag, accountFlag) {
4745
+ assertServerFlagWithinPin(pin, serverFlag);
4746
+ assertAccountFlagWithinPin(pin, accountFlag);
4747
+ }
4748
+ function assertServerFlagWithinPin(pin, serverFlag) {
4749
+ if ((serverFlag ?? "").trim() === "") {
4750
+ throw new TenantPinError(
4751
+ `--server is required while dr is pinned to ${describePin(pin)}; pass --server ${pin.server}`
4752
+ );
4753
+ }
4754
+ const server = resolveServer(serverFlag);
4755
+ if (server !== pin.server) {
4756
+ throw new TenantPinError(
4757
+ `--server ${server} is outside the pin; this session may only reach ${describePin(pin)}`
4758
+ );
4759
+ }
4760
+ }
4761
+ function assertAccountFlagWithinPin(pin, accountFlag) {
4762
+ if (pin.kind !== "customer") return;
4763
+ const account = normaliseAccount(accountFlag);
4764
+ if (account === void 0) {
4765
+ throw new TenantPinError(
4766
+ `--account is required while dr is pinned to ${describePin(pin)}; pass --account ${pin.account}`
4767
+ );
4768
+ }
4769
+ if (account !== pin.account) {
4770
+ throw new TenantPinError(
4771
+ `--account ${account} is outside the pin; this session may only act as ${pin.account}`
4772
+ );
4773
+ }
4774
+ }
4775
+ function assertIdentityWithinPin(pin, serverKey, email) {
4776
+ let canonical = serverKey;
4777
+ try {
4778
+ canonical = resolve(serverKey).key;
4779
+ } catch {
4780
+ }
4781
+ if (canonical !== pin.server) {
4782
+ throw new TenantPinError(
4783
+ `server ${serverKey} is outside the pin; this session may only reach ${describePin(pin)}`
4784
+ );
4785
+ }
4786
+ if (pin.kind === "customer" && email !== pin.account) {
4787
+ throw new TenantPinError(
4788
+ `account ${email} is outside the pin; this session may only act as ${pin.account}`
4789
+ );
4790
+ }
4791
+ }
4792
+ function declaresOption(cmd, flag) {
4793
+ for (let c = cmd; c !== null; c = c.parent) {
4794
+ if (c.options.some((o) => o.long === flag)) return true;
4795
+ }
4796
+ return false;
4797
+ }
4798
+ function assertReadOnly(key, map) {
4799
+ if (map === void 0) {
4800
+ throw new TenantPinError(
4801
+ `org mode is read-only, and without scopes-map.json dr cannot tell whether \`dr ${key}\` writes`
4802
+ );
4803
+ }
4804
+ const entry = Object.prototype.hasOwnProperty.call(map.commands, key) ? map.commands[key] : void 0;
4805
+ const writes = entry === void 0 || isDeniedEntry(entry) || (entry.requires.length === 0 ? !ORG_MODE_UNSCOPED_ALLOWED.has(key) : entry.requires.some((r) => r.action !== "read") || ORG_MODE_READ_SCOPED_REFUSED.has(key));
4806
+ if (writes) {
4807
+ throw new TenantPinError(`org mode is read-only; \`dr ${key}\` is not a read-only command`);
4808
+ }
4809
+ }
4810
+ var defaultTenantPinDeps = {
4811
+ get env() {
4812
+ return process.env;
4813
+ },
4814
+ loadMap: () => loadScopesMap()
4815
+ };
4816
+ function enforceTenantPin(cmd, deps = defaultTenantPinDeps) {
4817
+ const pin = readTenantPin(deps.env);
4818
+ if (pin === void 0) return;
4819
+ const key = scopesMapKey(cmd);
4820
+ if (key === "logout") {
4821
+ throw new TenantPinError(`\`dr logout\` is refused while dr is pinned to ${describePin(pin)}`);
4822
+ }
4823
+ const opts = cmd.optsWithGlobals();
4824
+ if (key === "login-as") {
4825
+ const target = normaliseAccount(cmd.processedArgs[0]);
4826
+ if (pin.kind !== "customer" || target !== pin.account || opts.userId !== void 0) {
4827
+ throw new TenantPinError(
4828
+ `\`dr login-as\` may only target the pinned account, by email, while dr is pinned to ${describePin(pin)}`
4829
+ );
4830
+ }
4831
+ }
4832
+ for (const { flag, value, envVar } of [
4833
+ { flag: "--app-space-host", value: opts.appSpaceHost, envVar: "DR_APP_SPACE_HOST" },
4834
+ { flag: "--connectors-url", value: opts.connectorsUrl, envVar: void 0 }
4835
+ ]) {
4836
+ if (!declaresOption(cmd, flag)) continue;
4837
+ const fromEnv = envVar === void 0 ? "" : deps.env[envVar] ?? "";
4838
+ if ((value ?? "").trim() !== "" || fromEnv.trim() !== "") {
4839
+ throw new TenantPinError(
4840
+ `${flag}${envVar === void 0 ? "" : ` / ${envVar}`} is refused while dr is pinned to ${describePin(pin)}`
4841
+ );
4842
+ }
4843
+ }
4844
+ const required2 = !LOCAL_COMMANDS_WITH_TARGET_FLAGS.has(key);
4845
+ if (declaresOption(cmd, "--server") && (required2 || opts.server !== void 0)) {
4846
+ assertServerFlagWithinPin(pin, opts.server);
4847
+ }
4848
+ if (declaresOption(cmd, "--account") && (required2 || opts.account !== void 0)) {
4849
+ assertAccountFlagWithinPin(pin, opts.account);
4850
+ }
4851
+ if (pin.kind === "org") assertReadOnly(key, deps.loadMap());
4852
+ }
4853
+
4285
4854
  // src/telemetry/context.ts
4286
4855
  var NO_IDENTITY = {
4287
4856
  operator: "",
@@ -4551,10 +5120,13 @@ function batchSetupOptions(timeoutFlag) {
4551
5120
  var BATCH_TIMEOUT_FLAG_HELP = `Minimum whole-command budget in seconds. Default ${DEFAULT_CALL_TIMEOUT_MS / 1e3}s for one item; a batch gets ${DEFAULT_CALL_TIMEOUT_MS / 1e3}s + ${BATCH_PER_TARGET_TIMEOUT_MS / 1e3}s per call (capped at ${BATCH_MAX_TIMEOUT_MS / 1e3}s). This is a floor, so it only ever widens that budget; max ${BATCH_TIMEOUT_MAX_SECONDS}s`;
4552
5121
  async function setupCall(serverFlag, accountFlag, options = {}) {
4553
5122
  setConfirmCmdAccountFlag(normaliseAccount(accountFlag));
5123
+ const pin = readTenantPin();
5124
+ if (pin !== void 0) assertFlagsWithinPin(pin, serverFlag, accountFlag);
4554
5125
  const fileStore = await resolveFileStore(serverFlag);
4555
5126
  const key = fileStore?.key ?? resolveServer(serverFlag);
4556
5127
  const store = options.store ?? fileStore?.store ?? await KeyringStore.defaultWithCleanup();
4557
5128
  const email = await resolveAccount(store, key, accountFlag);
5129
+ if (pin !== void 0) assertIdentityWithinPin(pin, key, email);
4558
5130
  const stored = await store.getSession(key, email);
4559
5131
  if (stored !== void 0) {
4560
5132
  emitApiKeyExpiryWarning(stored, `${key}:${email}`);
@@ -4610,56 +5182,8 @@ async function setupCall(serverFlag, accountFlag, options = {}) {
4610
5182
  };
4611
5183
  }
4612
5184
 
4613
- // src/scopes/matcher.ts
4614
- var SCOPE_RE = /^(\*|[a-z][a-z0-9-]*):(\*|read|write|destructive)$/;
4615
- var ACTION_RANK = new Map(
4616
- ["read", "write", "destructive"].map((action, rank) => [action, rank])
4617
- );
4618
- function actionSatisfies(granted, wanted) {
4619
- if (granted === "*" || granted === wanted) return true;
4620
- const g = ACTION_RANK.get(granted);
4621
- const w = ACTION_RANK.get(wanted);
4622
- if (g === void 0 || w === void 0) return false;
4623
- return g > w;
4624
- }
4625
- var WHITESPACE = "\\t\\n\\v\\f\\r \\x1c-\\x1f\\x85\\xa0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff";
4626
- var TRIM_RE = new RegExp(`^[${WHITESPACE}]+|[${WHITESPACE}]+$`, "g");
4627
- var SPLIT_RE = new RegExp(`[${WHITESPACE}]+`);
4628
- function trimShared(value) {
4629
- return value.replace(TRIM_RE, "");
4630
- }
4631
- function splitScopeString(value) {
4632
- return value.split(SPLIT_RE).filter((part) => part !== "");
4633
- }
4634
- function parseScope(value) {
4635
- if (typeof value !== "string") return null;
4636
- const m = SCOPE_RE.exec(trimShared(value).toLowerCase());
4637
- return m ? [m[1], m[2]] : null;
4638
- }
4639
- function parsed(scopes) {
4640
- const list28 = scopes ?? [];
4641
- const entries = typeof list28 === "string" ? splitScopeString(list28) : Array.isArray(list28) ? list28 : [];
4642
- return entries.map(parseScope).filter((p) => p !== null);
4643
- }
4644
- function scopeEntries(scopes) {
4645
- return parsed(scopes);
4646
- }
4647
- function isAgentScoped(scopes) {
4648
- return parsed(scopes).length > 0;
4649
- }
4650
- function isAllowed(scopes, resource, action) {
4651
- const entries = parsed(scopes);
4652
- if (entries.length === 0) return true;
4653
- const res = trimShared(resource).toLowerCase();
4654
- const act = trimShared(action).toLowerCase();
4655
- return entries.some(([r, a]) => (r === "*" || r === res) && actionSatisfies(a, act));
4656
- }
4657
- function isEntryAllowed(scopes, entry) {
4658
- return entry.requires.every((r) => isAllowed(scopes, r.resource, r.action));
4659
- }
4660
-
4661
5185
  // src/scopes/render.ts
4662
- var ACTIONS = ["read", "write", "destructive"];
5186
+ var ACTIONS2 = ["read", "write", "destructive"];
4663
5187
  var OTHER = "Other";
4664
5188
  var ALL_PRODUCTS = "All products";
4665
5189
  var ALL_RESOURCES = "* (all resources)";
@@ -4673,7 +5197,7 @@ function describeScopes(scopes, resources) {
4673
5197
  const byResource = /* @__PURE__ */ new Map();
4674
5198
  for (const [resource, action] of entries) {
4675
5199
  const actions = byResource.get(resource) ?? /* @__PURE__ */ new Set();
4676
- if (action === "*") for (const a of ACTIONS) actions.add(a);
5200
+ if (action === "*") for (const a of ACTIONS2) actions.add(a);
4677
5201
  else actions.add(action);
4678
5202
  byResource.set(resource, actions);
4679
5203
  }
@@ -4703,7 +5227,7 @@ function renderTree(byResource, resources) {
4703
5227
  const width = Math.max(...[...byResource.keys()].map((r) => label(r).length));
4704
5228
  const row = (resource, indent) => {
4705
5229
  const actions = byResource.get(resource);
4706
- return `${indent}${label(resource).padEnd(width)} ${ACTIONS.filter((a) => actions.has(a)).join(", ")}`;
5230
+ return `${indent}${label(resource).padEnd(width)} ${ACTIONS2.filter((a) => actions.has(a)).join(", ")}`;
4707
5231
  };
4708
5232
  const sorted = [...byResource.keys()].sort();
4709
5233
  if (resources === void 0 || Object.keys(resources).length === 0)
@@ -4724,182 +5248,6 @@ function renderTree(byResource, resources) {
4724
5248
  return lines;
4725
5249
  }
4726
5250
 
4727
- // src/scopes/scopesMap.ts
4728
- import { readFileSync } from "fs";
4729
- import * as path3 from "path";
4730
- import { fileURLToPath } from "url";
4731
- function isDeniedEntry(entry) {
4732
- return entry !== void 0 && Object.prototype.hasOwnProperty.call(entry, "denied");
4733
- }
4734
- var ACTIONS2 = /* @__PURE__ */ new Set(["read", "write", "destructive"]);
4735
- var RESOURCE_RE = /^[a-z][a-z0-9-]*$/;
4736
- function isValidResourceName(v) {
4737
- return RESOURCE_RE.test(v);
4738
- }
4739
- function isAction(v) {
4740
- return typeof v === "string" && ACTIONS2.has(v);
4741
- }
4742
- function isResource(v) {
4743
- return typeof v === "string" && isValidResourceName(v);
4744
- }
4745
- function parseRequirement(v) {
4746
- if (typeof v !== "object" || v === null) return null;
4747
- const o = v;
4748
- if (!isResource(o.resource) || !isAction(o.action)) return null;
4749
- return { resource: o.resource, action: o.action };
4750
- }
4751
- function parseDenial(v) {
4752
- if (typeof v !== "object" || v === null || Array.isArray(v)) return null;
4753
- const o = v;
4754
- if (typeof o.reason !== "string" || o.reason.trim() === "") return null;
4755
- if (typeof o.until !== "string" || o.until.trim() === "") return null;
4756
- return { reason: o.reason, until: o.until };
4757
- }
4758
- function parseEntry(v) {
4759
- if (typeof v !== "object" || v === null) return null;
4760
- const o = v;
4761
- if (Object.prototype.hasOwnProperty.call(o, "denied")) {
4762
- if (Object.prototype.hasOwnProperty.call(o, "requires")) return null;
4763
- const denied = parseDenial(o.denied);
4764
- if (denied === null) return null;
4765
- return { denied };
4766
- }
4767
- if (!Array.isArray(o.requires)) return null;
4768
- const requires = [];
4769
- for (const raw of o.requires) {
4770
- const req = parseRequirement(raw);
4771
- if (req === null) return null;
4772
- requires.push(req);
4773
- }
4774
- return { requires };
4775
- }
4776
- function emptyTable() {
4777
- return /* @__PURE__ */ Object.create(null);
4778
- }
4779
- function parseStringMap(v) {
4780
- if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
4781
- const out = emptyTable();
4782
- for (const [k, val] of Object.entries(v)) {
4783
- if (typeof val === "string") out[k] = val;
4784
- }
4785
- return out;
4786
- }
4787
- function parseResources(v) {
4788
- if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
4789
- const out = emptyTable();
4790
- for (const [resource, val] of Object.entries(v)) {
4791
- if (!isResource(resource)) continue;
4792
- if (typeof val !== "object" || val === null || Array.isArray(val)) continue;
4793
- const o = val;
4794
- if (typeof o.product !== "string" || o.product === "") continue;
4795
- if (typeof o.productName !== "string" || o.productName === "") continue;
4796
- out[resource] = { product: o.product, productName: o.productName };
4797
- }
4798
- return out;
4799
- }
4800
- function parsePendingCommands(v, pending) {
4801
- if (typeof v !== "object" || v === null || Array.isArray(v)) return void 0;
4802
- const out = emptyTable();
4803
- for (const [group, val] of Object.entries(v)) {
4804
- if (pending === void 0 || !Object.prototype.hasOwnProperty.call(pending, group)) continue;
4805
- if (!Array.isArray(val)) continue;
4806
- if (!val.every((p) => typeof p === "string" && p.split(" ")[0] === group)) continue;
4807
- out[group] = val;
4808
- }
4809
- return out;
4810
- }
4811
- function parseScopesMap(text2) {
4812
- let raw;
4813
- try {
4814
- raw = JSON.parse(text2);
4815
- } catch {
4816
- return void 0;
4817
- }
4818
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
4819
- const o = raw;
4820
- if (o.version !== 1 || o.cliVersion !== void 0 && typeof o.cliVersion !== "string" || typeof o.commands !== "object" || o.commands === null || Array.isArray(o.commands))
4821
- return void 0;
4822
- const commands2 = emptyTable();
4823
- for (const [k, v] of Object.entries(o.commands)) {
4824
- const entry = parseEntry(v);
4825
- if (entry === null) return void 0;
4826
- commands2[k] = entry;
4827
- }
4828
- const map = { version: 1, commands: commands2 };
4829
- if (typeof o.cliVersion === "string") map.cliVersion = o.cliVersion;
4830
- if (typeof o._contract === "string") map._contract = o._contract;
4831
- const aliases = parseStringMap(o.aliases);
4832
- if (aliases !== void 0) map.aliases = aliases;
4833
- const pending = parseStringMap(o.pending);
4834
- if (pending !== void 0) map.pending = pending;
4835
- const pendingCommands = parsePendingCommands(o.pendingCommands, pending);
4836
- if (pendingCommands !== void 0) map.pendingCommands = pendingCommands;
4837
- const resources = parseResources(o.resources);
4838
- if (resources !== void 0) map.resources = resources;
4839
- const deferred = new Set(Object.values(pendingCommands ?? {}).flat());
4840
- for (const cmdPath of Object.keys(commands2)) {
4841
- if (deferred.has(cmdPath)) return void 0;
4842
- if (pending !== void 0 && Object.prototype.hasOwnProperty.call(pending, cmdPath.split(" ")[0]))
4843
- return void 0;
4844
- }
4845
- return map;
4846
- }
4847
- function loadScopesMap(here = path3.dirname(fileURLToPath(import.meta.url))) {
4848
- let text2;
4849
- try {
4850
- text2 = readFileSync(path3.join(here, "scopes-map.json"), "utf8");
4851
- } catch {
4852
- return void 0;
4853
- }
4854
- return parseScopesMap(text2);
4855
- }
4856
- function scopesMapKey(cmd) {
4857
- const names = [];
4858
- for (let c = cmd; c !== null && c.parent !== null; c = c.parent)
4859
- names.unshift(c.name());
4860
- return names.join(" ");
4861
- }
4862
- function commandPathOf(argv, root) {
4863
- const names = [];
4864
- let cmd = root;
4865
- for (let i = 0; i < argv.length; i += 1) {
4866
- const token = argv[i];
4867
- if (token === "--") break;
4868
- if (token.startsWith("-") && token !== "-") {
4869
- if (!token.startsWith("--") && token.length > 2) continue;
4870
- if (token.includes("=")) continue;
4871
- if (takesValue(token, cmd)) i += 1;
4872
- continue;
4873
- }
4874
- const child2 = childNamed(cmd, token);
4875
- if (child2 === void 0) break;
4876
- names.push(child2.name());
4877
- cmd = child2;
4878
- }
4879
- return names.join(" ");
4880
- }
4881
- function childNamed(cmd, token) {
4882
- return cmd.commands.find((c) => c.name() === token || c.aliases().includes(token));
4883
- }
4884
- function takesValue(flag, cmd) {
4885
- for (let c = cmd; c !== null; c = c.parent) {
4886
- for (const opt of c.options) {
4887
- if (opt.short !== flag && opt.long !== flag) continue;
4888
- return opt.required || opt.optional;
4889
- }
4890
- }
4891
- return declaredWithValueBelow(flag, cmd);
4892
- }
4893
- function declaredWithValueBelow(flag, cmd) {
4894
- for (const child2 of cmd.commands) {
4895
- for (const opt of child2.options) {
4896
- if ((opt.short === flag || opt.long === flag) && (opt.required || opt.optional)) return true;
4897
- }
4898
- if (declaredWithValueBelow(flag, child2)) return true;
4899
- }
4900
- return false;
4901
- }
4902
-
4903
5251
  // src/commands/whoami.ts
4904
5252
  function registerWhoamiCommand(root) {
4905
5253
  const cmd = root.command("whoami").description(
@@ -72064,156 +72412,6 @@ function copySessionCredentials(mountPath = SESSION_AUTH_MOUNT, target = credent
72064
72412
  }
72065
72413
  }
72066
72414
 
72067
- // src/scopes/filter.ts
72068
- var SCOPE_EXIT_CODE = 3;
72069
- var ScopeFilterError = class extends Error {
72070
- constructor(command, message) {
72071
- super(message);
72072
- this.command = command;
72073
- }
72074
- command;
72075
- exitCode = SCOPE_EXIT_CODE;
72076
- };
72077
- var ScopeHiddenError = class extends ScopeFilterError {
72078
- constructor(command, required2) {
72079
- super(
72080
- command,
72081
- `'dr ${command}' is not available under this identity's credential scope (needs ${required2.join(", ")}); run 'dr whoami' to see the scopes this identity carries`
72082
- );
72083
- this.required = required2;
72084
- this.name = "ScopeHiddenError";
72085
- }
72086
- required;
72087
- };
72088
- var ScopeUnavailableError = class extends ScopeFilterError {
72089
- constructor(command, reason, until) {
72090
- super(
72091
- command,
72092
- `'dr ${command}' is unavailable to any agent-scoped credential (one whose scopes claim carries at least one resource:action entry): ${reason} (this is not a missing scope \u2014 no scope grants it; ${until} lifts the restriction)`
72093
- );
72094
- this.reason = reason;
72095
- this.until = until;
72096
- this.name = "ScopeUnavailableError";
72097
- }
72098
- reason;
72099
- until;
72100
- };
72101
- var ScopeGroupHiddenError = class extends ScopeFilterError {
72102
- constructor(command, required2, tickets) {
72103
- super(
72104
- command,
72105
- `'dr ${command}' has no subcommand available to this identity.` + (required2.length > 0 ? ` Its subcommands need ${required2.join(", ")} \u2014 run 'dr whoami' to see the scopes this identity carries.` : "") + (tickets.length > 0 ? ` Some are closed to any agent-scoped credential whatever its scopes (${tickets.join(", ")}).` : "")
72106
- );
72107
- this.required = required2;
72108
- this.tickets = tickets;
72109
- this.name = "ScopeGroupHiddenError";
72110
- }
72111
- required;
72112
- tickets;
72113
- };
72114
- function hide(cmd) {
72115
- cmd._hidden = true;
72116
- }
72117
- function refuseHelp(cmd, err) {
72118
- cmd.outputHelp = () => {
72119
- throw err;
72120
- };
72121
- }
72122
- function leafAnswer(leaf, scopes, map) {
72123
- const key = scopesMapKey(leaf);
72124
- if (!Object.prototype.hasOwnProperty.call(map.commands, key)) return void 0;
72125
- const entry = map.commands[key];
72126
- if (entry === void 0) return void 0;
72127
- if (isDeniedEntry(entry))
72128
- return {
72129
- err: new ScopeUnavailableError(key, entry.denied.reason, entry.denied.until),
72130
- required: [],
72131
- denial: entry.denied
72132
- };
72133
- if (isEntryAllowed(scopes, entry)) return void 0;
72134
- const required2 = entry.requires.map((r) => `${r.resource}:${r.action}`);
72135
- return { err: new ScopeHiddenError(key, required2), required: required2, denial: void 0 };
72136
- }
72137
- var intercepted = /* @__PURE__ */ new WeakMap();
72138
- function intercept(cmd, err, armed) {
72139
- intercepted.set(cmd, err);
72140
- refuseHelp(cmd, err);
72141
- cmd.action(() => {
72142
- throw err;
72143
- });
72144
- const parent = cmd.parent;
72145
- if (parent === null || armed.has(parent)) return;
72146
- armed.add(parent);
72147
- parent.hook("preSubcommand", (_parent, sub) => {
72148
- const denial = intercepted.get(sub);
72149
- if (denial !== void 0) throw denial;
72150
- });
72151
- }
72152
- function interceptGroup(group, err) {
72153
- group.allowExcessArguments(true);
72154
- group.allowUnknownOption(true);
72155
- group.helpCommand(true);
72156
- refuseHelp(group, err);
72157
- group.action(() => {
72158
- throw err;
72159
- });
72160
- }
72161
- function groupAnswer(group, below) {
72162
- const key = scopesMapKey(group);
72163
- const [mark] = below.denials;
72164
- if (mark !== void 0 && below.denials.length === 1 && below.required.length === 0)
72165
- return new ScopeUnavailableError(key, mark.reason, mark.until);
72166
- if (below.required.length === 0 && below.denials.length === 0) return void 0;
72167
- return new ScopeGroupHiddenError(
72168
- key,
72169
- below.required,
72170
- below.denials.map((d) => d.until).filter((t, i, all) => all.indexOf(t) === i)
72171
- );
72172
- }
72173
- function filterBelow(node, scopes, map, armed) {
72174
- const here = { count: 0, answer: void 0, required: [], denials: [] };
72175
- let allAnswered = node.commands.length > 0;
72176
- for (const child2 of node.commands) {
72177
- if (child2.commands.length > 0) {
72178
- const below = filterBelow(child2, scopes, map, armed);
72179
- here.count += below.count;
72180
- if (below.answer === void 0) {
72181
- allAnswered = false;
72182
- continue;
72183
- }
72184
- interceptGroup(child2, below.answer);
72185
- hide(child2);
72186
- addRequired(here, below.required);
72187
- addDenials(here, below.denials);
72188
- continue;
72189
- }
72190
- const answer = leafAnswer(child2, scopes, map);
72191
- if (answer === void 0) {
72192
- allAnswered = false;
72193
- continue;
72194
- }
72195
- intercept(child2, answer.err, armed);
72196
- hide(child2);
72197
- here.count += 1;
72198
- addRequired(here, answer.required);
72199
- if (answer.denial !== void 0) addDenials(here, [answer.denial]);
72200
- }
72201
- if (allAnswered) here.answer = groupAnswer(node, here);
72202
- return here;
72203
- }
72204
- function addRequired(into, required2) {
72205
- for (const r of required2) if (!into.required.includes(r)) into.required.push(r);
72206
- }
72207
- function addDenials(into, denials) {
72208
- for (const d of denials)
72209
- if (!into.denials.some((x) => x.reason === d.reason && x.until === d.until))
72210
- into.denials.push(d);
72211
- }
72212
- function applyScopeFilter(root, scopes, map) {
72213
- if (map === void 0 || !isAgentScoped(scopes)) return 0;
72214
- return filterBelow(root, scopes, map, /* @__PURE__ */ new Set()).count;
72215
- }
72216
-
72217
72415
  // src/scopes/identityScopes.ts
72218
72416
  function peekIdentityFlags(argv) {
72219
72417
  const flags = {};
@@ -72605,6 +72803,9 @@ function buildRootCommand() {
72605
72803
  root.hook("preAction", (_thisCommand, actionCommand) => {
72606
72804
  recordCommandStart(commandPath(actionCommand), Date.now());
72607
72805
  });
72806
+ root.hook("preAction", (_thisCommand, actionCommand) => {
72807
+ enforceTenantPin(actionCommand);
72808
+ });
72608
72809
  registerLoginCommand(root);
72609
72810
  registerLoginAsCommand(root);
72610
72811
  registerWhoamiCommand(root);
@@ -72758,6 +72959,7 @@ function annotateScopeDenial(err, argv, root, deps = defaultScopeDenialDeps) {
72758
72959
  function exitCodeFor(err) {
72759
72960
  if (err instanceof ScopeFilterError) return err.exitCode;
72760
72961
  if (err instanceof ScopeDeniedError) return SCOPE_EXIT_CODE;
72962
+ if (err instanceof TenantPinError) return err.exitCode;
72761
72963
  return 1;
72762
72964
  }
72763
72965
  var UNKNOWN_OPTION_PATTERN = /(unknown option ')(-[\s\S]*)'/g;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.140",
3
+ "cliVersion": "1.6.142",
4
4
  "_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
5
5
  "commands": {
6
6
  "login": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.140",
3
+ "version": "1.6.142",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@datarails-ai/dr-appspace-cli": "0.12.14",
20
+ "@datarails-ai/dr-appspace-cli": "0.12.15",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "@napi-rs/keyring": "^1.3.0",
23
23
  "commander": "^12.1.0",