@gmickel/gno 1.11.0 → 1.12.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.
@@ -739,11 +739,11 @@ Install GNO skill for AI coding assistants.
739
739
  gno skill install [options]
740
740
  ```
741
741
 
742
- | Option | Default | Description |
743
- | -------------- | ------- | -------------------------------------------------------- |
744
- | `-t, --target` | claude | Target: `claude`, `codex`, `opencode`, `openclaw`, `all` |
745
- | `-s, --scope` | project | Scope: `project`, `user` |
746
- | `-f, --force` | false | Overwrite existing |
742
+ | Option | Default | Description |
743
+ | -------------- | ------- | ------------------------------------------------------------------ |
744
+ | `-t, --target` | claude | Target: `claude`, `codex`, `opencode`, `openclaw`, `hermes`, `all` |
745
+ | `-s, --scope` | project | Scope: `project`, `user` |
746
+ | `-f, --force` | false | Overwrite existing |
747
747
 
748
748
  Examples:
749
749
 
@@ -751,6 +751,7 @@ Examples:
751
751
  gno skill install --target claude --scope project
752
752
  gno skill install --target codex --scope user
753
753
  gno skill install --target openclaw --scope user
754
+ gno skill install --target hermes --scope user
754
755
  gno skill install --target all --force # Install to all targets
755
756
  ```
756
757
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Install GNO agent skill to Claude Code or Codex.
2
+ * Install GNO agent skill to supported agent targets.
3
3
  * Atomic install via temp directory + rename.
4
4
  *
5
5
  * @module src/cli/commands/skill/install
