switchroom 0.15.19 → 0.15.20

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.
@@ -15644,6 +15644,240 @@ function classifyTimezoneSource(config, resolvedAgent) {
15644
15644
  }
15645
15645
  var init_timezone = () => {};
15646
15646
 
15647
+ // src/config/google-workspace-acl.ts
15648
+ function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
15649
+ if (!agentGoogleAccount)
15650
+ return false;
15651
+ const account = agentGoogleAccount.trim().toLowerCase();
15652
+ if (account.length === 0)
15653
+ return false;
15654
+ const acctEntry = googleAccounts?.[account];
15655
+ if (!acctEntry)
15656
+ return false;
15657
+ const enabledFor = acctEntry.enabled_for ?? [];
15658
+ return enabledFor.includes(agentName);
15659
+ }
15660
+ function vaultRefKey(value) {
15661
+ if (typeof value !== "string" || !value.startsWith("vault:"))
15662
+ return null;
15663
+ const key = value.slice("vault:".length).split("#")[0];
15664
+ return key.length > 0 ? key : null;
15665
+ }
15666
+ function isGoogleClientCredentialKeyForAgent(config, agentName, key) {
15667
+ if (!agentName || !key)
15668
+ return false;
15669
+ const agentConfig = config.agents?.[agentName];
15670
+ if (!agentConfig)
15671
+ return false;
15672
+ if (agentConfig.mcp_servers?.["gdrive"] === false) {
15673
+ return false;
15674
+ }
15675
+ const account = agentConfig.google_workspace?.account;
15676
+ if (!shouldEmitGdriveMcp(agentName, account, config.google_accounts)) {
15677
+ return false;
15678
+ }
15679
+ const gw = config.google_workspace;
15680
+ if (!gw)
15681
+ return false;
15682
+ for (const ref of [gw.google_client_id, gw.google_client_secret]) {
15683
+ if (vaultRefKey(ref) === key)
15684
+ return true;
15685
+ }
15686
+ return false;
15687
+ }
15688
+
15689
+ // src/vault/broker/acl.ts
15690
+ function isWebkiteCredentialKeyForAgent(agentConfig, key) {
15691
+ if (!WEBKITE_VAULT_KEYS.has(key))
15692
+ return false;
15693
+ if ((agentConfig?.mcp_servers ?? {})["webkite"] === false)
15694
+ return false;
15695
+ return true;
15696
+ }
15697
+ function parseCronUnit(unitName) {
15698
+ const m = unitName.match(/^switchroom-([a-zA-Z0-9_-]+)-cron-(\d+)\.service$/);
15699
+ if (!m)
15700
+ return null;
15701
+ const agentName = m[1];
15702
+ const index = parseInt(m[2], 10);
15703
+ if (!agentName)
15704
+ return null;
15705
+ return { agentName, index };
15706
+ }
15707
+ function agentSlugFromPeer(peer) {
15708
+ if (peer.systemdUnit === null)
15709
+ return null;
15710
+ const parsed = parseCronUnit(peer.systemdUnit);
15711
+ return parsed?.agentName ?? null;
15712
+ }
15713
+ function checkEntryScope(scope, agentSlug) {
15714
+ if (scope === undefined || scope === null) {
15715
+ return { allow: true };
15716
+ }
15717
+ const deny = scope.deny ?? [];
15718
+ const allow = scope.allow ?? [];
15719
+ if (agentSlug !== null && deny.includes(agentSlug)) {
15720
+ return {
15721
+ allow: false,
15722
+ reason: `agent '${agentSlug}' is in the entry's deny list (scope-deny)`
15723
+ };
15724
+ }
15725
+ if (allow.length > 0) {
15726
+ if (agentSlug === null || !allow.includes(agentSlug)) {
15727
+ return {
15728
+ allow: false,
15729
+ reason: agentSlug === null ? "caller agent slug could not be determined; entry has a non-empty allow list (scope-allow)" : `agent '${agentSlug}' is not in the entry's allow list (scope-allow)`
15730
+ };
15731
+ }
15732
+ }
15733
+ return { allow: true };
15734
+ }
15735
+ function checkAcl(peer, config, key) {
15736
+ if (peer.systemdUnit !== null) {
15737
+ const parsed = parseCronUnit(peer.systemdUnit);
15738
+ if (parsed === null) {
15739
+ return {
15740
+ allow: false,
15741
+ reason: `systemd unit '${peer.systemdUnit}' does not match switchroom cron unit naming convention`
15742
+ };
15743
+ }
15744
+ const { agentName, index } = parsed;
15745
+ const agentConfig = config.agents?.[agentName];
15746
+ if (!agentConfig) {
15747
+ return { allow: false, reason: `agent '${agentName}' not found in config` };
15748
+ }
15749
+ const schedule = agentConfig.schedule ?? [];
15750
+ if (index >= schedule.length || index < 0) {
15751
+ return {
15752
+ allow: false,
15753
+ reason: `schedule index ${index} out of range for agent '${agentName}' (${schedule.length} entries)`
15754
+ };
15755
+ }
15756
+ const entry = schedule[index];
15757
+ const allowedKeys = entry.secrets ?? [];
15758
+ if (!allowedKeys.includes(key)) {
15759
+ return {
15760
+ allow: false,
15761
+ reason: `key '${key}' not in ACL for ${agentName}/schedule[${index}]`
15762
+ };
15763
+ }
15764
+ return { allow: true };
15765
+ }
15766
+ return {
15767
+ allow: false,
15768
+ reason: "caller is not a switchroom cron unit; use 'switchroom vault get --no-broker' for interactive access"
15769
+ };
15770
+ }
15771
+ function checkAclByAgent(config, agentName, key) {
15772
+ if (!agentName) {
15773
+ return { allow: false, reason: "agent name unresolved" };
15774
+ }
15775
+ const agentConfig = config.agents?.[agentName];
15776
+ if (!agentConfig) {
15777
+ return { allow: false, reason: `agent '${agentName}' not found in config` };
15778
+ }
15779
+ const googleSlot = parseGoogleAccountSlotKey(key);
15780
+ if (googleSlot !== null) {
15781
+ return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
15782
+ }
15783
+ if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
15784
+ return { allow: true };
15785
+ }
15786
+ if (isWebkiteCredentialKeyForAgent(agentConfig, key)) {
15787
+ return { allow: true };
15788
+ }
15789
+ const agentBot = agentConfig.bot_token;
15790
+ const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
15791
+ if (typeof botRef === "string" && botRef.startsWith("vault:")) {
15792
+ const botKey = botRef.slice("vault:".length).split("#")[0];
15793
+ if (botKey.length > 0 && botKey === key) {
15794
+ return { allow: true };
15795
+ }
15796
+ }
15797
+ const cfgWithProfiles = config;
15798
+ const profileName = agentConfig.extends;
15799
+ const profileMcp = profileName != null && profileName.length > 0 ? cfgWithProfiles.profiles?.[profileName]?.mcp_servers ?? {} : {};
15800
+ const effectiveMcp = {
15801
+ ...cfgWithProfiles.defaults?.mcp_servers ?? {},
15802
+ ...profileMcp,
15803
+ ...agentConfig.mcp_servers ?? {}
15804
+ };
15805
+ for (const mcpEntry of Object.values(effectiveMcp)) {
15806
+ if (!mcpEntry || typeof mcpEntry !== "object")
15807
+ continue;
15808
+ const declared = mcpEntry.secrets;
15809
+ if (Array.isArray(declared) && declared.includes(key)) {
15810
+ return { allow: true };
15811
+ }
15812
+ }
15813
+ const cfgSecrets = config;
15814
+ const profileSecrets = profileName != null && profileName.length > 0 ? cfgSecrets.profiles?.[profileName]?.secrets : undefined;
15815
+ const standingSecrets = [
15816
+ ...Array.isArray(cfgSecrets.defaults?.secrets) ? cfgSecrets.defaults.secrets : [],
15817
+ ...Array.isArray(profileSecrets) ? profileSecrets : [],
15818
+ ...Array.isArray(agentConfig.secrets) ? agentConfig.secrets : []
15819
+ ];
15820
+ if (standingSecrets.includes(key)) {
15821
+ return { allow: true };
15822
+ }
15823
+ const schedule = agentConfig.schedule ?? [];
15824
+ if (schedule.length === 0) {
15825
+ return {
15826
+ allow: false,
15827
+ reason: `agent '${agentName}' has no schedule entries declaring 'secrets', no mcp_servers.*.secrets[], and no agents.${agentName}.secrets[] standing grant declaring '${key}'; nothing is broker-accessible`
15828
+ };
15829
+ }
15830
+ for (const entry of schedule) {
15831
+ const allowed = entry?.secrets ?? [];
15832
+ if (allowed.includes(key)) {
15833
+ return { allow: true };
15834
+ }
15835
+ }
15836
+ return {
15837
+ allow: false,
15838
+ reason: `key '${key}' not in ACL for agent '${agentName}'`
15839
+ };
15840
+ }
15841
+ function parseGoogleAccountSlotKey(key) {
15842
+ const match = key.match(/^google:([^:]+):([a-z_]+)$/);
15843
+ if (!match)
15844
+ return null;
15845
+ return { account: match[1], field: match[2] };
15846
+ }
15847
+ function checkGoogleAccountAcl(config, agentName, account, key) {
15848
+ const accounts = config.google_accounts ?? {};
15849
+ const accountKey = account.toLowerCase();
15850
+ const accountEntry = accounts[accountKey] ?? accounts[account];
15851
+ if (!accountEntry) {
15852
+ return {
15853
+ allow: false,
15854
+ reason: `google_accounts['${account}'] not configured (key '${key}')`
15855
+ };
15856
+ }
15857
+ const enabled = accountEntry.enabled_for ?? [];
15858
+ if (enabled.length === 0) {
15859
+ return {
15860
+ allow: false,
15861
+ reason: `google_accounts['${account}'].enabled_for is empty (key '${key}')`
15862
+ };
15863
+ }
15864
+ if (!enabled.includes(agentName)) {
15865
+ return {
15866
+ allow: false,
15867
+ reason: `agent '${agentName}' not in google_accounts['${account}'].enabled_for (key '${key}')`
15868
+ };
15869
+ }
15870
+ return { allow: true };
15871
+ }
15872
+ var WEBKITE_VAULT_KEYS;
15873
+ var init_acl = __esm(() => {
15874
+ WEBKITE_VAULT_KEYS = new Set([
15875
+ "webkite/cloudflare-account-id",
15876
+ "webkite/cloudflare-api-token",
15877
+ "webkite/firecrawl-api-key"
15878
+ ]);
15879
+ });
15880
+
15647
15881
  // node_modules/.bun/handlebars@4.7.9/node_modules/handlebars/dist/cjs/handlebars/utils.js
