set-prompt 0.7.1 → 0.8.1

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,52 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.8.1] - 2026-05-02
8
+
9
+ ### Changed
10
+ - **Hermes auto-enable** — `link hermes` now writes `set-prompt` into the existing `~/.hermes/config.yaml` automatically, matching Claude Code / Codex behavior. Previously the file was left untouched and only a manual-merge snippet was printed, which left the plugin detected-but-disabled (`× set-prompt — not enabled in config`).
11
+ - Existing `plugins.enabled` list → `set-prompt` is appended (other entries preserved).
12
+ - `plugins` map exists but no `enabled` key → `enabled: [set-prompt]` is added.
13
+ - `plugins` key missing entirely → `plugins.enabled: [set-prompt]` is added.
14
+ - YAML comments and formatting are preserved (AST-based modification via `yaml` package).
15
+ - Atomic write with timestamped backup + rollback on failure (same pattern as `link claudecode` / `link codex`).
16
+ - **`unlinkHermes` surgical removal** — for user-authored `config.yaml` (no `# Generated by set-prompt` header), only the `- set-prompt` entry is removed; the rest of the file is preserved. Auto-generated configs are still deleted whole.
17
+
18
+ ### Added
19
+ - `yaml` (eemeli/yaml) dependency for AST-based YAML editing.
20
+ - Tests for the new auto-enable branches: `plugins` key absent, `plugins.enabled` absent, comment preservation, and `unlinkHermes` surgical removal (17 cases total in `link-hermes.test.ts`).
21
+
22
+ ---
23
+
24
+ ## [0.8.0] - 2026-05-02
25
+
26
+ ### Added
27
+ - **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
28
+ - `plugin.yaml` — Hermes plugin manifest (`name`, `version`, `description`)
29
+ - `__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()`.
30
+ - 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.
31
+ - 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": {...}}`.
32
+ - `~/.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.
33
+ - `HermesConfigSchema` + `HermesConfig` type + `configManager.hermes` getter/setter + `isHermesEnabled()`
34
+ - `HERMES_DIR`, `HERMES_PLUGIN_DIR`, `HERMES_CONFIG_PATH` constants in `_defs`
35
+ - `linkHermes()` / `unlinkHermes()` in `src/link/hermes.ts`
36
+ - `SET_PROMPT_GUIDE` template: Hermes commands/hooks integration sections (set-prompt's adapter behavior, event whitelist table, payload format, decision-control caveat)
37
+ - 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)
38
+
39
+ ### Changed
40
+ - `status-command.ts` — added missing branches for Cursor / OpenCode / Gemini CLI / Hermes (previously fell through silently after Antigravity)
41
+ - `uninstall-command.ts` — added missing unlink calls for OpenCode / Gemini CLI in addition to Hermes
42
+ - `link-command.ts` description in `index.ts` — agent list extended with `hermes`
43
+ - Test mocks updated across `link-command`, `status-command`, `uninstall-command` to cover all current `is*Enabled()` predicates and `unlink*` exports
44
+
45
+ ### Notes / Caveats
46
+ - **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.
47
+ - **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.
48
+ - **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.
49
+ - **`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.
50
+
51
+ ---
52
+
7
53
  ## [0.7.1] - 2026-04-20
8
54
 
9
55
  ### Changed
package/README.md CHANGED
@@ -94,6 +94,7 @@ set-prompt link antigravity # link Antigravity only
94
94
  set-prompt link cursor # link Cursor only
95
95
  set-prompt link opencode # link OpenCode only
96
96
  set-prompt link geminicli # link Gemini CLI only
97
+ set-prompt link hermes # link Hermes only
97
98
  ```
98
99
 
99
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.
@@ -108,6 +109,7 @@ The interactive mode shows all agents with their current state. **Check to link,
108
109
  | Cursor | dir symlinks into `~/.cursor/` | `skills/`, `agents/`, `commands/`, `hooks/`, `mcp.json` (hardlink) |
109
110
  | OpenCode | dir symlinks into `~/.config/opencode/` | `skills/`, `commands/`, `agents/` |
110
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) |
111
113
 
112
114
  > **Note on Claude Code**: Operates as a plugin. Restart Claude Code after linking for the plugin to be recognized.
113
115
 
@@ -121,6 +123,8 @@ The interactive mode shows all agents with their current state. **Check to link,
121
123
 
122
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.
123
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 writes this entry automatically (creating the file when absent, or appending to the existing list while preserving other entries and comments via AST-based YAML editing with backup/rollback). 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
+
124
128
  ---
125
129
 
126
130
  ### Step 3 — Keep in sync
@@ -143,7 +147,7 @@ set-prompt repo path # print repo location (e.g. cd "$(sppt rep
143
147
  set-prompt repo open # open repo in OS file manager (--code VSCode, --stree Sourcetree)
144
148
  ```
145
149
 
146
- 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.
147
151
 
148
152
  ---
149
153
 
@@ -231,6 +235,12 @@ set-prompt uninstall
231
235
  ├── commands/ → repo/commands (Gemini reads *.toml)
232
236
  ├── agents/ → repo/agents (Gemini reads *.md)
