set-prompt 0.2.0 → 0.3.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,33 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.3.0] - 2026-04-06
8
+
9
+ ### Added
10
+ - Antigravity integration (`link antigravity`) — symlinks `skills/` into `~/.gemini/antigravity/skills/` with backup/restore
11
+ - `unlinkClaudeCode`, `unlinkRooCode`, `unlinkOpenclaw`, `unlinkAntigravity` — unlink functions with force mode
12
+ - `link` command interactive mode now supports **deselection** — unchecking a linked agent triggers unlink + backup restore
13
+ - `uninstall` delegates to `unlinkXxx(true)` instead of inlining rollback logic
14
+
15
+ ### Changed
16
+ - `uninstall` no longer contains rollback logic — all agent cleanup goes through `link-command.ts`
17
+ - `AntigravityConfig` schema updated to include `backup_path` (consistent with RooCode/OpenClaw)
18
+ - `AGENT_PROMPT_DIRS[ANTIGRAVITY]` set to `['skills']` only
19
+ - `scaffold` always overwrites `SET_PROMPT_GUIDE.md` — ensures the latest template on every run
20
+ - `SET_PROMPT_GUIDE.md` template updated: Antigravity frontmatter added, OpenClaw `homepage`/`user-invocable` fields added, `metadata` format corrected
21
+ - `build` script now runs `rimraf dist` before `tsup`
22
+ - README workflow restructured into 4 explicit steps
23
+
24
+ ---
25
+
26
+ ## [0.2.1] - 2026-04-02
27
+
28
+ ### Changed
29
+ - Add `repository`, `homepage`, `bugs` fields to `package.json` for npm registry links
30
+ - Update package description to "Sync your prompt library across AI coding tools from a single git repo"
31
+
32
+ ---
33
+
7
34
  ## [0.2.0] - 2026-04-02
8
35
 
9
36
  ### Added
package/README.md CHANGED
@@ -6,13 +6,19 @@ But every time you try a new AI agent, you have to set it all up again from scra
6
6
 
7
7
  `set-prompt` was built to solve this. It maintains a single git repository of prompts and symlinks them into each tool's expected location — so your prompt set stays in one place, stays versioned, and stays consistent across every AI agent you use.
8
8
 
9
+ One repo. Every agent. Always in sync.
10
+
9
11
  ```
10
- my-prompts/ (git repo)
11
- └── skills/, commands/, hooks/
12
- set-prompt install <git-url>
13
- set-prompt link claudecode → ~/.set-prompt/claudecode/ (Claude Code plugin)
14
- ↓ set-prompt link roocode → ~/.roo/ (symlinks)
15
- ↓ set-prompt link openclaw → ~/.openclaw/workspace/ (symlinks)
12
+ my-prompts/ (git repo)
13
+ ├── skills/
14
+ ├── commands/
15
+ └── hooks/
16
+
17
+ ┌────────────────┼─────────────────┬─────────────────┐
18
+ ▼ ▼ ▼ ▼
19
+ ~/.set-prompt/ ~/.roo/ ~/.openclaw/ ~/.gemini/
20
+ claudecode/ workspace/ antigravity/
21
+ (Claude Code plugin) (symlinks) (symlinks) (symlinks)
16
22
  ```
17
23
 
18
24
  ## 📦 Installation
@@ -25,69 +31,86 @@ npx set-prompt <command>
25
31
 
26
32
  ## 🚀 Workflow
27
33
 
28
- ### 1. Create a prompt repository
34
+ ### Step 1 Install set-prompt
35
+
36
+ ```bash
37
+ npm install -g set-prompt
38
+ ```
39
+
40
+ Or run without installing:
41
+
42
+ ```bash
43
+ npx set-prompt <command>
44
+ ```
45
+
46
+ ---
47
+
48
+ ### Step 2 — Connect your prompt repository
49
+
50
+ Point `set-prompt` at a git repo containing your prompts. It clones it to `~/.set-prompt/repo/` and registers it as your prompt source.
51
+
52
+ ```bash
53
+ set-prompt install https://github.com/you/my-prompts
54
+ ```
55
+
56
+ Don't have a repo yet? Scaffold one first:
29
57
 
30
58
  ```bash
31
59
  mkdir my-prompts && cd my-prompts && git init
32
60
  set-prompt scaffold .
33
61
  ```
34
62
 
35
- Creates:
63
+ This creates the expected directory structure:
36
64
 
37
65
  ```
38
66
  my-prompts/
39
- ├── SET_PROMPT_GUIDE.md
40
67
  ├── skills/
41
- │ └── <skill-name>/
42
- │ └── SKILL.md
43
68
  ├── commands/
44
- │ └── <command-name>/
45
- │ └── COMMAND.md
46
69
  ├── hooks/
47
70
  └── agents/
48
71
  ```
49
72
 
50
- ### 2. Register the repository
73
+ ---
74
+
75
+ ### Step 3 — Link to AI agents 🔗
51
76
 
