@zhixuan92/multi-model-agent 3.0.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/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/cli/index.d.ts +61 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +252 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install-skill.d.ts +158 -0
- package/dist/cli/install-skill.d.ts.map +1 -0
- package/dist/cli/install-skill.js +425 -0
- package/dist/cli/install-skill.js.map +1 -0
- package/dist/cli/print-token.d.ts +18 -0
- package/dist/cli/print-token.d.ts.map +1 -0
- package/dist/cli/print-token.js +60 -0
- package/dist/cli/print-token.js.map +1 -0
- package/dist/cli/serve.d.ts +44 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +61 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/status.d.ts +49 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +155 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/http/async-dispatch.d.ts +32 -0
- package/dist/http/async-dispatch.d.ts.map +1 -0
- package/dist/http/async-dispatch.js +53 -0
- package/dist/http/async-dispatch.js.map +1 -0
- package/dist/http/auth.d.ts +26 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +64 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/cwd-validator.d.ts +11 -0
- package/dist/http/cwd-validator.d.ts.map +1 -0
- package/dist/http/cwd-validator.js +115 -0
- package/dist/http/cwd-validator.js.map +1 -0
- package/dist/http/errors.d.ts +4 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +9 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/execution-context.d.ts +15 -0
- package/dist/http/execution-context.d.ts.map +1 -0
- package/dist/http/execution-context.js +35 -0
- package/dist/http/execution-context.js.map +1 -0
- package/dist/http/handler-deps.d.ts +16 -0
- package/dist/http/handler-deps.d.ts.map +1 -0
- package/dist/http/handler-deps.js +2 -0
- package/dist/http/handler-deps.js.map +1 -0
- package/dist/http/handlers/control/batch.d.ts +24 -0
- package/dist/http/handlers/control/batch.d.ts.map +1 -0
- package/dist/http/handlers/control/batch.js +81 -0
- package/dist/http/handlers/control/batch.js.map +1 -0
- package/dist/http/handlers/control/clarifications.d.ts +19 -0
- package/dist/http/handlers/control/clarifications.d.ts.map +1 -0
- package/dist/http/handlers/control/clarifications.js +58 -0
- package/dist/http/handlers/control/clarifications.js.map +1 -0
- package/dist/http/handlers/control/context-blocks.d.ts +22 -0
- package/dist/http/handlers/control/context-blocks.d.ts.map +1 -0
- package/dist/http/handlers/control/context-blocks.js +88 -0
- package/dist/http/handlers/control/context-blocks.js.map +1 -0
- package/dist/http/handlers/introspection/health.d.ts +13 -0
- package/dist/http/handlers/introspection/health.d.ts.map +1 -0
- package/dist/http/handlers/introspection/health.js +17 -0
- package/dist/http/handlers/introspection/health.js.map +1 -0
- package/dist/http/handlers/introspection/status.d.ts +26 -0
- package/dist/http/handlers/introspection/status.d.ts.map +1 -0
- package/dist/http/handlers/introspection/status.js +136 -0
- package/dist/http/handlers/introspection/status.js.map +1 -0
- package/dist/http/handlers/introspection/tools-list.d.ts +9 -0
- package/dist/http/handlers/introspection/tools-list.d.ts.map +1 -0
- package/dist/http/handlers/introspection/tools-list.js +28 -0
- package/dist/http/handlers/introspection/tools-list.js.map +1 -0
- package/dist/http/handlers/tools/audit.d.ts +4 -0
- package/dist/http/handlers/tools/audit.d.ts.map +1 -0
- package/dist/http/handlers/tools/audit.js +39 -0
- package/dist/http/handlers/tools/audit.js.map +1 -0
- package/dist/http/handlers/tools/debug.d.ts +4 -0
- package/dist/http/handlers/tools/debug.d.ts.map +1 -0
- package/dist/http/handlers/tools/debug.js +39 -0
- package/dist/http/handlers/tools/debug.js.map +1 -0
- package/dist/http/handlers/tools/delegate.d.ts +4 -0
- package/dist/http/handlers/tools/delegate.d.ts.map +1 -0
- package/dist/http/handlers/tools/delegate.js +57 -0
- package/dist/http/handlers/tools/delegate.js.map +1 -0
- package/dist/http/handlers/tools/execute-plan.d.ts +4 -0
- package/dist/http/handlers/tools/execute-plan.d.ts.map +1 -0
- package/dist/http/handlers/tools/execute-plan.js +39 -0
- package/dist/http/handlers/tools/execute-plan.js.map +1 -0
- package/dist/http/handlers/tools/retry.d.ts +4 -0
- package/dist/http/handlers/tools/retry.d.ts.map +1 -0
- package/dist/http/handlers/tools/retry.js +52 -0
- package/dist/http/handlers/tools/retry.js.map +1 -0
- package/dist/http/handlers/tools/review.d.ts +4 -0
- package/dist/http/handlers/tools/review.d.ts.map +1 -0
- package/dist/http/handlers/tools/review.js +39 -0
- package/dist/http/handlers/tools/review.js.map +1 -0
- package/dist/http/handlers/tools/verify.d.ts +4 -0
- package/dist/http/handlers/tools/verify.d.ts.map +1 -0
- package/dist/http/handlers/tools/verify.js +39 -0
- package/dist/http/handlers/tools/verify.js.map +1 -0
- package/dist/http/loopback.d.ts +17 -0
- package/dist/http/loopback.d.ts.map +1 -0
- package/dist/http/loopback.js +43 -0
- package/dist/http/loopback.js.map +1 -0
- package/dist/http/middleware/body-reader.d.ts +16 -0
- package/dist/http/middleware/body-reader.d.ts.map +1 -0
- package/dist/http/middleware/body-reader.js +44 -0
- package/dist/http/middleware/body-reader.js.map +1 -0
- package/dist/http/project-registry.d.ts +54 -0
- package/dist/http/project-registry.d.ts.map +1 -0
- package/dist/http/project-registry.js +132 -0
- package/dist/http/project-registry.js.map +1 -0
- package/dist/http/router.d.ts +14 -0
- package/dist/http/router.d.ts.map +1 -0
- package/dist/http/router.js +41 -0
- package/dist/http/router.js.map +1 -0
- package/dist/http/server.d.ts +16 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +235 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/types.d.ts +9 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/http/types.js +2 -0
- package/dist/http/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/install/claude-code.d.ts +43 -0
- package/dist/install/claude-code.d.ts.map +1 -0
- package/dist/install/claude-code.js +65 -0
- package/dist/install/claude-code.js.map +1 -0
- package/dist/install/codex-cli.d.ts +39 -0
- package/dist/install/codex-cli.d.ts.map +1 -0
- package/dist/install/codex-cli.js +318 -0
- package/dist/install/codex-cli.js.map +1 -0
- package/dist/install/cursor.d.ts +72 -0
- package/dist/install/cursor.d.ts.map +1 -0
- package/dist/install/cursor.js +81 -0
- package/dist/install/cursor.js.map +1 -0
- package/dist/install/gemini-cli.d.ts +66 -0
- package/dist/install/gemini-cli.d.ts.map +1 -0
- package/dist/install/gemini-cli.js +111 -0
- package/dist/install/gemini-cli.js.map +1 -0
- package/dist/install/include-utils.d.ts +27 -0
- package/dist/install/include-utils.d.ts.map +1 -0
- package/dist/install/include-utils.js +90 -0
- package/dist/install/include-utils.js.map +1 -0
- package/dist/install/manifest.d.ts +90 -0
- package/dist/install/manifest.d.ts.map +1 -0
- package/dist/install/manifest.js +200 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/openapi.d.ts +15 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +314 -0
- package/dist/openapi.js.map +1 -0
- package/dist/skills/_shared/auth.md +32 -0
- package/dist/skills/_shared/error-handling.md +31 -0
- package/dist/skills/_shared/polling.md +40 -0
- package/dist/skills/_shared/response-shape.md +46 -0
- package/dist/skills/mma-audit/SKILL.md +55 -0
- package/dist/skills/mma-clarifications/SKILL.md +68 -0
- package/dist/skills/mma-context-blocks/SKILL.md +69 -0
- package/dist/skills/mma-debug/SKILL.md +59 -0
- package/dist/skills/mma-delegate/SKILL.md +63 -0
- package/dist/skills/mma-execute-plan/SKILL.md +63 -0
- package/dist/skills/mma-retry/SKILL.md +54 -0
- package/dist/skills/mma-review/SKILL.md +55 -0
- package/dist/skills/mma-verify/SKILL.md +57 -0
- package/dist/skills/multi-model-agent/SKILL.md +55 -0
- package/package.json +60 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI skill writer for install-skill.
|
|
3
|
+
*
|
|
4
|
+
* Task 9.7 scope: write/install and remove/uninstall of skills to
|
|
5
|
+
* the Codex CLI's managed block in `<homeDir>/.codex/AGENTS.md`.
|
|
6
|
+
*
|
|
7
|
+
* Managed block delimiters:
|
|
8
|
+
* <!-- multi-model-agent:BEGIN -->
|
|
9
|
+
* ... skill content ...
|
|
10
|
+
* <!-- multi-model-agent:END -->
|
|
11
|
+
*
|
|
12
|
+
* Install behaviour:
|
|
13
|
+
* - If AGENTS.md does NOT exist: create it with just the managed block.
|
|
14
|
+
* - If AGENTS.md exists but has NO managed block markers: append the block
|
|
15
|
+
* at the end with a blank-line separator.
|
|
16
|
+
* - If AGENTS.md exists WITH managed block markers: replace the content
|
|
17
|
+
* between (and including) the markers with the new managed block.
|
|
18
|
+
* - User content OUTSIDE the markers is preserved verbatim.
|
|
19
|
+
*
|
|
20
|
+
* Uninstall behaviour:
|
|
21
|
+
* - Remove the managed block (including markers) from AGENTS.md.
|
|
22
|
+
* - If the file becomes empty or only whitespace after removal, delete the
|
|
23
|
+
* file.
|
|
24
|
+
* - If the file does not contain the markers, do nothing (no error).
|
|
25
|
+
* - If the file does not exist, do nothing (no error).
|
|
26
|
+
*
|
|
27
|
+
* @include resolution:
|
|
28
|
+
* Lines beginning with `@include _shared/<file>.md` are replaced with the
|
|
29
|
+
* file content read from `<skillsRoot>/_shared/<file>.md`. If the file
|
|
30
|
+
* cannot be read, a warning is written to stderr and the line is omitted
|
|
31
|
+
* from the output (the directive is NOT preserved — matching the Claude Code
|
|
32
|
+
* writer behaviour).
|
|
33
|
+
*
|
|
34
|
+
* Security: @include paths must begin with `_shared/` and are resolved
|
|
35
|
+
* strictly within `<skillsRoot>/_shared/` to prevent path traversal.
|
|
36
|
+
*
|
|
37
|
+
* @module
|
|
38
|
+
*/
|
|
39
|
+
import fs from 'node:fs';
|
|
40
|
+
import path from 'node:path';
|
|
41
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
42
|
+
const MANAGED_BEGIN = '<!-- multi-model-agent:BEGIN -->';
|
|
43
|
+
const MANAGED_END = '<!-- multi-model-agent:END -->';
|
|
44
|
+
// ─── Managed block helpers ────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Build the complete managed block string including delimiters.
|
|
47
|
+
*
|
|
48
|
+
* Block format:
|
|
49
|
+
* <!-- multi-model-agent:BEGIN -->
|
|
50
|
+
* <content>
|
|
51
|
+
* <!-- multi-model-agent:END -->
|
|
52
|
+
*
|
|
53
|
+
* Key newline rules:
|
|
54
|
+
* - BEGIN is always followed by `\n` so content starts on its own line.
|
|
55
|
+
* - The content line(s) end with a newline. If content doesn't already end
|
|
56
|
+
* with `\n`, we add one before END.
|
|
57
|
+
* - END is the final line of the block; no trailing newline is added after
|
|
58
|
+
* END (the block ends with the `>` character of the END tag).
|
|
59
|
+
*
|
|
60
|
+
* This means the block always ends with `MANAGED_END` as the last characters
|
|
61
|
+
* of the file (no trailing newline after it).
|
|
62
|
+
*/
|
|
63
|
+
function buildManagedBlock(inlinedContent) {
|
|
64
|
+
const contentEndsWithNewline = inlinedContent.endsWith('\n');
|
|
65
|
+
// Block format: BEGIN + "\n" + content (ending with \n) + END
|
|
66
|
+
// No trailing newline after END — callers join suffix directly.
|
|
67
|
+
return `${MANAGED_BEGIN}\n${inlinedContent}${contentEndsWithNewline ? '' : '\n'}${MANAGED_END}`;
|
|
68
|
+
}
|
|
69
|
+
// ─── @include inlining (private) ─────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* Inline `@include _shared/<path>` directives in `content`.
|
|
72
|
+
*
|
|
73
|
+
* Each line matching `@include _shared/<path>` is replaced with the full
|
|
74
|
+
* content of the corresponding file under `<skillsRoot>/_shared/<path>`.
|
|
75
|
+
*
|
|
76
|
+
* Security constraints:
|
|
77
|
+
* - Only paths beginning with `_shared/` are accepted.
|
|
78
|
+
* - The resolved path must stay within `<skillsRoot>/_shared/`. Path
|
|
79
|
+
* traversal attempts (e.g. `_shared/../secrets.txt`) are rejected with a
|
|
80
|
+
* warning and the directive line is dropped.
|
|
81
|
+
*
|
|
82
|
+
* Missing shared files:
|
|
83
|
+
* - A warning containing "missing shared file" is written to stderr.
|
|
84
|
+
* - The directive line is dropped (not preserved) — matching the Claude
|
|
85
|
+
* Code writer behaviour as required by the brief.
|
|
86
|
+
*
|
|
87
|
+
* @param skillName Used in warning messages to identify the skill.
|
|
88
|
+
* @param content Raw skill content (may contain @include directives).
|
|
89
|
+
* @param skillsRoot Root directory containing `_shared/`.
|
|
90
|
+
* @returns The content with directives inlined.
|
|
91
|
+
*/
|
|
92
|
+
function inlineIncludes(skillName, content, skillsRoot) {
|
|
93
|
+
const lines = content.split('\n');
|
|
94
|
+
const result = [];
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
const match = line.match(/^@include\s+(.+)$/);
|
|
97
|
+
if (!match) {
|
|
98
|
+
result.push(line);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Trim trailing whitespace from the path token.
|
|
102
|
+
const relativePath = match[1].trimEnd();
|
|
103
|
+
// Security: only accept paths beginning with `_shared/`
|
|
104
|
+
if (!relativePath.startsWith('_shared/')) {
|
|
105
|
+
process.stderr.write(`Warning: Codex CLI skill writer [${skillName}]: @include path must ` +
|
|
106
|
+
`start with "_shared/": ${relativePath}\n`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Security: reject path traversal attempts
|
|
110
|
+
const resolvedPath = path.resolve(skillsRoot, relativePath);
|
|
111
|
+
const sharedDir = path.resolve(skillsRoot, '_shared');
|
|
112
|
+
if (!resolvedPath.startsWith(sharedDir + path.sep) &&
|
|
113
|
+
resolvedPath !== sharedDir) {
|
|
114
|
+
process.stderr.write(`Warning: Codex CLI skill writer [${skillName}]: @include path ` +
|
|
115
|
+
`rejected (path traversal): ${relativePath}\n`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const sharedFilePath = path.join(skillsRoot, relativePath);
|
|
119
|
+
try {
|
|
120
|
+
const sharedContent = fs.readFileSync(sharedFilePath, 'utf-8');
|
|
121
|
+
result.push(sharedContent);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const nodeErr = err;
|
|
125
|
+
if (nodeErr.code === 'ENOENT') {
|
|
126
|
+
process.stderr.write(`Warning: Codex CLI skill writer [${skillName}]: missing shared ` +
|
|
127
|
+
`file: ${sharedFilePath} (referenced by @include ${relativePath})\n`);
|
|
128
|
+
// Directive is dropped — do not push anything.
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Permission errors, EISDIR, etc. — re-throw so the caller notices.
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return result.join('\n');
|
|
137
|
+
}
|
|
138
|
+
// ─── Marker helpers ────────────────────────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* Returns true only when both managed block markers are present in
|
|
141
|
+
* `content` and BEGIN appears before END. An orphan marker or corrupt
|
|
142
|
+
* ordering does NOT constitute a valid block.
|
|
143
|
+
*/
|
|
144
|
+
function hasManagedBlock(content) {
|
|
145
|
+
const beginIdx = content.indexOf(MANAGED_BEGIN);
|
|
146
|
+
const endIdx = content.indexOf(MANAGED_END);
|
|
147
|
+
return beginIdx !== -1 && endIdx !== -1 && beginIdx < endIdx;
|
|
148
|
+
}
|
|
149
|
+
// ─── Core write logic ─────────────────────────────────────────────────────────
|
|
150
|
+
/**
|
|
151
|
+
* Find the span of the existing managed block (including both markers) in
|
|
152
|
+
* `content`. Returns `{ prefix, suffix }` where `prefix` is everything
|
|
153
|
+
* before BEGIN and `suffix` is everything after the block's final character.
|
|
154
|
+
*
|
|
155
|
+
* The block always ends with `MANAGED_END` (no trailing newline after END).
|
|
156
|
+
* After END, there may be a structural newline (the newline that terminates
|
|
157
|
+
* the END line). This structural newline is NOT part of the user suffix — it
|
|
158
|
+
* belongs to the block boundary and must not appear as an extra blank line
|
|
159
|
+
* when prefix and suffix are joined.
|
|
160
|
+
*
|
|
161
|
+
* Edge cases:
|
|
162
|
+
* - If END is the last character of the file (no trailing newline), suffix
|
|
163
|
+
* is empty.
|
|
164
|
+
* - If BEGIN and END are in corrupt order (END before BEGIN), returns null
|
|
165
|
+
* so no user content is inadvertently destroyed.
|
|
166
|
+
*/
|
|
167
|
+
function splitAroundBlock(content) {
|
|
168
|
+
const beginIdx = content.indexOf(MANAGED_BEGIN);
|
|
169
|
+
const endIdx = content.indexOf(MANAGED_END);
|
|
170
|
+
// No valid block without both markers
|
|
171
|
+
if (beginIdx === -1 || endIdx === -1) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
// Corrupt order: END before BEGIN — treat as no valid block to protect
|
|
175
|
+
// user content from inadvertent removal.
|
|
176
|
+
if (beginIdx > endIdx) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
// suffix starts immediately after MANAGED_END (includes any trailing newlines).
|
|
180
|
+
// Callers decide how to handle the leading newlines in suffix:
|
|
181
|
+
// - Install (replace): suffix begins with the separator between END and
|
|
182
|
+
// following user content; joining block + suffix reconstructs the file.
|
|
183
|
+
// - Uninstall: strip all leading newlines from suffix before joining.
|
|
184
|
+
const suffixStart = endIdx + MANAGED_END.length;
|
|
185
|
+
return {
|
|
186
|
+
prefix: content.slice(0, beginIdx),
|
|
187
|
+
suffix: content.slice(suffixStart),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Write (or overwrite) the managed block in the Codex CLI's AGENTS.md.
|
|
192
|
+
*
|
|
193
|
+
* The algorithm:
|
|
194
|
+
* 1. Inline @include directives.
|
|
195
|
+
* 2. Read existing file if present.
|
|
196
|
+
* 3. Determine new content: create / append / replace.
|
|
197
|
+
* 4. Write to disk.
|
|
198
|
+
*
|
|
199
|
+
* @throws If the AGENTS.md file exists but cannot be read.
|
|
200
|
+
* @throws If the AGENTS.md path is a directory.
|
|
201
|
+
*/
|
|
202
|
+
export function installCodexCli(opts) {
|
|
203
|
+
const agentsPath = path.join(opts.homeDir, '.codex', 'AGENTS.md');
|
|
204
|
+
// 1. Inline @include directives
|
|
205
|
+
const inlinedContent = inlineIncludes(opts.skillName, opts.content, opts.skillsRoot);
|
|
206
|
+
const block = buildManagedBlock(inlinedContent);
|
|
207
|
+
// 2. Read existing file
|
|
208
|
+
let existingContent;
|
|
209
|
+
let fileExists = false;
|
|
210
|
+
try {
|
|
211
|
+
const stat = fs.statSync(agentsPath);
|
|
212
|
+
if (stat.isDirectory()) {
|
|
213
|
+
throw new Error(`AGENTS.md path is a directory: ${agentsPath}`);
|
|
214
|
+
}
|
|
215
|
+
existingContent = fs.readFileSync(agentsPath, 'utf-8');
|
|
216
|
+
fileExists = true;
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (err.code === 'ENOENT') {
|
|
220
|
+
existingContent = '';
|
|
221
|
+
fileExists = false;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 3. Determine new content
|
|
228
|
+
let newContent;
|
|
229
|
+
if (!fileExists) {
|
|
230
|
+
// Case A: file does not exist → create with just the managed block
|
|
231
|
+
newContent = block;
|
|
232
|
+
}
|
|
233
|
+
else if (!hasManagedBlock(existingContent)) {
|
|
234
|
+
// Case B: file exists but no managed block → append with blank-line separator
|
|
235
|
+
//
|
|
236
|
+
// The block begins with BEGIN + "\n". To create exactly one blank line
|
|
237
|
+
// between existing content and the BEGIN marker:
|
|
238
|
+
// - If existingContent ends with "\n": add one "\n" → existing ends with
|
|
239
|
+
// "\n", block starts with "\n" → two newlines = one blank line. ✓
|
|
240
|
+
// - If existingContent does NOT end with "\n": add "\n\n" → existing ends
|
|
241
|
+
// with "\n", block starts with "\n" → two newlines = one blank line. ✓
|
|
242
|
+
newContent = existingContent.endsWith('\n')
|
|
243
|
+
? `${existingContent}\n${block}`
|
|
244
|
+
: `${existingContent}\n\n${block}`;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Case C: file exists with managed block → replace it
|
|
248
|
+
const split = splitAroundBlock(existingContent);
|
|
249
|
+
if (split === null) {
|
|
250
|
+
// Both markers present but split returned null — corrupt marker order.
|
|
251
|
+
// Do not overwrite; append with separator instead.
|
|
252
|
+
newContent = existingContent.endsWith('\n')
|
|
253
|
+
? `${existingContent}\n${block}`
|
|
254
|
+
: `${existingContent}\n\n${block}`;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const { prefix, suffix } = split;
|
|
258
|
+
newContent = `${prefix}${block}${suffix}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// 4. Write to disk
|
|
262
|
+
fs.mkdirSync(path.join(opts.homeDir, '.codex'), { recursive: true });
|
|
263
|
+
fs.writeFileSync(agentsPath, newContent, 'utf-8');
|
|
264
|
+
}
|
|
265
|
+
// ─── Uninstall ────────────────────────────────────────────────────────────────
|
|
266
|
+
/**
|
|
267
|
+
* Remove the managed block (including markers) from AGENTS.md.
|
|
268
|
+
*
|
|
269
|
+
* Behaviour:
|
|
270
|
+
* - File does not exist → no-op.
|
|
271
|
+
* - File has no managed block markers → no-op (file unchanged).
|
|
272
|
+
* - File has managed block → remove it; write remaining content back.
|
|
273
|
+
* - Remaining content is empty or whitespace-only → delete the file.
|
|
274
|
+
*
|
|
275
|
+
* @throws On filesystem errors other than ENOENT when reading/writing.
|
|
276
|
+
*/
|
|
277
|
+
export function uninstallCodexCli(homeDir) {
|
|
278
|
+
const agentsPath = path.join(homeDir, '.codex', 'AGENTS.md');
|
|
279
|
+
let content;
|
|
280
|
+
try {
|
|
281
|
+
content = fs.readFileSync(agentsPath, 'utf-8');
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
if (err.code === 'ENOENT') {
|
|
285
|
+
// File does not exist — no-op.
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// Re-throw on read errors, preserving the original error context.
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
if (!hasManagedBlock(content)) {
|
|
292
|
+
// No managed block to remove — file unchanged.
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const split = splitAroundBlock(content);
|
|
296
|
+
if (split === null) {
|
|
297
|
+
// Both markers present but split returned null — corrupt marker order.
|
|
298
|
+
// No safe block to remove — leave file unchanged.
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const { prefix, suffix } = split;
|
|
302
|
+
// Strip all leading newlines from suffix. The suffix begins with any
|
|
303
|
+
// newlines that followed MANAGED_END (including blank-line separators).
|
|
304
|
+
// Removing them avoids leaving orphan blank lines after the managed block
|
|
305
|
+
// is gone. The prefix already ends before BEGIN (including any trailing
|
|
306
|
+
// newlines the user placed before the block), so this does not disturb
|
|
307
|
+
// user-authored spacing in the prefix.
|
|
308
|
+
const trimmedSuffix = suffix.replace(/^\n+/, '');
|
|
309
|
+
const newContent = `${prefix}${trimmedSuffix}`;
|
|
310
|
+
if (newContent.trim() === '') {
|
|
311
|
+
// File is empty after removal — delete it.
|
|
312
|
+
fs.unlinkSync(agentsPath);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
fs.writeFileSync(agentsPath, newContent, 'utf-8');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=codex-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-cli.js","sourceRoot":"","sources":["../../src/install/codex-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for installing a Cursor skill.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: `homeDir` is accepted for API parity with other skill writers
|
|
5
|
+
* (Claude Code, Gemini CLI, etc.) but is intentionally not used by this
|
|
6
|
+
* writer. The Cursor rules target path is always CWD-relative under
|
|
7
|
+
* `<cwd>/.cursor/rules/`, not homeDir-relative. This is intentional to
|
|
8
|
+
* keep Cursor rules scoped to the project directory.
|
|
9
|
+
*/
|
|
10
|
+
export interface CursorInstallOpts {
|
|
11
|
+
/**
|
|
12
|
+
* Raw skill content. May contain `@include _shared/<file>.md` directives
|
|
13
|
+
* which are inlined before writing.
|
|
14
|
+
*/
|
|
15
|
+
content: string;
|
|
16
|
+
/**
|
|
17
|
+
* Working directory — replaces `process.cwd()`.
|
|
18
|
+
* Used to construct the CWD-relative target path:
|
|
19
|
+
* `<cwd>/.cursor/rules/multi-model-agent.mdc`
|
|
20
|
+
*/
|
|
21
|
+
cwd: string;
|
|
22
|
+
/**
|
|
23
|
+
* Home directory. Accepted for API signature compatibility with other
|
|
24
|
+
* skill writers, but NOT used by this writer. Cursor rules are always
|
|
25
|
+
* written relative to `cwd`, not `homeDir`.
|
|
26
|
+
*/
|
|
27
|
+
homeDir: string;
|
|
28
|
+
/**
|
|
29
|
+
* Where shared files live. The writer reads `<skillsRoot>/_shared/<path>`
|
|
30
|
+
* when inlining `@include` directives.
|
|
31
|
+
*/
|
|
32
|
+
skillsRoot: string;
|
|
33
|
+
/**
|
|
34
|
+
* If true, overwrite the existing file. If false (default), skip writing
|
|
35
|
+
* when the file already exists.
|
|
36
|
+
*/
|
|
37
|
+
force?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Result of `installCursor`.
|
|
41
|
+
*/
|
|
42
|
+
export interface CursorInstallResult {
|
|
43
|
+
/**
|
|
44
|
+
* `true` if the file was written, `false` if it was skipped because it
|
|
45
|
+
* already exists and `force` was not set.
|
|
46
|
+
*/
|
|
47
|
+
written: boolean;
|
|
48
|
+
/** The full path that was (or would have been) written. */
|
|
49
|
+
targetPath: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Install the multi-model-agent skill for Cursor.
|
|
53
|
+
*
|
|
54
|
+
* - Target path is `<cwd>/.cursor/rules/multi-model-agent.mdc` (CWD-relative).
|
|
55
|
+
* - Creates `<cwd>/.cursor/rules/` if it does not exist.
|
|
56
|
+
* - Inlines `@include` directives before writing (see `inlineIncludes`).
|
|
57
|
+
* - If the file already exists and `force` is not set, skips writing and
|
|
58
|
+
* returns `written: false`.
|
|
59
|
+
*
|
|
60
|
+
* @param opts Installation options (see `CursorInstallOpts`).
|
|
61
|
+
*/
|
|
62
|
+
export declare function installCursor(opts: CursorInstallOpts): CursorInstallResult;
|
|
63
|
+
/**
|
|
64
|
+
* Uninstall the multi-model-agent Cursor skill.
|
|
65
|
+
*
|
|
66
|
+
* Removes `<cwd>/.cursor/rules/multi-model-agent.mdc`.
|
|
67
|
+
* This is a no-op when the file does not exist (no error is thrown).
|
|
68
|
+
*
|
|
69
|
+
* @param cwd Working directory (replaces `process.cwd()`).
|
|
70
|
+
*/
|
|
71
|
+
export declare function uninstallCursor(cwd: string): void;
|
|
72
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/install/cursor.ts"],"names":[],"mappings":"AA8BA;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,CAmB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAmBjD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor skill writer.
|
|
3
|
+
*
|
|
4
|
+
* Writes the skill content to `<cwd>/.cursor/rules/multi-model-agent.mdc`.
|
|
5
|
+
*
|
|
6
|
+
* Before writing, inlines any `@include _shared/<file>.md` directives found in
|
|
7
|
+
* the content. The directive line is replaced with the full content of
|
|
8
|
+
* the corresponding shared file sourced from `<skillsRoot>/_shared/<path>`.
|
|
9
|
+
* The `@include` directive is NOT preserved in the written file.
|
|
10
|
+
*
|
|
11
|
+
* If a referenced shared file is missing (ENOENT):
|
|
12
|
+
* - A warning is logged to stderr.
|
|
13
|
+
* - The include line is removed from the output (not preserved).
|
|
14
|
+
* - Processing continues for remaining directives.
|
|
15
|
+
*
|
|
16
|
+
* Security constraints on `@include` directives:
|
|
17
|
+
* - Only paths beginning with `_shared/` are accepted.
|
|
18
|
+
* - The resolved path must stay within `<skillsRoot>/_shared/`.
|
|
19
|
+
* Path traversal attempts (e.g. `_shared/../secrets.txt`) are rejected
|
|
20
|
+
* with a warning and the directive line is dropped.
|
|
21
|
+
* - Non-`_shared/` paths are rejected with a warning and the directive
|
|
22
|
+
* line is dropped.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import { inlineIncludes } from './include-utils.js';
|
|
29
|
+
/**
|
|
30
|
+
* Install the multi-model-agent skill for Cursor.
|
|
31
|
+
*
|
|
32
|
+
* - Target path is `<cwd>/.cursor/rules/multi-model-agent.mdc` (CWD-relative).
|
|
33
|
+
* - Creates `<cwd>/.cursor/rules/` if it does not exist.
|
|
34
|
+
* - Inlines `@include` directives before writing (see `inlineIncludes`).
|
|
35
|
+
* - If the file already exists and `force` is not set, skips writing and
|
|
36
|
+
* returns `written: false`.
|
|
37
|
+
*
|
|
38
|
+
* @param opts Installation options (see `CursorInstallOpts`).
|
|
39
|
+
*/
|
|
40
|
+
export function installCursor(opts) {
|
|
41
|
+
const { content, cwd, skillsRoot, force } = opts;
|
|
42
|
+
const targetPath = path.join(cwd, '.cursor', 'rules', 'multi-model-agent.mdc');
|
|
43
|
+
if (!force && fs.existsSync(targetPath)) {
|
|
44
|
+
process.stderr.write(`Warning: Cursor skill writer: file already exists: ${targetPath} — skipping (use force: true to overwrite)\n`);
|
|
45
|
+
return { written: false, targetPath };
|
|
46
|
+
}
|
|
47
|
+
const rulesDir = path.join(cwd, '.cursor', 'rules');
|
|
48
|
+
fs.mkdirSync(rulesDir, { recursive: true, mode: 0o700 });
|
|
49
|
+
const finalContent = inlineIncludes('Cursor skill writer', content, skillsRoot);
|
|
50
|
+
fs.writeFileSync(targetPath, finalContent, 'utf-8');
|
|
51
|
+
return { written: true, targetPath };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Uninstall the multi-model-agent Cursor skill.
|
|
55
|
+
*
|
|
56
|
+
* Removes `<cwd>/.cursor/rules/multi-model-agent.mdc`.
|
|
57
|
+
* This is a no-op when the file does not exist (no error is thrown).
|
|
58
|
+
*
|
|
59
|
+
* @param cwd Working directory (replaces `process.cwd()`).
|
|
60
|
+
*/
|
|
61
|
+
export function uninstallCursor(cwd) {
|
|
62
|
+
const targetPath = path.join(cwd, '.cursor', 'rules', 'multi-model-agent.mdc');
|
|
63
|
+
// Only remove the file if it exists and is a file (not a directory).
|
|
64
|
+
// This is a no-op when the path does not exist.
|
|
65
|
+
try {
|
|
66
|
+
const stat = fs.statSync(targetPath);
|
|
67
|
+
if (stat.isFile()) {
|
|
68
|
+
fs.unlinkSync(targetPath);
|
|
69
|
+
}
|
|
70
|
+
// If it's a directory or symlink to directory, do nothing — the brief
|
|
71
|
+
// specifies removing a file, not a directory tree.
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const nodeErr = err;
|
|
75
|
+
// ENOENT means the file doesn't exist — that's fine, we were asked to remove it
|
|
76
|
+
if (nodeErr.code !== 'ENOENT') {
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/install/cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,IAAuB;IACnD,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAEjD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;IAE/E,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sDAAsD,UAAU,8CAA8C,CAC/G,CAAC;QACF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IACxC,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACpD,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEzD,MAAM,YAAY,GAAG,cAAc,CAAC,qBAAqB,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAChF,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAEpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;IAE/E,qEAAqE;IACrE,gDAAgD;IAChD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YAClB,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC5B,CAAC;QACD,sEAAsE;QACtE,mDAAmD;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAA4B,CAAC;QAC7C,gFAAgF;QAChF,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for installing a skill via the Gemini CLI writer.
|
|
3
|
+
*/
|
|
4
|
+
export interface GeminiCliInstallOpts {
|
|
5
|
+
/** Skill name (currently informational; writes always go to multi-model-agent extension). */
|
|
6
|
+
skillName: string;
|
|
7
|
+
/**
|
|
8
|
+
* Raw SKILL.md content. May contain `@include _shared/<file>.md` directives
|
|
9
|
+
* which are inlined before writing.
|
|
10
|
+
*/
|
|
11
|
+
content: string;
|
|
12
|
+
/**
|
|
13
|
+
* Version string for the extension manifest's `version` field.
|
|
14
|
+
*/
|
|
15
|
+
skillVersion: string;
|
|
16
|
+
/**
|
|
17
|
+
* The "home directory" that replaces `os.homedir()`.
|
|
18
|
+
* Must NOT default to `os.homedir()` — always required explicitly.
|
|
19
|
+
*/
|
|
20
|
+
homeDir: string;
|
|
21
|
+
/**
|
|
22
|
+
* Where shared files live. The writer reads `<skillsRoot>/_shared/<file>.md`
|
|
23
|
+
* when inlining `@include` directives.
|
|
24
|
+
*/
|
|
25
|
+
skillsRoot: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Inline `@include _shared/<file>.md` directives in `content`.
|
|
29
|
+
*
|
|
30
|
+
* Each line matching `@include <path>` (space after `@include`) is replaced with
|
|
31
|
+
* the full content of `<skillsRoot>/_shared/<path>`.
|
|
32
|
+
*
|
|
33
|
+
* If a shared file is missing:
|
|
34
|
+
* - A warning is written to stderr.
|
|
35
|
+
* - The include line is removed from the output (not preserved).
|
|
36
|
+
* - Processing continues for remaining directives.
|
|
37
|
+
*
|
|
38
|
+
* @param content Raw SKILL.md content (may contain @include directives).
|
|
39
|
+
* @param skillsRoot Root directory containing `_shared/` sub-directory.
|
|
40
|
+
* @returns The content with directives inlined.
|
|
41
|
+
*/
|
|
42
|
+
export declare function inlineIncludes(content: string, skillsRoot: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Install a skill to the Gemini CLI extensions directory.
|
|
45
|
+
*
|
|
46
|
+
* Writes two files into `<homeDir>/.gemini/extensions/multi-model-agent/`:
|
|
47
|
+
* 1. `gemini-extension.json` — the extension manifest
|
|
48
|
+
* 2. `SKILL.md` — the skill content with @include directives inlined
|
|
49
|
+
*
|
|
50
|
+
* The directory (and any parent directories) are created with mode `0o700`.
|
|
51
|
+
* Calling this function multiple times overwrites the previous installation
|
|
52
|
+
* (idempotent).
|
|
53
|
+
*
|
|
54
|
+
* @param opts Installation options (see `GeminiCliInstallOpts`).
|
|
55
|
+
*/
|
|
56
|
+
export declare function installGeminiCli(opts: GeminiCliInstallOpts): void;
|
|
57
|
+
/**
|
|
58
|
+
* Uninstall the multi-model-agent Gemini CLI extension.
|
|
59
|
+
*
|
|
60
|
+
* Recursively removes `<homeDir>/.gemini/extensions/multi-model-agent/`.
|
|
61
|
+
* This is a no-op when the directory does not exist (no error is thrown).
|
|
62
|
+
*
|
|
63
|
+
* @param homeDir The "home directory" that replaces `os.homedir()`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function uninstallGeminiCli(homeDir: string): void;
|
|
66
|
+
//# sourceMappingURL=gemini-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-cli.d.ts","sourceRoot":"","sources":["../../src/install/gemini-cli.ts"],"names":[],"mappings":"AAwBA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CA6B1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAuBjE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAKxD"}
|