233
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
234
244
  ```
235
245
 
236
246
  ## Warning
@@ -245,6 +255,7 @@ set-prompt uninstall
245
255
  - **Cursor** — replaces directories and `mcp.json` in `~/.cursor/`
246
256
  - **OpenCode** — replaces directories in `~/.config/opencode/`
247
257
  - **Gemini CLI** — replaces directories in `~/.gemini/` (`antigravity/` subtree untouched)
258
+ - **Hermes** — generates `~/.hermes/plugins/set-prompt/{plugin.yaml, __init__.py}` and adds `set-prompt` to `~/.hermes/config.yaml` under `plugins.enabled` (creates the file when absent; appends to the existing list while preserving other entries and comments)
248
259
 
249
260
  Before making any changes, `set-prompt` creates a backup and rolls back automatically on failure. However, you should be aware that:
250
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,330 @@ 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
+ import YAML from "yaml";
2494
+ var PLUGIN_YAML = `name: ${MARKET_NAME}
2495
+ version: "1.0"
2496
+ description: Managed by set-prompt
2497
+ `;
2498
+ var buildInitPy = (repoPath) => {
2499
+ const repoLiteral = JSON.stringify(repoPath);
2500
+ return `"""Auto-generated by set-prompt. Do not edit by hand."""
2501
+ import json
2502
+ import os
2503
+ import subprocess
2504
+ from pathlib import Path
2505
+
2506
+ REPO_DIR = Path(${repoLiteral})
2507
+
2508
+ HERMES_EVENTS = {
2509
+ "pre_tool_call",
2510
+ "post_tool_call",
2511
+ "pre_llm_call",
2512
+ "post_llm_call",
2513
+ "on_session_start",
2514
+ "on_session_end",
2515
+ "on_session_finalize",
2516
+ "on_session_reset",
2517
+ "subagent_stop",
2518
+ "pre_gateway_dispatch",
2519
+ }
2520
+
2521
+
2522
+ def _parse_frontmatter(text):
2523
+ if not text.startswith("---"):
2524
+ return {}, text
2525
+ end = text.find("\\n---", 3)
2526
+ if end == -1:
2527
+ return {}, text
2528
+ meta_block = text[3:end].strip()
2529
+ body = text[end + 4:].lstrip("\\n")
2530
+ meta = {}
2531
+ for line in meta_block.splitlines():
2532
+ if ":" not in line:
2533
+ continue
2534
+ key, _, value = line.partition(":")
2535
+ meta[key.strip()] = value.strip().strip('"').strip("'")
2536
+ return meta, body
2537
+
2538
+
2539
+ def _make_command_handler(ctx, body):
2540
+ def handler(_args=None):
2541
+ ctx.inject_message(body, role="user")
2542
+ return handler
2543
+
2544
+
2545
+ def _make_hook_runner(event, raw_cmd, timeout):
2546
+ def runner(*args, **kwargs):
2547
+ cmd = raw_cmd.replace("\${SET_PROMPT_REPO}", str(REPO_DIR))
2548
+ env = dict(os.environ)
2549
+ env["SET_PROMPT_REPO"] = str(REPO_DIR)
2550
+ payload = json.dumps({
2551
+ "event": event,
2552
+ "args": [repr(a) for a in args],
2553
+ "kwargs": {k: repr(v) for k, v in kwargs.items()},
2554
+ }).encode("utf-8")
2555
+ try:
2556
+ subprocess.run(
2557
+ cmd, shell=True, input=payload, env=env,
2558
+ timeout=timeout if isinstance(timeout, (int, float)) else None,
2559
+ check=False,
2560
+ )
2561
+ except (subprocess.TimeoutExpired, OSError):
2562
+ pass
2563
+ return runner
2564
+
2565
+
2566
+ def _iter_hook_commands(entries):
2567
+ if not isinstance(entries, list):
2568
+ return
2569
+ for entry in entries:
2570
+ if not isinstance(entry, dict):
2571
+ continue
2572
+ sub = entry.get("hooks")
2573
+ if not isinstance(sub, list):
2574
+ continue
2575
+ for h in sub:
2576
+ if not isinstance(h, dict):
2577
+ continue
2578
+ if h.get("type") != "command":
2579
+ continue
2580
+ cmd = h.get("command")
2581
+ if not isinstance(cmd, str) or not cmd:
2582
+ continue
2583
+ yield cmd, h.get("timeout")
2584
+
2585
+
2586
+ def _register_hooks(ctx):
2587
+ hooks_json = REPO_DIR / "hooks" / "hooks.json"
2588
+ if not hooks_json.exists():
2589
+ return
2590
+ try:
2591
+ data = json.loads(hooks_json.read_text(encoding="utf-8"))
2592
+ except (OSError, ValueError):
2593
+ return
2594
+ block = data.get("hooks") if isinstance(data, dict) else None
2595
+ if not isinstance(block, dict):
2596
+ return
2597
+ for event, entries in block.items():
2598
+ if event not in HERMES_EVENTS:
2599
+ continue
2600
+ for cmd, timeout in _iter_hook_commands(entries):
2601
+ ctx.register_hook(event, _make_hook_runner(event, cmd, timeout))
2602
+
2603
+
2604
+ def register(ctx):
2605
+ skills_dir = REPO_DIR / "skills"
2606
+ if skills_dir.exists():
2607
+ for skill_path in sorted(skills_dir.iterdir()):
2608
+ if not skill_path.is_dir():
2609
+ continue
2610
+ if not (skill_path / "SKILL.md").exists():
2611
+ continue
2612
+ ctx.register_skill(skill_path.name, str(skill_path))
2613
+
2614
+ commands_dir = REPO_DIR / "commands"
2615
+ if commands_dir.exists():
2616
+ for cmd_file in sorted(commands_dir.glob("*.md")):
2617
+ try:
2618
+ text = cmd_file.read_text(encoding="utf-8")
2619
+ except OSError:
2620
+ continue
2621
+ meta, body = _parse_frontmatter(text)
2622
+ name = meta.get("name") or cmd_file.stem
2623
+ description = meta.get("description") or ""
2624
+ ctx.register_command(name, _make_command_handler(ctx, body), description)
2625
+
2626
+ _register_hooks(ctx)
2627
+ `;
2628
+ };
2629
+ var FRESH_CONFIG_YAML = `# Generated by set-prompt
2630
+ plugins:
2631
+ enabled:
2632
+ - ${MARKET_NAME}
2633
+ `;
2634
+ var writePluginManifest = (repoPath) => {
2635
+ fs13.writeFileSync(path12.join(HERMES_PLUGIN_DIR, "plugin.yaml"), PLUGIN_YAML, "utf-8");
2636
+ fs13.writeFileSync(path12.join(HERMES_PLUGIN_DIR, "__init__.py"), buildInitPy(repoPath), "utf-8");
2637
+ };
2638
+ var writeWithBackup = (filePath, content) => {
2639
+ let backupPath = null;
2640
+ if (fs13.existsSync(filePath)) {
2641
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2642
+ backupPath = `${filePath}.bak.${timestamp}`;
2643
+ try {
2644
+ fs13.copyFileSync(filePath, backupPath);
2645
+ } catch {
2646
+ backupPath = null;
2647
+ }
2648
+ }
2649
+ try {
2650
+ fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
2651
+ fs13.writeFileSync(filePath, content, "utf-8");
2652
+ } catch (ex) {
2653
+ if (backupPath !== null) {
2654
+ try {
2655
+ fs13.copyFileSync(backupPath, filePath);
2656
+ fs13.unlinkSync(backupPath);
2657
+ console.warn(chalk14.yellow(" \u26A0 Write failed \u2014 rolled back to original."));
2658
+ } catch {
2659
+ console.error(chalk14.red(` \u274C Rollback failed. Backup preserved at: ${backupPath}`));
2660
+ }
2661
+ }
2662
+ throw ex;
2663
+ }
2664
+ if (backupPath !== null) {
2665
+ try {
2666
+ fs13.unlinkSync(backupPath);
2667
+ } catch {
2668
+ }
2669
+ }
2670
+ };
2671
+ var ensureHermesConfigEnabled = () => {
2672
+ if (fs13.existsSync(HERMES_CONFIG_PATH) === false) {
2673
+ fs13.mkdirSync(path12.dirname(HERMES_CONFIG_PATH), { recursive: true });
2674
+ fs13.writeFileSync(HERMES_CONFIG_PATH, FRESH_CONFIG_YAML, "utf-8");
2675
+ console.log(chalk14.green(" \u2713 ") + chalk14.dim(`wrote ${HERMES_CONFIG_PATH}`));
2676
+ return;
2677
+ }
2678
+ const content = fs13.readFileSync(HERMES_CONFIG_PATH, "utf-8");
2679
+ let doc;
2680
+ try {
2681
+ doc = YAML.parseDocument(content);
2682
+ } catch (ex) {
2683
+ console.warn(chalk14.yellow(` \u26A0 Failed to parse Hermes config (${ex.message}) \u2014 leaving file untouched.`));
2684
+ console.log(chalk14.dim(` File: ${HERMES_CONFIG_PATH}`));
2685
+ return;
2686
+ }
2687
+ if (doc.errors.length > 0) {
2688
+ console.warn(chalk14.yellow(` \u26A0 Hermes config has YAML errors \u2014 leaving file untouched.`));
2689
+ console.log(chalk14.dim(` File: ${HERMES_CONFIG_PATH}`));
2690
+ return;
2691
+ }
2692
+ let plugins = doc.get("plugins");
2693
+ if (plugins == null) {
2694
+ doc.set("plugins", doc.createNode({ enabled: [MARKET_NAME] }));
2695
+ writeWithBackup(HERMES_CONFIG_PATH, String(doc));
2696
+ console.log(chalk14.green(" \u2713 ") + chalk14.dim(`added plugins.enabled to ${HERMES_CONFIG_PATH}`));
2697
+ return;
2698
+ }
2699
+ if (!YAML.isMap(plugins)) {
2700
+ console.warn(chalk14.yellow(` \u26A0 "plugins" is not a map \u2014 leaving file untouched.`));
2701
+ console.log(chalk14.dim(` File: ${HERMES_CONFIG_PATH}`));
2702
+ return;
2703
+ }
2704
+ let enabled = plugins.get("enabled");
2705
+ if (enabled == null) {
2706
+ plugins.set("enabled", doc.createNode([MARKET_NAME]));
2707
+ writeWithBackup(HERMES_CONFIG_PATH, String(doc));
2708
+ console.log(chalk14.green(" \u2713 ") + chalk14.dim(`added plugins.enabled to ${HERMES_CONFIG_PATH}`));
2709
+ return;
2710
+ }
2711
+ if (!YAML.isSeq(enabled)) {
2712
+ console.warn(chalk14.yellow(` \u26A0 "plugins.enabled" is not a list \u2014 leaving file untouched.`));
2713
+ console.log(chalk14.dim(` File: ${HERMES_CONFIG_PATH}`));
2714
+ return;
2715
+ }
2716
+ const alreadyListed = enabled.items.some((item) => {
2717
+ const v = YAML.isScalar(item) ? item.value : item;
2718
+ return v === MARKET_NAME;
2719
+ });
2720
+ if (alreadyListed) {
2721
+ console.log(chalk14.dim(` \u2713 ${MARKET_NAME} already listed in ${HERMES_CONFIG_PATH}`));
2722
+ return;
2723
+ }
2724
+ enabled.add(MARKET_NAME);
2725
+ writeWithBackup(HERMES_CONFIG_PATH, String(doc));
2726
+ console.log(chalk14.green(" \u2713 ") + chalk14.dim(`enabled "${MARKET_NAME}" in ${HERMES_CONFIG_PATH}`));
2727
+ };
2728
+ var linkHermes = async () => {
2729
+ const repoPath = resolveRepoPath();
2730
+ if (repoPath == null) {
2731
+ return;
2732
+ }
2733
+ console.log(chalk14.green(`
2734
+ Setting up Hermes plugin...`));
2735
+ console.log(chalk14.dim(HERMES_PLUGIN_DIR));
2736
+ try {
2737
+ fs13.mkdirSync(HERMES_PLUGIN_DIR, { recursive: true });
2738
+ writePluginManifest(repoPath);
2739
+ console.log(chalk14.dim(" \u251C\u2500\u2500 ") + chalk14.bold("plugin.yaml") + chalk14.green(" \u2713"));
2740
+ console.log(chalk14.dim(" \u2514\u2500\u2500 ") + chalk14.bold("__init__.py") + chalk14.dim(` (REPO_DIR \u2192 ${repoPath})`) + chalk14.green(" \u2713"));
2741
+ ensureHermesConfigEnabled();
2742
+ configManager.hermes = { path: HERMES_PLUGIN_DIR };
2743
+ configManager.save();
2744
+ console.log(chalk14.dim(`
2745
+ \u2139 Hermes loads plugins at startup \u2014 restart Hermes to pick up new skills/commands.`));
2746
+ } catch (ex) {
2747
+ console.error(chalk14.red(`\u274C Failed to set up Hermes plugin: ${ex.message}`));
2748
+ }
2749
+ };
2750
+ var unlinkHermes = async (force = false) => {
2751
+ if (!force) {
2752
+ const ok = await confirm11({
2753
+ message: `Remove Hermes plugin dir (${HERMES_PLUGIN_DIR})?`,
2754
+ default: false
2755
+ });
2756
+ if (!ok) {
2757
+ console.log(chalk14.yellow("Cancelled."));
2758
+ return;
2759
+ }
2760
+ }
2761
+ console.log(chalk14.red(`
2762
+ Removing Hermes plugin...`));
2763
+ console.log(chalk14.dim(HERMES_PLUGIN_DIR));
2764
+ if (fs13.existsSync(HERMES_PLUGIN_DIR)) {
2765
+ fs13.rmSync(HERMES_PLUGIN_DIR, { recursive: true, force: true });
2766
+ console.log(chalk14.red(" removed") + chalk14.dim(`: ${HERMES_PLUGIN_DIR}`));
2767
+ }
2768
+ if (fs13.existsSync(HERMES_CONFIG_PATH)) {
2769
+ const content = fs13.readFileSync(HERMES_CONFIG_PATH, "utf-8");
2770
+ if (content.trimStart().startsWith("# Generated by set-prompt")) {
2771
+ fs13.unlinkSync(HERMES_CONFIG_PATH);
2772
+ console.log(chalk14.red(" removed") + chalk14.dim(`: ${HERMES_CONFIG_PATH}`));
2773
+ } else {
2774
+ removeMarketEntryFromConfig();
2775
+ }
2776
+ }
2777
+ configManager.hermes = null;
2778
+ configManager.save();
2779
+ };
2780
+ var removeMarketEntryFromConfig = () => {
2781
+ const content = fs13.readFileSync(HERMES_CONFIG_PATH, "utf-8");
2782
+ let doc;
2783
+ try {
2784
+ doc = YAML.parseDocument(content);
2785
+ } catch (ex) {
2786
+ console.warn(chalk14.yellow(` \u26A0 Failed to parse Hermes config (${ex.message}) \u2014 leaving file untouched.`));
2787
+ return;
2788
+ }
2789
+ if (doc.errors.length > 0) {
2790
+ return;
2791
+ }
2792
+ const plugins = doc.get("plugins");
2793
+ if (!YAML.isMap(plugins)) {
2794
+ return;
2795
+ }
2796
+ const enabled = plugins.get("enabled");
2797
+ if (!YAML.isSeq(enabled)) {
2798
+ return;
2799
+ }
2800
+ const idx = enabled.items.findIndex((item) => {
2801
+ const v = YAML.isScalar(item) ? item.value : item;
2802
+ return v === MARKET_NAME;
2803
+ });
2804
+ if (idx === -1) {
2805
+ return;
2806
+ }
2807
+ enabled.delete(idx);
2808
+ writeWithBackup(HERMES_CONFIG_PATH, String(doc));
2809
+ console.log(chalk14.red(" removed") + chalk14.dim(` "${MARKET_NAME}" from: ${HERMES_CONFIG_PATH}`));
2810
+ };
2811
+
2369
2812
  // src/commands/link-command.ts
2370
2813
  var LINK_MAP = {
2371
2814
  ["claudecode" /* CLAUDECODE */]: linkClaudeCode,
@@ -2375,7 +2818,8 @@ var LINK_MAP = {
2375
2818
  ["antigravity" /* ANTIGRAVITY */]: linkAntigravity,
2376
2819
  ["cursor" /* CURSOR */]: linkCursor,
2377
2820
  ["opencode" /* OPENCODE */]: linkOpencode,
2378
- ["geminicli" /* GEMINICLI */]: linkGeminicli
2821
+ ["geminicli" /* GEMINICLI */]: linkGeminicli,
2822
+ ["hermes" /* HERMES */]: linkHermes
2379
2823
  };
2380
2824
  var UNLINK_MAP = {
2381
2825
  ["claudecode" /* CLAUDECODE */]: unlinkClaudeCode,
@@ -2385,13 +2829,14 @@ var UNLINK_MAP = {
2385
2829
  ["antigravity" /* ANTIGRAVITY */]: unlinkAntigravity,
2386
2830
  ["cursor" /* CURSOR */]: unlinkCursor,
2387
2831
  ["opencode" /* OPENCODE */]: unlinkOpencode,
2388
- ["geminicli" /* GEMINICLI */]: unlinkGeminicli
2832
+ ["geminicli" /* GEMINICLI */]: unlinkGeminicli,
2833
+ ["hermes" /* HERMES */]: unlinkHermes
2389
2834
  };
2390
2835
  var linkCommand = async (tool) => {
2391
2836
  if (tool != null) {
2392
2837
  const known = ALL_AGENTS.some((a) => a.value === tool);
2393
2838
  if (!known) {
2394
- console.log(chalk14.red(`Unknown vendor: ${tool}`));
2839
+ console.log(chalk15.red(`Unknown vendor: ${tool}`));
2395
2840
  process.exit(1);
2396
2841
  }
2397
2842
  await LINK_MAP[tool]();
@@ -2405,12 +2850,13 @@ var linkCommand = async (tool) => {
2405
2850
  ["antigravity" /* ANTIGRAVITY */]: configManager.isAntigravityEnabled(),
2406
2851
  ["cursor" /* CURSOR */]: configManager.isCursorEnabled(),
2407
2852
  ["opencode" /* OPENCODE */]: configManager.isOpencodeEnabled(),
2408
- ["geminicli" /* GEMINICLI */]: configManager.isGeminicliEnabled()
2853
+ ["geminicli" /* GEMINICLI */]: configManager.isGeminicliEnabled(),
2854
+ ["hermes" /* HERMES */]: configManager.isHermesEnabled()
2409
2855
  };
2410
2856
  const selected = await checkbox({
2411
2857
  message: "Which AI agent do you want to integrate?",
2412
2858
  choices: ALL_AGENTS.map((a) => ({
2413
- name: prevLinked[a.value] ? `${a.name} ${chalk14.dim("(applied)")}` : a.name,
2859
+ name: prevLinked[a.value] ? `${a.name} ${chalk15.dim("(applied)")}` : a.name,
2414
2860
  value: a.value,
2415
2861
  checked: prevLinked[a.value]
2416
2862
  })),
@@ -2421,10 +2867,10 @@ var linkCommand = async (tool) => {
2421
2867
  const toUnlink = ALL_AGENTS.filter((a) => prevLinked[a.value] && !selected.includes(a.value));
2422
2868
  console.log();
2423
2869
  if (toLink.length > 0) {
2424
- console.log(chalk14.green(" Link ") + chalk14.dim("\u2192 ") + toLink.map((a) => chalk14.bold(a.name)).join(chalk14.dim(", ")));
2870
+ console.log(chalk15.green(" Link ") + chalk15.dim("\u2192 ") + toLink.map((a) => chalk15.bold(a.name)).join(chalk15.dim(", ")));
2425
2871
  }
2426
2872
  if (toUnlink.length > 0) {
2427
- console.log(chalk14.red(" Unlink ") + chalk14.dim("\u2192 ") + toUnlink.map((a) => chalk14.bold(a.name)).join(chalk14.dim(", ")));
2873
+ console.log(chalk15.red(" Unlink ") + chalk15.dim("\u2192 ") + toUnlink.map((a) => chalk15.bold(a.name)).join(chalk15.dim(", ")));
2428
2874
  }
2429
2875
  console.log();
2430
2876
  if (toLink.length === 0 && toUnlink.length === 0) {
@@ -2443,47 +2889,59 @@ var linkCommand = async (tool) => {
2443
2889
  };
2444
2890
 
2445
2891
  // src/commands/uninstall-command.ts
2446
- import fs13 from "fs";
2447
- import chalk15 from "chalk";
2448
- import { confirm as confirm11 } from "@inquirer/prompts";
2892
+ import fs14 from "fs";
2893
+ import chalk16 from "chalk";
2894
+ import { confirm as confirm12 } from "@inquirer/prompts";
2449
2895
  var uninstallCommand = async () => {
2450
2896
  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));
2897
+ { label: `Config file ${chalk16.dim(CONFIG_PATH)}`, path: CONFIG_PATH },
2898
+ { label: `Home dir ${chalk16.dim(HOME_DIR)}`, path: HOME_DIR }
2899
+ ].filter((t) => fs14.existsSync(t.path));
2454
2900
  const hasClaudeCode = configManager.isClaudeCodeEnabled();
2455
2901
  const hasRooCode = configManager.isRooCodeEnabled();
2456
2902
  const hasOpenclaw = configManager.isOpenclawEnabled();
2457
2903
  const hasAntigravity = configManager.isAntigravityEnabled();
2458
2904
  const hasCodex = configManager.isCodexEnabled();
2459
2905
  const hasCursor = configManager.isCursorEnabled();
2460
- if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity && !hasCodex && !hasCursor) {
2461
- console.log(chalk15.yellow("Nothing to remove."));
2906
+ const hasOpencode = configManager.isOpencodeEnabled();
2907
+ const hasGeminicli = configManager.isGeminicliEnabled();
2908
+ const hasHermes = configManager.isHermesEnabled();
2909
+ if (targets.length === 0 && !hasClaudeCode && !hasRooCode && !hasOpenclaw && !hasAntigravity && !hasCodex && !hasCursor && !hasOpencode && !hasGeminicli && !hasHermes) {
2910
+ console.log(chalk16.yellow("Nothing to remove."));
2462
2911
  return;
2463
2912
  }
2464
- console.log(chalk15.red("\nThe following will be removed:"));
2913
+ console.log(chalk16.red("\nThe following will be removed:"));
2465
2914
  targets.forEach((t) => console.log(` ${t.label}`));
2466
2915
  if (hasClaudeCode) {
2467
- console.log(` Claude Code plugin dir ${chalk15.dim(CLAUDE_CODE_DIR)}`);
2916
+ console.log(` Claude Code plugin dir ${chalk16.dim(CLAUDE_CODE_DIR)}`);
2468
2917
  }
2469
2918
  if (hasRooCode) {
2470
- console.log(` RooCode symlinks ${chalk15.dim("(backup will be restored)")}`);
2919
+ console.log(` RooCode symlinks ${chalk16.dim("(backup will be restored)")}`);
2471
2920
  }
2472
2921
  if (hasOpenclaw) {
2473
- console.log(` OpenClaw symlinks ${chalk15.dim("(backup will be restored)")}`);
2922
+ console.log(` OpenClaw symlinks ${chalk16.dim("(backup will be restored)")}`);
2474
2923
  }
2475
2924
  if (hasAntigravity) {
2476
- console.log(` Antigravity symlinks ${chalk15.dim("(backup will be restored)")}`);
2925
+ console.log(` Antigravity symlinks ${chalk16.dim("(backup will be restored)")}`);
2477
2926
  }
2478
2927
  if (hasCodex) {
2479
- console.log(` Codex symlinks ${chalk15.dim("(backup will be restored)")}`);
2928
+ console.log(` Codex symlinks ${chalk16.dim("(backup will be restored)")}`);
2480
2929
  }
2481
2930
  if (hasCursor) {
2482
- console.log(` Cursor plugin dir ${chalk15.dim("(symlink will be removed)")}`);
2931
+ console.log(` Cursor plugin dir ${chalk16.dim("(symlink will be removed)")}`);
2483
2932
  }
2484
- const ok = await confirm11({ message: "Proceed?", default: false });
2933
+ if (hasOpencode) {
2934
+ console.log(` OpenCode symlinks ${chalk16.dim("(backup will be restored)")}`);
2935
+ }
2936
+ if (hasGeminicli) {
2937
+ console.log(` Gemini CLI symlinks ${chalk16.dim("(backup will be restored)")}`);
2938
+ }
2939
+ if (hasHermes) {
2940
+ console.log(` Hermes plugin dir ${chalk16.dim("(plugin folder will be removed)")}`);
2941
+ }
2942
+ const ok = await confirm12({ message: "Proceed?", default: false });
2485
2943
  if (!ok) {
2486
- console.log(chalk15.yellow("Cancelled."));
2944
+ console.log(chalk16.yellow("Cancelled."));
2487
2945
  return;
2488
2946
  }
2489
2947
  if (hasClaudeCode) {
@@ -2504,27 +2962,36 @@ var uninstallCommand = async () => {
2504
2962
  if (hasCursor) {
2505
2963
  await unlinkCursor(true);
2506
2964
  }
2965
+ if (hasOpencode) {
2966
+ await unlinkOpencode(true);
2967
+ }
2968
+ if (hasGeminicli) {
2969
+ await unlinkGeminicli(true);
2970
+ }
2971
+ if (hasHermes) {
2972
+ await unlinkHermes(true);
2973
+ }
2507
2974
  for (const t of targets) {
2508
- fs13.rmSync(t.path, { recursive: true, force: true });
2509
- console.log(chalk15.dim(` removed: ${t.path}`));
2975
+ fs14.rmSync(t.path, { recursive: true, force: true });
2976
+ console.log(chalk16.dim(` removed: ${t.path}`));
2510
2977
  }
2511
- console.log(chalk15.green("\nUninstalled."));
2978
+ console.log(chalk16.green("\nUninstalled."));
2512
2979
  };
2513
2980
 
2514
2981
  // src/commands/status-command.ts
2515
- import chalk16 from "chalk";
2982
+ import chalk17 from "chalk";
2516
2983
  var statusCommand = () => {
2517
2984
  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>`));