52
77
  ```bash
53
- # remote git URL cloned to ~/.set-prompt/repo/
54
- set-prompt install https://github.com/you/my-prompts
78
+ set-prompt link # interactive checkboxselect agents to link
79
+ set-prompt link claudecode # link Claude Code only
80
+ set-prompt link roocode # link RooCode only
81
+ set-prompt link openclaw # link OpenClaw only
82
+ set-prompt link antigravity # link Antigravity only
55
83
  ```
56
84
 
57
- ### 3. Link to AI tools 🔗
85
+ 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
+ | Agent | Where prompts land | What gets linked |
88
+ |---|---|---|
89
+ | Claude Code | `~/.set-prompt/claudecode/` (plugin) | `skills/`, `commands/`, `hooks/`, `agents/` |
90
+ | RooCode | `~/.roo/` | `skills/`, `commands/` |
91
+ | OpenClaw | `~/.openclaw/workspace/` | `skills/` |
92
+ | Antigravity | `~/.gemini/antigravity/` | `skills/` |
93
+
94
+ ---
95
+
96
+ ### Step 4 — Uninstall
97
+
98
+ Removes all set-prompt data, reverts symlinks, and restores any backed-up directories.
58
99
 
59
100
  ```bash
60
- set-prompt link # interactive selection
61
- set-prompt link claudecode # Claude Code only
62
- set-prompt link roocode # RooCode only
101
+ set-prompt uninstall
63
102
  ```
64
103
 
65
- - **Claude Code**: creates a plugin at `~/.set-prompt/claudecode/`, registers via `~/.claude/settings.json`
66
- - **RooCode**: symlinks `skills/`, `commands/` into `~/.roo/` — backs up existing dirs first
67
- - **OpenClaw**: symlinks `skills/` into `~/.openclaw/workspace/` — backs up existing dir first
68
-
69
104
  ## 📋 Commands
70
105
 
71
- | Command | Description | Status |
72
- |---------|-------------|--------|
73
- | `scaffold [path]` | Verify and scaffold repo structure | |
74
- | `install <url>` | Clone remote git repo and register as prompt source | ✅ |
75
- | `link [agent]` | Link prompts to AI agents (interactive if omitted) | ✅ |
76
- | `link claudecode` | Link to Claude Code | |
77
- | `link roocode` | Link to RooCode | |
78
- | `link openclaw` | Link to OpenClaw | |
79
- | `link codex` | Link to Codex | 🔜 planned |
80
- | `link antigravity` | Link to Antigravity | 🔜 planned |
81
- | `update` | Fetch and pull latest changes from remote repo | ✅ |
82
- | `status` | Show current repo and linked agents | ✅ |
83
- | `uninstall` | Remove all set-prompt data | ✅ |
84
-
85
- ## 🗓️ Planned Integrations
86
-
87
- | Agent | Provider | Status |
88
- |-------|----------|--------|
89
- | Codex | OpenAI | 🔜 planned |
90
- | Antigravity | Google | 🔜 planned |
106
+ | Command | Description |
107
+ |---------|-------------|
108
+ | `install <url>` | Clone remote git repo and register as prompt source |
109
+ | `link [agent]` | Link/unlink agents interactively, or target one directly |
110
+ | `update` | Fetch and pull latest changes from remote repo |
111
+ | `status` | Show current repo and linked agents |
112
+ | `scaffold [path]` | Verify and create required directories in a prompt repo |
113
+ | `uninstall` | Remove all set-prompt data and restore backups |
91
114
 
92
115
  ## ☕ Support
93
116
 
@@ -161,6 +184,11 @@ Stored at `~/.set-prompt/config.json`, managed via `ConfigManager`.
161
184
  ├── SET_PROMPT_BACKUP/ # backup of original dirs before linking
162
185
  │ └── skills/
163
186
  └── skills/ → symlink to repo/skills/
