switchroom 0.15.18 → 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";
@@ -25739,6 +25931,7 @@ var init_inject = __esm(() => {
25739
25931
  ["/hooks", { description: "List configured hooks", expectsOutput: true }],
25740
25932
  ["/memory", { description: "Open memory picker", expectsOutput: true }],
25741
25933
  ["/model", { description: "Open model picker", expectsOutput: true }],
25934
+ ["/effort", { description: "Set reasoning effort", expectsOutput: true }],
25742
25935
  [
25743
25936
  "/clear",
25744
25937
  { description: "Clear session screen", expectsOutput: false }
@@ -28405,198 +28598,6 @@ var init_via_claude = __esm(() => {
28405
28598
  ];
28406
28599
  });
28407
28600
 
28408
- // src/vault/broker/acl.ts
28409
- function isWebkiteCredentialKeyForAgent(agentConfig, key) {
28410
- if (!WEBKITE_VAULT_KEYS.has(key))
28411
- return false;
28412
- if ((agentConfig?.mcp_servers ?? {})["webkite"] === false)
28413
- return false;
28414
- return true;
28415
- }
28416
- function parseCronUnit(unitName) {
28417
- const m = unitName.match(/^switchroom-([a-zA-Z0-9_-]+)-cron-(\d+)\.service$/);
28418
- if (!m)
28419
- return null;
28420
- const agentName = m[1];
28421
- const index = parseInt(m[2], 10);
28422
- if (!agentName)
28423
- return null;
28424
- return { agentName, index };
28425
- }
28426
- function agentSlugFromPeer(peer) {
28427
- if (peer.systemdUnit === null)
28428
- return null;
28429
- const parsed = parseCronUnit(peer.systemdUnit);
28430
- return parsed?.agentName ?? null;
28431
- }
28432
- function checkEntryScope(scope, agentSlug) {
28433
- if (scope === undefined || scope === null) {
28434
- return { allow: true };
28435
- }
28436
- const deny = scope.deny ?? [];
28437
- const allow = scope.allow ?? [];
28438
- if (agentSlug !== null && deny.includes(agentSlug)) {
28439
- return {
28440
- allow: false,
28441
- reason: `agent '${agentSlug}' is in the entry's deny list (scope-deny)`
28442
- };
28443
- }
28444
- if (allow.length > 0) {
28445
- if (agentSlug === null || !allow.includes(agentSlug)) {
28446
- return {
28447
- allow: false,
28448
- 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)`
28449
- };
28450
- }
28451
- }
28452
- return { allow: true };
28453
- }
28454
- function checkAcl(peer, config, key) {
28455
- if (peer.systemdUnit !== null) {
28456
- const parsed = parseCronUnit(peer.systemdUnit);
28457
- if (parsed === null) {
28458
- return {
28459
- allow: false,
28460
- reason: `systemd unit '${peer.systemdUnit}' does not match switchroom cron unit naming convention`
28461
- };
28462
- }
28463
- const { agentName, index } = parsed;
28464
- const agentConfig = config.agents?.[agentName];
28465
- if (!agentConfig) {
28466
- return { allow: false, reason: `agent '${agentName}' not found in config` };
28467
- }
28468
- const schedule = agentConfig.schedule ?? [];
28469
- if (index >= schedule.length || index < 0) {
28470
- return {
28471
- allow: false,
28472
- reason: `schedule index ${index} out of range for agent '${agentName}' (${schedule.length} entries)`
28473
- };
28474
- }
28475
- const entry = schedule[index];
28476
- const allowedKeys = entry.secrets ?? [];
28477
- if (!allowedKeys.includes(key)) {
28478
- return {
28479
- allow: false,
28480
- reason: `key '${key}' not in ACL for ${agentName}/schedule[${index}]`
28481
- };
28482
- }
28483
- return { allow: true };
28484
- }
28485
- return {
28486
- allow: false,
28487
- reason: "caller is not a switchroom cron unit; use 'switchroom vault get --no-broker' for interactive access"
28488
- };
28489
- }
28490
- function checkAclByAgent(config, agentName, key) {
28491
- if (!agentName) {
28492
- return { allow: false, reason: "agent name unresolved" };
28493
- }
28494
- const agentConfig = config.agents?.[agentName];
28495
- if (!agentConfig) {
28496
- return { allow: false, reason: `agent '${agentName}' not found in config` };
28497
- }
28498
- const googleSlot = parseGoogleAccountSlotKey(key);
28499
- if (googleSlot !== null) {
28500
- return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
28501
- }
28502
- if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
28503
- return { allow: true };
28504
- }
28505
- if (isWebkiteCredentialKeyForAgent(agentConfig, key)) {
28506
- return { allow: true };
28507
- }
28508
- const agentBot = agentConfig.bot_token;
28509
- const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
28510
- if (typeof botRef === "string" && botRef.startsWith("vault:")) {
28511
- const botKey = botRef.slice("vault:".length).split("#")[0];
28512
- if (botKey.length > 0 && botKey === key) {
28513
- return { allow: true };
28514
- }
28515
- }
28516
- const cfgWithProfiles = config;
28517
- const profileName = agentConfig.extends;
28518
- const profileMcp = profileName != null && profileName.length > 0 ? cfgWithProfiles.profiles?.[profileName]?.mcp_servers ?? {} : {};
28519
- const effectiveMcp = {
28520
- ...cfgWithProfiles.defaults?.mcp_servers ?? {},
28521
- ...profileMcp,
28522
- ...agentConfig.mcp_servers ?? {}
28523
- };
28524
- for (const mcpEntry of Object.values(effectiveMcp)) {
28525
- if (!mcpEntry || typeof mcpEntry !== "object")
28526
- continue;
28527
- const declared = mcpEntry.secrets;
28528
- if (Array.isArray(declared) && declared.includes(key)) {
28529
- return { allow: true };
28530
- }
28531
- }
28532
- const cfgSecrets = config;
28533
- const profileSecrets = profileName != null && profileName.length > 0 ? cfgSecrets.profiles?.[profileName]?.secrets : undefined;
28534
- const standingSecrets = [
28535
- ...Array.isArray(cfgSecrets.defaults?.secrets) ? cfgSecrets.defaults.secrets : [],
28536
- ...Array.isArray(profileSecrets) ? profileSecrets : [],
28537
- ...Array.isArray(agentConfig.secrets) ? agentConfig.secrets : []
28538
- ];
28539
- if (standingSecrets.includes(key)) {
28540
- return { allow: true };
28541
- }
28542
- const schedule = agentConfig.schedule ?? [];
28543
- if (schedule.length === 0) {
28544
- return {
28545
- allow: false,
28546
- 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`
28547
- };
28548
- }
28549
- for (const entry of schedule) {
28550
- const allowed = entry?.secrets ?? [];
28551
- if (allowed.includes(key)) {
28552
- return { allow: true };
28553
- }
28554
- }
28555
- return {
28556
- allow: false,
28557
- reason: `key '${key}' not in ACL for agent '${agentName}'`
28558
- };
28559
- }
28560
- function parseGoogleAccountSlotKey(key) {
28561
- const match = key.match(/^google:([^:]+):([a-z_]+)$/);
28562
- if (!match)
28563
- return null;
28564
- return { account: match[1], field: match[2] };
28565
- }
28566
- function checkGoogleAccountAcl(config, agentName, account, key) {
28567
- const accounts = config.google_accounts ?? {};
28568
- const accountKey = account.toLowerCase();
28569
- const accountEntry = accounts[accountKey] ?? accounts[account];
28570
- if (!accountEntry) {
28571
- return {
28572
- allow: false,
28573
- reason: `google_accounts['${account}'] not configured (key '${key}')`
28574
- };
28575
- }
28576
- const enabled = accountEntry.enabled_for ?? [];
28577
- if (enabled.length === 0) {
28578
- return {
28579
- allow: false,
28580
- reason: `google_accounts['${account}'].enabled_for is empty (key '${key}')`
28581
- };
28582
- }
28583
- if (!enabled.includes(agentName)) {
28584
- return {
28585
- allow: false,
28586
- reason: `agent '${agentName}' not in google_accounts['${account}'].enabled_for (key '${key}')`
28587
- };
28588
- }
28589
- return { allow: true };
28590
- }
28591
- var WEBKITE_VAULT_KEYS;
28592
- var init_acl = __esm(() => {
28593
- WEBKITE_VAULT_KEYS = new Set([
28594
- "webkite/cloudflare-account-id",
28595
- "webkite/cloudflare-api-token",
28596
- "webkite/firecrawl-api-key"
28597
- ]);
28598
- });
28599
-
28600
28601
  // src/util/audit-hashchain.ts
28601
28602
  import { createHash as createHash7 } from "node:crypto";
28602
28603
  import { openSync as openSync7, readSync as readSync2, fstatSync as fstatSync2, closeSync as closeSync7 } from "node:fs";
@@ -49611,6 +49612,10 @@ function dispatchTool(name, args) {
49611
49612
  cliArgs = buildArgs(["config", "get"], args);
49612
49613
  parseMode = "json";
49613
49614
  break;
49615
+ case "whoami":
49616
+ cliArgs = buildArgs(["config", "whoami"], args);
49617
+ parseMode = "json";
49618
+ break;
49614
49619
  case "cron_list":
49615
49620
  cliArgs = buildArgs(["cron", "list"], args);
49616
49621
  parseMode = "json";
@@ -49817,6 +49822,19 @@ var init_server3 = __esm(() => {
49817
49822
  }
49818
49823
  }
49819
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
+ },
49820
49838
  {
49821
49839
  name: "cron_list",
49822
49840
  description: "List the agent's scheduled cron entries (schedule array) as JSON.",
@@ -49839,7 +49857,7 @@ var init_server3 = __esm(() => {
49839
49857
  },
49840
49858
  {
49841
49859
  name: "schedule_add",
49842
- 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.",
49843
49861
  inputSchema: {
49844
49862
  type: "object",
49845
49863
  required: ["cron_expr", "prompt"],
@@ -49850,7 +49868,7 @@ var init_server3 = __esm(() => {
49850
49868
  name: { type: "string", pattern: "^[a-z0-9-]{1,40}$" },
49851
49869
  model: {
49852
49870
  type: "string",
49853
- 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)."
49854
49872
  },
49855
49873
  context: {
49856
49874
  type: "string",
@@ -50048,7 +50066,7 @@ __export(exports_server2, {
50048
50066
  TOOLS: () => TOOLS2
50049
50067
  });
50050
50068
  import { randomBytes as randomBytes15 } from "node:crypto";
50051
- import { existsSync as existsSync83, readFileSync as readFileSync70 } from "node:fs";
50069
+ import { existsSync as existsSync83, readFileSync as readFileSync71 } from "node:fs";
50052
50070
  function selfSocketPath() {
50053
50071
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
50054
50072
  }
@@ -50226,7 +50244,7 @@ function getLastUpdateApplyStatus() {
50226
50244
  }
50227
50245
  let raw;
50228
50246
  try {
50229
- raw = readFileSync70(path8, "utf-8");
50247
+ raw = readFileSync71(path8, "utf-8");
50230
50248
  } catch (err2) {
50231
50249
  return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
50232
50250
  }
@@ -50459,8 +50477,8 @@ var {
50459
50477
  } = import__.default;
50460
50478
 
50461
50479
  // src/build-info.ts
50462
- var VERSION = "0.15.18";
50463
- var COMMIT_SHA = "d7c044b9";
50480
+ var VERSION = "0.15.20";
50481
+ var COMMIT_SHA = "0b63ab9e";
50464
50482
 
50465
50483
  // src/cli/agent.ts
50466
50484
  init_source();
@@ -50577,6 +50595,8 @@ init_timezone();
50577
50595
 
50578
50596
  // src/cli/agent-config.ts
50579
50597
  init_helpers();
50598
+ init_merge();
50599
+ init_acl();
50580
50600
  import { join as join2 } from "node:path";
50581
50601
  import { homedir as homedir2 } from "node:os";
50582
50602
  import {
@@ -50672,6 +50692,66 @@ function scheduleLiveNote(agent) {
50672
50692
  const hotReload = (process.env.SWITCHROOM_SCHEDULER_HOT_RELOAD ?? "") !== "0";
50673
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.`;
50674
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
+ }
50675
50755
  function getAgentSlice(config, agent) {
50676
50756
  const slice = config.agents?.[agent];
50677
50757
  if (!slice) {
@@ -50728,6 +50808,29 @@ function registerAgentConfigCommands(program2) {
50728
50808
  process.exit(1);
50729
50809
  }
50730
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
+ }));
50731
50834
  const cron = program2.command("cron").description("Read-only access to an agent's cron schedule");
50732
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) => {
50733
50836
  let agent;
@@ -57061,6 +57164,7 @@ function registerAgentCommand(program3) {
57061
57164
  status: status?.active ?? "unknown",
57062
57165
  uptime: formatUptime2(status?.uptime ?? null),
57063
57166
  model: resolved.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL,
57167
+ thinking_effort: resolved.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
57064
57168
  extends: agentConfig.extends ?? "default",
57065
57169
  topic_name: agentConfig.topic_name,
57066
57170
  topic_emoji: agentConfig.topic_emoji,
@@ -76327,10 +76431,48 @@ init_doctor();
76327
76431
  init_source();
76328
76432
  init_loader();
76329
76433
  init_lifecycle();
76330
- 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";
76331
76435
  import { spawnSync as spawnSync9 } from "node:child_process";
76332
76436
  import { join as join60, dirname as dirname14, resolve as resolve34 } from "node:path";
76333
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
+ }
76334
76476
  var DEFAULT_COMPOSE_PATH = join60(homedir36(), ".switchroom", "compose", "docker-compose.yml");
76335
76477
  function runningFromSwitchroomCheckout(scriptPath) {
76336
76478
  let dir = dirname14(scriptPath);
@@ -76363,6 +76505,15 @@ function planUpdate(opts) {
76363
76505
  const runner = opts.runner ?? defaultRunner;
76364
76506
  const scriptPath = opts.scriptPath ?? process.argv[1] ?? "";
76365
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
+ }
76366
76517
  const releaseOverrideArgs = [];
76367
76518
  if (opts.channel)
76368
76519
  releaseOverrideArgs.push("--channel", opts.channel);
@@ -76812,6 +76963,8 @@ function registerUpdateCommand(program3) {
76812
76963
  // src/cli/rollout.ts
76813
76964
  init_helpers();
76814
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();
76815
76968
  function normalizeVersion(v) {
76816
76969
  return v.trim().replace(/^v/, "");
76817
76970
  }
@@ -76824,7 +76977,10 @@ function orderAgentsCanaryFirst(agents) {
76824
76977
  return [...canary, ...rest];
76825
76978
  }
76826
76979
  function planRollout(agents, opts = {}) {
76827
- 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" });
76828
76984
  for (const agent of orderAgentsCanaryFirst(agents)) {
76829
76985
  steps.push({ kind: "restart-agent", agent });
76830
76986
  }
@@ -76841,6 +76997,9 @@ function formatRolloutPlan(steps, target) {
76841
76997
  for (const s of steps) {
76842
76998
  n += 1;
76843
76999
  switch (s.kind) {
77000
+ case "persist-pin":
77001
+ lines.push(` ${n}. persist release.pin=${s.pin} to switchroom.yaml (durable)`);
77002
+ break;
76844
77003
  case "apply":
76845
77004
  lines.push(` ${n}. apply \u2014 regenerate compose with ${target} image refs`);
76846
77005
  break;
@@ -76863,16 +77022,24 @@ function formatRolloutPlan(steps, target) {
76863
77022
  return lines.join(`
76864
77023
  `);
76865
77024
  }
76866
- function executeRollout(steps, target, deps, pinOnApply) {
77025
+ function executeRollout(steps, target, deps) {
76867
77026
  const targetNorm = normalizeVersion(target);
76868
77027
  const rolled = [];
76869
77028
  const warnings = [];
76870
77029
  for (const step of steps) {
76871
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
+ }
76872
77040
  case "apply": {
76873
77041
  deps.log(`\u2192 apply \u2014 regenerating compose for ${target}`);
76874
- const args = pinOnApply ? ["apply", "--pin", target] : ["apply"];
76875
- const r = deps.run(args);
77042
+ const r = deps.run(["apply"]);
76876
77043
  if (r.status !== 0) {
76877
77044
  return { ok: false, rolled, failedStep: "apply", warnings };
76878
77045
  }
@@ -76954,12 +77121,16 @@ function registerRolloutCommand(program3) {
76954
77121
  process.exitCode = 2;
76955
77122
  return;
76956
77123
  }
76957
- const steps = planRollout(requested, { skipWeb: opts.skipWeb });
77124
+ const steps = planRollout(requested, {
77125
+ skipWeb: opts.skipWeb,
77126
+ pinToPersist: opts.pin ?? undefined
77127
+ });
76958
77128
  if (opts.dryRun) {
76959
77129
  process.stdout.write(formatRolloutPlan(steps, target) + `
76960
77130
  `);
76961
77131
  return;
76962
77132
  }
77133
+ const configPath = getConfigPath(program3);
76963
77134
  const scriptPath = process.argv[1] ?? "switchroom";
76964
77135
  const deps = {
76965
77136
  run: (args) => {
@@ -76974,11 +77145,25 @@ function registerRolloutCommand(program3) {
76974
77145
  `).pop()?.trim() ?? null;
76975
77146
  },
76976
77147
  log: (line) => process.stdout.write(line + `
76977
- `)
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
+ }
76978
77163
  };
76979
77164
  process.stdout.write(`Rolling ${requested.length} agent(s) to ${target}\u2026
76980
77165
  `);
76981
- const result = executeRollout(steps, target, deps, opts.pin != null);
77166
+ const result = executeRollout(steps, target, deps);
76982
77167
  for (const w of result.warnings)
76983
77168
  process.stderr.write(`\u26a0\ufe0f ${w}
76984
77169
  `);
@@ -77009,7 +77194,7 @@ init_source();
77009
77194
  init_helpers();
77010
77195
  init_lifecycle();
77011
77196
  import { execSync as execSync4 } from "node:child_process";
77012
- import { existsSync as existsSync59, readFileSync as readFileSync53 } from "node:fs";
77197
+ import { existsSync as existsSync59, readFileSync as readFileSync54 } from "node:fs";
77013
77198
  import { dirname as dirname15, join as join61 } from "node:path";
77014
77199
  function getClaudeCodeVersion() {
77015
77200
  try {
@@ -77063,7 +77248,7 @@ function locateSwitchroomInstallDir() {
77063
77248
  const pkgPath = join61(dir, "package.json");
77064
77249
  if (existsSync59(pkgPath)) {
77065
77250
  try {
77066
- const pkg = JSON.parse(readFileSync53(pkgPath, "utf-8"));
77251
+ const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
77067
77252
  if (pkg.name === "switchroom" && existsSync59(join61(dir, ".git"))) {
77068
77253
  return dir;
77069
77254
  }
@@ -77294,9 +77479,9 @@ import {
77294
77479
  mkdirSync as mkdirSync33,
77295
77480
  openSync as openSync11,
77296
77481
  readdirSync as readdirSync22,
77297
- readFileSync as readFileSync54,
77482
+ readFileSync as readFileSync55,
77298
77483
  renameSync as renameSync12,
77299
- statSync as statSync27,
77484
+ statSync as statSync28,
77300
77485
  unlinkSync as unlinkSync11,
77301
77486
  writeFileSync as writeFileSync28,
77302
77487
  writeSync as writeSync7
@@ -77704,7 +77889,7 @@ function readAll(stateDir) {
77704
77889
  return [];
77705
77890
  let raw;
77706
77891
  try {
77707
- raw = readFileSync54(path4, "utf-8");
77892
+ raw = readFileSync55(path4, "utf-8");
77708
77893
  } catch {
77709
77894
  return [];
77710
77895
  }
@@ -77870,7 +78055,7 @@ function sweepOrphanTmpFiles(stateDir) {
77870
78055
  continue;
77871
78056
  const tmpPath = join62(stateDir, entry);
77872
78057
  try {
77873
- const stat = statSync27(tmpPath);
78058
+ const stat = statSync28(tmpPath);
77874
78059
  if (stat.mtimeMs < cutoff) {
77875
78060
  unlinkSync11(tmpPath);
77876
78061
  }
@@ -77915,7 +78100,7 @@ function withLock(stateDir, fn) {
77915
78100
  function tryStealStaleLock(lockPath) {
77916
78101
  let pidStr;
77917
78102
  try {
77918
- pidStr = readFileSync54(lockPath, "utf-8").trim();
78103
+ pidStr = readFileSync55(lockPath, "utf-8").trim();
77919
78104
  } catch {
77920
78105
  return true;
77921
78106
  }
@@ -78172,7 +78357,7 @@ import { createHash as createHash11 } from "node:crypto";
78172
78357
  import {
78173
78358
  existsSync as existsSync61,
78174
78359
  mkdirSync as mkdirSync34,
78175
- readFileSync as readFileSync55,
78360
+ readFileSync as readFileSync56,
78176
78361
  rmSync as rmSync13,
78177
78362
  writeFileSync as writeFileSync29
78178
78363
  } from "node:fs";
@@ -78192,7 +78377,7 @@ function defaultPythonCacheRoot() {
78192
78377
  return join63(homedir37(), ".switchroom", "deps", "python");
78193
78378
  }
78194
78379
  function hashFile(path4) {
78195
- return createHash11("sha256").update(readFileSync55(path4)).digest("hex");
78380
+ return createHash11("sha256").update(readFileSync56(path4)).digest("hex");
78196
78381
  }
78197
78382
  function ensurePythonEnv(opts) {
78198
78383
  const { skillName, requirementsPath, force = false } = opts;
@@ -78208,7 +78393,7 @@ function ensurePythonEnv(opts) {
78208
78393
  const pipBin = join63(binDir, "pip");
78209
78394
  const targetHash = hashFile(requirementsPath);
78210
78395
  if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
78211
- const existingHash = readFileSync55(stampPath, "utf8").trim();
78396
+ const existingHash = readFileSync56(stampPath, "utf8").trim();
78212
78397
  if (existingHash === targetHash) {
78213
78398
  return {
78214
78399
  skillName,
@@ -78260,7 +78445,7 @@ import {
78260
78445
  copyFileSync as copyFileSync9,
78261
78446
  existsSync as existsSync62,
78262
78447
  mkdirSync as mkdirSync35,
78263
- readFileSync as readFileSync56,
78448
+ readFileSync as readFileSync57,
78264
78449
  rmSync as rmSync14,
78265
78450
  writeFileSync as writeFileSync30
78266
78451
  } from "node:fs";
@@ -78295,7 +78480,7 @@ function hashDepInputs(packageJsonPath) {
78295
78480
  const hasher = createHash12("sha256");
78296
78481
  hasher.update(`package.json
78297
78482
  `);
78298
- hasher.update(readFileSync56(packageJsonPath));
78483
+ hasher.update(readFileSync57(packageJsonPath));
78299
78484
  for (const lockName of ALL_LOCKFILES) {
78300
78485
  const lockPath = join64(sourceDir, lockName);
78301
78486
  if (existsSync62(lockPath)) {
@@ -78304,7 +78489,7 @@ function hashDepInputs(packageJsonPath) {
78304
78489
  hasher.update(lockName);
78305
78490
  hasher.update(`
78306
78491
  `);
78307
- hasher.update(readFileSync56(lockPath));
78492
+ hasher.update(readFileSync57(lockPath));
78308
78493
  }
78309
78494
  }
78310
78495
  return hasher.digest("hex");
@@ -78323,7 +78508,7 @@ function ensureNodeEnv(opts) {
78323
78508
  const binDir = join64(nodeModulesDir, ".bin");
78324
78509
  const targetHash = hashDepInputs(packageJsonPath);
78325
78510
  if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
78326
- const existingHash = readFileSync56(stampPath, "utf8").trim();
78511
+ const existingHash = readFileSync57(stampPath, "utf8").trim();
78327
78512
  if (existingHash === targetHash) {
78328
78513
  return {
78329
78514
  skillName,
@@ -79342,7 +79527,7 @@ function safeParseInt(value, fallback) {
79342
79527
  init_helpers();
79343
79528
  init_loader();
79344
79529
  init_merge();
79345
- 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";
79346
79531
  import { join as join66, resolve as resolve40 } from "node:path";
79347
79532
  init_schema();
79348
79533
  function resolveSoulTargetOrExit(program3, agentName) {
@@ -79388,7 +79573,7 @@ function registerSoulCommand(program3) {
79388
79573
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
79389
79574
  process.exit(1);
79390
79575
  }
79391
- process.stdout.write(readFileSync57(t.soulPath, "utf-8"));
79576
+ process.stdout.write(readFileSync58(t.soulPath, "utf-8"));
79392
79577
  }));
79393
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) => {
79394
79579
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -79433,7 +79618,7 @@ function registerSoulCommand(program3) {
79433
79618
  // src/cli/debug.ts
79434
79619
  init_helpers();
79435
79620
  init_loader();
79436
- 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";
79437
79622
  import { resolve as resolve41, join as join67 } from "node:path";
79438
79623
  import { createHash as createHash13 } from "node:crypto";
79439
79624
  init_merge();
@@ -79449,7 +79634,7 @@ function readMcpServerNames(agentDir) {
79449
79634
  if (!existsSync66(mcpPath))
79450
79635
  return [];
79451
79636
  try {
79452
- const parsed = JSON.parse(readFileSync58(mcpPath, "utf-8"));
79637
+ const parsed = JSON.parse(readFileSync59(mcpPath, "utf-8"));
79453
79638
  return Object.keys(parsed.mcpServers ?? {});
79454
79639
  } catch {
79455
79640
  return null;
@@ -79472,7 +79657,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
79472
79657
  const transcriptPath = join67(projectPath, "transcript.jsonl");
79473
79658
  if (!existsSync66(transcriptPath))
79474
79659
  continue;
79475
- const stat3 = statSync28(transcriptPath);
79660
+ const stat3 = statSync29(transcriptPath);
79476
79661
  if (!latest || stat3.mtimeMs > latest.mtime) {
79477
79662
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
79478
79663
  }
@@ -79484,7 +79669,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
79484
79669
  }
79485
79670
  function extractLatestUserMessage(transcriptPath) {
79486
79671
  try {
79487
- const content = readFileSync58(transcriptPath, "utf-8");
79672
+ const content = readFileSync59(transcriptPath, "utf-8");
79488
79673
  const lines = content.trim().split(`
79489
79674
  `).filter(Boolean);
79490
79675
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -79588,7 +79773,7 @@ function registerDebugCommand(program3) {
79588
79773
  }
79589
79774
  console.log(`=== Append System Prompt (per-session) ===
79590
79775
  `);
79591
- const handoffContent = existsSync66(handoffPath) ? readFileSync58(handoffPath, "utf-8") : "";
79776
+ const handoffContent = existsSync66(handoffPath) ? readFileSync59(handoffPath, "utf-8") : "";
79592
79777
  if (handoffContent.trim().length > 0) {
79593
79778
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
79594
79779
  console.log(handoffContent);
@@ -79599,7 +79784,7 @@ function registerDebugCommand(program3) {
79599
79784
  }
79600
79785
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
79601
79786
  `);
79602
- const claudeMdContent = existsSync66(claudeMdPath) ? readFileSync58(claudeMdPath, "utf-8") : "";
79787
+ const claudeMdContent = existsSync66(claudeMdPath) ? readFileSync59(claudeMdPath, "utf-8") : "";
79603
79788
  if (claudeMdContent.trim().length > 0) {
79604
79789
  console.log(`(${formatBytes(claudeMdContent.length)})`);
79605
79790
  console.log(claudeMdContent);
@@ -79610,7 +79795,7 @@ function registerDebugCommand(program3) {
79610
79795
  }
79611
79796
  console.log(`=== Persona (SOUL.md) ===
79612
79797
  `);
79613
- 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") : "";
79614
79799
  if (soulMdContent.trim().length > 0) {
79615
79800
  console.log(`(${formatBytes(soulMdContent.length)})`);
79616
79801
  console.log(soulMdContent);
@@ -79674,8 +79859,8 @@ function registerDebugCommand(program3) {
79674
79859
  const fleetDir = join67(agentsDir, "..", "fleet");
79675
79860
  const fleetInvPath = join67(fleetDir, "switchroom-invariants.md");
79676
79861
  const fleetClaudePath = join67(fleetDir, "CLAUDE.md");
79677
- const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync58(fleetInvPath, "utf-8").length : 0;
79678
- 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;
79679
79864
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
79680
79865
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
79681
79866
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -79717,7 +79902,7 @@ import { randomBytes as randomBytes13 } from "node:crypto";
79717
79902
  import {
79718
79903
  mkdirSync as mkdirSync36,
79719
79904
  writeFileSync as writeFileSync32,
79720
- readFileSync as readFileSync59,
79905
+ readFileSync as readFileSync60,
79721
79906
  readdirSync as readdirSync24,
79722
79907
  unlinkSync as unlinkSync12,
79723
79908
  existsSync as existsSync67,
@@ -79745,7 +79930,7 @@ function writeRecord(record2) {
79745
79930
  function readRecord(id) {
79746
79931
  const path7 = recordPath(id);
79747
79932
  try {
79748
- const raw = readFileSync59(path7, "utf8");
79933
+ const raw = readFileSync60(path7, "utf8");
79749
79934
  return JSON.parse(raw);
79750
79935
  } catch {
79751
79936
  return null;
@@ -80812,7 +80997,7 @@ function registerNotionMcpLauncherCommand(program3) {
80812
80997
 
80813
80998
  // src/cli/deliver-file.ts
80814
80999
  init_client2();
80815
- import { readFileSync as readFileSync60, statSync as statSync29 } from "node:fs";
81000
+ import { readFileSync as readFileSync61, statSync as statSync30 } from "node:fs";
80816
81001
  import { basename as basename8 } from "node:path";
80817
81002
 
80818
81003
  // src/delivery/onedrive.ts
@@ -81151,8 +81336,8 @@ async function defaultResolveProvider() {
81151
81336
  }
81152
81337
  async function runDeliverFile(localPath, deps = {}) {
81153
81338
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
81154
- const sizeOf = deps.fileSize ?? ((p) => statSync29(p).size);
81155
- 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)));
81156
81341
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
81157
81342
  let size;
81158
81343
  try {
@@ -81442,7 +81627,7 @@ async function fetchToken(vaultKey) {
81442
81627
 
81443
81628
  // src/cli/apply.ts
81444
81629
  init_source();
81445
- 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";
81446
81631
  import { mkdir as mkdir2 } from "node:fs/promises";
81447
81632
  import { spawnSync as childSpawnSync } from "node:child_process";
81448
81633
  import readline from "node:readline";
@@ -81839,7 +82024,7 @@ init_loader();
81839
82024
  init_loader();
81840
82025
 
81841
82026
  // src/cli/update-prompt-hook.ts
81842
- 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";
81843
82028
  import { join as join72 } from "node:path";
81844
82029
  var HOOK_FILENAME = "update-card-on-prompt.sh";
81845
82030
  function updatePromptHookScript() {
@@ -81911,7 +82096,7 @@ function installUpdatePromptHook(agentDir) {
81911
82096
  const scriptPath = join72(hooksDir, HOOK_FILENAME);
81912
82097
  const desired = updatePromptHookScript();
81913
82098
  let installed = false;
81914
- const existing = existsSync72(scriptPath) ? readFileSync61(scriptPath, "utf-8") : "";
82099
+ const existing = existsSync72(scriptPath) ? readFileSync62(scriptPath, "utf-8") : "";
81915
82100
  if (existing !== desired) {
81916
82101
  writeFileSync36(scriptPath, desired, { mode: 493 });
81917
82102
  chmodSync10(scriptPath, 493);
@@ -81925,7 +82110,7 @@ function installUpdatePromptHook(agentDir) {
81925
82110
  if (!existsSync72(settingsPath)) {
81926
82111
  return { scriptPath, settingsPath, installed };
81927
82112
  }
81928
- const raw = readFileSync61(settingsPath, "utf-8");
82113
+ const raw = readFileSync62(settingsPath, "utf-8");
81929
82114
  let parsed;
81930
82115
  try {
81931
82116
  parsed = JSON.parse(raw);
@@ -82102,14 +82287,14 @@ async function ensureHostMountSources(config) {
82102
82287
  }
82103
82288
  try {
82104
82289
  const uid = allocateAgentUid(name);
82105
- chownSync5(tokenPath, uid, uid);
82290
+ chownSync7(tokenPath, uid, uid);
82106
82291
  } catch {}
82107
82292
  }
82108
82293
  const fleetDir = join74(home2, ".switchroom", "fleet");
82109
82294
  await mkdir2(fleetDir, { recursive: true });
82110
82295
  const invariantsPath = join74(fleetDir, "switchroom-invariants.md");
82111
82296
  const invariantsCanonical = renderFleetInvariants();
82112
- const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync62(invariantsPath, "utf-8") : null;
82297
+ const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync63(invariantsPath, "utf-8") : null;
82113
82298
  if (invariantsCurrent !== invariantsCanonical) {
82114
82299
  writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
82115
82300
  }
@@ -82634,7 +82819,7 @@ function runRedactStdin() {
82634
82819
  }
82635
82820
 
82636
82821
  // src/cli/status-ask.ts
82637
- 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";
82638
82823
  import { join as join75 } from "node:path";
82639
82824
  import { homedir as homedir44 } from "node:os";
82640
82825
 
@@ -82910,7 +83095,7 @@ function runReport(opts) {
82910
83095
  for (const src of sources) {
82911
83096
  let content;
82912
83097
  try {
82913
- content = readFileSync63(src.path, "utf-8");
83098
+ content = readFileSync64(src.path, "utf-8");
82914
83099
  } catch (err) {
82915
83100
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
82916
83101
  `);
@@ -83004,7 +83189,7 @@ function inferAgentFromPath(p) {
83004
83189
  }
83005
83190
 
83006
83191
  // src/cli/agent-config-write.ts
83007
- var import_yaml19 = __toESM(require_dist(), 1);
83192
+ var import_yaml20 = __toESM(require_dist(), 1);
83008
83193
 
83009
83194
  // src/config/overlay-writer.ts
83010
83195
  init_paths();
@@ -83015,9 +83200,9 @@ import {
83015
83200
  mkdirSync as mkdirSync43,
83016
83201
  openSync as openSync13,
83017
83202
  readdirSync as readdirSync28,
83018
- readFileSync as readFileSync64,
83203
+ readFileSync as readFileSync65,
83019
83204
  renameSync as renameSync15,
83020
- statSync as statSync30,
83205
+ statSync as statSync31,
83021
83206
  unlinkSync as unlinkSync14,
83022
83207
  writeSync as writeSync8
83023
83208
  } from "node:fs";
@@ -83061,7 +83246,7 @@ function withAgentLock(paths, fn) {
83061
83246
  if (e.code !== "EEXIST")
83062
83247
  throw err;
83063
83248
  try {
83064
- const age = Date.now() - statSync30(paths.lockPath).mtimeMs;
83249
+ const age = Date.now() - statSync31(paths.lockPath).mtimeMs;
83065
83250
  if (age > 30000) {
83066
83251
  unlinkSync14(paths.lockPath);
83067
83252
  continue;
@@ -83139,7 +83324,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
83139
83324
  continue;
83140
83325
  const full = join76(paths.skillsDir, name);
83141
83326
  try {
83142
- const raw = readFileSync64(full, "utf-8");
83327
+ const raw = readFileSync65(full, "utf-8");
83143
83328
  const slug = name.replace(/\.ya?ml$/i, "");
83144
83329
  out.push({ slug, path: full, raw });
83145
83330
  } catch {}
@@ -83166,7 +83351,7 @@ function listOverlayEntries(agent, opts = {}) {
83166
83351
  continue;
83167
83352
  const full = join76(paths.scheduleDir, name);
83168
83353
  try {
83169
- const raw = readFileSync64(full, "utf-8");
83354
+ const raw = readFileSync65(full, "utf-8");
83170
83355
  const slug = name.replace(/\.ya?ml$/i, "");
83171
83356
  out.push({ slug, path: full, raw });
83172
83357
  } catch {}
@@ -83197,7 +83382,7 @@ function filterOverlaySecrets(doc, source) {
83197
83382
  // src/agents/reconcile-dry-run.ts
83198
83383
  init_overlay_schema();
83199
83384
  init_schema();
83200
- var import_yaml18 = __toESM(require_dist(), 1);
83385
+ var import_yaml19 = __toESM(require_dist(), 1);
83201
83386
  init_lifecycle();
83202
83387
  var MIN_CRON_INTERVAL_SECS = 5 * 60;
83203
83388
  function violatesMinInterval(cron) {
@@ -83219,7 +83404,7 @@ function violatesMinInterval(cron) {
83219
83404
  function dryRunReconcile(input) {
83220
83405
  let parsed;
83221
83406
  try {
83222
- parsed = import_yaml18.parse(input.yamlText);
83407
+ parsed = import_yaml19.parse(input.yamlText);
83223
83408
  } catch (err) {
83224
83409
  return {
83225
83410
  ok: false,
@@ -83314,7 +83499,7 @@ import {
83314
83499
  mkdirSync as mkdirSync44,
83315
83500
  openSync as openSync14,
83316
83501
  readdirSync as readdirSync29,
83317
- readFileSync as readFileSync65,
83502
+ readFileSync as readFileSync66,
83318
83503
  renameSync as renameSync16,
83319
83504
  unlinkSync as unlinkSync15,
83320
83505
  writeFileSync as writeFileSync38,
@@ -83378,7 +83563,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
83378
83563
  if (!existsSync77(yamlPath))
83379
83564
  continue;
83380
83565
  try {
83381
- const meta = JSON.parse(readFileSync65(metaPath, "utf-8"));
83566
+ const meta = JSON.parse(readFileSync66(metaPath, "utf-8"));
83382
83567
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
83383
83568
  continue;
83384
83569
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -83416,7 +83601,7 @@ function denyPendingScheduleEntry(opts) {
83416
83601
  }
83417
83602
 
83418
83603
  // src/cli/agent-config-write.ts
83419
- import { existsSync as existsSync78, readFileSync as readFileSync66 } from "node:fs";
83604
+ import { existsSync as existsSync78, readFileSync as readFileSync67 } from "node:fs";
83420
83605
  import { execFileSync as execFileSync25 } from "node:child_process";
83421
83606
 
83422
83607
  // src/scheduler/schedule-report.ts
@@ -83605,7 +83790,7 @@ function scheduleAdd(opts) {
83605
83790
  ]
83606
83791
  };
83607
83792
  const yamlText = (() => {
83608
- const body = import_yaml19.stringify(doc);
83793
+ const body = import_yaml20.stringify(doc);
83609
83794
  const header = opts.name ? `# name: ${opts.name}
83610
83795
  ` : "";
83611
83796
  return header + body;
@@ -83719,7 +83904,7 @@ function scheduleAddOrStage(opts) {
83719
83904
  ]
83720
83905
  };
83721
83906
  const yamlText = (opts.name ? `# name: ${opts.name}
83722
- ` : "") + import_yaml19.stringify(doc);
83907
+ ` : "") + import_yaml20.stringify(doc);
83723
83908
  const summary = (() => {
83724
83909
  const parts = [`cron=${opts.cronExpr}`];
83725
83910
  if (opts.secrets?.length)
@@ -83769,7 +83954,7 @@ function scheduleRemove(opts) {
83769
83954
  break;
83770
83955
  }
83771
83956
  try {
83772
- const parsed = import_yaml19.parse(e.raw);
83957
+ const parsed = import_yaml20.parse(e.raw);
83773
83958
  if (parsed && parsed.name === opts.name) {
83774
83959
  match = e;
83775
83960
  break;
@@ -83788,7 +83973,7 @@ function scheduleRemove(opts) {
83788
83973
  let priorContent = null;
83789
83974
  try {
83790
83975
  if (existsSync78(match.path))
83791
- priorContent = readFileSync66(match.path, "utf-8");
83976
+ priorContent = readFileSync67(match.path, "utf-8");
83792
83977
  } catch {}
83793
83978
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
83794
83979
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -83991,7 +84176,7 @@ function registerAgentConfigWriteCommands(program3) {
83991
84176
  }
83992
84177
  let blob;
83993
84178
  if (opts.jsonl) {
83994
- blob = existsSync78(opts.jsonl) ? readFileSync66(opts.jsonl, "utf-8") : "";
84179
+ blob = existsSync78(opts.jsonl) ? readFileSync67(opts.jsonl, "utf-8") : "";
83995
84180
  } else {
83996
84181
  try {
83997
84182
  blob = execFileSync25("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -84022,10 +84207,10 @@ function registerAgentConfigWriteCommands(program3) {
84022
84207
  }
84023
84208
 
84024
84209
  // src/cli/agent-config-skill-write.ts
84025
- var import_yaml20 = __toESM(require_dist(), 1);
84210
+ var import_yaml21 = __toESM(require_dist(), 1);
84026
84211
  import { existsSync as existsSync79 } from "node:fs";
84027
84212
  init_reconcile_default_skills();
84028
- var import_yaml21 = __toESM(require_dist(), 1);
84213
+ var import_yaml22 = __toESM(require_dist(), 1);
84029
84214
  import { join as join78 } from "node:path";
84030
84215
  var MAX_SKILLS_PER_AGENT = 20;
84031
84216
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
@@ -84075,7 +84260,7 @@ function countCurrentSkills(agent, opts) {
84075
84260
  let total = 0;
84076
84261
  for (const e of entries) {
84077
84262
  try {
84078
- const doc = import_yaml21.parse(e.raw);
84263
+ const doc = import_yaml22.parse(e.raw);
84079
84264
  total += (doc?.skills ?? []).length;
84080
84265
  } catch {}
84081
84266
  }
@@ -84105,7 +84290,7 @@ function skillInstall(opts) {
84105
84290
  if (!existsSync79(skillPath)) {
84106
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.`);
84107
84292
  }
84108
- const yamlText = import_yaml20.stringify({ skills: [skillName] });
84293
+ const yamlText = import_yaml21.stringify({ skills: [skillName] });
84109
84294
  let path8;
84110
84295
  try {
84111
84296
  path8 = writeSkillsOverlayEntry(agent, slug, yamlText, { root: opts.root });
@@ -84271,12 +84456,12 @@ import {
84271
84456
  mkdirSync as mkdirSync45,
84272
84457
  mkdtempSync as mkdtempSync5,
84273
84458
  openSync as openSync15,
84274
- readFileSync as readFileSync67,
84459
+ readFileSync as readFileSync68,
84275
84460
  readdirSync as readdirSync30,
84276
84461
  realpathSync as realpathSync7,
84277
84462
  renameSync as renameSync17,
84278
84463
  rmSync as rmSync16,
84279
- statSync as statSync31,
84464
+ statSync as statSync32,
84280
84465
  writeFileSync as writeFileSync39
84281
84466
  } from "node:fs";
84282
84467
  import { tmpdir as tmpdir5, homedir as homedir45 } from "node:os";
@@ -84284,7 +84469,7 @@ import { dirname as dirname23, join as join79, relative as relative2, resolve as
84284
84469
  import { spawnSync as spawnSync12 } from "node:child_process";
84285
84470
 
84286
84471
  // src/cli/skill-common.ts
84287
- var import_yaml22 = __toESM(require_dist(), 1);
84472
+ var import_yaml23 = __toESM(require_dist(), 1);
84288
84473
  var MAX_FILE_BYTES = 256 * 1024;
84289
84474
  var MAX_SKILL_BYTES = 2 * 1024 * 1024;
84290
84475
  var MAX_FILES_PER_SKILL = 50;
@@ -84388,7 +84573,7 @@ function validateSkillMd(content, expectedName) {
84388
84573
  }
84389
84574
  let parsed;
84390
84575
  try {
84391
- parsed = import_yaml22.parse(fmText);
84576
+ parsed = import_yaml23.parse(fmText);
84392
84577
  } catch (e) {
84393
84578
  return authorErr("E_SKILL_INVALID_FRONTMATTER", `frontmatter is not valid YAML: ${e.message}`);
84394
84579
  }
@@ -84506,7 +84691,7 @@ function isTarballPath(p) {
84506
84691
  }
84507
84692
  function loadFromDir(dir) {
84508
84693
  const abs = realpathSync7(dir);
84509
- if (!statSync31(abs).isDirectory()) {
84694
+ if (!statSync32(abs).isDirectory()) {
84510
84695
  fail3(`--from path is not a directory: ${dir}`);
84511
84696
  }
84512
84697
  const files = {};
@@ -84523,7 +84708,7 @@ function loadFromDir(dir) {
84523
84708
  continue;
84524
84709
  }
84525
84710
  if (ent.isFile()) {
84526
- const buf = readFileSync67(full);
84711
+ const buf = readFileSync68(full);
84527
84712
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
84528
84713
  }
84529
84714
  }
@@ -84572,7 +84757,7 @@ function loadFromTarball(tarPath) {
84572
84757
  }
84573
84758
  }
84574
84759
  function loadSingleFile(filePath) {
84575
- const content = readFileSync67(filePath, "utf-8");
84760
+ const content = readFileSync68(filePath, "utf-8");
84576
84761
  return { "SKILL.md": content };
84577
84762
  }
84578
84763
  function loadFromStdin() {
@@ -84663,7 +84848,7 @@ function diffSummary(currentDir, files) {
84663
84848
  if (ent.isDirectory()) {
84664
84849
  walk2(full);
84665
84850
  } else if (ent.isFile()) {
84666
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync67(full, "utf-8");
84851
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync68(full, "utf-8");
84667
84852
  }
84668
84853
  }
84669
84854
  };
@@ -84766,7 +84951,7 @@ function registerSkillCommand(program3) {
84766
84951
  if (!existsSync80(fromPath)) {
84767
84952
  fail3(`--from path does not exist: ${opts.from}`);
84768
84953
  }
84769
- const st = statSync31(fromPath);
84954
+ const st = statSync32(fromPath);
84770
84955
  if (st.isDirectory()) {
84771
84956
  files = loadFromDir(fromPath);
84772
84957
  } else if (isTarballPath(fromPath)) {
@@ -84823,11 +85008,11 @@ import {
84823
85008
  mkdirSync as mkdirSync46,
84824
85009
  mkdtempSync as mkdtempSync6,
84825
85010
  openSync as openSync16,
84826
- readFileSync as readFileSync68,
85011
+ readFileSync as readFileSync69,
84827
85012
  readdirSync as readdirSync31,
84828
85013
  renameSync as renameSync18,
84829
85014
  rmSync as rmSync17,
84830
- statSync as statSync32,
85015
+ statSync as statSync33,
84831
85016
  utimesSync,
84832
85017
  writeFileSync as writeFileSync40
84833
85018
  } from "node:fs";
@@ -84905,7 +85090,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
84905
85090
  if (ent.isDirectory())
84906
85091
  walk2(s, d);
84907
85092
  else if (ent.isFile()) {
84908
- writeFileSync40(d, readFileSync68(s));
85093
+ writeFileSync40(d, readFileSync69(s));
84909
85094
  }
84910
85095
  }
84911
85096
  };
@@ -84968,7 +85153,7 @@ function readStdinSync2() {
84968
85153
  }
84969
85154
  function loadFromDir2(dir) {
84970
85155
  const abs = resolve48(dir);
84971
- if (!statSync32(abs).isDirectory()) {
85156
+ if (!statSync33(abs).isDirectory()) {
84972
85157
  fail4(`--from path is not a directory: ${dir}`);
84973
85158
  }
84974
85159
  const files = {};
@@ -84984,7 +85169,7 @@ function loadFromDir2(dir) {
84984
85169
  }
84985
85170
  if (ent.isFile()) {
84986
85171
  const rel = relative3(abs, full).replace(/\\/g, "/");
84987
- files[rel] = readFileSync68(full, "utf-8");
85172
+ files[rel] = readFileSync69(full, "utf-8");
84988
85173
  }
84989
85174
  }
84990
85175
  };
@@ -85054,7 +85239,7 @@ function sweepTrash(agentsRoot, agent) {
85054
85239
  continue;
85055
85240
  const entPath = join80(trash, ent.name);
85056
85241
  try {
85057
- const st = statSync32(entPath);
85242
+ const st = statSync33(entPath);
85058
85243
  if (now - st.mtimeMs > TRASH_TTL_MS) {
85059
85244
  rmSync17(entPath, { recursive: true, force: true });
85060
85245
  }
@@ -85165,12 +85350,12 @@ function loadFiles(opts) {
85165
85350
  if (!existsSync81(p)) {
85166
85351
  fail4(`--from path does not exist: ${opts.from}`);
85167
85352
  }
85168
- const st = statSync32(p);
85353
+ const st = statSync33(p);
85169
85354
  if (st.isDirectory()) {
85170
85355
  return loadFromDir2(p);
85171
85356
  }
85172
85357
  if (p.endsWith(".md")) {
85173
- return { "SKILL.md": readFileSync68(p, "utf-8") };
85358
+ return { "SKILL.md": readFileSync69(p, "utf-8") };
85174
85359
  }
85175
85360
  fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
85176
85361
  }
@@ -85259,7 +85444,7 @@ function readSourceFiles(dir) {
85259
85444
  fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
85260
85445
  }
85261
85446
  } catch {}
85262
- files[rel] = readFileSync68(full, "utf-8");
85447
+ files[rel] = readFileSync69(full, "utf-8");
85263
85448
  }
85264
85449
  }
85265
85450
  };
@@ -85386,7 +85571,7 @@ function listPersonalAction(opts) {
85386
85571
  if (e.isFile()) {
85387
85572
  fileCount += 1;
85388
85573
  try {
85389
- totalBytes += statSync32(join80(sub, e.name)).size;
85574
+ totalBytes += statSync33(join80(sub, e.name)).size;
85390
85575
  } catch {}
85391
85576
  } else if (e.isDirectory()) {
85392
85577
  walk2(join80(sub, e.name));
@@ -85427,8 +85612,8 @@ function registerSkillPersonalCommands(program3) {
85427
85612
 
85428
85613
  // src/cli/skill-search.ts
85429
85614
  init_helpers();
85430
- var import_yaml23 = __toESM(require_dist(), 1);
85431
- 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";
85432
85617
  import { homedir as homedir47 } from "node:os";
85433
85618
  import { join as join81, resolve as resolve49 } from "node:path";
85434
85619
  var PERSONAL_PREFIX2 = "personal-";
@@ -85449,7 +85634,7 @@ function readSkillFrontmatter(skillDir) {
85449
85634
  return null;
85450
85635
  let content;
85451
85636
  try {
85452
- content = readFileSync69(mdPath, "utf-8");
85637
+ content = readFileSync70(mdPath, "utf-8");
85453
85638
  } catch {
85454
85639
  return null;
85455
85640
  }
@@ -85467,7 +85652,7 @@ function readSkillFrontmatter(skillDir) {
85467
85652
  const fmText = rest.slice(0, endIdx);
85468
85653
  let parsed;
85469
85654
  try {
85470
- parsed = import_yaml23.parse(fmText);
85655
+ parsed = import_yaml24.parse(fmText);
85471
85656
  } catch (e) {
85472
85657
  return { error: `yaml parse: ${e.message}` };
85473
85658
  }
@@ -85479,7 +85664,7 @@ function readSkillFrontmatter(skillDir) {
85479
85664
  function statSkillMd(skillDir) {
85480
85665
  const mdPath = join81(skillDir, "SKILL.md");
85481
85666
  try {
85482
- const st = statSync33(mdPath);
85667
+ const st = statSync34(mdPath);
85483
85668
  return { size: st.size, mtime: st.mtime.toISOString() };
85484
85669
  } catch {
85485
85670
  return null;
@@ -85503,7 +85688,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
85503
85688
  continue;
85504
85689
  const dirPath = join81(skillsDir, ent);
85505
85690
  try {
85506
- if (!statSync33(dirPath).isDirectory())
85691
+ if (!statSync34(dirPath).isDirectory())
85507
85692
  continue;
85508
85693
  } catch {
85509
85694
  continue;
@@ -85543,7 +85728,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
85543
85728
  continue;
85544
85729
  const dirPath = join81(sharedRoot, ent);
85545
85730
  try {
85546
- if (!statSync33(dirPath).isDirectory())
85731
+ if (!statSync34(dirPath).isDirectory())
85547
85732
  continue;
85548
85733
  } catch {
85549
85734
  continue;
@@ -85579,7 +85764,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
85579
85764
  continue;
85580
85765
  const dirPath = join81(bundledRoot, ent);
85581
85766
  try {
85582
- if (!statSync33(dirPath).isDirectory())
85767
+ if (!statSync34(dirPath).isDirectory())
85583
85768
  continue;
85584
85769
  } catch {
85585
85770
  continue;
@@ -85721,7 +85906,7 @@ function registerHostdMcpCommand(program3) {
85721
85906
  // src/cli/hostd.ts
85722
85907
  init_source();
85723
85908
  init_helpers();
85724
- 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";
85725
85910
  import { homedir as homedir48 } from "node:os";
85726
85911
  import { join as join82 } from "node:path";
85727
85912
  import { spawnSync as spawnSync15 } from "node:child_process";
@@ -85957,7 +86142,7 @@ function doStatus() {
85957
86142
  continue;
85958
86143
  const sockPath = join82(dir, name, "sock");
85959
86144
  if (existsSync84(sockPath)) {
85960
- const st = statSync34(sockPath);
86145
+ const st = statSync35(sockPath);
85961
86146
  if ((st.mode & 61440) === 49152) {
85962
86147
  entries.push(`${name} \u2192 ${sockPath}`);
85963
86148
  }
@@ -86004,7 +86189,7 @@ function registerHostdCommand(program3) {
86004
86189
  The log is created when hostd handles its first privileged-verb request.`));
86005
86190
  return;
86006
86191
  }
86007
- const raw = readFileSync71(logPath, "utf-8");
86192
+ const raw = readFileSync72(logPath, "utf-8");
86008
86193
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
86009
86194
  const filters = {
86010
86195
  agent: opts.agent,