2985
+ console.log(chalk17.yellow("\u274C No repo installed."));
2986
+ console.log(chalk17.dim(` Run: set-prompt install <repo-url>`));
2520
2987
  return;
2521
2988
  }
2522
- console.log(chalk16.bold("\nRepo"));
2523
- console.log(`${TAB}path ${chalk16.cyan(configManager.repo_path)}`);
2989
+ console.log(chalk17.bold("\nRepo"));
2990
+ console.log(`${TAB}path ${chalk17.cyan(configManager.repo_path)}`);
2524
2991
  if (configManager.remote_url != null) {
2525
- console.log(`${TAB}remote ${chalk16.dim(configManager.remote_url)}`);
2992
+ console.log(`${TAB}remote ${chalk17.dim(configManager.remote_url)}`);
2526
2993
  }
2527
- console.log(chalk16.bold("\nLinked agents"));
2994
+ console.log(chalk17.bold("\nLinked agents"));
2528
2995
  for (const agent of ALL_AGENTS) {
2529
2996
  let linked = false;
2530
2997
  let agentPath = null;
@@ -2543,9 +3010,21 @@ var statusCommand = () => {
2543
3010
  } else if (agent.value === "antigravity" /* ANTIGRAVITY */) {
2544
3011
  linked = configManager.isAntigravityEnabled();
2545
3012
  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}`) : "";
3013
+ } else if (agent.value === "cursor" /* CURSOR */) {
3014
+ linked = configManager.isCursorEnabled();
3015
+ agentPath = configManager.cursor?.path;
3016
+ } else if (agent.value === "opencode" /* OPENCODE */) {
3017
+ linked = configManager.isOpencodeEnabled();
3018
+ agentPath = configManager.opencode?.path;
3019
+ } else if (agent.value === "geminicli" /* GEMINICLI */) {
3020
+ linked = configManager.isGeminicliEnabled();
3021
+ agentPath = configManager.geminicli?.path;
3022
+ } else if (agent.value === "hermes" /* HERMES */) {
3023
+ linked = configManager.isHermesEnabled();
3024
+ agentPath = configManager.hermes?.path;
3025
+ }
3026
+ const label = linked ? chalk17.green("linked") : chalk17.dim("not linked");
3027
+ const pathStr = linked && agentPath ? chalk17.dim(` \u2192 ${agentPath}`) : "";
2549
3028
  console.log(`${TAB}${agent.name.padEnd(12)} ${label}${pathStr}`);
2550
3029
  }
2551
3030
  console.log("");
@@ -2553,92 +3032,92 @@ var statusCommand = () => {
2553
3032
 
2554
3033
  // src/commands/repo/pull-command.ts
2555
3034
  import { spawnSync as spawnSync4 } from "child_process";
2556
- import chalk17 from "chalk";
3035
+ import chalk18 from "chalk";
2557
3036
  var repoPullCommand = () => {
2558
3037
  const repoPath = configManager.repo_path;
2559
3038
  if (repoPath == null) {
2560
- console.error(chalk17.red("\u274C No repo installed."));
2561
- console.log(chalk17.yellow("Run: set-prompt install <git-url>"));
3039
+ console.error(chalk18.red("\u274C No repo installed."));
3040
+ console.log(chalk18.yellow("Run: set-prompt install <git-url>"));
2562
3041
  return;
2563
3042
  }
2564
3043
  if (configManager.remote_url == null) {
2565
- console.error(chalk17.red("\u274C No remote URL registered. Cannot pull."));
3044
+ console.error(chalk18.red("\u274C No remote URL registered. Cannot pull."));
2566
3045
  return;
2567
3046
  }
2568
- console.log(chalk17.green("\nPulling prompt repo..."));
2569
- console.log(chalk17.dim(repoPath));
3047
+ console.log(chalk18.green("\nPulling prompt repo..."));
3048
+ console.log(chalk18.dim(repoPath));
2570
3049
  const fetch = spawnSync4("git", ["fetch"], { cwd: repoPath, stdio: "inherit" });
2571
3050
  if (fetch.status !== 0) {
2572
- console.error(chalk17.red("\u274C git fetch failed."));
3051
+ console.error(chalk18.red("\u274C git fetch failed."));
2573
3052
  return;
2574
3053
  }
2575
3054
  const pull = spawnSync4("git", ["pull"], { cwd: repoPath, stdio: "inherit" });
2576
3055
  if (pull.status !== 0) {
2577
- console.error(chalk17.red("\u274C git pull failed."));
3056
+ console.error(chalk18.red("\u274C git pull failed."));
2578
3057
  return;
2579
3058
  }
2580
- console.log(chalk17.green("\u2705 Repo pulled."));
3059
+ console.log(chalk18.green("\u2705 Repo pulled."));
2581
3060
  };
2582
3061
 
2583
3062
  // src/commands/repo/commit-command.ts
2584
3063
  import { spawnSync as spawnSync5 } from "child_process";
2585
- import chalk18 from "chalk";
3064
+ import chalk19 from "chalk";
2586
3065
  var repoCommitCommand = (options = {}) => {
2587
3066
  const repoPath = configManager.repo_path;
2588
3067
  if (repoPath == null) {
2589
- console.error(chalk18.red("\u274C No repo installed."));
2590
- console.log(chalk18.yellow("Run: set-prompt install <git-url>"));
3068
+ console.error(chalk19.red("\u274C No repo installed."));
3069
+ console.log(chalk19.yellow("Run: set-prompt install <git-url>"));
2591
3070
  return false;
2592
3071
  }
2593
3072
  let message = options.message;
2594
3073
  if (message == null || message.trim() === "") {
2595
3074
  const generated = generateCommitMessage(repoPath);
2596
3075
  if (generated == null) {
2597
- console.error(chalk18.red("\u274C Nothing to commit \u2014 working tree is clean."));
3076
+ console.error(chalk19.red("\u274C Nothing to commit \u2014 working tree is clean."));
2598
3077
  return false;
2599
3078
  }
2600
3079
  message = generated;
2601
3080
  const subject = message.split("\n")[0];
2602
- console.log(chalk18.dim(` (auto-generated: ${subject})`));
3081
+ console.log(chalk19.dim(` (auto-generated: ${subject})`));
2603
3082
  }
2604
- console.log(chalk18.green("\nCommitting prompt repo changes..."));
2605
- console.log(chalk18.dim(repoPath));
3083
+ console.log(chalk19.green("\nCommitting prompt repo changes..."));
3084
+ console.log(chalk19.dim(repoPath));
2606
3085
  const add = spawnSync5("git", ["add", "-A"], { cwd: repoPath, stdio: "inherit" });
2607
3086
  if (add.status !== 0) {
2608
- console.error(chalk18.red("\u274C git add failed."));
3087
+ console.error(chalk19.red("\u274C git add failed."));
2609
3088
  return false;
2610
3089
  }
2611
3090
  const commit = spawnSync5("git", ["commit", "-m", message], { cwd: repoPath, stdio: "inherit" });
2612
3091
  if (commit.status !== 0) {
2613
- console.error(chalk18.red("\u274C git commit failed \u2014 nothing to commit, or commit rejected."));
3092
+ console.error(chalk19.red("\u274C git commit failed \u2014 nothing to commit, or commit rejected."));
2614
3093
  return false;
2615
3094
  }
2616
- console.log(chalk18.green("\u2705 Committed."));
3095
+ console.log(chalk19.green("\u2705 Committed."));
2617
3096
  return true;
2618
3097
  };
2619
3098
 
2620
3099
  // src/commands/repo/push-command.ts
2621
3100
  import { spawnSync as spawnSync6 } from "child_process";
2622
- import chalk19 from "chalk";
3101
+ import chalk20 from "chalk";
2623
3102
  var repoPushCommand = () => {
2624
3103
  const repoPath = configManager.repo_path;
2625
3104
  if (repoPath == null) {
2626
- console.error(chalk19.red("\u274C No repo installed."));
2627
- console.log(chalk19.yellow("Run: set-prompt install <git-url>"));
3105
+ console.error(chalk20.red("\u274C No repo installed."));
3106
+ console.log(chalk20.yellow("Run: set-prompt install <git-url>"));
2628
3107
  return false;
2629
3108
  }
2630
3109
  if (configManager.remote_url == null) {
2631
- console.error(chalk19.red("\u274C No remote URL registered. Cannot push."));
3110
+ console.error(chalk20.red("\u274C No remote URL registered. Cannot push."));
2632
3111
  return false;
2633
3112
  }
2634
- console.log(chalk19.green("\nPushing prompt repo..."));
2635
- console.log(chalk19.dim(repoPath));
3113
+ console.log(chalk20.green("\nPushing prompt repo..."));
3114
+ console.log(chalk20.dim(repoPath));
2636
3115
  const push = spawnSync6("git", ["push"], { cwd: repoPath, stdio: "inherit" });
2637
3116
  if (push.status !== 0) {
2638
- console.error(chalk19.red("\u274C git push failed."));
3117
+ console.error(chalk20.red("\u274C git push failed."));
2639
3118
  return false;
2640
3119
  }
2641
- console.log(chalk19.green("\u2705 Pushed."));
3120
+ console.log(chalk20.green("\u2705 Pushed."));
2642
3121
  return true;
2643
3122
  };
2644
3123
 
@@ -2651,7 +3130,7 @@ var repoSaveCommand = (options = {}) => {
2651
3130
 
2652
3131
  // src/commands/repo/status-command.ts
2653
3132
  import { spawnSync as spawnSync7 } from "child_process";
2654
- import chalk20 from "chalk";
3133
+ import chalk21 from "chalk";
2655
3134
  var parseBranchLine = (line) => {
2656
3135
  const body = line.replace(/^## /, "");
2657
3136
  if (body.startsWith("HEAD ") || body.includes("(no branch)")) {
@@ -2679,27 +3158,27 @@ var parseFileLine = (line) => {
2679
3158
  const arrowIdx = name.indexOf(" -> ");
2680
3159
  if (arrowIdx >= 0) name = name.slice(arrowIdx + 4);
2681
3160
  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 };
3161
+ if (status.includes("?")) return { label: "untracked", color: chalk21.gray, path: name };
3162
+ if (status.includes("D")) return { label: "deleted", color: chalk21.red, path: name };
3163
+ if (status.includes("R")) return { label: "renamed", color: chalk21.cyan, path: name };
3164
+ if (status.includes("A")) return { label: "added", color: chalk21.green, path: name };
3165
+ if (status.includes("M")) return { label: "modified", color: chalk21.yellow, path: name };
2687
3166
  return null;
2688
3167
  };
2689
3168
  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)")}`;
3169
+ if (info.branch == null) return chalk21.red("(detached HEAD)");
3170
+ if (info.upstream == null) return `${info.branch} ${chalk21.yellow("(no upstream)")}`;
2692
3171
  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}`;
3172
+ if (info.ahead > 0) segs.push(chalk21.green(`ahead ${info.ahead}`));
3173
+ if (info.behind > 0) segs.push(chalk21.red(`behind ${info.behind}`));
3174
+ const trailing = segs.length > 0 ? ` (${segs.join(", ")})` : chalk21.dim(" (up to date)");
3175
+ return `${info.branch} ${chalk21.dim("\u2192")} ${info.upstream}${trailing}`;
2697
3176
  };
2698
3177
  var repoStatusCommand = () => {
2699
3178
  const repoPath = configManager.repo_path;
2700
3179
  if (repoPath == null) {
2701
- console.error(chalk20.red("\u274C No repo installed."));
2702
- console.log(chalk20.yellow("Run: set-prompt install <git-url>"));
3180
+ console.error(chalk21.red("\u274C No repo installed."));
3181
+ console.log(chalk21.yellow("Run: set-prompt install <git-url>"));
2703
3182
  return;
2704
3183
  }
2705
3184
  const result = spawnSync7("git", ["status", "--porcelain=v1", "--branch", "--untracked-files=all"], {
@@ -2707,8 +3186,8 @@ var repoStatusCommand = () => {
2707
3186
  encoding: "utf8"
2708
3187
  });
2709
3188
  if (result.status !== 0) {
2710
- console.error(chalk20.red("\u274C git status failed."));
2711
- if (result.stderr) console.error(chalk20.dim(result.stderr));
3189
+ console.error(chalk21.red("\u274C git status failed."));
3190
+ if (result.stderr) console.error(chalk21.dim(result.stderr));
2712
3191
  return;
2713
3192
  }
2714
3193
  const lines = result.stdout.split("\n").filter((l) => l.length > 0);
@@ -2716,14 +3195,14 @@ var repoStatusCommand = () => {
2716
3195
  const fileLines = lines.slice(1);
2717
3196
  const branchInfo = parseBranchLine(branchLine);
2718
3197
  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)}`);
