@goondocks/myco 0.10.0 → 0.11.0
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/CONTRIBUTING.md +3 -19
- package/README.md +16 -9
- package/dist/{agent-run-CGXF5PPC.js → agent-run-CGM75RS6.js} +4 -4
- package/dist/{agent-tasks-T7NVI3R7.js → agent-tasks-3RQKPRSW.js} +4 -4
- package/dist/{chunk-ZMW6KQX2.js → chunk-6LL2MQHP.js} +5 -5
- package/dist/{chunk-MSXYUXZR.js → chunk-CHG652UO.js} +2 -2
- package/dist/{chunk-FMIWFRAM.js → chunk-IXOHLPH7.js} +9 -8
- package/dist/chunk-IXOHLPH7.js.map +1 -0
- package/dist/{chunk-G2LQBFE3.js → chunk-JYXMRW3T.js} +2 -2
- package/dist/chunk-K2UZNK25.js +83 -0
- package/dist/chunk-K2UZNK25.js.map +1 -0
- package/dist/chunk-LYFDTF7G.js +792 -0
- package/dist/chunk-LYFDTF7G.js.map +1 -0
- package/dist/{chunk-5PEUFJ6U.js → chunk-QFMBZ72S.js} +24 -8
- package/dist/chunk-QFMBZ72S.js.map +1 -0
- package/dist/{chunk-YZMNEIFI.js → chunk-YKOEMLJJ.js} +2 -2
- package/dist/{chunk-5LPERML5.js → chunk-ZAHDA2PQ.js} +8 -8
- package/dist/{cli-6CPFJGRZ.js → cli-FL754H6S.js} +36 -34
- package/dist/cli-FL754H6S.js.map +1 -0
- package/dist/{client-B27SN5QG.js → client-4NP7ZMLV.js} +3 -3
- package/dist/{detect-H5OPI7GD.js → detect-27DN6UTL.js} +3 -3
- package/dist/{doctor-RHHWJTMB.js → doctor-OFGWOYBC.js} +18 -24
- package/dist/doctor-OFGWOYBC.js.map +1 -0
- package/dist/{executor-A5C5KDLP.js → executor-SWXSN7ZC.js} +9 -9
- package/dist/{init-ARJROOWV.js → init-JZJJKC4G.js} +37 -56
- package/dist/init-JZJJKC4G.js.map +1 -0
- package/dist/{init-wizard-XNFOZCEB.js → init-wizard-4VHNOYFO.js} +2 -2
- package/dist/{loader-GKXR5ONU.js → loader-DGWP4EFB.js} +4 -4
- package/dist/{main-PVX6R3I6.js → main-UBUZTMGV.js} +674 -86
- package/dist/main-UBUZTMGV.js.map +1 -0
- package/dist/{post-compact-LR3DSGT3.js → post-compact-O7HMEFKP.js} +11 -7
- package/dist/post-compact-O7HMEFKP.js.map +1 -0
- package/dist/{post-tool-use-SOFVNFU3.js → post-tool-use-OVNMZ3UG.js} +20 -13
- package/dist/post-tool-use-OVNMZ3UG.js.map +1 -0
- package/dist/{post-tool-use-failure-2CZZZASB.js → post-tool-use-failure-HPXTFYBY.js} +13 -9
- package/dist/post-tool-use-failure-HPXTFYBY.js.map +1 -0
- package/dist/{pre-compact-3E3D6565.js → pre-compact-JD3D4PBB.js} +10 -6
- package/dist/pre-compact-JD3D4PBB.js.map +1 -0
- package/dist/{registry-WVZG6R2R.js → registry-33MEKDHT.js} +5 -5
- package/dist/remove-XDFMOYUL.js +92 -0
- package/dist/remove-XDFMOYUL.js.map +1 -0
- package/dist/{restart-XIUFVS33.js → restart-JBAMRKRJ.js} +5 -5
- package/dist/{search-VB6Z2ZXV.js → search-43TS5RGA.js} +4 -4
- package/dist/{server-AKPBRP6Z.js → server-UBU7NALJ.js} +14 -14
- package/dist/{session-UVZS6CY5.js → session-CPBLMD7M.js} +4 -4
- package/dist/{session-end-YMQ44U6Z.js → session-end-DUHUYE6J.js} +11 -6
- package/dist/session-end-DUHUYE6J.js.map +1 -0
- package/dist/{session-start-3754HF3N.js → session-start-I7XM3CME.js} +12 -9
- package/dist/{session-start-3754HF3N.js.map → session-start-I7XM3CME.js.map} +1 -1
- package/dist/{setup-llm-NWHOPJUV.js → setup-llm-TTHEUWDA.js} +7 -7
- package/dist/src/cli.js +1 -1
- package/dist/src/daemon/main.js +1 -1
- package/dist/src/hooks/post-tool-use.js +1 -1
- package/dist/src/hooks/session-end.js +1 -1
- package/dist/src/hooks/session-start.js +1 -1
- package/dist/src/hooks/stop.js +1 -1
- package/dist/src/hooks/user-prompt-submit.js +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/symbionts/manifests/claude-code.yaml +8 -4
- package/dist/src/symbionts/manifests/codex.yaml +19 -0
- package/dist/src/symbionts/manifests/cursor.yaml +6 -3
- package/dist/src/symbionts/manifests/gemini.yaml +20 -0
- package/dist/src/symbionts/manifests/vscode-copilot.yaml +17 -0
- package/dist/src/symbionts/manifests/windsurf.yaml +16 -0
- package/dist/src/symbionts/templates/claude-code/hooks.json +134 -0
- package/dist/src/symbionts/templates/claude-code/mcp.json +7 -0
- package/dist/src/symbionts/templates/claude-code/settings.json +10 -0
- package/dist/src/symbionts/templates/codex/hooks.json +46 -0
- package/dist/src/symbionts/templates/codex/mcp.json +6 -0
- package/dist/src/symbionts/templates/cursor/mcp.json +7 -0
- package/dist/src/symbionts/templates/cursor/settings.json +6 -0
- package/dist/src/symbionts/templates/gemini/hooks.json +74 -0
- package/dist/src/symbionts/templates/gemini/mcp.json +6 -0
- package/dist/src/symbionts/templates/gemini/settings.json +6 -0
- package/dist/src/symbionts/templates/instructions-stub.md +9 -0
- package/dist/src/symbionts/templates/vscode-copilot/hooks.json +79 -0
- package/dist/src/symbionts/templates/vscode-copilot/mcp.json +7 -0
- package/dist/src/symbionts/templates/vscode-copilot/settings.json +6 -0
- package/dist/src/symbionts/templates/windsurf/hooks.json +22 -0
- package/dist/src/symbionts/templates/windsurf/settings.json +6 -0
- package/dist/{stats-CDQXOTEC.js → stats-7CE6GEWE.js} +8 -8
- package/dist/{stop-WSFGRPXZ.js → stop-BF3AWA7S.js} +16 -10
- package/dist/stop-BF3AWA7S.js.map +1 -0
- package/dist/{stop-failure-4FR7574F.js → stop-failure-7Q2LQF2R.js} +11 -7
- package/dist/stop-failure-7Q2LQF2R.js.map +1 -0
- package/dist/{subagent-start-7SGBXJYP.js → subagent-start-QG2J3AN4.js} +11 -7
- package/dist/{subagent-start-7SGBXJYP.js.map → subagent-start-QG2J3AN4.js.map} +1 -1
- package/dist/subagent-stop-WVA7RDIM.js +32 -0
- package/dist/subagent-stop-WVA7RDIM.js.map +1 -0
- package/dist/{task-completed-XXPYPSRV.js → task-completed-AQVQ7GFL.js} +12 -8
- package/dist/task-completed-AQVQ7GFL.js.map +1 -0
- package/dist/{team-XMHYCKFF.js → team-LC3K7UXD.js} +4 -4
- package/dist/ui/assets/{index-CPA_uq_j.js → index-UFE9l-Hb.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/{update-W3UFZU4G.js → update-3EKXZF3H.js} +22 -31
- package/dist/update-3EKXZF3H.js.map +1 -0
- package/dist/{user-prompt-submit-LSWCYUW3.js → user-prompt-submit-22YQD4XM.js} +14 -7
- package/dist/user-prompt-submit-22YQD4XM.js.map +1 -0
- package/dist/{verify-O7TQ5DDY.js → verify-DVIWHZXA.js} +3 -3
- package/dist/{version-VWWY7SPQ.js → version-VPI6ERF7.js} +2 -2
- package/package.json +3 -3
- package/skills/rules/SKILL.md +32 -9
- package/.claude-plugin/marketplace.json +0 -26
- package/.claude-plugin/plugin.json +0 -27
- package/dist/chunk-5PEUFJ6U.js.map +0 -1
- package/dist/chunk-FMIWFRAM.js.map +0 -1
- package/dist/chunk-J4RVYUH4.js +0 -21
- package/dist/chunk-J4RVYUH4.js.map +0 -1
- package/dist/chunk-WXSJKESH.js +0 -441
- package/dist/chunk-WXSJKESH.js.map +0 -1
- package/dist/cli-6CPFJGRZ.js.map +0 -1
- package/dist/doctor-RHHWJTMB.js.map +0 -1
- package/dist/init-ARJROOWV.js.map +0 -1
- package/dist/main-PVX6R3I6.js.map +0 -1
- package/dist/post-compact-LR3DSGT3.js.map +0 -1
- package/dist/post-tool-use-SOFVNFU3.js.map +0 -1
- package/dist/post-tool-use-failure-2CZZZASB.js.map +0 -1
- package/dist/pre-compact-3E3D6565.js.map +0 -1
- package/dist/session-end-YMQ44U6Z.js.map +0 -1
- package/dist/stop-WSFGRPXZ.js.map +0 -1
- package/dist/stop-failure-4FR7574F.js.map +0 -1
- package/dist/subagent-stop-MRVTNX3V.js +0 -28
- package/dist/subagent-stop-MRVTNX3V.js.map +0 -1
- package/dist/task-completed-XXPYPSRV.js.map +0 -1
- package/dist/update-W3UFZU4G.js.map +0 -1
- package/dist/user-prompt-submit-LSWCYUW3.js.map +0 -1
- /package/dist/{agent-run-CGXF5PPC.js.map → agent-run-CGM75RS6.js.map} +0 -0
- /package/dist/{agent-tasks-T7NVI3R7.js.map → agent-tasks-3RQKPRSW.js.map} +0 -0
- /package/dist/{chunk-ZMW6KQX2.js.map → chunk-6LL2MQHP.js.map} +0 -0
- /package/dist/{chunk-MSXYUXZR.js.map → chunk-CHG652UO.js.map} +0 -0
- /package/dist/{chunk-G2LQBFE3.js.map → chunk-JYXMRW3T.js.map} +0 -0
- /package/dist/{chunk-YZMNEIFI.js.map → chunk-YKOEMLJJ.js.map} +0 -0
- /package/dist/{chunk-5LPERML5.js.map → chunk-ZAHDA2PQ.js.map} +0 -0
- /package/dist/{client-B27SN5QG.js.map → client-4NP7ZMLV.js.map} +0 -0
- /package/dist/{detect-H5OPI7GD.js.map → detect-27DN6UTL.js.map} +0 -0
- /package/dist/{executor-A5C5KDLP.js.map → executor-SWXSN7ZC.js.map} +0 -0
- /package/dist/{init-wizard-XNFOZCEB.js.map → init-wizard-4VHNOYFO.js.map} +0 -0
- /package/dist/{loader-GKXR5ONU.js.map → loader-DGWP4EFB.js.map} +0 -0
- /package/dist/{registry-WVZG6R2R.js.map → registry-33MEKDHT.js.map} +0 -0
- /package/dist/{restart-XIUFVS33.js.map → restart-JBAMRKRJ.js.map} +0 -0
- /package/dist/{search-VB6Z2ZXV.js.map → search-43TS5RGA.js.map} +0 -0
- /package/dist/{server-AKPBRP6Z.js.map → server-UBU7NALJ.js.map} +0 -0
- /package/dist/{session-UVZS6CY5.js.map → session-CPBLMD7M.js.map} +0 -0
- /package/dist/{setup-llm-NWHOPJUV.js.map → setup-llm-TTHEUWDA.js.map} +0 -0
- /package/dist/{stats-CDQXOTEC.js.map → stats-7CE6GEWE.js.map} +0 -0
- /package/dist/{team-XMHYCKFF.js.map → team-LC3K7UXD.js.map} +0 -0
- /package/dist/{verify-O7TQ5DDY.js.map → verify-DVIWHZXA.js.map} +0 -0
- /package/dist/{version-VWWY7SPQ.js.map → version-VPI6ERF7.js.map} +0 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
LmStudioBackend,
|
|
4
|
+
OllamaBackend
|
|
5
|
+
} from "./chunk-5SDH75YC.js";
|
|
6
|
+
import {
|
|
7
|
+
closeDatabase,
|
|
8
|
+
initDatabase,
|
|
9
|
+
vaultDbPath
|
|
10
|
+
} from "./chunk-MYX5NCRH.js";
|
|
11
|
+
import {
|
|
12
|
+
DaemonClient
|
|
13
|
+
} from "./chunk-YKOEMLJJ.js";
|
|
14
|
+
|
|
15
|
+
// src/symbionts/installer.ts
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
var MYCO_HOOK_COMMAND_PREFIX = "myco-run";
|
|
19
|
+
function isMycoHookGroup(group) {
|
|
20
|
+
if (Array.isArray(group.hooks) && group.hooks.some((h) => h.command?.startsWith(MYCO_HOOK_COMMAND_PREFIX))) return true;
|
|
21
|
+
if (typeof group.command === "string" && group.command.startsWith(MYCO_HOOK_COMMAND_PREFIX)) return true;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
var GITIGNORE_SKILLS_COMMENT = "# Myco skill symlinks (machine-specific)";
|
|
25
|
+
var TEMPLATES_SUBDIR = "src/symbionts/templates";
|
|
26
|
+
var SKILLS_SUBDIR = "skills";
|
|
27
|
+
var CANONICAL_SKILLS_DIR = ".agents/skills";
|
|
28
|
+
var MYCO_MCP_SERVER_NAME = "myco";
|
|
29
|
+
var INSTRUCTIONS_STUB_MARKER = "Edit AGENTS.md, not this file";
|
|
30
|
+
var INSTRUCTIONS_REF_START = "<!-- myco:agents-ref:start -->";
|
|
31
|
+
var INSTRUCTIONS_REF_END = "<!-- myco:agents-ref:end -->";
|
|
32
|
+
var INSTRUCTIONS_REF_BLOCK = `${INSTRUCTIONS_REF_START}
|
|
33
|
+
> **Project intelligence:** This project uses [Myco](https://myco.sh). The canonical project rules are in [\`AGENTS.md\`](AGENTS.md) \u2014 read and follow it alongside this file.
|
|
34
|
+
${INSTRUCTIONS_REF_END}
|
|
35
|
+
|
|
36
|
+
`;
|
|
37
|
+
var SymbiontInstaller = class {
|
|
38
|
+
constructor(manifest, projectRoot, packageRoot) {
|
|
39
|
+
this.manifest = manifest;
|
|
40
|
+
this.projectRoot = projectRoot;
|
|
41
|
+
this.packageRoot = packageRoot;
|
|
42
|
+
}
|
|
43
|
+
/** Load a JSON template file for this symbiont. Returns null if not found. */
|
|
44
|
+
loadTemplate(name) {
|
|
45
|
+
const candidates = [
|
|
46
|
+
path.join(this.packageRoot, TEMPLATES_SUBDIR, this.manifest.name, `${name}.json`),
|
|
47
|
+
// tsup preserves the src/ prefix under dist/, so the same subdir works in both layouts
|
|
48
|
+
path.join(this.packageRoot, "dist", TEMPLATES_SUBDIR, this.manifest.name, `${name}.json`)
|
|
49
|
+
];
|
|
50
|
+
for (const filePath of candidates) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/** Run all registration steps. */
|
|
59
|
+
install() {
|
|
60
|
+
const reg = this.manifest.registration;
|
|
61
|
+
const result = this.shouldBatchJsonTargets(reg) ? this.installBatchedJson(reg) : {
|
|
62
|
+
hooks: this.installHooks(),
|
|
63
|
+
mcp: this.installMcp(),
|
|
64
|
+
skills: this.installSkills(),
|
|
65
|
+
settings: this.installSettings(),
|
|
66
|
+
instructions: this.installInstructions()
|
|
67
|
+
};
|
|
68
|
+
this.updateGitignore();
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if ALL non-null JSON targets share the same file (e.g., Gemini).
|
|
73
|
+
* Only batches when every target resolves to one path — partial overlaps
|
|
74
|
+
* (e.g., Claude Code: hooks+settings share but MCP is separate) use normal path.
|
|
75
|
+
*/
|
|
76
|
+
shouldBatchJsonTargets(reg) {
|
|
77
|
+
if (!reg) return false;
|
|
78
|
+
const mcpFormat = reg.mcpFormat ?? "json";
|
|
79
|
+
if (mcpFormat !== "json") return false;
|
|
80
|
+
const targets = [reg.hooksTarget, reg.mcpTarget, reg.settingsTarget].filter(Boolean);
|
|
81
|
+
return targets.length > 1 && new Set(targets).size === 1;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Batched install for agents where hooks, MCP, and settings share one JSON file.
|
|
85
|
+
* Single read → apply all transforms in memory → single write.
|
|
86
|
+
*/
|
|
87
|
+
installBatchedJson(reg) {
|
|
88
|
+
const targetPath = path.join(this.projectRoot, reg.hooksTarget ?? reg.mcpTarget ?? reg.settingsTarget);
|
|
89
|
+
let data = readJsonFile(targetPath);
|
|
90
|
+
let hooks = false, mcp = false, settings = false;
|
|
91
|
+
const hooksTemplate = reg.hooksTarget ? this.loadTemplate("hooks") : null;
|
|
92
|
+
if (hooksTemplate) {
|
|
93
|
+
const existingHooks = data.hooks ?? {};
|
|
94
|
+
const mergedHooks = {};
|
|
95
|
+
for (const [event, groups] of Object.entries(existingHooks)) {
|
|
96
|
+
const nonMyco = groups.filter((g) => !isMycoHookGroup(g));
|
|
97
|
+
if (nonMyco.length > 0) mergedHooks[event] = nonMyco;
|
|
98
|
+
}
|
|
99
|
+
for (const [event, groups] of Object.entries(hooksTemplate)) {
|
|
100
|
+
mergedHooks[event] = [...mergedHooks[event] ?? [], ...groups];
|
|
101
|
+
}
|
|
102
|
+
data.hooks = mergedHooks;
|
|
103
|
+
hooks = true;
|
|
104
|
+
}
|
|
105
|
+
const mcpTemplate = reg.mcpTarget ? this.loadTemplate("mcp") : null;
|
|
106
|
+
if (mcpTemplate) {
|
|
107
|
+
const servers = data.mcpServers ?? {};
|
|
108
|
+
for (const [name, def] of Object.entries(mcpTemplate)) {
|
|
109
|
+
servers[name] = def;
|
|
110
|
+
}
|
|
111
|
+
data.mcpServers = servers;
|
|
112
|
+
mcp = true;
|
|
113
|
+
}
|
|
114
|
+
const settingsTemplate = reg.settingsTarget ? this.loadTemplate("settings") : null;
|
|
115
|
+
if (settingsTemplate) {
|
|
116
|
+
data = deepMergeSettings(data, settingsTemplate);
|
|
117
|
+
settings = true;
|
|
118
|
+
}
|
|
119
|
+
writeJsonFile(targetPath, data);
|
|
120
|
+
return {
|
|
121
|
+
hooks,
|
|
122
|
+
mcp,
|
|
123
|
+
skills: this.installSkills(),
|
|
124
|
+
settings,
|
|
125
|
+
instructions: this.installInstructions()
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Remove all Myco registration from this symbiont's project files. */
|
|
129
|
+
uninstall() {
|
|
130
|
+
const reg = this.manifest.registration;
|
|
131
|
+
const result = this.shouldBatchJsonTargets(reg) ? this.uninstallBatchedJson(reg) : {
|
|
132
|
+
hooks: this.uninstallHooks(),
|
|
133
|
+
mcp: this.uninstallMcp(),
|
|
134
|
+
skills: this.uninstallSkills(),
|
|
135
|
+
settings: this.uninstallSettings(),
|
|
136
|
+
instructions: this.uninstallInstructions()
|
|
137
|
+
};
|
|
138
|
+
this.cleanGitignore();
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Batched uninstall for agents where hooks, MCP, and settings share one JSON file.
|
|
143
|
+
*/
|
|
144
|
+
uninstallBatchedJson(reg) {
|
|
145
|
+
const targetPath = path.join(this.projectRoot, reg.hooksTarget ?? reg.mcpTarget ?? reg.settingsTarget);
|
|
146
|
+
const data = readJsonFile(targetPath);
|
|
147
|
+
if (Object.keys(data).length === 0) {
|
|
148
|
+
return { hooks: false, mcp: false, skills: this.uninstallSkills(), settings: false, instructions: this.uninstallInstructions() };
|
|
149
|
+
}
|
|
150
|
+
let hooks = false, mcp = false, settings = false;
|
|
151
|
+
if (reg.hooksTarget) {
|
|
152
|
+
const existingHooks = data.hooks ?? {};
|
|
153
|
+
if (Object.keys(existingHooks).length > 0) {
|
|
154
|
+
const cleaned = {};
|
|
155
|
+
for (const [event, groups] of Object.entries(existingHooks)) {
|
|
156
|
+
const nonMyco = groups.filter((g) => !isMycoHookGroup(g));
|
|
157
|
+
if (nonMyco.length > 0) cleaned[event] = nonMyco;
|
|
158
|
+
}
|
|
159
|
+
if (Object.keys(cleaned).length === 0) {
|
|
160
|
+
delete data.hooks;
|
|
161
|
+
} else {
|
|
162
|
+
data.hooks = cleaned;
|
|
163
|
+
}
|
|
164
|
+
hooks = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (reg.mcpTarget) {
|
|
168
|
+
const servers = data.mcpServers ?? {};
|
|
169
|
+
if (servers[MYCO_MCP_SERVER_NAME]) {
|
|
170
|
+
delete servers[MYCO_MCP_SERVER_NAME];
|
|
171
|
+
if (Object.keys(servers).length === 0) delete data.mcpServers;
|
|
172
|
+
else data.mcpServers = servers;
|
|
173
|
+
mcp = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const settingsTemplate = reg.settingsTarget ? this.loadTemplate("settings") : null;
|
|
177
|
+
if (settingsTemplate) {
|
|
178
|
+
settings = deepRemoveSettings(data, settingsTemplate);
|
|
179
|
+
}
|
|
180
|
+
writeOrDeleteJsonFile(targetPath, data);
|
|
181
|
+
return { hooks, mcp, skills: this.uninstallSkills(), settings, instructions: this.uninstallInstructions() };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Ensure the instruction file references AGENTS.md.
|
|
185
|
+
* - File doesn't exist: write the full stub template.
|
|
186
|
+
* - File exists without reference: prepend a reference block.
|
|
187
|
+
* - File already has reference: skip (idempotent).
|
|
188
|
+
*/
|
|
189
|
+
installInstructions() {
|
|
190
|
+
const reg = this.manifest.registration;
|
|
191
|
+
if (!reg?.instructionsFile) return false;
|
|
192
|
+
const targetPath = path.join(this.projectRoot, reg.instructionsFile);
|
|
193
|
+
let existing = null;
|
|
194
|
+
try {
|
|
195
|
+
existing = fs.readFileSync(targetPath, "utf-8");
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
if (existing !== null) {
|
|
199
|
+
if (existing.includes(INSTRUCTIONS_REF_START) || existing.includes(INSTRUCTIONS_STUB_MARKER)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
fs.writeFileSync(targetPath, INSTRUCTIONS_REF_BLOCK + existing, "utf-8");
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
const templateCandidates = [
|
|
206
|
+
path.join(this.packageRoot, "src/symbionts/templates/instructions-stub.md"),
|
|
207
|
+
path.join(this.packageRoot, "dist/src/symbionts/templates/instructions-stub.md")
|
|
208
|
+
];
|
|
209
|
+
let stub = null;
|
|
210
|
+
for (const p of templateCandidates) {
|
|
211
|
+
try {
|
|
212
|
+
stub = fs.readFileSync(p, "utf-8");
|
|
213
|
+
break;
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!stub) return false;
|
|
218
|
+
stub = stub.replace("{agentDisplayName}", this.manifest.displayName);
|
|
219
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
220
|
+
fs.writeFileSync(targetPath, stub, "utf-8");
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Remove Myco's instruction file reference.
|
|
225
|
+
* - If file is the full stub (only Myco content): delete it.
|
|
226
|
+
* - If file has user content + prepended reference: remove just the reference block.
|
|
227
|
+
*/
|
|
228
|
+
uninstallInstructions() {
|
|
229
|
+
const reg = this.manifest.registration;
|
|
230
|
+
if (!reg?.instructionsFile) return false;
|
|
231
|
+
const targetPath = path.join(this.projectRoot, reg.instructionsFile);
|
|
232
|
+
let content;
|
|
233
|
+
try {
|
|
234
|
+
content = fs.readFileSync(targetPath, "utf-8");
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (content.includes(INSTRUCTIONS_STUB_MARKER)) {
|
|
239
|
+
fs.unlinkSync(targetPath);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (content.includes(INSTRUCTIONS_REF_START)) {
|
|
243
|
+
const startIdx = content.indexOf(INSTRUCTIONS_REF_START);
|
|
244
|
+
const endIdx = content.indexOf(INSTRUCTIONS_REF_END);
|
|
245
|
+
if (endIdx > startIdx) {
|
|
246
|
+
const afterEnd = endIdx + INSTRUCTIONS_REF_END.length;
|
|
247
|
+
const cleaned = (content.slice(0, startIdx) + content.slice(afterEnd)).replace(/^\n+/, "");
|
|
248
|
+
fs.writeFileSync(targetPath, cleaned, "utf-8");
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
/** List skill directory names from the package root. Returns empty array if not found. */
|
|
255
|
+
listSkillDirs() {
|
|
256
|
+
try {
|
|
257
|
+
return fs.readdirSync(path.join(this.packageRoot, SKILLS_SUBDIR), { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
258
|
+
} catch {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Add skill symlink paths to project .gitignore. */
|
|
263
|
+
updateGitignore() {
|
|
264
|
+
const reg = this.manifest.registration;
|
|
265
|
+
if (!reg?.skillsTarget) return;
|
|
266
|
+
const skillNames = this.listSkillDirs();
|
|
267
|
+
const entries = [
|
|
268
|
+
`${CANONICAL_SKILLS_DIR}/`,
|
|
269
|
+
...reg.skillsTarget !== CANONICAL_SKILLS_DIR ? skillNames.map((name) => `${reg.skillsTarget}/${name}`) : []
|
|
270
|
+
];
|
|
271
|
+
const gitignorePath = path.join(this.projectRoot, ".gitignore");
|
|
272
|
+
let existing = "";
|
|
273
|
+
try {
|
|
274
|
+
existing = fs.readFileSync(gitignorePath, "utf-8");
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
const newEntries = entries.filter((e) => !existing.includes(e));
|
|
278
|
+
if (newEntries.length === 0) return;
|
|
279
|
+
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
280
|
+
const block = `${separator}
|
|
281
|
+
${GITIGNORE_SKILLS_COMMENT}
|
|
282
|
+
${newEntries.join("\n")}
|
|
283
|
+
`;
|
|
284
|
+
fs.writeFileSync(gitignorePath, existing + block, "utf-8");
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Merge hooks template into the target settings file.
|
|
288
|
+
* Replaces all Myco-owned hook groups; preserves non-Myco hooks.
|
|
289
|
+
*/
|
|
290
|
+
installHooks() {
|
|
291
|
+
const reg = this.manifest.registration;
|
|
292
|
+
if (!reg?.hooksTarget) return false;
|
|
293
|
+
const template = this.loadTemplate("hooks");
|
|
294
|
+
if (!template) return false;
|
|
295
|
+
const targetPath = path.join(this.projectRoot, reg.hooksTarget);
|
|
296
|
+
const settings = readJsonFile(targetPath);
|
|
297
|
+
const existingHooks = settings.hooks ?? {};
|
|
298
|
+
const mergedHooks = {};
|
|
299
|
+
for (const [event, groups] of Object.entries(existingHooks)) {
|
|
300
|
+
const nonMycoGroups = groups.filter(
|
|
301
|
+
(group) => !isMycoHookGroup(group)
|
|
302
|
+
);
|
|
303
|
+
if (nonMycoGroups.length > 0) {
|
|
304
|
+
mergedHooks[event] = nonMycoGroups;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
for (const [event, groups] of Object.entries(template)) {
|
|
308
|
+
mergedHooks[event] = [...mergedHooks[event] ?? [], ...groups];
|
|
309
|
+
}
|
|
310
|
+
settings.hooks = mergedHooks;
|
|
311
|
+
writeJsonFile(targetPath, settings);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Merge MCP server template into the target config file.
|
|
316
|
+
* Replaces the `myco` server entry; preserves other servers.
|
|
317
|
+
*/
|
|
318
|
+
installMcp() {
|
|
319
|
+
const reg = this.manifest.registration;
|
|
320
|
+
if (!reg?.mcpTarget) return false;
|
|
321
|
+
const template = this.loadTemplate("mcp");
|
|
322
|
+
if (!template) return false;
|
|
323
|
+
const targetPath = path.join(this.projectRoot, reg.mcpTarget);
|
|
324
|
+
const mcpFormat = reg.mcpFormat ?? "json";
|
|
325
|
+
if (mcpFormat === "toml") {
|
|
326
|
+
return this.installMcpToml(targetPath, template);
|
|
327
|
+
}
|
|
328
|
+
return this.installMcpJson(targetPath, template);
|
|
329
|
+
}
|
|
330
|
+
/** Write MCP servers to a JSON config file. */
|
|
331
|
+
installMcpJson(targetPath, template) {
|
|
332
|
+
const config = readJsonFile(targetPath);
|
|
333
|
+
const servers = config.mcpServers ?? {};
|
|
334
|
+
for (const [name, def] of Object.entries(template)) {
|
|
335
|
+
servers[name] = def;
|
|
336
|
+
}
|
|
337
|
+
config.mcpServers = servers;
|
|
338
|
+
writeJsonFile(targetPath, config);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
/** Write MCP servers to a TOML config file. */
|
|
342
|
+
installMcpToml(targetPath, template) {
|
|
343
|
+
let raw = "";
|
|
344
|
+
try {
|
|
345
|
+
raw = fs.readFileSync(targetPath, "utf-8");
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
for (const [name, def] of Object.entries(template)) {
|
|
349
|
+
raw = buildTomlMcpSection(raw, name, def);
|
|
350
|
+
}
|
|
351
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
352
|
+
fs.writeFileSync(targetPath, raw, "utf-8");
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create symlinks for skills through .agents/skills/ canonical layer.
|
|
357
|
+
* Canonical: .agents/skills/<name> -> <packageRoot>/skills/<name>
|
|
358
|
+
* Agent-specific: <skillsTarget>/<name> -> ../../.agents/skills/<name>
|
|
359
|
+
*/
|
|
360
|
+
installSkills() {
|
|
361
|
+
const reg = this.manifest.registration;
|
|
362
|
+
if (!reg?.skillsTarget) return false;
|
|
363
|
+
const skillNames = this.listSkillDirs();
|
|
364
|
+
if (skillNames.length === 0) return false;
|
|
365
|
+
const skillsSrc = path.join(this.packageRoot, SKILLS_SUBDIR);
|
|
366
|
+
const canonicalDir = path.join(this.projectRoot, CANONICAL_SKILLS_DIR);
|
|
367
|
+
fs.mkdirSync(canonicalDir, { recursive: true });
|
|
368
|
+
for (const name of skillNames) {
|
|
369
|
+
const canonicalLink = path.join(canonicalDir, name);
|
|
370
|
+
const target = path.join(skillsSrc, name);
|
|
371
|
+
ensureSymlink(canonicalLink, target);
|
|
372
|
+
}
|
|
373
|
+
const agentSkillsDir = path.join(this.projectRoot, reg.skillsTarget);
|
|
374
|
+
const canonicalRel = path.relative(agentSkillsDir, canonicalDir);
|
|
375
|
+
if (reg.skillsTarget !== CANONICAL_SKILLS_DIR) {
|
|
376
|
+
fs.mkdirSync(agentSkillsDir, { recursive: true });
|
|
377
|
+
for (const name of skillNames) {
|
|
378
|
+
const agentLink = path.join(agentSkillsDir, name);
|
|
379
|
+
const relTarget = path.join(canonicalRel, name);
|
|
380
|
+
ensureSymlink(agentLink, relTarget);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Merge settings template into the target settings file.
|
|
387
|
+
* Deep merges objects and deduplicates arrays.
|
|
388
|
+
*/
|
|
389
|
+
installSettings() {
|
|
390
|
+
const reg = this.manifest.registration;
|
|
391
|
+
if (!reg?.settingsTarget) return false;
|
|
392
|
+
const template = this.loadTemplate("settings");
|
|
393
|
+
if (!template) return false;
|
|
394
|
+
const targetPath = path.join(this.projectRoot, reg.settingsTarget);
|
|
395
|
+
const existing = readJsonFile(targetPath);
|
|
396
|
+
const merged = deepMergeSettings(existing, template);
|
|
397
|
+
writeJsonFile(targetPath, merged);
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Remove Myco entries from the target settings file.
|
|
402
|
+
* Template-driven: loads the settings template and removes matching values.
|
|
403
|
+
* Arrays: filter out values present in the template.
|
|
404
|
+
* Objects: delete keys present in the template.
|
|
405
|
+
*/
|
|
406
|
+
uninstallSettings() {
|
|
407
|
+
const reg = this.manifest.registration;
|
|
408
|
+
if (!reg?.settingsTarget) return false;
|
|
409
|
+
const template = this.loadTemplate("settings");
|
|
410
|
+
if (!template) return false;
|
|
411
|
+
const targetPath = path.join(this.projectRoot, reg.settingsTarget);
|
|
412
|
+
const settings = readJsonFile(targetPath);
|
|
413
|
+
if (Object.keys(settings).length === 0) return false;
|
|
414
|
+
const changed = deepRemoveSettings(settings, template);
|
|
415
|
+
if (!changed) return false;
|
|
416
|
+
writeOrDeleteJsonFile(targetPath, settings);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
/** Remove Myco hook groups from the target settings file. */
|
|
420
|
+
uninstallHooks() {
|
|
421
|
+
const reg = this.manifest.registration;
|
|
422
|
+
if (!reg?.hooksTarget) return false;
|
|
423
|
+
const targetPath = path.join(this.projectRoot, reg.hooksTarget);
|
|
424
|
+
const settings = readJsonFile(targetPath);
|
|
425
|
+
const existingHooks = settings.hooks ?? {};
|
|
426
|
+
if (Object.keys(existingHooks).length === 0) return false;
|
|
427
|
+
const cleaned = {};
|
|
428
|
+
for (const [event, groups] of Object.entries(existingHooks)) {
|
|
429
|
+
const nonMyco = groups.filter(
|
|
430
|
+
(group) => !isMycoHookGroup(group)
|
|
431
|
+
);
|
|
432
|
+
if (nonMyco.length > 0) {
|
|
433
|
+
cleaned[event] = nonMyco;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (Object.keys(cleaned).length === 0) {
|
|
437
|
+
delete settings.hooks;
|
|
438
|
+
} else {
|
|
439
|
+
settings.hooks = cleaned;
|
|
440
|
+
}
|
|
441
|
+
writeOrDeleteJsonFile(targetPath, settings);
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
/** Remove Myco MCP server entry from the target config file. */
|
|
445
|
+
uninstallMcp() {
|
|
446
|
+
const reg = this.manifest.registration;
|
|
447
|
+
if (!reg?.mcpTarget) return false;
|
|
448
|
+
const targetPath = path.join(this.projectRoot, reg.mcpTarget);
|
|
449
|
+
const mcpFormat = reg.mcpFormat ?? "json";
|
|
450
|
+
if (mcpFormat === "toml") {
|
|
451
|
+
return this.uninstallMcpToml(targetPath);
|
|
452
|
+
}
|
|
453
|
+
return this.uninstallMcpJson(targetPath);
|
|
454
|
+
}
|
|
455
|
+
uninstallMcpJson(targetPath) {
|
|
456
|
+
const config = readJsonFile(targetPath);
|
|
457
|
+
const servers = config.mcpServers ?? {};
|
|
458
|
+
if (!servers[MYCO_MCP_SERVER_NAME]) return false;
|
|
459
|
+
delete servers[MYCO_MCP_SERVER_NAME];
|
|
460
|
+
if (Object.keys(servers).length === 0) {
|
|
461
|
+
delete config.mcpServers;
|
|
462
|
+
} else {
|
|
463
|
+
config.mcpServers = servers;
|
|
464
|
+
}
|
|
465
|
+
writeOrDeleteJsonFile(targetPath, config);
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
uninstallMcpToml(targetPath) {
|
|
469
|
+
let raw = "";
|
|
470
|
+
try {
|
|
471
|
+
raw = fs.readFileSync(targetPath, "utf-8");
|
|
472
|
+
} catch {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
const sectionHeader = `[mcp_servers.${MYCO_MCP_SERVER_NAME}]`;
|
|
476
|
+
if (!raw.includes(sectionHeader)) return false;
|
|
477
|
+
const startIdx = raw.indexOf(sectionHeader);
|
|
478
|
+
const endIdx = findTomlSectionEnd(raw, startIdx + sectionHeader.length, MYCO_MCP_SERVER_NAME);
|
|
479
|
+
const before = raw.slice(0, startIdx).trimEnd();
|
|
480
|
+
const after = raw.slice(endIdx).trimStart();
|
|
481
|
+
const updated = (before + (before && after ? "\n\n" : "") + after).trimEnd();
|
|
482
|
+
if (!updated.trim()) {
|
|
483
|
+
try {
|
|
484
|
+
fs.unlinkSync(targetPath);
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
fs.writeFileSync(targetPath, updated + "\n", "utf-8");
|
|
489
|
+
}
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
/** Remove skill symlinks (canonical + agent-specific). */
|
|
493
|
+
uninstallSkills() {
|
|
494
|
+
const reg = this.manifest.registration;
|
|
495
|
+
if (!reg?.skillsTarget) return false;
|
|
496
|
+
const skillNames = this.listSkillDirs();
|
|
497
|
+
if (skillNames.length === 0) return false;
|
|
498
|
+
let removed = false;
|
|
499
|
+
if (reg.skillsTarget !== CANONICAL_SKILLS_DIR) {
|
|
500
|
+
for (const name of skillNames) {
|
|
501
|
+
const link = path.join(this.projectRoot, reg.skillsTarget, name);
|
|
502
|
+
try {
|
|
503
|
+
fs.unlinkSync(link);
|
|
504
|
+
removed = true;
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
fs.rmdirSync(path.join(this.projectRoot, reg.skillsTarget));
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const canonicalDir = path.join(this.projectRoot, CANONICAL_SKILLS_DIR);
|
|
514
|
+
for (const name of skillNames) {
|
|
515
|
+
const link = path.join(canonicalDir, name);
|
|
516
|
+
try {
|
|
517
|
+
fs.unlinkSync(link);
|
|
518
|
+
removed = true;
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
fs.rmdirSync(canonicalDir);
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
fs.rmdirSync(path.join(this.projectRoot, ".agents"));
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
return removed;
|
|
531
|
+
}
|
|
532
|
+
/** Remove Myco entries from project .gitignore. */
|
|
533
|
+
cleanGitignore() {
|
|
534
|
+
const gitignorePath = path.join(this.projectRoot, ".gitignore");
|
|
535
|
+
let content = "";
|
|
536
|
+
try {
|
|
537
|
+
content = fs.readFileSync(gitignorePath, "utf-8");
|
|
538
|
+
} catch {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const reg = this.manifest.registration;
|
|
542
|
+
const skillNames = reg?.skillsTarget && reg.skillsTarget !== CANONICAL_SKILLS_DIR ? this.listSkillDirs() : [];
|
|
543
|
+
const lines = content.split("\n");
|
|
544
|
+
const filtered = lines.filter((line) => {
|
|
545
|
+
if (line === GITIGNORE_SKILLS_COMMENT) return false;
|
|
546
|
+
if (line === `${CANONICAL_SKILLS_DIR}/`) return false;
|
|
547
|
+
if (skillNames.some((name) => line === `${reg.skillsTarget}/${name}`)) return false;
|
|
548
|
+
return true;
|
|
549
|
+
});
|
|
550
|
+
const cleaned = filtered.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
551
|
+
if (cleaned) {
|
|
552
|
+
fs.writeFileSync(gitignorePath, cleaned + "\n", "utf-8");
|
|
553
|
+
} else {
|
|
554
|
+
try {
|
|
555
|
+
fs.unlinkSync(gitignorePath);
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
var TOML_SECTION_RE = /^\[([^\]]+)\]/;
|
|
562
|
+
function findTomlSectionEnd(raw, searchStart, serverName) {
|
|
563
|
+
const subsectionPrefix = `mcp_servers.${serverName}.`;
|
|
564
|
+
const rawLines = raw.slice(searchStart).split("\n");
|
|
565
|
+
let offset = searchStart;
|
|
566
|
+
for (const line of rawLines) {
|
|
567
|
+
offset += line.length + 1;
|
|
568
|
+
const m = line.match(TOML_SECTION_RE);
|
|
569
|
+
if (m && !m[1].startsWith(subsectionPrefix) && m[1] !== `mcp_servers.${serverName}`) {
|
|
570
|
+
return offset - line.length - 1;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return raw.length;
|
|
574
|
+
}
|
|
575
|
+
function buildTomlMcpSection(raw, serverName, server) {
|
|
576
|
+
const sectionHeader = `[mcp_servers.${serverName}]`;
|
|
577
|
+
const lines = [sectionHeader];
|
|
578
|
+
for (const [key, val] of Object.entries(server)) {
|
|
579
|
+
if (key === "env" && typeof val === "object" && val !== null) continue;
|
|
580
|
+
if (typeof val === "string") {
|
|
581
|
+
lines.push(`${key} = "${val}"`);
|
|
582
|
+
} else if (Array.isArray(val)) {
|
|
583
|
+
lines.push(`${key} = [${val.map((v) => `"${v}"`).join(", ")}]`);
|
|
584
|
+
} else if (typeof val === "boolean") {
|
|
585
|
+
lines.push(`${key} = ${val}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
const env = server.env;
|
|
589
|
+
if (env && Object.keys(env).length > 0) {
|
|
590
|
+
lines.push("");
|
|
591
|
+
lines.push(`[mcp_servers.${serverName}.env]`);
|
|
592
|
+
for (const [key, val] of Object.entries(env)) {
|
|
593
|
+
lines.push(`${key} = "${val}"`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const block = lines.join("\n");
|
|
597
|
+
let updated;
|
|
598
|
+
if (raw.includes(sectionHeader)) {
|
|
599
|
+
const startIdx = raw.indexOf(sectionHeader);
|
|
600
|
+
const endIdx = findTomlSectionEnd(raw, startIdx + sectionHeader.length, serverName);
|
|
601
|
+
const before = raw.slice(0, startIdx).trimEnd();
|
|
602
|
+
const after = raw.slice(endIdx);
|
|
603
|
+
const separator = before ? "\n\n" : "";
|
|
604
|
+
updated = (before + separator + block + after).trimEnd() + "\n";
|
|
605
|
+
} else {
|
|
606
|
+
const separator = raw.trim() ? "\n\n" : "";
|
|
607
|
+
updated = (raw.trimEnd() + separator + block).trimEnd() + "\n";
|
|
608
|
+
}
|
|
609
|
+
return updated;
|
|
610
|
+
}
|
|
611
|
+
function deepMergeSettings(target, source) {
|
|
612
|
+
const result = { ...target };
|
|
613
|
+
for (const [key, sourceVal] of Object.entries(source)) {
|
|
614
|
+
const targetVal = result[key];
|
|
615
|
+
if (Array.isArray(sourceVal) && Array.isArray(targetVal)) {
|
|
616
|
+
result[key] = [.../* @__PURE__ */ new Set([...targetVal, ...sourceVal])];
|
|
617
|
+
} else if (isPlainObject(sourceVal) && isPlainObject(targetVal)) {
|
|
618
|
+
result[key] = deepMergeSettings(
|
|
619
|
+
targetVal,
|
|
620
|
+
sourceVal
|
|
621
|
+
);
|
|
622
|
+
} else {
|
|
623
|
+
result[key] = sourceVal;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
function isPlainObject(val) {
|
|
629
|
+
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
630
|
+
}
|
|
631
|
+
function deepRemoveSettings(target, template) {
|
|
632
|
+
let changed = false;
|
|
633
|
+
for (const [key, templateVal] of Object.entries(template)) {
|
|
634
|
+
const targetVal = target[key];
|
|
635
|
+
if (targetVal === void 0) continue;
|
|
636
|
+
if (Array.isArray(templateVal) && Array.isArray(targetVal)) {
|
|
637
|
+
const templateSet = new Set(templateVal.map(String));
|
|
638
|
+
const filtered = targetVal.filter((v) => !templateSet.has(String(v)));
|
|
639
|
+
if (filtered.length !== targetVal.length) {
|
|
640
|
+
if (filtered.length > 0) {
|
|
641
|
+
target[key] = filtered;
|
|
642
|
+
} else {
|
|
643
|
+
delete target[key];
|
|
644
|
+
}
|
|
645
|
+
changed = true;
|
|
646
|
+
}
|
|
647
|
+
} else if (isPlainObject(templateVal) && isPlainObject(targetVal)) {
|
|
648
|
+
if (deepRemoveSettings(targetVal, templateVal)) {
|
|
649
|
+
if (Object.keys(targetVal).length === 0) {
|
|
650
|
+
delete target[key];
|
|
651
|
+
}
|
|
652
|
+
changed = true;
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
if (String(targetVal) === String(templateVal)) {
|
|
656
|
+
delete target[key];
|
|
657
|
+
changed = true;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return changed;
|
|
662
|
+
}
|
|
663
|
+
function readJsonFile(filePath) {
|
|
664
|
+
try {
|
|
665
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
666
|
+
} catch {
|
|
667
|
+
return {};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function writeJsonFile(filePath, data) {
|
|
671
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
672
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
673
|
+
}
|
|
674
|
+
function writeOrDeleteJsonFile(filePath, data) {
|
|
675
|
+
if (Object.keys(data).length === 0) {
|
|
676
|
+
try {
|
|
677
|
+
fs.unlinkSync(filePath);
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
writeJsonFile(filePath, data);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function ensureSymlink(linkPath, target) {
|
|
685
|
+
try {
|
|
686
|
+
if (fs.readlinkSync(linkPath) === target) return;
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
try {
|
|
690
|
+
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
fs.symlinkSync(target, linkPath);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/cli/shared.ts
|
|
697
|
+
import fs2 from "fs";
|
|
698
|
+
import path2 from "path";
|
|
699
|
+
import os from "os";
|
|
700
|
+
function initVaultDb(vaultDir) {
|
|
701
|
+
initDatabase(vaultDbPath(vaultDir));
|
|
702
|
+
return closeDatabase;
|
|
703
|
+
}
|
|
704
|
+
async function connectToDaemon(vaultDir) {
|
|
705
|
+
const client = new DaemonClient(vaultDir);
|
|
706
|
+
const healthy = await client.ensureRunning();
|
|
707
|
+
if (!healthy) {
|
|
708
|
+
console.error("Failed to connect to daemon");
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
return client;
|
|
712
|
+
}
|
|
713
|
+
function loadEnv() {
|
|
714
|
+
const envPath = path2.resolve(process.cwd(), ".env");
|
|
715
|
+
if (!fs2.existsSync(envPath)) return;
|
|
716
|
+
for (const line of fs2.readFileSync(envPath, "utf-8").split("\n")) {
|
|
717
|
+
const match = line.match(/^\s*([^#=]+?)\s*=\s*(.*?)\s*$/);
|
|
718
|
+
if (match && !process.env[match[1]]) {
|
|
719
|
+
process.env[match[1]] = match[2];
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function isProcessAlive(pid) {
|
|
724
|
+
try {
|
|
725
|
+
process.kill(pid, 0);
|
|
726
|
+
return true;
|
|
727
|
+
} catch {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
var PROVIDER_DEFAULTS = {
|
|
732
|
+
ollama: { base_url: OllamaBackend.DEFAULT_BASE_URL },
|
|
733
|
+
"lm-studio": { base_url: LmStudioBackend.DEFAULT_BASE_URL }
|
|
734
|
+
};
|
|
735
|
+
var VAULT_GITIGNORE = `# SQLite database
|
|
736
|
+
myco.db*
|
|
737
|
+
vectors.db*
|
|
738
|
+
|
|
739
|
+
# Daemon state \u2014 per-machine, ephemeral
|
|
740
|
+
daemon.json
|
|
741
|
+
buffer/
|
|
742
|
+
logs/
|
|
743
|
+
|
|
744
|
+
# Secrets \u2014 API keys for cloud providers
|
|
745
|
+
secrets.env
|
|
746
|
+
|
|
747
|
+
# Machine ID
|
|
748
|
+
machine_id
|
|
749
|
+
|
|
750
|
+
# Binary attachments \u2014 screenshots captured from transcripts
|
|
751
|
+
attachments/
|
|
752
|
+
|
|
753
|
+
# Team worker deployment \u2014 patched wrangler.toml + source copy
|
|
754
|
+
.team-worker/
|
|
755
|
+
`;
|
|
756
|
+
function registerSymbionts(manifests, projectRoot, packageRoot, verb) {
|
|
757
|
+
let count = 0;
|
|
758
|
+
for (const manifest of manifests) {
|
|
759
|
+
try {
|
|
760
|
+
const installer = new SymbiontInstaller(manifest, projectRoot, packageRoot);
|
|
761
|
+
const result = installer.install();
|
|
762
|
+
const installed = [
|
|
763
|
+
result.hooks && "hooks",
|
|
764
|
+
result.mcp && "MCP server",
|
|
765
|
+
result.skills && "skills",
|
|
766
|
+
result.settings && "settings",
|
|
767
|
+
result.instructions && "instructions"
|
|
768
|
+
].filter(Boolean);
|
|
769
|
+
if (installed.length > 0) {
|
|
770
|
+
console.log(` \u2713 ${verb} ${manifest.displayName}: ${installed.join(", ")}`);
|
|
771
|
+
count++;
|
|
772
|
+
} else {
|
|
773
|
+
console.log(` \u2013 ${manifest.displayName}: no registration targets configured`);
|
|
774
|
+
}
|
|
775
|
+
} catch (err) {
|
|
776
|
+
console.error(` \u2717 Failed to register ${manifest.displayName}: ${err.message}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return count;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export {
|
|
783
|
+
MYCO_MCP_SERVER_NAME,
|
|
784
|
+
SymbiontInstaller,
|
|
785
|
+
initVaultDb,
|
|
786
|
+
connectToDaemon,
|
|
787
|
+
loadEnv,
|
|
788
|
+
isProcessAlive,
|
|
789
|
+
VAULT_GITIGNORE,
|
|
790
|
+
registerSymbionts
|
|
791
|
+
};
|
|
792
|
+
//# sourceMappingURL=chunk-LYFDTF7G.js.map
|