switchroom 0.13.54 → 0.13.56

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.
@@ -13520,7 +13520,7 @@ var init_zod = __esm(() => {
13520
13520
  });
13521
13521
 
13522
13522
  // src/config/schema.ts
13523
- var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
13523
+ var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
13524
13524
  var init_schema = __esm(() => {
13525
13525
  init_zod();
13526
13526
  CodeRepoEntrySchema = exports_external.object({
@@ -13705,6 +13705,16 @@ var init_schema = __esm(() => {
13705
13705
  authority: exports_external.string().url().optional().describe("Microsoft authority endpoint. Defaults to " + "'https://login.microsoftonline.com/common' which accepts both " + "personal MSA and work/school tenants. Override only for " + "single-tenant deployments."),
13706
13706
  org_mode: exports_external.boolean().optional().describe("Opt-in to Teams + SharePoint surfaces (RFC \u00a76.4). When true, " + "the v1 scope set adds Sites.ReadWrite.All AND the launcher " + "spawns softeria with --org-mode. Defaults to false \u2014 personal " + "MSA + standard work surfaces only. Flipping for an existing " + "consented account requires re-running 'auth microsoft account " + "add --replace' to consent the additional scope.")
13707
13707
  }).optional();
13708
+ NotionWorkspaceConfigSchema = exports_external.object({
13709
+ vault_key: exports_external.string().min(1).default("notion/integration-token").describe("Vault key holding the Notion internal-integration token. Default " + "`notion/integration-token`. Override only for non-standard vault " + "layouts. The broker's --allow ACL on this key is the authoritative " + "list of which agents may receive the token."),
13710
+ databases: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
13711
+ message: "notion_workspace.databases friendly names must match " + "/^[a-z0-9][a-z0-9_-]{0,62}$/ \u2014 lowercase letters, digits, " + "hyphens, underscores. Got: '%s'."
13712
+ }), exports_external.string().regex(/^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}$/, {
13713
+ message: "notion_workspace.databases values must be Notion database " + "UUIDs (32 hex characters, optional dashes)."
13714
+ })).default({}).describe("Friendly-name \u2192 Notion database UUID map. Operator-managed; agents " + "reference databases by friendly name only \u2014 they never see or type " + "UUIDs. Populate via `switchroom notion list-dbs` (PR 4) after " + "vault-putting the integration token and sharing DBs with the " + "integration in Notion's UI."),
13715
+ mcp_version: exports_external.string().min(1).optional().describe("Optional pin for the upstream `@notionhq/notion-mcp-server` npm " + "package version. Default is the build-time `NOTION_MCP_PINNED_VERSION` " + "constant. Override only when reproducing operator-specific bugs."),
13716
+ rate_limit_rps: exports_external.number().int().positive().max(10).optional().describe("Optional global rate-limit budget in requests per second across all " + "switchroom agents sharing this integration token. Defaults to 3 " + "(Notion's documented public-API limit). Lower it if you also use " + "the integration token from outside switchroom and need to share " + "budget. Higher than 10 is rejected \u2014 if you think you need it, " + "your usage probably needs a workspace-tier upgrade with Notion.")
13717
+ }).optional();
13708
13718
  AgentGoogleWorkspaceConfigSchema = exports_external.object({
13709
13719
  account: exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
13710
13720
  message: "google_workspace.account must be a Google account email like " + "'alice@example.com' (colons not allowed)"
@@ -13718,6 +13728,13 @@ var init_schema = __esm(() => {
13718
13728
  }).transform((v) => v.trim().toLowerCase()).optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized)."),
13719
13729
  org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 \u00a76.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false).")
13720
13730
  }).optional();