3198
+ console.log(`${chalk21.cyan("\u{1F4C2}")} ${chalk21.dim(repoPath)}`);
3199
+ console.log(`${chalk21.cyan("\u{1F33F}")} ${formatUpstream(branchInfo)}`);
2721
3200
  console.log("");
2722
3201
  if (changes.length === 0) {
2723
- console.log(chalk20.green("\u2705 Working tree clean"));
3202
+ console.log(chalk21.green("\u2705 Working tree clean"));
2724
3203
  return;
2725
3204
  }
2726
- console.log(chalk20.bold(`\u{1F4DD} Changes (${changes.length}):`));
3205
+ console.log(chalk21.bold(`\u{1F4DD} Changes (${changes.length}):`));
2727
3206
  const labelWidth = Math.max(...changes.map((c) => c.label.length));
2728
3207
  for (const c of changes) {
2729
3208
  const label = c.color(c.label.padEnd(labelWidth));
@@ -2732,12 +3211,12 @@ var repoStatusCommand = () => {
2732
3211
  };
2733
3212
 
2734
3213
  // src/commands/repo/path-command.ts
2735
- import chalk21 from "chalk";
3214
+ import chalk22 from "chalk";
2736
3215
  var repoPathCommand = () => {
2737
3216
  const repoPath = configManager.repo_path;
2738
3217
  if (repoPath == null) {
2739
- console.error(chalk21.red("\u274C No repo installed."));
2740
- console.error(chalk21.yellow("Run: set-prompt install <git-url>"));
3218
+ console.error(chalk22.red("\u274C No repo installed."));
3219
+ console.error(chalk22.yellow("Run: set-prompt install <git-url>"));
2741
3220
  process.exitCode = 1;
2742
3221
  return;
2743
3222
  }
@@ -2746,8 +3225,8 @@ var repoPathCommand = () => {
2746
3225
 
2747
3226
  // src/commands/repo/open-command.ts
2748
3227
  import { spawn } from "child_process";
2749
- import path12 from "path";
2750
- import chalk22 from "chalk";
3228
+ import path13 from "path";
3229
+ import chalk23 from "chalk";
2751
3230
  var resolveVscodeTarget = (repoPath) => {
2752
3231
  if (isOnPath("code") === false) return null;
2753
3232
  return { bin: "code", args: [repoPath] };
@@ -2758,9 +3237,9 @@ var resolveSourcetreeTarget = (repoPath) => {
2758
3237
  }
2759
3238
  if (process.platform === "win32") {
2760
3239
  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")
3240
+ process.env.LOCALAPPDATA && path13.join(process.env.LOCALAPPDATA, "SourceTree", "SourceTree.exe"),
3241
+ process.env["ProgramFiles(x86)"] && path13.join(process.env["ProgramFiles(x86)"], "Atlassian", "SourceTree", "SourceTree.exe"),
3242
+ process.env.ProgramFiles && path13.join(process.env.ProgramFiles, "Atlassian", "SourceTree", "SourceTree.exe")
2764
3243
  ]);
2765
3244
  if (exe != null) return { bin: exe, args: ["-f", repoPath] };
2766
3245
  }
@@ -2777,22 +3256,22 @@ var sourcetreeInstallHint = () => {
2777
3256
  return "Sourcetree is not available on Linux \u2014 try a native Git GUI instead";
2778
3257
  };
2779
3258
  var runLaunch = (label, target) => {
2780
- console.log(chalk22.green(`Opening in ${label}: ${chalk22.dim(target.args[target.args.length - 1])}`));
3259
+ console.log(chalk23.green(`Opening in ${label}: ${chalk23.dim(target.args[target.args.length - 1])}`));
2781
3260
  const child = spawn(target.bin, target.args, { stdio: "ignore", detached: true, shell: true });
2782
3261
  child.unref();
2783
3262
  };
2784
3263
  var repoOpenCommand = (options = {}) => {
2785
3264
  const repoPath = configManager.repo_path;
2786
3265
  if (repoPath == null) {
2787
- console.error(chalk22.red("\u274C No repo installed."));
2788
- console.log(chalk22.yellow("Run: set-prompt install <git-url>"));
3266
+ console.error(chalk23.red("\u274C No repo installed."));
3267
+ console.log(chalk23.yellow("Run: set-prompt install <git-url>"));
2789
3268
  return;
2790
3269
  }
2791
3270
  if (options.code === true) {
2792
3271
  const target = resolveVscodeTarget(repoPath);
2793
3272
  if (target == null) {
2794
- console.error(chalk22.red("\u274C VSCode CLI (`code`) not found on PATH."));
2795
- console.log(chalk22.dim(` ${VSCODE_INSTALL_HINT}`));
3273
+ console.error(chalk23.red("\u274C VSCode CLI (`code`) not found on PATH."));
3274
+ console.log(chalk23.dim(` ${VSCODE_INSTALL_HINT}`));
2796
3275
  return;
2797
3276
  }
2798
3277
  runLaunch("VSCode", target);
@@ -2801,8 +3280,8 @@ var repoOpenCommand = (options = {}) => {
2801
3280
  if (options.stree === true) {
2802
3281
  const target = resolveSourcetreeTarget(repoPath);
2803
3282
  if (target == null) {
2804
- console.error(chalk22.red("\u274C Sourcetree not found."));
2805
- console.log(chalk22.dim(` ${sourcetreeInstallHint()}`));
3283
+ console.error(chalk23.red("\u274C Sourcetree not found."));
3284
+ console.log(chalk23.dim(` ${sourcetreeInstallHint()}`));
2806
3285
  return;
2807
3286
  }
2808
3287
  runLaunch("Sourcetree", target);
@@ -2810,46 +3289,46 @@ var repoOpenCommand = (options = {}) => {
2810
3289
  }
2811
3290
  const platform = process.platform;
2812
3291
  const opener = platform === "win32" ? "explorer" : platform === "darwin" ? "open" : "xdg-open";
2813
- console.log(chalk22.green(`Opening: ${chalk22.dim(repoPath)}`));
3292
+ console.log(chalk23.green(`Opening: ${chalk23.dim(repoPath)}`));
2814
3293
  const child = spawn(opener, [repoPath], { stdio: "ignore", detached: true });
2815
3294
  child.on("error", (ex) => {
2816
- console.error(chalk22.red(`\u274C Failed to open: ${ex.message}`));
3295
+ console.error(chalk23.red(`\u274C Failed to open: ${ex.message}`));
2817
3296
  });
2818
3297
  child.unref();
2819
3298
  };
2820
3299
 
2821
3300
  // src/index.ts
2822
3301
  process.on("SIGINT", () => {
2823
- console.log(chalk23.yellow("\nCancelled."));
3302
+ console.log(chalk24.yellow("\nCancelled."));
2824
3303
  process.exit(0);
2825
3304
  });
2826
3305
  process.on("unhandledRejection", (reason) => {
2827
3306
  if (reason instanceof Error && reason.name === "ExitPromptError") {
2828
- console.log(chalk23.yellow("\nCancelled."));
3307
+ console.log(chalk24.yellow("\nCancelled."));
2829
3308
  process.exit(0);
2830
3309
  }
2831
3310
  throw reason;
2832
3311
  });
2833
- var __dirname = path13.dirname(fileURLToPath(import.meta.url));
2834
- var pkg = JSON.parse(fs14.readFileSync(path13.join(__dirname, "../package.json"), "utf-8"));
3312
+ var __dirname = path14.dirname(fileURLToPath(import.meta.url));
3313
+ var pkg = JSON.parse(fs15.readFileSync(path14.join(__dirname, "../package.json"), "utf-8"));
2835
3314
  configManager.init();
2836
3315
  var program = new Command();
2837
- var banner = chalk23.cyan(figlet.textSync("Set-Prompt", { horizontalLayout: "full" }));
3316
+ var banner = chalk24.cyan(figlet.textSync("Set-Prompt", { horizontalLayout: "full" }));
2838
3317
  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) => {
3318
+ 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
3319
  await installCommand(source);
2841
3320
  });
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) => {
3321
+ 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
3322
  await linkCommand(agent);
2844
3323
  });
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) => {
3324
+ 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
3325
  await scaffoldCommand(localPath);
2847
3326
  });
