@sanctuary-framework/mcp-server 1.1.1 → 1.1.3

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdir, writeFile, readFile, stat, unlink, readdir, rm, chmod, access, constants, appendFile, lstat, copyFile, realpath } from 'fs/promises';
3
- import { join, dirname, isAbsolute, resolve, basename, sep } from 'path';
3
+ import { join, dirname, resolve, isAbsolute, basename, sep } from 'path';
4
4
  import { platform, homedir, userInfo, hostname } from 'os';
5
5
  import { createRequire } from 'module';
6
6
  import { createHash, randomBytes as randomBytes$1, randomUUID, createHmac } from 'crypto';
@@ -20,8 +20,8 @@ import { get, createServer as createServer$1 } from 'https';
20
20
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
21
21
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
22
22
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
23
- import { createInterface as createInterface$1 } from 'readline/promises';
24
- import { createInterface } from 'readline';
23
+ import { createInterface } from 'readline/promises';
24
+ import { createInterface as createInterface$1 } from 'readline';
25
25
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
26
26
 
27
27
  var __defProp = Object.defineProperty;
@@ -11884,6 +11884,20 @@ async function handleRequest(deps, req, res) {
11884
11884
  const url = new URL(req.url ?? "/", `http://${host}`);
11885
11885
  const method = (req.method ?? "GET").toUpperCase();
11886
11886
  const path = url.pathname;
11887
+ if (deps.v11Bindings) {
11888
+ const handled = await dispatchV11Request(
11889
+ {
11890
+ bindings: deps.v11Bindings,
11891
+ ...deps.authToken !== void 0 ? { authToken: deps.authToken } : {},
11892
+ loopbackAutoAuth: deps.loopbackAutoAuth ?? false
11893
+ },
11894
+ req,
11895
+ res,
11896
+ url,
11897
+ method
11898
+ );
11899
+ if (handled) return true;
11900
+ }
11887
11901
  if (!isAuthorized(deps, req, url)) {
11888
11902
  writeJSON(res, 401, { error: "unauthorized" });
11889
11903
  return true;
@@ -12064,6 +12078,7 @@ var init_api = __esm({
12064
12078
  init_registry();
12065
12079
  init_init();
12066
12080
  init_discovery();
12081
+ init_dispatch();
12067
12082
  }
12068
12083
  });
12069
12084
 
@@ -13599,6 +13614,53 @@ var init_v1_1 = __esm({
13599
13614
  init_client();
13600
13615
  }
13601
13616
  });
