@contextecf/guardian-cli 0.1.6 → 0.1.9

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.
@@ -17061,6 +17061,8 @@ async function runGuardianCli(argv, options = {}) {
17061
17061
  return runReleaseCommand(argv.slice(1), flags, options);
17062
17062
  case "open":
17063
17063
  return runControlTowerOpenCommand(flags, options);
17064
+ case "connect":
17065
+ return runExtensionConnectCommand(flags, options);
17064
17066
  case "status":
17065
17067
  write(stdout, await statusGuardian(flags, options));
17066
17068
  return 0;
@@ -17072,6 +17074,14 @@ async function runGuardianCli(argv, options = {}) {
17072
17074
  return runLaunchCommand(flags, options);
17073
17075
  case "stop":
17074
17076
  return runStopCommand(flags, options);
17077
+ case "pair": {
17078
+ if (!flags.has("json")) {
17079
+ return runExtensionConnectCommand(flags, options);
17080
+ }
17081
+ const result = await daemonPair(flags, options);
17082
+ write(result.exitCode === 0 ? stdout : stderr, result.message);
17083
+ return result.exitCode;
17084
+ }
17075
17085
  case "daemon":
17076
17086
  return runDaemonCommand(argv.slice(1), flags, options);
17077
17087
  case "service":
@@ -17094,6 +17104,9 @@ async function runGuardianCli(argv, options = {}) {
17094
17104
  return runPrivacyCommand(argv.slice(1), flags, options);
17095
17105
  case "mcp":
17096
17106
  return runMcpCommand(argv.slice(1), flags, options);
17107
+ case "coach":
17108
+ case "prompt-coach":
17109
+ return runCoachCommand(argv.slice(1), flags, options);
17097
17110
  case "policy":
17098
17111
  return runPolicyCommand(argv.slice(1), flags, options);
17099
17112
  case "marketplace":
@@ -17116,7 +17129,7 @@ async function runGuardianCli(argv, options = {}) {
17116
17129
  case "help":
17117
17130
  case "--help":
17118
17131
  case "-h":
17119
- write(stdout, `${HELP_TEXT}
17132
+ write(stdout, `${flags.has("advanced") ? ADVANCED_HELP_TEXT : HELP_TEXT}
17120
17133
  `);
17121
17134
  return 0;
17122
17135
  default:
@@ -17195,11 +17208,13 @@ async function installGuardian(flags, options = {}) {
17195
17208
  "guardian start",
17196
17209
  "guardian open",
17197
17210
  "guardian mcp install",
17198
- "guardian daemon pair --json",
17199
17211
  "guardian service install",
17200
17212
  "guardian key-store install --yes",
17201
- "guardian extension open-setup",
17202
- "guardian extension native-host install --extension-id=<browser-extension-id>"
17213
+ "guardian connect"
17214
+ ],
17215
+ advancedCommands: [
17216
+ `guardian extension native-host install --extension-id=${GUARDIAN_HALO_CHROME_EXTENSION_ID}`,
17217
+ "guardian pair --json"
17203
17218
  ]
17204
17219
  };
17205
17220
  return flags.has("json") ? `${JSON.stringify(result, null, 2)}
@@ -17212,7 +17227,8 @@ async function installGuardian(flags, options = {}) {
17212
17227
  `First run: ${GUARDIAN_FIRST_RUN_COMMAND}`,
17213
17228
  `Open Control Tower: ${GUARDIAN_OPEN_CONTROL_TOWER_COMMAND}`,
17214
17229
  `No global install: ${GUARDIAN_NPX_SETUP_COMMAND}`,
17215
- "Next: guardian setup; guardian doctor; guardian readiness; guardian launch; guardian open; guardian mcp install; guardian extension open-setup; guardian daemon pair --json; guardian extension native-host install --extension-id=<browser-extension-id>"
17230
+ "Next: guardian setup; open ContextECF Halo and click Connect; guardian open",
17231
+ "Help: run guardian doctor if setup needs attention."
17216
17232
  ].join("\n")}
17217
17233
  `;
17218
17234
  }
@@ -17220,8 +17236,9 @@ async function runSetupCommand(flags, options) {
17220
17236
  const stdout = options.stdout ?? process.stdout;
17221
17237
  const stderr = options.stderr ?? process.stderr;
17222
17238
  const home = resolveGuardianHome(options.env, options.platform);
17223
- const extensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
17224
- if (extensionId && !isValidChromeExtensionId(extensionId)) {
17239
+ const requestedExtensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
17240
+ const extensionId = requestedExtensionId ?? GUARDIAN_HALO_CHROME_EXTENSION_ID;
17241
+ if (requestedExtensionId && !isValidChromeExtensionId(requestedExtensionId)) {
17225
17242
  write(
17226
17243
  stderr,
17227
17244
  "Invalid --extension-id. Chrome extension ids must be 32 lowercase letters from a-p.\n"
@@ -17277,10 +17294,80 @@ async function runSetupCommand(flags, options) {
17277
17294
  }
17278
17295
  launch = JSON.parse(launchOutput);
17279
17296
  }
17297
+ const requestedHost = stringFlag(flags, "host") ?? "127.0.0.1";
17298
+ const requestedPort = numberFlag(flags, "port") ?? 4317;
17299
+ const requestedControlTowerUrl = `${formatDaemonBaseUrl(requestedHost, requestedPort)}/control-tower`;
17280
17300
  const resolvedControlTowerUrl = resolveControlTowerUrl(/* @__PURE__ */ new Map(), options.env, profile);
17281
- const controlTowerUrl = launch?.controlTowerUrl ?? (resolvedControlTowerUrl.ok ? resolvedControlTowerUrl.url : GUARDIAN_DEFAULT_CONTROL_TOWER_URL);
17301
+ const controlTowerUrl = launch?.controlTowerUrl ?? (flags.has("host") || flags.has("port") ? requestedControlTowerUrl : resolvedControlTowerUrl.ok ? resolvedControlTowerUrl.url : GUARDIAN_DEFAULT_CONTROL_TOWER_URL);
17302
+ if (!launch && (flags.has("host") || flags.has("port"))) {
17303
+ profile.control_tower = {
17304
+ url: controlTowerUrl,
17305
+ source: "env_or_flag"
17306
+ };
17307
+ await writeProfile(home, profile);
17308
+ }
17282
17309
  const setupUrl = buildControlTowerSetupUrl(controlTowerUrl);
17283
- const nativeHostCommand = `guardian extension native-host install --extension-id=${extensionId ?? "<browser-extension-id>"}`;
17310
+ const nativeHostCommand = `guardian extension native-host install --extension-id=${extensionId}`;
17311
+ let browserHelper;
17312
+ if (flags.has("no-browser-connection") || flags.has("no-browser-helper")) {
17313
+ browserHelper = {
17314
+ success: false,
17315
+ skipped: true,
17316
+ tokenPrinted: false
17317
+ };
17318
+ } else {
17319
+ let nativeHostOutput = "";
17320
+ let nativeHostError = "";
17321
+ const nativeHostFlags = new Map([
17322
+ ["json", true],
17323
+ ["force", true],
17324
+ ["extension-id", extensionId],
17325
+ ...copyStringFlags(flags, ["browser", "target"])
17326
+ ]);
17327
+ if ((options.platform ?? process.platform) === "win32") {
17328
+ nativeHostFlags.set("write-registry", true);
17329
+ nativeHostFlags.set("yes", true);
17330
+ }
17331
+ const nativeHostExitCode = await runExtensionCommand(
17332
+ ["native-host", "install"],
17333
+ nativeHostFlags,
17334
+ {
17335
+ ...options,
17336
+ stdout: {
17337
+ write: (chunk) => {
17338
+ nativeHostOutput += chunk.toString();
17339
+ return true;
17340
+ }
17341
+ },
17342
+ stderr: {
17343
+ write: (chunk) => {
17344
+ nativeHostError += chunk.toString();
17345
+ return true;
17346
+ }
17347
+ }
17348
+ }
17349
+ );
17350
+ if (nativeHostExitCode === 0) {
17351
+ const nativeHost = JSON.parse(nativeHostOutput);
17352
+ browserHelper = {
17353
+ success: true,
17354
+ skipped: false,
17355
+ browser: nativeHost.browser,
17356
+ hostName: nativeHost.hostName,
17357
+ manifestPath: nativeHost.manifestPath,
17358
+ allowedOrigins: nativeHost.allowedOrigins,
17359
+ tokenPrinted: nativeHost.tokenPrinted
17360
+ };
17361
+ } else {
17362
+ browserHelper = {
17363
+ success: false,
17364
+ skipped: false,
17365
+ error: nativeHostError.trim() || "Guardian browser connection setup failed.",
17366
+ exitCode: nativeHostExitCode,
17367
+ tokenPrinted: false
17368
+ };
17369
+ }
17370
+ }
17284
17371
  const result = {
17285
17372
  schema_version: "contextecf/project-guardian-setup/v1",
17286
17373
  success: true,
@@ -17330,10 +17417,14 @@ async function runSetupCommand(flags, options) {
17330
17417
  },
17331
17418
  browserSetup: {
17332
17419
  setupUrl,
17333
- extensionId: extensionId ?? null,
17420
+ extensionId,
17421
+ defaultExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
17334
17422
  opened: false,
17335
17423
  tokenPrinted: false,
17336
- nativeHostCommand
17424
+ nativeHostCommand,
17425
+ connectInstruction: "Open ContextECF Halo and click Connect. No setup code is needed.",
17426
+ manualFallbackCommand: "guardian pair --json",
17427
+ nativeHost: browserHelper
17337
17428
  },
17338
17429
  nextCommands: [
17339
17430
  "guardian status",
@@ -17342,12 +17433,10 @@ async function runSetupCommand(flags, options) {
17342
17433
  "guardian policy list",
17343
17434
  "guardian posture list",
17344
17435
  "guardian preferences show",
17345
- "guardian extension open-setup",
17346
- "guardian daemon pair --json",
17347
17436
  "guardian service install",
17348
- "guardian key-store install --yes",
17349
- nativeHostCommand
17350
- ]
17437
+ "guardian key-store install --yes"
17438
+ ],
17439
+ advancedCommands: ["guardian extension connect", nativeHostCommand, "guardian pair --json"]
17351
17440
  };
17352
17441
  write(
17353
17442
  stdout,
@@ -17360,8 +17449,11 @@ async function runSetupCommand(flags, options) {
17360
17449
  result.launch.opened ? `Opening: ${result.launch.controlTowerUrl}` : void 0,
17361
17450
  `MCP artifacts: ${result.mcp.writtenFiles.length > 0 ? result.mcp.writtenFiles.join(", ") : "skipped" in result.mcp && result.mcp.skipped ? "skipped" : "none"}`,
17362
17451
  `Browser setup: ${result.browserSetup.setupUrl}`,
17452
+ result.browserSetup.nativeHost.success ? `Browser connection: ready for ContextECF Halo (${result.browserSetup.extensionId})` : result.browserSetup.nativeHost.skipped ? "Browser connection: skipped" : `Browser connection: needs attention${result.browserSetup.nativeHost.error ? ` (${result.browserSetup.nativeHost.error})` : ""}`,
17453
+ "Extension setup: open ContextECF Halo and click Connect. No setup code needed.",
17363
17454
  "Token printed: no",
17364
- `Next: ${result.nextCommands.join("; ")}`
17455
+ `Next: ${result.nextCommands.join("; ")}`,
17456
+ "Help: run guardian connect if the browser says Local Guardian is not connected."
17365
17457
  ].filter(Boolean).join("\n")}
17366
17458
  `
17367
17459
  );
@@ -17436,8 +17528,8 @@ function buildGuardianDistributionReadiness(npmPreviewReady) {
17436
17528
  },
17437
17529
  aiToolIntegration: {
17438
17530
  mcpInstallCommand: "guardian mcp install --client=all --write",
17439
- browserSetupCommand: "guardian extension open-setup",
17440
- nativeHostInstallCommand: "guardian extension native-host install --extension-id=<browser-extension-id>"
17531
+ browserSetupCommand: "guardian connect",
17532
+ nativeHostInstallCommand: `guardian extension native-host install --extension-id=${GUARDIAN_HALO_CHROME_EXTENSION_ID}`
17441
17533
  },
17442
17534
  browserStoreHandoff: {
17443
17535
  status: npmPreviewReady ? "operator_handoff_ready_not_submitted" : "blocked_until_preview_ready",
@@ -17453,15 +17545,17 @@ function buildGuardianDistributionReadiness(npmPreviewReady) {
17453
17545
  workflow: GUARDIAN_RELEASE_DISPATCH_WORKFLOW,
17454
17546
  dryRunCommand: buildGuardianReleaseDispatchCommand(true),
17455
17547
  publishCommand: buildGuardianReleaseDispatchCommand(false),
17456
- requiredPublishCredentialName: "NPM_TOKEN",
17548
+ defaultPublishAuth: GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH,
17549
+ tokenFallbackCredentialName: "NPM_TOKEN",
17457
17550
  channels: ["all", "npm", "browser", "evidence"],
17458
17551
  defaultDockerSmoke: true,
17459
17552
  provenance: "github_actions_oidc_supported",
17460
17553
  status: npmPreviewReady ? "ready_for_dispatch_after_preview_evidence" : "blocked_until_preview_release_evidence_passes",
17461
17554
  boundaries: [
17462
17555
  "Use dry_run=true before public publication.",
17463
- "dry_run=false requires the repository secret NPM_TOKEN and must not print npm tokens, OTP values, or .npmrc contents.",
17464
- "The workflow can publish the npm preview package, upload evidence, and run public install smokes; it does not sign native installers or approve future browser-store listings.",
17556
+ "dry_run=false defaults to npm Trusted Publishing and must not print npm tokens, OTP values, or .npmrc contents.",
17557
+ "The token fallback route remains available with publish_auth=token and requires the repository secret NPM_TOKEN.",
17558
+ "The workflow can publish the npm preview package, upload evidence, submit future Chrome Web Store updates for review when browser_store_publish=api, and run public install smokes; it does not sign native installers or prove future browser-store approval.",
17465
17559
  "GitHub Actions OIDC/provenance applies to CI release context; manual local publishes are recorded separately and should not claim CI provenance."
17466
17560
  ]
17467
17561
  },
@@ -17482,12 +17576,16 @@ function buildGuardianReleaseDispatchCommand(dryRun) {
17482
17576
  });
17483
17577
  }
17484
17578
  function buildGuardianReleaseDispatchCommandFor(input2) {
17579
+ const publishAuth = input2.publishAuth ?? GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH;
17580
+ const browserStorePublish = input2.browserStorePublish ?? "manual";
17485
17581
  return [
17486
17582
  `gh workflow run ${GUARDIAN_RELEASE_DISPATCH_WORKFLOW} \\`,
17487
17583
  ` -f release_version=${input2.releaseVersion} \\`,
17488
17584
  ` -f channels=${input2.channels} \\`,
17489
17585
  ` -f dry_run=${input2.dryRun ? "true" : "false"} \\`,
17490
- ` -f run_docker_smoke=${input2.runDockerSmoke ? "true" : "false"}`
17586
+ ` -f run_docker_smoke=${input2.runDockerSmoke ? "true" : "false"} \\`,
17587
+ ` -f publish_auth=${publishAuth} \\`,
17588
+ ` -f browser_store_publish=${browserStorePublish}`
17491
17589
  ].join("\n");
17492
17590
  }
17493
17591
  async function runReleaseCommand(argv, flags, options) {
@@ -17594,11 +17692,23 @@ async function runReleaseCommand(argv, flags, options) {
17594
17692
  }
17595
17693
  const dryRun = flags.has("publish") ? false : booleanFlag(flags, "dry-run", true);
17596
17694
  const runDockerSmoke = booleanFlag(flags, "run-docker-smoke", true);
17695
+ const publishAuth = stringFlag(flags, "publish-auth") ?? GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH;
17696
+ if (!isValidReleasePublishAuth(publishAuth)) {
17697
+ write(stderr, "Invalid --publish-auth. Expected one of: token, trusted_publisher.\n");
17698
+ return 1;
17699
+ }
17700
+ const browserStorePublish = stringFlag(flags, "browser-store-publish") ?? "manual";
17701
+ if (!isValidBrowserStorePublish(browserStorePublish)) {
17702
+ write(stderr, "Invalid --browser-store-publish. Expected one of: manual, api.\n");
17703
+ return 1;
17704
+ }
17597
17705
  const result = buildGuardianReleaseDispatchCommandResult({
17598
17706
  releaseVersion,
17599
17707
  channels,
17600
17708
  dryRun,
17601
- runDockerSmoke
17709
+ runDockerSmoke,
17710
+ publishAuth,
17711
+ browserStorePublish
17602
17712
  });
17603
17713
  write(stdout, renderGuardianReleaseDispatchCommand(result, flags));
17604
17714
  return 0;
@@ -17666,6 +17776,7 @@ function renderGuardianReleaseHelp(result, flags) {
17666
17776
  `;
17667
17777
  }
17668
17778
  function buildGuardianReleaseDispatchCommandResult(input2) {
17779
+ const npmLaneSelected = input2.channels === "all" || input2.channels === "npm";
17669
17780
  return {
17670
17781
  schema_version: "contextecf/project-guardian-release-dispatch-command/v1",
17671
17782
  cli_version: GUARDIAN_CLI_VERSION,
@@ -17674,13 +17785,21 @@ function buildGuardianReleaseDispatchCommandResult(input2) {
17674
17785
  channels: input2.channels,
17675
17786
  dryRun: input2.dryRun,
17676
17787
  runDockerSmoke: input2.runDockerSmoke,
17788
+ publishAuth: input2.publishAuth,
17789
+ browserStorePublish: input2.browserStorePublish,
17677
17790
  command: buildGuardianReleaseDispatchCommandFor(input2),
17678
- ...input2.dryRun ? {} : { requiredPublishCredentialName: "NPM_TOKEN" },
17791
+ ...input2.dryRun || input2.publishAuth === "trusted_publisher" || !npmLaneSelected ? {} : { requiredPublishCredentialName: "NPM_TOKEN" },
17792
+ ...input2.browserStorePublish === "api" && !input2.dryRun ? {
17793
+ requiredBrowserStoreCredentialNames: [...GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES]
17794
+ } : {},
17679
17795
  provenance: "github_actions_oidc_supported",
17680
17796
  boundaries: [
17681
17797
  "Use dry_run=true before public publication.",
17682
- "dry_run=false requires the repository secret NPM_TOKEN and must not print npm tokens, OTP values, or .npmrc contents.",
17683
- "This dispatch can publish the npm preview package, upload evidence, and run public install smokes; it does not sign native installers or approve future browser-store listings.",
17798
+ "dry_run=false with publish_auth=token requires the repository secret NPM_TOKEN and must not print npm tokens, OTP values, or .npmrc contents.",
17799
+ "dry_run=false with publish_auth=trusted_publisher requires npm Trusted Publishing to be configured for this GitHub Actions workflow and package.",
17800
+ "browser_store_publish=manual keeps Chrome Web Store upload as an operator ZIP handoff.",
17801
+ "browser_store_publish=api uploads an existing Chrome Web Store item update and submits it for review when Chrome Web Store API secrets are configured.",
17802
+ "This dispatch can publish the npm preview package, upload evidence, submit future Chrome Web Store updates for review, and run public install smokes; it does not sign native installers or prove future browser-store approval.",
17684
17803
  "A release-dispatch command is operator guidance, not production-gate proof."
17685
17804
  ],
17686
17805
  tokenPrinted: false,
@@ -17699,7 +17818,10 @@ function renderGuardianReleaseDispatchCommand(result, flags) {
17699
17818
  `Workflow: ${result.workflow}`,
17700
17819
  `Channels: ${result.channels}`,
17701
17820
  `Docker smoke: ${result.runDockerSmoke ? "yes" : "no"}`,
17702
- result.requiredPublishCredentialName ? `Required repository secret: ${result.requiredPublishCredentialName}` : "Required repository secret: not needed for dry run",
17821
+ `Publish auth: ${result.publishAuth}`,
17822
+ `Browser store publish: ${result.browserStorePublish}`,
17823
+ result.requiredPublishCredentialName ? `Required repository secret: ${result.requiredPublishCredentialName}` : result.publishAuth === "trusted_publisher" && !result.dryRun ? "Required npm setup: Trusted Publisher connection" : "Required repository secret: not needed for dry run",
17824
+ result.requiredBrowserStoreCredentialNames ? `Required browser-store secrets: ${result.requiredBrowserStoreCredentialNames.join(", ")}` : "Required browser-store secrets: not needed for manual browser handoff",
17703
17825
  "Command:",
17704
17826
  result.command,
17705
17827
  "Boundaries:",
@@ -17714,6 +17836,7 @@ function buildGuardianBrowserStoreHandoffCommandResult() {
17714
17836
  cli_version: GUARDIAN_CLI_VERSION,
17715
17837
  status: "chrome_distribution_proven_next_submission_pack_ready",
17716
17838
  primaryStore: "chrome_web_store",
17839
+ publicListingUrl: GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL,
17717
17840
  productionGate: {
17718
17841
  id: "browser-store-distribution",
17719
17842
  status: "proven",
@@ -17780,6 +17903,7 @@ function renderGuardianBrowserStoreHandoffCommand(result, flags) {
17780
17903
  "Project Guardian Browser Store Handoff",
17781
17904
  `Status: ${result.status}`,
17782
17905
  `Primary store: ${result.primaryStore}`,
17906
+ `Public listing: ${result.publicListingUrl}`,
17783
17907
  `Production gate: ${result.productionGate.id} (${result.productionGate.status})`,
17784
17908
  "1. Build submission pack:",
17785
17909
  result.commands.submissionPack,
@@ -17800,15 +17924,25 @@ function renderGuardianBrowserStoreHandoffCommand(result, flags) {
17800
17924
  `;
17801
17925
  }
17802
17926
  function buildGuardianReleaseSecretSetup() {
17927
+ const chromeWebStoreSecretMatcher = GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES.join("|");
17803
17928
  return {
17804
17929
  schema_version: "contextecf/project-guardian-release-secret-setup/v1",
17805
17930
  cli_version: GUARDIAN_CLI_VERSION,
17806
17931
  repository: GUARDIAN_RELEASE_REPOSITORY,
17807
- requiredForPublish: "NPM_TOKEN",
17932
+ defaultPublishAuth: GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH,
17933
+ requiredForPublish: "npm_trusted_publisher_connection",
17934
+ tokenFallbackCredentialName: "NPM_TOKEN",
17935
+ requiredForBrowserStoreApi: GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES,
17808
17936
  githubActionsSecretUrl: `https://github.com/${GUARDIAN_RELEASE_REPOSITORY}/settings/secrets/actions/new`,
17809
17937
  commands: {
17810
17938
  setNpmToken: `gh secret set NPM_TOKEN --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17939
+ setChromeWebStoreClientId: `gh secret set CHROME_WEB_STORE_CLIENT_ID --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17940
+ setChromeWebStoreClientSecret: `gh secret set CHROME_WEB_STORE_CLIENT_SECRET --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17941
+ setChromeWebStoreRefreshToken: `gh secret set CHROME_WEB_STORE_REFRESH_TOKEN --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17942
+ setChromeWebStorePublisherId: `gh secret set CHROME_WEB_STORE_PUBLISHER_ID --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17943
+ setChromeWebStoreExtensionId: `gh secret set CHROME_WEB_STORE_EXTENSION_ID --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17811
17944
  verifyNpmTokenPresence: `gh secret list --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions | rg '^NPM_TOKEN\\b'`,
17945
+ verifyChromeWebStoreSecretsPresence: `gh secret list --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions | rg '^(${chromeWebStoreSecretMatcher})\\b'`,
17812
17946
  dryRunDispatch: buildGuardianReleaseDispatchCommandFor({
17813
17947
  dryRun: true,
17814
17948
  releaseVersion: "<version>",
@@ -17820,13 +17954,24 @@ function buildGuardianReleaseSecretSetup() {
17820
17954
  releaseVersion: "<version>",
17821
17955
  channels: "all",
17822
17956
  runDockerSmoke: true
17957
+ }),
17958
+ browserStoreApiPublishDispatch: buildGuardianReleaseDispatchCommandFor({
17959
+ dryRun: false,
17960
+ releaseVersion: "<version>",
17961
+ channels: "browser",
17962
+ runDockerSmoke: true,
17963
+ publishAuth: "trusted_publisher",
17964
+ browserStorePublish: "api"
17823
17965
  })
17824
17966
  },
17825
17967
  boundaries: [
17826
- "NPM_TOKEN belongs only in GitHub Actions repository secrets for guarded dry_run=false npm publication.",
17968
+ "npm Trusted Publishing is the default Guardian release path and uses this GitHub Actions workflow identity.",
17969
+ "NPM_TOKEN is a fallback credential only; when used, it belongs only in GitHub Actions repository secrets for guarded dry_run=false npm publication.",
17970
+ "Chrome Web Store API OAuth values belong only in GitHub Actions repository secrets for guarded browser_store_publish=api upload/submission.",
17827
17971
  "Do not paste npm tokens, OTP values, .npmrc contents, signing keys, browser-store credentials, raw prompts, raw responses, private policy packs, or customer content into chat, PR bodies, release evidence, or CLI output.",
17828
- "Use dry_run=true before dry_run=false; dry-run dispatch does not require NPM_TOKEN.",
17829
- "Adding NPM_TOKEN enables the npm preview publish lane only; it does not close signed-installer, browser-store, high-risk desktop, or autonomous app production gates."
17972
+ "Use dry_run=true before dry_run=false; dry-run dispatch does not require npm publish credentials.",
17973
+ "Adding NPM_TOKEN enables the npm preview publish lane only; it does not close signed-installer, browser-store, high-risk desktop, or autonomous app production gates.",
17974
+ "Chrome Web Store API secrets enable browser update upload/submission only; official review approval evidence is still required for production browser-store claims."
17830
17975
  ],
17831
17976
  tokenPrinted: false,
17832
17977
  rawContentIncluded: false
@@ -17840,16 +17985,28 @@ function renderGuardianReleaseSecretSetup(result, flags) {
17840
17985
  return `${[
17841
17986
  "Project Guardian Release Secret Setup",
17842
17987
  `Repository: ${result.repository}`,
17843
- `Required for publish: ${result.requiredForPublish}`,
17988
+ "Default npm publish setup: Trusted Publisher connection",
17989
+ `Fallback npm publish secret: ${result.tokenFallbackCredentialName}`,
17990
+ `Required for Chrome Web Store API: ${result.requiredForBrowserStoreApi.join(", ")}`,
17844
17991
  `GitHub Actions secret page: ${result.githubActionsSecretUrl}`,
17845
- "Set secret:",
17992
+ "NPM publish secret:",
17846
17993
  result.commands.setNpmToken,
17847
- "Verify secret presence:",
17994
+ "Verify npm secret presence:",
17848
17995
  result.commands.verifyNpmTokenPresence,
17996
+ "Chrome Web Store API secrets:",
17997
+ result.commands.setChromeWebStoreClientId,
17998
+ result.commands.setChromeWebStoreClientSecret,
17999
+ result.commands.setChromeWebStoreRefreshToken,
18000
+ result.commands.setChromeWebStorePublisherId,
18001
+ result.commands.setChromeWebStoreExtensionId,
18002
+ "Verify Chrome Web Store API secret presence:",
18003
+ result.commands.verifyChromeWebStoreSecretsPresence,
17849
18004
  "Dry-run release command:",
17850
18005
  result.commands.dryRunDispatch,
17851
- "Publish release command:",
18006
+ "Default publish command with manual browser-store upload:",
17852
18007
  result.commands.publishDispatch,
18008
+ "Browser-store API publish command:",
18009
+ result.commands.browserStoreApiPublishDispatch,
17853
18010
  "Boundaries:",
17854
18011
  ...result.boundaries.map((boundary) => `- ${boundary}`),
17855
18012
  "Token printed: no"
@@ -17880,7 +18037,8 @@ function buildGuardianReleaseChecklist(releaseVersion) {
17880
18037
  browserStoreHandoff: "guardian release browser-store",
17881
18038
  readiness: "guardian readiness"
17882
18039
  },
17883
- requiredRepositoryCredentialName: "NPM_TOKEN",
18040
+ defaultPublishAuth: GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH,
18041
+ tokenFallbackCredentialName: "NPM_TOKEN",
17884
18042
  evidenceOutputs: [
17885
18043
  "release/evidence/guardian-release-dispatch-summary.json",
17886
18044
  "release/evidence/guardian-npm-publish-execution-summary.json",
@@ -17897,7 +18055,8 @@ function buildGuardianReleaseChecklist(releaseVersion) {
17897
18055
  boundaries: [
17898
18056
  "This checklist is an operator command surface for Guardian preview releases; it is not production-gate proof.",
17899
18057
  "Run the dry-run dispatch before any dry_run=false publish dispatch.",
17900
- "dry_run=false requires NPM_TOKEN in GitHub Actions repository secrets and must not print npm tokens, OTP values, or .npmrc contents.",
18058
+ "dry_run=false defaults to npm Trusted Publishing and must not print npm tokens, OTP values, or .npmrc contents.",
18059
+ "The token fallback route remains available with publish_auth=token and requires NPM_TOKEN in GitHub Actions repository secrets.",
17901
18060
  "The checklist does not sign native installers, submit browser-store packages, execute high-risk desktop actions, or enable autonomous local app execution.",
17902
18061
  "Full production claims remain blocked until every not-proven production gate has real validated external evidence."
17903
18062
  ],
@@ -17935,7 +18094,7 @@ function buildGuardianReleaseFlow(releaseVersion) {
17935
18094
  },
17936
18095
  {
17937
18096
  id: "verify-secrets",
17938
- label: "Verify npm publish secret setup",
18097
+ label: "Verify npm Trusted Publisher setup or token fallback",
17939
18098
  command: "guardian release secrets",
17940
18099
  credentialRequired: false
17941
18100
  },
@@ -17949,7 +18108,7 @@ function buildGuardianReleaseFlow(releaseVersion) {
17949
18108
  id: "publish-dispatch",
17950
18109
  label: "Run the guarded publish dispatch after dry-run evidence passes",
17951
18110
  command: publishDispatch,
17952
- credentialRequired: true
18111
+ credentialRequired: false
17953
18112
  },
17954
18113
  {
17955
18114
  id: "verify-public-release",
@@ -17985,7 +18144,8 @@ function buildGuardianReleaseFlow(releaseVersion) {
17985
18144
  })),
17986
18145
  boundaries: [
17987
18146
  "This release flow is copy-paste operator guidance only; it is not release evidence or production-gate proof.",
17988
- "Publish dispatch requires NPM_TOKEN in GitHub Actions repository secrets and must not print npm tokens, OTP values, or .npmrc contents.",
18147
+ "Publish dispatch defaults to npm Trusted Publishing and must not print npm tokens, OTP values, or .npmrc contents.",
18148
+ "The token fallback route remains available with publish_auth=token and requires NPM_TOKEN in GitHub Actions repository secrets.",
17989
18149
  "Run the dry-run dispatch before the guarded dry_run=false publish dispatch.",
17990
18150
  "Public npm preview publication and local install smoke prove the CLI preview lane only.",
17991
18151
  "Signed native installers, high-risk desktop action execution, and autonomous local app execution remain separate not-proven production gates until real external evidence is collected and validated."
@@ -18034,7 +18194,8 @@ function renderGuardianReleaseChecklist(result, flags) {
18034
18194
  `Release: ${result.releaseVersion}`,
18035
18195
  `Repository: ${result.repository}`,
18036
18196
  `Status: ${result.status}`,
18037
- `Required repository credential: ${result.requiredRepositoryCredentialName}`,
18197
+ "Default npm publish setup: Trusted Publisher connection",
18198
+ `Fallback npm publish secret: ${result.tokenFallbackCredentialName}`,
18038
18199
  "1. Secret setup:",
18039
18200
  result.operatorCommands.secretSetup,
18040
18201
  "2. Dry-run dispatch:",
@@ -18070,9 +18231,9 @@ function buildGuardianReleaseVerify(releaseVersion) {
18070
18231
  commands: {
18071
18232
  registryVersion: `npm view ${GUARDIAN_NPM_PACKAGE_NAME}@${releaseVersion} version --registry=${GUARDIAN_NPM_REGISTRY}`,
18072
18233
  publicInstall: `npm install -g ${GUARDIAN_NPM_PACKAGE_NAME}@${releaseVersion}`,
18234
+ chromeWebStoreListing: GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL,
18073
18235
  installedVersion: "guardian --version",
18074
18236
  setup: "guardian setup",
18075
- launch: "guardian launch",
18076
18237
  status: "guardian status",
18077
18238
  openControlTower: "guardian open",
18078
18239
  copyPasteLocalSmoke,
@@ -18101,9 +18262,8 @@ function buildGuardianReleasePaste(releaseVersion) {
18101
18262
  "guardian --version",
18102
18263
  "guardian stop",
18103
18264
  "guardian setup",
18104
- "guardian launch",
18105
- "guardian open",
18106
- "guardian status"
18265
+ "guardian status",
18266
+ "guardian open"
18107
18267
  ];
18108
18268
  return {
18109
18269
  schema_version: "contextecf/project-guardian-release-paste/v1",
@@ -18114,9 +18274,9 @@ function buildGuardianReleasePaste(releaseVersion) {
18114
18274
  command: commands.join("\n"),
18115
18275
  commands,
18116
18276
  boundaries: [
18117
- "This paste block verifies public npm visibility, global install, first-run setup, daemon launch, Control Tower open, and status.",
18277
+ "This paste block verifies public npm visibility, global install, stale-process cleanup, first-run setup, Local Guardian status, and Control Tower open.",
18118
18278
  "It does not publish to npm and must not include npm tokens, OTP values, .npmrc contents, signing keys, browser-store credentials, raw prompts, raw responses, private policy packs, or customer content.",
18119
- "guardian stop is included before setup so stale local daemons or local token mismatches are cleared before launch."
18279
+ "guardian stop is included before setup so stale Local Guardian processes or local token mismatches are cleared before setup relaunches the local service."
18120
18280
  ],
18121
18281
  tokenPrinted: false,
18122
18282
  rawContentIncluded: false
@@ -18143,18 +18303,19 @@ function renderGuardianReleaseVerify(result, flags) {
18143
18303
  `Status: ${result.status}`,
18144
18304
  "1. Confirm registry visibility:",
18145
18305
  result.commands.registryVersion,
18146
- "2. Install the published version:",
18306
+ "2. Confirm public Chrome Web Store listing:",
18307
+ result.commands.chromeWebStoreListing,
18308
+ "3. Install the published version:",
18147
18309
  result.commands.publicInstall,
18148
- "3. Confirm installed CLI version:",
18310
+ "4. Confirm installed CLI version:",
18149
18311
  result.commands.installedVersion,
18150
- "4. Run first-use smoke:",
18312
+ "5. Run first-use smoke:",
18151
18313
  result.commands.setup,
18152
- result.commands.launch,
18153
18314
  result.commands.status,
18154
18315
  result.commands.openControlTower,
18155
- "5. Copy-paste local smoke block:",
18316
+ "6. Copy-paste local smoke block:",
18156
18317
  result.commands.copyPasteLocalSmoke,
18157
- "6. Verify release evidence:",
18318
+ "7. Verify release evidence:",
18158
18319
  result.commands.publishStatusVerification,
18159
18320
  result.commands.finishLineStatus,
18160
18321
  "Evidence outputs:",
@@ -18171,6 +18332,12 @@ function isValidReleaseVersion(value) {
18171
18332
  function isValidReleaseChannels(value) {
18172
18333
  return ["all", "npm", "browser", "evidence"].includes(value);
18173
18334
  }
18335
+ function isValidReleasePublishAuth(value) {
18336
+ return value === "token" || value === "trusted_publisher";
18337
+ }
18338
+ function isValidBrowserStorePublish(value) {
18339
+ return value === "manual" || value === "api";
18340
+ }
18174
18341
  function renderGuardianProductionReadiness(result, flags) {
18175
18342
  if (flags.has("json")) {
18176
18343
  return `${JSON.stringify(result, null, 2)}
@@ -18273,9 +18440,9 @@ async function statusGuardian(flags, options) {
18273
18440
  `Profile: ${home}`,
18274
18441
  `Runtime profile: ${result.runtime.profileDaemonStatus}`,
18275
18442
  `Control Tower: ${result.controlTowerUrl ?? "not configured"}`,
18276
- `Daemon health: ${result.daemon.status}${result.daemon.statusCode ? ` (HTTP ${result.daemon.statusCode})` : ""}`,
18277
- result.daemon.error ? `Daemon detail: ${result.daemon.error}` : void 0,
18278
- result.daemonVersionMismatch ? `Daemon version: ${result.daemonVersionMismatch.daemonVersion} (this CLI is ${result.daemonVersionMismatch.cliVersion} \u2014 versions differ, likely because the CLI was reinstalled or changed version while this daemon kept running; run guardian stop && guardian launch to restart it on the current version)` : void 0,
18443
+ `Local Guardian: ${result.daemon.status}${result.daemon.statusCode ? ` (HTTP ${result.daemon.statusCode})` : ""}`,
18444
+ result.daemon.error ? `Connection detail: ${result.daemon.error}` : void 0,
18445
+ result.daemonVersionMismatch ? `Local Guardian version: ${result.daemonVersionMismatch.daemonVersion} (this CLI is ${result.daemonVersionMismatch.cliVersion} \u2014 versions differ, likely because the CLI was reinstalled or changed version while Local Guardian kept running; run guardian stop && guardian launch to restart it on the current version)` : void 0,
18279
18446
  `MCP stdio: ${result.runtime.mcpStdioAvailable ? "available" : "unavailable"}`,
18280
18447
  `Posture Profile: ${result.postureProfile.selected}`,
18281
18448
  `Prompt Coach: ${result.preferences.promptCoachMode}`,
@@ -18382,7 +18549,7 @@ async function runLaunchCommand(flags, options) {
18382
18549
  nextCommands: [
18383
18550
  "guardian open",
18384
18551
  "guardian status",
18385
- "guardian daemon pair --json",
18552
+ "Open ContextECF Halo and click Connect",
18386
18553
  "guardian policy list"
18387
18554
  ]
18388
18555
  };
@@ -18391,7 +18558,7 @@ async function runLaunchCommand(flags, options) {
18391
18558
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
18392
18559
  ` : `${[
18393
18560
  "Project Guardian launched.",
18394
- `Daemon: ${result.daemonUrl}${result.reusedExistingDaemon ? " (already running)" : ""}`,
18561
+ `Local Guardian: ${result.daemonUrl}${result.reusedExistingDaemon ? " (already running)" : ""}`,
18395
18562
  `Control Tower: ${result.controlTowerUrl}`,
18396
18563
  shouldOpen ? `Opening: ${result.controlTowerUrl}` : void 0,
18397
18564
  "Next: guardian open; guardian status"
@@ -18502,14 +18669,14 @@ async function runStartCommand(flags, options) {
18502
18669
  controlTowerUrl: requestedControlTowerUrl,
18503
18670
  healthUrl,
18504
18671
  healthStatus: "reachable",
18505
- nextCommands: ["guardian open", "guardian status", "guardian daemon pair --json"]
18672
+ nextCommands: ["guardian open", "guardian status", "Open ContextECF Halo and click Connect"]
18506
18673
  };
18507
18674
  write(
18508
18675
  stdout,
18509
18676
  flags.has("json") ? `${JSON.stringify(result2, null, 2)}
18510
18677
  ` : `${[
18511
- "Guardian daemon already running.",
18512
- `Daemon: ${requestedDaemonUrl}`,
18678
+ "Local Guardian already running.",
18679
+ `Local Guardian: ${requestedDaemonUrl}`,
18513
18680
  `Control Tower: ${requestedControlTowerUrl}`,
18514
18681
  "Next: guardian open"
18515
18682
  ].join("\n")}
@@ -18518,7 +18685,7 @@ async function runStartCommand(flags, options) {
18518
18685
  return 0;
18519
18686
  }
18520
18687
  if (!options.startDetachedDaemon) {
18521
- write(stderr, "Guardian detached daemon launcher is unavailable in this runtime.\n");
18688
+ write(stderr, "Guardian local background launcher is unavailable in this runtime.\n");
18522
18689
  return 1;
18523
18690
  }
18524
18691
  const started = await options.startDetachedDaemon({ home, host, port: selected.port });
@@ -18539,14 +18706,14 @@ async function runStartCommand(flags, options) {
18539
18706
  pid: started.pid,
18540
18707
  healthUrl,
18541
18708
  healthStatus: existing.error ? "unreachable" : "not_reachable",
18542
- nextCommands: ["guardian open", "guardian status", "guardian daemon pair --json"]
18709
+ nextCommands: ["guardian open", "guardian status", "Open ContextECF Halo and click Connect"]
18543
18710
  };
18544
18711
  write(
18545
18712
  stdout,
18546
18713
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
18547
18714
  ` : `${[
18548
- "Guardian daemon start requested.",
18549
- `Daemon: ${daemonUrl}`,
18715
+ "Local Guardian start requested.",
18716
+ `Local Guardian: ${daemonUrl}`,
18550
18717
  `Control Tower: ${controlTowerUrl}`,
18551
18718
  started.pid ? `PID: ${started.pid}` : void 0,
18552
18719
  "Next: guardian open"
@@ -18557,8 +18724,8 @@ async function runStartCommand(flags, options) {
18557
18724
  }
18558
18725
  function renderDaemonTokenMismatchMessage(requestedDaemonUrl, port) {
18559
18726
  return [
18560
- `Guardian found another daemon or container at ${requestedDaemonUrl}, but it rejected this profile's local runtime token.`,
18561
- "This usually means an older Guardian daemon or Docker demo is still using the port.",
18727
+ `Guardian found another local service or container at ${requestedDaemonUrl}, but it rejected this profile's local connection token.`,
18728
+ "This usually means an older Guardian background service or Docker demo is still using the port.",
18562
18729
  `Recovery: stop the other process/container, or run Guardian on a different port with guardian launch --port=${port + 1}.`,
18563
18730
  `Diagnostics: guardian status; docker ps --filter publish=${port}`,
18564
18731
  ""
@@ -18591,7 +18758,7 @@ async function runStopCommand(flags, options) {
18591
18758
  if (!stopped.ok) {
18592
18759
  write(
18593
18760
  stderr,
18594
- `Guardian daemon did not stop cleanly${stopped.statusCode ? ` (HTTP ${stopped.statusCode})` : ""}: ${stopped.error ?? "unknown_error"}
18761
+ `Local Guardian did not stop cleanly${stopped.statusCode ? ` (HTTP ${stopped.statusCode})` : ""}: ${stopped.error ?? "unknown_error"}
18595
18762
  `
18596
18763
  );
18597
18764
  return 1;
@@ -18602,12 +18769,12 @@ async function runStopCommand(flags, options) {
18602
18769
  success: true,
18603
18770
  daemonStatus: profile.runtime.daemon_status,
18604
18771
  stopUrl: stopUrl.url,
18605
- note: "Guardian daemon stop requested."
18772
+ note: "Local Guardian stop requested."
18606
18773
  };
18607
18774
  write(
18608
18775
  stdout,
18609
18776
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
18610
- ` : `${["Guardian daemon stop requested.", "Status: not_started_mvp"].join("\n")}
18777
+ ` : `${["Local Guardian stop requested.", "Status: not_started_mvp"].join("\n")}
18611
18778
  `
18612
18779
  );
18613
18780
  return 0;
@@ -18679,7 +18846,7 @@ Proof: ${result2.proofPath}
18679
18846
  }
18680
18847
  const host = stringFlag(flags, "host") ?? "127.0.0.1";
18681
18848
  if (!isLoopbackHost(host)) {
18682
- write(stderr, "Guardian service supervision only accepts loopback daemon hosts.\n");
18849
+ write(stderr, "Guardian service supervision only accepts loopback Local Guardian hosts.\n");
18683
18850
  return 1;
18684
18851
  }
18685
18852
  const port = numberFlag(flags, "port") ?? 4317;
@@ -18936,7 +19103,7 @@ async function daemonPair(flags, options) {
18936
19103
  [GUARDIAN_BRIDGE_CONFIG_STORAGE_KEY]: bridgeConfig,
18937
19104
  [GUARDIAN_BRIDGE_TOKEN_STORAGE_KEY]: token
18938
19105
  },
18939
- note: "Store this token in the Guardian browser extension local settings, then run guardian daemon serve."
19106
+ note: "Advanced fallback only. Most users should run guardian setup, open ContextECF Halo, and click Connect."
18940
19107
  };
18941
19108
  if (flags.has("json")) {
18942
19109
  return { exitCode: 0, message: `${JSON.stringify(pairing, null, 2)}
@@ -18945,13 +19112,10 @@ async function daemonPair(flags, options) {
18945
19112
  return {
18946
19113
  exitCode: 0,
18947
19114
  message: `${[
18948
- "Guardian browser bridge pairing",
18949
- `Storage key: ${pairing.storageKey}`,
18950
- `Loopback URL: ${pairing.loopbackBaseUrl}`,
18951
- `Transport: ${pairing.transport}`,
18952
- `Native host: ${pairing.nativeHostName}`,
18953
- `Token: ${pairing.token}`,
18954
- pairing.note
19115
+ "Guardian browser connection uses one-click setup now.",
19116
+ "Run guardian connect, then open ContextECF Halo and click Connect.",
19117
+ "Support setup codes are only printed by guardian pair --json.",
19118
+ "Token printed: no"
18955
19119
  ].join("\n")}
18956
19120
  `
18957
19121
  };
@@ -18960,6 +19124,9 @@ async function runExtensionCommand(argv, flags, options) {
18960
19124
  const subcommand = argv[0] ?? "native-host";
18961
19125
  const stdout = options.stdout ?? process.stdout;
18962
19126
  const stderr = options.stderr ?? process.stderr;
19127
+ if (subcommand === "connect") {
19128
+ return runExtensionConnectCommand(flags, options);
19129
+ }
18963
19130
  if (subcommand === "open-setup") {
18964
19131
  return runExtensionOpenSetupCommand(flags, options);
18965
19132
  }
@@ -19147,13 +19314,18 @@ Re-run with --force to replace it.
19147
19314
  registryMutationRequested: writeRegistry,
19148
19315
  registryProofPath,
19149
19316
  tokenPrinted: false,
19150
- nextCommands: ["guardian launch", "guardian daemon pair --json"]
19317
+ nextCommands: [
19318
+ "guardian launch",
19319
+ "Open ContextECF Halo and click Connect",
19320
+ "guardian extension native-host status"
19321
+ ],
19322
+ advancedCommands: ["guardian pair --json"]
19151
19323
  };
19152
19324
  write(
19153
19325
  stdout,
19154
19326
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
19155
19327
  ` : `${[
19156
- "Guardian native host installed.",
19328
+ "Guardian browser connection installed.",
19157
19329
  `Browser: ${browser}`,
19158
19330
  `Host: ${plan.hostName}`,
19159
19331
  `Manifest: ${plan.manifestPath}`,
@@ -19168,6 +19340,121 @@ Re-run with --force to replace it.
19168
19340
  );
19169
19341
  return 0;
19170
19342
  }
19343
+ async function runExtensionConnectCommand(flags, options) {
19344
+ const stdout = options.stdout ?? process.stdout;
19345
+ const stderr = options.stderr ?? process.stderr;
19346
+ const requestedExtensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
19347
+ const extensionId = requestedExtensionId ?? GUARDIAN_HALO_CHROME_EXTENSION_ID;
19348
+ if (requestedExtensionId && !isValidChromeExtensionId(requestedExtensionId)) {
19349
+ write(
19350
+ stderr,
19351
+ "Invalid --extension-id. Chrome extension ids must be 32 lowercase letters from a-p.\n"
19352
+ );
19353
+ return 1;
19354
+ }
19355
+ const install = JSON.parse(await installGuardian(/* @__PURE__ */ new Map([["json", true]]), options));
19356
+ let launchOutput = "";
19357
+ const launchFlags = new Map([
19358
+ ["json", true],
19359
+ ["print", true],
19360
+ ...copyStringFlags(flags, ["host", "port"])
19361
+ ]);
19362
+ const launchExitCode = await runLaunchCommand(launchFlags, {
19363
+ ...options,
19364
+ stdout: {
19365
+ write: (chunk) => {
19366
+ launchOutput += chunk.toString();
19367
+ return true;
19368
+ }
19369
+ },
19370
+ stderr
19371
+ });
19372
+ if (launchExitCode !== 0) {
19373
+ return launchExitCode;
19374
+ }
19375
+ const launch = JSON.parse(launchOutput);
19376
+ const helperFlags = new Map([
19377
+ ["json", true],
19378
+ ["force", true],
19379
+ ["extension-id", extensionId],
19380
+ ...copyStringFlags(flags, ["browser", "target"])
19381
+ ]);
19382
+ if ((options.platform ?? process.platform) === "win32") {
19383
+ helperFlags.set("write-registry", true);
19384
+ helperFlags.set("yes", true);
19385
+ }
19386
+ let helperOutput = "";
19387
+ let helperError = "";
19388
+ const helperExitCode = await runExtensionCommand(["native-host", "install"], helperFlags, {
19389
+ ...options,
19390
+ stdout: {
19391
+ write: (chunk) => {
19392
+ helperOutput += chunk.toString();
19393
+ return true;
19394
+ }
19395
+ },
19396
+ stderr: {
19397
+ write: (chunk) => {
19398
+ helperError += chunk.toString();
19399
+ return true;
19400
+ }
19401
+ }
19402
+ });
19403
+ if (helperExitCode !== 0) {
19404
+ write(
19405
+ stderr,
19406
+ helperError.trim() ? `${helperError.trim()}
19407
+ ` : "Guardian could not prepare the browser connection. Run guardian setup, then try again.\n"
19408
+ );
19409
+ return helperExitCode;
19410
+ }
19411
+ const helper = JSON.parse(helperOutput);
19412
+ const result = {
19413
+ schema_version: "contextecf/project-guardian-browser-connect/v1",
19414
+ success: true,
19415
+ extensionId,
19416
+ defaultExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
19417
+ install: {
19418
+ profileDir: install.profileDir,
19419
+ tokenCreated: install.tokenCreated,
19420
+ tokenPrinted: false
19421
+ },
19422
+ localGuardian: {
19423
+ running: true,
19424
+ opened: launch.opened,
19425
+ reusedExistingDaemon: launch.reusedExistingDaemon,
19426
+ daemonUrl: launch.daemonUrl,
19427
+ controlTowerUrl: launch.controlTowerUrl,
19428
+ pid: launch.pid,
19429
+ healthStatus: launch.healthStatus
19430
+ },
19431
+ browser: helper.browser,
19432
+ helperInstalled: true,
19433
+ helperStatus: {
19434
+ hostName: helper.hostName,
19435
+ manifestPath: helper.manifestPath,
19436
+ allowedOrigins: helper.allowedOrigins,
19437
+ tokenPrinted: helper.tokenPrinted
19438
+ },
19439
+ tokenPrinted: false,
19440
+ nextCommands: ["Open ContextECF Halo and click Connect", "guardian open", "guardian status"],
19441
+ supportCommands: ["guardian extension open-setup --print", "guardian pair --json"]
19442
+ };
19443
+ write(
19444
+ stdout,
19445
+ flags.has("json") ? `${JSON.stringify(result, null, 2)}
19446
+ ` : `${[
19447
+ "Guardian browser connection is ready.",
19448
+ `Local Guardian: ${result.localGuardian.daemonUrl}${result.localGuardian.reusedExistingDaemon ? " (already running)" : ""}`,
19449
+ `Browser: ${result.browser}`,
19450
+ "Next: open ContextECF Halo and click Connect.",
19451
+ "No setup code needed.",
19452
+ "Token printed: no"
19453
+ ].join("\n")}
19454
+ `
19455
+ );
19456
+ return 0;
19457
+ }
19171
19458
  async function runExtensionOpenSetupCommand(flags, options) {
19172
19459
  const stdout = options.stdout ?? process.stdout;
19173
19460
  const stderr = options.stderr ?? process.stderr;
@@ -19177,8 +19464,9 @@ async function runExtensionOpenSetupCommand(flags, options) {
19177
19464
  write(stderr, "Guardian is not installed. Run guardian install first.\n");
19178
19465
  return 1;
19179
19466
  }
19180
- const extensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
19181
- if (extensionId && !isValidChromeExtensionId(extensionId)) {
19467
+ const requestedExtensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
19468
+ const extensionId = requestedExtensionId ?? GUARDIAN_HALO_CHROME_EXTENSION_ID;
19469
+ if (requestedExtensionId && !isValidChromeExtensionId(requestedExtensionId)) {
19182
19470
  write(
19183
19471
  stderr,
19184
19472
  "Invalid --extension-id. Chrome extension ids must be 32 lowercase letters from a-p.\n"
@@ -19192,22 +19480,24 @@ async function runExtensionOpenSetupCommand(flags, options) {
19192
19480
  return 1;
19193
19481
  }
19194
19482
  const setupUrl = buildControlTowerSetupUrl(resolved.url);
19195
- const nativeHostCommand = `guardian extension native-host install --extension-id=${extensionId ?? "<browser-extension-id>"}`;
19483
+ const nativeHostCommand = `guardian extension native-host install --extension-id=${extensionId}`;
19196
19484
  const result = {
19197
19485
  schema_version: "contextecf/project-guardian-extension-setup/v1",
19198
19486
  success: true,
19199
19487
  opened: !flags.has("print"),
19200
19488
  controlTowerUrl: resolved.url,
19201
19489
  setupUrl,
19202
- extensionId: extensionId ?? null,
19490
+ extensionId,
19491
+ defaultExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
19203
19492
  tokenPrinted: false,
19204
19493
  nextCommands: [
19205
19494
  "guardian launch",
19206
- "guardian daemon pair --json",
19207
19495
  nativeHostCommand,
19208
- "guardian extension native-host status"
19496
+ "guardian extension native-host status",
19497
+ "Open ContextECF Halo and click Connect"
19209
19498
  ],
19210
- note: extensionId ? "Guardian browser setup opened with a concrete native host install command." : "Guardian browser setup opened with placeholder extension-id guidance."
19499
+ advancedCommands: ["guardian pair --json"],
19500
+ note: extensionId ? "Guardian browser setup opened for ContextECF Halo." : "Guardian browser setup opened."
19211
19501
  };
19212
19502
  if (result.opened) {
19213
19503
  const opener = options.openUrl ?? ((url2) => openUrlInDefaultBrowser(url2, options));
@@ -19219,7 +19509,8 @@ async function runExtensionOpenSetupCommand(flags, options) {
19219
19509
  ` : `${[
19220
19510
  result.opened ? `Opening Guardian browser setup: ${setupUrl}` : `Guardian browser setup: ${setupUrl}`,
19221
19511
  "Token printed: no",
19222
- `Next: ${result.nextCommands.join("; ")}`
19512
+ `Next: ${result.nextCommands.join("; ")}`,
19513
+ `Advanced fallback: ${result.advancedCommands.join("; ")}`
19223
19514
  ].join("\n")}
19224
19515
  `
19225
19516
  );
@@ -19746,7 +20037,7 @@ function buildGuardianServiceArtifact(input2) {
19746
20037
  if (input2.platform === "systemd") {
19747
20038
  return `${[
19748
20039
  "[Unit]",
19749
- "Description=Project Guardian local daemon",
20040
+ "Description=Project Guardian Local Guardian background service",
19750
20041
  "After=network.target",
19751
20042
  "",
19752
20043
  "[Service]",
@@ -19765,7 +20056,7 @@ function buildGuardianServiceArtifact(input2) {
19765
20056
  '<?xml version="1.0" encoding="UTF-16"?>',
19766
20057
  '<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
19767
20058
  " <RegistrationInfo>",
19768
- " <Description>Project Guardian local daemon</Description>",
20059
+ " <Description>Project Guardian Local Guardian background service</Description>",
19769
20060
  " </RegistrationInfo>",
19770
20061
  " <Triggers>",
19771
20062
  " <LogonTrigger><Enabled>true</Enabled></LogonTrigger>",
@@ -20747,6 +21038,59 @@ async function runReceiptsCommand(argv, flags, options) {
20747
21038
  write(stdout, await verifyReceipts(flags, options));
20748
21039
  return 0;
20749
21040
  }
21041
+ async function runCoachCommand(argv, flags, options) {
21042
+ const subcommand = argv[0] ?? "options";
21043
+ const stdout = options.stdout ?? process.stdout;
21044
+ const stderr = options.stderr ?? process.stderr;
21045
+ if (subcommand === "options" || subcommand === "catalog") {
21046
+ write(stdout, renderGuardianPromptCardOptions(buildGuardianPromptCardOptions(), flags));
21047
+ return 0;
21048
+ }
21049
+ write(stderr, `Unknown Guardian coach command: ${subcommand}
21050
+ `);
21051
+ return 1;
21052
+ }
21053
+ function buildGuardianPromptCardOptions() {
21054
+ return {
21055
+ schema_version: "contextecf/project-guardian-prompt-card-options/v1",
21056
+ cli_version: GUARDIAN_CLI_VERSION,
21057
+ status: "catalog_ready",
21058
+ summary: "Guardian prompt cards keep the user in control: warnings and safer rewrites can be sent as-is, while hard blocks cannot.",
21059
+ catalog: GUARDIAN_PROMPT_CARD_OPTIONS_CATALOG,
21060
+ rules: [
21061
+ "Show a clear reason before asking the user to decide.",
21062
+ "Offer Send as-is for warnings and safer rewrites when policy allows override.",
21063
+ "Never offer Send as-is for hard blocks or unavailable Local Guardian checks.",
21064
+ "Keep Edit prompt available as the safe path for every visible send-time card.",
21065
+ "Do not store raw sensitive prompt text in this catalog or CLI output."
21066
+ ],
21067
+ tokenPrinted: false,
21068
+ rawContentIncluded: false
21069
+ };
21070
+ }
21071
+ function renderGuardianPromptCardOptions(result, flags) {
21072
+ if (flags.has("json")) {
21073
+ return `${JSON.stringify(result, null, 2)}
21074
+ `;
21075
+ }
21076
+ return `${[
21077
+ "Guardian Prompt Card Options",
21078
+ result.summary,
21079
+ "",
21080
+ "Programmed cards:",
21081
+ ...result.catalog.map((entry) => {
21082
+ const visibleActions = entry.actions.filter((action) => action.visibleToUser).map((action) => action.label);
21083
+ const labels = visibleActions.length > 0 ? visibleActions.join(", ") : "Prompt sends automatically";
21084
+ return `- ${entry.cardStyle}: ${labels}`;
21085
+ }),
21086
+ "",
21087
+ "Rules:",
21088
+ ...result.rules.map((rule) => `- ${rule}`),
21089
+ "Token printed: no",
21090
+ "Raw prompt content included: no"
21091
+ ].join("\n")}
21092
+ `;
21093
+ }
20750
21094
  async function runPolicyCommand(argv, flags, options) {
20751
21095
  const subcommand = argv[0] ?? "list";
20752
21096
  const stdout = options.stdout ?? process.stdout;
@@ -23166,22 +23510,32 @@ function openUrlInDefaultBrowser(url2, options = {}) {
23166
23510
  });
23167
23511
  });
23168
23512
  }
23169
- var GUARDIAN_CLI_VERSION, GUARDIAN_PROFILE_SCHEMA_VERSION, GUARDIAN_NPM_PACKAGE_NAME, GUARDIAN_NPM_REGISTRY, GUARDIAN_GLOBAL_INSTALL_COMMAND, GUARDIAN_NPX_SETUP_COMMAND, GUARDIAN_FIRST_RUN_COMMAND, GUARDIAN_OPEN_CONTROL_TOWER_COMMAND, GUARDIAN_RELEASE_DISPATCH_WORKFLOW, GUARDIAN_RELEASE_REPOSITORY, GUARDIAN_DEFAULT_CONTROL_TOWER_URL, GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ENV, GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ID_ENV, GUARDIAN_SQLITE_PAGE_ENCRYPTION_KEY_ENV, GUARDIAN_SQLITE_PAGE_ENCRYPTION_KEY_ID_ENV, GUARDIAN_DESKTOP_ADAPTER_SIMULATE_OPEN_ENV, GUARDIAN_DESKTOP_URL_OPEN_ADAPTER_ID, GUARDIAN_DESKTOP_URL_OPEN_ADAPTER_VERSION, GUARDIAN_DESKTOP_URL_OPEN_ADAPTER_PROOF_SCHEMA_VERSION, GUARDIAN_SERVICE_PROOF_SCHEMA_VERSION, GUARDIAN_OS_KEY_STORAGE_PROOF_SCHEMA_VERSION, GUARDIAN_WINDOWS_NATIVE_HOST_REGISTRY_PROOF_SCHEMA_VERSION, PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION2, PROMPT_COACH_MODES, DEFAULT_PROMPT_COACH_DAILY_SOFT_CARD_LIMIT, GUARDIAN_POSTURE_PROFILE_IDS, GUARDIAN_POSTURE_PROFILE_CATALOG, GUARDIAN_POLICY_PACK_MARKETPLACE_CATALOG, GUARDIAN_PROVIDER_IDS, GUARDIAN_SOURCE_TYPES, GUARDIAN_NOT_PROVEN_PRODUCTION_GATES, DEFAULT_POLICY_PACKS, GUARDIAN_BRIDGE_CONFIG_STORAGE_KEY, GUARDIAN_BRIDGE_TOKEN_STORAGE_KEY, GUARDIAN_BRIDGE_CONFIG_SCHEMA_VERSION, GUARDIAN_NATIVE_HOST_NAME, MCP_CLIENTS, NATIVE_HOST_BROWSERS, HELP_TEXT, normalizeGuardianPreferences, GUARDIAN_DATA_FILE_NAMES, GUARDIAN_DATA_DIR_NAMES, GUARDIAN_MIN_SUPPORTED_NODE_VERSION;
23513
+ var GUARDIAN_CLI_VERSION, GUARDIAN_PROFILE_SCHEMA_VERSION, GUARDIAN_NPM_PACKAGE_NAME, GUARDIAN_NPM_REGISTRY, GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH, GUARDIAN_GLOBAL_INSTALL_COMMAND, GUARDIAN_NPX_SETUP_COMMAND, GUARDIAN_FIRST_RUN_COMMAND, GUARDIAN_OPEN_CONTROL_TOWER_COMMAND, GUARDIAN_HALO_CHROME_EXTENSION_ID, GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL, GUARDIAN_RELEASE_DISPATCH_WORKFLOW, GUARDIAN_RELEASE_REPOSITORY, GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES, GUARDIAN_DEFAULT_CONTROL_TOWER_URL, GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ENV, GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ID_ENV, GUARDIAN_SQLITE_PAGE_ENCRYPTION_KEY_ENV, GUARDIAN_SQLITE_PAGE_ENCRYPTION_KEY_ID_ENV, GUARDIAN_DESKTOP_ADAPTER_SIMULATE_OPEN_ENV, GUARDIAN_DESKTOP_URL_OPEN_ADAPTER_ID, GUARDIAN_DESKTOP_URL_OPEN_ADAPTER_VERSION, GUARDIAN_DESKTOP_URL_OPEN_ADAPTER_PROOF_SCHEMA_VERSION, GUARDIAN_SERVICE_PROOF_SCHEMA_VERSION, GUARDIAN_OS_KEY_STORAGE_PROOF_SCHEMA_VERSION, GUARDIAN_WINDOWS_NATIVE_HOST_REGISTRY_PROOF_SCHEMA_VERSION, PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION2, PROMPT_COACH_MODES, DEFAULT_PROMPT_COACH_DAILY_SOFT_CARD_LIMIT, GUARDIAN_PROMPT_CARD_OPTIONS_CATALOG, GUARDIAN_POSTURE_PROFILE_IDS, GUARDIAN_POSTURE_PROFILE_CATALOG, GUARDIAN_POLICY_PACK_MARKETPLACE_CATALOG, GUARDIAN_PROVIDER_IDS, GUARDIAN_SOURCE_TYPES, GUARDIAN_NOT_PROVEN_PRODUCTION_GATES, DEFAULT_POLICY_PACKS, GUARDIAN_BRIDGE_CONFIG_STORAGE_KEY, GUARDIAN_BRIDGE_TOKEN_STORAGE_KEY, GUARDIAN_BRIDGE_CONFIG_SCHEMA_VERSION, GUARDIAN_NATIVE_HOST_NAME, MCP_CLIENTS, NATIVE_HOST_BROWSERS, HELP_TEXT, ADVANCED_HELP_TEXT, normalizeGuardianPreferences, GUARDIAN_DATA_FILE_NAMES, GUARDIAN_DATA_DIR_NAMES, GUARDIAN_MIN_SUPPORTED_NODE_VERSION;
23170
23514
  var init_runtime = __esm({
23171
23515
  "packages/guardian-cli/src/runtime.ts"() {
23172
23516
  "use strict";
23173
23517
  init_src();
23174
23518
  init_src2();
23175
- GUARDIAN_CLI_VERSION = "0.1.6";
23519
+ GUARDIAN_CLI_VERSION = "0.1.9";
23176
23520
  GUARDIAN_PROFILE_SCHEMA_VERSION = "contextecf/project-guardian-profile/v1";
23177
23521
  GUARDIAN_NPM_PACKAGE_NAME = "@contextecf/guardian-cli";
23178
23522
  GUARDIAN_NPM_REGISTRY = "https://registry.npmjs.org/";
23523
+ GUARDIAN_DEFAULT_NPM_PUBLISH_AUTH = "trusted_publisher";
23179
23524
  GUARDIAN_GLOBAL_INSTALL_COMMAND = `npm install -g ${GUARDIAN_NPM_PACKAGE_NAME}`;
23180
23525
  GUARDIAN_NPX_SETUP_COMMAND = `npx ${GUARDIAN_NPM_PACKAGE_NAME}@latest setup`;
23181
23526
  GUARDIAN_FIRST_RUN_COMMAND = "guardian setup";
23182
23527
  GUARDIAN_OPEN_CONTROL_TOWER_COMMAND = "guardian open";
23528
+ GUARDIAN_HALO_CHROME_EXTENSION_ID = "dopkdppbiakhbglhgjfacfdhpheejkck";
23529
+ GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL = `https://chromewebstore.google.com/detail/contextecf-halo/${GUARDIAN_HALO_CHROME_EXTENSION_ID}`;
23183
23530
  GUARDIAN_RELEASE_DISPATCH_WORKFLOW = "guardian-release-dispatch.yml";
23184
23531
  GUARDIAN_RELEASE_REPOSITORY = "Intelligent-Context-AI-Inc/ContextECF-GITHUB";
23532
+ GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES = [
23533
+ "CHROME_WEB_STORE_CLIENT_ID",
23534
+ "CHROME_WEB_STORE_CLIENT_SECRET",
23535
+ "CHROME_WEB_STORE_REFRESH_TOKEN",
23536
+ "CHROME_WEB_STORE_PUBLISHER_ID",
23537
+ "CHROME_WEB_STORE_EXTENSION_ID"
23538
+ ];
23185
23539
  GUARDIAN_DEFAULT_CONTROL_TOWER_URL = "http://127.0.0.1:4317/control-tower";
23186
23540
  GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ENV = "GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY";
23187
23541
  GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ID_ENV = "GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ID";
@@ -23197,6 +23551,195 @@ var init_runtime = __esm({
23197
23551
  PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION2 = "contextecf/personal-fabric-contracts/v1";
23198
23552
  PROMPT_COACH_MODES = ["off", "quiet", "balanced", "hands_on"];
23199
23553
  DEFAULT_PROMPT_COACH_DAILY_SOFT_CARD_LIMIT = 6;
23554
+ GUARDIAN_PROMPT_CARD_OPTIONS_CATALOG = [
23555
+ {
23556
+ disposition: "allow",
23557
+ cardStyle: "No visible card",
23558
+ whenShown: "Local Guardian approves the prompt before it leaves the browser.",
23559
+ actions: [
23560
+ {
23561
+ id: "auto-send",
23562
+ label: "Send prompt",
23563
+ role: "none",
23564
+ sendsPrompt: true,
23565
+ sendsRevisedPrompt: false,
23566
+ requiresConfirmation: false,
23567
+ visibleToUser: false
23568
+ }
23569
+ ],
23570
+ sendAsIsAllowed: true,
23571
+ hardBlock: false,
23572
+ receiptExpected: true
23573
+ },
23574
+ {
23575
+ disposition: "warn",
23576
+ cardStyle: "Quick check",
23577
+ whenShown: "Guardian found a low or medium-risk concern, but policy still allows the user to continue.",
23578
+ actions: [
23579
+ {
23580
+ id: "send-as-is",
23581
+ label: "Send as-is",
23582
+ role: "allowed_override",
23583
+ sendsPrompt: true,
23584
+ sendsRevisedPrompt: false,
23585
+ requiresConfirmation: false,
23586
+ visibleToUser: true
23587
+ },
23588
+ {
23589
+ id: "edit-prompt",
23590
+ label: "Edit prompt",
23591
+ role: "safe_escape",
23592
+ sendsPrompt: false,
23593
+ sendsRevisedPrompt: false,
23594
+ requiresConfirmation: false,
23595
+ visibleToUser: true
23596
+ }
23597
+ ],
23598
+ sendAsIsAllowed: true,
23599
+ hardBlock: false,
23600
+ receiptExpected: true
23601
+ },
23602
+ {
23603
+ disposition: "rewrite",
23604
+ cardStyle: "Safer version ready",
23605
+ whenShown: "Guardian prepared a safer prompt, usually by masking sensitive text or narrowing scope.",
23606
+ actions: [
23607
+ {
23608
+ id: "send-revised-prompt",
23609
+ label: "Send revised prompt",
23610
+ role: "recommended",
23611
+ sendsPrompt: true,
23612
+ sendsRevisedPrompt: true,
23613
+ requiresConfirmation: false,
23614
+ visibleToUser: true
23615
+ },
23616
+ {
23617
+ id: "send-as-is",
23618
+ label: "Send as-is",
23619
+ role: "allowed_override",
23620
+ sendsPrompt: true,
23621
+ sendsRevisedPrompt: false,
23622
+ requiresConfirmation: false,
23623
+ visibleToUser: true
23624
+ },
23625
+ {
23626
+ id: "edit-prompt",
23627
+ label: "Edit prompt",
23628
+ role: "safe_escape",
23629
+ sendsPrompt: false,
23630
+ sendsRevisedPrompt: false,
23631
+ requiresConfirmation: false,
23632
+ visibleToUser: true
23633
+ }
23634
+ ],
23635
+ sendAsIsAllowed: true,
23636
+ hardBlock: false,
23637
+ receiptExpected: true
23638
+ },
23639
+ {
23640
+ disposition: "confirm",
23641
+ cardStyle: "Review needed",
23642
+ whenShown: "Guardian needs deliberate approval before sending higher-risk content or action requests.",
23643
+ actions: [
23644
+ {
23645
+ id: "approve-and-send",
23646
+ label: "Approve and send",
23647
+ role: "recommended",
23648
+ sendsPrompt: true,
23649
+ sendsRevisedPrompt: false,
23650
+ requiresConfirmation: true,
23651
+ visibleToUser: true
23652
+ },
23653
+ {
23654
+ id: "edit-prompt",
23655
+ label: "Edit prompt",
23656
+ role: "safe_escape",
23657
+ sendsPrompt: false,
23658
+ sendsRevisedPrompt: false,
23659
+ requiresConfirmation: false,
23660
+ visibleToUser: true
23661
+ }
23662
+ ],
23663
+ sendAsIsAllowed: false,
23664
+ hardBlock: false,
23665
+ receiptExpected: true
23666
+ },
23667
+ {
23668
+ disposition: "block",
23669
+ cardStyle: "Blocked",
23670
+ whenShown: "The prompt violates policy, contains protected secrets, or targets a denied destination.",
23671
+ actions: [
23672
+ {
23673
+ id: "edit-prompt",
23674
+ label: "Edit prompt",
23675
+ role: "safe_escape",
23676
+ sendsPrompt: false,
23677
+ sendsRevisedPrompt: false,
23678
+ requiresConfirmation: false,
23679
+ visibleToUser: true
23680
+ }
23681
+ ],
23682
+ sendAsIsAllowed: false,
23683
+ hardBlock: true,
23684
+ receiptExpected: true
23685
+ },
23686
+ {
23687
+ disposition: "unavailable",
23688
+ cardStyle: "Setup needed",
23689
+ whenShown: "The browser cannot reach Local Guardian, so the prompt is held back.",
23690
+ actions: [
23691
+ {
23692
+ id: "edit-prompt",
23693
+ label: "Edit prompt",
23694
+ role: "safe_escape",
23695
+ sendsPrompt: false,
23696
+ sendsRevisedPrompt: false,
23697
+ requiresConfirmation: false,
23698
+ visibleToUser: true
23699
+ }
23700
+ ],
23701
+ sendAsIsAllowed: false,
23702
+ hardBlock: false,
23703
+ receiptExpected: false
23704
+ },
23705
+ {
23706
+ disposition: "live_sensitive_warning",
23707
+ cardStyle: "Early heads-up",
23708
+ whenShown: "Guardian notices sensitive text while the user is typing, before the formal send-time check.",
23709
+ actions: [
23710
+ {
23711
+ id: "use-placeholder",
23712
+ label: "Use placeholder",
23713
+ role: "recommended",
23714
+ sendsPrompt: false,
23715
+ sendsRevisedPrompt: true,
23716
+ requiresConfirmation: false,
23717
+ visibleToUser: true
23718
+ },
23719
+ {
23720
+ id: "send-as-is",
23721
+ label: "Send as-is",
23722
+ role: "allowed_override",
23723
+ sendsPrompt: true,
23724
+ sendsRevisedPrompt: false,
23725
+ requiresConfirmation: false,
23726
+ visibleToUser: true
23727
+ },
23728
+ {
23729
+ id: "keep-drafting",
23730
+ label: "Keep drafting",
23731
+ role: "safe_escape",
23732
+ sendsPrompt: false,
23733
+ sendsRevisedPrompt: false,
23734
+ requiresConfirmation: false,
23735
+ visibleToUser: true
23736
+ }
23737
+ ],
23738
+ sendAsIsAllowed: true,
23739
+ hardBlock: false,
23740
+ receiptExpected: false
23741
+ }
23742
+ ];
23200
23743
  GUARDIAN_POSTURE_PROFILE_IDS = [
23201
23744
  "calm",
23202
23745
  "balanced",
@@ -23689,10 +24232,49 @@ Open Control Tower:
23689
24232
  ${GUARDIAN_OPEN_CONTROL_TOWER_COMMAND}
23690
24233
  start aliases: guardian launch, guardian tower, guardian control-tower
23691
24234
 
24235
+ Everyday commands:
24236
+ guardian --version
24237
+ guardian setup
24238
+ guardian launch
24239
+ guardian open
24240
+ guardian status
24241
+ guardian doctor
24242
+ guardian connect
24243
+ guardian coach options
24244
+ guardian policy list
24245
+ guardian marketplace list
24246
+ guardian posture list
24247
+ guardian privacy show
24248
+
24249
+ Recommended first run:
24250
+ ${GUARDIAN_GLOBAL_INSTALL_COMMAND}
24251
+ guardian setup
24252
+ Open ContextECF Halo and click Connect.
24253
+ guardian open
24254
+
24255
+ Support and developer commands:
24256
+ guardian help --advanced
24257
+
24258
+ Defaults are local-first: raw capture, cloud sync, learning graph, and app autopilot stay off.`;
24259
+ ADVANCED_HELP_TEXT = `guardian \u2014 Project Guardian Personal Context Fabric
24260
+
24261
+ Global install:
24262
+ ${GUARDIAN_GLOBAL_INSTALL_COMMAND}
24263
+
24264
+ First run:
24265
+ ${GUARDIAN_FIRST_RUN_COMMAND}
24266
+
24267
+ No global install:
24268
+ ${GUARDIAN_NPX_SETUP_COMMAND}
24269
+
24270
+ Open Control Tower:
24271
+ ${GUARDIAN_OPEN_CONTROL_TOWER_COMMAND}
24272
+ start aliases: guardian launch, guardian tower, guardian control-tower
24273
+
23692
24274
  Usage:
23693
24275
  guardian --version
23694
24276
  guardian version [--json]
23695
- guardian setup [--json] [--open] [--print] [--host=127.0.0.1] [--port=4317] [--no-launch] [--no-mcp] [--extension-id=<chrome-extension-id>]
24277
+ guardian setup [--json] [--open] [--print] [--host=127.0.0.1] [--port=4317] [--no-launch] [--no-mcp] [--no-browser-connection] [--extension-id=<chrome-extension-id>]
23696
24278
  guardian install [--json]
23697
24279
  guardian doctor [--json]
23698
24280
  guardian readiness [--json] [--strict]
@@ -23700,7 +24282,7 @@ Usage:
23700
24282
  guardian release flow --version=<semver> [--json]
23701
24283
  guardian release verify --version=<semver> [--json]
23702
24284
  guardian release paste --version=<semver> [--json]
23703
- guardian release command --version=<semver> [--publish|--dry-run=false] [--channels=all|npm|browser|evidence] [--run-docker-smoke=true|false] [--json]
24285
+ guardian release command --version=<semver> [--publish|--dry-run=false] [--channels=all|npm|browser|evidence] [--run-docker-smoke=true|false] [--publish-auth=token|trusted_publisher] [--browser-store-publish=manual|api] [--json]
23704
24286
  guardian release secrets [--json]
23705
24287
  guardian release browser-store [--json]
23706
24288
  guardian open [--json] [--print] [--url=http://127.0.0.1:<port>]
@@ -23708,6 +24290,8 @@ Usage:
23708
24290
  guardian start [--json] [--host=127.0.0.1] [--port=4317]
23709
24291
  guardian launch|tower|control-tower [--json] [--open] [--print] [--host=127.0.0.1] [--port=4317]
23710
24292
  guardian stop [--json] [--url=http://127.0.0.1:<port>/control-tower]
24293
+ guardian pair [--json]
24294
+ guardian connect [--json] [--browser=chrome|chromium|edge] [--extension-id=<chrome-extension-id>] [--host=127.0.0.1] [--port=4317]
23711
24295
  guardian service install [--json] [--platform=auto|launchd|systemd|windows-task-scheduler] [--force]
23712
24296
  guardian service status [--json]
23713
24297
  guardian service uninstall [--yes] [--json]
@@ -23716,6 +24300,7 @@ Usage:
23716
24300
  guardian key-store uninstall [--yes] [--json]
23717
24301
  guardian daemon serve [--host=127.0.0.1] [--port=4317]
23718
24302
  guardian daemon pair [--json]
24303
+ guardian extension connect [--json] [--browser=chrome|chromium|edge] [--extension-id=<chrome-extension-id>]
23719
24304
  guardian extension open-setup [--json] [--print] [--url=http://127.0.0.1:<port>/control-tower] [--extension-id=<chrome-extension-id>]
23720
24305
  guardian extension native-host install --extension-id=<chrome-extension-id> [--browser=chrome|chromium|edge] [--target=/path/to/manifest] [--force] [--write-registry --yes] [--json]
23721
24306
  guardian extension native-host status [--browser=chrome|chromium|edge] [--target=/path/to/manifest] [--json]
@@ -23731,6 +24316,7 @@ Usage:
23731
24316
  guardian privacy mode on [--json]
23732
24317
  guardian mcp install [--json] [--client=generic|codex|claude-desktop|cursor|all] [--write] [--target=/path/to/config] [--force]
23733
24318
  guardian mcp serve
24319
+ guardian coach options [--json]
23734
24320
  guardian policy list [--json]
23735
24321
  guardian policy enable <pack-id> [--json]
23736
24322
  guardian policy disable <pack-id> [--json]
@@ -23940,7 +24526,7 @@ function detectRisk(promptManifest, policyPacks) {
23940
24526
  risks.add("healthcare_data");
23941
24527
  matchedPolicyRefs.push("guardian:builtin:healthcare-data");
23942
24528
  }
23943
- if (WORK_PATTERN.test(prompt)) {
24529
+ if (containsSensitiveWorkData(prompt)) {
23944
24530
  risks.add("work_data");
23945
24531
  matchedPolicyRefs.push("guardian:builtin:work-data");
23946
24532
  }
@@ -23952,7 +24538,7 @@ function detectRisk(promptManifest, policyPacks) {
23952
24538
  risks.add("destructive_action");
23953
24539
  matchedPolicyRefs.push("guardian:builtin:destructive-action");
23954
24540
  }
23955
- if (promptManifest.source_permissions_snapshot.length === 0 && /\b(this|that|them|it|latest)\b/i.test(prompt)) {
24541
+ if (shouldOfferContextHelp(promptManifest, policyPacks)) {
23956
24542
  risks.add("insufficient_context");
23957
24543
  matchedPolicyRefs.push("guardian:builtin:context-sufficiency");
23958
24544
  }
@@ -24067,11 +24653,12 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24067
24653
  return void 0;
24068
24654
  }
24069
24655
  const fullSsnRisk = hasFullSsnRisk(riskReport.matched_policy_refs);
24656
+ const riskCopy = buildPromptCoachRiskCopy(riskReport, fullSsnRisk);
24070
24657
  const card = {
24071
24658
  card_id: `coach:${riskReport.risk_report_id}`,
24072
24659
  intervention_level: coachDecision.intervention_level,
24073
- title: disposition === "block" ? "Guardian blocked this prompt" : disposition === "confirm" ? "Guardian needs your confirmation" : coachDecision.intervention_level === "soft_tag_along" ? "Guardian found useful context" : fullSsnRisk ? "Guardian found an identity number" : "Guardian can improve this prompt",
24074
- body: disposition === "confirm" ? "This prompt includes higher-risk content and needs confirmation before it leaves your machine." : coachDecision.intervention_level === "soft_tag_along" ? "This prompt may work better with a little more local context attached first." : fullSsnRisk ? "Guardian can mask the Social Security number before anything leaves your machine." : "Guardian found context or privacy improvements that may make this prompt safer and more useful.",
24660
+ title: disposition === "block" ? "Guardian blocked this prompt" : disposition === "confirm" ? "Guardian needs your confirmation" : coachDecision.intervention_level === "soft_tag_along" ? "Guardian found useful context" : riskCopy.title,
24661
+ body: disposition === "confirm" ? `${riskCopy.body} Please review before it leaves your machine.` : coachDecision.intervention_level === "soft_tag_along" ? "This prompt may work better with a little more local context attached first." : riskCopy.body,
24075
24662
  recommended_choice_id: disposition === "confirm" ? "confirm-after-review" : coachDecision.intervention_level === "soft_tag_along" ? "add-context" : "use-guardian-version",
24076
24663
  choices: disposition === "confirm" ? [
24077
24664
  {
@@ -24096,8 +24683,8 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24096
24683
  resulting_disposition: "warn"
24097
24684
  },
24098
24685
  {
24099
- choice_id: "continue",
24100
- label: "Continue",
24686
+ choice_id: "send-as-is",
24687
+ label: "Send as-is",
24101
24688
  description: "Send without adding context.",
24102
24689
  resulting_disposition: "allow"
24103
24690
  }
@@ -24114,6 +24701,12 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24114
24701
  label: "Cancel",
24115
24702
  description: "Do not send this prompt.",
24116
24703
  resulting_disposition: "block"
24704
+ },
24705
+ {
24706
+ choice_id: "send-as-is",
24707
+ label: "Send as-is",
24708
+ description: "Send the original prompt after reviewing the identity-number risk.",
24709
+ resulting_disposition: "allow"
24117
24710
  }
24118
24711
  ] : [
24119
24712
  {
@@ -24134,6 +24727,60 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24134
24727
  const parsedCard = PromptCoachCardSchema.parse(card);
24135
24728
  return shouldQuietPromptCoachCard(parsedCard, disposition, learningSignals) ? void 0 : parsedCard;
24136
24729
  }
24730
+ function buildPromptCoachRiskCopy(riskReport, fullSsnRisk) {
24731
+ if (fullSsnRisk) {
24732
+ return {
24733
+ title: "Guardian found an identity number",
24734
+ body: "Guardian can mask the Social Security number before anything leaves your machine."
24735
+ };
24736
+ }
24737
+ if (riskReport.detected_risks.includes("secret")) {
24738
+ return {
24739
+ title: "Guardian found a secret or credential",
24740
+ body: "Guardian can remove tokens, keys, or passwords before this prompt leaves your machine."
24741
+ };
24742
+ }
24743
+ if (riskReport.detected_risks.includes("financial_data")) {
24744
+ return {
24745
+ title: "Guardian found financial information",
24746
+ body: "Guardian can mask financial details or help you keep them out of the prompt."
24747
+ };
24748
+ }
24749
+ if (riskReport.detected_risks.includes("healthcare_data")) {
24750
+ return {
24751
+ title: "Guardian found health information",
24752
+ body: "Guardian can help remove identifying health details before you send this prompt."
24753
+ };
24754
+ }
24755
+ if (riskReport.detected_risks.includes("prompt_injection")) {
24756
+ return {
24757
+ title: "Guardian found prompt-injection language",
24758
+ body: "Guardian noticed instructions that may try to override your intended task or safety rules."
24759
+ };
24760
+ }
24761
+ if (riskReport.detected_risks.includes("unsafe_destination")) {
24762
+ return {
24763
+ title: "Guardian found an unfamiliar destination",
24764
+ body: "Guardian noticed this prompt may be going somewhere that needs an extra check."
24765
+ };
24766
+ }
24767
+ if (riskReport.detected_risks.includes("pii")) {
24768
+ return {
24769
+ title: "Guardian found personal information",
24770
+ body: "Guardian can mask personal details before this prompt leaves your machine."
24771
+ };
24772
+ }
24773
+ if (riskReport.detected_risks.includes("work_data")) {
24774
+ return {
24775
+ title: "Guardian found work or customer data",
24776
+ body: "Guardian can help keep private work details out of the prompt or replace them with placeholders."
24777
+ };
24778
+ }
24779
+ return {
24780
+ title: "Guardian can improve this prompt",
24781
+ body: "Guardian found context or privacy improvements that may make this prompt safer and more useful."
24782
+ };
24783
+ }
24137
24784
  function shouldQuietPromptCoachCard(card, disposition, learningSignals) {
24138
24785
  if (!learningSignals || disposition === "block" || disposition === "confirm") {
24139
24786
  return false;
@@ -24218,6 +24865,19 @@ function containsFullSsn(prompt) {
24218
24865
  SSN_PATTERN.lastIndex = 0;
24219
24866
  return SSN_PATTERN.test(prompt);
24220
24867
  }
24868
+ function containsSensitiveWorkData(prompt) {
24869
+ return WORK_SENSITIVE_PATTERN.test(prompt) || WORK_ENTITY_CONTEXT_PATTERN.test(prompt) || WORK_CONTEXT_ENTITY_PATTERN.test(prompt);
24870
+ }
24871
+ function shouldOfferContextHelp(promptManifest, policyPacks) {
24872
+ if (promptManifest.source_permissions_snapshot.length > 0) {
24873
+ return false;
24874
+ }
24875
+ if (effectivePromptCoachMode(policyPacks) !== "hands_on") {
24876
+ return false;
24877
+ }
24878
+ const prompt = promptManifest.prompt_text;
24879
+ return CONTEXT_HELP_ACTION_PATTERN.test(prompt) && CONTEXT_HELP_REFERENCE_PATTERN.test(prompt);
24880
+ }
24221
24881
  function hasFullSsnRisk(policyRefs) {
24222
24882
  return policyRefs.includes("guardian:builtin:ssn-full-value");
24223
24883
  }
@@ -24316,7 +24976,7 @@ function digest2(value) {
24316
24976
  function isPresent(value) {
24317
24977
  return value !== void 0;
24318
24978
  }
24319
- var DEFAULT_RISK_THRESHOLDS, POLICY_PACK_PRESET_VERSION, HIGH_RISK_TERMS, SECRET_PATTERNS, EMAIL_PATTERN, SSN_PATTERN, SSN_CONTEXT_PATTERN, FINANCIAL_PATTERN, HEALTHCARE_PATTERN, WORK_PATTERN, PROMPT_INJECTION_PATTERN, GUARDIAN_POLICY_PACK_PRESETS;
24979
+ var DEFAULT_RISK_THRESHOLDS, POLICY_PACK_PRESET_VERSION, HIGH_RISK_TERMS, SECRET_PATTERNS, EMAIL_PATTERN, SSN_PATTERN, SSN_CONTEXT_PATTERN, FINANCIAL_PATTERN, HEALTHCARE_PATTERN, WORK_SENSITIVE_PATTERN, WORK_ENTITY_CONTEXT_PATTERN, WORK_CONTEXT_ENTITY_PATTERN, PROMPT_INJECTION_PATTERN, CONTEXT_HELP_ACTION_PATTERN, CONTEXT_HELP_REFERENCE_PATTERN, GUARDIAN_POLICY_PACK_PRESETS;
24320
24980
  var init_src3 = __esm({
24321
24981
  "packages/personal-fabric-runtime/src/index.ts"() {
24322
24982
  "use strict";
@@ -24341,8 +25001,12 @@ var init_src3 = __esm({
24341
25001
  SSN_CONTEXT_PATTERN = /\b(ssn|social security(?: number)?)\b/i;
24342
25002
  FINANCIAL_PATTERN = /\b(bank|wire|routing number|account number|payroll|invoice|revenue|forecast|contract value|credit card)\b/i;
24343
25003
  HEALTHCARE_PATTERN = /\b(patient|diagnosis|medication|hipaa|medical record|prescription|clinical)\b/i;
24344
- WORK_PATTERN = /\b(contract|client|customer|salesforce|pipeline|renewal|board|employee|confidential)\b/i;
25004
+ WORK_SENSITIVE_PATTERN = /\b(confidential|internal|non[-\s]?public|private|proprietary|salesforce|pipeline|renewal)\b/i;
25005
+ WORK_ENTITY_CONTEXT_PATTERN = /\b(?:client|customer|employee|vendor|partner)\b[\s\S]{0,80}\b(?:contract|account|record|data|list|pricing|renewal|pipeline|payroll|confidential|private|proprietary)\b/i;
25006
+ WORK_CONTEXT_ENTITY_PATTERN = /\b(?:contract|account|record|data|list|pricing|renewal|pipeline|payroll|confidential|private|proprietary)\b[\s\S]{0,80}\b(?:client|customer|employee|vendor|partner)\b/i;
24345
25007
  PROMPT_INJECTION_PATTERN = /\b(ignore previous instructions|system prompt|developer message|jailbreak|exfiltrate|bypass policy|reveal hidden)\b/i;
25008
+ CONTEXT_HELP_ACTION_PATTERN = /\b(summarize|summarise|analyze|analyse|review|compare|extract|rewrite|improve|turn|convert|update)\b/i;
25009
+ CONTEXT_HELP_REFERENCE_PATTERN = /\b(this|that|these|those|attached|uploaded|latest version|current version|file|document|deck|slide|email|thread|conversation|transcript|meeting|pdf)\b/i;
24346
25010
  GUARDIAN_POLICY_PACK_PRESETS = [
24347
25011
  createPolicyPackPreset({
24348
25012
  pack_id: "privacy-first",
@@ -25781,10 +26445,14 @@ __export(daemon_exports, {
25781
26445
  processGuardianControlTowerActivityRequest: () => processGuardianControlTowerActivityRequest,
25782
26446
  processGuardianControlTowerAppPermissionRequest: () => processGuardianControlTowerAppPermissionRequest,
25783
26447
  processGuardianControlTowerDataRequest: () => processGuardianControlTowerDataRequest,
26448
+ processGuardianControlTowerMarketplaceRequest: () => processGuardianControlTowerMarketplaceRequest,
26449
+ processGuardianControlTowerNoticeRequest: () => processGuardianControlTowerNoticeRequest,
25784
26450
  processGuardianControlTowerPolicyRequest: () => processGuardianControlTowerPolicyRequest,
25785
26451
  processGuardianControlTowerPreferenceRequest: () => processGuardianControlTowerPreferenceRequest,
26452
+ processGuardianControlTowerPrimePackRequest: () => processGuardianControlTowerPrimePackRequest,
25786
26453
  processGuardianControlTowerProviderPermissionRequest: () => processGuardianControlTowerProviderPermissionRequest,
25787
26454
  processGuardianControlTowerSourcePermissionRequest: () => processGuardianControlTowerSourcePermissionRequest,
26455
+ processGuardianControlTowerViewModelRequest: () => processGuardianControlTowerViewModelRequest,
25788
26456
  processGuardianDaemonHealthRequest: () => processGuardianDaemonHealthRequest,
25789
26457
  processGuardianDaemonStopRequest: () => processGuardianDaemonStopRequest,
25790
26458
  processGuardianLiveCoachEventRequest: () => processGuardianLiveCoachEventRequest,
@@ -25794,7 +26462,7 @@ __export(daemon_exports, {
25794
26462
  });
25795
26463
  import { createHash as createHash5, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
25796
26464
  import { existsSync as existsSync2 } from "node:fs";
25797
- import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, stat as stat3 } from "node:fs/promises";
26465
+ import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
25798
26466
  import { createServer } from "node:http";
25799
26467
  import path4 from "node:path";
25800
26468
  import { StringDecoder } from "node:string_decoder";
@@ -25920,12 +26588,22 @@ async function handleRequest(input2) {
25920
26588
  return;
25921
26589
  }
25922
26590
  if (input2.request.method === "GET" && (input2.request.url === "/control-tower" || input2.request.url === "/control-tower/")) {
25923
- const [receipts, searchGraph, learningEvents, localDataStatus, releaseDoctorStatus] = await Promise.all([
26591
+ const [
26592
+ receipts,
26593
+ searchGraph,
26594
+ learningEvents,
26595
+ localDataStatus,
26596
+ releaseDoctorStatus,
26597
+ adminNotices,
26598
+ marketplaceRequests
26599
+ ] = await Promise.all([
25924
26600
  input2.store.listReceipts(),
25925
26601
  input2.store.searchGraph({ limit: CONTROL_TOWER_GRAPH_NODE_LIMIT }),
25926
26602
  input2.store.listLearningEvents({ limit: CONTROL_TOWER_LEARNING_EVENT_LIMIT }),
25927
26603
  buildControlTowerLocalDataStatus(input2.profile, input2.env),
25928
- buildControlTowerReleaseDoctorStatus(input2.profile, input2.env)
26604
+ buildControlTowerReleaseDoctorStatus(input2.profile, input2.env),
26605
+ readControlTowerAdminNotices(input2.profile.profile_dir),
26606
+ readControlTowerMarketplaceRequests(input2.profile.profile_dir)
25929
26607
  ]);
25930
26608
  writeHtml(
25931
26609
  input2.response,
@@ -25936,7 +26614,9 @@ async function handleRequest(input2) {
25936
26614
  searchGraph,
25937
26615
  learningEvents,
25938
26616
  localDataStatus,
25939
- releaseDoctorStatus
26617
+ releaseDoctorStatus,
26618
+ adminNotices,
26619
+ marketplaceRequests
25940
26620
  ),
25941
26621
  {
25942
26622
  controlToken: input2.controlToken
@@ -25945,10 +26625,45 @@ async function handleRequest(input2) {
25945
26625
  return;
25946
26626
  }
25947
26627
  const activityUrl = parseGuardianUrl(input2.request.url);
25948
- if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/activity") {
25949
- const filter = parseActivityFilter(activityUrl.searchParams.get("filter") ?? "all");
25950
- const receipts = (await input2.store.listReceipts()).slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
25951
- const result2 = processGuardianControlTowerActivityRequest(receipts, filter);
26628
+ if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/control-tower/view-model") {
26629
+ const [
26630
+ receipts,
26631
+ searchGraph,
26632
+ learningEvents,
26633
+ localDataStatus,
26634
+ releaseDoctorStatus,
26635
+ adminNotices,
26636
+ marketplaceRequests
26637
+ ] = await Promise.all([
26638
+ input2.store.listReceipts(),
26639
+ input2.store.searchGraph({ limit: CONTROL_TOWER_GRAPH_NODE_LIMIT }),
26640
+ input2.store.listLearningEvents({ limit: CONTROL_TOWER_LEARNING_EVENT_LIMIT }),
26641
+ buildControlTowerLocalDataStatus(input2.profile, input2.env),
26642
+ buildControlTowerReleaseDoctorStatus(input2.profile, input2.env),
26643
+ readControlTowerAdminNotices(input2.profile.profile_dir),
26644
+ readControlTowerMarketplaceRequests(input2.profile.profile_dir)
26645
+ ]);
26646
+ const result2 = processGuardianControlTowerViewModelRequest(
26647
+ input2.profile,
26648
+ receipts,
26649
+ searchGraph,
26650
+ learningEvents,
26651
+ localDataStatus,
26652
+ releaseDoctorStatus,
26653
+ adminNotices,
26654
+ marketplaceRequests
26655
+ );
26656
+ writeJson(input2.response, result2.statusCode, result2.body);
26657
+ return;
26658
+ }
26659
+ if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/activity") {
26660
+ const filter = parseActivityFilter(activityUrl.searchParams.get("filter") ?? "all");
26661
+ const [receipts, searchGraph] = await Promise.all([
26662
+ input2.store.listReceipts(),
26663
+ input2.store.searchGraph({ limit: 100 })
26664
+ ]);
26665
+ const recentReceipts = receipts.slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
26666
+ const result2 = processGuardianControlTowerActivityRequest(recentReceipts, filter, searchGraph);
25952
26667
  writeJson(input2.response, result2.statusCode, result2.body);
25953
26668
  return;
25954
26669
  }
@@ -25989,6 +26704,24 @@ async function handleRequest(input2) {
25989
26704
  writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
25990
26705
  return;
25991
26706
  }
26707
+ if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/prime-pack") {
26708
+ const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
26709
+ if (!parsedBody2.ok) {
26710
+ writeJson(input2.response, parsedBody2.statusCode, parsedBody2.body);
26711
+ return;
26712
+ }
26713
+ const result2 = await processGuardianControlTowerPrimePackRequest({
26714
+ profile: input2.profile,
26715
+ token: input2.token,
26716
+ controlToken: input2.controlToken,
26717
+ suppliedToken: headerValue(input2.request, ADMIN_HEADER) ?? readCookieValue(input2.request, CONTROL_TOWER_COOKIE),
26718
+ body: parsedBody2.body,
26719
+ now: input2.now,
26720
+ store: input2.store
26721
+ });
26722
+ writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26723
+ return;
26724
+ }
25992
26725
  if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/preferences") {
25993
26726
  const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
25994
26727
  if (!parsedBody2.ok) {
@@ -26069,6 +26802,42 @@ async function handleRequest(input2) {
26069
26802
  writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26070
26803
  return;
26071
26804
  }
26805
+ if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/notice") {
26806
+ const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
26807
+ if (!parsedBody2.ok) {
26808
+ writeJson(input2.response, parsedBody2.statusCode, parsedBody2.body);
26809
+ return;
26810
+ }
26811
+ const result2 = await processGuardianControlTowerNoticeRequest({
26812
+ profile: input2.profile,
26813
+ token: input2.token,
26814
+ controlToken: input2.controlToken,
26815
+ suppliedToken: headerValue(input2.request, ADMIN_HEADER) ?? readCookieValue(input2.request, CONTROL_TOWER_COOKIE),
26816
+ body: parsedBody2.body,
26817
+ now: input2.now,
26818
+ store: input2.store
26819
+ });
26820
+ writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26821
+ return;
26822
+ }
26823
+ if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/marketplace-request") {
26824
+ const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
26825
+ if (!parsedBody2.ok) {
26826
+ writeJson(input2.response, parsedBody2.statusCode, parsedBody2.body);
26827
+ return;
26828
+ }
26829
+ const result2 = await processGuardianControlTowerMarketplaceRequest({
26830
+ profile: input2.profile,
26831
+ token: input2.token,
26832
+ controlToken: input2.controlToken,
26833
+ suppliedToken: headerValue(input2.request, ADMIN_HEADER) ?? readCookieValue(input2.request, CONTROL_TOWER_COOKIE),
26834
+ body: parsedBody2.body,
26835
+ now: input2.now,
26836
+ store: input2.store
26837
+ });
26838
+ writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26839
+ return;
26840
+ }
26072
26841
  if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/live-coach-event") {
26073
26842
  const suppliedToken2 = headerValue(input2.request, ADMIN_HEADER);
26074
26843
  const parsedBody2 = await readGuardianDaemonJsonBody(input2.request);
@@ -26127,7 +26896,7 @@ function processGuardianDaemonHealthRequest(input2) {
26127
26896
  }
26128
26897
  };
26129
26898
  }
26130
- function processGuardianControlTowerActivityRequest(receipts, filterValue) {
26899
+ function processGuardianControlTowerActivityRequest(receipts, filterValue, searchGraph = { nodes: [], edges: [] }) {
26131
26900
  const filter = parseActivityFilter(filterValue);
26132
26901
  return {
26133
26902
  statusCode: 200,
@@ -26137,10 +26906,27 @@ function processGuardianControlTowerActivityRequest(receipts, filterValue) {
26137
26906
  filter,
26138
26907
  raw_content_included: false,
26139
26908
  token_printed: false,
26140
- activities: buildActivityItems(receipts, filter)
26909
+ activities: buildActivityItems(receipts, filter, searchGraph)
26141
26910
  }
26142
26911
  };
26143
26912
  }
26913
+ function processGuardianControlTowerViewModelRequest(profile, receipts = [], searchGraph = { nodes: [], edges: [] }, learningEvents = [], localDataStatus = createEmptyControlTowerLocalDataStatus(profile), releaseDoctorStatus = createEmptyControlTowerReleaseDoctorStatus(
26914
+ profile
26915
+ ), adminNotices = [], marketplaceRequests = []) {
26916
+ return {
26917
+ statusCode: 200,
26918
+ body: buildControlTowerViewModel(
26919
+ profile,
26920
+ receipts,
26921
+ searchGraph,
26922
+ learningEvents,
26923
+ localDataStatus,
26924
+ releaseDoctorStatus,
26925
+ adminNotices,
26926
+ marketplaceRequests
26927
+ )
26928
+ };
26929
+ }
26144
26930
  function processGuardianDaemonStopRequest(input2) {
26145
26931
  if (!isAuthorized(input2.token, input2.suppliedToken)) {
26146
26932
  return {
@@ -26206,6 +26992,105 @@ async function processGuardianControlTowerPolicyRequest(input2) {
26206
26992
  };
26207
26993
  }
26208
26994
  }
26995
+ async function processGuardianControlTowerPrimePackRequest(input2) {
26996
+ if (!isAdminAuthorized(input2)) {
26997
+ return {
26998
+ statusCode: 401,
26999
+ body: {
27000
+ ok: false,
27001
+ error: "unauthorized"
27002
+ }
27003
+ };
27004
+ }
27005
+ const action = parsePrimePackAction(input2.body.action);
27006
+ if (!action) {
27007
+ return {
27008
+ statusCode: 400,
27009
+ body: {
27010
+ ok: false,
27011
+ error: "invalid_prime_pack_action"
27012
+ }
27013
+ };
27014
+ }
27015
+ const inputs = {
27016
+ sensitive_terms: parsePrimePackEntries([
27017
+ input2.body.sensitiveTerms,
27018
+ parsePrimePackImportEntries(input2.body.importContent, "sensitive_terms")
27019
+ ]),
27020
+ approved_domains: parsePrimePackEntries(
27021
+ [
27022
+ input2.body.approvedDomains,
27023
+ parsePrimePackImportEntries(input2.body.importContent, "approved_domains")
27024
+ ],
27025
+ { lowercase: true }
27026
+ ),
27027
+ blocked_destinations: parsePrimePackEntries(
27028
+ [
27029
+ input2.body.blockedDestinations,
27030
+ parsePrimePackImportEntries(input2.body.importContent, "blocked_destinations")
27031
+ ],
27032
+ {
27033
+ lowercase: true
27034
+ }
27035
+ ),
27036
+ escalation_phrases: parsePrimePackEntries([
27037
+ input2.body.escalationPhrases,
27038
+ parsePrimePackImportEntries(input2.body.importContent, "escalation_phrases")
27039
+ ])
27040
+ };
27041
+ const preview = buildPrimePackPreview(inputs);
27042
+ if (preview.total_inputs === 0) {
27043
+ return {
27044
+ statusCode: 400,
27045
+ body: {
27046
+ ok: false,
27047
+ error: "empty_prime_pack"
27048
+ }
27049
+ };
27050
+ }
27051
+ const now = (input2.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
27052
+ const draftSeed = JSON.stringify(inputs);
27053
+ const transactionId = `txn:prime-pack:${digest4(`${draftSeed}:${now}`).slice(0, 20)}`;
27054
+ const guardianReceipt = action === "deploy" ? buildPrimePackDeployGuardianReceipt(inputs, preview, transactionId, now) : void 0;
27055
+ const draft = {
27056
+ schema_version: "contextecf/project-guardian-prime-pack-draft/v1",
27057
+ draft_id: `prime-pack:${digest4(draftSeed).slice(0, 16)}`,
27058
+ status: action === "deploy" ? "deployed_metadata_only" : "preview_ready",
27059
+ updated_at: now,
27060
+ ...action === "deploy" ? { deployed_at: now } : {},
27061
+ inputs,
27062
+ preview,
27063
+ ...guardianReceipt ? {
27064
+ local_receipt: buildPrimePackDeployReceipt(guardianReceipt)
27065
+ } : {},
27066
+ raw_prompt_content_included: false,
27067
+ token_printed: false
27068
+ };
27069
+ if (guardianReceipt && input2.store) {
27070
+ await input2.store.putReceipt(guardianReceipt);
27071
+ await input2.store.appendEvent(
27072
+ buildPrimePackDeployEvent(inputs, preview, transactionId, now, guardianReceipt)
27073
+ );
27074
+ }
27075
+ await writeControlTowerPrimePackDraft(input2.profile.profile_dir, draft);
27076
+ return {
27077
+ statusCode: 200,
27078
+ body: {
27079
+ ok: true,
27080
+ schema_version: "contextecf/project-guardian-control-tower-prime-pack/v1",
27081
+ action,
27082
+ status: draft.status,
27083
+ draft_id: draft.draft_id,
27084
+ file: CONTROL_TOWER_PRIME_PACK_FILE_NAME,
27085
+ preview,
27086
+ local_receipt: draft.local_receipt,
27087
+ receipt_id: guardianReceipt?.receipt_id,
27088
+ stored_locally: true,
27089
+ raw_prompt_content_included: false,
27090
+ token_printed: false
27091
+ }
27092
+ };
27093
+ }
26209
27094
  async function processGuardianControlTowerPreferenceRequest(input2) {
26210
27095
  if (!isAdminAuthorized(input2)) {
26211
27096
  return {
@@ -26492,6 +27377,190 @@ async function processGuardianControlTowerDataRequest(input2) {
26492
27377
  };
26493
27378
  }
26494
27379
  }
27380
+ async function processGuardianControlTowerNoticeRequest(input2) {
27381
+ if (!isAdminAuthorized(input2)) {
27382
+ return {
27383
+ statusCode: 401,
27384
+ body: {
27385
+ ok: false,
27386
+ error: "unauthorized"
27387
+ }
27388
+ };
27389
+ }
27390
+ const action = input2.body.action?.trim() || "publish";
27391
+ if (action === "clear") {
27392
+ const clearedAt = input2.now().toISOString();
27393
+ const transactionId2 = `txn:admin-notice:${digest4(`clear:${clearedAt}`).slice(0, 20)}`;
27394
+ const receipt2 = buildAdminNoticeReceipt({
27395
+ action,
27396
+ transactionId: transactionId2,
27397
+ timestamp: clearedAt
27398
+ });
27399
+ await input2.store.putReceipt(receipt2);
27400
+ await input2.store.appendEvent(
27401
+ buildAdminNoticeEvent({ action, transactionId: transactionId2, timestamp: clearedAt, receipt: receipt2 })
27402
+ );
27403
+ await writeControlTowerAdminNotices(input2.profile.profile_dir, []);
27404
+ return {
27405
+ statusCode: 200,
27406
+ body: {
27407
+ ok: true,
27408
+ schema_version: "contextecf/project-guardian-control-tower-admin-notice/v1",
27409
+ action,
27410
+ notices: [],
27411
+ receipt_id: receipt2.receipt_id,
27412
+ raw_content_included: false,
27413
+ token_printed: false
27414
+ }
27415
+ };
27416
+ }
27417
+ if (action !== "publish") {
27418
+ return {
27419
+ statusCode: 400,
27420
+ body: {
27421
+ ok: false,
27422
+ error: "invalid_notice_action"
27423
+ }
27424
+ };
27425
+ }
27426
+ const title = boundedNoticeText(input2.body.title, CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT);
27427
+ const message = boundedNoticeText(input2.body.message, CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT);
27428
+ const audience = parseControlTowerAdminNoticeAudience(input2.body.audience);
27429
+ const tone = parseControlTowerAdminNoticeTone(input2.body.tone);
27430
+ if (!title || !message || !audience || !tone) {
27431
+ return {
27432
+ statusCode: 400,
27433
+ body: {
27434
+ ok: false,
27435
+ error: "invalid_notice_request"
27436
+ }
27437
+ };
27438
+ }
27439
+ const existingNotices = await readControlTowerAdminNotices(input2.profile.profile_dir);
27440
+ const createdAt = input2.now().toISOString();
27441
+ const transactionId = `txn:admin-notice:${digest4(
27442
+ `${title}:${message}:${audience}:${tone}:${createdAt}`
27443
+ ).slice(0, 20)}`;
27444
+ const receipt = buildAdminNoticeReceipt({
27445
+ action,
27446
+ transactionId,
27447
+ timestamp: createdAt,
27448
+ title,
27449
+ message,
27450
+ audience,
27451
+ tone
27452
+ });
27453
+ const notice = {
27454
+ id: `notice:${digest4(`${title}:${message}:${createdAt}`).slice(0, 16)}`,
27455
+ title,
27456
+ message,
27457
+ audience,
27458
+ tone,
27459
+ created_at: createdAt,
27460
+ receipt_id: receipt.receipt_id,
27461
+ receipt_hash: receipt.receipt_hash,
27462
+ raw_content_included: false
27463
+ };
27464
+ const notices = [notice, ...existingNotices].slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT);
27465
+ await input2.store.putReceipt(receipt);
27466
+ await input2.store.appendEvent(
27467
+ buildAdminNoticeEvent({
27468
+ action,
27469
+ transactionId,
27470
+ timestamp: createdAt,
27471
+ receipt,
27472
+ title,
27473
+ message,
27474
+ audience,
27475
+ tone
27476
+ })
27477
+ );
27478
+ await writeControlTowerAdminNotices(input2.profile.profile_dir, notices);
27479
+ return {
27480
+ statusCode: 200,
27481
+ body: {
27482
+ ok: true,
27483
+ schema_version: "contextecf/project-guardian-control-tower-admin-notice/v1",
27484
+ action,
27485
+ notice,
27486
+ notices,
27487
+ receipt_id: receipt.receipt_id,
27488
+ raw_content_included: false,
27489
+ token_printed: false
27490
+ }
27491
+ };
27492
+ }
27493
+ async function processGuardianControlTowerMarketplaceRequest(input2) {
27494
+ if (!isAdminAuthorized(input2)) {
27495
+ return {
27496
+ statusCode: 401,
27497
+ body: {
27498
+ ok: false,
27499
+ error: "unauthorized"
27500
+ }
27501
+ };
27502
+ }
27503
+ const action = parseMarketplaceRequestAction(input2.body.action);
27504
+ const targetId = input2.body.packId?.trim();
27505
+ const manifest = targetId ? findControlTowerMarketplaceManifest(targetId) : void 0;
27506
+ if (!action || !manifest) {
27507
+ return {
27508
+ statusCode: 400,
27509
+ body: {
27510
+ ok: false,
27511
+ error: "invalid_marketplace_request"
27512
+ }
27513
+ };
27514
+ }
27515
+ const requestedAt = input2.now().toISOString();
27516
+ const transactionId = `txn:marketplace-request:${digest4(
27517
+ `${manifest.marketplace_manifest_id}:${action}:${requestedAt}`
27518
+ ).slice(0, 20)}`;
27519
+ const receipt = buildMarketplaceRequestReceipt(manifest, action, transactionId, requestedAt);
27520
+ const request = {
27521
+ schema_version: "contextecf/project-guardian-marketplace-request/v1",
27522
+ request_id: `request:${transactionId}`,
27523
+ pack_id: manifest.pack_id,
27524
+ marketplace_manifest_id: manifest.marketplace_manifest_id,
27525
+ pack_version: manifest.pack_version,
27526
+ pack_name: manifest.display_name,
27527
+ publisher: manifest.publisher_display_name,
27528
+ action,
27529
+ status: "requested_metadata_only",
27530
+ requested_at: requestedAt,
27531
+ receipt_id: receipt.receipt_id,
27532
+ receipt_hash: receipt.receipt_hash,
27533
+ raw_content_included: false,
27534
+ token_printed: false
27535
+ };
27536
+ const existingRequests = await readControlTowerMarketplaceRequests(input2.profile.profile_dir);
27537
+ const requests = [
27538
+ request,
27539
+ ...existingRequests.filter(
27540
+ (existing) => existing.marketplace_manifest_id !== manifest.marketplace_manifest_id
27541
+ )
27542
+ ].slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT);
27543
+ await input2.store.putReceipt(receipt);
27544
+ await input2.store.appendEvent(
27545
+ buildMarketplaceRequestEvent(manifest, action, transactionId, requestedAt, receipt)
27546
+ );
27547
+ await writeControlTowerMarketplaceRequests(input2.profile.profile_dir, requests);
27548
+ return {
27549
+ statusCode: 200,
27550
+ body: {
27551
+ ok: true,
27552
+ schema_version: "contextecf/project-guardian-control-tower-marketplace-request/v1",
27553
+ action,
27554
+ status: request.status,
27555
+ request,
27556
+ request_file: CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME,
27557
+ receipt_id: receipt.receipt_id,
27558
+ stored_locally: true,
27559
+ raw_content_included: false,
27560
+ token_printed: false
27561
+ }
27562
+ };
27563
+ }
26495
27564
  function parseAppPermissionAction(value) {
26496
27565
  return value === "revoke" || value === "allow" ? value : void 0;
26497
27566
  }
@@ -26507,6 +27576,25 @@ function parseGuardianSourceType2(value) {
26507
27576
  }
26508
27577
  return GUARDIAN_SOURCE_TYPES.includes(value) ? value : void 0;
26509
27578
  }
27579
+ function parseControlTowerAdminNoticeAudience(value) {
27580
+ return value === "all_users" || value === "admins" || value === "developers" ? value : void 0;
27581
+ }
27582
+ function parseControlTowerAdminNoticeTone(value) {
27583
+ return value === "info" || value === "warning" || value === "success" ? value : void 0;
27584
+ }
27585
+ function boundedNoticeText(value, limit) {
27586
+ if (typeof value !== "string") {
27587
+ return void 0;
27588
+ }
27589
+ const trimmed = value.replace(/\s+/gu, " ").trim();
27590
+ if (!trimmed) {
27591
+ return void 0;
27592
+ }
27593
+ return trimmed.slice(0, limit);
27594
+ }
27595
+ function isRecord(value) {
27596
+ return typeof value === "object" && value !== null;
27597
+ }
26510
27598
  async function processGuardianBrowserBridgeRequest(input2) {
26511
27599
  if (!isAuthorized(input2.token, input2.suppliedToken)) {
26512
27600
  return {
@@ -26647,6 +27735,11 @@ async function processBrowserPrompt(input2) {
26647
27735
  visible_message: decision.visible_message,
26648
27736
  required_confirmation: decision.required_confirmation,
26649
27737
  coach_card: decision.coach_card,
27738
+ risk_report: {
27739
+ risk_tier: decision.risk_report.risk_tier,
27740
+ detected_risks: decision.risk_report.detected_risks,
27741
+ matched_policy_refs: decision.risk_report.matched_policy_refs
27742
+ },
26650
27743
  receipt_id: decision.receipt.receipt_id
26651
27744
  };
26652
27745
  }
@@ -26837,6 +27930,114 @@ function buildLiveCoachEvent(request, transactionId, timestamp, receipt) {
26837
27930
  redaction_state: "metadata_only"
26838
27931
  };
26839
27932
  }
27933
+ function parseMarketplaceRequestAction(value) {
27934
+ return value === "request_review" ? value : void 0;
27935
+ }
27936
+ function findControlTowerMarketplaceManifest(targetId) {
27937
+ return GUARDIAN_POLICY_PACK_MARKETPLACE_CATALOG.find(
27938
+ (manifest) => manifest.pack_id === targetId || manifest.marketplace_manifest_id === targetId
27939
+ );
27940
+ }
27941
+ function buildMarketplaceRequestReceipt(manifest, action, transactionId, timestamp) {
27942
+ const core = {
27943
+ transaction_id: transactionId,
27944
+ pack_id: manifest.pack_id,
27945
+ marketplace_manifest_id: manifest.marketplace_manifest_id,
27946
+ pack_version: manifest.pack_version,
27947
+ action,
27948
+ created_at: timestamp,
27949
+ raw_content_included: false
27950
+ };
27951
+ return {
27952
+ schema_version: PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION,
27953
+ receipt_id: `receipt:${transactionId}:marketplace-request`,
27954
+ transaction_id: transactionId,
27955
+ receipt_type: "policy",
27956
+ disposition: "allow",
27957
+ policy_basis: [
27958
+ "marketplace-request-metadata-only",
27959
+ `pack:${manifest.pack_id}`,
27960
+ `manifest:${manifest.marketplace_manifest_id}`
27961
+ ],
27962
+ receipt_hash: `sha256:${digest4(JSON.stringify(core))}`,
27963
+ raw_content_included: false,
27964
+ created_at: timestamp,
27965
+ summary: `Guardian recorded a marketplace review request for ${manifest.display_name}.`
27966
+ };
27967
+ }
27968
+ function buildMarketplaceRequestEvent(manifest, action, transactionId, timestamp, receipt) {
27969
+ const payload = {
27970
+ pack_id: manifest.pack_id,
27971
+ marketplace_manifest_id: manifest.marketplace_manifest_id,
27972
+ pack_version: manifest.pack_version,
27973
+ action,
27974
+ status: "requested_metadata_only",
27975
+ raw_content_included: false
27976
+ };
27977
+ return {
27978
+ event_id: `event:${transactionId}:marketplace-request`,
27979
+ transaction_id: transactionId,
27980
+ event_type: "policy_decided",
27981
+ occurred_at: timestamp,
27982
+ actor: "guardian-daemon",
27983
+ summary: `Marketplace pack review requested for ${manifest.display_name}.`,
27984
+ payload_hash: `sha256:${digest4(JSON.stringify(payload))}`,
27985
+ receipt_id: receipt.receipt_id,
27986
+ graph_refs: [`policy-pack:${manifest.pack_id}`, `receipt:${receipt.receipt_id}`],
27987
+ redaction_state: "metadata_only"
27988
+ };
27989
+ }
27990
+ function buildAdminNoticeReceipt(input2) {
27991
+ const core = {
27992
+ transaction_id: input2.transactionId,
27993
+ action: input2.action,
27994
+ title_hash: input2.title ? digest4(input2.title) : void 0,
27995
+ message_hash: input2.message ? digest4(input2.message) : void 0,
27996
+ audience: input2.audience ?? "all_users",
27997
+ tone: input2.tone ?? "info",
27998
+ created_at: input2.timestamp,
27999
+ raw_content_included: false
28000
+ };
28001
+ const audienceLabel = adminNoticeAudienceLabel(input2.audience ?? "all_users");
28002
+ return {
28003
+ schema_version: PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION,
28004
+ receipt_id: `receipt:${input2.transactionId}:admin-notice`,
28005
+ transaction_id: input2.transactionId,
28006
+ receipt_type: "policy",
28007
+ disposition: "allow",
28008
+ policy_basis: [
28009
+ "admin-notice-metadata-only",
28010
+ `action:${input2.action}`,
28011
+ `audience:${input2.audience ?? "all_users"}`
28012
+ ],
28013
+ receipt_hash: `sha256:${digest4(JSON.stringify(core))}`,
28014
+ raw_content_included: false,
28015
+ created_at: input2.timestamp,
28016
+ summary: input2.action === "clear" ? "Guardian cleared local admin notices." : `Guardian published a local admin notice for ${audienceLabel}.`
28017
+ };
28018
+ }
28019
+ function buildAdminNoticeEvent(input2) {
28020
+ const payload = {
28021
+ action: input2.action,
28022
+ title_hash: input2.title ? digest4(input2.title) : void 0,
28023
+ message_hash: input2.message ? digest4(input2.message) : void 0,
28024
+ audience: input2.audience ?? "all_users",
28025
+ tone: input2.tone ?? "info",
28026
+ raw_content_included: false
28027
+ };
28028
+ return {
28029
+ event_id: `event:${input2.transactionId}:admin-notice`,
28030
+ transaction_id: input2.transactionId,
28031
+ event_type: "policy_decided",
28032
+ occurred_at: input2.timestamp,
28033
+ actor: "guardian-daemon",
28034
+ summary: input2.action === "clear" ? "Admin notice bar cleared." : `Admin notice bar updated for ${adminNoticeAudienceLabel(input2.audience ?? "all_users")}.`,
28035
+ payload_hash: `sha256:${digest4(JSON.stringify(payload))}`,
28036
+ receipt_id: input2.receipt.receipt_id,
28037
+ graph_refs: [`admin-notice:${input2.action}`, `receipt:${input2.receipt.receipt_id}`],
28038
+ redaction_state: "metadata_only"
28039
+ };
28040
+ }
26840
28041
  function liveCoachCategoryLabel(category) {
26841
28042
  if (category === "ssn") return "possible SSN sharing";
26842
28043
  if (category === "password") return "possible credential sharing";
@@ -26952,6 +28153,218 @@ function isAdminAuthorized(input2) {
26952
28153
  function parsePolicyPackAction(value) {
26953
28154
  return value === "enable" || value === "disable" ? value : void 0;
26954
28155
  }
28156
+ function parsePrimePackAction(value) {
28157
+ return value === "preview" || value === "deploy" ? value : void 0;
28158
+ }
28159
+ function parsePrimePackEntries(value, options = {}) {
28160
+ const seen = /* @__PURE__ */ new Set();
28161
+ const entries = [];
28162
+ for (const rawEntry of flattenPrimePackEntryValues(value)) {
28163
+ const normalized = rawEntry.replace(/\s+/gu, " ").trim();
28164
+ if (!normalized) {
28165
+ continue;
28166
+ }
28167
+ const bounded = normalized.slice(0, CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT);
28168
+ const entry = options.lowercase ? bounded.toLowerCase() : bounded;
28169
+ const key = entry.toLowerCase();
28170
+ if (seen.has(key)) {
28171
+ continue;
28172
+ }
28173
+ seen.add(key);
28174
+ entries.push(entry);
28175
+ if (entries.length >= CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT) {
28176
+ break;
28177
+ }
28178
+ }
28179
+ return entries;
28180
+ }
28181
+ function flattenPrimePackEntryValues(value) {
28182
+ if (Array.isArray(value)) {
28183
+ return value.flatMap((entry) => flattenPrimePackEntryValues(entry));
28184
+ }
28185
+ if (typeof value !== "string") {
28186
+ return [];
28187
+ }
28188
+ return value.split(/[\n,;]+/u);
28189
+ }
28190
+ function parsePrimePackImportEntries(value, key) {
28191
+ if (typeof value !== "string" || !value.trim()) {
28192
+ return [];
28193
+ }
28194
+ const bounded = value.slice(0, CONTROL_TOWER_PRIME_PACK_IMPORT_LIMIT);
28195
+ const parsedJson = parsePrimePackImportJson(bounded, key);
28196
+ if (parsedJson.length > 0) {
28197
+ return parsedJson;
28198
+ }
28199
+ const rows = parsePrimePackImportRows(bounded);
28200
+ return rows.filter((row) => normalizePrimePackImportKey(row[0]) === key).flatMap((row) => row.slice(1));
28201
+ }
28202
+ function parsePrimePackImportJson(value, key) {
28203
+ const trimmed = value.trim();
28204
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
28205
+ return [];
28206
+ }
28207
+ try {
28208
+ const parsed = JSON.parse(trimmed);
28209
+ return extractPrimePackImportJsonValues(parsed, key);
28210
+ } catch {
28211
+ return [];
28212
+ }
28213
+ }
28214
+ function extractPrimePackImportJsonValues(value, key) {
28215
+ if (Array.isArray(value)) {
28216
+ return value.flatMap((entry) => extractPrimePackImportJsonValues(entry, key));
28217
+ }
28218
+ if (!isRecord(value)) {
28219
+ return [];
28220
+ }
28221
+ const direct = value[key];
28222
+ if (Array.isArray(direct)) {
28223
+ return direct.filter((entry) => typeof entry === "string");
28224
+ }
28225
+ if (typeof direct === "string") {
28226
+ return [direct];
28227
+ }
28228
+ const type = normalizePrimePackImportKey(value.type);
28229
+ const term = value.value ?? value.term ?? value.pattern ?? value.phrase ?? value.domain ?? value.destination;
28230
+ if (type === key && typeof term === "string") {
28231
+ return [term];
28232
+ }
28233
+ return [];
28234
+ }
28235
+ function parsePrimePackImportRows(value) {
28236
+ return value.split(/\r?\n/u).map((line) => splitPrimePackImportRow(line).map((cell) => cell.trim())).filter((row) => row.some(Boolean));
28237
+ }
28238
+ function splitPrimePackImportRow(line) {
28239
+ const delimiter = line.includes(" ") ? " " : ",";
28240
+ return line.split(delimiter);
28241
+ }
28242
+ function normalizePrimePackImportKey(value) {
28243
+ if (typeof value !== "string") {
28244
+ return void 0;
28245
+ }
28246
+ const normalized = value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
28247
+ switch (normalized) {
28248
+ case "sensitive":
28249
+ case "sensitive_term":
28250
+ case "sensitive_terms":
28251
+ case "term":
28252
+ case "terms":
28253
+ return "sensitive_terms";
28254
+ case "approved_domain":
28255
+ case "approved_domains":
28256
+ case "allow_domain":
28257
+ case "allowed_domain":
28258
+ case "domain":
28259
+ return "approved_domains";
28260
+ case "blocked_destination":
28261
+ case "blocked_destinations":
28262
+ case "blocked_domain":
28263
+ case "deny_destination":
28264
+ case "destination":
28265
+ return "blocked_destinations";
28266
+ case "escalation":
28267
+ case "escalation_phrase":
28268
+ case "escalation_phrases":
28269
+ case "confirmation_phrase":
28270
+ case "phrase":
28271
+ return "escalation_phrases";
28272
+ default:
28273
+ return void 0;
28274
+ }
28275
+ }
28276
+ function buildPrimePackPreview(inputs) {
28277
+ const totalInputs = inputs.sensitive_terms.length + inputs.approved_domains.length + inputs.blocked_destinations.length + inputs.escalation_phrases.length;
28278
+ const examples = [
28279
+ inputs.sensitive_terms.length > 0 ? `${inputs.sensitive_terms.length} sensitive term(s) will be flagged before prompt send.` : "",
28280
+ inputs.blocked_destinations.length > 0 ? `${inputs.blocked_destinations.length} blocked destination rule(s) will stop risky sharing.` : "",
28281
+ inputs.escalation_phrases.length > 0 ? `${inputs.escalation_phrases.length} escalation phrase(s) will require confirmation.` : "",
28282
+ inputs.approved_domains.length > 0 ? `${inputs.approved_domains.length} approved domain(s) will lower false positives.` : ""
28283
+ ].filter(Boolean);
28284
+ return {
28285
+ total_inputs: totalInputs,
28286
+ flag_rules: inputs.sensitive_terms.length,
28287
+ block_rules: inputs.blocked_destinations.length,
28288
+ confirm_rules: inputs.escalation_phrases.length,
28289
+ examples
28290
+ };
28291
+ }
28292
+ function buildPrimePackDeployReceipt(receipt) {
28293
+ return {
28294
+ receipt_id: receipt.receipt_id,
28295
+ action: "deploy",
28296
+ created_at: receipt.created_at,
28297
+ receipt_hash: receipt.receipt_hash,
28298
+ raw_prompt_content_included: false,
28299
+ token_printed: false
28300
+ };
28301
+ }
28302
+ function buildPrimePackDeployGuardianReceipt(inputs, preview, transactionId, timestamp) {
28303
+ const core = {
28304
+ transaction_id: transactionId,
28305
+ action: "deploy",
28306
+ input_counts: buildPrimePackInputCounts(inputs),
28307
+ preview,
28308
+ input_hashes: buildPrimePackInputHashes(inputs),
28309
+ created_at: timestamp,
28310
+ raw_content_included: false
28311
+ };
28312
+ return {
28313
+ schema_version: PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION,
28314
+ receipt_id: `receipt:${transactionId}:prime-pack-deploy`,
28315
+ transaction_id: transactionId,
28316
+ receipt_type: "policy",
28317
+ disposition: "allow",
28318
+ policy_basis: [
28319
+ "prime-pack-deploy-metadata-only",
28320
+ `inputs:${preview.total_inputs}`,
28321
+ `flag_rules:${preview.flag_rules}`,
28322
+ `block_rules:${preview.block_rules}`,
28323
+ `confirm_rules:${preview.confirm_rules}`
28324
+ ],
28325
+ receipt_hash: `sha256:${digest4(JSON.stringify(core))}`,
28326
+ raw_content_included: false,
28327
+ created_at: timestamp,
28328
+ summary: `Guardian deployed a local Prime Pack with ${preview.total_inputs} metadata rule input(s).`
28329
+ };
28330
+ }
28331
+ function buildPrimePackDeployEvent(inputs, preview, transactionId, timestamp, receipt) {
28332
+ const payload = {
28333
+ action: "deploy",
28334
+ input_counts: buildPrimePackInputCounts(inputs),
28335
+ input_hashes: buildPrimePackInputHashes(inputs),
28336
+ preview,
28337
+ raw_content_included: false
28338
+ };
28339
+ return {
28340
+ event_id: `event:${transactionId}:prime-pack-deploy`,
28341
+ transaction_id: transactionId,
28342
+ event_type: "policy_decided",
28343
+ occurred_at: timestamp,
28344
+ actor: "guardian-daemon",
28345
+ summary: "Prime Pack deployment recorded with metadata-only hashes.",
28346
+ payload_hash: `sha256:${digest4(JSON.stringify(payload))}`,
28347
+ receipt_id: receipt.receipt_id,
28348
+ graph_refs: ["policy-pack:prime-pack", `receipt:${receipt.receipt_id}`],
28349
+ redaction_state: "metadata_only"
28350
+ };
28351
+ }
28352
+ function buildPrimePackInputCounts(inputs) {
28353
+ return {
28354
+ sensitive_terms: inputs.sensitive_terms.length,
28355
+ approved_domains: inputs.approved_domains.length,
28356
+ blocked_destinations: inputs.blocked_destinations.length,
28357
+ escalation_phrases: inputs.escalation_phrases.length
28358
+ };
28359
+ }
28360
+ function buildPrimePackInputHashes(inputs) {
28361
+ return {
28362
+ sensitive_terms: inputs.sensitive_terms.map((entry) => `sha256:${digest4(entry)}`),
28363
+ approved_domains: inputs.approved_domains.map((entry) => `sha256:${digest4(entry)}`),
28364
+ blocked_destinations: inputs.blocked_destinations.map((entry) => `sha256:${digest4(entry)}`),
28365
+ escalation_phrases: inputs.escalation_phrases.map((entry) => `sha256:${digest4(entry)}`)
28366
+ };
28367
+ }
26955
28368
  function parsePromptCoachMode2(value) {
26956
28369
  return PROMPT_COACH_MODES2.includes(value) ? value : void 0;
26957
28370
  }
@@ -27031,7 +28444,7 @@ function writeHtml(response, statusCode, body, options = {}) {
27031
28444
  response.writeHead(statusCode, {
27032
28445
  "Content-Type": "text/html; charset=utf-8",
27033
28446
  "Referrer-Policy": "no-referrer",
27034
- "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'",
28447
+ "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'",
27035
28448
  "X-Content-Type-Options": "nosniff"
27036
28449
  });
27037
28450
  response.end(body);
@@ -27068,9 +28481,158 @@ function parseGuardianUrl(url2) {
27068
28481
  return void 0;
27069
28482
  }
27070
28483
  }
28484
+ function buildControlTowerViewModel(profile, receipts, searchGraph, learningEvents, localDataStatus, releaseDoctorStatus, adminNotices = [], marketplaceRequests = []) {
28485
+ const recentReceipts = receipts.slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
28486
+ const blocked = recentReceipts.filter((receipt) => receipt.disposition === "block").length;
28487
+ const flagged = recentReceipts.filter(
28488
+ (receipt) => receipt.disposition === "warn" || receipt.disposition === "confirm"
28489
+ ).length;
28490
+ const allowed = recentReceipts.filter(
28491
+ (receipt) => receipt.disposition === "allow" || receipt.disposition === "rewrite"
28492
+ ).length;
28493
+ const privacyDefaults = profile.privacy.privacy_mode_enabled && !profile.privacy.raw_content_capture_enabled && !profile.privacy.cloud_sync_enabled && !profile.privacy.local_app_autopilot_enabled ? "on" : "needs review";
28494
+ const protectedState = releaseDoctorStatus.doctor.checkCounts.fail === 0 && privacyDefaults === "on";
28495
+ return {
28496
+ ok: true,
28497
+ schema_version: "contextecf/project-guardian-control-tower-view-model/v1",
28498
+ surfaces: [
28499
+ {
28500
+ id: "personal",
28501
+ label: "Personal Guardian",
28502
+ audience: "Individual users",
28503
+ purpose: "Protection status, prompt coaching, privacy, receipts, and browser connection."
28504
+ },
28505
+ {
28506
+ id: "admin",
28507
+ label: "Guardian Admin",
28508
+ audience: "Organizations",
28509
+ purpose: "Groups, policy packs, marketplace approvals, Prime Pack, scope, and audit."
28510
+ },
28511
+ {
28512
+ id: "power_user",
28513
+ label: "Power User",
28514
+ audience: "Developers and technical operators",
28515
+ purpose: "MCP, local data, doctor checks, receipts, and release evidence."
28516
+ },
28517
+ {
28518
+ id: "marketplace_contributor",
28519
+ label: "Marketplace Contributor",
28520
+ audience: "Policy pack creators",
28521
+ purpose: "Submission metadata, examples, license terms, and review state."
28522
+ }
28523
+ ],
28524
+ personal: {
28525
+ protectionLabel: protectedState ? "You're Protected" : "Needs Review",
28526
+ protectionDetail: protectedState ? "Guardian is running locally with metadata-only privacy defaults." : "Guardian is running, but one or more install checks need attention.",
28527
+ privacyDefaults,
28528
+ activePolicyPacks: [...profile.policy_packs.active],
28529
+ activityCounts: { blocked, flagged, allowed }
28530
+ },
28531
+ admin: {
28532
+ organizationLabel: "Example organization deployment",
28533
+ notices: adminNotices.slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT),
28534
+ marketplaceRequests: marketplaceRequests.slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT),
28535
+ managedGroups: buildControlTowerDeploymentGroups(profile),
28536
+ primePack: {
28537
+ status: "draft_ready",
28538
+ supportedInputs: ["typed terms", "text upload", "CSV upload", "JSON upload"],
28539
+ deployable: true,
28540
+ receiptRequired: true
28541
+ }
28542
+ },
28543
+ marketplace: buildControlTowerMarketplaceRows(profile, marketplaceRequests),
28544
+ releaseAutomation: {
28545
+ repository: GUARDIAN_RELEASE_REPOSITORY,
28546
+ workflow: GUARDIAN_RELEASE_DISPATCH_WORKFLOW,
28547
+ npmPackage: GUARDIAN_NPM_PACKAGE_NAME,
28548
+ registry: GUARDIAN_NPM_REGISTRY,
28549
+ chromeExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
28550
+ dryRunCommand: CONTROL_TOWER_DRY_RUN_COMMAND_TEMPLATE,
28551
+ publishCommand: CONTROL_TOWER_RELEASE_COMMAND_TEMPLATE,
28552
+ humanIntervention: [
28553
+ "Chrome Web Store upload/review remains a store-console step until Google approval APIs are wired.",
28554
+ "Native installer signing still requires platform signing credentials.",
28555
+ `Latest doctor status: ${releaseDoctorStatus.doctor.overall}.`,
28556
+ `Search Graph has ${searchGraph.nodes.length} node(s); Learning Graph has ${learningEvents.length} event(s).`,
28557
+ `Local data encryption: ${localDataStatus.encryptionStatus}.`
28558
+ ]
28559
+ },
28560
+ raw_content_included: false,
28561
+ token_printed: false
28562
+ };
28563
+ }
28564
+ function buildControlTowerDeploymentGroups(profile) {
28565
+ return [
28566
+ {
28567
+ id: "group:individual-default",
28568
+ label: "Personal default",
28569
+ users: 1,
28570
+ posture: profile.posture_profile.selected,
28571
+ packs: [...profile.policy_packs.active]
28572
+ },
28573
+ {
28574
+ id: "group:employees",
28575
+ label: "Employees",
28576
+ users: 24,
28577
+ posture: "balanced",
28578
+ packs: ["privacy-first", "executive"]
28579
+ },
28580
+ {
28581
+ id: "group:technical-users",
28582
+ label: "Technical users",
28583
+ users: 8,
28584
+ posture: "guided",
28585
+ packs: ["privacy-first", "developer"]
28586
+ },
28587
+ {
28588
+ id: "group:high-risk-functions",
28589
+ label: "High-risk functions",
28590
+ users: 5,
28591
+ posture: "high_assurance",
28592
+ packs: ["privacy-first", "finance"]
28593
+ }
28594
+ ];
28595
+ }
28596
+ function buildControlTowerMarketplaceRows(profile, marketplaceRequests = []) {
28597
+ const activePacks = new Set(profile.policy_packs.active);
28598
+ const requestsByManifest = new Map(
28599
+ marketplaceRequests.map((request) => [request.marketplace_manifest_id, request])
28600
+ );
28601
+ return GUARDIAN_POLICY_PACK_MARKETPLACE_CATALOG.map((manifest) => {
28602
+ const request = requestsByManifest.get(manifest.marketplace_manifest_id);
28603
+ const connected = activePacks.has(manifest.pack_id);
28604
+ return {
28605
+ packId: manifest.pack_id,
28606
+ name: manifest.display_name,
28607
+ version: manifest.pack_version,
28608
+ audience: manifest.audiences.join(", "),
28609
+ category: manifest.policy_domains.join(", "),
28610
+ publisher: manifest.publisher_display_name,
28611
+ price: manifest.price_model,
28612
+ licenseRef: manifest.license_ref,
28613
+ supportRef: manifest.support_ref ?? "Not provided",
28614
+ state: connected ? "connected" : request?.status ?? manifest.listing_status,
28615
+ permissions: [
28616
+ manifest.provider_rules_summary,
28617
+ manifest.source_rules_summary,
28618
+ manifest.app_permission_summary
28619
+ ],
28620
+ protectedDataTypes: manifest.protected_data_types,
28621
+ examples: [
28622
+ manifest.short_description,
28623
+ manifest.confirmation_rules_summary,
28624
+ manifest.data_handling_summary
28625
+ ],
28626
+ risk: manifest.limitations.length > 0 ? manifest.limitations[0] : "Standard false-positive review recommended before wide deployment.",
28627
+ requestState: connected ? "connected" : request?.status ?? "not_requested",
28628
+ requestReceiptId: request?.receipt_id,
28629
+ requestedAt: request?.requested_at
28630
+ };
28631
+ });
28632
+ }
27071
28633
  function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { nodes: [], edges: [] }, learningEvents = [], localDataStatus = createEmptyControlTowerLocalDataStatus(profile), releaseDoctorStatus = createEmptyControlTowerReleaseDoctorStatus(
27072
28634
  profile
27073
- )) {
28635
+ ), adminNotices = [], marketplaceRequests = []) {
27074
28636
  const policyPacks = profile.policy_packs.available.length;
27075
28637
  const activePolicyPacks = profile.policy_packs.active.join(", ") || "none";
27076
28638
  const recentReceipts = receipts.slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
@@ -27199,7 +28761,7 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27199
28761
  <div class="metric-detail">${escapeHtml(metric.detail)}</div>
27200
28762
  </article>`
27201
28763
  ).join("\n");
27202
- const activityFeedHtml = renderActivityFeed(recentReceipts);
28764
+ const activityFeedHtml = renderActivityFeed(recentReceipts, searchGraph);
27203
28765
  const postureCardsHtml = GUARDIAN_POSTURE_PROFILE_CATALOG.map((postureProfile) => {
27204
28766
  const selected = profile.posture_profile.selected === postureProfile.id;
27205
28767
  const icon = postureProfile.id === "calm" ? "&#9790;" : postureProfile.id === "balanced" ? "&#9878;" : postureProfile.id === "guided" ? "&#127891;" : postureProfile.id === "high_assurance" ? "&#128274;" : "&#9881;";
@@ -27248,9 +28810,28 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27248
28810
  <button type="submit" class="provider-pill" aria-label="Revoke ${escapeHtml(providerDisplayName(row.id))}"><span></span>${escapeHtml(providerDisplayName(row.id))}</button>
27249
28811
  </form>`
27250
28812
  ).join("\n");
28813
+ const viewModel = buildControlTowerViewModel(
28814
+ profile,
28815
+ recentReceipts,
28816
+ searchGraph,
28817
+ learningEvents,
28818
+ localDataStatus,
28819
+ releaseDoctorStatus,
28820
+ adminNotices,
28821
+ marketplaceRequests
28822
+ );
28823
+ const roleCardsHtml = renderControlTowerRoleCards(viewModel);
28824
+ const adminNoticeBarHtml = renderAdminNoticeBar(viewModel.admin.notices);
28825
+ const adminPanelsHtml = renderGuardianAdminSurface(
28826
+ viewModel,
28827
+ profile,
28828
+ recentReceipts,
28829
+ appPermissionRows
28830
+ );
28831
+ const localGuardianStatus = renderLocalGuardianStatus(profile.runtime.daemon_status);
27251
28832
  const rows = [
27252
28833
  ["Profile", profile.profile_dir],
27253
- ["Daemon", profile.runtime.daemon_status],
28834
+ ["Local Guardian", localGuardianStatus],
27254
28835
  [
27255
28836
  "Posture Profile",
27256
28837
  `${profile.posture_profile.selected}; managed baseline ${profile.posture_profile.managed_baseline_id ?? "none"}`
@@ -27418,6 +28999,161 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27418
28999
  background: rgba(15, 23, 42, 0.78);
27419
29000
  font-size: 16px;
27420
29001
  }
29002
+ .surface-tabs, .role-grid, .admin-grid, .marketplace-grid {
29003
+ display: grid;
29004
+ gap: 12px;
29005
+ }
29006
+ .surface-tabs {
29007
+ grid-template-columns: repeat(2, minmax(0, 1fr));
29008
+ margin: 28px 0 16px;
29009
+ }
29010
+ .surface-tab {
29011
+ display: grid;
29012
+ gap: 6px;
29013
+ width: 100%;
29014
+ min-height: 78px;
29015
+ padding: 15px 16px;
29016
+ border-color: rgba(148, 163, 184, 0.2);
29017
+ background: rgba(13, 24, 40, 0.84);
29018
+ color: #eff6ff;
29019
+ text-align: left;
29020
+ }
29021
+ .surface-tab strong, .role-card strong, .admin-card strong { font-size: 17px; }
29022
+ .surface-tab span, .role-card span, .admin-card span, .marketplace-card span {
29023
+ color: #98a7bc;
29024
+ font-size: 13px;
29025
+ line-height: 1.35;
29026
+ }
29027
+ .surface-tab.is-active, .surface-tab[aria-pressed="true"] {
29028
+ border-color: rgba(34, 197, 94, 0.5);
29029
+ background: rgba(6, 45, 37, 0.78);
29030
+ box-shadow: inset 0 0 0 1px rgba(34, 197, 94, 0.12);
29031
+ }
29032
+ .surface-panel[hidden] { display: none; }
29033
+ .role-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
29034
+ .role-card, .admin-card, .marketplace-card, .prime-pack-builder, .release-command-card {
29035
+ border: 1px solid rgba(148, 163, 184, 0.18);
29036
+ border-radius: 8px;
29037
+ background: rgba(11, 24, 40, 0.82);
29038
+ padding: 18px;
29039
+ }
29040
+ .admin-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
29041
+ .marketplace-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
29042
+ .marketplace-card h3, .admin-card h3, .prime-pack-builder h3, .release-command-card h3 {
29043
+ margin: 0 0 8px;
29044
+ color: #f6f8ff;
29045
+ font-size: 18px;
29046
+ }
29047
+ .marketplace-card p, .admin-card p, .prime-pack-builder p, .release-command-card p {
29048
+ margin-bottom: 12px;
29049
+ }
29050
+ .marketplace-detail-drawer {
29051
+ margin-top: 14px;
29052
+ border-top: 1px solid rgba(148, 163, 184, 0.16);
29053
+ padding-top: 12px;
29054
+ }
29055
+ .marketplace-detail-drawer summary {
29056
+ cursor: pointer;
29057
+ color: #dce7f7;
29058
+ font-size: 14px;
29059
+ font-weight: 800;
29060
+ }
29061
+ .marketplace-detail-drawer dl {
29062
+ display: grid;
29063
+ gap: 8px;
29064
+ margin: 12px 0 0;
29065
+ }
29066
+ .marketplace-detail-drawer dt {
29067
+ color: #f6f8ff;
29068
+ font-size: 13px;
29069
+ font-weight: 800;
29070
+ }
29071
+ .marketplace-detail-drawer dd {
29072
+ margin: 0;
29073
+ color: #98a7bc;
29074
+ font-size: 13px;
29075
+ line-height: 1.45;
29076
+ }
29077
+ .marketplace-request-form {
29078
+ display: grid;
29079
+ gap: 8px;
29080
+ margin-top: 14px;
29081
+ }
29082
+ .marketplace-request-form span,
29083
+ .marketplace-request-state {
29084
+ margin: 14px 0 0;
29085
+ color: #9fb0c6;
29086
+ font-size: 13px;
29087
+ line-height: 1.45;
29088
+ }
29089
+ .marketplace-request-list {
29090
+ display: grid;
29091
+ gap: 10px;
29092
+ margin-top: 16px;
29093
+ }
29094
+ .marketplace-request-card {
29095
+ display: grid;
29096
+ gap: 6px;
29097
+ padding: 12px 14px;
29098
+ border: 1px solid rgba(34, 197, 94, 0.28);
29099
+ border-radius: 8px;
29100
+ background: rgba(6, 45, 37, 0.52);
29101
+ }
29102
+ .marketplace-request-card span {
29103
+ color: #9fb0c6;
29104
+ font-size: 13px;
29105
+ }
29106
+ .metadata-list {
29107
+ display: grid;
29108
+ gap: 8px;
29109
+ margin: 14px 0 0;
29110
+ padding: 0;
29111
+ list-style: none;
29112
+ }
29113
+ .metadata-list li {
29114
+ display: flex;
29115
+ gap: 8px;
29116
+ justify-content: space-between;
29117
+ border-top: 1px solid rgba(148, 163, 184, 0.12);
29118
+ padding-top: 8px;
29119
+ color: #c8d2e3;
29120
+ font-size: 13px;
29121
+ }
29122
+ .metadata-list b { color: #eff6ff; }
29123
+ .builder-grid {
29124
+ display: grid;
29125
+ grid-template-columns: repeat(2, minmax(0, 1fr));
29126
+ gap: 12px;
29127
+ margin-top: 12px;
29128
+ }
29129
+ .button-row {
29130
+ display: flex;
29131
+ flex-wrap: wrap;
29132
+ gap: 10px;
29133
+ margin-top: 12px;
29134
+ }
29135
+ textarea {
29136
+ width: 100%;
29137
+ min-height: 104px;
29138
+ border: 1px solid rgba(148, 163, 184, 0.24);
29139
+ border-radius: 8px;
29140
+ background: rgba(8, 13, 24, 0.92);
29141
+ color: #f6f8ff;
29142
+ padding: 10px;
29143
+ font: inherit;
29144
+ resize: vertical;
29145
+ }
29146
+ .command-box {
29147
+ display: block;
29148
+ width: 100%;
29149
+ padding: 12px;
29150
+ border: 1px solid rgba(148, 163, 184, 0.2);
29151
+ border-radius: 8px;
29152
+ background: rgba(5, 10, 20, 0.88);
29153
+ color: #d8f8e7;
29154
+ overflow-x: auto;
29155
+ white-space: nowrap;
29156
+ }
27421
29157
  .eyebrow {
27422
29158
  width: fit-content;
27423
29159
  margin-bottom: 16px;
@@ -27599,6 +29335,22 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27599
29335
  }
27600
29336
  .provider-pill:hover { background: rgba(30, 41, 59, 0.78); }
27601
29337
  .provider-pill span { width: 8px; height: 8px; border-radius: 999px; background: #22c55e; }
29338
+ .notice-bar { display: grid; gap: 10px; margin: 20px 0 26px; }
29339
+ .notice-card {
29340
+ display: grid;
29341
+ gap: 6px;
29342
+ padding: 14px 16px;
29343
+ border: 1px solid rgba(96, 165, 250, 0.32);
29344
+ border-radius: 8px;
29345
+ color: #d8e1f0;
29346
+ background: rgba(15, 23, 42, 0.78);
29347
+ }
29348
+ .notice-card.warning { border-color: rgba(245, 158, 11, 0.46); background: rgba(39, 26, 9, 0.82); }
29349
+ .notice-card.success { border-color: rgba(34, 197, 94, 0.46); background: rgba(6, 45, 37, 0.82); }
29350
+ .notice-card strong { color: #f6f8ff; font-size: 16px; }
29351
+ .notice-card span { color: #91a1b8; font-size: 13px; font-weight: 700; }
29352
+ .notice-card p { margin: 0; color: #d8e1f0; line-height: 1.45; }
29353
+ .notice-form { display: grid; gap: 12px; }
27602
29354
  .activity-heading { justify-content: space-between; gap: 16px; margin-bottom: 12px; }
27603
29355
  .activity-heading h2 { margin: 0; }
27604
29356
  .filter-chips { gap: 8px; flex-wrap: wrap; }
@@ -27619,6 +29371,14 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27619
29371
  .chip:hover { color: #f5f7fb; background: rgba(30, 41, 59, 0.78); }
27620
29372
  .chip.is-active, .chip[aria-pressed="true"] { border-color: rgba(34, 197, 94, 0.38); color: #d7ffe5; background: rgba(21, 128, 61, 0.22); }
27621
29373
  .activity-card[hidden], .empty-filter-state[hidden] { display: none; }
29374
+ .receipt-row.is-selected-receipt {
29375
+ outline: 2px solid rgba(56, 230, 120, 0.7);
29376
+ outline-offset: 4px;
29377
+ background: rgba(34, 197, 94, 0.08);
29378
+ }
29379
+ .receipt-row.is-selected-receipt details {
29380
+ border-radius: 8px;
29381
+ }
27622
29382
  .empty-filter-state {
27623
29383
  padding: 22px;
27624
29384
  border: 1px dashed rgba(148, 163, 184, 0.24);
@@ -27676,6 +29436,7 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27676
29436
  .protection-card { min-height: 320px; }
27677
29437
  .activity-card { grid-template-columns: 48px 1fr; }
27678
29438
  .activity-badge { grid-column: 2; width: fit-content; }
29439
+ .surface-tabs, .role-grid, .admin-grid, .marketplace-grid, .builder-grid { grid-template-columns: 1fr; }
27679
29440
  section { padding: 16px; overflow-x: auto; }
27680
29441
  table { min-width: 720px; }
27681
29442
  .activity-heading { align-items: flex-start; flex-direction: column; }
@@ -27688,7 +29449,7 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27688
29449
  <header class="topbar">
27689
29450
  <div class="brand"><span class="brand-mark">G</span><span>Guardian</span></div>
27690
29451
  <div class="topbar-actions">
27691
- <span class="status-pill"><span class="status-dot"></span>${escapeHtml(profile.runtime.daemon_status)}</span>
29452
+ <span class="status-pill"><span class="status-dot"></span>${escapeHtml(localGuardianStatus)}</span>
27692
29453
  <span class="icon-button" aria-label="Settings">&#9881;</span>
27693
29454
  </div>
27694
29455
  </header>
@@ -27696,6 +29457,25 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27696
29457
  <div class="eyebrow">Local-first status</div>
27697
29458
  <h1>Project Guardian Control Tower</h1>
27698
29459
  <p class="intro">This local page confirms Guardian is watching your AI surface with privacy-first, metadata-only governance. It never exposes the runtime token, raw prompts, raw responses, or local action payloads.</p>
29460
+ ${adminNoticeBarHtml}
29461
+ <div class="surface-tabs" aria-label="Control Tower surfaces">
29462
+ <button type="button" class="surface-tab is-active" data-surface-tab="personal" aria-pressed="true">
29463
+ <strong>Personal Guardian</strong>
29464
+ <span>Simple protection, privacy, coaching, browser connection, and receipts.</span>
29465
+ </button>
29466
+ <button type="button" class="surface-tab" data-surface-tab="admin" aria-pressed="false">
29467
+ <strong>Guardian Admin</strong>
29468
+ <span>Policy packs, marketplace approvals, Prime Pack, deployment, audit, and releases.</span>
29469
+ </button>
29470
+ </div>
29471
+ <section aria-labelledby="role-experiences">
29472
+ <h2 id="role-experiences">Role Experiences</h2>
29473
+ <p>Guardian ships one local brain with different surfaces for individuals, power users, enterprise admins, and marketplace contributors.</p>
29474
+ <div class="role-grid">
29475
+ ${roleCardsHtml}
29476
+ </div>
29477
+ </section>
29478
+ <div class="surface-panel" data-surface-panel="personal">
27699
29479
  <div class="hero-grid" aria-label="Guardian protection overview">
27700
29480
  <article class="protection-card">
27701
29481
  <div class="${protectionRingClass}"><div class="ring-core">G</div></div>
@@ -27717,6 +29497,7 @@ ${heroMetricCards}
27717
29497
  </div>
27718
29498
  </div>
27719
29499
  <p class="activity-filter-status" data-activity-filter-status aria-live="polite">Showing all local Guardian activity.</p>
29500
+ <p class="activity-filter-status" data-guardian-time-zone-label>Times use your browser's local timezone.</p>
27720
29501
  <div class="activity-list" data-activity-list>
27721
29502
  ${activityFeedHtml}
27722
29503
  <div class="empty-filter-state" data-activity-empty hidden>No activity matches this filter yet.</div>
@@ -27885,7 +29666,7 @@ ${releaseDoctorRows}
27885
29666
  </tbody>
27886
29667
  </table>
27887
29668
  </section>
27888
- <section aria-labelledby="receipt-drilldowns">
29669
+ <section id="receipts" aria-labelledby="receipt-drilldowns">
27889
29670
  <div class="activity-heading">
27890
29671
  <h2 id="receipt-drilldowns">What Guardian did today</h2>
27891
29672
  <div class="filter-chips" aria-label="Activity filters">
@@ -27895,7 +29676,7 @@ ${releaseDoctorRows}
27895
29676
  <span class="chip">Allowed</span>
27896
29677
  </div>
27897
29678
  </div>
27898
- <p><span class="visually-hidden">Activity &amp; Receipts.</span>Inspect recent Guardian decisions without raw prompt, response, or local-action payload content.</p>
29679
+ <p><span class="visually-hidden">Activity &amp; Receipts.</span>Inspect recent Guardian decisions without raw prompt, response, or local-action payload content. Links from Halo open the matching receipt here.</p>
27899
29680
  <table aria-label="Guardian receipt drilldowns">
27900
29681
  <thead>
27901
29682
  <tr><th scope="col">Receipt</th><th scope="col">Type</th><th scope="col">Decision</th><th scope="col">Details</th></tr>
@@ -27975,9 +29756,47 @@ ${learningEventRows}
27975
29756
  </section>
27976
29757
  </div>
27977
29758
  </details>
29759
+ </div>
29760
+ <div class="surface-panel" data-surface-panel="admin" hidden>
29761
+ ${adminPanelsHtml}
29762
+ </div>
27978
29763
  </main>
27979
29764
  <script>
27980
29765
  (() => {
29766
+ const surfaceTabs = Array.from(document.querySelectorAll('[data-surface-tab]'));
29767
+ const surfacePanels = Array.from(document.querySelectorAll('[data-surface-panel]'));
29768
+ const selectSurface = (surface) => {
29769
+ for (const tab of surfaceTabs) {
29770
+ const selected = tab.getAttribute('data-surface-tab') === surface;
29771
+ tab.classList.toggle('is-active', selected);
29772
+ tab.setAttribute('aria-pressed', selected ? 'true' : 'false');
29773
+ }
29774
+ for (const panel of surfacePanels) {
29775
+ panel.hidden = panel.getAttribute('data-surface-panel') !== surface;
29776
+ }
29777
+ };
29778
+ for (const tab of surfaceTabs) {
29779
+ tab.addEventListener('click', () => selectSurface(tab.getAttribute('data-surface-tab') || 'personal'));
29780
+ }
29781
+ const hydrateViewModel = async () => {
29782
+ const controller = new AbortController();
29783
+ const timeoutId = window.setTimeout(() => controller.abort(), 2500);
29784
+ try {
29785
+ const response = await fetch('/v1/guardian/control-tower/view-model', {
29786
+ headers: { accept: 'application/json' },
29787
+ signal: controller.signal,
29788
+ });
29789
+ if (!response.ok) return;
29790
+ const body = await response.json();
29791
+ if (!body || body.ok !== true) return;
29792
+ document.documentElement.setAttribute('data-view-model', body.schema_version || 'loaded');
29793
+ } catch {
29794
+ // Keep the server-rendered Control Tower if local hydration is unavailable.
29795
+ } finally {
29796
+ window.clearTimeout(timeoutId);
29797
+ }
29798
+ };
29799
+ hydrateViewModel();
27981
29800
  const filters = Array.from(document.querySelectorAll('[data-activity-filter]'));
27982
29801
  const list = document.querySelector('[data-activity-list]');
27983
29802
  const emptyState = document.querySelector('[data-activity-empty]');
@@ -28013,6 +29832,85 @@ ${learningEventRows}
28013
29832
  }
28014
29833
  setFilterCopy(selectedFilter);
28015
29834
  };
29835
+ const absoluteLocalTimeFormatter = new Intl.DateTimeFormat(navigator.language || undefined, {
29836
+ month: 'short',
29837
+ day: 'numeric',
29838
+ year: 'numeric',
29839
+ hour: 'numeric',
29840
+ minute: '2-digit',
29841
+ timeZoneName: 'short',
29842
+ });
29843
+ const relativeTimeFormatter = new Intl.RelativeTimeFormat(navigator.language || undefined, {
29844
+ numeric: 'auto',
29845
+ });
29846
+ const formatAbsoluteLocalTime = (createdAt) => {
29847
+ const date = new Date(createdAt);
29848
+ if (Number.isNaN(date.getTime())) return createdAt || '';
29849
+ return absoluteLocalTimeFormatter.format(date);
29850
+ };
29851
+ const formatLocalTime = (createdAt) => {
29852
+ const date = new Date(createdAt);
29853
+ if (Number.isNaN(date.getTime())) return createdAt || '';
29854
+ const diffMs = date.getTime() - Date.now();
29855
+ const absMs = Math.abs(diffMs);
29856
+ if (absMs < 60 * 1000) return 'just now';
29857
+ if (absMs < 60 * 60 * 1000) {
29858
+ return relativeTimeFormatter.format(Math.round(diffMs / (60 * 1000)), 'minute');
29859
+ }
29860
+ if (absMs < 24 * 60 * 60 * 1000) {
29861
+ return relativeTimeFormatter.format(Math.round(diffMs / (60 * 60 * 1000)), 'hour');
29862
+ }
29863
+ if (absMs < 7 * 24 * 60 * 60 * 1000) {
29864
+ return relativeTimeFormatter.format(Math.round(diffMs / (24 * 60 * 60 * 1000)), 'day');
29865
+ }
29866
+ return formatAbsoluteLocalTime(createdAt);
29867
+ };
29868
+ const hydrateLocalTimes = () => {
29869
+ for (const element of Array.from(document.querySelectorAll('[data-guardian-local-time]'))) {
29870
+ const createdAt = element.getAttribute('datetime') || element.getAttribute('data-created-at') || '';
29871
+ const formatted = formatLocalTime(createdAt);
29872
+ if (formatted) element.textContent = formatted;
29873
+ if (createdAt) {
29874
+ element.setAttribute('title', 'Your local time: ' + formatAbsoluteLocalTime(createdAt) + ' \xB7 Stored by Guardian as ' + createdAt);
29875
+ }
29876
+ }
29877
+ };
29878
+ const hydrateLocalTimeZoneLabels = () => {
29879
+ let timeZone = '';
29880
+ try {
29881
+ timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
29882
+ } catch {
29883
+ timeZone = '';
29884
+ }
29885
+ const label = timeZone
29886
+ ? 'Times use your browser timezone: ' + timeZone + '.'
29887
+ : "Times use your browser's local timezone.";
29888
+ for (const element of Array.from(document.querySelectorAll('[data-guardian-time-zone-label]'))) {
29889
+ element.textContent = label;
29890
+ }
29891
+ };
29892
+ const receiptIdFromHash = () => {
29893
+ const hash = window.location.hash || '';
29894
+ if (!hash.startsWith('#receipt=')) return '';
29895
+ try {
29896
+ return decodeURIComponent(hash.slice('#receipt='.length));
29897
+ } catch {
29898
+ return hash.slice('#receipt='.length);
29899
+ }
29900
+ };
29901
+ const highlightReceiptFromHash = () => {
29902
+ const selectedReceiptId = receiptIdFromHash();
29903
+ let matchedRow = null;
29904
+ for (const row of Array.from(document.querySelectorAll('[data-receipt-row]'))) {
29905
+ const selected = Boolean(selectedReceiptId) && row.getAttribute('data-receipt-id') === selectedReceiptId;
29906
+ row.classList.toggle('is-selected-receipt', selected);
29907
+ if (selected) matchedRow = row;
29908
+ }
29909
+ if (!matchedRow) return;
29910
+ const details = matchedRow.querySelector('details');
29911
+ if (details) details.open = true;
29912
+ matchedRow.scrollIntoView({ block: 'center', behavior: 'smooth' });
29913
+ };
28016
29914
  const createActivityCard = (activity) => {
28017
29915
  const card = document.createElement('article');
28018
29916
  card.className = 'activity-card ' + activity.tone;
@@ -28026,7 +29924,16 @@ ${learningEventRows}
28026
29924
  title.textContent = activity.title;
28027
29925
  const meta = document.createElement('div');
28028
29926
  meta.className = 'activity-meta';
28029
- meta.textContent = activity.meta;
29927
+ if (activity.created_at) {
29928
+ const time = document.createElement('time');
29929
+ time.setAttribute('datetime', activity.created_at);
29930
+ time.setAttribute('data-guardian-local-time', '');
29931
+ time.textContent = formatLocalTime(activity.created_at);
29932
+ time.title = 'Your local time: ' + formatAbsoluteLocalTime(activity.created_at) + ' \xB7 Stored by Guardian as ' + activity.created_at;
29933
+ meta.append(time, ' \xB7 ' + (activity.meta_detail || activity.receipt_type || 'receipt'));
29934
+ } else {
29935
+ meta.textContent = activity.meta;
29936
+ }
28030
29937
  body.append(title, meta);
28031
29938
  const badge = document.createElement('div');
28032
29939
  badge.className = 'activity-badge ' + activity.tone;
@@ -28069,6 +29976,11 @@ ${learningEventRows}
28069
29976
  applyFilter(filter.getAttribute('data-activity-filter') || 'all');
28070
29977
  });
28071
29978
  }
29979
+ window.addEventListener('hashchange', highlightReceiptFromHash);
29980
+ hydrateLocalTimeZoneLabels();
29981
+ hydrateLocalTimes();
29982
+ window.setInterval(hydrateLocalTimes, 60 * 1000);
29983
+ highlightReceiptFromHash();
28072
29984
  })();
28073
29985
  </script>
28074
29986
  </body>
@@ -28205,6 +30117,120 @@ function renderReleaseDoctorRows(status) {
28205
30117
  function renderDoctorCounts(doctor) {
28206
30118
  return `${doctor.checkCounts.pass} pass; ${doctor.checkCounts.warn} warn; ${doctor.checkCounts.fail} fail`;
28207
30119
  }
30120
+ async function readControlTowerAdminNotices(profileDir) {
30121
+ try {
30122
+ const raw = await readFile4(path4.join(profileDir, CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME), "utf8");
30123
+ const parsed = JSON.parse(raw);
30124
+ if (!isRecord(parsed) || !Array.isArray(parsed.notices)) {
30125
+ return [];
30126
+ }
30127
+ return parsed.notices.map(parseControlTowerAdminNotice).filter((notice) => Boolean(notice)).slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT);
30128
+ } catch {
30129
+ return [];
30130
+ }
30131
+ }
30132
+ async function writeControlTowerAdminNotices(profileDir, notices) {
30133
+ await writeFile2(
30134
+ path4.join(profileDir, CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME),
30135
+ `${JSON.stringify(
30136
+ {
30137
+ schema_version: "contextecf/project-guardian-control-tower-admin-notices/v1",
30138
+ notices: notices.slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT),
30139
+ raw_content_included: false,
30140
+ token_printed: false
30141
+ },
30142
+ null,
30143
+ 2
30144
+ )}
30145
+ `,
30146
+ "utf8"
30147
+ );
30148
+ }
30149
+ async function readControlTowerMarketplaceRequests(profileDir) {
30150
+ try {
30151
+ const raw = await readFile4(
30152
+ path4.join(profileDir, CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME),
30153
+ "utf8"
30154
+ );
30155
+ const parsed = JSON.parse(raw);
30156
+ if (!isRecord(parsed) || !Array.isArray(parsed.requests)) {
30157
+ return [];
30158
+ }
30159
+ return parsed.requests.map(parseControlTowerMarketplaceRequest).filter((request) => Boolean(request)).slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT);
30160
+ } catch {
30161
+ return [];
30162
+ }
30163
+ }
30164
+ async function writeControlTowerMarketplaceRequests(profileDir, requests) {
30165
+ await writeFile2(
30166
+ path4.join(profileDir, CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME),
30167
+ `${JSON.stringify(
30168
+ {
30169
+ schema_version: "contextecf/project-guardian-marketplace-requests/v1",
30170
+ requests: requests.slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT),
30171
+ raw_content_included: false,
30172
+ token_printed: false
30173
+ },
30174
+ null,
30175
+ 2
30176
+ )}
30177
+ `,
30178
+ "utf8"
30179
+ );
30180
+ }
30181
+ async function writeControlTowerPrimePackDraft(profileDir, draft) {
30182
+ await writeFile2(
30183
+ path4.join(profileDir, CONTROL_TOWER_PRIME_PACK_FILE_NAME),
30184
+ `${JSON.stringify(draft, null, 2)}
30185
+ `,
30186
+ "utf8"
30187
+ );
30188
+ }
30189
+ function parseControlTowerAdminNotice(value) {
30190
+ if (!isRecord(value)) {
30191
+ return null;
30192
+ }
30193
+ const audience = parseControlTowerAdminNoticeAudience(value.audience);
30194
+ const tone = parseControlTowerAdminNoticeTone(value.tone);
30195
+ if (typeof value.id !== "string" || typeof value.title !== "string" || typeof value.message !== "string" || typeof value.created_at !== "string" || !audience || !tone) {
30196
+ return null;
30197
+ }
30198
+ return {
30199
+ id: value.id.slice(0, 80),
30200
+ title: value.title.slice(0, CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT),
30201
+ message: value.message.slice(0, CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT),
30202
+ audience,
30203
+ tone,
30204
+ created_at: value.created_at,
30205
+ receipt_id: typeof value.receipt_id === "string" ? value.receipt_id.slice(0, 160) : void 0,
30206
+ receipt_hash: typeof value.receipt_hash === "string" ? value.receipt_hash.slice(0, 80) : void 0,
30207
+ raw_content_included: false
30208
+ };
30209
+ }
30210
+ function parseControlTowerMarketplaceRequest(value) {
30211
+ if (!isRecord(value)) {
30212
+ return null;
30213
+ }
30214
+ if (value.schema_version !== "contextecf/project-guardian-marketplace-request/v1" || value.action !== "request_review" || value.status !== "requested_metadata_only" || typeof value.request_id !== "string" || typeof value.pack_id !== "string" || typeof value.marketplace_manifest_id !== "string" || typeof value.pack_version !== "string" || typeof value.pack_name !== "string" || typeof value.publisher !== "string" || typeof value.requested_at !== "string" || typeof value.receipt_id !== "string" || typeof value.receipt_hash !== "string") {
30215
+ return null;
30216
+ }
30217
+ return {
30218
+ schema_version: "contextecf/project-guardian-marketplace-request/v1",
30219
+ request_id: value.request_id.slice(0, 120),
30220
+ pack_id: value.pack_id.slice(0, 120),
30221
+ marketplace_manifest_id: value.marketplace_manifest_id.slice(0, 160),
30222
+ pack_version: value.pack_version.slice(0, 40),
30223
+ pack_name: value.pack_name.slice(0, 120),
30224
+ publisher: value.publisher.slice(0, 120),
30225
+ action: "request_review",
30226
+ status: "requested_metadata_only",
30227
+ requested_at: value.requested_at,
30228
+ receipt_id: value.receipt_id.slice(0, 160),
30229
+ receipt_hash: value.receipt_hash.slice(0, 80),
30230
+ raw_content_included: false,
30231
+ token_printed: false
30232
+ };
30233
+ }
28208
30234
  async function buildControlTowerLocalDataStatus(profile, env = process.env) {
28209
30235
  const profileDir = profile.profile_dir;
28210
30236
  const encryption = buildControlTowerEncryptionStatus(
@@ -28440,7 +30466,7 @@ function renderReceiptRows(receipts) {
28440
30466
  }
28441
30467
  return receipts.map((receipt) => renderReceiptRow(receipt)).join("\n");
28442
30468
  }
28443
- function renderActivityFeed(receipts) {
30469
+ function renderActivityFeed(receipts, searchGraph = { nodes: [], edges: [] }) {
28444
30470
  if (receipts.length === 0) {
28445
30471
  return ` <article class="activity-card allow" data-activity-state="allowed">
28446
30472
  <div class="activity-icon">&#9989;</div>
@@ -28451,7 +30477,8 @@ function renderActivityFeed(receipts) {
28451
30477
  <div class="activity-badge allow">READY</div>
28452
30478
  </article>`;
28453
30479
  }
28454
- return receipts.map((receipt) => renderActivityCard(receipt)).join("\n");
30480
+ const attribution = buildActivityAttributionIndex(searchGraph);
30481
+ return receipts.map((receipt) => renderActivityCard(receipt, attribution)).join("\n");
28455
30482
  }
28456
30483
  function parseActivityFilter(value) {
28457
30484
  if (value === "blocked" || value === "flagged" || value === "allowed") {
@@ -28459,36 +30486,149 @@ function parseActivityFilter(value) {
28459
30486
  }
28460
30487
  return "all";
28461
30488
  }
28462
- function buildActivityItems(receipts, filter) {
30489
+ function buildActivityItems(receipts, filter, searchGraph = { nodes: [], edges: [] }) {
30490
+ const attribution = buildActivityAttributionIndex(searchGraph);
28463
30491
  return receipts.filter((receipt) => {
28464
30492
  const group = activityFilterGroup(receipt.disposition);
28465
30493
  return filter === "all" || group === filter;
28466
- }).map((receipt) => ({
28467
- id: receipt.receipt_id,
28468
- state: activityFilterGroup(receipt.disposition),
28469
- tone: activityTone(receipt.disposition),
28470
- icon: activityPlainIcon(receipt.disposition),
28471
- title: receipt.summary,
28472
- meta: `${formatActivityTime(receipt.created_at)} \xB7 ${receipt.receipt_type}`,
28473
- label: activityLabel(receipt.disposition),
28474
- raw_content_included: false
28475
- }));
30494
+ }).map((receipt) => {
30495
+ const detail = activityDetailForReceipt(receipt, attribution);
30496
+ return {
30497
+ id: receipt.receipt_id,
30498
+ state: activityFilterGroup(receipt.disposition),
30499
+ tone: activityTone(receipt.disposition),
30500
+ icon: activityPlainIcon(receipt.disposition),
30501
+ title: receipt.summary,
30502
+ created_at: receipt.created_at,
30503
+ receipt_type: receipt.receipt_type,
30504
+ source_label: detail.surfaceLabel,
30505
+ provider_label: detail.providerLabel,
30506
+ meta_detail: detail.metaDetail,
30507
+ meta: `${formatActivityTime(receipt.created_at)} \xB7 ${detail.metaDetail}`,
30508
+ label: activityLabel(receipt.disposition),
30509
+ raw_content_included: false
30510
+ };
30511
+ });
28476
30512
  }
28477
- function renderActivityCard(receipt) {
30513
+ function renderActivityCard(receipt, attribution = /* @__PURE__ */ new Map()) {
28478
30514
  const tone = activityTone(receipt.disposition);
28479
30515
  const filterGroup = activityFilterGroup(receipt.disposition);
30516
+ const detail = activityDetailForReceipt(receipt, attribution);
28480
30517
  return ` <article class="activity-card ${tone}" data-activity-state="${escapeHtml(filterGroup)}">
28481
30518
  <div class="activity-icon">${activityIcon(receipt.disposition)}</div>
28482
30519
  <div>
28483
30520
  <div class="activity-title">${escapeHtml(receipt.summary)}</div>
28484
- <div class="activity-meta">${escapeHtml(formatActivityTime(receipt.created_at))} \xB7 ${escapeHtml(receipt.receipt_type)}</div>
30521
+ <div class="activity-meta">${renderLocalTime(receipt.created_at)} \xB7 ${escapeHtml(detail.metaDetail)}</div>
28485
30522
  </div>
28486
30523
  <div class="activity-badge ${tone}">${escapeHtml(activityLabel(receipt.disposition))}</div>
28487
30524
  </article>`;
28488
30525
  }
30526
+ function buildActivityAttributionIndex(searchGraph) {
30527
+ const index = /* @__PURE__ */ new Map();
30528
+ for (const node of searchGraph.nodes) {
30529
+ if (node.node_type !== "prompt" || !node.summary) {
30530
+ continue;
30531
+ }
30532
+ const attribution = parsePromptNodeAttribution(node.summary);
30533
+ if (!attribution) {
30534
+ continue;
30535
+ }
30536
+ for (const evidenceRef of node.evidence_refs) {
30537
+ if (evidenceRef.startsWith("receipt:")) {
30538
+ index.set(evidenceRef, attribution);
30539
+ }
30540
+ }
30541
+ }
30542
+ return index;
30543
+ }
30544
+ function parsePromptNodeAttribution(summary) {
30545
+ const match = /^Prompt sent from ([a-z_]+) to ([a-z_]+)\.$/.exec(summary.trim());
30546
+ if (!match) {
30547
+ return null;
30548
+ }
30549
+ return {
30550
+ surfaceLabel: friendlySurfaceLabel(match[1]),
30551
+ providerLabel: friendlyProviderLabel(match[2])
30552
+ };
30553
+ }
30554
+ function activityDetailForReceipt(receipt, attribution) {
30555
+ const graphAttribution = attribution.get(receipt.receipt_id);
30556
+ const surfaceLabel = graphAttribution?.surfaceLabel ?? (receipt.policy_basis.some((basis) => basis.includes("guardian:mcp:")) ? "MCP" : void 0);
30557
+ const providerLabel = graphAttribution?.providerLabel;
30558
+ const parts = [
30559
+ surfaceLabel,
30560
+ providerLabel,
30561
+ friendlyReceiptTypeLabel(receipt.receipt_type)
30562
+ ].filter((part) => Boolean(part));
30563
+ return {
30564
+ surfaceLabel,
30565
+ providerLabel,
30566
+ metaDetail: parts.join(" \xB7 ") || friendlyReceiptTypeLabel(receipt.receipt_type)
30567
+ };
30568
+ }
30569
+ function friendlySurfaceLabel(surface) {
30570
+ switch (surface) {
30571
+ case "browser":
30572
+ return "Browser";
30573
+ case "mcp":
30574
+ return "MCP";
30575
+ case "ide":
30576
+ return "IDE";
30577
+ case "cli":
30578
+ return "CLI";
30579
+ case "desktop":
30580
+ return "Desktop app";
30581
+ case "rest":
30582
+ return "API";
30583
+ case "voice":
30584
+ return "Voice";
30585
+ default:
30586
+ return void 0;
30587
+ }
30588
+ }
30589
+ function friendlyProviderLabel(provider) {
30590
+ switch (provider) {
30591
+ case "chatgpt":
30592
+ return "ChatGPT";
30593
+ case "claude":
30594
+ return "Claude";
30595
+ case "gemini":
30596
+ return "Gemini";
30597
+ case "perplexity":
30598
+ return "Perplexity";
30599
+ case "cursor":
30600
+ return "Cursor";
30601
+ case "windsurf":
30602
+ return "Windsurf";
30603
+ case "openrouter":
30604
+ return "OpenRouter";
30605
+ case "local_model":
30606
+ return "Local AI";
30607
+ case "unknown":
30608
+ return void 0;
30609
+ default:
30610
+ return provider ? provider.split(/[_-]+/g).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ") : void 0;
30611
+ }
30612
+ }
30613
+ function friendlyReceiptTypeLabel(receiptType) {
30614
+ switch (receiptType) {
30615
+ case "prompt":
30616
+ return "Prompt check";
30617
+ case "policy":
30618
+ return "Policy receipt";
30619
+ case "context":
30620
+ return "Context receipt";
30621
+ case "local_action":
30622
+ return "Local action";
30623
+ case "learning":
30624
+ return "Learning receipt";
30625
+ case "output":
30626
+ return "Output check";
30627
+ }
30628
+ }
28489
30629
  function renderReceiptRow(receipt) {
28490
30630
  const policyBasis = receipt.policy_basis.length > 0 ? receipt.policy_basis.join(", ") : "none recorded";
28491
- return ` <tr><th scope="row"><code>${escapeHtml(receipt.receipt_id)}</code></th><td>${escapeHtml(receipt.receipt_type)}</td><td>${escapeHtml(receipt.disposition)}</td><td><details><summary>${escapeHtml(receipt.summary)}</summary><dl><dt>Transaction</dt><dd><code>${escapeHtml(receipt.transaction_id)}</code></dd><dt>Created</dt><dd>${escapeHtml(receipt.created_at)}</dd><dt>Policy basis</dt><dd>${escapeHtml(policyBasis)}</dd><dt>Receipt hash</dt><dd><code>${escapeHtml(receipt.receipt_hash)}</code></dd><dt>Raw content</dt><dd>${receipt.raw_content_included ? "included" : "metadata-only"}</dd></dl></details></td></tr>`;
30631
+ return ` <tr class="receipt-row" data-receipt-row data-receipt-id="${escapeHtml(receipt.receipt_id)}"><th scope="row"><code>${escapeHtml(receipt.receipt_id)}</code></th><td>${escapeHtml(receipt.receipt_type)}</td><td>${escapeHtml(receipt.disposition)}</td><td><details><summary>${escapeHtml(receipt.summary)}</summary><dl><dt>Transaction</dt><dd><code>${escapeHtml(receipt.transaction_id)}</code></dd><dt>Created</dt><dd>${renderLocalTime(receipt.created_at)}</dd><dt>Policy basis</dt><dd>${escapeHtml(policyBasis)}</dd><dt>Receipt hash</dt><dd><code>${escapeHtml(receipt.receipt_hash)}</code></dd><dt>Raw content</dt><dd>${receipt.raw_content_included ? "included" : "metadata-only"}</dd></dl></details></td></tr>`;
28492
30632
  }
28493
30633
  function activityTone(disposition) {
28494
30634
  if (disposition === "block") return "block";
@@ -28517,17 +30657,321 @@ function activityFilterGroup(disposition) {
28517
30657
  if (disposition === "warn" || disposition === "confirm") return "flagged";
28518
30658
  return "allowed";
28519
30659
  }
28520
- function formatActivityTime(createdAt) {
30660
+ function formatActivityTime(createdAt, nowMs = Date.now()) {
28521
30661
  const parsed = Date.parse(createdAt);
28522
30662
  if (Number.isNaN(parsed)) {
28523
30663
  return createdAt;
28524
30664
  }
30665
+ const diffMs = parsed - nowMs;
30666
+ const absMs = Math.abs(diffMs);
30667
+ if (absMs < 60 * 1e3) {
30668
+ return "just now";
30669
+ }
30670
+ if (absMs < 60 * 60 * 1e3) {
30671
+ const minutes = Math.round(absMs / (60 * 1e3));
30672
+ return diffMs < 0 ? `${minutes} min ago` : `in ${minutes} min`;
30673
+ }
30674
+ if (absMs < 24 * 60 * 60 * 1e3) {
30675
+ const hours = Math.round(absMs / (60 * 60 * 1e3));
30676
+ return diffMs < 0 ? `${hours} hr ago` : `in ${hours} hr`;
30677
+ }
30678
+ if (absMs < 7 * 24 * 60 * 60 * 1e3) {
30679
+ const days = Math.round(absMs / (24 * 60 * 60 * 1e3));
30680
+ return diffMs < 0 ? `${days} day${days === 1 ? "" : "s"} ago` : `in ${days} day${days === 1 ? "" : "s"}`;
30681
+ }
28525
30682
  return new Date(parsed).toLocaleString("en-US", {
28526
- dateStyle: "medium",
28527
- timeStyle: "short",
28528
- timeZone: "UTC"
30683
+ month: "short",
30684
+ day: "numeric",
30685
+ year: "numeric",
30686
+ hour: "numeric",
30687
+ minute: "2-digit",
30688
+ timeZoneName: "short"
28529
30689
  });
28530
30690
  }
30691
+ function renderLocalTime(createdAt) {
30692
+ const parsed = Date.parse(createdAt);
30693
+ if (Number.isNaN(parsed)) {
30694
+ return escapeHtml(createdAt);
30695
+ }
30696
+ return `<time data-guardian-local-time datetime="${escapeHtml(createdAt)}" title="${escapeHtml(
30697
+ `Stored by Guardian as ${createdAt}`
30698
+ )}">${escapeHtml(formatActivityTime(createdAt))}</time>`;
30699
+ }
30700
+ function renderLocalGuardianStatus(status) {
30701
+ switch (status) {
30702
+ case "start_requested":
30703
+ return "Running";
30704
+ case "not_started_mvp":
30705
+ return "Not running";
30706
+ }
30707
+ }
30708
+ function renderControlTowerRoleCards(viewModel) {
30709
+ return viewModel.surfaces.map(
30710
+ (surface) => ` <article class="role-card">
30711
+ <strong>${escapeHtml(surface.label)}</strong>
30712
+ <span>${escapeHtml(surface.audience)}</span>
30713
+ <span>${escapeHtml(surface.purpose)}</span>
30714
+ </article>`
30715
+ ).join("\n");
30716
+ }
30717
+ function renderAdminNoticeBar(notices) {
30718
+ if (notices.length === 0) {
30719
+ return "";
30720
+ }
30721
+ return ` <section class="notice-bar" aria-label="Guardian notifications">
30722
+ ${notices.map((notice) => renderAdminNoticeCard(notice)).join("\n")}
30723
+ </section>`;
30724
+ }
30725
+ function renderAdminNoticeList(notices) {
30726
+ if (notices.length === 0) {
30727
+ return " <p>No local admin notices are published.</p>";
30728
+ }
30729
+ return ` <div class="notice-bar" aria-label="Current admin notices">
30730
+ ${notices.map((notice) => renderAdminNoticeCard(notice)).join("\n")}
30731
+ </div>`;
30732
+ }
30733
+ function renderAdminNoticeCard(notice) {
30734
+ return ` <article class="notice-card ${escapeHtml(notice.tone)}">
30735
+ <strong>${escapeHtml(notice.title)}</strong>
30736
+ <p>${escapeHtml(notice.message)}</p>
30737
+ <span>${escapeHtml(adminNoticeAudienceLabel(notice.audience))} \xB7 ${renderLocalTime(notice.created_at)}</span>
30738
+ ${notice.receipt_id ? `<code>${escapeHtml(notice.receipt_id)}</code>` : ""}
30739
+ </article>`;
30740
+ }
30741
+ function adminNoticeAudienceLabel(audience) {
30742
+ switch (audience) {
30743
+ case "admins":
30744
+ return "Admins";
30745
+ case "developers":
30746
+ return "Developers";
30747
+ case "all_users":
30748
+ return "Everyone";
30749
+ }
30750
+ }
30751
+ function renderGuardianAdminSurface(viewModel, profile, receipts, appPermissionRows) {
30752
+ return ` <section aria-labelledby="guardian-admin-overview">
30753
+ <h2 id="guardian-admin-overview">Guardian Admin</h2>
30754
+ <p>Manage policy packs, marketplace approvals, Prime Pack custom rules, deployment scope, receipts, and release automation from the same local Guardian brain used by the browser extension and MCP clients.</p>
30755
+ <div class="admin-grid">
30756
+ ${renderAdminSummaryCards(viewModel, profile, receipts, appPermissionRows)}
30757
+ </div>
30758
+ </section>
30759
+ <section aria-labelledby="admin-notifications">
30760
+ <h2 id="admin-notifications">Admin Notifications</h2>
30761
+ <p>Send a short local notice into the Control Tower notification bar. Notices are metadata-only and stay on this device in <code>${escapeHtml(CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME)}</code>.</p>
30762
+ <form class="notice-form" method="post" action="/v1/guardian/admin/notice">
30763
+ <input type="hidden" name="action" value="publish">
30764
+ <label>Title<input name="title" maxlength="${CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT}" placeholder="Policy reminder" required></label>
30765
+ <label>Message<textarea name="message" maxlength="${CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT}" placeholder="Use approved customer-data placeholders in external AI tools." required></textarea></label>
30766
+ <label>Audience<select name="audience"><option value="all_users">Everyone</option><option value="admins">Admins</option><option value="developers">Developers</option></select></label>
30767
+ <label>Tone<select name="tone"><option value="info">Helpful note</option><option value="warning">Watch out</option><option value="success">Good news</option></select></label>
30768
+ <button type="submit">Publish local notice</button>
30769
+ </form>
30770
+ <form method="post" action="/v1/guardian/admin/notice">
30771
+ <input type="hidden" name="action" value="clear">
30772
+ <button type="submit">Clear local notices</button>
30773
+ </form>
30774
+ ${renderAdminNoticeList(viewModel.admin.notices)}
30775
+ </section>
30776
+ <section aria-labelledby="admin-policy-packs">
30777
+ <h2 id="admin-policy-packs">Policy Pack Management</h2>
30778
+ <p>Turn built-in packs on or off for this device now. In enterprise deployments, the same model becomes group assignment.</p>
30779
+ <table aria-label="Admin policy pack assignment">
30780
+ <thead>
30781
+ <tr><th scope="col">Pack</th><th scope="col">State</th><th scope="col">Local action</th></tr>
30782
+ </thead>
30783
+ <tbody>
30784
+ ${profile.policy_packs.available.map((packId) => {
30785
+ const active = profile.policy_packs.active.includes(packId);
30786
+ const disabled = active && profile.policy_packs.active.length <= 1;
30787
+ return ` <tr><th scope="row">${escapeHtml(packId)}</th><td>${active ? "active" : "available"}</td><td>${renderPolicyPackForm(active ? "disable" : "enable", packId, disabled)}</td></tr>`;
30788
+ }).join("\n")}
30789
+ </tbody>
30790
+ </table>
30791
+ </section>
30792
+ <section aria-labelledby="admin-marketplace">
30793
+ <h2 id="admin-marketplace">Policy Pack Marketplace</h2>
30794
+ <p>Review pack metadata before connecting it to a deployment. Packs can start as request/approval metadata before billing automation is added.</p>
30795
+ <div class="marketplace-grid">
30796
+ ${renderMarketplaceCards(viewModel)}
30797
+ </div>
30798
+ ${renderMarketplaceRequestList(viewModel.admin.marketplaceRequests)}
30799
+ </section>
30800
+ <section aria-labelledby="admin-prime-pack">
30801
+ <h2 id="admin-prime-pack">Prime Pack Builder</h2>
30802
+ <p>Create one organization-specific pack from terms, domains, blocked destinations, and escalation phrases. Upload support is designed for text, CSV, and JSON sources.</p>
30803
+ <article class="prime-pack-builder">
30804
+ <h3>Custom protection inputs</h3>
30805
+ <form method="post" action="/v1/guardian/admin/prime-pack">
30806
+ <div class="builder-grid">
30807
+ <label>Sensitive terms<textarea name="sensitiveTerms" maxlength="${CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT * CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT}" placeholder="Customer IDs, internal code names, regulated phrases"></textarea></label>
30808
+ <label>Approved domains<textarea name="approvedDomains" maxlength="${CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT * CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT}" placeholder="company.com, secure.vendor.com"></textarea></label>
30809
+ <label>Blocked destinations<textarea name="blockedDestinations" maxlength="${CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT * CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT}" placeholder="paste sites, personal email domains, unknown file shares"></textarea></label>
30810
+ <label>Escalation phrases<textarea name="escalationPhrases" maxlength="${CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT * CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT}" placeholder="payment approval, publish externally, send to customer"></textarea></label>
30811
+ </div>
30812
+ <label>Import from text, CSV, or JSON<textarea name="importContent" maxlength="${CONTROL_TOWER_PRIME_PACK_IMPORT_LIMIT}" placeholder="Examples: sensitive_terms,Project Falcon&#10;blocked_destinations,paste.example&#10;or JSON with sensitive_terms, approved_domains, blocked_destinations, and escalation_phrases arrays"></textarea></label>
30813
+ <div class="button-row">
30814
+ <button type="submit" name="action" value="preview">Preview impact</button>
30815
+ <button type="submit" name="action" value="deploy">Deploy locally</button>
30816
+ </div>
30817
+ </form>
30818
+ <ul class="metadata-list">
30819
+ <li><b>Input formats</b><span>${escapeHtml(viewModel.admin.primePack.supportedInputs.join(", "))}</span></li>
30820
+ <li><b>Preview</b><span>stores counts and sample impact in <code>${escapeHtml(CONTROL_TOWER_PRIME_PACK_FILE_NAME)}</code></span></li>
30821
+ <li><b>Deploy</b><span>writes a local admin receipt before assignment</span></li>
30822
+ </ul>
30823
+ </article>
30824
+ </section>
30825
+ <section aria-labelledby="admin-deployment-scope">
30826
+ <h2 id="admin-deployment-scope">Deployment Scope</h2>
30827
+ <p>Assign posture and packs by user or group. Personal Guardian uses the first row; enterprise deployments add managed groups.</p>
30828
+ <table aria-label="Guardian deployment groups">
30829
+ <thead>
30830
+ <tr><th scope="col">Group</th><th scope="col">Users</th><th scope="col">Posture</th><th scope="col">Packs</th></tr>
30831
+ </thead>
30832
+ <tbody>
30833
+ ${viewModel.admin.managedGroups.map(
30834
+ (group) => ` <tr><th scope="row">${escapeHtml(group.label)}</th><td>${group.users}</td><td>${escapeHtml(group.posture)}</td><td>${escapeHtml(group.packs.join(", ") || "none")}</td></tr>`
30835
+ ).join("\n")}
30836
+ </tbody>
30837
+ </table>
30838
+ </section>
30839
+ <section aria-labelledby="admin-release-automation">
30840
+ <h2 id="admin-release-automation">Release Automation</h2>
30841
+ <p>Use GitHub Actions as the default release path so package evidence, browser artifacts, Docker smoke tests, and npm publication stay repeatable.</p>
30842
+ <div class="builder-grid">
30843
+ <article class="release-command-card">
30844
+ <h3>Dry run</h3>
30845
+ <code class="command-box">${escapeHtml(viewModel.releaseAutomation.dryRunCommand)}</code>
30846
+ </article>
30847
+ <article class="release-command-card">
30848
+ <h3>Publish</h3>
30849
+ <code class="command-box">${escapeHtml(viewModel.releaseAutomation.publishCommand)}</code>
30850
+ </article>
30851
+ </div>
30852
+ <ul class="metadata-list">
30853
+ <li><b>Repository</b><span>${escapeHtml(viewModel.releaseAutomation.repository)}</span></li>
30854
+ <li><b>Workflow</b><span>${escapeHtml(viewModel.releaseAutomation.workflow)}</span></li>
30855
+ <li><b>NPM package</b><span>${escapeHtml(viewModel.releaseAutomation.npmPackage)}</span></li>
30856
+ <li><b>Chrome extension</b><span>${escapeHtml(viewModel.releaseAutomation.chromeExtensionId)}</span></li>
30857
+ <li><b>Still manual</b><span>${escapeHtml(viewModel.releaseAutomation.humanIntervention[0])}</span></li>
30858
+ </ul>
30859
+ </section>
30860
+ <section aria-labelledby="admin-audit">
30861
+ <h2 id="admin-audit">Receipts And Audit</h2>
30862
+ <p>Admins can review metadata-only decisions without exposing raw prompt text, raw responses, local action payloads, or runtime tokens.</p>
30863
+ <table aria-label="Guardian admin receipt audit">
30864
+ <thead>
30865
+ <tr><th scope="col">Receipt</th><th scope="col">Type</th><th scope="col">Decision</th><th scope="col">Details</th></tr>
30866
+ </thead>
30867
+ <tbody>
30868
+ ${renderReceiptRows(receipts)}
30869
+ </tbody>
30870
+ </table>
30871
+ </section>`;
30872
+ }
30873
+ function renderAdminSummaryCards(viewModel, profile, receipts, appPermissionRows) {
30874
+ const cards = [
30875
+ {
30876
+ title: "Managed groups",
30877
+ value: String(viewModel.admin.managedGroups.length),
30878
+ detail: "Group-level posture and pack assignment model."
30879
+ },
30880
+ {
30881
+ title: "Marketplace packs",
30882
+ value: String(viewModel.marketplace.length),
30883
+ detail: "Metadata listings ready for approval review."
30884
+ },
30885
+ {
30886
+ title: "Prime Pack",
30887
+ value: viewModel.admin.primePack.status.replace(/_/gu, " "),
30888
+ detail: "Custom strings, domains, destinations, and escalation phrases."
30889
+ },
30890
+ {
30891
+ title: "Active local packs",
30892
+ value: String(profile.policy_packs.active.length),
30893
+ detail: profile.policy_packs.active.join(", ") || "none"
30894
+ },
30895
+ {
30896
+ title: "Governed apps",
30897
+ value: String(appPermissionRows.length),
30898
+ detail: "Local app access remains confirmation-gated."
30899
+ },
30900
+ {
30901
+ title: "Audit receipts",
30902
+ value: String(receipts.length),
30903
+ detail: "Metadata-only receipt chain."
30904
+ }
30905
+ ];
30906
+ return cards.map(
30907
+ (card) => ` <article class="admin-card">
30908
+ <h3>${escapeHtml(card.title)}</h3>
30909
+ <strong>${escapeHtml(card.value)}</strong>
30910
+ <span>${escapeHtml(card.detail)}</span>
30911
+ </article>`
30912
+ ).join("\n");
30913
+ }
30914
+ function renderMarketplaceCards(viewModel) {
30915
+ return viewModel.marketplace.map(
30916
+ (pack) => ` <article class="marketplace-card" data-marketplace-pack-id="${escapeHtml(pack.packId)}">
30917
+ <h3>${escapeHtml(pack.name)}</h3>
30918
+ <p>${escapeHtml(pack.examples[0] ?? "Policy pack metadata listing.")}</p>
30919
+ <ul class="metadata-list">
30920
+ <li><b>Audience</b><span>${escapeHtml(pack.audience)}</span></li>
30921
+ <li><b>Category</b><span>${escapeHtml(pack.category)}</span></li>
30922
+ <li><b>Maintainer</b><span>${escapeHtml(pack.publisher)}</span></li>
30923
+ <li><b>Version</b><span>${escapeHtml(pack.version)}</span></li>
30924
+ <li><b>Price/license</b><span>${escapeHtml(pack.price)}</span></li>
30925
+ <li><b>Approval state</b><span>${escapeHtml(pack.state)}</span></li>
30926
+ <li><b>False-positive risk</b><span>${escapeHtml(pack.risk)}</span></li>
30927
+ </ul>
30928
+ ${renderMarketplaceRequestAction(pack)}
30929
+ <details class="marketplace-detail-drawer">
30930
+ <summary>Review details before connecting</summary>
30931
+ <dl>
30932
+ <dt>Permissions requested</dt>
30933
+ <dd>${escapeHtml(pack.permissions.join(" "))}</dd>
30934
+ <dt>Protected data</dt>
30935
+ <dd>${escapeHtml(pack.protectedDataTypes.join(", "))}</dd>
30936
+ <dt>Examples</dt>
30937
+ <dd>${escapeHtml(pack.examples.join(" "))}</dd>
30938
+ <dt>License reference</dt>
30939
+ <dd><code>${escapeHtml(pack.licenseRef)}</code></dd>
30940
+ <dt>Support reference</dt>
30941
+ <dd><code>${escapeHtml(pack.supportRef)}</code></dd>
30942
+ </dl>
30943
+ </details>
30944
+ </article>`
30945
+ ).join("\n");
30946
+ }
30947
+ function renderMarketplaceRequestAction(pack) {
30948
+ if (pack.requestState === "connected") {
30949
+ return ' <p class="marketplace-request-state">Connected locally through active policy packs.</p>';
30950
+ }
30951
+ if (pack.requestState === "requested_metadata_only") {
30952
+ return ` <p class="marketplace-request-state">Review requested ${pack.requestedAt ? renderLocalTime(pack.requestedAt) : "locally"}.</p>`;
30953
+ }
30954
+ return ` <form class="marketplace-request-form" method="post" action="/v1/guardian/admin/marketplace-request">
30955
+ <input type="hidden" name="action" value="request_review">
30956
+ <input type="hidden" name="packId" value="${escapeHtml(pack.packId)}">
30957
+ <button type="submit">Request review</button>
30958
+ <span>Writes a local receipt; paid pack connection remains approval-gated.</span>
30959
+ </form>`;
30960
+ }
30961
+ function renderMarketplaceRequestList(requests) {
30962
+ if (requests.length === 0) {
30963
+ return " <p>No marketplace pack reviews have been requested on this device yet.</p>";
30964
+ }
30965
+ return ` <div class="marketplace-request-list" aria-label="Marketplace review requests">
30966
+ ${requests.map(
30967
+ (request) => ` <article class="marketplace-request-card">
30968
+ <strong>${escapeHtml(request.pack_name)}</strong>
30969
+ <span>${escapeHtml(request.publisher)} \xB7 ${renderLocalTime(request.requested_at)}</span>
30970
+ <code>${escapeHtml(request.receipt_id)}</code>
30971
+ </article>`
30972
+ ).join("\n")}
30973
+ </div>`;
30974
+ }
28531
30975
  function providerDisplayName(providerId) {
28532
30976
  if (providerId === "chatgpt") return "ChatGPT";
28533
30977
  if (providerId === "claude") return "Claude";
@@ -28823,7 +31267,7 @@ function listen(server, host, port) {
28823
31267
  });
28824
31268
  });
28825
31269
  }
28826
- var MAX_BODY_BYTES, ADMIN_HEADER, CONTROL_TOWER_COOKIE, PROMPT_COACH_MODES2, CONTROL_TOWER_RECEIPT_LIMIT, CONTROL_TOWER_GRAPH_NODE_LIMIT, CONTROL_TOWER_GRAPH_EDGE_LIMIT, CONTROL_TOWER_LEARNING_EVENT_LIMIT, CONTROL_TOWER_DATA_FILE_NAMES, CONTROL_TOWER_DATA_DIRECTORY_NAMES, CONTROL_TOWER_SUPPORTED_LIFECYCLE_ACTIONS;
31270
+ var MAX_BODY_BYTES, ADMIN_HEADER, CONTROL_TOWER_COOKIE, PROMPT_COACH_MODES2, CONTROL_TOWER_RECEIPT_LIMIT, CONTROL_TOWER_GRAPH_NODE_LIMIT, CONTROL_TOWER_GRAPH_EDGE_LIMIT, CONTROL_TOWER_LEARNING_EVENT_LIMIT, CONTROL_TOWER_ADMIN_NOTICE_LIMIT, CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME, CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT, CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT, CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME, CONTROL_TOWER_PRIME_PACK_FILE_NAME, CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT, CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT, CONTROL_TOWER_PRIME_PACK_IMPORT_LIMIT, CONTROL_TOWER_DATA_FILE_NAMES, CONTROL_TOWER_DATA_DIRECTORY_NAMES, CONTROL_TOWER_SUPPORTED_LIFECYCLE_ACTIONS, CONTROL_TOWER_RELEASE_COMMAND_TEMPLATE, CONTROL_TOWER_DRY_RUN_COMMAND_TEMPLATE;
28827
31271
  var init_daemon = __esm({
28828
31272
  "packages/guardian-cli/src/daemon.ts"() {
28829
31273
  "use strict";
@@ -28839,12 +31283,25 @@ var init_daemon = __esm({
28839
31283
  CONTROL_TOWER_GRAPH_NODE_LIMIT = 12;
28840
31284
  CONTROL_TOWER_GRAPH_EDGE_LIMIT = 16;
28841
31285
  CONTROL_TOWER_LEARNING_EVENT_LIMIT = 12;
31286
+ CONTROL_TOWER_ADMIN_NOTICE_LIMIT = 5;
31287
+ CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME = "admin-notices.json";
31288
+ CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT = 80;
31289
+ CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT = 240;
31290
+ CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT = 20;
31291
+ CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME = "marketplace-requests.json";
31292
+ CONTROL_TOWER_PRIME_PACK_FILE_NAME = "prime-pack-draft.json";
31293
+ CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT = 120;
31294
+ CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT = 100;
31295
+ CONTROL_TOWER_PRIME_PACK_IMPORT_LIMIT = 24 * 1024;
28842
31296
  CONTROL_TOWER_DATA_FILE_NAMES = [
28843
31297
  "profile.json",
28844
31298
  "policy-packs.json",
28845
31299
  "posture-profile.json",
28846
31300
  "preferences.json",
28847
31301
  "permissions.json",
31302
+ CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME,
31303
+ CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME,
31304
+ CONTROL_TOWER_PRIME_PACK_FILE_NAME,
28848
31305
  "runtime-token"
28849
31306
  ];
28850
31307
  CONTROL_TOWER_DATA_DIRECTORY_NAMES = ["ecl", "receipts", "mcp"];
@@ -28855,6 +31312,8 @@ var init_daemon = __esm({
28855
31312
  "restore",
28856
31313
  "delete"
28857
31314
  ];
31315
+ CONTROL_TOWER_RELEASE_COMMAND_TEMPLATE = `gh workflow run ${GUARDIAN_RELEASE_DISPATCH_WORKFLOW} -f release_version=<version> -f channels=all -f dry_run=false -f run_docker_smoke=true -f publish_auth=token -f browser_store_publish=manual`;
31316
+ CONTROL_TOWER_DRY_RUN_COMMAND_TEMPLATE = `gh workflow run ${GUARDIAN_RELEASE_DISPATCH_WORKFLOW} -f release_version=<version> -f channels=all -f dry_run=true -f run_docker_smoke=true -f publish_auth=token -f browser_store_publish=manual`;
28858
31317
  }
28859
31318
  });
28860
31319