2848
- program.command("status").description(`\u{1F4CB} Show registered ${chalk23.cyan("repo")} and which ${chalk23.cyan("agents")} are linked`).action(() => {
3327
+ program.command("status").description(`\u{1F4CB} Show registered ${chalk24.cyan("repo")} and which ${chalk24.cyan("agents")} are linked`).action(() => {
2849
3328
  statusCommand();
2850
3329
  });
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", `
3330
+ var repo = program.command("repo").description(`\u{1F5C2}\uFE0F Manage the installed prompt repo ${chalk24.dim("(status | pull/update | commit | push | save | path | open)")}`);
3331
+ repo.command("status").description(`\u{1F4CB} Show VCS status of the repo ${chalk24.dim("(branch, ahead/behind, changed files)")}`).addHelpText("after", `
2853
3332
  Example output:
2854
3333
  \u{1F4C2} ~/.set-prompt/repo
2855
3334
  \u{1F33F} main \u2192 origin/main (ahead 2)
@@ -2860,47 +3339,47 @@ Example output:
2860
3339
  `).action(() => {
2861
3340
  repoStatusCommand();
2862
3341
  });
2863
- repo.command("pull").description(`\u{1F504} Fetch and pull the latest changes from the ${chalk23.cyan("remote repo")}`).action(() => {
3342
+ repo.command("pull").alias("update").description(`\u{1F504} Fetch and pull the latest changes from the ${chalk24.cyan("remote repo")}`).action(() => {
2864
3343
  repoPullCommand();
2865
3344
  });
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", `
3345
+ 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
3346
  Examples:
2868
3347
  $ sppt repo commit -m "edit dbml skill"
2869
- $ sppt repo commit ${chalk23.dim('# auto-generates "update N files" + file list')}
3348
+ $ sppt repo commit ${chalk24.dim('# auto-generates "update N files" + file list')}
2870
3349
  `).action((opts) => {
2871
3350
  repoCommitCommand({ message: opts.message });
2872
3351
  });
2873
3352
  repo.command("push").description(`\u2B06\uFE0F Push local commits to the remote`).action(() => {
2874
3353
  repoPushCommand();
2875
3354
  });
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", `
3355
+ 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
3356
  Examples:
2878
3357
  $ sppt repo save -m "edit dbml skill"
2879
- $ sppt repo save ${chalk23.dim("# auto-generates message and pushes")}
3358
+ $ sppt repo save ${chalk24.dim("# auto-generates message and pushes")}
2880
3359
 
2881
3360
  Equivalent to: sppt repo commit && sppt repo push
2882
3361
  `).action((opts) => {
2883
3362
  repoSaveCommand({ message: opts.message });
2884
3363
  });
2885
- repo.command("path").description(`\u{1F4CD} Print the repo path to stdout ${chalk23.dim("(e.g. cd $(sppt repo path))")}`).addHelpText("after", `
3364
+ repo.command("path").description(`\u{1F4CD} Print the repo path to stdout ${chalk24.dim("(e.g. cd $(sppt repo path))")}`).addHelpText("after", `
2886
3365
  Examples:
2887
3366
  $ sppt repo path
2888
- ${chalk23.dim("/Users/me/.set-prompt/repo")}
3367
+ ${chalk24.dim("/Users/me/.set-prompt/repo")}
2889
3368
 
2890
- $ cd "$(sppt repo path)" ${chalk23.dim("# jump into the repo")}
2891
- $ code "$(sppt repo path)" ${chalk23.dim("# open in VSCode")}
3369
+ $ cd "$(sppt repo path)" ${chalk24.dim("# jump into the repo")}
3370
+ $ code "$(sppt repo path)" ${chalk24.dim("# open in VSCode")}
2892
3371
  `).action(() => {
2893
3372
  repoPathCommand();
2894
3373
  });
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", `
3374
+ 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
3375
  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")}
3376
+ $ sppt repo open ${chalk24.dim("# Explorer / Finder / xdg-open")}
3377
+ $ sppt repo open --code ${chalk24.dim("# open in VSCode")}
3378
+ $ sppt repo open --stree ${chalk24.dim("# open in Sourcetree")}
2900
3379
  `).action((opts) => {
2901
3380
  repoOpenCommand({ code: opts.code, stree: opts.stree });
2902
3381
  });
2903
- program.command("uninstall").description(`\u{1F5D1}\uFE0F Remove all set-prompt data ${chalk23.dim("(~/.set-prompt/, plugin dirs, settings entries)")}`).action(async () => {
3382
+ program.command("uninstall").description(`\u{1F5D1}\uFE0F Remove all set-prompt data ${chalk24.dim("(~/.set-prompt/, plugin dirs, settings entries)")}`).action(async () => {
2904
3383
  await uninstallCommand();
2905
3384
  });
2906
3385
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "set-prompt",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "One repo. Every AI coding tool. Always in sync.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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": [
@@ -51,6 +52,7 @@
51
52
  "ora": "^9.3.0",
52
53
  "smol-toml": "^1.6.0",
53
54
  "typia": "^11.0.3",
55
+ "yaml": "^2.8.4",
54
56
  "zod": "^4.3.6"
55
57
  },
56
58
  "devDependencies": {