set-prompt 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.4.0] - 2026-04-10
8
+
9
+ ### Added
10
+ - Cursor integration (`link cursor`) — creates plugin structure at `~/.cursor/` with backup/restore
11
+ - `linkCommand` now shows a Link / Unlink summary before executing; exits early when there are no changes
12
+ - All `unlink*` functions print a colored header banner on start (consistent with `link*`)
13
+ - Console output: `removed` → red, `restored` → green, `backed up` → yellow
14
+ - Console output: `Config loaded` is now green (consistent with `Config saved`)
15
+
16
+ ### Changed
17
+ - Claude Code plugin: `~/.claude/plugins/installed_plugins.json` is now directly patched so `installPath` points to the source plugin directory — bypasses Claude Code's cache management that was deleting the symlink on startup
18
+ - `install`: re-installing the same URL is blocked with a `set-prompt update` hint
19
+ - `install`: re-installing a different URL shows a "Switching repo" warning with confirmation (default: No)
20
+ - `install`: EPERM on directory rename is handled gracefully with a descriptive error message
21
+ - `install`: existing repo backup is automatically removed after a successful clone
22
+
23
+ ### Disabled
24
+ - `link codex` is temporarily unavailable in this release (implementation preserved, re-enable in next release)
25
+
26
+ ---
27
+
7
28
  ## [0.3.0] - 2026-04-06
8
29
 
9
30
  ### Added
package/README.md CHANGED
@@ -80,16 +80,18 @@ set-prompt link claudecode # link Claude Code only
80
80
  set-prompt link roocode # link RooCode only
81
81
  set-prompt link openclaw # link OpenClaw only
82
82
  set-prompt link antigravity # link Antigravity only
