switchroom 0.13.53 → 0.13.55
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 +53 -1
- package/dist/auth-broker/index.js +53 -1
- package/dist/cli/ms-365-write-pretool.mjs +259 -0
- package/dist/cli/notion-write-pretool.mjs +13388 -0
- package/dist/cli/switchroom.js +1601 -380
- package/dist/host-control/main.js +53 -1
- package/dist/vault/approvals/kernel-server.js +54 -2
- package/dist/vault/broker/server.js +54 -2
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +17 -0
- package/profiles/_shared/telegram-style.md.hbs +2 -0
- package/skills/notion/SKILL.md +144 -0
- package/telegram-plugin/dist/gateway/gateway.js +406 -43
- package/telegram-plugin/gateway/gateway.ts +227 -17
- package/telegram-plugin/gateway/ipc-protocol.ts +37 -0
- package/telegram-plugin/gateway/ipc-server.ts +59 -0
- package/telegram-plugin/gateway/ms365-write-approval.test.ts +314 -0
- package/telegram-plugin/gateway/ms365-write-approval.ts +335 -0
- package/telegram-plugin/tests/ipc-validator.test.ts +61 -0
- package/telegram-plugin/tests/slash-command-smart-split.test.ts +115 -0
- package/vendor/hindsight-memory/scripts/lib/gateway_ipc.py +35 -0
- package/vendor/hindsight-memory/scripts/recall.py +164 -4
- package/vendor/hindsight-memory/scripts/retain.py +52 -0
- package/vendor/hindsight-memory/scripts/tests/test_gateway_ipc.py +42 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_topic_filter.py +139 -0
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({
|
|
@@ -13561,7 +13561,8 @@ var init_schema = __esm(() => {
|
|
|
13561
13561
|
recall: exports_external.object({
|
|
13562
13562
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
13563
13563
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
13564
|
-
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default \u2014 " + "current behaviour). Try 0.10\u20130.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`.")
|
|
13564
|
+
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default \u2014 " + "current behaviour). Try 0.10\u20130.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
13565
|
+
topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
|
|
13565
13566
|
}).optional().describe("Auto-recall tuning knobs")
|
|
13566
13567
|
}).optional();
|
|
13567
13568
|
HookEntrySchema = exports_external.object({
|
|
@@ -13704,6 +13705,16 @@ var init_schema = __esm(() => {
|
|
|
13704
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."),
|
|
13705
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.")
|
|
13706
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();
|
|
13707
13718
|
AgentGoogleWorkspaceConfigSchema = exports_external.object({
|
|
13708
13719
|
account: exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
13709
13720
|
message: "google_workspace.account must be a Google account email like " + "'alice@example.com' (colons not allowed)"
|
|
@@ -13717,6 +13728,13 @@ var init_schema = __esm(() => {
|
|
|
13717
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)."),
|
|
13718
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).")
|
|
13719
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();
|
|
13720
13738
|
ReactionsSchema = exports_external.object({
|
|
13721
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."),
|
|
13722
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`."),
|
|
@@ -13853,6 +13871,7 @@ var init_schema = __esm(() => {
|
|
|
13853
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."),
|
|
13854
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)."),
|
|
13855
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."),
|
|
13856
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({
|
|
13857
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."),
|
|
13858
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.")
|
|
@@ -13976,6 +13995,7 @@ var init_schema = __esm(() => {
|
|
|
13976
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."),
|
|
13977
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)."),
|
|
13978
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."),
|
|
13979
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."),
|
|
13980
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)."),
|
|
13981
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)."),
|
|
@@ -14525,6 +14545,46 @@ var init_merge = __esm(() => {
|
|
|
14525
14545
|
})(mergeAgentConfig ||= {});
|
|
14526
14546
|
});
|
|
14527
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
|
+
|
|
14528
14588
|
// src/config/loader.ts
|
|
14529
14589
|
var exports_loader = {};
|
|
14530
14590
|
__export(exports_loader, {
|
|
@@ -14647,6 +14707,10 @@ function loadConfig(configPath) {
|
|
|
14647
14707
|
}
|
|
14648
14708
|
applyAgentOverlays(config);
|
|
14649
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
|
+
}
|
|
14650
14714
|
return config;
|
|
14651
14715
|
}
|
|
14652
14716
|
function validateAllCronTopicAliases(config, filePath) {
|
|
@@ -20767,6 +20831,30 @@ function getGdriveMcpSettingsEntry(switchroomCliPath, options = {}) {
|
|
|
20767
20831
|
}
|
|
20768
20832
|
};
|
|
20769
20833
|
}
|
|
20834
|
+
function getMs365McpSettingsEntry(switchroomCliPath, options = {}) {
|
|
20835
|
+
const orgArgs = options.orgMode ? ["--org-mode"] : [];
|
|
20836
|
+
return {
|
|
20837
|
+
key: "ms-365",
|
|
20838
|
+
value: {
|
|
20839
|
+
command: switchroomCliPath,
|
|
20840
|
+
args: ["m365-mcp-launcher", ...orgArgs]
|
|
20841
|
+
}
|
|
20842
|
+
};
|
|
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
|
+
}
|
|
20770
20858
|
function getBuiltinDefaultMcpEntries() {
|
|
20771
20859
|
const playwright = getPlaywrightMcpSettingsEntry();
|
|
20772
20860
|
return [
|
|
@@ -20794,7 +20882,7 @@ function getBuiltinDefaultSkillEntries() {
|
|
|
20794
20882
|
...switchroomCore.map((key) => ({ key, optOutKey: key, source: "switchroom" }))
|
|
20795
20883
|
];
|
|
20796
20884
|
}
|
|
20797
|
-
var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "9d69115b63e6bc2ef0d4b5d7a3b962396382b44c";
|
|
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";
|
|
20798
20886
|
var init_scaffold_integration = __esm(() => {
|
|
20799
20887
|
init_hindsight();
|
|
20800
20888
|
});
|
|
@@ -29571,21 +29659,403 @@ var init_doctor_drive = __esm(() => {
|
|
|
29571
29659
|
init_doctor_secret_access();
|
|
29572
29660
|
});
|
|
29573
29661
|
|
|
29574
|
-
// src/cli/doctor-
|
|
29662
|
+
// src/cli/doctor-microsoft.ts
|
|
29575
29663
|
import {
|
|
29576
29664
|
existsSync as realExistsSync2,
|
|
29577
|
-
|
|
29578
|
-
statSync as realStatSync
|
|
29665
|
+
readFileSync as realReadFileSync2
|
|
29579
29666
|
} from "node:fs";
|
|
29580
|
-
import { homedir as homedir25 } from "node:os";
|
|
29581
29667
|
import { join as join45 } from "node:path";
|
|
29668
|
+
import { homedir as homedir25 } from "node:os";
|
|
29669
|
+
function resolveDeps2(deps) {
|
|
29670
|
+
const home2 = deps.homeDir?.() ?? homedir25();
|
|
29671
|
+
return {
|
|
29672
|
+
existsSync: deps.existsSync ?? realExistsSync2,
|
|
29673
|
+
readFileSync: deps.readFileSync ?? realReadFileSync2,
|
|
29674
|
+
agentsDir: join45(home2, ".switchroom", "agents"),
|
|
29675
|
+
now: deps.now ?? Date.now
|
|
29676
|
+
};
|
|
29677
|
+
}
|
|
29678
|
+
function agentMicrosoftAccount(config, agentName) {
|
|
29679
|
+
const agent = config.agents?.[agentName];
|
|
29680
|
+
if (!agent)
|
|
29681
|
+
return;
|
|
29682
|
+
return agent.microsoft_workspace?.account;
|
|
29683
|
+
}
|
|
29684
|
+
function clientValuePresent2(v) {
|
|
29685
|
+
return typeof v === "string" && v.length > 0;
|
|
29686
|
+
}
|
|
29687
|
+
function checkConfigMatrix2(config) {
|
|
29688
|
+
const results = [];
|
|
29689
|
+
const accounts = config.microsoft_accounts ?? {};
|
|
29690
|
+
for (const [name, agent] of Object.entries(config.agents ?? {})) {
|
|
29691
|
+
const acct = agent.microsoft_workspace?.account;
|
|
29692
|
+
if (!acct)
|
|
29693
|
+
continue;
|
|
29694
|
+
const entry = accounts[acct];
|
|
29695
|
+
if (!entry) {
|
|
29696
|
+
results.push({
|
|
29697
|
+
name: `microsoft:matrix:${name}`,
|
|
29698
|
+
status: "fail",
|
|
29699
|
+
detail: `agent '${name}' has microsoft_workspace.account=${acct} but no microsoft_accounts.${acct} block`,
|
|
29700
|
+
fix: `Run 'switchroom auth microsoft account add ${acct}' to register, then 'switchroom auth microsoft enable ${acct} ${name}'.`
|
|
29701
|
+
});
|
|
29702
|
+
continue;
|
|
29703
|
+
}
|
|
29704
|
+
if (!(entry.enabled_for ?? []).includes(name)) {
|
|
29705
|
+
results.push({
|
|
29706
|
+
name: `microsoft:matrix:${name}`,
|
|
29707
|
+
status: "fail",
|
|
29708
|
+
detail: `agent '${name}' is selected to use microsoft account ${acct}, but not listed in microsoft_accounts.${acct}.enabled_for[]`,
|
|
29709
|
+
fix: `Run 'switchroom auth microsoft enable ${acct} ${name}' to grant access.`
|
|
29710
|
+
});
|
|
29711
|
+
continue;
|
|
29712
|
+
}
|
|
29713
|
+
results.push({
|
|
29714
|
+
name: `microsoft:matrix:${name}`,
|
|
29715
|
+
status: "ok",
|
|
29716
|
+
detail: `agent '${name}' aligned with account ${acct}`
|
|
29717
|
+
});
|
|
29718
|
+
}
|
|
29719
|
+
for (const [acct, entry] of Object.entries(accounts)) {
|
|
29720
|
+
for (const name of entry.enabled_for ?? []) {
|
|
29721
|
+
const agentAcct = agentMicrosoftAccount(config, name);
|
|
29722
|
+
if (agentAcct !== acct) {
|
|
29723
|
+
const got = agentAcct ?? "(unset)";
|
|
29724
|
+
results.push({
|
|
29725
|
+
name: `microsoft:matrix:reverse:${name}:${acct}`,
|
|
29726
|
+
status: "fail",
|
|
29727
|
+
detail: `microsoft_accounts.${acct}.enabled_for[] includes '${name}', but agent's microsoft_workspace.account is '${got}' \u2014 broker will return ACCOUNT_NOT_FOUND for this agent`,
|
|
29728
|
+
fix: `Either set agents.${name}.microsoft_workspace.account=${acct}, or remove '${name}' from microsoft_accounts.${acct}.enabled_for[] via 'switchroom auth microsoft disable ${acct} ${name}'.`
|
|
29729
|
+
});
|
|
29730
|
+
}
|
|
29731
|
+
}
|
|
29732
|
+
}
|
|
29733
|
+
return results;
|
|
29734
|
+
}
|
|
29735
|
+
function checkOAuthClient2(config, anyAgentEnabled) {
|
|
29736
|
+
if (!anyAgentEnabled)
|
|
29737
|
+
return [];
|
|
29738
|
+
const mw = config.microsoft_workspace;
|
|
29739
|
+
if (!mw) {
|
|
29740
|
+
return [
|
|
29741
|
+
{
|
|
29742
|
+
name: "microsoft:oauth-client-configured",
|
|
29743
|
+
status: "fail",
|
|
29744
|
+
detail: "agents have microsoft_workspace.account set but the top-level microsoft_workspace: block is missing",
|
|
29745
|
+
fix: "Add a microsoft_workspace block with microsoft_client_id (and optionally microsoft_client_secret) to switchroom.yaml. See `switchroom auth microsoft account add` error output for the full walkthrough."
|
|
29746
|
+
}
|
|
29747
|
+
];
|
|
29748
|
+
}
|
|
29749
|
+
if (!clientValuePresent2(mw.microsoft_client_id)) {
|
|
29750
|
+
return [
|
|
29751
|
+
{
|
|
29752
|
+
name: "microsoft:oauth-client-configured",
|
|
29753
|
+
status: "fail",
|
|
29754
|
+
detail: "microsoft_workspace block present but microsoft_client_id is empty",
|
|
29755
|
+
fix: "Register an Entra app at https://entra.microsoft.com \u2192 App registrations \u2192 New. Copy the Application (client) ID and vault it: `switchroom vault set microsoft-oauth-client-id`."
|
|
29756
|
+
}
|
|
29757
|
+
];
|
|
29758
|
+
}
|
|
29759
|
+
return [
|
|
29760
|
+
{
|
|
29761
|
+
name: "microsoft:oauth-client-configured",
|
|
29762
|
+
status: "ok",
|
|
29763
|
+
detail: clientValuePresent2(mw.microsoft_client_secret) ? "microsoft_client_id + microsoft_client_secret present (confidential client)" : "microsoft_client_id present (public-client app, no secret)"
|
|
29764
|
+
}
|
|
29765
|
+
];
|
|
29766
|
+
}
|
|
29767
|
+
function readHeartbeat(d, agentName) {
|
|
29768
|
+
const path4 = join45(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
|
|
29769
|
+
if (!d.existsSync(path4)) {
|
|
29770
|
+
return { error: "heartbeat file missing \u2014 launcher has not yet started" };
|
|
29771
|
+
}
|
|
29772
|
+
try {
|
|
29773
|
+
const raw = d.readFileSync(path4, "utf-8");
|
|
29774
|
+
const parsed = JSON.parse(raw);
|
|
29775
|
+
if (typeof parsed.lastRefreshMs !== "number" || typeof parsed.nextRefreshMs !== "number" || typeof parsed.expiresAtMs !== "number") {
|
|
29776
|
+
return { error: "heartbeat file malformed (missing required numeric fields)" };
|
|
29777
|
+
}
|
|
29778
|
+
return parsed;
|
|
29779
|
+
} catch (err) {
|
|
29780
|
+
return { error: `heartbeat parse error: ${err instanceof Error ? err.message : String(err)}` };
|
|
29781
|
+
}
|
|
29782
|
+
}
|
|
29783
|
+
function checkLauncherHeartbeat(msEnabledAgents, d) {
|
|
29784
|
+
const results = [];
|
|
29785
|
+
for (const name of msEnabledAgents) {
|
|
29786
|
+
const hb = readHeartbeat(d, name);
|
|
29787
|
+
if ("error" in hb) {
|
|
29788
|
+
results.push({
|
|
29789
|
+
name: `microsoft:launcher-heartbeat:${name}`,
|
|
29790
|
+
status: "warn",
|
|
29791
|
+
detail: `${hb.error} (path: ~/.switchroom/agents/${name}/m365-launcher.heartbeat.json)`,
|
|
29792
|
+
fix: `Launcher writes the heartbeat on its first refresh tick. If agent has been running for >5min and the file is still missing, check the launcher log via 'docker logs switchroom-${name}'.`
|
|
29793
|
+
});
|
|
29794
|
+
continue;
|
|
29795
|
+
}
|
|
29796
|
+
const now = d.now();
|
|
29797
|
+
const ageSinceLastRefresh = now - hb.lastRefreshMs;
|
|
29798
|
+
const untilNextRefresh = hb.nextRefreshMs - now;
|
|
29799
|
+
if (ageSinceLastRefresh > 90 * 60 * 1000) {
|
|
29800
|
+
results.push({
|
|
29801
|
+
name: `microsoft:launcher-heartbeat:${name}`,
|
|
29802
|
+
status: "fail",
|
|
29803
|
+
detail: `last refresh was ${Math.round(ageSinceLastRefresh / 60000)}min ago (>90min) \u2014 launcher refresh loop appears dead`,
|
|
29804
|
+
fix: `Restart the agent: 'switchroom agent restart ${name}'. If problem recurs, check launcher logs for broker/network failures.`
|
|
29805
|
+
});
|
|
29806
|
+
} else if (untilNextRefresh < -5 * 60 * 1000) {
|
|
29807
|
+
results.push({
|
|
29808
|
+
name: `microsoft:launcher-heartbeat:${name}`,
|
|
29809
|
+
status: "warn",
|
|
29810
|
+
detail: `nextRefreshMs was ${Math.round(-untilNextRefresh / 60000)}min ago \u2014 refresh tick missed its scheduled time`,
|
|
29811
|
+
fix: `Tick is overdue but not yet stale. Monitor; if it doesn't update within a few minutes, restart the agent.`
|
|
29812
|
+
});
|
|
29813
|
+
} else {
|
|
29814
|
+
results.push({
|
|
29815
|
+
name: `microsoft:launcher-heartbeat:${name}`,
|
|
29816
|
+
status: "ok",
|
|
29817
|
+
detail: `last refresh ${Math.round(ageSinceLastRefresh / 60000)}min ago, next in ${Math.round(untilNextRefresh / 60000)}min`
|
|
29818
|
+
});
|
|
29819
|
+
}
|
|
29820
|
+
}
|
|
29821
|
+
return results;
|
|
29822
|
+
}
|
|
29823
|
+
function computeMicrosoftEnabledAgents(config) {
|
|
29824
|
+
const accounts = config.microsoft_accounts ?? {};
|
|
29825
|
+
return Object.keys(config.agents ?? {}).filter((name) => {
|
|
29826
|
+
const acct = agentMicrosoftAccount(config, name);
|
|
29827
|
+
return !!acct && !!accounts[acct] && (accounts[acct].enabled_for ?? []).includes(name);
|
|
29828
|
+
});
|
|
29829
|
+
}
|
|
29830
|
+
function runMicrosoftChecks(config, deps = {}) {
|
|
29831
|
+
const accounts = config.microsoft_accounts;
|
|
29832
|
+
const anyAgentAccount = Object.keys(config.agents ?? {}).some((n) => agentMicrosoftAccount(config, n) !== undefined);
|
|
29833
|
+
const accountsConfigured = !!accounts && Object.keys(accounts).length > 0;
|
|
29834
|
+
if (!accountsConfigured && !anyAgentAccount && !config.microsoft_workspace) {
|
|
29835
|
+
return [];
|
|
29836
|
+
}
|
|
29837
|
+
const d = resolveDeps2(deps);
|
|
29838
|
+
const results = [];
|
|
29839
|
+
results.push(...checkConfigMatrix2(config));
|
|
29840
|
+
const msAgents = computeMicrosoftEnabledAgents(config);
|
|
29841
|
+
results.push(...checkOAuthClient2(config, msAgents.length > 0));
|
|
29842
|
+
results.push(...checkLauncherHeartbeat(msAgents, d));
|
|
29843
|
+
return results;
|
|
29844
|
+
}
|
|
29845
|
+
var init_doctor_microsoft = () => {};
|
|
29846
|
+
|
|
29847
|
+
// src/cli/doctor-notion.ts
|
|
29848
|
+
import {
|
|
29849
|
+
existsSync as realExistsSync3,
|
|
29850
|
+
readFileSync as realReadFileSync3,
|
|
29851
|
+
statSync as realStatSync
|
|
29852
|
+
} from "node:fs";
|
|
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";
|
|
29582
30052
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
29583
|
-
const credDir = deps.credentialsDir ??
|
|
29584
|
-
const existsSync49 = deps.existsSync ?? ((p) =>
|
|
30053
|
+
const credDir = deps.credentialsDir ?? join47(homedir27(), ".switchroom", "credentials");
|
|
30054
|
+
const existsSync49 = deps.existsSync ?? ((p) => realExistsSync4(p));
|
|
29585
30055
|
const readdirSync19 = deps.readdirSync ?? ((p) => realReaddirSync(p));
|
|
29586
30056
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
29587
30057
|
try {
|
|
29588
|
-
return
|
|
30058
|
+
return realStatSync2(p).isDirectory();
|
|
29589
30059
|
} catch {
|
|
29590
30060
|
return false;
|
|
29591
30061
|
}
|
|
@@ -29608,7 +30078,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
29608
30078
|
const flat = [];
|
|
29609
30079
|
const perAgentDirs = [];
|
|
29610
30080
|
for (const e of entries) {
|
|
29611
|
-
const full =
|
|
30081
|
+
const full = join47(credDir, e);
|
|
29612
30082
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
29613
30083
|
perAgentDirs.push(e);
|
|
29614
30084
|
} else {
|
|
@@ -29731,19 +30201,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
29731
30201
|
|
|
29732
30202
|
// src/cli/doctor-audit-integrity.ts
|
|
29733
30203
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
29734
|
-
import { homedir as
|
|
29735
|
-
import { join as
|
|
30204
|
+
import { homedir as homedir28 } from "node:os";
|
|
30205
|
+
import { join as join48 } from "node:path";
|
|
29736
30206
|
function rootWrittenLogs(home2) {
|
|
29737
30207
|
return [
|
|
29738
|
-
{ label: "vault-broker", path:
|
|
30208
|
+
{ label: "vault-broker", path: join48(home2, ".switchroom", "vault-audit.log") },
|
|
29739
30209
|
{
|
|
29740
30210
|
label: "hostd",
|
|
29741
|
-
path:
|
|
30211
|
+
path: join48(home2, ".switchroom", "host-control-audit.log")
|
|
29742
30212
|
}
|
|
29743
30213
|
];
|
|
29744
30214
|
}
|
|
29745
30215
|
function runAuditIntegrityChecks(deps = {}) {
|
|
29746
|
-
const home2 = deps.homeDir ??
|
|
30216
|
+
const home2 = deps.homeDir ?? homedir28();
|
|
29747
30217
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
29748
30218
|
const results = [];
|
|
29749
30219
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -29881,14 +30351,14 @@ var init_client4 = __esm(() => {
|
|
|
29881
30351
|
|
|
29882
30352
|
// src/cli/doctor-agent-smoke.ts
|
|
29883
30353
|
import { existsSync as existsSync49 } from "node:fs";
|
|
29884
|
-
import { homedir as
|
|
29885
|
-
import { join as
|
|
30354
|
+
import { homedir as homedir29 } from "node:os";
|
|
30355
|
+
import { join as join49 } from "node:path";
|
|
29886
30356
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
29887
30357
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
29888
30358
|
if (deps.fast)
|
|
29889
30359
|
return [];
|
|
29890
|
-
const home2 = deps.homeDir ??
|
|
29891
|
-
const sock = deps.operatorSockPath ??
|
|
30360
|
+
const home2 = deps.homeDir ?? homedir29();
|
|
30361
|
+
const sock = deps.operatorSockPath ?? join49(home2, ".switchroom", "hostd", "operator", "sock");
|
|
29892
30362
|
if (!deps.hostdRequestImpl && !existsSync49(sock)) {
|
|
29893
30363
|
return [
|
|
29894
30364
|
{
|
|
@@ -29968,8 +30438,8 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
29968
30438
|
// src/cli/doctor-vault-broker-durability.ts
|
|
29969
30439
|
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
29970
30440
|
import { existsSync as existsSync50, statSync as statSync22 } from "node:fs";
|
|
29971
|
-
import { homedir as
|
|
29972
|
-
import { join as
|
|
30441
|
+
import { homedir as homedir30 } from "node:os";
|
|
30442
|
+
import { join as join50 } from "node:path";
|
|
29973
30443
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
29974
30444
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
29975
30445
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -30109,19 +30579,19 @@ function defaultBrokerStatusProbe() {
|
|
|
30109
30579
|
}
|
|
30110
30580
|
}
|
|
30111
30581
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
30112
|
-
const home2 =
|
|
30582
|
+
const home2 = homedir30();
|
|
30113
30583
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
30114
30584
|
return [
|
|
30115
30585
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
30116
30586
|
probeAutoUnlockBlob(home2),
|
|
30117
30587
|
probeMachineIdMount(),
|
|
30118
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
30119
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
30120
|
-
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"))
|
|
30121
30591
|
];
|
|
30122
30592
|
}
|
|
30123
30593
|
function probeAutoUnlockBlob(home2) {
|
|
30124
|
-
const blobPath =
|
|
30594
|
+
const blobPath = join50(home2, ".switchroom", "vault-auto-unlock");
|
|
30125
30595
|
if (!existsSync50(blobPath)) {
|
|
30126
30596
|
return {
|
|
30127
30597
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -30232,16 +30702,16 @@ import {
|
|
|
30232
30702
|
readdirSync as readdirSync19,
|
|
30233
30703
|
statSync as statSync23
|
|
30234
30704
|
} from "node:fs";
|
|
30235
|
-
import { dirname as dirname12, join as
|
|
30705
|
+
import { dirname as dirname12, join as join51, resolve as resolve30 } from "node:path";
|
|
30236
30706
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
30237
30707
|
function findInNvm(bin) {
|
|
30238
|
-
const nvmRoot =
|
|
30708
|
+
const nvmRoot = join51(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
30239
30709
|
if (!existsSync51(nvmRoot))
|
|
30240
30710
|
return null;
|
|
30241
30711
|
try {
|
|
30242
30712
|
const versions = readdirSync19(nvmRoot).sort().reverse();
|
|
30243
30713
|
for (const v of versions) {
|
|
30244
|
-
const candidate =
|
|
30714
|
+
const candidate = join51(nvmRoot, v, "bin", bin);
|
|
30245
30715
|
try {
|
|
30246
30716
|
const s = statSync23(candidate);
|
|
30247
30717
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -30406,7 +30876,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
30406
30876
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
30407
30877
|
cacheLocations.push(envBrowsersPath);
|
|
30408
30878
|
}
|
|
30409
|
-
cacheLocations.push(
|
|
30879
|
+
cacheLocations.push(join51(homeDir, ".cache", "ms-playwright"));
|
|
30410
30880
|
for (const cacheDir of cacheLocations) {
|
|
30411
30881
|
if (!existsSync51(cacheDir))
|
|
30412
30882
|
continue;
|
|
@@ -30414,10 +30884,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
30414
30884
|
const entries = readdirSync19(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
30415
30885
|
for (const entry of entries) {
|
|
30416
30886
|
const candidates2 = [
|
|
30417
|
-
|
|
30418
|
-
|
|
30419
|
-
|
|
30420
|
-
|
|
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")
|
|
30421
30891
|
];
|
|
30422
30892
|
for (const path4 of candidates2) {
|
|
30423
30893
|
if (existsSync51(path4))
|
|
@@ -30527,7 +30997,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
|
|
|
30527
30997
|
function checkLegacyState() {
|
|
30528
30998
|
const results = [];
|
|
30529
30999
|
const h = process.env.HOME ?? "/root";
|
|
30530
|
-
const clerkDir =
|
|
31000
|
+
const clerkDir = join51(h, LEGACY_STATE_DIR);
|
|
30531
31001
|
const clerkPresent = existsSync51(clerkDir);
|
|
30532
31002
|
results.push({
|
|
30533
31003
|
name: "legacy ~/.clerk state",
|
|
@@ -30537,7 +31007,7 @@ function checkLegacyState() {
|
|
|
30537
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."
|
|
30538
31008
|
} : {}
|
|
30539
31009
|
});
|
|
30540
|
-
const legacySock =
|
|
31010
|
+
const legacySock = join51(h, ".switchroom", "vault-broker.sock");
|
|
30541
31011
|
let sockStat = null;
|
|
30542
31012
|
try {
|
|
30543
31013
|
sockStat = lstatSync5(legacySock);
|
|
@@ -30833,7 +31303,7 @@ async function checkHindsight(config) {
|
|
|
30833
31303
|
}
|
|
30834
31304
|
function checkPendingRetainsQueue(dir) {
|
|
30835
31305
|
const home2 = process.env.HOME ?? "";
|
|
30836
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
31306
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join51(home2, ".hindsight", "pending-retains");
|
|
30837
31307
|
if (!existsSync51(pendingDir)) {
|
|
30838
31308
|
return {
|
|
30839
31309
|
name: "pending-retains queue",
|
|
@@ -30964,7 +31434,7 @@ async function checkTelegram(config) {
|
|
|
30964
31434
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
30965
31435
|
if (plugin !== "switchroom")
|
|
30966
31436
|
continue;
|
|
30967
|
-
const envPath =
|
|
31437
|
+
const envPath = join51(agentsDir, name, "telegram", ".env");
|
|
30968
31438
|
const read = tryReadHostFile(envPath);
|
|
30969
31439
|
if (read.kind === "eacces") {
|
|
30970
31440
|
results.push({
|
|
@@ -31047,7 +31517,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
31047
31517
|
}
|
|
31048
31518
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
31049
31519
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
31050
|
-
const path4 =
|
|
31520
|
+
const path4 = join51(agentDir, "home", ".switchroom");
|
|
31051
31521
|
let stats;
|
|
31052
31522
|
try {
|
|
31053
31523
|
stats = lstatSync5(path4);
|
|
@@ -31084,7 +31554,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
31084
31554
|
}
|
|
31085
31555
|
function checkRepoHygiene(repoRoot) {
|
|
31086
31556
|
const results = [];
|
|
31087
|
-
const exportDir =
|
|
31557
|
+
const exportDir = join51(repoRoot, "clerk-export");
|
|
31088
31558
|
if (existsSync51(exportDir)) {
|
|
31089
31559
|
results.push({
|
|
31090
31560
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
@@ -31093,7 +31563,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
31093
31563
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
31094
31564
|
});
|
|
31095
31565
|
}
|
|
31096
|
-
const knownTarball =
|
|
31566
|
+
const knownTarball = join51(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
31097
31567
|
if (existsSync51(knownTarball)) {
|
|
31098
31568
|
results.push({
|
|
31099
31569
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
@@ -31111,7 +31581,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
31111
31581
|
results.push({
|
|
31112
31582
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
31113
31583
|
status: "warn",
|
|
31114
|
-
detail: `${
|
|
31584
|
+
detail: `${join51(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
31115
31585
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
31116
31586
|
});
|
|
31117
31587
|
}
|
|
@@ -31134,9 +31604,9 @@ function checkRepoHygiene(repoRoot) {
|
|
|
31134
31604
|
}
|
|
31135
31605
|
function isSwitchroomCheckout(dir) {
|
|
31136
31606
|
try {
|
|
31137
|
-
if (!existsSync51(
|
|
31607
|
+
if (!existsSync51(join51(dir, ".git")))
|
|
31138
31608
|
return false;
|
|
31139
|
-
const pkgPath =
|
|
31609
|
+
const pkgPath = join51(dir, "package.json");
|
|
31140
31610
|
if (!existsSync51(pkgPath))
|
|
31141
31611
|
return false;
|
|
31142
31612
|
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
@@ -31173,7 +31643,7 @@ function checkAgents(config, configPath) {
|
|
|
31173
31643
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
31174
31644
|
});
|
|
31175
31645
|
}
|
|
31176
|
-
results.push(checkStartShStale(name,
|
|
31646
|
+
results.push(checkStartShStale(name, join51(agentDir, "start.sh")));
|
|
31177
31647
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
31178
31648
|
const status = statuses[name];
|
|
31179
31649
|
const active = status?.active ?? "unknown";
|
|
@@ -31250,7 +31720,7 @@ function checkAgents(config, configPath) {
|
|
|
31250
31720
|
}
|
|
31251
31721
|
}
|
|
31252
31722
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
31253
|
-
const mcpJsonPath =
|
|
31723
|
+
const mcpJsonPath = join51(agentDir, ".mcp.json");
|
|
31254
31724
|
if (!existsSync51(mcpJsonPath)) {
|
|
31255
31725
|
results.push({
|
|
31256
31726
|
name: `${name}: .mcp.json`,
|
|
@@ -31552,7 +32022,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
31552
32022
|
};
|
|
31553
32023
|
}
|
|
31554
32024
|
const credDir = dirname12(envPath);
|
|
31555
|
-
const authScript =
|
|
32025
|
+
const authScript = join51(credDir, "claude-auth.py");
|
|
31556
32026
|
if (!existsSync51(authScript)) {
|
|
31557
32027
|
return {
|
|
31558
32028
|
name: "mff: auth flow",
|
|
@@ -31855,6 +32325,32 @@ function registerDoctorCommand(program3) {
|
|
|
31855
32325
|
...await runDriveBrokerReachabilityChecks(config)
|
|
31856
32326
|
]
|
|
31857
32327
|
},
|
|
32328
|
+
{
|
|
32329
|
+
title: "Microsoft 365 (RFC #1873)",
|
|
32330
|
+
results: runMicrosoftChecks(config)
|
|
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
|
+
},
|
|
31858
32354
|
{ title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) }
|
|
31859
32355
|
];
|
|
31860
32356
|
const cwd = process.cwd();
|
|
@@ -31914,6 +32410,8 @@ var init_doctor = __esm(() => {
|
|
|
31914
32410
|
init_doctor_auth_broker();
|
|
31915
32411
|
init_doctor_hostd();
|
|
31916
32412
|
init_doctor_drive();
|
|
32413
|
+
init_doctor_microsoft();
|
|
32414
|
+
init_doctor_notion();
|
|
31917
32415
|
init_doctor_credentials_migration();
|
|
31918
32416
|
init_doctor_secret_access();
|
|
31919
32417
|
init_doctor_inlined_secrets();
|
|
@@ -48021,7 +48519,7 @@ __export(exports_server2, {
|
|
|
48021
48519
|
TOOLS: () => TOOLS2
|
|
48022
48520
|
});
|
|
48023
48521
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
48024
|
-
import { existsSync as
|
|
48522
|
+
import { existsSync as existsSync79, readFileSync as readFileSync65 } from "node:fs";
|
|
48025
48523
|
function selfSocketPath() {
|
|
48026
48524
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
48027
48525
|
}
|
|
@@ -48036,7 +48534,7 @@ async function dispatchTool2(name, args) {
|
|
|
48036
48534
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
48037
48535
|
}
|
|
48038
48536
|
const sockPath = selfSocketPath();
|
|
48039
|
-
if (!
|
|
48537
|
+
if (!existsSync79(sockPath)) {
|
|
48040
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.`);
|
|
48041
48539
|
}
|
|
48042
48540
|
let req;
|
|
@@ -48188,13 +48686,13 @@ function resolveAuditLogPath() {
|
|
|
48188
48686
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
48189
48687
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
48190
48688
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
48191
|
-
if (
|
|
48689
|
+
if (existsSync79(bindMounted))
|
|
48192
48690
|
return bindMounted;
|
|
48193
48691
|
return defaultAuditLogPath2();
|
|
48194
48692
|
}
|
|
48195
48693
|
function getLastUpdateApplyStatus() {
|
|
48196
48694
|
const path8 = resolveAuditLogPath();
|
|
48197
|
-
if (!
|
|
48695
|
+
if (!existsSync79(path8)) {
|
|
48198
48696
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
48199
48697
|
}
|
|
48200
48698
|
let raw;
|
|
@@ -48432,8 +48930,8 @@ var {
|
|
|
48432
48930
|
} = import__.default;
|
|
48433
48931
|
|
|
48434
48932
|
// src/build-info.ts
|
|
48435
|
-
var VERSION = "0.13.
|
|
48436
|
-
var COMMIT_SHA = "
|
|
48933
|
+
var VERSION = "0.13.55";
|
|
48934
|
+
var COMMIT_SHA = "98cf8e68";
|
|
48437
48935
|
|
|
48438
48936
|
// src/cli/agent.ts
|
|
48439
48937
|
init_source();
|
|
@@ -48680,6 +49178,22 @@ function renderProfileClaudeTemplate(profileName, profilesRoot = PROFILES_ROOT)
|
|
|
48680
49178
|
|
|
48681
49179
|
// src/agents/scaffold.ts
|
|
48682
49180
|
init_scaffold_integration();
|
|
49181
|
+
|
|
49182
|
+
// src/config/microsoft-workspace-acl.ts
|
|
49183
|
+
function shouldEmitMs365Mcp(agentName, agentMicrosoftAccount, microsoftAccounts) {
|
|
49184
|
+
if (!agentMicrosoftAccount)
|
|
49185
|
+
return false;
|
|
49186
|
+
const account = agentMicrosoftAccount.trim().toLowerCase();
|
|
49187
|
+
if (account.length === 0)
|
|
49188
|
+
return false;
|
|
49189
|
+
const acctEntry = microsoftAccounts?.[account];
|
|
49190
|
+
if (!acctEntry)
|
|
49191
|
+
return false;
|
|
49192
|
+
const enabledFor = acctEntry.enabled_for ?? [];
|
|
49193
|
+
return enabledFor.includes(agentName);
|
|
49194
|
+
}
|
|
49195
|
+
|
|
49196
|
+
// src/agents/scaffold.ts
|
|
48683
49197
|
init_reconcile_default_skills();
|
|
48684
49198
|
|
|
48685
49199
|
// src/agents/sub-agent-telegram-prompt.ts
|
|
@@ -49799,7 +50313,9 @@ function buildWorkspaceContext(args) {
|
|
|
49799
50313
|
hindsightApiBaseUrl,
|
|
49800
50314
|
hindsightRecallMaxMemories,
|
|
49801
50315
|
hindsightRecallCacheTtlSecs,
|
|
49802
|
-
hindsightRecallMinOverlap
|
|
50316
|
+
hindsightRecallMinOverlap,
|
|
50317
|
+
hindsightTopicAliasesJson,
|
|
50318
|
+
hindsightTopicFilterMode
|
|
49803
50319
|
} = args;
|
|
49804
50320
|
return {
|
|
49805
50321
|
name,
|
|
@@ -49833,6 +50349,8 @@ function buildWorkspaceContext(args) {
|
|
|
49833
50349
|
hindsightRecallMaxMemories,
|
|
49834
50350
|
hindsightRecallCacheTtlSecs,
|
|
49835
50351
|
hindsightRecallMinOverlap,
|
|
50352
|
+
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
50353
|
+
hindsightTopicFilterMode,
|
|
49836
50354
|
switchroomConfigPathQ: switchroomConfigPath ? shellSingleQuote(resolve10(switchroomConfigPath)) : undefined,
|
|
49837
50355
|
hostHomeQ: process.env.HOME ? shellSingleQuote(process.env.HOME) : undefined,
|
|
49838
50356
|
modelQ: shellSingleQuote(agentConfig.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL),
|
|
@@ -49917,6 +50435,46 @@ function resolveGdriveMcpEntry(agentName, agentConfig, switchroomConfig) {
|
|
|
49917
50435
|
};
|
|
49918
50436
|
return entry;
|
|
49919
50437
|
}
|
|
50438
|
+
function resolveMs365McpEntry(agentName, agentConfig, switchroomConfig) {
|
|
50439
|
+
if ((agentConfig.mcp_servers ?? {})["ms-365"] === false)
|
|
50440
|
+
return null;
|
|
50441
|
+
const account = agentConfig.microsoft_workspace?.account;
|
|
50442
|
+
const microsoftAccounts = switchroomConfig?.microsoft_accounts;
|
|
50443
|
+
if (!shouldEmitMs365Mcp(agentName, account, microsoftAccounts))
|
|
50444
|
+
return null;
|
|
50445
|
+
const orgMode = agentConfig.microsoft_workspace?.org_mode ?? switchroomConfig?.microsoft_workspace?.org_mode ?? false;
|
|
50446
|
+
const entry = getMs365McpSettingsEntry(DOCKER_SWITCHROOM_CLI_PATH, orgMode ? { orgMode: true } : {});
|
|
50447
|
+
entry.value.env = {
|
|
50448
|
+
SWITCHROOM_CONFIG: DOCKER_CONFIG_PATH,
|
|
50449
|
+
SWITCHROOM_AGENT_NAME: agentName,
|
|
50450
|
+
SWITCHROOM_CONTAINER: "1",
|
|
50451
|
+
SWITCHROOM_AUTH_BROKER_SOCKET: DOCKER_AUTH_BROKER_SOCKET,
|
|
50452
|
+
HOME: DOCKER_AGENT_HOME
|
|
50453
|
+
};
|
|
50454
|
+
return entry;
|
|
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
|
+
}
|
|
49920
50478
|
function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchroomConfig, userIdOverride, switchroomConfigPath) {
|
|
49921
50479
|
const agentConfig = resolveAgentConfig(switchroomConfig?.defaults, switchroomConfig?.profiles, agentConfigRaw);
|
|
49922
50480
|
const agentDir = resolve10(agentsDir, name);
|
|
@@ -49958,6 +50516,9 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
49958
50516
|
const hindsightRecallMaxMemories = agentConfig.memory?.recall?.max_memories;
|
|
49959
50517
|
const hindsightRecallCacheTtlSecs = agentConfig.memory?.recall?.cache_ttl_secs;
|
|
49960
50518
|
const hindsightRecallMinOverlap = agentConfig.memory?.recall?.min_overlap;
|
|
50519
|
+
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
50520
|
+
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
50521
|
+
const hindsightTopicFilterMode = agentConfig.memory?.recall?.topic_filter_mode;
|
|
49961
50522
|
const context = buildWorkspaceContext({
|
|
49962
50523
|
name,
|
|
49963
50524
|
agentDir,
|
|
@@ -49976,7 +50537,9 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
49976
50537
|
hindsightApiBaseUrl,
|
|
49977
50538
|
hindsightRecallMaxMemories,
|
|
49978
50539
|
hindsightRecallCacheTtlSecs,
|
|
49979
|
-
hindsightRecallMinOverlap
|
|
50540
|
+
hindsightRecallMinOverlap,
|
|
50541
|
+
hindsightTopicAliasesJson,
|
|
50542
|
+
hindsightTopicFilterMode
|
|
49980
50543
|
});
|
|
49981
50544
|
const dirs = [
|
|
49982
50545
|
agentDir,
|
|
@@ -50025,6 +50588,14 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
50025
50588
|
if (gdrive && !settings.mcpServers[gdrive.key]) {
|
|
50026
50589
|
settings.mcpServers[gdrive.key] = gdrive.value;
|
|
50027
50590
|
}
|
|
50591
|
+
const ms365 = resolveMs365McpEntry(name, agentConfig, switchroomConfig);
|
|
50592
|
+
if (ms365 && !settings.mcpServers[ms365.key]) {
|
|
50593
|
+
settings.mcpServers[ms365.key] = ms365.value;
|
|
50594
|
+
}
|
|
50595
|
+
const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
|
|
50596
|
+
if (notion && !settings.mcpServers[notion.key]) {
|
|
50597
|
+
settings.mcpServers[notion.key] = notion.value;
|
|
50598
|
+
}
|
|
50028
50599
|
}
|
|
50029
50600
|
installHindsightPlugin(name, agentDir, switchroomConfig);
|
|
50030
50601
|
const hindsightOn = isHindsightEnabled(switchroomConfig) && switchroomConfig.agents[name]?.memory?.auto_recall !== false;
|
|
@@ -50053,12 +50624,13 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
50053
50624
|
const pluginDir = DOCKER_TELEGRAM_PLUGIN_PATH;
|
|
50054
50625
|
const switchroomCliPath = "/usr/local/bin/switchroom";
|
|
50055
50626
|
const resolvedConfigPath = DOCKER_CONFIG_PATH;
|
|
50627
|
+
const telegramStateDir = `${DOCKER_AGENT_HOME}/.switchroom/agents/${name}/telegram`;
|
|
50056
50628
|
const mcpServers = {
|
|
50057
50629
|
"switchroom-telegram": {
|
|
50058
50630
|
command: "bun",
|
|
50059
50631
|
args: ["run", "--cwd", pluginDir, "--shell=bun", "--silent", "start"],
|
|
50060
50632
|
env: {
|
|
50061
|
-
TELEGRAM_STATE_DIR:
|
|
50633
|
+
TELEGRAM_STATE_DIR: telegramStateDir,
|
|
50062
50634
|
SWITCHROOM_CONFIG: resolvedConfigPath,
|
|
50063
50635
|
SWITCHROOM_CLI_PATH: switchroomCliPath
|
|
50064
50636
|
}
|
|
@@ -50093,6 +50665,14 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
50093
50665
|
if (gdrive) {
|
|
50094
50666
|
mcpServers[gdrive.key] = gdrive.value;
|
|
50095
50667
|
}
|
|
50668
|
+
const ms365 = resolveMs365McpEntry(name, agentConfig, switchroomConfig);
|
|
50669
|
+
if (ms365) {
|
|
50670
|
+
mcpServers[ms365.key] = ms365.value;
|
|
50671
|
+
}
|
|
50672
|
+
const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
|
|
50673
|
+
if (notion) {
|
|
50674
|
+
mcpServers[notion.key] = notion.value;
|
|
50675
|
+
}
|
|
50096
50676
|
}
|
|
50097
50677
|
if (agentConfig.mcp_servers) {
|
|
50098
50678
|
const filtered = filterMcpServers(agentConfig.mcp_servers);
|
|
@@ -50475,6 +51055,26 @@ function buildSettingsHooksBlock(p) {
|
|
|
50475
51055
|
}
|
|
50476
51056
|
]
|
|
50477
51057
|
},
|
|
51058
|
+
{
|
|
51059
|
+
matcher: "^mcp__ms-365__",
|
|
51060
|
+
hooks: [
|
|
51061
|
+
{
|
|
51062
|
+
type: "command",
|
|
51063
|
+
command: wrap("hook:ms-365-write-pretool", `node "${join8(DOCKER_BUNDLED_HOOKS_PATH, "ms-365-write-pretool.mjs")}"`),
|
|
51064
|
+
timeout: 5 * 60 + 30
|
|
51065
|
+
}
|
|
51066
|
+
]
|
|
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
|
+
},
|
|
50478
51078
|
{
|
|
50479
51079
|
matcher: "^(Write|Edit|MultiEdit)$",
|
|
50480
51080
|
hooks: [
|
|
@@ -50715,6 +51315,9 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
50715
51315
|
const hindsightRecallMaxMemories = agentConfig.memory?.recall?.max_memories;
|
|
50716
51316
|
const hindsightRecallCacheTtlSecs = agentConfig.memory?.recall?.cache_ttl_secs;
|
|
50717
51317
|
const hindsightRecallMinOverlap = agentConfig.memory?.recall?.min_overlap;
|
|
51318
|
+
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
51319
|
+
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
51320
|
+
const hindsightTopicFilterMode = agentConfig.memory?.recall?.topic_filter_mode;
|
|
50718
51321
|
const startShPath = join8(agentDir, "start.sh");
|
|
50719
51322
|
if (!options.skipProfileTemplates) {
|
|
50720
51323
|
const basePath = getBaseProfilePath();
|
|
@@ -50735,6 +51338,8 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
50735
51338
|
hindsightRecallMaxMemories,
|
|
50736
51339
|
hindsightRecallCacheTtlSecs,
|
|
50737
51340
|
hindsightRecallMinOverlap,
|
|
51341
|
+
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
51342
|
+
hindsightTopicFilterMode,
|
|
50738
51343
|
hostHomeQ: process.env.HOME ? shellSingleQuote(process.env.HOME) : undefined,
|
|
50739
51344
|
modelQ: shellSingleQuote(agentConfig.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL),
|
|
50740
51345
|
thinkingEffort: agentConfig.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
|
|
@@ -50870,6 +51475,18 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
50870
51475
|
} else {
|
|
50871
51476
|
delete mcpServers["gdrive"];
|
|
50872
51477
|
}
|
|
51478
|
+
const ms365 = resolveMs365McpEntry(name, agentConfig, switchroomConfig);
|
|
51479
|
+
if (ms365) {
|
|
51480
|
+
mcpServers[ms365.key] = ms365.value;
|
|
51481
|
+
} else {
|
|
51482
|
+
delete mcpServers["ms-365"];
|
|
51483
|
+
}
|
|
51484
|
+
const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
|
|
51485
|
+
if (notion) {
|
|
51486
|
+
mcpServers[notion.key] = notion.value;
|
|
51487
|
+
} else {
|
|
51488
|
+
delete mcpServers["notion"];
|
|
51489
|
+
}
|
|
50873
51490
|
}
|
|
50874
51491
|
if (agentConfig.mcp_servers) {
|
|
50875
51492
|
for (const [key, value] of Object.entries(agentConfig.mcp_servers)) {
|
|
@@ -51014,12 +51631,13 @@ ${body}
|
|
|
51014
51631
|
const pluginDir = DOCKER_TELEGRAM_PLUGIN_PATH;
|
|
51015
51632
|
const switchroomCliPath = "/usr/local/bin/switchroom";
|
|
51016
51633
|
const resolvedConfigPath = DOCKER_CONFIG_PATH;
|
|
51634
|
+
const telegramStateDir = `${DOCKER_AGENT_HOME}/.switchroom/agents/${name}/telegram`;
|
|
51017
51635
|
const mcpServers = {
|
|
51018
51636
|
"switchroom-telegram": {
|
|
51019
51637
|
command: "bun",
|
|
51020
51638
|
args: ["run", "--cwd", pluginDir, "--shell=bun", "--silent", "start"],
|
|
51021
51639
|
env: {
|
|
51022
|
-
TELEGRAM_STATE_DIR:
|
|
51640
|
+
TELEGRAM_STATE_DIR: telegramStateDir,
|
|
51023
51641
|
SWITCHROOM_CONFIG: resolvedConfigPath,
|
|
51024
51642
|
SWITCHROOM_CLI_PATH: switchroomCliPath
|
|
51025
51643
|
}
|
|
@@ -51054,6 +51672,14 @@ ${body}
|
|
|
51054
51672
|
if (gdrive) {
|
|
51055
51673
|
mcpServers[gdrive.key] = gdrive.value;
|
|
51056
51674
|
}
|
|
51675
|
+
const ms365 = resolveMs365McpEntry(name, agentConfig, switchroomConfig);
|
|
51676
|
+
if (ms365) {
|
|
51677
|
+
mcpServers[ms365.key] = ms365.value;
|
|
51678
|
+
}
|
|
51679
|
+
const notion = resolveNotionMcpEntry(name, agentConfig, switchroomConfig);
|
|
51680
|
+
if (notion) {
|
|
51681
|
+
mcpServers[notion.key] = notion.value;
|
|
51682
|
+
}
|
|
51057
51683
|
}
|
|
51058
51684
|
if (agentConfig.mcp_servers) {
|
|
51059
51685
|
const filtered = filterMcpServers(agentConfig.mcp_servers);
|
|
@@ -51094,7 +51720,9 @@ ${body}
|
|
|
51094
51720
|
hindsightApiBaseUrl,
|
|
51095
51721
|
hindsightRecallMaxMemories,
|
|
51096
51722
|
hindsightRecallCacheTtlSecs,
|
|
51097
|
-
hindsightRecallMinOverlap
|
|
51723
|
+
hindsightRecallMinOverlap,
|
|
51724
|
+
hindsightTopicAliasesJson,
|
|
51725
|
+
hindsightTopicFilterMode
|
|
51098
51726
|
});
|
|
51099
51727
|
mkdirSync7(reconcileWorkspaceDir, { recursive: true });
|
|
51100
51728
|
migrateLegacyAgentsMdIfPresent(reconcileWorkspaceDir, changes);
|
|
@@ -71545,15 +72173,15 @@ init_loader();
|
|
|
71545
72173
|
init_lifecycle();
|
|
71546
72174
|
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";
|
|
71547
72175
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
71548
|
-
import { join as
|
|
71549
|
-
import { homedir as
|
|
71550
|
-
var DEFAULT_COMPOSE_PATH =
|
|
72176
|
+
import { join as join52, dirname as dirname13, resolve as resolve31 } from "node:path";
|
|
72177
|
+
import { homedir as homedir31 } from "node:os";
|
|
72178
|
+
var DEFAULT_COMPOSE_PATH = join52(homedir31(), ".switchroom", "compose", "docker-compose.yml");
|
|
71551
72179
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
71552
72180
|
let dir = dirname13(scriptPath);
|
|
71553
72181
|
for (let i = 0;i < 12; i++) {
|
|
71554
|
-
if (existsSync52(
|
|
72182
|
+
if (existsSync52(join52(dir, ".git"))) {
|
|
71555
72183
|
try {
|
|
71556
|
-
const pkg = JSON.parse(readFileSync47(
|
|
72184
|
+
const pkg = JSON.parse(readFileSync47(join52(dir, "package.json"), "utf-8"));
|
|
71557
72185
|
if (pkg.name === "switchroom")
|
|
71558
72186
|
return true;
|
|
71559
72187
|
} catch {}
|
|
@@ -71684,7 +72312,7 @@ function planUpdate(opts) {
|
|
|
71684
72312
|
return;
|
|
71685
72313
|
}
|
|
71686
72314
|
const source = resolve31(import.meta.dirname, "../../skills");
|
|
71687
|
-
const dest =
|
|
72315
|
+
const dest = join52(homedir31(), ".switchroom", "skills", "_bundled");
|
|
71688
72316
|
if (!existsSync52(source)) {
|
|
71689
72317
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
71690
72318
|
`);
|
|
@@ -71798,7 +72426,7 @@ function defaultStatusProbe(composePath) {
|
|
|
71798
72426
|
} catch {}
|
|
71799
72427
|
let dir = dirname13(scriptPath);
|
|
71800
72428
|
for (let i = 0;i < 8; i++) {
|
|
71801
|
-
const pkgPath =
|
|
72429
|
+
const pkgPath = join52(dir, "package.json");
|
|
71802
72430
|
if (existsSync52(pkgPath)) {
|
|
71803
72431
|
try {
|
|
71804
72432
|
const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
|
|
@@ -72018,7 +72646,7 @@ init_helpers();
|
|
|
72018
72646
|
init_lifecycle();
|
|
72019
72647
|
import { execSync as execSync4 } from "node:child_process";
|
|
72020
72648
|
import { existsSync as existsSync53, readFileSync as readFileSync48 } from "node:fs";
|
|
72021
|
-
import { dirname as dirname14, join as
|
|
72649
|
+
import { dirname as dirname14, join as join53 } from "node:path";
|
|
72022
72650
|
function getClaudeCodeVersion() {
|
|
72023
72651
|
try {
|
|
72024
72652
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -72068,11 +72696,11 @@ function formatUptime3(timestamp) {
|
|
|
72068
72696
|
function locateSwitchroomInstallDir() {
|
|
72069
72697
|
let dir = import.meta.dirname;
|
|
72070
72698
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
72071
|
-
const pkgPath =
|
|
72699
|
+
const pkgPath = join53(dir, "package.json");
|
|
72072
72700
|
if (existsSync53(pkgPath)) {
|
|
72073
72701
|
try {
|
|
72074
72702
|
const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
|
|
72075
|
-
if (pkg.name === "switchroom" && existsSync53(
|
|
72703
|
+
if (pkg.name === "switchroom" && existsSync53(join53(dir, ".git"))) {
|
|
72076
72704
|
return dir;
|
|
72077
72705
|
}
|
|
72078
72706
|
} catch {}
|
|
@@ -72309,7 +72937,7 @@ import {
|
|
|
72309
72937
|
writeFileSync as writeFileSync26,
|
|
72310
72938
|
writeSync as writeSync7
|
|
72311
72939
|
} from "node:fs";
|
|
72312
|
-
import { join as
|
|
72940
|
+
import { join as join54 } from "node:path";
|
|
72313
72941
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
72314
72942
|
import { execSync as execSync5 } from "node:child_process";
|
|
72315
72943
|
|
|
@@ -72629,7 +73257,7 @@ function redactedMarker(ruleId) {
|
|
|
72629
73257
|
var ISSUES_FILE = "issues.jsonl";
|
|
72630
73258
|
var ISSUES_LOCK = "issues.lock";
|
|
72631
73259
|
function readAll(stateDir) {
|
|
72632
|
-
const path4 =
|
|
73260
|
+
const path4 = join54(stateDir, ISSUES_FILE);
|
|
72633
73261
|
if (!existsSync54(path4))
|
|
72634
73262
|
return [];
|
|
72635
73263
|
let raw;
|
|
@@ -72707,7 +73335,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
72707
73335
|
});
|
|
72708
73336
|
}
|
|
72709
73337
|
function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
72710
|
-
if (!existsSync54(
|
|
73338
|
+
if (!existsSync54(join54(stateDir, ISSUES_FILE)))
|
|
72711
73339
|
return 0;
|
|
72712
73340
|
return withLock(stateDir, () => {
|
|
72713
73341
|
const all = readAll(stateDir);
|
|
@@ -72725,7 +73353,7 @@ function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
72725
73353
|
});
|
|
72726
73354
|
}
|
|
72727
73355
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
72728
|
-
if (!existsSync54(
|
|
73356
|
+
if (!existsSync54(join54(stateDir, ISSUES_FILE)))
|
|
72729
73357
|
return 0;
|
|
72730
73358
|
return withLock(stateDir, () => {
|
|
72731
73359
|
const all = readAll(stateDir);
|
|
@@ -72743,7 +73371,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
72743
73371
|
});
|
|
72744
73372
|
}
|
|
72745
73373
|
function prune(stateDir, opts = {}) {
|
|
72746
|
-
if (!existsSync54(
|
|
73374
|
+
if (!existsSync54(join54(stateDir, ISSUES_FILE)))
|
|
72747
73375
|
return 0;
|
|
72748
73376
|
return withLock(stateDir, () => {
|
|
72749
73377
|
const all = readAll(stateDir);
|
|
@@ -72776,7 +73404,7 @@ function ensureDir(stateDir) {
|
|
|
72776
73404
|
mkdirSync29(stateDir, { recursive: true });
|
|
72777
73405
|
}
|
|
72778
73406
|
function writeAll(stateDir, events) {
|
|
72779
|
-
const path4 =
|
|
73407
|
+
const path4 = join54(stateDir, ISSUES_FILE);
|
|
72780
73408
|
sweepOrphanTmpFiles(stateDir);
|
|
72781
73409
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
72782
73410
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -72798,7 +73426,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
72798
73426
|
for (const entry of entries) {
|
|
72799
73427
|
if (!entry.startsWith(TMP_PREFIX))
|
|
72800
73428
|
continue;
|
|
72801
|
-
const tmpPath =
|
|
73429
|
+
const tmpPath = join54(stateDir, entry);
|
|
72802
73430
|
try {
|
|
72803
73431
|
const stat = statSync25(tmpPath);
|
|
72804
73432
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -72810,7 +73438,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
72810
73438
|
var LOCK_RETRY_MS = 25;
|
|
72811
73439
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
72812
73440
|
function withLock(stateDir, fn) {
|
|
72813
|
-
const lockPath =
|
|
73441
|
+
const lockPath = join54(stateDir, ISSUES_LOCK);
|
|
72814
73442
|
const startedAt = Date.now();
|
|
72815
73443
|
let fd = null;
|
|
72816
73444
|
while (fd === null) {
|
|
@@ -73094,8 +73722,8 @@ function relTime(deltaMs) {
|
|
|
73094
73722
|
// src/cli/deps.ts
|
|
73095
73723
|
init_source();
|
|
73096
73724
|
import { existsSync as existsSync57 } from "node:fs";
|
|
73097
|
-
import { homedir as
|
|
73098
|
-
import { join as
|
|
73725
|
+
import { homedir as homedir34 } from "node:os";
|
|
73726
|
+
import { join as join57, resolve as resolve35 } from "node:path";
|
|
73099
73727
|
|
|
73100
73728
|
// src/deps/python.ts
|
|
73101
73729
|
import { createHash as createHash11 } from "node:crypto";
|
|
@@ -73106,8 +73734,8 @@ import {
|
|
|
73106
73734
|
rmSync as rmSync13,
|
|
73107
73735
|
writeFileSync as writeFileSync27
|
|
73108
73736
|
} from "node:fs";
|
|
73109
|
-
import { dirname as dirname15, join as
|
|
73110
|
-
import { homedir as
|
|
73737
|
+
import { dirname as dirname15, join as join55 } from "node:path";
|
|
73738
|
+
import { homedir as homedir32 } from "node:os";
|
|
73111
73739
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
73112
73740
|
|
|
73113
73741
|
class PythonEnvError extends Error {
|
|
@@ -73119,7 +73747,7 @@ class PythonEnvError extends Error {
|
|
|
73119
73747
|
}
|
|
73120
73748
|
}
|
|
73121
73749
|
function defaultPythonCacheRoot() {
|
|
73122
|
-
return
|
|
73750
|
+
return join55(homedir32(), ".switchroom", "deps", "python");
|
|
73123
73751
|
}
|
|
73124
73752
|
function hashFile(path4) {
|
|
73125
73753
|
return createHash11("sha256").update(readFileSync50(path4)).digest("hex");
|
|
@@ -73131,11 +73759,11 @@ function ensurePythonEnv(opts) {
|
|
|
73131
73759
|
if (!existsSync55(requirementsPath)) {
|
|
73132
73760
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
73133
73761
|
}
|
|
73134
|
-
const venvDir =
|
|
73135
|
-
const stampPath =
|
|
73136
|
-
const binDir =
|
|
73137
|
-
const pythonBin =
|
|
73138
|
-
const pipBin =
|
|
73762
|
+
const venvDir = join55(cacheRoot, skillName);
|
|
73763
|
+
const stampPath = join55(venvDir, ".requirements.sha256");
|
|
73764
|
+
const binDir = join55(venvDir, "bin");
|
|
73765
|
+
const pythonBin = join55(binDir, "python");
|
|
73766
|
+
const pipBin = join55(binDir, "pip");
|
|
73139
73767
|
const targetHash = hashFile(requirementsPath);
|
|
73140
73768
|
if (!force && existsSync55(stampPath) && existsSync55(pythonBin)) {
|
|
73141
73769
|
const existingHash = readFileSync50(stampPath, "utf8").trim();
|
|
@@ -73194,8 +73822,8 @@ import {
|
|
|
73194
73822
|
rmSync as rmSync14,
|
|
73195
73823
|
writeFileSync as writeFileSync28
|
|
73196
73824
|
} from "node:fs";
|
|
73197
|
-
import { dirname as dirname16, join as
|
|
73198
|
-
import { homedir as
|
|
73825
|
+
import { dirname as dirname16, join as join56 } from "node:path";
|
|
73826
|
+
import { homedir as homedir33 } from "node:os";
|
|
73199
73827
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
73200
73828
|
|
|
73201
73829
|
class NodeEnvError extends Error {
|
|
@@ -73218,7 +73846,7 @@ var LOCKFILES_FOR = {
|
|
|
73218
73846
|
npm: ["package-lock.json"]
|
|
73219
73847
|
};
|
|
73220
73848
|
function defaultNodeCacheRoot() {
|
|
73221
|
-
return
|
|
73849
|
+
return join56(homedir33(), ".switchroom", "deps", "node");
|
|
73222
73850
|
}
|
|
73223
73851
|
function hashDepInputs(packageJsonPath) {
|
|
73224
73852
|
const sourceDir = dirname16(packageJsonPath);
|
|
@@ -73227,7 +73855,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
73227
73855
|
`);
|
|
73228
73856
|
hasher.update(readFileSync51(packageJsonPath));
|
|
73229
73857
|
for (const lockName of ALL_LOCKFILES) {
|
|
73230
|
-
const lockPath =
|
|
73858
|
+
const lockPath = join56(sourceDir, lockName);
|
|
73231
73859
|
if (existsSync56(lockPath)) {
|
|
73232
73860
|
hasher.update(`
|
|
73233
73861
|
`);
|
|
@@ -73247,10 +73875,10 @@ function ensureNodeEnv(opts) {
|
|
|
73247
73875
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
73248
73876
|
}
|
|
73249
73877
|
const sourceDir = dirname16(packageJsonPath);
|
|
73250
|
-
const envDir =
|
|
73251
|
-
const stampPath =
|
|
73252
|
-
const nodeModulesDir =
|
|
73253
|
-
const binDir =
|
|
73878
|
+
const envDir = join56(cacheRoot, skillName);
|
|
73879
|
+
const stampPath = join56(envDir, ".package.sha256");
|
|
73880
|
+
const nodeModulesDir = join56(envDir, "node_modules");
|
|
73881
|
+
const binDir = join56(nodeModulesDir, ".bin");
|
|
73254
73882
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
73255
73883
|
if (!force && existsSync56(stampPath) && existsSync56(nodeModulesDir)) {
|
|
73256
73884
|
const existingHash = readFileSync51(stampPath, "utf8").trim();
|
|
@@ -73268,12 +73896,12 @@ function ensureNodeEnv(opts) {
|
|
|
73268
73896
|
rmSync14(envDir, { recursive: true, force: true });
|
|
73269
73897
|
}
|
|
73270
73898
|
mkdirSync31(envDir, { recursive: true });
|
|
73271
|
-
copyFileSync9(packageJsonPath,
|
|
73899
|
+
copyFileSync9(packageJsonPath, join56(envDir, "package.json"));
|
|
73272
73900
|
let copiedLockfile = false;
|
|
73273
73901
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
73274
|
-
const lockPath =
|
|
73902
|
+
const lockPath = join56(sourceDir, lockName);
|
|
73275
73903
|
if (existsSync56(lockPath)) {
|
|
73276
|
-
copyFileSync9(lockPath,
|
|
73904
|
+
copyFileSync9(lockPath, join56(envDir, lockName));
|
|
73277
73905
|
copiedLockfile = true;
|
|
73278
73906
|
}
|
|
73279
73907
|
}
|
|
@@ -73302,7 +73930,7 @@ function ensureNodeEnv(opts) {
|
|
|
73302
73930
|
|
|
73303
73931
|
// src/cli/deps.ts
|
|
73304
73932
|
function builtinSkillsRoot() {
|
|
73305
|
-
return resolve35(
|
|
73933
|
+
return resolve35(homedir34(), ".switchroom/skills/_bundled");
|
|
73306
73934
|
}
|
|
73307
73935
|
function registerDepsCommand(program3) {
|
|
73308
73936
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -73312,13 +73940,13 @@ function registerDepsCommand(program3) {
|
|
|
73312
73940
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
73313
73941
|
process.exit(1);
|
|
73314
73942
|
}
|
|
73315
|
-
const skillDir =
|
|
73943
|
+
const skillDir = join57(skillsRoot, skill);
|
|
73316
73944
|
if (!existsSync57(skillDir)) {
|
|
73317
73945
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
73318
73946
|
process.exit(1);
|
|
73319
73947
|
}
|
|
73320
|
-
const requirementsPath =
|
|
73321
|
-
const packageJsonPath =
|
|
73948
|
+
const requirementsPath = join57(skillDir, "requirements.txt");
|
|
73949
|
+
const packageJsonPath = join57(skillDir, "package.json");
|
|
73322
73950
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync57(requirementsPath));
|
|
73323
73951
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync57(packageJsonPath));
|
|
73324
73952
|
let did = 0;
|
|
@@ -74273,7 +74901,7 @@ init_helpers();
|
|
|
74273
74901
|
init_loader();
|
|
74274
74902
|
init_merge();
|
|
74275
74903
|
import { copyFileSync as copyFileSync10, existsSync as existsSync59, readFileSync as readFileSync52, writeFileSync as writeFileSync29 } from "node:fs";
|
|
74276
|
-
import { join as
|
|
74904
|
+
import { join as join58, resolve as resolve37 } from "node:path";
|
|
74277
74905
|
init_schema();
|
|
74278
74906
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
74279
74907
|
const config = getConfig(program3);
|
|
@@ -74297,7 +74925,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
74297
74925
|
profileName,
|
|
74298
74926
|
profilePath,
|
|
74299
74927
|
workspaceDir,
|
|
74300
|
-
soulPath:
|
|
74928
|
+
soulPath: join58(workspaceDir, "SOUL.md"),
|
|
74301
74929
|
soul: merged.soul
|
|
74302
74930
|
};
|
|
74303
74931
|
}
|
|
@@ -74364,7 +74992,7 @@ function registerSoulCommand(program3) {
|
|
|
74364
74992
|
init_helpers();
|
|
74365
74993
|
init_loader();
|
|
74366
74994
|
import { existsSync as existsSync60, readFileSync as readFileSync53, readdirSync as readdirSync21, statSync as statSync26 } from "node:fs";
|
|
74367
|
-
import { resolve as resolve38, join as
|
|
74995
|
+
import { resolve as resolve38, join as join59 } from "node:path";
|
|
74368
74996
|
import { createHash as createHash13 } from "node:crypto";
|
|
74369
74997
|
init_merge();
|
|
74370
74998
|
init_hindsight();
|
|
@@ -74378,7 +75006,7 @@ function sha256(content) {
|
|
|
74378
75006
|
return createHash13("sha256").update(content).digest("hex").slice(0, 16);
|
|
74379
75007
|
}
|
|
74380
75008
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
74381
|
-
const projectsDir =
|
|
75009
|
+
const projectsDir = join59(claudeConfigDir, "projects");
|
|
74382
75010
|
if (!existsSync60(projectsDir))
|
|
74383
75011
|
return;
|
|
74384
75012
|
try {
|
|
@@ -74387,8 +75015,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
74387
75015
|
for (const entry of entries) {
|
|
74388
75016
|
if (!entry.isDirectory())
|
|
74389
75017
|
continue;
|
|
74390
|
-
const projectPath =
|
|
74391
|
-
const transcriptPath =
|
|
75018
|
+
const projectPath = join59(projectsDir, entry.name);
|
|
75019
|
+
const transcriptPath = join59(projectPath, "transcript.jsonl");
|
|
74392
75020
|
if (!existsSync60(transcriptPath))
|
|
74393
75021
|
continue;
|
|
74394
75022
|
const stat3 = statSync26(transcriptPath);
|
|
@@ -74457,11 +75085,11 @@ function registerDebugCommand(program3) {
|
|
|
74457
75085
|
process.exit(1);
|
|
74458
75086
|
}
|
|
74459
75087
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
74460
|
-
const claudeConfigDir =
|
|
74461
|
-
const claudeMdPath =
|
|
74462
|
-
const soulMdPath =
|
|
74463
|
-
const workspaceSoulMdPath =
|
|
74464
|
-
const handoffPath =
|
|
75088
|
+
const claudeConfigDir = join59(agentDir, ".claude");
|
|
75089
|
+
const claudeMdPath = join59(agentDir, "CLAUDE.md");
|
|
75090
|
+
const soulMdPath = join59(agentDir, "SOUL.md");
|
|
75091
|
+
const workspaceSoulMdPath = join59(workspaceDir, "SOUL.md");
|
|
75092
|
+
const handoffPath = join59(agentDir, ".handoff.md");
|
|
74465
75093
|
const lastN = parseInt(opts.last, 10);
|
|
74466
75094
|
if (isNaN(lastN) || lastN < 1) {
|
|
74467
75095
|
console.error("--last must be a positive integer");
|
|
@@ -74611,8 +75239,8 @@ init_source();
|
|
|
74611
75239
|
// src/worktree/claim.ts
|
|
74612
75240
|
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
74613
75241
|
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync62, unlinkSync as unlinkSync13 } from "node:fs";
|
|
74614
|
-
import { join as
|
|
74615
|
-
import { homedir as
|
|
75242
|
+
import { join as join61, resolve as resolve40 } from "node:path";
|
|
75243
|
+
import { homedir as homedir36 } from "node:os";
|
|
74616
75244
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
74617
75245
|
|
|
74618
75246
|
// src/worktree/registry.ts
|
|
@@ -74625,13 +75253,13 @@ import {
|
|
|
74625
75253
|
existsSync as existsSync61,
|
|
74626
75254
|
renameSync as renameSync12
|
|
74627
75255
|
} from "node:fs";
|
|
74628
|
-
import { join as
|
|
74629
|
-
import { homedir as
|
|
75256
|
+
import { join as join60, resolve as resolve39 } from "node:path";
|
|
75257
|
+
import { homedir as homedir35 } from "node:os";
|
|
74630
75258
|
function registryDir() {
|
|
74631
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
75259
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join60(homedir35(), ".switchroom", "worktrees"));
|
|
74632
75260
|
}
|
|
74633
75261
|
function recordPath(id) {
|
|
74634
|
-
return
|
|
75262
|
+
return join60(registryDir(), `${id}.json`);
|
|
74635
75263
|
}
|
|
74636
75264
|
function ensureDir2() {
|
|
74637
75265
|
mkdirSync32(registryDir(), { recursive: true });
|
|
@@ -74682,7 +75310,7 @@ function acquireRepoLock(repoPath) {
|
|
|
74682
75310
|
const lockDir = registryDir();
|
|
74683
75311
|
mkdirSync33(lockDir, { recursive: true });
|
|
74684
75312
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
74685
|
-
const lockPath =
|
|
75313
|
+
const lockPath = join61(lockDir, `.lock-${lockName}`);
|
|
74686
75314
|
const deadline = Date.now() + 5000;
|
|
74687
75315
|
let fd = null;
|
|
74688
75316
|
while (fd === null) {
|
|
@@ -74709,7 +75337,7 @@ function acquireRepoLock(repoPath) {
|
|
|
74709
75337
|
}
|
|
74710
75338
|
var DEFAULT_CONCURRENCY = 5;
|
|
74711
75339
|
function worktreesBaseDir() {
|
|
74712
|
-
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
75340
|
+
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join61(homedir36(), ".switchroom", "worktree-checkouts"));
|
|
74713
75341
|
}
|
|
74714
75342
|
function shortId() {
|
|
74715
75343
|
return randomBytes13(4).toString("hex");
|
|
@@ -74731,7 +75359,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
74731
75359
|
}
|
|
74732
75360
|
function expandHome(p) {
|
|
74733
75361
|
if (p.startsWith("~/"))
|
|
74734
|
-
return
|
|
75362
|
+
return join61(homedir36(), p.slice(2));
|
|
74735
75363
|
return p;
|
|
74736
75364
|
}
|
|
74737
75365
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -74759,7 +75387,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
74759
75387
|
branch = `task/${taskSuffix}-${id}`;
|
|
74760
75388
|
const baseDir = worktreesBaseDir();
|
|
74761
75389
|
mkdirSync33(baseDir, { recursive: true });
|
|
74762
|
-
worktreePath =
|
|
75390
|
+
worktreePath = join61(baseDir, `${id}-${taskSuffix}`);
|
|
74763
75391
|
const now = new Date().toISOString();
|
|
74764
75392
|
const record2 = {
|
|
74765
75393
|
id,
|
|
@@ -75014,7 +75642,7 @@ import {
|
|
|
75014
75642
|
rmSync as rmSync15,
|
|
75015
75643
|
writeFileSync as writeFileSync31
|
|
75016
75644
|
} from "node:fs";
|
|
75017
|
-
import { join as
|
|
75645
|
+
import { join as join62 } from "node:path";
|
|
75018
75646
|
function encodeCredentialsFilename(email) {
|
|
75019
75647
|
const SAFE = new Set([
|
|
75020
75648
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -75204,16 +75832,16 @@ function resolveCredentialsDir(env2) {
|
|
|
75204
75832
|
if (explicit && explicit.length > 0)
|
|
75205
75833
|
return explicit;
|
|
75206
75834
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
75207
|
-
return
|
|
75835
|
+
return join62(stateBase, "google-workspace-mcp", "credentials");
|
|
75208
75836
|
}
|
|
75209
75837
|
function writeSeedFile(dir, email, seed) {
|
|
75210
75838
|
mkdirSync34(dir, { recursive: true, mode: 448 });
|
|
75211
75839
|
chmodSync9(dir, 448);
|
|
75212
75840
|
for (const name of readdirSync23(dir)) {
|
|
75213
|
-
rmSync15(
|
|
75841
|
+
rmSync15(join62(dir, name), { force: true, recursive: true });
|
|
75214
75842
|
}
|
|
75215
75843
|
const filename = encodeCredentialsFilename(email);
|
|
75216
|
-
const filePath =
|
|
75844
|
+
const filePath = join62(dir, filename);
|
|
75217
75845
|
writeFileSync31(filePath, JSON.stringify(seed), { mode: 384 });
|
|
75218
75846
|
chmodSync9(filePath, 384);
|
|
75219
75847
|
return filePath;
|
|
@@ -75369,9 +75997,599 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
75369
75997
|
});
|
|
75370
75998
|
}
|
|
75371
75999
|
|
|
76000
|
+
// src/cli/m365-mcp-launcher.ts
|
|
76001
|
+
init_scaffold_integration();
|
|
76002
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
76003
|
+
import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync35 } from "node:fs";
|
|
76004
|
+
import { dirname as dirname17, join as join63 } from "node:path";
|
|
76005
|
+
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
76006
|
+
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
76007
|
+
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
76008
|
+
function buildSofteriaArgs(opts = {}) {
|
|
76009
|
+
const pkg = `${MICROSOFT_WORKSPACE_MCP_PACKAGE}@${MICROSOFT_WORKSPACE_MCP_PINNED_VERSION}`;
|
|
76010
|
+
const args = ["-y", pkg];
|
|
76011
|
+
if (opts.orgMode)
|
|
76012
|
+
args.push("--org-mode");
|
|
76013
|
+
return args;
|
|
76014
|
+
}
|
|
76015
|
+
function buildSofteriaEnv(accessToken, parentEnv = process.env) {
|
|
76016
|
+
const env2 = { ...parentEnv };
|
|
76017
|
+
env2[SOFTERIA_TOKEN_ENV] = accessToken;
|
|
76018
|
+
return env2;
|
|
76019
|
+
}
|
|
76020
|
+
function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS) {
|
|
76021
|
+
const remaining = expiresAt - now - leadMs;
|
|
76022
|
+
if (remaining <= 0)
|
|
76023
|
+
return 0;
|
|
76024
|
+
return Math.min(remaining, MAX_REFRESH_INTERVAL_MS);
|
|
76025
|
+
}
|
|
76026
|
+
function writeRefreshHeartbeat(agentName, data) {
|
|
76027
|
+
const path7 = heartbeatPath(agentName);
|
|
76028
|
+
try {
|
|
76029
|
+
mkdirSync35(dirname17(path7), { recursive: true });
|
|
76030
|
+
writeFileSync32(path7, JSON.stringify(data, null, 2), { mode: 420 });
|
|
76031
|
+
} catch {}
|
|
76032
|
+
}
|
|
76033
|
+
function heartbeatPath(agentName) {
|
|
76034
|
+
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
76035
|
+
if (override) {
|
|
76036
|
+
return join63(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
76037
|
+
}
|
|
76038
|
+
return "/state/agent/m365-launcher.heartbeat.json";
|
|
76039
|
+
}
|
|
76040
|
+
function wireStdio(child) {
|
|
76041
|
+
const onParentStdin = (chunk2) => {
|
|
76042
|
+
try {
|
|
76043
|
+
child.stdin?.write(chunk2);
|
|
76044
|
+
} catch {}
|
|
76045
|
+
};
|
|
76046
|
+
process.stdin.on("data", onParentStdin);
|
|
76047
|
+
child.stdout?.pipe(process.stdout, { end: false });
|
|
76048
|
+
child.stderr?.pipe(process.stderr, { end: false });
|
|
76049
|
+
return () => {
|
|
76050
|
+
process.stdin.off("data", onParentStdin);
|
|
76051
|
+
try {
|
|
76052
|
+
child.stdout?.unpipe(process.stdout);
|
|
76053
|
+
child.stderr?.unpipe(process.stderr);
|
|
76054
|
+
} catch {}
|
|
76055
|
+
};
|
|
76056
|
+
}
|
|
76057
|
+
async function killChild(child, gracefulMs = 3000) {
|
|
76058
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
76059
|
+
return;
|
|
76060
|
+
return new Promise((resolve41) => {
|
|
76061
|
+
let killTimer = null;
|
|
76062
|
+
const onExit = () => {
|
|
76063
|
+
if (killTimer) {
|
|
76064
|
+
clearTimeout(killTimer);
|
|
76065
|
+
killTimer = null;
|
|
76066
|
+
}
|
|
76067
|
+
resolve41();
|
|
76068
|
+
};
|
|
76069
|
+
child.once("exit", onExit);
|
|
76070
|
+
try {
|
|
76071
|
+
child.kill("SIGTERM");
|
|
76072
|
+
} catch {
|
|
76073
|
+
resolve41();
|
|
76074
|
+
return;
|
|
76075
|
+
}
|
|
76076
|
+
killTimer = setTimeout(() => {
|
|
76077
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
76078
|
+
try {
|
|
76079
|
+
child.kill("SIGKILL");
|
|
76080
|
+
} catch {}
|
|
76081
|
+
}
|
|
76082
|
+
}, gracefulMs);
|
|
76083
|
+
});
|
|
76084
|
+
}
|
|
76085
|
+
async function runMs365McpLauncher(opts, rt) {
|
|
76086
|
+
const setTimer = rt.setTimer ?? setTimeout;
|
|
76087
|
+
const clearTimer = rt.clearTimer ?? clearTimeout;
|
|
76088
|
+
const now = rt.now ?? Date.now;
|
|
76089
|
+
const log = rt.log ?? ((msg) => process.stderr.write(`${msg}
|
|
76090
|
+
`));
|
|
76091
|
+
const agentName = process.env.SWITCHROOM_AGENT_NAME ?? "unknown";
|
|
76092
|
+
let currentChild = null;
|
|
76093
|
+
let teardownStdio = null;
|
|
76094
|
+
let refreshTimer = null;
|
|
76095
|
+
let restartingForRefresh = false;
|
|
76096
|
+
let resolveLauncher = null;
|
|
76097
|
+
const exitLauncher = (code) => {
|
|
76098
|
+
if (refreshTimer) {
|
|
76099
|
+
clearTimer(refreshTimer);
|
|
76100
|
+
refreshTimer = null;
|
|
76101
|
+
}
|
|
76102
|
+
if (resolveLauncher) {
|
|
76103
|
+
const r = resolveLauncher;
|
|
76104
|
+
resolveLauncher = null;
|
|
76105
|
+
r(code);
|
|
76106
|
+
}
|
|
76107
|
+
};
|
|
76108
|
+
const launchChild = (accessToken) => {
|
|
76109
|
+
const env2 = buildSofteriaEnv(accessToken);
|
|
76110
|
+
const child = rt.spawnSofteria(env2);
|
|
76111
|
+
teardownStdio = wireStdio(child);
|
|
76112
|
+
child.once("exit", (code, signal) => {
|
|
76113
|
+
if (teardownStdio) {
|
|
76114
|
+
teardownStdio();
|
|
76115
|
+
teardownStdio = null;
|
|
76116
|
+
}
|
|
76117
|
+
if (restartingForRefresh) {
|
|
76118
|
+
return;
|
|
76119
|
+
}
|
|
76120
|
+
const resolved = code ?? (signal ? 128 : 0);
|
|
76121
|
+
log(`m365-launcher: softeria exited unexpectedly code=${resolved} signal=${signal}`);
|
|
76122
|
+
exitLauncher(resolved);
|
|
76123
|
+
});
|
|
76124
|
+
return child;
|
|
76125
|
+
};
|
|
76126
|
+
const scheduleRefresh = (expiresAtMs) => {
|
|
76127
|
+
const delayMs = computeRefreshDelayMs(expiresAtMs, now());
|
|
76128
|
+
const nextRefreshMs = now() + delayMs;
|
|
76129
|
+
writeRefreshHeartbeat(agentName, {
|
|
76130
|
+
lastRefreshMs: now(),
|
|
76131
|
+
nextRefreshMs,
|
|
76132
|
+
expiresAtMs
|
|
76133
|
+
});
|
|
76134
|
+
log(`m365-launcher: scheduled refresh in ${Math.round(delayMs / 1000)}s (token expires at ${new Date(expiresAtMs).toISOString()})`);
|
|
76135
|
+
refreshTimer = setTimer(async () => {
|
|
76136
|
+
try {
|
|
76137
|
+
log("m365-launcher: refreshing token + restarting softeria");
|
|
76138
|
+
restartingForRefresh = true;
|
|
76139
|
+
try {
|
|
76140
|
+
process.stdin.pause();
|
|
76141
|
+
} catch {}
|
|
76142
|
+
const fresh = await rt.fetchCreds();
|
|
76143
|
+
if (currentChild) {
|
|
76144
|
+
await killChild(currentChild);
|
|
76145
|
+
}
|
|
76146
|
+
restartingForRefresh = false;
|
|
76147
|
+
currentChild = launchChild(fresh.accessToken);
|
|
76148
|
+
try {
|
|
76149
|
+
process.stdin.resume();
|
|
76150
|
+
} catch {}
|
|
76151
|
+
scheduleRefresh(fresh.expiresAt);
|
|
76152
|
+
} catch (err) {
|
|
76153
|
+
restartingForRefresh = false;
|
|
76154
|
+
try {
|
|
76155
|
+
process.stdin.resume();
|
|
76156
|
+
} catch {}
|
|
76157
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
76158
|
+
log(`m365-launcher: refresh failed \u2014 ${msg}`);
|
|
76159
|
+
refreshTimer = setTimer(() => {
|
|
76160
|
+
scheduleRefresh(now() + 60000);
|
|
76161
|
+
}, 30000);
|
|
76162
|
+
}
|
|
76163
|
+
}, delayMs);
|
|
76164
|
+
};
|
|
76165
|
+
let initial;
|
|
76166
|
+
try {
|
|
76167
|
+
initial = await rt.fetchCreds();
|
|
76168
|
+
} catch (err) {
|
|
76169
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
76170
|
+
log(`m365-launcher: initial broker call failed \u2014 ${msg}`);
|
|
76171
|
+
return 1;
|
|
76172
|
+
}
|
|
76173
|
+
currentChild = launchChild(initial.accessToken);
|
|
76174
|
+
scheduleRefresh(initial.expiresAt);
|
|
76175
|
+
const onSignal = (signal) => {
|
|
76176
|
+
log(`m365-launcher: received ${signal}, shutting down`);
|
|
76177
|
+
if (currentChild) {
|
|
76178
|
+
try {
|
|
76179
|
+
currentChild.kill(signal);
|
|
76180
|
+
} catch {}
|
|
76181
|
+
}
|
|
76182
|
+
const sigCode = signal === "SIGTERM" ? 143 : signal === "SIGINT" ? 130 : 0;
|
|
76183
|
+
exitLauncher(sigCode);
|
|
76184
|
+
};
|
|
76185
|
+
process.on("SIGINT", onSignal);
|
|
76186
|
+
process.on("SIGTERM", onSignal);
|
|
76187
|
+
return new Promise((resolve41) => {
|
|
76188
|
+
resolveLauncher = resolve41;
|
|
76189
|
+
});
|
|
76190
|
+
}
|
|
76191
|
+
function registerM365McpLauncherCommand(program3) {
|
|
76192
|
+
program3.command("m365-mcp-launcher", { hidden: true }).option("--org-mode", "Pass --org-mode to softeria (Teams/SharePoint tools).", false).description("Internal \u2014 Microsoft 365 MCP launcher. Acquires a fresh access token from the auth-broker and execs softeria/ms-365-mcp-server in BYOT mode, restarting it ~55min before token expiry. RFC #1873 PR 3.").action(async (opts) => {
|
|
76193
|
+
const { brokerCall: brokerCall2 } = await Promise.resolve().then(() => (init_broker_call(), exports_broker_call));
|
|
76194
|
+
const code = await runMs365McpLauncher(opts, {
|
|
76195
|
+
fetchCreds: async () => {
|
|
76196
|
+
return await brokerCall2(async (client2) => {
|
|
76197
|
+
const data = await client2.getCredentials("microsoft");
|
|
76198
|
+
const mc = data.credentials ?? {};
|
|
76199
|
+
const accessToken = mc.microsoftOauth?.accessToken;
|
|
76200
|
+
const expiresAt = mc.microsoftOauth?.expiresAt;
|
|
76201
|
+
if (!accessToken || typeof expiresAt !== "number") {
|
|
76202
|
+
throw new Error("auth-broker returned credentials without microsoftOauth.accessToken or .expiresAt");
|
|
76203
|
+
}
|
|
76204
|
+
return { accessToken, expiresAt };
|
|
76205
|
+
});
|
|
76206
|
+
},
|
|
76207
|
+
spawnSofteria: (env2) => {
|
|
76208
|
+
return spawn6("npx", buildSofteriaArgs(opts), {
|
|
76209
|
+
env: env2,
|
|
76210
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
76211
|
+
});
|
|
76212
|
+
}
|
|
76213
|
+
});
|
|
76214
|
+
process.exit(code);
|
|
76215
|
+
});
|
|
76216
|
+
}
|
|
76217
|
+
|
|
76218
|
+
// src/cli/notion-mcp-launcher.ts
|
|
76219
|
+
init_scaffold_integration();
|
|
76220
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
76221
|
+
import { existsSync as existsSync65, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "node:fs";
|
|
76222
|
+
import { dirname as dirname18 } from "node:path";
|
|
76223
|
+
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
76224
|
+
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
76225
|
+
var DEFAULT_VAULT_KEY = "notion/integration-token";
|
|
76226
|
+
function buildNotionMcpArgs(opts) {
|
|
76227
|
+
const version2 = opts.mcpVersion ?? NOTION_MCP_PINNED_VERSION;
|
|
76228
|
+
return ["-y", `${NOTION_MCP_PACKAGE}@${version2}`];
|
|
76229
|
+
}
|
|
76230
|
+
function defaultWriteHeartbeat(path7, contents) {
|
|
76231
|
+
try {
|
|
76232
|
+
const dir = dirname18(path7);
|
|
76233
|
+
if (!existsSync65(dir))
|
|
76234
|
+
mkdirSync36(dir, { recursive: true });
|
|
76235
|
+
writeFileSync33(path7, contents);
|
|
76236
|
+
} catch {}
|
|
76237
|
+
}
|
|
76238
|
+
async function runNotionMcpLauncher(opts, runtime) {
|
|
76239
|
+
const heartbeatPath2 = opts.heartbeatPath ?? DEFAULT_HEARTBEAT_PATH;
|
|
76240
|
+
const writeHeartbeat = runtime.writeHeartbeat ?? defaultWriteHeartbeat;
|
|
76241
|
+
const setTimer = runtime.setTimer ?? ((cb, ms) => setInterval(cb, ms));
|
|
76242
|
+
const clearTimer = runtime.clearTimer ?? clearInterval;
|
|
76243
|
+
const now = runtime.now ?? (() => Date.now());
|
|
76244
|
+
let token;
|
|
76245
|
+
try {
|
|
76246
|
+
token = await runtime.fetchToken();
|
|
76247
|
+
} catch (err) {
|
|
76248
|
+
process.stderr.write(`notion-mcp-launcher: failed to fetch token from vault-broker: ${err.message}
|
|
76249
|
+
`);
|
|
76250
|
+
return 1;
|
|
76251
|
+
}
|
|
76252
|
+
if (!token || typeof token !== "string") {
|
|
76253
|
+
process.stderr.write(`notion-mcp-launcher: vault-broker returned an empty/invalid token.
|
|
76254
|
+
`);
|
|
76255
|
+
return 1;
|
|
76256
|
+
}
|
|
76257
|
+
const childEnv = {
|
|
76258
|
+
...process.env,
|
|
76259
|
+
[NOTION_TOKEN_ENV]: token
|
|
76260
|
+
};
|
|
76261
|
+
const args = buildNotionMcpArgs(opts);
|
|
76262
|
+
const child = runtime.spawnMcp(childEnv, args);
|
|
76263
|
+
if (child.stdin == null || child.stdout == null || child.stderr == null) {
|
|
76264
|
+
process.stderr.write(`notion-mcp-launcher: child stdio handles missing \u2014 aborting.
|
|
76265
|
+
`);
|
|
76266
|
+
return 1;
|
|
76267
|
+
}
|
|
76268
|
+
process.stdin.pipe(child.stdin);
|
|
76269
|
+
child.stdout.pipe(process.stdout);
|
|
76270
|
+
child.stderr.pipe(process.stderr);
|
|
76271
|
+
const writeOne = () => {
|
|
76272
|
+
const payload = {
|
|
76273
|
+
ts: now(),
|
|
76274
|
+
pid: process.pid,
|
|
76275
|
+
agent: process.env.SWITCHROOM_AGENT_NAME,
|
|
76276
|
+
mcp_package: NOTION_MCP_PACKAGE,
|
|
76277
|
+
mcp_version: opts.mcpVersion ?? NOTION_MCP_PINNED_VERSION
|
|
76278
|
+
};
|
|
76279
|
+
writeHeartbeat(heartbeatPath2, JSON.stringify(payload, null, 2));
|
|
76280
|
+
};
|
|
76281
|
+
writeOne();
|
|
76282
|
+
const heartbeatHandle = setTimer(writeOne, HEARTBEAT_WRITE_INTERVAL_MS);
|
|
76283
|
+
const forward = (sig) => {
|
|
76284
|
+
try {
|
|
76285
|
+
child.kill(sig);
|
|
76286
|
+
} catch {}
|
|
76287
|
+
};
|
|
76288
|
+
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
76289
|
+
process.on("SIGINT", () => forward("SIGINT"));
|
|
76290
|
+
const exitCode = await new Promise((resolve41) => {
|
|
76291
|
+
child.once("exit", (code, signal) => {
|
|
76292
|
+
clearTimer(heartbeatHandle);
|
|
76293
|
+
if (typeof code === "number") {
|
|
76294
|
+
resolve41(code);
|
|
76295
|
+
} else if (signal) {
|
|
76296
|
+
const sigCode = { SIGTERM: 15, SIGINT: 2, SIGKILL: 9 }[signal] ?? 1;
|
|
76297
|
+
resolve41(128 + sigCode);
|
|
76298
|
+
} else {
|
|
76299
|
+
resolve41(1);
|
|
76300
|
+
}
|
|
76301
|
+
});
|
|
76302
|
+
child.once("error", (err) => {
|
|
76303
|
+
clearTimer(heartbeatHandle);
|
|
76304
|
+
process.stderr.write(`notion-mcp-launcher: child spawn error: ${err.message}
|
|
76305
|
+
`);
|
|
76306
|
+
resolve41(1);
|
|
76307
|
+
});
|
|
76308
|
+
});
|
|
76309
|
+
return exitCode;
|
|
76310
|
+
}
|
|
76311
|
+
function registerNotionMcpLauncherCommand(program3) {
|
|
76312
|
+
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) => {
|
|
76313
|
+
const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
76314
|
+
const code = await runNotionMcpLauncher(opts, {
|
|
76315
|
+
fetchToken: async () => {
|
|
76316
|
+
const key = opts.vaultKey ?? DEFAULT_VAULT_KEY;
|
|
76317
|
+
const result = await getViaBrokerStructured2(key);
|
|
76318
|
+
if (result.kind === "unreachable") {
|
|
76319
|
+
throw new Error(`vault-broker unreachable: ${result.msg}. Is the broker socket mounted?`);
|
|
76320
|
+
}
|
|
76321
|
+
if (result.kind === "not_found") {
|
|
76322
|
+
throw new Error(`vault key ${key} is missing. Run \`switchroom vault set ${key}\` on the host to populate it.`);
|
|
76323
|
+
}
|
|
76324
|
+
if (result.kind === "denied") {
|
|
76325
|
+
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.`);
|
|
76326
|
+
}
|
|
76327
|
+
if (result.entry.kind !== "string") {
|
|
76328
|
+
throw new Error(`vault key ${key} is not a string entry \u2014 Notion expects an integration token string.`);
|
|
76329
|
+
}
|
|
76330
|
+
return result.entry.value;
|
|
76331
|
+
},
|
|
76332
|
+
spawnMcp: (env2, args) => {
|
|
76333
|
+
return spawn7("npx", args, {
|
|
76334
|
+
env: env2,
|
|
76335
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
76336
|
+
});
|
|
76337
|
+
}
|
|
76338
|
+
});
|
|
76339
|
+
process.exit(code);
|
|
76340
|
+
});
|
|
76341
|
+
}
|
|
76342
|
+
|
|
76343
|
+
// src/cli/notion.ts
|
|
76344
|
+
init_loader();
|
|
76345
|
+
|
|
76346
|
+
// src/notion/api-client.ts
|
|
76347
|
+
var DEFAULT_BASE = "https://api.notion.com";
|
|
76348
|
+
var DEFAULT_VERSION = "2022-06-28";
|
|
76349
|
+
var DEFAULT_TIMEOUT_MS4 = 5000;
|
|
76350
|
+
function createNotionApiClient(opts) {
|
|
76351
|
+
const base = opts.base ?? DEFAULT_BASE;
|
|
76352
|
+
const version2 = opts.notionVersion ?? DEFAULT_VERSION;
|
|
76353
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
76354
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
|
|
76355
|
+
async function request(method, path7, body) {
|
|
76356
|
+
const ac = new AbortController;
|
|
76357
|
+
const timeout = setTimeout(() => ac.abort(), timeoutMs);
|
|
76358
|
+
try {
|
|
76359
|
+
const res = await fetchImpl(`${base}${path7}`, {
|
|
76360
|
+
method,
|
|
76361
|
+
headers: {
|
|
76362
|
+
Authorization: `Bearer ${opts.token}`,
|
|
76363
|
+
"Notion-Version": version2,
|
|
76364
|
+
"Content-Type": "application/json"
|
|
76365
|
+
},
|
|
76366
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
76367
|
+
signal: ac.signal
|
|
76368
|
+
});
|
|
76369
|
+
if (!res.ok) {
|
|
76370
|
+
let detail = "";
|
|
76371
|
+
try {
|
|
76372
|
+
detail = await res.text();
|
|
76373
|
+
} catch {}
|
|
76374
|
+
throw new Error(`Notion API ${method} ${path7} failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail.slice(0, 200)}` : ""}`);
|
|
76375
|
+
}
|
|
76376
|
+
return await res.json();
|
|
76377
|
+
} finally {
|
|
76378
|
+
clearTimeout(timeout);
|
|
76379
|
+
}
|
|
76380
|
+
}
|
|
76381
|
+
return {
|
|
76382
|
+
async getPage(pageId) {
|
|
76383
|
+
const data = await request("GET", `/v1/pages/${encodeURIComponent(pageId)}`);
|
|
76384
|
+
return { parent: data.parent };
|
|
76385
|
+
},
|
|
76386
|
+
async getBlock(blockId) {
|
|
76387
|
+
const data = await request("GET", `/v1/blocks/${encodeURIComponent(blockId)}`);
|
|
76388
|
+
return { parent: data.parent };
|
|
76389
|
+
},
|
|
76390
|
+
async search(query, pageSize = 20) {
|
|
76391
|
+
return await request("POST", "/v1/search", {
|
|
76392
|
+
query,
|
|
76393
|
+
page_size: pageSize
|
|
76394
|
+
});
|
|
76395
|
+
}
|
|
76396
|
+
};
|
|
76397
|
+
}
|
|
76398
|
+
|
|
76399
|
+
// src/cli/notion.ts
|
|
76400
|
+
init_client();
|
|
76401
|
+
function registerNotionCommand(program3) {
|
|
76402
|
+
const cmd = program3.command("notion").description("Notion integration operator helpers (list-dbs, test). See docs/rfcs/notion-integration.md.");
|
|
76403
|
+
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) => {
|
|
76404
|
+
const code = await runListDbs(opts);
|
|
76405
|
+
process.exit(code);
|
|
76406
|
+
});
|
|
76407
|
+
cmd.command("test <agent>").description("Smoke-test Notion access for an agent. Calls /v1/users/me via the 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) => {
|
|
76408
|
+
const code = await runTest(agent, opts);
|
|
76409
|
+
process.exit(code);
|
|
76410
|
+
});
|
|
76411
|
+
}
|
|
76412
|
+
async function runListDbs(opts) {
|
|
76413
|
+
let token;
|
|
76414
|
+
try {
|
|
76415
|
+
token = await fetchToken(opts.vaultKey);
|
|
76416
|
+
} catch (err) {
|
|
76417
|
+
process.stderr.write(`switchroom notion list-dbs: ${err.message}
|
|
76418
|
+
`);
|
|
76419
|
+
return 1;
|
|
76420
|
+
}
|
|
76421
|
+
const client2 = createNotionApiClient({ token });
|
|
76422
|
+
let dbs;
|
|
76423
|
+
try {
|
|
76424
|
+
dbs = await searchAllDatabases(token);
|
|
76425
|
+
} catch (err) {
|
|
76426
|
+
process.stderr.write(`switchroom notion list-dbs: failed to enumerate databases: ${err.message}
|
|
76427
|
+
`);
|
|
76428
|
+
return 1;
|
|
76429
|
+
}
|
|
76430
|
+
if (dbs.length === 0) {
|
|
76431
|
+
process.stderr.write(`switchroom notion list-dbs: the integration has no databases shared with it.
|
|
76432
|
+
` + "Open Notion \u2192 page/database you want switchroom to access \u2192 top-right \u22ef \u2192 Connections \u2192 add `switchroom`, then re-run.\n");
|
|
76433
|
+
return 1;
|
|
76434
|
+
}
|
|
76435
|
+
process.stdout.write("# Paste under `notion_workspace:` in switchroom.yaml:\n");
|
|
76436
|
+
process.stdout.write(`databases:
|
|
76437
|
+
`);
|
|
76438
|
+
const seen = new Set;
|
|
76439
|
+
for (const db of dbs) {
|
|
76440
|
+
const slug = toFriendlyName(db.title || db.id, seen);
|
|
76441
|
+
seen.add(slug);
|
|
76442
|
+
process.stdout.write(` ${slug}: "${db.id}"
|
|
76443
|
+
`);
|
|
76444
|
+
}
|
|
76445
|
+
if (process.stdout.isTTY) {
|
|
76446
|
+
process.stderr.write(`
|
|
76447
|
+
Found ${dbs.length} database(s). Edit the friendly names to taste \u2014 agents reference them by name in \`agents.<name>.notion_workspace.databases\`.
|
|
76448
|
+
`);
|
|
76449
|
+
}
|
|
76450
|
+
return 0;
|
|
76451
|
+
}
|
|
76452
|
+
function toFriendlyName(title, seen) {
|
|
76453
|
+
let base = title.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
76454
|
+
if (!base || !/^[a-z0-9]/.test(base))
|
|
76455
|
+
base = `db-${base.replace(/^[^a-z0-9]+/, "")}`;
|
|
76456
|
+
if (!base)
|
|
76457
|
+
base = "db";
|
|
76458
|
+
let candidate = base;
|
|
76459
|
+
let i = 2;
|
|
76460
|
+
while (seen.has(candidate)) {
|
|
76461
|
+
candidate = `${base}-${i}`;
|
|
76462
|
+
i += 1;
|
|
76463
|
+
}
|
|
76464
|
+
return candidate;
|
|
76465
|
+
}
|
|
76466
|
+
async function searchAllDatabases(token) {
|
|
76467
|
+
const out = [];
|
|
76468
|
+
let cursor = null;
|
|
76469
|
+
for (let i = 0;i < 10; i += 1) {
|
|
76470
|
+
const body = {
|
|
76471
|
+
filter: { value: "database", property: "object" },
|
|
76472
|
+
page_size: 100
|
|
76473
|
+
};
|
|
76474
|
+
if (cursor)
|
|
76475
|
+
body.start_cursor = cursor;
|
|
76476
|
+
const res = await postSearch(token, body);
|
|
76477
|
+
for (const hit of res.results) {
|
|
76478
|
+
const title = extractDbTitle(hit) || hit.id;
|
|
76479
|
+
out.push({ id: hit.id, title });
|
|
76480
|
+
}
|
|
76481
|
+
if (!res.next_cursor)
|
|
76482
|
+
break;
|
|
76483
|
+
cursor = res.next_cursor;
|
|
76484
|
+
}
|
|
76485
|
+
return out;
|
|
76486
|
+
}
|
|
76487
|
+
function extractDbTitle(hit) {
|
|
76488
|
+
if (!hit.title || !Array.isArray(hit.title))
|
|
76489
|
+
return null;
|
|
76490
|
+
return hit.title.map((t) => t.plain_text ?? "").join("").trim() || null;
|
|
76491
|
+
}
|
|
76492
|
+
async function postSearch(token, body) {
|
|
76493
|
+
const ac = new AbortController;
|
|
76494
|
+
const timeout = setTimeout(() => ac.abort(), 1e4);
|
|
76495
|
+
try {
|
|
76496
|
+
const res = await fetch("https://api.notion.com/v1/search", {
|
|
76497
|
+
method: "POST",
|
|
76498
|
+
headers: {
|
|
76499
|
+
Authorization: `Bearer ${token}`,
|
|
76500
|
+
"Notion-Version": "2022-06-28",
|
|
76501
|
+
"Content-Type": "application/json"
|
|
76502
|
+
},
|
|
76503
|
+
body: JSON.stringify(body),
|
|
76504
|
+
signal: ac.signal
|
|
76505
|
+
});
|
|
76506
|
+
if (!res.ok) {
|
|
76507
|
+
throw new Error(`Notion /v1/search ${res.status}: ${await res.text()}`);
|
|
76508
|
+
}
|
|
76509
|
+
return await res.json();
|
|
76510
|
+
} finally {
|
|
76511
|
+
clearTimeout(timeout);
|
|
76512
|
+
}
|
|
76513
|
+
}
|
|
76514
|
+
async function runTest(agent, opts) {
|
|
76515
|
+
let config;
|
|
76516
|
+
try {
|
|
76517
|
+
config = loadConfig();
|
|
76518
|
+
} catch (err) {
|
|
76519
|
+
process.stderr.write(`switchroom notion test: failed to load switchroom.yaml: ${err.message}
|
|
76520
|
+
`);
|
|
76521
|
+
return 1;
|
|
76522
|
+
}
|
|
76523
|
+
const agentCfg = config.agents?.[agent];
|
|
76524
|
+
if (!agentCfg) {
|
|
76525
|
+
process.stderr.write(`switchroom notion test: agent '${agent}' not found in switchroom.yaml.
|
|
76526
|
+
`);
|
|
76527
|
+
return 1;
|
|
76528
|
+
}
|
|
76529
|
+
if (!agentCfg.notion_workspace) {
|
|
76530
|
+
process.stderr.write(`switchroom notion test: agent '${agent}' has no notion_workspace block. Set \`agents.${agent}.notion_workspace: {}\` (or with databases:) in switchroom.yaml.
|
|
76531
|
+
`);
|
|
76532
|
+
return 1;
|
|
76533
|
+
}
|
|
76534
|
+
let token;
|
|
76535
|
+
try {
|
|
76536
|
+
token = await fetchToken(opts.vaultKey);
|
|
76537
|
+
} catch (err) {
|
|
76538
|
+
process.stderr.write(`switchroom notion test: ${err.message}
|
|
76539
|
+
`);
|
|
76540
|
+
return 1;
|
|
76541
|
+
}
|
|
76542
|
+
try {
|
|
76543
|
+
const ac = new AbortController;
|
|
76544
|
+
const timeout = setTimeout(() => ac.abort(), 1e4);
|
|
76545
|
+
let res;
|
|
76546
|
+
try {
|
|
76547
|
+
res = await fetch("https://api.notion.com/v1/users/me", {
|
|
76548
|
+
method: "GET",
|
|
76549
|
+
headers: {
|
|
76550
|
+
Authorization: `Bearer ${token}`,
|
|
76551
|
+
"Notion-Version": "2022-06-28"
|
|
76552
|
+
},
|
|
76553
|
+
signal: ac.signal
|
|
76554
|
+
});
|
|
76555
|
+
} finally {
|
|
76556
|
+
clearTimeout(timeout);
|
|
76557
|
+
}
|
|
76558
|
+
if (!res.ok) {
|
|
76559
|
+
process.stderr.write(`switchroom notion test: Notion API returned ${res.status} \u2014 token may be revoked. Detail: ${await res.text()}
|
|
76560
|
+
`);
|
|
76561
|
+
return 1;
|
|
76562
|
+
}
|
|
76563
|
+
const data = await res.json();
|
|
76564
|
+
process.stdout.write(`OK \u2014 agent='${agent}', integration bot='${data.name ?? "?"}', workspace='${data.bot?.workspace_name ?? "?"}'
|
|
76565
|
+
`);
|
|
76566
|
+
return 0;
|
|
76567
|
+
} catch (err) {
|
|
76568
|
+
process.stderr.write(`switchroom notion test: ${err.message}
|
|
76569
|
+
`);
|
|
76570
|
+
return 1;
|
|
76571
|
+
}
|
|
76572
|
+
}
|
|
76573
|
+
async function fetchToken(vaultKey) {
|
|
76574
|
+
const result = await getViaBrokerStructured(vaultKey);
|
|
76575
|
+
if (result.kind === "unreachable") {
|
|
76576
|
+
throw new Error(`vault-broker unreachable: ${result.msg}`);
|
|
76577
|
+
}
|
|
76578
|
+
if (result.kind === "not_found") {
|
|
76579
|
+
throw new Error(`vault key '${vaultKey}' is missing. Run \`switchroom vault set ${vaultKey}\` first.`);
|
|
76580
|
+
}
|
|
76581
|
+
if (result.kind === "denied") {
|
|
76582
|
+
throw new Error(`vault-broker denied access to '${vaultKey}': ${result.msg}`);
|
|
76583
|
+
}
|
|
76584
|
+
if (result.entry.kind !== "string") {
|
|
76585
|
+
throw new Error(`vault key '${vaultKey}' is not a string entry`);
|
|
76586
|
+
}
|
|
76587
|
+
return result.entry.value;
|
|
76588
|
+
}
|
|
76589
|
+
|
|
75372
76590
|
// src/cli/apply.ts
|
|
75373
76591
|
init_source();
|
|
75374
|
-
import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as
|
|
76592
|
+
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";
|
|
75375
76593
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
75376
76594
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
75377
76595
|
import readline from "node:readline";
|
|
@@ -75734,16 +76952,16 @@ agents:
|
|
|
75734
76952
|
|
|
75735
76953
|
// src/cli/apply.ts
|
|
75736
76954
|
init_resolver();
|
|
75737
|
-
import { dirname as
|
|
75738
|
-
import { homedir as
|
|
76955
|
+
import { dirname as dirname21, join as join67, resolve as resolve42 } from "node:path";
|
|
76956
|
+
import { homedir as homedir38 } from "node:os";
|
|
75739
76957
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
75740
76958
|
init_vault();
|
|
75741
76959
|
init_loader();
|
|
75742
76960
|
init_loader();
|
|
75743
76961
|
|
|
75744
76962
|
// src/cli/update-prompt-hook.ts
|
|
75745
|
-
import { existsSync as
|
|
75746
|
-
import { join as
|
|
76963
|
+
import { existsSync as existsSync66, readFileSync as readFileSync55, writeFileSync as writeFileSync34, chmodSync as chmodSync10, mkdirSync as mkdirSync37 } from "node:fs";
|
|
76964
|
+
import { join as join64 } from "node:path";
|
|
75747
76965
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
75748
76966
|
function updatePromptHookScript() {
|
|
75749
76967
|
return `#!/bin/bash
|
|
@@ -75809,14 +77027,14 @@ exit 0
|
|
|
75809
77027
|
`;
|
|
75810
77028
|
}
|
|
75811
77029
|
function installUpdatePromptHook(agentDir) {
|
|
75812
|
-
const hooksDir =
|
|
75813
|
-
|
|
75814
|
-
const scriptPath =
|
|
77030
|
+
const hooksDir = join64(agentDir, ".claude", "hooks");
|
|
77031
|
+
mkdirSync37(hooksDir, { recursive: true });
|
|
77032
|
+
const scriptPath = join64(hooksDir, HOOK_FILENAME);
|
|
75815
77033
|
const desired = updatePromptHookScript();
|
|
75816
77034
|
let installed = false;
|
|
75817
|
-
const existing =
|
|
77035
|
+
const existing = existsSync66(scriptPath) ? readFileSync55(scriptPath, "utf-8") : "";
|
|
75818
77036
|
if (existing !== desired) {
|
|
75819
|
-
|
|
77037
|
+
writeFileSync34(scriptPath, desired, { mode: 493 });
|
|
75820
77038
|
chmodSync10(scriptPath, 493);
|
|
75821
77039
|
installed = true;
|
|
75822
77040
|
} else {
|
|
@@ -75824,8 +77042,8 @@ function installUpdatePromptHook(agentDir) {
|
|
|
75824
77042
|
chmodSync10(scriptPath, 493);
|
|
75825
77043
|
} catch {}
|
|
75826
77044
|
}
|
|
75827
|
-
const settingsPath =
|
|
75828
|
-
if (!
|
|
77045
|
+
const settingsPath = join64(agentDir, ".claude", "settings.json");
|
|
77046
|
+
if (!existsSync66(settingsPath)) {
|
|
75829
77047
|
return { scriptPath, settingsPath, installed };
|
|
75830
77048
|
}
|
|
75831
77049
|
const raw = readFileSync55(settingsPath, "utf-8");
|
|
@@ -75861,7 +77079,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
75861
77079
|
});
|
|
75862
77080
|
hooks.UserPromptSubmit = list2;
|
|
75863
77081
|
parsed.hooks = hooks;
|
|
75864
|
-
|
|
77082
|
+
writeFileSync34(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
75865
77083
|
`, { mode: 384 });
|
|
75866
77084
|
installed = true;
|
|
75867
77085
|
}
|
|
@@ -75944,13 +77162,13 @@ function detectInstallType() {
|
|
|
75944
77162
|
// src/cli/operator-uid.ts
|
|
75945
77163
|
import {
|
|
75946
77164
|
chownSync as chownSync3,
|
|
75947
|
-
existsSync as
|
|
77165
|
+
existsSync as existsSync68,
|
|
75948
77166
|
lstatSync as lstatSync7,
|
|
75949
77167
|
readdirSync as readdirSync24,
|
|
75950
77168
|
realpathSync as realpathSync6,
|
|
75951
77169
|
statSync as statSync27
|
|
75952
77170
|
} from "node:fs";
|
|
75953
|
-
import { join as
|
|
77171
|
+
import { join as join66 } from "node:path";
|
|
75954
77172
|
function resolveOperatorUid() {
|
|
75955
77173
|
const sudoUid = process.env.SUDO_UID;
|
|
75956
77174
|
if (sudoUid !== undefined) {
|
|
@@ -75966,19 +77184,19 @@ function resolveOperatorUid() {
|
|
|
75966
77184
|
return;
|
|
75967
77185
|
}
|
|
75968
77186
|
function operatorOwnedPaths(home2) {
|
|
75969
|
-
const root =
|
|
77187
|
+
const root = join66(home2, ".switchroom");
|
|
75970
77188
|
return [
|
|
75971
|
-
|
|
75972
|
-
|
|
75973
|
-
|
|
75974
|
-
|
|
75975
|
-
|
|
75976
|
-
|
|
77189
|
+
join66(root, "vault"),
|
|
77190
|
+
join66(root, "vault-auto-unlock"),
|
|
77191
|
+
join66(root, "vault-audit.log"),
|
|
77192
|
+
join66(root, "host-control-audit.log"),
|
|
77193
|
+
join66(root, "accounts"),
|
|
77194
|
+
join66(root, "compose")
|
|
75977
77195
|
];
|
|
75978
77196
|
}
|
|
75979
77197
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
75980
77198
|
const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
|
|
75981
|
-
const exists = deps.exists ?? ((p) =>
|
|
77199
|
+
const exists = deps.exists ?? ((p) => existsSync68(p));
|
|
75982
77200
|
const isSymlink = deps.isSymlink ?? ((p) => {
|
|
75983
77201
|
try {
|
|
75984
77202
|
return lstatSync7(p).isSymbolicLink();
|
|
@@ -76022,7 +77240,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
76022
77240
|
} catch {}
|
|
76023
77241
|
if (isDir(target)) {
|
|
76024
77242
|
for (const entry of readdir2(target)) {
|
|
76025
|
-
visit(
|
|
77243
|
+
visit(join66(target, entry));
|
|
76026
77244
|
}
|
|
76027
77245
|
}
|
|
76028
77246
|
};
|
|
@@ -76036,17 +77254,17 @@ var EMBEDDED_EXAMPLES = {
|
|
|
76036
77254
|
switchroom: switchroom_default,
|
|
76037
77255
|
minimal: minimal_default
|
|
76038
77256
|
};
|
|
76039
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
77257
|
+
var DEFAULT_COMPOSE_PATH2 = join67(homedir38(), ".switchroom", "compose", "docker-compose.yml");
|
|
76040
77258
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
76041
77259
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
76042
77260
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
76043
77261
|
if (isCustomPath && ctx.customVaultPath) {
|
|
76044
|
-
return
|
|
77262
|
+
return dirname21(ctx.customVaultPath);
|
|
76045
77263
|
}
|
|
76046
|
-
return
|
|
77264
|
+
return join67(homeDir, ".switchroom", "vault");
|
|
76047
77265
|
}
|
|
76048
77266
|
function inspectVaultBindMountDir(vaultDir) {
|
|
76049
|
-
if (!
|
|
77267
|
+
if (!existsSync69(vaultDir))
|
|
76050
77268
|
return { kind: "missing" };
|
|
76051
77269
|
const entries = readdirSync25(vaultDir);
|
|
76052
77270
|
const unknown = [];
|
|
@@ -76072,63 +77290,63 @@ function hasVaultRefs(value) {
|
|
|
76072
77290
|
return false;
|
|
76073
77291
|
}
|
|
76074
77292
|
async function ensureHostMountSources(config) {
|
|
76075
|
-
const home2 =
|
|
77293
|
+
const home2 = homedir38();
|
|
76076
77294
|
const dirs = [
|
|
76077
|
-
|
|
76078
|
-
|
|
76079
|
-
|
|
76080
|
-
|
|
76081
|
-
|
|
77295
|
+
join67(home2, ".switchroom", "approvals"),
|
|
77296
|
+
join67(home2, ".switchroom", "scheduler"),
|
|
77297
|
+
join67(home2, ".switchroom", "logs"),
|
|
77298
|
+
join67(home2, ".switchroom", "compose"),
|
|
77299
|
+
join67(home2, ".switchroom", "broker-operator")
|
|
76082
77300
|
];
|
|
76083
77301
|
for (const name of Object.keys(config.agents)) {
|
|
76084
|
-
dirs.push(
|
|
76085
|
-
dirs.push(
|
|
76086
|
-
dirs.push(
|
|
76087
|
-
dirs.push(
|
|
76088
|
-
if (
|
|
76089
|
-
dirs.push(
|
|
77302
|
+
dirs.push(join67(home2, ".switchroom", "agents", name));
|
|
77303
|
+
dirs.push(join67(home2, ".switchroom", "logs", name));
|
|
77304
|
+
dirs.push(join67(home2, ".claude", "projects", name));
|
|
77305
|
+
dirs.push(join67(home2, ".switchroom", "audit", name));
|
|
77306
|
+
if (existsSync69(join67(home2, ".switchroom-config"))) {
|
|
77307
|
+
dirs.push(join67(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
76090
77308
|
}
|
|
76091
77309
|
}
|
|
76092
77310
|
for (const dir of dirs) {
|
|
76093
77311
|
await mkdir(dir, { recursive: true });
|
|
76094
77312
|
}
|
|
76095
|
-
const autoUnlockPath =
|
|
76096
|
-
if (!
|
|
76097
|
-
|
|
77313
|
+
const autoUnlockPath = join67(home2, ".switchroom", "vault-auto-unlock");
|
|
77314
|
+
if (!existsSync69(autoUnlockPath)) {
|
|
77315
|
+
writeFileSync35(autoUnlockPath, "", { mode: 384 });
|
|
76098
77316
|
}
|
|
76099
|
-
const auditLogPath =
|
|
76100
|
-
if (!
|
|
76101
|
-
|
|
77317
|
+
const auditLogPath = join67(home2, ".switchroom", "vault-audit.log");
|
|
77318
|
+
if (!existsSync69(auditLogPath)) {
|
|
77319
|
+
writeFileSync35(auditLogPath, "", { mode: 420 });
|
|
76102
77320
|
}
|
|
76103
|
-
const grantsDbPath =
|
|
76104
|
-
if (!
|
|
76105
|
-
|
|
77321
|
+
const grantsDbPath = join67(home2, ".switchroom", "vault-grants.db");
|
|
77322
|
+
if (!existsSync69(grantsDbPath)) {
|
|
77323
|
+
writeFileSync35(grantsDbPath, "", { mode: 384 });
|
|
76106
77324
|
}
|
|
76107
|
-
const hostdAuditLogPath =
|
|
76108
|
-
if (!
|
|
76109
|
-
|
|
77325
|
+
const hostdAuditLogPath = join67(home2, ".switchroom", "host-control-audit.log");
|
|
77326
|
+
if (!existsSync69(hostdAuditLogPath)) {
|
|
77327
|
+
writeFileSync35(hostdAuditLogPath, "", { mode: 420 });
|
|
76110
77328
|
}
|
|
76111
77329
|
for (const name of Object.keys(config.agents)) {
|
|
76112
|
-
const tokenPath =
|
|
76113
|
-
if (!
|
|
76114
|
-
|
|
77330
|
+
const tokenPath = join67(home2, ".switchroom", "agents", name, ".vault-token");
|
|
77331
|
+
if (!existsSync69(tokenPath)) {
|
|
77332
|
+
writeFileSync35(tokenPath, "", { mode: 384 });
|
|
76115
77333
|
}
|
|
76116
77334
|
try {
|
|
76117
77335
|
const uid = allocateAgentUid(name);
|
|
76118
77336
|
chownSync4(tokenPath, uid, uid);
|
|
76119
77337
|
} catch {}
|
|
76120
77338
|
}
|
|
76121
|
-
const fleetDir =
|
|
77339
|
+
const fleetDir = join67(home2, ".switchroom", "fleet");
|
|
76122
77340
|
await mkdir(fleetDir, { recursive: true });
|
|
76123
|
-
const invariantsPath =
|
|
77341
|
+
const invariantsPath = join67(fleetDir, "switchroom-invariants.md");
|
|
76124
77342
|
const invariantsCanonical = renderFleetInvariants();
|
|
76125
|
-
const invariantsCurrent =
|
|
77343
|
+
const invariantsCurrent = existsSync69(invariantsPath) ? readFileSync56(invariantsPath, "utf-8") : null;
|
|
76126
77344
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
76127
|
-
|
|
77345
|
+
writeFileSync35(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
76128
77346
|
}
|
|
76129
|
-
const fleetClaudePath =
|
|
76130
|
-
if (!
|
|
76131
|
-
|
|
77347
|
+
const fleetClaudePath = join67(fleetDir, "CLAUDE.md");
|
|
77348
|
+
if (!existsSync69(fleetClaudePath)) {
|
|
77349
|
+
writeFileSync35(fleetClaudePath, [
|
|
76132
77350
|
"# Switchroom fleet defaults",
|
|
76133
77351
|
"",
|
|
76134
77352
|
"Operator-owned fleet brain. Every agent reads this via",
|
|
@@ -76159,7 +77377,7 @@ ${out.trim()}`;
|
|
|
76159
77377
|
}
|
|
76160
77378
|
function runApplyPreflight(config, opts = {}) {
|
|
76161
77379
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
76162
|
-
if (hasVaultRefs(config) && !
|
|
77380
|
+
if (hasVaultRefs(config) && !existsSync69(vaultPath)) {
|
|
76163
77381
|
throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
|
|
76164
77382
|
}
|
|
76165
77383
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
@@ -76170,7 +77388,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
76170
77388
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
76171
77389
|
}
|
|
76172
77390
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
76173
|
-
if (!
|
|
77391
|
+
if (!existsSync69(vaultPath))
|
|
76174
77392
|
return;
|
|
76175
77393
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
76176
77394
|
if (!passphrase)
|
|
@@ -76209,18 +77427,18 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
76209
77427
|
`));
|
|
76210
77428
|
}
|
|
76211
77429
|
}
|
|
76212
|
-
function writeInstallTypeCache(homeDir =
|
|
77430
|
+
function writeInstallTypeCache(homeDir = homedir38()) {
|
|
76213
77431
|
const ctx = detectInstallType();
|
|
76214
|
-
const dir =
|
|
76215
|
-
const out =
|
|
77432
|
+
const dir = join67(homeDir, ".switchroom");
|
|
77433
|
+
const out = join67(dir, "install-type.json");
|
|
76216
77434
|
const tmp = `${out}.tmp`;
|
|
76217
|
-
|
|
77435
|
+
mkdirSync38(dir, { recursive: true });
|
|
76218
77436
|
const payload = {
|
|
76219
77437
|
install_type: ctx.install_type,
|
|
76220
77438
|
detected_at: new Date().toISOString(),
|
|
76221
77439
|
source_paths: ctx.source_paths
|
|
76222
77440
|
};
|
|
76223
|
-
|
|
77441
|
+
writeFileSync35(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
76224
77442
|
renameSync13(tmp, out);
|
|
76225
77443
|
return out;
|
|
76226
77444
|
}
|
|
@@ -76261,14 +77479,14 @@ Applying switchroom config...
|
|
|
76261
77479
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
76262
77480
|
`));
|
|
76263
77481
|
try {
|
|
76264
|
-
installUpdatePromptHook(
|
|
77482
|
+
installUpdatePromptHook(join67(agentsDir, name));
|
|
76265
77483
|
} catch (hookErr) {
|
|
76266
77484
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
76267
77485
|
`));
|
|
76268
77486
|
}
|
|
76269
77487
|
try {
|
|
76270
77488
|
const uid = allocateAgentUid(name);
|
|
76271
|
-
alignAgentUid(name,
|
|
77489
|
+
alignAgentUid(name, join67(agentsDir, name), uid, {
|
|
76272
77490
|
confirm: !options.nonInteractive,
|
|
76273
77491
|
writeOut
|
|
76274
77492
|
});
|
|
@@ -76305,7 +77523,7 @@ Applying switchroom config...
|
|
|
76305
77523
|
for (const name of agentNames) {
|
|
76306
77524
|
try {
|
|
76307
77525
|
const uid = allocateAgentUid(name);
|
|
76308
|
-
alignAgentUid(name,
|
|
77526
|
+
alignAgentUid(name, join67(agentsDir, name), uid, {
|
|
76309
77527
|
confirm: !options.nonInteractive,
|
|
76310
77528
|
writeOut
|
|
76311
77529
|
});
|
|
@@ -76319,7 +77537,7 @@ Applying switchroom config...
|
|
|
76319
77537
|
}
|
|
76320
77538
|
const vaultPathConfigured = config.vault?.path;
|
|
76321
77539
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
76322
|
-
const migrationResult = migrateVaultLayout(
|
|
77540
|
+
const migrationResult = migrateVaultLayout(homedir38(), {
|
|
76323
77541
|
customVaultPath
|
|
76324
77542
|
});
|
|
76325
77543
|
switch (migrationResult.kind) {
|
|
@@ -76345,7 +77563,7 @@ Applying switchroom config...
|
|
|
76345
77563
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
76346
77564
|
process.exit(4);
|
|
76347
77565
|
}
|
|
76348
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
77566
|
+
const postMigrationInspect = inspectVaultLayout(homedir38());
|
|
76349
77567
|
const acceptable = [
|
|
76350
77568
|
"no-vault",
|
|
76351
77569
|
"already-migrated",
|
|
@@ -76360,7 +77578,7 @@ Applying switchroom config...
|
|
|
76360
77578
|
`));
|
|
76361
77579
|
process.exit(5);
|
|
76362
77580
|
}
|
|
76363
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
77581
|
+
const vaultDir = resolveVaultBindMountDir(homedir38(), {
|
|
76364
77582
|
migrationKind: migrationResult.kind,
|
|
76365
77583
|
customVaultPath
|
|
76366
77584
|
});
|
|
@@ -76390,11 +77608,11 @@ Applying switchroom config...
|
|
|
76390
77608
|
imageTag: composeImageTag,
|
|
76391
77609
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
76392
77610
|
buildContext: options.buildContext,
|
|
76393
|
-
homeDir:
|
|
77611
|
+
homeDir: homedir38(),
|
|
76394
77612
|
switchroomConfigPath,
|
|
76395
77613
|
operatorUid
|
|
76396
77614
|
});
|
|
76397
|
-
await mkdir(
|
|
77615
|
+
await mkdir(dirname21(composePath), { recursive: true });
|
|
76398
77616
|
await writeFile(composePath, composeContent, {
|
|
76399
77617
|
encoding: "utf8",
|
|
76400
77618
|
mode: 384
|
|
@@ -76410,7 +77628,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
76410
77628
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
76411
77629
|
`));
|
|
76412
77630
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
76413
|
-
const restored = restoreOperatorOwnership(
|
|
77631
|
+
const restored = restoreOperatorOwnership(homedir38(), operatorUid);
|
|
76414
77632
|
if (restored.length > 0) {
|
|
76415
77633
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
76416
77634
|
`));
|
|
@@ -76458,18 +77676,18 @@ function copyExampleConfig2(name) {
|
|
|
76458
77676
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
76459
77677
|
}
|
|
76460
77678
|
const dest = resolve42(process.cwd(), "switchroom.yaml");
|
|
76461
|
-
if (
|
|
77679
|
+
if (existsSync69(dest)) {
|
|
76462
77680
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
76463
77681
|
return;
|
|
76464
77682
|
}
|
|
76465
77683
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
76466
77684
|
if (embedded !== undefined) {
|
|
76467
|
-
|
|
77685
|
+
writeFileSync35(dest, embedded, { encoding: "utf8" });
|
|
76468
77686
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
76469
77687
|
return;
|
|
76470
77688
|
}
|
|
76471
77689
|
const exampleFile = resolve42(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
76472
|
-
if (!
|
|
77690
|
+
if (!existsSync69(exampleFile)) {
|
|
76473
77691
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
76474
77692
|
}
|
|
76475
77693
|
copyFileSync11(exampleFile, dest);
|
|
@@ -76480,8 +77698,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
76480
77698
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
76481
77699
|
const unwritable = [];
|
|
76482
77700
|
for (const name of targets) {
|
|
76483
|
-
const startSh =
|
|
76484
|
-
if (!
|
|
77701
|
+
const startSh = join67(agentsDir, name, "start.sh");
|
|
77702
|
+
if (!existsSync69(startSh))
|
|
76485
77703
|
continue;
|
|
76486
77704
|
try {
|
|
76487
77705
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -76659,9 +77877,9 @@ function runRedactStdin() {
|
|
|
76659
77877
|
}
|
|
76660
77878
|
|
|
76661
77879
|
// src/cli/status-ask.ts
|
|
76662
|
-
import { readFileSync as readFileSync57, existsSync as
|
|
76663
|
-
import { join as
|
|
76664
|
-
import { homedir as
|
|
77880
|
+
import { readFileSync as readFileSync57, existsSync as existsSync70, readdirSync as readdirSync26 } from "node:fs";
|
|
77881
|
+
import { join as join68 } from "node:path";
|
|
77882
|
+
import { homedir as homedir39 } from "node:os";
|
|
76665
77883
|
|
|
76666
77884
|
// src/status-ask/report.ts
|
|
76667
77885
|
function parseJsonl(content) {
|
|
@@ -76982,7 +78200,7 @@ function runReport(opts) {
|
|
|
76982
78200
|
function resolveSources(explicitPath) {
|
|
76983
78201
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
76984
78202
|
const trimmed = explicitPath.trim();
|
|
76985
|
-
if (!
|
|
78203
|
+
if (!existsSync70(trimmed)) {
|
|
76986
78204
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
76987
78205
|
`);
|
|
76988
78206
|
process.exit(1);
|
|
@@ -76996,9 +78214,9 @@ function resolveSources(explicitPath) {
|
|
|
76996
78214
|
const config = loadConfig();
|
|
76997
78215
|
agentsDir = resolveAgentsDir(config);
|
|
76998
78216
|
} catch {
|
|
76999
|
-
agentsDir =
|
|
78217
|
+
agentsDir = join68(homedir39(), ".switchroom", "agents");
|
|
77000
78218
|
}
|
|
77001
|
-
if (!
|
|
78219
|
+
if (!existsSync70(agentsDir))
|
|
77002
78220
|
return [];
|
|
77003
78221
|
const sources = [];
|
|
77004
78222
|
let entries;
|
|
@@ -77008,8 +78226,8 @@ function resolveSources(explicitPath) {
|
|
|
77008
78226
|
return [];
|
|
77009
78227
|
}
|
|
77010
78228
|
for (const name of entries) {
|
|
77011
|
-
const path8 =
|
|
77012
|
-
if (
|
|
78229
|
+
const path8 = join68(agentsDir, name, "runtime-metrics.jsonl");
|
|
78230
|
+
if (existsSync70(path8)) {
|
|
77013
78231
|
sources.push({ path: path8, agent: name });
|
|
77014
78232
|
}
|
|
77015
78233
|
}
|
|
@@ -77030,17 +78248,17 @@ function inferAgentFromPath(p) {
|
|
|
77030
78248
|
|
|
77031
78249
|
// src/cli/agent-config.ts
|
|
77032
78250
|
init_helpers();
|
|
77033
|
-
import { join as
|
|
77034
|
-
import { homedir as
|
|
78251
|
+
import { join as join69 } from "node:path";
|
|
78252
|
+
import { homedir as homedir40 } from "node:os";
|
|
77035
78253
|
import {
|
|
77036
|
-
existsSync as
|
|
77037
|
-
mkdirSync as
|
|
78254
|
+
existsSync as existsSync71,
|
|
78255
|
+
mkdirSync as mkdirSync39,
|
|
77038
78256
|
appendFileSync as appendFileSync4,
|
|
77039
78257
|
readFileSync as readFileSync58
|
|
77040
78258
|
} from "node:fs";
|
|
77041
|
-
var AUDIT_ROOT =
|
|
78259
|
+
var AUDIT_ROOT = join69(homedir40(), ".switchroom", "audit");
|
|
77042
78260
|
function auditPathFor(agent) {
|
|
77043
|
-
return
|
|
78261
|
+
return join69(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
77044
78262
|
}
|
|
77045
78263
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
77046
78264
|
const row = {
|
|
@@ -77054,8 +78272,8 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
|
77054
78272
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
77055
78273
|
const dir = path8.slice(0, path8.lastIndexOf("/"));
|
|
77056
78274
|
try {
|
|
77057
|
-
if (!
|
|
77058
|
-
|
|
78275
|
+
if (!existsSync71(dir)) {
|
|
78276
|
+
mkdirSync39(dir, { recursive: true });
|
|
77059
78277
|
}
|
|
77060
78278
|
appendFileSync4(path8, JSON.stringify(row) + `
|
|
77061
78279
|
`, { flag: "a" });
|
|
@@ -77066,7 +78284,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
|
|
|
77066
78284
|
return true;
|
|
77067
78285
|
const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
|
|
77068
78286
|
try {
|
|
77069
|
-
if (
|
|
78287
|
+
if (existsSync71(probe2))
|
|
77070
78288
|
return true;
|
|
77071
78289
|
} catch {}
|
|
77072
78290
|
return false;
|
|
@@ -77127,7 +78345,7 @@ function getAgentSlice(config, agent) {
|
|
|
77127
78345
|
}
|
|
77128
78346
|
function readAuditTail(agent, limit, opts = {}) {
|
|
77129
78347
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
77130
|
-
if (!
|
|
78348
|
+
if (!existsSync71(path8))
|
|
77131
78349
|
return [];
|
|
77132
78350
|
let raw;
|
|
77133
78351
|
try {
|
|
@@ -77287,9 +78505,9 @@ var import_yaml15 = __toESM(require_dist(), 1);
|
|
|
77287
78505
|
init_paths();
|
|
77288
78506
|
import {
|
|
77289
78507
|
closeSync as closeSync13,
|
|
77290
|
-
existsSync as
|
|
78508
|
+
existsSync as existsSync72,
|
|
77291
78509
|
fsyncSync as fsyncSync6,
|
|
77292
|
-
mkdirSync as
|
|
78510
|
+
mkdirSync as mkdirSync40,
|
|
77293
78511
|
openSync as openSync13,
|
|
77294
78512
|
readdirSync as readdirSync27,
|
|
77295
78513
|
readFileSync as readFileSync59,
|
|
@@ -77298,34 +78516,34 @@ import {
|
|
|
77298
78516
|
unlinkSync as unlinkSync14,
|
|
77299
78517
|
writeSync as writeSync8
|
|
77300
78518
|
} from "node:fs";
|
|
77301
|
-
import { join as
|
|
78519
|
+
import { join as join70, resolve as resolve43 } from "node:path";
|
|
77302
78520
|
var STAGING_SUBDIR = ".staging";
|
|
77303
78521
|
function overlayPathsFor(agent, opts = {}) {
|
|
77304
78522
|
const base = opts.root ? resolve43(opts.root, agent) : resolve43(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
77305
|
-
const scheduleDir =
|
|
77306
|
-
const scheduleStagingDir =
|
|
77307
|
-
const skillsDir =
|
|
77308
|
-
const skillsStagingDir =
|
|
78523
|
+
const scheduleDir = join70(base, "schedule.d");
|
|
78524
|
+
const scheduleStagingDir = join70(scheduleDir, STAGING_SUBDIR);
|
|
78525
|
+
const skillsDir = join70(base, "skills.d");
|
|
78526
|
+
const skillsStagingDir = join70(skillsDir, STAGING_SUBDIR);
|
|
77309
78527
|
return {
|
|
77310
78528
|
agentRoot: base,
|
|
77311
78529
|
scheduleDir,
|
|
77312
78530
|
scheduleStagingDir,
|
|
77313
78531
|
skillsDir,
|
|
77314
78532
|
skillsStagingDir,
|
|
77315
|
-
lockPath:
|
|
78533
|
+
lockPath: join70(base, ".lock"),
|
|
77316
78534
|
stagingDir: scheduleStagingDir
|
|
77317
78535
|
};
|
|
77318
78536
|
}
|
|
77319
78537
|
function ensureDirs(paths) {
|
|
77320
|
-
|
|
77321
|
-
|
|
78538
|
+
mkdirSync40(paths.scheduleDir, { recursive: true });
|
|
78539
|
+
mkdirSync40(paths.scheduleStagingDir, { recursive: true });
|
|
77322
78540
|
}
|
|
77323
78541
|
function ensureSkillsDirs(paths) {
|
|
77324
|
-
|
|
77325
|
-
|
|
78542
|
+
mkdirSync40(paths.skillsDir, { recursive: true });
|
|
78543
|
+
mkdirSync40(paths.skillsStagingDir, { recursive: true });
|
|
77326
78544
|
}
|
|
77327
78545
|
function withAgentLock(paths, fn) {
|
|
77328
|
-
|
|
78546
|
+
mkdirSync40(paths.agentRoot, { recursive: true });
|
|
77329
78547
|
const start = Date.now();
|
|
77330
78548
|
const TIMEOUT_MS = 5000;
|
|
77331
78549
|
let fd = null;
|
|
@@ -77366,8 +78584,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
77366
78584
|
const paths = overlayPathsFor(agent, opts);
|
|
77367
78585
|
return withAgentLock(paths, () => {
|
|
77368
78586
|
ensureDirs(paths);
|
|
77369
|
-
const stagingPath =
|
|
77370
|
-
const finalPath =
|
|
78587
|
+
const stagingPath = join70(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
78588
|
+
const finalPath = join70(paths.scheduleDir, `${slug}.yaml`);
|
|
77371
78589
|
const fd = openSync13(stagingPath, "w", 384);
|
|
77372
78590
|
try {
|
|
77373
78591
|
writeSync8(fd, yamlText);
|
|
@@ -77383,8 +78601,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
77383
78601
|
const paths = overlayPathsFor(agent, opts);
|
|
77384
78602
|
return withAgentLock(paths, () => {
|
|
77385
78603
|
ensureSkillsDirs(paths);
|
|
77386
|
-
const stagingPath =
|
|
77387
|
-
const finalPath =
|
|
78604
|
+
const stagingPath = join70(paths.skillsStagingDir, `${slug}.yaml`);
|
|
78605
|
+
const finalPath = join70(paths.skillsDir, `${slug}.yaml`);
|
|
77388
78606
|
const fd = openSync13(stagingPath, "w", 384);
|
|
77389
78607
|
try {
|
|
77390
78608
|
writeSync8(fd, yamlText);
|
|
@@ -77399,8 +78617,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
77399
78617
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
77400
78618
|
const paths = overlayPathsFor(agent, opts);
|
|
77401
78619
|
return withAgentLock(paths, () => {
|
|
77402
|
-
const finalPath =
|
|
77403
|
-
if (!
|
|
78620
|
+
const finalPath = join70(paths.skillsDir, `${slug}.yaml`);
|
|
78621
|
+
if (!existsSync72(finalPath))
|
|
77404
78622
|
return false;
|
|
77405
78623
|
unlinkSync14(finalPath);
|
|
77406
78624
|
return true;
|
|
@@ -77408,13 +78626,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
77408
78626
|
}
|
|
77409
78627
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
77410
78628
|
const paths = overlayPathsFor(agent, opts);
|
|
77411
|
-
if (!
|
|
78629
|
+
if (!existsSync72(paths.skillsDir))
|
|
77412
78630
|
return [];
|
|
77413
78631
|
const out = [];
|
|
77414
78632
|
for (const name of readdirSync27(paths.skillsDir)) {
|
|
77415
78633
|
if (!/\.ya?ml$/i.test(name))
|
|
77416
78634
|
continue;
|
|
77417
|
-
const full =
|
|
78635
|
+
const full = join70(paths.skillsDir, name);
|
|
77418
78636
|
try {
|
|
77419
78637
|
const raw = readFileSync59(full, "utf-8");
|
|
77420
78638
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -77426,8 +78644,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
77426
78644
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
77427
78645
|
const paths = overlayPathsFor(agent, opts);
|
|
77428
78646
|
return withAgentLock(paths, () => {
|
|
77429
|
-
const finalPath =
|
|
77430
|
-
if (!
|
|
78647
|
+
const finalPath = join70(paths.scheduleDir, `${slug}.yaml`);
|
|
78648
|
+
if (!existsSync72(finalPath))
|
|
77431
78649
|
return false;
|
|
77432
78650
|
unlinkSync14(finalPath);
|
|
77433
78651
|
return true;
|
|
@@ -77435,13 +78653,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
77435
78653
|
}
|
|
77436
78654
|
function listOverlayEntries(agent, opts = {}) {
|
|
77437
78655
|
const paths = overlayPathsFor(agent, opts);
|
|
77438
|
-
if (!
|
|
78656
|
+
if (!existsSync72(paths.scheduleDir))
|
|
77439
78657
|
return [];
|
|
77440
78658
|
const out = [];
|
|
77441
78659
|
for (const name of readdirSync27(paths.scheduleDir)) {
|
|
77442
78660
|
if (!/\.ya?ml$/i.test(name))
|
|
77443
78661
|
continue;
|
|
77444
|
-
const full =
|
|
78662
|
+
const full = join70(paths.scheduleDir, name);
|
|
77445
78663
|
try {
|
|
77446
78664
|
const raw = readFileSync59(full, "utf-8");
|
|
77447
78665
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -77586,27 +78804,27 @@ function reconcileAgentCronOnly(agent) {
|
|
|
77586
78804
|
// src/cli/agent-config-pending.ts
|
|
77587
78805
|
import {
|
|
77588
78806
|
closeSync as closeSync14,
|
|
77589
|
-
existsSync as
|
|
78807
|
+
existsSync as existsSync73,
|
|
77590
78808
|
fsyncSync as fsyncSync7,
|
|
77591
|
-
mkdirSync as
|
|
78809
|
+
mkdirSync as mkdirSync41,
|
|
77592
78810
|
openSync as openSync14,
|
|
77593
78811
|
readdirSync as readdirSync28,
|
|
77594
78812
|
readFileSync as readFileSync60,
|
|
77595
78813
|
renameSync as renameSync15,
|
|
77596
78814
|
unlinkSync as unlinkSync15,
|
|
77597
|
-
writeFileSync as
|
|
78815
|
+
writeFileSync as writeFileSync36,
|
|
77598
78816
|
writeSync as writeSync9
|
|
77599
78817
|
} from "node:fs";
|
|
77600
|
-
import { join as
|
|
78818
|
+
import { join as join71 } from "node:path";
|
|
77601
78819
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
77602
78820
|
var STAGE_ID_PREFIX = "cap_";
|
|
77603
78821
|
function pendingDir(agent, opts = {}) {
|
|
77604
78822
|
const paths = overlayPathsFor(agent, opts);
|
|
77605
|
-
return
|
|
78823
|
+
return join71(paths.scheduleDir, ".pending");
|
|
77606
78824
|
}
|
|
77607
78825
|
function ensurePendingDir(agent, opts = {}) {
|
|
77608
78826
|
const dir = pendingDir(agent, opts);
|
|
77609
|
-
|
|
78827
|
+
mkdirSync41(dir, { recursive: true });
|
|
77610
78828
|
return dir;
|
|
77611
78829
|
}
|
|
77612
78830
|
function newStageId() {
|
|
@@ -77615,8 +78833,8 @@ function newStageId() {
|
|
|
77615
78833
|
function stagePendingScheduleEntry(opts) {
|
|
77616
78834
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
77617
78835
|
const stageId = opts.stageId ?? newStageId();
|
|
77618
|
-
const yamlPath =
|
|
77619
|
-
const metaPath =
|
|
78836
|
+
const yamlPath = join71(dir, `${stageId}.yaml`);
|
|
78837
|
+
const metaPath = join71(dir, `${stageId}.meta.json`);
|
|
77620
78838
|
const meta = {
|
|
77621
78839
|
v: 1,
|
|
77622
78840
|
stage_id: stageId,
|
|
@@ -77637,22 +78855,22 @@ function stagePendingScheduleEntry(opts) {
|
|
|
77637
78855
|
}
|
|
77638
78856
|
renameSync15(yamlTmp, yamlPath);
|
|
77639
78857
|
}
|
|
77640
|
-
|
|
78858
|
+
writeFileSync36(metaPath, JSON.stringify(meta, null, 2) + `
|
|
77641
78859
|
`, { mode: 384 });
|
|
77642
78860
|
return { stageId, yamlPath, metaPath };
|
|
77643
78861
|
}
|
|
77644
78862
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
77645
78863
|
const dir = pendingDir(agent, opts);
|
|
77646
|
-
if (!
|
|
78864
|
+
if (!existsSync73(dir))
|
|
77647
78865
|
return [];
|
|
77648
78866
|
const out = [];
|
|
77649
78867
|
for (const name of readdirSync28(dir).sort()) {
|
|
77650
78868
|
if (!name.endsWith(".meta.json"))
|
|
77651
78869
|
continue;
|
|
77652
78870
|
const stageId = name.slice(0, -".meta.json".length);
|
|
77653
|
-
const metaPath =
|
|
77654
|
-
const yamlPath =
|
|
77655
|
-
if (!
|
|
78871
|
+
const metaPath = join71(dir, name);
|
|
78872
|
+
const yamlPath = join71(dir, `${stageId}.yaml`);
|
|
78873
|
+
if (!existsSync73(yamlPath))
|
|
77656
78874
|
continue;
|
|
77657
78875
|
try {
|
|
77658
78876
|
const meta = JSON.parse(readFileSync60(metaPath, "utf-8"));
|
|
@@ -77670,8 +78888,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
77670
78888
|
return { committed: false, reason: "not_found" };
|
|
77671
78889
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
77672
78890
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
77673
|
-
const finalPath =
|
|
77674
|
-
if (
|
|
78891
|
+
const finalPath = join71(paths.scheduleDir, `${slug}.yaml`);
|
|
78892
|
+
if (existsSync73(finalPath)) {
|
|
77675
78893
|
return { committed: false, reason: "slug_collision" };
|
|
77676
78894
|
}
|
|
77677
78895
|
renameSync15(match.yamlPath, finalPath);
|
|
@@ -77694,7 +78912,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
77694
78912
|
|
|
77695
78913
|
// src/cli/agent-config-write.ts
|
|
77696
78914
|
init_protocol3();
|
|
77697
|
-
import { existsSync as
|
|
78915
|
+
import { existsSync as existsSync74, readFileSync as readFileSync61 } from "node:fs";
|
|
77698
78916
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
77699
78917
|
var MIN_CRON_INTERVAL_MIN = 5;
|
|
77700
78918
|
function extractCronSmallestGapMin(expr) {
|
|
@@ -77980,7 +79198,7 @@ function scheduleRemove(opts) {
|
|
|
77980
79198
|
}
|
|
77981
79199
|
let priorContent = null;
|
|
77982
79200
|
try {
|
|
77983
|
-
if (
|
|
79201
|
+
if (existsSync74(match.path))
|
|
77984
79202
|
priorContent = readFileSync61(match.path, "utf-8");
|
|
77985
79203
|
} catch {}
|
|
77986
79204
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -78173,10 +79391,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
78173
79391
|
|
|
78174
79392
|
// src/cli/agent-config-skill-write.ts
|
|
78175
79393
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
78176
|
-
import { existsSync as
|
|
79394
|
+
import { existsSync as existsSync75 } from "node:fs";
|
|
78177
79395
|
init_reconcile_default_skills();
|
|
78178
79396
|
var import_yaml17 = __toESM(require_dist(), 1);
|
|
78179
|
-
import { join as
|
|
79397
|
+
import { join as join72 } from "node:path";
|
|
78180
79398
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
78181
79399
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
78182
79400
|
function exitCodeFor2(code) {
|
|
@@ -78251,8 +79469,8 @@ function skillInstall(opts) {
|
|
|
78251
79469
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
78252
79470
|
}
|
|
78253
79471
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
78254
|
-
const skillPath =
|
|
78255
|
-
if (!
|
|
79472
|
+
const skillPath = join72(poolDir, skillName);
|
|
79473
|
+
if (!existsSync75(skillPath)) {
|
|
78256
79474
|
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.`);
|
|
78257
79475
|
}
|
|
78258
79476
|
const yamlText = import_yaml16.stringify({ skills: [skillName] });
|
|
@@ -78416,9 +79634,9 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
78416
79634
|
// src/cli/skill.ts
|
|
78417
79635
|
import {
|
|
78418
79636
|
closeSync as closeSync15,
|
|
78419
|
-
existsSync as
|
|
79637
|
+
existsSync as existsSync76,
|
|
78420
79638
|
lstatSync as lstatSync8,
|
|
78421
|
-
mkdirSync as
|
|
79639
|
+
mkdirSync as mkdirSync42,
|
|
78422
79640
|
mkdtempSync as mkdtempSync5,
|
|
78423
79641
|
openSync as openSync15,
|
|
78424
79642
|
readFileSync as readFileSync62,
|
|
@@ -78427,10 +79645,10 @@ import {
|
|
|
78427
79645
|
renameSync as renameSync16,
|
|
78428
79646
|
rmSync as rmSync16,
|
|
78429
79647
|
statSync as statSync29,
|
|
78430
|
-
writeFileSync as
|
|
79648
|
+
writeFileSync as writeFileSync37
|
|
78431
79649
|
} from "node:fs";
|
|
78432
|
-
import { tmpdir as tmpdir4, homedir as
|
|
78433
|
-
import { dirname as
|
|
79650
|
+
import { tmpdir as tmpdir4, homedir as homedir41 } from "node:os";
|
|
79651
|
+
import { dirname as dirname22, join as join73, relative as relative2, resolve as resolve44 } from "node:path";
|
|
78434
79652
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
78435
79653
|
|
|
78436
79654
|
// src/cli/skill-common.ts
|
|
@@ -78624,10 +79842,10 @@ function scanForClaudeP2(content) {
|
|
|
78624
79842
|
function resolveSkillsPoolDir2(override) {
|
|
78625
79843
|
const raw = override ?? "~/.switchroom/skills";
|
|
78626
79844
|
if (raw.startsWith("~/")) {
|
|
78627
|
-
return
|
|
79845
|
+
return join73(homedir41(), raw.slice(2));
|
|
78628
79846
|
}
|
|
78629
79847
|
if (raw === "~")
|
|
78630
|
-
return
|
|
79848
|
+
return homedir41();
|
|
78631
79849
|
return resolve44(raw);
|
|
78632
79850
|
}
|
|
78633
79851
|
function readStdinSync() {
|
|
@@ -78663,7 +79881,7 @@ function loadFromDir(dir) {
|
|
|
78663
79881
|
const walk2 = (sub) => {
|
|
78664
79882
|
const entries = readdirSync29(sub, { withFileTypes: true });
|
|
78665
79883
|
for (const ent of entries) {
|
|
78666
|
-
const full =
|
|
79884
|
+
const full = join73(sub, ent.name);
|
|
78667
79885
|
const rel = relative2(abs, full);
|
|
78668
79886
|
if (ent.isSymbolicLink()) {
|
|
78669
79887
|
fail2(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -78698,7 +79916,7 @@ function loadFromTarball(tarPath) {
|
|
|
78698
79916
|
fail2(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
78699
79917
|
}
|
|
78700
79918
|
}
|
|
78701
|
-
const staging = mkdtempSync5(
|
|
79919
|
+
const staging = mkdtempSync5(join73(tmpdir4(), "skill-apply-extract-"));
|
|
78702
79920
|
try {
|
|
78703
79921
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
78704
79922
|
const r = spawnSync10("tar", [
|
|
@@ -78784,10 +80002,10 @@ function validatePayload(name, files) {
|
|
|
78784
80002
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
78785
80003
|
}
|
|
78786
80004
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
78787
|
-
const tmp = mkdtempSync5(
|
|
78788
|
-
const tmpPy =
|
|
80005
|
+
const tmp = mkdtempSync5(join73(tmpdir4(), "skill-apply-py-"));
|
|
80006
|
+
const tmpPy = join73(tmp, "check.py");
|
|
78789
80007
|
try {
|
|
78790
|
-
|
|
80008
|
+
writeFileSync37(tmpPy, content);
|
|
78791
80009
|
const r = spawnSync10("python3", ["-m", "py_compile", tmpPy], {
|
|
78792
80010
|
encoding: "utf-8"
|
|
78793
80011
|
});
|
|
@@ -78805,10 +80023,10 @@ function validatePayload(name, files) {
|
|
|
78805
80023
|
function diffSummary(currentDir, files) {
|
|
78806
80024
|
const lines = [];
|
|
78807
80025
|
const currentFiles = {};
|
|
78808
|
-
if (
|
|
80026
|
+
if (existsSync76(currentDir)) {
|
|
78809
80027
|
const walk2 = (sub) => {
|
|
78810
80028
|
for (const ent of readdirSync29(sub, { withFileTypes: true })) {
|
|
78811
|
-
const full =
|
|
80029
|
+
const full = join73(sub, ent.name);
|
|
78812
80030
|
const rel = relative2(currentDir, full);
|
|
78813
80031
|
if (ent.isDirectory()) {
|
|
78814
80032
|
walk2(full);
|
|
@@ -78841,10 +80059,10 @@ function diffSummary(currentDir, files) {
|
|
|
78841
80059
|
`);
|
|
78842
80060
|
}
|
|
78843
80061
|
function writePayload(poolDir, name, files) {
|
|
78844
|
-
if (!
|
|
78845
|
-
|
|
80062
|
+
if (!existsSync76(poolDir)) {
|
|
80063
|
+
mkdirSync42(poolDir, { recursive: true, mode: 493 });
|
|
78846
80064
|
}
|
|
78847
|
-
const target =
|
|
80065
|
+
const target = join73(poolDir, name);
|
|
78848
80066
|
let targetIsSymlink = false;
|
|
78849
80067
|
try {
|
|
78850
80068
|
const st = lstatSync8(target);
|
|
@@ -78855,15 +80073,15 @@ function writePayload(poolDir, name, files) {
|
|
|
78855
80073
|
if (targetIsSymlink) {
|
|
78856
80074
|
fail2(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
78857
80075
|
}
|
|
78858
|
-
const staging = mkdtempSync5(
|
|
80076
|
+
const staging = mkdtempSync5(join73(poolDir, `.skill-apply-stage-${name}-`));
|
|
78859
80077
|
let oldRename = null;
|
|
78860
80078
|
try {
|
|
78861
80079
|
for (const [path8, content] of Object.entries(files)) {
|
|
78862
|
-
const full =
|
|
78863
|
-
|
|
80080
|
+
const full = join73(staging, path8);
|
|
80081
|
+
mkdirSync42(dirname22(full), { recursive: true, mode: 493 });
|
|
78864
80082
|
const fd = openSync15(full, "wx");
|
|
78865
80083
|
try {
|
|
78866
|
-
|
|
80084
|
+
writeFileSync37(fd, content);
|
|
78867
80085
|
} finally {
|
|
78868
80086
|
closeSync15(fd);
|
|
78869
80087
|
}
|
|
@@ -78890,9 +80108,9 @@ function writePayload(poolDir, name, files) {
|
|
|
78890
80108
|
try {
|
|
78891
80109
|
rmSync16(staging, { recursive: true, force: true });
|
|
78892
80110
|
} catch {}
|
|
78893
|
-
if (oldRename &&
|
|
80111
|
+
if (oldRename && existsSync76(oldRename)) {
|
|
78894
80112
|
try {
|
|
78895
|
-
if (
|
|
80113
|
+
if (existsSync76(target)) {
|
|
78896
80114
|
rmSync16(target, { recursive: true, force: true });
|
|
78897
80115
|
}
|
|
78898
80116
|
renameSync16(oldRename, target);
|
|
@@ -78913,7 +80131,7 @@ function registerSkillCommand(program3) {
|
|
|
78913
80131
|
files = loadFromStdin();
|
|
78914
80132
|
} else {
|
|
78915
80133
|
const fromPath = resolve44(opts.from);
|
|
78916
|
-
if (!
|
|
80134
|
+
if (!existsSync76(fromPath)) {
|
|
78917
80135
|
fail2(`--from path does not exist: ${opts.from}`);
|
|
78918
80136
|
}
|
|
78919
80137
|
const st = statSync29(fromPath);
|
|
@@ -78937,7 +80155,7 @@ function registerSkillCommand(program3) {
|
|
|
78937
80155
|
}
|
|
78938
80156
|
const config = loadConfig();
|
|
78939
80157
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
78940
|
-
const currentDir =
|
|
80158
|
+
const currentDir = join73(poolDir, name);
|
|
78941
80159
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
78942
80160
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
78943
80161
|
console.log(diffSummary(currentDir, files));
|
|
@@ -78968,9 +80186,9 @@ function sumBytes(files) {
|
|
|
78968
80186
|
// src/cli/skill-personal.ts
|
|
78969
80187
|
import {
|
|
78970
80188
|
closeSync as closeSync16,
|
|
78971
|
-
existsSync as
|
|
80189
|
+
existsSync as existsSync77,
|
|
78972
80190
|
lstatSync as lstatSync9,
|
|
78973
|
-
mkdirSync as
|
|
80191
|
+
mkdirSync as mkdirSync43,
|
|
78974
80192
|
mkdtempSync as mkdtempSync6,
|
|
78975
80193
|
openSync as openSync16,
|
|
78976
80194
|
readFileSync as readFileSync63,
|
|
@@ -78979,10 +80197,10 @@ import {
|
|
|
78979
80197
|
rmSync as rmSync17,
|
|
78980
80198
|
statSync as statSync30,
|
|
78981
80199
|
utimesSync,
|
|
78982
|
-
writeFileSync as
|
|
80200
|
+
writeFileSync as writeFileSync38
|
|
78983
80201
|
} from "node:fs";
|
|
78984
|
-
import { dirname as
|
|
78985
|
-
import { homedir as
|
|
80202
|
+
import { dirname as dirname23, join as join74, relative as relative3, resolve as resolve45 } from "node:path";
|
|
80203
|
+
import { homedir as homedir42, tmpdir as tmpdir5 } from "node:os";
|
|
78986
80204
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
78987
80205
|
init_helpers();
|
|
78988
80206
|
init_source();
|
|
@@ -78992,15 +80210,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
78992
80210
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
78993
80211
|
function resolveConfigSkillsDir(agent) {
|
|
78994
80212
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
78995
|
-
const candidate = override ? resolve45(override) :
|
|
78996
|
-
if (!
|
|
80213
|
+
const candidate = override ? resolve45(override) : join74(homedir42(), ".switchroom-config");
|
|
80214
|
+
if (!existsSync77(candidate))
|
|
78997
80215
|
return null;
|
|
78998
|
-
return
|
|
80216
|
+
return join74(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
78999
80217
|
}
|
|
79000
80218
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
79001
80219
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
79002
80220
|
try {
|
|
79003
|
-
if (!
|
|
80221
|
+
if (!existsSync77(configSkillsRoot))
|
|
79004
80222
|
return;
|
|
79005
80223
|
const now = Date.now();
|
|
79006
80224
|
for (const ent of readdirSync30(configSkillsRoot)) {
|
|
@@ -79013,7 +80231,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
79013
80231
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
79014
80232
|
continue;
|
|
79015
80233
|
try {
|
|
79016
|
-
rmSync17(
|
|
80234
|
+
rmSync17(join74(configSkillsRoot, ent), { recursive: true, force: true });
|
|
79017
80235
|
} catch {}
|
|
79018
80236
|
}
|
|
79019
80237
|
} catch {}
|
|
@@ -79022,7 +80240,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
79022
80240
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
79023
80241
|
if (!configSkillsRoot)
|
|
79024
80242
|
return;
|
|
79025
|
-
const dest =
|
|
80243
|
+
const dest = join74(configSkillsRoot, name);
|
|
79026
80244
|
try {
|
|
79027
80245
|
if (liveSkillDir !== null) {
|
|
79028
80246
|
try {
|
|
@@ -79036,32 +80254,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
79036
80254
|
}
|
|
79037
80255
|
if (liveSkillDir === null) {
|
|
79038
80256
|
sweepMirrorPriors(configSkillsRoot);
|
|
79039
|
-
if (
|
|
79040
|
-
const trash =
|
|
80257
|
+
if (existsSync77(dest)) {
|
|
80258
|
+
const trash = join74(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
79041
80259
|
renameSync17(dest, trash);
|
|
79042
80260
|
}
|
|
79043
80261
|
return;
|
|
79044
80262
|
}
|
|
79045
|
-
|
|
80263
|
+
mkdirSync43(configSkillsRoot, { recursive: true, mode: 493 });
|
|
79046
80264
|
sweepMirrorPriors(configSkillsRoot);
|
|
79047
|
-
const staging = mkdtempSync6(
|
|
80265
|
+
const staging = mkdtempSync6(join74(configSkillsRoot, `.${name}-staging-`));
|
|
79048
80266
|
const walk2 = (src, dst) => {
|
|
79049
|
-
|
|
80267
|
+
mkdirSync43(dst, { recursive: true, mode: 493 });
|
|
79050
80268
|
for (const ent of readdirSync30(src, { withFileTypes: true })) {
|
|
79051
|
-
const s =
|
|
79052
|
-
const d =
|
|
80269
|
+
const s = join74(src, ent.name);
|
|
80270
|
+
const d = join74(dst, ent.name);
|
|
79053
80271
|
if (ent.isSymbolicLink())
|
|
79054
80272
|
continue;
|
|
79055
80273
|
if (ent.isDirectory())
|
|
79056
80274
|
walk2(s, d);
|
|
79057
80275
|
else if (ent.isFile()) {
|
|
79058
|
-
|
|
80276
|
+
writeFileSync38(d, readFileSync63(s));
|
|
79059
80277
|
}
|
|
79060
80278
|
}
|
|
79061
80279
|
};
|
|
79062
80280
|
walk2(liveSkillDir, staging);
|
|
79063
|
-
if (
|
|
79064
|
-
const prior =
|
|
80281
|
+
if (existsSync77(dest)) {
|
|
80282
|
+
const prior = join74(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
79065
80283
|
renameSync17(dest, prior);
|
|
79066
80284
|
}
|
|
79067
80285
|
renameSync17(staging, dest);
|
|
@@ -79088,13 +80306,13 @@ function resolveAgent(opts) {
|
|
|
79088
80306
|
function resolveAgentsRoot(opts) {
|
|
79089
80307
|
if (opts.root)
|
|
79090
80308
|
return resolve45(opts.root);
|
|
79091
|
-
return
|
|
80309
|
+
return join74(homedir42(), ".switchroom", "agents");
|
|
79092
80310
|
}
|
|
79093
80311
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
79094
|
-
return
|
|
80312
|
+
return join74(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
79095
80313
|
}
|
|
79096
80314
|
function trashDir(agentsRoot, agent) {
|
|
79097
|
-
return
|
|
80315
|
+
return join74(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
79098
80316
|
}
|
|
79099
80317
|
function readStdinSync2() {
|
|
79100
80318
|
const chunks = [];
|
|
@@ -79124,7 +80342,7 @@ function loadFromDir2(dir) {
|
|
|
79124
80342
|
const files = {};
|
|
79125
80343
|
const walk2 = (sub) => {
|
|
79126
80344
|
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
79127
|
-
const full =
|
|
80345
|
+
const full = join74(sub, ent.name);
|
|
79128
80346
|
if (ent.isSymbolicLink()) {
|
|
79129
80347
|
fail3(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
79130
80348
|
}
|
|
@@ -79177,10 +80395,10 @@ function behavioralValidate(files) {
|
|
|
79177
80395
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
79178
80396
|
}
|
|
79179
80397
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
79180
|
-
const tmp = mkdtempSync6(
|
|
79181
|
-
const tmpPy =
|
|
80398
|
+
const tmp = mkdtempSync6(join74(tmpdir5(), "skill-personal-py-"));
|
|
80399
|
+
const tmpPy = join74(tmp, "check.py");
|
|
79182
80400
|
try {
|
|
79183
|
-
|
|
80401
|
+
writeFileSync38(tmpPy, content);
|
|
79184
80402
|
const r = spawnSync11("python3", ["-m", "py_compile", tmpPy], {
|
|
79185
80403
|
encoding: "utf-8"
|
|
79186
80404
|
});
|
|
@@ -79196,13 +80414,13 @@ function behavioralValidate(files) {
|
|
|
79196
80414
|
}
|
|
79197
80415
|
function sweepTrash(agentsRoot, agent) {
|
|
79198
80416
|
const trash = trashDir(agentsRoot, agent);
|
|
79199
|
-
if (!
|
|
80417
|
+
if (!existsSync77(trash))
|
|
79200
80418
|
return;
|
|
79201
80419
|
const now = Date.now();
|
|
79202
80420
|
for (const ent of readdirSync30(trash, { withFileTypes: true })) {
|
|
79203
80421
|
if (!ent.isDirectory())
|
|
79204
80422
|
continue;
|
|
79205
|
-
const entPath =
|
|
80423
|
+
const entPath = join74(trash, ent.name);
|
|
79206
80424
|
try {
|
|
79207
80425
|
const st = statSync30(entPath);
|
|
79208
80426
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -79222,16 +80440,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
79222
80440
|
if (targetIsSymlink) {
|
|
79223
80441
|
fail3(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
79224
80442
|
}
|
|
79225
|
-
|
|
79226
|
-
const staging = mkdtempSync6(
|
|
80443
|
+
mkdirSync43(dirname23(targetDir), { recursive: true, mode: 493 });
|
|
80444
|
+
const staging = mkdtempSync6(join74(dirname23(targetDir), `.skill-personal-stage-`));
|
|
79227
80445
|
let oldRename = null;
|
|
79228
80446
|
try {
|
|
79229
80447
|
for (const [path8, content] of Object.entries(files)) {
|
|
79230
|
-
const full =
|
|
79231
|
-
|
|
80448
|
+
const full = join74(staging, path8);
|
|
80449
|
+
mkdirSync43(dirname23(full), { recursive: true, mode: 493 });
|
|
79232
80450
|
const fd = openSync16(full, "wx");
|
|
79233
80451
|
try {
|
|
79234
|
-
|
|
80452
|
+
writeFileSync38(fd, content);
|
|
79235
80453
|
} finally {
|
|
79236
80454
|
closeSync16(fd);
|
|
79237
80455
|
}
|
|
@@ -79258,9 +80476,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
79258
80476
|
try {
|
|
79259
80477
|
rmSync17(staging, { recursive: true, force: true });
|
|
79260
80478
|
} catch {}
|
|
79261
|
-
if (oldRename &&
|
|
80479
|
+
if (oldRename && existsSync77(oldRename)) {
|
|
79262
80480
|
try {
|
|
79263
|
-
if (
|
|
80481
|
+
if (existsSync77(targetDir)) {
|
|
79264
80482
|
rmSync17(targetDir, { recursive: true, force: true });
|
|
79265
80483
|
}
|
|
79266
80484
|
renameSync17(oldRename, targetDir);
|
|
@@ -79312,7 +80530,7 @@ function loadFiles(opts) {
|
|
|
79312
80530
|
return loadFromStdin2();
|
|
79313
80531
|
}
|
|
79314
80532
|
const p = resolve45(opts.from);
|
|
79315
|
-
if (!
|
|
80533
|
+
if (!existsSync77(p)) {
|
|
79316
80534
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
79317
80535
|
}
|
|
79318
80536
|
const st = statSync30(p);
|
|
@@ -79360,10 +80578,10 @@ function editPersonalAction(name, opts) {
|
|
|
79360
80578
|
}
|
|
79361
80579
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
79362
80580
|
function defaultSharedRoot() {
|
|
79363
|
-
return
|
|
80581
|
+
return join74(homedir42(), ".switchroom", "skills");
|
|
79364
80582
|
}
|
|
79365
80583
|
function defaultBundledRoot() {
|
|
79366
|
-
return
|
|
80584
|
+
return join74(homedir42(), ".switchroom", "skills", "_bundled");
|
|
79367
80585
|
}
|
|
79368
80586
|
function resolveCloneSource(source, opts) {
|
|
79369
80587
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -79373,8 +80591,8 @@ function resolveCloneSource(source, opts) {
|
|
|
79373
80591
|
const tier = m[1];
|
|
79374
80592
|
const slug = m[2];
|
|
79375
80593
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
79376
|
-
const dir =
|
|
79377
|
-
if (!
|
|
80594
|
+
const dir = join74(root, slug);
|
|
80595
|
+
if (!existsSync77(dir)) {
|
|
79378
80596
|
fail3(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
79379
80597
|
}
|
|
79380
80598
|
const st = lstatSync9(dir);
|
|
@@ -79389,7 +80607,7 @@ function readSourceFiles(dir) {
|
|
|
79389
80607
|
const skipped = [];
|
|
79390
80608
|
const walk2 = (sub) => {
|
|
79391
80609
|
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
79392
|
-
const full =
|
|
80610
|
+
const full = join74(sub, ent.name);
|
|
79393
80611
|
if (ent.isSymbolicLink()) {
|
|
79394
80612
|
continue;
|
|
79395
80613
|
}
|
|
@@ -79498,9 +80716,9 @@ function removePersonalAction(name, opts) {
|
|
|
79498
80716
|
throw err2;
|
|
79499
80717
|
}
|
|
79500
80718
|
const trashRoot = trashDir(agentsRoot, agent);
|
|
79501
|
-
|
|
80719
|
+
mkdirSync43(trashRoot, { recursive: true, mode: 493 });
|
|
79502
80720
|
const ts = Date.now();
|
|
79503
|
-
const trashTarget =
|
|
80721
|
+
const trashTarget = join74(trashRoot, `${name}-${ts}`);
|
|
79504
80722
|
renameSync17(target, trashTarget);
|
|
79505
80723
|
const now = new Date(ts);
|
|
79506
80724
|
utimesSync(trashTarget, now, now);
|
|
@@ -79519,16 +80737,16 @@ function listPersonalAction(opts) {
|
|
|
79519
80737
|
const agent = resolveAgent(opts);
|
|
79520
80738
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
79521
80739
|
sweepTrash(agentsRoot, agent);
|
|
79522
|
-
const skillsDir =
|
|
80740
|
+
const skillsDir = join74(agentsRoot, agent, ".claude", "skills");
|
|
79523
80741
|
const personal = [];
|
|
79524
|
-
if (
|
|
80742
|
+
if (existsSync77(skillsDir)) {
|
|
79525
80743
|
for (const ent of readdirSync30(skillsDir, { withFileTypes: true })) {
|
|
79526
80744
|
if (!ent.isDirectory())
|
|
79527
80745
|
continue;
|
|
79528
80746
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
79529
80747
|
continue;
|
|
79530
80748
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
79531
|
-
const skillPath =
|
|
80749
|
+
const skillPath = join74(skillsDir, ent.name);
|
|
79532
80750
|
let fileCount = 0;
|
|
79533
80751
|
let totalBytes = 0;
|
|
79534
80752
|
const walk2 = (sub) => {
|
|
@@ -79536,10 +80754,10 @@ function listPersonalAction(opts) {
|
|
|
79536
80754
|
if (e.isFile()) {
|
|
79537
80755
|
fileCount += 1;
|
|
79538
80756
|
try {
|
|
79539
|
-
totalBytes += statSync30(
|
|
80757
|
+
totalBytes += statSync30(join74(sub, e.name)).size;
|
|
79540
80758
|
} catch {}
|
|
79541
80759
|
} else if (e.isDirectory()) {
|
|
79542
|
-
walk2(
|
|
80760
|
+
walk2(join74(sub, e.name));
|
|
79543
80761
|
}
|
|
79544
80762
|
}
|
|
79545
80763
|
};
|
|
@@ -79578,24 +80796,24 @@ function registerSkillPersonalCommands(program3) {
|
|
|
79578
80796
|
// src/cli/skill-search.ts
|
|
79579
80797
|
init_helpers();
|
|
79580
80798
|
var import_yaml19 = __toESM(require_dist(), 1);
|
|
79581
|
-
import { existsSync as
|
|
79582
|
-
import { homedir as
|
|
79583
|
-
import { join as
|
|
80799
|
+
import { existsSync as existsSync78, readdirSync as readdirSync31, readFileSync as readFileSync64, statSync as statSync31 } from "node:fs";
|
|
80800
|
+
import { homedir as homedir43 } from "node:os";
|
|
80801
|
+
import { join as join75, resolve as resolve46 } from "node:path";
|
|
79584
80802
|
var PERSONAL_PREFIX2 = "personal-";
|
|
79585
80803
|
var BUNDLED_SUBDIR = "_bundled";
|
|
79586
80804
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
79587
80805
|
function defaultAgentsRoot() {
|
|
79588
|
-
return resolve46(
|
|
80806
|
+
return resolve46(homedir43(), ".switchroom/agents");
|
|
79589
80807
|
}
|
|
79590
80808
|
function defaultSharedRoot2() {
|
|
79591
|
-
return resolve46(
|
|
80809
|
+
return resolve46(homedir43(), ".switchroom/skills");
|
|
79592
80810
|
}
|
|
79593
80811
|
function defaultBundledRoot2() {
|
|
79594
|
-
return resolve46(
|
|
80812
|
+
return resolve46(homedir43(), ".switchroom/skills/_bundled");
|
|
79595
80813
|
}
|
|
79596
80814
|
function readSkillFrontmatter(skillDir) {
|
|
79597
|
-
const mdPath =
|
|
79598
|
-
if (!
|
|
80815
|
+
const mdPath = join75(skillDir, "SKILL.md");
|
|
80816
|
+
if (!existsSync78(mdPath))
|
|
79599
80817
|
return null;
|
|
79600
80818
|
let content;
|
|
79601
80819
|
try {
|
|
@@ -79627,7 +80845,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
79627
80845
|
return { fm: parsed };
|
|
79628
80846
|
}
|
|
79629
80847
|
function statSkillMd(skillDir) {
|
|
79630
|
-
const mdPath =
|
|
80848
|
+
const mdPath = join75(skillDir, "SKILL.md");
|
|
79631
80849
|
try {
|
|
79632
80850
|
const st = statSync31(mdPath);
|
|
79633
80851
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -79638,8 +80856,8 @@ function statSkillMd(skillDir) {
|
|
|
79638
80856
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
79639
80857
|
if (!AGENT_NAME_RE3.test(agent))
|
|
79640
80858
|
return [];
|
|
79641
|
-
const skillsDir =
|
|
79642
|
-
if (!
|
|
80859
|
+
const skillsDir = join75(agentsRoot, agent, ".claude/skills");
|
|
80860
|
+
if (!existsSync78(skillsDir))
|
|
79643
80861
|
return [];
|
|
79644
80862
|
const out = [];
|
|
79645
80863
|
let entries;
|
|
@@ -79651,7 +80869,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
79651
80869
|
for (const ent of entries) {
|
|
79652
80870
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
79653
80871
|
continue;
|
|
79654
|
-
const dirPath =
|
|
80872
|
+
const dirPath = join75(skillsDir, ent);
|
|
79655
80873
|
try {
|
|
79656
80874
|
if (!statSync31(dirPath).isDirectory())
|
|
79657
80875
|
continue;
|
|
@@ -79677,7 +80895,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
79677
80895
|
return out;
|
|
79678
80896
|
}
|
|
79679
80897
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
79680
|
-
if (!
|
|
80898
|
+
if (!existsSync78(sharedRoot))
|
|
79681
80899
|
return [];
|
|
79682
80900
|
const out = [];
|
|
79683
80901
|
let entries;
|
|
@@ -79691,7 +80909,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
79691
80909
|
continue;
|
|
79692
80910
|
if (ent.startsWith("."))
|
|
79693
80911
|
continue;
|
|
79694
|
-
const dirPath =
|
|
80912
|
+
const dirPath = join75(sharedRoot, ent);
|
|
79695
80913
|
try {
|
|
79696
80914
|
if (!statSync31(dirPath).isDirectory())
|
|
79697
80915
|
continue;
|
|
@@ -79715,7 +80933,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
79715
80933
|
return out;
|
|
79716
80934
|
}
|
|
79717
80935
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
79718
|
-
if (!
|
|
80936
|
+
if (!existsSync78(bundledRoot))
|
|
79719
80937
|
return [];
|
|
79720
80938
|
const out = [];
|
|
79721
80939
|
let entries;
|
|
@@ -79727,7 +80945,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
79727
80945
|
for (const ent of entries) {
|
|
79728
80946
|
if (ent.startsWith("."))
|
|
79729
80947
|
continue;
|
|
79730
|
-
const dirPath =
|
|
80948
|
+
const dirPath = join75(bundledRoot, ent);
|
|
79731
80949
|
try {
|
|
79732
80950
|
if (!statSync31(dirPath).isDirectory())
|
|
79733
80951
|
continue;
|
|
@@ -79871,9 +81089,9 @@ function registerHostdMcpCommand(program3) {
|
|
|
79871
81089
|
// src/cli/hostd.ts
|
|
79872
81090
|
init_source();
|
|
79873
81091
|
init_helpers();
|
|
79874
|
-
import { existsSync as
|
|
79875
|
-
import { homedir as
|
|
79876
|
-
import { join as
|
|
81092
|
+
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";
|
|
81093
|
+
import { homedir as homedir44 } from "node:os";
|
|
81094
|
+
import { join as join76 } from "node:path";
|
|
79877
81095
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
79878
81096
|
init_audit_reader();
|
|
79879
81097
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
@@ -79964,14 +81182,14 @@ networks:
|
|
|
79964
81182
|
`;
|
|
79965
81183
|
}
|
|
79966
81184
|
function hostdDir() {
|
|
79967
|
-
return
|
|
81185
|
+
return join76(homedir44(), ".switchroom", "hostd");
|
|
79968
81186
|
}
|
|
79969
81187
|
function hostdComposePath() {
|
|
79970
|
-
return
|
|
81188
|
+
return join76(hostdDir(), "docker-compose.yml");
|
|
79971
81189
|
}
|
|
79972
81190
|
function backupExistingCompose() {
|
|
79973
81191
|
const p = hostdComposePath();
|
|
79974
|
-
if (!
|
|
81192
|
+
if (!existsSync80(p))
|
|
79975
81193
|
return null;
|
|
79976
81194
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
79977
81195
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -80004,9 +81222,9 @@ async function doInstall(opts, program3) {
|
|
|
80004
81222
|
}
|
|
80005
81223
|
const dir = hostdDir();
|
|
80006
81224
|
const composePath = hostdComposePath();
|
|
80007
|
-
|
|
81225
|
+
mkdirSync44(dir, { recursive: true });
|
|
80008
81226
|
const yaml = renderHostdComposeFile({
|
|
80009
|
-
hostHome:
|
|
81227
|
+
hostHome: homedir44(),
|
|
80010
81228
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
80011
81229
|
operatorUid: resolveOperatorUid()
|
|
80012
81230
|
});
|
|
@@ -80019,7 +81237,7 @@ async function doInstall(opts, program3) {
|
|
|
80019
81237
|
const bak = backupExistingCompose();
|
|
80020
81238
|
if (bak)
|
|
80021
81239
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
80022
|
-
|
|
81240
|
+
writeFileSync39(composePath, yaml, "utf8");
|
|
80023
81241
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
80024
81242
|
console.log(source_default.dim(` admin agents: ${adminAgents.length === 0 ? "(none)" : adminAgents.join(", ")}`));
|
|
80025
81243
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-hostd:${opts.tag ?? DEFAULT_IMAGE_TAG}\u2026`));
|
|
@@ -80048,7 +81266,7 @@ function doStatus() {
|
|
|
80048
81266
|
const composeYml = hostdComposePath();
|
|
80049
81267
|
console.log(source_default.bold("switchroom-hostd"));
|
|
80050
81268
|
console.log("");
|
|
80051
|
-
if (!
|
|
81269
|
+
if (!existsSync80(composeYml)) {
|
|
80052
81270
|
console.log(source_default.yellow(" compose: not installed"));
|
|
80053
81271
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
80054
81272
|
return;
|
|
@@ -80069,14 +81287,14 @@ function doStatus() {
|
|
|
80069
81287
|
} else {
|
|
80070
81288
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
80071
81289
|
}
|
|
80072
|
-
if (
|
|
81290
|
+
if (existsSync80(dir)) {
|
|
80073
81291
|
const entries = [];
|
|
80074
81292
|
try {
|
|
80075
81293
|
for (const name of readdirSync32(dir)) {
|
|
80076
81294
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
80077
81295
|
continue;
|
|
80078
|
-
const sockPath =
|
|
80079
|
-
if (
|
|
81296
|
+
const sockPath = join76(dir, name, "sock");
|
|
81297
|
+
if (existsSync80(sockPath)) {
|
|
80080
81298
|
const st = statSync32(sockPath);
|
|
80081
81299
|
if ((st.mode & 61440) === 49152) {
|
|
80082
81300
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
@@ -80095,7 +81313,7 @@ function doStatus() {
|
|
|
80095
81313
|
}
|
|
80096
81314
|
function doUninstall() {
|
|
80097
81315
|
const composeYml = hostdComposePath();
|
|
80098
|
-
if (!
|
|
81316
|
+
if (!existsSync80(composeYml)) {
|
|
80099
81317
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
80100
81318
|
return;
|
|
80101
81319
|
}
|
|
@@ -80119,7 +81337,7 @@ function registerHostdCommand(program3) {
|
|
|
80119
81337
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
80120
81338
|
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) => {
|
|
80121
81339
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
80122
|
-
if (!
|
|
81340
|
+
if (!existsSync80(logPath)) {
|
|
80123
81341
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
80124
81342
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
80125
81343
|
return;
|
|
@@ -80196,6 +81414,9 @@ registerDebugCommand(program3);
|
|
|
80196
81414
|
registerWorktreeCommand(program3);
|
|
80197
81415
|
registerDriveCommand(program3);
|
|
80198
81416
|
registerDriveMcpLauncherCommand(program3);
|
|
81417
|
+
registerM365McpLauncherCommand(program3);
|
|
81418
|
+
registerNotionMcpLauncherCommand(program3);
|
|
81419
|
+
registerNotionCommand(program3);
|
|
80199
81420
|
registerApplyCommand(program3);
|
|
80200
81421
|
registerSecretDetectCommand(program3);
|
|
80201
81422
|
registerStatusAskCommand(program3);
|