@contextecf/guardian-cli 0.1.5 → 0.1.8

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.
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ var __esm = (fn, res, err) => function __init() {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
6
11
  };
7
12
  var __export = (target, all) => {
8
13
  for (var name in all)
@@ -73,7 +78,7 @@ var init_core = __esm({
73
78
  NEVER = /* @__PURE__ */ Object.freeze({
74
79
  status: "aborted"
75
80
  });
76
- $brand = Symbol("zod_brand");
81
+ $brand = /* @__PURE__ */ Symbol("zod_brand");
77
82
  $ZodAsyncError = class extends Error {
78
83
  constructor() {
79
84
  super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
@@ -10242,8 +10247,8 @@ function registry() {
10242
10247
  var _a2, $output, $input, $ZodRegistry, globalRegistry;
10243
10248
  var init_registries = __esm({
10244
10249
  "node_modules/zod/v4/core/registries.js"() {
10245
- $output = Symbol("ZodOutput");
10246
- $input = Symbol("ZodInput");
10250
+ $output = /* @__PURE__ */ Symbol("ZodOutput");
10251
+ $input = /* @__PURE__ */ Symbol("ZodInput");
10247
10252
  $ZodRegistry = class {
10248
10253
  constructor() {
10249
10254
  this._map = /* @__PURE__ */ new WeakMap();
@@ -17056,6 +17061,8 @@ async function runGuardianCli(argv, options = {}) {
17056
17061
  return runReleaseCommand(argv.slice(1), flags, options);
17057
17062
  case "open":
17058
17063
  return runControlTowerOpenCommand(flags, options);
17064
+ case "connect":
17065
+ return runExtensionConnectCommand(flags, options);
17059
17066
  case "status":
17060
17067
  write(stdout, await statusGuardian(flags, options));
17061
17068
  return 0;
@@ -17067,6 +17074,14 @@ async function runGuardianCli(argv, options = {}) {
17067
17074
  return runLaunchCommand(flags, options);
17068
17075
  case "stop":
17069
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
+ }
17070
17085
  case "daemon":
17071
17086
  return runDaemonCommand(argv.slice(1), flags, options);
17072
17087
  case "service":
@@ -17089,6 +17104,9 @@ async function runGuardianCli(argv, options = {}) {
17089
17104
  return runPrivacyCommand(argv.slice(1), flags, options);
17090
17105
  case "mcp":
17091
17106
  return runMcpCommand(argv.slice(1), flags, options);
17107
+ case "coach":
17108
+ case "prompt-coach":
17109
+ return runCoachCommand(argv.slice(1), flags, options);
17092
17110
  case "policy":
17093
17111
  return runPolicyCommand(argv.slice(1), flags, options);
17094
17112
  case "marketplace":
@@ -17111,7 +17129,7 @@ async function runGuardianCli(argv, options = {}) {
17111
17129
  case "help":
17112
17130
  case "--help":
17113
17131
  case "-h":
17114
- write(stdout, `${HELP_TEXT}
17132
+ write(stdout, `${flags.has("advanced") ? ADVANCED_HELP_TEXT : HELP_TEXT}
17115
17133
  `);
17116
17134
  return 0;
17117
17135
  default:
@@ -17190,11 +17208,13 @@ async function installGuardian(flags, options = {}) {
17190
17208
  "guardian start",
17191
17209
  "guardian open",
17192
17210
  "guardian mcp install",
17193
- "guardian daemon pair --json",
17194
17211
  "guardian service install",
17195
17212
  "guardian key-store install --yes",
17196
- "guardian extension open-setup",
17197
- "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"
17198
17218
  ]
17199
17219
  };
17200
17220
  return flags.has("json") ? `${JSON.stringify(result, null, 2)}
@@ -17207,7 +17227,8 @@ async function installGuardian(flags, options = {}) {
17207
17227
  `First run: ${GUARDIAN_FIRST_RUN_COMMAND}`,
17208
17228
  `Open Control Tower: ${GUARDIAN_OPEN_CONTROL_TOWER_COMMAND}`,
17209
17229
  `No global install: ${GUARDIAN_NPX_SETUP_COMMAND}`,
17210
- "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."
17211
17232
  ].join("\n")}
17212
17233
  `;
17213
17234
  }
@@ -17215,8 +17236,9 @@ async function runSetupCommand(flags, options) {
17215
17236
  const stdout = options.stdout ?? process.stdout;
17216
17237
  const stderr = options.stderr ?? process.stderr;
17217
17238
  const home = resolveGuardianHome(options.env, options.platform);
17218
- const extensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
17219
- 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)) {
17220
17242
  write(
17221
17243
  stderr,
17222
17244
  "Invalid --extension-id. Chrome extension ids must be 32 lowercase letters from a-p.\n"
@@ -17272,10 +17294,80 @@ async function runSetupCommand(flags, options) {
17272
17294
  }
17273
17295
  launch = JSON.parse(launchOutput);
17274
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`;
17275
17300
  const resolvedControlTowerUrl = resolveControlTowerUrl(/* @__PURE__ */ new Map(), options.env, profile);
17276
- 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
+ }
17277
17309
  const setupUrl = buildControlTowerSetupUrl(controlTowerUrl);
17278
- 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
+ }
17279
17371
  const result = {
17280
17372
  schema_version: "contextecf/project-guardian-setup/v1",
17281
17373
  success: true,
@@ -17325,10 +17417,14 @@ async function runSetupCommand(flags, options) {
17325
17417
  },
17326
17418
  browserSetup: {
17327
17419
  setupUrl,
17328
- extensionId: extensionId ?? null,
17420
+ extensionId,
17421
+ defaultExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
17329
17422
  opened: false,
17330
17423
  tokenPrinted: false,
17331
- nativeHostCommand
17424
+ nativeHostCommand,
17425
+ connectInstruction: "Open ContextECF Halo and click Connect. No setup code is needed.",
17426
+ manualFallbackCommand: "guardian pair --json",
17427
+ nativeHost: browserHelper
17332
17428
  },
17333
17429
  nextCommands: [
17334
17430
  "guardian status",
@@ -17337,12 +17433,10 @@ async function runSetupCommand(flags, options) {
17337
17433
  "guardian policy list",
17338
17434
  "guardian posture list",
17339
17435
  "guardian preferences show",
17340
- "guardian extension open-setup",
17341
- "guardian daemon pair --json",
17342
17436
  "guardian service install",
17343
- "guardian key-store install --yes",
17344
- nativeHostCommand
17345
- ]
17437
+ "guardian key-store install --yes"
17438
+ ],
17439
+ advancedCommands: ["guardian extension connect", nativeHostCommand, "guardian pair --json"]
17346
17440
  };
17347
17441
  write(
17348
17442
  stdout,
@@ -17355,8 +17449,11 @@ async function runSetupCommand(flags, options) {
17355
17449
  result.launch.opened ? `Opening: ${result.launch.controlTowerUrl}` : void 0,
17356
17450
  `MCP artifacts: ${result.mcp.writtenFiles.length > 0 ? result.mcp.writtenFiles.join(", ") : "skipped" in result.mcp && result.mcp.skipped ? "skipped" : "none"}`,
17357
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.",
17358
17454
  "Token printed: no",
17359
- `Next: ${result.nextCommands.join("; ")}`
17455
+ `Next: ${result.nextCommands.join("; ")}`,
17456
+ "Help: run guardian connect if the browser says Local Guardian is not connected."
17360
17457
  ].filter(Boolean).join("\n")}
17361
17458
  `
17362
17459
  );
@@ -17431,8 +17528,8 @@ function buildGuardianDistributionReadiness(npmPreviewReady) {
17431
17528
  },
17432
17529
  aiToolIntegration: {
17433
17530
  mcpInstallCommand: "guardian mcp install --client=all --write",
17434
- browserSetupCommand: "guardian extension open-setup",
17435
- 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}`
17436
17533
  },
17437
17534
  browserStoreHandoff: {
17438
17535
  status: npmPreviewReady ? "operator_handoff_ready_not_submitted" : "blocked_until_preview_ready",
@@ -17456,7 +17553,7 @@ function buildGuardianDistributionReadiness(npmPreviewReady) {
17456
17553
  boundaries: [
17457
17554
  "Use dry_run=true before public publication.",
17458
17555
  "dry_run=false requires the repository secret NPM_TOKEN and must not print npm tokens, OTP values, or .npmrc contents.",
17459
- "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
+ "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.",
17460
17557
  "GitHub Actions OIDC/provenance applies to CI release context; manual local publishes are recorded separately and should not claim CI provenance."
17461
17558
  ]
17462
17559
  },
@@ -17477,12 +17574,16 @@ function buildGuardianReleaseDispatchCommand(dryRun) {
17477
17574
  });
17478
17575
  }
17479
17576
  function buildGuardianReleaseDispatchCommandFor(input2) {
17577
+ const publishAuth = input2.publishAuth ?? "token";
17578
+ const browserStorePublish = input2.browserStorePublish ?? "manual";
17480
17579
  return [
17481
17580
  `gh workflow run ${GUARDIAN_RELEASE_DISPATCH_WORKFLOW} \\`,
17482
17581
  ` -f release_version=${input2.releaseVersion} \\`,
17483
17582
  ` -f channels=${input2.channels} \\`,
17484
17583
  ` -f dry_run=${input2.dryRun ? "true" : "false"} \\`,
17485
- ` -f run_docker_smoke=${input2.runDockerSmoke ? "true" : "false"}`
17584
+ ` -f run_docker_smoke=${input2.runDockerSmoke ? "true" : "false"} \\`,
17585
+ ` -f publish_auth=${publishAuth} \\`,
17586
+ ` -f browser_store_publish=${browserStorePublish}`
17486
17587
  ].join("\n");
17487
17588
  }
17488
17589
  async function runReleaseCommand(argv, flags, options) {
@@ -17589,11 +17690,23 @@ async function runReleaseCommand(argv, flags, options) {
17589
17690
  }
17590
17691
  const dryRun = flags.has("publish") ? false : booleanFlag(flags, "dry-run", true);
17591
17692
  const runDockerSmoke = booleanFlag(flags, "run-docker-smoke", true);
17693
+ const publishAuth = stringFlag(flags, "publish-auth") ?? "token";
17694
+ if (!isValidReleasePublishAuth(publishAuth)) {
17695
+ write(stderr, "Invalid --publish-auth. Expected one of: token, trusted_publisher.\n");
17696
+ return 1;
17697
+ }
17698
+ const browserStorePublish = stringFlag(flags, "browser-store-publish") ?? "manual";
17699
+ if (!isValidBrowserStorePublish(browserStorePublish)) {
17700
+ write(stderr, "Invalid --browser-store-publish. Expected one of: manual, api.\n");
17701
+ return 1;
17702
+ }
17592
17703
  const result = buildGuardianReleaseDispatchCommandResult({
17593
17704
  releaseVersion,
17594
17705
  channels,
17595
17706
  dryRun,
17596
- runDockerSmoke
17707
+ runDockerSmoke,
17708
+ publishAuth,
17709
+ browserStorePublish
17597
17710
  });
17598
17711
  write(stdout, renderGuardianReleaseDispatchCommand(result, flags));
17599
17712
  return 0;
@@ -17661,6 +17774,7 @@ function renderGuardianReleaseHelp(result, flags) {
17661
17774
  `;
17662
17775
  }
17663
17776
  function buildGuardianReleaseDispatchCommandResult(input2) {
17777
+ const npmLaneSelected = input2.channels === "all" || input2.channels === "npm";
17664
17778
  return {
17665
17779
  schema_version: "contextecf/project-guardian-release-dispatch-command/v1",
17666
17780
  cli_version: GUARDIAN_CLI_VERSION,
@@ -17669,13 +17783,21 @@ function buildGuardianReleaseDispatchCommandResult(input2) {
17669
17783
  channels: input2.channels,
17670
17784
  dryRun: input2.dryRun,
17671
17785
  runDockerSmoke: input2.runDockerSmoke,
17786
+ publishAuth: input2.publishAuth,
17787
+ browserStorePublish: input2.browserStorePublish,
17672
17788
  command: buildGuardianReleaseDispatchCommandFor(input2),
17673
- ...input2.dryRun ? {} : { requiredPublishCredentialName: "NPM_TOKEN" },
17789
+ ...input2.dryRun || input2.publishAuth === "trusted_publisher" || !npmLaneSelected ? {} : { requiredPublishCredentialName: "NPM_TOKEN" },
17790
+ ...input2.browserStorePublish === "api" && !input2.dryRun ? {
17791
+ requiredBrowserStoreCredentialNames: [...GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES]
17792
+ } : {},
17674
17793
  provenance: "github_actions_oidc_supported",
17675
17794
  boundaries: [
17676
17795
  "Use dry_run=true before public publication.",
17677
- "dry_run=false requires the repository secret NPM_TOKEN and must not print npm tokens, OTP values, or .npmrc contents.",
17678
- "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.",
17796
+ "dry_run=false with publish_auth=token requires the repository secret NPM_TOKEN and must not print npm tokens, OTP values, or .npmrc contents.",
17797
+ "dry_run=false with publish_auth=trusted_publisher requires npm Trusted Publishing to be configured for this GitHub Actions workflow and package.",
17798
+ "browser_store_publish=manual keeps Chrome Web Store upload as an operator ZIP handoff.",
17799
+ "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.",
17800
+ "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.",
17679
17801
  "A release-dispatch command is operator guidance, not production-gate proof."
17680
17802
  ],
17681
17803
  tokenPrinted: false,
@@ -17694,7 +17816,10 @@ function renderGuardianReleaseDispatchCommand(result, flags) {
17694
17816
  `Workflow: ${result.workflow}`,
17695
17817
  `Channels: ${result.channels}`,
17696
17818
  `Docker smoke: ${result.runDockerSmoke ? "yes" : "no"}`,
17819
+ `Publish auth: ${result.publishAuth}`,
17820
+ `Browser store publish: ${result.browserStorePublish}`,
17697
17821
  result.requiredPublishCredentialName ? `Required repository secret: ${result.requiredPublishCredentialName}` : "Required repository secret: not needed for dry run",
17822
+ result.requiredBrowserStoreCredentialNames ? `Required browser-store secrets: ${result.requiredBrowserStoreCredentialNames.join(", ")}` : "Required browser-store secrets: not needed for manual browser handoff",
17698
17823
  "Command:",
17699
17824
  result.command,
17700
17825
  "Boundaries:",
@@ -17709,6 +17834,7 @@ function buildGuardianBrowserStoreHandoffCommandResult() {
17709
17834
  cli_version: GUARDIAN_CLI_VERSION,
17710
17835
  status: "chrome_distribution_proven_next_submission_pack_ready",
17711
17836
  primaryStore: "chrome_web_store",
17837
+ publicListingUrl: GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL,
17712
17838
  productionGate: {
17713
17839
  id: "browser-store-distribution",
17714
17840
  status: "proven",
@@ -17775,6 +17901,7 @@ function renderGuardianBrowserStoreHandoffCommand(result, flags) {
17775
17901
  "Project Guardian Browser Store Handoff",
17776
17902
  `Status: ${result.status}`,
17777
17903
  `Primary store: ${result.primaryStore}`,
17904
+ `Public listing: ${result.publicListingUrl}`,
17778
17905
  `Production gate: ${result.productionGate.id} (${result.productionGate.status})`,
17779
17906
  "1. Build submission pack:",
17780
17907
  result.commands.submissionPack,
@@ -17795,15 +17922,23 @@ function renderGuardianBrowserStoreHandoffCommand(result, flags) {
17795
17922
  `;
17796
17923
  }
17797
17924
  function buildGuardianReleaseSecretSetup() {
17925
+ const chromeWebStoreSecretMatcher = GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES.join("|");
17798
17926
  return {
17799
17927
  schema_version: "contextecf/project-guardian-release-secret-setup/v1",
17800
17928
  cli_version: GUARDIAN_CLI_VERSION,
17801
17929
  repository: GUARDIAN_RELEASE_REPOSITORY,
17802
17930
  requiredForPublish: "NPM_TOKEN",
17931
+ requiredForBrowserStoreApi: GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES,
17803
17932
  githubActionsSecretUrl: `https://github.com/${GUARDIAN_RELEASE_REPOSITORY}/settings/secrets/actions/new`,
17804
17933
  commands: {
17805
17934
  setNpmToken: `gh secret set NPM_TOKEN --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17935
+ setChromeWebStoreClientId: `gh secret set CHROME_WEB_STORE_CLIENT_ID --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17936
+ setChromeWebStoreClientSecret: `gh secret set CHROME_WEB_STORE_CLIENT_SECRET --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17937
+ setChromeWebStoreRefreshToken: `gh secret set CHROME_WEB_STORE_REFRESH_TOKEN --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17938
+ setChromeWebStorePublisherId: `gh secret set CHROME_WEB_STORE_PUBLISHER_ID --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17939
+ setChromeWebStoreExtensionId: `gh secret set CHROME_WEB_STORE_EXTENSION_ID --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions`,
17806
17940
  verifyNpmTokenPresence: `gh secret list --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions | rg '^NPM_TOKEN\\b'`,
17941
+ verifyChromeWebStoreSecretsPresence: `gh secret list --repo ${GUARDIAN_RELEASE_REPOSITORY} --app actions | rg '^(${chromeWebStoreSecretMatcher})\\b'`,
17807
17942
  dryRunDispatch: buildGuardianReleaseDispatchCommandFor({
17808
17943
  dryRun: true,
17809
17944
  releaseVersion: "<version>",
@@ -17815,13 +17950,23 @@ function buildGuardianReleaseSecretSetup() {
17815
17950
  releaseVersion: "<version>",
17816
17951
  channels: "all",
17817
17952
  runDockerSmoke: true
17953
+ }),
17954
+ browserStoreApiPublishDispatch: buildGuardianReleaseDispatchCommandFor({
17955
+ dryRun: false,
17956
+ releaseVersion: "<version>",
17957
+ channels: "browser",
17958
+ runDockerSmoke: true,
17959
+ publishAuth: "trusted_publisher",
17960
+ browserStorePublish: "api"
17818
17961
  })
17819
17962
  },
17820
17963
  boundaries: [
17821
17964
  "NPM_TOKEN belongs only in GitHub Actions repository secrets for guarded dry_run=false npm publication.",
17965
+ "Chrome Web Store API OAuth values belong only in GitHub Actions repository secrets for guarded browser_store_publish=api upload/submission.",
17822
17966
  "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.",
17823
17967
  "Use dry_run=true before dry_run=false; dry-run dispatch does not require NPM_TOKEN.",
17824
- "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."
17968
+ "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.",
17969
+ "Chrome Web Store API secrets enable browser update upload/submission only; official review approval evidence is still required for production browser-store claims."
17825
17970
  ],
17826
17971
  tokenPrinted: false,
17827
17972
  rawContentIncluded: false
@@ -17835,16 +17980,27 @@ function renderGuardianReleaseSecretSetup(result, flags) {
17835
17980
  return `${[
17836
17981
  "Project Guardian Release Secret Setup",
17837
17982
  `Repository: ${result.repository}`,
17838
- `Required for publish: ${result.requiredForPublish}`,
17983
+ `Required for npm publish: ${result.requiredForPublish}`,
17984
+ `Required for Chrome Web Store API: ${result.requiredForBrowserStoreApi.join(", ")}`,
17839
17985
  `GitHub Actions secret page: ${result.githubActionsSecretUrl}`,
17840
- "Set secret:",
17986
+ "NPM publish secret:",
17841
17987
  result.commands.setNpmToken,
17842
- "Verify secret presence:",
17988
+ "Verify npm secret presence:",
17843
17989
  result.commands.verifyNpmTokenPresence,
17990
+ "Chrome Web Store API secrets:",
17991
+ result.commands.setChromeWebStoreClientId,
17992
+ result.commands.setChromeWebStoreClientSecret,
17993
+ result.commands.setChromeWebStoreRefreshToken,
17994
+ result.commands.setChromeWebStorePublisherId,
17995
+ result.commands.setChromeWebStoreExtensionId,
17996
+ "Verify Chrome Web Store API secret presence:",
17997
+ result.commands.verifyChromeWebStoreSecretsPresence,
17844
17998
  "Dry-run release command:",
17845
17999
  result.commands.dryRunDispatch,
17846
- "Publish release command:",
18000
+ "Default publish command with manual browser-store upload:",
17847
18001
  result.commands.publishDispatch,
18002
+ "Browser-store API publish command:",
18003
+ result.commands.browserStoreApiPublishDispatch,
17848
18004
  "Boundaries:",
17849
18005
  ...result.boundaries.map((boundary) => `- ${boundary}`),
17850
18006
  "Token printed: no"
@@ -18065,9 +18221,9 @@ function buildGuardianReleaseVerify(releaseVersion) {
18065
18221
  commands: {
18066
18222
  registryVersion: `npm view ${GUARDIAN_NPM_PACKAGE_NAME}@${releaseVersion} version --registry=${GUARDIAN_NPM_REGISTRY}`,
18067
18223
  publicInstall: `npm install -g ${GUARDIAN_NPM_PACKAGE_NAME}@${releaseVersion}`,
18224
+ chromeWebStoreListing: GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL,
18068
18225
  installedVersion: "guardian --version",
18069
18226
  setup: "guardian setup",
18070
- launch: "guardian launch",
18071
18227
  status: "guardian status",
18072
18228
  openControlTower: "guardian open",
18073
18229
  copyPasteLocalSmoke,
@@ -18096,9 +18252,8 @@ function buildGuardianReleasePaste(releaseVersion) {
18096
18252
  "guardian --version",
18097
18253
  "guardian stop",
18098
18254
  "guardian setup",
18099
- "guardian launch",
18100
- "guardian open",
18101
- "guardian status"
18255
+ "guardian status",
18256
+ "guardian open"
18102
18257
  ];
18103
18258
  return {
18104
18259
  schema_version: "contextecf/project-guardian-release-paste/v1",
@@ -18109,9 +18264,9 @@ function buildGuardianReleasePaste(releaseVersion) {
18109
18264
  command: commands.join("\n"),
18110
18265
  commands,
18111
18266
  boundaries: [
18112
- "This paste block verifies public npm visibility, global install, first-run setup, daemon launch, Control Tower open, and status.",
18267
+ "This paste block verifies public npm visibility, global install, stale-process cleanup, first-run setup, Local Guardian status, and Control Tower open.",
18113
18268
  "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.",
18114
- "guardian stop is included before setup so stale local daemons or local token mismatches are cleared before launch."
18269
+ "guardian stop is included before setup so stale Local Guardian processes or local token mismatches are cleared before setup relaunches the local service."
18115
18270
  ],
18116
18271
  tokenPrinted: false,
18117
18272
  rawContentIncluded: false
@@ -18138,18 +18293,19 @@ function renderGuardianReleaseVerify(result, flags) {
18138
18293
  `Status: ${result.status}`,
18139
18294
  "1. Confirm registry visibility:",
18140
18295
  result.commands.registryVersion,
18141
- "2. Install the published version:",
18296
+ "2. Confirm public Chrome Web Store listing:",
18297
+ result.commands.chromeWebStoreListing,
18298
+ "3. Install the published version:",
18142
18299
  result.commands.publicInstall,
18143
- "3. Confirm installed CLI version:",
18300
+ "4. Confirm installed CLI version:",
18144
18301
  result.commands.installedVersion,
18145
- "4. Run first-use smoke:",
18302
+ "5. Run first-use smoke:",
18146
18303
  result.commands.setup,
18147
- result.commands.launch,
18148
18304
  result.commands.status,
18149
18305
  result.commands.openControlTower,
18150
- "5. Copy-paste local smoke block:",
18306
+ "6. Copy-paste local smoke block:",
18151
18307
  result.commands.copyPasteLocalSmoke,
18152
- "6. Verify release evidence:",
18308
+ "7. Verify release evidence:",
18153
18309
  result.commands.publishStatusVerification,
18154
18310
  result.commands.finishLineStatus,
18155
18311
  "Evidence outputs:",
@@ -18166,6 +18322,12 @@ function isValidReleaseVersion(value) {
18166
18322
  function isValidReleaseChannels(value) {
18167
18323
  return ["all", "npm", "browser", "evidence"].includes(value);
18168
18324
  }
18325
+ function isValidReleasePublishAuth(value) {
18326
+ return value === "token" || value === "trusted_publisher";
18327
+ }
18328
+ function isValidBrowserStorePublish(value) {
18329
+ return value === "manual" || value === "api";
18330
+ }
18169
18331
  function renderGuardianProductionReadiness(result, flags) {
18170
18332
  if (flags.has("json")) {
18171
18333
  return `${JSON.stringify(result, null, 2)}
@@ -18268,9 +18430,9 @@ async function statusGuardian(flags, options) {
18268
18430
  `Profile: ${home}`,
18269
18431
  `Runtime profile: ${result.runtime.profileDaemonStatus}`,
18270
18432
  `Control Tower: ${result.controlTowerUrl ?? "not configured"}`,
18271
- `Daemon health: ${result.daemon.status}${result.daemon.statusCode ? ` (HTTP ${result.daemon.statusCode})` : ""}`,
18272
- result.daemon.error ? `Daemon detail: ${result.daemon.error}` : void 0,
18273
- 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,
18433
+ `Local Guardian: ${result.daemon.status}${result.daemon.statusCode ? ` (HTTP ${result.daemon.statusCode})` : ""}`,
18434
+ result.daemon.error ? `Connection detail: ${result.daemon.error}` : void 0,
18435
+ 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,
18274
18436
  `MCP stdio: ${result.runtime.mcpStdioAvailable ? "available" : "unavailable"}`,
18275
18437
  `Posture Profile: ${result.postureProfile.selected}`,
18276
18438
  `Prompt Coach: ${result.preferences.promptCoachMode}`,
@@ -18377,7 +18539,7 @@ async function runLaunchCommand(flags, options) {
18377
18539
  nextCommands: [
18378
18540
  "guardian open",
18379
18541
  "guardian status",
18380
- "guardian daemon pair --json",
18542
+ "Open ContextECF Halo and click Connect",
18381
18543
  "guardian policy list"
18382
18544
  ]
18383
18545
  };
@@ -18386,7 +18548,7 @@ async function runLaunchCommand(flags, options) {
18386
18548
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
18387
18549
  ` : `${[
18388
18550
  "Project Guardian launched.",
18389
- `Daemon: ${result.daemonUrl}${result.reusedExistingDaemon ? " (already running)" : ""}`,
18551
+ `Local Guardian: ${result.daemonUrl}${result.reusedExistingDaemon ? " (already running)" : ""}`,
18390
18552
  `Control Tower: ${result.controlTowerUrl}`,
18391
18553
  shouldOpen ? `Opening: ${result.controlTowerUrl}` : void 0,
18392
18554
  "Next: guardian open; guardian status"
@@ -18497,14 +18659,14 @@ async function runStartCommand(flags, options) {
18497
18659
  controlTowerUrl: requestedControlTowerUrl,
18498
18660
  healthUrl,
18499
18661
  healthStatus: "reachable",
18500
- nextCommands: ["guardian open", "guardian status", "guardian daemon pair --json"]
18662
+ nextCommands: ["guardian open", "guardian status", "Open ContextECF Halo and click Connect"]
18501
18663
  };
18502
18664
  write(
18503
18665
  stdout,
18504
18666
  flags.has("json") ? `${JSON.stringify(result2, null, 2)}
18505
18667
  ` : `${[
18506
- "Guardian daemon already running.",
18507
- `Daemon: ${requestedDaemonUrl}`,
18668
+ "Local Guardian already running.",
18669
+ `Local Guardian: ${requestedDaemonUrl}`,
18508
18670
  `Control Tower: ${requestedControlTowerUrl}`,
18509
18671
  "Next: guardian open"
18510
18672
  ].join("\n")}
@@ -18513,7 +18675,7 @@ async function runStartCommand(flags, options) {
18513
18675
  return 0;
18514
18676
  }
18515
18677
  if (!options.startDetachedDaemon) {
18516
- write(stderr, "Guardian detached daemon launcher is unavailable in this runtime.\n");
18678
+ write(stderr, "Guardian local background launcher is unavailable in this runtime.\n");
18517
18679
  return 1;
18518
18680
  }
18519
18681
  const started = await options.startDetachedDaemon({ home, host, port: selected.port });
@@ -18534,14 +18696,14 @@ async function runStartCommand(flags, options) {
18534
18696
  pid: started.pid,
18535
18697
  healthUrl,
18536
18698
  healthStatus: existing.error ? "unreachable" : "not_reachable",
18537
- nextCommands: ["guardian open", "guardian status", "guardian daemon pair --json"]
18699
+ nextCommands: ["guardian open", "guardian status", "Open ContextECF Halo and click Connect"]
18538
18700
  };
18539
18701
  write(
18540
18702
  stdout,
18541
18703
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
18542
18704
  ` : `${[
18543
- "Guardian daemon start requested.",
18544
- `Daemon: ${daemonUrl}`,
18705
+ "Local Guardian start requested.",
18706
+ `Local Guardian: ${daemonUrl}`,
18545
18707
  `Control Tower: ${controlTowerUrl}`,
18546
18708
  started.pid ? `PID: ${started.pid}` : void 0,
18547
18709
  "Next: guardian open"
@@ -18552,8 +18714,8 @@ async function runStartCommand(flags, options) {
18552
18714
  }
18553
18715
  function renderDaemonTokenMismatchMessage(requestedDaemonUrl, port) {
18554
18716
  return [
18555
- `Guardian found another daemon or container at ${requestedDaemonUrl}, but it rejected this profile's local runtime token.`,
18556
- "This usually means an older Guardian daemon or Docker demo is still using the port.",
18717
+ `Guardian found another local service or container at ${requestedDaemonUrl}, but it rejected this profile's local connection token.`,
18718
+ "This usually means an older Guardian background service or Docker demo is still using the port.",
18557
18719
  `Recovery: stop the other process/container, or run Guardian on a different port with guardian launch --port=${port + 1}.`,
18558
18720
  `Diagnostics: guardian status; docker ps --filter publish=${port}`,
18559
18721
  ""
@@ -18586,7 +18748,7 @@ async function runStopCommand(flags, options) {
18586
18748
  if (!stopped.ok) {
18587
18749
  write(
18588
18750
  stderr,
18589
- `Guardian daemon did not stop cleanly${stopped.statusCode ? ` (HTTP ${stopped.statusCode})` : ""}: ${stopped.error ?? "unknown_error"}
18751
+ `Local Guardian did not stop cleanly${stopped.statusCode ? ` (HTTP ${stopped.statusCode})` : ""}: ${stopped.error ?? "unknown_error"}
18590
18752
  `
18591
18753
  );
18592
18754
  return 1;
@@ -18597,12 +18759,12 @@ async function runStopCommand(flags, options) {
18597
18759
  success: true,
18598
18760
  daemonStatus: profile.runtime.daemon_status,
18599
18761
  stopUrl: stopUrl.url,
18600
- note: "Guardian daemon stop requested."
18762
+ note: "Local Guardian stop requested."
18601
18763
  };
18602
18764
  write(
18603
18765
  stdout,
18604
18766
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
18605
- ` : `${["Guardian daemon stop requested.", "Status: not_started_mvp"].join("\n")}
18767
+ ` : `${["Local Guardian stop requested.", "Status: not_started_mvp"].join("\n")}
18606
18768
  `
18607
18769
  );
18608
18770
  return 0;
@@ -18674,7 +18836,7 @@ Proof: ${result2.proofPath}
18674
18836
  }
18675
18837
  const host = stringFlag(flags, "host") ?? "127.0.0.1";
18676
18838
  if (!isLoopbackHost(host)) {
18677
- write(stderr, "Guardian service supervision only accepts loopback daemon hosts.\n");
18839
+ write(stderr, "Guardian service supervision only accepts loopback Local Guardian hosts.\n");
18678
18840
  return 1;
18679
18841
  }
18680
18842
  const port = numberFlag(flags, "port") ?? 4317;
@@ -18931,7 +19093,7 @@ async function daemonPair(flags, options) {
18931
19093
  [GUARDIAN_BRIDGE_CONFIG_STORAGE_KEY]: bridgeConfig,
18932
19094
  [GUARDIAN_BRIDGE_TOKEN_STORAGE_KEY]: token
18933
19095
  },
18934
- note: "Store this token in the Guardian browser extension local settings, then run guardian daemon serve."
19096
+ note: "Advanced fallback only. Most users should run guardian setup, open ContextECF Halo, and click Connect."
18935
19097
  };
18936
19098
  if (flags.has("json")) {
18937
19099
  return { exitCode: 0, message: `${JSON.stringify(pairing, null, 2)}
@@ -18940,13 +19102,10 @@ async function daemonPair(flags, options) {
18940
19102
  return {
18941
19103
  exitCode: 0,
18942
19104
  message: `${[
18943
- "Guardian browser bridge pairing",
18944
- `Storage key: ${pairing.storageKey}`,
18945
- `Loopback URL: ${pairing.loopbackBaseUrl}`,
18946
- `Transport: ${pairing.transport}`,
18947
- `Native host: ${pairing.nativeHostName}`,
18948
- `Token: ${pairing.token}`,
18949
- pairing.note
19105
+ "Guardian browser connection uses one-click setup now.",
19106
+ "Run guardian connect, then open ContextECF Halo and click Connect.",
19107
+ "Support setup codes are only printed by guardian pair --json.",
19108
+ "Token printed: no"
18950
19109
  ].join("\n")}
18951
19110
  `
18952
19111
  };
@@ -18955,6 +19114,9 @@ async function runExtensionCommand(argv, flags, options) {
18955
19114
  const subcommand = argv[0] ?? "native-host";
18956
19115
  const stdout = options.stdout ?? process.stdout;
18957
19116
  const stderr = options.stderr ?? process.stderr;
19117
+ if (subcommand === "connect") {
19118
+ return runExtensionConnectCommand(flags, options);
19119
+ }
18958
19120
  if (subcommand === "open-setup") {
18959
19121
  return runExtensionOpenSetupCommand(flags, options);
18960
19122
  }
@@ -19142,13 +19304,18 @@ Re-run with --force to replace it.
19142
19304
  registryMutationRequested: writeRegistry,
19143
19305
  registryProofPath,
19144
19306
  tokenPrinted: false,
19145
- nextCommands: ["guardian launch", "guardian daemon pair --json"]
19307
+ nextCommands: [
19308
+ "guardian launch",
19309
+ "Open ContextECF Halo and click Connect",
19310
+ "guardian extension native-host status"
19311
+ ],
19312
+ advancedCommands: ["guardian pair --json"]
19146
19313
  };
19147
19314
  write(
19148
19315
  stdout,
19149
19316
  flags.has("json") ? `${JSON.stringify(result, null, 2)}
19150
19317
  ` : `${[
19151
- "Guardian native host installed.",
19318
+ "Guardian browser connection installed.",
19152
19319
  `Browser: ${browser}`,
19153
19320
  `Host: ${plan.hostName}`,
19154
19321
  `Manifest: ${plan.manifestPath}`,
@@ -19163,6 +19330,121 @@ Re-run with --force to replace it.
19163
19330
  );
19164
19331
  return 0;
19165
19332
  }
19333
+ async function runExtensionConnectCommand(flags, options) {
19334
+ const stdout = options.stdout ?? process.stdout;
19335
+ const stderr = options.stderr ?? process.stderr;
19336
+ const requestedExtensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
19337
+ const extensionId = requestedExtensionId ?? GUARDIAN_HALO_CHROME_EXTENSION_ID;
19338
+ if (requestedExtensionId && !isValidChromeExtensionId(requestedExtensionId)) {
19339
+ write(
19340
+ stderr,
19341
+ "Invalid --extension-id. Chrome extension ids must be 32 lowercase letters from a-p.\n"
19342
+ );
19343
+ return 1;
19344
+ }
19345
+ const install = JSON.parse(await installGuardian(/* @__PURE__ */ new Map([["json", true]]), options));
19346
+ let launchOutput = "";
19347
+ const launchFlags = new Map([
19348
+ ["json", true],
19349
+ ["print", true],
19350
+ ...copyStringFlags(flags, ["host", "port"])
19351
+ ]);
19352
+ const launchExitCode = await runLaunchCommand(launchFlags, {
19353
+ ...options,
19354
+ stdout: {
19355
+ write: (chunk) => {
19356
+ launchOutput += chunk.toString();
19357
+ return true;
19358
+ }
19359
+ },
19360
+ stderr
19361
+ });
19362
+ if (launchExitCode !== 0) {
19363
+ return launchExitCode;
19364
+ }
19365
+ const launch = JSON.parse(launchOutput);
19366
+ const helperFlags = new Map([
19367
+ ["json", true],
19368
+ ["force", true],
19369
+ ["extension-id", extensionId],
19370
+ ...copyStringFlags(flags, ["browser", "target"])
19371
+ ]);
19372
+ if ((options.platform ?? process.platform) === "win32") {
19373
+ helperFlags.set("write-registry", true);
19374
+ helperFlags.set("yes", true);
19375
+ }
19376
+ let helperOutput = "";
19377
+ let helperError = "";
19378
+ const helperExitCode = await runExtensionCommand(["native-host", "install"], helperFlags, {
19379
+ ...options,
19380
+ stdout: {
19381
+ write: (chunk) => {
19382
+ helperOutput += chunk.toString();
19383
+ return true;
19384
+ }
19385
+ },
19386
+ stderr: {
19387
+ write: (chunk) => {
19388
+ helperError += chunk.toString();
19389
+ return true;
19390
+ }
19391
+ }
19392
+ });
19393
+ if (helperExitCode !== 0) {
19394
+ write(
19395
+ stderr,
19396
+ helperError.trim() ? `${helperError.trim()}
19397
+ ` : "Guardian could not prepare the browser connection. Run guardian setup, then try again.\n"
19398
+ );
19399
+ return helperExitCode;
19400
+ }
19401
+ const helper = JSON.parse(helperOutput);
19402
+ const result = {
19403
+ schema_version: "contextecf/project-guardian-browser-connect/v1",
19404
+ success: true,
19405
+ extensionId,
19406
+ defaultExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
19407
+ install: {
19408
+ profileDir: install.profileDir,
19409
+ tokenCreated: install.tokenCreated,
19410
+ tokenPrinted: false
19411
+ },
19412
+ localGuardian: {
19413
+ running: true,
19414
+ opened: launch.opened,
19415
+ reusedExistingDaemon: launch.reusedExistingDaemon,
19416
+ daemonUrl: launch.daemonUrl,
19417
+ controlTowerUrl: launch.controlTowerUrl,
19418
+ pid: launch.pid,
19419
+ healthStatus: launch.healthStatus
19420
+ },
19421
+ browser: helper.browser,
19422
+ helperInstalled: true,
19423
+ helperStatus: {
19424
+ hostName: helper.hostName,
19425
+ manifestPath: helper.manifestPath,
19426
+ allowedOrigins: helper.allowedOrigins,
19427
+ tokenPrinted: helper.tokenPrinted
19428
+ },
19429
+ tokenPrinted: false,
19430
+ nextCommands: ["Open ContextECF Halo and click Connect", "guardian open", "guardian status"],
19431
+ supportCommands: ["guardian extension open-setup --print", "guardian pair --json"]
19432
+ };
19433
+ write(
19434
+ stdout,
19435
+ flags.has("json") ? `${JSON.stringify(result, null, 2)}
19436
+ ` : `${[
19437
+ "Guardian browser connection is ready.",
19438
+ `Local Guardian: ${result.localGuardian.daemonUrl}${result.localGuardian.reusedExistingDaemon ? " (already running)" : ""}`,
19439
+ `Browser: ${result.browser}`,
19440
+ "Next: open ContextECF Halo and click Connect.",
19441
+ "No setup code needed.",
19442
+ "Token printed: no"
19443
+ ].join("\n")}
19444
+ `
19445
+ );
19446
+ return 0;
19447
+ }
19166
19448
  async function runExtensionOpenSetupCommand(flags, options) {
19167
19449
  const stdout = options.stdout ?? process.stdout;
19168
19450
  const stderr = options.stderr ?? process.stderr;
@@ -19172,8 +19454,9 @@ async function runExtensionOpenSetupCommand(flags, options) {
19172
19454
  write(stderr, "Guardian is not installed. Run guardian install first.\n");
19173
19455
  return 1;
19174
19456
  }
19175
- const extensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
19176
- if (extensionId && !isValidChromeExtensionId(extensionId)) {
19457
+ const requestedExtensionId = stringFlag(flags, "extension-id") ?? stringFlag(flags, "id");
19458
+ const extensionId = requestedExtensionId ?? GUARDIAN_HALO_CHROME_EXTENSION_ID;
19459
+ if (requestedExtensionId && !isValidChromeExtensionId(requestedExtensionId)) {
19177
19460
  write(
19178
19461
  stderr,
19179
19462
  "Invalid --extension-id. Chrome extension ids must be 32 lowercase letters from a-p.\n"
@@ -19187,22 +19470,24 @@ async function runExtensionOpenSetupCommand(flags, options) {
19187
19470
  return 1;
19188
19471
  }
19189
19472
  const setupUrl = buildControlTowerSetupUrl(resolved.url);
19190
- const nativeHostCommand = `guardian extension native-host install --extension-id=${extensionId ?? "<browser-extension-id>"}`;
19473
+ const nativeHostCommand = `guardian extension native-host install --extension-id=${extensionId}`;
19191
19474
  const result = {
19192
19475
  schema_version: "contextecf/project-guardian-extension-setup/v1",
19193
19476
  success: true,
19194
19477
  opened: !flags.has("print"),
19195
19478
  controlTowerUrl: resolved.url,
19196
19479
  setupUrl,
19197
- extensionId: extensionId ?? null,
19480
+ extensionId,
19481
+ defaultExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
19198
19482
  tokenPrinted: false,
19199
19483
  nextCommands: [
19200
19484
  "guardian launch",
19201
- "guardian daemon pair --json",
19202
19485
  nativeHostCommand,
19203
- "guardian extension native-host status"
19486
+ "guardian extension native-host status",
19487
+ "Open ContextECF Halo and click Connect"
19204
19488
  ],
19205
- note: extensionId ? "Guardian browser setup opened with a concrete native host install command." : "Guardian browser setup opened with placeholder extension-id guidance."
19489
+ advancedCommands: ["guardian pair --json"],
19490
+ note: extensionId ? "Guardian browser setup opened for ContextECF Halo." : "Guardian browser setup opened."
19206
19491
  };
19207
19492
  if (result.opened) {
19208
19493
  const opener = options.openUrl ?? ((url2) => openUrlInDefaultBrowser(url2, options));
@@ -19214,7 +19499,8 @@ async function runExtensionOpenSetupCommand(flags, options) {
19214
19499
  ` : `${[
19215
19500
  result.opened ? `Opening Guardian browser setup: ${setupUrl}` : `Guardian browser setup: ${setupUrl}`,
19216
19501
  "Token printed: no",
19217
- `Next: ${result.nextCommands.join("; ")}`
19502
+ `Next: ${result.nextCommands.join("; ")}`,
19503
+ `Advanced fallback: ${result.advancedCommands.join("; ")}`
19218
19504
  ].join("\n")}
19219
19505
  `
19220
19506
  );
@@ -19741,7 +20027,7 @@ function buildGuardianServiceArtifact(input2) {
19741
20027
  if (input2.platform === "systemd") {
19742
20028
  return `${[
19743
20029
  "[Unit]",
19744
- "Description=Project Guardian local daemon",
20030
+ "Description=Project Guardian Local Guardian background service",
19745
20031
  "After=network.target",
19746
20032
  "",
19747
20033
  "[Service]",
@@ -19760,7 +20046,7 @@ function buildGuardianServiceArtifact(input2) {
19760
20046
  '<?xml version="1.0" encoding="UTF-16"?>',
19761
20047
  '<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
19762
20048
  " <RegistrationInfo>",
19763
- " <Description>Project Guardian local daemon</Description>",
20049
+ " <Description>Project Guardian Local Guardian background service</Description>",
19764
20050
  " </RegistrationInfo>",
19765
20051
  " <Triggers>",
19766
20052
  " <LogonTrigger><Enabled>true</Enabled></LogonTrigger>",
@@ -20742,6 +21028,59 @@ async function runReceiptsCommand(argv, flags, options) {
20742
21028
  write(stdout, await verifyReceipts(flags, options));
20743
21029
  return 0;
20744
21030
  }
21031
+ async function runCoachCommand(argv, flags, options) {
21032
+ const subcommand = argv[0] ?? "options";
21033
+ const stdout = options.stdout ?? process.stdout;
21034
+ const stderr = options.stderr ?? process.stderr;
21035
+ if (subcommand === "options" || subcommand === "catalog") {
21036
+ write(stdout, renderGuardianPromptCardOptions(buildGuardianPromptCardOptions(), flags));
21037
+ return 0;
21038
+ }
21039
+ write(stderr, `Unknown Guardian coach command: ${subcommand}
21040
+ `);
21041
+ return 1;
21042
+ }
21043
+ function buildGuardianPromptCardOptions() {
21044
+ return {
21045
+ schema_version: "contextecf/project-guardian-prompt-card-options/v1",
21046
+ cli_version: GUARDIAN_CLI_VERSION,
21047
+ status: "catalog_ready",
21048
+ summary: "Guardian prompt cards keep the user in control: warnings and safer rewrites can be sent as-is, while hard blocks cannot.",
21049
+ catalog: GUARDIAN_PROMPT_CARD_OPTIONS_CATALOG,
21050
+ rules: [
21051
+ "Show a clear reason before asking the user to decide.",
21052
+ "Offer Send as-is for warnings and safer rewrites when policy allows override.",
21053
+ "Never offer Send as-is for hard blocks or unavailable Local Guardian checks.",
21054
+ "Keep Edit prompt available as the safe path for every visible send-time card.",
21055
+ "Do not store raw sensitive prompt text in this catalog or CLI output."
21056
+ ],
21057
+ tokenPrinted: false,
21058
+ rawContentIncluded: false
21059
+ };
21060
+ }
21061
+ function renderGuardianPromptCardOptions(result, flags) {
21062
+ if (flags.has("json")) {
21063
+ return `${JSON.stringify(result, null, 2)}
21064
+ `;
21065
+ }
21066
+ return `${[
21067
+ "Guardian Prompt Card Options",
21068
+ result.summary,
21069
+ "",
21070
+ "Programmed cards:",
21071
+ ...result.catalog.map((entry) => {
21072
+ const visibleActions = entry.actions.filter((action) => action.visibleToUser).map((action) => action.label);
21073
+ const labels = visibleActions.length > 0 ? visibleActions.join(", ") : "Prompt sends automatically";
21074
+ return `- ${entry.cardStyle}: ${labels}`;
21075
+ }),
21076
+ "",
21077
+ "Rules:",
21078
+ ...result.rules.map((rule) => `- ${rule}`),
21079
+ "Token printed: no",
21080
+ "Raw prompt content included: no"
21081
+ ].join("\n")}
21082
+ `;
21083
+ }
20745
21084
  async function runPolicyCommand(argv, flags, options) {
20746
21085
  const subcommand = argv[0] ?? "list";
20747
21086
  const stdout = options.stdout ?? process.stdout;
@@ -23161,13 +23500,13 @@ function openUrlInDefaultBrowser(url2, options = {}) {
23161
23500
  });
23162
23501
  });
23163
23502
  }
23164
- 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;
23503
+ 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_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;
23165
23504
  var init_runtime = __esm({
23166
23505
  "packages/guardian-cli/src/runtime.ts"() {
23167
23506
  "use strict";
23168
23507
  init_src();
23169
23508
  init_src2();
23170
- GUARDIAN_CLI_VERSION = "0.1.5";
23509
+ GUARDIAN_CLI_VERSION = "0.1.8";
23171
23510
  GUARDIAN_PROFILE_SCHEMA_VERSION = "contextecf/project-guardian-profile/v1";
23172
23511
  GUARDIAN_NPM_PACKAGE_NAME = "@contextecf/guardian-cli";
23173
23512
  GUARDIAN_NPM_REGISTRY = "https://registry.npmjs.org/";
@@ -23175,8 +23514,17 @@ var init_runtime = __esm({
23175
23514
  GUARDIAN_NPX_SETUP_COMMAND = `npx ${GUARDIAN_NPM_PACKAGE_NAME}@latest setup`;
23176
23515
  GUARDIAN_FIRST_RUN_COMMAND = "guardian setup";
23177
23516
  GUARDIAN_OPEN_CONTROL_TOWER_COMMAND = "guardian open";
23517
+ GUARDIAN_HALO_CHROME_EXTENSION_ID = "dopkdppbiakhbglhgjfacfdhpheejkck";
23518
+ GUARDIAN_HALO_CHROME_WEB_STORE_LISTING_URL = `https://chromewebstore.google.com/detail/contextecf-halo/${GUARDIAN_HALO_CHROME_EXTENSION_ID}`;
23178
23519
  GUARDIAN_RELEASE_DISPATCH_WORKFLOW = "guardian-release-dispatch.yml";
23179
23520
  GUARDIAN_RELEASE_REPOSITORY = "Intelligent-Context-AI-Inc/ContextECF-GITHUB";
23521
+ GUARDIAN_CHROME_WEB_STORE_API_SECRET_NAMES = [
23522
+ "CHROME_WEB_STORE_CLIENT_ID",
23523
+ "CHROME_WEB_STORE_CLIENT_SECRET",
23524
+ "CHROME_WEB_STORE_REFRESH_TOKEN",
23525
+ "CHROME_WEB_STORE_PUBLISHER_ID",
23526
+ "CHROME_WEB_STORE_EXTENSION_ID"
23527
+ ];
23180
23528
  GUARDIAN_DEFAULT_CONTROL_TOWER_URL = "http://127.0.0.1:4317/control-tower";
23181
23529
  GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ENV = "GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY";
23182
23530
  GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ID_ENV = "GUARDIAN_LOCAL_DATA_ENCRYPTION_KEY_ID";
@@ -23192,6 +23540,195 @@ var init_runtime = __esm({
23192
23540
  PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION2 = "contextecf/personal-fabric-contracts/v1";
23193
23541
  PROMPT_COACH_MODES = ["off", "quiet", "balanced", "hands_on"];
23194
23542
  DEFAULT_PROMPT_COACH_DAILY_SOFT_CARD_LIMIT = 6;
23543
+ GUARDIAN_PROMPT_CARD_OPTIONS_CATALOG = [
23544
+ {
23545
+ disposition: "allow",
23546
+ cardStyle: "No visible card",
23547
+ whenShown: "Local Guardian approves the prompt before it leaves the browser.",
23548
+ actions: [
23549
+ {
23550
+ id: "auto-send",
23551
+ label: "Send prompt",
23552
+ role: "none",
23553
+ sendsPrompt: true,
23554
+ sendsRevisedPrompt: false,
23555
+ requiresConfirmation: false,
23556
+ visibleToUser: false
23557
+ }
23558
+ ],
23559
+ sendAsIsAllowed: true,
23560
+ hardBlock: false,
23561
+ receiptExpected: true
23562
+ },
23563
+ {
23564
+ disposition: "warn",
23565
+ cardStyle: "Quick check",
23566
+ whenShown: "Guardian found a low or medium-risk concern, but policy still allows the user to continue.",
23567
+ actions: [
23568
+ {
23569
+ id: "send-as-is",
23570
+ label: "Send as-is",
23571
+ role: "allowed_override",
23572
+ sendsPrompt: true,
23573
+ sendsRevisedPrompt: false,
23574
+ requiresConfirmation: false,
23575
+ visibleToUser: true
23576
+ },
23577
+ {
23578
+ id: "edit-prompt",
23579
+ label: "Edit prompt",
23580
+ role: "safe_escape",
23581
+ sendsPrompt: false,
23582
+ sendsRevisedPrompt: false,
23583
+ requiresConfirmation: false,
23584
+ visibleToUser: true
23585
+ }
23586
+ ],
23587
+ sendAsIsAllowed: true,
23588
+ hardBlock: false,
23589
+ receiptExpected: true
23590
+ },
23591
+ {
23592
+ disposition: "rewrite",
23593
+ cardStyle: "Safer version ready",
23594
+ whenShown: "Guardian prepared a safer prompt, usually by masking sensitive text or narrowing scope.",
23595
+ actions: [
23596
+ {
23597
+ id: "send-revised-prompt",
23598
+ label: "Send revised prompt",
23599
+ role: "recommended",
23600
+ sendsPrompt: true,
23601
+ sendsRevisedPrompt: true,
23602
+ requiresConfirmation: false,
23603
+ visibleToUser: true
23604
+ },
23605
+ {
23606
+ id: "send-as-is",
23607
+ label: "Send as-is",
23608
+ role: "allowed_override",
23609
+ sendsPrompt: true,
23610
+ sendsRevisedPrompt: false,
23611
+ requiresConfirmation: false,
23612
+ visibleToUser: true
23613
+ },
23614
+ {
23615
+ id: "edit-prompt",
23616
+ label: "Edit prompt",
23617
+ role: "safe_escape",
23618
+ sendsPrompt: false,
23619
+ sendsRevisedPrompt: false,
23620
+ requiresConfirmation: false,
23621
+ visibleToUser: true
23622
+ }
23623
+ ],
23624
+ sendAsIsAllowed: true,
23625
+ hardBlock: false,
23626
+ receiptExpected: true
23627
+ },
23628
+ {
23629
+ disposition: "confirm",
23630
+ cardStyle: "Review needed",
23631
+ whenShown: "Guardian needs deliberate approval before sending higher-risk content or action requests.",
23632
+ actions: [
23633
+ {
23634
+ id: "approve-and-send",
23635
+ label: "Approve and send",
23636
+ role: "recommended",
23637
+ sendsPrompt: true,
23638
+ sendsRevisedPrompt: false,
23639
+ requiresConfirmation: true,
23640
+ visibleToUser: true
23641
+ },
23642
+ {
23643
+ id: "edit-prompt",
23644
+ label: "Edit prompt",
23645
+ role: "safe_escape",
23646
+ sendsPrompt: false,
23647
+ sendsRevisedPrompt: false,
23648
+ requiresConfirmation: false,
23649
+ visibleToUser: true
23650
+ }
23651
+ ],
23652
+ sendAsIsAllowed: false,
23653
+ hardBlock: false,
23654
+ receiptExpected: true
23655
+ },
23656
+ {
23657
+ disposition: "block",
23658
+ cardStyle: "Blocked",
23659
+ whenShown: "The prompt violates policy, contains protected secrets, or targets a denied destination.",
23660
+ actions: [
23661
+ {
23662
+ id: "edit-prompt",
23663
+ label: "Edit prompt",
23664
+ role: "safe_escape",
23665
+ sendsPrompt: false,
23666
+ sendsRevisedPrompt: false,
23667
+ requiresConfirmation: false,
23668
+ visibleToUser: true
23669
+ }
23670
+ ],
23671
+ sendAsIsAllowed: false,
23672
+ hardBlock: true,
23673
+ receiptExpected: true
23674
+ },
23675
+ {
23676
+ disposition: "unavailable",
23677
+ cardStyle: "Setup needed",
23678
+ whenShown: "The browser cannot reach Local Guardian, so the prompt is held back.",
23679
+ actions: [
23680
+ {
23681
+ id: "edit-prompt",
23682
+ label: "Edit prompt",
23683
+ role: "safe_escape",
23684
+ sendsPrompt: false,
23685
+ sendsRevisedPrompt: false,
23686
+ requiresConfirmation: false,
23687
+ visibleToUser: true
23688
+ }
23689
+ ],
23690
+ sendAsIsAllowed: false,
23691
+ hardBlock: false,
23692
+ receiptExpected: false
23693
+ },
23694
+ {
23695
+ disposition: "live_sensitive_warning",
23696
+ cardStyle: "Early heads-up",
23697
+ whenShown: "Guardian notices sensitive text while the user is typing, before the formal send-time check.",
23698
+ actions: [
23699
+ {
23700
+ id: "use-placeholder",
23701
+ label: "Use placeholder",
23702
+ role: "recommended",
23703
+ sendsPrompt: false,
23704
+ sendsRevisedPrompt: true,
23705
+ requiresConfirmation: false,
23706
+ visibleToUser: true
23707
+ },
23708
+ {
23709
+ id: "send-as-is",
23710
+ label: "Send as-is",
23711
+ role: "allowed_override",
23712
+ sendsPrompt: true,
23713
+ sendsRevisedPrompt: false,
23714
+ requiresConfirmation: false,
23715
+ visibleToUser: true
23716
+ },
23717
+ {
23718
+ id: "keep-drafting",
23719
+ label: "Keep drafting",
23720
+ role: "safe_escape",
23721
+ sendsPrompt: false,
23722
+ sendsRevisedPrompt: false,
23723
+ requiresConfirmation: false,
23724
+ visibleToUser: true
23725
+ }
23726
+ ],
23727
+ sendAsIsAllowed: true,
23728
+ hardBlock: false,
23729
+ receiptExpected: false
23730
+ }
23731
+ ];
23195
23732
  GUARDIAN_POSTURE_PROFILE_IDS = [
23196
23733
  "calm",
23197
23734
  "balanced",
@@ -23684,10 +24221,49 @@ Open Control Tower:
23684
24221
  ${GUARDIAN_OPEN_CONTROL_TOWER_COMMAND}
23685
24222
  start aliases: guardian launch, guardian tower, guardian control-tower
23686
24223
 
24224
+ Everyday commands:
24225
+ guardian --version
24226
+ guardian setup
24227
+ guardian launch
24228
+ guardian open
24229
+ guardian status
24230
+ guardian doctor
24231
+ guardian connect
24232
+ guardian coach options
24233
+ guardian policy list
24234
+ guardian marketplace list
24235
+ guardian posture list
24236
+ guardian privacy show
24237
+
24238
+ Recommended first run:
24239
+ ${GUARDIAN_GLOBAL_INSTALL_COMMAND}
24240
+ guardian setup
24241
+ Open ContextECF Halo and click Connect.
24242
+ guardian open
24243
+
24244
+ Support and developer commands:
24245
+ guardian help --advanced
24246
+
24247
+ Defaults are local-first: raw capture, cloud sync, learning graph, and app autopilot stay off.`;
24248
+ ADVANCED_HELP_TEXT = `guardian \u2014 Project Guardian Personal Context Fabric
24249
+
24250
+ Global install:
24251
+ ${GUARDIAN_GLOBAL_INSTALL_COMMAND}
24252
+
24253
+ First run:
24254
+ ${GUARDIAN_FIRST_RUN_COMMAND}
24255
+
24256
+ No global install:
24257
+ ${GUARDIAN_NPX_SETUP_COMMAND}
24258
+
24259
+ Open Control Tower:
24260
+ ${GUARDIAN_OPEN_CONTROL_TOWER_COMMAND}
24261
+ start aliases: guardian launch, guardian tower, guardian control-tower
24262
+
23687
24263
  Usage:
23688
24264
  guardian --version
23689
24265
  guardian version [--json]
23690
- guardian setup [--json] [--open] [--print] [--host=127.0.0.1] [--port=4317] [--no-launch] [--no-mcp] [--extension-id=<chrome-extension-id>]
24266
+ guardian setup [--json] [--open] [--print] [--host=127.0.0.1] [--port=4317] [--no-launch] [--no-mcp] [--no-browser-connection] [--extension-id=<chrome-extension-id>]
23691
24267
  guardian install [--json]
23692
24268
  guardian doctor [--json]
23693
24269
  guardian readiness [--json] [--strict]
@@ -23695,7 +24271,7 @@ Usage:
23695
24271
  guardian release flow --version=<semver> [--json]
23696
24272
  guardian release verify --version=<semver> [--json]
23697
24273
  guardian release paste --version=<semver> [--json]
23698
- guardian release command --version=<semver> [--publish|--dry-run=false] [--channels=all|npm|browser|evidence] [--run-docker-smoke=true|false] [--json]
24274
+ 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]
23699
24275
  guardian release secrets [--json]
23700
24276
  guardian release browser-store [--json]
23701
24277
  guardian open [--json] [--print] [--url=http://127.0.0.1:<port>]
@@ -23703,6 +24279,8 @@ Usage:
23703
24279
  guardian start [--json] [--host=127.0.0.1] [--port=4317]
23704
24280
  guardian launch|tower|control-tower [--json] [--open] [--print] [--host=127.0.0.1] [--port=4317]
23705
24281
  guardian stop [--json] [--url=http://127.0.0.1:<port>/control-tower]
24282
+ guardian pair [--json]
24283
+ guardian connect [--json] [--browser=chrome|chromium|edge] [--extension-id=<chrome-extension-id>] [--host=127.0.0.1] [--port=4317]
23706
24284
  guardian service install [--json] [--platform=auto|launchd|systemd|windows-task-scheduler] [--force]
23707
24285
  guardian service status [--json]
23708
24286
  guardian service uninstall [--yes] [--json]
@@ -23711,6 +24289,7 @@ Usage:
23711
24289
  guardian key-store uninstall [--yes] [--json]
23712
24290
  guardian daemon serve [--host=127.0.0.1] [--port=4317]
23713
24291
  guardian daemon pair [--json]
24292
+ guardian extension connect [--json] [--browser=chrome|chromium|edge] [--extension-id=<chrome-extension-id>]
23714
24293
  guardian extension open-setup [--json] [--print] [--url=http://127.0.0.1:<port>/control-tower] [--extension-id=<chrome-extension-id>]
23715
24294
  guardian extension native-host install --extension-id=<chrome-extension-id> [--browser=chrome|chromium|edge] [--target=/path/to/manifest] [--force] [--write-registry --yes] [--json]
23716
24295
  guardian extension native-host status [--browser=chrome|chromium|edge] [--target=/path/to/manifest] [--json]
@@ -23726,6 +24305,7 @@ Usage:
23726
24305
  guardian privacy mode on [--json]
23727
24306
  guardian mcp install [--json] [--client=generic|codex|claude-desktop|cursor|all] [--write] [--target=/path/to/config] [--force]
23728
24307
  guardian mcp serve
24308
+ guardian coach options [--json]
23729
24309
  guardian policy list [--json]
23730
24310
  guardian policy enable <pack-id> [--json]
23731
24311
  guardian policy disable <pack-id> [--json]
@@ -24062,11 +24642,12 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24062
24642
  return void 0;
24063
24643
  }
24064
24644
  const fullSsnRisk = hasFullSsnRisk(riskReport.matched_policy_refs);
24645
+ const riskCopy = buildPromptCoachRiskCopy(riskReport, fullSsnRisk);
24065
24646
  const card = {
24066
24647
  card_id: `coach:${riskReport.risk_report_id}`,
24067
24648
  intervention_level: coachDecision.intervention_level,
24068
- 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",
24069
- 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.",
24649
+ 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,
24650
+ 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,
24070
24651
  recommended_choice_id: disposition === "confirm" ? "confirm-after-review" : coachDecision.intervention_level === "soft_tag_along" ? "add-context" : "use-guardian-version",
24071
24652
  choices: disposition === "confirm" ? [
24072
24653
  {
@@ -24091,8 +24672,8 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24091
24672
  resulting_disposition: "warn"
24092
24673
  },
24093
24674
  {
24094
- choice_id: "continue",
24095
- label: "Continue",
24675
+ choice_id: "send-as-is",
24676
+ label: "Send as-is",
24096
24677
  description: "Send without adding context.",
24097
24678
  resulting_disposition: "allow"
24098
24679
  }
@@ -24109,6 +24690,12 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24109
24690
  label: "Cancel",
24110
24691
  description: "Do not send this prompt.",
24111
24692
  resulting_disposition: "block"
24693
+ },
24694
+ {
24695
+ choice_id: "send-as-is",
24696
+ label: "Send as-is",
24697
+ description: "Send the original prompt after reviewing the identity-number risk.",
24698
+ resulting_disposition: "allow"
24112
24699
  }
24113
24700
  ] : [
24114
24701
  {
@@ -24129,6 +24716,60 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
24129
24716
  const parsedCard = PromptCoachCardSchema.parse(card);
24130
24717
  return shouldQuietPromptCoachCard(parsedCard, disposition, learningSignals) ? void 0 : parsedCard;
24131
24718
  }
24719
+ function buildPromptCoachRiskCopy(riskReport, fullSsnRisk) {
24720
+ if (fullSsnRisk) {
24721
+ return {
24722
+ title: "Guardian found an identity number",
24723
+ body: "Guardian can mask the Social Security number before anything leaves your machine."
24724
+ };
24725
+ }
24726
+ if (riskReport.detected_risks.includes("secret")) {
24727
+ return {
24728
+ title: "Guardian found a secret or credential",
24729
+ body: "Guardian can remove tokens, keys, or passwords before this prompt leaves your machine."
24730
+ };
24731
+ }
24732
+ if (riskReport.detected_risks.includes("financial_data")) {
24733
+ return {
24734
+ title: "Guardian found financial information",
24735
+ body: "Guardian can mask financial details or help you keep them out of the prompt."
24736
+ };
24737
+ }
24738
+ if (riskReport.detected_risks.includes("healthcare_data")) {
24739
+ return {
24740
+ title: "Guardian found health information",
24741
+ body: "Guardian can help remove identifying health details before you send this prompt."
24742
+ };
24743
+ }
24744
+ if (riskReport.detected_risks.includes("prompt_injection")) {
24745
+ return {
24746
+ title: "Guardian found prompt-injection language",
24747
+ body: "Guardian noticed instructions that may try to override your intended task or safety rules."
24748
+ };
24749
+ }
24750
+ if (riskReport.detected_risks.includes("unsafe_destination")) {
24751
+ return {
24752
+ title: "Guardian found an unfamiliar destination",
24753
+ body: "Guardian noticed this prompt may be going somewhere that needs an extra check."
24754
+ };
24755
+ }
24756
+ if (riskReport.detected_risks.includes("pii")) {
24757
+ return {
24758
+ title: "Guardian found personal information",
24759
+ body: "Guardian can mask personal details before this prompt leaves your machine."
24760
+ };
24761
+ }
24762
+ if (riskReport.detected_risks.includes("work_data")) {
24763
+ return {
24764
+ title: "Guardian found work or customer data",
24765
+ body: "Guardian can help keep private work details out of the prompt or replace them with placeholders."
24766
+ };
24767
+ }
24768
+ return {
24769
+ title: "Guardian can improve this prompt",
24770
+ body: "Guardian found context or privacy improvements that may make this prompt safer and more useful."
24771
+ };
24772
+ }
24132
24773
  function shouldQuietPromptCoachCard(card, disposition, learningSignals) {
24133
24774
  if (!learningSignals || disposition === "block" || disposition === "confirm") {
24134
24775
  return false;
@@ -25776,10 +26417,14 @@ __export(daemon_exports, {
25776
26417
  processGuardianControlTowerActivityRequest: () => processGuardianControlTowerActivityRequest,
25777
26418
  processGuardianControlTowerAppPermissionRequest: () => processGuardianControlTowerAppPermissionRequest,
25778
26419
  processGuardianControlTowerDataRequest: () => processGuardianControlTowerDataRequest,
26420
+ processGuardianControlTowerMarketplaceRequest: () => processGuardianControlTowerMarketplaceRequest,
26421
+ processGuardianControlTowerNoticeRequest: () => processGuardianControlTowerNoticeRequest,
25779
26422
  processGuardianControlTowerPolicyRequest: () => processGuardianControlTowerPolicyRequest,
25780
26423
  processGuardianControlTowerPreferenceRequest: () => processGuardianControlTowerPreferenceRequest,
26424
+ processGuardianControlTowerPrimePackRequest: () => processGuardianControlTowerPrimePackRequest,
25781
26425
  processGuardianControlTowerProviderPermissionRequest: () => processGuardianControlTowerProviderPermissionRequest,
25782
26426
  processGuardianControlTowerSourcePermissionRequest: () => processGuardianControlTowerSourcePermissionRequest,
26427
+ processGuardianControlTowerViewModelRequest: () => processGuardianControlTowerViewModelRequest,
25783
26428
  processGuardianDaemonHealthRequest: () => processGuardianDaemonHealthRequest,
25784
26429
  processGuardianDaemonStopRequest: () => processGuardianDaemonStopRequest,
25785
26430
  processGuardianLiveCoachEventRequest: () => processGuardianLiveCoachEventRequest,
@@ -25789,7 +26434,7 @@ __export(daemon_exports, {
25789
26434
  });
25790
26435
  import { createHash as createHash5, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
25791
26436
  import { existsSync as existsSync2 } from "node:fs";
25792
- import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, stat as stat3 } from "node:fs/promises";
26437
+ import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
25793
26438
  import { createServer } from "node:http";
25794
26439
  import path4 from "node:path";
25795
26440
  import { StringDecoder } from "node:string_decoder";
@@ -25915,12 +26560,22 @@ async function handleRequest(input2) {
25915
26560
  return;
25916
26561
  }
25917
26562
  if (input2.request.method === "GET" && (input2.request.url === "/control-tower" || input2.request.url === "/control-tower/")) {
25918
- const [receipts, searchGraph, learningEvents, localDataStatus, releaseDoctorStatus] = await Promise.all([
26563
+ const [
26564
+ receipts,
26565
+ searchGraph,
26566
+ learningEvents,
26567
+ localDataStatus,
26568
+ releaseDoctorStatus,
26569
+ adminNotices,
26570
+ marketplaceRequests
26571
+ ] = await Promise.all([
25919
26572
  input2.store.listReceipts(),
25920
26573
  input2.store.searchGraph({ limit: CONTROL_TOWER_GRAPH_NODE_LIMIT }),
25921
26574
  input2.store.listLearningEvents({ limit: CONTROL_TOWER_LEARNING_EVENT_LIMIT }),
25922
26575
  buildControlTowerLocalDataStatus(input2.profile, input2.env),
25923
- buildControlTowerReleaseDoctorStatus(input2.profile, input2.env)
26576
+ buildControlTowerReleaseDoctorStatus(input2.profile, input2.env),
26577
+ readControlTowerAdminNotices(input2.profile.profile_dir),
26578
+ readControlTowerMarketplaceRequests(input2.profile.profile_dir)
25924
26579
  ]);
25925
26580
  writeHtml(
25926
26581
  input2.response,
@@ -25931,7 +26586,9 @@ async function handleRequest(input2) {
25931
26586
  searchGraph,
25932
26587
  learningEvents,
25933
26588
  localDataStatus,
25934
- releaseDoctorStatus
26589
+ releaseDoctorStatus,
26590
+ adminNotices,
26591
+ marketplaceRequests
25935
26592
  ),
25936
26593
  {
25937
26594
  controlToken: input2.controlToken
@@ -25940,14 +26597,45 @@ async function handleRequest(input2) {
25940
26597
  return;
25941
26598
  }
25942
26599
  const activityUrl = parseGuardianUrl(input2.request.url);
25943
- if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/activity") {
25944
- const filter = parseActivityFilter(activityUrl.searchParams.get("filter") ?? "all");
25945
- const receipts = (await input2.store.listReceipts()).slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
25946
- const result2 = processGuardianControlTowerActivityRequest(receipts, filter);
25947
- writeJson(input2.response, result2.statusCode, result2.body);
25948
- return;
25949
- }
25950
- if (input2.request.method === "GET" && input2.request.url === "/v1/guardian/admin/health") {
26600
+ if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/control-tower/view-model") {
26601
+ const [
26602
+ receipts,
26603
+ searchGraph,
26604
+ learningEvents,
26605
+ localDataStatus,
26606
+ releaseDoctorStatus,
26607
+ adminNotices,
26608
+ marketplaceRequests
26609
+ ] = await Promise.all([
26610
+ input2.store.listReceipts(),
26611
+ input2.store.searchGraph({ limit: CONTROL_TOWER_GRAPH_NODE_LIMIT }),
26612
+ input2.store.listLearningEvents({ limit: CONTROL_TOWER_LEARNING_EVENT_LIMIT }),
26613
+ buildControlTowerLocalDataStatus(input2.profile, input2.env),
26614
+ buildControlTowerReleaseDoctorStatus(input2.profile, input2.env),
26615
+ readControlTowerAdminNotices(input2.profile.profile_dir),
26616
+ readControlTowerMarketplaceRequests(input2.profile.profile_dir)
26617
+ ]);
26618
+ const result2 = processGuardianControlTowerViewModelRequest(
26619
+ input2.profile,
26620
+ receipts,
26621
+ searchGraph,
26622
+ learningEvents,
26623
+ localDataStatus,
26624
+ releaseDoctorStatus,
26625
+ adminNotices,
26626
+ marketplaceRequests
26627
+ );
26628
+ writeJson(input2.response, result2.statusCode, result2.body);
26629
+ return;
26630
+ }
26631
+ if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/activity") {
26632
+ const filter = parseActivityFilter(activityUrl.searchParams.get("filter") ?? "all");
26633
+ const receipts = (await input2.store.listReceipts()).slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
26634
+ const result2 = processGuardianControlTowerActivityRequest(receipts, filter);
26635
+ writeJson(input2.response, result2.statusCode, result2.body);
26636
+ return;
26637
+ }
26638
+ if (input2.request.method === "GET" && input2.request.url === "/v1/guardian/admin/health") {
25951
26639
  const result2 = processGuardianDaemonHealthRequest({
25952
26640
  token: input2.token,
25953
26641
  suppliedToken: headerValue(input2.request, ADMIN_HEADER)
@@ -25984,6 +26672,24 @@ async function handleRequest(input2) {
25984
26672
  writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
25985
26673
  return;
25986
26674
  }
26675
+ if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/prime-pack") {
26676
+ const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
26677
+ if (!parsedBody2.ok) {
26678
+ writeJson(input2.response, parsedBody2.statusCode, parsedBody2.body);
26679
+ return;
26680
+ }
26681
+ const result2 = await processGuardianControlTowerPrimePackRequest({
26682
+ profile: input2.profile,
26683
+ token: input2.token,
26684
+ controlToken: input2.controlToken,
26685
+ suppliedToken: headerValue(input2.request, ADMIN_HEADER) ?? readCookieValue(input2.request, CONTROL_TOWER_COOKIE),
26686
+ body: parsedBody2.body,
26687
+ now: input2.now,
26688
+ store: input2.store
26689
+ });
26690
+ writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26691
+ return;
26692
+ }
25987
26693
  if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/preferences") {
25988
26694
  const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
25989
26695
  if (!parsedBody2.ok) {
@@ -26064,6 +26770,42 @@ async function handleRequest(input2) {
26064
26770
  writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26065
26771
  return;
26066
26772
  }
26773
+ if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/notice") {
26774
+ const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
26775
+ if (!parsedBody2.ok) {
26776
+ writeJson(input2.response, parsedBody2.statusCode, parsedBody2.body);
26777
+ return;
26778
+ }
26779
+ const result2 = await processGuardianControlTowerNoticeRequest({
26780
+ profile: input2.profile,
26781
+ token: input2.token,
26782
+ controlToken: input2.controlToken,
26783
+ suppliedToken: headerValue(input2.request, ADMIN_HEADER) ?? readCookieValue(input2.request, CONTROL_TOWER_COOKIE),
26784
+ body: parsedBody2.body,
26785
+ now: input2.now,
26786
+ store: input2.store
26787
+ });
26788
+ writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26789
+ return;
26790
+ }
26791
+ if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/admin/marketplace-request") {
26792
+ const parsedBody2 = await readGuardianDaemonAdminBody(input2.request);
26793
+ if (!parsedBody2.ok) {
26794
+ writeJson(input2.response, parsedBody2.statusCode, parsedBody2.body);
26795
+ return;
26796
+ }
26797
+ const result2 = await processGuardianControlTowerMarketplaceRequest({
26798
+ profile: input2.profile,
26799
+ token: input2.token,
26800
+ controlToken: input2.controlToken,
26801
+ suppliedToken: headerValue(input2.request, ADMIN_HEADER) ?? readCookieValue(input2.request, CONTROL_TOWER_COOKIE),
26802
+ body: parsedBody2.body,
26803
+ now: input2.now,
26804
+ store: input2.store
26805
+ });
26806
+ writeAdminResponse(input2.response, result2.statusCode, result2.body, parsedBody2.redirect);
26807
+ return;
26808
+ }
26067
26809
  if (input2.request.method === "POST" && input2.request.url === "/v1/guardian/live-coach-event") {
26068
26810
  const suppliedToken2 = headerValue(input2.request, ADMIN_HEADER);
26069
26811
  const parsedBody2 = await readGuardianDaemonJsonBody(input2.request);
@@ -26136,6 +26878,23 @@ function processGuardianControlTowerActivityRequest(receipts, filterValue) {
26136
26878
  }
26137
26879
  };
26138
26880
  }
26881
+ function processGuardianControlTowerViewModelRequest(profile, receipts = [], searchGraph = { nodes: [], edges: [] }, learningEvents = [], localDataStatus = createEmptyControlTowerLocalDataStatus(profile), releaseDoctorStatus = createEmptyControlTowerReleaseDoctorStatus(
26882
+ profile
26883
+ ), adminNotices = [], marketplaceRequests = []) {
26884
+ return {
26885
+ statusCode: 200,
26886
+ body: buildControlTowerViewModel(
26887
+ profile,
26888
+ receipts,
26889
+ searchGraph,
26890
+ learningEvents,
26891
+ localDataStatus,
26892
+ releaseDoctorStatus,
26893
+ adminNotices,
26894
+ marketplaceRequests
26895
+ )
26896
+ };
26897
+ }
26139
26898
  function processGuardianDaemonStopRequest(input2) {
26140
26899
  if (!isAuthorized(input2.token, input2.suppliedToken)) {
26141
26900
  return {
@@ -26201,6 +26960,105 @@ async function processGuardianControlTowerPolicyRequest(input2) {
26201
26960
  };
26202
26961
  }
26203
26962
  }
26963
+ async function processGuardianControlTowerPrimePackRequest(input2) {
26964
+ if (!isAdminAuthorized(input2)) {
26965
+ return {
26966
+ statusCode: 401,
26967
+ body: {
26968
+ ok: false,
26969
+ error: "unauthorized"
26970
+ }
26971
+ };
26972
+ }
26973
+ const action = parsePrimePackAction(input2.body.action);
26974
+ if (!action) {
26975
+ return {
26976
+ statusCode: 400,
26977
+ body: {
26978
+ ok: false,
26979
+ error: "invalid_prime_pack_action"
26980
+ }
26981
+ };
26982
+ }
26983
+ const inputs = {
26984
+ sensitive_terms: parsePrimePackEntries([
26985
+ input2.body.sensitiveTerms,
26986
+ parsePrimePackImportEntries(input2.body.importContent, "sensitive_terms")
26987
+ ]),
26988
+ approved_domains: parsePrimePackEntries(
26989
+ [
26990
+ input2.body.approvedDomains,
26991
+ parsePrimePackImportEntries(input2.body.importContent, "approved_domains")
26992
+ ],
26993
+ { lowercase: true }
26994
+ ),
26995
+ blocked_destinations: parsePrimePackEntries(
26996
+ [
26997
+ input2.body.blockedDestinations,
26998
+ parsePrimePackImportEntries(input2.body.importContent, "blocked_destinations")
26999
+ ],
27000
+ {
27001
+ lowercase: true
27002
+ }
27003
+ ),
27004
+ escalation_phrases: parsePrimePackEntries([
27005
+ input2.body.escalationPhrases,
27006
+ parsePrimePackImportEntries(input2.body.importContent, "escalation_phrases")
27007
+ ])
27008
+ };
27009
+ const preview = buildPrimePackPreview(inputs);
27010
+ if (preview.total_inputs === 0) {
27011
+ return {
27012
+ statusCode: 400,
27013
+ body: {
27014
+ ok: false,
27015
+ error: "empty_prime_pack"
27016
+ }
27017
+ };
27018
+ }
27019
+ const now = (input2.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
27020
+ const draftSeed = JSON.stringify(inputs);
27021
+ const transactionId = `txn:prime-pack:${digest4(`${draftSeed}:${now}`).slice(0, 20)}`;
27022
+ const guardianReceipt = action === "deploy" ? buildPrimePackDeployGuardianReceipt(inputs, preview, transactionId, now) : void 0;
27023
+ const draft = {
27024
+ schema_version: "contextecf/project-guardian-prime-pack-draft/v1",
27025
+ draft_id: `prime-pack:${digest4(draftSeed).slice(0, 16)}`,
27026
+ status: action === "deploy" ? "deployed_metadata_only" : "preview_ready",
27027
+ updated_at: now,
27028
+ ...action === "deploy" ? { deployed_at: now } : {},
27029
+ inputs,
27030
+ preview,
27031
+ ...guardianReceipt ? {
27032
+ local_receipt: buildPrimePackDeployReceipt(guardianReceipt)
27033
+ } : {},
27034
+ raw_prompt_content_included: false,
27035
+ token_printed: false
27036
+ };
27037
+ if (guardianReceipt && input2.store) {
27038
+ await input2.store.putReceipt(guardianReceipt);
27039
+ await input2.store.appendEvent(
27040
+ buildPrimePackDeployEvent(inputs, preview, transactionId, now, guardianReceipt)
27041
+ );
27042
+ }
27043
+ await writeControlTowerPrimePackDraft(input2.profile.profile_dir, draft);
27044
+ return {
27045
+ statusCode: 200,
27046
+ body: {
27047
+ ok: true,
27048
+ schema_version: "contextecf/project-guardian-control-tower-prime-pack/v1",
27049
+ action,
27050
+ status: draft.status,
27051
+ draft_id: draft.draft_id,
27052
+ file: CONTROL_TOWER_PRIME_PACK_FILE_NAME,
27053
+ preview,
27054
+ local_receipt: draft.local_receipt,
27055
+ receipt_id: guardianReceipt?.receipt_id,
27056
+ stored_locally: true,
27057
+ raw_prompt_content_included: false,
27058
+ token_printed: false
27059
+ }
27060
+ };
27061
+ }
26204
27062
  async function processGuardianControlTowerPreferenceRequest(input2) {
26205
27063
  if (!isAdminAuthorized(input2)) {
26206
27064
  return {
@@ -26487,6 +27345,190 @@ async function processGuardianControlTowerDataRequest(input2) {
26487
27345
  };
26488
27346
  }
26489
27347
  }
27348
+ async function processGuardianControlTowerNoticeRequest(input2) {
27349
+ if (!isAdminAuthorized(input2)) {
27350
+ return {
27351
+ statusCode: 401,
27352
+ body: {
27353
+ ok: false,
27354
+ error: "unauthorized"
27355
+ }
27356
+ };
27357
+ }
27358
+ const action = input2.body.action?.trim() || "publish";
27359
+ if (action === "clear") {
27360
+ const clearedAt = input2.now().toISOString();
27361
+ const transactionId2 = `txn:admin-notice:${digest4(`clear:${clearedAt}`).slice(0, 20)}`;
27362
+ const receipt2 = buildAdminNoticeReceipt({
27363
+ action,
27364
+ transactionId: transactionId2,
27365
+ timestamp: clearedAt
27366
+ });
27367
+ await input2.store.putReceipt(receipt2);
27368
+ await input2.store.appendEvent(
27369
+ buildAdminNoticeEvent({ action, transactionId: transactionId2, timestamp: clearedAt, receipt: receipt2 })
27370
+ );
27371
+ await writeControlTowerAdminNotices(input2.profile.profile_dir, []);
27372
+ return {
27373
+ statusCode: 200,
27374
+ body: {
27375
+ ok: true,
27376
+ schema_version: "contextecf/project-guardian-control-tower-admin-notice/v1",
27377
+ action,
27378
+ notices: [],
27379
+ receipt_id: receipt2.receipt_id,
27380
+ raw_content_included: false,
27381
+ token_printed: false
27382
+ }
27383
+ };
27384
+ }
27385
+ if (action !== "publish") {
27386
+ return {
27387
+ statusCode: 400,
27388
+ body: {
27389
+ ok: false,
27390
+ error: "invalid_notice_action"
27391
+ }
27392
+ };
27393
+ }
27394
+ const title = boundedNoticeText(input2.body.title, CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT);
27395
+ const message = boundedNoticeText(input2.body.message, CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT);
27396
+ const audience = parseControlTowerAdminNoticeAudience(input2.body.audience);
27397
+ const tone = parseControlTowerAdminNoticeTone(input2.body.tone);
27398
+ if (!title || !message || !audience || !tone) {
27399
+ return {
27400
+ statusCode: 400,
27401
+ body: {
27402
+ ok: false,
27403
+ error: "invalid_notice_request"
27404
+ }
27405
+ };
27406
+ }
27407
+ const existingNotices = await readControlTowerAdminNotices(input2.profile.profile_dir);
27408
+ const createdAt = input2.now().toISOString();
27409
+ const transactionId = `txn:admin-notice:${digest4(
27410
+ `${title}:${message}:${audience}:${tone}:${createdAt}`
27411
+ ).slice(0, 20)}`;
27412
+ const receipt = buildAdminNoticeReceipt({
27413
+ action,
27414
+ transactionId,
27415
+ timestamp: createdAt,
27416
+ title,
27417
+ message,
27418
+ audience,
27419
+ tone
27420
+ });
27421
+ const notice = {
27422
+ id: `notice:${digest4(`${title}:${message}:${createdAt}`).slice(0, 16)}`,
27423
+ title,
27424
+ message,
27425
+ audience,
27426
+ tone,
27427
+ created_at: createdAt,
27428
+ receipt_id: receipt.receipt_id,
27429
+ receipt_hash: receipt.receipt_hash,
27430
+ raw_content_included: false
27431
+ };
27432
+ const notices = [notice, ...existingNotices].slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT);
27433
+ await input2.store.putReceipt(receipt);
27434
+ await input2.store.appendEvent(
27435
+ buildAdminNoticeEvent({
27436
+ action,
27437
+ transactionId,
27438
+ timestamp: createdAt,
27439
+ receipt,
27440
+ title,
27441
+ message,
27442
+ audience,
27443
+ tone
27444
+ })
27445
+ );
27446
+ await writeControlTowerAdminNotices(input2.profile.profile_dir, notices);
27447
+ return {
27448
+ statusCode: 200,
27449
+ body: {
27450
+ ok: true,
27451
+ schema_version: "contextecf/project-guardian-control-tower-admin-notice/v1",
27452
+ action,
27453
+ notice,
27454
+ notices,
27455
+ receipt_id: receipt.receipt_id,
27456
+ raw_content_included: false,
27457
+ token_printed: false
27458
+ }
27459
+ };
27460
+ }
27461
+ async function processGuardianControlTowerMarketplaceRequest(input2) {
27462
+ if (!isAdminAuthorized(input2)) {
27463
+ return {
27464
+ statusCode: 401,
27465
+ body: {
27466
+ ok: false,
27467
+ error: "unauthorized"
27468
+ }
27469
+ };
27470
+ }
27471
+ const action = parseMarketplaceRequestAction(input2.body.action);
27472
+ const targetId = input2.body.packId?.trim();
27473
+ const manifest = targetId ? findControlTowerMarketplaceManifest(targetId) : void 0;
27474
+ if (!action || !manifest) {
27475
+ return {
27476
+ statusCode: 400,
27477
+ body: {
27478
+ ok: false,
27479
+ error: "invalid_marketplace_request"
27480
+ }
27481
+ };
27482
+ }
27483
+ const requestedAt = input2.now().toISOString();
27484
+ const transactionId = `txn:marketplace-request:${digest4(
27485
+ `${manifest.marketplace_manifest_id}:${action}:${requestedAt}`
27486
+ ).slice(0, 20)}`;
27487
+ const receipt = buildMarketplaceRequestReceipt(manifest, action, transactionId, requestedAt);
27488
+ const request = {
27489
+ schema_version: "contextecf/project-guardian-marketplace-request/v1",
27490
+ request_id: `request:${transactionId}`,
27491
+ pack_id: manifest.pack_id,
27492
+ marketplace_manifest_id: manifest.marketplace_manifest_id,
27493
+ pack_version: manifest.pack_version,
27494
+ pack_name: manifest.display_name,
27495
+ publisher: manifest.publisher_display_name,
27496
+ action,
27497
+ status: "requested_metadata_only",
27498
+ requested_at: requestedAt,
27499
+ receipt_id: receipt.receipt_id,
27500
+ receipt_hash: receipt.receipt_hash,
27501
+ raw_content_included: false,
27502
+ token_printed: false
27503
+ };
27504
+ const existingRequests = await readControlTowerMarketplaceRequests(input2.profile.profile_dir);
27505
+ const requests = [
27506
+ request,
27507
+ ...existingRequests.filter(
27508
+ (existing) => existing.marketplace_manifest_id !== manifest.marketplace_manifest_id
27509
+ )
27510
+ ].slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT);
27511
+ await input2.store.putReceipt(receipt);
27512
+ await input2.store.appendEvent(
27513
+ buildMarketplaceRequestEvent(manifest, action, transactionId, requestedAt, receipt)
27514
+ );
27515
+ await writeControlTowerMarketplaceRequests(input2.profile.profile_dir, requests);
27516
+ return {
27517
+ statusCode: 200,
27518
+ body: {
27519
+ ok: true,
27520
+ schema_version: "contextecf/project-guardian-control-tower-marketplace-request/v1",
27521
+ action,
27522
+ status: request.status,
27523
+ request,
27524
+ request_file: CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME,
27525
+ receipt_id: receipt.receipt_id,
27526
+ stored_locally: true,
27527
+ raw_content_included: false,
27528
+ token_printed: false
27529
+ }
27530
+ };
27531
+ }
26490
27532
  function parseAppPermissionAction(value) {
26491
27533
  return value === "revoke" || value === "allow" ? value : void 0;
26492
27534
  }
@@ -26502,6 +27544,25 @@ function parseGuardianSourceType2(value) {
26502
27544
  }
26503
27545
  return GUARDIAN_SOURCE_TYPES.includes(value) ? value : void 0;
26504
27546
  }
27547
+ function parseControlTowerAdminNoticeAudience(value) {
27548
+ return value === "all_users" || value === "admins" || value === "developers" ? value : void 0;
27549
+ }
27550
+ function parseControlTowerAdminNoticeTone(value) {
27551
+ return value === "info" || value === "warning" || value === "success" ? value : void 0;
27552
+ }
27553
+ function boundedNoticeText(value, limit) {
27554
+ if (typeof value !== "string") {
27555
+ return void 0;
27556
+ }
27557
+ const trimmed = value.replace(/\s+/gu, " ").trim();
27558
+ if (!trimmed) {
27559
+ return void 0;
27560
+ }
27561
+ return trimmed.slice(0, limit);
27562
+ }
27563
+ function isRecord(value) {
27564
+ return typeof value === "object" && value !== null;
27565
+ }
26505
27566
  async function processGuardianBrowserBridgeRequest(input2) {
26506
27567
  if (!isAuthorized(input2.token, input2.suppliedToken)) {
26507
27568
  return {
@@ -26642,6 +27703,11 @@ async function processBrowserPrompt(input2) {
26642
27703
  visible_message: decision.visible_message,
26643
27704
  required_confirmation: decision.required_confirmation,
26644
27705
  coach_card: decision.coach_card,
27706
+ risk_report: {
27707
+ risk_tier: decision.risk_report.risk_tier,
27708
+ detected_risks: decision.risk_report.detected_risks,
27709
+ matched_policy_refs: decision.risk_report.matched_policy_refs
27710
+ },
26645
27711
  receipt_id: decision.receipt.receipt_id
26646
27712
  };
26647
27713
  }
@@ -26832,6 +27898,114 @@ function buildLiveCoachEvent(request, transactionId, timestamp, receipt) {
26832
27898
  redaction_state: "metadata_only"
26833
27899
  };
26834
27900
  }
27901
+ function parseMarketplaceRequestAction(value) {
27902
+ return value === "request_review" ? value : void 0;
27903
+ }
27904
+ function findControlTowerMarketplaceManifest(targetId) {
27905
+ return GUARDIAN_POLICY_PACK_MARKETPLACE_CATALOG.find(
27906
+ (manifest) => manifest.pack_id === targetId || manifest.marketplace_manifest_id === targetId
27907
+ );
27908
+ }
27909
+ function buildMarketplaceRequestReceipt(manifest, action, transactionId, timestamp) {
27910
+ const core = {
27911
+ transaction_id: transactionId,
27912
+ pack_id: manifest.pack_id,
27913
+ marketplace_manifest_id: manifest.marketplace_manifest_id,
27914
+ pack_version: manifest.pack_version,
27915
+ action,
27916
+ created_at: timestamp,
27917
+ raw_content_included: false
27918
+ };
27919
+ return {
27920
+ schema_version: PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION,
27921
+ receipt_id: `receipt:${transactionId}:marketplace-request`,
27922
+ transaction_id: transactionId,
27923
+ receipt_type: "policy",
27924
+ disposition: "allow",
27925
+ policy_basis: [
27926
+ "marketplace-request-metadata-only",
27927
+ `pack:${manifest.pack_id}`,
27928
+ `manifest:${manifest.marketplace_manifest_id}`
27929
+ ],
27930
+ receipt_hash: `sha256:${digest4(JSON.stringify(core))}`,
27931
+ raw_content_included: false,
27932
+ created_at: timestamp,
27933
+ summary: `Guardian recorded a marketplace review request for ${manifest.display_name}.`
27934
+ };
27935
+ }
27936
+ function buildMarketplaceRequestEvent(manifest, action, transactionId, timestamp, receipt) {
27937
+ const payload = {
27938
+ pack_id: manifest.pack_id,
27939
+ marketplace_manifest_id: manifest.marketplace_manifest_id,
27940
+ pack_version: manifest.pack_version,
27941
+ action,
27942
+ status: "requested_metadata_only",
27943
+ raw_content_included: false
27944
+ };
27945
+ return {
27946
+ event_id: `event:${transactionId}:marketplace-request`,
27947
+ transaction_id: transactionId,
27948
+ event_type: "policy_decided",
27949
+ occurred_at: timestamp,
27950
+ actor: "guardian-daemon",
27951
+ summary: `Marketplace pack review requested for ${manifest.display_name}.`,
27952
+ payload_hash: `sha256:${digest4(JSON.stringify(payload))}`,
27953
+ receipt_id: receipt.receipt_id,
27954
+ graph_refs: [`policy-pack:${manifest.pack_id}`, `receipt:${receipt.receipt_id}`],
27955
+ redaction_state: "metadata_only"
27956
+ };
27957
+ }
27958
+ function buildAdminNoticeReceipt(input2) {
27959
+ const core = {
27960
+ transaction_id: input2.transactionId,
27961
+ action: input2.action,
27962
+ title_hash: input2.title ? digest4(input2.title) : void 0,
27963
+ message_hash: input2.message ? digest4(input2.message) : void 0,
27964
+ audience: input2.audience ?? "all_users",
27965
+ tone: input2.tone ?? "info",
27966
+ created_at: input2.timestamp,
27967
+ raw_content_included: false
27968
+ };
27969
+ const audienceLabel = adminNoticeAudienceLabel(input2.audience ?? "all_users");
27970
+ return {
27971
+ schema_version: PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION,
27972
+ receipt_id: `receipt:${input2.transactionId}:admin-notice`,
27973
+ transaction_id: input2.transactionId,
27974
+ receipt_type: "policy",
27975
+ disposition: "allow",
27976
+ policy_basis: [
27977
+ "admin-notice-metadata-only",
27978
+ `action:${input2.action}`,
27979
+ `audience:${input2.audience ?? "all_users"}`
27980
+ ],
27981
+ receipt_hash: `sha256:${digest4(JSON.stringify(core))}`,
27982
+ raw_content_included: false,
27983
+ created_at: input2.timestamp,
27984
+ summary: input2.action === "clear" ? "Guardian cleared local admin notices." : `Guardian published a local admin notice for ${audienceLabel}.`
27985
+ };
27986
+ }
27987
+ function buildAdminNoticeEvent(input2) {
27988
+ const payload = {
27989
+ action: input2.action,
27990
+ title_hash: input2.title ? digest4(input2.title) : void 0,
27991
+ message_hash: input2.message ? digest4(input2.message) : void 0,
27992
+ audience: input2.audience ?? "all_users",
27993
+ tone: input2.tone ?? "info",
27994
+ raw_content_included: false
27995
+ };
27996
+ return {
27997
+ event_id: `event:${input2.transactionId}:admin-notice`,
27998
+ transaction_id: input2.transactionId,
27999
+ event_type: "policy_decided",
28000
+ occurred_at: input2.timestamp,
28001
+ actor: "guardian-daemon",
28002
+ summary: input2.action === "clear" ? "Admin notice bar cleared." : `Admin notice bar updated for ${adminNoticeAudienceLabel(input2.audience ?? "all_users")}.`,
28003
+ payload_hash: `sha256:${digest4(JSON.stringify(payload))}`,
28004
+ receipt_id: input2.receipt.receipt_id,
28005
+ graph_refs: [`admin-notice:${input2.action}`, `receipt:${input2.receipt.receipt_id}`],
28006
+ redaction_state: "metadata_only"
28007
+ };
28008
+ }
26835
28009
  function liveCoachCategoryLabel(category) {
26836
28010
  if (category === "ssn") return "possible SSN sharing";
26837
28011
  if (category === "password") return "possible credential sharing";
@@ -26947,6 +28121,218 @@ function isAdminAuthorized(input2) {
26947
28121
  function parsePolicyPackAction(value) {
26948
28122
  return value === "enable" || value === "disable" ? value : void 0;
26949
28123
  }
28124
+ function parsePrimePackAction(value) {
28125
+ return value === "preview" || value === "deploy" ? value : void 0;
28126
+ }
28127
+ function parsePrimePackEntries(value, options = {}) {
28128
+ const seen = /* @__PURE__ */ new Set();
28129
+ const entries = [];
28130
+ for (const rawEntry of flattenPrimePackEntryValues(value)) {
28131
+ const normalized = rawEntry.replace(/\s+/gu, " ").trim();
28132
+ if (!normalized) {
28133
+ continue;
28134
+ }
28135
+ const bounded = normalized.slice(0, CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT);
28136
+ const entry = options.lowercase ? bounded.toLowerCase() : bounded;
28137
+ const key = entry.toLowerCase();
28138
+ if (seen.has(key)) {
28139
+ continue;
28140
+ }
28141
+ seen.add(key);
28142
+ entries.push(entry);
28143
+ if (entries.length >= CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT) {
28144
+ break;
28145
+ }
28146
+ }
28147
+ return entries;
28148
+ }
28149
+ function flattenPrimePackEntryValues(value) {
28150
+ if (Array.isArray(value)) {
28151
+ return value.flatMap((entry) => flattenPrimePackEntryValues(entry));
28152
+ }
28153
+ if (typeof value !== "string") {
28154
+ return [];
28155
+ }
28156
+ return value.split(/[\n,;]+/u);
28157
+ }
28158
+ function parsePrimePackImportEntries(value, key) {
28159
+ if (typeof value !== "string" || !value.trim()) {
28160
+ return [];
28161
+ }
28162
+ const bounded = value.slice(0, CONTROL_TOWER_PRIME_PACK_IMPORT_LIMIT);
28163
+ const parsedJson = parsePrimePackImportJson(bounded, key);
28164
+ if (parsedJson.length > 0) {
28165
+ return parsedJson;
28166
+ }
28167
+ const rows = parsePrimePackImportRows(bounded);
28168
+ return rows.filter((row) => normalizePrimePackImportKey(row[0]) === key).flatMap((row) => row.slice(1));
28169
+ }
28170
+ function parsePrimePackImportJson(value, key) {
28171
+ const trimmed = value.trim();
28172
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
28173
+ return [];
28174
+ }
28175
+ try {
28176
+ const parsed = JSON.parse(trimmed);
28177
+ return extractPrimePackImportJsonValues(parsed, key);
28178
+ } catch {
28179
+ return [];
28180
+ }
28181
+ }
28182
+ function extractPrimePackImportJsonValues(value, key) {
28183
+ if (Array.isArray(value)) {
28184
+ return value.flatMap((entry) => extractPrimePackImportJsonValues(entry, key));
28185
+ }
28186
+ if (!isRecord(value)) {
28187
+ return [];
28188
+ }
28189
+ const direct = value[key];
28190
+ if (Array.isArray(direct)) {
28191
+ return direct.filter((entry) => typeof entry === "string");
28192
+ }
28193
+ if (typeof direct === "string") {
28194
+ return [direct];
28195
+ }
28196
+ const type = normalizePrimePackImportKey(value.type);
28197
+ const term = value.value ?? value.term ?? value.pattern ?? value.phrase ?? value.domain ?? value.destination;
28198
+ if (type === key && typeof term === "string") {
28199
+ return [term];
28200
+ }
28201
+ return [];
28202
+ }
28203
+ function parsePrimePackImportRows(value) {
28204
+ return value.split(/\r?\n/u).map((line) => splitPrimePackImportRow(line).map((cell) => cell.trim())).filter((row) => row.some(Boolean));
28205
+ }
28206
+ function splitPrimePackImportRow(line) {
28207
+ const delimiter = line.includes(" ") ? " " : ",";
28208
+ return line.split(delimiter);
28209
+ }
28210
+ function normalizePrimePackImportKey(value) {
28211
+ if (typeof value !== "string") {
28212
+ return void 0;
28213
+ }
28214
+ const normalized = value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
28215
+ switch (normalized) {
28216
+ case "sensitive":
28217
+ case "sensitive_term":
28218
+ case "sensitive_terms":
28219
+ case "term":
28220
+ case "terms":
28221
+ return "sensitive_terms";
28222
+ case "approved_domain":
28223
+ case "approved_domains":
28224
+ case "allow_domain":
28225
+ case "allowed_domain":
28226
+ case "domain":
28227
+ return "approved_domains";
28228
+ case "blocked_destination":
28229
+ case "blocked_destinations":
28230
+ case "blocked_domain":
28231
+ case "deny_destination":
28232
+ case "destination":
28233
+ return "blocked_destinations";
28234
+ case "escalation":
28235
+ case "escalation_phrase":
28236
+ case "escalation_phrases":
28237
+ case "confirmation_phrase":
28238
+ case "phrase":
28239
+ return "escalation_phrases";
28240
+ default:
28241
+ return void 0;
28242
+ }
28243
+ }
28244
+ function buildPrimePackPreview(inputs) {
28245
+ const totalInputs = inputs.sensitive_terms.length + inputs.approved_domains.length + inputs.blocked_destinations.length + inputs.escalation_phrases.length;
28246
+ const examples = [
28247
+ inputs.sensitive_terms.length > 0 ? `${inputs.sensitive_terms.length} sensitive term(s) will be flagged before prompt send.` : "",
28248
+ inputs.blocked_destinations.length > 0 ? `${inputs.blocked_destinations.length} blocked destination rule(s) will stop risky sharing.` : "",
28249
+ inputs.escalation_phrases.length > 0 ? `${inputs.escalation_phrases.length} escalation phrase(s) will require confirmation.` : "",
28250
+ inputs.approved_domains.length > 0 ? `${inputs.approved_domains.length} approved domain(s) will lower false positives.` : ""
28251
+ ].filter(Boolean);
28252
+ return {
28253
+ total_inputs: totalInputs,
28254
+ flag_rules: inputs.sensitive_terms.length,
28255
+ block_rules: inputs.blocked_destinations.length,
28256
+ confirm_rules: inputs.escalation_phrases.length,
28257
+ examples
28258
+ };
28259
+ }
28260
+ function buildPrimePackDeployReceipt(receipt) {
28261
+ return {
28262
+ receipt_id: receipt.receipt_id,
28263
+ action: "deploy",
28264
+ created_at: receipt.created_at,
28265
+ receipt_hash: receipt.receipt_hash,
28266
+ raw_prompt_content_included: false,
28267
+ token_printed: false
28268
+ };
28269
+ }
28270
+ function buildPrimePackDeployGuardianReceipt(inputs, preview, transactionId, timestamp) {
28271
+ const core = {
28272
+ transaction_id: transactionId,
28273
+ action: "deploy",
28274
+ input_counts: buildPrimePackInputCounts(inputs),
28275
+ preview,
28276
+ input_hashes: buildPrimePackInputHashes(inputs),
28277
+ created_at: timestamp,
28278
+ raw_content_included: false
28279
+ };
28280
+ return {
28281
+ schema_version: PERSONAL_FABRIC_CONTRACTS_SCHEMA_VERSION,
28282
+ receipt_id: `receipt:${transactionId}:prime-pack-deploy`,
28283
+ transaction_id: transactionId,
28284
+ receipt_type: "policy",
28285
+ disposition: "allow",
28286
+ policy_basis: [
28287
+ "prime-pack-deploy-metadata-only",
28288
+ `inputs:${preview.total_inputs}`,
28289
+ `flag_rules:${preview.flag_rules}`,
28290
+ `block_rules:${preview.block_rules}`,
28291
+ `confirm_rules:${preview.confirm_rules}`
28292
+ ],
28293
+ receipt_hash: `sha256:${digest4(JSON.stringify(core))}`,
28294
+ raw_content_included: false,
28295
+ created_at: timestamp,
28296
+ summary: `Guardian deployed a local Prime Pack with ${preview.total_inputs} metadata rule input(s).`
28297
+ };
28298
+ }
28299
+ function buildPrimePackDeployEvent(inputs, preview, transactionId, timestamp, receipt) {
28300
+ const payload = {
28301
+ action: "deploy",
28302
+ input_counts: buildPrimePackInputCounts(inputs),
28303
+ input_hashes: buildPrimePackInputHashes(inputs),
28304
+ preview,
28305
+ raw_content_included: false
28306
+ };
28307
+ return {
28308
+ event_id: `event:${transactionId}:prime-pack-deploy`,
28309
+ transaction_id: transactionId,
28310
+ event_type: "policy_decided",
28311
+ occurred_at: timestamp,
28312
+ actor: "guardian-daemon",
28313
+ summary: "Prime Pack deployment recorded with metadata-only hashes.",
28314
+ payload_hash: `sha256:${digest4(JSON.stringify(payload))}`,
28315
+ receipt_id: receipt.receipt_id,
28316
+ graph_refs: ["policy-pack:prime-pack", `receipt:${receipt.receipt_id}`],
28317
+ redaction_state: "metadata_only"
28318
+ };
28319
+ }
28320
+ function buildPrimePackInputCounts(inputs) {
28321
+ return {
28322
+ sensitive_terms: inputs.sensitive_terms.length,
28323
+ approved_domains: inputs.approved_domains.length,
28324
+ blocked_destinations: inputs.blocked_destinations.length,
28325
+ escalation_phrases: inputs.escalation_phrases.length
28326
+ };
28327
+ }
28328
+ function buildPrimePackInputHashes(inputs) {
28329
+ return {
28330
+ sensitive_terms: inputs.sensitive_terms.map((entry) => `sha256:${digest4(entry)}`),
28331
+ approved_domains: inputs.approved_domains.map((entry) => `sha256:${digest4(entry)}`),
28332
+ blocked_destinations: inputs.blocked_destinations.map((entry) => `sha256:${digest4(entry)}`),
28333
+ escalation_phrases: inputs.escalation_phrases.map((entry) => `sha256:${digest4(entry)}`)
28334
+ };
28335
+ }
26950
28336
  function parsePromptCoachMode2(value) {
26951
28337
  return PROMPT_COACH_MODES2.includes(value) ? value : void 0;
26952
28338
  }
@@ -27026,7 +28412,7 @@ function writeHtml(response, statusCode, body, options = {}) {
27026
28412
  response.writeHead(statusCode, {
27027
28413
  "Content-Type": "text/html; charset=utf-8",
27028
28414
  "Referrer-Policy": "no-referrer",
27029
- "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'",
28415
+ "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'",
27030
28416
  "X-Content-Type-Options": "nosniff"
27031
28417
  });
27032
28418
  response.end(body);
@@ -27053,19 +28439,168 @@ function closeGuardianDaemonServer(server) {
27053
28439
  } catch {
27054
28440
  }
27055
28441
  }
27056
- function parseGuardianUrl(url2) {
27057
- if (!url2) {
27058
- return void 0;
27059
- }
27060
- try {
27061
- return new URL(url2, "http://127.0.0.1");
27062
- } catch {
27063
- return void 0;
27064
- }
28442
+ function parseGuardianUrl(url2) {
28443
+ if (!url2) {
28444
+ return void 0;
28445
+ }
28446
+ try {
28447
+ return new URL(url2, "http://127.0.0.1");
28448
+ } catch {
28449
+ return void 0;
28450
+ }
28451
+ }
28452
+ function buildControlTowerViewModel(profile, receipts, searchGraph, learningEvents, localDataStatus, releaseDoctorStatus, adminNotices = [], marketplaceRequests = []) {
28453
+ const recentReceipts = receipts.slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
28454
+ const blocked = recentReceipts.filter((receipt) => receipt.disposition === "block").length;
28455
+ const flagged = recentReceipts.filter(
28456
+ (receipt) => receipt.disposition === "warn" || receipt.disposition === "confirm"
28457
+ ).length;
28458
+ const allowed = recentReceipts.filter(
28459
+ (receipt) => receipt.disposition === "allow" || receipt.disposition === "rewrite"
28460
+ ).length;
28461
+ 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";
28462
+ const protectedState = releaseDoctorStatus.doctor.checkCounts.fail === 0 && privacyDefaults === "on";
28463
+ return {
28464
+ ok: true,
28465
+ schema_version: "contextecf/project-guardian-control-tower-view-model/v1",
28466
+ surfaces: [
28467
+ {
28468
+ id: "personal",
28469
+ label: "Personal Guardian",
28470
+ audience: "Individual users",
28471
+ purpose: "Protection status, prompt coaching, privacy, receipts, and browser connection."
28472
+ },
28473
+ {
28474
+ id: "admin",
28475
+ label: "Guardian Admin",
28476
+ audience: "Organizations",
28477
+ purpose: "Groups, policy packs, marketplace approvals, Prime Pack, scope, and audit."
28478
+ },
28479
+ {
28480
+ id: "power_user",
28481
+ label: "Power User",
28482
+ audience: "Developers and technical operators",
28483
+ purpose: "MCP, local data, doctor checks, receipts, and release evidence."
28484
+ },
28485
+ {
28486
+ id: "marketplace_contributor",
28487
+ label: "Marketplace Contributor",
28488
+ audience: "Policy pack creators",
28489
+ purpose: "Submission metadata, examples, license terms, and review state."
28490
+ }
28491
+ ],
28492
+ personal: {
28493
+ protectionLabel: protectedState ? "You're Protected" : "Needs Review",
28494
+ protectionDetail: protectedState ? "Guardian is running locally with metadata-only privacy defaults." : "Guardian is running, but one or more install checks need attention.",
28495
+ privacyDefaults,
28496
+ activePolicyPacks: [...profile.policy_packs.active],
28497
+ activityCounts: { blocked, flagged, allowed }
28498
+ },
28499
+ admin: {
28500
+ organizationLabel: "Example organization deployment",
28501
+ notices: adminNotices.slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT),
28502
+ marketplaceRequests: marketplaceRequests.slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT),
28503
+ managedGroups: buildControlTowerDeploymentGroups(profile),
28504
+ primePack: {
28505
+ status: "draft_ready",
28506
+ supportedInputs: ["typed terms", "text upload", "CSV upload", "JSON upload"],
28507
+ deployable: true,
28508
+ receiptRequired: true
28509
+ }
28510
+ },
28511
+ marketplace: buildControlTowerMarketplaceRows(profile, marketplaceRequests),
28512
+ releaseAutomation: {
28513
+ repository: GUARDIAN_RELEASE_REPOSITORY,
28514
+ workflow: GUARDIAN_RELEASE_DISPATCH_WORKFLOW,
28515
+ npmPackage: GUARDIAN_NPM_PACKAGE_NAME,
28516
+ registry: GUARDIAN_NPM_REGISTRY,
28517
+ chromeExtensionId: GUARDIAN_HALO_CHROME_EXTENSION_ID,
28518
+ dryRunCommand: CONTROL_TOWER_DRY_RUN_COMMAND_TEMPLATE,
28519
+ publishCommand: CONTROL_TOWER_RELEASE_COMMAND_TEMPLATE,
28520
+ humanIntervention: [
28521
+ "Chrome Web Store upload/review remains a store-console step until Google approval APIs are wired.",
28522
+ "Native installer signing still requires platform signing credentials.",
28523
+ `Latest doctor status: ${releaseDoctorStatus.doctor.overall}.`,
28524
+ `Search Graph has ${searchGraph.nodes.length} node(s); Learning Graph has ${learningEvents.length} event(s).`,
28525
+ `Local data encryption: ${localDataStatus.encryptionStatus}.`
28526
+ ]
28527
+ },
28528
+ raw_content_included: false,
28529
+ token_printed: false
28530
+ };
28531
+ }
28532
+ function buildControlTowerDeploymentGroups(profile) {
28533
+ return [
28534
+ {
28535
+ id: "group:individual-default",
28536
+ label: "Personal default",
28537
+ users: 1,
28538
+ posture: profile.posture_profile.selected,
28539
+ packs: [...profile.policy_packs.active]
28540
+ },
28541
+ {
28542
+ id: "group:employees",
28543
+ label: "Employees",
28544
+ users: 24,
28545
+ posture: "balanced",
28546
+ packs: ["privacy-first", "executive"]
28547
+ },
28548
+ {
28549
+ id: "group:technical-users",
28550
+ label: "Technical users",
28551
+ users: 8,
28552
+ posture: "guided",
28553
+ packs: ["privacy-first", "developer"]
28554
+ },
28555
+ {
28556
+ id: "group:high-risk-functions",
28557
+ label: "High-risk functions",
28558
+ users: 5,
28559
+ posture: "high_assurance",
28560
+ packs: ["privacy-first", "finance"]
28561
+ }
28562
+ ];
28563
+ }
28564
+ function buildControlTowerMarketplaceRows(profile, marketplaceRequests = []) {
28565
+ const activePacks = new Set(profile.policy_packs.active);
28566
+ const requestsByManifest = new Map(
28567
+ marketplaceRequests.map((request) => [request.marketplace_manifest_id, request])
28568
+ );
28569
+ return GUARDIAN_POLICY_PACK_MARKETPLACE_CATALOG.map((manifest) => {
28570
+ const request = requestsByManifest.get(manifest.marketplace_manifest_id);
28571
+ const connected = activePacks.has(manifest.pack_id);
28572
+ return {
28573
+ packId: manifest.pack_id,
28574
+ name: manifest.display_name,
28575
+ version: manifest.pack_version,
28576
+ audience: manifest.audiences.join(", "),
28577
+ category: manifest.policy_domains.join(", "),
28578
+ publisher: manifest.publisher_display_name,
28579
+ price: manifest.price_model,
28580
+ licenseRef: manifest.license_ref,
28581
+ supportRef: manifest.support_ref ?? "Not provided",
28582
+ state: connected ? "connected" : request?.status ?? manifest.listing_status,
28583
+ permissions: [
28584
+ manifest.provider_rules_summary,
28585
+ manifest.source_rules_summary,
28586
+ manifest.app_permission_summary
28587
+ ],
28588
+ protectedDataTypes: manifest.protected_data_types,
28589
+ examples: [
28590
+ manifest.short_description,
28591
+ manifest.confirmation_rules_summary,
28592
+ manifest.data_handling_summary
28593
+ ],
28594
+ risk: manifest.limitations.length > 0 ? manifest.limitations[0] : "Standard false-positive review recommended before wide deployment.",
28595
+ requestState: connected ? "connected" : request?.status ?? "not_requested",
28596
+ requestReceiptId: request?.receipt_id,
28597
+ requestedAt: request?.requested_at
28598
+ };
28599
+ });
27065
28600
  }
27066
28601
  function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { nodes: [], edges: [] }, learningEvents = [], localDataStatus = createEmptyControlTowerLocalDataStatus(profile), releaseDoctorStatus = createEmptyControlTowerReleaseDoctorStatus(
27067
28602
  profile
27068
- )) {
28603
+ ), adminNotices = [], marketplaceRequests = []) {
27069
28604
  const policyPacks = profile.policy_packs.available.length;
27070
28605
  const activePolicyPacks = profile.policy_packs.active.join(", ") || "none";
27071
28606
  const recentReceipts = receipts.slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
@@ -27243,9 +28778,28 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27243
28778
  <button type="submit" class="provider-pill" aria-label="Revoke ${escapeHtml(providerDisplayName(row.id))}"><span></span>${escapeHtml(providerDisplayName(row.id))}</button>
27244
28779
  </form>`
27245
28780
  ).join("\n");
28781
+ const viewModel = buildControlTowerViewModel(
28782
+ profile,
28783
+ recentReceipts,
28784
+ searchGraph,
28785
+ learningEvents,
28786
+ localDataStatus,
28787
+ releaseDoctorStatus,
28788
+ adminNotices,
28789
+ marketplaceRequests
28790
+ );
28791
+ const roleCardsHtml = renderControlTowerRoleCards(viewModel);
28792
+ const adminNoticeBarHtml = renderAdminNoticeBar(viewModel.admin.notices);
28793
+ const adminPanelsHtml = renderGuardianAdminSurface(
28794
+ viewModel,
28795
+ profile,
28796
+ recentReceipts,
28797
+ appPermissionRows
28798
+ );
28799
+ const localGuardianStatus = renderLocalGuardianStatus(profile.runtime.daemon_status);
27246
28800
  const rows = [
27247
28801
  ["Profile", profile.profile_dir],
27248
- ["Daemon", profile.runtime.daemon_status],
28802
+ ["Local Guardian", localGuardianStatus],
27249
28803
  [
27250
28804
  "Posture Profile",
27251
28805
  `${profile.posture_profile.selected}; managed baseline ${profile.posture_profile.managed_baseline_id ?? "none"}`
@@ -27413,6 +28967,161 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27413
28967
  background: rgba(15, 23, 42, 0.78);
27414
28968
  font-size: 16px;
27415
28969
  }
28970
+ .surface-tabs, .role-grid, .admin-grid, .marketplace-grid {
28971
+ display: grid;
28972
+ gap: 12px;
28973
+ }
28974
+ .surface-tabs {
28975
+ grid-template-columns: repeat(2, minmax(0, 1fr));
28976
+ margin: 28px 0 16px;
28977
+ }
28978
+ .surface-tab {
28979
+ display: grid;
28980
+ gap: 6px;
28981
+ width: 100%;
28982
+ min-height: 78px;
28983
+ padding: 15px 16px;
28984
+ border-color: rgba(148, 163, 184, 0.2);
28985
+ background: rgba(13, 24, 40, 0.84);
28986
+ color: #eff6ff;
28987
+ text-align: left;
28988
+ }
28989
+ .surface-tab strong, .role-card strong, .admin-card strong { font-size: 17px; }
28990
+ .surface-tab span, .role-card span, .admin-card span, .marketplace-card span {
28991
+ color: #98a7bc;
28992
+ font-size: 13px;
28993
+ line-height: 1.35;
28994
+ }
28995
+ .surface-tab.is-active, .surface-tab[aria-pressed="true"] {
28996
+ border-color: rgba(34, 197, 94, 0.5);
28997
+ background: rgba(6, 45, 37, 0.78);
28998
+ box-shadow: inset 0 0 0 1px rgba(34, 197, 94, 0.12);
28999
+ }
29000
+ .surface-panel[hidden] { display: none; }
29001
+ .role-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
29002
+ .role-card, .admin-card, .marketplace-card, .prime-pack-builder, .release-command-card {
29003
+ border: 1px solid rgba(148, 163, 184, 0.18);
29004
+ border-radius: 8px;
29005
+ background: rgba(11, 24, 40, 0.82);
29006
+ padding: 18px;
29007
+ }
29008
+ .admin-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
29009
+ .marketplace-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
29010
+ .marketplace-card h3, .admin-card h3, .prime-pack-builder h3, .release-command-card h3 {
29011
+ margin: 0 0 8px;
29012
+ color: #f6f8ff;
29013
+ font-size: 18px;
29014
+ }
29015
+ .marketplace-card p, .admin-card p, .prime-pack-builder p, .release-command-card p {
29016
+ margin-bottom: 12px;
29017
+ }
29018
+ .marketplace-detail-drawer {
29019
+ margin-top: 14px;
29020
+ border-top: 1px solid rgba(148, 163, 184, 0.16);
29021
+ padding-top: 12px;
29022
+ }
29023
+ .marketplace-detail-drawer summary {
29024
+ cursor: pointer;
29025
+ color: #dce7f7;
29026
+ font-size: 14px;
29027
+ font-weight: 800;
29028
+ }
29029
+ .marketplace-detail-drawer dl {
29030
+ display: grid;
29031
+ gap: 8px;
29032
+ margin: 12px 0 0;
29033
+ }
29034
+ .marketplace-detail-drawer dt {
29035
+ color: #f6f8ff;
29036
+ font-size: 13px;
29037
+ font-weight: 800;
29038
+ }
29039
+ .marketplace-detail-drawer dd {
29040
+ margin: 0;
29041
+ color: #98a7bc;
29042
+ font-size: 13px;
29043
+ line-height: 1.45;
29044
+ }
29045
+ .marketplace-request-form {
29046
+ display: grid;
29047
+ gap: 8px;
29048
+ margin-top: 14px;
29049
+ }
29050
+ .marketplace-request-form span,
29051
+ .marketplace-request-state {
29052
+ margin: 14px 0 0;
29053
+ color: #9fb0c6;
29054
+ font-size: 13px;
29055
+ line-height: 1.45;
29056
+ }
29057
+ .marketplace-request-list {
29058
+ display: grid;
29059
+ gap: 10px;
29060
+ margin-top: 16px;
29061
+ }
29062
+ .marketplace-request-card {
29063
+ display: grid;
29064
+ gap: 6px;
29065
+ padding: 12px 14px;
29066
+ border: 1px solid rgba(34, 197, 94, 0.28);
29067
+ border-radius: 8px;
29068
+ background: rgba(6, 45, 37, 0.52);
29069
+ }
29070
+ .marketplace-request-card span {
29071
+ color: #9fb0c6;
29072
+ font-size: 13px;
29073
+ }
29074
+ .metadata-list {
29075
+ display: grid;
29076
+ gap: 8px;
29077
+ margin: 14px 0 0;
29078
+ padding: 0;
29079
+ list-style: none;
29080
+ }
29081
+ .metadata-list li {
29082
+ display: flex;
29083
+ gap: 8px;
29084
+ justify-content: space-between;
29085
+ border-top: 1px solid rgba(148, 163, 184, 0.12);
29086
+ padding-top: 8px;
29087
+ color: #c8d2e3;
29088
+ font-size: 13px;
29089
+ }
29090
+ .metadata-list b { color: #eff6ff; }
29091
+ .builder-grid {
29092
+ display: grid;
29093
+ grid-template-columns: repeat(2, minmax(0, 1fr));
29094
+ gap: 12px;
29095
+ margin-top: 12px;
29096
+ }
29097
+ .button-row {
29098
+ display: flex;
29099
+ flex-wrap: wrap;
29100
+ gap: 10px;
29101
+ margin-top: 12px;
29102
+ }
29103
+ textarea {
29104
+ width: 100%;
29105
+ min-height: 104px;
29106
+ border: 1px solid rgba(148, 163, 184, 0.24);
29107
+ border-radius: 8px;
29108
+ background: rgba(8, 13, 24, 0.92);
29109
+ color: #f6f8ff;
29110
+ padding: 10px;
29111
+ font: inherit;
29112
+ resize: vertical;
29113
+ }
29114
+ .command-box {
29115
+ display: block;
29116
+ width: 100%;
29117
+ padding: 12px;
29118
+ border: 1px solid rgba(148, 163, 184, 0.2);
29119
+ border-radius: 8px;
29120
+ background: rgba(5, 10, 20, 0.88);
29121
+ color: #d8f8e7;
29122
+ overflow-x: auto;
29123
+ white-space: nowrap;
29124
+ }
27416
29125
  .eyebrow {
27417
29126
  width: fit-content;
27418
29127
  margin-bottom: 16px;
@@ -27594,6 +29303,22 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27594
29303
  }
27595
29304
  .provider-pill:hover { background: rgba(30, 41, 59, 0.78); }
27596
29305
  .provider-pill span { width: 8px; height: 8px; border-radius: 999px; background: #22c55e; }
29306
+ .notice-bar { display: grid; gap: 10px; margin: 20px 0 26px; }
29307
+ .notice-card {
29308
+ display: grid;
29309
+ gap: 6px;
29310
+ padding: 14px 16px;
29311
+ border: 1px solid rgba(96, 165, 250, 0.32);
29312
+ border-radius: 8px;
29313
+ color: #d8e1f0;
29314
+ background: rgba(15, 23, 42, 0.78);
29315
+ }
29316
+ .notice-card.warning { border-color: rgba(245, 158, 11, 0.46); background: rgba(39, 26, 9, 0.82); }
29317
+ .notice-card.success { border-color: rgba(34, 197, 94, 0.46); background: rgba(6, 45, 37, 0.82); }
29318
+ .notice-card strong { color: #f6f8ff; font-size: 16px; }
29319
+ .notice-card span { color: #91a1b8; font-size: 13px; font-weight: 700; }
29320
+ .notice-card p { margin: 0; color: #d8e1f0; line-height: 1.45; }
29321
+ .notice-form { display: grid; gap: 12px; }
27597
29322
  .activity-heading { justify-content: space-between; gap: 16px; margin-bottom: 12px; }
27598
29323
  .activity-heading h2 { margin: 0; }
27599
29324
  .filter-chips { gap: 8px; flex-wrap: wrap; }
@@ -27614,6 +29339,14 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27614
29339
  .chip:hover { color: #f5f7fb; background: rgba(30, 41, 59, 0.78); }
27615
29340
  .chip.is-active, .chip[aria-pressed="true"] { border-color: rgba(34, 197, 94, 0.38); color: #d7ffe5; background: rgba(21, 128, 61, 0.22); }
27616
29341
  .activity-card[hidden], .empty-filter-state[hidden] { display: none; }
29342
+ .receipt-row.is-selected-receipt {
29343
+ outline: 2px solid rgba(56, 230, 120, 0.7);
29344
+ outline-offset: 4px;
29345
+ background: rgba(34, 197, 94, 0.08);
29346
+ }
29347
+ .receipt-row.is-selected-receipt details {
29348
+ border-radius: 8px;
29349
+ }
27617
29350
  .empty-filter-state {
27618
29351
  padding: 22px;
27619
29352
  border: 1px dashed rgba(148, 163, 184, 0.24);
@@ -27671,6 +29404,7 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27671
29404
  .protection-card { min-height: 320px; }
27672
29405
  .activity-card { grid-template-columns: 48px 1fr; }
27673
29406
  .activity-badge { grid-column: 2; width: fit-content; }
29407
+ .surface-tabs, .role-grid, .admin-grid, .marketplace-grid, .builder-grid { grid-template-columns: 1fr; }
27674
29408
  section { padding: 16px; overflow-x: auto; }
27675
29409
  table { min-width: 720px; }
27676
29410
  .activity-heading { align-items: flex-start; flex-direction: column; }
@@ -27683,7 +29417,7 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27683
29417
  <header class="topbar">
27684
29418
  <div class="brand"><span class="brand-mark">G</span><span>Guardian</span></div>
27685
29419
  <div class="topbar-actions">
27686
- <span class="status-pill"><span class="status-dot"></span>${escapeHtml(profile.runtime.daemon_status)}</span>
29420
+ <span class="status-pill"><span class="status-dot"></span>${escapeHtml(localGuardianStatus)}</span>
27687
29421
  <span class="icon-button" aria-label="Settings">&#9881;</span>
27688
29422
  </div>
27689
29423
  </header>
@@ -27691,6 +29425,25 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
27691
29425
  <div class="eyebrow">Local-first status</div>
27692
29426
  <h1>Project Guardian Control Tower</h1>
27693
29427
  <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>
29428
+ ${adminNoticeBarHtml}
29429
+ <div class="surface-tabs" aria-label="Control Tower surfaces">
29430
+ <button type="button" class="surface-tab is-active" data-surface-tab="personal" aria-pressed="true">
29431
+ <strong>Personal Guardian</strong>
29432
+ <span>Simple protection, privacy, coaching, browser connection, and receipts.</span>
29433
+ </button>
29434
+ <button type="button" class="surface-tab" data-surface-tab="admin" aria-pressed="false">
29435
+ <strong>Guardian Admin</strong>
29436
+ <span>Policy packs, marketplace approvals, Prime Pack, deployment, audit, and releases.</span>
29437
+ </button>
29438
+ </div>
29439
+ <section aria-labelledby="role-experiences">
29440
+ <h2 id="role-experiences">Role Experiences</h2>
29441
+ <p>Guardian ships one local brain with different surfaces for individuals, power users, enterprise admins, and marketplace contributors.</p>
29442
+ <div class="role-grid">
29443
+ ${roleCardsHtml}
29444
+ </div>
29445
+ </section>
29446
+ <div class="surface-panel" data-surface-panel="personal">
27694
29447
  <div class="hero-grid" aria-label="Guardian protection overview">
27695
29448
  <article class="protection-card">
27696
29449
  <div class="${protectionRingClass}"><div class="ring-core">G</div></div>
@@ -27712,6 +29465,7 @@ ${heroMetricCards}
27712
29465
  </div>
27713
29466
  </div>
27714
29467
  <p class="activity-filter-status" data-activity-filter-status aria-live="polite">Showing all local Guardian activity.</p>
29468
+ <p class="activity-filter-status" data-guardian-time-zone-label>Times use your browser's local timezone.</p>
27715
29469
  <div class="activity-list" data-activity-list>
27716
29470
  ${activityFeedHtml}
27717
29471
  <div class="empty-filter-state" data-activity-empty hidden>No activity matches this filter yet.</div>
@@ -27880,7 +29634,7 @@ ${releaseDoctorRows}
27880
29634
  </tbody>
27881
29635
  </table>
27882
29636
  </section>
27883
- <section aria-labelledby="receipt-drilldowns">
29637
+ <section id="receipts" aria-labelledby="receipt-drilldowns">
27884
29638
  <div class="activity-heading">
27885
29639
  <h2 id="receipt-drilldowns">What Guardian did today</h2>
27886
29640
  <div class="filter-chips" aria-label="Activity filters">
@@ -27890,7 +29644,7 @@ ${releaseDoctorRows}
27890
29644
  <span class="chip">Allowed</span>
27891
29645
  </div>
27892
29646
  </div>
27893
- <p><span class="visually-hidden">Activity &amp; Receipts.</span>Inspect recent Guardian decisions without raw prompt, response, or local-action payload content.</p>
29647
+ <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>
27894
29648
  <table aria-label="Guardian receipt drilldowns">
27895
29649
  <thead>
27896
29650
  <tr><th scope="col">Receipt</th><th scope="col">Type</th><th scope="col">Decision</th><th scope="col">Details</th></tr>
@@ -27970,9 +29724,47 @@ ${learningEventRows}
27970
29724
  </section>
27971
29725
  </div>
27972
29726
  </details>
29727
+ </div>
29728
+ <div class="surface-panel" data-surface-panel="admin" hidden>
29729
+ ${adminPanelsHtml}
29730
+ </div>
27973
29731
  </main>
27974
29732
  <script>
27975
29733
  (() => {
29734
+ const surfaceTabs = Array.from(document.querySelectorAll('[data-surface-tab]'));
29735
+ const surfacePanels = Array.from(document.querySelectorAll('[data-surface-panel]'));
29736
+ const selectSurface = (surface) => {
29737
+ for (const tab of surfaceTabs) {
29738
+ const selected = tab.getAttribute('data-surface-tab') === surface;
29739
+ tab.classList.toggle('is-active', selected);
29740
+ tab.setAttribute('aria-pressed', selected ? 'true' : 'false');
29741
+ }
29742
+ for (const panel of surfacePanels) {
29743
+ panel.hidden = panel.getAttribute('data-surface-panel') !== surface;
29744
+ }
29745
+ };
29746
+ for (const tab of surfaceTabs) {
29747
+ tab.addEventListener('click', () => selectSurface(tab.getAttribute('data-surface-tab') || 'personal'));
29748
+ }
29749
+ const hydrateViewModel = async () => {
29750
+ const controller = new AbortController();
29751
+ const timeoutId = window.setTimeout(() => controller.abort(), 2500);
29752
+ try {
29753
+ const response = await fetch('/v1/guardian/control-tower/view-model', {
29754
+ headers: { accept: 'application/json' },
29755
+ signal: controller.signal,
29756
+ });
29757
+ if (!response.ok) return;
29758
+ const body = await response.json();
29759
+ if (!body || body.ok !== true) return;
29760
+ document.documentElement.setAttribute('data-view-model', body.schema_version || 'loaded');
29761
+ } catch {
29762
+ // Keep the server-rendered Control Tower if local hydration is unavailable.
29763
+ } finally {
29764
+ window.clearTimeout(timeoutId);
29765
+ }
29766
+ };
29767
+ hydrateViewModel();
27976
29768
  const filters = Array.from(document.querySelectorAll('[data-activity-filter]'));
27977
29769
  const list = document.querySelector('[data-activity-list]');
27978
29770
  const emptyState = document.querySelector('[data-activity-empty]');
@@ -28008,6 +29800,85 @@ ${learningEventRows}
28008
29800
  }
28009
29801
  setFilterCopy(selectedFilter);
28010
29802
  };
29803
+ const absoluteLocalTimeFormatter = new Intl.DateTimeFormat(navigator.language || undefined, {
29804
+ month: 'short',
29805
+ day: 'numeric',
29806
+ year: 'numeric',
29807
+ hour: 'numeric',
29808
+ minute: '2-digit',
29809
+ timeZoneName: 'short',
29810
+ });
29811
+ const relativeTimeFormatter = new Intl.RelativeTimeFormat(navigator.language || undefined, {
29812
+ numeric: 'auto',
29813
+ });
29814
+ const formatAbsoluteLocalTime = (createdAt) => {
29815
+ const date = new Date(createdAt);
29816
+ if (Number.isNaN(date.getTime())) return createdAt || '';
29817
+ return absoluteLocalTimeFormatter.format(date);
29818
+ };
29819
+ const formatLocalTime = (createdAt) => {
29820
+ const date = new Date(createdAt);
29821
+ if (Number.isNaN(date.getTime())) return createdAt || '';
29822
+ const diffMs = date.getTime() - Date.now();
29823
+ const absMs = Math.abs(diffMs);
29824
+ if (absMs < 60 * 1000) return 'just now';
29825
+ if (absMs < 60 * 60 * 1000) {
29826
+ return relativeTimeFormatter.format(Math.round(diffMs / (60 * 1000)), 'minute');
29827
+ }
29828
+ if (absMs < 24 * 60 * 60 * 1000) {
29829
+ return relativeTimeFormatter.format(Math.round(diffMs / (60 * 60 * 1000)), 'hour');
29830
+ }
29831
+ if (absMs < 7 * 24 * 60 * 60 * 1000) {
29832
+ return relativeTimeFormatter.format(Math.round(diffMs / (24 * 60 * 60 * 1000)), 'day');
29833
+ }
29834
+ return formatAbsoluteLocalTime(createdAt);
29835
+ };
29836
+ const hydrateLocalTimes = () => {
29837
+ for (const element of Array.from(document.querySelectorAll('[data-guardian-local-time]'))) {
29838
+ const createdAt = element.getAttribute('datetime') || element.getAttribute('data-created-at') || '';
29839
+ const formatted = formatLocalTime(createdAt);
29840
+ if (formatted) element.textContent = formatted;
29841
+ if (createdAt) {
29842
+ element.setAttribute('title', 'Your local time: ' + formatAbsoluteLocalTime(createdAt) + ' \xB7 Stored by Guardian as ' + createdAt);
29843
+ }
29844
+ }
29845
+ };
29846
+ const hydrateLocalTimeZoneLabels = () => {
29847
+ let timeZone = '';
29848
+ try {
29849
+ timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
29850
+ } catch {
29851
+ timeZone = '';
29852
+ }
29853
+ const label = timeZone
29854
+ ? 'Times use your browser timezone: ' + timeZone + '.'
29855
+ : "Times use your browser's local timezone.";
29856
+ for (const element of Array.from(document.querySelectorAll('[data-guardian-time-zone-label]'))) {
29857
+ element.textContent = label;
29858
+ }
29859
+ };
29860
+ const receiptIdFromHash = () => {
29861
+ const hash = window.location.hash || '';
29862
+ if (!hash.startsWith('#receipt=')) return '';
29863
+ try {
29864
+ return decodeURIComponent(hash.slice('#receipt='.length));
29865
+ } catch {
29866
+ return hash.slice('#receipt='.length);
29867
+ }
29868
+ };
29869
+ const highlightReceiptFromHash = () => {
29870
+ const selectedReceiptId = receiptIdFromHash();
29871
+ let matchedRow = null;
29872
+ for (const row of Array.from(document.querySelectorAll('[data-receipt-row]'))) {
29873
+ const selected = Boolean(selectedReceiptId) && row.getAttribute('data-receipt-id') === selectedReceiptId;
29874
+ row.classList.toggle('is-selected-receipt', selected);
29875
+ if (selected) matchedRow = row;
29876
+ }
29877
+ if (!matchedRow) return;
29878
+ const details = matchedRow.querySelector('details');
29879
+ if (details) details.open = true;
29880
+ matchedRow.scrollIntoView({ block: 'center', behavior: 'smooth' });
29881
+ };
28011
29882
  const createActivityCard = (activity) => {
28012
29883
  const card = document.createElement('article');
28013
29884
  card.className = 'activity-card ' + activity.tone;
@@ -28021,7 +29892,16 @@ ${learningEventRows}
28021
29892
  title.textContent = activity.title;
28022
29893
  const meta = document.createElement('div');
28023
29894
  meta.className = 'activity-meta';
28024
- meta.textContent = activity.meta;
29895
+ if (activity.created_at) {
29896
+ const time = document.createElement('time');
29897
+ time.setAttribute('datetime', activity.created_at);
29898
+ time.setAttribute('data-guardian-local-time', '');
29899
+ time.textContent = formatLocalTime(activity.created_at);
29900
+ time.title = 'Your local time: ' + formatAbsoluteLocalTime(activity.created_at) + ' \xB7 Stored by Guardian as ' + activity.created_at;
29901
+ meta.append(time, ' \xB7 ' + (activity.receipt_type || 'receipt'));
29902
+ } else {
29903
+ meta.textContent = activity.meta;
29904
+ }
28025
29905
  body.append(title, meta);
28026
29906
  const badge = document.createElement('div');
28027
29907
  badge.className = 'activity-badge ' + activity.tone;
@@ -28064,6 +29944,11 @@ ${learningEventRows}
28064
29944
  applyFilter(filter.getAttribute('data-activity-filter') || 'all');
28065
29945
  });
28066
29946
  }
29947
+ window.addEventListener('hashchange', highlightReceiptFromHash);
29948
+ hydrateLocalTimeZoneLabels();
29949
+ hydrateLocalTimes();
29950
+ window.setInterval(hydrateLocalTimes, 60 * 1000);
29951
+ highlightReceiptFromHash();
28067
29952
  })();
28068
29953
  </script>
28069
29954
  </body>
@@ -28200,6 +30085,120 @@ function renderReleaseDoctorRows(status) {
28200
30085
  function renderDoctorCounts(doctor) {
28201
30086
  return `${doctor.checkCounts.pass} pass; ${doctor.checkCounts.warn} warn; ${doctor.checkCounts.fail} fail`;
28202
30087
  }
30088
+ async function readControlTowerAdminNotices(profileDir) {
30089
+ try {
30090
+ const raw = await readFile4(path4.join(profileDir, CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME), "utf8");
30091
+ const parsed = JSON.parse(raw);
30092
+ if (!isRecord(parsed) || !Array.isArray(parsed.notices)) {
30093
+ return [];
30094
+ }
30095
+ return parsed.notices.map(parseControlTowerAdminNotice).filter((notice) => Boolean(notice)).slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT);
30096
+ } catch {
30097
+ return [];
30098
+ }
30099
+ }
30100
+ async function writeControlTowerAdminNotices(profileDir, notices) {
30101
+ await writeFile2(
30102
+ path4.join(profileDir, CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME),
30103
+ `${JSON.stringify(
30104
+ {
30105
+ schema_version: "contextecf/project-guardian-control-tower-admin-notices/v1",
30106
+ notices: notices.slice(0, CONTROL_TOWER_ADMIN_NOTICE_LIMIT),
30107
+ raw_content_included: false,
30108
+ token_printed: false
30109
+ },
30110
+ null,
30111
+ 2
30112
+ )}
30113
+ `,
30114
+ "utf8"
30115
+ );
30116
+ }
30117
+ async function readControlTowerMarketplaceRequests(profileDir) {
30118
+ try {
30119
+ const raw = await readFile4(
30120
+ path4.join(profileDir, CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME),
30121
+ "utf8"
30122
+ );
30123
+ const parsed = JSON.parse(raw);
30124
+ if (!isRecord(parsed) || !Array.isArray(parsed.requests)) {
30125
+ return [];
30126
+ }
30127
+ return parsed.requests.map(parseControlTowerMarketplaceRequest).filter((request) => Boolean(request)).slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT);
30128
+ } catch {
30129
+ return [];
30130
+ }
30131
+ }
30132
+ async function writeControlTowerMarketplaceRequests(profileDir, requests) {
30133
+ await writeFile2(
30134
+ path4.join(profileDir, CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME),
30135
+ `${JSON.stringify(
30136
+ {
30137
+ schema_version: "contextecf/project-guardian-marketplace-requests/v1",
30138
+ requests: requests.slice(0, CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT),
30139
+ raw_content_included: false,
30140
+ token_printed: false
30141
+ },
30142
+ null,
30143
+ 2
30144
+ )}
30145
+ `,
30146
+ "utf8"
30147
+ );
30148
+ }
30149
+ async function writeControlTowerPrimePackDraft(profileDir, draft) {
30150
+ await writeFile2(
30151
+ path4.join(profileDir, CONTROL_TOWER_PRIME_PACK_FILE_NAME),
30152
+ `${JSON.stringify(draft, null, 2)}
30153
+ `,
30154
+ "utf8"
30155
+ );
30156
+ }
30157
+ function parseControlTowerAdminNotice(value) {
30158
+ if (!isRecord(value)) {
30159
+ return null;
30160
+ }
30161
+ const audience = parseControlTowerAdminNoticeAudience(value.audience);
30162
+ const tone = parseControlTowerAdminNoticeTone(value.tone);
30163
+ if (typeof value.id !== "string" || typeof value.title !== "string" || typeof value.message !== "string" || typeof value.created_at !== "string" || !audience || !tone) {
30164
+ return null;
30165
+ }
30166
+ return {
30167
+ id: value.id.slice(0, 80),
30168
+ title: value.title.slice(0, CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT),
30169
+ message: value.message.slice(0, CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT),
30170
+ audience,
30171
+ tone,
30172
+ created_at: value.created_at,
30173
+ receipt_id: typeof value.receipt_id === "string" ? value.receipt_id.slice(0, 160) : void 0,
30174
+ receipt_hash: typeof value.receipt_hash === "string" ? value.receipt_hash.slice(0, 80) : void 0,
30175
+ raw_content_included: false
30176
+ };
30177
+ }
30178
+ function parseControlTowerMarketplaceRequest(value) {
30179
+ if (!isRecord(value)) {
30180
+ return null;
30181
+ }
30182
+ 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") {
30183
+ return null;
30184
+ }
30185
+ return {
30186
+ schema_version: "contextecf/project-guardian-marketplace-request/v1",
30187
+ request_id: value.request_id.slice(0, 120),
30188
+ pack_id: value.pack_id.slice(0, 120),
30189
+ marketplace_manifest_id: value.marketplace_manifest_id.slice(0, 160),
30190
+ pack_version: value.pack_version.slice(0, 40),
30191
+ pack_name: value.pack_name.slice(0, 120),
30192
+ publisher: value.publisher.slice(0, 120),
30193
+ action: "request_review",
30194
+ status: "requested_metadata_only",
30195
+ requested_at: value.requested_at,
30196
+ receipt_id: value.receipt_id.slice(0, 160),
30197
+ receipt_hash: value.receipt_hash.slice(0, 80),
30198
+ raw_content_included: false,
30199
+ token_printed: false
30200
+ };
30201
+ }
28203
30202
  async function buildControlTowerLocalDataStatus(profile, env = process.env) {
28204
30203
  const profileDir = profile.profile_dir;
28205
30204
  const encryption = buildControlTowerEncryptionStatus(
@@ -28464,6 +30463,8 @@ function buildActivityItems(receipts, filter) {
28464
30463
  tone: activityTone(receipt.disposition),
28465
30464
  icon: activityPlainIcon(receipt.disposition),
28466
30465
  title: receipt.summary,
30466
+ created_at: receipt.created_at,
30467
+ receipt_type: receipt.receipt_type,
28467
30468
  meta: `${formatActivityTime(receipt.created_at)} \xB7 ${receipt.receipt_type}`,
28468
30469
  label: activityLabel(receipt.disposition),
28469
30470
  raw_content_included: false
@@ -28476,14 +30477,14 @@ function renderActivityCard(receipt) {
28476
30477
  <div class="activity-icon">${activityIcon(receipt.disposition)}</div>
28477
30478
  <div>
28478
30479
  <div class="activity-title">${escapeHtml(receipt.summary)}</div>
28479
- <div class="activity-meta">${escapeHtml(formatActivityTime(receipt.created_at))} \xB7 ${escapeHtml(receipt.receipt_type)}</div>
30480
+ <div class="activity-meta">${renderLocalTime(receipt.created_at)} \xB7 ${escapeHtml(receipt.receipt_type)}</div>
28480
30481
  </div>
28481
30482
  <div class="activity-badge ${tone}">${escapeHtml(activityLabel(receipt.disposition))}</div>
28482
30483
  </article>`;
28483
30484
  }
28484
30485
  function renderReceiptRow(receipt) {
28485
30486
  const policyBasis = receipt.policy_basis.length > 0 ? receipt.policy_basis.join(", ") : "none recorded";
28486
- 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>`;
30487
+ 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>`;
28487
30488
  }
28488
30489
  function activityTone(disposition) {
28489
30490
  if (disposition === "block") return "block";
@@ -28512,17 +30513,321 @@ function activityFilterGroup(disposition) {
28512
30513
  if (disposition === "warn" || disposition === "confirm") return "flagged";
28513
30514
  return "allowed";
28514
30515
  }
28515
- function formatActivityTime(createdAt) {
30516
+ function formatActivityTime(createdAt, nowMs = Date.now()) {
28516
30517
  const parsed = Date.parse(createdAt);
28517
30518
  if (Number.isNaN(parsed)) {
28518
30519
  return createdAt;
28519
30520
  }
30521
+ const diffMs = parsed - nowMs;
30522
+ const absMs = Math.abs(diffMs);
30523
+ if (absMs < 60 * 1e3) {
30524
+ return "just now";
30525
+ }
30526
+ if (absMs < 60 * 60 * 1e3) {
30527
+ const minutes = Math.round(absMs / (60 * 1e3));
30528
+ return diffMs < 0 ? `${minutes} min ago` : `in ${minutes} min`;
30529
+ }
30530
+ if (absMs < 24 * 60 * 60 * 1e3) {
30531
+ const hours = Math.round(absMs / (60 * 60 * 1e3));
30532
+ return diffMs < 0 ? `${hours} hr ago` : `in ${hours} hr`;
30533
+ }
30534
+ if (absMs < 7 * 24 * 60 * 60 * 1e3) {
30535
+ const days = Math.round(absMs / (24 * 60 * 60 * 1e3));
30536
+ return diffMs < 0 ? `${days} day${days === 1 ? "" : "s"} ago` : `in ${days} day${days === 1 ? "" : "s"}`;
30537
+ }
28520
30538
  return new Date(parsed).toLocaleString("en-US", {
28521
- dateStyle: "medium",
28522
- timeStyle: "short",
28523
- timeZone: "UTC"
30539
+ month: "short",
30540
+ day: "numeric",
30541
+ year: "numeric",
30542
+ hour: "numeric",
30543
+ minute: "2-digit",
30544
+ timeZoneName: "short"
28524
30545
  });
28525
30546
  }
30547
+ function renderLocalTime(createdAt) {
30548
+ const parsed = Date.parse(createdAt);
30549
+ if (Number.isNaN(parsed)) {
30550
+ return escapeHtml(createdAt);
30551
+ }
30552
+ return `<time data-guardian-local-time datetime="${escapeHtml(createdAt)}" title="${escapeHtml(
30553
+ `Stored by Guardian as ${createdAt}`
30554
+ )}">${escapeHtml(formatActivityTime(createdAt))}</time>`;
30555
+ }
30556
+ function renderLocalGuardianStatus(status) {
30557
+ switch (status) {
30558
+ case "start_requested":
30559
+ return "Running";
30560
+ case "not_started_mvp":
30561
+ return "Not running";
30562
+ }
30563
+ }
30564
+ function renderControlTowerRoleCards(viewModel) {
30565
+ return viewModel.surfaces.map(
30566
+ (surface) => ` <article class="role-card">
30567
+ <strong>${escapeHtml(surface.label)}</strong>
30568
+ <span>${escapeHtml(surface.audience)}</span>
30569
+ <span>${escapeHtml(surface.purpose)}</span>
30570
+ </article>`
30571
+ ).join("\n");
30572
+ }
30573
+ function renderAdminNoticeBar(notices) {
30574
+ if (notices.length === 0) {
30575
+ return "";
30576
+ }
30577
+ return ` <section class="notice-bar" aria-label="Guardian notifications">
30578
+ ${notices.map((notice) => renderAdminNoticeCard(notice)).join("\n")}
30579
+ </section>`;
30580
+ }
30581
+ function renderAdminNoticeList(notices) {
30582
+ if (notices.length === 0) {
30583
+ return " <p>No local admin notices are published.</p>";
30584
+ }
30585
+ return ` <div class="notice-bar" aria-label="Current admin notices">
30586
+ ${notices.map((notice) => renderAdminNoticeCard(notice)).join("\n")}
30587
+ </div>`;
30588
+ }
30589
+ function renderAdminNoticeCard(notice) {
30590
+ return ` <article class="notice-card ${escapeHtml(notice.tone)}">
30591
+ <strong>${escapeHtml(notice.title)}</strong>
30592
+ <p>${escapeHtml(notice.message)}</p>
30593
+ <span>${escapeHtml(adminNoticeAudienceLabel(notice.audience))} \xB7 ${renderLocalTime(notice.created_at)}</span>
30594
+ ${notice.receipt_id ? `<code>${escapeHtml(notice.receipt_id)}</code>` : ""}
30595
+ </article>`;
30596
+ }
30597
+ function adminNoticeAudienceLabel(audience) {
30598
+ switch (audience) {
30599
+ case "admins":
30600
+ return "Admins";
30601
+ case "developers":
30602
+ return "Developers";
30603
+ case "all_users":
30604
+ return "Everyone";
30605
+ }
30606
+ }
30607
+ function renderGuardianAdminSurface(viewModel, profile, receipts, appPermissionRows) {
30608
+ return ` <section aria-labelledby="guardian-admin-overview">
30609
+ <h2 id="guardian-admin-overview">Guardian Admin</h2>
30610
+ <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>
30611
+ <div class="admin-grid">
30612
+ ${renderAdminSummaryCards(viewModel, profile, receipts, appPermissionRows)}
30613
+ </div>
30614
+ </section>
30615
+ <section aria-labelledby="admin-notifications">
30616
+ <h2 id="admin-notifications">Admin Notifications</h2>
30617
+ <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>
30618
+ <form class="notice-form" method="post" action="/v1/guardian/admin/notice">
30619
+ <input type="hidden" name="action" value="publish">
30620
+ <label>Title<input name="title" maxlength="${CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT}" placeholder="Policy reminder" required></label>
30621
+ <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>
30622
+ <label>Audience<select name="audience"><option value="all_users">Everyone</option><option value="admins">Admins</option><option value="developers">Developers</option></select></label>
30623
+ <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>
30624
+ <button type="submit">Publish local notice</button>
30625
+ </form>
30626
+ <form method="post" action="/v1/guardian/admin/notice">
30627
+ <input type="hidden" name="action" value="clear">
30628
+ <button type="submit">Clear local notices</button>
30629
+ </form>
30630
+ ${renderAdminNoticeList(viewModel.admin.notices)}
30631
+ </section>
30632
+ <section aria-labelledby="admin-policy-packs">
30633
+ <h2 id="admin-policy-packs">Policy Pack Management</h2>
30634
+ <p>Turn built-in packs on or off for this device now. In enterprise deployments, the same model becomes group assignment.</p>
30635
+ <table aria-label="Admin policy pack assignment">
30636
+ <thead>
30637
+ <tr><th scope="col">Pack</th><th scope="col">State</th><th scope="col">Local action</th></tr>
30638
+ </thead>
30639
+ <tbody>
30640
+ ${profile.policy_packs.available.map((packId) => {
30641
+ const active = profile.policy_packs.active.includes(packId);
30642
+ const disabled = active && profile.policy_packs.active.length <= 1;
30643
+ return ` <tr><th scope="row">${escapeHtml(packId)}</th><td>${active ? "active" : "available"}</td><td>${renderPolicyPackForm(active ? "disable" : "enable", packId, disabled)}</td></tr>`;
30644
+ }).join("\n")}
30645
+ </tbody>
30646
+ </table>
30647
+ </section>
30648
+ <section aria-labelledby="admin-marketplace">
30649
+ <h2 id="admin-marketplace">Policy Pack Marketplace</h2>
30650
+ <p>Review pack metadata before connecting it to a deployment. Packs can start as request/approval metadata before billing automation is added.</p>
30651
+ <div class="marketplace-grid">
30652
+ ${renderMarketplaceCards(viewModel)}
30653
+ </div>
30654
+ ${renderMarketplaceRequestList(viewModel.admin.marketplaceRequests)}
30655
+ </section>
30656
+ <section aria-labelledby="admin-prime-pack">
30657
+ <h2 id="admin-prime-pack">Prime Pack Builder</h2>
30658
+ <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>
30659
+ <article class="prime-pack-builder">
30660
+ <h3>Custom protection inputs</h3>
30661
+ <form method="post" action="/v1/guardian/admin/prime-pack">
30662
+ <div class="builder-grid">
30663
+ <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>
30664
+ <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>
30665
+ <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>
30666
+ <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>
30667
+ </div>
30668
+ <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>
30669
+ <div class="button-row">
30670
+ <button type="submit" name="action" value="preview">Preview impact</button>
30671
+ <button type="submit" name="action" value="deploy">Deploy locally</button>
30672
+ </div>
30673
+ </form>
30674
+ <ul class="metadata-list">
30675
+ <li><b>Input formats</b><span>${escapeHtml(viewModel.admin.primePack.supportedInputs.join(", "))}</span></li>
30676
+ <li><b>Preview</b><span>stores counts and sample impact in <code>${escapeHtml(CONTROL_TOWER_PRIME_PACK_FILE_NAME)}</code></span></li>
30677
+ <li><b>Deploy</b><span>writes a local admin receipt before assignment</span></li>
30678
+ </ul>
30679
+ </article>
30680
+ </section>
30681
+ <section aria-labelledby="admin-deployment-scope">
30682
+ <h2 id="admin-deployment-scope">Deployment Scope</h2>
30683
+ <p>Assign posture and packs by user or group. Personal Guardian uses the first row; enterprise deployments add managed groups.</p>
30684
+ <table aria-label="Guardian deployment groups">
30685
+ <thead>
30686
+ <tr><th scope="col">Group</th><th scope="col">Users</th><th scope="col">Posture</th><th scope="col">Packs</th></tr>
30687
+ </thead>
30688
+ <tbody>
30689
+ ${viewModel.admin.managedGroups.map(
30690
+ (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>`
30691
+ ).join("\n")}
30692
+ </tbody>
30693
+ </table>
30694
+ </section>
30695
+ <section aria-labelledby="admin-release-automation">
30696
+ <h2 id="admin-release-automation">Release Automation</h2>
30697
+ <p>Use GitHub Actions as the default release path so package evidence, browser artifacts, Docker smoke tests, and npm publication stay repeatable.</p>
30698
+ <div class="builder-grid">
30699
+ <article class="release-command-card">
30700
+ <h3>Dry run</h3>
30701
+ <code class="command-box">${escapeHtml(viewModel.releaseAutomation.dryRunCommand)}</code>
30702
+ </article>
30703
+ <article class="release-command-card">
30704
+ <h3>Publish</h3>
30705
+ <code class="command-box">${escapeHtml(viewModel.releaseAutomation.publishCommand)}</code>
30706
+ </article>
30707
+ </div>
30708
+ <ul class="metadata-list">
30709
+ <li><b>Repository</b><span>${escapeHtml(viewModel.releaseAutomation.repository)}</span></li>
30710
+ <li><b>Workflow</b><span>${escapeHtml(viewModel.releaseAutomation.workflow)}</span></li>
30711
+ <li><b>NPM package</b><span>${escapeHtml(viewModel.releaseAutomation.npmPackage)}</span></li>
30712
+ <li><b>Chrome extension</b><span>${escapeHtml(viewModel.releaseAutomation.chromeExtensionId)}</span></li>
30713
+ <li><b>Still manual</b><span>${escapeHtml(viewModel.releaseAutomation.humanIntervention[0])}</span></li>
30714
+ </ul>
30715
+ </section>
30716
+ <section aria-labelledby="admin-audit">
30717
+ <h2 id="admin-audit">Receipts And Audit</h2>
30718
+ <p>Admins can review metadata-only decisions without exposing raw prompt text, raw responses, local action payloads, or runtime tokens.</p>
30719
+ <table aria-label="Guardian admin receipt audit">
30720
+ <thead>
30721
+ <tr><th scope="col">Receipt</th><th scope="col">Type</th><th scope="col">Decision</th><th scope="col">Details</th></tr>
30722
+ </thead>
30723
+ <tbody>
30724
+ ${renderReceiptRows(receipts)}
30725
+ </tbody>
30726
+ </table>
30727
+ </section>`;
30728
+ }
30729
+ function renderAdminSummaryCards(viewModel, profile, receipts, appPermissionRows) {
30730
+ const cards = [
30731
+ {
30732
+ title: "Managed groups",
30733
+ value: String(viewModel.admin.managedGroups.length),
30734
+ detail: "Group-level posture and pack assignment model."
30735
+ },
30736
+ {
30737
+ title: "Marketplace packs",
30738
+ value: String(viewModel.marketplace.length),
30739
+ detail: "Metadata listings ready for approval review."
30740
+ },
30741
+ {
30742
+ title: "Prime Pack",
30743
+ value: viewModel.admin.primePack.status.replace(/_/gu, " "),
30744
+ detail: "Custom strings, domains, destinations, and escalation phrases."
30745
+ },
30746
+ {
30747
+ title: "Active local packs",
30748
+ value: String(profile.policy_packs.active.length),
30749
+ detail: profile.policy_packs.active.join(", ") || "none"
30750
+ },
30751
+ {
30752
+ title: "Governed apps",
30753
+ value: String(appPermissionRows.length),
30754
+ detail: "Local app access remains confirmation-gated."
30755
+ },
30756
+ {
30757
+ title: "Audit receipts",
30758
+ value: String(receipts.length),
30759
+ detail: "Metadata-only receipt chain."
30760
+ }
30761
+ ];
30762
+ return cards.map(
30763
+ (card) => ` <article class="admin-card">
30764
+ <h3>${escapeHtml(card.title)}</h3>
30765
+ <strong>${escapeHtml(card.value)}</strong>
30766
+ <span>${escapeHtml(card.detail)}</span>
30767
+ </article>`
30768
+ ).join("\n");
30769
+ }
30770
+ function renderMarketplaceCards(viewModel) {
30771
+ return viewModel.marketplace.map(
30772
+ (pack) => ` <article class="marketplace-card" data-marketplace-pack-id="${escapeHtml(pack.packId)}">
30773
+ <h3>${escapeHtml(pack.name)}</h3>
30774
+ <p>${escapeHtml(pack.examples[0] ?? "Policy pack metadata listing.")}</p>
30775
+ <ul class="metadata-list">
30776
+ <li><b>Audience</b><span>${escapeHtml(pack.audience)}</span></li>
30777
+ <li><b>Category</b><span>${escapeHtml(pack.category)}</span></li>
30778
+ <li><b>Maintainer</b><span>${escapeHtml(pack.publisher)}</span></li>
30779
+ <li><b>Version</b><span>${escapeHtml(pack.version)}</span></li>
30780
+ <li><b>Price/license</b><span>${escapeHtml(pack.price)}</span></li>
30781
+ <li><b>Approval state</b><span>${escapeHtml(pack.state)}</span></li>
30782
+ <li><b>False-positive risk</b><span>${escapeHtml(pack.risk)}</span></li>
30783
+ </ul>
30784
+ ${renderMarketplaceRequestAction(pack)}
30785
+ <details class="marketplace-detail-drawer">
30786
+ <summary>Review details before connecting</summary>
30787
+ <dl>
30788
+ <dt>Permissions requested</dt>
30789
+ <dd>${escapeHtml(pack.permissions.join(" "))}</dd>
30790
+ <dt>Protected data</dt>
30791
+ <dd>${escapeHtml(pack.protectedDataTypes.join(", "))}</dd>
30792
+ <dt>Examples</dt>
30793
+ <dd>${escapeHtml(pack.examples.join(" "))}</dd>
30794
+ <dt>License reference</dt>
30795
+ <dd><code>${escapeHtml(pack.licenseRef)}</code></dd>
30796
+ <dt>Support reference</dt>
30797
+ <dd><code>${escapeHtml(pack.supportRef)}</code></dd>
30798
+ </dl>
30799
+ </details>
30800
+ </article>`
30801
+ ).join("\n");
30802
+ }
30803
+ function renderMarketplaceRequestAction(pack) {
30804
+ if (pack.requestState === "connected") {
30805
+ return ' <p class="marketplace-request-state">Connected locally through active policy packs.</p>';
30806
+ }
30807
+ if (pack.requestState === "requested_metadata_only") {
30808
+ return ` <p class="marketplace-request-state">Review requested ${pack.requestedAt ? renderLocalTime(pack.requestedAt) : "locally"}.</p>`;
30809
+ }
30810
+ return ` <form class="marketplace-request-form" method="post" action="/v1/guardian/admin/marketplace-request">
30811
+ <input type="hidden" name="action" value="request_review">
30812
+ <input type="hidden" name="packId" value="${escapeHtml(pack.packId)}">
30813
+ <button type="submit">Request review</button>
30814
+ <span>Writes a local receipt; paid pack connection remains approval-gated.</span>
30815
+ </form>`;
30816
+ }
30817
+ function renderMarketplaceRequestList(requests) {
30818
+ if (requests.length === 0) {
30819
+ return " <p>No marketplace pack reviews have been requested on this device yet.</p>";
30820
+ }
30821
+ return ` <div class="marketplace-request-list" aria-label="Marketplace review requests">
30822
+ ${requests.map(
30823
+ (request) => ` <article class="marketplace-request-card">
30824
+ <strong>${escapeHtml(request.pack_name)}</strong>
30825
+ <span>${escapeHtml(request.publisher)} \xB7 ${renderLocalTime(request.requested_at)}</span>
30826
+ <code>${escapeHtml(request.receipt_id)}</code>
30827
+ </article>`
30828
+ ).join("\n")}
30829
+ </div>`;
30830
+ }
28526
30831
  function providerDisplayName(providerId) {
28527
30832
  if (providerId === "chatgpt") return "ChatGPT";
28528
30833
  if (providerId === "claude") return "Claude";
@@ -28818,7 +31123,7 @@ function listen(server, host, port) {
28818
31123
  });
28819
31124
  });
28820
31125
  }
28821
- 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;
31126
+ 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;
28822
31127
  var init_daemon = __esm({
28823
31128
  "packages/guardian-cli/src/daemon.ts"() {
28824
31129
  "use strict";
@@ -28834,12 +31139,25 @@ var init_daemon = __esm({
28834
31139
  CONTROL_TOWER_GRAPH_NODE_LIMIT = 12;
28835
31140
  CONTROL_TOWER_GRAPH_EDGE_LIMIT = 16;
28836
31141
  CONTROL_TOWER_LEARNING_EVENT_LIMIT = 12;
31142
+ CONTROL_TOWER_ADMIN_NOTICE_LIMIT = 5;
31143
+ CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME = "admin-notices.json";
31144
+ CONTROL_TOWER_ADMIN_NOTICE_TITLE_LIMIT = 80;
31145
+ CONTROL_TOWER_ADMIN_NOTICE_MESSAGE_LIMIT = 240;
31146
+ CONTROL_TOWER_MARKETPLACE_REQUEST_LIMIT = 20;
31147
+ CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME = "marketplace-requests.json";
31148
+ CONTROL_TOWER_PRIME_PACK_FILE_NAME = "prime-pack-draft.json";
31149
+ CONTROL_TOWER_PRIME_PACK_VALUE_LIMIT = 120;
31150
+ CONTROL_TOWER_PRIME_PACK_ENTRY_LIMIT = 100;
31151
+ CONTROL_TOWER_PRIME_PACK_IMPORT_LIMIT = 24 * 1024;
28837
31152
  CONTROL_TOWER_DATA_FILE_NAMES = [
28838
31153
  "profile.json",
28839
31154
  "policy-packs.json",
28840
31155
  "posture-profile.json",
28841
31156
  "preferences.json",
28842
31157
  "permissions.json",
31158
+ CONTROL_TOWER_ADMIN_NOTICE_FILE_NAME,
31159
+ CONTROL_TOWER_MARKETPLACE_REQUEST_FILE_NAME,
31160
+ CONTROL_TOWER_PRIME_PACK_FILE_NAME,
28843
31161
  "runtime-token"
28844
31162
  ];
28845
31163
  CONTROL_TOWER_DATA_DIRECTORY_NAMES = ["ecl", "receipts", "mcp"];
@@ -28850,6 +31168,8 @@ var init_daemon = __esm({
28850
31168
  "restore",
28851
31169
  "delete"
28852
31170
  ];
31171
+ 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`;
31172
+ 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`;
28853
31173
  }
28854
31174
  });
28855
31175