83
+ set-prompt link cursor # link Cursor only
83
84
  ```
84
85
 
85
86
  The interactive mode shows all agents with their current state. **Check to link, uncheck to unlink** — existing directories are backed up before being replaced.
86
87
 
87
88
  | Agent | Where prompts land | What gets linked |
88
89
  |---|---|---|
89
- | Claude Code | `~/.set-prompt/claudecode/` (plugin) | `skills/`, `commands/`, `hooks/`, `agents/` |
90
+ | Claude Code | `~/.set-prompt/claude-code/` (plugin) | `skills/`, `commands/`, `hooks/`, `agents/` |
90
91
  | RooCode | `~/.roo/` | `skills/`, `commands/` |
91
92
  | OpenClaw | `~/.openclaw/workspace/` | `skills/` |
92
93
  | Antigravity | `~/.gemini/antigravity/` | `skills/` |
94
+ | Cursor | `~/.cursor/` (plugin) | `skills/`, `commands/` |
93
95
 
94
96
  ---
95
97
 
@@ -141,9 +143,10 @@ npm test # Run tests with vitest
141
143
 
142
144
  `set-prompt` modifies configuration files managed by third-party AI agent applications:
143
145
 
144
- - **Claude Code** — writes to `~/.claude/settings.json`
146
+ - **Claude Code** — writes to `~/.claude/settings.json` and `~/.claude/plugins/installed_plugins.json`
145
147
  - **RooCode** — replaces directories in `~/.roo/`
146
148
  - **OpenClaw** — replaces directories in `~/.openclaw/workspace/`
149
+ - **Cursor** — writes to `~/.cursor/`
147
150
 
148
151
  Before making any changes, `set-prompt` creates a backup and rolls back automatically on failure. However, you should be aware that:
149
152
 
@@ -164,14 +167,15 @@ Stored at `~/.set-prompt/config.json`, managed via `ConfigManager`.
164
167
  │ ├── skills/
165
168
  │ ├── commands/
166
169
  │ └── hooks/
167
- └── claudecode/ # Claude Code plugin output
170
+ └── claude-code/ # Claude Code plugin output
168
171
  ├── .claude-plugin/
169
172
  │ └── marketplace.json
170
- └── plugins/set-prompt/
173
+ └── plugins/sppt/
171
174
  ├── .claude-plugin/plugin.json
172
175
  ├── skills/ → symlink to repo/skills/
173
176
  ├── commands/ → symlink to repo/commands/
174
- └── hooks/ → symlink to repo/hooks/
177
+ ├── hooks/ → symlink to repo/hooks/
178
+ └── agents/ → symlink to repo/agents/
175
179
 
176
180
  ~/.roo/ # RooCode integration (symlinks)
177
181
  ├── SET_PROMPT_BACKUP/ # backup of original dirs before linking
@@ -195,7 +199,7 @@ Stored at `~/.set-prompt/config.json`, managed via `ConfigManager`.
195
199
 
196
200
  - **Node.js** 18+
197
201
  - **Git** (must be available in `PATH` for `set-prompt install`)
198
- - **Windows**: symlink creation requires either Developer Mode enabled or running as Administrator
202
+ - **Windows only**: symlink creation requires Developer Mode enabled or running as Administrator (Linux/macOS work out of the box)
199
203
 
200
204
  ## 🤝 Contributing
201
205
 
package/dist/index.js CHANGED
@@ -29,20 +29,26 @@ var OPENCLAW_DIR = path.join(os.homedir(), ".openclaw", "workspace");
29
29
  var OPENCLAW_BACKUP_DIR = path.join(OPENCLAW_DIR, "SET_PROMPT_BACKUP");
30
30
  var ANTIGRAVITY_DIR = path.join(os.homedir(), ".gemini", "antigravity");
31
31
  var ANTIGRAVITY_BACKUP_DIR = path.join(ANTIGRAVITY_DIR, "SET_PROMPT_BACKUP");
32
+ var CODEX_DIR = path.join(os.homedir(), ".codex");
33
+ var CODEX_BACKUP_DIR = path.join(CODEX_DIR, "SET_PROMPT_BACKUP");
34
+ var CURSOR_DIR = path.join(HOME_DIR, "cursor");
35
+ var CURSOR_PLUGIN_DIR = path.join(os.homedir(), ".cursor", "plugins", "local", "set-prompt");
32
36
  var PROMPT_DIR_NAMES = ["skills", "commands", "hooks", "agents"];
33
37
  var AGENT_PROMPT_DIRS = {
34
38
  ["claudecode" /* CLAUDECODE */]: ["skills", "commands", "hooks", "agents"],
35
39
  ["roocode" /* ROOCODE */]: ["skills", "commands"],
36
40
  ["openclaw" /* OPENCLAW */]: ["skills"],
37
41
  ["codex" /* CODEX */]: ["skills", "commands"],
38
- ["antigravity" /* ANTIGRAVITY */]: ["skills"]
42
+ ["antigravity" /* ANTIGRAVITY */]: ["skills"],
43
+ ["cursor" /* CURSOR */]: ["skills", "agents", "rules"]
39
44
  };
40
45
  var ALL_AGENTS = [
41
46
  { name: "Claude Code", value: "claudecode" /* CLAUDECODE */ },
42
47
  { name: "RooCode", value: "roocode" /* ROOCODE */ },
43
48
  { name: "OpenClaw", value: "openclaw" /* OPENCLAW */ },
44
49
  { name: "Codex", value: "codex" /* CODEX */ },
45
- { name: "Antigravity", value: "antigravity" /* ANTIGRAVITY */ }
50
+ { name: "Antigravity", value: "antigravity" /* ANTIGRAVITY */ },
51
+ { name: "Cursor", value: "cursor" /* CURSOR */ }
46
52
  ];
47
53
 
48
54
  // src/_libs/config.ts
@@ -65,12 +71,19 @@ var OpenclawConfigSchema = z.object({
65
71
  backup_path: z.string().nullish().optional()
66
72
  });
67
73
  var CodexConfigSchema = z.object({
68
- path: z.string().nullable()
74
+ path: z.string().nullable(),
75
+ backup_path: z.string().nullish().optional()
69
76
  });
70
77
  var AntigravityConfigSchema = z.object({
71
78
  path: z.string().nullable(),
72
79
  backup_path: z.string().nullish().optional()
73
80
  });
81
+ var CursorConfigSchema = z.object({
82
+ path: z.string().nullable(),
83
+ // ~/.set-prompt/cursor (플러그인 파일 구조)
84
+ plugin_dir: z.string().nullable()
85
+ // ~/.cursor/plugins/set-prompt (설치 symlink)
86
+ });
74
87
  var GlobalConfigSchema = z.object({
75
88
  repo_path: z.string(),
76
89
  remote_url: z.string().nullable(),
@@ -78,7 +91,8 @@ var GlobalConfigSchema = z.object({
78
91
  roocode: RoocodeConfigSchema.nullable(),
79
92
  openclaw: OpenclawConfigSchema.nullable(),
80
93
  codex: CodexConfigSchema.nullish().optional(),
81
- antigravity: AntigravityConfigSchema.nullish().optional()
94
+ antigravity: AntigravityConfigSchema.nullish().optional(),
95
+ cursor: CursorConfigSchema.nullish().optional()
82
96
  });
83
97
 
84
98
  // src/_libs/config.ts
@@ -91,6 +105,7 @@ var ConfigManager = class {
91
105
  this._openclaw = null;
92
106
  this._codex = null;
93
107
  this._antigravity = null;
108
+ this._cursor = null;
94
109
  }
95
110
  get repo_path() {
96
111
  return this._repo_path;
@@ -113,6 +128,9 @@ var ConfigManager = class {
113
128
  get antigravity() {
114
129
  return this._antigravity;
115
130
  }
131
+ get cursor() {
132
+ return this._cursor;
133
+ }
116
134
  set repo_path(v) {
117
135
  this._repo_path = v;
118
136
  }
@@ -134,10 +152,13 @@ var ConfigManager = class {
134
152
  set antigravity(v) {
135
153
  this._antigravity = v;
136
154
  }
155
+ set cursor(v) {
156
+ this._cursor = v;
157
+ }
137
158
  init() {
138
159
  this._loadFromDisk();
139
160
  if (this._repo_path != null) {
140
- console.log(chalk.dim(`Config loaded from ${CONFIG_PATH}`));
161
+ console.log(chalk.green(`Config loaded`) + chalk.dim(` from ${CONFIG_PATH}`));
141
162
  }
142
163
  }
143
164
  save() {
@@ -154,7 +175,8 @@ var ConfigManager = class {
154
175
  roocode: this._roocode,
155
176
  openclaw: this._openclaw,
156
177
  codex: this._codex,
157
- antigravity: this._antigravity
178
+ antigravity: this._antigravity,
179
+ cursor: this._cursor
158
180
  }, null, 4);
159
181
  fs.writeFileSync(CONFIG_PATH, configStr, "utf-8");
160
182
  console.log(chalk.green(`Config saved`) + chalk.dim(` \u2192 ${CONFIG_PATH}`));
@@ -188,6 +210,9 @@ var ConfigManager = class {
188
210
  isAntigravityEnabled() {
189
211
  return this._antigravity != null;
190
212
  }
213
+ isCursorEnabled() {
214
+ return this._cursor != null;
215
+ }
191
216
  _assign(config) {
192
217
  this._repo_path = config.repo_path;
193
218
  this._remote_url = config.remote_url;
@@ -196,6 +221,7 @@ var ConfigManager = class {
196
221
  this._openclaw = config.openclaw;
197
222
  this._codex = config.codex ?? null;
198
223
  this._antigravity = config.antigravity ?? null;
224
+ this._cursor = config.cursor ?? null;
199
225
  }
200
226
  _loadFromDisk() {
201
227
  if (fs.existsSync(CONFIG_PATH) === false) {
@@ -563,21 +589,23 @@ var scaffoldCommand = async (localPath, options = {}) => {
563
589
 
564
590
  // src/commands/install-command.ts
565
591
  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
592
  const localPath = path3.join(HOME_DIR, "repo");
593
+ let backupPath = null;
575
594
  if (fs3.existsSync(localPath) == true) {
576
- console.warn(chalk3.yellow(`Existing repo found. Backing up before proceeding.`));
577
595
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
578
- const backupPath = path3.join(HOME_DIR, `repo.bak.${timestamp}`);
579
- fs3.renameSync(localPath, backupPath);
580
- console.log(chalk3.dim(` Backed up to: ${backupPath}`));
596
+ backupPath = path3.join(HOME_DIR, `repo.bak.${timestamp}`);
597
+ try {
598
+ fs3.renameSync(localPath, backupPath);
599
+ console.log(chalk3.yellow(" backed up") + chalk3.dim(` existing repo \u2192 ${backupPath}`));
600
+ } catch (ex) {
601
+ if (ex.code === "EPERM") {
602
+ console.error(chalk3.red("\u274C Cannot rename existing repo \u2014 it may be open in another process."));
603
+ console.log(chalk3.dim(` Close any editors or terminals using: ${localPath}`));
604
+ } else {
605
+ console.error(chalk3.red(`\u274C Failed to backup existing repo: ${ex.message}`));
606
+ }
607
+ return false;
608
+ }
581
609
  }
582
610
  fs3.mkdirSync(path3.dirname(localPath), { recursive: true });
583
611
  console.log(`Cloning ${remoteUrl}...`);
@@ -587,6 +615,10 @@ var cloneRepo = async (remoteUrl) => {
587
615
  process.exit(1);
588
616
  }
589
617
  console.log("\u2705 Cloned successfully.");
618
+ if (backupPath != null) {
619
+ fs3.rmSync(backupPath, { recursive: true, force: true });
620
+ console.log(chalk3.red(" removed") + chalk3.dim(` backup \u2192 ${backupPath}`));
621
+ }
590
622
  await scaffoldCommand(localPath, { force: true });
591
623
  configManager.repo_path = localPath;
592
624
  configManager.remote_url = remoteUrl;
@@ -603,6 +635,26 @@ var installCommand = async (target) => {
603
635
  console.log(chalk3.dim(" Example: set-prompt install https://github.com/you/my-prompts"));
604
636
  process.exit(1);
605
637
  }
638
+ const normalizeUrl = (url) => url.replace(/\.git$/, "").toLowerCase();
639
+ if (configManager.repo_path != null) {
640
+ if (normalizeUrl(configManager.remote_url ?? "") === normalizeUrl(target)) {
641
+ console.error(chalk3.red(`\u274C Already installed from the same URL: ${target}`));
642
+ console.log(chalk3.dim(" Use `set-prompt update` to pull the latest changes."));
643
+ return false;
644
+ }
645
+ console.warn(chalk3.yellow(`\u26A0 Switching repo: ${configManager.remote_url} \u2192 ${target}`));
646
+ const proceed = await confirm2({ message: "Replace existing installation?", default: false });
647
+ if (!proceed) {
648
+ console.log(chalk3.yellow("Cancelled."));
649
+ return false;
650
+ }
651
+ } else {
652
+ const proceed = await confirm2({ message: `Clone and register "${target}"?`, default: true });
653
+ if (!proceed) {
654
+ console.log(chalk3.yellow("Cancelled."));
655
+ return false;
656
+ }
657
+ }
606
658
  return await cloneRepo(target);
607
659
  } catch (ex) {
608
660
  console.error(chalk3.red(`Unexpected error: ${ex.message}`), ex);
@@ -625,7 +677,8 @@ var resolveRepoPath = () => {
625
677
  }
626
678
  return configManager.repo_path;
627
679
  };
628
- var PLUGIN_NAME = "set-prompt";
680
+ var MARKET_NAME = "set-prompt";
681
+ var PLUGIN_NAME = "sppt";
629
682
  var linkClaudeCode = async () => {
630
683
  const repoPath = resolveRepoPath();
631
684
  if (repoPath == null) {
@@ -637,7 +690,7 @@ var linkClaudeCode = async () => {
637
690
  const marketplaceMetaDir = path4.join(CLAUDE_CODE_DIR, ".claude-plugin");
638
691
  fs4.mkdirSync(marketplaceMetaDir, { recursive: true });
639
692
  const marketplaceJson = {
640
- name: PLUGIN_NAME,
693
+ name: MARKET_NAME,
641
694
  owner: { name: os2.userInfo().username },
642
695
  metadata: { description: "Managed by set-prompt", version: "1.0.0" },
643
696
  plugins: [{ name: PLUGIN_NAME, source: `./plugins/${PLUGIN_NAME}`, description: "Managed by set-prompt" }]
@@ -714,11 +767,11 @@ var linkClaudeCode = async () => {
714
767
  }
715
768
  settings.extraKnownMarketplaces = {
716
769
  ...settings.extraKnownMarketplaces,
717
- [PLUGIN_NAME]: { source: { source: "directory", path: CLAUDE_CODE_DIR } }
770
+ [MARKET_NAME]: { source: { source: "directory", path: CLAUDE_CODE_DIR } }
718
771
  };
719
772
  settings.enabledPlugins = {
720
773
  ...settings.enabledPlugins,
721
- [`${PLUGIN_NAME}@${PLUGIN_NAME}`]: true
774
+ [`${PLUGIN_NAME}@${MARKET_NAME}`]: true
722
775
  };
723
776
  let backupPath = null;
724
777
  if (fs4.existsSync(claudeSettingsPath)) {
@@ -761,6 +814,38 @@ var linkClaudeCode = async () => {
761
814
  return false;
762
815
  }
763
816
  };
817
+ const patchInstalledPlugins = () => {
818
+ const installPath = path4.join(CLAUDE_CODE_DIR, "plugins", PLUGIN_NAME);
819
+ const installedPluginsPath = path4.join(os2.homedir(), ".claude", "plugins", "installed_plugins.json");
820
+ const pluginKey = `${PLUGIN_NAME}@${MARKET_NAME}`;
821
+ try {
822
+ let data = { version: 2, plugins: {} };
823
+ if (fs4.existsSync(installedPluginsPath)) {
824
+ try {
825
+ data = JSON.parse(fs4.readFileSync(installedPluginsPath, "utf-8"));
826
+ } catch {
827
+ }
828
+ }
829
+ if (data.plugins == null) {
830
+ data.plugins = {};
831
+ }
832
+ data.plugins[pluginKey] = [
833
+ {
834
+ scope: "user",
835
+ installPath,
836
+ version: "1.0.0",
837
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
838
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
839
+ }
840
+ ];
841
+ fs4.mkdirSync(path4.dirname(installedPluginsPath), { recursive: true });
842
+ fs4.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2), "utf-8");
843
+ console.log(`\u2705 Patched installed_plugins.json \u2192 installPath points to source.`);
844
+ console.log(chalk4.dim(` ${installPath}`));
845
+ } catch (ex) {
846
+ console.warn(chalk4.yellow(` \u26A0 Could not patch installed_plugins.json: ${ex.message}`));
847
+ }
848
+ };
764
849
  console.log(chalk4.green(`