13731
+ AgentNotionWorkspaceConfigSchema = exports_external.object({
13732
+ databases: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
13733
+ message: "notion_workspace.databases entries must be friendly names " + "matching /^[a-z0-9][a-z0-9_-]{0,62}$/ \u2014 these must appear as " + "keys in top-level notion_workspace.databases."
13734
+ })).min(1, {
13735
+ message: "notion_workspace.databases must list at least one friendly " + "name. An empty list rejects every Notion tool call \u2014 if you " + "want to remove this agent's Notion access, delete the entire " + "notion_workspace block instead."
13736
+ }).optional().describe("Optional per-agent allowlist of database friendly names this " + "agent may read/write. Each name must exist as a key in top-level " + "notion_workspace.databases. Omit the field (or leave it undefined) " + "to grant access to every DB the upstream integration can see \u2014 " + "appropriate for an admin/orchestrator agent. Set the list to " + "narrow access for specialist agents.")
13737
+ }).optional();
13721
13738
  ReactionsSchema = exports_external.object({
13722
13739
  enabled: exports_external.boolean().optional().describe("Master switch for the reaction-trigger path. When false, " + "reactions are still persisted via recordReaction but never " + "dispatched to the agent as synthetic inbound turns. Default true."),
13723
13740
  trigger_emojis: exports_external.array(exports_external.string()).optional().describe("Emoji allowlist that triggers a synthetic inbound when reacted " + "to a bot message. Default ['\uD83D\uDC4E', '\u274c', '\uD83D\uDC4D', '\u2705']. Cascade " + "mode: REPLACE (not union) \u2014 setting this at a layer replaces " + "lower layers entirely, so an operator can narrow to [] to " + "disable triggering without flipping `enabled`."),
@@ -13854,6 +13871,7 @@ var init_schema = __esm(() => {
13854
13871
  drive: AgentGoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Per-agent " + "google_workspace overrides (currently approvers + tier). When set, " + "replaces the top-level approvers list for this agent. " + "google_client_id/secret are not per-agent \u2014 they live at the top level."),
13855
13872
  google_workspace: AgentGoogleWorkspaceConfigSchema.describe("RFC G canonical key. Per-agent Google Workspace overrides \u2014 currently " + "approvers (replaces, does not extend the top-level list) and tier " + "(`core` | `extended` | `complete`, replaces top-level default). " + "google_client_id/secret are not per-agent \u2014 they live at the top level. " + "Mutually exclusive with `drive:` on the same agent (loader fails fast " + "if both are set)."),
13856
13873
  microsoft_workspace: AgentMicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Per-agent Microsoft Workspace " + "override \u2014 pins the Microsoft account this agent reads via the " + "auth-broker (must be a key in top-level `microsoft_accounts:` with " + "this agent in its `enabled_for[]`) and optionally overrides org_mode. " + "microsoft_client_id/secret are not per-agent."),
13874
+ notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC docs/rfcs/notion-integration.md. Per-agent Notion access. " + "Presence opts the agent IN (launcher scaffolded, MCP entry emitted, " + "broker grants the integration token). Optional `databases:` filter " + "narrows which DBs this agent may read/write \u2014 names must resolve in " + "top-level notion_workspace.databases. Absence opts the agent OUT."),
13857
13875
  repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
13858
13876
  url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
13859
13877
  branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
@@ -13977,6 +13995,7 @@ var init_schema = __esm(() => {
13977
13995
  drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
13978
13996
  google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration \u2014 " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
13979
13997
  microsoft_workspace: MicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Top-level Microsoft Workspace " + "configuration \u2014 OAuth client credentials (Entra app), authority " + "endpoint (defaults to /common for personal MSA + work), and the " + "org_mode opt-in for Teams/SharePoint surfaces. Block is optional; " + "when omitted the broker does not register the Microsoft provider."),
13998
+ notion_workspace: NotionWorkspaceConfigSchema.describe("RFC docs/rfcs/notion-integration.md. Top-level Notion integration " + "config \u2014 vault key for the integration token, friendly-name \u2192 " + "database UUID map, optional MCP-package version pin, and optional " + "global rate-limit override (default 3 rps, Notion's documented " + "public-API limit). Block is optional; when omitted no agent gets a " + "Notion MCP entry regardless of per-agent config."),
13980
13999
  quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
13981
14000
  host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
13982
14001
  hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Currently scopes the opt-in flag and rate cap for the new " + "`config_propose_edit` verb (PR 1a \u2014 disabled by default)."),
@@ -14526,6 +14545,46 @@ var init_merge = __esm(() => {
14526
14545
  })(mergeAgentConfig ||= {});
14527
14546
  });
14528
14547
 
14548
+ // src/config/notion-workspace-acl.ts
14549
+ function shouldEmitNotionMcp(agentName, config) {
14550
+ if (!agentName)
14551
+ return false;
14552
+ if (!config.notion_workspace)
14553
+ return false;
14554
+ const agentConfig = config.agents?.[agentName];
14555
+ if (!agentConfig)
14556
+ return false;
14557
+ if (agentConfig.notion_workspace === undefined)
14558
+ return false;
14559
+ return true;
14560
+ }
14561
+ function validateNotionWorkspaceConfig(config) {
14562
+ const issues = [];
14563
+ const dbMap = config.notion_workspace?.databases ?? {};
14564
+ const known = new Set(Object.keys(dbMap));
14565
+ for (const [agentName, agentRaw] of Object.entries(config.agents ?? {})) {
14566
+ if (!agentRaw)
14567
+ continue;
14568
+ const dbFilter = agentRaw.notion_workspace?.databases;
14569
+ if (dbFilter === undefined)
14570
+ continue;
14571
+ if (dbFilter.length === 0) {
14572
+ issues.push(` agents.${agentName}.notion_workspace.databases is an empty ` + `list. Delete the entire notion_workspace block to remove Notion ` + `access, or list at least one database friendly name.`);
14573
+ continue;
14574
+ }
14575
+ if (config.notion_workspace === undefined) {
14576
+ issues.push(` agents.${agentName}.notion_workspace is set but the top-level ` + `notion_workspace block is missing. Configure the integration ` + `globally first (vault_key + databases map), then grant per-agent ` + `access.`);
14577
+ continue;
14578
+ }
14579
+ for (const name of dbFilter) {
14580
+ if (!known.has(name)) {
14581
+ issues.push(` agents.${agentName}.notion_workspace.databases references ` + `unknown database "${name}". Add it to top-level ` + `notion_workspace.databases (run \`switchroom notion list-dbs\` ` + `to see the UUIDs the integration can read), or remove the ` + `reference.`);
14582
+ }
14583
+ }
14584
+ }
14585
+ return issues;
14586
+ }
14587
+
14529
14588
  // src/config/loader.ts
14530
14589
  var exports_loader = {};
14531
14590
  __export(exports_loader, {
@@ -14648,6 +14707,10 @@ function loadConfig(configPath) {
14648
14707
  }
14649
14708
  applyAgentOverlays(config);
14650
14709
  validateAllCronTopicAliases(config, filePath);
14710
+ const notionIssues = validateNotionWorkspaceConfig(config);
14711
+ if (notionIssues.length > 0) {
14712
+ throw new ConfigError(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
14713
+ }
14651
14714
  return config;
14652
14715
  }
14653
14716
  function validateAllCronTopicAliases(config, filePath) {
@@ -20778,6 +20841,20 @@ function getMs365McpSettingsEntry(switchroomCliPath, options = {}) {
20778
20841
  }
20779
20842
  };
20780
20843
  }
20844
+ function getNotionMcpSettingsEntry(switchroomCliPath, options = {}) {
20845
+ const args = ["notion-mcp-launcher"];
20846
+ if (options.vaultKey)
20847
+ args.push("--vault-key", options.vaultKey);
20848
+ if (options.mcpVersion)
20849
+ args.push("--mcp-version", options.mcpVersion);
20850
+ return {
20851
+ key: "notion",
20852
+ value: {
20853
+ command: switchroomCliPath,
20854
+ args
20855
+ }
20856
+ };
20857
+ }
20781
20858
  function getBuiltinDefaultMcpEntries() {
20782
20859
  const playwright = getPlaywrightMcpSettingsEntry();
20783
20860
  return [
@@ -20805,7 +20882,7 @@ function getBuiltinDefaultSkillEntries() {
20805
20882
  ...switchroomCore.map((key) => ({ key, optOutKey: key, source: "switchroom" }))
20806
20883
  ];
20807
20884
  }
20808
- var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "9d69115b63e6bc2ef0d4b5d7a3b962396382b44c", MICROSOFT_WORKSPACE_MCP_PINNED_VERSION = "0.113.0", MICROSOFT_WORKSPACE_MCP_PACKAGE = "@softeria/ms-365-mcp-server";
20885
+ var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "9d69115b63e6bc2ef0d4b5d7a3b962396382b44c", MICROSOFT_WORKSPACE_MCP_PINNED_VERSION = "0.113.0", MICROSOFT_WORKSPACE_MCP_PACKAGE = "@softeria/ms-365-mcp-server", NOTION_MCP_PINNED_VERSION = "1.8.1", NOTION_MCP_PACKAGE = "@notionhq/notion-mcp-server", NOTION_TOKEN_ENV = "NOTION_TOKEN";
20809
20886
  var init_scaffold_integration = __esm(() => {
20810
20887
  init_hindsight();
20811
20888
  });
@@ -29767,21 +29844,218 @@ function runMicrosoftChecks(config, deps = {}) {
29767
29844
  }
29768
29845
  var init_doctor_microsoft = () => {};
29769
29846
 
29770
- // src/cli/doctor-credentials-migration.ts
29847
+ // src/cli/doctor-notion.ts
29771
29848
  import {
29772
29849
  existsSync as realExistsSync3,
29773
- readdirSync as realReaddirSync,
29850
+ readFileSync as realReadFileSync3,
29774
29851
  statSync as realStatSync
29775
29852
  } from "node:fs";
29776
- import { homedir as homedir26 } from "node:os";
29777
29853
  import { join as join46 } from "node:path";
29854
+ import { homedir as homedir26 } from "node:os";
29855
+ function resolveDeps3(deps) {
29856
+ const home2 = deps.homeDir?.() ?? homedir26();
29857
+ return {
29858
+ existsSync: deps.existsSync ?? realExistsSync3,
29859
+ readFileSync: deps.readFileSync ?? realReadFileSync3,
29860
+ statSync: deps.statSync ?? realStatSync,
29861
+ agentsDir: join46(home2, ".switchroom", "agents"),
29862
+ now: deps.now ?? Date.now,
29863
+ vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
29864
+ };
29865
+ }
29866
+ function computeNotionEnabledAgents(config) {
29867
+ return Object.entries(config.agents ?? {}).filter(([, a]) => a && a.notion_workspace !== undefined).map(([name]) => name);
29868
+ }
29869
+ function checkConfigMatrix3(config) {
29870
+ const results = [];
29871
+ const agents = computeNotionEnabledAgents(config);
29872
+ if (agents.length === 0) {
29873
+ return [];
29874
+ }
29875
+ if (!config.notion_workspace) {
29876
+ results.push({
29877
+ name: "notion:top-level-block-present",
29878
+ status: "fail",
29879
+ detail: `${agents.length} agent(s) have notion_workspace: set but the top-level notion_workspace: block is missing`,
29880
+ fix: "Add `notion_workspace:` to switchroom.yaml with at least `databases:` populated. Run `switchroom notion list-dbs` to print a template after putting the integration token in the vault."
29881
+ });
29882
+ return results;
29883
+ }
29884
+ results.push({
29885
+ name: "notion:top-level-block-present",
29886
+ status: "ok"
29887
+ });
29888
+ const known = new Set(Object.keys(config.notion_workspace.databases ?? {}));
29889
+ let any = false;
29890
+ for (const name of agents) {
29891
+ const filter = config.agents?.[name]?.notion_workspace?.databases;
29892
+ if (!filter)
29893
+ continue;
29894
+ any = true;
29895
+ const missing = filter.filter((n) => !known.has(n));
29896
+ if (missing.length > 0) {
29897
+ results.push({
29898
+ name: `notion:db-references-resolvable:${name}`,
29899
+ status: "fail",
29900
+ detail: `agent '${name}' references unknown databases: ${missing.join(", ")}`,
29901
+ fix: `Add the database(s) to notion_workspace.databases in switchroom.yaml, or remove the references from agents.${name}.notion_workspace.databases. Run \`switchroom notion list-dbs\` to see UUIDs.`
29902
+ });
29903
+ } else {
29904
+ results.push({
29905
+ name: `notion:db-references-resolvable:${name}`,
29906
+ status: "ok",
29907
+ detail: `${filter.length} db(s) all resolved`
29908
+ });
29909
+ }
29910
+ }
29911
+ if (!any) {
29912
+ results.push({
29913
+ name: "notion:db-references-resolvable",
29914
+ status: "ok",
29915
+ detail: "no per-agent databases filter set; all admin-shaped"
29916
+ });
29917
+ }
29918
+ return results;
29919
+ }
29920
+ async function checkVaultAclAligned(config, notionAgents, d) {
29921
+ if (notionAgents.length === 0)
29922
+ return [];
29923
+ const key = config.notion_workspace?.vault_key ?? "notion/integration-token";
29924
+ const acl = await d.vaultAclReader(key);
29925
+ if (acl.kind === "unreachable") {
29926
+ return [
29927
+ {
29928
+ name: "notion:vault-acl-aligned",
29929
+ status: "warn",
29930
+ detail: `vault-broker unreachable: ${acl.msg}`,
29931
+ fix: "Ensure the vault-broker is running and the operator socket is reachable, then re-run doctor."
29932
+ }
29933
+ ];
29934
+ }
29935
+ if (acl.kind === "not_found") {
29936
+ return [
29937
+ {
29938
+ name: "notion:integration-token-present",
29939
+ status: "fail",
29940
+ detail: `vault key '${key}' is missing but ${notionAgents.length} agent(s) want Notion`,
29941
+ fix: `Create the Notion integration in Notion settings, then \`switchroom vault set ${key} --allow ${notionAgents.join(",")}\` on the host.`
29942
+ }
29943
+ ];
29944
+ }
29945
+ const allowedSet = new Set(acl.allow);
29946
+ const results = [];
29947
+ results.push({
29948
+ name: "notion:integration-token-present",
29949
+ status: "ok",
29950
+ detail: `vault key '${key}' exists; ${acl.allow.length} agent(s) on ACL`
29951
+ });
29952
+ for (const name of notionAgents) {
29953
+ if (!allowedSet.has(name)) {
29954
+ const updated = [...acl.allow, name].join(",");
29955
+ results.push({
29956
+ name: `notion:vault-acl-aligned:${name}`,
29957
+ status: "fail",
29958
+ detail: `agent '${name}' has notion_workspace: set but is NOT in the vault ACL for ${key} \u2014 launcher will 503 at runtime`,
29959
+ fix: `Re-run \`switchroom vault set ${key} --allow ${updated}\` on the host (vault set overwrites the scope, so re-state the full list including '${name}').`
29960
+ });
29961
+ } else {
29962
+ results.push({
29963
+ name: `notion:vault-acl-aligned:${name}`,
29964
+ status: "ok"
29965
+ });
29966
+ }
29967
+ }
29968
+ for (const a of acl.allow) {
29969
+ if (!notionAgents.includes(a)) {
29970
+ const trimmed = acl.allow.filter((x) => x !== a).join(",");
29971
+ results.push({
29972
+ name: `notion:vault-acl-aligned:${a}`,
29973
+ status: "warn",
29974
+ detail: `agent '${a}' is on the vault ACL for ${key} but has no notion_workspace: block \u2014 never reads the token`,
29975
+ fix: trimmed.length > 0 ? `Re-run \`switchroom vault set ${key} --allow ${trimmed}\` to drop '${a}' from the allowlist, or add a notion_workspace: block to agent '${a}' in switchroom.yaml.` : `Re-run \`switchroom vault set ${key} --allow ''\` to clear the allowlist (no other agent uses this key), or add a notion_workspace: block to agent '${a}' in switchroom.yaml.`
29976
+ });
29977
+ }
29978
+ }
29979
+ return results;
29980
+ }
29981
+ function checkLauncherHeartbeat2(notionAgents, d) {
29982
+ if (notionAgents.length === 0)
29983
+ return [];
29984
+ const results = [];
29985
+ for (const name of notionAgents) {
29986
+ const heartbeatPath = join46(d.agentsDir, name, "notion-launcher.heartbeat.json");
29987
+ if (!d.existsSync(heartbeatPath)) {
29988
+ results.push({
29989
+ name: `notion:launcher-heartbeat:${name}`,
29990
+ status: "warn",
29991
+ detail: `heartbeat file missing at ${heartbeatPath}`,
29992
+ fix: `Verify the launcher started \u2014 \`switchroom agent restart ${name} --wait\` and check the agent's logs for "notion-mcp-launcher" stderr.`
29993
+ });
29994
+ continue;
29995
+ }
29996
+ let mtimeMs;
29997
+ try {
29998
+ mtimeMs = d.statSync(heartbeatPath).mtimeMs;
29999
+ } catch (err) {
30000
+ results.push({
30001
+ name: `notion:launcher-heartbeat:${name}`,
30002
+ status: "warn",
30003
+ detail: `cannot stat heartbeat: ${err.message}`
30004
+ });
30005
+ continue;
30006
+ }
30007
+ const age = d.now() - mtimeMs;
30008
+ if (age > HEARTBEAT_STALE_MS) {
30009
+ results.push({
30010
+ name: `notion:launcher-heartbeat:${name}`,
30011
+ status: "warn",
30012
+ detail: `heartbeat is ${Math.round(age / 1000)}s old (stale: >${HEARTBEAT_STALE_MS / 1000}s)`,
30013
+ fix: `Restart the agent: \`switchroom agent restart ${name} --wait\`.`
30014
+ });
30015
+ } else {
30016
+ results.push({
30017
+ name: `notion:launcher-heartbeat:${name}`,
30018
+ status: "ok",
30019
+ detail: `heartbeat ${Math.round(age / 1000)}s old`
30020
+ });
30021
+ }
30022
+ }
30023
+ return results;
30024
+ }
30025
+ async function runNotionChecks(config, deps = {}) {
30026
+ const notionAgents = computeNotionEnabledAgents(config);
30027
+ if (notionAgents.length === 0 && !config.notion_workspace) {
30028
+ return [];
30029
+ }
30030
+ const d = resolveDeps3(deps);
30031
+ const results = [];
30032
+ results.push(...checkConfigMatrix3(config));
30033
+ if (notionAgents.length > 0 && config.notion_workspace) {
30034
+ results.push(...await checkVaultAclAligned(config, notionAgents, d));
30035
+ results.push(...checkLauncherHeartbeat2(notionAgents, d));
30036
+ }
30037
+ return results;
30038
+ }
30039
+ var HEARTBEAT_STALE_MS;
30040
+ var init_doctor_notion = __esm(() => {
30041
+ HEARTBEAT_STALE_MS = 60 * 1000;
30042
+ });
30043
+
30044
+ // src/cli/doctor-credentials-migration.ts
30045
+ import {
30046
+ existsSync as realExistsSync4,
30047
+ readdirSync as realReaddirSync,
30048
+ statSync as realStatSync2
30049
+ } from "node:fs";
30050
+ import { homedir as homedir27 } from "node:os";
30051
+ import { join as join47 } from "node:path";
29778
30052
  function runCredentialsMigrationChecks(config, deps = {}) {
29779
- const credDir = deps.credentialsDir ?? join46(homedir26(), ".switchroom", "credentials");
29780
- const existsSync49 = deps.existsSync ?? ((p) => realExistsSync3(p));
30053
+ const credDir = deps.credentialsDir ?? join47(homedir27(), ".switchroom", "credentials");
30054
+ const existsSync49 = deps.existsSync ?? ((p) => realExistsSync4(p));
29781
30055
  const readdirSync19 = deps.readdirSync ?? ((p) => realReaddirSync(p));
29782
30056
  const isDirectory = deps.isDirectory ?? ((p) => {
29783
30057
  try {
29784
- return realStatSync(p).isDirectory();
30058
+ return realStatSync2(p).isDirectory();
29785
30059
  } catch {
29786
30060
  return false;
29787
30061
  }
@@ -29804,7 +30078,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
29804
30078
  const flat = [];
29805
30079
  const perAgentDirs = [];
29806
30080
  for (const e of entries) {
29807
- const full = join46(credDir, e);
30081
+ const full = join47(credDir, e);
29808
30082
  if (isDirectory(full) && agentNames.has(e)) {
29809
30083
  perAgentDirs.push(e);
29810
30084
  } else {
@@ -29927,19 +30201,19 @@ var init_doctor_inlined_secrets = __esm(() => {
29927
30201
 
29928
30202
  // src/cli/doctor-audit-integrity.ts
29929
30203
  import { readFileSync as fsReadFileSync2 } from "node:fs";
29930
- import { homedir as homedir27 } from "node:os";
29931
- import { join as join47 } from "node:path";
30204
+ import { homedir as homedir28 } from "node:os";
30205
+ import { join as join48 } from "node:path";
29932
30206
  function rootWrittenLogs(home2) {
29933
30207
  return [
29934
- { label: "vault-broker", path: join47(home2, ".switchroom", "vault-audit.log") },
30208
+ { label: "vault-broker", path: join48(home2, ".switchroom", "vault-audit.log") },
29935
30209
  {
29936
30210
  label: "hostd",
29937
- path: join47(home2, ".switchroom", "host-control-audit.log")
30211
+ path: join48(home2, ".switchroom", "host-control-audit.log")
29938
30212
  }
29939
30213
  ];
29940
30214
  }
29941
30215
  function runAuditIntegrityChecks(deps = {}) {
29942
- const home2 = deps.homeDir ?? homedir27();
30216
+ const home2 = deps.homeDir ?? homedir28();
29943
30217
  const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
29944
30218
  const results = [];
29945
30219
  for (const { label, path: path4 } of rootWrittenLogs(home2)) {
@@ -30077,14 +30351,14 @@ var init_client4 = __esm(() => {
30077
30351
 
30078
30352
  // src/cli/doctor-agent-smoke.ts
30079
30353
  import { existsSync as existsSync49 } from "node:fs";
30080
- import { homedir as homedir28 } from "node:os";
30081
- import { join as join48 } from "node:path";
30354
+ import { homedir as homedir29 } from "node:os";
30355
+ import { join as join49 } from "node:path";
30082
30356
  import { randomUUID as randomUUID4 } from "node:crypto";
30083
30357
  async function runAgentSmokeChecks(config, deps = {}) {
30084
30358
  if (deps.fast)
30085
30359
  return [];
30086
- const home2 = deps.homeDir ?? homedir28();
30087
- const sock = deps.operatorSockPath ?? join48(home2, ".switchroom", "hostd", "operator", "sock");
30360
+ const home2 = deps.homeDir ?? homedir29();
30361
+ const sock = deps.operatorSockPath ?? join49(home2, ".switchroom", "hostd", "operator", "sock");
30088
30362
  if (!deps.hostdRequestImpl && !existsSync49(sock)) {
30089
30363
  return [
30090
30364
  {
@@ -30164,8 +30438,8 @@ var init_doctor_agent_smoke = __esm(() => {
30164
30438
  // src/cli/doctor-vault-broker-durability.ts
30165
30439
  import { execFileSync as execFileSync14 } from "node:child_process";
30166
30440
  import { existsSync as existsSync50, statSync as statSync22 } from "node:fs";
30167
- import { homedir as homedir29 } from "node:os";
30168
- import { join as join49 } from "node:path";
30441
+ import { homedir as homedir30 } from "node:os";
30442
+ import { join as join50 } from "node:path";
30169
30443
  function probeBindMountInode(hostPath, brokerContainerPath, opts) {
30170
30444
  const statHost = opts?.statHost ?? defaultStatHost;
30171
30445
  const statBroker = opts?.statBroker ?? defaultStatBroker;
@@ -30305,19 +30579,19 @@ function defaultBrokerStatusProbe() {
30305
30579
  }
30306
30580
  }
30307
30581
  function runVaultBrokerDurabilityChecks(_config, opts) {
30308
- const home2 = homedir29();
30582
+ const home2 = homedir30();
30309
30583
  const probe2 = opts?.inodeProbe ?? probeBindMountInode;
30310
30584
  return [
30311
30585
  probeBrokerUnlocked(opts?.statusProbe),
30312
30586
  probeAutoUnlockBlob(home2),
30313
30587
  probeMachineIdMount(),
30314
- formatBindMountResult("vault-broker: vault.enc bind mount", join49(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join49(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
30315
- formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join49(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join49(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
30316
- formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join49(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join49(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log"))
30588
+ formatBindMountResult("vault-broker: vault.enc bind mount", join50(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join50(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
30589
+ formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join50(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join50(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
30590
+ formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join50(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join50(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log"))
30317
30591
  ];
30318
30592
  }
30319
30593
  function probeAutoUnlockBlob(home2) {
30320
- const blobPath = join49(home2, ".switchroom", "vault-auto-unlock");
30594
+ const blobPath = join50(home2, ".switchroom", "vault-auto-unlock");
30321
30595
  if (!existsSync50(blobPath)) {
30322
30596
  return {
30323
30597
  name: "vault-broker: auto-unlock blob",
@@ -30428,16 +30702,16 @@ import {
30428
30702
  readdirSync as readdirSync19,
30429
30703
  statSync as statSync23
30430
30704
  } from "node:fs";
30431
- import { dirname as dirname12, join as join50, resolve as resolve30 } from "node:path";
30705
+ import { dirname as dirname12, join as join51, resolve as resolve30 } from "node:path";
30432
30706
  import { createPublicKey, createPrivateKey } from "node:crypto";
30433
30707
  function findInNvm(bin) {
30434
- const nvmRoot = join50(process.env.HOME ?? "", ".nvm", "versions", "node");
30708
+ const nvmRoot = join51(process.env.HOME ?? "", ".nvm", "versions", "node");
30435
30709
  if (!existsSync51(nvmRoot))
30436
30710
  return null;
30437
30711
  try {
30438
30712
  const versions = readdirSync19(nvmRoot).sort().reverse();
30439
30713
  for (const v of versions) {
30440
- const candidate = join50(nvmRoot, v, "bin", bin);
30714
+ const candidate = join51(nvmRoot, v, "bin", bin);
30441
30715
  try {
30442
30716
  const s = statSync23(candidate);
30443
30717
  if (s.isFile() || s.isSymbolicLink()) {
@@ -30602,7 +30876,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
30602
30876
  if (envBrowsersPath && envBrowsersPath.length > 0) {
30603
30877
  cacheLocations.push(envBrowsersPath);
30604
30878
  }
30605
- cacheLocations.push(join50(homeDir, ".cache", "ms-playwright"));
30879
+ cacheLocations.push(join51(homeDir, ".cache", "ms-playwright"));
30606
30880
  for (const cacheDir of cacheLocations) {
30607
30881
  if (!existsSync51(cacheDir))
30608
30882
  continue;
@@ -30610,10 +30884,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
30610
30884
  const entries = readdirSync19(cacheDir).filter((e) => e.startsWith("chromium"));
30611
30885
  for (const entry of entries) {
30612
30886
  const candidates2 = [
30613
- join50(cacheDir, entry, "chrome-linux64", "chrome"),
30614
- join50(cacheDir, entry, "chrome-linux", "chrome"),
30615
- join50(cacheDir, entry, "chrome-linux64", "headless_shell"),
30616
- join50(cacheDir, entry, "chrome-linux", "headless_shell")
30887
+ join51(cacheDir, entry, "chrome-linux64", "chrome"),
30888
+ join51(cacheDir, entry, "chrome-linux", "chrome"),
30889
+ join51(cacheDir, entry, "chrome-linux64", "headless_shell"),
30890
+ join51(cacheDir, entry, "chrome-linux", "headless_shell")
30617
30891
  ];
30618
30892
  for (const path4 of candidates2) {
30619
30893
  if (existsSync51(path4))
@@ -30723,7 +30997,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
30723
30997
  function checkLegacyState() {
30724
30998
  const results = [];
30725
30999
  const h = process.env.HOME ?? "/root";
30726
- const clerkDir = join50(h, LEGACY_STATE_DIR);
31000
+ const clerkDir = join51(h, LEGACY_STATE_DIR);
30727
31001
  const clerkPresent = existsSync51(clerkDir);
30728
31002
  results.push({
30729
31003
  name: "legacy ~/.clerk state",
@@ -30733,7 +31007,7 @@ function checkLegacyState() {
30733
31007
  fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
30734
31008
  } : {}
30735
31009
  });
30736
- const legacySock = join50(h, ".switchroom", "vault-broker.sock");
31010
+ const legacySock = join51(h, ".switchroom", "vault-broker.sock");
30737
31011
  let sockStat = null;
30738
31012
  try {
30739
31013
  sockStat = lstatSync5(legacySock);
@@ -31029,7 +31303,7 @@ async function checkHindsight(config) {
31029
31303
  }
31030
31304
  function checkPendingRetainsQueue(dir) {
31031
31305
  const home2 = process.env.HOME ?? "";
31032
- const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join50(home2, ".hindsight", "pending-retains");
31306
+ const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join51(home2, ".hindsight", "pending-retains");
31033
31307
  if (!existsSync51(pendingDir)) {
31034
31308
  return {
31035
31309
  name: "pending-retains queue",
@@ -31160,7 +31434,7 @@ async function checkTelegram(config) {
31160
31434
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
31161
31435
  if (plugin !== "switchroom")
31162
31436
  continue;
31163
- const envPath = join50(agentsDir, name, "telegram", ".env");
31437
+ const envPath = join51(agentsDir, name, "telegram", ".env");
31164
31438
  const read = tryReadHostFile(envPath);
31165
31439
  if (read.kind === "eacces") {
31166
31440
  results.push({
@@ -31243,7 +31517,7 @@ function checkStartShStale(agentName, startShPath) {
31243
31517
  }
31244
31518
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
31245
31519
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
31246
- const path4 = join50(agentDir, "home", ".switchroom");
31520
+ const path4 = join51(agentDir, "home", ".switchroom");
31247
31521
  let stats;
31248
31522
  try {
31249
31523
  stats = lstatSync5(path4);
@@ -31280,7 +31554,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
31280
31554
  }
31281
31555
  function checkRepoHygiene(repoRoot) {
31282
31556
  const results = [];
31283
- const exportDir = join50(repoRoot, "clerk-export");
31557
+ const exportDir = join51(repoRoot, "clerk-export");
31284
31558
  if (existsSync51(exportDir)) {
31285
31559
  results.push({
31286
31560
  name: "repo hygiene: clerk-export/ on disk (#1072)",
@@ -31289,7 +31563,7 @@ function checkRepoHygiene(repoRoot) {
31289
31563
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
31290
31564
  });
31291
31565
  }
31292
- const knownTarball = join50(repoRoot, "clerk-export-with-secrets.tar.gz");
31566
+ const knownTarball = join51(repoRoot, "clerk-export-with-secrets.tar.gz");
31293
31567
  if (existsSync51(knownTarball)) {
31294
31568
  results.push({
31295
31569
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
@@ -31307,7 +31581,7 @@ function checkRepoHygiene(repoRoot) {
31307
31581
  results.push({
31308
31582
  name: `repo hygiene: ${name} on disk (#1072)`,
31309
31583
  status: "warn",
31310
- detail: `${join50(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
31584
+ detail: `${join51(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
31311
31585
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
31312
31586
  });
31313
31587
  }
@@ -31330,9 +31604,9 @@ function checkRepoHygiene(repoRoot) {
31330
31604
  }
31331
31605
  function isSwitchroomCheckout(dir) {
31332
31606
  try {
31333
- if (!existsSync51(join50(dir, ".git")))
31607
+ if (!existsSync51(join51(dir, ".git")))
31334
31608
  return false;
31335
- const pkgPath = join50(dir, "package.json");
31609
+ const pkgPath = join51(dir, "package.json");
31336
31610
  if (!existsSync51(pkgPath))
31337
31611
  return false;
31338
31612
  const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
@@ -31369,7 +31643,7 @@ function checkAgents(config, configPath) {
31369
31643
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
31370
31644
  });
31371
31645
  }
31372
- results.push(checkStartShStale(name, join50(agentDir, "start.sh")));
31646
+ results.push(checkStartShStale(name, join51(agentDir, "start.sh")));
31373
31647
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
31374
31648
  const status = statuses[name];
31375
31649
  const active = status?.active ?? "unknown";
@@ -31446,7 +31720,7 @@ function checkAgents(config, configPath) {
31446
31720
  }
31447
31721
  }
31448
31722
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
31449
- const mcpJsonPath = join50(agentDir, ".mcp.json");
31723
+ const mcpJsonPath = join51(agentDir, ".mcp.json");
31450
31724
  if (!existsSync51(mcpJsonPath)) {
31451
31725
  results.push({
31452
31726
  name: `${name}: .mcp.json`,
@@ -31748,7 +32022,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
31748
32022
  };
31749
32023
  }
31750
32024
  const credDir = dirname12(envPath);
31751
- const authScript = join50(credDir, "claude-auth.py");
32025
+ const authScript = join51(credDir, "claude-auth.py");
31752
32026
  if (!existsSync51(authScript)) {
31753
32027
  return {
31754
32028
  name: "mff: auth flow",
@@ -32055,6 +32329,28 @@ function registerDoctorCommand(program3) {
32055
32329
  title: "Microsoft 365 (RFC #1873)",
32056
32330
  results: runMicrosoftChecks(config)
32057
32331
  },
32332
+ {
32333
+ title: "Notion (RFC notion-integration)",
32334
+ results: await runNotionChecks(config, {
32335
+ vaultAclReader: async (key) => {
32336
+ try {
32337
+ const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
32338
+ const result = await getViaBrokerStructured2(key);
32339
+ if (result.kind === "ok") {
32340
+ return {
32341
+ kind: "ok",
32342
+ allow: result.entry.scope?.allow ?? []
32343
+ };
32344
+ }
32345
+ if (result.kind === "not_found")
32346
+ return { kind: "not_found" };
32347
+ return { kind: "unreachable", msg: result.msg };
32348
+ } catch (err) {
32349
+ return { kind: "unreachable", msg: err.message };
32350
+ }
32351
+ }
32352
+ })
32353
+ },
32058
32354
  { title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) }
32059
32355
  ];
32060
32356
  const cwd = process.cwd();
@@ -32115,6 +32411,7 @@ var init_doctor = __esm(() => {
32115
32411
  init_doctor_hostd();
32116
32412
  init_doctor_drive();
32117
32413
  init_doctor_microsoft();
32414
+ init_doctor_notion();
32118
32415
  init_doctor_credentials_migration();
32119
32416
  init_doctor_secret_access();
32120
32417
  init_doctor_inlined_secrets();
@@ -48222,7 +48519,7 @@ __export(exports_server2, {
48222
48519
  TOOLS: () => TOOLS2
48223
48520
  });
48224
48521
  import { randomBytes as randomBytes15 } from "node:crypto";
48225
- import { existsSync as existsSync78, readFileSync as readFileSync65 } from "node:fs";
48522
+ import { existsSync as existsSync79, readFileSync as readFileSync65 } from "node:fs";
48226
48523
  function selfSocketPath() {
48227
48524
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
48228
48525
  }
@@ -48237,7 +48534,7 @@ async function dispatchTool2(name, args) {
48237
48534
  return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
48238
48535
  }
48239
48536
  const sockPath = selfSocketPath();
48240
- if (!existsSync78(sockPath)) {
48537
+ if (!existsSync79(sockPath)) {
48241
48538
  return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
48242
48539
  }
48243
48540
  let req;
@@ -48389,13 +48686,13 @@ function resolveAuditLogPath() {
48389
48686
  if (process.env.HOSTD_AUDIT_LOG_PATH)
48390
48687
  return process.env.HOSTD_AUDIT_LOG_PATH;
48391
48688
  const bindMounted = "/host-home/.switchroom/host-control-audit.log";
48392
- if (existsSync78(bindMounted))
48689
+ if (existsSync79(bindMounted))
48393
48690
  return bindMounted;
48394
48691
  return defaultAuditLogPath2();
48395
48692
  }
48396
48693
  function getLastUpdateApplyStatus() {
48397
48694
  const path8 = resolveAuditLogPath();
48398
- if (!existsSync78(path8)) {
48695
+ if (!existsSync79(path8)) {
48399
48696
  return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
48400
48697
  }
48401
48698
  let raw;
@@ -48633,8 +48930,8 @@ var {
48633
48930
  } = import__.default;
48634
48931
 
48635
48932
  // src/build-info.ts
48636
- var VERSION = "0.13.54";
48637
- var COMMIT_SHA = "95784728";
48933
+ var VERSION = "0.13.56";
48934
+ var COMMIT_SHA = "821a114e";
48638
48935
 
48639
48936
  // src/cli/agent.ts
48640
48937
  init_source();
@@ -50156,6 +50453,28 @@ function resolveMs365McpEntry(agentName, agentConfig, switchroomConfig) {
50156
50453
  };
50157
50454
  return entry;
50158
50455
  }
50456
+ function resolveNotionMcpEntry(agentName, agentConfig, switchroomConfig) {
50457
+ if ((agentConfig.mcp_servers ?? {})["notion"] === false)
50458
+ return null;
50459
+ if (!switchroomConfig)
50460
+ return null;
50461
+ if (!shouldEmitNotionMcp(agentName, switchroomConfig))
50462
+ return null;
50463
+ const vaultKey = switchroomConfig.notion_workspace?.vault_key ?? "notion/integration-token";
50464
+ const mcpVersion = switchroomConfig.notion_workspace?.mcp_version;
50465
+ const entry = getNotionMcpSettingsEntry(DOCKER_SWITCHROOM_CLI_PATH, {
50466
+ vaultKey,
50467
+ mcpVersion
50468
+ });
50469
+ entry.value.env = {
50470
+ SWITCHROOM_CONFIG: DOCKER_CONFIG_PATH,
50471
+ SWITCHROOM_AGENT_NAME: agentName,
50472
+ SWITCHROOM_CONTAINER: "1",
50473
+ SWITCHROOM_VAULT_BROKER_SOCK: DOCKER_VAULT_BROKER_SOCKET,
50474
+ HOME: DOCKER_AGENT_HOME
50475
+ };
50476
+ return entry;
50477
+ }
50159
50478
  function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchroomConfig, userIdOverride, switchroomConfigPath) {
50160
50479
  const agentConfig = resolveAgentConfig(switchroomConfig?.defaults, switchroomConfig?.profiles, agentConfigRaw);
50161
50480
  const agentDir = resolve10(agentsDir, name);
@@ -50273,6 +50592,10 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
50273
50592
  if (ms365 && !settings.mcpServers[ms365.key]) {
50274
50593
  settings.mcpServers[ms365.key] = ms365.value;
50275
50594
  }
50595
+ const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
50596
+ if (notion && !settings.mcpServers[notion.key]) {
50597
+ settings.mcpServers[notion.key] = notion.value;
50598
+ }
50276
50599
  }
50277
50600
  installHindsightPlugin(name, agentDir, switchroomConfig);
50278
50601
  const hindsightOn = isHindsightEnabled(switchroomConfig) && switchroomConfig.agents[name]?.memory?.auto_recall !== false;
@@ -50346,6 +50669,10 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
50346
50669
  if (ms365) {
50347
50670
  mcpServers[ms365.key] = ms365.value;
50348
50671
  }
50672
+ const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
50673
+ if (notion) {
50674
+ mcpServers[notion.key] = notion.value;
50675
+ }
50349
50676
  }
50350
50677
  if (agentConfig.mcp_servers) {
50351
50678
  const filtered = filterMcpServers(agentConfig.mcp_servers);
@@ -50738,6 +51065,25 @@ function buildSettingsHooksBlock(p) {
50738
51065
  }
50739
51066
  ]
50740
51067
  },
51068
+ {
51069
+ matcher: "^mcp__notion__",
51070
+ hooks: [
51071
+ {
51072
+ type: "command",
51073
+ command: wrap("hook:notion-write-pretool", `node "${join8(DOCKER_BUNDLED_HOOKS_PATH, "notion-write-pretool.mjs")}"`),
51074
+ timeout: 30
51075
+ }
51076
+ ]
51077
+ },
51078
+ {
51079
+ hooks: [
51080
+ {
51081
+ type: "command",
51082
+ command: wrap("hook:ack-first-pretool", `node "${join8(DOCKER_BUNDLED_HOOKS_PATH, "ack-first-pretool.mjs")}"`),
51083
+ timeout: 5
51084
+ }
51085
+ ]
51086
+ },
50741
51087
  {
50742
51088
  matcher: "^(Write|Edit|MultiEdit)$",
50743
51089
  hooks: [
@@ -51144,6 +51490,12 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
51144
51490
  } else {
51145
51491
  delete mcpServers["ms-365"];
51146
51492
  }
51493
+ const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
51494
+ if (notion) {
51495
+ mcpServers[notion.key] = notion.value;
51496
+ } else {
51497
+ delete mcpServers["notion"];
51498
+ }
51147
51499
  }
51148
51500
  if (agentConfig.mcp_servers) {
51149
51501
  for (const [key, value] of Object.entries(agentConfig.mcp_servers)) {
@@ -51333,6 +51685,10 @@ ${body}
51333
51685
  if (ms365) {
51334
51686
  mcpServers[ms365.key] = ms365.value;
51335
51687
  }
51688
+ const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
51689
+ if (notion) {
51690
+ mcpServers[notion.key] = notion.value;
51691
+ }
51336
51692
  }
51337
51693
  if (agentConfig.mcp_servers) {
51338
51694
  const filtered = filterMcpServers(agentConfig.mcp_servers);
@@ -71826,15 +72182,15 @@ init_loader();
71826
72182
  init_lifecycle();
71827
72183
  import { cpSync as cpSync2, existsSync as existsSync52, mkdirSync as mkdirSync28, readFileSync as readFileSync47, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync24 } from "node:fs";
71828
72184
  import { spawnSync as spawnSync8 } from "node:child_process";
71829
- import { join as join51, dirname as dirname13, resolve as resolve31 } from "node:path";
71830
- import { homedir as homedir30 } from "node:os";
71831
- var DEFAULT_COMPOSE_PATH = join51(homedir30(), ".switchroom", "compose", "docker-compose.yml");
72185
+ import { join as join52, dirname as dirname13, resolve as resolve31 } from "node:path";
72186
+ import { homedir as homedir31 } from "node:os";
72187
+ var DEFAULT_COMPOSE_PATH = join52(homedir31(), ".switchroom", "compose", "docker-compose.yml");
71832
72188
  function runningFromSwitchroomCheckout(scriptPath) {
71833
72189
  let dir = dirname13(scriptPath);
71834
72190
  for (let i = 0;i < 12; i++) {
71835
- if (existsSync52(join51(dir, ".git"))) {
72191
+ if (existsSync52(join52(dir, ".git"))) {
71836
72192
  try {
71837
- const pkg = JSON.parse(readFileSync47(join51(dir, "package.json"), "utf-8"));
72193
+ const pkg = JSON.parse(readFileSync47(join52(dir, "package.json"), "utf-8"));
71838
72194
  if (pkg.name === "switchroom")
71839
72195
  return true;
71840
72196
  } catch {}
@@ -71965,7 +72321,7 @@ function planUpdate(opts) {
71965
72321
  return;
71966
72322
  }
71967
72323
  const source = resolve31(import.meta.dirname, "../../skills");
71968
- const dest = join51(homedir30(), ".switchroom", "skills", "_bundled");
72324
+ const dest = join52(homedir31(), ".switchroom", "skills", "_bundled");
71969
72325
  if (!existsSync52(source)) {
71970
72326
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
71971
72327
  `);
@@ -72079,7 +72435,7 @@ function defaultStatusProbe(composePath) {
72079
72435
  } catch {}
72080
72436
  let dir = dirname13(scriptPath);
72081
72437
  for (let i = 0;i < 8; i++) {
72082
- const pkgPath = join51(dir, "package.json");
72438
+ const pkgPath = join52(dir, "package.json");
72083
72439
  if (existsSync52(pkgPath)) {
72084
72440
  try {
72085
72441
  const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
@@ -72299,7 +72655,7 @@ init_helpers();
72299
72655
  init_lifecycle();
72300
72656
  import { execSync as execSync4 } from "node:child_process";
72301
72657
  import { existsSync as existsSync53, readFileSync as readFileSync48 } from "node:fs";
72302
- import { dirname as dirname14, join as join52 } from "node:path";
72658
+ import { dirname as dirname14, join as join53 } from "node:path";
72303
72659
  function getClaudeCodeVersion() {
72304
72660
  try {
72305
72661
  const out = execSync4("claude --version 2>/dev/null", {
@@ -72349,11 +72705,11 @@ function formatUptime3(timestamp) {
72349
72705
  function locateSwitchroomInstallDir() {
72350
72706
  let dir = import.meta.dirname;
72351
72707
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
72352
- const pkgPath = join52(dir, "package.json");
72708
+ const pkgPath = join53(dir, "package.json");
72353
72709
  if (existsSync53(pkgPath)) {
72354
72710
  try {
72355
72711
  const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
72356
- if (pkg.name === "switchroom" && existsSync53(join52(dir, ".git"))) {
72712
+ if (pkg.name === "switchroom" && existsSync53(join53(dir, ".git"))) {
72357
72713
  return dir;
72358
72714
  }
72359
72715
  } catch {}
@@ -72590,7 +72946,7 @@ import {
72590
72946
  writeFileSync as writeFileSync26,
72591
72947
  writeSync as writeSync7
72592
72948
  } from "node:fs";
72593
- import { join as join53 } from "node:path";
72949
+ import { join as join54 } from "node:path";
72594
72950
  import { randomBytes as randomBytes12 } from "node:crypto";
72595
72951
  import { execSync as execSync5 } from "node:child_process";
72596
72952
 
@@ -72910,7 +73266,7 @@ function redactedMarker(ruleId) {
72910
73266
  var ISSUES_FILE = "issues.jsonl";
72911
73267
  var ISSUES_LOCK = "issues.lock";
72912
73268
  function readAll(stateDir) {
72913
- const path4 = join53(stateDir, ISSUES_FILE);
73269
+ const path4 = join54(stateDir, ISSUES_FILE);
72914
73270
  if (!existsSync54(path4))
72915
73271
  return [];
72916
73272
  let raw;
@@ -72988,7 +73344,7 @@ function record(stateDir, input, nowFn = Date.now) {
72988
73344
  });
72989
73345
  }
72990
73346
  function resolve34(stateDir, fingerprint, nowFn = Date.now) {
72991
- if (!existsSync54(join53(stateDir, ISSUES_FILE)))
73347
+ if (!existsSync54(join54(stateDir, ISSUES_FILE)))
72992
73348
  return 0;
72993
73349
  return withLock(stateDir, () => {
72994
73350
  const all = readAll(stateDir);
@@ -73006,7 +73362,7 @@ function resolve34(stateDir, fingerprint, nowFn = Date.now) {
73006
73362
  });
73007
73363
  }
73008
73364
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
73009
- if (!existsSync54(join53(stateDir, ISSUES_FILE)))
73365
+ if (!existsSync54(join54(stateDir, ISSUES_FILE)))
73010
73366
  return 0;
73011
73367
  return withLock(stateDir, () => {
73012
73368
  const all = readAll(stateDir);
@@ -73024,7 +73380,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
73024
73380
  });
73025
73381
  }
73026
73382
  function prune(stateDir, opts = {}) {
73027
- if (!existsSync54(join53(stateDir, ISSUES_FILE)))
73383
+ if (!existsSync54(join54(stateDir, ISSUES_FILE)))
73028
73384
  return 0;
73029
73385
  return withLock(stateDir, () => {
73030
73386
  const all = readAll(stateDir);
@@ -73057,7 +73413,7 @@ function ensureDir(stateDir) {
73057
73413
  mkdirSync29(stateDir, { recursive: true });
73058
73414
  }
73059
73415
  function writeAll(stateDir, events) {
73060
- const path4 = join53(stateDir, ISSUES_FILE);
73416
+ const path4 = join54(stateDir, ISSUES_FILE);
73061
73417
  sweepOrphanTmpFiles(stateDir);
73062
73418
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
73063
73419
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -73079,7 +73435,7 @@ function sweepOrphanTmpFiles(stateDir) {
73079
73435
  for (const entry of entries) {
73080
73436
  if (!entry.startsWith(TMP_PREFIX))
73081
73437
  continue;
73082
- const tmpPath = join53(stateDir, entry);
73438
+ const tmpPath = join54(stateDir, entry);
73083
73439
  try {
73084
73440
  const stat = statSync25(tmpPath);
73085
73441
  if (stat.mtimeMs < cutoff) {
@@ -73091,7 +73447,7 @@ function sweepOrphanTmpFiles(stateDir) {
73091
73447
  var LOCK_RETRY_MS = 25;
73092
73448
  var LOCK_TIMEOUT_MS = 1e4;
73093
73449
  function withLock(stateDir, fn) {
73094
- const lockPath = join53(stateDir, ISSUES_LOCK);
73450
+ const lockPath = join54(stateDir, ISSUES_LOCK);
73095
73451
  const startedAt = Date.now();
73096
73452
  let fd = null;
73097
73453
  while (fd === null) {
@@ -73375,8 +73731,8 @@ function relTime(deltaMs) {
73375
73731
  // src/cli/deps.ts
73376
73732
  init_source();
73377
73733
  import { existsSync as existsSync57 } from "node:fs";
73378
- import { homedir as homedir33 } from "node:os";
73379
- import { join as join56, resolve as resolve35 } from "node:path";
73734
+ import { homedir as homedir34 } from "node:os";
73735
+ import { join as join57, resolve as resolve35 } from "node:path";
73380
73736
 
73381
73737
  // src/deps/python.ts
73382
73738
  import { createHash as createHash11 } from "node:crypto";
@@ -73387,8 +73743,8 @@ import {
73387
73743
  rmSync as rmSync13,
73388
73744
  writeFileSync as writeFileSync27
73389
73745
  } from "node:fs";
73390
- import { dirname as dirname15, join as join54 } from "node:path";
73391
- import { homedir as homedir31 } from "node:os";
73746
+ import { dirname as dirname15, join as join55 } from "node:path";
73747
+ import { homedir as homedir32 } from "node:os";
73392
73748
  import { execFileSync as execFileSync15 } from "node:child_process";
73393
73749
 
73394
73750
  class PythonEnvError extends Error {
@@ -73400,7 +73756,7 @@ class PythonEnvError extends Error {
73400
73756
  }
73401
73757
  }
73402
73758
  function defaultPythonCacheRoot() {
73403
- return join54(homedir31(), ".switchroom", "deps", "python");
73759
+ return join55(homedir32(), ".switchroom", "deps", "python");
73404
73760
  }
73405
73761
  function hashFile(path4) {
73406
73762
  return createHash11("sha256").update(readFileSync50(path4)).digest("hex");
@@ -73412,11 +73768,11 @@ function ensurePythonEnv(opts) {
73412
73768
  if (!existsSync55(requirementsPath)) {
73413
73769
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
73414
73770
  }
73415
- const venvDir = join54(cacheRoot, skillName);
73416
- const stampPath = join54(venvDir, ".requirements.sha256");
73417
- const binDir = join54(venvDir, "bin");
73418
- const pythonBin = join54(binDir, "python");
73419
- const pipBin = join54(binDir, "pip");
73771
+ const venvDir = join55(cacheRoot, skillName);
73772
+ const stampPath = join55(venvDir, ".requirements.sha256");
73773
+ const binDir = join55(venvDir, "bin");
73774
+ const pythonBin = join55(binDir, "python");
73775
+ const pipBin = join55(binDir, "pip");
73420
73776
  const targetHash = hashFile(requirementsPath);
73421
73777
  if (!force && existsSync55(stampPath) && existsSync55(pythonBin)) {
73422
73778
  const existingHash = readFileSync50(stampPath, "utf8").trim();
@@ -73475,8 +73831,8 @@ import {
73475
73831
  rmSync as rmSync14,
73476
73832
  writeFileSync as writeFileSync28
73477
73833
  } from "node:fs";
73478
- import { dirname as dirname16, join as join55 } from "node:path";
73479
- import { homedir as homedir32 } from "node:os";
73834
+ import { dirname as dirname16, join as join56 } from "node:path";
73835
+ import { homedir as homedir33 } from "node:os";
73480
73836
  import { execFileSync as execFileSync16 } from "node:child_process";
73481
73837
 
73482
73838
  class NodeEnvError extends Error {
@@ -73499,7 +73855,7 @@ var LOCKFILES_FOR = {
73499
73855
  npm: ["package-lock.json"]
73500
73856
  };
73501
73857
  function defaultNodeCacheRoot() {
73502
- return join55(homedir32(), ".switchroom", "deps", "node");
73858
+ return join56(homedir33(), ".switchroom", "deps", "node");
73503
73859
  }
73504
73860
  function hashDepInputs(packageJsonPath) {
73505
73861
  const sourceDir = dirname16(packageJsonPath);
@@ -73508,7 +73864,7 @@ function hashDepInputs(packageJsonPath) {
73508
73864
  `);
73509
73865
  hasher.update(readFileSync51(packageJsonPath));
73510
73866
  for (const lockName of ALL_LOCKFILES) {
73511
- const lockPath = join55(sourceDir, lockName);
73867
+ const lockPath = join56(sourceDir, lockName);
73512
73868
  if (existsSync56(lockPath)) {
73513
73869
  hasher.update(`
73514
73870
  `);
@@ -73528,10 +73884,10 @@ function ensureNodeEnv(opts) {
73528
73884
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
73529
73885
  }
73530
73886
  const sourceDir = dirname16(packageJsonPath);
73531
- const envDir = join55(cacheRoot, skillName);
73532
- const stampPath = join55(envDir, ".package.sha256");
73533
- const nodeModulesDir = join55(envDir, "node_modules");
73534
- const binDir = join55(nodeModulesDir, ".bin");
73887
+ const envDir = join56(cacheRoot, skillName);
73888
+ const stampPath = join56(envDir, ".package.sha256");
73889
+ const nodeModulesDir = join56(envDir, "node_modules");
73890
+ const binDir = join56(nodeModulesDir, ".bin");
73535
73891
  const targetHash = hashDepInputs(packageJsonPath);
73536
73892
  if (!force && existsSync56(stampPath) && existsSync56(nodeModulesDir)) {
73537
73893
  const existingHash = readFileSync51(stampPath, "utf8").trim();
@@ -73549,12 +73905,12 @@ function ensureNodeEnv(opts) {
73549
73905
  rmSync14(envDir, { recursive: true, force: true });
73550
73906
  }
73551
73907
  mkdirSync31(envDir, { recursive: true });
73552
- copyFileSync9(packageJsonPath, join55(envDir, "package.json"));
73908
+ copyFileSync9(packageJsonPath, join56(envDir, "package.json"));
73553
73909
  let copiedLockfile = false;
73554
73910
  for (const lockName of LOCKFILES_FOR[installer]) {
73555
- const lockPath = join55(sourceDir, lockName);
73911
+ const lockPath = join56(sourceDir, lockName);
73556
73912
  if (existsSync56(lockPath)) {
73557
- copyFileSync9(lockPath, join55(envDir, lockName));
73913
+ copyFileSync9(lockPath, join56(envDir, lockName));
73558
73914
  copiedLockfile = true;
73559
73915
  }
73560
73916
  }
@@ -73583,7 +73939,7 @@ function ensureNodeEnv(opts) {
73583
73939
 
73584
73940
  // src/cli/deps.ts
73585
73941
  function builtinSkillsRoot() {
73586
- return resolve35(homedir33(), ".switchroom/skills/_bundled");
73942
+ return resolve35(homedir34(), ".switchroom/skills/_bundled");
73587
73943
  }
73588
73944
  function registerDepsCommand(program3) {
73589
73945
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -73593,13 +73949,13 @@ function registerDepsCommand(program3) {
73593
73949
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
73594
73950
  process.exit(1);
73595
73951
  }
73596
- const skillDir = join56(skillsRoot, skill);
73952
+ const skillDir = join57(skillsRoot, skill);
73597
73953
  if (!existsSync57(skillDir)) {
73598
73954
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
73599
73955
  process.exit(1);
73600
73956
  }
73601
- const requirementsPath = join56(skillDir, "requirements.txt");
73602
- const packageJsonPath = join56(skillDir, "package.json");
73957
+ const requirementsPath = join57(skillDir, "requirements.txt");
73958
+ const packageJsonPath = join57(skillDir, "package.json");
73603
73959
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync57(requirementsPath));
73604
73960
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync57(packageJsonPath));
73605
73961
  let did = 0;
@@ -74554,7 +74910,7 @@ init_helpers();
74554
74910
  init_loader();
74555
74911
  init_merge();
74556
74912
  import { copyFileSync as copyFileSync10, existsSync as existsSync59, readFileSync as readFileSync52, writeFileSync as writeFileSync29 } from "node:fs";
74557
- import { join as join57, resolve as resolve37 } from "node:path";
74913
+ import { join as join58, resolve as resolve37 } from "node:path";
74558
74914
  init_schema();
74559
74915
  function resolveSoulTargetOrExit(program3, agentName) {
74560
74916
  const config = getConfig(program3);
@@ -74578,7 +74934,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
74578
74934
  profileName,
74579
74935
  profilePath,
74580
74936
  workspaceDir,
74581
- soulPath: join57(workspaceDir, "SOUL.md"),
74937
+ soulPath: join58(workspaceDir, "SOUL.md"),
74582
74938
  soul: merged.soul
74583
74939
  };
74584
74940
  }
@@ -74645,7 +75001,7 @@ function registerSoulCommand(program3) {
74645
75001
  init_helpers();
74646
75002
  init_loader();
74647
75003
  import { existsSync as existsSync60, readFileSync as readFileSync53, readdirSync as readdirSync21, statSync as statSync26 } from "node:fs";
74648
- import { resolve as resolve38, join as join58 } from "node:path";
75004
+ import { resolve as resolve38, join as join59 } from "node:path";
74649
75005
  import { createHash as createHash13 } from "node:crypto";
74650
75006
  init_merge();
74651
75007
  init_hindsight();
@@ -74659,7 +75015,7 @@ function sha256(content) {
74659
75015
  return createHash13("sha256").update(content).digest("hex").slice(0, 16);
74660
75016
  }
74661
75017
  function findLatestTranscriptJsonl(claudeConfigDir) {
74662
- const projectsDir = join58(claudeConfigDir, "projects");
75018
+ const projectsDir = join59(claudeConfigDir, "projects");
74663
75019
  if (!existsSync60(projectsDir))
74664
75020
  return;
74665
75021
  try {
@@ -74668,8 +75024,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
74668
75024
  for (const entry of entries) {
74669
75025
  if (!entry.isDirectory())
74670
75026
  continue;
74671
- const projectPath = join58(projectsDir, entry.name);
74672
- const transcriptPath = join58(projectPath, "transcript.jsonl");
75027
+ const projectPath = join59(projectsDir, entry.name);
75028
+ const transcriptPath = join59(projectPath, "transcript.jsonl");
74673
75029
  if (!existsSync60(transcriptPath))
74674
75030
  continue;
74675
75031
  const stat3 = statSync26(transcriptPath);
@@ -74738,11 +75094,11 @@ function registerDebugCommand(program3) {
74738
75094
  process.exit(1);
74739
75095
  }
74740
75096
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
74741
- const claudeConfigDir = join58(agentDir, ".claude");
74742
- const claudeMdPath = join58(agentDir, "CLAUDE.md");
74743
- const soulMdPath = join58(agentDir, "SOUL.md");
74744
- const workspaceSoulMdPath = join58(workspaceDir, "SOUL.md");
74745
- const handoffPath = join58(agentDir, ".handoff.md");
75097
+ const claudeConfigDir = join59(agentDir, ".claude");
75098
+ const claudeMdPath = join59(agentDir, "CLAUDE.md");
75099
+ const soulMdPath = join59(agentDir, "SOUL.md");
75100
+ const workspaceSoulMdPath = join59(workspaceDir, "SOUL.md");
75101
+ const handoffPath = join59(agentDir, ".handoff.md");
74746
75102
  const lastN = parseInt(opts.last, 10);
74747
75103
  if (isNaN(lastN) || lastN < 1) {
74748
75104
  console.error("--last must be a positive integer");
@@ -74892,8 +75248,8 @@ init_source();
74892
75248
  // src/worktree/claim.ts
74893
75249
  import { execFileSync as execFileSync17 } from "node:child_process";
74894
75250
  import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync62, unlinkSync as unlinkSync13 } from "node:fs";
74895
- import { join as join60, resolve as resolve40 } from "node:path";
74896
- import { homedir as homedir35 } from "node:os";
75251
+ import { join as join61, resolve as resolve40 } from "node:path";
75252
+ import { homedir as homedir36 } from "node:os";
74897
75253
  import { randomBytes as randomBytes13 } from "node:crypto";
74898
75254
 
74899
75255
  // src/worktree/registry.ts
@@ -74906,13 +75262,13 @@ import {
74906
75262
  existsSync as existsSync61,
74907
75263
  renameSync as renameSync12
74908
75264
  } from "node:fs";
74909
- import { join as join59, resolve as resolve39 } from "node:path";
74910
- import { homedir as homedir34 } from "node:os";
75265
+ import { join as join60, resolve as resolve39 } from "node:path";
75266
+ import { homedir as homedir35 } from "node:os";
74911
75267
  function registryDir() {
74912
- return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join59(homedir34(), ".switchroom", "worktrees"));
75268
+ return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join60(homedir35(), ".switchroom", "worktrees"));
74913
75269
  }
74914
75270
  function recordPath(id) {
74915
- return join59(registryDir(), `${id}.json`);
75271
+ return join60(registryDir(), `${id}.json`);
74916
75272
  }
74917
75273
  function ensureDir2() {
74918
75274
  mkdirSync32(registryDir(), { recursive: true });
@@ -74963,7 +75319,7 @@ function acquireRepoLock(repoPath) {
74963
75319
  const lockDir = registryDir();
74964
75320
  mkdirSync33(lockDir, { recursive: true });
74965
75321
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
74966
- const lockPath = join60(lockDir, `.lock-${lockName}`);
75322
+ const lockPath = join61(lockDir, `.lock-${lockName}`);
74967
75323
  const deadline = Date.now() + 5000;
74968
75324
  let fd = null;
74969
75325
  while (fd === null) {
@@ -74990,7 +75346,7 @@ function acquireRepoLock(repoPath) {
74990
75346
  }
74991
75347
  var DEFAULT_CONCURRENCY = 5;
74992
75348
  function worktreesBaseDir() {
74993
- return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join60(homedir35(), ".switchroom", "worktree-checkouts"));
75349
+ return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join61(homedir36(), ".switchroom", "worktree-checkouts"));
74994
75350
  }
74995
75351
  function shortId() {
74996
75352
  return randomBytes13(4).toString("hex");
@@ -75012,7 +75368,7 @@ function resolveRepoPath(repo, codeRepos) {
75012
75368
  }
75013
75369
  function expandHome(p) {
75014
75370
  if (p.startsWith("~/"))
75015
- return join60(homedir35(), p.slice(2));
75371
+ return join61(homedir36(), p.slice(2));
75016
75372
  return p;
75017
75373
  }
75018
75374
  async function claimWorktree(input, codeRepos) {
@@ -75040,7 +75396,7 @@ async function claimWorktree(input, codeRepos) {
75040
75396
  branch = `task/${taskSuffix}-${id}`;
75041
75397
  const baseDir = worktreesBaseDir();
75042
75398
  mkdirSync33(baseDir, { recursive: true });
75043
- worktreePath = join60(baseDir, `${id}-${taskSuffix}`);
75399
+ worktreePath = join61(baseDir, `${id}-${taskSuffix}`);
75044
75400
  const now = new Date().toISOString();
75045
75401
  const record2 = {
75046
75402
  id,
@@ -75295,7 +75651,7 @@ import {
75295
75651
  rmSync as rmSync15,
75296
75652
  writeFileSync as writeFileSync31
75297
75653
  } from "node:fs";
75298
- import { join as join61 } from "node:path";
75654
+ import { join as join62 } from "node:path";
75299
75655
  function encodeCredentialsFilename(email) {
75300
75656
  const SAFE = new Set([
75301
75657
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -75485,16 +75841,16 @@ function resolveCredentialsDir(env2) {
75485
75841
  if (explicit && explicit.length > 0)
75486
75842
  return explicit;
75487
75843
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
75488
- return join61(stateBase, "google-workspace-mcp", "credentials");
75844
+ return join62(stateBase, "google-workspace-mcp", "credentials");
75489
75845
  }
75490
75846
  function writeSeedFile(dir, email, seed) {
75491
75847
  mkdirSync34(dir, { recursive: true, mode: 448 });
75492
75848
  chmodSync9(dir, 448);
75493
75849
  for (const name of readdirSync23(dir)) {
75494
- rmSync15(join61(dir, name), { force: true, recursive: true });
75850
+ rmSync15(join62(dir, name), { force: true, recursive: true });
75495
75851
  }
75496
75852
  const filename = encodeCredentialsFilename(email);
75497
- const filePath = join61(dir, filename);
75853
+ const filePath = join62(dir, filename);
75498
75854
  writeFileSync31(filePath, JSON.stringify(seed), { mode: 384 });
75499
75855
  chmodSync9(filePath, 384);
75500
75856
  return filePath;
@@ -75654,7 +76010,7 @@ function registerDriveMcpLauncherCommand(program3) {
75654
76010
  init_scaffold_integration();
75655
76011
  import { spawn as spawn6 } from "node:child_process";
75656
76012
  import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync35 } from "node:fs";
75657
- import { dirname as dirname17, join as join62 } from "node:path";
76013
+ import { dirname as dirname17, join as join63 } from "node:path";
75658
76014
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
75659
76015
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
75660
76016
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -75686,7 +76042,7 @@ function writeRefreshHeartbeat(agentName, data) {
75686
76042
  function heartbeatPath(agentName) {
75687
76043
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
75688
76044
  if (override) {
75689
- return join62(override, `m365-launcher-${agentName}.heartbeat.json`);
76045
+ return join63(override, `m365-launcher-${agentName}.heartbeat.json`);
75690
76046
  }
75691
76047
  return "/state/agent/m365-launcher.heartbeat.json";
75692
76048
  }
@@ -75868,9 +76224,381 @@ function registerM365McpLauncherCommand(program3) {
75868
76224
  });
75869
76225
  }
75870
76226
 
76227
+ // src/cli/notion-mcp-launcher.ts
76228
+ init_scaffold_integration();
76229
+ import { spawn as spawn7 } from "node:child_process";
76230
+ import { existsSync as existsSync65, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "node:fs";
76231
+ import { dirname as dirname18 } from "node:path";
76232
+ var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
76233
+ var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
76234
+ var DEFAULT_VAULT_KEY = "notion/integration-token";
76235
+ function buildNotionMcpArgs(opts) {
76236
+ const version2 = opts.mcpVersion ?? NOTION_MCP_PINNED_VERSION;
76237
+ return ["-y", `${NOTION_MCP_PACKAGE}@${version2}`];
76238
+ }
76239
+ function defaultWriteHeartbeat(path7, contents) {
76240
+ try {
76241
+ const dir = dirname18(path7);
76242
+ if (!existsSync65(dir))
76243
+ mkdirSync36(dir, { recursive: true });
76244
+ writeFileSync33(path7, contents);
76245
+ } catch {}
76246
+ }
76247
+ async function runNotionMcpLauncher(opts, runtime) {
76248
+ const heartbeatPath2 = opts.heartbeatPath ?? DEFAULT_HEARTBEAT_PATH;
76249
+ const writeHeartbeat = runtime.writeHeartbeat ?? defaultWriteHeartbeat;
76250
+ const setTimer = runtime.setTimer ?? ((cb, ms) => setInterval(cb, ms));
76251
+ const clearTimer = runtime.clearTimer ?? clearInterval;
76252
+ const now = runtime.now ?? (() => Date.now());
76253
+ let token;
76254
+ try {
76255
+ token = await runtime.fetchToken();
76256
+ } catch (err) {
76257
+ process.stderr.write(`notion-mcp-launcher: failed to fetch token from vault-broker: ${err.message}
76258
+ `);
76259
+ return 1;
76260
+ }
76261
+ if (!token || typeof token !== "string") {
76262
+ process.stderr.write(`notion-mcp-launcher: vault-broker returned an empty/invalid token.
76263
+ `);
76264
+ return 1;
76265
+ }
76266
+ const childEnv = {
76267
+ ...process.env,
76268
+ [NOTION_TOKEN_ENV]: token
76269
+ };
76270
+ const args = buildNotionMcpArgs(opts);
76271
+ const child = runtime.spawnMcp(childEnv, args);
76272
+ if (child.stdin == null || child.stdout == null || child.stderr == null) {
76273
+ process.stderr.write(`notion-mcp-launcher: child stdio handles missing \u2014 aborting.
76274
+ `);
76275
+ return 1;
76276
+ }
76277
+ process.stdin.pipe(child.stdin);
76278
+ child.stdout.pipe(process.stdout);
76279
+ child.stderr.pipe(process.stderr);
76280
+ const writeOne = () => {
76281
+ const payload = {
76282
+ ts: now(),
76283
+ pid: process.pid,
76284
+ agent: process.env.SWITCHROOM_AGENT_NAME,
76285
+ mcp_package: NOTION_MCP_PACKAGE,
76286
+ mcp_version: opts.mcpVersion ?? NOTION_MCP_PINNED_VERSION
76287
+ };
76288
+ writeHeartbeat(heartbeatPath2, JSON.stringify(payload, null, 2));
76289
+ };
76290
+ writeOne();
76291
+ const heartbeatHandle = setTimer(writeOne, HEARTBEAT_WRITE_INTERVAL_MS);
76292
+ const forward = (sig) => {
76293
+ try {
76294
+ child.kill(sig);
76295
+ } catch {}
76296
+ };
76297
+ process.on("SIGTERM", () => forward("SIGTERM"));
76298
+ process.on("SIGINT", () => forward("SIGINT"));
76299
+ const exitCode = await new Promise((resolve41) => {
76300
+ child.once("exit", (code, signal) => {
76301
+ clearTimer(heartbeatHandle);
76302
+ if (typeof code === "number") {
76303
+ resolve41(code);
76304
+ } else if (signal) {
76305
+ const sigCode = { SIGTERM: 15, SIGINT: 2, SIGKILL: 9 }[signal] ?? 1;
76306
+ resolve41(128 + sigCode);
76307
+ } else {
76308
+ resolve41(1);
76309
+ }
76310
+ });
76311
+ child.once("error", (err) => {
76312
+ clearTimer(heartbeatHandle);
76313
+ process.stderr.write(`notion-mcp-launcher: child spawn error: ${err.message}
76314
+ `);
76315
+ resolve41(1);
76316
+ });
76317
+ });
76318
+ return exitCode;
76319
+ }
76320
+ function registerNotionMcpLauncherCommand(program3) {
76321
+ program3.command("notion-mcp-launcher", { hidden: true }).option("--vault-key <key>", "Override the vault key holding the Notion integration token. " + "Defaults to `notion/integration-token`.", DEFAULT_VAULT_KEY).option("--mcp-version <semver>", "Override the @notionhq/notion-mcp-server version to spawn.").option("--heartbeat-path <path>", "Override the heartbeat file path. Default: /state/agent/notion-launcher.heartbeat.json").description("Internal \u2014 Notion MCP launcher. Fetches the integration token from the vault-broker and execs @notionhq/notion-mcp-server in stdio mode. RFC docs/rfcs/notion-integration.md PR 2.").action(async (opts) => {
76322
+ const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
76323
+ const code = await runNotionMcpLauncher(opts, {
76324
+ fetchToken: async () => {
76325
+ const key = opts.vaultKey ?? DEFAULT_VAULT_KEY;
76326
+ const result = await getViaBrokerStructured2(key);
76327
+ if (result.kind === "unreachable") {
76328
+ throw new Error(`vault-broker unreachable: ${result.msg}. Is the broker socket mounted?`);
76329
+ }
76330
+ if (result.kind === "not_found") {
76331
+ throw new Error(`vault key ${key} is missing. Run \`switchroom vault set ${key}\` on the host to populate it.`);
76332
+ }
76333
+ if (result.kind === "denied") {
76334
+ throw new Error(`vault-broker denied access to ${key}: ${result.msg}. Re-run \`switchroom vault set ${key} --allow <comma-separated-agents>\` on the host including this agent.`);
76335
+ }
76336
+ if (result.entry.kind !== "string") {
76337
+ throw new Error(`vault key ${key} is not a string entry \u2014 Notion expects an integration token string.`);
76338
+ }
76339
+ return result.entry.value;
76340
+ },
76341
+ spawnMcp: (env2, args) => {
76342
+ return spawn7("npx", args, {
76343
+ env: env2,
76344
+ stdio: ["pipe", "pipe", "pipe"]
76345
+ });
76346
+ }
76347
+ });
76348
+ process.exit(code);
76349
+ });
76350
+ }
76351
+
76352
+ // src/cli/notion.ts
76353
+ init_loader();
76354
+
76355
+ // src/notion/api-client.ts
76356
+ var DEFAULT_BASE = "https://api.notion.com";
76357
+ var DEFAULT_VERSION = "2022-06-28";
76358
+ var DEFAULT_TIMEOUT_MS4 = 5000;
76359
+ function createNotionApiClient(opts) {
76360
+ const base = opts.base ?? DEFAULT_BASE;
76361
+ const version2 = opts.notionVersion ?? DEFAULT_VERSION;
76362
+ const fetchImpl = opts.fetchImpl ?? fetch;
76363
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
76364
+ async function request(method, path7, body) {
76365
+ const ac = new AbortController;
76366
+ const timeout = setTimeout(() => ac.abort(), timeoutMs);
76367
+ try {
76368
+ const res = await fetchImpl(`${base}${path7}`, {
76369
+ method,
76370
+ headers: {
76371
+ Authorization: `Bearer ${opts.token}`,
76372
+ "Notion-Version": version2,
76373
+ "Content-Type": "application/json"
76374
+ },
76375
+ body: body === undefined ? undefined : JSON.stringify(body),
76376
+ signal: ac.signal
76377
+ });
76378
+ if (!res.ok) {
76379
+ let detail = "";
76380
+ try {
76381
+ detail = await res.text();
76382
+ } catch {}
76383
+ throw new Error(`Notion API ${method} ${path7} failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail.slice(0, 200)}` : ""}`);
76384
+ }
76385
+ return await res.json();
76386
+ } finally {
76387
+ clearTimeout(timeout);
76388
+ }
76389
+ }
76390
+ return {
76391
+ async getPage(pageId) {
76392
+ const data = await request("GET", `/v1/pages/${encodeURIComponent(pageId)}`);
76393
+ return { parent: data.parent };
76394
+ },
76395
+ async getBlock(blockId) {
76396
+ const data = await request("GET", `/v1/blocks/${encodeURIComponent(blockId)}`);
76397
+ return { parent: data.parent };
76398
+ },
76399
+ async search(query, pageSize = 20) {
76400
+ return await request("POST", "/v1/search", {
76401
+ query,
76402
+ page_size: pageSize
76403
+ });
76404
+ }
76405
+ };
76406
+ }
76407
+
76408
+ // src/cli/notion.ts
76409
+ init_client();
76410
+ function registerNotionCommand(program3) {
76411
+ const cmd = program3.command("notion").description("Notion integration operator helpers (list-dbs, test). See docs/rfcs/notion-integration.md.");
76412
+ cmd.command("list-dbs").description("List the databases the Notion integration can access. Output is a ready-to-paste YAML block for `notion_workspace.databases`.").option("--vault-key <key>", "Override the vault key holding the integration token.", "notion/integration-token").action(async (opts) => {
76413
+ const code = await runListDbs(opts);
76414
+ process.exit(code);
76415
+ });
76416
+ cmd.command("test <agent>").description("Smoke-test the Notion integration token (does NOT exercise per-agent ACL \u2014 use `switchroom doctor` for that). Verifies the agent has notion_workspace configured + the token works against Notion's API. Calls /v1/users/me via the operator-side broker and prints the integration's bot user.").option("--vault-key <key>", "Override the vault key holding the integration token.", "notion/integration-token").action(async (agent, opts) => {
76417
+ const code = await runTest(agent, opts);
76418
+ process.exit(code);
76419
+ });
76420
+ }
76421
+ async function runListDbs(opts) {
76422
+ let token;
76423
+ try {
76424
+ token = await fetchToken(opts.vaultKey);
76425
+ } catch (err) {
76426
+ process.stderr.write(`switchroom notion list-dbs: ${err.message}
76427
+ `);
76428
+ return 1;
76429
+ }
76430
+ const client2 = createNotionApiClient({ token });
76431
+ let dbs;
76432
+ try {
76433
+ dbs = await searchAllDatabases(token);
76434
+ } catch (err) {
76435
+ process.stderr.write(`switchroom notion list-dbs: failed to enumerate databases: ${err.message}
76436
+ `);
76437
+ return 1;
76438
+ }
76439
+ if (dbs.length === 0) {
76440
+ process.stderr.write(`switchroom notion list-dbs: the integration has no databases shared with it.
76441
+ ` + "Open Notion \u2192 page/database you want switchroom to access \u2192 top-right \u22ef \u2192 Connections \u2192 add `switchroom`, then re-run.\n");
76442
+ return 1;
76443
+ }
76444
+ process.stdout.write("# Paste under `notion_workspace:` in switchroom.yaml:\n");
76445
+ process.stdout.write(`databases:
76446
+ `);
76447
+ const seen = new Set;
76448
+ for (const db of dbs) {
76449
+ const slug = toFriendlyName(db.title || db.id, seen);
76450
+ seen.add(slug);
76451
+ process.stdout.write(` ${slug}: "${db.id}"
76452
+ `);
76453
+ }
76454
+ if (process.stdout.isTTY) {
76455
+ process.stderr.write(`
76456
+ Found ${dbs.length} database(s). Edit the friendly names to taste \u2014 agents reference them by name in \`agents.<name>.notion_workspace.databases\`.
76457
+ `);
76458
+ }
76459
+ return 0;
76460
+ }
76461
+ function toFriendlyName(title, seen) {
76462
+ let base = title.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
76463
+ if (!base || !/^[a-z0-9]/.test(base))
76464
+ base = `db-${base.replace(/^[^a-z0-9]+/, "")}`;
76465
+ if (!base)
76466
+ base = "db";
76467
+ let candidate = base;
76468
+ let i = 2;
76469
+ while (seen.has(candidate)) {
76470
+ candidate = `${base}-${i}`;
76471
+ i += 1;
76472
+ }
76473
+ return candidate;
76474
+ }
76475
+ async function searchAllDatabases(token) {
76476
+ const out = [];
76477
+ let cursor = null;
76478
+ for (let i = 0;i < 10; i += 1) {
76479
+ const body = {
76480
+ filter: { value: "database", property: "object" },
76481
+ page_size: 100
76482
+ };
76483
+ if (cursor)
76484
+ body.start_cursor = cursor;
76485
+ const res = await postSearch(token, body);
76486
+ for (const hit of res.results) {
76487
+ const title = extractDbTitle(hit) || hit.id;
76488
+ out.push({ id: hit.id, title });
76489
+ }
76490
+ if (!res.next_cursor)
76491
+ break;
76492
+ cursor = res.next_cursor;
76493
+ }
76494
+ return out;
76495
+ }
76496
+ function extractDbTitle(hit) {
76497
+ if (!hit.title || !Array.isArray(hit.title))
76498
+ return null;
76499
+ return hit.title.map((t) => t.plain_text ?? "").join("").trim() || null;
76500
+ }
76501
+ async function postSearch(token, body) {
76502
+ const ac = new AbortController;
76503
+ const timeout = setTimeout(() => ac.abort(), 1e4);
76504
+ try {
76505
+ const res = await fetch("https://api.notion.com/v1/search", {
76506
+ method: "POST",
76507
+ headers: {
76508
+ Authorization: `Bearer ${token}`,
76509
+ "Notion-Version": "2022-06-28",
76510
+ "Content-Type": "application/json"
76511
+ },
76512
+ body: JSON.stringify(body),
76513
+ signal: ac.signal
76514
+ });
76515
+ if (!res.ok) {
76516
+ throw new Error(`Notion /v1/search ${res.status}: ${await res.text()}`);
76517
+ }
76518
+ return await res.json();
76519
+ } finally {
76520
+ clearTimeout(timeout);
76521
+ }
76522
+ }
76523
+ async function runTest(agent, opts) {
76524
+ let config;
76525
+ try {
76526
+ config = loadConfig();
76527
+ } catch (err) {
76528
+ process.stderr.write(`switchroom notion test: failed to load switchroom.yaml: ${err.message}
76529
+ `);
76530
+ return 1;
76531
+ }
76532
+ const agentCfg = config.agents?.[agent];
76533
+ if (!agentCfg) {
76534
+ process.stderr.write(`switchroom notion test: agent '${agent}' not found in switchroom.yaml.
76535
+ `);
76536
+ return 1;
76537
+ }
76538
+ if (!agentCfg.notion_workspace) {
76539
+ process.stderr.write(`switchroom notion test: agent '${agent}' has no notion_workspace block. Set \`agents.${agent}.notion_workspace: {}\` (or with databases:) in switchroom.yaml.
76540
+ `);
76541
+ return 1;
76542
+ }
76543
+ let token;
76544
+ try {
76545
+ token = await fetchToken(opts.vaultKey);
76546
+ } catch (err) {
76547
+ process.stderr.write(`switchroom notion test: ${err.message}
76548
+ `);
76549
+ return 1;
76550
+ }
76551
+ try {
76552
+ const ac = new AbortController;
76553
+ const timeout = setTimeout(() => ac.abort(), 1e4);
76554
+ let res;
76555
+ try {
76556
+ res = await fetch("https://api.notion.com/v1/users/me", {
76557
+ method: "GET",
76558
+ headers: {
76559
+ Authorization: `Bearer ${token}`,
76560
+ "Notion-Version": "2022-06-28"
76561
+ },
76562
+ signal: ac.signal
76563
+ });
76564
+ } finally {
76565
+ clearTimeout(timeout);
76566
+ }
76567
+ if (!res.ok) {
76568
+ process.stderr.write(`switchroom notion test: Notion API returned ${res.status} \u2014 token may be revoked. Detail: ${await res.text()}
76569
+ `);
76570
+ return 1;
76571
+ }
76572
+ const data = await res.json();
76573
+ process.stdout.write(`OK \u2014 agent='${agent}', integration bot='${data.name ?? "?"}', workspace='${data.bot?.workspace_name ?? "?"}'
76574
+ `);
76575
+ return 0;
76576
+ } catch (err) {
76577
+ process.stderr.write(`switchroom notion test: ${err.message}
76578
+ `);
76579
+ return 1;
76580
+ }
76581
+ }
76582
+ async function fetchToken(vaultKey) {
76583
+ const result = await getViaBrokerStructured(vaultKey);
76584
+ if (result.kind === "unreachable") {
76585
+ throw new Error(`vault-broker unreachable: ${result.msg}`);
76586
+ }
76587
+ if (result.kind === "not_found") {
76588
+ throw new Error(`vault key '${vaultKey}' is missing. Run \`switchroom vault set ${vaultKey}\` first.`);
76589
+ }
76590
+ if (result.kind === "denied") {
76591
+ throw new Error(`vault-broker denied access to '${vaultKey}': ${result.msg}`);
76592
+ }
76593
+ if (result.entry.kind !== "string") {
76594
+ throw new Error(`vault key '${vaultKey}' is not a string entry`);
76595
+ }
76596
+ return result.entry.value;
76597
+ }
76598
+
75871
76599
  // src/cli/apply.ts
75872
76600
  init_source();
75873
- import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync37, readFileSync as readFileSync56, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync34 } from "node:fs";
76601
+ import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync69, mkdirSync as mkdirSync38, readFileSync as readFileSync56, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync35 } from "node:fs";
75874
76602
  import { mkdir, writeFile } from "node:fs/promises";
75875
76603
  import { spawnSync as childSpawnSync } from "node:child_process";
75876
76604
  import readline from "node:readline";
@@ -76233,16 +76961,16 @@ agents:
76233
76961
 
76234
76962
  // src/cli/apply.ts
76235
76963
  init_resolver();
76236
- import { dirname as dirname20, join as join66, resolve as resolve42 } from "node:path";
76237
- import { homedir as homedir37 } from "node:os";
76964
+ import { dirname as dirname21, join as join67, resolve as resolve42 } from "node:path";
76965
+ import { homedir as homedir38 } from "node:os";
76238
76966
  import { execFileSync as execFileSync20 } from "node:child_process";
76239
76967
  init_vault();
76240
76968
  init_loader();
76241
76969
  init_loader();
76242
76970
 
76243
76971
  // src/cli/update-prompt-hook.ts
76244
- import { existsSync as existsSync65, readFileSync as readFileSync55, writeFileSync as writeFileSync33, chmodSync as chmodSync10, mkdirSync as mkdirSync36 } from "node:fs";
76245
- import { join as join63 } from "node:path";
76972
+ import { existsSync as existsSync66, readFileSync as readFileSync55, writeFileSync as writeFileSync34, chmodSync as chmodSync10, mkdirSync as mkdirSync37 } from "node:fs";
76973
+ import { join as join64 } from "node:path";
76246
76974
  var HOOK_FILENAME = "update-card-on-prompt.sh";
76247
76975
  function updatePromptHookScript() {
76248
76976
  return `#!/bin/bash
@@ -76308,14 +77036,14 @@ exit 0
76308
77036
  `;
76309
77037
  }
76310
77038
  function installUpdatePromptHook(agentDir) {
76311
- const hooksDir = join63(agentDir, ".claude", "hooks");
76312
- mkdirSync36(hooksDir, { recursive: true });
76313
- const scriptPath = join63(hooksDir, HOOK_FILENAME);
77039
+ const hooksDir = join64(agentDir, ".claude", "hooks");
77040
+ mkdirSync37(hooksDir, { recursive: true });
77041
+ const scriptPath = join64(hooksDir, HOOK_FILENAME);
76314
77042
  const desired = updatePromptHookScript();
76315
77043
  let installed = false;
76316
- const existing = existsSync65(scriptPath) ? readFileSync55(scriptPath, "utf-8") : "";
77044
+ const existing = existsSync66(scriptPath) ? readFileSync55(scriptPath, "utf-8") : "";
76317
77045
  if (existing !== desired) {
76318
- writeFileSync33(scriptPath, desired, { mode: 493 });
77046
+ writeFileSync34(scriptPath, desired, { mode: 493 });
76319
77047
  chmodSync10(scriptPath, 493);
76320
77048
  installed = true;
76321
77049
  } else {
@@ -76323,8 +77051,8 @@ function installUpdatePromptHook(agentDir) {
76323
77051
  chmodSync10(scriptPath, 493);
76324
77052
  } catch {}
76325
77053
  }
76326
- const settingsPath = join63(agentDir, ".claude", "settings.json");
76327
- if (!existsSync65(settingsPath)) {
77054
+ const settingsPath = join64(agentDir, ".claude", "settings.json");
77055
+ if (!existsSync66(settingsPath)) {
76328
77056
  return { scriptPath, settingsPath, installed };
76329
77057
  }
76330
77058
  const raw = readFileSync55(settingsPath, "utf-8");
@@ -76360,7 +77088,7 @@ function installUpdatePromptHook(agentDir) {
76360
77088
  });
76361
77089
  hooks.UserPromptSubmit = list2;
76362
77090
  parsed.hooks = hooks;
76363
- writeFileSync33(settingsPath, JSON.stringify(parsed, null, 2) + `
77091
+ writeFileSync34(settingsPath, JSON.stringify(parsed, null, 2) + `
76364
77092
  `, { mode: 384 });
76365
77093
  installed = true;
76366
77094
  }
@@ -76443,13 +77171,13 @@ function detectInstallType() {
76443
77171
  // src/cli/operator-uid.ts
76444
77172
  import {
76445
77173
  chownSync as chownSync3,
76446
- existsSync as existsSync67,
77174
+ existsSync as existsSync68,
76447
77175
  lstatSync as lstatSync7,
76448
77176
  readdirSync as readdirSync24,
76449
77177
  realpathSync as realpathSync6,
76450
77178
  statSync as statSync27
76451
77179
  } from "node:fs";
76452
- import { join as join65 } from "node:path";
77180
+ import { join as join66 } from "node:path";
76453
77181
  function resolveOperatorUid() {
76454
77182
  const sudoUid = process.env.SUDO_UID;
76455
77183
  if (sudoUid !== undefined) {
@@ -76465,19 +77193,19 @@ function resolveOperatorUid() {
76465
77193
  return;
76466
77194
  }
76467
77195
  function operatorOwnedPaths(home2) {
76468
- const root = join65(home2, ".switchroom");
77196
+ const root = join66(home2, ".switchroom");
76469
77197
  return [
76470
- join65(root, "vault"),
76471
- join65(root, "vault-auto-unlock"),
76472
- join65(root, "vault-audit.log"),
76473
- join65(root, "host-control-audit.log"),
76474
- join65(root, "accounts"),
76475
- join65(root, "compose")
77198
+ join66(root, "vault"),
77199
+ join66(root, "vault-auto-unlock"),
77200
+ join66(root, "vault-audit.log"),
77201
+ join66(root, "host-control-audit.log"),
77202
+ join66(root, "accounts"),
77203
+ join66(root, "compose")
76476
77204
  ];
76477
77205
  }
76478
77206
  function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
76479
77207
  const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
76480
- const exists = deps.exists ?? ((p) => existsSync67(p));
77208
+ const exists = deps.exists ?? ((p) => existsSync68(p));
76481
77209
  const isSymlink = deps.isSymlink ?? ((p) => {
76482
77210
  try {
76483
77211
  return lstatSync7(p).isSymbolicLink();
@@ -76521,7 +77249,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
76521
77249
  } catch {}
76522
77250
  if (isDir(target)) {
76523
77251
  for (const entry of readdir2(target)) {
76524
- visit(join65(target, entry));
77252
+ visit(join66(target, entry));
76525
77253
  }
76526
77254
  }
76527
77255
  };
@@ -76535,17 +77263,17 @@ var EMBEDDED_EXAMPLES = {
76535
77263
  switchroom: switchroom_default,
76536
77264
  minimal: minimal_default
76537
77265
  };
76538
- var DEFAULT_COMPOSE_PATH2 = join66(homedir37(), ".switchroom", "compose", "docker-compose.yml");
77266
+ var DEFAULT_COMPOSE_PATH2 = join67(homedir38(), ".switchroom", "compose", "docker-compose.yml");
76539
77267
  var COMPOSE_PROJECT2 = "switchroom";
76540
77268
  function resolveVaultBindMountDir(homeDir, ctx) {
76541
77269
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
76542
77270
  if (isCustomPath && ctx.customVaultPath) {
76543
- return dirname20(ctx.customVaultPath);
77271
+ return dirname21(ctx.customVaultPath);
76544
77272
  }
76545
- return join66(homeDir, ".switchroom", "vault");
77273
+ return join67(homeDir, ".switchroom", "vault");
76546
77274
  }
76547
77275
  function inspectVaultBindMountDir(vaultDir) {
76548
- if (!existsSync68(vaultDir))
77276
+ if (!existsSync69(vaultDir))
76549
77277
  return { kind: "missing" };
76550
77278
  const entries = readdirSync25(vaultDir);
76551
77279
  const unknown = [];
@@ -76571,63 +77299,63 @@ function hasVaultRefs(value) {
76571
77299
  return false;
76572
77300
  }
76573
77301
  async function ensureHostMountSources(config) {
76574
- const home2 = homedir37();
77302
+ const home2 = homedir38();
76575
77303
  const dirs = [
76576
- join66(home2, ".switchroom", "approvals"),
76577
- join66(home2, ".switchroom", "scheduler"),
76578
- join66(home2, ".switchroom", "logs"),
76579
- join66(home2, ".switchroom", "compose"),
76580
- join66(home2, ".switchroom", "broker-operator")
77304
+ join67(home2, ".switchroom", "approvals"),
77305
+ join67(home2, ".switchroom", "scheduler"),
77306
+ join67(home2, ".switchroom", "logs"),
77307
+ join67(home2, ".switchroom", "compose"),
77308
+ join67(home2, ".switchroom", "broker-operator")
76581
77309
  ];
76582
77310
  for (const name of Object.keys(config.agents)) {
76583
- dirs.push(join66(home2, ".switchroom", "agents", name));
76584
- dirs.push(join66(home2, ".switchroom", "logs", name));
76585
- dirs.push(join66(home2, ".claude", "projects", name));
76586
- dirs.push(join66(home2, ".switchroom", "audit", name));
76587
- if (existsSync68(join66(home2, ".switchroom-config"))) {
76588
- dirs.push(join66(home2, ".switchroom-config", "agents", name, "personal-skills"));
77311
+ dirs.push(join67(home2, ".switchroom", "agents", name));
77312
+ dirs.push(join67(home2, ".switchroom", "logs", name));
77313
+ dirs.push(join67(home2, ".claude", "projects", name));
77314
+ dirs.push(join67(home2, ".switchroom", "audit", name));
77315
+ if (existsSync69(join67(home2, ".switchroom-config"))) {
77316
+ dirs.push(join67(home2, ".switchroom-config", "agents", name, "personal-skills"));
76589
77317
  }
76590
77318
  }
76591
77319
  for (const dir of dirs) {
76592
77320
  await mkdir(dir, { recursive: true });
76593
77321
  }
76594
- const autoUnlockPath = join66(home2, ".switchroom", "vault-auto-unlock");
76595
- if (!existsSync68(autoUnlockPath)) {
76596
- writeFileSync34(autoUnlockPath, "", { mode: 384 });
77322
+ const autoUnlockPath = join67(home2, ".switchroom", "vault-auto-unlock");
77323
+ if (!existsSync69(autoUnlockPath)) {
77324
+ writeFileSync35(autoUnlockPath, "", { mode: 384 });
76597
77325
  }
76598
- const auditLogPath = join66(home2, ".switchroom", "vault-audit.log");
76599
- if (!existsSync68(auditLogPath)) {
76600
- writeFileSync34(auditLogPath, "", { mode: 420 });
77326
+ const auditLogPath = join67(home2, ".switchroom", "vault-audit.log");
77327
+ if (!existsSync69(auditLogPath)) {
77328
+ writeFileSync35(auditLogPath, "", { mode: 420 });
76601
77329
  }
76602
- const grantsDbPath = join66(home2, ".switchroom", "vault-grants.db");
76603
- if (!existsSync68(grantsDbPath)) {
76604
- writeFileSync34(grantsDbPath, "", { mode: 384 });
77330
+ const grantsDbPath = join67(home2, ".switchroom", "vault-grants.db");
77331
+ if (!existsSync69(grantsDbPath)) {
77332
+ writeFileSync35(grantsDbPath, "", { mode: 384 });
76605
77333
  }
76606
- const hostdAuditLogPath = join66(home2, ".switchroom", "host-control-audit.log");
76607
- if (!existsSync68(hostdAuditLogPath)) {
76608
- writeFileSync34(hostdAuditLogPath, "", { mode: 420 });
77334
+ const hostdAuditLogPath = join67(home2, ".switchroom", "host-control-audit.log");
77335
+ if (!existsSync69(hostdAuditLogPath)) {
77336
+ writeFileSync35(hostdAuditLogPath, "", { mode: 420 });
76609
77337
  }
76610
77338
  for (const name of Object.keys(config.agents)) {
76611
- const tokenPath = join66(home2, ".switchroom", "agents", name, ".vault-token");
76612
- if (!existsSync68(tokenPath)) {
76613
- writeFileSync34(tokenPath, "", { mode: 384 });
77339
+ const tokenPath = join67(home2, ".switchroom", "agents", name, ".vault-token");
77340
+ if (!existsSync69(tokenPath)) {
77341
+ writeFileSync35(tokenPath, "", { mode: 384 });
76614
77342
  }
76615
77343
  try {
76616
77344
  const uid = allocateAgentUid(name);
76617
77345
  chownSync4(tokenPath, uid, uid);
76618
77346
  } catch {}
76619
77347
  }
76620
- const fleetDir = join66(home2, ".switchroom", "fleet");
77348
+ const fleetDir = join67(home2, ".switchroom", "fleet");
76621
77349
  await mkdir(fleetDir, { recursive: true });
76622
- const invariantsPath = join66(fleetDir, "switchroom-invariants.md");
77350
+ const invariantsPath = join67(fleetDir, "switchroom-invariants.md");
76623
77351
  const invariantsCanonical = renderFleetInvariants();
76624
- const invariantsCurrent = existsSync68(invariantsPath) ? readFileSync56(invariantsPath, "utf-8") : null;
77352
+ const invariantsCurrent = existsSync69(invariantsPath) ? readFileSync56(invariantsPath, "utf-8") : null;
76625
77353
  if (invariantsCurrent !== invariantsCanonical) {
76626
- writeFileSync34(invariantsPath, invariantsCanonical, { mode: 420 });
77354
+ writeFileSync35(invariantsPath, invariantsCanonical, { mode: 420 });
76627
77355
  }
76628
- const fleetClaudePath = join66(fleetDir, "CLAUDE.md");
76629
- if (!existsSync68(fleetClaudePath)) {
76630
- writeFileSync34(fleetClaudePath, [
77356
+ const fleetClaudePath = join67(fleetDir, "CLAUDE.md");
77357
+ if (!existsSync69(fleetClaudePath)) {
77358
+ writeFileSync35(fleetClaudePath, [
76631
77359
  "# Switchroom fleet defaults",
76632
77360
  "",
76633
77361
  "Operator-owned fleet brain. Every agent reads this via",
@@ -76658,7 +77386,7 @@ ${out.trim()}`;
76658
77386
  }
76659
77387
  function runApplyPreflight(config, opts = {}) {
76660
77388
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
76661
- if (hasVaultRefs(config) && !existsSync68(vaultPath)) {
77389
+ if (hasVaultRefs(config) && !existsSync69(vaultPath)) {
76662
77390
  throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
76663
77391
  }
76664
77392
  const detect = opts.detectComposeV2 ?? detectComposeV2;
@@ -76669,7 +77397,7 @@ function runApplyPreflight(config, opts = {}) {
76669
77397
  detectAndReportLegacyGdriveSlots(vaultPath);
76670
77398
  }
76671
77399
  function detectAndReportLegacyGdriveSlots(vaultPath) {
76672
- if (!existsSync68(vaultPath))
77400
+ if (!existsSync69(vaultPath))
76673
77401
  return;
76674
77402
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
76675
77403
  if (!passphrase)
@@ -76708,18 +77436,18 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
76708
77436
  `));
76709
77437
  }
76710
77438
  }
76711
- function writeInstallTypeCache(homeDir = homedir37()) {
77439
+ function writeInstallTypeCache(homeDir = homedir38()) {
76712
77440
  const ctx = detectInstallType();
76713
- const dir = join66(homeDir, ".switchroom");
76714
- const out = join66(dir, "install-type.json");
77441
+ const dir = join67(homeDir, ".switchroom");
77442
+ const out = join67(dir, "install-type.json");
76715
77443
  const tmp = `${out}.tmp`;
76716
- mkdirSync37(dir, { recursive: true });
77444
+ mkdirSync38(dir, { recursive: true });
76717
77445
  const payload = {
76718
77446
  install_type: ctx.install_type,
76719
77447
  detected_at: new Date().toISOString(),
76720
77448
  source_paths: ctx.source_paths
76721
77449
  };
76722
- writeFileSync34(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
77450
+ writeFileSync35(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
76723
77451
  renameSync13(tmp, out);
76724
77452
  return out;
76725
77453
  }
@@ -76760,14 +77488,14 @@ Applying switchroom config...
76760
77488
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
76761
77489
  `));
76762
77490
  try {
76763
- installUpdatePromptHook(join66(agentsDir, name));
77491
+ installUpdatePromptHook(join67(agentsDir, name));
76764
77492
  } catch (hookErr) {
76765
77493
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
76766
77494
  `));
76767
77495
  }
76768
77496
  try {
76769
77497
  const uid = allocateAgentUid(name);
76770
- alignAgentUid(name, join66(agentsDir, name), uid, {
77498
+ alignAgentUid(name, join67(agentsDir, name), uid, {
76771
77499
  confirm: !options.nonInteractive,
76772
77500
  writeOut
76773
77501
  });
@@ -76804,7 +77532,7 @@ Applying switchroom config...
76804
77532
  for (const name of agentNames) {
76805
77533
  try {
76806
77534
  const uid = allocateAgentUid(name);
76807
- alignAgentUid(name, join66(agentsDir, name), uid, {
77535
+ alignAgentUid(name, join67(agentsDir, name), uid, {
76808
77536
  confirm: !options.nonInteractive,
76809
77537
  writeOut
76810
77538
  });
@@ -76818,7 +77546,7 @@ Applying switchroom config...
76818
77546
  }
76819
77547
  const vaultPathConfigured = config.vault?.path;
76820
77548
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
76821
- const migrationResult = migrateVaultLayout(homedir37(), {
77549
+ const migrationResult = migrateVaultLayout(homedir38(), {
76822
77550
  customVaultPath
76823
77551
  });
76824
77552
  switch (migrationResult.kind) {
@@ -76844,7 +77572,7 @@ Applying switchroom config...
76844
77572
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
76845
77573
  process.exit(4);
76846
77574
  }
76847
- const postMigrationInspect = inspectVaultLayout(homedir37());
77575
+ const postMigrationInspect = inspectVaultLayout(homedir38());
76848
77576
  const acceptable = [
76849
77577
  "no-vault",
76850
77578
  "already-migrated",
@@ -76859,7 +77587,7 @@ Applying switchroom config...
76859
77587
  `));
76860
77588
  process.exit(5);
76861
77589
  }
76862
- const vaultDir = resolveVaultBindMountDir(homedir37(), {
77590
+ const vaultDir = resolveVaultBindMountDir(homedir38(), {
76863
77591
  migrationKind: migrationResult.kind,
76864
77592
  customVaultPath
76865
77593
  });
@@ -76889,11 +77617,11 @@ Applying switchroom config...
76889
77617
  imageTag: composeImageTag,
76890
77618
  buildMode: options.buildLocal ? "local" : "pull",
76891
77619
  buildContext: options.buildContext,
76892
- homeDir: homedir37(),
77620
+ homeDir: homedir38(),
76893
77621
  switchroomConfigPath,
76894
77622
  operatorUid
76895
77623
  });
76896
- await mkdir(dirname20(composePath), { recursive: true });
77624
+ await mkdir(dirname21(composePath), { recursive: true });
76897
77625
  await writeFile(composePath, composeContent, {
76898
77626
  encoding: "utf8",
76899
77627
  mode: 384
@@ -76909,7 +77637,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
76909
77637
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
76910
77638
  `));
76911
77639
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
76912
- const restored = restoreOperatorOwnership(homedir37(), operatorUid);
77640
+ const restored = restoreOperatorOwnership(homedir38(), operatorUid);
76913
77641
  if (restored.length > 0) {
76914
77642
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
76915
77643
  `));
@@ -76957,18 +77685,18 @@ function copyExampleConfig2(name) {
76957
77685
  throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
76958
77686
  }
76959
77687
  const dest = resolve42(process.cwd(), "switchroom.yaml");
76960
- if (existsSync68(dest)) {
77688
+ if (existsSync69(dest)) {
76961
77689
  console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
76962
77690
  return;
76963
77691
  }
76964
77692
  const embedded = EMBEDDED_EXAMPLES[name];
76965
77693
  if (embedded !== undefined) {
76966
- writeFileSync34(dest, embedded, { encoding: "utf8" });
77694
+ writeFileSync35(dest, embedded, { encoding: "utf8" });
76967
77695
  console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
76968
77696
  return;
76969
77697
  }
76970
77698
  const exampleFile = resolve42(import.meta.dirname, `../../examples/${name}.yaml`);
76971
- if (!existsSync68(exampleFile)) {
77699
+ if (!existsSync69(exampleFile)) {
76972
77700
  throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
76973
77701
  }
76974
77702
  copyFileSync11(exampleFile, dest);
@@ -76979,8 +77707,8 @@ function findUnwritableAgentDirs(config, opts) {
76979
77707
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
76980
77708
  const unwritable = [];
76981
77709
  for (const name of targets) {
76982
- const startSh = join66(agentsDir, name, "start.sh");
76983
- if (!existsSync68(startSh))
77710
+ const startSh = join67(agentsDir, name, "start.sh");
77711
+ if (!existsSync69(startSh))
76984
77712
  continue;
76985
77713
  try {
76986
77714
  accessSync3(startSh, fsConstants6.W_OK);
@@ -77158,9 +77886,9 @@ function runRedactStdin() {
77158
77886
  }
77159
77887
 
77160
77888
  // src/cli/status-ask.ts
77161
- import { readFileSync as readFileSync57, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
77162
- import { join as join67 } from "node:path";
77163
- import { homedir as homedir38 } from "node:os";
77889
+ import { readFileSync as readFileSync57, existsSync as existsSync70, readdirSync as readdirSync26 } from "node:fs";
77890
+ import { join as join68 } from "node:path";
77891
+ import { homedir as homedir39 } from "node:os";
77164
77892
 
77165
77893
  // src/status-ask/report.ts
77166
77894
  function parseJsonl(content) {
@@ -77481,7 +78209,7 @@ function runReport(opts) {
77481
78209
  function resolveSources(explicitPath) {
77482
78210
  if (explicitPath != null && explicitPath.trim() !== "") {
77483
78211
  const trimmed = explicitPath.trim();
77484
- if (!existsSync69(trimmed)) {
78212
+ if (!existsSync70(trimmed)) {
77485
78213
  process.stderr.write(`status-ask report: ${trimmed}: file not found
77486
78214
  `);
77487
78215
  process.exit(1);
@@ -77495,9 +78223,9 @@ function resolveSources(explicitPath) {
77495
78223
  const config = loadConfig();
77496
78224
  agentsDir = resolveAgentsDir(config);
77497
78225
  } catch {
77498
- agentsDir = join67(homedir38(), ".switchroom", "agents");
78226
+ agentsDir = join68(homedir39(), ".switchroom", "agents");
77499
78227
  }
77500
- if (!existsSync69(agentsDir))
78228
+ if (!existsSync70(agentsDir))
77501
78229
  return [];
77502
78230
  const sources = [];
77503
78231
  let entries;
@@ -77507,8 +78235,8 @@ function resolveSources(explicitPath) {
77507
78235
  return [];
77508
78236
  }
77509
78237
  for (const name of entries) {
77510
- const path8 = join67(agentsDir, name, "runtime-metrics.jsonl");
77511
- if (existsSync69(path8)) {
78238
+ const path8 = join68(agentsDir, name, "runtime-metrics.jsonl");
78239
+ if (existsSync70(path8)) {
77512
78240
  sources.push({ path: path8, agent: name });
77513
78241
  }
77514
78242
  }
@@ -77529,17 +78257,17 @@ function inferAgentFromPath(p) {
77529
78257
 
77530
78258
  // src/cli/agent-config.ts
77531
78259
  init_helpers();
77532
- import { join as join68 } from "node:path";
77533
- import { homedir as homedir39 } from "node:os";
78260
+ import { join as join69 } from "node:path";
78261
+ import { homedir as homedir40 } from "node:os";
77534
78262
  import {
77535
- existsSync as existsSync70,
77536
- mkdirSync as mkdirSync38,
78263
+ existsSync as existsSync71,
78264
+ mkdirSync as mkdirSync39,
77537
78265
  appendFileSync as appendFileSync4,
77538
78266
  readFileSync as readFileSync58
77539
78267
  } from "node:fs";
77540
- var AUDIT_ROOT = join68(homedir39(), ".switchroom", "audit");
78268
+ var AUDIT_ROOT = join69(homedir40(), ".switchroom", "audit");
77541
78269
  function auditPathFor(agent) {
77542
- return join68(AUDIT_ROOT, agent, "agent-config.jsonl");
78270
+ return join69(AUDIT_ROOT, agent, "agent-config.jsonl");
77543
78271
  }
77544
78272
  function appendAudit(agent, cmd, args, exit, opts = {}) {
77545
78273
  const row = {
@@ -77553,8 +78281,8 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
77553
78281
  const path8 = opts.auditPath ?? auditPathFor(agent);
77554
78282
  const dir = path8.slice(0, path8.lastIndexOf("/"));
77555
78283
  try {
77556
- if (!existsSync70(dir)) {
77557
- mkdirSync38(dir, { recursive: true });
78284
+ if (!existsSync71(dir)) {
78285
+ mkdirSync39(dir, { recursive: true });
77558
78286
  }
77559
78287
  appendFileSync4(path8, JSON.stringify(row) + `
77560
78288
  `, { flag: "a" });
@@ -77565,7 +78293,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
77565
78293
  return true;
77566
78294
  const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
77567
78295
  try {
77568
- if (existsSync70(probe2))
78296
+ if (existsSync71(probe2))
77569
78297
  return true;
77570
78298
  } catch {}
77571
78299
  return false;
@@ -77626,7 +78354,7 @@ function getAgentSlice(config, agent) {
77626
78354
  }
77627
78355
  function readAuditTail(agent, limit, opts = {}) {
77628
78356
  const path8 = opts.auditPath ?? auditPathFor(agent);
77629
- if (!existsSync70(path8))
78357
+ if (!existsSync71(path8))
77630
78358
  return [];
77631
78359
  let raw;
77632
78360
  try {
@@ -77786,9 +78514,9 @@ var import_yaml15 = __toESM(require_dist(), 1);
77786
78514
  init_paths();
77787
78515
  import {
77788
78516
  closeSync as closeSync13,
77789
- existsSync as existsSync71,
78517
+ existsSync as existsSync72,
77790
78518
  fsyncSync as fsyncSync6,
77791
- mkdirSync as mkdirSync39,
78519
+ mkdirSync as mkdirSync40,
77792
78520
  openSync as openSync13,
77793
78521
  readdirSync as readdirSync27,
77794
78522
  readFileSync as readFileSync59,
@@ -77797,34 +78525,34 @@ import {
77797
78525
  unlinkSync as unlinkSync14,
77798
78526
  writeSync as writeSync8
77799
78527
  } from "node:fs";
77800
- import { join as join69, resolve as resolve43 } from "node:path";
78528
+ import { join as join70, resolve as resolve43 } from "node:path";
77801
78529
  var STAGING_SUBDIR = ".staging";
77802
78530
  function overlayPathsFor(agent, opts = {}) {
77803
78531
  const base = opts.root ? resolve43(opts.root, agent) : resolve43(resolveDualPath(`~/.switchroom/agents/${agent}`));
77804
- const scheduleDir = join69(base, "schedule.d");
77805
- const scheduleStagingDir = join69(scheduleDir, STAGING_SUBDIR);
77806
- const skillsDir = join69(base, "skills.d");
77807
- const skillsStagingDir = join69(skillsDir, STAGING_SUBDIR);
78532
+ const scheduleDir = join70(base, "schedule.d");
78533
+ const scheduleStagingDir = join70(scheduleDir, STAGING_SUBDIR);
78534
+ const skillsDir = join70(base, "skills.d");
78535
+ const skillsStagingDir = join70(skillsDir, STAGING_SUBDIR);
77808
78536
  return {
77809
78537
  agentRoot: base,
77810
78538
  scheduleDir,
77811
78539
  scheduleStagingDir,
77812
78540
  skillsDir,
77813
78541
  skillsStagingDir,
77814
- lockPath: join69(base, ".lock"),
78542
+ lockPath: join70(base, ".lock"),
77815
78543
  stagingDir: scheduleStagingDir
77816
78544
  };
77817
78545
  }
77818
78546
  function ensureDirs(paths) {
77819
- mkdirSync39(paths.scheduleDir, { recursive: true });
77820
- mkdirSync39(paths.scheduleStagingDir, { recursive: true });
78547
+ mkdirSync40(paths.scheduleDir, { recursive: true });
78548
+ mkdirSync40(paths.scheduleStagingDir, { recursive: true });
77821
78549
  }
77822
78550
  function ensureSkillsDirs(paths) {
77823
- mkdirSync39(paths.skillsDir, { recursive: true });
77824
- mkdirSync39(paths.skillsStagingDir, { recursive: true });
78551
+ mkdirSync40(paths.skillsDir, { recursive: true });
78552
+ mkdirSync40(paths.skillsStagingDir, { recursive: true });
77825
78553
  }
77826
78554
  function withAgentLock(paths, fn) {
77827
- mkdirSync39(paths.agentRoot, { recursive: true });
78555
+ mkdirSync40(paths.agentRoot, { recursive: true });
77828
78556
  const start = Date.now();
77829
78557
  const TIMEOUT_MS = 5000;
77830
78558
  let fd = null;
@@ -77865,8 +78593,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
77865
78593
  const paths = overlayPathsFor(agent, opts);
77866
78594
  return withAgentLock(paths, () => {
77867
78595
  ensureDirs(paths);
77868
- const stagingPath = join69(paths.scheduleStagingDir, `${slug}.yaml`);
77869
- const finalPath = join69(paths.scheduleDir, `${slug}.yaml`);
78596
+ const stagingPath = join70(paths.scheduleStagingDir, `${slug}.yaml`);
78597
+ const finalPath = join70(paths.scheduleDir, `${slug}.yaml`);
77870
78598
  const fd = openSync13(stagingPath, "w", 384);
77871
78599
  try {
77872
78600
  writeSync8(fd, yamlText);
@@ -77882,8 +78610,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
77882
78610
  const paths = overlayPathsFor(agent, opts);
77883
78611
  return withAgentLock(paths, () => {
77884
78612
  ensureSkillsDirs(paths);
77885
- const stagingPath = join69(paths.skillsStagingDir, `${slug}.yaml`);
77886
- const finalPath = join69(paths.skillsDir, `${slug}.yaml`);
78613
+ const stagingPath = join70(paths.skillsStagingDir, `${slug}.yaml`);
78614
+ const finalPath = join70(paths.skillsDir, `${slug}.yaml`);
77887
78615
  const fd = openSync13(stagingPath, "w", 384);
77888
78616
  try {
77889
78617
  writeSync8(fd, yamlText);
@@ -77898,8 +78626,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
77898
78626
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
77899
78627
  const paths = overlayPathsFor(agent, opts);
77900
78628
  return withAgentLock(paths, () => {
77901
- const finalPath = join69(paths.skillsDir, `${slug}.yaml`);
77902
- if (!existsSync71(finalPath))
78629
+ const finalPath = join70(paths.skillsDir, `${slug}.yaml`);
78630
+ if (!existsSync72(finalPath))
77903
78631
  return false;
77904
78632
  unlinkSync14(finalPath);
77905
78633
  return true;
@@ -77907,13 +78635,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
77907
78635
  }
77908
78636
  function listSkillsOverlayEntries(agent, opts = {}) {
77909
78637
  const paths = overlayPathsFor(agent, opts);
77910
- if (!existsSync71(paths.skillsDir))
78638
+ if (!existsSync72(paths.skillsDir))
77911
78639
  return [];
77912
78640
  const out = [];
77913
78641
  for (const name of readdirSync27(paths.skillsDir)) {
77914
78642
  if (!/\.ya?ml$/i.test(name))
77915
78643
  continue;
77916
- const full = join69(paths.skillsDir, name);
78644
+ const full = join70(paths.skillsDir, name);
77917
78645
  try {
77918
78646
  const raw = readFileSync59(full, "utf-8");
77919
78647
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -77925,8 +78653,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
77925
78653
  function deleteOverlayEntry(agent, slug, opts = {}) {
77926
78654
  const paths = overlayPathsFor(agent, opts);
77927
78655
  return withAgentLock(paths, () => {
77928
- const finalPath = join69(paths.scheduleDir, `${slug}.yaml`);
77929
- if (!existsSync71(finalPath))
78656
+ const finalPath = join70(paths.scheduleDir, `${slug}.yaml`);
78657
+ if (!existsSync72(finalPath))
77930
78658
  return false;
77931
78659
  unlinkSync14(finalPath);
77932
78660
  return true;
@@ -77934,13 +78662,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
77934
78662
  }
77935
78663
  function listOverlayEntries(agent, opts = {}) {
77936
78664
  const paths = overlayPathsFor(agent, opts);
77937
- if (!existsSync71(paths.scheduleDir))
78665
+ if (!existsSync72(paths.scheduleDir))
77938
78666
  return [];
77939
78667
  const out = [];
77940
78668
  for (const name of readdirSync27(paths.scheduleDir)) {
77941
78669
  if (!/\.ya?ml$/i.test(name))
77942
78670
  continue;
77943
- const full = join69(paths.scheduleDir, name);
78671
+ const full = join70(paths.scheduleDir, name);
77944
78672
  try {
77945
78673
  const raw = readFileSync59(full, "utf-8");
77946
78674
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -78085,27 +78813,27 @@ function reconcileAgentCronOnly(agent) {
78085
78813
  // src/cli/agent-config-pending.ts
78086
78814
  import {
78087
78815
  closeSync as closeSync14,
78088
- existsSync as existsSync72,
78816
+ existsSync as existsSync73,
78089
78817
  fsyncSync as fsyncSync7,
78090
- mkdirSync as mkdirSync40,
78818
+ mkdirSync as mkdirSync41,
78091
78819
  openSync as openSync14,
78092
78820
  readdirSync as readdirSync28,
78093
78821
  readFileSync as readFileSync60,
78094
78822
  renameSync as renameSync15,
78095
78823
  unlinkSync as unlinkSync15,
78096
- writeFileSync as writeFileSync35,
78824
+ writeFileSync as writeFileSync36,
78097
78825
  writeSync as writeSync9
78098
78826
  } from "node:fs";
78099
- import { join as join70 } from "node:path";
78827
+ import { join as join71 } from "node:path";
78100
78828
  import { randomBytes as randomBytes14 } from "node:crypto";
78101
78829
  var STAGE_ID_PREFIX = "cap_";
78102
78830
  function pendingDir(agent, opts = {}) {
78103
78831
  const paths = overlayPathsFor(agent, opts);
78104
- return join70(paths.scheduleDir, ".pending");
78832
+ return join71(paths.scheduleDir, ".pending");
78105
78833
  }
78106
78834
  function ensurePendingDir(agent, opts = {}) {
78107
78835
  const dir = pendingDir(agent, opts);
78108
- mkdirSync40(dir, { recursive: true });
78836
+ mkdirSync41(dir, { recursive: true });
78109
78837
  return dir;
78110
78838
  }
78111
78839
  function newStageId() {
@@ -78114,8 +78842,8 @@ function newStageId() {
78114
78842
  function stagePendingScheduleEntry(opts) {
78115
78843
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
78116
78844
  const stageId = opts.stageId ?? newStageId();
78117
- const yamlPath = join70(dir, `${stageId}.yaml`);
78118
- const metaPath = join70(dir, `${stageId}.meta.json`);
78845
+ const yamlPath = join71(dir, `${stageId}.yaml`);
78846
+ const metaPath = join71(dir, `${stageId}.meta.json`);
78119
78847
  const meta = {
78120
78848
  v: 1,
78121
78849
  stage_id: stageId,
@@ -78136,22 +78864,22 @@ function stagePendingScheduleEntry(opts) {
78136
78864
  }
78137
78865
  renameSync15(yamlTmp, yamlPath);
78138
78866
  }
78139
- writeFileSync35(metaPath, JSON.stringify(meta, null, 2) + `
78867
+ writeFileSync36(metaPath, JSON.stringify(meta, null, 2) + `
78140
78868
  `, { mode: 384 });
78141
78869
  return { stageId, yamlPath, metaPath };
78142
78870
  }
78143
78871
  function listPendingScheduleEntries(agent, opts = {}) {
78144
78872
  const dir = pendingDir(agent, opts);
78145
- if (!existsSync72(dir))
78873
+ if (!existsSync73(dir))
78146
78874
  return [];
78147
78875
  const out = [];
78148
78876
  for (const name of readdirSync28(dir).sort()) {
78149
78877
  if (!name.endsWith(".meta.json"))
78150
78878
  continue;
78151
78879
  const stageId = name.slice(0, -".meta.json".length);
78152
- const metaPath = join70(dir, name);
78153
- const yamlPath = join70(dir, `${stageId}.yaml`);
78154
- if (!existsSync72(yamlPath))
78880
+ const metaPath = join71(dir, name);
78881
+ const yamlPath = join71(dir, `${stageId}.yaml`);
78882
+ if (!existsSync73(yamlPath))
78155
78883
  continue;
78156
78884
  try {
78157
78885
  const meta = JSON.parse(readFileSync60(metaPath, "utf-8"));
@@ -78169,8 +78897,8 @@ function commitPendingScheduleEntry(opts) {
78169
78897
  return { committed: false, reason: "not_found" };
78170
78898
  const slug = match.meta.entry.name ?? match.stageId;
78171
78899
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
78172
- const finalPath = join70(paths.scheduleDir, `${slug}.yaml`);
78173
- if (existsSync72(finalPath)) {
78900
+ const finalPath = join71(paths.scheduleDir, `${slug}.yaml`);
78901
+ if (existsSync73(finalPath)) {
78174
78902
  return { committed: false, reason: "slug_collision" };
78175
78903
  }
78176
78904
  renameSync15(match.yamlPath, finalPath);
@@ -78193,7 +78921,7 @@ function denyPendingScheduleEntry(opts) {
78193
78921
 
78194
78922
  // src/cli/agent-config-write.ts
78195
78923
  init_protocol3();
78196
- import { existsSync as existsSync73, readFileSync as readFileSync61 } from "node:fs";
78924
+ import { existsSync as existsSync74, readFileSync as readFileSync61 } from "node:fs";
78197
78925
  var MAX_ENTRIES_PER_AGENT = 20;
78198
78926
  var MIN_CRON_INTERVAL_MIN = 5;
78199
78927
  function extractCronSmallestGapMin(expr) {
@@ -78479,7 +79207,7 @@ function scheduleRemove(opts) {
78479
79207
  }
78480
79208
  let priorContent = null;
78481
79209
  try {
78482
- if (existsSync73(match.path))
79210
+ if (existsSync74(match.path))
78483
79211
  priorContent = readFileSync61(match.path, "utf-8");
78484
79212
  } catch {}
78485
79213
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
@@ -78672,10 +79400,10 @@ function registerAgentConfigWriteCommands(program3) {
78672
79400
 
78673
79401
  // src/cli/agent-config-skill-write.ts
78674
79402
  var import_yaml16 = __toESM(require_dist(), 1);
78675
- import { existsSync as existsSync74 } from "node:fs";
79403
+ import { existsSync as existsSync75 } from "node:fs";
78676
79404
  init_reconcile_default_skills();
78677
79405
  var import_yaml17 = __toESM(require_dist(), 1);
78678
- import { join as join71 } from "node:path";
79406
+ import { join as join72 } from "node:path";
78679
79407
  var MAX_SKILLS_PER_AGENT = 20;
78680
79408
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
78681
79409
  function exitCodeFor2(code) {
@@ -78750,8 +79478,8 @@ function skillInstall(opts) {
78750
79478
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
78751
79479
  }
78752
79480
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
78753
- const skillPath = join71(poolDir, skillName);
78754
- if (!existsSync74(skillPath)) {
79481
+ const skillPath = join72(poolDir, skillName);
79482
+ if (!existsSync75(skillPath)) {
78755
79483
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
78756
79484
  }
78757
79485
  const yamlText = import_yaml16.stringify({ skills: [skillName] });
@@ -78915,9 +79643,9 @@ function registerAgentConfigSkillWriteCommands(program3) {
78915
79643
  // src/cli/skill.ts
78916
79644
  import {
78917
79645
  closeSync as closeSync15,
78918
- existsSync as existsSync75,
79646
+ existsSync as existsSync76,
78919
79647
  lstatSync as lstatSync8,
78920
- mkdirSync as mkdirSync41,
79648
+ mkdirSync as mkdirSync42,
78921
79649
  mkdtempSync as mkdtempSync5,
78922
79650
  openSync as openSync15,
78923
79651
  readFileSync as readFileSync62,
@@ -78926,10 +79654,10 @@ import {
78926
79654
  renameSync as renameSync16,
78927
79655
  rmSync as rmSync16,
78928
79656
  statSync as statSync29,
78929
- writeFileSync as writeFileSync36
79657
+ writeFileSync as writeFileSync37
78930
79658
  } from "node:fs";
78931
- import { tmpdir as tmpdir4, homedir as homedir40 } from "node:os";
78932
- import { dirname as dirname21, join as join72, relative as relative2, resolve as resolve44 } from "node:path";
79659
+ import { tmpdir as tmpdir4, homedir as homedir41 } from "node:os";
79660
+ import { dirname as dirname22, join as join73, relative as relative2, resolve as resolve44 } from "node:path";
78933
79661
  import { spawnSync as spawnSync10 } from "node:child_process";
78934
79662
 
78935
79663
  // src/cli/skill-common.ts
@@ -79123,10 +79851,10 @@ function scanForClaudeP2(content) {
79123
79851
  function resolveSkillsPoolDir2(override) {
79124
79852
  const raw = override ?? "~/.switchroom/skills";
79125
79853
  if (raw.startsWith("~/")) {
79126
- return join72(homedir40(), raw.slice(2));
79854
+ return join73(homedir41(), raw.slice(2));
79127
79855
  }
79128
79856
  if (raw === "~")
79129
- return homedir40();
79857
+ return homedir41();
79130
79858
  return resolve44(raw);
79131
79859
  }
79132
79860
  function readStdinSync() {
@@ -79162,7 +79890,7 @@ function loadFromDir(dir) {
79162
79890
  const walk2 = (sub) => {
79163
79891
  const entries = readdirSync29(sub, { withFileTypes: true });
79164
79892
  for (const ent of entries) {
79165
- const full = join72(sub, ent.name);
79893
+ const full = join73(sub, ent.name);
79166
79894
  const rel = relative2(abs, full);
79167
79895
  if (ent.isSymbolicLink()) {
79168
79896
  fail2(`refusing to read symlink inside --from dir: ${rel}`);
@@ -79197,7 +79925,7 @@ function loadFromTarball(tarPath) {
79197
79925
  fail2(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
79198
79926
  }
79199
79927
  }
79200
- const staging = mkdtempSync5(join72(tmpdir4(), "skill-apply-extract-"));
79928
+ const staging = mkdtempSync5(join73(tmpdir4(), "skill-apply-extract-"));
79201
79929
  try {
79202
79930
  const flags = isGz ? ["-xzf"] : ["-xf"];
79203
79931
  const r = spawnSync10("tar", [
@@ -79283,10 +80011,10 @@ function validatePayload(name, files) {
79283
80011
  errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
79284
80012
  }
79285
80013
  } else if (PY_SCRIPT_RE2.test(path8)) {
79286
- const tmp = mkdtempSync5(join72(tmpdir4(), "skill-apply-py-"));
79287
- const tmpPy = join72(tmp, "check.py");
80014
+ const tmp = mkdtempSync5(join73(tmpdir4(), "skill-apply-py-"));
80015
+ const tmpPy = join73(tmp, "check.py");
79288
80016
  try {
79289
- writeFileSync36(tmpPy, content);
80017
+ writeFileSync37(tmpPy, content);
79290
80018
  const r = spawnSync10("python3", ["-m", "py_compile", tmpPy], {
79291
80019
  encoding: "utf-8"
79292
80020
  });
@@ -79304,10 +80032,10 @@ function validatePayload(name, files) {
79304
80032
  function diffSummary(currentDir, files) {
79305
80033
  const lines = [];
79306
80034
  const currentFiles = {};
79307
- if (existsSync75(currentDir)) {
80035
+ if (existsSync76(currentDir)) {
79308
80036
  const walk2 = (sub) => {
79309
80037
  for (const ent of readdirSync29(sub, { withFileTypes: true })) {
79310
- const full = join72(sub, ent.name);
80038
+ const full = join73(sub, ent.name);
79311
80039
  const rel = relative2(currentDir, full);
79312
80040
  if (ent.isDirectory()) {
79313
80041
  walk2(full);
@@ -79340,10 +80068,10 @@ function diffSummary(currentDir, files) {
79340
80068
  `);
79341
80069
  }
79342
80070
  function writePayload(poolDir, name, files) {
79343
- if (!existsSync75(poolDir)) {
79344
- mkdirSync41(poolDir, { recursive: true, mode: 493 });
80071
+ if (!existsSync76(poolDir)) {
80072
+ mkdirSync42(poolDir, { recursive: true, mode: 493 });
79345
80073
  }
79346
- const target = join72(poolDir, name);
80074
+ const target = join73(poolDir, name);
79347
80075
  let targetIsSymlink = false;
79348
80076
  try {
79349
80077
  const st = lstatSync8(target);
@@ -79354,15 +80082,15 @@ function writePayload(poolDir, name, files) {
79354
80082
  if (targetIsSymlink) {
79355
80083
  fail2(`refusing to overwrite symlink at ${target}; investigate manually`);
79356
80084
  }
79357
- const staging = mkdtempSync5(join72(poolDir, `.skill-apply-stage-${name}-`));
80085
+ const staging = mkdtempSync5(join73(poolDir, `.skill-apply-stage-${name}-`));
79358
80086
  let oldRename = null;
79359
80087
  try {
79360
80088
  for (const [path8, content] of Object.entries(files)) {
79361
- const full = join72(staging, path8);
79362
- mkdirSync41(dirname21(full), { recursive: true, mode: 493 });
80089
+ const full = join73(staging, path8);
80090
+ mkdirSync42(dirname22(full), { recursive: true, mode: 493 });
79363
80091
  const fd = openSync15(full, "wx");
79364
80092
  try {
79365
- writeFileSync36(fd, content);
80093
+ writeFileSync37(fd, content);
79366
80094
  } finally {
79367
80095
  closeSync15(fd);
79368
80096
  }
@@ -79389,9 +80117,9 @@ function writePayload(poolDir, name, files) {
79389
80117
  try {
79390
80118
  rmSync16(staging, { recursive: true, force: true });
79391
80119
  } catch {}
79392
- if (oldRename && existsSync75(oldRename)) {
80120
+ if (oldRename && existsSync76(oldRename)) {
79393
80121
  try {
79394
- if (existsSync75(target)) {
80122
+ if (existsSync76(target)) {
79395
80123
  rmSync16(target, { recursive: true, force: true });
79396
80124
  }
79397
80125
  renameSync16(oldRename, target);
@@ -79412,7 +80140,7 @@ function registerSkillCommand(program3) {
79412
80140
  files = loadFromStdin();
79413
80141
  } else {
79414
80142
  const fromPath = resolve44(opts.from);
79415
- if (!existsSync75(fromPath)) {
80143
+ if (!existsSync76(fromPath)) {
79416
80144
  fail2(`--from path does not exist: ${opts.from}`);
79417
80145
  }
79418
80146
  const st = statSync29(fromPath);
@@ -79436,7 +80164,7 @@ function registerSkillCommand(program3) {
79436
80164
  }
79437
80165
  const config = loadConfig();
79438
80166
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
79439
- const currentDir = join72(poolDir, name);
80167
+ const currentDir = join73(poolDir, name);
79440
80168
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
79441
80169
  console.log(source_default.bold("Diff vs current pool content:"));
79442
80170
  console.log(diffSummary(currentDir, files));
@@ -79467,9 +80195,9 @@ function sumBytes(files) {
79467
80195
  // src/cli/skill-personal.ts
79468
80196
  import {
79469
80197
  closeSync as closeSync16,
79470
- existsSync as existsSync76,
80198
+ existsSync as existsSync77,
79471
80199
  lstatSync as lstatSync9,
79472
- mkdirSync as mkdirSync42,
80200
+ mkdirSync as mkdirSync43,
79473
80201
  mkdtempSync as mkdtempSync6,
79474
80202
  openSync as openSync16,
79475
80203
  readFileSync as readFileSync63,
@@ -79478,10 +80206,10 @@ import {
79478
80206
  rmSync as rmSync17,
79479
80207
  statSync as statSync30,
79480
80208
  utimesSync,
79481
- writeFileSync as writeFileSync37
80209
+ writeFileSync as writeFileSync38
79482
80210
  } from "node:fs";
79483
- import { dirname as dirname22, join as join73, relative as relative3, resolve as resolve45 } from "node:path";
79484
- import { homedir as homedir41, tmpdir as tmpdir5 } from "node:os";
80211
+ import { dirname as dirname23, join as join74, relative as relative3, resolve as resolve45 } from "node:path";
80212
+ import { homedir as homedir42, tmpdir as tmpdir5 } from "node:os";
79485
80213
  import { spawnSync as spawnSync11 } from "node:child_process";
79486
80214
  init_helpers();
79487
80215
  init_source();
@@ -79491,15 +80219,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
79491
80219
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
79492
80220
  function resolveConfigSkillsDir(agent) {
79493
80221
  const override = process.env.SWITCHROOM_CONFIG_DIR;
79494
- const candidate = override ? resolve45(override) : join73(homedir41(), ".switchroom-config");
79495
- if (!existsSync76(candidate))
80222
+ const candidate = override ? resolve45(override) : join74(homedir42(), ".switchroom-config");
80223
+ if (!existsSync77(candidate))
79496
80224
  return null;
79497
- return join73(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
80225
+ return join74(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
79498
80226
  }
79499
80227
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
79500
80228
  function sweepMirrorPriors(configSkillsRoot) {
79501
80229
  try {
79502
- if (!existsSync76(configSkillsRoot))
80230
+ if (!existsSync77(configSkillsRoot))
79503
80231
  return;
79504
80232
  const now = Date.now();
79505
80233
  for (const ent of readdirSync30(configSkillsRoot)) {
@@ -79512,7 +80240,7 @@ function sweepMirrorPriors(configSkillsRoot) {
79512
80240
  if (now - ts < MIRROR_PRIOR_TTL_MS)
79513
80241
  continue;
79514
80242
  try {
79515
- rmSync17(join73(configSkillsRoot, ent), { recursive: true, force: true });
80243
+ rmSync17(join74(configSkillsRoot, ent), { recursive: true, force: true });
79516
80244
  } catch {}
79517
80245
  }
79518
80246
  } catch {}
@@ -79521,7 +80249,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
79521
80249
  const configSkillsRoot = resolveConfigSkillsDir(agent);
79522
80250
  if (!configSkillsRoot)
79523
80251
  return;
79524
- const dest = join73(configSkillsRoot, name);
80252
+ const dest = join74(configSkillsRoot, name);
79525
80253
  try {
79526
80254
  if (liveSkillDir !== null) {
79527
80255
  try {
@@ -79535,32 +80263,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
79535
80263
  }
79536
80264
  if (liveSkillDir === null) {
79537
80265
  sweepMirrorPriors(configSkillsRoot);
79538
- if (existsSync76(dest)) {
79539
- const trash = join73(configSkillsRoot, `.${name}-trash-${Date.now()}`);
80266
+ if (existsSync77(dest)) {
80267
+ const trash = join74(configSkillsRoot, `.${name}-trash-${Date.now()}`);
79540
80268
  renameSync17(dest, trash);
79541
80269
  }
79542
80270
  return;
79543
80271
  }
79544
- mkdirSync42(configSkillsRoot, { recursive: true, mode: 493 });
80272
+ mkdirSync43(configSkillsRoot, { recursive: true, mode: 493 });
79545
80273
  sweepMirrorPriors(configSkillsRoot);
79546
- const staging = mkdtempSync6(join73(configSkillsRoot, `.${name}-staging-`));
80274
+ const staging = mkdtempSync6(join74(configSkillsRoot, `.${name}-staging-`));
79547
80275
  const walk2 = (src, dst) => {
79548
- mkdirSync42(dst, { recursive: true, mode: 493 });
80276
+ mkdirSync43(dst, { recursive: true, mode: 493 });
79549
80277
  for (const ent of readdirSync30(src, { withFileTypes: true })) {
79550
- const s = join73(src, ent.name);
79551
- const d = join73(dst, ent.name);
80278
+ const s = join74(src, ent.name);
80279
+ const d = join74(dst, ent.name);
79552
80280
  if (ent.isSymbolicLink())
79553
80281
  continue;
79554
80282
  if (ent.isDirectory())
79555
80283
  walk2(s, d);
79556
80284
  else if (ent.isFile()) {
79557
- writeFileSync37(d, readFileSync63(s));
80285
+ writeFileSync38(d, readFileSync63(s));
79558
80286
  }
79559
80287
  }
79560
80288
  };
79561
80289
  walk2(liveSkillDir, staging);
79562
- if (existsSync76(dest)) {
79563
- const prior = join73(configSkillsRoot, `.${name}-prior-${Date.now()}`);
80290
+ if (existsSync77(dest)) {
80291
+ const prior = join74(configSkillsRoot, `.${name}-prior-${Date.now()}`);
79564
80292
  renameSync17(dest, prior);
79565
80293
  }
79566
80294
  renameSync17(staging, dest);
@@ -79587,13 +80315,13 @@ function resolveAgent(opts) {
79587
80315
  function resolveAgentsRoot(opts) {
79588
80316
  if (opts.root)
79589
80317
  return resolve45(opts.root);
79590
- return join73(homedir41(), ".switchroom", "agents");
80318
+ return join74(homedir42(), ".switchroom", "agents");
79591
80319
  }
79592
80320
  function personalSkillDir(agentsRoot, agent, name) {
79593
- return join73(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
80321
+ return join74(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
79594
80322
  }
79595
80323
  function trashDir(agentsRoot, agent) {
79596
- return join73(agentsRoot, agent, ".claude", TRASH_DIRNAME);
80324
+ return join74(agentsRoot, agent, ".claude", TRASH_DIRNAME);
79597
80325
  }
79598
80326
  function readStdinSync2() {
79599
80327
  const chunks = [];
@@ -79623,7 +80351,7 @@ function loadFromDir2(dir) {
79623
80351
  const files = {};
79624
80352
  const walk2 = (sub) => {
79625
80353
  for (const ent of readdirSync30(sub, { withFileTypes: true })) {
79626
- const full = join73(sub, ent.name);
80354
+ const full = join74(sub, ent.name);
79627
80355
  if (ent.isSymbolicLink()) {
79628
80356
  fail3(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
79629
80357
  }
@@ -79676,10 +80404,10 @@ function behavioralValidate(files) {
79676
80404
  errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
79677
80405
  }
79678
80406
  } else if (PY_SCRIPT_RE.test(path8)) {
79679
- const tmp = mkdtempSync6(join73(tmpdir5(), "skill-personal-py-"));
79680
- const tmpPy = join73(tmp, "check.py");
80407
+ const tmp = mkdtempSync6(join74(tmpdir5(), "skill-personal-py-"));
80408
+ const tmpPy = join74(tmp, "check.py");
79681
80409
  try {
79682
- writeFileSync37(tmpPy, content);
80410
+ writeFileSync38(tmpPy, content);
79683
80411
  const r = spawnSync11("python3", ["-m", "py_compile", tmpPy], {
79684
80412
  encoding: "utf-8"
79685
80413
  });
@@ -79695,13 +80423,13 @@ function behavioralValidate(files) {
79695
80423
  }
79696
80424
  function sweepTrash(agentsRoot, agent) {
79697
80425
  const trash = trashDir(agentsRoot, agent);
79698
- if (!existsSync76(trash))
80426
+ if (!existsSync77(trash))
79699
80427
  return;
79700
80428
  const now = Date.now();
79701
80429
  for (const ent of readdirSync30(trash, { withFileTypes: true })) {
79702
80430
  if (!ent.isDirectory())
79703
80431
  continue;
79704
- const entPath = join73(trash, ent.name);
80432
+ const entPath = join74(trash, ent.name);
79705
80433
  try {
79706
80434
  const st = statSync30(entPath);
79707
80435
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -79721,16 +80449,16 @@ function writePersonalSkill(targetDir, files) {
79721
80449
  if (targetIsSymlink) {
79722
80450
  fail3(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
79723
80451
  }
79724
- mkdirSync42(dirname22(targetDir), { recursive: true, mode: 493 });
79725
- const staging = mkdtempSync6(join73(dirname22(targetDir), `.skill-personal-stage-`));
80452
+ mkdirSync43(dirname23(targetDir), { recursive: true, mode: 493 });
80453
+ const staging = mkdtempSync6(join74(dirname23(targetDir), `.skill-personal-stage-`));
79726
80454
  let oldRename = null;
79727
80455
  try {
79728
80456
  for (const [path8, content] of Object.entries(files)) {
79729
- const full = join73(staging, path8);
79730
- mkdirSync42(dirname22(full), { recursive: true, mode: 493 });
80457
+ const full = join74(staging, path8);
80458
+ mkdirSync43(dirname23(full), { recursive: true, mode: 493 });
79731
80459
  const fd = openSync16(full, "wx");
79732
80460
  try {
79733
- writeFileSync37(fd, content);
80461
+ writeFileSync38(fd, content);
79734
80462
  } finally {
79735
80463
  closeSync16(fd);
79736
80464
  }
@@ -79757,9 +80485,9 @@ function writePersonalSkill(targetDir, files) {
79757
80485
  try {
79758
80486
  rmSync17(staging, { recursive: true, force: true });
79759
80487
  } catch {}
79760
- if (oldRename && existsSync76(oldRename)) {
80488
+ if (oldRename && existsSync77(oldRename)) {
79761
80489
  try {
79762
- if (existsSync76(targetDir)) {
80490
+ if (existsSync77(targetDir)) {
79763
80491
  rmSync17(targetDir, { recursive: true, force: true });
79764
80492
  }
79765
80493
  renameSync17(oldRename, targetDir);
@@ -79811,7 +80539,7 @@ function loadFiles(opts) {
79811
80539
  return loadFromStdin2();
79812
80540
  }
79813
80541
  const p = resolve45(opts.from);
79814
- if (!existsSync76(p)) {
80542
+ if (!existsSync77(p)) {
79815
80543
  fail3(`--from path does not exist: ${opts.from}`);
79816
80544
  }
79817
80545
  const st = statSync30(p);
@@ -79859,10 +80587,10 @@ function editPersonalAction(name, opts) {
79859
80587
  }
79860
80588
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
79861
80589
  function defaultSharedRoot() {
79862
- return join73(homedir41(), ".switchroom", "skills");
80590
+ return join74(homedir42(), ".switchroom", "skills");
79863
80591
  }
79864
80592
  function defaultBundledRoot() {
79865
- return join73(homedir41(), ".switchroom", "skills", "_bundled");
80593
+ return join74(homedir42(), ".switchroom", "skills", "_bundled");
79866
80594
  }
79867
80595
  function resolveCloneSource(source, opts) {
79868
80596
  const m = CLONE_SOURCE_RE.exec(source);
@@ -79872,8 +80600,8 @@ function resolveCloneSource(source, opts) {
79872
80600
  const tier = m[1];
79873
80601
  const slug = m[2];
79874
80602
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
79875
- const dir = join73(root, slug);
79876
- if (!existsSync76(dir)) {
80603
+ const dir = join74(root, slug);
80604
+ if (!existsSync77(dir)) {
79877
80605
  fail3(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
79878
80606
  }
79879
80607
  const st = lstatSync9(dir);
@@ -79888,7 +80616,7 @@ function readSourceFiles(dir) {
79888
80616
  const skipped = [];
79889
80617
  const walk2 = (sub) => {
79890
80618
  for (const ent of readdirSync30(sub, { withFileTypes: true })) {
79891
- const full = join73(sub, ent.name);
80619
+ const full = join74(sub, ent.name);
79892
80620
  if (ent.isSymbolicLink()) {
79893
80621
  continue;
79894
80622
  }
@@ -79997,9 +80725,9 @@ function removePersonalAction(name, opts) {
79997
80725
  throw err2;
79998
80726
  }
79999
80727
  const trashRoot = trashDir(agentsRoot, agent);
80000
- mkdirSync42(trashRoot, { recursive: true, mode: 493 });
80728
+ mkdirSync43(trashRoot, { recursive: true, mode: 493 });
80001
80729
  const ts = Date.now();
80002
- const trashTarget = join73(trashRoot, `${name}-${ts}`);
80730
+ const trashTarget = join74(trashRoot, `${name}-${ts}`);
80003
80731
  renameSync17(target, trashTarget);
80004
80732
  const now = new Date(ts);
80005
80733
  utimesSync(trashTarget, now, now);
@@ -80018,16 +80746,16 @@ function listPersonalAction(opts) {
80018
80746
  const agent = resolveAgent(opts);
80019
80747
  const agentsRoot = resolveAgentsRoot(opts);
80020
80748
  sweepTrash(agentsRoot, agent);
80021
- const skillsDir = join73(agentsRoot, agent, ".claude", "skills");
80749
+ const skillsDir = join74(agentsRoot, agent, ".claude", "skills");
80022
80750
  const personal = [];
80023
- if (existsSync76(skillsDir)) {
80751
+ if (existsSync77(skillsDir)) {
80024
80752
  for (const ent of readdirSync30(skillsDir, { withFileTypes: true })) {
80025
80753
  if (!ent.isDirectory())
80026
80754
  continue;
80027
80755
  if (!ent.name.startsWith(PERSONAL_PREFIX))
80028
80756
  continue;
80029
80757
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
80030
- const skillPath = join73(skillsDir, ent.name);
80758
+ const skillPath = join74(skillsDir, ent.name);
80031
80759
  let fileCount = 0;
80032
80760
  let totalBytes = 0;
80033
80761
  const walk2 = (sub) => {
@@ -80035,10 +80763,10 @@ function listPersonalAction(opts) {
80035
80763
  if (e.isFile()) {
80036
80764
  fileCount += 1;
80037
80765
  try {
80038
- totalBytes += statSync30(join73(sub, e.name)).size;
80766
+ totalBytes += statSync30(join74(sub, e.name)).size;
80039
80767
  } catch {}
80040
80768
  } else if (e.isDirectory()) {
80041
- walk2(join73(sub, e.name));
80769
+ walk2(join74(sub, e.name));
80042
80770
  }
80043
80771
  }
80044
80772
  };
@@ -80077,24 +80805,24 @@ function registerSkillPersonalCommands(program3) {
80077
80805
  // src/cli/skill-search.ts
80078
80806
  init_helpers();
80079
80807
  var import_yaml19 = __toESM(require_dist(), 1);
80080
- import { existsSync as existsSync77, readdirSync as readdirSync31, readFileSync as readFileSync64, statSync as statSync31 } from "node:fs";
80081
- import { homedir as homedir42 } from "node:os";
80082
- import { join as join74, resolve as resolve46 } from "node:path";
80808
+ import { existsSync as existsSync78, readdirSync as readdirSync31, readFileSync as readFileSync64, statSync as statSync31 } from "node:fs";
80809
+ import { homedir as homedir43 } from "node:os";
80810
+ import { join as join75, resolve as resolve46 } from "node:path";
80083
80811
  var PERSONAL_PREFIX2 = "personal-";
80084
80812
  var BUNDLED_SUBDIR = "_bundled";
80085
80813
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
80086
80814
  function defaultAgentsRoot() {
80087
- return resolve46(homedir42(), ".switchroom/agents");
80815
+ return resolve46(homedir43(), ".switchroom/agents");
80088
80816
  }
80089
80817
  function defaultSharedRoot2() {
80090
- return resolve46(homedir42(), ".switchroom/skills");
80818
+ return resolve46(homedir43(), ".switchroom/skills");
80091
80819
  }
80092
80820
  function defaultBundledRoot2() {
80093
- return resolve46(homedir42(), ".switchroom/skills/_bundled");
80821
+ return resolve46(homedir43(), ".switchroom/skills/_bundled");
80094
80822
  }
80095
80823
  function readSkillFrontmatter(skillDir) {
80096
- const mdPath = join74(skillDir, "SKILL.md");
80097
- if (!existsSync77(mdPath))
80824
+ const mdPath = join75(skillDir, "SKILL.md");
80825
+ if (!existsSync78(mdPath))
80098
80826
  return null;
80099
80827
  let content;
80100
80828
  try {
@@ -80126,7 +80854,7 @@ function readSkillFrontmatter(skillDir) {
80126
80854
  return { fm: parsed };
80127
80855
  }
80128
80856
  function statSkillMd(skillDir) {
80129
- const mdPath = join74(skillDir, "SKILL.md");
80857
+ const mdPath = join75(skillDir, "SKILL.md");
80130
80858
  try {
80131
80859
  const st = statSync31(mdPath);
80132
80860
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -80137,8 +80865,8 @@ function statSkillMd(skillDir) {
80137
80865
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
80138
80866
  if (!AGENT_NAME_RE3.test(agent))
80139
80867
  return [];
80140
- const skillsDir = join74(agentsRoot, agent, ".claude/skills");
80141
- if (!existsSync77(skillsDir))
80868
+ const skillsDir = join75(agentsRoot, agent, ".claude/skills");
80869
+ if (!existsSync78(skillsDir))
80142
80870
  return [];
80143
80871
  const out = [];
80144
80872
  let entries;
@@ -80150,7 +80878,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
80150
80878
  for (const ent of entries) {
80151
80879
  if (!ent.startsWith(PERSONAL_PREFIX2))
80152
80880
  continue;
80153
- const dirPath = join74(skillsDir, ent);
80881
+ const dirPath = join75(skillsDir, ent);
80154
80882
  try {
80155
80883
  if (!statSync31(dirPath).isDirectory())
80156
80884
  continue;
@@ -80176,7 +80904,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
80176
80904
  return out;
80177
80905
  }
80178
80906
  function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
80179
- if (!existsSync77(sharedRoot))
80907
+ if (!existsSync78(sharedRoot))
80180
80908
  return [];
80181
80909
  const out = [];
80182
80910
  let entries;
@@ -80190,7 +80918,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
80190
80918
  continue;
80191
80919
  if (ent.startsWith("."))
80192
80920
  continue;
80193
- const dirPath = join74(sharedRoot, ent);
80921
+ const dirPath = join75(sharedRoot, ent);
80194
80922
  try {
80195
80923
  if (!statSync31(dirPath).isDirectory())
80196
80924
  continue;
@@ -80214,7 +80942,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
80214
80942
  return out;
80215
80943
  }
80216
80944
  function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
80217
- if (!existsSync77(bundledRoot))
80945
+ if (!existsSync78(bundledRoot))
80218
80946
  return [];
80219
80947
  const out = [];
80220
80948
  let entries;
@@ -80226,7 +80954,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
80226
80954
  for (const ent of entries) {
80227
80955
  if (ent.startsWith("."))
80228
80956
  continue;
80229
- const dirPath = join74(bundledRoot, ent);
80957
+ const dirPath = join75(bundledRoot, ent);
80230
80958
  try {
80231
80959
  if (!statSync31(dirPath).isDirectory())
80232
80960
  continue;
@@ -80370,9 +81098,9 @@ function registerHostdMcpCommand(program3) {
80370
81098
  // src/cli/hostd.ts
80371
81099
  init_source();
80372
81100
  init_helpers();
80373
- import { existsSync as existsSync79, mkdirSync as mkdirSync43, readdirSync as readdirSync32, readFileSync as readFileSync66, writeFileSync as writeFileSync38, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
80374
- import { homedir as homedir43 } from "node:os";
80375
- import { join as join75 } from "node:path";
81101
+ import { existsSync as existsSync80, mkdirSync as mkdirSync44, readdirSync as readdirSync32, readFileSync as readFileSync66, writeFileSync as writeFileSync39, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
81102
+ import { homedir as homedir44 } from "node:os";
81103
+ import { join as join76 } from "node:path";
80376
81104
  import { spawnSync as spawnSync13 } from "node:child_process";
80377
81105
  init_audit_reader();
80378
81106
  var DEFAULT_IMAGE_TAG = "latest";
@@ -80463,14 +81191,14 @@ networks:
80463
81191
  `;
80464
81192
  }
80465
81193
  function hostdDir() {
80466
- return join75(homedir43(), ".switchroom", "hostd");
81194
+ return join76(homedir44(), ".switchroom", "hostd");
80467
81195
  }
80468
81196
  function hostdComposePath() {
80469
- return join75(hostdDir(), "docker-compose.yml");
81197
+ return join76(hostdDir(), "docker-compose.yml");
80470
81198
  }
80471
81199
  function backupExistingCompose() {
80472
81200
  const p = hostdComposePath();
80473
- if (!existsSync79(p))
81201
+ if (!existsSync80(p))
80474
81202
  return null;
80475
81203
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
80476
81204
  const bak = `${p}.bak-${ts}`;
@@ -80503,9 +81231,9 @@ async function doInstall(opts, program3) {
80503
81231
  }
80504
81232
  const dir = hostdDir();
80505
81233
  const composePath = hostdComposePath();
80506
- mkdirSync43(dir, { recursive: true });
81234
+ mkdirSync44(dir, { recursive: true });
80507
81235
  const yaml = renderHostdComposeFile({
80508
- hostHome: homedir43(),
81236
+ hostHome: homedir44(),
80509
81237
  imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
80510
81238
  operatorUid: resolveOperatorUid()
80511
81239
  });
@@ -80518,7 +81246,7 @@ async function doInstall(opts, program3) {
80518
81246
  const bak = backupExistingCompose();
80519
81247
  if (bak)
80520
81248
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
80521
- writeFileSync38(composePath, yaml, "utf8");
81249
+ writeFileSync39(composePath, yaml, "utf8");
80522
81250
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
80523
81251
  console.log(source_default.dim(` admin agents: ${adminAgents.length === 0 ? "(none)" : adminAgents.join(", ")}`));
80524
81252
  console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-hostd:${opts.tag ?? DEFAULT_IMAGE_TAG}\u2026`));
@@ -80547,7 +81275,7 @@ function doStatus() {
80547
81275
  const composeYml = hostdComposePath();
80548
81276
  console.log(source_default.bold("switchroom-hostd"));
80549
81277
  console.log("");
80550
- if (!existsSync79(composeYml)) {
81278
+ if (!existsSync80(composeYml)) {
80551
81279
  console.log(source_default.yellow(" compose: not installed"));
80552
81280
  console.log(source_default.dim(" run `switchroom hostd install` to set up."));
80553
81281
  return;
@@ -80568,14 +81296,14 @@ function doStatus() {
80568
81296
  } else {
80569
81297
  console.log(source_default.green(` container: ${ps.stdout.trim()}`));
80570
81298
  }
80571
- if (existsSync79(dir)) {
81299
+ if (existsSync80(dir)) {
80572
81300
  const entries = [];
80573
81301
  try {
80574
81302
  for (const name of readdirSync32(dir)) {
80575
81303
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
80576
81304
  continue;
80577
- const sockPath = join75(dir, name, "sock");
80578
- if (existsSync79(sockPath)) {
81305
+ const sockPath = join76(dir, name, "sock");
81306
+ if (existsSync80(sockPath)) {
80579
81307
  const st = statSync32(sockPath);
80580
81308
  if ((st.mode & 61440) === 49152) {
80581
81309
  entries.push(`${name} \u2192 ${sockPath}`);
@@ -80594,7 +81322,7 @@ function doStatus() {
80594
81322
  }
80595
81323
  function doUninstall() {
80596
81324
  const composeYml = hostdComposePath();
80597
- if (!existsSync79(composeYml)) {
81325
+ if (!existsSync80(composeYml)) {
80598
81326
  console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
80599
81327
  return;
80600
81328
  }
@@ -80618,7 +81346,7 @@ function registerHostdCommand(program3) {
80618
81346
  hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
80619
81347
  hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
80620
81348
  const logPath = opts.path ?? defaultAuditLogPath2();
80621
- if (!existsSync79(logPath)) {
81349
+ if (!existsSync80(logPath)) {
80622
81350
  console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
80623
81351
  The log is created when hostd handles its first privileged-verb request.`));
80624
81352
  return;
@@ -80696,6 +81424,8 @@ registerWorktreeCommand(program3);
80696
81424
  registerDriveCommand(program3);
80697
81425
  registerDriveMcpLauncherCommand(program3);
80698
81426
  registerM365McpLauncherCommand(program3);
81427
+ registerNotionMcpLauncherCommand(program3);
81428
+ registerNotionCommand(program3);
80699
81429
  registerApplyCommand(program3);
80700
81430
  registerSecretDetectCommand(program3);
80701
81431
  registerStatusAskCommand(program3);