set-prompt 0.7.0 → 0.8.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,43 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.8.0] - 2026-05-02
8
+
9
+ ### Added
10
+ - **Hermes integration** (`link hermes`) — generates a Hermes plugin at `~/.hermes/plugins/set-prompt/` that loads skills, commands, and hooks directly from the registered prompt repo at Hermes startup
11
+ - `plugin.yaml` — Hermes plugin manifest (`name`, `version`, `description`)
12
+ - `__init__.py` — Python adapter generated at link time. The repo's absolute path is baked into a `REPO_DIR` constant (JSON-encoded for Windows-safe backslash handling), so no symlinks are required. At Hermes startup, `register(ctx)` walks `<repo>/skills/<name>/SKILL.md` for `ctx.register_skill()`, `<repo>/commands/*.md` for `ctx.register_command()` (handler injects the markdown body via `ctx.inject_message()`), and `<repo>/hooks/hooks.json` for `ctx.register_hook()`.
13
+ - Shared `hooks.json` strategy — the same `hooks/hooks.json` used by Claude Code/Cursor is reused. Only Hermes-native event keys (`pre_tool_call`, `post_tool_call`, `pre_llm_call`, `post_llm_call`, `on_session_start`, `on_session_end`, `on_session_finalize`, `on_session_reset`, `subagent_stop`, `pre_gateway_dispatch`) are picked up; other tools' keys are ignored, so a single file can drive multiple platforms.
14
+ - Hook command substitution: `${SET_PROMPT_REPO}` token + `SET_PROMPT_REPO` env var. Hermes hook callbacks receive a JSON payload on stdin: `{"event": "<event>", "args": [...], "kwargs": {...}}`.
15
+ - `~/.hermes/config.yaml` activation — created with `plugins.enabled: [set-prompt]` if absent. Existing files are never auto-modified; if `set-prompt` is missing from `enabled`, set-prompt prints the snippet for manual merge.
16
+ - `HermesConfigSchema` + `HermesConfig` type + `configManager.hermes` getter/setter + `isHermesEnabled()`
17
+ - `HERMES_DIR`, `HERMES_PLUGIN_DIR`, `HERMES_CONFIG_PATH` constants in `_defs`
18
+ - `linkHermes()` / `unlinkHermes()` in `src/link/hermes.ts`
19
+ - `SET_PROMPT_GUIDE` template: Hermes commands/hooks integration sections (set-prompt's adapter behavior, event whitelist table, payload format, decision-control caveat)
20
+ - Tests: `tests/commands/link-hermes.test.ts` (11 cases — REPO_DIR baking, Windows backslash JSON encoding, no-symlink assertion, three config.yaml branches, unlink behavior with auto-generated vs user-authored config)
21
+
22
+ ### Changed
23
+ - `status-command.ts` — added missing branches for Cursor / OpenCode / Gemini CLI / Hermes (previously fell through silently after Antigravity)
24
+ - `uninstall-command.ts` — added missing unlink calls for OpenCode / Gemini CLI in addition to Hermes
25
+ - `link-command.ts` description in `index.ts` — agent list extended with `hermes`
26
+ - Test mocks updated across `link-command`, `status-command`, `uninstall-command` to cover all current `is*Enabled()` predicates and `unlink*` exports
27
+
28
+ ### Notes / Caveats
29
+ - **Hermes loads plugins once at startup** — `register(ctx)` is called exactly once. Add/remove a skill, command, or hook in your repo and Hermes must be restarted to pick it up. Other tools' auto-discovery is not available here.
30
+ - **Hermes hooks are observation-only via this bridge** — block/allow decision semantics are not propagated back to Hermes. Use platform-native hooks if you need to gate tool calls.
31
+ - **Hermes does not auto-modify `~/.hermes/config.yaml`** — to avoid clobbering memory-provider / context-engine selections, set-prompt only writes the file when it's absent. Existing files are read-only; the user is asked to add `- set-prompt` under `plugins.enabled` manually if missing.
32
+ - **`AGENT_PROMPT_DIRS[HERMES]` is empty** — Hermes intentionally does **not** symlink directories. The Python adapter reads from `REPO_DIR` directly, sidestepping Windows symlink/junction permission edge cases.
33
+
34
+ ---
35
+
36
+ ## [0.7.1] - 2026-04-20
37
+
38
+ ### Changed
39
+ - README hero: replaced ASCII structure diagram with hand-drawn architecture image (`docs/imgs/architecture.png`), loaded via GitHub raw URL so it renders on both GitHub and npmjs.com
40
+ - Added `docs/` directory for documentation assets (`docs/imgs/` for images); excluded from npm package via `.npmignore`
41
+
42
+ ---
43
+
7
44
  ## [0.7.0] - 2026-04-20
8
45
 
9
46
  ### Added
package/README.md CHANGED
@@ -12,25 +12,7 @@
12
12
 
13
13
  Your skills, commands, and agents live in git. One command syncs them into every AI coding tool you use — so the prompts you built stay with you no matter which tool you're in.
14
14
 
15
- ```
16
- repo/ (git)
17
- ├── skills/
18
- ├── commands/
19
- ├── hooks/
20
- ├── agents/
21
- ├── rules/
22
- ├── .mcp.json
23
- ├── .app.json
24
- ├── .claude-plugin/plugin.json
25
- └── .codex-plugin/plugin.json
26
-
27
- ┌──────────────────┼──────────────────────┐
28
- ▼ ▼ ▼
29
- Claude Code Codex RooCode, OpenClaw,
30
- (marketplace + (marketplace + Antigravity, Cursor,
31
- repo symlink) cache symlink) OpenCode, Gemini CLI
32
- (dir symlinks)
33
- ```
15
+ ![set-prompt architecture](https://raw.githubusercontent.com/juncha9/set-prompt/main/docs/imgs/architecture.png)
34
16
 
35
17
  ## Quick Start
36
18
 
@@ -112,6 +94,7 @@ set-prompt link antigravity # link Antigravity only
112
94
  set-prompt link cursor # link Cursor only
113
95
  set-prompt link opencode # link OpenCode only
114
96
  set-prompt link geminicli # link Gemini CLI only
97
+ set-prompt link hermes # link Hermes only
115
98
  ```
116
99
 
117
100
  The interactive mode shows all agents with their current state. **Check to link, uncheck to unlink** — existing directories are backed up before being replaced.
@@ -126,6 +109,7 @@ The interactive mode shows all agents with their current state. **Check to link,
126
109
  | Cursor | dir symlinks into `~/.cursor/` | `skills/`, `agents/`, `commands/`, `hooks/`, `mcp.json` (hardlink) |
127
110
  | OpenCode | dir symlinks into `~/.config/opencode/` | `skills/`, `commands/`, `agents/` |
128
111
  | Gemini CLI | dir symlinks into `~/.gemini/` | `skills/`, `commands/`, `agents/` |
112
+ | Hermes | Python plugin adapter at `~/.hermes/plugins/set-prompt/` | `skills/`, `commands/`, `hooks/hooks.json` (read directly from repo at startup) |
129
113
 
130
114
  > **Note on Claude Code**: Operates as a plugin. Restart Claude Code after linking for the plugin to be recognized.
131
115
 
@@ -139,6 +123,8 @@ The interactive mode shows all agents with their current state. **Check to link,
139
123
 
140
124
  > **Note on Gemini CLI**: Skills follow the standard `skills/<name>/SKILL.md` pattern. Commands use `.toml` format (not `.md`) and agents use `.md` with YAML frontmatter. Files in your repo's `commands/` must be TOML for Gemini CLI to recognize them. **Agents have strict frontmatter validation** — unknown keys (e.g. `allowed-tools`, `color`, `mode` from other platforms) cause Gemini CLI to reject the agent. See `SET_PROMPT_GUIDE.md` for the allowed keys.
141
125
 
126
+ > **Note on Hermes**: Hermes plugins must register skills/commands/hooks programmatically — directory drop-ins are not auto-discovered. set-prompt generates a small Python adapter (`~/.hermes/plugins/set-prompt/__init__.py`) with the repo's absolute path baked in, so no symlinks are needed. **Restart Hermes after `link` (or after adding/removing a skill in your repo)** — `register()` runs only once at Hermes startup. Activation requires `set-prompt` listed under `plugins.enabled` in `~/.hermes/config.yaml` — set-prompt creates this file if absent but never modifies an existing one (it prints the snippet to add manually). Hooks are observation-only on the Hermes side: the same `hooks/hooks.json` is reused, but Hermes events (`pre_tool_call`, `on_session_start`, etc.) are picked up while Claude/Cursor keys are ignored. Hook scripts can reference `${SET_PROMPT_REPO}`.
127
+
142
128
  ---
143
129
 
144
130
  ### Step 3 — Keep in sync
@@ -161,7 +147,7 @@ set-prompt repo path # print repo location (e.g. cd "$(sppt rep
161
147
  set-prompt repo open # open repo in OS file manager (--code VSCode, --stree Sourcetree)
162
148
  ```
163
149
 
164
- Symlink-based agents (Claude Code, Codex, RooCode, OpenClaw, Antigravity, OpenCode, Gemini CLI) reflect changes immediately after pull. Cursor's `mcp.json` is a hardlink to repo's `.mcp.json`, so edits to either side are reflected automatically.
150
+ Symlink-based agents (Claude Code, Codex, RooCode, OpenClaw, Antigravity, OpenCode, Gemini CLI) reflect changes immediately after pull. Cursor's `mcp.json` is a hardlink to repo's `.mcp.json`, so edits to either side are reflected automatically. **Hermes** is the exception — its Python adapter only re-scans the repo at process startup, so restart Hermes after pulling new content.
165
151
 
166
152
  ---
167
153
 
@@ -249,6 +235,12 @@ set-prompt uninstall
249
235
  ├── commands/ → repo/commands (Gemini reads *.toml)
250
236
  ├── agents/ → repo/agents (Gemini reads *.md)
251
237
  └── antigravity/ (Antigravity's own subtree, unrelated)
238
+
239
+ ~/.hermes/ # Hermes (Python plugin adapter, no symlinks)
240
+ ├── config.yaml # plugins.enabled: [set-prompt] (created if absent)
241
+ └── plugins/set-prompt/
242
+ ├── plugin.yaml # Hermes manifest
243
+ └── __init__.py # REPO_DIR baked in; reads <repo>/{skills,commands,hooks/hooks.json} at startup
252
244
  ```
253
245
 
254
246
  ## Warning
@@ -263,6 +255,7 @@ set-prompt uninstall
263
255
  - **Cursor** — replaces directories and `mcp.json` in `~/.cursor/`
264
256
  - **OpenCode** — replaces directories in `~/.config/opencode/`
265
257
  - **Gemini CLI** — replaces directories in `~/.gemini/` (`antigravity/` subtree untouched)
258
+ - **Hermes** — generates `~/.hermes/plugins/set-prompt/{plugin.yaml, __init__.py}` and writes `~/.hermes/config.yaml` only when absent (existing files are never auto-modified)
266
259
 
267
260
  Before making any changes, `set-prompt` creates a backup and rolls back automatically on failure. However, you should be aware that:
268
261
 
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk23 from "chalk";
5
+ import chalk24 from "chalk";
6
6
  import figlet from "figlet";
7
- import fs14 from "fs";
8
- import path13 from "path";
7
+ import fs15 from "fs";
8
+ import path14 from "path";
9
9
  import { fileURLToPath } from "url";
10
10
 
11
11
  // src/commands/install-command.ts
@@ -38,6 +38,9 @@ var OPENCODE_DIR = path.join(os.homedir(), ".config", "opencode");
38
38
  var OPENCODE_BACKUP_DIR = path.join(OPENCODE_DIR, "SET_PROMPT_BACKUP");
39
39
  var GEMINICLI_DIR = path.join(os.homedir(), ".gemini");
40
40
  var GEMINICLI_BACKUP_DIR = path.join(GEMINICLI_DIR, "SET_PROMPT_BACKUP");
41
+ var HERMES_DIR = path.join(os.homedir(), ".hermes");
42
+ var HERMES_PLUGIN_DIR = path.join(HERMES_DIR, "plugins", MARKET_NAME);
43
+ var HERMES_CONFIG_PATH = path.join(HERMES_DIR, "config.yaml");
41
44
  var PROMPT_DIR_NAMES = ["skills", "commands", "hooks", "agents", "rules"];
42
45
  var AGENT_PROMPT_DIRS = {
43
46
  ["claudecode" /* CLAUDECODE */]: ["skills", "commands", "hooks", "agents"],
@@ -47,7 +50,8 @@ var AGENT_PROMPT_DIRS = {
47
50
  ["antigravity" /* ANTIGRAVITY */]: ["skills"],
48
51
  ["cursor" /* CURSOR */]: ["skills", "agents", "commands", "hooks"],
49
52
  ["opencode" /* OPENCODE */]: ["skills", "commands", "agents"],
50
- ["geminicli" /* GEMINICLI */]: ["skills", "commands", "agents"]
53
+ ["geminicli" /* GEMINICLI */]: ["skills", "commands", "agents"],
54
+ ["hermes" /* HERMES */]: []
51
55
  };
52
56
  var ALL_AGENTS = [
53
57
  { name: "Claude Code", value: "claudecode" /* CLAUDECODE */ },
@@ -57,7 +61,8 @@ var ALL_AGENTS = [
57
61
  { name: "Antigravity", value: "antigravity" /* ANTIGRAVITY */ },
58
62
  { name: "Cursor", value: "cursor" /* CURSOR */ },
59
63
  { name: "OpenCode", value: "opencode" /* OPENCODE */ },
60
- { name: "Gemini CLI", value: "geminicli" /* GEMINICLI */ }
64
+ { name: "Gemini CLI", value: "geminicli" /* GEMINICLI */ },
65
+ { name: "Hermes", value: "hermes" /* HERMES */ }
61
66
  ];
62
67
 
63
68
  // src/_libs/config.ts
@@ -99,6 +104,9 @@ var GeminicliConfigSchema = z.object({
99
104
  path: z.string().nullable(),
100
105
  backup_path: z.string().nullish().optional()
101
106
  });
107
+ var HermesConfigSchema = z.object({
108
+ path: z.string().nullable()
109
+ });
102
110
  var GlobalConfigSchema = z.object({
103
111
  repo_path: z.string(),
104
112
  remote_url: z.string().nullable(),
@@ -109,7 +117,8 @@ var GlobalConfigSchema = z.object({
109
117
  antigravity: AntigravityConfigSchema.nullish().optional(),
110
118
  cursor: CursorConfigSchema.nullish().optional(),
111
119
  opencode: OpencodeConfigSchema.nullish().optional(),
112
- geminicli: GeminicliConfigSchema.nullish().optional()
120
+ geminicli: GeminicliConfigSchema.nullish().optional(),
121
+ hermes: HermesConfigSchema.nullish().optional()
113
122
  });
114
123
 
115
124
  // src/_libs/config.ts
@@ -125,6 +134,7 @@ var ConfigManager = class {
125
134
  this._cursor = null;
126
135
  this._opencode = null;
127
136
  this._geminicli = null;
137
+ this._hermes = null;
128
138
  }
129
139
  get repo_path() {
130
140
  return this._repo_path;
@@ -156,6 +166,9 @@ var ConfigManager = class {
156
166
  get geminicli() {
157
167
  return this._geminicli;
158
168
  }
169
+ get hermes() {
170
+ return this._hermes;
171
+ }
159
172
  set repo_path(v) {
160
173
  this._repo_path = v;
161
174
  }
@@ -186,6 +199,9 @@ var ConfigManager = class {
186
199
  set geminicli(v) {
187
200
  this._geminicli = v;
188
201
  }
202
+ set hermes(v) {
203
+ this._hermes = v;
204
+ }
189
205
  init() {
190
206
  this._loadFromDisk();
191
207
  if (this._repo_path != null) {
@@ -209,7 +225,8 @@ var ConfigManager = class {
209
225
  antigravity: this._antigravity,
210
226
  cursor: this._cursor,
211
227
  opencode: this._opencode,
212
- geminicli: this._geminicli
228
+ geminicli: this._geminicli,
229
+ hermes: this._hermes
213
230
  }, null, 4);
214
231
  fs.writeFileSync(CONFIG_PATH, configStr, "utf-8");
215
232
  console.log(chalk.green(`Config saved`) + chalk.dim(` \u2192 ${CONFIG_PATH}`));
@@ -252,6 +269,9 @@ var ConfigManager = class {
252
269
  isGeminicliEnabled() {
253
270
  return this._geminicli != null;
254
271
  }
272
+ isHermesEnabled() {
273
+ return this._hermes != null;
274
+ }
255
275
  _assign(config) {
256
276
  this._repo_path = config.repo_path;
257
277
  this._remote_url = config.remote_url;
@@ -263,6 +283,7 @@ var ConfigManager = class {
263
283
  this._cursor = config.cursor ?? null;
264
284
  this._opencode = config.opencode ?? null;
265
285
  this._geminicli = config.geminicli ?? null;
286
+ this._hermes = config.hermes ?? null;
266
287
  }
267
288
  _loadFromDisk() {
268
289
  if (fs.existsSync(CONFIG_PATH) === false) {
@@ -490,11 +511,46 @@ metadata:
490
511
  # Gemini CLI
491
512
  name: my-skill
492
513
  description: "What this skill does and when Gemini should use it"
514
+
515
+ # Hermes
516
+ name: my-skill
517
+ description: "What this skill does and when to use it"
518
+ version: "1.0.0"
519
+ platforms:
520
+ - macos
521
+ - linux
522
+ - windows
523
+ metadata:
524
+ hermes:
525
+ tags:
526
+ - python
527
+ - automation
528
+ category: devops
529
+ requires_toolsets:
530
+ - terminal
531
+ fallback_for_toolsets:
532
+ - browser
533
+ requires_tools:
534
+ - rg
535
+ fallback_for_tools:
536
+ - grep
537
+ config:
538
+ - key: my.setting
539
+ description: "What this controls"
540
+ default: "value"
541
+ prompt: "Prompt for setup"
542
+ required_environment_variables:
543
+ - name: TENOR_API_KEY
544
+ prompt: "Tenor API key"
545
+ help: "Get a key from https://developers.google.com/tenor"
546
+ required_for: "full functionality"
493
547
  ---
494
548
  \`\`\`
495
549
 
496
550
  > **Gemini CLI note**: Only \`name\` and \`description\` are recognized. \`name\` must be lowercase with hyphens and match the directory name.
497
551
 
552
+ > **Hermes note (set-prompt integration)**: Hermes does not auto-discover files in standard directories \u2014 plugins must register skills programmatically. set-prompt generates \`~/.hermes/plugins/set-prompt/{plugin.yaml, __init__.py}\` on \`set-prompt link hermes\`. The \`__init__.py\` reads \`<repo>/skills/<skill-name>/SKILL.md\` directly (REPO_DIR is baked in at link time) and calls \`ctx.register_skill()\` at Hermes startup. The skill directory layout is the same as other platforms \u2014 no nested category folder.
553
+
498
554
  | Field | Required | Platform | Description |
499
555
  |-------|----------|----------|-------------|
500
556
  | \`name\` | Yes | All | Display name. Claude Code / OpenCode / RooCode: lowercase, numbers, hyphens only (no underscores; Claude Code / OpenCode limit to 64 chars). Gemini CLI: lowercase with hyphens, must match the directory name. Antigravity: optional, defaults to folder name. |
@@ -519,7 +575,17 @@ description: "What this skill does and when Gemini should use it"
519
575
  | \`command-arg-mode\` | No | OpenClaw | How arguments are forwarded to the tool. (default: \`"raw"\`) |
520
576
  | \`license\` | No | Cursor, OpenCode | License name or reference to a bundled license file. |
521
577
  | \`compatibility\` | No | Cursor, OpenCode | Environment requirements (system packages, network access, etc.) |
522
- | \`metadata\` | No | Cursor, OpenCode | Arbitrary key-value mapping for additional metadata. OpenCode requires string-to-string values only. |
578
+ | \`metadata\` | No | Cursor, OpenCode, Hermes | Arbitrary key-value mapping for additional metadata. OpenCode requires string-to-string values only. Hermes uses the \`metadata.hermes.*\` namespace for platform-specific fields. |
579
+ | \`version\` | No | Hermes | Version number (e.g. \`"1.0.0"\`) |
580
+ | \`platforms\` | No | Hermes | OS allow-list. Values: \`macos\`, \`linux\`, \`windows\`. (default: all) |
581
+ | \`metadata.hermes.tags\` | No | Hermes | Categorization keywords array. |
582
+ | \`metadata.hermes.category\` | No | Hermes | Skill category \u2014 should match the parent directory name. |
583
+ | \`metadata.hermes.requires_toolsets\` | No | Hermes | Skill is hidden unless these toolsets are available. |
584
+ | \`metadata.hermes.fallback_for_toolsets\` | No | Hermes | Skill is hidden when these toolsets are already available (acts as a fallback). |
585
+ | \`metadata.hermes.requires_tools\` | No | Hermes | Skill is hidden unless these specific tools are available. |
586
+ | \`metadata.hermes.fallback_for_tools\` | No | Hermes | Skill is hidden when these specific tools are already available. |
587
+ | \`metadata.hermes.config\` | No | Hermes | Array of configuration declarations. Each entry: \`key\`, \`description\`, \`default\`, \`prompt\`. |
588
+ | \`required_environment_variables\` | No | Hermes | Array of required env vars. Each entry: \`name\`, \`prompt\`, \`help\`, \`required_for\`. |
523
589
 
524
590
  ---
525
591
 
@@ -591,6 +657,10 @@ Include: 1) Refactored code. 2) Explanation of changes.
591
657
 
592
658
  **Namespacing**: subdirectories create namespaced commands \u2014 \`commands/git/commit.toml\` becomes \`/git:commit\`. After edits, run \`/commands reload\` in Gemini CLI.
593
659
 
660
+ #### Hermes commands (set-prompt integration)
661
+
662
+ Hermes commands are registered programmatically. set-prompt's generated \`__init__.py\` walks \`<repo>/commands/*.md\`, parses each file's YAML frontmatter for \`name\` / \`description\`, and calls \`ctx.register_command(name, handler, description)\`. The handler injects the markdown body as a user message via \`ctx.inject_message(body, role="user")\` when the user invokes the slash command. Hermes does **not** read \`allowed-tools\`, \`model\`, \`agent\`, or other platform-specific frontmatter \u2014 only \`name\` and \`description\` are honored.
663
+
594
664
  | Field | Required | Platform | Description |
595
665
  |-------|----------|----------|-------------|
596
666
  | \`name\` | No | All | Display name \u2014 lowercase, numbers, hyphens only (max 64 chars). Defaults to directory name. |
@@ -877,6 +947,55 @@ echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"
877
947
  echo '{"permission":"deny","user_message":"Blocked by policy","agent_message":"Not allowed"}'
878
948
  \`\`\`
879
949
 
950
+ ---
951
+
952
+ #### Hermes Hooks (set-prompt integration)
953
+
954
+ Hermes uses Python callbacks rather than declarative JSON \u2014 set-prompt bridges the gap by reading the **same** \`hooks/hooks.json\` used by Claude Code/Cursor, but only picks up entries whose top-level key is a Hermes event name. Other tools' keys (\`UserPromptSubmit\`, \`afterFileEdit\`, etc.) are ignored, so the file can hold hooks for multiple platforms side-by-side.
955
+
956
+ \`\`\`json
957
+ {
958
+ "hooks": {
959
+ "UserPromptSubmit": [
960
+ { "hooks": [{ "type": "command", "command": "...claude-only..." }] }
961
+ ],
962
+ "pre_tool_call": [
963
+ {
964
+ "hooks": [
965
+ {
966
+ "type": "command",
967
+ "command": "node \\"\${SET_PROMPT_REPO}/hooks/audit.mjs\\"",
968
+ "timeout": 5
969
+ }
970
+ ]
971
+ }
972
+ ]
973
+ }
974
+ }
975
+ \`\`\`
976
+
977
+ **Hermes events** (whitelist applied by the generated \`__init__.py\`)
978
+
979
+ | Event | When it fires |
980
+ |-------|---------------|
981
+ | \`pre_tool_call\` / \`post_tool_call\` | Before / after a tool runs |
982
+ | \`pre_llm_call\` / \`post_llm_call\` | Before / after an LLM call |
983
+ | \`on_session_start\` / \`on_session_end\` / \`on_session_finalize\` / \`on_session_reset\` | Session lifecycle |
984
+ | \`subagent_stop\` | Subagent completes |
985
+ | \`pre_gateway_dispatch\` | Before a gateway dispatch |
986
+
987
+ **Handler fields** (only \`type: "command"\` is supported)
988
+
989
+ | Field | Description |
990
+ |-------|-------------|
991
+ | \`type\` | Must be \`"command"\`. Other types are ignored. |
992
+ | \`command\` | Shell command. \`\${SET_PROMPT_REPO}\` is substituted with the repo path; the \`SET_PROMPT_REPO\` env var is also set. |
993
+ | \`timeout\` | Seconds before the subprocess is killed. Optional. |
994
+
995
+ **Payload**: when the hook fires, set-prompt's runner pipes a JSON object on stdin to the command \u2014 \`{"event": "<event_name>", "args": [...], "kwargs": {...}}\`. \`args\`/\`kwargs\` mirror whatever Hermes passed to the callback (stringified via \`repr()\` for safety).
996
+
997
+ **Decision control**: Hermes does not currently expose block/allow semantics through this bridge \u2014 the subprocess return value is not propagated back. Hooks are observation-only on the Hermes side.
998
+
880
999
  `;
881
1000
 
882
1001
  // src/commands/scaffold-command.ts
@@ -1098,7 +1217,7 @@ var installCommand = async (target) => {
1098
1217
  };
1099
1218
 
1100
1219
  // src/commands/link-command.ts
1101
- import chalk14 from "chalk";
1220
+ import chalk15 from "chalk";
1102
1221
  import { checkbox } from "@inquirer/prompts";
1103
1222
 
1104
1223
  // src/link/claudecode.ts
@@ -2366,6 +2485,232 @@ Removing Gemini CLI integration...`));
2366
2485
  configManager.save();
2367
2486
  };
2368
2487
 
2488
+ // src/link/hermes.ts
2489
+ import path12 from "path";
2490
+ import fs13 from "fs";
2491
+ import chalk14 from "chalk";
2492
+ import { confirm as confirm11 } from "@inquirer/prompts";
2493
+ var PLUGIN_YAML = `name: ${MARKET_NAME}
2494
+ version: "1.0"
2495
+ description: Managed by set-prompt
2496
+ `;
2497
+ var buildInitPy = (repoPath) => {
2498
+ const repoLiteral = JSON.stringify(repoPath);
2499
+ return `"""Auto-generated by set-prompt. Do not edit by hand."""
2500
+ import json
2501
+ import os
2502
+ import subprocess
2503
+ from pathlib import Path
2504
+
2505
+ REPO_DIR = Path(${repoLiteral})
2506
+
2507
+ HERMES_EVENTS = {
2508
+ "pre_tool_call",
2509
+ "post_tool_call",
2510
+ "pre_llm_call",
2511
+ "post_llm_call",
2512
+ "on_session_start",
2513
+ "on_session_end",
2514
+ "on_session_finalize",
2515
+ "on_session_reset",
2516
+ "subagent_stop",
2517
+ "pre_gateway_dispatch",
2518
+ }
2519
+
2520
+
2521
+ def _parse_frontmatter(text):
2522
+ if not text.startswith("---"):
2523
+ return {}, text
2524
+ end = text.find("\\n---", 3)
2525
+ if end == -1:
2526
+ return {}, text
2527
+ meta_block = text[3:end].strip()
2528
+ body = text[end + 4:].lstrip("\\n")
2529
+ meta = {}
2530
+ for line in meta_block.splitlines():
2531
+ if ":" not in line:
2532
+ continue
2533
+ key, _, value = line.partition(":")
2534
+ meta[key.strip()] = value.strip().strip('"').strip("'")
2535
+ return meta, body
2536
+
2537
+
2538
+ def _make_command_handler(ctx, body):
2539
+ def handler(_args=None):
2540
+ ctx.inject_message(body, role="user")
2541
+ return handler
2542
+
2543
+
2544
+ def _make_hook_runner(event, raw_cmd, timeout):
2545
+ def runner(*args, **kwargs):
2546
+ cmd = raw_cmd.replace("\${SET_PROMPT_REPO}", str(REPO_DIR))
2547
+ env = dict(os.environ)
2548
+ env["SET_PROMPT_REPO"] = str(REPO_DIR)
2549
+ payload = json.dumps({
2550
+ "event": event,
2551
+ "args": [repr(a) for a in args],
2552
+ "kwargs": {k: repr(v) for k, v in kwargs.items()},
2553
+ }).encode("utf-8")
2554
+ try:
2555
+ subprocess.run(
2556
+ cmd, shell=True, input=payload, env=env,
2557
+ timeout=timeout if isinstance(timeout, (int, float)) else None,
2558
+ check=False,
2559
+ )
2560
+ except (subprocess.TimeoutExpired, OSError):
2561
+ pass
2562
+ return runner
2563
+
2564
+
2565
+ def _iter_hook_commands(entries):
2566
+ if not isinstance(entries, list):
2567
+ return
2568
+ for entry in entries:
2569
+ if not isinstance(entry, dict):
2570
+ continue
2571
+ sub = entry.get("hooks")
2572
+ if not isinstance(sub, list):
2573
+ continue
2574
+ for h in sub:
2575
+ if not isinstance(h, dict):
2576
+ continue
2577
+ if h.get("type") != "command":
2578
+ continue
2579
+ cmd = h.get("command")
2580
+ if not isinstance(cmd, str) or not cmd:
2581
+ continue
2582
+ yield cmd, h.get("timeout")
2583
+
2584
+
2585
+ def _register_hooks(ctx):
2586
+ hooks_json = REPO_DIR / "hooks" / "hooks.json"
2587
+ if not hooks_json.exists():
2588
+ return
2589
+ try:
2590
+ data = json.loads(hooks_json.read_text(encoding="utf-8"))
2591
+ except (OSError, ValueError):
2592
+ return
2593
+ block = data.get("hooks") if isinstance(data, dict) else None
2594
+ if not isinstance(block, dict):
2595
+ return
2596
+ for event, entries in block.items():
2597
+ if event not in HERMES_EVENTS:
2598
+ continue
2599
+ for cmd, timeout in _iter_hook_commands(entries):
2600
+ ctx.register_hook(event, _make_hook_runner(event, cmd, timeout))
2601
+
2602
+
2603
+ def register(ctx):
2604
+ skills_dir = REPO_DIR / "skills"
2605
+ if skills_dir.exists():
2606
+ for skill_path in sorted(skills_dir.iterdir()):
2607
+ if not skill_path.is_dir():
2608
+ continue
2609
+ if not (skill_path / "SKILL.md").exists():
2610
+ continue
2611
+ ctx.register_skill(skill_path.name, str(skill_path))
2612
+
2613
+ commands_dir = REPO_DIR / "commands"
2614
+ if commands_dir.exists():
2615
+ for cmd_file in sorted(commands_dir.glob("*.md")):
2616
+ try:
2617
+ text = cmd_file.read_text(encoding="utf-8")
2618
+ except OSError:
2619
+ continue
2620
+ meta, body = _parse_frontmatter(text)
2621
+ name = meta.get("name") or cmd_file.stem
2622
+ description = meta.get("description") or ""
2623
+ ctx.register_command(name, _make_command_handler(ctx, body), description)
2624
+
2625
+ _register_hooks(ctx)
2626
+ `;
2627
+ };
2628
+ var FRESH_CONFIG_YAML = `# Generated by set-prompt
2629
+ plugins:
2630
+ enabled:
2631
+ - ${MARKET_NAME}
2632
+ `;
2633
+ var ENABLE_SNIPPET = `plugins:
2634
+ enabled:
2635
+ - ${MARKET_NAME}`;
2636
+ var writePluginManifest = (repoPath) => {
2637
+ fs13.writeFileSync(path12.join(HERMES_PLUGIN_DIR, "plugin.yaml"), PLUGIN_YAML, "utf-8");
2638
+ fs13.writeFileSync(path12.join(HERMES_PLUGIN_DIR, "__init__.py"), buildInitPy(repoPath), "utf-8");
2639
+ };
2640
+ var ensureHermesConfigEnabled = () => {
2641
+ if (fs13.existsSync(HERMES_CONFIG_PATH) === false) {
2642
+ fs13.mkdirSync(path12.dirname(HERMES_CONFIG_PATH), { recursive: true });
2643
+ fs13.writeFileSync(HERMES_CONFIG_PATH, FRESH_CONFIG_YAML, "utf-8");
2644
+ console.log(chalk14.green(" \u2713 ") + chalk14.dim(`wrote ${HERMES_CONFIG_PATH}`));
2645
+ return;
2646
+ }
2647
+ const content = fs13.readFileSync(HERMES_CONFIG_PATH, "utf-8");
2648
+ const enabledPattern = new RegExp(`^\\s*-\\s+${MARKET_NAME}\\s*$`, "m");
2649
+ if (enabledPattern.test(content)) {
2650
+ console.log(chalk14.dim(` \u2713 ${MARKET_NAME} already listed in ${HERMES_CONFIG_PATH}`));
2651
+ return;
2652
+ }
2653
+ console.log(chalk14.yellow(`
2654
+ \u26A0 Hermes config exists but does not list "${MARKET_NAME}" as enabled.`));
2655
+ console.log(chalk14.dim(" Add the following entry under plugins.enabled to activate this plugin:\n"));
2656
+ console.log(chalk14.cyan(ENABLE_SNIPPET.split("\n").map((l) => ` ${l}`).join("\n")));
2657
+ console.log(chalk14.dim(`
2658
+ File: ${HERMES_CONFIG_PATH}`));
2659
+ };
2660
+ var linkHermes = async () => {
2661
+ const repoPath = resolveRepoPath();
2662
+ if (repoPath == null) {
2663
+ return;
2664
+ }
2665
+ console.log(chalk14.green(`
2666
+ Setting up Hermes plugin...`));
2667
+ console.log(chalk14.dim(HERMES_PLUGIN_DIR));
2668
+ try {
2669
+ fs13.mkdirSync(HERMES_PLUGIN_DIR, { recursive: true });
2670
+ writePluginManifest(repoPath);
2671
+ console.log(chalk14.dim(" \u251C\u2500\u2500 ") + chalk14.bold("plugin.yaml") + chalk14.green(" \u2713"));
2672
+ console.log(chalk14.dim(" \u2514\u2500\u2500 ") + chalk14.bold("__init__.py") + chalk14.dim(` (REPO_DIR \u2192 ${repoPath})`) + chalk14.green(" \u2713"));
2673
+ ensureHermesConfigEnabled();
2674
+ configManager.hermes = { path: HERMES_PLUGIN_DIR };
2675
+ configManager.save();
2676
+ console.log(chalk14.dim(`
2677
+ \u2139 Hermes loads plugins at startup \u2014 restart Hermes to pick up new skills/commands.`));
2678
+ } catch (ex) {
2679
+ console.error(chalk14.red(`\u274C Failed to set up Hermes plugin: ${ex.message}`));
2680
+ }
2681
+ };
2682
+ var unlinkHermes = async (force = false) => {
2683
+ if (!force) {
2684
+ const ok = await confirm11({
2685
+ message: `Remove Hermes plugin dir (${HERMES_PLUGIN_DIR})?`,
2686
+ default: false
2687
+ });
2688
+ if (!ok) {
2689
+ console.log(chalk14.yellow("Cancelled."));
2690
+ return;
2691
+ }
2692
+ }
2693
+ console.log(chalk14.red(`
2694
+ Removing Hermes plugin...`));
2695
+ console.log(chalk14.dim(HERMES_PLUGIN_DIR));
2696
+ if (fs13.existsSync(HERMES_PLUGIN_DIR)) {
2697
+ fs13.rmSync(HERMES_PLUGIN_DIR, { recursive: true, force: true });
2698
+ console.log(chalk14.red(" removed") + chalk14.dim(`: ${HERMES_PLUGIN_DIR}`));
2699
+ }
2700
+ if (fs13.existsSync(HERMES_CONFIG_PATH)) {
2701
+ const content = fs13.readFileSync(HERMES_CONFIG_PATH, "utf-8");
2702
+ if (content.trimStart().startsWith("# Generated by set-prompt")) {
2703
+ fs13.unlinkSync(HERMES_CONFIG_PATH);
2704
+ console.log(chalk14.red(" removed") + chalk14.dim(`: ${HERMES_CONFIG_PATH}`));
2705
+ } else if (new RegExp(`^\\s*-\\s+${MARKET_NAME}\\s*$`, "m").test(content)) {
2706
+ console.log(chalk14.yellow(` \u26A0 Hermes config still lists "${MARKET_NAME}" \u2014 remove it manually:`));
2707
+ console.log(chalk14.dim(` ${HERMES_CONFIG_PATH}`));
2708
+ }
2709
+ }
2710
+ configManager.hermes = null;
2711
+ configManager.save();
2712
+ };
2713
+
2369
2714
  // src/commands/link-command.ts
2370
2715
  var LINK_MAP = {
2371
2716
  ["claudecode" /* CLAUDECODE */]: linkClaudeCode,
@@ -2375,7 +2720,8 @@ var LINK_MAP = {
2375
2720
  ["antigravity" /* ANTIGRAVITY */]: linkAntigravity,
2376
2721
  ["cursor" /* CURSOR */]: linkCursor,
2377
2722
  ["opencode" /* OPENCODE */]: linkOpencode,
2378
- ["geminicli" /* GEMINICLI */]: linkGeminicli
2723
+ ["geminicli" /* GEMINICLI */]: linkGeminicli,
2724
+ ["hermes" /* HERMES */]: linkHermes
2379
2725
  };
2380
2726
  var UNLINK_MAP = {
2381
2727
  ["claudecode" /* CLAUDECODE */]: unlinkClaudeCode,
@@ -2385,13 +2731,14 @@ var UNLINK_MAP = {
2385
2731
  ["antigravity" /* ANTIGRAVITY */]: unlinkAntigravity,
2386
2732
  ["cursor" /* CURSOR */]: unlinkCursor,
2387
2733
  ["opencode" /* OPENCODE */]: unlinkOpencode,
2388
- ["geminicli" /* GEMINICLI */]: unlinkGeminicli
2734
+ ["geminicli" /* GEMINICLI */]: unlinkGeminicli,
2735
+ ["hermes" /* HERMES */]: unlinkHermes
2389
2736
  };
2390
2737
  var linkCommand = async (tool) => {
2391
2738
  if (tool != null) {
2392
2739
  const known = ALL_AGENTS.some((a) => a.value === tool);
2393
2740
  if (!known) {
2394
- console.log(chalk14.red(`Unknown vendor: ${tool}`));
2741
+ console.log(chalk15.red(`Unknown vendor: ${tool}`));
2395
2742
  process.exit(1);
2396
2743
  }
2397
2744
  await LINK_MAP[tool]();
@@ -2405,12 +2752,13 @@ var linkCommand = async (tool) => {
2405
2752
  ["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled(),
2406
2753
  ["cursor" /* CURSOR */]: configManager.isCursorEnabled(),
2407
2754
  ["opencode" /* OPENCODE */]: configManager.isOpencodeEnabled(),
2408
- ["geminicli" /* GEMINICLI */]: configManager.isGeminicliEnabled()
2755
+ ["geminicli" /* GEMINICLI */]: configManager.isGeminicliEnabled(),
2756
+ ["hermes" /* HERMES */]: configManager.isHermesEnabled()
2409
2757
  };
2410
2758
  const selected = await checkbox({
2411
2759
  message: "Which AI agent do you want to integrate?",
2412
2760
  choices: ALL_AGENTS.map((a) => ({
2413
- name: prevLinked[a.value] ? `${a.name} ${chalk14.dim("(applied)")}` : a.name,
2761
+ name: prevLinked[a.value] ? `${a.name} ${chalk15.dim("(applied)")}` : a.name,
2414
2762
  value: a.value,
2415
2763
  checked: prevLinked[a.value]
2416
2764
  })),
@@ -2421,10 +2769,10 @@ var linkCommand = async (tool) => {
2421
2769
  const toUnlink = ALL_AGENTS.filter((a) => prevLinked[a.value] && !selected.includes(a.value));
2422
2770
  console.log();
2423
2771
  if (toLink.length > 0) {
2424
- console.log(chalk14.green(" Link ") + chalk14.dim("\u2192 ") + toLink.map((a) => chalk14.bold(a.name)).join(chalk14.dim(", ")));
2772
+ console.log(chalk15.green(" Link ") + chalk15.dim("\u2192 ") + toLink.map((a) => chalk15.bold(a.name)).join(chalk15.dim(", ")));
2425
2773
  }
2426
2774
  if (toUnlink.length > 0) {
2427
- console.log(chalk14.red(" Unlink ") + chalk14.dim("\u2192 ") + toUnlink.map((a) => chalk14.bold(a.name)).join(chalk14.dim(", ")));
2775
+ console.log(chalk15.red(" Unlink ") + chalk15.dim("\u2192 ") + toUnlink.map((a) => chalk15.bold(a.name)).join(chalk15.dim(", ")));
2428
2776
  }
2429
2777
  console.log();
2430
2778
  if (toLink.length === 0 && toUnlink.length === 0) {
@@ -2443,47 +2791,59 @@ var linkCommand = async (tool) => {
2443
2791
  };
2444
2792
 
2445
2793
  // src/commands/uninstall-command.ts
2446
- import fs13 from "fs";
2447
- import chalk15 from "chalk";
2448
- import { confirm as confirm11 } from "@inquirer/prompts";
2794
+ import fs14 from "fs";
2795
+ import chalk16 from "chalk";
2796
+ import { confirm as confirm12 } from "@inquirer/prompts";
2449
2797
  var uninstallCommand = async () => {
2450
2798
  const targets = [
2451
- { label: `Config file ${chalk15.dim(CONFIG_PATH)}`, path: CONFIG_PATH },
2452
- { label: `Home dir ${chalk15.dim(HOME_DIR)}`, path: HOME_DIR }
2453
- ].filter((t) => fs13.existsSync(t.path));
2799
+ { label: `Config file ${chalk16.dim(CONFIG_PATH)}`, path: CONFIG_PATH },
2800
+ { label: `Home dir ${chalk16.dim(HOME_DIR)}`, path: HOME_DIR }
2801
+ ].filter((t) => fs14.existsSync(t.path));
2454
2802
  const hasClaudeCode = configManager.isClaudeCodeEnabled();
2455
2803
  const hasRooCode = configManager.isRooCodeEnabled();
2456
2804
  const hasOpenclaw = configManager.isOpenclawEnabled();
2457
2805
  const hasAntigravity = configManager.isAntigravityEnabled();
2458
2806
  const hasCodex = configManager.isCodexEnabled();
2459
2807
  const hasCursor = configManager.isCursorEnabled();
2460
- if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity && !hasCodex && !hasCursor) {
2461
- console.log(chalk15.yellow("Nothing to remove."));
2808
+ const hasOpencode = configManager.isOpencodeEnabled();
2809
+ const hasGeminicli = configManager.isGeminicliEnabled();
2810
+ const hasHermes = configManager.isHermesEnabled();
2811
+ if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity && !hasCodex && !hasCursor && !hasOpencode && !hasGeminicli && !hasHermes) {
2812
+ console.log(chalk16.yellow("Nothing to remove."));
2462
2813
  return;
2463
2814
  }
2464
- console.log(chalk15.red("\nThe following will be removed:"));
2815
+ console.log(chalk16.red("\nThe following will be removed:"));
2465
2816
  targets.forEach((t) => console.log(` ${t.label}`));
2466
2817
  if (hasClaudeCode) {
2467
- console.log(` Claude Code plugin dir ${chalk15.dim(CLAUDE_CODE_DIR)}`);
2818
+ console.log(` Claude Code plugin dir ${chalk16.dim(CLAUDE_CODE_DIR)}`);
2468
2819
  }
2469
2820
  if (hasRooCode) {
2470
- console.log(` RooCode symlinks ${chalk15.dim("(backup will be restored)")}`);
2821
+ console.log(` RooCode symlinks ${chalk16.dim("(backup will be restored)")}`);
2471
2822
  }
2472
2823
  if (hasOpenclaw) {
2473
- console.log(` OpenClaw symlinks ${chalk15.dim("(backup will be restored)")}`);
2824
+ console.log(` OpenClaw symlinks ${chalk16.dim("(backup will be restored)")}`);
2474
2825
  }
2475
2826
  if (hasAntigravity) {
2476
- console.log(` Antigravity symlinks ${chalk15.dim("(backup will be restored)")}`);
2827
+ console.log(` Antigravity symlinks ${chalk16.dim("(backup will be restored)")}`);
2477
2828
  }
2478
2829
  if (hasCodex) {
2479
- console.log(` Codex symlinks ${chalk15.dim("(backup will be restored)")}`);
2830
+ console.log(` Codex symlinks ${chalk16.dim("(backup will be restored)")}`);
2480
2831
  }
2481
2832
  if (hasCursor) {
2482
- console.log(` Cursor plugin dir ${chalk15.dim("(symlink will be removed)")}`);
2833
+ console.log(` Cursor plugin dir ${chalk16.dim("(symlink will be removed)")}`);
2483
2834
  }
2484
- const ok = await confirm11({ message: "Proceed?", default: false });
2835
+ if (hasOpencode) {
2836
+ console.log(` OpenCode symlinks ${chalk16.dim("(backup will be restored)")}`);
2837
+ }
2838
+ if (hasGeminicli) {
2839
+ console.log(` Gemini CLI symlinks ${chalk16.dim("(backup will be restored)")}`);
2840
+ }
2841
+ if (hasHermes) {
2842
+ console.log(` Hermes plugin dir ${chalk16.dim("(plugin folder will be removed)")}`);
2843
+ }
2844
+ const ok = await confirm12({ message: "Proceed?", default: false });
2485
2845
  if (!ok) {
2486
- console.log(chalk15.yellow("Cancelled."));
2846
+ console.log(chalk16.yellow("Cancelled."));
2487
2847
  return;
2488
2848
  }
2489
2849
  if (hasClaudeCode) {
@@ -2504,27 +2864,36 @@ var uninstallCommand = async () => {
2504
2864
  if (hasCursor) {
2505
2865
  await unlinkCursor(true);
2506
2866
  }
2867
+ if (hasOpencode) {
2868
+ await unlinkOpencode(true);
2869
+ }
2870
+ if (hasGeminicli) {
2871
+ await unlinkGeminicli(true);
2872
+ }
2873
+ if (hasHermes) {
2874
+ await unlinkHermes(true);
2875
+ }
2507
2876
  for (const t of targets) {
2508
- fs13.rmSync(t.path, { recursive: true, force: true });
2509
- console.log(chalk15.dim(` removed: ${t.path}`));
2877
+ fs14.rmSync(t.path, { recursive: true, force: true });
2878
+ console.log(chalk16.dim(` removed: ${t.path}`));
2510
2879
  }
2511
- console.log(chalk15.green("\nUninstalled."));
2880
+ console.log(chalk16.green("\nUninstalled."));
2512
2881
  };
2513
2882
 
2514
2883
  // src/commands/status-command.ts
2515
- import chalk16 from "chalk";
2884
+ import chalk17 from "chalk";
2516
2885
  var statusCommand = () => {
2517
2886
  if (configManager.repo_path == null) {
2518
- console.log(chalk16.yellow("\u274C No repo installed."));
2519
- console.log(chalk16.dim(` Run: set-prompt install <repo-url>`));
2887
+ console.log(chalk17.yellow("\u274C No repo installed."));
2888
+ console.log(chalk17.dim(` Run: set-prompt install <repo-url>`));
2520
2889
  return;
2521
2890
  }
2522
- console.log(chalk16.bold("\nRepo"));
2523
- console.log(`${TAB}path ${chalk16.cyan(configManager.repo_path)}`);
2891
+ console.log(chalk17.bold("\nRepo"));
2892
+ console.log(`${TAB}path ${chalk17.cyan(configManager.repo_path)}`);
2524
2893
  if (configManager.remote_url != null) {
2525
- console.log(`${TAB}remote ${chalk16.dim(configManager.remote_url)}`);
2894
+ console.log(`${TAB}remote ${chalk17.dim(configManager.remote_url)}`);
2526
2895
  }
2527
- console.log(chalk16.bold("\nLinked agents"));
2896
+ console.log(chalk17.bold("\nLinked agents"));
2528
2897
  for (const agent of ALL_AGENTS) {
2529
2898
  let linked = false;
2530
2899
  let agentPath = null;
@@ -2543,9 +2912,21 @@ var statusCommand = () => {
2543
2912
  } else if (agent.value === "antigravity" /* ANTIGRAVITY */) {
2544
2913
  linked = configManager.isAntigravityEnabled();
2545
2914
  agentPath = configManager.antigravity?.path;
2546
- }
2547
- const label = linked ? chalk16.green("linked") : chalk16.dim("not linked");
2548
- const pathStr = linked && agentPath ? chalk16.dim(` \u2192 ${agentPath}`) : "";
2915
+ } else if (agent.value === "cursor" /* CURSOR */) {
2916
+ linked = configManager.isCursorEnabled();
2917
+ agentPath = configManager.cursor?.path;
2918
+ } else if (agent.value === "opencode" /* OPENCODE */) {
2919
+ linked = configManager.isOpencodeEnabled();
2920
+ agentPath = configManager.opencode?.path;
2921
+ } else if (agent.value === "geminicli" /* GEMINICLI */) {
2922
+ linked = configManager.isGeminicliEnabled();
2923
+ agentPath = configManager.geminicli?.path;
2924
+ } else if (agent.value === "hermes" /* HERMES */) {
2925
+ linked = configManager.isHermesEnabled();
2926
+ agentPath = configManager.hermes?.path;
2927
+ }
2928
+ const label = linked ? chalk17.green("linked") : chalk17.dim("not linked");
2929
+ const pathStr = linked && agentPath ? chalk17.dim(` \u2192 ${agentPath}`) : "";
2549
2930
  console.log(`${TAB}${agent.name.padEnd(12)} ${label}${pathStr}`);
2550
2931
  }
2551
2932
  console.log("");
@@ -2553,92 +2934,92 @@ var statusCommand = () => {
2553
2934
 
2554
2935
  // src/commands/repo/pull-command.ts
2555
2936
  import { spawnSync as spawnSync4 } from "child_process";
2556
- import chalk17 from "chalk";
2937
+ import chalk18 from "chalk";
2557
2938
  var repoPullCommand = () => {
2558
2939
  const repoPath = configManager.repo_path;
2559
2940
  if (repoPath == null) {
2560
- console.error(chalk17.red("\u274C No repo installed."));
2561
- console.log(chalk17.yellow("Run: set-prompt install <git-url>"));
2941
+ console.error(chalk18.red("\u274C No repo installed."));
2942
+ console.log(chalk18.yellow("Run: set-prompt install <git-url>"));
2562
2943
  return;
2563
2944
  }
2564
2945
  if (configManager.remote_url == null) {
2565
- console.error(chalk17.red("\u274C No remote URL registered. Cannot pull."));
2946
+ console.error(chalk18.red("\u274C No remote URL registered. Cannot pull."));
2566
2947
  return;
2567
2948
  }
2568
- console.log(chalk17.green("\nPulling prompt repo..."));
2569
- console.log(chalk17.dim(repoPath));
2949
+ console.log(chalk18.green("\nPulling prompt repo..."));
2950
+ console.log(chalk18.dim(repoPath));
2570
2951
  const fetch = spawnSync4("git", ["fetch"], { cwd: repoPath, stdio: "inherit" });
2571
2952
  if (fetch.status !== 0) {
2572
- console.error(chalk17.red("\u274C git fetch failed."));
2953
+ console.error(chalk18.red("\u274C git fetch failed."));
2573
2954
  return;
2574
2955
  }
2575
2956
  const pull = spawnSync4("git", ["pull"], { cwd: repoPath, stdio: "inherit" });
2576
2957
  if (pull.status !== 0) {
2577
- console.error(chalk17.red("\u274C git pull failed."));
2958
+ console.error(chalk18.red("\u274C git pull failed."));
2578
2959
  return;
2579
2960
  }
2580
- console.log(chalk17.green("\u2705 Repo pulled."));
2961
+ console.log(chalk18.green("\u2705 Repo pulled."));
2581
2962
  };
2582
2963
 
2583
2964
  // src/commands/repo/commit-command.ts
2584
2965
  import { spawnSync as spawnSync5 } from "child_process";
2585
- import chalk18 from "chalk";
2966
+ import chalk19 from "chalk";
2586
2967
  var repoCommitCommand = (options = {}) => {
2587
2968
  const repoPath = configManager.repo_path;
2588
2969
  if (repoPath == null) {
2589
- console.error(chalk18.red("\u274C No repo installed."));
2590
- console.log(chalk18.yellow("Run: set-prompt install <git-url>"));
2970
+ console.error(chalk19.red("\u274C No repo installed."));
2971
+ console.log(chalk19.yellow("Run: set-prompt install <git-url>"));
2591
2972
  return false;
2592
2973
  }
2593
2974
  let message = options.message;
2594
2975
  if (message == null || message.trim() === "") {
2595
2976
  const generated = generateCommitMessage(repoPath);
2596
2977
  if (generated == null) {
2597
- console.error(chalk18.red("\u274C Nothing to commit \u2014 working tree is clean."));
2978
+ console.error(chalk19.red("\u274C Nothing to commit \u2014 working tree is clean."));
2598
2979
  return false;
2599
2980
  }
2600
2981
  message = generated;
2601
2982
  const subject = message.split("\n")[0];
2602
- console.log(chalk18.dim(` (auto-generated: ${subject})`));
2983
+ console.log(chalk19.dim(` (auto-generated: ${subject})`));
2603
2984
  }
2604
- console.log(chalk18.green("\nCommitting prompt repo changes..."));
2605
- console.log(chalk18.dim(repoPath));
2985
+ console.log(chalk19.green("\nCommitting prompt repo changes..."));
2986
+ console.log(chalk19.dim(repoPath));
2606
2987
  const add = spawnSync5("git", ["add", "-A"], { cwd: repoPath, stdio: "inherit" });
2607
2988
  if (add.status !== 0) {
2608
- console.error(chalk18.red("\u274C git add failed."));
2989
+ console.error(chalk19.red("\u274C git add failed."));
2609
2990
  return false;
2610
2991
  }
2611
2992
  const commit = spawnSync5("git", ["commit", "-m", message], { cwd: repoPath, stdio: "inherit" });
2612
2993
  if (commit.status !== 0) {
2613
- console.error(chalk18.red("\u274C git commit failed \u2014 nothing to commit, or commit rejected."));
2994
+ console.error(chalk19.red("\u274C git commit failed \u2014 nothing to commit, or commit rejected."));
2614
2995
  return false;
2615
2996
  }
2616
- console.log(chalk18.green("\u2705 Committed."));
2997
+ console.log(chalk19.green("\u2705 Committed."));
2617
2998
  return true;
2618
2999
  };
2619
3000
 
2620
3001
  // src/commands/repo/push-command.ts
2621
3002
  import { spawnSync as spawnSync6 } from "child_process";
2622
- import chalk19 from "chalk";
3003
+ import chalk20 from "chalk";
2623
3004
  var repoPushCommand = () => {
2624
3005
  const repoPath = configManager.repo_path;
2625
3006
  if (repoPath == null) {
2626
- console.error(chalk19.red("\u274C No repo installed."));
2627
- console.log(chalk19.yellow("Run: set-prompt install <git-url>"));
3007
+ console.error(chalk20.red("\u274C No repo installed."));
3008
+ console.log(chalk20.yellow("Run: set-prompt install <git-url>"));
2628
3009
  return false;
2629
3010
  }
2630
3011
  if (configManager.remote_url == null) {
2631
- console.error(chalk19.red("\u274C No remote URL registered. Cannot push."));
3012
+ console.error(chalk20.red("\u274C No remote URL registered. Cannot push."));
2632
3013
  return false;
2633
3014
  }
2634
- console.log(chalk19.green("\nPushing prompt repo..."));
2635
- console.log(chalk19.dim(repoPath));
3015
+ console.log(chalk20.green("\nPushing prompt repo..."));
3016
+ console.log(chalk20.dim(repoPath));
2636
3017
  const push = spawnSync6("git", ["push"], { cwd: repoPath, stdio: "inherit" });
2637
3018
  if (push.status !== 0) {
2638
- console.error(chalk19.red("\u274C git push failed."));
3019
+ console.error(chalk20.red("\u274C git push failed."));
2639
3020
  return false;
2640
3021
  }
2641
- console.log(chalk19.green("\u2705 Pushed."));
3022
+ console.log(chalk20.green("\u2705 Pushed."));
2642
3023
  return true;
2643
3024
  };
2644
3025
 
@@ -2651,7 +3032,7 @@ var repoSaveCommand = (options = {}) => {
2651
3032
 
2652
3033
  // src/commands/repo/status-command.ts
2653
3034
  import { spawnSync as spawnSync7 } from "child_process";
2654
- import chalk20 from "chalk";
3035
+ import chalk21 from "chalk";
2655
3036
  var parseBranchLine = (line) => {
2656
3037
  const body = line.replace(/^## /, "");
2657
3038
  if (body.startsWith("HEAD ") || body.includes("(no branch)")) {
@@ -2679,27 +3060,27 @@ var parseFileLine = (line) => {
2679
3060
  const arrowIdx = name.indexOf(" -> ");
2680
3061
  if (arrowIdx >= 0) name = name.slice(arrowIdx + 4);
2681
3062
  if (name.startsWith('"') && name.endsWith('"')) name = name.slice(1, -1);
2682
- if (status.includes("?")) return { label: "untracked", color: chalk20.gray, path: name };
2683
- if (status.includes("D")) return { label: "deleted", color: chalk20.red, path: name };
2684
- if (status.includes("R")) return { label: "renamed", color: chalk20.cyan, path: name };
2685
- if (status.includes("A")) return { label: "added", color: chalk20.green, path: name };
2686
- if (status.includes("M")) return { label: "modified", color: chalk20.yellow, path: name };
3063
+ if (status.includes("?")) return { label: "untracked", color: chalk21.gray, path: name };
3064
+ if (status.includes("D")) return { label: "deleted", color: chalk21.red, path: name };
3065
+ if (status.includes("R")) return { label: "renamed", color: chalk21.cyan, path: name };
3066
+ if (status.includes("A")) return { label: "added", color: chalk21.green, path: name };
3067
+ if (status.includes("M")) return { label: "modified", color: chalk21.yellow, path: name };
2687
3068
  return null;
2688
3069
  };
2689
3070
  var formatUpstream = (info) => {
2690
- if (info.branch == null) return chalk20.red("(detached HEAD)");
2691
- if (info.upstream == null) return `${info.branch} ${chalk20.yellow("(no upstream)")}`;
3071
+ if (info.branch == null) return chalk21.red("(detached HEAD)");
3072
+ if (info.upstream == null) return `${info.branch} ${chalk21.yellow("(no upstream)")}`;
2692
3073
  const segs = [];
2693
- if (info.ahead > 0) segs.push(chalk20.green(`ahead ${info.ahead}`));
2694
- if (info.behind > 0) segs.push(chalk20.red(`behind ${info.behind}`));
2695
- const trailing = segs.length > 0 ? ` (${segs.join(", ")})` : chalk20.dim(" (up to date)");
2696
- return `${info.branch} ${chalk20.dim("\u2192")} ${info.upstream}${trailing}`;
3074
+ if (info.ahead > 0) segs.push(chalk21.green(`ahead ${info.ahead}`));
3075
+ if (info.behind > 0) segs.push(chalk21.red(`behind ${info.behind}`));
3076
+ const trailing = segs.length > 0 ? ` (${segs.join(", ")})` : chalk21.dim(" (up to date)");
3077
+ return `${info.branch} ${chalk21.dim("\u2192")} ${info.upstream}${trailing}`;
2697
3078
  };
2698
3079
  var repoStatusCommand = () => {
2699
3080
  const repoPath = configManager.repo_path;
2700
3081
  if (repoPath == null) {
2701
- console.error(chalk20.red("\u274C No repo installed."));
2702
- console.log(chalk20.yellow("Run: set-prompt install <git-url>"));
3082
+ console.error(chalk21.red("\u274C No repo installed."));
3083
+ console.log(chalk21.yellow("Run: set-prompt install <git-url>"));
2703
3084
  return;
2704
3085
  }
2705
3086
  const result = spawnSync7("git", ["status", "--porcelain=v1", "--branch", "--untracked-files=all"], {
@@ -2707,8 +3088,8 @@ var repoStatusCommand = () => {
2707
3088
  encoding: "utf8"
2708
3089
  });
2709
3090
  if (result.status !== 0) {
2710
- console.error(chalk20.red("\u274C git status failed."));
2711
- if (result.stderr) console.error(chalk20.dim(result.stderr));
3091
+ console.error(chalk21.red("\u274C git status failed."));
3092
+ if (result.stderr) console.error(chalk21.dim(result.stderr));
2712
3093
  return;
2713
3094
  }
2714
3095
  const lines = result.stdout.split("\n").filter((l) => l.length > 0);
@@ -2716,14 +3097,14 @@ var repoStatusCommand = () => {
2716
3097
  const fileLines = lines.slice(1);
2717
3098
  const branchInfo = parseBranchLine(branchLine);
2718
3099
  const changes = fileLines.map(parseFileLine).filter((f) => f !== null);
2719
- console.log(`${chalk20.cyan("\u{1F4C2}")} ${chalk20.dim(repoPath)}`);
2720
- console.log(`${chalk20.cyan("\u{1F33F}")} ${formatUpstream(branchInfo)}`);
3100
+ console.log(`${chalk21.cyan("\u{1F4C2}")} ${chalk21.dim(repoPath)}`);
3101
+ console.log(`${chalk21.cyan("\u{1F33F}")} ${formatUpstream(branchInfo)}`);
2721
3102
  console.log("");
2722
3103
  if (changes.length === 0) {
2723
- console.log(chalk20.green("\u2705 Working tree clean"));
3104
+ console.log(chalk21.green("\u2705 Working tree clean"));
2724
3105
  return;
2725
3106
  }
2726
- console.log(chalk20.bold(`\u{1F4DD} Changes (${changes.length}):`));
3107
+ console.log(chalk21.bold(`\u{1F4DD} Changes (${changes.length}):`));
2727
3108
  const labelWidth = Math.max(...changes.map((c) => c.label.length));
2728
3109
  for (const c of changes) {
2729
3110
  const label = c.color(c.label.padEnd(labelWidth));
@@ -2732,12 +3113,12 @@ var repoStatusCommand = () => {
2732
3113
  };
2733
3114
 
2734
3115
  // src/commands/repo/path-command.ts
2735
- import chalk21 from "chalk";
3116
+ import chalk22 from "chalk";
2736
3117
  var repoPathCommand = () => {
2737
3118
  const repoPath = configManager.repo_path;
2738
3119
  if (repoPath == null) {
2739
- console.error(chalk21.red("\u274C No repo installed."));
2740
- console.error(chalk21.yellow("Run: set-prompt install <git-url>"));
3120
+ console.error(chalk22.red("\u274C No repo installed."));
3121
+ console.error(chalk22.yellow("Run: set-prompt install <git-url>"));
2741
3122
  process.exitCode = 1;
2742
3123
  return;
2743
3124
  }
@@ -2746,8 +3127,8 @@ var repoPathCommand = () => {
2746
3127
 
2747
3128
  // src/commands/repo/open-command.ts
2748
3129
  import { spawn } from "child_process";
2749
- import path12 from "path";
2750
- import chalk22 from "chalk";
3130
+ import path13 from "path";
3131
+ import chalk23 from "chalk";
2751
3132
  var resolveVscodeTarget = (repoPath) => {
2752
3133
  if (isOnPath("code") === false) return null;
2753
3134
  return { bin: "code", args: [repoPath] };
@@ -2758,9 +3139,9 @@ var resolveSourcetreeTarget = (repoPath) => {
2758
3139
  }
2759
3140
  if (process.platform === "win32") {
2760
3141
  const exe = firstExistingPath([
2761
- process.env.LOCALAPPDATA && path12.join(process.env.LOCALAPPDATA, "SourceTree", "SourceTree.exe"),
2762
- process.env["ProgramFiles(x86)"] && path12.join(process.env["ProgramFiles(x86)"], "Atlassian", "SourceTree", "SourceTree.exe"),
2763
- process.env.ProgramFiles && path12.join(process.env.ProgramFiles, "Atlassian", "SourceTree", "SourceTree.exe")
3142
+ process.env.LOCALAPPDATA && path13.join(process.env.LOCALAPPDATA, "SourceTree", "SourceTree.exe"),
3143
+ process.env["ProgramFiles(x86)"] && path13.join(process.env["ProgramFiles(x86)"], "Atlassian", "SourceTree", "SourceTree.exe"),
3144
+ process.env.ProgramFiles && path13.join(process.env.ProgramFiles, "Atlassian", "SourceTree", "SourceTree.exe")
2764
3145
  ]);
2765
3146
  if (exe != null) return { bin: exe, args: ["-f", repoPath] };
2766
3147
  }
@@ -2777,22 +3158,22 @@ var sourcetreeInstallHint = () => {
2777
3158
  return "Sourcetree is not available on Linux \u2014 try a native Git GUI instead";
2778
3159
  };
2779
3160
  var runLaunch = (label, target) => {
2780
- console.log(chalk22.green(`Opening in ${label}: ${chalk22.dim(target.args[target.args.length - 1])}`));
3161
+ console.log(chalk23.green(`Opening in ${label}: ${chalk23.dim(target.args[target.args.length - 1])}`));
2781
3162
  const child = spawn(target.bin, target.args, { stdio: "ignore", detached: true, shell: true });
2782
3163
  child.unref();
2783
3164
  };
2784
3165
  var repoOpenCommand = (options = {}) => {
2785
3166
  const repoPath = configManager.repo_path;
2786
3167
  if (repoPath == null) {
2787
- console.error(chalk22.red("\u274C No repo installed."));
2788
- console.log(chalk22.yellow("Run: set-prompt install <git-url>"));
3168
+ console.error(chalk23.red("\u274C No repo installed."));
3169
+ console.log(chalk23.yellow("Run: set-prompt install <git-url>"));
2789
3170
  return;
2790
3171
  }
2791
3172
  if (options.code === true) {
2792
3173
  const target = resolveVscodeTarget(repoPath);
2793
3174
  if (target == null) {
2794
- console.error(chalk22.red("\u274C VSCode CLI (`code`) not found on PATH."));
2795
- console.log(chalk22.dim(` ${VSCODE_INSTALL_HINT}`));
3175
+ console.error(chalk23.red("\u274C VSCode CLI (`code`) not found on PATH."));
3176
+ console.log(chalk23.dim(` ${VSCODE_INSTALL_HINT}`));
2796
3177
  return;
2797
3178
  }
2798
3179
  runLaunch("VSCode", target);
@@ -2801,8 +3182,8 @@ var repoOpenCommand = (options = {}) => {
2801
3182
  if (options.stree === true) {
2802
3183
  const target = resolveSourcetreeTarget(repoPath);
2803
3184
  if (target == null) {
2804
- console.error(chalk22.red("\u274C Sourcetree not found."));
2805
- console.log(chalk22.dim(` ${sourcetreeInstallHint()}`));
3185
+ console.error(chalk23.red("\u274C Sourcetree not found."));
3186
+ console.log(chalk23.dim(` ${sourcetreeInstallHint()}`));
2806
3187
  return;
2807
3188
  }
2808
3189
  runLaunch("Sourcetree", target);
@@ -2810,46 +3191,46 @@ var repoOpenCommand = (options = {}) => {
2810
3191
  }
2811
3192
  const platform = process.platform;
2812
3193
  const opener = platform === "win32" ? "explorer" : platform === "darwin" ? "open" : "xdg-open";
2813
- console.log(chalk22.green(`Opening: ${chalk22.dim(repoPath)}`));
3194
+ console.log(chalk23.green(`Opening: ${chalk23.dim(repoPath)}`));
2814
3195
  const child = spawn(opener, [repoPath], { stdio: "ignore", detached: true });
2815
3196
  child.on("error", (ex) => {
2816
- console.error(chalk22.red(`\u274C Failed to open: ${ex.message}`));
3197
+ console.error(chalk23.red(`\u274C Failed to open: ${ex.message}`));
2817
3198
  });
2818
3199
  child.unref();
2819
3200
  };
2820
3201
 
2821
3202
  // src/index.ts
2822
3203
  process.on("SIGINT", () => {
2823
- console.log(chalk23.yellow("\nCancelled."));
3204
+ console.log(chalk24.yellow("\nCancelled."));
2824
3205
  process.exit(0);
2825
3206
  });
2826
3207
  process.on("unhandledRejection", (reason) => {
2827
3208
  if (reason instanceof Error && reason.name === "ExitPromptError") {
2828
- console.log(chalk23.yellow("\nCancelled."));
3209
+ console.log(chalk24.yellow("\nCancelled."));
2829
3210
  process.exit(0);
2830
3211
  }
2831
3212
  throw reason;
2832
3213
  });
2833
- var __dirname = path13.dirname(fileURLToPath(import.meta.url));
2834
- var pkg = JSON.parse(fs14.readFileSync(path13.join(__dirname, "../package.json"), "utf-8"));
3214
+ var __dirname = path14.dirname(fileURLToPath(import.meta.url));
3215
+ var pkg = JSON.parse(fs15.readFileSync(path14.join(__dirname, "../package.json"), "utf-8"));
2835
3216
  configManager.init();
2836
3217
  var program = new Command();
2837
- var banner = chalk23.cyan(figlet.textSync("Set-Prompt", { horizontalLayout: "full" }));
3218
+ var banner = chalk24.cyan(figlet.textSync("Set-Prompt", { horizontalLayout: "full" }));
2838
3219
  program.name("set-prompt").description(pkg.description).version(pkg.version).addHelpText("beforeAll", ({ command }) => command === program ? banner + "\n" : "");
2839
- program.command("install").description(`\u{1F4E6} Clone a ${chalk23.cyan("git repo")} into ${chalk23.dim("~/.set-prompt/repo/")} and register it as your prompt source`).argument("<url>", "remote git URL").action(async (source) => {
3220
+ program.command("install").description(`\u{1F4E6} Clone a ${chalk24.cyan("git repo")} into ${chalk24.dim("~/.set-prompt/repo/")} and register it as your prompt source`).argument("<url>", "remote git URL").action(async (source) => {
2840
3221
  await installCommand(source);
2841
3222
  });
2842
- program.command("link").description(`\u{1F517} Symlink your prompt repo into an ${chalk23.cyan("AI agent")} plugin dir ${chalk23.dim("(claudecode | roocode | openclaw | codex | antigravity)")}`).argument("[agent]", `target agent ${chalk23.dim("(omit for interactive selection)")}`).action(async (agent) => {
3223
+ program.command("link").description(`\u{1F517} Symlink your prompt repo into an ${chalk24.cyan("AI agent")} plugin dir ${chalk24.dim("(claudecode | roocode | openclaw | codex | antigravity | cursor | opencode | geminicli | hermes)")}`).argument("[agent]", `target agent ${chalk24.dim("(omit for interactive selection)")}`).action(async (agent) => {
2843
3224
  await linkCommand(agent);
2844
3225
  });
2845
- program.command("scaffold").description(`\u{1F6E0}\uFE0F Verify and create ${chalk23.cyan("required directories")} in a prompt repo ${chalk23.dim("(-f to force overwrite)")}`).argument("[path]", `path to repo ${chalk23.dim("(defaults to installed source)")}`).action(async (localPath) => {
3226
+ program.command("scaffold").description(`\u{1F6E0}\uFE0F Verify and create ${chalk24.cyan("required directories")} in a prompt repo ${chalk24.dim("(-f to force overwrite)")}`).argument("[path]", `path to repo ${chalk24.dim("(defaults to installed source)")}`).action(async (localPath) => {
2846
3227
  await scaffoldCommand(localPath);
2847
3228
  });
2848
- program.command("status").description(`\u{1F4CB} Show registered ${chalk23.cyan("repo")} and which ${chalk23.cyan("agents")} are linked`).action(() => {
3229
+ program.command("status").description(`\u{1F4CB} Show registered ${chalk24.cyan("repo")} and which ${chalk24.cyan("agents")} are linked`).action(() => {
2849
3230
  statusCommand();
2850
3231
  });
2851
- var repo = program.command("repo").description(`\u{1F5C2}\uFE0F Manage the installed prompt repo ${chalk23.dim("(status | pull | commit | push | save | path | open)")}`);
2852
- repo.command("status").description(`\u{1F4CB} Show VCS status of the repo ${chalk23.dim("(branch, ahead/behind, changed files)")}`).addHelpText("after", `
3232
+ var repo = program.command("repo").description(`\u{1F5C2}\uFE0F Manage the installed prompt repo ${chalk24.dim("(status | pull/update | commit | push | save | path | open)")}`);
3233
+ repo.command("status").description(`\u{1F4CB} Show VCS status of the repo ${chalk24.dim("(branch, ahead/behind, changed files)")}`).addHelpText("after", `
2853
3234
  Example output:
2854
3235
  \u{1F4C2} ~/.set-prompt/repo
2855
3236
  \u{1F33F} main \u2192 origin/main (ahead 2)
@@ -2860,47 +3241,47 @@ Example output:
2860
3241
  `).action(() => {
2861
3242
  repoStatusCommand();
2862
3243
  });
2863
- repo.command("pull").description(`\u{1F504} Fetch and pull the latest changes from the ${chalk23.cyan("remote repo")}`).action(() => {
3244
+ repo.command("pull").alias("update").description(`\u{1F504} Fetch and pull the latest changes from the ${chalk24.cyan("remote repo")}`).action(() => {
2864
3245
  repoPullCommand();
2865
3246
  });
2866
- repo.command("commit").description(`\u{1F4DD} Stage all changes and commit ${chalk23.dim("(auto-generates message if -m omitted; does not push)")}`).option("-m, --message <msg>", "commit message (auto-generated from changed files if omitted)").addHelpText("after", `
3247
+ repo.command("commit").description(`\u{1F4DD} Stage all changes and commit ${chalk24.dim("(auto-generates message if -m omitted; does not push)")}`).option("-m, --message <msg>", "commit message (auto-generated from changed files if omitted)").addHelpText("after", `
2867
3248
  Examples:
2868
3249
  $ sppt repo commit -m "edit dbml skill"
2869
- $ sppt repo commit ${chalk23.dim('# auto-generates "update N files" + file list')}
3250
+ $ sppt repo commit ${chalk24.dim('# auto-generates "update N files" + file list')}
2870
3251
  `).action((opts) => {
2871
3252
  repoCommitCommand({ message: opts.message });
2872
3253
  });
2873
3254
  repo.command("push").description(`\u2B06\uFE0F Push local commits to the remote`).action(() => {
2874
3255
  repoPushCommand();
2875
3256
  });
2876
- repo.command("save").description(`\u{1F4BE} Stage + commit + push in one step ${chalk23.dim("(macro for commit \u2192 push; auto-generates message if -m omitted)")}`).option("-m, --message <msg>", "commit message (auto-generated from changed files if omitted)").addHelpText("after", `
3257
+ repo.command("save").description(`\u{1F4BE} Stage + commit + push in one step ${chalk24.dim("(macro for commit \u2192 push; auto-generates message if -m omitted)")}`).option("-m, --message <msg>", "commit message (auto-generated from changed files if omitted)").addHelpText("after", `
2877
3258
  Examples:
2878
3259
  $ sppt repo save -m "edit dbml skill"
2879
- $ sppt repo save ${chalk23.dim("# auto-generates message and pushes")}
3260
+ $ sppt repo save ${chalk24.dim("# auto-generates message and pushes")}
2880
3261
 
2881
3262
  Equivalent to: sppt repo commit && sppt repo push
2882
3263
  `).action((opts) => {
2883
3264
  repoSaveCommand({ message: opts.message });
2884
3265
  });
2885
- repo.command("path").description(`\u{1F4CD} Print the repo path to stdout ${chalk23.dim("(e.g. cd $(sppt repo path))")}`).addHelpText("after", `
3266
+ repo.command("path").description(`\u{1F4CD} Print the repo path to stdout ${chalk24.dim("(e.g. cd $(sppt repo path))")}`).addHelpText("after", `
2886
3267
  Examples:
2887
3268
  $ sppt repo path
2888
- ${chalk23.dim("/Users/me/.set-prompt/repo")}
3269
+ ${chalk24.dim("/Users/me/.set-prompt/repo")}
2889
3270
 
2890
- $ cd "$(sppt repo path)" ${chalk23.dim("# jump into the repo")}
2891
- $ code "$(sppt repo path)" ${chalk23.dim("# open in VSCode")}
3271
+ $ cd "$(sppt repo path)" ${chalk24.dim("# jump into the repo")}
3272
+ $ code "$(sppt repo path)" ${chalk24.dim("# open in VSCode")}
2892
3273
  `).action(() => {
2893
3274
  repoPathCommand();
2894
3275
  });
2895
- repo.command("open").description(`\u{1F4C2} Open the repo in the OS file manager ${chalk23.dim("(--code: VSCode, --stree: Sourcetree)")}`).option("--code", "open with VSCode (`code` CLI)").option("--stree", "open with Sourcetree (`stree` CLI)").addHelpText("after", `
3276
+ repo.command("open").description(`\u{1F4C2} Open the repo in the OS file manager ${chalk24.dim("(--code: VSCode, --stree: Sourcetree)")}`).option("--code", "open with VSCode (`code` CLI)").option("--stree", "open with Sourcetree (`stree` CLI)").addHelpText("after", `
2896
3277
  Examples:
2897
- $ sppt repo open ${chalk23.dim("# Explorer / Finder / xdg-open")}
2898
- $ sppt repo open --code ${chalk23.dim("# open in VSCode")}
2899
- $ sppt repo open --stree ${chalk23.dim("# open in Sourcetree")}
3278
+ $ sppt repo open ${chalk24.dim("# Explorer / Finder / xdg-open")}
3279
+ $ sppt repo open --code ${chalk24.dim("# open in VSCode")}
3280
+ $ sppt repo open --stree ${chalk24.dim("# open in Sourcetree")}
2900
3281
  `).action((opts) => {
2901
3282
  repoOpenCommand({ code: opts.code, stree: opts.stree });
2902
3283
  });
2903
- program.command("uninstall").description(`\u{1F5D1}\uFE0F Remove all set-prompt data ${chalk23.dim("(~/.set-prompt/, plugin dirs, settings entries)")}`).action(async () => {
3284
+ program.command("uninstall").description(`\u{1F5D1}\uFE0F Remove all set-prompt data ${chalk24.dim("(~/.set-prompt/, plugin dirs, settings entries)")}`).action(async () => {
2904
3285
  await uninstallCommand();
2905
3286
  });
2906
3287
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "set-prompt",
3
- "version": "0.7.0",
4
- "description": "Sync your prompt library across AI coding tools from a single git repo",
3
+ "version": "0.8.0",
4
+ "description": "One repo. Every AI coding tool. Always in sync.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "set-prompt": "./dist/index.js",
@@ -14,6 +14,7 @@
14
14
  "scripts": {
15
15
  "start:dev": "tsx src/index.ts",
16
16
  "build": "rimraf dist && tsup",
17
+ "publish": "npm run build && npm publish",
17
18
  "test": "vitest --reporter=verbose"
18
19
  },
19
20
  "keywords": [