765
850
  Setting up Claude Code plugin...`));
766
851
  console.log(chalk4.dim(CLAUDE_CODE_DIR));
@@ -772,6 +857,7 @@ Setting up Claude Code plugin...`));
772
857
  if (settingsOk === false) {
773
858
  return;
774
859
  }
860
+ patchInstalledPlugins();
775
861
  configManager.claude_code = { path: CLAUDE_CODE_DIR };
776
862
  configManager.save();
777
863
  };
@@ -790,19 +876,30 @@ Setting up RooCode integration...`));
790
876
  const dirsToBackup = [];
791
877
  for (const dir of roocodeDirs) {
792
878
  const target = path4.join(ROO_DIR, dir);
793
- if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink() === false) {
879
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink() === false && fs4.readdirSync(target).length > 0) {
794
880
  dirsToBackup.push(dir);
795
881
  }
796
882
  }
797
883
  if (dirsToBackup.length === 0) {
798
884
  return true;
799
885
  }
886
+ console.log(chalk4.yellow(`
887
+ \u26A0 The following existing directories will be replaced by symlinks:`));
888
+ for (const dir of dirsToBackup) {
889
+ console.log(chalk4.dim(` - ${path4.join(ROO_DIR, dir)}`));
890
+ }
891
+ console.log(chalk4.yellow(` They will be backed up to: `) + chalk4.dim(ROO_BACKUP_DIR));
892
+ const ok = await confirm3({ message: "Back up existing directories?", default: true });
893
+ if (!ok) {
894
+ console.log(chalk4.yellow("Skipped RooCode linking."));
895
+ return false;
896
+ }
800
897
  fs4.mkdirSync(ROO_BACKUP_DIR, { recursive: true });
801
898
  for (const dir of dirsToBackup) {
802
899
  const src = path4.join(ROO_DIR, dir);
803
900
  const dest = path4.join(ROO_BACKUP_DIR, dir);
804
901
  fs4.renameSync(src, dest);
805
- console.log(chalk4.dim(` backed up: ${dir}/ \u2192 ${dest}`));
902
+ console.log(chalk4.yellow(" backed up") + chalk4.dim(`: ${dir}/ \u2192 ${dest}`));
806
903
  }
807
904
  return true;
808
905
  } catch (ex) {
@@ -863,19 +960,30 @@ Setting up OpenClaw integration...`));
863
960
  const dirsToBackup = [];
