@morphllm/morph-setup 1.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/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @morphllm/morph-setup
2
+
3
+ One-line installer for Morph MCP and bundled skills.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx -y @morphllm/morph-setup
9
+ ```
10
+
11
+ The CLI will guide you through selecting which apps to install to.
12
+
13
+ ### With API Key
14
+
15
+ ```bash
16
+ npx -y @morphllm/morph-setup --morph-api-key <your-key>
17
+ ```
18
+
19
+ Or set the environment variable:
20
+
21
+ ```bash
22
+ MORPH_API_KEY=<your-key> npx -y @morphllm/morph-setup
23
+ ```
24
+
25
+ ### Non-interactive
26
+
27
+ ```bash
28
+ npx -y @morphllm/morph-setup -y
29
+ ```
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,1107 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk from "chalk";
6
+ import * as p from "@clack/prompts";
7
+ import path3 from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { createRequire } from "module";
10
+
11
+ // src/source-parser.ts
12
+ import { isAbsolute, resolve } from "path";
13
+ function isLocalPath(input) {
14
+ return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || // Windows absolute paths like C:\ or D:\
15
+ /^[a-zA-Z]:[/\\]/.test(input);
16
+ }
17
+ function isDirectSkillUrl(input) {
18
+ if (!input.startsWith("http://") && !input.startsWith("https://")) {
19
+ return false;
20
+ }
21
+ if (!input.toLowerCase().endsWith("/skill.md")) {
22
+ return false;
23
+ }
24
+ if (input.includes("github.com/") && !input.includes("raw.githubusercontent.com")) {
25
+ if (!input.includes("/blob/") && !input.includes("/raw/")) {
26
+ return false;
27
+ }
28
+ }
29
+ if (input.includes("gitlab.com/") && !input.includes("/-/raw/")) {
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ function parseSource(input) {
35
+ if (isLocalPath(input)) {
36
+ const resolvedPath = resolve(input);
37
+ return {
38
+ type: "local",
39
+ url: resolvedPath,
40
+ // Store resolved path in url for consistency
41
+ localPath: resolvedPath
42
+ };
43
+ }
44
+ if (isDirectSkillUrl(input)) {
45
+ return {
46
+ type: "direct-url",
47
+ url: input
48
+ };
49
+ }
50
+ const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
51
+ if (githubTreeWithPathMatch) {
52
+ const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
53
+ return {
54
+ type: "github",
55
+ url: `https://github.com/${owner}/${repo}.git`,
56
+ ref,
57
+ subpath
58
+ };
59
+ }
60
+ const githubTreeMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
61
+ if (githubTreeMatch) {
62
+ const [, owner, repo, ref] = githubTreeMatch;
63
+ return {
64
+ type: "github",
65
+ url: `https://github.com/${owner}/${repo}.git`,
66
+ ref
67
+ };
68
+ }
69
+ const githubRepoMatch = input.match(/github\.com\/([^/]+)\/([^/]+)/);
70
+ if (githubRepoMatch) {
71
+ const [, owner, repo] = githubRepoMatch;
72
+ const cleanRepo = repo.replace(/\.git$/, "");
73
+ return {
74
+ type: "github",
75
+ url: `https://github.com/${owner}/${cleanRepo}.git`
76
+ };
77
+ }
78
+ const gitlabTreeWithPathMatch = input.match(
79
+ /gitlab\.com\/([^/]+)\/([^/]+)\/-\/tree\/([^/]+)\/(.+)/
80
+ );
81
+ if (gitlabTreeWithPathMatch) {
82
+ const [, owner, repo, ref, subpath] = gitlabTreeWithPathMatch;
83
+ return {
84
+ type: "gitlab",
85
+ url: `https://gitlab.com/${owner}/${repo}.git`,
86
+ ref,
87
+ subpath
88
+ };
89
+ }
90
+ const gitlabTreeMatch = input.match(/gitlab\.com\/([^/]+)\/([^/]+)\/-\/tree\/([^/]+)$/);
91
+ if (gitlabTreeMatch) {
92
+ const [, owner, repo, ref] = gitlabTreeMatch;
93
+ return {
94
+ type: "gitlab",
95
+ url: `https://gitlab.com/${owner}/${repo}.git`,
96
+ ref
97
+ };
98
+ }
99
+ const gitlabRepoMatch = input.match(/gitlab\.com\/([^/]+)\/([^/]+)/);
100
+ if (gitlabRepoMatch) {
101
+ const [, owner, repo] = gitlabRepoMatch;
102
+ const cleanRepo = repo.replace(/\.git$/, "");
103
+ return {
104
+ type: "gitlab",
105
+ url: `https://gitlab.com/${owner}/${cleanRepo}.git`
106
+ };
107
+ }
108
+ const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
109
+ if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
110
+ const [, owner, repo, subpath] = shorthandMatch;
111
+ return {
112
+ type: "github",
113
+ url: `https://github.com/${owner}/${repo}.git`,
114
+ subpath
115
+ };
116
+ }
117
+ return {
118
+ type: "git",
119
+ url: input
120
+ };
121
+ }
122
+
123
+ // src/git.ts
124
+ import simpleGit from "simple-git";
125
+ import { join, normalize, resolve as resolve2, sep } from "path";
126
+ import { mkdtemp, rm } from "fs/promises";
127
+ import { tmpdir } from "os";
128
+ async function cloneRepo(url, ref) {
129
+ const tempDir = await mkdtemp(join(tmpdir(), "add-skill-"));
130
+ const git = simpleGit();
131
+ const cloneOptions = ref ? ["--depth", "1", "--branch", ref] : ["--depth", "1"];
132
+ await git.clone(url, tempDir, cloneOptions);
133
+ return tempDir;
134
+ }
135
+ async function cleanupTempDir(dir) {
136
+ const normalizedDir = normalize(resolve2(dir));
137
+ const normalizedTmpDir = normalize(resolve2(tmpdir()));
138
+ if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) {
139
+ throw new Error("Attempted to clean up directory outside of temp directory");
140
+ }
141
+ await rm(dir, { recursive: true, force: true });
142
+ }
143
+
144
+ // src/skills.ts
145
+ import { readdir, readFile, stat } from "fs/promises";
146
+ import { join as join2, basename, dirname } from "path";
147
+ import matter from "gray-matter";
148
+ var SKIP_DIRS = ["node_modules", ".git", "dist", "build", "__pycache__"];
149
+ async function hasSkillMd(dir) {
150
+ try {
151
+ const skillPath = join2(dir, "SKILL.md");
152
+ const stats = await stat(skillPath);
153
+ return stats.isFile();
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+ async function parseSkillMd(skillMdPath) {
159
+ try {
160
+ const content = await readFile(skillMdPath, "utf-8");
161
+ const { data } = matter(content);
162
+ if (!data.name || !data.description) {
163
+ return null;
164
+ }
165
+ return {
166
+ name: data.name,
167
+ description: data.description,
168
+ path: dirname(skillMdPath),
169
+ rawContent: content,
170
+ metadata: data.metadata
171
+ };
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+ async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
177
+ const skillDirs = [];
178
+ if (depth > maxDepth) return skillDirs;
179
+ try {
180
+ if (await hasSkillMd(dir)) {
181
+ skillDirs.push(dir);
182
+ }
183
+ const entries = await readdir(dir, { withFileTypes: true });
184
+ for (const entry of entries) {
185
+ if ((entry.isDirectory() || entry.isSymbolicLink()) && !SKIP_DIRS.includes(entry.name)) {
186
+ const subDirs = await findSkillDirs(join2(dir, entry.name), depth + 1, maxDepth);
187
+ skillDirs.push(...subDirs);
188
+ }
189
+ }
190
+ } catch {
191
+ }
192
+ return skillDirs;
193
+ }
194
+ async function discoverSkills(basePath, subpath) {
195
+ const skills = [];
196
+ const seenNames = /* @__PURE__ */ new Set();
197
+ const searchPath = subpath ? join2(basePath, subpath) : basePath;
198
+ if (await hasSkillMd(searchPath)) {
199
+ const skill = await parseSkillMd(join2(searchPath, "SKILL.md"));
200
+ if (skill) {
201
+ skills.push(skill);
202
+ return skills;
203
+ }
204
+ }
205
+ const prioritySearchDirs = [
206
+ searchPath,
207
+ join2(searchPath, "skills"),
208
+ join2(searchPath, "skills/.curated"),
209
+ join2(searchPath, "skills/.experimental"),
210
+ join2(searchPath, "skills/.system"),
211
+ join2(searchPath, ".agent/skills"),
212
+ join2(searchPath, ".agents/skills"),
213
+ join2(searchPath, ".claude/skills"),
214
+ join2(searchPath, ".cline/skills"),
215
+ join2(searchPath, ".codex/skills"),
216
+ join2(searchPath, ".commandcode/skills"),
217
+ join2(searchPath, ".cursor/skills"),
218
+ join2(searchPath, ".github/skills"),
219
+ join2(searchPath, ".goose/skills"),
220
+ join2(searchPath, ".kilocode/skills"),
221
+ join2(searchPath, ".kiro/skills"),
222
+ join2(searchPath, ".neovate/skills"),
223
+ join2(searchPath, ".opencode/skills"),
224
+ join2(searchPath, ".openhands/skills"),
225
+ join2(searchPath, ".pi/skills"),
226
+ join2(searchPath, ".qoder/skills"),
227
+ join2(searchPath, ".roo/skills"),
228
+ join2(searchPath, ".trae/skills"),
229
+ join2(searchPath, ".windsurf/skills"),
230
+ join2(searchPath, ".zencoder/skills")
231
+ ];
232
+ for (const dir of prioritySearchDirs) {
233
+ try {
234
+ const entries = await readdir(dir, { withFileTypes: true });
235
+ for (const entry of entries) {
236
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
237
+ const skillDir = join2(dir, entry.name);
238
+ if (await hasSkillMd(skillDir)) {
239
+ const skill = await parseSkillMd(join2(skillDir, "SKILL.md"));
240
+ if (skill && !seenNames.has(skill.name)) {
241
+ skills.push(skill);
242
+ seenNames.add(skill.name);
243
+ }
244
+ }
245
+ }
246
+ }
247
+ } catch {
248
+ }
249
+ }
250
+ if (skills.length === 0) {
251
+ const allSkillDirs = await findSkillDirs(searchPath);
252
+ for (const skillDir of allSkillDirs) {
253
+ const skill = await parseSkillMd(join2(skillDir, "SKILL.md"));
254
+ if (skill && !seenNames.has(skill.name)) {
255
+ skills.push(skill);
256
+ seenNames.add(skill.name);
257
+ }
258
+ }
259
+ }
260
+ return skills;
261
+ }
262
+ function getSkillDisplayName(skill) {
263
+ return skill.name || basename(skill.path);
264
+ }
265
+
266
+ // src/installer.ts
267
+ import { mkdir, cp, readdir as readdir2, symlink, lstat, rm as rm2, readlink, writeFile } from "fs/promises";
268
+ import { join as join4, basename as basename2, normalize as normalize2, resolve as resolve3, sep as sep2, relative } from "path";
269
+ import { homedir as homedir2, platform } from "os";
270
+
271
+ // src/agents.ts
272
+ import { homedir } from "os";
273
+ import { join as join3 } from "path";
274
+ import { existsSync } from "fs";
275
+ var home = homedir();
276
+ var agents = {
277
+ amp: {
278
+ name: "amp",
279
+ displayName: "Amp",
280
+ skillsDir: ".agents/skills",
281
+ globalSkillsDir: join3(home, ".config/agents/skills"),
282
+ detectInstalled: async () => {
283
+ return existsSync(join3(home, ".config/amp"));
284
+ }
285
+ },
286
+ antigravity: {
287
+ name: "antigravity",
288
+ displayName: "Antigravity",
289
+ skillsDir: ".agent/skills",
290
+ globalSkillsDir: join3(home, ".gemini/antigravity/skills"),
291
+ detectInstalled: async () => {
292
+ return existsSync(join3(process.cwd(), ".agent")) || existsSync(join3(home, ".gemini/antigravity"));
293
+ }
294
+ },
295
+ "claude-code": {
296
+ name: "claude-code",
297
+ displayName: "Claude Code",
298
+ skillsDir: ".claude/skills",
299
+ globalSkillsDir: join3(home, ".claude/skills"),
300
+ detectInstalled: async () => {
301
+ return existsSync(join3(home, ".claude"));
302
+ }
303
+ },
304
+ codex: {
305
+ name: "codex",
306
+ displayName: "Codex",
307
+ skillsDir: ".codex/skills",
308
+ globalSkillsDir: join3(home, ".codex/skills"),
309
+ detectInstalled: async () => {
310
+ return existsSync(join3(home, ".codex"));
311
+ }
312
+ },
313
+ cursor: {
314
+ name: "cursor",
315
+ displayName: "Cursor",
316
+ skillsDir: ".cursor/skills",
317
+ globalSkillsDir: join3(home, ".cursor/skills"),
318
+ detectInstalled: async () => {
319
+ return existsSync(join3(home, ".cursor"));
320
+ }
321
+ },
322
+ opencode: {
323
+ name: "opencode",
324
+ displayName: "OpenCode",
325
+ skillsDir: ".opencode/skills",
326
+ globalSkillsDir: join3(home, ".config/opencode/skills"),
327
+ detectInstalled: async () => {
328
+ return existsSync(join3(home, ".config/opencode")) || existsSync(join3(home, ".claude/skills"));
329
+ }
330
+ },
331
+ windsurf: {
332
+ name: "windsurf",
333
+ displayName: "Windsurf",
334
+ skillsDir: ".windsurf/skills",
335
+ globalSkillsDir: join3(home, ".codeium/windsurf/skills"),
336
+ detectInstalled: async () => {
337
+ return existsSync(join3(home, ".codeium/windsurf"));
338
+ }
339
+ }
340
+ };
341
+ async function detectInstalledAgents() {
342
+ const installed = [];
343
+ for (const [type, config] of Object.entries(agents)) {
344
+ if (await config.detectInstalled()) {
345
+ installed.push(type);
346
+ }
347
+ }
348
+ return installed;
349
+ }
350
+
351
+ // src/installer.ts
352
+ var AGENTS_DIR = ".agents";
353
+ var SKILLS_SUBDIR = "skills";
354
+ var AGENTS_THAT_SHOULD_NOT_GET_SYMLINKS = /* @__PURE__ */ new Set(["cursor"]);
355
+ function sanitizeName(name) {
356
+ let sanitized = name.replace(/[\/\\:\0]/g, "");
357
+ sanitized = sanitized.replace(/^[.\s]+|[.\s]+$/g, "");
358
+ sanitized = sanitized.replace(/^\.+/, "");
359
+ if (!sanitized || sanitized.length === 0) {
360
+ sanitized = "unnamed-skill";
361
+ }
362
+ if (sanitized.length > 255) {
363
+ sanitized = sanitized.substring(0, 255);
364
+ }
365
+ return sanitized;
366
+ }
367
+ function isPathSafe(basePath, targetPath) {
368
+ const normalizedBase = normalize2(resolve3(basePath));
369
+ const normalizedTarget = normalize2(resolve3(targetPath));
370
+ return normalizedTarget.startsWith(normalizedBase + sep2) || normalizedTarget === normalizedBase;
371
+ }
372
+ function getCanonicalSkillsDir(global, cwd) {
373
+ const baseDir = global ? homedir2() : cwd || process.cwd();
374
+ return join4(baseDir, AGENTS_DIR, SKILLS_SUBDIR);
375
+ }
376
+ async function createSymlink(target, linkPath) {
377
+ try {
378
+ try {
379
+ const stats = await lstat(linkPath);
380
+ if (stats.isSymbolicLink()) {
381
+ const existingTarget = await readlink(linkPath);
382
+ if (resolve3(existingTarget) === resolve3(target)) {
383
+ return true;
384
+ }
385
+ await rm2(linkPath);
386
+ } else {
387
+ await rm2(linkPath, { recursive: true });
388
+ }
389
+ } catch (err) {
390
+ if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") {
391
+ try {
392
+ await rm2(linkPath, { force: true });
393
+ } catch {
394
+ }
395
+ }
396
+ }
397
+ const linkDir = join4(linkPath, "..");
398
+ await mkdir(linkDir, { recursive: true });
399
+ const relativePath = relative(linkDir, target);
400
+ const symlinkType = platform() === "win32" ? "junction" : void 0;
401
+ await symlink(relativePath, linkPath, symlinkType);
402
+ return true;
403
+ } catch {
404
+ return false;
405
+ }
406
+ }
407
+ async function installSkillForAgent(skill, agentType, options = {}) {
408
+ const agent = agents[agentType];
409
+ const isGlobal = options.global ?? false;
410
+ const cwd = options.cwd || process.cwd();
411
+ const rawSkillName = skill.name || basename2(skill.path);
412
+ const skillName = sanitizeName(rawSkillName);
413
+ const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
414
+ const canonicalDir = join4(canonicalBase, skillName);
415
+ const agentBase = isGlobal ? agent.globalSkillsDir : join4(cwd, agent.skillsDir);
416
+ const agentDir = join4(agentBase, skillName);
417
+ const installMode = options.mode ?? "symlink";
418
+ if (!isPathSafe(canonicalBase, canonicalDir)) {
419
+ return {
420
+ success: false,
421
+ path: agentDir,
422
+ mode: installMode,
423
+ error: "Invalid skill name: potential path traversal detected"
424
+ };
425
+ }
426
+ if (!isPathSafe(agentBase, agentDir)) {
427
+ return {
428
+ success: false,
429
+ path: agentDir,
430
+ mode: installMode,
431
+ error: "Invalid skill name: potential path traversal detected"
432
+ };
433
+ }
434
+ try {
435
+ if (installMode === "copy") {
436
+ await mkdir(agentDir, { recursive: true });
437
+ await copyDirectory(skill.path, agentDir);
438
+ return {
439
+ success: true,
440
+ path: agentDir,
441
+ mode: "copy"
442
+ };
443
+ }
444
+ await mkdir(canonicalDir, { recursive: true });
445
+ await copyDirectory(skill.path, canonicalDir);
446
+ const symlinkCreated = AGENTS_THAT_SHOULD_NOT_GET_SYMLINKS.has(agentType) ? false : await createSymlink(canonicalDir, agentDir);
447
+ if (!symlinkCreated) {
448
+ try {
449
+ await rm2(agentDir, { recursive: true, force: true });
450
+ } catch {
451
+ }
452
+ await mkdir(agentDir, { recursive: true });
453
+ await copyDirectory(skill.path, agentDir);
454
+ return {
455
+ success: true,
456
+ path: agentDir,
457
+ canonicalPath: canonicalDir,
458
+ mode: "symlink",
459
+ symlinkFailed: true
460
+ };
461
+ }
462
+ return {
463
+ success: true,
464
+ path: agentDir,
465
+ canonicalPath: canonicalDir,
466
+ mode: "symlink"
467
+ };
468
+ } catch (error) {
469
+ return {
470
+ success: false,
471
+ path: agentDir,
472
+ mode: installMode,
473
+ error: error instanceof Error ? error.message : "Unknown error"
474
+ };
475
+ }
476
+ }
477
+ var EXCLUDE_FILES = /* @__PURE__ */ new Set(["README.md", "metadata.json"]);
478
+ var isExcluded = (name) => {
479
+ if (EXCLUDE_FILES.has(name)) return true;
480
+ if (name.startsWith("_")) return true;
481
+ return false;
482
+ };
483
+ async function copyDirectory(src, dest) {
484
+ await mkdir(dest, { recursive: true });
485
+ const entries = await readdir2(src, { withFileTypes: true });
486
+ for (const entry of entries) {
487
+ if (isExcluded(entry.name)) {
488
+ continue;
489
+ }
490
+ const srcPath = join4(src, entry.name);
491
+ const destPath = join4(dest, entry.name);
492
+ if (entry.isDirectory()) {
493
+ await copyDirectory(srcPath, destPath);
494
+ } else {
495
+ await cp(srcPath, destPath);
496
+ }
497
+ }
498
+ }
499
+ async function isSkillInstalled(skillName, agentType, options = {}) {
500
+ const agent = agents[agentType];
501
+ const sanitized = sanitizeName(skillName);
502
+ const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
503
+ const skillDir = join4(targetBase, sanitized);
504
+ if (!isPathSafe(targetBase, skillDir)) {
505
+ return false;
506
+ }
507
+ try {
508
+ const st = await lstat(skillDir);
509
+ if (AGENTS_THAT_SHOULD_NOT_GET_SYMLINKS.has(agentType) && st.isSymbolicLink()) {
510
+ return false;
511
+ }
512
+ return true;
513
+ } catch {
514
+ return false;
515
+ }
516
+ }
517
+
518
+ // src/mcp-install.ts
519
+ import fs from "fs/promises";
520
+ import { existsSync as existsSync2 } from "fs";
521
+ import path2 from "path";
522
+ import JSON5 from "json5";
523
+ import { execFile } from "child_process";
524
+ import { promisify } from "util";
525
+
526
+ // src/mcp-client-paths.ts
527
+ import { homedir as homedir3, platform as platform2 } from "os";
528
+ import path from "path";
529
+ function expandHome(p2) {
530
+ if (p2.startsWith("~/") || p2 === "~") {
531
+ return path.join(homedir3(), p2.slice(1));
532
+ }
533
+ return p2;
534
+ }
535
+ function getWindsurfConfigPath() {
536
+ const os = platform2();
537
+ if (os === "darwin" || os === "linux") {
538
+ return expandHome("~/.codeium/windsurf/mcp_config.json");
539
+ }
540
+ if (os === "win32") {
541
+ const appData = process.env.APPDATA;
542
+ if (!appData) return null;
543
+ return path.join(appData, "Codeium", "Windsurf", "mcp_config.json");
544
+ }
545
+ return null;
546
+ }
547
+ function getClaudeCodeUserConfigPath() {
548
+ return path.join(homedir3(), ".claude.json");
549
+ }
550
+ function getAmpSettingsPath() {
551
+ const os = platform2();
552
+ if (os === "darwin" || os === "linux") {
553
+ return path.join(homedir3(), ".config", "amp", "settings.json");
554
+ }
555
+ if (os === "win32") {
556
+ return path.join(homedir3(), ".config", "amp", "settings.json");
557
+ }
558
+ return null;
559
+ }
560
+ function getAntigravityMcpConfigPath() {
561
+ return path.join(homedir3(), ".gemini", "antigravity", "mcp_config.json");
562
+ }
563
+ function getCodexConfigTomlPath() {
564
+ return path.join(homedir3(), ".codex", "config.toml");
565
+ }
566
+ var MCP_CLIENT_SPECS = {
567
+ amp: {
568
+ id: "amp",
569
+ label: "Amp",
570
+ getPaths: () => ({ userConfigPath: getAmpSettingsPath() })
571
+ },
572
+ antigravity: {
573
+ id: "antigravity",
574
+ label: "Antigravity",
575
+ getPaths: () => ({ userConfigPath: getAntigravityMcpConfigPath() })
576
+ },
577
+ codex: {
578
+ id: "codex",
579
+ label: "Codex",
580
+ getPaths: () => ({ userConfigPath: getCodexConfigTomlPath() })
581
+ },
582
+ opencode: {
583
+ id: "opencode",
584
+ label: "OpenCode",
585
+ getPaths: () => ({ userConfigPath: expandHome("~/.config/opencode/opencode.json") })
586
+ },
587
+ cursor: {
588
+ id: "cursor",
589
+ label: "Cursor",
590
+ getPaths: () => ({ userConfigPath: expandHome("~/.cursor/mcp.json") })
591
+ },
592
+ "claude-code": {
593
+ id: "claude-code",
594
+ label: "Claude Code",
595
+ getPaths: () => ({ userConfigPath: getClaudeCodeUserConfigPath() })
596
+ },
597
+ windsurf: {
598
+ id: "windsurf",
599
+ label: "Windsurf",
600
+ getPaths: () => ({ userConfigPath: getWindsurfConfigPath() })
601
+ }
602
+ };
603
+
604
+ // src/mcp-install.ts
605
+ var execFileAsync = promisify(execFile);
606
+ function isObject(value) {
607
+ return !!value && typeof value === "object" && !Array.isArray(value);
608
+ }
609
+ async function ensureDirForFile(filePath) {
610
+ await fs.mkdir(path2.dirname(filePath), { recursive: true });
611
+ }
612
+ async function readJsonish(filePath) {
613
+ const raw = await fs.readFile(filePath, "utf-8");
614
+ if (raw.trim().length === 0) return null;
615
+ return JSON5.parse(raw);
616
+ }
617
+ async function writeJson(filePath, data) {
618
+ await ensureDirForFile(filePath);
619
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
620
+ }
621
+ function tomlEscapeString(value) {
622
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
623
+ }
624
+ function buildCodexMorphBlock(input) {
625
+ const lines = [];
626
+ lines.push("[mcp_servers.morph-mcp]");
627
+ lines.push('command = "npx"');
628
+ lines.push('args = ["-y", "@morphllm/morphmcp"]');
629
+ lines.push("enabled = true");
630
+ const env = [];
631
+ if (input.apiKey) env.push(`MORPH_API_KEY = "${tomlEscapeString(input.apiKey)}"`);
632
+ if (input.enabledTools) env.push(`ENABLED_TOOLS = "${tomlEscapeString(input.enabledTools)}"`);
633
+ if (env.length > 0) {
634
+ lines.push(`env = { ${env.join(", ")} }`);
635
+ }
636
+ return lines;
637
+ }
638
+ async function installToCodexConfigToml(configPath, input) {
639
+ let raw = "";
640
+ try {
641
+ raw = await fs.readFile(configPath, "utf-8");
642
+ } catch (err) {
643
+ const code = err?.code;
644
+ if (code !== "ENOENT") throw err;
645
+ }
646
+ const existingLines = raw.length > 0 ? raw.split(/\r?\n/) : [];
647
+ const blockLines = buildCodexMorphBlock(input);
648
+ const header = "[mcp_servers.morph-mcp]";
649
+ const start = existingLines.findIndex((l) => l.trim() === header);
650
+ let nextLines;
651
+ if (start >= 0) {
652
+ let end = start + 1;
653
+ while (end < existingLines.length && !/^\s*\[/.test(existingLines[end])) end++;
654
+ nextLines = [
655
+ ...existingLines.slice(0, start),
656
+ ...blockLines,
657
+ ...existingLines.slice(end)
658
+ ];
659
+ } else {
660
+ nextLines = existingLines.slice();
661
+ if (nextLines.length > 0 && nextLines[nextLines.length - 1]?.trim() !== "") {
662
+ nextLines.push("");
663
+ }
664
+ nextLines.push(...blockLines);
665
+ }
666
+ const out = nextLines.join("\n").replace(/\n*$/, "\n");
667
+ await ensureDirForFile(configPath);
668
+ await fs.writeFile(configPath, out, "utf-8");
669
+ }
670
+ function mergeMorphEnv(existing, input) {
671
+ const existingEnv = existing && isObject(existing.env) ? existing.env : void 0;
672
+ const env = {};
673
+ if (existingEnv) {
674
+ for (const [k, v] of Object.entries(existingEnv)) {
675
+ if (typeof v === "string") env[k] = v;
676
+ }
677
+ }
678
+ if (input.apiKey) env.MORPH_API_KEY = input.apiKey;
679
+ if (input.enabledTools) env.ENABLED_TOOLS = input.enabledTools;
680
+ return Object.keys(env).length > 0 ? env : void 0;
681
+ }
682
+ async function installToMcpServersJson(configPath, input, options) {
683
+ const key = options?.key ?? "morph-mcp";
684
+ let data;
685
+ try {
686
+ data = await readJsonish(configPath) ?? { mcpServers: {} };
687
+ } catch (err) {
688
+ const code = err?.code;
689
+ if (code === "ENOENT") {
690
+ data = { mcpServers: {} };
691
+ } else {
692
+ throw err;
693
+ }
694
+ }
695
+ const obj = isObject(data) ? data : {};
696
+ if (!isObject(obj.mcpServers)) obj.mcpServers = {};
697
+ const servers = obj.mcpServers;
698
+ const existing = isObject(servers[key]) ? servers[key] : void 0;
699
+ const env = mergeMorphEnv(existing, input);
700
+ servers[key] = {
701
+ ...existing ?? {},
702
+ command: "npx",
703
+ args: ["-y", "@morphllm/morphmcp"],
704
+ ...env ? { env } : {}
705
+ };
706
+ await writeJson(configPath, obj);
707
+ }
708
+ async function installToClaudeCodeUserConfig(configPath, input) {
709
+ let data;
710
+ try {
711
+ data = await readJsonish(configPath) ?? { mcpServers: {} };
712
+ } catch (err) {
713
+ const code = err?.code;
714
+ if (code === "ENOENT") {
715
+ data = { mcpServers: {} };
716
+ } else {
717
+ throw err;
718
+ }
719
+ }
720
+ const obj = isObject(data) ? data : {};
721
+ if (!isObject(obj.mcpServers)) obj.mcpServers = {};
722
+ const servers = obj.mcpServers;
723
+ const key = "morph-mcp";
724
+ const existing = isObject(servers[key]) ? servers[key] : void 0;
725
+ const env = mergeMorphEnv(existing, input);
726
+ servers[key] = {
727
+ ...existing ?? {},
728
+ type: "stdio",
729
+ command: "npx",
730
+ args: ["-y", "@morphllm/morphmcp"],
731
+ ...env ? { env } : {}
732
+ };
733
+ await writeJson(configPath, obj);
734
+ }
735
+ async function installToOpenCodeConfig(configPath, input) {
736
+ let data;
737
+ try {
738
+ data = await readJsonish(configPath) ?? {};
739
+ } catch (err) {
740
+ const code = err?.code;
741
+ if (code === "ENOENT") {
742
+ data = {};
743
+ } else {
744
+ throw err;
745
+ }
746
+ }
747
+ const obj = isObject(data) ? data : {};
748
+ if (!isObject(obj.mcp)) obj.mcp = {};
749
+ const mcp = obj.mcp;
750
+ const key = "morph-mcp";
751
+ const existing = isObject(mcp[key]) ? mcp[key] : void 0;
752
+ const existingEnv = existing && isObject(existing.environment) ? existing.environment : void 0;
753
+ const environment = {};
754
+ if (existingEnv) {
755
+ for (const [k, v] of Object.entries(existingEnv)) {
756
+ if (typeof v === "string") environment[k] = v;
757
+ }
758
+ }
759
+ if (input.apiKey) environment.MORPH_API_KEY = input.apiKey;
760
+ if (input.enabledTools) environment.ENABLED_TOOLS = input.enabledTools;
761
+ mcp[key] = {
762
+ ...existing ?? {},
763
+ type: "local",
764
+ command: ["npx", "-y", "@morphllm/morphmcp"],
765
+ enabled: true,
766
+ ...Object.keys(environment).length > 0 ? { environment } : {}
767
+ };
768
+ await writeJson(configPath, obj);
769
+ }
770
+ async function tryAmpCliAddMorphMcp() {
771
+ try {
772
+ await execFileAsync("amp", ["mcp", "add", "morph-mcp", "--", "npx", "-y", "@morphllm/morphmcp"], {
773
+ windowsHide: true
774
+ });
775
+ } catch {
776
+ }
777
+ }
778
+ async function installToAmpSettings(configPath, input) {
779
+ await tryAmpCliAddMorphMcp();
780
+ let data;
781
+ try {
782
+ data = await readJsonish(configPath) ?? {};
783
+ } catch (err) {
784
+ const code = err?.code;
785
+ if (code === "ENOENT") {
786
+ data = {};
787
+ } else {
788
+ throw err;
789
+ }
790
+ }
791
+ const obj = isObject(data) ? data : {};
792
+ const key = "amp.mcpServers";
793
+ if (!isObject(obj[key])) obj[key] = {};
794
+ const servers = obj[key];
795
+ const serverKey = "morph-mcp";
796
+ const existing = isObject(servers[serverKey]) ? servers[serverKey] : void 0;
797
+ const env = mergeMorphEnv(existing, input);
798
+ servers[serverKey] = {
799
+ ...existing ?? {},
800
+ command: "npx",
801
+ args: ["-y", "@morphllm/morphmcp"],
802
+ ...env ? { env } : {}
803
+ };
804
+ await writeJson(configPath, obj);
805
+ }
806
+ function getMcpClientConfigPath(client) {
807
+ return MCP_CLIENT_SPECS[client].getPaths().userConfigPath;
808
+ }
809
+ function listSupportedMcpClients() {
810
+ return Object.keys(MCP_CLIENT_SPECS).map((id) => ({
811
+ id,
812
+ label: MCP_CLIENT_SPECS[id].label,
813
+ configPath: getMcpClientConfigPath(id)
814
+ }));
815
+ }
816
+ function detectMcpClients() {
817
+ const detected = [];
818
+ for (const id of Object.keys(MCP_CLIENT_SPECS)) {
819
+ const configPath = getMcpClientConfigPath(id);
820
+ if (!configPath) continue;
821
+ if (existsSync2(path2.dirname(configPath))) detected.push(id);
822
+ }
823
+ return detected;
824
+ }
825
+ async function installMorphMcpToClient(client, input) {
826
+ const configPath = getMcpClientConfigPath(client);
827
+ if (!configPath) {
828
+ return { success: false, client, error: "Unsupported on this OS (no config path)." };
829
+ }
830
+ try {
831
+ switch (client) {
832
+ case "amp":
833
+ await installToAmpSettings(configPath, input);
834
+ break;
835
+ case "antigravity":
836
+ case "cursor":
837
+ case "windsurf":
838
+ await installToMcpServersJson(configPath, input);
839
+ break;
840
+ case "claude-code":
841
+ await installToClaudeCodeUserConfig(configPath, input);
842
+ break;
843
+ case "opencode":
844
+ await installToOpenCodeConfig(configPath, input);
845
+ break;
846
+ case "codex":
847
+ await installToCodexConfigToml(configPath, input);
848
+ break;
849
+ }
850
+ return { success: true, client, configPath };
851
+ } catch (err) {
852
+ return {
853
+ success: false,
854
+ client,
855
+ configPath,
856
+ error: err instanceof Error ? err.message : "Unknown error"
857
+ };
858
+ }
859
+ }
860
+
861
+ // src/telemetry.ts
862
+ var TELEMETRY_URL = "https://add-skill.vercel.sh/t";
863
+ var cliVersion = null;
864
+ function isCI() {
865
+ return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.JENKINS_URL || process.env.TEAMCITY_VERSION);
866
+ }
867
+ function isEnabled() {
868
+ return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
869
+ }
870
+ function setVersion(version) {
871
+ cliVersion = version;
872
+ }
873
+ function track(data) {
874
+ if (!isEnabled()) return;
875
+ try {
876
+ const params = new URLSearchParams();
877
+ if (cliVersion) {
878
+ params.set("v", cliVersion);
879
+ }
880
+ if (isCI()) {
881
+ params.set("ci", "1");
882
+ }
883
+ for (const [key, value] of Object.entries(data)) {
884
+ if (value !== void 0 && value !== null) {
885
+ params.set(key, String(value));
886
+ }
887
+ }
888
+ fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {
889
+ });
890
+ } catch {
891
+ }
892
+ }
893
+
894
+ // src/index.ts
895
+ var __filename = fileURLToPath(import.meta.url);
896
+ var __dirname = path3.dirname(__filename);
897
+ var require2 = createRequire(import.meta.url);
898
+ var MORPH_ASCII_LINES = [
899
+ "\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 ",
900
+ "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ",
901
+ "\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 ",
902
+ "\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 ",
903
+ "\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2557",
904
+ "\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D"
905
+ ];
906
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
907
+ async function showMorphSplash() {
908
+ const LINE_DELAY_MS = 60;
909
+ const CHAR_DELAY_MS = 1;
910
+ const isTTY = process.stdout.isTTY;
911
+ process.stdout.write("\n");
912
+ for (const line of MORPH_ASCII_LINES) {
913
+ if (isTTY) {
914
+ for (const char of line) {
915
+ process.stdout.write(chalk.greenBright(char));
916
+ if (CHAR_DELAY_MS > 0) await sleep(CHAR_DELAY_MS);
917
+ }
918
+ process.stdout.write("\n");
919
+ await sleep(LINE_DELAY_MS);
920
+ } else {
921
+ process.stdout.write(chalk.greenBright(line) + "\n");
922
+ }
923
+ }
924
+ if (isTTY) await sleep(1e3);
925
+ process.stdout.write("\n");
926
+ }
927
+ function getBundledSkillsDir() {
928
+ return path3.resolve(__dirname, "..", "skills");
929
+ }
930
+ function getAllAgentTypes() {
931
+ return Object.keys(agents);
932
+ }
933
+ async function selectAgents(opts) {
934
+ const allAgents = getAllAgentTypes();
935
+ if (opts.cliAgents.length > 0) return opts.cliAgents;
936
+ if (opts.yes) return allAgents;
937
+ const detected = await detectInstalledAgents();
938
+ const initial = detected.length > 0 ? detected : allAgents;
939
+ const res = await p.multiselect({
940
+ message: "Choose which agents to install skills into",
941
+ options: allAgents.map((id) => ({
942
+ value: id,
943
+ label: agents[id].displayName,
944
+ hint: detected.includes(id) ? "detected" : void 0
945
+ })),
946
+ initialValues: initial,
947
+ required: true
948
+ });
949
+ if (p.isCancel(res)) process.exit(0);
950
+ return res;
951
+ }
952
+ async function selectMcpClients(opts) {
953
+ const supported = listSupportedMcpClients();
954
+ const detected = detectMcpClients();
955
+ if (opts.yes) {
956
+ return detected.length > 0 ? detected : supported.map((c) => c.id);
957
+ }
958
+ const res = await p.multiselect({
959
+ message: "Choose which apps to install the MCP into",
960
+ options: supported.map((c) => ({
961
+ value: c.id,
962
+ label: c.label,
963
+ hint: detected.includes(c.id) ? "detected" : void 0
964
+ })),
965
+ initialValues: detected.length > 0 ? detected : supported.map((c) => c.id),
966
+ required: true
967
+ });
968
+ if (p.isCancel(res)) process.exit(0);
969
+ return res;
970
+ }
971
+ async function getMorphMcpInput(opts) {
972
+ const envApiKey = process.env.MORPH_API_KEY;
973
+ const effectiveApiKey = opts.cliApiKey ?? envApiKey;
974
+ const effectiveEnabledTools = "edit_file,warpgrep_codebase_search";
975
+ if (opts.yes) {
976
+ return { apiKey: effectiveApiKey, enabledTools: effectiveEnabledTools };
977
+ }
978
+ const apiKey = effectiveApiKey ?? await p.password({
979
+ message: "MORPH_API_KEY (optional, press enter to skip)",
980
+ mask: "*"
981
+ });
982
+ if (p.isCancel(apiKey)) process.exit(0);
983
+ return {
984
+ apiKey: typeof apiKey === "string" && apiKey.trim() ? apiKey.trim() : void 0,
985
+ enabledTools: effectiveEnabledTools
986
+ };
987
+ }
988
+ async function loadSkills(source) {
989
+ if (!source) {
990
+ const skillsDir = getBundledSkillsDir();
991
+ const skills2 = await discoverSkills(skillsDir);
992
+ return { skills: skills2, sourceId: "bundled" };
993
+ }
994
+ const parsed = parseSource(source);
995
+ if (parsed.type === "local") {
996
+ const skills2 = await discoverSkills(parsed.localPath, parsed.subpath);
997
+ return { skills: skills2, sourceId: parsed.localPath };
998
+ }
999
+ if (parsed.type === "direct-url") {
1000
+ throw new Error("Direct SKILL.md URLs are not supported in this build.");
1001
+ }
1002
+ const tempDir = await cloneRepo(parsed.url, parsed.ref);
1003
+ const skills = await discoverSkills(tempDir, parsed.subpath);
1004
+ return { skills, tempDir, sourceId: parsed.url };
1005
+ }
1006
+ async function installBundledSkills(params) {
1007
+ const skillsDir = getBundledSkillsDir();
1008
+ const skills = await discoverSkills(skillsDir);
1009
+ if (skills.length === 0) {
1010
+ p.log.warn(`No bundled skills found at ${chalk.dim(skillsDir)}.`);
1011
+ return;
1012
+ }
1013
+ p.log.info(chalk.greenBright(`Installing ${skills.length} bundled skills to all chosen agents...`));
1014
+ for (const agentType of params.targetAgents) {
1015
+ for (const skill of skills) {
1016
+ const already = await isSkillInstalled(getSkillDisplayName(skill), agentType, {
1017
+ global: params.global
1018
+ });
1019
+ if (already) continue;
1020
+ await installSkillForAgent(skill, agentType, { global: params.global, mode: "symlink" });
1021
+ }
1022
+ }
1023
+ }
1024
+ async function installMcp(params) {
1025
+ if (params.clients.length === 0) return;
1026
+ p.log.info(chalk.greenBright(`Installing Morph MCP to ${params.clients.length} chosen app(s)...`));
1027
+ for (const client of params.clients) {
1028
+ const res = await installMorphMcpToClient(client, params.input);
1029
+ if (res.success) {
1030
+ p.log.success(`${client}${res.configPath ? chalk.dim(` \u2192 ${res.configPath}`) : ""}`);
1031
+ } else {
1032
+ p.log.warn(`${client}${res.error ? chalk.dim(`: ${res.error}`) : ""}`);
1033
+ }
1034
+ }
1035
+ }
1036
+ async function main() {
1037
+ const pkg = require2("../package.json");
1038
+ setVersion(pkg.version);
1039
+ await showMorphSplash();
1040
+ const program = new Command().name("morph-setup").description("Install Morph MCP and bundled skills onto coding agents").argument("[source]", "Optional skills source (repo/path). Bundled skills are always installed.").option("--morph-api-key <key>", "MORPH_API_KEY to write into MCP config (overrides env)").option("-g, --global", "Install skills globally (default)").option("--project", "Install skills to the current project instead of global").option("-a, --agent <agents...>", "Target specific agents (repeatable)").option("-s, --skill <skills...>", "Install specific skills by name (only affects <source> skills)").option("-l, --list", "List available skills without installing").option("-y, --yes", "Skip all confirmation prompts").version(pkg.version);
1041
+ const argv = process.argv.slice();
1042
+ if (argv[2] === "--") argv.splice(2, 1);
1043
+ program.parse(argv);
1044
+ const opts = program.opts();
1045
+ const source = program.args[0];
1046
+ const yes = !!opts.yes;
1047
+ const global = opts.project ? false : true;
1048
+ p.intro(chalk.greenBright(" Morph MCP Install "));
1049
+ const mcpClients = await selectMcpClients({ yes });
1050
+ const mcpInput = await getMorphMcpInput({
1051
+ yes,
1052
+ cliApiKey: opts.morphApiKey
1053
+ });
1054
+ const cliAgents = (opts.agent ?? []).map((a) => a);
1055
+ const targetAgents = await selectAgents({ yes, cliAgents });
1056
+ if (opts.list) {
1057
+ const bundled = await discoverSkills(getBundledSkillsDir());
1058
+ p.log.info(chalk.greenBright("Bundled skills:"));
1059
+ for (const s of bundled) p.log.info(`- ${getSkillDisplayName(s)}`);
1060
+ if (source) {
1061
+ const { skills, tempDir } = await loadSkills(source);
1062
+ try {
1063
+ p.log.info(chalk.greenBright(`
1064
+ Skills from ${source}:`));
1065
+ for (const s of skills) p.log.info(`- ${getSkillDisplayName(s)}`);
1066
+ } finally {
1067
+ if (tempDir) await cleanupTempDir(tempDir);
1068
+ }
1069
+ }
1070
+ p.outro("Done.");
1071
+ return;
1072
+ }
1073
+ const skillsFromSource = source ? await loadSkills(source) : null;
1074
+ try {
1075
+ await installMcp({ clients: mcpClients, input: mcpInput });
1076
+ await installBundledSkills({ targetAgents, global, yes });
1077
+ if (skillsFromSource) {
1078
+ const requested = new Set(opts.skill ?? []);
1079
+ const extraSkills = requested.size > 0 ? skillsFromSource.skills.filter((s) => requested.has(getSkillDisplayName(s))) : skillsFromSource.skills;
1080
+ if (extraSkills.length > 0) {
1081
+ p.log.info(
1082
+ chalk.greenBright(`Installing ${extraSkills.length} skill(s) from source to chosen agents...`)
1083
+ );
1084
+ for (const agentType of targetAgents) {
1085
+ for (const skill of extraSkills) {
1086
+ const already = await isSkillInstalled(getSkillDisplayName(skill), agentType, { global });
1087
+ if (already) continue;
1088
+ await installSkillForAgent(skill, agentType, { global, mode: "symlink" });
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ track({
1094
+ event: "install",
1095
+ source: skillsFromSource?.sourceId ?? "bundled",
1096
+ skills: "bundled",
1097
+ agents: targetAgents.join(","),
1098
+ ...global ? { global: "1" } : {}
1099
+ });
1100
+ p.outro(chalk.greenBright("All done."));
1101
+ } finally {
1102
+ if (skillsFromSource?.tempDir) {
1103
+ await cleanupTempDir(skillsFromSource.tempDir);
1104
+ }
1105
+ }
1106
+ }
1107
+ await main();
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@morphllm/morph-setup",
3
+ "version": "1.0.0",
4
+ "description": "Install Morph MCP and bundled skills onto coding agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "morph-setup": "./dist/index.js"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "skills",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format esm --dts --clean",
19
+ "dev": "tsx src/index.ts",
20
+ "test": "tsx scripts/execute-tests.ts",
21
+ "prepublishOnly": "npm run build",
22
+ "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.ts' 'tests/**/*.ts'",
23
+ "format:check": "prettier --check 'src/**/*.ts' 'scripts/**/*.ts' 'tests/**/*.ts'",
24
+ "prepare": "husky"
25
+ },
26
+ "lint-staged": {
27
+ "src/**/*.ts": "prettier --write",
28
+ "scripts/**/*.ts": "prettier --write",
29
+ "tests/**/*.ts": "prettier --write"
30
+ },
31
+ "keywords": [
32
+ "cli",
33
+ "agent-skills",
34
+ "skills",
35
+ "ai-agents",
36
+ "amp",
37
+ "antigravity",
38
+ "claude-code",
39
+ "clawdbot",
40
+ "cline",
41
+ "codex",
42
+ "command-code",
43
+ "cursor",
44
+ "droid",
45
+ "gemini-cli",
46
+ "github-copilot",
47
+ "goose",
48
+ "kilo",
49
+ "kiro-cli",
50
+ "mcpjam",
51
+ "opencode",
52
+ "openhands",
53
+ "pi",
54
+ "qoder",
55
+ "qwen-code",
56
+ "roo",
57
+ "trae",
58
+ "windsurf",
59
+ "zencoder",
60
+ "neovate"
61
+ ],
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "git+https://github.com/vercel-labs/add-skill.git"
65
+ },
66
+ "homepage": "https://github.com/vercel-labs/add-skill#readme",
67
+ "bugs": {
68
+ "url": "https://github.com/vercel-labs/add-skill/issues"
69
+ },
70
+ "author": "",
71
+ "license": "MIT",
72
+ "dependencies": {
73
+ "@clack/prompts": "^0.9.1",
74
+ "chalk": "^5.4.1",
75
+ "commander": "^13.1.0",
76
+ "gray-matter": "^4.0.3",
77
+ "json5": "^2.2.3",
78
+ "simple-git": "^3.27.0"
79
+ },
80
+ "devDependencies": {
81
+ "@types/node": "^22.10.0",
82
+ "husky": "^9.1.7",
83
+ "lint-staged": "^16.2.7",
84
+ "prettier": "^3.8.1",
85
+ "tsup": "^8.3.5",
86
+ "tsx": "^4.19.2",
87
+ "typescript": "^5.7.2"
88
+ },
89
+ "engines": {
90
+ "node": ">=18"
91
+ },
92
+ "packageManager": "pnpm@10.17.1"
93
+ }