15648
15882
  var require_utils = __commonJS((exports) => {
15649
15883
  exports.__esModule = true;
@@ -21292,48 +21526,6 @@ var init_scaffold_integration = __esm(() => {
21292
21526
  init_hindsight();
21293
21527
  });
21294
21528
 
21295
- // src/config/google-workspace-acl.ts
21296
- function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
21297
- if (!agentGoogleAccount)
21298
- return false;
21299
- const account = agentGoogleAccount.trim().toLowerCase();
21300
- if (account.length === 0)
21301
- return false;
21302
- const acctEntry = googleAccounts?.[account];
21303
- if (!acctEntry)
21304
- return false;
21305
- const enabledFor = acctEntry.enabled_for ?? [];
21306
- return enabledFor.includes(agentName);
21307
- }
21308
- function vaultRefKey(value) {
21309
- if (typeof value !== "string" || !value.startsWith("vault:"))
21310
- return null;
21311
- const key = value.slice("vault:".length).split("#")[0];
21312
- return key.length > 0 ? key : null;
21313
- }
21314
- function isGoogleClientCredentialKeyForAgent(config, agentName, key) {
21315
- if (!agentName || !key)
21316
- return false;
21317
- const agentConfig = config.agents?.[agentName];
21318
- if (!agentConfig)
21319
- return false;
21320
- if (agentConfig.mcp_servers?.["gdrive"] === false) {
21321
- return false;
21322
- }
21323
- const account = agentConfig.google_workspace?.account;
21324
- if (!shouldEmitGdriveMcp(agentName, account, config.google_accounts)) {
21325
- return false;
21326
- }
21327
- const gw = config.google_workspace;
21328
- if (!gw)
21329
- return false;
21330
- for (const ref of [gw.google_client_id, gw.google_client_secret]) {
21331
- if (vaultRefKey(ref) === key)
21332
- return true;
21333
- }
21334
- return false;
21335
- }
21336
-
21337
21529
  // src/agents/reconcile-default-skills.ts
21338
21530
  import { existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readlinkSync as readlinkSync2, rmSync, symlinkSync } from "node:fs";
21339
21531
  import { homedir as homedir3 } from "node:os";
@@ -28406,198 +28598,6 @@ var init_via_claude = __esm(() => {
28406
28598
  ];
28407
28599
  });
28408
28600
 
28409
- // src/vault/broker/acl.ts
28410
- function isWebkiteCredentialKeyForAgent(agentConfig, key) {
28411
- if (!WEBKITE_VAULT_KEYS.has(key))
28412
- return false;
28413
- if ((agentConfig?.mcp_servers ?? {})["webkite"] === false)
28414
- return false;
28415
- return true;
28416
- }
28417
- function parseCronUnit(unitName) {
28418
- const m = unitName.match(/^switchroom-([a-zA-Z0-9_-]+)-cron-(\d+)\.service$/);
28419
- if (!m)
28420
- return null;
28421
- const agentName = m[1];
28422
- const index = parseInt(m[2], 10);
28423
- if (!agentName)
28424
- return null;
28425
- return { agentName, index };
28426
- }
28427
- function agentSlugFromPeer(peer) {
28428
- if (peer.systemdUnit === null)
28429
- return null;
28430
- const parsed = parseCronUnit(peer.systemdUnit);
28431
- return parsed?.agentName ?? null;
28432
- }
28433
- function checkEntryScope(scope, agentSlug) {
28434
- if (scope === undefined || scope === null) {
28435
- return { allow: true };
28436
- }
28437
- const deny = scope.deny ?? [];
28438
- const allow = scope.allow ?? [];
28439
- if (agentSlug !== null && deny.includes(agentSlug)) {
28440
- return {
28441
- allow: false,
28442
- reason: `agent '${agentSlug}' is in the entry's deny list (scope-deny)`
28443
- };
28444
- }
28445
- if (allow.length > 0) {
28446
- if (agentSlug === null || !allow.includes(agentSlug)) {
28447
- return {
28448
- allow: false,
28449
- reason: agentSlug === null ? "caller agent slug could not be determined; entry has a non-empty allow list (scope-allow)" : `agent '${agentSlug}' is not in the entry's allow list (scope-allow)`
28450
- };
28451
- }
28452
- }
28453
- return { allow: true };
28454
- }
28455
- function checkAcl(peer, config, key) {
28456
- if (peer.systemdUnit !== null) {
28457
- const parsed = parseCronUnit(peer.systemdUnit);
28458
- if (parsed === null) {
28459
- return {
28460
- allow: false,
28461
- reason: `systemd unit '${peer.systemdUnit}' does not match switchroom cron unit naming convention`
28462
- };
28463
- }
28464
- const { agentName, index } = parsed;
28465
- const agentConfig = config.agents?.[agentName];
28466
- if (!agentConfig) {
28467
- return { allow: false, reason: `agent '${agentName}' not found in config` };
28468
- }
28469
- const schedule = agentConfig.schedule ?? [];
28470
- if (index >= schedule.length || index < 0) {
28471
- return {
28472
- allow: false,
28473
- reason: `schedule index ${index} out of range for agent '${agentName}' (${schedule.length} entries)`
28474
- };
28475
- }
28476
- const entry = schedule[index];
28477
- const allowedKeys = entry.secrets ?? [];
28478
- if (!allowedKeys.includes(key)) {
28479
- return {
28480
- allow: false,
28481
- reason: `key '${key}' not in ACL for ${agentName}/schedule[${index}]`
28482
- };
28483
- }
28484
- return { allow: true };
28485
- }
28486
- return {
28487
- allow: false,
28488
- reason: "caller is not a switchroom cron unit; use 'switchroom vault get --no-broker' for interactive access"
28489
- };
28490
- }
28491
- function checkAclByAgent(config, agentName, key) {
28492
- if (!agentName) {
28493
- return { allow: false, reason: "agent name unresolved" };
28494
- }
28495
- const agentConfig = config.agents?.[agentName];
28496
- if (!agentConfig) {
28497
- return { allow: false, reason: `agent '${agentName}' not found in config` };
28498
- }
28499
- const googleSlot = parseGoogleAccountSlotKey(key);
28500
- if (googleSlot !== null) {
28501
- return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
28502
- }
28503
- if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
28504
- return { allow: true };
28505
- }
28506
- if (isWebkiteCredentialKeyForAgent(agentConfig, key)) {
28507
- return { allow: true };
28508
- }
28509
- const agentBot = agentConfig.bot_token;
28510
- const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
28511
- if (typeof botRef === "string" && botRef.startsWith("vault:")) {
28512
- const botKey = botRef.slice("vault:".length).split("#")[0];
28513
- if (botKey.length > 0 && botKey === key) {
28514
- return { allow: true };
28515
- }
28516
- }
28517
- const cfgWithProfiles = config;
28518
- const profileName = agentConfig.extends;
28519
- const profileMcp = profileName != null && profileName.length > 0 ? cfgWithProfiles.profiles?.[profileName]?.mcp_servers ?? {} : {};
28520
- const effectiveMcp = {
28521
- ...cfgWithProfiles.defaults?.mcp_servers ?? {},
28522
- ...profileMcp,
28523
- ...agentConfig.mcp_servers ?? {}
28524
- };
28525
- for (const mcpEntry of Object.values(effectiveMcp)) {
28526
- if (!mcpEntry || typeof mcpEntry !== "object")
28527
- continue;
28528
- const declared = mcpEntry.secrets;
28529
- if (Array.isArray(declared) && declared.includes(key)) {
28530
- return { allow: true };
28531
- }
28532
- }
28533
- const cfgSecrets = config;
28534
- const profileSecrets = profileName != null && profileName.length > 0 ? cfgSecrets.profiles?.[profileName]?.secrets : undefined;
28535
- const standingSecrets = [
28536
- ...Array.isArray(cfgSecrets.defaults?.secrets) ? cfgSecrets.defaults.secrets : [],
28537
- ...Array.isArray(profileSecrets) ? profileSecrets : [],
28538
- ...Array.isArray(agentConfig.secrets) ? agentConfig.secrets : []
28539
- ];
28540
- if (standingSecrets.includes(key)) {
28541
- return { allow: true };
28542
- }
28543
- const schedule = agentConfig.schedule ?? [];
28544
- if (schedule.length === 0) {
28545
- return {
28546
- allow: false,
28547
- reason: `agent '${agentName}' has no schedule entries declaring 'secrets', no mcp_servers.*.secrets[], and no agents.${agentName}.secrets[] standing grant declaring '${key}'; nothing is broker-accessible`
28548
- };
28549
- }
28550
- for (const entry of schedule) {
28551
- const allowed = entry?.secrets ?? [];
28552
- if (allowed.includes(key)) {
28553
- return { allow: true };
28554
- }
28555
- }
28556
- return {
28557
- allow: false,
28558
- reason: `key '${key}' not in ACL for agent '${agentName}'`
28559
- };
28560
- }
28561
- function parseGoogleAccountSlotKey(key) {
28562
- const match = key.match(/^google:([^:]+):([a-z_]+)$/);
28563
- if (!match)
28564
- return null;
28565
- return { account: match[1], field: match[2] };
28566
- }
28567
- function checkGoogleAccountAcl(config, agentName, account, key) {
28568
- const accounts = config.google_accounts ?? {};
28569
- const accountKey = account.toLowerCase();
28570
- const accountEntry = accounts[accountKey] ?? accounts[account];
28571
- if (!accountEntry) {
28572
- return {
28573
- allow: false,
28574
- reason: `google_accounts['${account}'] not configured (key '${key}')`
28575
- };
28576
- }
28577
- const enabled = accountEntry.enabled_for ?? [];
28578
- if (enabled.length === 0) {
28579
- return {
28580
- allow: false,
28581
- reason: `google_accounts['${account}'].enabled_for is empty (key '${key}')`
28582
- };
28583
- }
28584
- if (!enabled.includes(agentName)) {
28585
- return {
28586
- allow: false,
28587
- reason: `agent '${agentName}' not in google_accounts['${account}'].enabled_for (key '${key}')`
28588
- };
28589
- }
28590
- return { allow: true };
28591
- }
28592
- var WEBKITE_VAULT_KEYS;
28593
- var init_acl = __esm(() => {
28594
- WEBKITE_VAULT_KEYS = new Set([
28595
- "webkite/cloudflare-account-id",
28596
- "webkite/cloudflare-api-token",
28597
- "webkite/firecrawl-api-key"
28598
- ]);
28599
- });
28600
-
28601
28601
  // src/util/audit-hashchain.ts
