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