864
961
  for (const dir of openclawDirs) {
865
962
  const target = path4.join(OPENCLAW_DIR, dir);
866
- if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink() === false) {
963
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink() === false && fs4.readdirSync(target).length > 0) {
867
964
  dirsToBackup.push(dir);
868
965
  }
869
966
  }
870
967
  if (dirsToBackup.length === 0) {
871
968
  return true;
872
969
  }
970
+ console.log(chalk4.yellow(`
971
+ \u26A0 The following existing directories will be replaced by symlinks:`));
972
+ for (const dir of dirsToBackup) {
973
+ console.log(chalk4.dim(` - ${path4.join(OPENCLAW_DIR, dir)}`));
974
+ }
975
+ console.log(chalk4.yellow(` They will be backed up to: `) + chalk4.dim(OPENCLAW_BACKUP_DIR));
976
+ const ok = await confirm3({ message: "Back up existing directories?", default: true });
977
+ if (!ok) {
978
+ console.log(chalk4.yellow("Skipped OpenClaw linking."));
979
+ return false;
980
+ }
873
981
  fs4.mkdirSync(OPENCLAW_BACKUP_DIR, { recursive: true });
874
982
  for (const dir of dirsToBackup) {
875
983
  const src = path4.join(OPENCLAW_DIR, dir);
876
984
  const dest = path4.join(OPENCLAW_BACKUP_DIR, dir);
877
985
  fs4.renameSync(src, dest);
878
- console.log(chalk4.dim(` backed up: ${dir}/ \u2192 ${dest}`));
986
+ console.log(chalk4.yellow(" backed up") + chalk4.dim(`: ${dir}/ \u2192 ${dest}`));
879
987
  }
880
988
  return true;