28602
28602
  import { createHash as createHash7 } from "node:crypto";
28603
28603
  import { openSync as openSync7, readSync as readSync2, fstatSync as fstatSync2, closeSync as closeSync7 } from "node:fs";
@@ -49612,6 +49612,10 @@ function dispatchTool(name, args) {
49612
49612
  cliArgs = buildArgs(["config", "get"], args);
49613
49613
  parseMode = "json";
49614
49614
  break;
49615
+ case "whoami":
49616
+ cliArgs = buildArgs(["config", "whoami"], args);
49617
+ parseMode = "json";
49618
+ break;
49615
49619
  case "cron_list":
49616
49620
  cliArgs = buildArgs(["cron", "list"], args);
49617
49621
  parseMode = "json";
@@ -49818,6 +49822,19 @@ var init_server3 = __esm(() => {
49818
49822
  }
49819
49823
  }
49820
49824
  },
49825
+ {
49826
+ name: "whoami",
49827
+ description: "See your own sandbox: the tools, MCP servers, vault key-NAMES (never " + "values), skills, schedule and privileges (admin/root/config-edit) you " + "actually have, as JSON \u2014 computed from live enforcement. Call this " + "FIRST when you hit a permission wall, instead of guessing or asking. " + "Read-only; defaults to your own identity.",
49828
+ inputSchema: {
49829
+ type: "object",
49830
+ properties: {
49831
+ agent: {
49832
+ type: "string",
49833
+ description: "Target agent name. Optional \u2014 defaults to the env-pinned agent identity."
49834
+ }
49835
+ }
49836
+ }
49837
+ },
49821
49838
  {
49822
49839
  name: "cron_list",
49823
49840
  description: "List the agent's scheduled cron entries (schedule array) as JSON.",
@@ -49840,7 +49857,7 @@ var init_server3 = __esm(() => {
49840
49857
  },
49841
49858
  {
49842
49859
  name: "schedule_add",
49843
- description: "Append a cron schedule entry to your overlay. Takes effect within ~30s \u2014 " + "the scheduler hot-reloads, no restart needed. Overlay entries with " + "non-empty `secrets:` are REJECTED (E_OVERLAY_SECRETS_REQUIRES_APPROVAL). " + "COST: by default each fire runs as a full turn in your live session " + "(your model, your whole context) \u2014 fine for work that needs your memory/" + "persona, but costly for routine checks. For a lighter recurring task, set " + '`model: "sonnet"` to run that fire in a cheap, minimal-context cron ' + "session instead (saves tokens; needs cheap-cron enabled by the operator). " + "For 'only do something when X changes' (e.g. a webpage, or a reaction), " + "ask the operator to set up a poll or reaction-dispatch instead of a " + "frequent prompt cron \u2014 far cheaper than polling with a full turn.",
49860
+ description: "Append a cron schedule entry to your overlay. Takes effect within ~30s \u2014 " + "the scheduler hot-reloads, no restart needed. Overlay entries with " + "non-empty `secrets:` are REJECTED (E_OVERLAY_SECRETS_REQUIRES_APPROVAL). " + "COST: a FREQUENT cron (every <=60min) is already auto-routed to a cheap, " + "minimal-context cron session (Tier 1) by default; a DAILY/WEEKLY cron runs " + 'as a full turn in your live session (Tier 2). Set `model: "sonnet"` / ' + '`context: "fresh"` to force the cheap session for a self-contained ' + 'daily/weekly job, or `context: "agent"` to keep a fire on your full ' + "session when it needs your accumulated context. CHEAPER STILL (request " + "from the operator \u2014 you can't self-author these): a `kind: action` runs a " + "FIXED post/webhook MODEL-FREE (zero tokens, no session) for a set " + "reminder/ping; a `kind: poll` or reaction-dispatch handles 'only act when " + "X changes' without a wasted turn each tick.",
49844
49861
  inputSchema: {
49845
49862
  type: "object",
49846
49863
  required: ["cron_expr", "prompt"],
@@ -49851,7 +49868,7 @@ var init_server3 = __esm(() => {
49851
49868
  name: { type: "string", pattern: "^[a-z0-9-]{1,40}$" },
49852
49869
  model: {
49853
49870
  type: "string",
49854
- description: "Optional cheap-cron tier hint. A known-cheap model ('sonnet'/'haiku') " + "routes this fire to a fresh, minimal-context cron session (Tier 1) " + "instead of your full live session (Tier 2) \u2014 cheaper per fire. Omit " + "for context-heavy work that needs your memory/persona. Inert unless " + "the operator has enabled cheap-cron."
49871
+ description: "Optional cheap-cron tier hint. A known-cheap model ('sonnet'/'haiku') " + "routes this fire to a fresh, minimal-context cron session (Tier 1) " + "instead of your full live session (Tier 2) \u2014 cheaper per fire. Omit " + "for context-heavy work that needs your accumulated conversation " + "context (a frequent cron is already Tier-1 by default; this is mainly " + "to make a daily/weekly self-contained job cheap too)."
49855
49872
  },
49856
49873
  context: {
49857
49874
  type: "string",
@@ -50049,7 +50066,7 @@ __export(exports_server2, {
50049
50066
  TOOLS: () => TOOLS2
50050
50067
  });
50051
50068
  import { randomBytes as randomBytes15 } from "node:crypto";
50052
- import { existsSync as existsSync83, readFileSync as readFileSync70 } from "node:fs";
50069
+ import { existsSync as existsSync83, readFileSync as readFileSync71 } from "node:fs";
50053
50070
  function selfSocketPath() {
50054
50071
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
50055
50072
  }
@@ -50227,7 +50244,7 @@ function getLastUpdateApplyStatus() {
50227
50244
  }
50228
50245
  let raw;
50229
50246
  try {
50230
- raw = readFileSync70(path8, "utf-8");
50247
+ raw = readFileSync71(path8, "utf-8");
50231
50248
  } catch (err2) {
50232
50249
  return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
50233
50250
  }
@@ -50460,8 +50477,8 @@ var {
50460
50477
  } = import__.default;
50461
50478
 
50462
50479
  // src/build-info.ts
50463
- var VERSION = "0.15.19";
50464
- var COMMIT_SHA = "3f40c6f9";
50480
+ var VERSION = "0.15.20";
50481
+ var COMMIT_SHA = "0b63ab9e";
50465
50482
 
50466
50483
  // src/cli/agent.ts
50467
50484
  init_source();
@@ -50578,6 +50595,8 @@ init_timezone();
50578
50595
 
50579
50596
  // src/cli/agent-config.ts
50580
50597
  init_helpers();
50598
+ init_merge();
50599
+ init_acl();
50581
50600
  import { join as join2 } from "node:path";
50582
50601
  import { homedir as homedir2 } from "node:os";
50583
50602
  import {
@@ -50673,6 +50692,66 @@ function scheduleLiveNote(agent) {
50673
50692
  const hotReload = (process.env.SWITCHROOM_SCHEDULER_HOT_RELOAD ?? "") !== "0";
50674
50693
  return hotReload ? `Live within ~30s \u2014 the in-agent scheduler hot-reloads the overlay; no restart needed.` : `Not live yet \u2014 hot-reload is disabled (SWITCHROOM_SCHEDULER_HOT_RELOAD=0). ` + `Run \`switchroom agent restart ${agent}\` for this to take effect.`;
50675
50694
  }
50695
+ function collectVaultKeys(value, out) {
50696
+ if (typeof value === "string") {
50697
+ if (value.startsWith("vault:"))
50698
+ out.add(value.slice("vault:".length));
50699
+ return;
50700
+ }
50701
+ if (Array.isArray(value)) {
50702
+ for (const v of value)
50703
+ collectVaultKeys(v, out);
50704
+ return;
50705
+ }
50706
+ if (value && typeof value === "object") {
50707
+ for (const [k, v] of Object.entries(value)) {
50708
+ if (k === "secrets" && Array.isArray(v)) {
50709
+ for (const s of v)
50710
+ if (typeof s === "string")
50711
+ out.add(s);
50712
+ } else {
50713
+ collectVaultKeys(v, out);
50714
+ }
50715
+ }
50716
+ }
50717
+ }
50718
+ function buildWhoami(config, agentName) {
50719
+ const slice = config.agents?.[agentName];
50720
+ if (!slice)
50721
+ throw new Error(`agent "${agentName}" not defined in switchroom.yaml`);
50722
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, slice);
50723
+ const admin = resolved.admin === true || resolved.root === true;
50724
+ const root = resolved.root === true;
50725
+ const toolsBlock = resolved.tools ?? {};
50726
+ const allow = [
50727
+ ...toolsBlock.allow ?? [],
50728
+ ...resolved.allowed_tools ?? []
50729
+ ];
50730
+ const deny = [
50731
+ ...toolsBlock.deny ?? [],
50732
+ ...resolved.disallowed_tools ?? []
50733
+ ];
50734
+ const mcpServers = Object.keys(resolved.mcp_servers ?? {});
50735
+ const skills = (resolved.skills ?? []).slice();
50736
+ const vaultKeys = new Set;
50737
+ collectVaultKeys(resolved, vaultKeys);
50738
+ const vault = [...vaultKeys].sort().map((key) => ({ key, readable: checkAclByAgent(config, agentName, key).allow }));
50739
+ const configEdit = config.hostd?.config_edit_enabled === true;
50740
+ const schedule = resolved.schedule ?? [];
50741
+ return {
50742
+ name: agentName,
50743
+ persona: resolved.description ?? resolved.persona ?? null,
50744
+ model: resolved.model ?? null,
50745
+ tier: root ? "root" : admin ? "admin" : "standard",
50746
+ tools: { allow, deny },
50747
+ mcpServers,
50748
+ skills,
50749
+ vault,
50750
+ powers: { admin, root, configEdit, crossAgentHostVerbs: admin },
50751
+ scheduleCount: schedule.length,
50752
+ memoryBackend: config.memory?.backend ?? null
50753
+ };
50754
+ }
50676
50755
  function getAgentSlice(config, agent) {
50677
50756
  const slice = config.agents?.[agent];
50678
50757
  if (!slice) {
@@ -50729,6 +50808,29 @@ function registerAgentConfigCommands(program2) {
50729
50808
  process.exit(1);
50730
50809
  }
50731
50810
  }));
50811
+ config.command("whoami").description("Emit what this agent is allowed to do (tools, MCP, vault key-names, powers) as JSON \u2014 its own sandbox, computed from live enforcement").option("--agent <name>", "Target agent (defaults to $SWITCHROOM_AGENT_NAME)").action(withConfigError(async (opts) => {
50812
+ let agent;
50813
+ try {
50814
+ agent = resolveTargetAgent(opts.agent);
50815
+ } catch (err) {
50816
+ process.stderr.write(`${err.message}
50817
+ `);
50818
+ appendAudit(opts.agent ?? "<unknown>", "config.whoami", { ...opts }, 7);
50819
+ process.exit(7);
50820
+ }
50821
+ const cfg = getConfig(program2);
50822
+ try {
50823
+ const view = buildWhoami(cfg, agent);
50824
+ process.stdout.write(JSON.stringify(view) + `
50825
+ `);
50826
+ appendAudit(agent, "config.whoami", { ...opts }, 0);
50827
+ } catch (err) {
50828
+ process.stderr.write(`${err.message}
50829
+ `);
50830
+ appendAudit(agent, "config.whoami", { ...opts }, 1);
50831
+ process.exit(1);
50832
+ }
50833
+ }));
50732
50834
  const cron = program2.command("cron").description("Read-only access to an agent's cron schedule");
50733
50835
  cron.command("list").description("List the agent's scheduled cron entries as JSON").option("--agent <name>", "Target agent (defaults to $SWITCHROOM_AGENT_NAME)").action(withConfigError(async (opts) => {
50734
50836
  let agent;
@@ -76329,10 +76431,48 @@ init_doctor();
76329
76431
  init_source();
76330
76432
  init_loader();
76331
76433
  init_lifecycle();
76332
- import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync26 } from "node:fs";
76434
+ import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync26, chownSync as chownSync5 } from "node:fs";
76333
76435
  import { spawnSync as spawnSync9 } from "node:child_process";
76334
76436
  import { join as join60, dirname as dirname14, resolve as resolve34 } from "node:path";
76335
76437
  import { homedir as homedir36 } from "node:os";
76438
+
76439
+ // src/cli/release-yaml.ts
76440
+ var import_yaml18 = __toESM(require_dist(), 1);
76441
+ function setReleasePinInConfig(yamlText, pin, now = new Date().toISOString().slice(0, 10)) {
76442
+ const doc = import_yaml18.parseDocument(yamlText);
76443
+ const currentPin = doc.getIn(["release", "pin"]);
76444
+ const hasChannel = doc.hasIn(["release", "channel"]);
76445
+ if (currentPin === pin && !hasChannel)
76446
+ return yamlText;
76447
+ if (hasChannel)
76448
+ doc.deleteIn(["release", "channel"]);
76449
+ doc.setIn(["release", "pin"], pin);
76450
+ const node = doc.getIn(["release", "pin"], true);
76451
+ if (import_yaml18.isScalar(node)) {
76452
+ node.comment = ` ${now}: rolled by switchroom rollout`;
76453
+ }
76454
+ return String(doc);
76455
+ }
76456
+
76457
+ // src/cli/update.ts
76458
+ init_atomic();
76459
+ function defaultPersistPin(configPath) {
76460
+ return (pin) => {
76461
+ const path4 = configPath ?? findConfigFile();
76462
+ const before = readFileSync52(path4, "utf8");
76463
+ const after = setReleasePinInConfig(before, pin);
76464
+ if (after === before)
76465
+ return;
76466
+ atomicWriteFileSync2(path4, after, statSync26(path4).mode & 511);
76467
+ try {
76468
+ if (typeof process.geteuid === "function" && process.geteuid() === 0) {
76469
+ const uid = resolveOperatorUid();
76470
+ if (uid !== undefined)
76471
+ chownSync5(path4, uid, uid);
76472
+ }
76473
+ } catch {}
76474
+ };
76475
+ }
76336
76476
  var DEFAULT_COMPOSE_PATH = join60(homedir36(), ".switchroom", "compose", "docker-compose.yml");
76337
76477
  function runningFromSwitchroomCheckout(scriptPath) {
76338
76478
  let dir = dirname14(scriptPath);
@@ -76365,6 +76505,15 @@ function planUpdate(opts) {
76365
76505
  const runner = opts.runner ?? defaultRunner;
76366
76506
  const scriptPath = opts.scriptPath ?? process.argv[1] ?? "";
76367
76507
  const steps = [];
76508
+ if (opts.pin) {
76509
+ const pin = opts.pin;
76510
+ const persist = opts.persistPinFn ?? defaultPersistPin(opts.configPath);
76511
+ steps.push({
76512
+ name: "persist-release-pin",
76513
+ description: `Persist release.pin=${pin} to switchroom.yaml (durable)`,
76514
+ run: () => persist(pin)
76515
+ });
76516
+ }
76368
76517
  const releaseOverrideArgs = [];
76369
76518
  if (opts.channel)
76370
76519
  releaseOverrideArgs.push("--channel", opts.channel);
@@ -76814,6 +76963,8 @@ function registerUpdateCommand(program3) {
76814
76963
  // src/cli/rollout.ts
76815
76964
  init_helpers();
76816
76965
  import { spawnSync as spawnSync10 } from "node:child_process";
76966
+ import { readFileSync as readFileSync53, chownSync as chownSync6, statSync as statSync27 } from "node:fs";
76967
+ init_atomic();
76817
76968
  function normalizeVersion(v) {
76818
76969
  return v.trim().replace(/^v/, "");
76819
76970
  }
@@ -76826,7 +76977,10 @@ function orderAgentsCanaryFirst(agents) {
76826
76977
  return [...canary, ...rest];
76827
76978
  }
76828
76979
  function planRollout(agents, opts = {}) {
76829
- const steps = [{ kind: "apply" }];
76980
+ const steps = [];
76981
+ if (opts.pinToPersist)
76982
+ steps.push({ kind: "persist-pin", pin: opts.pinToPersist });
76983
+ steps.push({ kind: "apply" });
76830
76984
  for (const agent of orderAgentsCanaryFirst(agents)) {
76831
76985
  steps.push({ kind: "restart-agent", agent });
76832
76986
  }
@@ -76843,6 +76997,9 @@ function formatRolloutPlan(steps, target) {
76843
76997
  for (const s of steps) {
76844
76998
  n += 1;
76845
76999
  switch (s.kind) {
77000
+ case "persist-pin":
77001
+ lines.push(` ${n}. persist release.pin=${s.pin} to switchroom.yaml (durable)`);
77002
+ break;
76846
77003
  case "apply":
76847
77004
  lines.push(` ${n}. apply \u2014 regenerate compose with ${target} image refs`);
76848
77005
  break;
@@ -76865,16 +77022,24 @@ function formatRolloutPlan(steps, target) {
76865
77022
  return lines.join(`
76866
77023
  `);
76867
77024
  }
76868
- function executeRollout(steps, target, deps, pinOnApply) {
77025
+ function executeRollout(steps, target, deps) {
76869
77026
  const targetNorm = normalizeVersion(target);
76870
77027
  const rolled = [];
76871
77028
  const warnings = [];
76872
77029
  for (const step of steps) {
76873
77030
  switch (step.kind) {
77031
+ case "persist-pin": {
77032
+ deps.log(`\u2192 persist release.pin=${step.pin} to switchroom.yaml`);
77033
+ if (deps.persistPin) {
77034
+ deps.persistPin(step.pin);
77035
+ } else {
77036
+ warnings.push(`persist-pin requested but no persist hook wired; pin NOT durable`);
77037
+ }
77038
+ break;
77039
+ }
76874
77040
  case "apply": {
76875
77041
  deps.log(`\u2192 apply \u2014 regenerating compose for ${target}`);
76876
- const args = pinOnApply ? ["apply", "--pin", target] : ["apply"];
76877
- const r = deps.run(args);
77042
+ const r = deps.run(["apply"]);
76878
77043
  if (r.status !== 0) {
76879
77044
  return { ok: false, rolled, failedStep: "apply", warnings };
76880
77045
  }
@@ -76956,12 +77121,16 @@ function registerRolloutCommand(program3) {
76956
77121
  process.exitCode = 2;
76957
77122
  return;
76958
77123
  }
76959
- const steps = planRollout(requested, { skipWeb: opts.skipWeb });
77124
+ const steps = planRollout(requested, {
77125
+ skipWeb: opts.skipWeb,
77126
+ pinToPersist: opts.pin ?? undefined
77127
+ });
76960
77128
  if (opts.dryRun) {
76961
77129
  process.stdout.write(formatRolloutPlan(steps, target) + `
76962
77130
  `);
76963
77131
  return;
76964
77132
  }
77133
+ const configPath = getConfigPath(program3);
76965
77134
  const scriptPath = process.argv[1] ?? "switchroom";
76966
77135
  const deps = {
76967
77136
  run: (args) => {
@@ -76976,11 +77145,25 @@ function registerRolloutCommand(program3) {
76976
77145
  `).pop()?.trim() ?? null;
76977
77146
  },
76978
77147
  log: (line) => process.stdout.write(line + `
76979
- `)
77148
+ `),
77149
+ persistPin: (pin) => {
77150
+ const before = readFileSync53(configPath, "utf8");
77151
+ const after = setReleasePinInConfig(before, pin);
77152
+ if (after === before)
77153
+ return;
77154
+ atomicWriteFileSync2(configPath, after, statSync27(configPath).mode & 511);
77155
+ try {
77156
+ if (typeof process.geteuid === "function" && process.geteuid() === 0) {
77157
+ const uid = resolveOperatorUid();
77158
+ if (uid !== undefined)
77159
+ chownSync6(configPath, uid, uid);
77160
+ }
77161
+ } catch {}
77162
+ }
76980
77163
  };
76981
77164
  process.stdout.write(`Rolling ${requested.length} agent(s) to ${target}\u2026
76982
77165
  `);
76983
- const result = executeRollout(steps, target, deps, opts.pin != null);
77166
+ const result = executeRollout(steps, target, deps);
76984
77167
  for (const w of result.warnings)
76985
77168
  process.stderr.write(`\u26a0\ufe0f ${w}
76986
77169
  `);
@@ -77011,7 +77194,7 @@ init_source();
77011
77194
  init_helpers();
77012
77195
  init_lifecycle();
77013
77196
  import { execSync as execSync4 } from "node:child_process";
77014
- import { existsSync as existsSync59, readFileSync as readFileSync53 } from "node:fs";
77197
+ import { existsSync as existsSync59, readFileSync as readFileSync54 } from "node:fs";
77015
77198
  import { dirname as dirname15, join as join61 } from "node:path";
77016
77199
  function getClaudeCodeVersion() {
77017
77200
  try {
@@ -77065,7 +77248,7 @@ function locateSwitchroomInstallDir() {
77065
77248
  const pkgPath = join61(dir, "package.json");
77066
77249
  if (existsSync59(pkgPath)) {
77067
77250
  try {
77068
- const pkg = JSON.parse(readFileSync53(pkgPath, "utf-8"));
77251
+ const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
77069
77252
  if (pkg.name === "switchroom" && existsSync59(join61(dir, ".git"))) {
77070
77253
  return dir;
77071
77254
  }
@@ -77296,9 +77479,9 @@ import {
77296
77479
  mkdirSync as mkdirSync33,
77297
77480
  openSync as openSync11,
77298
77481
  readdirSync as readdirSync22,
77299
- readFileSync as readFileSync54,
77482
+ readFileSync as readFileSync55,
77300
77483
  renameSync as renameSync12,
77301
- statSync as statSync27,
77484
+ statSync as statSync28,
77302
77485
  unlinkSync as unlinkSync11,
77303
77486
  writeFileSync as writeFileSync28,
77304
77487
  writeSync as writeSync7
@@ -77706,7 +77889,7 @@ function readAll(stateDir) {
77706
77889
  return [];
77707
77890
  let raw;
77708
77891
  try {
77709
- raw = readFileSync54(path4, "utf-8");
77892
+ raw = readFileSync55(path4, "utf-8");
77710
77893
  } catch {
77711
77894
  return [];
77712
77895
  }
@@ -77872,7 +78055,7 @@ function sweepOrphanTmpFiles(stateDir) {
77872
78055
  continue;
77873
78056
  const tmpPath = join62(stateDir, entry);
77874
78057
  try {
77875
- const stat = statSync27(tmpPath);
78058
+ const stat = statSync28(tmpPath);
77876
78059
  if (stat.mtimeMs < cutoff) {
77877
78060
  unlinkSync11(tmpPath);
77878
78061
  }
@@ -77917,7 +78100,7 @@ function withLock(stateDir, fn) {
77917
78100
  function tryStealStaleLock(lockPath) {
77918
78101
  let pidStr;
77919
78102
  try {
77920
- pidStr = readFileSync54(lockPath, "utf-8").trim();
78103
+ pidStr = readFileSync55(lockPath, "utf-8").trim();
77921
78104
  } catch {
77922
78105
  return true;
77923
78106
  }
@@ -78174,7 +78357,7 @@ import { createHash as createHash11 } from "node:crypto";
78174
78357
  import {
78175
78358
  existsSync as existsSync61,
78176
78359
  mkdirSync as mkdirSync34,
78177
- readFileSync as readFileSync55,
78360
+ readFileSync as readFileSync56,
78178
78361
  rmSync as rmSync13,
78179
78362
  writeFileSync as writeFileSync29
78180
78363
  } from "node:fs";
@@ -78194,7 +78377,7 @@ function defaultPythonCacheRoot() {
78194
78377
  return join63(homedir37(), ".switchroom", "deps", "python");
78195
78378
  }
78196
78379
  function hashFile(path4) {
78197
- return createHash11("sha256").update(readFileSync55(path4)).digest("hex");
78380
+ return createHash11("sha256").update(readFileSync56(path4)).digest("hex");
78198
78381
  }
78199
78382
  function ensurePythonEnv(opts) {
78200
78383
  const { skillName, requirementsPath, force = false } = opts;
@@ -78210,7 +78393,7 @@ function ensurePythonEnv(opts) {
78210
78393
  const pipBin = join63(binDir, "pip");
78211
78394
  const targetHash = hashFile(requirementsPath);
78212
78395
  if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
78213
- const existingHash = readFileSync55(stampPath, "utf8").trim();
78396
+ const existingHash = readFileSync56(stampPath, "utf8").trim();
78214
78397
  if (existingHash === targetHash) {
78215
78398
  return {
78216
78399
  skillName,
@@ -78262,7 +78445,7 @@ import {
78262
78445
  copyFileSync as copyFileSync9,
78263
78446
  existsSync as existsSync62,
78264
78447
  mkdirSync as mkdirSync35,
78265
- readFileSync as readFileSync56,
78448
+ readFileSync as readFileSync57,
78266
78449
  rmSync as rmSync14,
78267
78450
  writeFileSync as writeFileSync30
78268
78451
  } from "node:fs";
@@ -78297,7 +78480,7 @@ function hashDepInputs(packageJsonPath) {
78297
78480
  const hasher = createHash12("sha256");
78298
78481
  hasher.update(`package.json
78299
78482
  `);
78300
- hasher.update(readFileSync56(packageJsonPath));
78483
+ hasher.update(readFileSync57(packageJsonPath));
78301
78484
  for (const lockName of ALL_LOCKFILES) {
78302
78485
  const lockPath = join64(sourceDir, lockName);
78303
78486
  if (existsSync62(lockPath)) {
@@ -78306,7 +78489,7 @@ function hashDepInputs(packageJsonPath) {
78306
78489
  hasher.update(lockName);
78307
78490
  hasher.update(`
78308
78491
  `);
78309
- hasher.update(readFileSync56(lockPath));
78492
+ hasher.update(readFileSync57(lockPath));
78310
78493
  }
78311
78494
  }
78312
78495
  return hasher.digest("hex");
@@ -78325,7 +78508,7 @@ function ensureNodeEnv(opts) {
78325
78508
  const binDir = join64(nodeModulesDir, ".bin");
78326
78509
  const targetHash = hashDepInputs(packageJsonPath);
78327
78510
  if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
78328
- const existingHash = readFileSync56(stampPath, "utf8").trim();
78511
+ const existingHash = readFileSync57(stampPath, "utf8").trim();
78329
78512
  if (existingHash === targetHash) {
78330
78513
  return {
78331
78514
  skillName,
@@ -79344,7 +79527,7 @@ function safeParseInt(value, fallback) {
79344
79527
  init_helpers();
79345
79528
  init_loader();
79346
79529
  init_merge();
79347
- import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync57, writeFileSync as writeFileSync31 } from "node:fs";
79530
+ import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync58, writeFileSync as writeFileSync31 } from "node:fs";
79348
79531
  import { join as join66, resolve as resolve40 } from "node:path";
79349
79532
  init_schema();
79350
79533
  function resolveSoulTargetOrExit(program3, agentName) {
@@ -79390,7 +79573,7 @@ function registerSoulCommand(program3) {
79390
79573
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
79391
79574
  process.exit(1);
79392
79575
  }
79393
- process.stdout.write(readFileSync57(t.soulPath, "utf-8"));
79576
+ process.stdout.write(readFileSync58(t.soulPath, "utf-8"));
79394
79577
  }));
79395
79578
  cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
79396
79579
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -79435,7 +79618,7 @@ function registerSoulCommand(program3) {
79435
79618
  // src/cli/debug.ts
79436
79619
  init_helpers();
79437
79620
  init_loader();
79438
- import { existsSync as existsSync66, readFileSync as readFileSync58, readdirSync as readdirSync23, statSync as statSync28 } from "node:fs";
79621
+ import { existsSync as existsSync66, readFileSync as readFileSync59, readdirSync as readdirSync23, statSync as statSync29 } from "node:fs";
79439
79622
  import { resolve as resolve41, join as join67 } from "node:path";
79440
79623
  import { createHash as createHash13 } from "node:crypto";
79441
79624
  init_merge();
@@ -79451,7 +79634,7 @@ function readMcpServerNames(agentDir) {
79451
79634
  if (!existsSync66(mcpPath))
79452
79635
  return [];
79453
79636
  try {
79454
- const parsed = JSON.parse(readFileSync58(mcpPath, "utf-8"));
79637
+ const parsed = JSON.parse(readFileSync59(mcpPath, "utf-8"));
79455
79638
  return Object.keys(parsed.mcpServers ?? {});
79456
79639
  } catch {
79457
79640
  return null;
@@ -79474,7 +79657,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
79474
79657
  const transcriptPath = join67(projectPath, "transcript.jsonl");
79475
79658
  if (!existsSync66(transcriptPath))
79476
79659
  continue;
79477
- const stat3 = statSync28(transcriptPath);
79660
+ const stat3 = statSync29(transcriptPath);
79478
79661
  if (!latest || stat3.mtimeMs > latest.mtime) {
79479
79662
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
79480
79663
  }
@@ -79486,7 +79669,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
79486
79669
  }
79487
79670
  function extractLatestUserMessage(transcriptPath) {
79488
79671
  try {
79489
- const content = readFileSync58(transcriptPath, "utf-8");
79672
+ const content = readFileSync59(transcriptPath, "utf-8");
79490
79673
  const lines = content.trim().split(`
79491
79674
  `).filter(Boolean);
79492
79675
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -79590,7 +79773,7 @@ function registerDebugCommand(program3) {
79590
79773
  }
79591
79774
  console.log(`=== Append System Prompt (per-session) ===
79592
79775
  `);
79593
- const handoffContent = existsSync66(handoffPath) ? readFileSync58(handoffPath, "utf-8") : "";
79776
+ const handoffContent = existsSync66(handoffPath) ? readFileSync59(handoffPath, "utf-8") : "";
79594
79777
  if (handoffContent.trim().length > 0) {
79595
79778
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
79596
79779
  console.log(handoffContent);
@@ -79601,7 +79784,7 @@ function registerDebugCommand(program3) {
79601
79784
  }
79602
79785
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
79603
79786
  `);
79604
- const claudeMdContent = existsSync66(claudeMdPath) ? readFileSync58(claudeMdPath, "utf-8") : "";
79787
+ const claudeMdContent = existsSync66(claudeMdPath) ? readFileSync59(claudeMdPath, "utf-8") : "";
79605
79788
  if (claudeMdContent.trim().length > 0) {
79606
79789
  console.log(`(${formatBytes(claudeMdContent.length)})`);
79607
79790
  console.log(claudeMdContent);
@@ -79612,7 +79795,7 @@ function registerDebugCommand(program3) {
79612
79795
  }
79613
79796
  console.log(`=== Persona (SOUL.md) ===
79614
79797
  `);
79615
- const soulMdContent = existsSync66(soulMdPath) ? readFileSync58(soulMdPath, "utf-8") : existsSync66(workspaceSoulMdPath) ? readFileSync58(workspaceSoulMdPath, "utf-8") : "";
79798
+ const soulMdContent = existsSync66(soulMdPath) ? readFileSync59(soulMdPath, "utf-8") : existsSync66(workspaceSoulMdPath) ? readFileSync59(workspaceSoulMdPath, "utf-8") : "";
79616
79799
  if (soulMdContent.trim().length > 0) {
79617
79800
  console.log(`(${formatBytes(soulMdContent.length)})`);
79618
79801
  console.log(soulMdContent);
@@ -79676,8 +79859,8 @@ function registerDebugCommand(program3) {
79676
79859
  const fleetDir = join67(agentsDir, "..", "fleet");
79677
79860
  const fleetInvPath = join67(fleetDir, "switchroom-invariants.md");
79678
79861
  const fleetClaudePath = join67(fleetDir, "CLAUDE.md");
79679
- const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync58(fleetInvPath, "utf-8").length : 0;
79680
- const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync58(fleetClaudePath, "utf-8").length : 0;
79862
+ const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync59(fleetInvPath, "utf-8").length : 0;
79863
+ const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync59(fleetClaudePath, "utf-8").length : 0;
79681
79864
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
79682
79865
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
79683
79866
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -79719,7 +79902,7 @@ import { randomBytes as randomBytes13 } from "node:crypto";
79719
79902
  import {
79720
79903
  mkdirSync as mkdirSync36,
79721
79904
  writeFileSync as writeFileSync32,
79722
- readFileSync as readFileSync59,
79905
+ readFileSync as readFileSync60,
79723
79906
  readdirSync as readdirSync24,
79724
79907
  unlinkSync as unlinkSync12,
79725
79908
  existsSync as existsSync67,
@@ -79747,7 +79930,7 @@ function writeRecord(record2) {
79747
79930
  function readRecord(id) {
79748
79931
  const path7 = recordPath(id);
79749
79932
  try {
79750
- const raw = readFileSync59(path7, "utf8");
79933
+ const raw = readFileSync60(path7, "utf8");
79751
79934
  return JSON.parse(raw);
79752
79935
  } catch {
79753
79936
  return null;
@@ -80814,7 +80997,7 @@ function registerNotionMcpLauncherCommand(program3) {
80814
80997
 
80815
80998
  // src/cli/deliver-file.ts
80816
80999
  init_client2();
80817
- import { readFileSync as readFileSync60, statSync as statSync29 } from "node:fs";
81000
+ import { readFileSync as readFileSync61, statSync as statSync30 } from "node:fs";
80818
81001
  import { basename as basename8 } from "node:path";
80819
81002
 
80820
81003
  // src/delivery/onedrive.ts
@@ -81153,8 +81336,8 @@ async function defaultResolveProvider() {
81153
81336
  }
81154
81337
  async function runDeliverFile(localPath, deps = {}) {
81155
81338
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
81156
- const sizeOf = deps.fileSize ?? ((p) => statSync29(p).size);
81157
- const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync60(p)));
81339
+ const sizeOf = deps.fileSize ?? ((p) => statSync30(p).size);
81340
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync61(p)));
81158
81341
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
81159
81342
  let size;
81160
81343
  try {
@@ -81444,7 +81627,7 @@ async function fetchToken(vaultKey) {
81444
81627
 
81445
81628
  // src/cli/apply.ts
81446
81629
  init_source();
81447
- import { accessSync as accessSync3, chownSync as chownSync5, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync74, mkdirSync as mkdirSync42, readFileSync as readFileSync62, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync37 } from "node:fs";
81630
+ import { accessSync as accessSync3, chownSync as chownSync7, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync74, mkdirSync as mkdirSync42, readFileSync as readFileSync63, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync37 } from "node:fs";
81448
81631
  import { mkdir as mkdir2 } from "node:fs/promises";
81449
81632
  import { spawnSync as childSpawnSync } from "node:child_process";
81450
81633
  import readline from "node:readline";
@@ -81841,7 +82024,7 @@ init_loader();
81841
82024
  init_loader();
81842
82025
 
81843
82026
  // src/cli/update-prompt-hook.ts
81844
- import { existsSync as existsSync72, readFileSync as readFileSync61, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
82027
+ import { existsSync as existsSync72, readFileSync as readFileSync62, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
81845
82028
  import { join as join72 } from "node:path";
81846
82029
  var HOOK_FILENAME = "update-card-on-prompt.sh";
81847
82030
  function updatePromptHookScript() {
@@ -81913,7 +82096,7 @@ function installUpdatePromptHook(agentDir) {
81913
82096
  const scriptPath = join72(hooksDir, HOOK_FILENAME);
81914
82097
  const desired = updatePromptHookScript();
81915
82098
  let installed = false;
81916
- const existing = existsSync72(scriptPath) ? readFileSync61(scriptPath, "utf-8") : "";
82099
+ const existing = existsSync72(scriptPath) ? readFileSync62(scriptPath, "utf-8") : "";
81917
82100
  if (existing !== desired) {
81918
82101
  writeFileSync36(scriptPath, desired, { mode: 493 });
81919
82102
  chmodSync10(scriptPath, 493);
@@ -81927,7 +82110,7 @@ function installUpdatePromptHook(agentDir) {
81927
82110
  if (!existsSync72(settingsPath)) {
81928
82111
  return { scriptPath, settingsPath, installed };
81929
82112
  }
81930
- const raw = readFileSync61(settingsPath, "utf-8");
82113
+ const raw = readFileSync62(settingsPath, "utf-8");
81931
82114
  let parsed;
81932
82115
  try {
81933
82116
  parsed = JSON.parse(raw);
@@ -82104,14 +82287,14 @@ async function ensureHostMountSources(config) {
82104
82287
  }
82105
82288
  try {
82106
82289
  const uid = allocateAgentUid(name);
82107
- chownSync5(tokenPath, uid, uid);
82290
+ chownSync7(tokenPath, uid, uid);
82108
82291
  } catch {}
82109
82292
  }
82110
82293
  const fleetDir = join74(home2, ".switchroom", "fleet");
82111
82294
  await mkdir2(fleetDir, { recursive: true });
82112
82295
  const invariantsPath = join74(fleetDir, "switchroom-invariants.md");
82113
82296
  const invariantsCanonical = renderFleetInvariants();
82114
- const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync62(invariantsPath, "utf-8") : null;
82297
+ const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync63(invariantsPath, "utf-8") : null;
82115
82298
  if (invariantsCurrent !== invariantsCanonical) {
82116
82299
  writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
82117
82300
  }
@@ -82636,7 +82819,7 @@ function runRedactStdin() {
82636
82819
  }
82637
82820
 
82638
82821
  // src/cli/status-ask.ts
82639
- import { readFileSync as readFileSync63, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
82822
+ import { readFileSync as readFileSync64, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
82640
82823
  import { join as join75 } from "node:path";
82641
82824
  import { homedir as homedir44 } from "node:os";
82642
82825
 
@@ -82912,7 +83095,7 @@ function runReport(opts) {
82912
83095
  for (const src of sources) {
82913
83096
  let content;
82914
83097
  try {
82915
- content = readFileSync63(src.path, "utf-8");
83098
+ content = readFileSync64(src.path, "utf-8");
82916
83099
  } catch (err) {
82917
83100
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
82918
83101
  `);
@@ -83006,7 +83189,7 @@ function inferAgentFromPath(p) {
83006
83189
  }
83007
83190
 
83008
83191
  // src/cli/agent-config-write.ts
83009
- var import_yaml19 = __toESM(require_dist(), 1);
83192
+ var import_yaml20 = __toESM(require_dist(), 1);
83010
83193
 
83011
83194
  // src/config/overlay-writer.ts
83012
83195
  init_paths();
@@ -83017,9 +83200,9 @@ import {
83017
83200
  mkdirSync as mkdirSync43,
83018
83201
  openSync as openSync13,
83019
83202
  readdirSync as readdirSync28,
83020
- readFileSync as readFileSync64,
83203
+ readFileSync as readFileSync65,
83021
83204
  renameSync as renameSync15,
83022
- statSync as statSync30,
83205
+ statSync as statSync31,
83023
83206
  unlinkSync as unlinkSync14,
83024
83207
  writeSync as writeSync8
83025
83208
  } from "node:fs";
@@ -83063,7 +83246,7 @@ function withAgentLock(paths, fn) {
83063
83246
  if (e.code !== "EEXIST")
83064
83247
  throw err;
83065
83248
  try {
83066
- const age = Date.now() - statSync30(paths.lockPath).mtimeMs;
83249
+ const age = Date.now() - statSync31(paths.lockPath).mtimeMs;
83067
83250
  if (age > 30000) {
83068
83251
  unlinkSync14(paths.lockPath);
83069
83252
  continue;
@@ -83141,7 +83324,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
83141
83324
  continue;
83142
83325
  const full = join76(paths.skillsDir, name);
83143
83326
  try {
83144
- const raw = readFileSync64(full, "utf-8");
83327
+ const raw = readFileSync65(full, "utf-8");
83145
83328
  const slug = name.replace(/\.ya?ml$/i, "");
83146
83329
  out.push({ slug, path: full, raw });
83147
83330
  } catch {}
@@ -83168,7 +83351,7 @@ function listOverlayEntries(agent, opts = {}) {
83168
83351
  continue;
83169
83352
  const full = join76(paths.scheduleDir, name);
83170
83353
  try {
83171
- const raw = readFileSync64(full, "utf-8");
83354
+ const raw = readFileSync65(full, "utf-8");
83172
83355
  const slug = name.replace(/\.ya?ml$/i, "");
83173
83356
  out.push({ slug, path: full, raw });
83174
83357
  } catch {}
@@ -83199,7 +83382,7 @@ function filterOverlaySecrets(doc, source) {
83199
83382
  // src/agents/reconcile-dry-run.ts
83200
83383
  init_overlay_schema();
83201
83384
  init_schema();
83202
- var import_yaml18 = __toESM(require_dist(), 1);
83385
+ var import_yaml19 = __toESM(require_dist(), 1);
83203
83386
  init_lifecycle();
83204
83387
  var MIN_CRON_INTERVAL_SECS = 5 * 60;
83205
83388
  function violatesMinInterval(cron) {
@@ -83221,7 +83404,7 @@ function violatesMinInterval(cron) {
83221
83404
  function dryRunReconcile(input) {
83222
83405
  let parsed;
83223
83406
  try {
83224
- parsed = import_yaml18.parse(input.yamlText);
83407
+ parsed = import_yaml19.parse(input.yamlText);
83225
83408
  } catch (err) {
83226
83409
  return {
83227
83410
  ok: false,
@@ -83316,7 +83499,7 @@ import {
83316
83499
  mkdirSync as mkdirSync44,
83317
83500
  openSync as openSync14,
83318
83501
  readdirSync as readdirSync29,
83319
- readFileSync as readFileSync65,
83502
+ readFileSync as readFileSync66,
83320
83503
  renameSync as renameSync16,
83321
83504
  unlinkSync as unlinkSync15,
83322
83505
  writeFileSync as writeFileSync38,
@@ -83380,7 +83563,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
83380
83563
  if (!existsSync77(yamlPath))
83381
83564
  continue;
83382
83565
  try {
83383
- const meta = JSON.parse(readFileSync65(metaPath, "utf-8"));
83566
+ const meta = JSON.parse(readFileSync66(metaPath, "utf-8"));
83384
83567
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
83385
83568
  continue;
83386
83569
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -83418,7 +83601,7 @@ function denyPendingScheduleEntry(opts) {
83418
83601
  }
83419
83602
 
83420
83603
  // src/cli/agent-config-write.ts
83421
- import { existsSync as existsSync78, readFileSync as readFileSync66 } from "node:fs";
83604
+ import { existsSync as existsSync78, readFileSync as readFileSync67 } from "node:fs";
83422
83605
  import { execFileSync as execFileSync25 } from "node:child_process";
83423
83606
 
83424
83607
  // src/scheduler/schedule-report.ts
@@ -83607,7 +83790,7 @@ function scheduleAdd(opts) {
83607
83790
  ]
83608
83791
  };
83609
83792
  const yamlText = (() => {
83610
- const body = import_yaml19.stringify(doc);
83793
+ const body = import_yaml20.stringify(doc);
83611
83794
  const header = opts.name ? `# name: ${opts.name}
83612
83795
  ` : "";
83613
83796
  return header + body;
@@ -83721,7 +83904,7 @@ function scheduleAddOrStage(opts) {
83721
83904
  ]
83722
83905
  };
83723
83906
  const yamlText = (opts.name ? `# name: ${opts.name}
83724
- ` : "") + import_yaml19.stringify(doc);
83907
+ ` : "") + import_yaml20.stringify(doc);
83725
83908
  const summary = (() => {
83726
83909
  const parts = [`cron=${opts.cronExpr}`];
83727
83910
  if (opts.secrets?.length)
@@ -83771,7 +83954,7 @@ function scheduleRemove(opts) {
83771
83954
  break;
83772
83955
  }
83773
83956
  try {
83774
- const parsed = import_yaml19.parse(e.raw);
83957
+ const parsed = import_yaml20.parse(e.raw);
83775
83958
  if (parsed && parsed.name === opts.name) {
83776
83959
  match = e;
83777
83960
  break;
@@ -83790,7 +83973,7 @@ function scheduleRemove(opts) {
83790
83973
  let priorContent = null;
83791
83974
  try {
83792
83975
  if (existsSync78(match.path))
83793
- priorContent = readFileSync66(match.path, "utf-8");
83976
+ priorContent = readFileSync67(match.path, "utf-8");
83794
83977
  } catch {}
83795
83978
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
83796
83979
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -83993,7 +84176,7 @@ function registerAgentConfigWriteCommands(program3) {
83993
84176
  }
83994
84177
  let blob;
83995
84178
  if (opts.jsonl) {
83996
- blob = existsSync78(opts.jsonl) ? readFileSync66(opts.jsonl, "utf-8") : "";
84179
+ blob = existsSync78(opts.jsonl) ? readFileSync67(opts.jsonl, "utf-8") : "";
83997
84180
  } else {
83998
84181
  try {
83999
84182
  blob = execFileSync25("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -84024,10 +84207,10 @@ function registerAgentConfigWriteCommands(program3) {
84024
84207
  }
84025
84208
 
84026
84209
  // src/cli/agent-config-skill-write.ts
84027
- var import_yaml20 = __toESM(require_dist(), 1);
84210
+ var import_yaml21 = __toESM(require_dist(), 1);
84028
84211
  import { existsSync as existsSync79 } from "node:fs";
84029
84212
  init_reconcile_default_skills();
84030
- var import_yaml21 = __toESM(require_dist(), 1);
84213
+ var import_yaml22 = __toESM(require_dist(), 1);
84031
84214
  import { join as join78 } from "node:path";
84032
84215
  var MAX_SKILLS_PER_AGENT = 20;
84033
84216
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
@@ -84077,7 +84260,7 @@ function countCurrentSkills(agent, opts) {
84077
84260
  let total = 0;
84078
84261
  for (const e of entries) {
84079
84262
  try {
84080
- const doc = import_yaml21.parse(e.raw);
84263
+ const doc = import_yaml22.parse(e.raw);
84081
84264
  total += (doc?.skills ?? []).length;
84082
84265
  } catch {}
84083
84266
  }
@@ -84107,7 +84290,7 @@ function skillInstall(opts) {
84107
84290
  if (!existsSync79(skillPath)) {
84108
84291
  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.`);
84109
84292
  }
84110
- const yamlText = import_yaml20.stringify({ skills: [skillName] });
84293
+ const yamlText = import_yaml21.stringify({ skills: [skillName] });
84111
84294
  let path8;
84112
84295
  try {
84113
84296
  path8 = writeSkillsOverlayEntry(agent, slug, yamlText, { root: opts.root });
@@ -84273,12 +84456,12 @@ import {
84273
84456
  mkdirSync as mkdirSync45,
84274
84457
  mkdtempSync as mkdtempSync5,
84275
84458
  openSync as openSync15,
84276
- readFileSync as readFileSync67,
84459
+ readFileSync as readFileSync68,
84277
84460
  readdirSync as readdirSync30,
84278
84461
  realpathSync as realpathSync7,
84279
84462
  renameSync as renameSync17,
84280
84463
  rmSync as rmSync16,
84281
- statSync as statSync31,
84464
+ statSync as statSync32,
84282
84465
  writeFileSync as writeFileSync39
84283
84466
  } from "node:fs";
84284
84467
  import { tmpdir as tmpdir5, homedir as homedir45 } from "node:os";
@@ -84286,7 +84469,7 @@ import { dirname as dirname23, join as join79, relative as relative2, resolve as
84286
84469
  import { spawnSync as spawnSync12 } from "node:child_process";
84287
84470
 
84288
84471
  // src/cli/skill-common.ts
84289
- var import_yaml22 = __toESM(require_dist(), 1);
84472
+ var import_yaml23 = __toESM(require_dist(), 1);
84290
84473
  var MAX_FILE_BYTES = 256 * 1024;
84291
84474
  var MAX_SKILL_BYTES = 2 * 1024 * 1024;
84292
84475
  var MAX_FILES_PER_SKILL = 50;
@@ -84390,7 +84573,7 @@ function validateSkillMd(content, expectedName) {
84390
84573
  }
84391
84574
  let parsed;
84392
84575
  try {
84393
- parsed = import_yaml22.parse(fmText);
84576
+ parsed = import_yaml23.parse(fmText);
84394
84577
  } catch (e) {
84395
84578
  return authorErr("E_SKILL_INVALID_FRONTMATTER", `frontmatter is not valid YAML: ${e.message}`);
84396
84579
  }
@@ -84508,7 +84691,7 @@ function isTarballPath(p) {
84508
84691
  }
84509
84692
  function loadFromDir(dir) {
84510
84693
  const abs = realpathSync7(dir);
84511
- if (!statSync31(abs).isDirectory()) {
84694
+ if (!statSync32(abs).isDirectory()) {
84512
84695
  fail3(`--from path is not a directory: ${dir}`);
84513
84696
  }
84514
84697
  const files = {};
@@ -84525,7 +84708,7 @@ function loadFromDir(dir) {
84525
84708
  continue;
84526
84709
  }
84527
84710
  if (ent.isFile()) {
84528
- const buf = readFileSync67(full);
84711
+ const buf = readFileSync68(full);
84529
84712
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
84530
84713
  }
84531
84714
  }
@@ -84574,7 +84757,7 @@ function loadFromTarball(tarPath) {
84574
84757
  }
84575
84758
  }
84576
84759
  function loadSingleFile(filePath) {
84577
- const content = readFileSync67(filePath, "utf-8");
84760
+ const content = readFileSync68(filePath, "utf-8");
84578
84761
  return { "SKILL.md": content };
84579
84762
  }
84580
84763
  function loadFromStdin() {
@@ -84665,7 +84848,7 @@ function diffSummary(currentDir, files) {
84665
84848
  if (ent.isDirectory()) {
84666
84849
  walk2(full);
84667
84850
  } else if (ent.isFile()) {
84668
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync67(full, "utf-8");
84851
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync68(full, "utf-8");
84669
84852
  }
84670
84853
  }
84671
84854
  };
@@ -84768,7 +84951,7 @@ function registerSkillCommand(program3) {
84768
84951
  if (!existsSync80(fromPath)) {
84769
84952
  fail3(`--from path does not exist: ${opts.from}`);
84770
84953
  }
84771
- const st = statSync31(fromPath);
84954
+ const st = statSync32(fromPath);
84772
84955
  if (st.isDirectory()) {
84773
84956
  files = loadFromDir(fromPath);
84774
84957
  } else if (isTarballPath(fromPath)) {
@@ -84825,11 +85008,11 @@ import {
84825
85008
  mkdirSync as mkdirSync46,
84826
85009
  mkdtempSync as mkdtempSync6,
84827
85010
  openSync as openSync16,
84828
- readFileSync as readFileSync68,
85011
+ readFileSync as readFileSync69,
84829
85012
  readdirSync as readdirSync31,
84830
85013
  renameSync as renameSync18,
84831
85014
  rmSync as rmSync17,
84832
- statSync as statSync32,
85015
+ statSync as statSync33,
84833
85016
  utimesSync,
84834
85017
  writeFileSync as writeFileSync40
84835
85018
  } from "node:fs";
@@ -84907,7 +85090,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
84907
85090
  if (ent.isDirectory())
84908
85091
  walk2(s, d);
84909
85092
  else if (ent.isFile()) {
84910
- writeFileSync40(d, readFileSync68(s));
85093
+ writeFileSync40(d, readFileSync69(s));
84911
85094
  }
84912
85095
  }
84913
85096
  };
@@ -84970,7 +85153,7 @@ function readStdinSync2() {
84970
85153
  }
84971
85154
  function loadFromDir2(dir) {
84972
85155
  const abs = resolve48(dir);
84973
- if (!statSync32(abs).isDirectory()) {
85156
+ if (!statSync33(abs).isDirectory()) {
84974
85157
  fail4(`--from path is not a directory: ${dir}`);
84975
85158
  }
84976
85159
  const files = {};
@@ -84986,7 +85169,7 @@ function loadFromDir2(dir) {
84986
85169
  }
84987
85170
  if (ent.isFile()) {
84988
85171
  const rel = relative3(abs, full).replace(/\\/g, "/");
84989
- files[rel] = readFileSync68(full, "utf-8");
85172
+ files[rel] = readFileSync69(full, "utf-8");
84990
85173
  }
84991
85174
  }
84992
85175
  };
@@ -85056,7 +85239,7 @@ function sweepTrash(agentsRoot, agent) {
85056
85239
  continue;
85057
85240
  const entPath = join80(trash, ent.name);
85058
85241
  try {
85059
- const st = statSync32(entPath);
85242
+ const st = statSync33(entPath);
85060
85243
  if (now - st.mtimeMs > TRASH_TTL_MS) {
85061
85244
  rmSync17(entPath, { recursive: true, force: true });
85062
85245
  }
@@ -85167,12 +85350,12 @@ function loadFiles(opts) {
85167
85350
  if (!existsSync81(p)) {
85168
85351
  fail4(`--from path does not exist: ${opts.from}`);
85169
85352
  }
85170
- const st = statSync32(p);
85353
+ const st = statSync33(p);
85171
85354
  if (st.isDirectory()) {
85172
85355
  return loadFromDir2(p);
85173
85356
  }
85174
85357
  if (p.endsWith(".md")) {
85175
- return { "SKILL.md": readFileSync68(p, "utf-8") };
85358
+ return { "SKILL.md": readFileSync69(p, "utf-8") };
85176
85359
  }
85177
85360
  fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
85178
85361
  }
@@ -85261,7 +85444,7 @@ function readSourceFiles(dir) {
85261
85444
  fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
85262
85445
  }
85263
85446
  } catch {}
85264
- files[rel] = readFileSync68(full, "utf-8");
85447
+ files[rel] = readFileSync69(full, "utf-8");
85265
85448
  }
85266
85449
  }
85267
85450
  };
@@ -85388,7 +85571,7 @@ function listPersonalAction(opts) {
85388
85571
  if (e.isFile()) {
85389
85572
  fileCount += 1;
85390
85573
  try {
85391
- totalBytes += statSync32(join80(sub, e.name)).size;
85574
+ totalBytes += statSync33(join80(sub, e.name)).size;
85392
85575
  } catch {}
85393
85576
  } else if (e.isDirectory()) {
85394
85577
  walk2(join80(sub, e.name));
@@ -85429,8 +85612,8 @@ function registerSkillPersonalCommands(program3) {
85429
85612
 
85430
85613
  // src/cli/skill-search.ts
85431
85614
  init_helpers();
85432
- var import_yaml23 = __toESM(require_dist(), 1);
85433
- import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync69, statSync as statSync33 } from "node:fs";
85615
+ var import_yaml24 = __toESM(require_dist(), 1);
85616
+ import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync70, statSync as statSync34 } from "node:fs";
85434
85617
  import { homedir as homedir47 } from "node:os";
85435
85618
  import { join as join81, resolve as resolve49 } from "node:path";
85436
85619
  var PERSONAL_PREFIX2 = "personal-";
@@ -85451,7 +85634,7 @@ function readSkillFrontmatter(skillDir) {
85451
85634
  return null;
85452
85635
  let content;
85453
85636
  try {
85454
- content = readFileSync69(mdPath, "utf-8");
85637
+ content = readFileSync70(mdPath, "utf-8");
85455
85638
  } catch {
85456
85639
  return null;
85457
85640
  }
@@ -85469,7 +85652,7 @@ function readSkillFrontmatter(skillDir) {
85469
85652
  const fmText = rest.slice(0, endIdx);
85470
85653
  let parsed;
85471
85654
  try {
85472
- parsed = import_yaml23.parse(fmText);
85655
+ parsed = import_yaml24.parse(fmText);
85473
85656
  } catch (e) {
85474
85657
  return { error: `yaml parse: ${e.message}` };
85475
85658
  }
@@ -85481,7 +85664,7 @@ function readSkillFrontmatter(skillDir) {
85481
85664
  function statSkillMd(skillDir) {
85482
85665
  const mdPath = join81(skillDir, "SKILL.md");
85483
85666
  try {
85484
- const st = statSync33(mdPath);
85667
+ const st = statSync34(mdPath);
85485
85668
  return { size: st.size, mtime: st.mtime.toISOString() };
85486
85669
  } catch {
85487
85670
  return null;
@@ -85505,7 +85688,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
85505
85688
  continue;
85506
85689
  const dirPath = join81(skillsDir, ent);
85507
85690
  try {
85508
- if (!statSync33(dirPath).isDirectory())
85691
+ if (!statSync34(dirPath).isDirectory())
85509
85692
  continue;
85510
85693
  } catch {
85511
85694
  continue;
@@ -85545,7 +85728,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
85545
85728
  continue;
85546
85729
  const dirPath = join81(sharedRoot, ent);
85547
85730
  try {
85548
- if (!statSync33(dirPath).isDirectory())
85731
+ if (!statSync34(dirPath).isDirectory())
85549
85732
  continue;
85550
85733
  } catch {
85551
85734
  continue;
@@ -85581,7 +85764,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
85581
85764
  continue;
85582
85765
  const dirPath = join81(bundledRoot, ent);
85583
85766
  try {
85584
- if (!statSync33(dirPath).isDirectory())
85767
+ if (!statSync34(dirPath).isDirectory())
85585
85768
  continue;
85586
85769
  } catch {
85587
85770
  continue;
@@ -85723,7 +85906,7 @@ function registerHostdMcpCommand(program3) {
85723
85906
  // src/cli/hostd.ts
85724
85907
  init_source();
85725
85908
  init_helpers();
85726
- import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync71, writeFileSync as writeFileSync41, statSync as statSync34, copyFileSync as copyFileSync12 } from "node:fs";
85909
+ import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync72, writeFileSync as writeFileSync41, statSync as statSync35, copyFileSync as copyFileSync12 } from "node:fs";
85727
85910
  import { homedir as homedir48 } from "node:os";
85728
85911
  import { join as join82 } from "node:path";
85729
85912
  import { spawnSync as spawnSync15 } from "node:child_process";
@@ -85959,7 +86142,7 @@ function doStatus() {
85959
86142
  continue;
85960
86143
  const sockPath = join82(dir, name, "sock");
85961
86144
  if (existsSync84(sockPath)) {
85962
- const st = statSync34(sockPath);
86145
+ const st = statSync35(sockPath);
85963
86146
  if ((st.mode & 61440) === 49152) {
85964
86147
  entries.push(`${name} \u2192 ${sockPath}`);
85965
86148
  }
@@ -86006,7 +86189,7 @@ function registerHostdCommand(program3) {
86006
86189
  The log is created when hostd handles its first privileged-verb request.`));
86007
86190
  return;
86008
86191
  }
86009
- const raw = readFileSync71(logPath, "utf-8");
86192
+ const raw = readFileSync72(logPath, "utf-8");
86010
86193
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
86011
86194
  const filters = {
86012
86195
  agent: opts.agent,