@@ -149,7 +149,11 @@ export async function installSkillToTarget(
149
149
 
150
150
  // Remove existing if present (with safety check)
151
151
  if (destExists) {
152
- const validationError = validatePathForDeletion(paths.gnoDir, paths.base);
152
+ const validationError = validatePathForDeletion(
153
+ paths.gnoDir,
154
+ paths.base,
155
+ paths.gnoDir
156
+ );
153
157
  if (validationError) {
154
158
  throw new CliError(
155
159
  "RUNTIME",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Path resolution for skill installation.
3
- * Supports Claude Code, Codex, OpenCode, and OpenClaw targets with project/user scopes.
3
+ * Supports Claude Code, Codex, OpenCode, OpenClaw, and Hermes targets with project/user scopes.
4
4
  *
5
5
  * @module src/cli/commands/skill/paths
6
6
  */
@@ -30,18 +30,27 @@ export const ENV_OPENCODE_SKILLS_DIR = "OPENCODE_SKILLS_DIR";
30
30
  /** Override OpenClaw skills directory */
31
31
  export const ENV_OPENCLAW_SKILLS_DIR = "OPENCLAW_SKILLS_DIR";
32
32
 
33
+ /** Override Hermes skills directory */
34
+ export const ENV_HERMES_SKILLS_DIR = "HERMES_SKILLS_DIR";
35
+
33
36
  // ─────────────────────────────────────────────────────────────────────────────
34
37
  // Types
35
38
  // ─────────────────────────────────────────────────────────────────────────────
36
39
 
37
40
  export type SkillScope = "project" | "user";
38
- export type SkillTarget = "claude" | "codex" | "opencode" | "openclaw";
41
+ export type SkillTarget =
42
+ | "claude"
43
+ | "codex"
44
+ | "opencode"
45
+ | "openclaw"
46
+ | "hermes";
39
47
 
40
48
  export const SKILL_TARGETS: SkillTarget[] = [
41
49
  "claude",
42
50
  "codex",
43
51
  "opencode",
44
52
  "openclaw",
53
+ "hermes",
45
54
  ];
46
55
 
47
56
  export interface SkillPathOptions {
@@ -102,6 +111,12 @@ const TARGET_CONFIGS: Record<SkillTarget, TargetPathConfig> = {
102
111
  skillsSubdir: "skills",
103
112
  envVar: ENV_OPENCLAW_SKILLS_DIR,
104
113
  },
114
+ hermes: {
115
+ projectBase: ".hermes",
116
+ userBase: ".hermes",
117
+ skillsSubdir: "skills",
118
+ envVar: ENV_HERMES_SKILLS_DIR,
119
+ },
105
120
  };
106
121
 
107
122
  // ─────────────────────────────────────────────────────────────────────────────
@@ -201,17 +216,21 @@ function getExpectedSuffixes(): string[] {
201
216
  */
202
217
  export function validatePathForDeletion(
203
218
  destDir: string,
204
- base: string
219
+ base: string,
220
+ expectedDir?: string
205
221
  ): string | null {
206
222
  const normalized = normalize(destDir);
207
223
  const normalizedBase = normalize(base);
224
+ const normalizedExpected = expectedDir ? normalize(expectedDir) : undefined;
208
225
  const expectedSuffixes = getExpectedSuffixes();
209
226
 
210
- // Must end with /skills/gno or /skill/gno (platform-aware)
227
+ // Must be the resolved target directory, or end with a known skill suffix.
228
+ // The exact resolved path covers absolute skills-dir env overrides.
229
+ const matchesExpectedDir = normalizedExpected === normalized;
211
230
  const hasValidSuffix = expectedSuffixes.some((suffix) =>
212
231
  normalized.endsWith(suffix)
213
232
  );
214
- if (!hasValidSuffix) {
233
+ if (!(matchesExpectedDir || hasValidSuffix)) {
215
234
  return `Path does not end with expected suffix (${expectedSuffixes.join(" or ")})`;
216
235
  }
217
236
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Uninstall GNO agent skill from Claude Code or Codex.
2
+ * Uninstall GNO agent skill from supported agent targets.
3
3
  * Includes safety checks before deletion.
4
4
  *
5
5
  * @module src/cli/commands/skill/uninstall
@@ -58,7 +58,11 @@ async function uninstallFromTarget(
58
58
  }
59
59
 
60
60
  // Safety validation
61
- const validationError = validatePathForDeletion(paths.gnoDir, paths.base);
61
+ const validationError = validatePathForDeletion(
62
+ paths.gnoDir,
63
+ paths.base,
64
+ paths.gnoDir
65
+ );
62
66
  if (validationError) {
63
67
  throw new CliError(
64
68
  "RUNTIME",
@@ -1939,7 +1939,7 @@ function wireSkillCommands(program: Command): void {
1939
1939
 
1940
1940
  skillCmd
1941
1941
  .command("install")
1942
- .description("Install GNO skill to Claude Code or Codex")
1942
+ .description("Install GNO skill to supported agents")
1943
1943
  .option(
1944
1944
  "-s, --scope <scope>",
1945
1945
  "installation scope (project, user)",
@@ -1947,7 +1947,7 @@ function wireSkillCommands(program: Command): void {
1947
1947
  )
1948
1948
  .option(
1949
1949
  "-t, --target <target>",
1950
- "target agent (claude, codex, opencode, openclaw, all)",
1950
+ "target agent (claude, codex, opencode, openclaw, hermes, all)",
1951
1951
  "claude"
1952
1952
  )
1953
1953
  .option("-f, --force", "overwrite existing installation")
@@ -1965,18 +1965,26 @@ function wireSkillCommands(program: Command): void {
1965
1965
  }
1966
1966
  // Validate target
1967
1967
  if (
1968
- !["claude", "codex", "opencode", "openclaw", "all"].includes(target)
1968
+ !["claude", "codex", "opencode", "openclaw", "hermes", "all"].includes(
1969
+ target
1970
+ )
1969
1971
  ) {
1970
1972
  throw new CliError(
1971
1973
  "VALIDATION",
1972
- `Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', or 'all'.`
1974
+ `Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', 'hermes', or 'all'.`
1973
1975
  );
1974
1976
  }
1975
1977
 
1976
1978
  const { installSkill } = await import("./commands/skill/install.js");
1977
1979
  await installSkill({
1978
1980
  scope: scope as "project" | "user",
1979
- target: target as "claude" | "codex" | "opencode" | "openclaw" | "all",
1981
+ target: target as
1982
+ | "claude"
1983
+ | "codex"
1984
+ | "opencode"
1985
+ | "openclaw"
1986
+ | "hermes"
1987
+ | "all",
1980
1988
  force: Boolean(cmdOpts.force),
1981
1989
  json: Boolean(cmdOpts.json),
1982
1990
  });
@@ -1992,7 +2000,7 @@ function wireSkillCommands(program: Command): void {
1992
2000
  )
1993
2001
  .option(
1994
2002
  "-t, --target <target>",
1995
- "target agent (claude, codex, opencode, openclaw, all)",
2003
+ "target agent (claude, codex, opencode, openclaw, hermes, all)",
1996
2004
  "claude"
1997
2005
  )
1998
2006
  .option("--json", "JSON output")
@@ -2009,18 +2017,26 @@ function wireSkillCommands(program: Command): void {
2009
2017
  }
2010
2018
  // Validate target
2011
2019
  if (
2012
- !["claude", "codex", "opencode", "openclaw", "all"].includes(target)
2020
+ !["claude", "codex", "opencode", "openclaw", "hermes", "all"].includes(
2021
+ target
2022
+ )
2013
2023
  ) {
2014
2024
  throw new CliError(
2015
2025
  "VALIDATION",
2016
- `Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', or 'all'.`
2026
+ `Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', 'hermes', or 'all'.`
2017
2027
  );
2018
2028
  }
2019
2029
 
2020
2030
  const { uninstallSkill } = await import("./commands/skill/uninstall.js");
2021
2031
  await uninstallSkill({
2022
2032
  scope: scope as "project" | "user",
2023
- target: target as "claude" | "codex" | "opencode" | "openclaw" | "all",
2033
+ target: target as
2034
+ | "claude"
2035
+ | "codex"
2036
+ | "opencode"
2037
+ | "openclaw"
2038
+ | "hermes"
2039
+ | "all",
2024
2040
  json: Boolean(cmdOpts.json),
2025
2041
  });
2026
2042
  });
@@ -2048,7 +2064,7 @@ function wireSkillCommands(program: Command): void {
2048
2064
  )
2049
2065
  .option(
2050
2066
  "-t, --target <target>",
2051
- "filter by target (claude, codex, opencode, openclaw, all)",
2067
+ "filter by target (claude, codex, opencode, openclaw, hermes, all)",
2052
2068
  "all"
2053
2069
  )
2054
2070
  .option("--json", "JSON output")
@@ -2065,18 +2081,26 @@ function wireSkillCommands(program: Command): void {
2065
2081
  }
2066
2082
  // Validate target
2067
2083
  if (
2068
- !["claude", "codex", "opencode", "openclaw", "all"].includes(target)
2084
+ !["claude", "codex", "opencode", "openclaw", "hermes", "all"].includes(
2085
+ target
2086
+ )
2069
2087
  ) {
2070
2088
  throw new CliError(
2071
2089
  "VALIDATION",
2072
- `Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', or 'all'.`
2090
+ `Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', 'hermes', or 'all'.`
2073
2091
  );
2074
2092
  }
2075
2093
 
2076
2094
  const { showPaths } = await import("./commands/skill/paths-cmd.js");
2077
2095
  await showPaths({
2078
2096
  scope: scope as "project" | "user" | "all",
2079
- target: target as "claude" | "codex" | "opencode" | "openclaw" | "all",
2097
+ target: target as
2098
+ | "claude"
2099
+ | "codex"
2100
+ | "opencode"
2101
+ | "openclaw"
2102
+ | "hermes"
2103
+ | "all",
2080
2104
  json: Boolean(cmdOpts.json),
2081
2105
  });
2082
2106
  });
@@ -118,6 +118,18 @@ const CONNECTOR_DEFINITIONS: ConnectorDefinition[] = [
118
118
  "Recommended default for local agent access without manual file edits.",
119
119
  },
120
120
  },
121
+ {
122
+ id: "hermes-skill",
123
+ appName: "Hermes Agent",
124
+ installKind: "skill",
125
+ target: "hermes",
126
+ scope: "user",
127
+ mode: {
128
+ label: "Read/search via skill",
129
+ detail:
130
+ "Recommended default for Hermes Agent. Uses the standard ~/.hermes/skills path.",
131
+ },
132
+ },
121
133
  ] as const;
122
134
 
123
135
  export async function getConnectorStatuses(overrides?: {