13617
+
13618
+ // src/dashboard/v1_1/dispatch.ts
13619
+ async function dispatchV11Request(inputs, req, res, url, method) {
13620
+ const { bindings, authToken, loopbackAutoAuth } = inputs;
13621
+ if (method === "GET" && (url.pathname === "/v1.1" || url.pathname === "/v1.1/")) {
13622
+ return handleDashboardV11Route(
13623
+ {
13624
+ identityId: bindings.identityId,
13625
+ fortressId: bindings.fortressId,
13626
+ ...authToken !== void 0 ? { authToken } : {}
13627
+ },
13628
+ req,
13629
+ res
13630
+ );
13631
+ }
13632
+ if (url.pathname.startsWith("/api/hub/")) {
13633
+ const authConfig = {
13634
+ loopbackAutoAuth,
13635
+ ...authToken !== void 0 ? { authToken } : {}
13636
+ };
13637
+ return handleHubRoute(
13638
+ { authConfig, service: bindings.hubService },
13639
+ req,
13640
+ res
13641
+ );
13642
+ }
13643
+ if (method === "GET" && url.pathname === "/api/identities") {
13644
+ const authConfig = {
13645
+ loopbackAutoAuth,
13646
+ ...authToken !== void 0 ? { authToken } : {}
13647
+ };
13648
+ const aliasReq = Object.create(req);
13649
+ aliasReq.url = "/api/hub/agents" + url.search;
13650
+ return handleHubRoute(
13651
+ { authConfig, service: bindings.hubService },
13652
+ aliasReq,
13653
+ res
13654
+ );
13655
+ }
13656
+ return false;
13657
+ }
13658
+ var init_dispatch = __esm({
13659
+ "src/dashboard/v1_1/dispatch.ts"() {
13660
+ init_api_router();
13661
+ init_v1_1();
13662
+ }
13663
+ });
13602
13664
  function isDashboardViewRoute(method, path) {
13603
13665
  if (method !== "GET") return false;
13604
13666
  return path === "/" || path === "/dashboard" || path === "/fortress" || path === "/events";
@@ -13611,8 +13673,7 @@ var init_dashboard = __esm({
13611
13673
  init_dashboard_html();
13612
13674
  init_fortress_view();
13613
13675
  init_system_prompt_generator();
13614
- init_api_router();
13615
- init_v1_1();
13676
+ init_dispatch();
13616
13677
  SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
13617
13678
  SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
13618
13679
  MAX_SESSIONS = 1e3;
@@ -13734,45 +13795,17 @@ var init_dashboard = __esm({
13734
13795
  */
13735
13796
  async dispatchV11(req, res, url, method) {
13736
13797
  if (!this.v11Bindings) return false;
13737
- if (method === "GET" && (url.pathname === "/v1.1" || url.pathname === "/v1.1/")) {
13738
- const handled = handleDashboardV11Route(
13739
- {
13740
- identityId: this.v11Bindings.identityId,
13741
- fortressId: this.v11Bindings.fortressId,
13742
- ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
13743
- },
13744
- req,
13745
- res
13746
- );
13747
- return handled;
13748
- }
13749
- if (url.pathname.startsWith("/api/hub/")) {
13750
- const authConfig = {
13751
- loopbackAutoAuth: this._autoAuthLocalhost,
13752
- ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
13753
- };
13754
- const handled = await handleHubRoute(
13755
- { authConfig, service: this.v11Bindings.hubService },
13756
- req,
13757
- res
13758
- );
13759
- return handled;
13760
- }
13761
- if (method === "GET" && url.pathname === "/api/identities") {
13762
- const authConfig = {
13763
- loopbackAutoAuth: this._autoAuthLocalhost,
13764
- ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
13765
- };
13766
- const aliasReq = Object.create(req);
13767
- aliasReq.url = "/api/hub/agents" + url.search;
13768
- const handled = await handleHubRoute(
13769
- { authConfig, service: this.v11Bindings.hubService },
13770
- aliasReq,
13771
- res
13772
- );
13773
- return handled;
13774
- }
13775
- return false;
13798
+ return dispatchV11Request(
13799
+ {
13800
+ bindings: this.v11Bindings,
13801
+ ...this.authToken !== void 0 ? { authToken: this.authToken } : {},
13802
+ loopbackAutoAuth: this._autoAuthLocalhost
13803
+ },
13804
+ req,
13805
+ res,
13806
+ url,
13807
+ method
13808
+ );
13776
13809
  }
13777
13810
  /**
13778
13811
  * v0.10.2: enable (or disable) the loopback auto-auth fast path. See
@@ -27500,14 +27533,14 @@ var init_generator2 = __esm({
27500
27533
  ]);
27501
27534
  }
27502
27535
  });
27503
- function printRecoveryKeyBanner(recoveryKey, filePath, output = process.stderr) {
27536
+ function printSecretBanner(secret, filePath, copy, output = process.stderr) {
27504
27537
  const lines = [
27505
- "SANCTUARY: First Run, Recovery Key Generated",
27538
+ copy.bannerHeader,
27506
27539
  "",
27507
- `Recovery Key: ${recoveryKey}`,
27540
+ `${copy.bannerSecretLabel}: ${secret}`,
27508
27541
  "",
27509
- "SAVE THIS KEY. It will not be shown again.",
27510
- "Without it, your encrypted state is unrecoverable.",
27542
+ copy.bannerSaveLine,
27543
+ copy.bannerLossLine,
27511
27544
  "",
27512
27545
  "Plaintext copy written to:",
27513
27546
  ` ${filePath}`,
@@ -27526,8 +27559,8 @@ ${bottom}
27526
27559
 
27527
27560
  `);
27528
27561
  }
27529
- async function writeRecoveryKeyFile(opts) {
27530
- const filePath = join(opts.storagePath, RECOVERY_KEY_FILENAME);
27562
+ async function writeSecretFile(opts) {
27563
+ const filePath = join(opts.storagePath, opts.copy.fileName);
27531
27564
  try {
27532
27565
  await access(filePath, constants.F_OK);
27533
27566
  return { filePath, written: false };
@@ -27537,54 +27570,47 @@ async function writeRecoveryKeyFile(opts) {
27537
27570
  const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
27538
27571
  const fortressLine = opts.fortressId ? `Fortress: ${opts.fortressId}
27539
27572
  ` : "";
27540
- const content = `SANCTUARY RECOVERY KEY \u2014 DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.
27573
+ const content = `${opts.copy.fileWarningHeader}
27541
27574
  Generated: ${now}
27542
27575
  ` + fortressLine + `
27543
- Recovery key:
27544
- ${opts.recoveryKey}
27545
-
27546
- This file was created on first init. Sanctuary will NOT regenerate this file on
27547
- subsequent runs and will NOT display the key again. After moving this file off
27548
- the host (encrypted backup, password manager, paper safe), delete it from the
27549
- fortress directory. Do NOT keep it in the fortress; the recovery key bypasses
27550
- the cocoon passphrase by design.
27551
- `;
27576
+ ${opts.copy.fileSecretLabel}
27577
+ ${opts.secret}
27578
+
27579
+ ` + opts.copy.fileBody;
27552
27580
  await writeFile(filePath, content, { mode: 384 });
27553
27581
  return { filePath, written: true };
27554
27582
  }
27555
- async function confirmRecoveryKeySaved(io) {
27583
+ async function confirmSecretSaved(copy, declinedError, nonInteractiveError, io) {
27556
27584
  const input = io?.input ?? process.stdin;
27557
27585
  const output = io?.output ?? process.stderr;
27558
27586
  const realStdin = !io && process.stdin.isTTY !== true;
27559
27587
  if (realStdin) {
27560
- throw new RecoveryKeyConfirmationNonInteractiveError();
27588
+ throw new nonInteractiveError();
27561
27589
  }
27562
- const rl = createInterface$1({ input, output });
27590
+ const rl = createInterface({ input, output });
27563
27591
  try {
27564
27592
  const answer = await rl.question(
27565
- "Have you saved the recovery key? [y/N] "
27593
+ `Have you saved the ${copy.promptLabel}? [y/N] `
27566
27594
  );
27567
27595
  const normalized = answer.trim().toLowerCase();
27568
27596
  if (normalized !== "y" && normalized !== "yes") {
27569
- throw new RecoveryKeyConfirmationDeclinedError();
27597
+ throw new declinedError();
27570
27598
  }
27571
27599
  } finally {
27572
27600
  rl.close();
27573
27601
  }
27574
27602
  }
27575
- async function discloseRecoveryKey(opts) {
27603
+ async function discloseSecret(opts, copy, declinedError, nonInteractiveError) {
27576
27604
  const mode = opts.mode ?? "interactive";
27577
- const fileResult = await writeRecoveryKeyFile({
27605
+ const writeOpts = {
27578
27606
  storagePath: opts.storagePath,
27579
- recoveryKey: opts.recoveryKey,
27580
- ...opts.fortressId !== void 0 ? { fortressId: opts.fortressId } : {},
27581
- ...opts.now !== void 0 ? { now: opts.now } : {}
27582
- });
27583
- printRecoveryKeyBanner(
27584
- opts.recoveryKey,
27585
- fileResult.filePath,
27586
- opts.io?.output
27587
- );
27607
+ secret: opts.secret,
27608
+ copy
27609
+ };
27610
+ if (opts.fortressId !== void 0) writeOpts.fortressId = opts.fortressId;
27611
+ if (opts.now !== void 0) writeOpts.now = opts.now;
27612
+ const fileResult = await writeSecretFile(writeOpts);
27613
+ printSecretBanner(opts.secret, fileResult.filePath, copy, opts.io?.output);
27588
27614
  if (mode === "no-confirm" || mode === "stdio-server") {
27589
27615
  return {
27590
27616
  filePath: fileResult.filePath,
@@ -27592,17 +27618,72 @@ async function discloseRecoveryKey(opts) {
27592
27618
  confirmed: false
27593
27619
  };
27594
27620
  }
27595
- await confirmRecoveryKeySaved(opts.io);
27621
+ await confirmSecretSaved(copy, declinedError, nonInteractiveError, opts.io);
27596
27622
  return {
27597
27623
  filePath: fileResult.filePath,
27598
27624
  fileWritten: fileResult.written,
27599
27625
  confirmed: true
27600
27626
  };
27601
27627
  }
27602
- var RECOVERY_KEY_FILENAME, RecoveryKeyConfirmationDeclinedError, RecoveryKeyConfirmationNonInteractiveError;
27628
+ async function discloseRecoveryKey(opts) {
27629
+ const internalOpts = {
27630
+ secret: opts.recoveryKey,
27631
+ storagePath: opts.storagePath
27632
+ };
27633
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
27634
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
27635
+ if (opts.now !== void 0) internalOpts.now = opts.now;
27636
+ if (opts.io !== void 0) internalOpts.io = opts.io;
27637
+ return discloseSecret(
27638
+ internalOpts,
27639
+ RECOVERY_KEY_COPY,
27640
+ RecoveryKeyConfirmationDeclinedError,
27641
+ RecoveryKeyConfirmationNonInteractiveError
27642
+ );
27643
+ }
27644
+ async function disclosePassphrase(opts) {
27645
+ const internalOpts = {
27646
+ secret: opts.passphrase,
27647
+ storagePath: opts.storagePath
27648
+ };
27649
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
27650
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
27651
+ if (opts.now !== void 0) internalOpts.now = opts.now;
27652
+ if (opts.io !== void 0) internalOpts.io = opts.io;
27653
+ return discloseSecret(
27654
+ internalOpts,
27655
+ PASSPHRASE_BACKUP_COPY,
27656
+ PassphraseConfirmationDeclinedError,
27657
+ PassphraseConfirmationNonInteractiveError
27658
+ );
27659
+ }
27660
+ var RECOVERY_KEY_FILENAME, PASSPHRASE_BACKUP_FILENAME, RECOVERY_KEY_COPY, PASSPHRASE_BACKUP_COPY, RecoveryKeyConfirmationDeclinedError, RecoveryKeyConfirmationNonInteractiveError, PassphraseConfirmationDeclinedError, PassphraseConfirmationNonInteractiveError;
27603
27661
  var init_recovery_key_disclosure = __esm({
27604
27662
  "src/cocoon/recovery-key-disclosure.ts"() {
27605
27663
  RECOVERY_KEY_FILENAME = "recovery-key.txt";
27664
+ PASSPHRASE_BACKUP_FILENAME = "passphrase-backup.txt";
27665
+ RECOVERY_KEY_COPY = {
27666
+ fileName: RECOVERY_KEY_FILENAME,
27667
+ bannerHeader: "SANCTUARY: First Run, Recovery Key Generated",
27668
+ bannerSecretLabel: "Recovery Key",
27669
+ bannerSaveLine: "SAVE THIS KEY. It will not be shown again.",
27670
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
27671
+ fileWarningHeader: "SANCTUARY RECOVERY KEY, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
27672
+ fileSecretLabel: "Recovery key:",
27673
+ fileBody: "This file was created on first init. Sanctuary will NOT regenerate this file on\nsubsequent runs and will NOT display the key again. After moving this file off\nthe host (encrypted backup, password manager, paper safe), delete it from the\nfortress directory. Do NOT keep it in the fortress; the recovery key bypasses\nthe cocoon passphrase by design.\n",
27674
+ promptLabel: "recovery key"
27675
+ };
27676
+ PASSPHRASE_BACKUP_COPY = {
27677
+ fileName: PASSPHRASE_BACKUP_FILENAME,
27678
+ bannerHeader: "SANCTUARY: First Run, Passphrase Generated",
27679
+ bannerSecretLabel: "Passphrase",
27680
+ bannerSaveLine: "SAVE THIS PASSPHRASE. It will not be shown again.",
27681
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
27682
+ fileWarningHeader: "SANCTUARY PASSPHRASE, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
27683
+ fileSecretLabel: "Passphrase:",
27684
+ fileBody: "This file was created on first wrap when Sanctuary generated the passphrase.\nSanctuary will NOT regenerate this file on subsequent runs and will NOT display\nthe passphrase again. After moving this file off the host (encrypted backup,\npassword manager, paper safe), delete it from the fortress directory. Do NOT\nkeep it in the fortress; the keychain copy is recoverable only while the host\nand its OS keyring are intact.\n",
27685
+ promptLabel: "passphrase"
27686
+ };
27606
27687
  RecoveryKeyConfirmationDeclinedError = class extends Error {
27607
27688
  constructor() {
27608
27689
  super(
@@ -27619,6 +27700,22 @@ var init_recovery_key_disclosure = __esm({
27619
27700
  this.name = "RecoveryKeyConfirmationNonInteractiveError";
27620
27701
  }
27621
27702
  };
27703
+ PassphraseConfirmationDeclinedError = class extends Error {
27704
+ constructor() {
27705
+ super(
27706
+ "Passphrase confirmation declined. Save the passphrase (printed above and written to passphrase-backup.txt) before re-running wrap."
27707
+ );
27708
+ this.name = "PassphraseConfirmationDeclinedError";
27709
+ }
27710
+ };
27711
+ PassphraseConfirmationNonInteractiveError = class extends Error {
27712
+ constructor() {
27713
+ super(
27714
+ "Passphrase confirmation requires an interactive terminal. Re-run with --no-open for scripted use, or run from a TTY."
27715
+ );
27716
+ this.name = "PassphraseConfirmationNonInteractiveError";
27717
+ }
27718
+ };
27622
27719
  }
27623
27720
  });
27624
27721
 
@@ -30020,14 +30117,18 @@ async function startDashboardServer(options) {
30020
30117
  }
30021
30118
  }
30022
30119
  };
30023
- const deps = {
30024
- sources: options.sources,
30025
- authToken: options.authToken,
30026
- approvals: options.approvals,
30027
- onEvent
30028
- };
30120
+ let v11Bindings = null;
30121
+ let v11LoopbackAutoAuth = false;
30029
30122
  const server = createServer$2(async (req, res) => {
30030
30123
  try {
30124
+ const deps = {
30125
+ sources: options.sources,
30126
+ authToken: options.authToken,
30127
+ approvals: options.approvals,
30128
+ onEvent,
30129
+ v11Bindings,
30130
+ loopbackAutoAuth: v11LoopbackAutoAuth
30131
+ };
30031
30132
  const served = await handleRequest(deps, req, res);
30032
30133
  if (!served) {
30033
30134
  res.writeHead(404, { "Content-Type": "application/json" });
@@ -30065,7 +30166,13 @@ async function startDashboardServer(options) {
30065
30166
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
30066
30167
  publishApproval: (approval) => publish({ type: "approval", data: approval }),
30067
30168
  publishInbox: (item) => publish({ type: "inbox", data: item }),
30068
- publishAgentStatus: (snapshot) => publish({ type: "agent_status", data: snapshot })
30169
+ publishAgentStatus: (snapshot) => publish({ type: "agent_status", data: snapshot }),
30170
+ setV11Bindings: (bindings) => {
30171
+ v11Bindings = bindings;
30172
+ },
30173
+ setV11LoopbackAutoAuth: (enabled) => {
30174
+ v11LoopbackAutoAuth = enabled;
30175
+ }
30069
30176
  };
30070
30177
  }
30071
30178
  var DEFAULT_PORT, DEFAULT_HOST;
@@ -31329,12 +31436,14 @@ async function runWrap(options, deps = {}) {
31329
31436
  const storagePath = resolveStoragePath();
31330
31437
  let passphraseLocation;
31331
31438
  let passphraseSource;
31439
+ let passphraseValue;
31332
31440
  if (options.passphrase) {
31333
31441
  try {
31334
31442
  const persist = deps.persistPassphrase ?? ((value) => persistUserProvidedPassphrase(value, { storagePath }));
31335
31443
  const persisted = await persist(options.passphrase);
31336
31444
  passphraseLocation = persisted.location;
31337
31445
  passphraseSource = persisted.source;
31446
+ passphraseValue = options.passphrase;
31338
31447
  console.error(
31339
31448
  `
31340
31449
  \u{1F510} Persisted user-supplied passphrase (${persisted.location}).`
@@ -31352,12 +31461,14 @@ async function runWrap(options, deps = {}) {
31352
31461
  } else if (process.env.SANCTUARY_PASSPHRASE) {
31353
31462
  passphraseLocation = "SANCTUARY_PASSPHRASE";
31354
31463
  passphraseSource = "env";
31464
+ passphraseValue = process.env.SANCTUARY_PASSPHRASE;
31355
31465
  } else {
31356
31466
  try {
31357
31467
  const resolve5 = deps.resolvePassphrase ?? (() => getOrCreatePassphrase({ storagePath }));
31358
31468
  const resolved = await resolve5();
31359
31469
  passphraseLocation = resolved.location;
31360
31470
  passphraseSource = resolved.source;
31471
+ passphraseValue = resolved.value;
31361
31472
  if (resolved.source === "generated") {
31362
31473
  console.error(
31363
31474
  `
@@ -31393,6 +31504,27 @@ async function runWrap(options, deps = {}) {
31393
31504
  );
31394
31505
  }
31395
31506
  await mkdir(storagePath, { recursive: true, mode: 448 });
31507
+ if (passphraseSource === "generated" && passphraseValue !== void 0) {
31508
+ try {
31509
+ await disclosePassphrase({
31510
+ passphrase: passphraseValue,
31511
+ storagePath,
31512
+ fortressId: fortressIdFromStoragePath(storagePath),
31513
+ // --no-open (CI / scripted) or non-TTY stdin both skip the prompt
31514
+ // the same way init's --no-confirm does. Operator who scripted the
31515
+ // call still gets the banner + the file; they will not see a hang.
31516
+ mode: options.noOpen || process.stdin.isTTY !== true ? "no-confirm" : "interactive"
31517
+ });
31518
+ } catch (err) {
31519
+ if (err instanceof PassphraseConfirmationDeclinedError || err instanceof PassphraseConfirmationNonInteractiveError) {
31520
+ console.error(`
31521
+ Sanctuary wrap: ${err.message}
31522
+ `);
31523
+ process.exit(2);
31524
+ }
31525
+ throw err;
31526
+ }
31527
+ }
31396
31528
  const profile = createWrapProfile(upstreamServers);
31397
31529
  const profilePath = join(storagePath, "cocoon-profile.json");
31398
31530
  await writeFile(profilePath, JSON.stringify(profile, null, 2), {
@@ -31415,6 +31547,13 @@ async function runWrap(options, deps = {}) {
31415
31547
  if (process.env.SANCTUARY_DASHBOARD_ENABLED) {
31416
31548
  sanctuaryEnv.SANCTUARY_DASHBOARD_ENABLED = process.env.SANCTUARY_DASHBOARD_ENABLED;
31417
31549
  }
31550
+ if (options.fortress) {
31551
+ sanctuaryEnv.SANCTUARY_FORTRESS_PATH = resolve(options.fortress);
31552
+ } else if (process.env.SANCTUARY_FORTRESS_PATH) {
31553
+ sanctuaryEnv.SANCTUARY_FORTRESS_PATH = resolve(
31554
+ process.env.SANCTUARY_FORTRESS_PATH
31555
+ );
31556
+ }
31418
31557
  const rewrite = deps.rewriteConfig ?? rewriteConfigForCocoon;
31419
31558
  await rewrite(
31420
31559
  agentConfig,
@@ -31442,6 +31581,40 @@ async function runWrap(options, deps = {}) {
31442
31581
  authToken,
31443
31582
  readPackageVersion()
31444
31583
  );
31584
+ if (passphraseValue !== void 0) {
31585
+ try {
31586
+ const v11Storage = new FilesystemStorage(`${storagePath}/state`);
31587
+ let existingParams;
31588
+ try {
31589
+ const raw = await v11Storage.read("_meta", "key-params");
31590
+ if (raw) {
31591
+ existingParams = JSON.parse(bytesToString(raw));
31592
+ }
31593
+ } catch {
31594
+ }
31595
+ const derived = await deriveMasterKey(passphraseValue, existingParams);
31596
+ if (!existingParams) {
31597
+ await v11Storage.write(
31598
+ "_meta",
31599
+ "key-params",
31600
+ stringToBytes(JSON.stringify(derived.params))
31601
+ );
31602
+ }
31603
+ const wrapAuditLog = new AuditLog(v11Storage, derived.key);
31604
+ dashboard.setV11Bindings(
31605
+ buildV11Bindings({
31606
+ identityId: `fortress:${storagePath}`,
31607
+ fortressId: fortressIdFromStoragePath(storagePath),
31608
+ auditLog: wrapAuditLog
31609
+ })
31610
+ );
31611
+ dashboard.setV11LoopbackAutoAuth(true);
31612
+ } catch (err) {
31613
+ console.error(
31614
+ ` Note: v1.1 dashboard surfaces unavailable on wrap URL (${err.message}). Run \`sanctuary dashboard\` to reach them.`
31615
+ );
31616
+ }
31617
+ }
31445
31618
  const dashboardUrl = `${dashboard.url}?token=${authToken}`;
31446
31619
  const webhookCallbackPortRaw = process.env.SANCTUARY_WEBHOOK_CALLBACK_PORT;
31447
31620
  const webhookCallbackPort = webhookCallbackPortRaw ? parseInt(webhookCallbackPortRaw, 10) : void 0;
@@ -31804,9 +31977,15 @@ var init_cli2 = __esm({
31804
31977
  init_config_reader();
31805
31978
  init_passphrase();
31806
31979
  init_dashboard2();
31980
+ init_wiring();
31981
+ init_filesystem();
31982
+ init_key_derivation();
31983
+ init_encoding();
31984
+ init_audit_log();
31807
31985
  init_config();
31808
31986
  init_paths();
31809
31987
  init_runtime();
31988
+ init_recovery_key_disclosure();
31810
31989
  COCOON_GOVERNOR_DEFAULTS = {
31811
31990
  volume_limit: 200,
31812
31991
  rate_limit_per_tool: 20,
@@ -33809,7 +33988,7 @@ async function readValue(stdin, prompt2) {
33809
33988
  }
33810
33989
  async function readFirstLine(stdin) {
33811
33990
  return new Promise((resolve5, reject) => {
33812
- const rl = createInterface({ input: stdin });
33991
+ const rl = createInterface$1({ input: stdin });
33813
33992
  let resolved = false;
33814
33993
  const finish = (value) => {
33815
33994
  if (resolved) return;
@@ -34835,7 +35014,7 @@ var init_reset_passphrase = __esm({
34835
35014
  waiters = [];
34836
35015
  closed = false;
34837
35016
  constructor(stdin) {
34838
- this.rl = createInterface({ input: stdin });
35017
+ this.rl = createInterface$1({ input: stdin });
34839
35018
  this.rl.on("line", (line) => {
34840
35019
  const w = this.waiters.shift();
34841
35020
  if (w) {
@@ -35198,10 +35377,10 @@ __export(dashboard_standalone_exports, {
35198
35377
  renderTenantDiscoveryHint: () => renderTenantDiscoveryHint,
35199
35378
  startStandaloneDashboard: () => startStandaloneDashboard
35200
35379
  });
35201
- async function discoverableSubTenants(currentStoragePath) {
35380
+ async function discoverableSubTenants(currentStoragePath, discoveryOptions) {
35202
35381
  let all;
35203
35382
  try {
35204
- all = await discoverTenants();
35383
+ all = await discoverTenants(discoveryOptions);
35205
35384
  } catch {
35206
35385
  return [];
35207
35386
  }
@@ -35604,6 +35783,9 @@ var { version: PKG_VERSION4 } = require4("../package.json");
35604
35783
  async function main() {
35605
35784
  const args = process.argv.slice(2);
35606
35785
  let passphrase = process.env.SANCTUARY_PASSPHRASE;
35786
+ if (process.env.SANCTUARY_FORTRESS_PATH && !process.env.SANCTUARY_STORAGE_PATH) {
35787
+ process.env.SANCTUARY_STORAGE_PATH = process.env.SANCTUARY_FORTRESS_PATH;
35788
+ }
35607
35789
  if (args[0] === "dashboard") {
35608
35790
  await runStandaloneDashboard(args.slice(1));
35609
35791
  return;