187
+
188
+ ~/.gemini/antigravity/ # Antigravity integration (symlinks)
189
+ ├── SET_PROMPT_BACKUP/ # backup of original dirs before linking
190
+ │ └── skills/
191
+ └── skills/ → symlink to repo/skills/
164
192
  ```
165
193
 
166
194
  ## 🖥️ Requirements
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import chalk8 from "chalk";
6
6
  import figlet from "figlet";
7
7
  import fs6 from "fs";
8
- import path6 from "path";
8
+ import path5 from "path";
9
9
  import { fileURLToPath } from "url";
10
10
 
11
11
  // src/commands/install-command.ts
@@ -27,13 +27,15 @@ var ROO_DIR = path.join(os.homedir(), ".roo");
27
27
  var ROO_BACKUP_DIR = path.join(ROO_DIR, "SET_PROMPT_BACKUP");
28
28
  var OPENCLAW_DIR = path.join(os.homedir(), ".openclaw", "workspace");
29
29
  var OPENCLAW_BACKUP_DIR = path.join(OPENCLAW_DIR, "SET_PROMPT_BACKUP");
30
+ var ANTIGRAVITY_DIR = path.join(os.homedir(), ".gemini", "antigravity");
31
+ var ANTIGRAVITY_BACKUP_DIR = path.join(ANTIGRAVITY_DIR, "SET_PROMPT_BACKUP");
30
32
  var PROMPT_DIR_NAMES = ["skills", "commands", "hooks", "agents"];
31
33
  var AGENT_PROMPT_DIRS = {
32
34
  ["claudecode" /* CLAUDECODE */]: ["skills", "commands", "hooks", "agents"],
33
35
  ["roocode" /* ROOCODE */]: ["skills", "commands"],
34
36
  ["openclaw" /* OPENCLAW */]: ["skills"],
35
37
  ["codex" /* CODEX */]: ["skills", "commands"],
36
- ["antigravity" /* ANTIGRAVITY */]: ["skills", "commands"]
38
+ ["antigravity" /* ANTIGRAVITY */]: ["skills"]
37
39
  };
38
40
  var ALL_AGENTS = [
39
41
  { name: "Claude Code", value: "claudecode" /* CLAUDECODE */ },
@@ -66,7 +68,8 @@ var CodexConfigSchema = z.object({
66
68
  path: z.string().nullable()
67
69
  });
68
70
  var AntigravityConfigSchema = z.object({
69
- path: z.string().nullable()
71
+ path: z.string().nullable(),
72
+ backup_path: z.string().nullish().optional()
70
73
  });
71
74
  var GlobalConfigSchema = z.object({
72
75
  repo_path: z.string(),
@@ -282,15 +285,20 @@ customInstructions: "Additional behavior guidelines..."
282
285
 
283
286
  # OpenClaw
284
287
  homepage: "https://github.com/you/my-skill"
285
- metadata: {"openclaw":{"emoji":"\u{1F527}","os":["darwin","linux"]}}
288
+ user-invocable: true
289
+ disable-model-invocation: false
290
+ metadata: {"os":["darwin","linux"],"requires":{"bins":["git"],"env":["MY_API_KEY"]}}
291
+
292
+ # Antigravity
293
+ name: my-skill
294
+ description: "What this skill does and when to use it"
286
295
  ---
287
296
  \`\`\`
288
297
 
289
298
  | Field | Required | Platform | Description |
290
299
  |-------|----------|----------|-------------|
291
- | \`name\` | Yes | All | Display name. Claude Code: lowercase, numbers, hyphens only (max 64 chars). RooCode: emoji allowed. |
300
+ | \`name\` | Yes | All | Display name. Claude Code: lowercase, numbers, hyphens only (max 64 chars). RooCode: emoji allowed. Antigravity: optional, defaults to folder name. |
292
301
  | \`description\` | Yes | All | What it does and when to use it. Claude uses this to decide auto-loading. |
293
- | \`disable-model-invocation\` | No | CC, OpenClaw | \`true\` = prevent auto-loading, manual \`/name\` only. (default: \`false\`) |
294
302
  | \`allowed-tools\` | No | Claude Code | Tools Claude can use without asking. e.g. \`Read\` \`Write\` \`Edit\` \`Bash\` \`Grep\` \`Glob\` |
295
303
  | \`model\` | No | Claude Code | Model to use when active. \`sonnet\` or \`haiku\` |
296
304
  | \`context\` | No | Claude Code | \`fork\` = run in a forked subagent context |
@@ -301,15 +309,14 @@ metadata: {"openclaw":{"emoji":"\u{1F527}","os":["darwin","linux"]}}
301
309
  | \`groups\` | Yes | RooCode | Tool permissions: \`read\` \`edit\` \`command\` \`mcp\` \`browser\` |
302
310
  | \`whenToUse\` | No | RooCode | Guide for auto mode selection |
303
311
  | \`customInstructions\` | No | RooCode | Additional behavior guidelines |
304
- | \`homepage\` | No | OpenClaw | Website URL shown in the Skills UI |
305
- | \`metadata\` | No | OpenClaw | Single-line JSON for platform gating. e.g. \`os\`, \`requires.bins\`, \`requires.env\` |
306
-
307
- RooCode file-restricted edit example:
308
- \`\`\`yaml
309
- groups:
310
- - read
311
- - [edit, {fileRegex: '\\.(md|ts)$', description: "Markdown and TS only"}]
312
- \`\`\`
312
+ | \`groups\` (restricted) | No | RooCode | Restrict edit to file patterns: \`[edit, {fileRegex: '\\.(md|ts)$', description: "..."}]\` |
313
+ | \`metadata\` | No | OpenClaw | Single-line JSON for platform gating: \`os\` (platform filter), \`requires.bins\` (required binaries), \`requires.env\` (required env vars) |
314
+ | \`homepage\` | No | OpenClaw | URL shown as "Website" in the macOS Skills UI. Also settable via \`metadata.openclaw.homepage\`. |
315
+ | \`user-invocable\` | No | OpenClaw | \`false\` = hidden from \`/\` menu. (default: \`true\`) |
316
+ | \`disable-model-invocation\` | No | CC, OpenClaw | \`true\` = skill excluded from model prompt, still available via user invocation. (default: \`false\`) |
317
+ | \`command-dispatch\` | No | OpenClaw | \`"tool"\` = bypass model, dispatch directly to a tool |
318
+ | \`command-tool\` | No | OpenClaw | Tool to invoke when \`command-dispatch: "tool"\` |
319
+ | \`command-arg-mode\` | No | OpenClaw | How arguments are forwarded to the tool. (default: \`"raw"\`) |
313
320
 
314
321
  ---
315
322
 
@@ -360,9 +367,6 @@ command-tool: "Bash"
360
367
  | \`context\` | No | Claude Code | \`fork\` = run in a forked subagent context |
361
368
  | \`agent\` | No | Claude Code | Subagent type when \`context: fork\`. e.g. \`general-purpose\` \`Explore\` \`Plan\` |
362
369
  | \`hooks\` | No | Claude Code | Lifecycle hooks for pre/post processing. |
363
- | \`command-dispatch\` | No | OpenClaw | \`"tool"\` = bypass model, dispatch directly to a tool |
364
- | \`command-tool\` | No | OpenClaw | Tool to invoke when \`command-dispatch: "tool"\` |
365
- | \`command-arg-mode\` | No | OpenClaw | How arguments are forwarded to the tool. (default: \`"raw"\`) |
366
370
 
367
371
  ---
368
372
 
@@ -527,18 +531,23 @@ var scaffoldCommand = async (localPath, options = {}) => {
527
531
  }
528
532
  const created = [];
529
533
  const guideMdPath = path2.join(targetPath, "SET_PROMPT_GUIDE.md");
530
- if (options.force === true || fs2.existsSync(guideMdPath) === false) {
531
- fs2.writeFileSync(guideMdPath, SET_PROMPT_GUIDE, { encoding: "utf-8", flag: "w" });
532
- created.push(" SET_PROMPT_GUIDE.md");
533
- }
534
+ fs2.writeFileSync(guideMdPath, SET_PROMPT_GUIDE, { encoding: "utf-8", flag: "w" });
535
+ created.push(" SET_PROMPT_GUIDE.md");
534
536
  for (const dirName of PROMPT_DIR_NAMES) {
535
537
  const dirPath = path2.join(targetPath, dirName);
536
538
  if (fs2.existsSync(dirPath)) {
537
539
  console.warn(chalk2.yellow(`Directory already exists: '${dirName}' (skipping)`));
538
- continue;
540
+ } else {
541
+ fs2.mkdirSync(dirPath, { recursive: true });
542
+ created.push(` ${dirName}/`);
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
+ }
539
550
  }
540
- fs2.mkdirSync(dirPath, { recursive: true });
541
- created.push(` ${dirName}/`);
542
551
  }
543
552
  if (created.length > 0) {
544
553
  console.log(chalk2.green("Created:"));
@@ -606,7 +615,7 @@ import path4 from "path";
606
615
  import fs4 from "fs";
607
616
  import os2 from "os";
608
617
  import chalk4 from "chalk";
609
- import { checkbox } from "@inquirer/prompts";
618
+ import { confirm as confirm3, checkbox } from "@inquirer/prompts";
610
619
  import { pathExists } from "fs-extra";
611
620
  var resolveRepoPath = () => {
612
621
  if (configManager.repo_path == null) {
@@ -918,16 +927,241 @@ var linkCodex = async () => {
918
927
  }
919
928
  console.log(chalk4.yellow("Codex integration is not yet implemented."));
920
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) => {
975
+ if (!force) {
976
+ const ok = await confirm3({
977
+ message: `Remove RooCode symlinks from ${ROO_DIR}?`,
978
+ default: false
979
+ });
980
+ if (!ok) {
981
+ console.log(chalk4.yellow("Cancelled."));
982
+ return;
983
+ }
984
+ }
985
+ const backupPath = configManager.roocode?.backup_path ?? ROO_BACKUP_DIR;
986
+ for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
987
+ const target = path4.join(ROO_DIR, dir);
988
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
989
+ fs4.unlinkSync(target);
990
+ console.log(chalk4.dim(` removed symlink: ${target}`));
991
+ }
992
+ }
993
+ if (fs4.existsSync(backupPath)) {
994
+ try {
995
+ for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
996
+ const src = path4.join(backupPath, dir);
997
+ const dest = path4.join(ROO_DIR, dir);
998
+ if (!fs4.existsSync(src)) {
999
+ continue;
1000
+ }
1001
+ fs4.renameSync(src, dest);
1002
+ console.log(chalk4.dim(` restored: ${dir}/`));
1003
+ }
1004
+ fs4.rmdirSync(backupPath);
1005
+ } catch (ex) {
1006
+ console.error(chalk4.red(` \u274C Failed to restore RooCode backup: ${ex.message}`));
1007
+ }
1008
+ }
1009
+ configManager.roocode = null;
1010
+ configManager.save();
1011
+ };
1012
+ var unlinkOpenclaw = async (force = false) => {
1013
+ if (!force) {
1014
+ const ok = await confirm3({
1015
+ message: `Remove OpenClaw symlinks from ${OPENCLAW_DIR}?`,
1016
+ default: false
1017
+ });
1018
+ if (!ok) {
1019
+ console.log(chalk4.yellow("Cancelled."));
1020
+ return;
1021
+ }
1022
+ }
1023
+ const backupPath = configManager.openclaw?.backup_path ?? OPENCLAW_BACKUP_DIR;
1024
+ for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
1025
+ const target = path4.join(OPENCLAW_DIR, dir);
1026
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
1027
+ fs4.unlinkSync(target);
1028
+ console.log(chalk4.dim(` removed symlink: ${target}`));
1029
+ }
1030
+ }
1031
+ if (fs4.existsSync(backupPath)) {
1032
+ try {
1033
+ for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
1034
+ const src = path4.join(backupPath, dir);
1035
+ const dest = path4.join(OPENCLAW_DIR, dir);
1036
+ if (!fs4.existsSync(src)) {
1037
+ continue;
1038
+ }
1039
+ fs4.renameSync(src, dest);
1040
+ console.log(chalk4.dim(` restored: ${dir}/`));
1041
+ }
1042
+ fs4.rmdirSync(backupPath);
1043
+ } catch (ex) {
1044
+ console.error(chalk4.red(` \u274C Failed to restore OpenClaw backup: ${ex.message}`));
1045
+ }
1046
+ }
1047
+ configManager.openclaw = null;
1048
+ configManager.save();
1049
+ };
1050
+ var unlinkAntigravity = async (force = false) => {
1051
+ if (!force) {
1052
+ const ok = await confirm3({
1053
+ message: `Remove Antigravity symlinks from ${ANTIGRAVITY_DIR}?`,
1054
+ default: false
1055
+ });
1056
+ if (!ok) {
1057
+ console.log(chalk4.yellow("Cancelled."));
1058
+ return;
1059
+ }
1060
+ }
1061
+ const backupPath = configManager.antigravity?.backup_path ?? ANTIGRAVITY_BACKUP_DIR;
1062
+ for (const dir of AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */]) {
1063
+ const target = path4.join(ANTIGRAVITY_DIR, dir);
1064
+ if (fs4.existsSync(target) && fs4.lstatSync(target).isSymbolicLink()) {
1065
+ fs4.unlinkSync(target);
1066
+ console.log(chalk4.dim(` removed symlink: ${target}`));
1067
+ }
1068
+ }
1069
+ if (fs4.existsSync(backupPath)) {
1070
+ try {
1071
+ for (const dir of AGENT_PROMPT_DIRS["antigravity" /* ANTIGRAVITY */]) {
1072
+ const src = path4.join(backupPath, dir);
1073
+ const dest = path4.join(ANTIGRAVITY_DIR, dir);
1074
+ if (!fs4.existsSync(src)) {
1075
+ continue;
1076
+ }
1077
+ fs4.renameSync(src, dest);
1078
+ console.log(chalk4.dim(` restored: ${dir}/`));
1079
+ }
1080
+ fs4.rmdirSync(backupPath);
1081
+ } catch (ex) {
1082
+ console.error(chalk4.red(` \u274C Failed to restore Antigravity backup: ${ex.message}`));
1083
+ }
1084
+ }
1085
+ configManager.antigravity = null;
1086
+ configManager.save();
1087
+ };
921
1088
  var linkAntigravity = async () => {
922
- if (resolveRepoPath() == null) {
1089
+ const repoPath = resolveRepoPath();
1090
+ if (repoPath == null) {
923
1091
  return;
924
1092
  }
925
- console.log(chalk4.yellow("Antigravity integration is not yet implemented."));
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;
1121
+ }
1122
+ };
1123
+ const setAntigravityAssets = async () => {
1124
+ 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) {
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 });
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"));
1143
+ }
1144
+ return true;
1145
+ } catch (ex) {
1146
+ console.error(chalk4.red(`\u274C Failed to create symlinks: ${ex.message}`));
1147
+ return false;
1148
+ }
1149
+ };
1150
+ const backupOk = await backupExistingAntigravityFiles();
1151
+ if (backupOk === false) {
1152
+ return;
1153
+ }
1154
+ const linkOk = await setAntigravityAssets();
1155
+ if (linkOk === false) {
1156
+ return;
1157
+ }
1158
+ configManager.antigravity = { path: ANTIGRAVITY_DIR, backup_path: ANTIGRAVITY_BACKUP_DIR };
1159
+ configManager.save();
926
1160
  };
927
1161
  var linkCommand = async (tool) => {
928
1162
  if (tool != null) {
929
1163
  const known = ALL_AGENTS.some((a) => a.value === tool);
930
- if (known === false) {
1164
+ if (!known) {
931
1165
  console.log(chalk4.red(`Unknown vendor: ${tool}`));
932
1166
  process.exit(1);
933
1167
  }
@@ -944,187 +1178,98 @@ var linkCommand = async (tool) => {
944
1178
  }
945
1179
  return;
946
1180
  }
1181
+ const prevLinked = {
1182
+ ["claudecode" /* CLAUDECODE */]: configManager.isClaudeCodeEnabled(),
1183
+ ["roocode" /* ROOCODE */]: configManager.isRooCodeEnabled(),
1184
+ ["openclaw" /* OPENCLAW */]: configManager.isOpenclawEnabled(),
1185
+ ["codex" /* CODEX */]: configManager.isCodexEnabled(),
1186
+ ["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled()
1187
+ };
947
1188
  const selected = await checkbox({
948
1189
  message: "Which AI agent do you want to integrate?",
949
- choices: ALL_AGENTS.map((a) => {
950
- const applied = a.value === "claudecode" /* CLAUDECODE */ ? configManager.isClaudeCodeEnabled() : a.value === "roocode" /* ROOCODE */ ? configManager.isRooCodeEnabled() : a.value === "openclaw" /* OPENCLAW */ ? configManager.isOpenclawEnabled() : a.value === "codex" /* CODEX */ ? configManager.isCodexEnabled() : a.value === "antigravity" /* ANTIGRAVITY */ ? configManager.isAntigravityEnabled() : false;
951
- return {
952
- name: applied ? `${a.name} ${chalk4.dim("(applied)")}` : a.name,
953
- value: a.value,
954
- checked: applied
955
- };
956
- })
1190
+ choices: ALL_AGENTS.map((a) => ({
1191
+ name: prevLinked[a.value] ? `${a.name} ${chalk4.dim("(applied)")}` : a.name,
1192
+ value: a.value,
1193
+ checked: prevLinked[a.value]
1194
+ }))
957
1195
  });
958
- for (const a of selected) {
959
- if (a === "claudecode" /* CLAUDECODE */) {
960
- await linkClaudeCode();
961
- } else if (a === "roocode" /* ROOCODE */) {
962
- await linkRooCode();
963
- } else if (a === "openclaw" /* OPENCLAW */) {
964
- await linkOpenclaw();
965
- } else if (a === "codex" /* CODEX */) {
966
- await linkCodex();
967
- } else if (a === "antigravity" /* ANTIGRAVITY */) {
968
- await linkAntigravity();
1196
+ for (const a of ALL_AGENTS) {
1197
+ const was = prevLinked[a.value];
1198
+ const now = selected.includes(a.value);
1199
+ if (!was && now) {
1200
+ if (a.value === "claudecode" /* CLAUDECODE */) {
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
+ }
1211
+ }
1212
+ if (was && !now) {
1213
+ if (a.value === "claudecode" /* CLAUDECODE */) {
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
+ }
969
1222
  }
970
1223
  }
971
1224
  };
972
1225
 
973
1226
  // src/commands/uninstall-command.ts
974
1227
  import fs5 from "fs";
975
- import path5 from "path";
976
- import os3 from "os";
977
1228
  import chalk5 from "chalk";
978
1229
  import { confirm as confirm4 } from "@inquirer/prompts";
979
- var PLUGIN_NAME2 = "set-prompt";
980
- var rollbackClaudeCode = () => {
981
- const claudeSettingsPath = path5.join(os3.homedir(), ".claude", "settings.json");
982
- if (fs5.existsSync(claudeSettingsPath) === false) {
983
- return;
984
- }
985
- try {
986
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
987
- const backupPath = `${claudeSettingsPath}.bak.${timestamp}`;
988
- try {
989
- fs5.copyFileSync(claudeSettingsPath, backupPath);
990
- } catch (ex) {
991
- console.warn(chalk5.yellow(` \u26A0 Could not create backup: ${ex.message} \u2014 skipping cleanup`));
992
- return;
993
- }
994
- const raw = fs5.readFileSync(claudeSettingsPath, "utf-8");
995
- let settings;
996
- try {
997
- const parsed = JSON.parse(raw);
998
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
999
- console.warn(chalk5.yellow(" \u26A0 settings.json has unexpected format \u2014 skipping cleanup"));
1000
- fs5.unlinkSync(backupPath);
1001
- return;
1002
- }
1003
- settings = parsed;
1004
- } catch {
1005
- console.warn(chalk5.yellow(" \u26A0 Failed to parse settings.json \u2014 skipping cleanup"));
1006
- fs5.unlinkSync(backupPath);
1007
- return;
1008
- }
1009
- if (settings.extraKnownMarketplaces?.[PLUGIN_NAME2] !== void 0) {
1010
- delete settings.extraKnownMarketplaces[PLUGIN_NAME2];
1011
- }
1012
- if (settings.enabledPlugins?.[`${PLUGIN_NAME2}@${PLUGIN_NAME2}`] !== void 0) {
1013
- delete settings.enabledPlugins[`${PLUGIN_NAME2}@${PLUGIN_NAME2}`];
1014
- }
1015
- try {
1016
- fs5.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2), "utf-8");
1017
- } catch (ex) {
1018
- try {
1019
- fs5.copyFileSync(backupPath, claudeSettingsPath);
1020
- fs5.unlinkSync(backupPath);
1021
- console.warn(chalk5.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
1022
- } catch {
1023
- console.error(chalk5.red(` \u274C Rollback failed. Backup preserved at: ${backupPath}`));
1024
- }
1025
- throw ex;
1026
- }
1027
- try {
1028
- fs5.unlinkSync(backupPath);
1029
- } catch {
1030
- }
1031
- console.log(chalk5.dim(` removed set-prompt entries from: ${claudeSettingsPath}`));
1032
- } catch (ex) {
1033
- console.error(chalk5.red(` \u274C Failed to clean up settings.json: ${ex.message}`));
1034
- }
1035
- };
1036
- var rollbackRooCode = () => {
1037
- const backupPath = configManager.roocode?.backup_path ?? ROO_BACKUP_DIR;
1038
- for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
1039
- const target = path5.join(ROO_DIR, dir);
1040
- if (fs5.existsSync(target) && fs5.lstatSync(target).isSymbolicLink()) {
1041
- fs5.unlinkSync(target);
1042
- console.log(chalk5.dim(` removed symlink: ${target}`));
1043
- }
1044
- }
1045
- if (fs5.existsSync(backupPath) === false) {
1046
- return;
1047
- }
1048
- try {
1049
- for (const dir of AGENT_PROMPT_DIRS["roocode" /* ROOCODE */]) {
1050
- const src = path5.join(backupPath, dir);
1051
- const dest = path5.join(ROO_DIR, dir);
1052
- if (fs5.existsSync(src) === false) {
1053
- continue;
1054
- }
1055
- fs5.renameSync(src, dest);
1056
- console.log(chalk5.dim(` restored: ${dir}/`));
1057
- }
1058
- fs5.rmdirSync(backupPath);
1059
- } catch (ex) {
1060
- console.error(chalk5.red(` \u274C Failed to restore RooCode backup: ${ex.message}`));
1061
- }
1062
- };
1063
- var rollbackOpenclaw = () => {
1064
- const backupPath = configManager.openclaw?.backup_path ?? OPENCLAW_BACKUP_DIR;
1065
- for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
1066
- const target = path5.join(OPENCLAW_DIR, dir);
1067
- if (fs5.existsSync(target) && fs5.lstatSync(target).isSymbolicLink()) {
1068
- fs5.unlinkSync(target);
1069
- console.log(chalk5.dim(` removed symlink: ${target}`));
1070
- }
1071
- }
1072
- if (fs5.existsSync(backupPath) === false) {
1073
- return;
1074
- }
1075
- try {
1076
- for (const dir of AGENT_PROMPT_DIRS["openclaw" /* OPENCLAW */]) {
1077
- const src = path5.join(backupPath, dir);
1078
- const dest = path5.join(OPENCLAW_DIR, dir);
1079
- if (fs5.existsSync(src) === false) {
1080
- continue;
1081
- }
1082
- fs5.renameSync(src, dest);
1083
- console.log(chalk5.dim(` restored: ${dir}/`));
1084
- }
1085
- fs5.rmdirSync(backupPath);
1086
- } catch (ex) {
1087
- console.error(chalk5.red(` \u274C Failed to restore OpenClaw backup: ${ex.message}`));
1088
- }
1089
- };
1090
1230
  var uninstallCommand = async () => {
1091
1231
  const targets = [
1092
- { label: `Claude Code plugin dir ${chalk5.dim(CLAUDE_CODE_DIR)}`, path: CLAUDE_CODE_DIR },
1093
- { label: `Config file ${chalk5.dim(CONFIG_PATH)}`, path: CONFIG_PATH },
1094
- { label: `Home dir ${chalk5.dim(HOME_DIR)}`, path: HOME_DIR }
1232
+ { label: `Config file ${chalk5.dim(CONFIG_PATH)}`, path: CONFIG_PATH },
1233
+ { label: `Home dir ${chalk5.dim(HOME_DIR)}`, path: HOME_DIR }
1095
1234
  ].filter((t) => fs5.existsSync(t.path));
1096
- const hasClaudeCodeSettings = configManager.isClaudeCodeEnabled();
1235
+ const hasClaudeCode = configManager.isClaudeCodeEnabled();
1097
1236
  const hasRooCode = configManager.isRooCodeEnabled();
1098
1237
  const hasOpenclaw = configManager.isOpenclawEnabled();
1099
- if (targets.length === 0 && hasClaudeCodeSettings === false && hasRooCode === false && hasOpenclaw === false) {
1238
+ const hasAntigravity = configManager.isAntigravityEnabled();
1239
+ if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity) {
1100
1240
  console.log(chalk5.yellow("Nothing to remove."));
1101
1241
  return;
1102
1242
  }
1103
1243
  console.log(chalk5.red("\nThe following will be removed:"));
1104
1244
  targets.forEach((t) => console.log(` ${t.label}`));
1105
- if (hasClaudeCodeSettings) {
1106
- const claudeSettingsPath = path5.join(os3.homedir(), ".claude", "settings.json");
1107
- console.log(` set-prompt entries in ${chalk5.dim(claudeSettingsPath)}`);
1245
+ if (hasClaudeCode) {
1246
+ console.log(` Claude Code plugin dir ${chalk5.dim(CLAUDE_CODE_DIR)}`);
1108
1247
  }
1109
1248
  if (hasRooCode) {
1110
- console.log(` RooCode symlinks in ${chalk5.dim(ROO_DIR)} ${chalk5.dim("(backup will be restored)")}`);
1249
+ console.log(` RooCode symlinks ${chalk5.dim("(backup will be restored)")}`);
1111
1250
  }
1112
1251
  if (hasOpenclaw) {
1113
- console.log(` OpenClaw symlinks in ${chalk5.dim(OPENCLAW_DIR)} ${chalk5.dim("(backup will be restored)")}`);
1252
+ console.log(` OpenClaw symlinks ${chalk5.dim("(backup will be restored)")}`);
1253
+ }
1254
+ if (hasAntigravity) {
1255
+ console.log(` Antigravity symlinks ${chalk5.dim("(backup will be restored)")}`);
1114
1256
  }
1115
1257
  const ok = await confirm4({ message: "Proceed?", default: false });
1116
- if (ok === false) {
1258
+ if (!ok) {
1117
1259
  console.log(chalk5.yellow("Cancelled."));
1118
1260
  return;
1119
1261
  }
1120
- if (hasClaudeCodeSettings) {
1121
- rollbackClaudeCode();
1262
+ if (hasClaudeCode) {
1263
+ await unlinkClaudeCode(true);
1122
1264
  }
1123
1265
  if (hasRooCode) {
1124
- rollbackRooCode();
1266
+ await unlinkRooCode(true);
1125
1267
  }
1126
1268
  if (hasOpenclaw) {
1127
- rollbackOpenclaw();
1269
+ await unlinkOpenclaw(true);
1270
+ }
1271
+ if (hasAntigravity) {
1272
+ await unlinkAntigravity(true);
1128
1273
  }
1129
1274
  for (const t of targets) {
1130
1275
  fs5.rmSync(t.path, { recursive: true, force: true });
@@ -1214,8 +1359,8 @@ process.on("unhandledRejection", (reason) => {
1214
1359
  }
1215
1360
  throw reason;
1216
1361
  });
1217
- var __dirname = path6.dirname(fileURLToPath(import.meta.url));
1218
- var pkg = JSON.parse(fs6.readFileSync(path6.join(__dirname, "../package.json"), "utf-8"));
1362
+ var __dirname = path5.dirname(fileURLToPath(import.meta.url));
1363
+ var pkg = JSON.parse(fs6.readFileSync(path5.join(__dirname, "../package.json"), "utf-8"));
1219
1364
  configManager.init();
1220
1365
  var program = new Command();
1221
1366
  var banner = chalk8.cyan(figlet.textSync("Set-Prompt", { horizontalLayout: "full" }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "set-prompt",
3
- "version": "0.2.0",
3
+ "version": "0.3.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": {
@@ -9,9 +9,8 @@
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start:dev": "tsx src/index.ts",
12
- "build": "tsup && npm run copy_bin && npm run copy_templates",
12
+ "build": "rimraf dist && tsup && npm run copy_bin",
13
13
  "copy_bin": "cpx \"src/bin/set-prompt.js\" dist/bin",
14
- "copy_templates": "cpx \"src/templates/**/*\" dist/templates",
15
14
  "test": "vitest --reporter=verbose"
16
15
  },
17
16
  "keywords": [
@@ -23,6 +22,14 @@
23
22
  ],
24
23
  "author": "juncha9 (https://github.com/juncha9)",
25
24
  "license": "MIT",
25
+ "homepage": "https://github.com/juncha9/set-prompt#readme",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/juncha9/set-prompt.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/juncha9/set-prompt/issues"
32
+ },
26
33
  "dependencies": {
27
34
  "@inquirer/prompts": "^8.3.0",
28
35
  "chalk": "^5.6.2",
@@ -42,6 +49,7 @@
42
49
  "cpx": "^1.5.0",
43
50
  "memfs": "^4.56.11",
44
51
  "prettier": "^3.8.1",
52
+ "rimraf": "^6.1.3",
45
53
  "tsup": "^8.5.1",
46
54
  "tsx": "^4.21.0",
47
55
  "typescript": "^5.9.3",