881
989
  } catch (ex) {
@@ -921,11 +1029,181 @@ Setting up OpenClaw integration...`));
921
1029
  configManager.openclaw = { path: OPENCLAW_DIR, backup_path: OPENCLAW_BACKUP_DIR };
922
1030
  configManager.save();
923
1031
  };
1032
+ var linkAntigravity = async () => {
1033
+ const repoPath = resolveRepoPath();
1034
+ if (repoPath == null) {
1035
+ return;
1036
+ }
1037
+ console.log(chalk4.green(`
1038
+ Setting up Antigravity integration...`));
1039
+ console.log(chalk4.dim(ANTIGRAVITY_DIR));
1040
+ const antigravityDirs = AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */];
1041
+ const backupExistingAntigravityFiles = async () => {
1042
+ try {
1043
+ fs4.mkdirSync(ANTIGRAVITY_DIR, { recursive: true });
1044
+ const dirsToBackup = [];
1045
+ for (const dir of antigravityDirs) {
1046
+ const target = path4.join(ANTIGRAVITY_DIR, dir);
1047
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink() === false && fs4.readdirSync(target).length > 0) {
1048
+ dirsToBackup.push(dir);
1049
+ }
1050
+ }
1051
+ if (dirsToBackup.length === 0) {
1052
+ return true;
1053
+ }
1054
+ console.log(chalk4.yellow(`
1055
+ \u26A0 The following existing directories will be replaced by symlinks:`));
1056
+ for (const dir of dirsToBackup) {
1057
+ console.log(chalk4.dim(` - ${path4.join(ANTIGRAVITY_DIR, dir)}`));
1058
+ }
1059
+ console.log(chalk4.yellow(` They will be backed up to: `) + chalk4.dim(ANTIGRAVITY_BACKUP_DIR));
1060
+ const ok = await confirm3({ message: "Back up existing directories?", default: true });
1061
+ if (!ok) {
1062
+ console.log(chalk4.yellow("Skipped Antigravity linking."));
1063
+ return false;
1064
+ }
1065
+ fs4.mkdirSync(ANTIGRAVITY_BACKUP_DIR, { recursive: true });
1066
+ for (const dir of antigravityDirs) {
1067
+ const src = path4.join(ANTIGRAVITY_DIR, dir);
1068
+ const dest = path4.join(ANTIGRAVITY_BACKUP_DIR, dir);
1069
+ fs4.renameSync(src, dest);
1070
+ console.log(chalk4.yellow(" backed up") + chalk4.dim(`: ${dir}/ \u2192 ${dest}`));
1071
+ }
1072
+ return true;
1073
+ } catch (ex) {
1074
+ console.error(chalk4.red(`\u274C Failed to backup existing directories: ${ex.message}`));
1075
+ return false;
1076
+ }
1077
+ };
1078
+ const setAntigravityAssets = async () => {
1079
+ try {
1080
+ const linked = [];
1081
+ for (const dir of antigravityDirs) {
1082
+ const src = path4.join(repoPath, dir);
1083
+ const dest = path4.join(ANTIGRAVITY_DIR, dir);
1084
+ if (await pathExists(src) === false) {
1085
+ continue;
1086
+ }
1087
+ if (fs4.existsSync(dest)) {
1088
+ fs4.rmSync(dest, { recursive: true, force: true });
1089
+ }
1090
+ const symlinkType = process.platform === "win32" ? "junction" : "dir";
1091
+ fs4.symlinkSync(src, dest, symlinkType);
1092
+ linked.push({ dir, src });
1093
+ }
1094
+ for (const { dir, src } of linked) {
1095
+ const isLast = linked[linked.length - 1].dir === dir;
1096
+ const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
1097
+ console.log(chalk4.dim(` ${branch} `) + chalk4.bold(`${dir}/`) + chalk4.dim(` \u2192 ${src}`) + chalk4.green(" \u2713"));
1098
+ }
1099
+ return true;
1100
+ } catch (ex) {
1101
+ console.error(chalk4.red(`\u274C Failed to create symlinks: ${ex.message}`));
1102
+ return false;
1103
+ }
1104
+ };
1105
+ const backupOk = await backupExistingAntigravityFiles();
1106
+ if (backupOk === false) {
1107
+ return;
1108
+ }
1109
+ const linkOk = await setAntigravityAssets();
1110
+ if (linkOk === false) {
1111
+ return;
1112
+ }
1113
+ configManager.antigravity = { path: ANTIGRAVITY_DIR, backup_path: ANTIGRAVITY_BACKUP_DIR };
1114
+ configManager.save();
1115
+ };
924
1116
  var linkCodex = async () => {
925
- if (resolveRepoPath() == null) {
1117
+ console.error(chalk4.red("\u274C Codex integration is not available in this version."));
1118
+ return;
1119
+ };
1120
+ var linkCursor = async () => {
1121
+ const repoPath = resolveRepoPath();
1122
+ if (repoPath == null) {
926
1123
  return;
927
1124
  }
928
- console.log(chalk4.yellow("Codex integration is not yet implemented."));
1125
+ const CURSOR_LOCAL_DIR = "local";
1126
+ const CURSOR_PLUGIN_NAME = "set-prompt";
1127
+ const setCursorAssets = async () => {
1128
+ try {
1129
+ const marketplaceMetaDir = path4.join(CURSOR_DIR, ".cursor-plugin");
1130
+ fs4.mkdirSync(marketplaceMetaDir, { recursive: true });
1131
+ const marketplaceJson = {
1132
+ name: CURSOR_PLUGIN_NAME,
1133
+ owner: { name: os2.userInfo().username },
1134
+ metadata: { description: "Managed by set-prompt" },
1135
+ plugins: [{ name: CURSOR_PLUGIN_NAME, source: `./plugins/${CURSOR_LOCAL_DIR}/${CURSOR_PLUGIN_NAME}`, description: "Managed by set-prompt" }]
1136
+ };
1137
+ fs4.writeFileSync(
1138
+ path4.join(marketplaceMetaDir, "marketplace.json"),
1139
+ JSON.stringify(marketplaceJson, null, 2),
1140
+ "utf-8"
1141
+ );
1142
+ console.log(chalk4.dim(" \u251C\u2500\u2500 .cursor-plugin/"));
1143
+ console.log(chalk4.dim(" \u2502 \u2514\u2500\u2500 marketplace.json") + chalk4.green(" \u2713"));
1144
+ const pluginDir = path4.join(CURSOR_DIR, "plugins", CURSOR_LOCAL_DIR, CURSOR_PLUGIN_NAME);
1145
+ const pluginMetaDir = path4.join(pluginDir, ".cursor-plugin");
1146
+ fs4.mkdirSync(pluginMetaDir, { recursive: true });
1147
+ const cursorDirs = AGENT_PROMPT_DIRS["cursor" /* CURSOR */];
1148
+ const pluginJson = {
1149
+ name: CURSOR_PLUGIN_NAME,
1150
+ displayName: "set-prompt",
1151
+ version: "1.0.0",
1152
+ description: "Managed by set-prompt"
1153
+ };
1154
+ for (const dir of cursorDirs) {
1155
+ pluginJson[dir] = `./${dir}/`;
1156
+ }
1157
+ fs4.writeFileSync(
1158
+ path4.join(pluginMetaDir, "plugin.json"),
1159
+ JSON.stringify(pluginJson, null, 2),
1160
+ "utf-8"
1161
+ );
1162
+ console.log(chalk4.dim(" \u2514\u2500\u2500 plugins/"));
1163
+ console.log(chalk4.dim(` \u2514\u2500\u2500 ${CURSOR_PLUGIN_NAME}/`));
1164
+ console.log(chalk4.dim(" \u251C\u2500\u2500 .cursor-plugin/"));
1165
+ console.log(chalk4.dim(" \u2502 \u2514\u2500\u2500 plugin.json") + chalk4.green(" \u2713"));
1166
+ const linked = [];
1167
+ for (const dir of cursorDirs) {
1168
+ const src = path4.join(repoPath, dir);
1169
+ const dest = path4.join(pluginDir, dir);
1170
+ if (await pathExists(src) === false) {
1171
+ continue;
1172
+ }
1173
+ if (fs4.existsSync(dest)) {
1174
+ fs4.rmSync(dest, { recursive: true, force: true });
1175
+ }
1176
+ const symlinkType2 = process.platform === "win32" ? "junction" : "dir";
1177
+ fs4.symlinkSync(src, dest, symlinkType2);
1178
+ linked.push({ dir, src });
1179
+ }
1180
+ for (const { dir, src } of linked) {
1181
+ const isLast = linked[linked.length - 1].dir === dir;
1182
+ const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
1183
+ console.log(chalk4.dim(` ${branch} `) + chalk4.bold(`${dir}/`) + chalk4.dim(` \u2192 ${src}`) + chalk4.green(" \u2713"));
1184
+ }
1185
+ fs4.rmSync(CURSOR_PLUGIN_DIR, { recursive: true, force: true });
1186
+ fs4.mkdirSync(path4.dirname(CURSOR_PLUGIN_DIR), { recursive: true });
1187
+ const symlinkType = process.platform === "win32" ? "junction" : "dir";
1188
+ fs4.symlinkSync(pluginDir, CURSOR_PLUGIN_DIR, symlinkType);
1189
+ console.log(chalk4.green(`
1190
+ \u2705 Installed to Cursor plugins.`));
1191
+ console.log(chalk4.dim(` ${CURSOR_PLUGIN_DIR}`));
1192
+ return true;
1193
+ } catch (ex) {
1194
+ console.error(chalk4.red(`\u274C Failed to build Cursor plugin structure: ${ex.message}`));
1195
+ return false;
1196
+ }
1197
+ };
1198
+ console.log(chalk4.green(`
1199
+ Setting up Cursor plugin...`));
1200
+ console.log(chalk4.dim(CURSOR_DIR));
1201
+ const structureOk = await setCursorAssets();
1202
+ if (structureOk === false) {
1203
+ return;
1204
+ }
1205
+ configManager.cursor = { path: CURSOR_DIR, plugin_dir: CURSOR_PLUGIN_DIR };
1206
+ configManager.save();
929
1207
  };
930
1208
  var unlinkClaudeCode = async (force = false) => {
931
1209
  if (!force) {
@@ -938,6 +1216,9 @@ var unlinkClaudeCode = async (force = false) => {
938
1216
  return;
939
1217
  }
940
1218
  }
1219
+ console.log(chalk4.red(`
1220
+ Removing Claude Code plugin...`));
1221
+ console.log(chalk4.dim(CLAUDE_CODE_DIR));
941
1222
  const claudeSettingsPath = path4.join(os2.homedir(), ".claude", "settings.json");
942
1223
  if (fs4.existsSync(claudeSettingsPath)) {
943
1224
  try {
@@ -945,16 +1226,16 @@ var unlinkClaudeCode = async (force = false) => {
945
1226
  const backupPath = `${claudeSettingsPath}.bak.${timestamp}`;
946
1227
  fs4.copyFileSync(claudeSettingsPath, backupPath);
947
1228
  const settings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
948
- if (settings?.extraKnownMarketplaces?.[PLUGIN_NAME] !== void 0) {
949
- delete settings.extraKnownMarketplaces[PLUGIN_NAME];
1229
+ if (settings?.extraKnownMarketplaces?.[MARKET_NAME] !== void 0) {
1230
+ delete settings.extraKnownMarketplaces[MARKET_NAME];
950
1231
  }
951
- if (settings?.enabledPlugins?.[`${PLUGIN_NAME}@${PLUGIN_NAME}`] !== void 0) {
952
- delete settings.enabledPlugins[`${PLUGIN_NAME}@${PLUGIN_NAME}`];
1232
+ if (settings?.enabledPlugins?.[`${PLUGIN_NAME}@${MARKET_NAME}`] !== void 0) {
1233
+ delete settings.enabledPlugins[`${PLUGIN_NAME}@${MARKET_NAME}`];
953
1234
  }
954
1235
  try {
955
1236
  fs4.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2), "utf-8");
956
1237
  fs4.unlinkSync(backupPath);
957
- console.log(chalk4.dim(` removed set-prompt entries from: ${claudeSettingsPath}`));
1238
+ console.log(chalk4.red(" removed") + chalk4.dim(` set-prompt entries from: ${claudeSettingsPath}`));
958
1239
  } catch (ex) {
959
1240
  fs4.copyFileSync(backupPath, claudeSettingsPath);
960
1241
  fs4.unlinkSync(backupPath);
@@ -964,9 +1245,60 @@ var unlinkClaudeCode = async (force = false) => {
964
1245
  console.error(chalk4.red(` \u274C Failed to clean up settings.json: ${ex.message}`));
965
1246
  }
966
1247
  }
1248
+ const claudePluginsDir = path4.join(os2.homedir(), ".claude", "plugins");
1249
+ const installedPluginsPath = path4.join(claudePluginsDir, "installed_plugins.json");
1250
+ if (fs4.existsSync(installedPluginsPath)) {
1251
+ try {
1252
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1253
+ const backupPath = `${installedPluginsPath}.bak.${timestamp}`;
1254
+ fs4.copyFileSync(installedPluginsPath, backupPath);
1255
+ const installed = JSON.parse(fs4.readFileSync(installedPluginsPath, "utf-8"));
1256
+ if (installed?.plugins && typeof installed.plugins === "object") {
1257
+ for (const key of Object.keys(installed.plugins)) {
1258
+ if (key.endsWith(`@${MARKET_NAME}`)) {
1259
+ delete installed.plugins[key];
1260
+ }
1261
+ }
1262
+ }
1263
+ try {
1264
+ fs4.writeFileSync(installedPluginsPath, JSON.stringify(installed, null, 2), "utf-8");
1265
+ fs4.unlinkSync(backupPath);
1266
+ console.log(chalk4.red(" removed") + chalk4.dim(` set-prompt entries from: ${installedPluginsPath}`));
1267
+ } catch (ex) {
1268
+ fs4.copyFileSync(backupPath, installedPluginsPath);
1269
+ fs4.unlinkSync(backupPath);
1270
+ console.warn(chalk4.yellow(" \u26A0 Write failed \u2014 rolled back installed_plugins.json."));
1271
+ }
1272
+ } catch (ex) {
1273
+ console.error(chalk4.red(` \u274C Failed to clean up installed_plugins.json: ${ex.message}`));
1274
+ }
1275
+ }
1276
+ const knownMarketplacesPath = path4.join(claudePluginsDir, "known_marketplaces.json");
1277
+ if (fs4.existsSync(knownMarketplacesPath)) {
1278
+ try {
1279
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1280
+ const backupPath = `${knownMarketplacesPath}.bak.${timestamp}`;
1281
+ fs4.copyFileSync(knownMarketplacesPath, backupPath);
1282
+ const marketplaces = JSON.parse(fs4.readFileSync(knownMarketplacesPath, "utf-8"));
1283
+ if (marketplaces?.[MARKET_NAME] !== void 0) {
1284
+ delete marketplaces[MARKET_NAME];
1285
+ }
1286
+ try {
1287
+ fs4.writeFileSync(knownMarketplacesPath, JSON.stringify(marketplaces, null, 2), "utf-8");
1288
+ fs4.unlinkSync(backupPath);
1289
+ console.log(chalk4.red(" removed") + chalk4.dim(` set-prompt entry from: ${knownMarketplacesPath}`));
1290
+ } catch (ex) {
1291
+ fs4.copyFileSync(backupPath, knownMarketplacesPath);
1292
+ fs4.unlinkSync(backupPath);
1293
+ console.warn(chalk4.yellow(" \u26A0 Write failed \u2014 rolled back known_marketplaces.json."));
1294
+ }
1295
+ } catch (ex) {
1296
+ console.error(chalk4.red(` \u274C Failed to clean up known_marketplaces.json: ${ex.message}`));
1297
+ }
1298
+ }
967
1299
  if (fs4.existsSync(CLAUDE_CODE_DIR)) {
968
1300
  fs4.rmSync(CLAUDE_CODE_DIR, { recursive: true, force: true });
969
- console.log(chalk4.dim(` removed: ${CLAUDE_CODE_DIR}`));
1301
+ console.log(chalk4.red(" removed") + chalk4.dim(`: ${CLAUDE_CODE_DIR}`));
970
1302
  }
971
1303
  configManager.claude_code = null;
972
1304
  configManager.save();
@@ -982,12 +1314,15 @@ var unlinkRooCode = async (force = false) => {
982
1314
  return;
983
1315
  }
984
1316
  }
1317
+ console.log(chalk4.red(`
1318
+ Removing RooCode integration...`));
1319
+ console.log(chalk4.dim(ROO_DIR));
985
1320
  const backupPath = configManager.roocode?.backup_path ?? ROO_BACKUP_DIR;
986
1321
  for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
987
1322
  const target = path4.join(ROO_DIR, dir);
988
1323
  if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
989
1324
  fs4.unlinkSync(target);
990
- console.log(chalk4.dim(` removed symlink: ${target}`));
1325
+ console.log(chalk4.red(" removed symlink") + chalk4.dim(`: ${target}`));
991
1326
  }
992
1327
  }
993
1328
  if (fs4.existsSync(backupPath)) {
@@ -999,7 +1334,7 @@ var unlinkRooCode = async (force = false) => {
999
1334
  continue;
1000
1335
  }
1001
1336
  fs4.renameSync(src, dest);
1002
- console.log(chalk4.dim(` restored: ${dir}/`));
1337
+ console.log(chalk4.green(" restored") + chalk4.dim(`: ${dir}/`));
1003
1338
  }
1004
1339
  fs4.rmdirSync(backupPath);
1005
1340
  } catch (ex) {
@@ -1020,12 +1355,15 @@ var unlinkOpenclaw = async (force = false) => {
1020
1355
  return;
1021
1356
  }
1022
1357
  }
1358
+ console.log(chalk4.red(`
1359
+ Removing OpenClaw integration...`));
1360
+ console.log(chalk4.dim(OPENCLAW_DIR));
1023
1361
  const backupPath = configManager.openclaw?.backup_path ?? OPENCLAW_BACKUP_DIR;
1024
1362
  for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
1025
1363
  const target = path4.join(OPENCLAW_DIR, dir);
1026
1364
  if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
1027
1365
  fs4.unlinkSync(target);
1028
- console.log(chalk4.dim(` removed symlink: ${target}`));
1366
+ console.log(chalk4.red(" removed symlink") + chalk4.dim(`: ${target}`));
1029
1367
  }
1030
1368
  }
1031
1369
  if (fs4.existsSync(backupPath)) {
@@ -1037,7 +1375,7 @@ var unlinkOpenclaw = async (force = false) => {
1037
1375
  continue;
1038
1376
  }
1039
1377
  fs4.renameSync(src, dest);
1040
- console.log(chalk4.dim(` restored: ${dir}/`));
1378
+ console.log(chalk4.green(" restored") + chalk4.dim(`: ${dir}/`));
1041
1379
  }
1042
1380
  fs4.rmdirSync(backupPath);
1043
1381
  } catch (ex) {
@@ -1058,12 +1396,15 @@ var unlinkAntigravity = async (force = false) => {
1058
1396
  return;
1059
1397
  }
1060
1398
  }
1399
+ console.log(chalk4.red(`
1400
+ Removing Antigravity integration...`));
1401
+ console.log(chalk4.dim(ANTIGRAVITY_DIR));
1061
1402
  const backupPath = configManager.antigravity?.backup_path ?? ANTIGRAVITY_BACKUP_DIR;
1062
1403
  for (const dir of AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */]) {
1063
1404
  const target = path4.join(ANTIGRAVITY_DIR, dir);
1064
1405
  if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
1065
1406
  fs4.unlinkSync(target);
1066
- console.log(chalk4.dim(` removed symlink: ${target}`));
1407
+ console.log(chalk4.red(" removed symlink") + chalk4.dim(`: ${target}`));
1067
1408
  }
1068
1409
  }
1069
1410
  if (fs4.existsSync(backupPath)) {
@@ -1075,7 +1416,7 @@ var unlinkAntigravity = async (force = false) => {
1075
1416
  continue;
1076
1417
  }
1077
1418
  fs4.renameSync(src, dest);
1078
- console.log(chalk4.dim(` restored: ${dir}/`));
1419
+ console.log(chalk4.green(" restored") + chalk4.dim(`: ${dir}/`));
1079
1420
  }
1080
1421
  fs4.rmdirSync(backupPath);
1081
1422
  } catch (ex) {
@@ -1085,77 +1426,68 @@ var unlinkAntigravity = async (force = false) => {
1085
1426
  configManager.antigravity = null;
1086
1427
  configManager.save();
1087
1428
  };
1088
- var linkAntigravity = async () => {
1089
- const repoPath = resolveRepoPath();
1090
- if (repoPath == null) {
1091
- return;
1429
+ var unlinkCodex = async (force = false) => {
1430
+ if (!force) {
1431
+ const ok = await confirm3({
1432
+ message: `Remove Codex symlinks from ${CODEX_DIR}?`,
1433
+ default: false
1434
+ });
1435
+ if (!ok) {
1436
+ console.log(chalk4.yellow("Cancelled."));
1437
+ return;
1438
+ }
1092
1439
  }
1093
- console.log(chalk4.green(`
1094
- Setting up Antigravity integration...`));
1095
- console.log(chalk4.dim(ANTIGRAVITY_DIR));
1096
- const antigravityDirs = AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */];
1097
- const backupExistingAntigravityFiles = async () => {
1098
- try {
1099
- fs4.mkdirSync(ANTIGRAVITY_DIR, { recursive: true });
1100
- const dirsToBackup = [];
1101
- for (const dir of antigravityDirs) {
1102
- const target = path4.join(ANTIGRAVITY_DIR, dir);
1103
- if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink() === false) {
1104
- dirsToBackup.push(dir);
1105
- }
1106
- }
1107
- if (dirsToBackup.length === 0) {
1108
- return true;
1109
- }
1110
- fs4.mkdirSync(ANTIGRAVITY_BACKUP_DIR, { recursive: true });
1111
- for (const dir of dirsToBackup) {
1112
- const src = path4.join(ANTIGRAVITY_DIR, dir);
1113
- const dest = path4.join(ANTIGRAVITY_BACKUP_DIR, dir);
1114
- fs4.renameSync(src, dest);
1115
- console.log(chalk4.dim(` backed up: ${dir}/ \u2192 ${dest}`));
1116
- }
1117
- return true;
1118
- } catch (ex) {
1119
- console.error(chalk4.red(`\u274C Failed to backup existing directories: ${ex.message}`));
1120
- return false;
1440
+ console.log(chalk4.red(`
1441
+ Removing Codex integration...`));
1442
+ console.log(chalk4.dim(CODEX_DIR));
1443
+ const backupPath = configManager.codex?.backup_path ?? CODEX_BACKUP_DIR;
1444
+ for (const dir of AGENT_PROMPT_DIRS["codex" /* CODEX */]) {
1445
+ const target = path4.join(CODEX_DIR, dir);
1446
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
1447
+ fs4.unlinkSync(target);
1448
+ console.log(chalk4.red(" removed symlink") + chalk4.dim(`: ${target}`));
1121
1449
  }
1122
- };
1123
- const setAntigravityAssets = async () => {
1450
+ }
1451
+ if (fs4.existsSync(backupPath)) {
1124
1452
  try {
1125
- const linked = [];
1126
- for (const dir of antigravityDirs) {
1127
- const src = path4.join(repoPath, dir);
1128
- const dest = path4.join(ANTIGRAVITY_DIR, dir);
1129
- if (await pathExists(src) === false) {
1453
+ for (const dir of AGENT_PROMPT_DIRS["codex" /* CODEX */]) {
1454
+ const src = path4.join(backupPath, dir);
1455
+ const dest = path4.join(CODEX_DIR, dir);
1456
+ if (!fs4.existsSync(src)) {
1130
1457
  continue;
1131
1458
  }
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 });
1138
- }
1139
- for (const { dir, src } of linked) {
1140
- const isLast = linked[linked.length - 1].dir === dir;
1141
- const branch = isLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
1142
- console.log(chalk4.dim(` ${branch} `) + chalk4.bold(`${dir}/`) + chalk4.dim(` \u2192 ${src}`) + chalk4.green(" \u2713"));
1459
+ fs4.renameSync(src, dest);
1460
+ console.log(chalk4.green(" restored") + chalk4.dim(`: ${dir}/`));
1143
1461
  }
1144
- return true;
1462
+ fs4.rmdirSync(backupPath);
1145
1463
  } catch (ex) {
1146
- console.error(chalk4.red(`\u274C Failed to create symlinks: ${ex.message}`));
1147
- return false;
1464
+ console.error(chalk4.red(` \u274C Failed to restore Codex backup: ${ex.message}`));
1148
1465
  }
1149
- };
1150
- const backupOk = await backupExistingAntigravityFiles();
1151
- if (backupOk === false) {
1152
- return;
1153
1466
  }
1154
- const linkOk = await setAntigravityAssets();
1155
- if (linkOk === false) {
1156
- return;
1467
+ configManager.codex = null;
1468
+ configManager.save();
1469
+ };
1470
+ var unlinkCursor = async (force = false) => {
1471
+ if (!force) {
1472
+ const ok = await confirm3({
1473
+ message: `Remove Cursor plugin dir (${CURSOR_PLUGIN_DIR}) and plugin structure?`,
1474
+ default: false
1475
+ });
1476
+ if (!ok) {
1477
+ console.log(chalk4.yellow("Cancelled."));
1478
+ return;
1479
+ }
1157
1480
  }
1158
- configManager.antigravity = { path: ANTIGRAVITY_DIR, backup_path: ANTIGRAVITY_BACKUP_DIR };
1481
+ console.log(chalk4.red(`
1482
+ Removing Cursor plugin...`));
1483
+ console.log(chalk4.dim(CURSOR_PLUGIN_DIR));
1484
+ fs4.rmSync(CURSOR_PLUGIN_DIR, { recursive: true, force: true });
1485
+ console.log(chalk4.red(" removed") + chalk4.dim(`: ${CURSOR_PLUGIN_DIR}`));
1486
+ if (fs4.existsSync(CURSOR_DIR)) {
1487
+ fs4.rmSync(CURSOR_DIR, { recursive: true, force: true });
1488
+ console.log(chalk4.red(" removed") + chalk4.dim(`: ${CURSOR_DIR}`));
1489
+ }
1490
+ configManager.cursor = null;
1159
1491
  configManager.save();
1160
1492
  };
1161
1493
  var linkCommand = async (tool) => {
@@ -1175,6 +1507,8 @@ var linkCommand = async (tool) => {
1175
1507
  await linkCodex();
1176
1508
  } else if (tool === "antigravity" /* ANTIGRAVITY */) {
1177
1509
  await linkAntigravity();
1510
+ } else if (tool === "cursor" /* CURSOR */) {
1511
+ await linkCursor();
1178
1512
  }
1179
1513
  return;
1180
1514
  }
@@ -1183,7 +1517,8 @@ var linkCommand = async (tool) => {
1183
1517
  ["roocode" /* ROOCODE */]: configManager.isRooCodeEnabled(),
1184
1518
  ["openclaw" /* OPENCLAW */]: configManager.isOpenclawEnabled(),
1185
1519
  ["codex" /* CODEX */]: configManager.isCodexEnabled(),
1186
- ["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled()
1520
+ ["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled(),
1521
+ ["cursor" /* CURSOR */]: configManager.isCursorEnabled()
1187
1522
  };
1188
1523
  const selected = await checkbox({
1189
1524
  message: "Which AI agent do you want to integrate?",
@@ -1193,6 +1528,19 @@ var linkCommand = async (tool) => {
1193
1528
  checked: prevLinked[a.value]
1194
1529
  }))
1195
1530
  });
1531
+ const toLink = ALL_AGENTS.filter((a) => !prevLinked[a.value] && selected.includes(a.value));
1532
+ const toUnlink = ALL_AGENTS.filter((a) => prevLinked[a.value] && !selected.includes(a.value));
1533
+ console.log();
1534
+ if (toLink.length > 0) {
1535
+ console.log(chalk4.green(" Link ") + chalk4.dim("\u2192 ") + toLink.map((a) => chalk4.bold(a.name)).join(chalk4.dim(", ")));
1536
+ }
1537
+ if (toUnlink.length > 0) {
1538
+ console.log(chalk4.red(" Unlink ") + chalk4.dim("\u2192 ") + toUnlink.map((a) => chalk4.bold(a.name)).join(chalk4.dim(", ")));
1539
+ }
1540
+ console.log();
1541
+ if (toLink.length === 0 && toUnlink.length === 0) {
1542
+ return;
1543
+ }
1196
1544
  for (const a of ALL_AGENTS) {
1197
1545
  const was = prevLinked[a.value];
1198
1546
  const now = selected.includes(a.value);
@@ -1207,6 +1555,8 @@ var linkCommand = async (tool) => {
1207
1555
  await linkCodex();
1208
1556
  } else if (a.value === "antigravity" /* ANTIGRAVITY */) {
1209
1557
  await linkAntigravity();
1558
+ } else if (a.value === "cursor" /* CURSOR */) {
1559
+ await linkCursor();
1210
1560
  }
1211
1561
  }
1212
1562
  if (was && !now) {
@@ -1216,8 +1566,12 @@ var linkCommand = async (tool) => {
1216
1566
  await unlinkRooCode(true);
1217
1567
  } else if (a.value === "openclaw" /* OPENCLAW */) {
1218
1568
  await unlinkOpenclaw(true);
1569
+ } else if (a.value === "codex" /* CODEX */) {
1570
+ await unlinkCodex(true);
1219
1571
  } else if (a.value === "antigravity" /* ANTIGRAVITY */) {
1220
1572
  await unlinkAntigravity(true);
1573
+ } else if (a.value === "cursor" /* CURSOR */) {
1574
+ await unlinkCursor(true);
1221
1575
  }
1222
1576
  }
1223
1577
  }
@@ -1236,7 +1590,9 @@ var uninstallCommand = async () => {
1236
1590
  const hasRooCode = configManager.isRooCodeEnabled();
1237
1591
  const hasOpenclaw = configManager.isOpenclawEnabled();
1238
1592
  const hasAntigravity = configManager.isAntigravityEnabled();
1239
- if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity) {
1593
+ const hasCodex = configManager.isCodexEnabled();
1594
+ const hasCursor = configManager.isCursorEnabled();
1595
+ if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity && !hasCodex && !hasCursor) {
1240
1596
  console.log(chalk5.yellow("Nothing to remove."));
1241
1597
  return;
1242
1598
  }
@@ -1254,6 +1610,12 @@ var uninstallCommand = async () => {
1254
1610
  if (hasAntigravity) {
1255
1611
  console.log(` Antigravity symlinks ${chalk5.dim("(backup will be restored)")}`);
1256
1612
  }
1613
+ if (hasCodex) {
1614
+ console.log(` Codex symlinks ${chalk5.dim("(backup will be restored)")}`);
1615
+ }
1616
+ if (hasCursor) {
1617
+ console.log(` Cursor plugin dir ${chalk5.dim("(symlink will be removed)")}`);
1618
+ }
1257
1619
  const ok = await confirm4({ message: "Proceed?", default: false });
1258
1620
  if (!ok) {
1259
1621
  console.log(chalk5.yellow("Cancelled."));
@@ -1271,6 +1633,12 @@ var uninstallCommand = async () => {
1271
1633
  if (hasAntigravity) {
1272
1634
  await unlinkAntigravity(true);
1273
1635
  }
1636
+ if (hasCodex) {
1637
+ await unlinkCodex(true);
1638
+ }
1639
+ if (hasCursor) {
1640
+ await unlinkCursor(true);
1641
+ }
1274
1642
  for (const t of targets) {
1275
1643
  fs5.rmSync(t.path, { recursive: true, force: true });
1276
1644
  console.log(chalk5.dim(` removed: ${t.path}`));
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "set-prompt",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Sync your prompt library across AI coding tools from a single git repo",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "set-prompt": "./dist/index.js"
7
+ "set-prompt": "./dist/index.js",
8
+ "sppt": "./dist/index.js"
8
9
  },
9
10
  "type": "module",
10
11
  "scripts": {
11
12
  "start:dev": "tsx src/index.ts",
12
- "build": "rimraf dist && tsup && npm run copy_bin",
13
- "copy_bin": "cpx \"src/bin/set-prompt.js\" dist/bin",
13
+ "build": "rimraf dist && tsup",
14
14
  "test": "vitest --reporter=verbose"
15
15
  },
16
16
  "keywords": [
@@ -46,7 +46,6 @@
46
46
  "@types/fs-extra": "^11.0.4",
47
47
  "@types/node": "^25.4.0",
48
48
  "@vitest/coverage-v8": "^4.1.0",
49
- "cpx": "^1.5.0",
50
49
  "memfs": "^4.56.11",
51
50
  "prettier": "^3.8.1",
52
51
  "rimraf": "^6.1.3",
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import '../index.js';