set-prompt 0.3.0 → 0.5.2
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/CHANGELOG.md +131 -79
- package/README.md +138 -102
- package/dist/index.js +1151 -483
- package/package.json +13 -7
- package/dist/bin/set-prompt.js +0 -2
package/dist/index.js
CHANGED
|
@@ -2,23 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk15 from "chalk";
|
|
6
6
|
import figlet from "figlet";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import fs11 from "fs";
|
|
8
|
+
import path10 from "path";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
|
|
11
11
|
// src/commands/install-command.ts
|
|
12
12
|
import fs3 from "fs";
|
|
13
13
|
import path3 from "path";
|
|
14
14
|
import { spawnSync } from "child_process";
|
|
15
|
-
import
|
|
15
|
+
import chalk4 from "chalk";
|
|
16
16
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
17
17
|
|
|
18
18
|
// src/_defs/index.ts
|
|
19
19
|
import path from "path";
|
|
20
20
|
import os from "os";
|
|
21
21
|
var TAB = ` `;
|
|
22
|
+
var MARKET_NAME = "set-prompt";
|
|
23
|
+
var PLUGIN_NAME = "sppt";
|
|
22
24
|
var HOME_DIR = path.join(os.homedir(), ".set-prompt");
|
|
23
25
|
var CONFIG_PATH = path.join(HOME_DIR, "config.json");
|
|
24
26
|
var REPO_DIR = path.join(HOME_DIR, "repo");
|
|
@@ -29,20 +31,25 @@ var OPENCLAW_DIR = path.join(os.homedir(), ".openclaw", "workspace");
|
|
|
29
31
|
var OPENCLAW_BACKUP_DIR = path.join(OPENCLAW_DIR, "SET_PROMPT_BACKUP");
|
|
30
32
|
var ANTIGRAVITY_DIR = path.join(os.homedir(), ".gemini", "antigravity");
|
|
31
33
|
var ANTIGRAVITY_BACKUP_DIR = path.join(ANTIGRAVITY_DIR, "SET_PROMPT_BACKUP");
|
|
32
|
-
var
|
|
34
|
+
var CODEX_DIR = path.join(HOME_DIR, "codex");
|
|
35
|
+
var CODEX_BACKUP_DIR = path.join(CODEX_DIR, "SET_PROMPT_BACKUP");
|
|
36
|
+
var CURSOR_DIR = path.join(os.homedir(), ".cursor");
|
|
37
|
+
var PROMPT_DIR_NAMES = ["skills", "commands", "hooks", "agents", "rules"];
|
|
33
38
|
var AGENT_PROMPT_DIRS = {
|
|
34
39
|
["claudecode" /* CLAUDECODE */]: ["skills", "commands", "hooks", "agents"],
|
|
35
40
|
["roocode" /* ROOCODE */]: ["skills", "commands"],
|
|
36
41
|
["openclaw" /* OPENCLAW */]: ["skills"],
|
|
37
|
-
["codex" /* CODEX */]: ["skills"
|
|
38
|
-
["antigravity" /* ANTIGRAVITY */]: ["skills"]
|
|
42
|
+
["codex" /* CODEX */]: ["skills"],
|
|
43
|
+
["antigravity" /* ANTIGRAVITY */]: ["skills"],
|
|
44
|
+
["cursor" /* CURSOR */]: ["skills", "agents", "commands", "hooks"]
|
|
39
45
|
};
|
|
40
46
|
var ALL_AGENTS = [
|
|
41
47
|
{ name: "Claude Code", value: "claudecode" /* CLAUDECODE */ },
|
|
42
48
|
{ name: "RooCode", value: "roocode" /* ROOCODE */ },
|
|
43
49
|
{ name: "OpenClaw", value: "openclaw" /* OPENCLAW */ },
|
|
44
50
|
{ name: "Codex", value: "codex" /* CODEX */ },
|
|
45
|
-
{ name: "Antigravity", value: "antigravity" /* ANTIGRAVITY */ }
|
|
51
|
+
{ name: "Antigravity", value: "antigravity" /* ANTIGRAVITY */ },
|
|
52
|
+
{ name: "Cursor", value: "cursor" /* CURSOR */ }
|
|
46
53
|
];
|
|
47
54
|
|
|
48
55
|
// src/_libs/config.ts
|
|
@@ -65,12 +72,17 @@ var OpenclawConfigSchema = z.object({
|
|
|
65
72
|
backup_path: z.string().nullish().optional()
|
|
66
73
|
});
|
|
67
74
|
var CodexConfigSchema = z.object({
|
|
68
|
-
path: z.string().nullable()
|
|
75
|
+
path: z.string().nullable(),
|
|
76
|
+
backup_path: z.string().nullish().optional()
|
|
69
77
|
});
|
|
70
78
|
var AntigravityConfigSchema = z.object({
|
|
71
79
|
path: z.string().nullable(),
|
|
72
80
|
backup_path: z.string().nullish().optional()
|
|
73
81
|
});
|
|
82
|
+
var CursorConfigSchema = z.object({
|
|
83
|
+
path: z.string().nullable(),
|
|
84
|
+
backup_path: z.string().nullish().optional()
|
|
85
|
+
});
|
|
74
86
|
var GlobalConfigSchema = z.object({
|
|
75
87
|
repo_path: z.string(),
|
|
76
88
|
remote_url: z.string().nullable(),
|
|
@@ -78,7 +90,8 @@ var GlobalConfigSchema = z.object({
|
|
|
78
90
|
roocode: RoocodeConfigSchema.nullable(),
|
|
79
91
|
openclaw: OpenclawConfigSchema.nullable(),
|
|
80
92
|
codex: CodexConfigSchema.nullish().optional(),
|
|
81
|
-
antigravity: AntigravityConfigSchema.nullish().optional()
|
|
93
|
+
antigravity: AntigravityConfigSchema.nullish().optional(),
|
|
94
|
+
cursor: CursorConfigSchema.nullish().optional()
|
|
82
95
|
});
|
|
83
96
|
|
|
84
97
|
// src/_libs/config.ts
|
|
@@ -91,6 +104,7 @@ var ConfigManager = class {
|
|
|
91
104
|
this._openclaw = null;
|
|
92
105
|
this._codex = null;
|
|
93
106
|
this._antigravity = null;
|
|
107
|
+
this._cursor = null;
|
|
94
108
|
}
|
|
95
109
|
get repo_path() {
|
|
96
110
|
return this._repo_path;
|
|
@@ -113,6 +127,9 @@ var ConfigManager = class {
|
|
|
113
127
|
get antigravity() {
|
|
114
128
|
return this._antigravity;
|
|
115
129
|
}
|
|
130
|
+
get cursor() {
|
|
131
|
+
return this._cursor;
|
|
132
|
+
}
|
|
116
133
|
set repo_path(v) {
|
|
117
134
|
this._repo_path = v;
|
|
118
135
|
}
|
|
@@ -134,10 +151,13 @@ var ConfigManager = class {
|
|
|
134
151
|
set antigravity(v) {
|
|
135
152
|
this._antigravity = v;
|
|
136
153
|
}
|
|
154
|
+
set cursor(v) {
|
|
155
|
+
this._cursor = v;
|
|
156
|
+
}
|
|
137
157
|
init() {
|
|
138
158
|
this._loadFromDisk();
|
|
139
159
|
if (this._repo_path != null) {
|
|
140
|
-
console.log(chalk.
|
|
160
|
+
console.log(chalk.green(`Config loaded`) + chalk.dim(` from ${CONFIG_PATH}`));
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
163
|
save() {
|
|
@@ -154,7 +174,8 @@ var ConfigManager = class {
|
|
|
154
174
|
roocode: this._roocode,
|
|
155
175
|
openclaw: this._openclaw,
|
|
156
176
|
codex: this._codex,
|
|
157
|
-
antigravity: this._antigravity
|
|
177
|
+
antigravity: this._antigravity,
|
|
178
|
+
cursor: this._cursor
|
|
158
179
|
}, null, 4);
|
|
159
180
|
fs.writeFileSync(CONFIG_PATH, configStr, "utf-8");
|
|
160
181
|
console.log(chalk.green(`Config saved`) + chalk.dim(` \u2192 ${CONFIG_PATH}`));
|
|
@@ -188,6 +209,9 @@ var ConfigManager = class {
|
|
|
188
209
|
isAntigravityEnabled() {
|
|
189
210
|
return this._antigravity != null;
|
|
190
211
|
}
|
|
212
|
+
isCursorEnabled() {
|
|
213
|
+
return this._cursor != null;
|
|
214
|
+
}
|
|
191
215
|
_assign(config) {
|
|
192
216
|
this._repo_path = config.repo_path;
|
|
193
217
|
this._remote_url = config.remote_url;
|
|
@@ -196,6 +220,7 @@ var ConfigManager = class {
|
|
|
196
220
|
this._openclaw = config.openclaw;
|
|
197
221
|
this._codex = config.codex ?? null;
|
|
198
222
|
this._antigravity = config.antigravity ?? null;
|
|
223
|
+
this._cursor = config.cursor ?? null;
|
|
199
224
|
}
|
|
200
225
|
_loadFromDisk() {
|
|
201
226
|
if (fs.existsSync(CONFIG_PATH) === false) {
|
|
@@ -214,23 +239,33 @@ var ConfigManager = class {
|
|
|
214
239
|
var configManager = new ConfigManager();
|
|
215
240
|
|
|
216
241
|
// src/_libs/index.ts
|
|
242
|
+
import chalk2 from "chalk";
|
|
217
243
|
var isGitUrl = (source) => source.startsWith("http://") || source.startsWith("https://") || source.startsWith("git@") || source.startsWith("ssh://") || source.endsWith(".git") && (source.startsWith("http") || source.startsWith("git@") || source.startsWith("ssh://"));
|
|
244
|
+
var resolveRepoPath = () => {
|
|
245
|
+
if (configManager.repo_path == null) {
|
|
246
|
+
console.error(chalk2.red("\u274C No repo installed."));
|
|
247
|
+
console.log(chalk2.yellow("Run: set-prompt install <git-url>"));
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
return configManager.repo_path;
|
|
251
|
+
};
|
|
218
252
|
|
|
219
253
|
// src/commands/scaffold-command.ts
|
|
220
254
|
import fs2 from "fs";
|
|
221
255
|
import path2 from "path";
|
|
222
|
-
import
|
|
256
|
+
import chalk3 from "chalk";
|
|
223
257
|
import { confirm } from "@inquirer/prompts";
|
|
224
258
|
|
|
225
259
|
// src/_libs/templates.ts
|
|
226
260
|
var SET_PROMPT_GUIDE = `# Set Prompt Repository Guide
|
|
227
261
|
|
|
228
|
-
> Managed by [set-prompt](https://github.com/
|
|
262
|
+
> Managed by [set-prompt](https://github.com/juncha9/set-prompt)
|
|
263
|
+
|
|
264
|
+
This is a shared prompt repository linked to various AI agents via \`set-prompt link\`. When writing or editing prompts in this repository, refer to the structure below and the frontmatter reference for each platform's conventions.
|
|
229
265
|
|
|
230
266
|
## Structure
|
|
231
267
|
|
|
232
268
|
\`\`\`
|
|
233
|
-
\u251C\u2500\u2500 set-prompt.toml # Repository configuration
|
|
234
269
|
\u251C\u2500\u2500 skills/
|
|
235
270
|
\u2502 \u2514\u2500\u2500 <skill-name>/
|
|
236
271
|
\u2502 \u251C\u2500\u2500 SKILL.md # Platform-specific frontmatter + prompt content
|
|
@@ -239,10 +274,27 @@ var SET_PROMPT_GUIDE = `# Set Prompt Repository Guide
|
|
|
239
274
|
\u2502 \u2514\u2500\u2500 <command-name>/
|
|
240
275
|
\u2502 \u251C\u2500\u2500 COMMAND.md # Platform-specific frontmatter + prompt content
|
|
241
276
|
\u2502 \u2514\u2500\u2500 ... # Supporting files
|
|
242
|
-
\u251C\u2500\u2500 hooks/ # Lifecycle shell hooks
|
|
243
|
-
\
|
|
244
|
-
|
|
245
|
-
|
|
277
|
+
\u251C\u2500\u2500 hooks/ # Lifecycle shell hooks (Claude Code)
|
|
278
|
+
\u251C\u2500\u2500 agents/ # Agent definitions (Claude Code, Cursor)
|
|
279
|
+
\u2502 \u2514\u2500\u2500 <agent-name>/
|
|
280
|
+
\u2502 \u2514\u2500\u2500 AGENT.md
|
|
281
|
+
\u2514\u2500\u2500 rules/ # Rule definitions (Cursor)
|
|
282
|
+
\u2514\u2500\u2500 <rule-name>/
|
|
283
|
+
\u2514\u2500\u2500 RULE.md
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
## Usage
|
|
287
|
+
|
|
288
|
+
\`\`\`bash
|
|
289
|
+
# Scaffold this repo's directory structure
|
|
290
|
+
set-prompt scaffold .
|
|
291
|
+
|
|
292
|
+
# Install from remote and link to AI tools
|
|
293
|
+
set-prompt install https://github.com/you/my-prompts
|
|
294
|
+
set-prompt link
|
|
295
|
+
|
|
296
|
+
# Pull latest changes
|
|
297
|
+
set-prompt update
|
|
246
298
|
\`\`\`
|
|
247
299
|
|
|
248
300
|
## Frontmatter Reference
|
|
@@ -292,6 +344,15 @@ metadata: {"os":["darwin","linux"],"requires":{"bins":["git"],"env":["MY_API_KEY
|
|
|
292
344
|
# Antigravity
|
|
293
345
|
name: my-skill
|
|
294
346
|
description: "What this skill does and when to use it"
|
|
347
|
+
|
|
348
|
+
# Cursor
|
|
349
|
+
name: "my-skill"
|
|
350
|
+
description: "What this skill does and when to use it"
|
|
351
|
+
license: "MIT"
|
|
352
|
+
compatibility: "Requires Node.js 18+"
|
|
353
|
+
metadata:
|
|
354
|
+
category: "development"
|
|
355
|
+
disable-model-invocation: false
|
|
295
356
|
---
|
|
296
357
|
\`\`\`
|
|
297
358
|
|
|
@@ -313,10 +374,13 @@ description: "What this skill does and when to use it"
|
|
|
313
374
|
| \`metadata\` | No | OpenClaw | Single-line JSON for platform gating: \`os\` (platform filter), \`requires.bins\` (required binaries), \`requires.env\` (required env vars) |
|
|
314
375
|
| \`homepage\` | No | OpenClaw | URL shown as "Website" in the macOS Skills UI. Also settable via \`metadata.openclaw.homepage\`. |
|
|
315
376
|
| \`user-invocable\` | No | OpenClaw | \`false\` = hidden from \`/\` menu. (default: \`true\`) |
|
|
316
|
-
| \`disable-model-invocation\` | No |
|
|
377
|
+
| \`disable-model-invocation\` | No | Claude Code, OpenClaw, Cursor | \`true\` = skill only included when explicitly invoked via \`/skill-name\`. (default: \`false\`) |
|
|
317
378
|
| \`command-dispatch\` | No | OpenClaw | \`"tool"\` = bypass model, dispatch directly to a tool |
|
|
318
379
|
| \`command-tool\` | No | OpenClaw | Tool to invoke when \`command-dispatch: "tool"\` |
|
|
319
380
|
| \`command-arg-mode\` | No | OpenClaw | How arguments are forwarded to the tool. (default: \`"raw"\`) |
|
|
381
|
+
| \`license\` | No | Cursor | License name or reference to a bundled license file. |
|
|
382
|
+
| \`compatibility\` | No | Cursor | Environment requirements (system packages, network access, etc.) |
|
|
383
|
+
| \`metadata\` | No | Cursor | Arbitrary key-value mapping for additional metadata. |
|
|
320
384
|
|
|
321
385
|
---
|
|
322
386
|
|
|
@@ -360,7 +424,7 @@ command-tool: "Bash"
|
|
|
360
424
|
|-------|----------|----------|-------------|
|
|
361
425
|
| \`name\` | No | All | Display name \u2014 lowercase, numbers, hyphens only (max 64 chars). Defaults to directory name. |
|
|
362
426
|
| \`description\` | Yes | All | Shown in \`/\` menu. Claude uses this to decide auto-loading. |
|
|
363
|
-
| \`user-invocable\` | No |
|
|
427
|
+
| \`user-invocable\` | No | Claude Code, OpenClaw | \`false\` = hidden from \`/\` menu, background knowledge only. (default: \`true\`) |
|
|
364
428
|
| \`allowed-tools\` | No | Claude Code | Tools Claude can use without asking. e.g. \`Read\` \`Write\` \`Edit\` \`Bash\` \`Grep\` \`Glob\` |
|
|
365
429
|
| \`argument-hint\` | No | Claude Code | Hint shown during autocomplete. e.g. \`[issue-number]\` |
|
|
366
430
|
| \`model\` | No | Claude Code | Model to use when active. \`sonnet\` or \`haiku\` |
|
|
@@ -372,10 +436,11 @@ command-tool: "Bash"
|
|
|
372
436
|
|
|
373
437
|
### Agents
|
|
374
438
|
|
|
375
|
-
Custom subagent definitions loaded by Claude Code.
|
|
439
|
+
Custom subagent definitions loaded by Claude Code and Cursor.
|
|
376
440
|
|
|
377
441
|
\`\`\`yaml
|
|
378
442
|
---
|
|
443
|
+
# Claude Code
|
|
379
444
|
name: "my-agent"
|
|
380
445
|
description: "What this agent does and when to use it"
|
|
381
446
|
allowed-tools:
|
|
@@ -383,37 +448,87 @@ allowed-tools:
|
|
|
383
448
|
- Bash
|
|
384
449
|
model: sonnet
|
|
385
450
|
context: fork
|
|
451
|
+
|
|
452
|
+
# Cursor
|
|
453
|
+
name: "my-agent"
|
|
454
|
+
description: "What this agent does and when to use it"
|
|
455
|
+
model: inherit
|
|
456
|
+
readonly: false
|
|
457
|
+
is_background: false
|
|
458
|
+
---
|
|
459
|
+
\`\`\`
|
|
460
|
+
|
|
461
|
+
| Field | Required | Platform | Description |
|
|
462
|
+
|-------|----------|----------|-------------|
|
|
463
|
+
| \`name\` | Yes | All | Display name. Claude Code: lowercase, numbers, hyphens only (max 64 chars). Cursor: defaults to folder name. |
|
|
464
|
+
| \`description\` | Yes | All | When and how to use this agent. Used to decide when to spawn/delegate. |
|
|
465
|
+
| \`allowed-tools\` | No | Claude Code | Tools this agent can use without asking |
|
|
466
|
+
| \`model\` | No | All | Claude Code: \`sonnet\` or \`haiku\`. Cursor: \`fast\`, \`inherit\`, or a specific model ID. |
|
|
467
|
+
| \`context\` | No | Claude Code | \`fork\` = run in isolated subagent context |
|
|
468
|
+
| \`readonly\` | No | Cursor | \`true\` = sub-agent runs with restricted write permissions (no file edits or state-changing shell commands). (default: \`false\`) |
|
|
469
|
+
| \`is_background\` | No | Cursor | \`true\` = sub-agent runs in background without blocking parent. (default: \`false\`) |
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
### Rules
|
|
474
|
+
|
|
475
|
+
System-level instructions for the AI agent. Cursor only. Rules are markdown files (\`.md\` or \`.mdc\`) stored in \`.cursor/rules/\`. When a rule is active, its content is prepended to the model context.
|
|
476
|
+
|
|
477
|
+
\`\`\`yaml
|
|
478
|
+
---
|
|
479
|
+
description: "When and how this rule should be applied"
|
|
480
|
+
globs:
|
|
481
|
+
- "**/*.ts"
|
|
482
|
+
- "**/*.tsx"
|
|
483
|
+
alwaysApply: false
|
|
386
484
|
---
|
|
485
|
+
|
|
486
|
+
Use \`@filename\` to reference files instead of copying content.
|
|
387
487
|
\`\`\`
|
|
388
488
|
|
|
489
|
+
**Activation Types**
|
|
490
|
+
|
|
491
|
+
| Type | \`alwaysApply\` | \`globs\` | \`description\` | Behavior |
|
|
492
|
+
|------|:-----------:|:-----:|:-----------:|----------|
|
|
493
|
+
| Always | \`true\` | ignored | optional | Included in every conversation |
|
|
494
|
+
| Auto Attached | \`false\` | set | optional | Included when open files match glob patterns |
|
|
495
|
+
| Agent Requested | \`false\` | empty | set | AI decides based on description relevance |
|
|
496
|
+
| Manual | \`false\` | empty | empty | Invoked via \`@rule-name\` mention only |
|
|
497
|
+
|
|
389
498
|
| Field | Required | Description |
|
|
390
499
|
|-------|----------|-------------|
|
|
391
|
-
| \`
|
|
392
|
-
| \`
|
|
393
|
-
| \`
|
|
394
|
-
|
|
395
|
-
|
|
500
|
+
| \`description\` | No | Describes the rule's purpose. Used by AI to decide relevance (Agent Requested type). |
|
|
501
|
+
| \`globs\` | No | File patterns for auto-attachment. e.g. \`["**/*.ts", "src/components/**"]\` |
|
|
502
|
+
| \`alwaysApply\` | No | \`true\` = always included regardless of context. (default: \`false\`) |
|
|
503
|
+
|
|
504
|
+
**Priority**: Team Rules > Project Rules (\`.cursor/rules/\`) > User Rules (Cursor Settings) > \`AGENTS.md\`
|
|
505
|
+
|
|
506
|
+
**Best practices**: Keep rules under 500 lines. Reference files with \`@filename\` instead of copying. Be specific \u2014 avoid duplicating what linters or the agent already knows.
|
|
396
507
|
|
|
397
508
|
---
|
|
398
509
|
|
|
399
510
|
### Hooks
|
|
400
511
|
|
|
401
|
-
Lifecycle
|
|
512
|
+
Lifecycle scripts that fire at specific points in the agent loop. Both Claude Code and Cursor support hooks, but with different configuration formats and event models.
|
|
513
|
+
|
|
514
|
+
#### Claude Code Hooks
|
|
515
|
+
|
|
516
|
+
Defined in YAML frontmatter within skill/command files. Scoped to that component \u2014 active while it runs, removed when it finishes.
|
|
402
517
|
|
|
403
518
|
\`\`\`yaml
|
|
404
519
|
hooks:
|
|
405
520
|
PreToolUse:
|
|
406
|
-
- matcher: "Bash"
|
|
521
|
+
- matcher: "Bash"
|
|
407
522
|
hooks:
|
|
408
523
|
- type: command
|
|
409
524
|
command: ".claude/hooks/validate.sh"
|
|
410
|
-
timeout: 30
|
|
525
|
+
timeout: 30
|
|
411
526
|
PostToolUse:
|
|
412
527
|
- matcher: "Write|Edit"
|
|
413
528
|
hooks:
|
|
414
529
|
- type: command
|
|
415
530
|
command: "npm run lint"
|
|
416
|
-
async: true
|
|
531
|
+
async: true
|
|
417
532
|
Stop:
|
|
418
533
|
- hooks:
|
|
419
534
|
- type: prompt
|
|
@@ -429,12 +544,10 @@ hooks:
|
|
|
429
544
|
| \`PostToolUseFailure\` | tool name | No | After a tool fails |
|
|
430
545
|
| \`UserPromptSubmit\` | \u2014 | Yes | When user submits a prompt |
|
|
431
546
|
| \`SessionStart\` | \`startup\` \\| \`resume\` \\| \`clear\` \\| \`compact\` | No | Session begins |
|
|
432
|
-
| \`Stop\` | \u2014 | Yes |
|
|
547
|
+
| \`Stop\` | \u2014 | Yes | Agent finishes responding |
|
|
433
548
|
| \`Notification\` | \`permission_prompt\` \\| \`idle_prompt\` | No | Notification fires |
|
|
434
549
|
| \`SubagentStart\` / \`SubagentStop\` | agent type | No / Yes | Subagent spawned / finished |
|
|
435
550
|
|
|
436
|
-
Full event list: [hooks reference](https://code.claude.com/docs/en/hooks)
|
|
437
|
-
|
|
438
551
|
**Handler fields**
|
|
439
552
|
|
|
440
553
|
| Field | Type | Description |
|
|
@@ -443,57 +556,134 @@ Full event list: [hooks reference](https://code.claude.com/docs/en/hooks)
|
|
|
443
556
|
| \`command\` | command only | Shell command to execute. Receives hook JSON on stdin |
|
|
444
557
|
| \`prompt\` | prompt/agent | Prompt text. Use \`$ARGUMENTS\` for the hook JSON input |
|
|
445
558
|
| \`timeout\` | all | Seconds before cancel. Defaults: 600 / 30 / 60 |
|
|
446
|
-
| \`async\` | command only | \`true\` = run in background, cannot block
|
|
559
|
+
| \`async\` | command only | \`true\` = run in background, cannot block |
|
|
447
560
|
| \`once\` | command only | \`true\` = run once per session then remove (skills only) |
|
|
448
561
|
|
|
449
|
-
|
|
562
|
+
Full event list: [Claude Code hooks reference](https://code.claude.com/docs/en/hooks)
|
|
450
563
|
|
|
451
|
-
|
|
564
|
+
---
|
|
452
565
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
566
|
+
#### Cursor Hooks
|
|
567
|
+
|
|
568
|
+
Defined in \`hooks.json\` \u2014 not in frontmatter. Project-level (\`.cursor/hooks.json\`) or user-level (\`~/.cursor/hooks.json\`). Supports both command-based and prompt-based (LLM evaluation) handlers.
|
|
456
569
|
|
|
457
|
-
|
|
458
|
-
|
|
570
|
+
\`\`\`json
|
|
571
|
+
{
|
|
572
|
+
"version": 1,
|
|
573
|
+
"hooks": {
|
|
574
|
+
"afterFileEdit": [{ "command": ".cursor/hooks/format.sh" }],
|
|
575
|
+
"beforeShellExecution": [
|
|
576
|
+
{ "command": ".cursor/hooks/approve.sh", "matcher": "curl|wget", "timeout": 30 }
|
|
577
|
+
],
|
|
578
|
+
"stop": [{ "command": ".cursor/hooks/audit.sh", "loop_limit": 10 }],
|
|
579
|
+
"beforeReadFile": [{ "command": ".cursor/hooks/redact.sh", "failClosed": true }],
|
|
580
|
+
"preToolUse": [
|
|
581
|
+
{ "type": "prompt", "prompt": "Is this tool call safe?", "matcher": "Shell" }
|
|
582
|
+
]
|
|
583
|
+
}
|
|
584
|
+
}
|
|
459
585
|
\`\`\`
|
|
460
586
|
|
|
461
|
-
|
|
587
|
+
**Agent Events**
|
|
462
588
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
589
|
+
| Event | Matcher | Can Block | When it fires |
|
|
590
|
+
|-------|---------|:---------:|---------------|
|
|
591
|
+
| \`sessionStart\` | \u2014 | No | New conversation created |
|
|
592
|
+
| \`sessionEnd\` | \u2014 | No | Conversation ends |
|
|
593
|
+
| \`preToolUse\` | tool type | Yes | Before any tool runs |
|
|
594
|
+
| \`postToolUse\` | tool type | No | After a tool succeeds |
|
|
595
|
+
| \`postToolUseFailure\` | tool type | No | After a tool fails/times out |
|
|
596
|
+
| \`subagentStart\` | agent type | Yes | Before sub-agent spawns |
|
|
597
|
+
| \`subagentStop\` | agent type | Yes\\* | Sub-agent completes (\\*followup_message) |
|
|
598
|
+
| \`beforeShellExecution\` | command text | Yes | Before shell command runs |
|
|
599
|
+
| \`afterShellExecution\` | \u2014 | No | After shell command completes |
|
|
600
|
+
| \`beforeMCPExecution\` | \u2014 | Yes | Before MCP tool runs |
|
|
601
|
+
| \`afterMCPExecution\` | \u2014 | No | After MCP tool completes |
|
|
602
|
+
| \`beforeReadFile\` | tool type | Yes | Before file read |
|
|
603
|
+
| \`afterFileEdit\` | tool type | No | After file is edited |
|
|
604
|
+
| \`beforeSubmitPrompt\` | \u2014 | Yes | Before prompt sent to backend |
|
|
605
|
+
| \`preCompact\` | \u2014 | No | Before context compaction (observe only) |
|
|
606
|
+
| \`stop\` | \u2014 | Yes\\* | Agent loop ends (\\*followup_message) |
|
|
607
|
+
| \`afterAgentResponse\` | \u2014 | No | After assistant message |
|
|
608
|
+
| \`afterAgentThought\` | \u2014 | No | After thinking block |
|
|
466
609
|
|
|
467
|
-
|
|
468
|
-
set-prompt install https://github.com/you/my-prompts
|
|
469
|
-
set-prompt link
|
|
610
|
+
**Tab Events** (inline autocomplete only)
|
|
470
611
|
|
|
471
|
-
|
|
472
|
-
|
|
612
|
+
| Event | When it fires |
|
|
613
|
+
|-------|---------------|
|
|
614
|
+
| \`beforeTabFileRead\` | Before Tab reads a file |
|
|
615
|
+
| \`afterTabFileEdit\` | After Tab edits a file |
|
|
616
|
+
|
|
617
|
+
**Handler fields**
|
|
618
|
+
|
|
619
|
+
| Field | Type | Default | Description |
|
|
620
|
+
|-------|------|---------|-------------|
|
|
621
|
+
| \`command\` | string | required | Script path or shell command |
|
|
622
|
+
| \`type\` | string | \`"command"\` | \`"command"\` or \`"prompt"\` (LLM evaluation) |
|
|
623
|
+
| \`timeout\` | number | platform default | Execution timeout in seconds |
|
|
624
|
+
| \`matcher\` | string | \u2014 | Regex filter for when the hook fires |
|
|
625
|
+
| \`loop_limit\` | number\\|null | \`5\` | Max auto-followups for stop/subagentStop. \`null\` = unlimited |
|
|
626
|
+
| \`failClosed\` | boolean | \`false\` | \`true\` = block on hook failure instead of fail-open |
|
|
627
|
+
|
|
628
|
+
Full event list: [Cursor hooks reference](https://cursor.com/docs/hooks)
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
**Decision control** (both platforms)
|
|
633
|
+
|
|
634
|
+
Exit \`0\` = allow. Exit \`2\` = block. Claude Code reads stderr as the reason. Cursor uses JSON stdout:
|
|
635
|
+
|
|
636
|
+
\`\`\`bash
|
|
637
|
+
# Claude Code: deny via JSON
|
|
638
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"reason"}}'
|
|
639
|
+
|
|
640
|
+
# Cursor: deny via JSON
|
|
641
|
+
echo '{"permission":"deny","user_message":"Blocked by policy","agent_message":"Not allowed"}'
|
|
473
642
|
\`\`\`
|
|
643
|
+
|
|
474
644
|
`;
|
|
475
645
|
|
|
476
646
|
// src/commands/scaffold-command.ts
|
|
477
|
-
var
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
647
|
+
var ensureClaudePluginManifest = (repoPath) => {
|
|
648
|
+
const metaDir = path2.join(repoPath, ".claude-plugin");
|
|
649
|
+
const jsonPath = path2.join(metaDir, "plugin.json");
|
|
650
|
+
fs2.mkdirSync(metaDir, { recursive: true });
|
|
651
|
+
fs2.writeFileSync(jsonPath, JSON.stringify({
|
|
652
|
+
name: PLUGIN_NAME,
|
|
653
|
+
version: "1.0.0",
|
|
654
|
+
description: `Managed by set-prompt \u2014 ${repoPath}`
|
|
655
|
+
}, null, 4), { encoding: "utf-8" });
|
|
656
|
+
};
|
|
657
|
+
var ensureCodexPluginManifest = (repoPath) => {
|
|
658
|
+
const metaDir = path2.join(repoPath, ".codex-plugin");
|
|
659
|
+
const jsonPath = path2.join(metaDir, "plugin.json");
|
|
660
|
+
fs2.mkdirSync(metaDir, { recursive: true });
|
|
661
|
+
fs2.writeFileSync(jsonPath, JSON.stringify({
|
|
662
|
+
name: PLUGIN_NAME,
|
|
663
|
+
version: "1.0.0",
|
|
664
|
+
description: `Managed by set-prompt \u2014 ${repoPath}`,
|
|
665
|
+
skills: "./skills/",
|
|
666
|
+
mcpServers: "./mcp.json",
|
|
667
|
+
apps: "./.app.json"
|
|
668
|
+
}, null, 4), { encoding: "utf-8" });
|
|
669
|
+
};
|
|
670
|
+
var ensureMcpJson = (repoPath) => {
|
|
671
|
+
const mcpJsonPath = path2.join(repoPath, "mcp.json");
|
|
672
|
+
if (fs2.existsSync(mcpJsonPath)) {
|
|
673
|
+
return false;
|
|
489
674
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
675
|
+
fs2.writeFileSync(mcpJsonPath, JSON.stringify({ mcpServers: {} }, null, 4), { encoding: "utf-8" });
|
|
676
|
+
return true;
|
|
677
|
+
};
|
|
678
|
+
var ensureAppJson = (repoPath) => {
|
|
679
|
+
const appJsonPath = path2.join(repoPath, ".app.json");
|
|
680
|
+
if (fs2.existsSync(appJsonPath)) {
|
|
681
|
+
return false;
|
|
493
682
|
}
|
|
494
|
-
|
|
683
|
+
fs2.writeFileSync(appJsonPath, JSON.stringify({ apps: {} }, null, 4), { encoding: "utf-8" });
|
|
684
|
+
return true;
|
|
495
685
|
};
|
|
496
|
-
var scaffoldCommand = async (localPath
|
|
686
|
+
var scaffoldCommand = async (localPath) => {
|
|
497
687
|
try {
|
|
498
688
|
let targetPath = null;
|
|
499
689
|
if (localPath != null) {
|
|
@@ -502,82 +692,71 @@ var scaffoldCommand = async (localPath, options = {}) => {
|
|
|
502
692
|
if (configManager.repo_path != null) {
|
|
503
693
|
targetPath = configManager.repo_path;
|
|
504
694
|
} else {
|
|
505
|
-
console.error(
|
|
695
|
+
console.error(chalk3.red("No path provided and no repo registered. Please provide a path."));
|
|
506
696
|
process.exit(1);
|
|
507
697
|
}
|
|
508
698
|
}
|
|
509
699
|
if (fs2.existsSync(targetPath) === false || fs2.statSync(targetPath).isDirectory() === false) {
|
|
510
|
-
console.error(
|
|
700
|
+
console.error(chalk3.red(`Invalid directory path: '${targetPath}'`));
|
|
511
701
|
process.exit(1);
|
|
512
702
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
if (!valid) {
|
|
522
|
-
if (options.force !== true) {
|
|
523
|
-
const proceed = await confirm({
|
|
524
|
-
message: "Some directories are missing. Scaffold them now?",
|
|
525
|
-
default: true
|
|
526
|
-
});
|
|
527
|
-
if (proceed === false) {
|
|
528
|
-
console.log(chalk2.yellow("Scaffold skipped."));
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
const created = [];
|
|
703
|
+
console.log(chalk3.dim(`Scaffolding: ${targetPath}
|
|
704
|
+
`));
|
|
705
|
+
const writeGuide = await confirm({
|
|
706
|
+
message: "Generate SET_PROMPT_GUIDE.md? (reference doc for writing prompts)",
|
|
707
|
+
default: true
|
|
708
|
+
});
|
|
709
|
+
if (writeGuide) {
|
|
533
710
|
const guideMdPath = path2.join(targetPath, "SET_PROMPT_GUIDE.md");
|
|
534
711
|
fs2.writeFileSync(guideMdPath, SET_PROMPT_GUIDE, { encoding: "utf-8", flag: "w" });
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
const gitkeepPath = path2.join(dirPath, ".gitkeep");
|
|
545
|
-
if (!fs2.existsSync(gitkeepPath)) {
|
|
546
|
-
fs2.writeFileSync(gitkeepPath, "", { encoding: "utf-8" });
|
|
547
|
-
if (!created.includes(` ${dirName}/`)) {
|
|
548
|
-
created.push(` ${dirName}/.gitkeep`);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
712
|
+
console.log(`${TAB}${chalk3.green("\u2713")} SET_PROMPT_GUIDE.md`);
|
|
713
|
+
}
|
|
714
|
+
for (const dirName of PROMPT_DIR_NAMES) {
|
|
715
|
+
const dirPath = path2.join(targetPath, dirName);
|
|
716
|
+
if (fs2.existsSync(dirPath)) {
|
|
717
|
+
console.log(`${TAB}${chalk3.dim("\u2713")} ${dirName}/`);
|
|
718
|
+
} else {
|
|
719
|
+
fs2.mkdirSync(dirPath, { recursive: true });
|
|
720
|
+
console.log(`${TAB}${chalk3.green("+")} ${dirName}/`);
|
|
551
721
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
722
|
+
const gitkeepPath = path2.join(dirPath, ".gitkeep");
|
|
723
|
+
if (!fs2.existsSync(gitkeepPath)) {
|
|
724
|
+
fs2.writeFileSync(gitkeepPath, "", { encoding: "utf-8" });
|
|
555
725
|
}
|
|
556
726
|
}
|
|
727
|
+
ensureClaudePluginManifest(targetPath);
|
|
728
|
+
console.log(`${TAB}${chalk3.green("\u2713")} .claude-plugin/plugin.json`);
|
|
729
|
+
ensureCodexPluginManifest(targetPath);
|
|
730
|
+
console.log(`${TAB}${chalk3.green("\u2713")} .codex-plugin/plugin.json`);
|
|
731
|
+
console.log(`${TAB}${ensureMcpJson(targetPath) ? chalk3.green("+") : chalk3.dim("\u2713")} mcp.json`);
|
|
732
|
+
console.log(`${TAB}${ensureAppJson(targetPath) ? chalk3.green("+") : chalk3.dim("\u2713")} .app.json`);
|
|
733
|
+
console.log(chalk3.green("\nScaffold complete."));
|
|
557
734
|
return true;
|
|
558
735
|
} catch (ex) {
|
|
559
|
-
console.error(
|
|
736
|
+
console.error(chalk3.red(`Failed to scaffold repo structure: ${ex.message}`), ex);
|
|
560
737
|
throw ex;
|
|
561
738
|
}
|
|
562
739
|
};
|
|
563
740
|
|
|
564
741
|
// src/commands/install-command.ts
|
|
565
742
|
var cloneRepo = async (remoteUrl) => {
|
|
566
|
-
const proceed = await confirm2({
|
|
567
|
-
message: `Clone and register "${remoteUrl}"?`,
|
|
568
|
-
default: true
|
|
569
|
-
});
|
|
570
|
-
if (proceed == false) {
|
|
571
|
-
console.log(chalk3.yellow("Cancelled."));
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
743
|
const localPath = path3.join(HOME_DIR, "repo");
|
|
744
|
+
let backupPath = null;
|
|
575
745
|
if (fs3.existsSync(localPath) == true) {
|
|
576
|
-
console.warn(chalk3.yellow(`Existing repo found. Backing up before proceeding.`));
|
|
577
746
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
747
|
+
backupPath = path3.join(HOME_DIR, `repo.bak.${timestamp}`);
|
|
748
|
+
try {
|
|
749
|
+
fs3.renameSync(localPath, backupPath);
|
|
750
|
+
console.log(chalk4.yellow(" backed up") + chalk4.dim(` existing repo \u2192 ${backupPath}`));
|
|
751
|
+
} catch (ex) {
|
|
752
|
+
if (ex.code === "EPERM") {
|
|
753
|
+
console.error(chalk4.red("\u274C Cannot rename existing repo \u2014 it may be open in another process."));
|
|
754
|
+
console.log(chalk4.dim(` Close any editors or terminals using: ${localPath}`));
|
|
755
|
+
} else {
|
|
756
|
+
console.error(chalk4.red(`\u274C Failed to backup existing repo: ${ex.message}`));
|
|
757
|
+
}
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
581
760
|
}
|
|
582
761
|
fs3.mkdirSync(path3.dirname(localPath), { recursive: true });
|
|
583
762
|
console.log(`Cloning ${remoteUrl}...`);
|
|
@@ -587,11 +766,15 @@ var cloneRepo = async (remoteUrl) => {
|
|
|
587
766
|
process.exit(1);
|
|
588
767
|
}
|
|
589
768
|
console.log("\u2705 Cloned successfully.");
|
|
769
|
+
if (backupPath != null) {
|
|
770
|
+
fs3.rmSync(backupPath, { recursive: true, force: true });
|
|
771
|
+
console.log(chalk4.red(" removed") + chalk4.dim(` backup \u2192 ${backupPath}`));
|
|
772
|
+
}
|
|
590
773
|
await scaffoldCommand(localPath, { force: true });
|
|
591
774
|
configManager.repo_path = localPath;
|
|
592
775
|
configManager.remote_url = remoteUrl;
|
|
593
776
|
if (configManager.save() === false) {
|
|
594
|
-
console.error(
|
|
777
|
+
console.error(chalk4.red("Failed to save config."));
|
|
595
778
|
return false;
|
|
596
779
|
}
|
|
597
780
|
return true;
|
|
@@ -599,97 +782,81 @@ var cloneRepo = async (remoteUrl) => {
|
|
|
599
782
|
var installCommand = async (target) => {
|
|
600
783
|
try {
|
|
601
784
|
if (isGitUrl(target) === false) {
|
|
602
|
-
console.error(
|
|
603
|
-
console.log(
|
|
785
|
+
console.error(chalk4.red("\u274C Only remote git URLs are supported."));
|
|
786
|
+
console.log(chalk4.dim(" Example: set-prompt install https://github.com/you/my-prompts"));
|
|
604
787
|
process.exit(1);
|
|
605
788
|
}
|
|
789
|
+
const normalizeUrl = (url) => url.replace(/\.git$/, "").toLowerCase();
|
|
790
|
+
if (configManager.repo_path != null) {
|
|
791
|
+
if (normalizeUrl(configManager.remote_url ?? "") === normalizeUrl(target)) {
|
|
792
|
+
console.error(chalk4.red(`\u274C Already installed from the same URL: ${target}`));
|
|
793
|
+
console.log(chalk4.dim(" Use `set-prompt update` to pull the latest changes."));
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
console.warn(chalk4.yellow(`\u26A0 Switching repo: ${configManager.remote_url} \u2192 ${target}`));
|
|
797
|
+
const proceed = await confirm2({ message: "Replace existing installation?", default: false });
|
|
798
|
+
if (!proceed) {
|
|
799
|
+
console.log(chalk4.yellow("Cancelled."));
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
const proceed = await confirm2({ message: `Clone and register "${target}"?`, default: true });
|
|
804
|
+
if (!proceed) {
|
|
805
|
+
console.log(chalk4.yellow("Cancelled."));
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
606
809
|
return await cloneRepo(target);
|
|
607
810
|
} catch (ex) {
|
|
608
|
-
console.error(
|
|
811
|
+
console.error(chalk4.red(`Unexpected error: ${ex.message}`), ex);
|
|
609
812
|
process.exit(1);
|
|
610
813
|
}
|
|
611
814
|
};
|
|
612
815
|
|
|
613
816
|
// src/commands/link-command.ts
|
|
817
|
+
import chalk11 from "chalk";
|
|
818
|
+
import { checkbox } from "@inquirer/prompts";
|
|
819
|
+
|
|
820
|
+
// src/link/claudecode.ts
|
|
614
821
|
import path4 from "path";
|
|
615
822
|
import fs4 from "fs";
|
|
616
823
|
import os2 from "os";
|
|
617
|
-
import
|
|
618
|
-
import { confirm as confirm3
|
|
619
|
-
import { pathExists } from "fs-extra";
|
|
620
|
-
var resolveRepoPath = () => {
|
|
621
|
-
if (configManager.repo_path == null) {
|
|
622
|
-
console.error(chalk4.red("\u274C No repo installed."));
|
|
623
|
-
console.log(chalk4.yellow("Run: set-prompt install <git-url>"));
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
|
-
return configManager.repo_path;
|
|
627
|
-
};
|
|
628
|
-
var PLUGIN_NAME = "set-prompt";
|
|
824
|
+
import chalk5 from "chalk";
|
|
825
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
629
826
|
var linkClaudeCode = async () => {
|
|
630
827
|
const repoPath = resolveRepoPath();
|
|
631
828
|
if (repoPath == null) {
|
|
632
829
|
return;
|
|
633
830
|
}
|
|
634
|
-
const
|
|
831
|
+
const buildMarketplace = () => {
|
|
635
832
|
try {
|
|
636
|
-
fs4.mkdirSync(CLAUDE_CODE_DIR, { recursive: true });
|
|
637
833
|
const marketplaceMetaDir = path4.join(CLAUDE_CODE_DIR, ".claude-plugin");
|
|
638
834
|
fs4.mkdirSync(marketplaceMetaDir, { recursive: true });
|
|
639
835
|
const marketplaceJson = {
|
|
640
|
-
name:
|
|
836
|
+
name: MARKET_NAME,
|
|
641
837
|
owner: { name: os2.userInfo().username },
|
|
642
838
|
metadata: { description: "Managed by set-prompt", version: "1.0.0" },
|
|
643
839
|
plugins: [{ name: PLUGIN_NAME, source: `./plugins/${PLUGIN_NAME}`, description: "Managed by set-prompt" }]
|
|
644
840
|
};
|
|
645
841
|
fs4.writeFileSync(
|
|
646
842
|
path4.join(marketplaceMetaDir, "marketplace.json"),
|
|
647
|
-
JSON.stringify(marketplaceJson, null,
|
|
648
|
-
"utf-8"
|
|
649
|
-
);
|
|
650
|
-
console.log(chalk4.dim(" \u251C\u2500\u2500 .claude-plugin/"));
|
|
651
|
-
console.log(chalk4.dim(" \u2502 \u2514\u2500\u2500 marketplace.json") + chalk4.green(" \u2713"));
|
|
652
|
-
const pluginDir = path4.join(CLAUDE_CODE_DIR, "plugins", PLUGIN_NAME);
|
|
653
|
-
fs4.mkdirSync(pluginDir, { recursive: true });
|
|
654
|
-
console.log(chalk4.dim(" \u2514\u2500\u2500 plugins/"));
|
|
655
|
-
console.log(chalk4.dim(` \u2514\u2500\u2500 ${PLUGIN_NAME}/`));
|
|
656
|
-
const pluginMetaDir = path4.join(pluginDir, ".claude-plugin");
|
|
657
|
-
fs4.mkdirSync(pluginMetaDir, { recursive: true });
|
|
658
|
-
const pluginJson = {
|
|
659
|
-
name: PLUGIN_NAME,
|
|
660
|
-
version: "1.0.0",
|
|
661
|
-
description: "Managed by set-prompt",
|
|
662
|
-
author: { name: path4.basename(repoPath) }
|
|
663
|
-
};
|
|
664
|
-
fs4.writeFileSync(
|
|
665
|
-
path4.join(pluginMetaDir, "plugin.json"),
|
|
666
|
-
JSON.stringify(pluginJson, null, 2),
|
|
843
|
+
JSON.stringify(marketplaceJson, null, 4),
|
|
667
844
|
"utf-8"
|
|
668
845
|
);
|
|
669
|
-
console.log(
|
|
670
|
-
console.log(
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (await pathExists(src) === false) {
|
|
676
|
-
continue;
|
|
677
|
-
}
|
|
678
|
-
if (fs4.existsSync(dest)) {
|
|
679
|
-
fs4.rmSync(dest, { recursive: true, force: true });
|
|
680
|
-
}
|
|
681
|
-
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
682
|
-
fs4.symlinkSync(src, dest, symlinkType);
|
|
683
|
-
linked.push({ dir, src });
|
|
684
|
-
}
|
|
685
|
-
for (const { dir, src } of linked) {
|
|
686
|
-
const isLast = linked[linked.length - 1].dir === dir;
|
|
687
|
-
const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
688
|
-
console.log(chalk4.dim(` ${branch} `) + chalk4.bold(`${dir}/`) + chalk4.dim(` \u2192 ${src}`) + chalk4.green(" \u2713"));
|
|
846
|
+
console.log(chalk5.dim(" \u251C\u2500\u2500 .claude-plugin/"));
|
|
847
|
+
console.log(chalk5.dim(" \u2502 \u2514\u2500\u2500 marketplace.json") + chalk5.green(" \u2713"));
|
|
848
|
+
const pluginLink = path4.join(CLAUDE_CODE_DIR, "plugins", PLUGIN_NAME);
|
|
849
|
+
fs4.mkdirSync(path4.dirname(pluginLink), { recursive: true });
|
|
850
|
+
if (fs4.existsSync(pluginLink)) {
|
|
851
|
+
fs4.rmSync(pluginLink, { recursive: true, force: true });
|
|
689
852
|
}
|
|
853
|
+
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
854
|
+
fs4.symlinkSync(repoPath, pluginLink, symlinkType);
|
|
855
|
+
console.log(chalk5.dim(" \u2514\u2500\u2500 plugins/"));
|
|
856
|
+
console.log(chalk5.dim(` \u2514\u2500\u2500 ${PLUGIN_NAME}/`) + chalk5.dim(` \u2192 ${repoPath}`) + chalk5.green(" \u2713"));
|
|
690
857
|
return true;
|
|
691
858
|
} catch (ex) {
|
|
692
|
-
console.error(
|
|
859
|
+
console.error(chalk5.red(`\u274C Failed to build marketplace structure: ${ex.message}`));
|
|
693
860
|
return false;
|
|
694
861
|
}
|
|
695
862
|
};
|
|
@@ -704,21 +871,21 @@ var linkClaudeCode = async () => {
|
|
|
704
871
|
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
705
872
|
settings = parsed;
|
|
706
873
|
} else {
|
|
707
|
-
console.warn(
|
|
874
|
+
console.warn(chalk5.yellow(" \u26A0 settings.json has unexpected format \u2014 proceeding with caution"));
|
|
708
875
|
}
|
|
709
876
|
} catch {
|
|
710
|
-
console.warn(
|
|
711
|
-
console.error(
|
|
877
|
+
console.warn(chalk5.yellow(" \u26A0 Failed to parse settings.json \u2014 will not overwrite existing file"));
|
|
878
|
+
console.error(chalk5.red("\u274C Could not register plugin. Please add manually."));
|
|
712
879
|
return false;
|
|
713
880
|
}
|
|
714
881
|
}
|
|
715
882
|
settings.extraKnownMarketplaces = {
|
|
716
883
|
...settings.extraKnownMarketplaces,
|
|
717
|
-
[
|
|
884
|
+
[MARKET_NAME]: { source: { source: "directory", path: CLAUDE_CODE_DIR } }
|
|
718
885
|
};
|
|
719
886
|
settings.enabledPlugins = {
|
|
720
887
|
...settings.enabledPlugins,
|
|
721
|
-
[`${PLUGIN_NAME}@${
|
|
888
|
+
[`${PLUGIN_NAME}@${MARKET_NAME}`]: true
|
|
722
889
|
};
|
|
723
890
|
let backupPath = null;
|
|
724
891
|
if (fs4.existsSync(claudeSettingsPath)) {
|
|
@@ -727,21 +894,21 @@ var linkClaudeCode = async () => {
|
|
|
727
894
|
try {
|
|
728
895
|
fs4.copyFileSync(claudeSettingsPath, backupPath);
|
|
729
896
|
} catch (ex) {
|
|
730
|
-
console.warn(
|
|
897
|
+
console.warn(chalk5.yellow(` \u26A0 Could not create backup: ${ex.message}`));
|
|
731
898
|
backupPath = null;
|
|
732
899
|
}
|
|
733
900
|
}
|
|
734
901
|
try {
|
|
735
902
|
fs4.mkdirSync(path4.dirname(claudeSettingsPath), { recursive: true });
|
|
736
|
-
fs4.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null,
|
|
903
|
+
fs4.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 4), "utf-8");
|
|
737
904
|
} catch (ex) {
|
|
738
905
|
if (backupPath !== null) {
|
|
739
906
|
try {
|
|
740
907
|
fs4.copyFileSync(backupPath, claudeSettingsPath);
|
|
741
908
|
fs4.unlinkSync(backupPath);
|
|
742
|
-
console.warn(
|
|
909
|
+
console.warn(chalk5.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
|
|
743
910
|
} catch {
|
|
744
|
-
console.error(
|
|
911
|
+
console.error(chalk5.red(` \u274C Rollback failed. Backup preserved at: ${backupPath}`));
|
|
745
912
|
}
|
|
746
913
|
}
|
|
747
914
|
throw ex;
|
|
@@ -753,60 +920,209 @@ var linkClaudeCode = async () => {
|
|
|
753
920
|
}
|
|
754
921
|
}
|
|
755
922
|
console.log(`\u2705 Registered to Claude Code settings.`);
|
|
756
|
-
console.log(
|
|
923
|
+
console.log(chalk5.dim(` ${claudeSettingsPath}`));
|
|
757
924
|
return true;
|
|
758
925
|
} catch (ex) {
|
|
759
|
-
console.error(
|
|
760
|
-
console.log(
|
|
926
|
+
console.error(chalk5.red(`\u274C Failed to update settings.json: ${ex.message}`));
|
|
927
|
+
console.log(chalk5.dim(" Please add the plugin manually via Claude Code /plugins."));
|
|
761
928
|
return false;
|
|
762
929
|
}
|
|
763
930
|
};
|
|
764
|
-
|
|
931
|
+
const patchInstalledPlugins = () => {
|
|
932
|
+
const installPath = repoPath;
|
|
933
|
+
const installedPluginsPath = path4.join(os2.homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
934
|
+
const pluginKey = `${PLUGIN_NAME}@${MARKET_NAME}`;
|
|
935
|
+
try {
|
|
936
|
+
let data = { version: 2, plugins: {} };
|
|
937
|
+
if (fs4.existsSync(installedPluginsPath)) {
|
|
938
|
+
try {
|
|
939
|
+
data = JSON.parse(fs4.readFileSync(installedPluginsPath, "utf-8"));
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (data.plugins == null) {
|
|
944
|
+
data.plugins = {};
|
|
945
|
+
}
|
|
946
|
+
data.plugins[pluginKey] = [
|
|
947
|
+
{
|
|
948
|
+
scope: "user",
|
|
949
|
+
installPath,
|
|
950
|
+
version: "1.0.0",
|
|
951
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
952
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
953
|
+
}
|
|
954
|
+
];
|
|
955
|
+
fs4.mkdirSync(path4.dirname(installedPluginsPath), { recursive: true });
|
|
956
|
+
fs4.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 4), "utf-8");
|
|
957
|
+
console.log(`\u2705 Patched installed_plugins.json \u2192 installPath points to source.`);
|
|
958
|
+
console.log(chalk5.dim(` ${installPath}`));
|
|
959
|
+
} catch (ex) {
|
|
960
|
+
console.warn(chalk5.yellow(` \u26A0 Could not patch installed_plugins.json: ${ex.message}`));
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
console.log(chalk5.green(`
|
|
765
964
|
Setting up Claude Code plugin...`));
|
|
766
|
-
console.log(
|
|
767
|
-
const
|
|
768
|
-
if (
|
|
965
|
+
console.log(chalk5.dim(CLAUDE_CODE_DIR));
|
|
966
|
+
const marketplaceOk = buildMarketplace();
|
|
967
|
+
if (marketplaceOk === false) {
|
|
769
968
|
return;
|
|
770
969
|
}
|
|
771
970
|
const settingsOk = registerToClaudeSettings();
|
|
772
971
|
if (settingsOk === false) {
|
|
773
972
|
return;
|
|
774
973
|
}
|
|
974
|
+
patchInstalledPlugins();
|
|
775
975
|
configManager.claude_code = { path: CLAUDE_CODE_DIR };
|
|
776
976
|
configManager.save();
|
|
777
977
|
};
|
|
978
|
+
var unlinkClaudeCode = async (force = false) => {
|
|
979
|
+
if (!force) {
|
|
980
|
+
const ok = await confirm3({
|
|
981
|
+
message: `Remove Claude Code plugin dir (${CLAUDE_CODE_DIR}) and settings entries?`,
|
|
982
|
+
default: false
|
|
983
|
+
});
|
|
984
|
+
if (!ok) {
|
|
985
|
+
console.log(chalk5.yellow("Cancelled."));
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
console.log(chalk5.red(`
|
|
990
|
+
Removing Claude Code plugin...`));
|
|
991
|
+
console.log(chalk5.dim(CLAUDE_CODE_DIR));
|
|
992
|
+
const claudeSettingsPath = path4.join(os2.homedir(), ".claude", "settings.json");
|
|
993
|
+
if (fs4.existsSync(claudeSettingsPath)) {
|
|
994
|
+
try {
|
|
995
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
996
|
+
const backupPath = `${claudeSettingsPath}.bak.${timestamp}`;
|
|
997
|
+
fs4.copyFileSync(claudeSettingsPath, backupPath);
|
|
998
|
+
const settings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
|
|
999
|
+
if (settings?.extraKnownMarketplaces?.[MARKET_NAME] !== void 0) {
|
|
1000
|
+
delete settings.extraKnownMarketplaces[MARKET_NAME];
|
|
1001
|
+
}
|
|
1002
|
+
if (settings?.enabledPlugins?.[`${PLUGIN_NAME}@${MARKET_NAME}`] !== void 0) {
|
|
1003
|
+
delete settings.enabledPlugins[`${PLUGIN_NAME}@${MARKET_NAME}`];
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
fs4.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 4), "utf-8");
|
|
1007
|
+
fs4.unlinkSync(backupPath);
|
|
1008
|
+
console.log(chalk5.red(" removed") + chalk5.dim(` set-prompt entries from: ${claudeSettingsPath}`));
|
|
1009
|
+
} catch (ex) {
|
|
1010
|
+
fs4.copyFileSync(backupPath, claudeSettingsPath);
|
|
1011
|
+
fs4.unlinkSync(backupPath);
|
|
1012
|
+
console.warn(chalk5.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
|
|
1013
|
+
}
|
|
1014
|
+
} catch (ex) {
|
|
1015
|
+
console.error(chalk5.red(` \u274C Failed to clean up settings.json: ${ex.message}`));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const claudePluginsDir = path4.join(os2.homedir(), ".claude", "plugins");
|
|
1019
|
+
const installedPluginsPath = path4.join(claudePluginsDir, "installed_plugins.json");
|
|
1020
|
+
if (fs4.existsSync(installedPluginsPath)) {
|
|
1021
|
+
try {
|
|
1022
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1023
|
+
const backupPath = `${installedPluginsPath}.bak.${timestamp}`;
|
|
1024
|
+
fs4.copyFileSync(installedPluginsPath, backupPath);
|
|
1025
|
+
const installed = JSON.parse(fs4.readFileSync(installedPluginsPath, "utf-8"));
|
|
1026
|
+
if (installed?.plugins && typeof installed.plugins === "object") {
|
|
1027
|
+
for (const key of Object.keys(installed.plugins)) {
|
|
1028
|
+
if (key.endsWith(`@${MARKET_NAME}`)) {
|
|
1029
|
+
delete installed.plugins[key];
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
fs4.writeFileSync(installedPluginsPath, JSON.stringify(installed, null, 4), "utf-8");
|
|
1035
|
+
fs4.unlinkSync(backupPath);
|
|
1036
|
+
console.log(chalk5.red(" removed") + chalk5.dim(` set-prompt entries from: ${installedPluginsPath}`));
|
|
1037
|
+
} catch (ex) {
|
|
1038
|
+
fs4.copyFileSync(backupPath, installedPluginsPath);
|
|
1039
|
+
fs4.unlinkSync(backupPath);
|
|
1040
|
+
console.warn(chalk5.yellow(" \u26A0 Write failed \u2014 rolled back installed_plugins.json."));
|
|
1041
|
+
}
|
|
1042
|
+
} catch (ex) {
|
|
1043
|
+
console.error(chalk5.red(` \u274C Failed to clean up installed_plugins.json: ${ex.message}`));
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const knownMarketplacesPath = path4.join(claudePluginsDir, "known_marketplaces.json");
|
|
1047
|
+
if (fs4.existsSync(knownMarketplacesPath)) {
|
|
1048
|
+
try {
|
|
1049
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1050
|
+
const backupPath = `${knownMarketplacesPath}.bak.${timestamp}`;
|
|
1051
|
+
fs4.copyFileSync(knownMarketplacesPath, backupPath);
|
|
1052
|
+
const marketplaces = JSON.parse(fs4.readFileSync(knownMarketplacesPath, "utf-8"));
|
|
1053
|
+
if (marketplaces?.[MARKET_NAME] !== void 0) {
|
|
1054
|
+
delete marketplaces[MARKET_NAME];
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
fs4.writeFileSync(knownMarketplacesPath, JSON.stringify(marketplaces, null, 4), "utf-8");
|
|
1058
|
+
fs4.unlinkSync(backupPath);
|
|
1059
|
+
console.log(chalk5.red(" removed") + chalk5.dim(` set-prompt entry from: ${knownMarketplacesPath}`));
|
|
1060
|
+
} catch (ex) {
|
|
1061
|
+
fs4.copyFileSync(backupPath, knownMarketplacesPath);
|
|
1062
|
+
fs4.unlinkSync(backupPath);
|
|
1063
|
+
console.warn(chalk5.yellow(" \u26A0 Write failed \u2014 rolled back known_marketplaces.json."));
|
|
1064
|
+
}
|
|
1065
|
+
} catch (ex) {
|
|
1066
|
+
console.error(chalk5.red(` \u274C Failed to clean up known_marketplaces.json: ${ex.message}`));
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (fs4.existsSync(CLAUDE_CODE_DIR)) {
|
|
1070
|
+
fs4.rmSync(CLAUDE_CODE_DIR, { recursive: true, force: true });
|
|
1071
|
+
console.log(chalk5.red(" removed") + chalk5.dim(`: ${CLAUDE_CODE_DIR}`));
|
|
1072
|
+
}
|
|
1073
|
+
configManager.claude_code = null;
|
|
1074
|
+
configManager.save();
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// src/link/roocode.ts
|
|
1078
|
+
import path5 from "path";
|
|
1079
|
+
import fs5 from "fs";
|
|
1080
|
+
import chalk6 from "chalk";
|
|
1081
|
+
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
1082
|
+
import { pathExists } from "fs-extra";
|
|
778
1083
|
var linkRooCode = async () => {
|
|
779
1084
|
const repoPath = resolveRepoPath();
|
|
780
1085
|
if (repoPath == null) {
|
|
781
1086
|
return;
|
|
782
1087
|
}
|
|
783
|
-
console.log(
|
|
1088
|
+
console.log(chalk6.green(`
|
|
784
1089
|
Setting up RooCode integration...`));
|
|
785
|
-
console.log(
|
|
1090
|
+
console.log(chalk6.dim(ROO_DIR));
|
|
786
1091
|
const roocodeDirs = AGENT_PROMPT_DIRS["roocode" /* ROOCODE */];
|
|
787
1092
|
const backupExistingRooCodeFiles = async () => {
|
|
788
1093
|
try {
|
|
789
|
-
|
|
1094
|
+
fs5.mkdirSync(ROO_DIR, { recursive: true });
|
|
790
1095
|
const dirsToBackup = [];
|
|
791
1096
|
for (const dir of roocodeDirs) {
|
|
792
|
-
const target =
|
|
793
|
-
if (
|
|
1097
|
+
const target = path5.join(ROO_DIR, dir);
|
|
1098
|
+
if (fs5.existsSync(target) && fs5.lstatSync(target).isSymbolicLink() === false && fs5.readdirSync(target).length > 0) {
|
|
794
1099
|
dirsToBackup.push(dir);
|
|
795
1100
|
}
|
|
796
1101
|
}
|
|
797
1102
|
if (dirsToBackup.length === 0) {
|
|
798
1103
|
return true;
|
|
799
1104
|
}
|
|
800
|
-
|
|
1105
|
+
console.log(chalk6.yellow(`
|
|
1106
|
+
\u26A0 The following existing directories will be replaced by symlinks:`));
|
|
1107
|
+
for (const dir of dirsToBackup) {
|
|
1108
|
+
console.log(chalk6.dim(` - ${path5.join(ROO_DIR, dir)}`));
|
|
1109
|
+
}
|
|
1110
|
+
console.log(chalk6.yellow(` They will be backed up to: `) + chalk6.dim(ROO_BACKUP_DIR));
|
|
1111
|
+
const ok = await confirm4({ message: "Back up existing directories?", default: true });
|
|
1112
|
+
if (!ok) {
|
|
1113
|
+
console.log(chalk6.yellow("Skipped RooCode linking."));
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
fs5.mkdirSync(ROO_BACKUP_DIR, { recursive: true });
|
|
801
1117
|
for (const dir of dirsToBackup) {
|
|
802
|
-
const src =
|
|
803
|
-
const dest =
|
|
804
|
-
|
|
805
|
-
console.log(
|
|
1118
|
+
const src = path5.join(ROO_DIR, dir);
|
|
1119
|
+
const dest = path5.join(ROO_BACKUP_DIR, dir);
|
|
1120
|
+
fs5.renameSync(src, dest);
|
|
1121
|
+
console.log(chalk6.yellow(" backed up") + chalk6.dim(`: ${dir}/ \u2192 ${dest}`));
|
|
806
1122
|
}
|
|
807
1123
|
return true;
|
|
808
1124
|
} catch (ex) {
|
|
809
|
-
console.error(
|
|
1125
|
+
console.error(chalk6.red(`\u274C Failed to backup existing directories: ${ex.message}`));
|
|
810
1126
|
return false;
|
|
811
1127
|
}
|
|
812
1128
|
};
|
|
@@ -814,26 +1130,26 @@ Setting up RooCode integration...`));
|
|
|
814
1130
|
try {
|
|
815
1131
|
const linked = [];
|
|
816
1132
|
for (const dir of roocodeDirs) {
|
|
817
|
-
const src =
|
|
818
|
-
const dest =
|
|
1133
|
+
const src = path5.join(repoPath, dir);
|
|
1134
|
+
const dest = path5.join(ROO_DIR, dir);
|
|
819
1135
|
if (await pathExists(src) === false) {
|
|
820
1136
|
continue;
|
|
821
1137
|
}
|
|
822
|
-
if (
|
|
823
|
-
|
|
1138
|
+
if (fs5.existsSync(dest)) {
|
|
1139
|
+
fs5.rmSync(dest, { recursive: true, force: true });
|
|
824
1140
|
}
|
|
825
1141
|
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
826
|
-
|
|
1142
|
+
fs5.symlinkSync(src, dest, symlinkType);
|
|
827
1143
|
linked.push({ dir, src });
|
|
828
1144
|
}
|
|
829
1145
|
for (const { dir, src } of linked) {
|
|
830
1146
|
const isLast = linked[linked.length - 1].dir === dir;
|
|
831
1147
|
const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
832
|
-
console.log(
|
|
1148
|
+
console.log(chalk6.dim(` ${branch} `) + chalk6.bold(`${dir}/`) + chalk6.dim(` \u2192 ${src}`) + chalk6.green(" \u2713"));
|
|
833
1149
|
}
|
|
834
1150
|
return true;
|
|
835
1151
|
} catch (ex) {
|
|
836
|
-
console.error(
|
|
1152
|
+
console.error(chalk6.red(`\u274C Failed to create symlinks: ${ex.message}`));
|
|
837
1153
|
return false;
|
|
838
1154
|
}
|
|
839
1155
|
};
|
|
@@ -848,38 +1164,97 @@ Setting up RooCode integration...`));
|
|
|
848
1164
|
configManager.roocode = { path: ROO_DIR, backup_path: ROO_BACKUP_DIR };
|
|
849
1165
|
configManager.save();
|
|
850
1166
|
};
|
|
851
|
-
var
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1167
|
+
var unlinkRooCode = async (force = false) => {
|
|
1168
|
+
if (!force) {
|
|
1169
|
+
const ok = await confirm4({
|
|
1170
|
+
message: `Remove RooCode symlinks from ${ROO_DIR}?`,
|
|
1171
|
+
default: false
|
|
1172
|
+
});
|
|
1173
|
+
if (!ok) {
|
|
1174
|
+
console.log(chalk6.yellow("Cancelled."));
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
855
1177
|
}
|
|
856
|
-
console.log(
|
|
857
|
-
|
|
858
|
-
console.log(
|
|
859
|
-
const
|
|
860
|
-
const
|
|
1178
|
+
console.log(chalk6.red(`
|
|
1179
|
+
Removing RooCode integration...`));
|
|
1180
|
+
console.log(chalk6.dim(ROO_DIR));
|
|
1181
|
+
const backupPath = configManager.roocode?.backup_path ?? ROO_BACKUP_DIR;
|
|
1182
|
+
for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
|
|
1183
|
+
const target = path5.join(ROO_DIR, dir);
|
|
1184
|
+
if (fs5.existsSync(target) && fs5.lstatSync(target).isSymbolicLink()) {
|
|
1185
|
+
fs5.unlinkSync(target);
|
|
1186
|
+
console.log(chalk6.red(" removed symlink") + chalk6.dim(`: ${target}`));
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (fs5.existsSync(backupPath)) {
|
|
861
1190
|
try {
|
|
862
|
-
|
|
863
|
-
|
|
1191
|
+
for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
|
|
1192
|
+
const src = path5.join(backupPath, dir);
|
|
1193
|
+
const dest = path5.join(ROO_DIR, dir);
|
|
1194
|
+
if (!fs5.existsSync(src)) {
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
fs5.renameSync(src, dest);
|
|
1198
|
+
console.log(chalk6.green(" restored") + chalk6.dim(`: ${dir}/`));
|
|
1199
|
+
}
|
|
1200
|
+
fs5.rmdirSync(backupPath);
|
|
1201
|
+
} catch (ex) {
|
|
1202
|
+
console.error(chalk6.red(` \u274C Failed to restore RooCode backup: ${ex.message}`));
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
configManager.roocode = null;
|
|
1206
|
+
configManager.save();
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/link/openclaw.ts
|
|
1210
|
+
import path6 from "path";
|
|
1211
|
+
import fs6 from "fs";
|
|
1212
|
+
import chalk7 from "chalk";
|
|
1213
|
+
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
1214
|
+
import { pathExists as pathExists2 } from "fs-extra";
|
|
1215
|
+
var linkOpenclaw = async () => {
|
|
1216
|
+
const repoPath = resolveRepoPath();
|
|
1217
|
+
if (repoPath == null) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
console.log(chalk7.green(`
|
|
1221
|
+
Setting up OpenClaw integration...`));
|
|
1222
|
+
console.log(chalk7.dim(OPENCLAW_DIR));
|
|
1223
|
+
const openclawDirs = AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */];
|
|
1224
|
+
const backupExistingOpenclawFiles = async () => {
|
|
1225
|
+
try {
|
|
1226
|
+
fs6.mkdirSync(OPENCLAW_DIR, { recursive: true });
|
|
1227
|
+
const dirsToBackup = [];
|
|
864
1228
|
for (const dir of openclawDirs) {
|
|
865
|
-
const target =
|
|
866
|
-
if (
|
|
1229
|
+
const target = path6.join(OPENCLAW_DIR, dir);
|
|
1230
|
+
if (fs6.existsSync(target) && fs6.lstatSync(target).isSymbolicLink() === false && fs6.readdirSync(target).length > 0) {
|
|
867
1231
|
dirsToBackup.push(dir);
|
|
868
1232
|
}
|
|
869
1233
|
}
|
|
870
1234
|
if (dirsToBackup.length === 0) {
|
|
871
1235
|
return true;
|
|
872
1236
|
}
|
|
873
|
-
|
|
1237
|
+
console.log(chalk7.yellow(`
|
|
1238
|
+
\u26A0 The following existing directories will be replaced by symlinks:`));
|
|
874
1239
|
for (const dir of dirsToBackup) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1240
|
+
console.log(chalk7.dim(` - ${path6.join(OPENCLAW_DIR, dir)}`));
|
|
1241
|
+
}
|
|
1242
|
+
console.log(chalk7.yellow(` They will be backed up to: `) + chalk7.dim(OPENCLAW_BACKUP_DIR));
|
|
1243
|
+
const ok = await confirm5({ message: "Back up existing directories?", default: true });
|
|
1244
|
+
if (!ok) {
|
|
1245
|
+
console.log(chalk7.yellow("Skipped OpenClaw linking."));
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
fs6.mkdirSync(OPENCLAW_BACKUP_DIR, { recursive: true });
|
|
1249
|
+
for (const dir of dirsToBackup) {
|
|
1250
|
+
const src = path6.join(OPENCLAW_DIR, dir);
|
|
1251
|
+
const dest = path6.join(OPENCLAW_BACKUP_DIR, dir);
|
|
1252
|
+
fs6.renameSync(src, dest);
|
|
1253
|
+
console.log(chalk7.yellow(" backed up") + chalk7.dim(`: ${dir}/ \u2192 ${dest}`));
|
|
879
1254
|
}
|
|
880
1255
|
return true;
|
|
881
1256
|
} catch (ex) {
|
|
882
|
-
console.error(
|
|
1257
|
+
console.error(chalk7.red(`\u274C Failed to backup existing directories: ${ex.message}`));
|
|
883
1258
|
return false;
|
|
884
1259
|
}
|
|
885
1260
|
};
|
|
@@ -887,26 +1262,26 @@ Setting up OpenClaw integration...`));
|
|
|
887
1262
|
try {
|
|
888
1263
|
const linked = [];
|
|
889
1264
|
for (const dir of openclawDirs) {
|
|
890
|
-
const src =
|
|
891
|
-
const dest =
|
|
892
|
-
if (await
|
|
1265
|
+
const src = path6.join(repoPath, dir);
|
|
1266
|
+
const dest = path6.join(OPENCLAW_DIR, dir);
|
|
1267
|
+
if (await pathExists2(src) === false) {
|
|
893
1268
|
continue;
|
|
894
1269
|
}
|
|
895
|
-
if (
|
|
896
|
-
|
|
1270
|
+
if (fs6.existsSync(dest)) {
|
|
1271
|
+
fs6.rmSync(dest, { recursive: true, force: true });
|
|
897
1272
|
}
|
|
898
1273
|
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
899
|
-
|
|
1274
|
+
fs6.symlinkSync(src, dest, symlinkType);
|
|
900
1275
|
linked.push({ dir, src });
|
|
901
1276
|
}
|
|
902
1277
|
for (const { dir, src } of linked) {
|
|
903
1278
|
const isLast = linked[linked.length - 1].dir === dir;
|
|
904
1279
|
const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
905
|
-
console.log(
|
|
1280
|
+
console.log(chalk7.dim(` ${branch} `) + chalk7.bold(`${dir}/`) + chalk7.dim(` \u2192 ${src}`) + chalk7.green(" \u2713"));
|
|
906
1281
|
}
|
|
907
1282
|
return true;
|
|
908
1283
|
} catch (ex) {
|
|
909
|
-
console.error(
|
|
1284
|
+
console.error(chalk7.red(`\u274C Failed to create symlinks: ${ex.message}`));
|
|
910
1285
|
return false;
|
|
911
1286
|
}
|
|
912
1287
|
};
|
|
@@ -921,261 +1296,544 @@ Setting up OpenClaw integration...`));
|
|
|
921
1296
|
configManager.openclaw = { path: OPENCLAW_DIR, backup_path: OPENCLAW_BACKUP_DIR };
|
|
922
1297
|
configManager.save();
|
|
923
1298
|
};
|
|
924
|
-
var
|
|
925
|
-
if (resolveRepoPath() == null) {
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
console.log(chalk4.yellow("Codex integration is not yet implemented."));
|
|
929
|
-
};
|
|
930
|
-
var unlinkClaudeCode = async (force = false) => {
|
|
931
|
-
if (!force) {
|
|
932
|
-
const ok = await confirm3({
|
|
933
|
-
message: `Remove Claude Code plugin dir (${CLAUDE_CODE_DIR}) and settings entries?`,
|
|
934
|
-
default: false
|
|
935
|
-
});
|
|
936
|
-
if (!ok) {
|
|
937
|
-
console.log(chalk4.yellow("Cancelled."));
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
const claudeSettingsPath = path4.join(os2.homedir(), ".claude", "settings.json");
|
|
942
|
-
if (fs4.existsSync(claudeSettingsPath)) {
|
|
943
|
-
try {
|
|
944
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
945
|
-
const backupPath = `${claudeSettingsPath}.bak.${timestamp}`;
|
|
946
|
-
fs4.copyFileSync(claudeSettingsPath, backupPath);
|
|
947
|
-
const settings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
|
|
948
|
-
if (settings?.extraKnownMarketplaces?.[PLUGIN_NAME] !== void 0) {
|
|
949
|
-
delete settings.extraKnownMarketplaces[PLUGIN_NAME];
|
|
950
|
-
}
|
|
951
|
-
if (settings?.enabledPlugins?.[`${PLUGIN_NAME}@${PLUGIN_NAME}`] !== void 0) {
|
|
952
|
-
delete settings.enabledPlugins[`${PLUGIN_NAME}@${PLUGIN_NAME}`];
|
|
953
|
-
}
|
|
954
|
-
try {
|
|
955
|
-
fs4.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
956
|
-
fs4.unlinkSync(backupPath);
|
|
957
|
-
console.log(chalk4.dim(` removed set-prompt entries from: ${claudeSettingsPath}`));
|
|
958
|
-
} catch (ex) {
|
|
959
|
-
fs4.copyFileSync(backupPath, claudeSettingsPath);
|
|
960
|
-
fs4.unlinkSync(backupPath);
|
|
961
|
-
console.warn(chalk4.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
|
|
962
|
-
}
|
|
963
|
-
} catch (ex) {
|
|
964
|
-
console.error(chalk4.red(` \u274C Failed to clean up settings.json: ${ex.message}`));
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
if (fs4.existsSync(CLAUDE_CODE_DIR)) {
|
|
968
|
-
fs4.rmSync(CLAUDE_CODE_DIR, { recursive: true, force: true });
|
|
969
|
-
console.log(chalk4.dim(` removed: ${CLAUDE_CODE_DIR}`));
|
|
970
|
-
}
|
|
971
|
-
configManager.claude_code = null;
|
|
972
|
-
configManager.save();
|
|
973
|
-
};
|
|
974
|
-
var unlinkRooCode = async (force = false) => {
|
|
1299
|
+
var unlinkOpenclaw = async (force = false) => {
|
|
975
1300
|
if (!force) {
|
|
976
|
-
const ok = await
|
|
977
|
-
message: `Remove
|
|
1301
|
+
const ok = await confirm5({
|
|
1302
|
+
message: `Remove OpenClaw symlinks from ${OPENCLAW_DIR}?`,
|
|
978
1303
|
default: false
|
|
979
1304
|
});
|
|
980
1305
|
if (!ok) {
|
|
981
|
-
console.log(
|
|
1306
|
+
console.log(chalk7.yellow("Cancelled."));
|
|
982
1307
|
return;
|
|
983
1308
|
}
|
|
984
1309
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1310
|
+
console.log(chalk7.red(`
|
|
1311
|
+
Removing OpenClaw integration...`));
|
|
1312
|
+
console.log(chalk7.dim(OPENCLAW_DIR));
|
|
1313
|
+
const backupPath = configManager.openclaw?.backup_path ?? OPENCLAW_BACKUP_DIR;
|
|
1314
|
+
for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
|
|
1315
|
+
const target = path6.join(OPENCLAW_DIR, dir);
|
|
1316
|
+
if (fs6.existsSync(target) && fs6.lstatSync(target).isSymbolicLink()) {
|
|
1317
|
+
fs6.unlinkSync(target);
|
|
1318
|
+
console.log(chalk7.red(" removed symlink") + chalk7.dim(`: ${target}`));
|
|
991
1319
|
}
|
|
992
1320
|
}
|
|
993
|
-
if (
|
|
1321
|
+
if (fs6.existsSync(backupPath)) {
|
|
994
1322
|
try {
|
|
995
|
-
for (const dir of AGENT_PROMPT_DIRS["
|
|
996
|
-
const src =
|
|
997
|
-
const dest =
|
|
998
|
-
if (!
|
|
1323
|
+
for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
|
|
1324
|
+
const src = path6.join(backupPath, dir);
|
|
1325
|
+
const dest = path6.join(OPENCLAW_DIR, dir);
|
|
1326
|
+
if (!fs6.existsSync(src)) {
|
|
999
1327
|
continue;
|
|
1000
1328
|
}
|
|
1001
|
-
|
|
1002
|
-
console.log(
|
|
1329
|
+
fs6.renameSync(src, dest);
|
|
1330
|
+
console.log(chalk7.green(" restored") + chalk7.dim(`: ${dir}/`));
|
|
1003
1331
|
}
|
|
1004
|
-
|
|
1332
|
+
fs6.rmdirSync(backupPath);
|
|
1005
1333
|
} catch (ex) {
|
|
1006
|
-
console.error(
|
|
1334
|
+
console.error(chalk7.red(` \u274C Failed to restore OpenClaw backup: ${ex.message}`));
|
|
1007
1335
|
}
|
|
1008
1336
|
}
|
|
1009
|
-
configManager.
|
|
1337
|
+
configManager.openclaw = null;
|
|
1010
1338
|
configManager.save();
|
|
1011
1339
|
};
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1340
|
+
|
|
1341
|
+
// src/link/antigravity.ts
|
|
1342
|
+
import path7 from "path";
|
|
1343
|
+
import fs7 from "fs";
|
|
1344
|
+
import chalk8 from "chalk";
|
|
1345
|
+
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
1346
|
+
import { pathExists as pathExists3 } from "fs-extra";
|
|
1347
|
+
var linkAntigravity = async () => {
|
|
1348
|
+
const repoPath = resolveRepoPath();
|
|
1349
|
+
if (repoPath == null) {
|
|
1350
|
+
return;
|
|
1022
1351
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1352
|
+
console.log(chalk8.green(`
|
|
1353
|
+
Setting up Antigravity integration...`));
|
|
1354
|
+
console.log(chalk8.dim(ANTIGRAVITY_DIR));
|
|
1355
|
+
const antigravityDirs = AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */];
|
|
1356
|
+
const backupExistingAntigravityFiles = async () => {
|
|
1357
|
+
try {
|
|
1358
|
+
fs7.mkdirSync(ANTIGRAVITY_DIR, { recursive: true });
|
|
1359
|
+
const dirsToBackup = [];
|
|
1360
|
+
for (const dir of antigravityDirs) {
|
|
1361
|
+
const target = path7.join(ANTIGRAVITY_DIR, dir);
|
|
1362
|
+
if (fs7.existsSync(target) && fs7.lstatSync(target).isSymbolicLink() === false && fs7.readdirSync(target).length > 0) {
|
|
1363
|
+
dirsToBackup.push(dir);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (dirsToBackup.length === 0) {
|
|
1367
|
+
return true;
|
|
1368
|
+
}
|
|
1369
|
+
console.log(chalk8.yellow(`
|
|
1370
|
+
\u26A0 The following existing directories will be replaced by symlinks:`));
|
|
1371
|
+
for (const dir of dirsToBackup) {
|
|
1372
|
+
console.log(chalk8.dim(` - ${path7.join(ANTIGRAVITY_DIR, dir)}`));
|
|
1373
|
+
}
|
|
1374
|
+
console.log(chalk8.yellow(` They will be backed up to: `) + chalk8.dim(ANTIGRAVITY_BACKUP_DIR));
|
|
1375
|
+
const ok = await confirm6({ message: "Back up existing directories?", default: true });
|
|
1376
|
+
if (!ok) {
|
|
1377
|
+
console.log(chalk8.yellow("Skipped Antigravity linking."));
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1380
|
+
fs7.mkdirSync(ANTIGRAVITY_BACKUP_DIR, { recursive: true });
|
|
1381
|
+
for (const dir of antigravityDirs) {
|
|
1382
|
+
const src = path7.join(ANTIGRAVITY_DIR, dir);
|
|
1383
|
+
const dest = path7.join(ANTIGRAVITY_BACKUP_DIR, dir);
|
|
1384
|
+
fs7.renameSync(src, dest);
|
|
1385
|
+
console.log(chalk8.yellow(" backed up") + chalk8.dim(`: ${dir}/ \u2192 ${dest}`));
|
|
1386
|
+
}
|
|
1387
|
+
return true;
|
|
1388
|
+
} catch (ex) {
|
|
1389
|
+
console.error(chalk8.red(`\u274C Failed to backup existing directories: ${ex.message}`));
|
|
1390
|
+
return false;
|
|
1029
1391
|
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1392
|
+
};
|
|
1393
|
+
const setAntigravityAssets = async () => {
|
|
1032
1394
|
try {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1395
|
+
const linked = [];
|
|
1396
|
+
for (const dir of antigravityDirs) {
|
|
1397
|
+
const src = path7.join(repoPath, dir);
|
|
1398
|
+
const dest = path7.join(ANTIGRAVITY_DIR, dir);
|
|
1399
|
+
if (await pathExists3(src) === false) {
|
|
1037
1400
|
continue;
|
|
1038
1401
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1402
|
+
if (fs7.existsSync(dest)) {
|
|
1403
|
+
fs7.rmSync(dest, { recursive: true, force: true });
|
|
1404
|
+
}
|
|
1405
|
+
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
1406
|
+
fs7.symlinkSync(src, dest, symlinkType);
|
|
1407
|
+
linked.push({ dir, src });
|
|
1041
1408
|
}
|
|
1042
|
-
|
|
1409
|
+
for (const { dir, src } of linked) {
|
|
1410
|
+
const isLast = linked[linked.length - 1].dir === dir;
|
|
1411
|
+
const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
1412
|
+
console.log(chalk8.dim(` ${branch} `) + chalk8.bold(`${dir}/`) + chalk8.dim(` \u2192 ${src}`) + chalk8.green(" \u2713"));
|
|
1413
|
+
}
|
|
1414
|
+
return true;
|
|
1043
1415
|
} catch (ex) {
|
|
1044
|
-
console.error(
|
|
1416
|
+
console.error(chalk8.red(`\u274C Failed to create symlinks: ${ex.message}`));
|
|
1417
|
+
return false;
|
|
1045
1418
|
}
|
|
1419
|
+
};
|
|
1420
|
+
const backupOk = await backupExistingAntigravityFiles();
|
|
1421
|
+
if (backupOk === false) {
|
|
1422
|
+
return;
|
|
1046
1423
|
}
|
|
1047
|
-
|
|
1424
|
+
const linkOk = await setAntigravityAssets();
|
|
1425
|
+
if (linkOk === false) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
configManager.antigravity = { path: ANTIGRAVITY_DIR, backup_path: ANTIGRAVITY_BACKUP_DIR };
|
|
1048
1429
|
configManager.save();
|
|
1049
1430
|
};
|
|
1050
1431
|
var unlinkAntigravity = async (force = false) => {
|
|
1051
1432
|
if (!force) {
|
|
1052
|
-
const ok = await
|
|
1433
|
+
const ok = await confirm6({
|
|
1053
1434
|
message: `Remove Antigravity symlinks from ${ANTIGRAVITY_DIR}?`,
|
|
1054
1435
|
default: false
|
|
1055
1436
|
});
|
|
1056
1437
|
if (!ok) {
|
|
1057
|
-
console.log(
|
|
1438
|
+
console.log(chalk8.yellow("Cancelled."));
|
|
1058
1439
|
return;
|
|
1059
1440
|
}
|
|
1060
1441
|
}
|
|
1442
|
+
console.log(chalk8.red(`
|
|
1443
|
+
Removing Antigravity integration...`));
|
|
1444
|
+
console.log(chalk8.dim(ANTIGRAVITY_DIR));
|
|
1061
1445
|
const backupPath = configManager.antigravity?.backup_path ?? ANTIGRAVITY_BACKUP_DIR;
|
|
1062
1446
|
for (const dir of AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */]) {
|
|
1063
|
-
const target =
|
|
1064
|
-
if (
|
|
1065
|
-
|
|
1066
|
-
console.log(
|
|
1447
|
+
const target = path7.join(ANTIGRAVITY_DIR, dir);
|
|
1448
|
+
if (fs7.existsSync(target) && fs7.lstatSync(target).isSymbolicLink()) {
|
|
1449
|
+
fs7.unlinkSync(target);
|
|
1450
|
+
console.log(chalk8.red(" removed symlink") + chalk8.dim(`: ${target}`));
|
|
1067
1451
|
}
|
|
1068
1452
|
}
|
|
1069
|
-
if (
|
|
1453
|
+
if (fs7.existsSync(backupPath)) {
|
|
1070
1454
|
try {
|
|
1071
1455
|
for (const dir of AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */]) {
|
|
1072
|
-
const src =
|
|
1073
|
-
const dest =
|
|
1074
|
-
if (!
|
|
1456
|
+
const src = path7.join(backupPath, dir);
|
|
1457
|
+
const dest = path7.join(ANTIGRAVITY_DIR, dir);
|
|
1458
|
+
if (!fs7.existsSync(src)) {
|
|
1075
1459
|
continue;
|
|
1076
1460
|
}
|
|
1077
|
-
|
|
1078
|
-
console.log(
|
|
1461
|
+
fs7.renameSync(src, dest);
|
|
1462
|
+
console.log(chalk8.green(" restored") + chalk8.dim(`: ${dir}/`));
|
|
1079
1463
|
}
|
|
1080
|
-
|
|
1464
|
+
fs7.rmdirSync(backupPath);
|
|
1081
1465
|
} catch (ex) {
|
|
1082
|
-
console.error(
|
|
1466
|
+
console.error(chalk8.red(` \u274C Failed to restore Antigravity backup: ${ex.message}`));
|
|
1083
1467
|
}
|
|
1084
1468
|
}
|
|
1085
1469
|
configManager.antigravity = null;
|
|
1086
1470
|
configManager.save();
|
|
1087
1471
|
};
|
|
1088
|
-
|
|
1472
|
+
|
|
1473
|
+
// src/link/codex.ts
|
|
1474
|
+
import path8 from "path";
|
|
1475
|
+
import fs8 from "fs";
|
|
1476
|
+
import os3 from "os";
|
|
1477
|
+
import chalk9 from "chalk";
|
|
1478
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
1479
|
+
import TOML from "smol-toml";
|
|
1480
|
+
var CODEX_AGENTS_DIR = path8.join(os3.homedir(), ".agents", "plugins");
|
|
1481
|
+
var CODEX_CACHE_DIR = path8.join(os3.homedir(), ".codex", "plugins", "cache");
|
|
1482
|
+
var CODEX_MARKETPLACE_NAME = "local-repo";
|
|
1483
|
+
var linkCodex = async () => {
|
|
1089
1484
|
const repoPath = resolveRepoPath();
|
|
1090
1485
|
if (repoPath == null) {
|
|
1091
1486
|
return;
|
|
1092
1487
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1488
|
+
ensureCodexPluginManifest(repoPath);
|
|
1489
|
+
ensureMcpJson(repoPath);
|
|
1490
|
+
ensureAppJson(repoPath);
|
|
1491
|
+
const registerToMarketplace = () => {
|
|
1492
|
+
const marketplacePath = path8.join(CODEX_AGENTS_DIR, "marketplace.json");
|
|
1098
1493
|
try {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1494
|
+
let marketplace = {
|
|
1495
|
+
name: CODEX_MARKETPLACE_NAME,
|
|
1496
|
+
interface: { displayName: "Local Repository" },
|
|
1497
|
+
plugins: []
|
|
1498
|
+
};
|
|
1499
|
+
if (fs8.existsSync(marketplacePath)) {
|
|
1500
|
+
const raw = fs8.readFileSync(marketplacePath, "utf-8");
|
|
1501
|
+
try {
|
|
1502
|
+
const parsed = JSON.parse(raw);
|
|
1503
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1504
|
+
marketplace = parsed;
|
|
1505
|
+
}
|
|
1506
|
+
} catch {
|
|
1507
|
+
console.warn(chalk9.yellow(" \u26A0 Failed to parse marketplace.json \u2014 will not overwrite existing file"));
|
|
1508
|
+
console.error(chalk9.red("\u274C Could not register plugin. Please add manually."));
|
|
1509
|
+
return false;
|
|
1105
1510
|
}
|
|
1106
1511
|
}
|
|
1107
|
-
if (
|
|
1108
|
-
|
|
1512
|
+
if (!Array.isArray(marketplace.plugins)) {
|
|
1513
|
+
marketplace.plugins = [];
|
|
1109
1514
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1515
|
+
marketplace.plugins = marketplace.plugins.filter(
|
|
1516
|
+
(p) => p?.name !== PLUGIN_NAME
|
|
1517
|
+
);
|
|
1518
|
+
const relRepoPath = `./${path8.relative(os3.homedir(), repoPath).replace(/\\/g, "/")}`;
|
|
1519
|
+
marketplace.plugins.push({
|
|
1520
|
+
name: PLUGIN_NAME,
|
|
1521
|
+
source: {
|
|
1522
|
+
source: "local",
|
|
1523
|
+
path: relRepoPath
|
|
1524
|
+
},
|
|
1525
|
+
policy: {
|
|
1526
|
+
installation: "AVAILABLE"
|
|
1527
|
+
},
|
|
1528
|
+
category: "Productivity"
|
|
1529
|
+
});
|
|
1530
|
+
let backupPath = null;
|
|
1531
|
+
if (fs8.existsSync(marketplacePath)) {
|
|
1532
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1533
|
+
backupPath = `${marketplacePath}.bak.${timestamp}`;
|
|
1534
|
+
try {
|
|
1535
|
+
fs8.copyFileSync(marketplacePath, backupPath);
|
|
1536
|
+
} catch (ex) {
|
|
1537
|
+
console.warn(chalk9.yellow(` \u26A0 Could not create backup: ${ex.message}`));
|
|
1538
|
+
backupPath = null;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
try {
|
|
1542
|
+
fs8.mkdirSync(path8.dirname(marketplacePath), { recursive: true });
|
|
1543
|
+
fs8.writeFileSync(marketplacePath, JSON.stringify(marketplace, null, 4), "utf-8");
|
|
1544
|
+
} catch (ex) {
|
|
1545
|
+
if (backupPath !== null) {
|
|
1546
|
+
try {
|
|
1547
|
+
fs8.copyFileSync(backupPath, marketplacePath);
|
|
1548
|
+
fs8.unlinkSync(backupPath);
|
|
1549
|
+
console.warn(chalk9.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
|
|
1550
|
+
} catch {
|
|
1551
|
+
console.error(chalk9.red(` \u274C Rollback failed. Backup preserved at: ${backupPath}`));
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
throw ex;
|
|
1116
1555
|
}
|
|
1556
|
+
if (backupPath !== null) {
|
|
1557
|
+
try {
|
|
1558
|
+
fs8.unlinkSync(backupPath);
|
|
1559
|
+
} catch {
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
console.log(`\u2705 Registered to marketplace.json`);
|
|
1563
|
+
console.log(chalk9.dim(` ${marketplacePath}`));
|
|
1117
1564
|
return true;
|
|
1118
1565
|
} catch (ex) {
|
|
1119
|
-
console.error(
|
|
1566
|
+
console.error(chalk9.red(`\u274C Failed to update marketplace.json: ${ex.message}`));
|
|
1120
1567
|
return false;
|
|
1121
1568
|
}
|
|
1122
1569
|
};
|
|
1123
|
-
const
|
|
1570
|
+
const patchPluginCache = () => {
|
|
1571
|
+
const cachePath = path8.join(CODEX_CACHE_DIR, CODEX_MARKETPLACE_NAME, PLUGIN_NAME, "1.0.0");
|
|
1124
1572
|
try {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
const dest = path4.join(ANTIGRAVITY_DIR, dir);
|
|
1129
|
-
if (await pathExists(src) === false) {
|
|
1130
|
-
continue;
|
|
1131
|
-
}
|
|
1132
|
-
if (fs4.existsSync(dest)) {
|
|
1133
|
-
fs4.rmSync(dest, { recursive: true, force: true });
|
|
1134
|
-
}
|
|
1135
|
-
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
1136
|
-
fs4.symlinkSync(src, dest, symlinkType);
|
|
1137
|
-
linked.push({ dir, src });
|
|
1573
|
+
fs8.mkdirSync(path8.dirname(cachePath), { recursive: true });
|
|
1574
|
+
if (fs8.existsSync(cachePath)) {
|
|
1575
|
+
fs8.rmSync(cachePath, { recursive: true, force: true });
|
|
1138
1576
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1577
|
+
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
1578
|
+
fs8.symlinkSync(repoPath, cachePath, symlinkType);
|
|
1579
|
+
console.log(`\u2705 Patched plugin cache.`);
|
|
1580
|
+
console.log(chalk9.dim(` ${cachePath}`) + chalk9.dim(" \u2192 ") + chalk9.dim(repoPath));
|
|
1581
|
+
} catch (ex) {
|
|
1582
|
+
console.warn(chalk9.yellow(` \u26A0 Could not patch plugin cache: ${ex.message}`));
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
const enableInConfig = () => {
|
|
1586
|
+
const configPath = path8.join(os3.homedir(), ".codex", "config.toml");
|
|
1587
|
+
const pluginKey = `${PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`;
|
|
1588
|
+
try {
|
|
1589
|
+
let config = {};
|
|
1590
|
+
if (fs8.existsSync(configPath)) {
|
|
1591
|
+
config = TOML.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
1143
1592
|
}
|
|
1144
|
-
|
|
1593
|
+
if (config.plugins == null) {
|
|
1594
|
+
config.plugins = {};
|
|
1595
|
+
}
|
|
1596
|
+
config.plugins[pluginKey] = { enabled: true };
|
|
1597
|
+
fs8.mkdirSync(path8.dirname(configPath), { recursive: true });
|
|
1598
|
+
fs8.writeFileSync(configPath, TOML.stringify(config), "utf-8");
|
|
1599
|
+
console.log(`\u2705 Enabled plugin in config.toml`);
|
|
1600
|
+
console.log(chalk9.dim(` ${configPath}`));
|
|
1145
1601
|
} catch (ex) {
|
|
1146
|
-
console.
|
|
1147
|
-
return false;
|
|
1602
|
+
console.warn(chalk9.yellow(` \u26A0 Could not update config.toml: ${ex.message}`));
|
|
1148
1603
|
}
|
|
1149
1604
|
};
|
|
1150
|
-
|
|
1151
|
-
|
|
1605
|
+
console.log(chalk9.green(`
|
|
1606
|
+
Setting up Codex plugin...`));
|
|
1607
|
+
const marketplaceOk = registerToMarketplace();
|
|
1608
|
+
if (marketplaceOk === false) {
|
|
1152
1609
|
return;
|
|
1153
1610
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1611
|
+
patchPluginCache();
|
|
1612
|
+
enableInConfig();
|
|
1613
|
+
configManager.codex = { path: CODEX_DIR };
|
|
1614
|
+
configManager.save();
|
|
1615
|
+
};
|
|
1616
|
+
var unlinkCodex = async (force = false) => {
|
|
1617
|
+
if (!force) {
|
|
1618
|
+
const ok = await confirm7({
|
|
1619
|
+
message: `Remove Codex plugin and marketplace entries?`,
|
|
1620
|
+
default: false
|
|
1621
|
+
});
|
|
1622
|
+
if (!ok) {
|
|
1623
|
+
console.log(chalk9.yellow("Cancelled."));
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
console.log(chalk9.red(`
|
|
1628
|
+
Removing Codex plugin...`));
|
|
1629
|
+
const marketplacePath = path8.join(CODEX_AGENTS_DIR, "marketplace.json");
|
|
1630
|
+
if (fs8.existsSync(marketplacePath)) {
|
|
1631
|
+
try {
|
|
1632
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1633
|
+
const backupPath = `${marketplacePath}.bak.${timestamp}`;
|
|
1634
|
+
fs8.copyFileSync(marketplacePath, backupPath);
|
|
1635
|
+
const marketplace = JSON.parse(fs8.readFileSync(marketplacePath, "utf-8"));
|
|
1636
|
+
if (Array.isArray(marketplace?.plugins)) {
|
|
1637
|
+
marketplace.plugins = marketplace.plugins.filter(
|
|
1638
|
+
(p) => p?.name !== PLUGIN_NAME
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
try {
|
|
1642
|
+
fs8.writeFileSync(marketplacePath, JSON.stringify(marketplace, null, 4), "utf-8");
|
|
1643
|
+
fs8.unlinkSync(backupPath);
|
|
1644
|
+
console.log(chalk9.red(" removed") + chalk9.dim(` ${PLUGIN_NAME} from: ${marketplacePath}`));
|
|
1645
|
+
} catch (ex) {
|
|
1646
|
+
fs8.copyFileSync(backupPath, marketplacePath);
|
|
1647
|
+
fs8.unlinkSync(backupPath);
|
|
1648
|
+
console.warn(chalk9.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
|
|
1649
|
+
}
|
|
1650
|
+
} catch (ex) {
|
|
1651
|
+
console.error(chalk9.red(` \u274C Failed to clean up marketplace.json: ${ex.message}`));
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
const configPath = path8.join(os3.homedir(), ".codex", "config.toml");
|
|
1655
|
+
if (fs8.existsSync(configPath)) {
|
|
1656
|
+
try {
|
|
1657
|
+
const config = TOML.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
1658
|
+
const pluginKey = `${PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`;
|
|
1659
|
+
if (config.plugins?.[pluginKey] !== void 0) {
|
|
1660
|
+
delete config.plugins[pluginKey];
|
|
1661
|
+
fs8.writeFileSync(configPath, TOML.stringify(config), "utf-8");
|
|
1662
|
+
console.log(chalk9.red(" removed") + chalk9.dim(` ${pluginKey} from: ${configPath}`));
|
|
1663
|
+
}
|
|
1664
|
+
} catch (ex) {
|
|
1665
|
+
console.error(chalk9.red(` \u274C Failed to clean up config.toml: ${ex.message}`));
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
const cacheMarketDir = path8.join(CODEX_CACHE_DIR, CODEX_MARKETPLACE_NAME);
|
|
1669
|
+
if (fs8.existsSync(cacheMarketDir)) {
|
|
1670
|
+
fs8.rmSync(cacheMarketDir, { recursive: true, force: true });
|
|
1671
|
+
console.log(chalk9.red(" removed") + chalk9.dim(`: ${cacheMarketDir}`));
|
|
1672
|
+
}
|
|
1673
|
+
configManager.codex = null;
|
|
1674
|
+
configManager.save();
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
// src/link/cursor.ts
|
|
1678
|
+
import path9 from "path";
|
|
1679
|
+
import fs9 from "fs";
|
|
1680
|
+
import chalk10 from "chalk";
|
|
1681
|
+
import { confirm as confirm8 } from "@inquirer/prompts";
|
|
1682
|
+
import { pathExists as pathExists4 } from "fs-extra";
|
|
1683
|
+
var CURSOR_BACKUP_DIR = path9.join(CURSOR_DIR, "SET_PROMPT_BACKUP");
|
|
1684
|
+
var linkCursor = async () => {
|
|
1685
|
+
const repoPath = resolveRepoPath();
|
|
1686
|
+
if (repoPath == null) {
|
|
1156
1687
|
return;
|
|
1157
1688
|
}
|
|
1158
|
-
|
|
1689
|
+
console.log(chalk10.green(`
|
|
1690
|
+
Setting up Cursor integration...`));
|
|
1691
|
+
console.log(chalk10.dim(CURSOR_DIR));
|
|
1692
|
+
const cursorDirs = AGENT_PROMPT_DIRS["cursor" /* CURSOR */];
|
|
1693
|
+
const dirsToBackup = [];
|
|
1694
|
+
for (const dir of cursorDirs) {
|
|
1695
|
+
const target = path9.join(CURSOR_DIR, dir);
|
|
1696
|
+
if (fs9.existsSync(target) && fs9.lstatSync(target).isSymbolicLink() === false && fs9.readdirSync(target).length > 0) {
|
|
1697
|
+
dirsToBackup.push(dir);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
if (dirsToBackup.length > 0) {
|
|
1701
|
+
console.log(chalk10.yellow(`
|
|
1702
|
+
\u26A0 The following existing directories will be replaced by symlinks:`));
|
|
1703
|
+
for (const dir of dirsToBackup) {
|
|
1704
|
+
console.log(chalk10.dim(` - ${path9.join(CURSOR_DIR, dir)}`));
|
|
1705
|
+
}
|
|
1706
|
+
console.log(chalk10.yellow(` They will be backed up to: `) + chalk10.dim(CURSOR_BACKUP_DIR));
|
|
1707
|
+
const ok = await confirm8({ message: "Back up existing directories?", default: true });
|
|
1708
|
+
if (!ok) {
|
|
1709
|
+
console.log(chalk10.yellow("Skipped Cursor linking."));
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
fs9.mkdirSync(CURSOR_BACKUP_DIR, { recursive: true });
|
|
1713
|
+
for (const dir of dirsToBackup) {
|
|
1714
|
+
const src = path9.join(CURSOR_DIR, dir);
|
|
1715
|
+
const dest = path9.join(CURSOR_BACKUP_DIR, dir);
|
|
1716
|
+
fs9.renameSync(src, dest);
|
|
1717
|
+
console.log(chalk10.yellow(" backed up") + chalk10.dim(`: ${dir}/ \u2192 ${dest}`));
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
try {
|
|
1721
|
+
const linked = [];
|
|
1722
|
+
for (const dir of cursorDirs) {
|
|
1723
|
+
const src = path9.join(repoPath, dir);
|
|
1724
|
+
const dest = path9.join(CURSOR_DIR, dir);
|
|
1725
|
+
if (await pathExists4(src) === false) {
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
if (fs9.existsSync(dest)) {
|
|
1729
|
+
fs9.rmSync(dest, { recursive: true, force: true });
|
|
1730
|
+
}
|
|
1731
|
+
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
1732
|
+
fs9.symlinkSync(src, dest, symlinkType);
|
|
1733
|
+
linked.push({ dir, src });
|
|
1734
|
+
}
|
|
1735
|
+
for (const { dir, src } of linked) {
|
|
1736
|
+
console.log(chalk10.dim(` \u251C\u2500\u2500 `) + chalk10.bold(`${dir}/`) + chalk10.dim(` \u2192 ${src}`) + chalk10.green(" \u2713"));
|
|
1737
|
+
}
|
|
1738
|
+
const mcpSrc = path9.join(repoPath, "mcp.json");
|
|
1739
|
+
const mcpDest = path9.join(CURSOR_DIR, "mcp.json");
|
|
1740
|
+
if (fs9.existsSync(mcpSrc)) {
|
|
1741
|
+
if (fs9.existsSync(mcpDest) && fs9.statSync(mcpDest).ino !== fs9.statSync(mcpSrc).ino) {
|
|
1742
|
+
const mcpBackup = path9.join(CURSOR_BACKUP_DIR, "mcp.json");
|
|
1743
|
+
fs9.mkdirSync(CURSOR_BACKUP_DIR, { recursive: true });
|
|
1744
|
+
fs9.renameSync(mcpDest, mcpBackup);
|
|
1745
|
+
console.log(chalk10.yellow(" backed up") + chalk10.dim(`: mcp.json \u2192 ${mcpBackup}`));
|
|
1746
|
+
}
|
|
1747
|
+
if (fs9.existsSync(mcpDest)) {
|
|
1748
|
+
fs9.unlinkSync(mcpDest);
|
|
1749
|
+
}
|
|
1750
|
+
fs9.linkSync(mcpSrc, mcpDest);
|
|
1751
|
+
console.log(chalk10.dim(` \u2514\u2500\u2500 `) + chalk10.bold("mcp.json") + chalk10.dim(` \u21D4 ${mcpSrc}`) + chalk10.green(" \u2713"));
|
|
1752
|
+
}
|
|
1753
|
+
} catch (ex) {
|
|
1754
|
+
console.error(chalk10.red(`\u274C Failed to set up Cursor: ${ex.message}`));
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
configManager.cursor = { path: CURSOR_DIR, backup_path: CURSOR_BACKUP_DIR };
|
|
1159
1758
|
configManager.save();
|
|
1160
1759
|
};
|
|
1760
|
+
var unlinkCursor = async (force = false) => {
|
|
1761
|
+
if (!force) {
|
|
1762
|
+
const ok = await confirm8({
|
|
1763
|
+
message: `Remove Cursor symlinks from ${CURSOR_DIR}?`,
|
|
1764
|
+
default: false
|
|
1765
|
+
});
|
|
1766
|
+
if (!ok) {
|
|
1767
|
+
console.log(chalk10.yellow("Cancelled."));
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
console.log(chalk10.red(`
|
|
1772
|
+
Removing Cursor integration...`));
|
|
1773
|
+
console.log(chalk10.dim(CURSOR_DIR));
|
|
1774
|
+
const backupPath = configManager.cursor?.backup_path ?? CURSOR_BACKUP_DIR;
|
|
1775
|
+
for (const dir of AGENT_PROMPT_DIRS["cursor" /* CURSOR */]) {
|
|
1776
|
+
const target = path9.join(CURSOR_DIR, dir);
|
|
1777
|
+
if (fs9.existsSync(target) && fs9.lstatSync(target).isSymbolicLink()) {
|
|
1778
|
+
fs9.unlinkSync(target);
|
|
1779
|
+
console.log(chalk10.red(" removed symlink") + chalk10.dim(`: ${target}`));
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
const mcpDest = path9.join(CURSOR_DIR, "mcp.json");
|
|
1783
|
+
if (fs9.existsSync(mcpDest)) {
|
|
1784
|
+
fs9.unlinkSync(mcpDest);
|
|
1785
|
+
console.log(chalk10.red(" removed") + chalk10.dim(`: ${mcpDest}`));
|
|
1786
|
+
}
|
|
1787
|
+
if (fs9.existsSync(backupPath)) {
|
|
1788
|
+
try {
|
|
1789
|
+
for (const dir of AGENT_PROMPT_DIRS["cursor" /* CURSOR */]) {
|
|
1790
|
+
const src = path9.join(backupPath, dir);
|
|
1791
|
+
const dest = path9.join(CURSOR_DIR, dir);
|
|
1792
|
+
if (!fs9.existsSync(src)) {
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
fs9.renameSync(src, dest);
|
|
1796
|
+
console.log(chalk10.green(" restored") + chalk10.dim(`: ${dir}/`));
|
|
1797
|
+
}
|
|
1798
|
+
const mcpBackup = path9.join(backupPath, "mcp.json");
|
|
1799
|
+
if (fs9.existsSync(mcpBackup)) {
|
|
1800
|
+
fs9.renameSync(mcpBackup, mcpDest);
|
|
1801
|
+
console.log(chalk10.green(" restored") + chalk10.dim(`: mcp.json`));
|
|
1802
|
+
}
|
|
1803
|
+
fs9.rmSync(backupPath, { recursive: true, force: true });
|
|
1804
|
+
} catch (ex) {
|
|
1805
|
+
console.error(chalk10.red(` \u274C Failed to restore Cursor backup: ${ex.message}`));
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
configManager.cursor = null;
|
|
1809
|
+
configManager.save();
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
// src/commands/link-command.ts
|
|
1813
|
+
var LINK_MAP = {
|
|
1814
|
+
["claudecode" /* CLAUDECODE */]: linkClaudeCode,
|
|
1815
|
+
["roocode" /* ROOCODE */]: linkRooCode,
|
|
1816
|
+
["openclaw" /* OPENCLAW */]: linkOpenclaw,
|
|
1817
|
+
["codex" /* CODEX */]: linkCodex,
|
|
1818
|
+
["antigravity" /* ANTIGRAVITY */]: linkAntigravity,
|
|
1819
|
+
["cursor" /* CURSOR */]: linkCursor
|
|
1820
|
+
};
|
|
1821
|
+
var UNLINK_MAP = {
|
|
1822
|
+
["claudecode" /* CLAUDECODE */]: unlinkClaudeCode,
|
|
1823
|
+
["roocode" /* ROOCODE */]: unlinkRooCode,
|
|
1824
|
+
["openclaw" /* OPENCLAW */]: unlinkOpenclaw,
|
|
1825
|
+
["codex" /* CODEX */]: unlinkCodex,
|
|
1826
|
+
["antigravity" /* ANTIGRAVITY */]: unlinkAntigravity,
|
|
1827
|
+
["cursor" /* CURSOR */]: unlinkCursor
|
|
1828
|
+
};
|
|
1161
1829
|
var linkCommand = async (tool) => {
|
|
1162
1830
|
if (tool != null) {
|
|
1163
1831
|
const known = ALL_AGENTS.some((a) => a.value === tool);
|
|
1164
1832
|
if (!known) {
|
|
1165
|
-
console.log(
|
|
1833
|
+
console.log(chalk11.red(`Unknown vendor: ${tool}`));
|
|
1166
1834
|
process.exit(1);
|
|
1167
1835
|
}
|
|
1168
|
-
|
|
1169
|
-
await linkClaudeCode();
|
|
1170
|
-
} else if (tool === "roocode" /* ROOCODE */) {
|
|
1171
|
-
await linkRooCode();
|
|
1172
|
-
} else if (tool === "openclaw" /* OPENCLAW */) {
|
|
1173
|
-
await linkOpenclaw();
|
|
1174
|
-
} else if (tool === "codex" /* CODEX */) {
|
|
1175
|
-
await linkCodex();
|
|
1176
|
-
} else if (tool === "antigravity" /* ANTIGRAVITY */) {
|
|
1177
|
-
await linkAntigravity();
|
|
1178
|
-
}
|
|
1836
|
+
await LINK_MAP[tool]();
|
|
1179
1837
|
return;
|
|
1180
1838
|
}
|
|
1181
1839
|
const prevLinked = {
|
|
@@ -1183,80 +1841,84 @@ var linkCommand = async (tool) => {
|
|
|
1183
1841
|
["roocode" /* ROOCODE */]: configManager.isRooCodeEnabled(),
|
|
1184
1842
|
["openclaw" /* OPENCLAW */]: configManager.isOpenclawEnabled(),
|
|
1185
1843
|
["codex" /* CODEX */]: configManager.isCodexEnabled(),
|
|
1186
|
-
["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled()
|
|
1844
|
+
["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled(),
|
|
1845
|
+
["cursor" /* CURSOR */]: configManager.isCursorEnabled()
|
|
1187
1846
|
};
|
|
1188
1847
|
const selected = await checkbox({
|
|
1189
1848
|
message: "Which AI agent do you want to integrate?",
|
|
1190
1849
|
choices: ALL_AGENTS.map((a) => ({
|
|
1191
|
-
name: prevLinked[a.value] ? `${a.name} ${
|
|
1850
|
+
name: prevLinked[a.value] ? `${a.name} ${chalk11.dim("(applied)")}` : a.name,
|
|
1192
1851
|
value: a.value,
|
|
1193
1852
|
checked: prevLinked[a.value]
|
|
1194
1853
|
}))
|
|
1195
1854
|
});
|
|
1855
|
+
const toLink = ALL_AGENTS.filter((a) => !prevLinked[a.value] && selected.includes(a.value));
|
|
1856
|
+
const toUnlink = ALL_AGENTS.filter((a) => prevLinked[a.value] && !selected.includes(a.value));
|
|
1857
|
+
console.log();
|
|
1858
|
+
if (toLink.length > 0) {
|
|
1859
|
+
console.log(chalk11.green(" Link ") + chalk11.dim("\u2192 ") + toLink.map((a) => chalk11.bold(a.name)).join(chalk11.dim(", ")));
|
|
1860
|
+
}
|
|
1861
|
+
if (toUnlink.length > 0) {
|
|
1862
|
+
console.log(chalk11.red(" Unlink ") + chalk11.dim("\u2192 ") + toUnlink.map((a) => chalk11.bold(a.name)).join(chalk11.dim(", ")));
|
|
1863
|
+
}
|
|
1864
|
+
console.log();
|
|
1865
|
+
if (toLink.length === 0 && toUnlink.length === 0) {
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1196
1868
|
for (const a of ALL_AGENTS) {
|
|
1197
1869
|
const was = prevLinked[a.value];
|
|
1198
1870
|
const now = selected.includes(a.value);
|
|
1199
1871
|
if (!was && now) {
|
|
1200
|
-
|
|
1201
|
-
await linkClaudeCode();
|
|
1202
|
-
} else if (a.value === "roocode" /* ROOCODE */) {
|
|
1203
|
-
await linkRooCode();
|
|
1204
|
-
} else if (a.value === "openclaw" /* OPENCLAW */) {
|
|
1205
|
-
await linkOpenclaw();
|
|
1206
|
-
} else if (a.value === "codex" /* CODEX */) {
|
|
1207
|
-
await linkCodex();
|
|
1208
|
-
} else if (a.value === "antigravity" /* ANTIGRAVITY */) {
|
|
1209
|
-
await linkAntigravity();
|
|
1210
|
-
}
|
|
1872
|
+
await LINK_MAP[a.value]();
|
|
1211
1873
|
}
|
|
1212
1874
|
if (was && !now) {
|
|
1213
|
-
|
|
1214
|
-
await unlinkClaudeCode(true);
|
|
1215
|
-
} else if (a.value === "roocode" /* ROOCODE */) {
|
|
1216
|
-
await unlinkRooCode(true);
|
|
1217
|
-
} else if (a.value === "openclaw" /* OPENCLAW */) {
|
|
1218
|
-
await unlinkOpenclaw(true);
|
|
1219
|
-
} else if (a.value === "antigravity" /* ANTIGRAVITY */) {
|
|
1220
|
-
await unlinkAntigravity(true);
|
|
1221
|
-
}
|
|
1875
|
+
await UNLINK_MAP[a.value](true);
|
|
1222
1876
|
}
|
|
1223
1877
|
}
|
|
1224
1878
|
};
|
|
1225
1879
|
|
|
1226
1880
|
// src/commands/uninstall-command.ts
|
|
1227
|
-
import
|
|
1228
|
-
import
|
|
1229
|
-
import { confirm as
|
|
1881
|
+
import fs10 from "fs";
|
|
1882
|
+
import chalk12 from "chalk";
|
|
1883
|
+
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
1230
1884
|
var uninstallCommand = async () => {
|
|
1231
1885
|
const targets = [
|
|
1232
|
-
{ label: `Config file ${
|
|
1233
|
-
{ label: `Home dir ${
|
|
1234
|
-
].filter((t) =>
|
|
1886
|
+
{ label: `Config file ${chalk12.dim(CONFIG_PATH)}`, path: CONFIG_PATH },
|
|
1887
|
+
{ label: `Home dir ${chalk12.dim(HOME_DIR)}`, path: HOME_DIR }
|
|
1888
|
+
].filter((t) => fs10.existsSync(t.path));
|
|
1235
1889
|
const hasClaudeCode = configManager.isClaudeCodeEnabled();
|
|
1236
1890
|
const hasRooCode = configManager.isRooCodeEnabled();
|
|
1237
1891
|
const hasOpenclaw = configManager.isOpenclawEnabled();
|
|
1238
1892
|
const hasAntigravity = configManager.isAntigravityEnabled();
|
|
1239
|
-
|
|
1240
|
-
|
|
1893
|
+
const hasCodex = configManager.isCodexEnabled();
|
|
1894
|
+
const hasCursor = configManager.isCursorEnabled();
|
|
1895
|
+
if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity && !hasCodex && !hasCursor) {
|
|
1896
|
+
console.log(chalk12.yellow("Nothing to remove."));
|
|
1241
1897
|
return;
|
|
1242
1898
|
}
|
|
1243
|
-
console.log(
|
|
1899
|
+
console.log(chalk12.red("\nThe following will be removed:"));
|
|
1244
1900
|
targets.forEach((t) => console.log(` ${t.label}`));
|
|
1245
1901
|
if (hasClaudeCode) {
|
|
1246
|
-
console.log(` Claude Code plugin dir ${
|
|
1902
|
+
console.log(` Claude Code plugin dir ${chalk12.dim(CLAUDE_CODE_DIR)}`);
|
|
1247
1903
|
}
|
|
1248
1904
|
if (hasRooCode) {
|
|
1249
|
-
console.log(` RooCode symlinks ${
|
|
1905
|
+
console.log(` RooCode symlinks ${chalk12.dim("(backup will be restored)")}`);
|
|
1250
1906
|
}
|
|
1251
1907
|
if (hasOpenclaw) {
|
|
1252
|
-
console.log(` OpenClaw symlinks ${
|
|
1908
|
+
console.log(` OpenClaw symlinks ${chalk12.dim("(backup will be restored)")}`);
|
|
1253
1909
|
}
|
|
1254
1910
|
if (hasAntigravity) {
|
|
1255
|
-
console.log(` Antigravity symlinks ${
|
|
1911
|
+
console.log(` Antigravity symlinks ${chalk12.dim("(backup will be restored)")}`);
|
|
1912
|
+
}
|
|
1913
|
+
if (hasCodex) {
|
|
1914
|
+
console.log(` Codex symlinks ${chalk12.dim("(backup will be restored)")}`);
|
|
1915
|
+
}
|
|
1916
|
+
if (hasCursor) {
|
|
1917
|
+
console.log(` Cursor plugin dir ${chalk12.dim("(symlink will be removed)")}`);
|
|
1256
1918
|
}
|
|
1257
|
-
const ok = await
|
|
1919
|
+
const ok = await confirm9({ message: "Proceed?", default: false });
|
|
1258
1920
|
if (!ok) {
|
|
1259
|
-
console.log(
|
|
1921
|
+
console.log(chalk12.yellow("Cancelled."));
|
|
1260
1922
|
return;
|
|
1261
1923
|
}
|
|
1262
1924
|
if (hasClaudeCode) {
|
|
@@ -1271,27 +1933,33 @@ var uninstallCommand = async () => {
|
|
|
1271
1933
|
if (hasAntigravity) {
|
|
1272
1934
|
await unlinkAntigravity(true);
|
|
1273
1935
|
}
|
|
1936
|
+
if (hasCodex) {
|
|
1937
|
+
await unlinkCodex(true);
|
|
1938
|
+
}
|
|
1939
|
+
if (hasCursor) {
|
|
1940
|
+
await unlinkCursor(true);
|
|
1941
|
+
}
|
|
1274
1942
|
for (const t of targets) {
|
|
1275
|
-
|
|
1276
|
-
console.log(
|
|
1943
|
+
fs10.rmSync(t.path, { recursive: true, force: true });
|
|
1944
|
+
console.log(chalk12.dim(` removed: ${t.path}`));
|
|
1277
1945
|
}
|
|
1278
|
-
console.log(
|
|
1946
|
+
console.log(chalk12.green("\nUninstalled."));
|
|
1279
1947
|
};
|
|
1280
1948
|
|
|
1281
1949
|
// src/commands/status-command.ts
|
|
1282
|
-
import
|
|
1950
|
+
import chalk13 from "chalk";
|
|
1283
1951
|
var statusCommand = () => {
|
|
1284
1952
|
if (configManager.repo_path == null) {
|
|
1285
|
-
console.log(
|
|
1286
|
-
console.log(
|
|
1953
|
+
console.log(chalk13.yellow("\u274C No repo installed."));
|
|
1954
|
+
console.log(chalk13.dim(` Run: set-prompt install <repo-url>`));
|
|
1287
1955
|
return;
|
|
1288
1956
|
}
|
|
1289
|
-
console.log(
|
|
1290
|
-
console.log(`${TAB}path ${
|
|
1957
|
+
console.log(chalk13.bold("\nRepo"));
|
|
1958
|
+
console.log(`${TAB}path ${chalk13.cyan(configManager.repo_path)}`);
|
|
1291
1959
|
if (configManager.remote_url != null) {
|
|
1292
|
-
console.log(`${TAB}remote ${
|
|
1960
|
+
console.log(`${TAB}remote ${chalk13.dim(configManager.remote_url)}`);
|
|
1293
1961
|
}
|
|
1294
|
-
console.log(
|
|
1962
|
+
console.log(chalk13.bold("\nLinked agents"));
|
|
1295
1963
|
for (const agent of ALL_AGENTS) {
|
|
1296
1964
|
let linked = false;
|
|
1297
1965
|
let agentPath = null;
|
|
@@ -1311,8 +1979,8 @@ var statusCommand = () => {
|
|
|
1311
1979
|
linked = configManager.isAntigravityEnabled();
|
|
1312
1980
|
agentPath = configManager.antigravity?.path;
|
|
1313
1981
|
}
|
|
1314
|
-
const label = linked ?
|
|
1315
|
-
const pathStr = linked && agentPath ?
|
|
1982
|
+
const label = linked ? chalk13.green("linked") : chalk13.dim("not linked");
|
|
1983
|
+
const pathStr = linked && agentPath ? chalk13.dim(` \u2192 ${agentPath}`) : "";
|
|
1316
1984
|
console.log(`${TAB}${agent.name.padEnd(12)} ${label}${pathStr}`);
|
|
1317
1985
|
}
|
|
1318
1986
|
console.log("");
|
|
@@ -1320,69 +1988,69 @@ var statusCommand = () => {
|
|
|
1320
1988
|
|
|
1321
1989
|
// src/commands/update-command.ts
|
|
1322
1990
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1323
|
-
import
|
|
1991
|
+
import chalk14 from "chalk";
|
|
1324
1992
|
var updateCommand = () => {
|
|
1325
1993
|
const repoPath = configManager.repo_path;
|
|
1326
1994
|
if (repoPath == null) {
|
|
1327
|
-
console.error(
|
|
1328
|
-
console.log(
|
|
1995
|
+
console.error(chalk14.red("\u274C No repo installed."));
|
|
1996
|
+
console.log(chalk14.yellow("Run: set-prompt install <git-url>"));
|
|
1329
1997
|
return;
|
|
1330
1998
|
}
|
|
1331
1999
|
if (configManager.remote_url == null) {
|
|
1332
|
-
console.error(
|
|
2000
|
+
console.error(chalk14.red("\u274C No remote URL registered. Cannot update."));
|
|
1333
2001
|
return;
|
|
1334
2002
|
}
|
|
1335
|
-
console.log(
|
|
1336
|
-
console.log(
|
|
2003
|
+
console.log(chalk14.green("\nUpdating prompt repo..."));
|
|
2004
|
+
console.log(chalk14.dim(repoPath));
|
|
1337
2005
|
const fetch = spawnSync2("git", ["fetch"], { cwd: repoPath, stdio: "inherit" });
|
|
1338
2006
|
if (fetch.status !== 0) {
|
|
1339
|
-
console.error(
|
|
2007
|
+
console.error(chalk14.red("\u274C git fetch failed."));
|
|
1340
2008
|
return;
|
|
1341
2009
|
}
|
|
1342
2010
|
const pull = spawnSync2("git", ["pull"], { cwd: repoPath, stdio: "inherit" });
|
|
1343
2011
|
if (pull.status !== 0) {
|
|
1344
|
-
console.error(
|
|
2012
|
+
console.error(chalk14.red("\u274C git pull failed."));
|
|
1345
2013
|
return;
|
|
1346
2014
|
}
|
|
1347
|
-
console.log(
|
|
2015
|
+
console.log(chalk14.green("\u2705 Repo updated."));
|
|
1348
2016
|
};
|
|
1349
2017
|
|
|
1350
2018
|
// src/index.ts
|
|
1351
2019
|
process.on("SIGINT", () => {
|
|
1352
|
-
console.log(
|
|
2020
|
+
console.log(chalk15.yellow("\nCancelled."));
|
|
1353
2021
|
process.exit(0);
|
|
1354
2022
|
});
|
|
1355
2023
|
process.on("unhandledRejection", (reason) => {
|
|
1356
2024
|
if (reason instanceof Error && reason.name === "ExitPromptError") {
|
|
1357
|
-
console.log(
|
|
2025
|
+
console.log(chalk15.yellow("\nCancelled."));
|
|
1358
2026
|
process.exit(0);
|
|
1359
2027
|
}
|
|
1360
2028
|
throw reason;
|
|
1361
2029
|
});
|
|
1362
|
-
var __dirname =
|
|
1363
|
-
var pkg = JSON.parse(
|
|
2030
|
+
var __dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
2031
|
+
var pkg = JSON.parse(fs11.readFileSync(path10.join(__dirname, "../package.json"), "utf-8"));
|
|
1364
2032
|
configManager.init();
|
|
1365
2033
|
var program = new Command();
|
|
1366
|
-
var banner =
|
|
2034
|
+
var banner = chalk15.cyan(figlet.textSync("Set-Prompt", { horizontalLayout: "full" }));
|
|
1367
2035
|
program.name("set-prompt").description(pkg.description).version(pkg.version).addHelpText("beforeAll", banner + "\n").action(() => {
|
|
1368
2036
|
program.help();
|
|
1369
2037
|
});
|
|
1370
|
-
program.command("install").description(`\u{1F4E6} Clone a ${
|
|
2038
|
+
program.command("install").description(`\u{1F4E6} Clone a ${chalk15.cyan("git repo")} into ${chalk15.dim("~/.set-prompt/repo/")} and register it as your prompt source`).argument("<url>", "remote git URL").action(async (source) => {
|
|
1371
2039
|
await installCommand(source);
|
|
1372
2040
|
});
|
|
1373
|
-
program.command("link").description(`\u{1F517} Symlink your prompt repo into an ${
|
|
2041
|
+
program.command("link").description(`\u{1F517} Symlink your prompt repo into an ${chalk15.cyan("AI agent")} plugin dir ${chalk15.dim("(claudecode | roocode | openclaw | codex | antigravity)")}`).argument("[agent]", `target agent ${chalk15.dim("(omit for interactive selection)")}`).action(async (agent) => {
|
|
1374
2042
|
await linkCommand(agent);
|
|
1375
2043
|
});
|
|
1376
|
-
program.command("scaffold").description(`\u{1F6E0}\uFE0F Verify and create ${
|
|
1377
|
-
await scaffoldCommand(localPath
|
|
2044
|
+
program.command("scaffold").description(`\u{1F6E0}\uFE0F Verify and create ${chalk15.cyan("required directories")} in a prompt repo ${chalk15.dim("(-f to force overwrite)")}`).argument("[path]", `path to repo ${chalk15.dim("(defaults to installed source)")}`).action(async (localPath) => {
|
|
2045
|
+
await scaffoldCommand(localPath);
|
|
1378
2046
|
});
|
|
1379
|
-
program.command("status").description(`\u{1F4CB} Show registered ${
|
|
2047
|
+
program.command("status").description(`\u{1F4CB} Show registered ${chalk15.cyan("repo")} and which ${chalk15.cyan("agents")} are linked`).action(() => {
|
|
1380
2048
|
statusCommand();
|
|
1381
2049
|
});
|
|
1382
|
-
program.command("update").description(`\u{1F504} Fetch and pull the latest changes from the ${
|
|
2050
|
+
program.command("update").description(`\u{1F504} Fetch and pull the latest changes from the ${chalk15.cyan("remote repo")}`).action(() => {
|
|
1383
2051
|
updateCommand();
|
|
1384
2052
|
});
|
|
1385
|
-
program.command("uninstall").description(`\u{1F5D1}\uFE0F Remove all set-prompt data ${
|
|
2053
|
+
program.command("uninstall").description(`\u{1F5D1}\uFE0F Remove all set-prompt data ${chalk15.dim("(~/.set-prompt/, plugin dirs, settings entries)")}`).action(async () => {
|
|
1386
2054
|
await uninstallCommand();
|
|
1387
2055
|
});
|
|
1388
2056
|
program.parse(process.argv);
|