@sellable/install 0.1.308 → 0.1.309

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/README.md CHANGED
@@ -122,6 +122,13 @@ It creates:
122
122
  /srv/hermes/profiles/acme/.env # only when Slack token inputs are supplied
123
123
  ```
124
124
 
125
+ For profile paths under a gateway root such as
126
+ `/srv/hermes/profiles/acme`, bootstrap also registers
127
+ `/srv/hermes/profiles/acme/skills/sellable` in the gateway root
128
+ `/srv/hermes/config.yaml` as `skills.external_dirs`. Keep this behavior:
129
+ Hermes Desktop command autocomplete scans the gateway/root skill config, not
130
+ only the selected profile's local skill tree.
131
+
125
132
  If no token is supplied, bootstrap still creates a pending
126
133
  `sellable/config.json` so the profile path is concrete from the first MCP
127
134
  launch. Finish auth with:
@@ -13,7 +13,7 @@ import {
13
13
  writeFileSync,
14
14
  } from "node:fs";
15
15
  import { homedir } from "node:os";
16
- import { dirname, join, relative, resolve } from "node:path";
16
+ import { basename, dirname, join, relative, resolve } from "node:path";
17
17
  import { stdout as output } from "node:process";
18
18
  import { createInterface } from "node:readline/promises";
19
19
  import { fileURLToPath } from "node:url";
@@ -3663,6 +3663,19 @@ function hermesSkillsRoot(opts = {}) {
3663
3663
  return join(hermesHome(opts), "skills", "sellable");
3664
3664
  }
3665
3665
 
3666
+ function hermesGatewayRoot(opts = {}) {
3667
+ const profileRoot = hermesHome(opts);
3668
+ const profilesRoot = dirname(profileRoot);
3669
+ if (basename(profilesRoot) !== "profiles") return null;
3670
+ return dirname(profilesRoot);
3671
+ }
3672
+
3673
+ function hermesGatewayConfigPath(opts = {}) {
3674
+ const root = hermesGatewayRoot(opts);
3675
+ if (!root) return null;
3676
+ return join(root, "config.yaml");
3677
+ }
3678
+
3666
3679
  function hermesLikelyInstalled() {
3667
3680
  return (
3668
3681
  Boolean(process.env.HERMES_HOME?.trim()) ||
@@ -3742,6 +3755,38 @@ function writeHermesMcpServer(opts) {
3742
3755
  return configPath;
3743
3756
  }
3744
3757
 
3758
+ function writeHermesGatewaySkillDirectory(opts) {
3759
+ const configPath = hermesGatewayConfigPath(opts);
3760
+ if (!configPath || configPath === hermesConfigPath(opts)) return null;
3761
+
3762
+ const skillDir = hermesSkillsRoot(opts);
3763
+ const raw = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
3764
+ const config = parseHermesConfig(raw, configPath);
3765
+ // Hermes Desktop slash autocomplete scans the gateway/root HERMES_HOME. A
3766
+ // profile can execute its own skills without this, but Desktop will not show
3767
+ // /sellable-* commands until the root scanner sees the profile skill dir.
3768
+ const skills =
3769
+ config.skills && typeof config.skills === "object" && !Array.isArray(config.skills)
3770
+ ? config.skills
3771
+ : {};
3772
+ const current = Array.isArray(skills.external_dirs)
3773
+ ? skills.external_dirs
3774
+ : skills.external_dirs
3775
+ ? [skills.external_dirs]
3776
+ : [];
3777
+ const externalDirs = current.includes(skillDir) ? current : [...current, skillDir];
3778
+ const nextConfig = {
3779
+ ...config,
3780
+ skills: {
3781
+ ...skills,
3782
+ external_dirs: externalDirs,
3783
+ },
3784
+ };
3785
+
3786
+ writeHermesConfig(configPath, nextConfig, opts);
3787
+ return { configPath, skillDir };
3788
+ }
3789
+
3745
3790
  function installHermesSkills(opts) {
3746
3791
  const root = hermesSkillsRoot(opts);
3747
3792
  for (const skill of hermesSkillDefinitions()) {
@@ -3762,8 +3807,15 @@ function installHermes(opts) {
3762
3807
  try {
3763
3808
  skillsRoot = installHermesSkills(opts);
3764
3809
  const configPath = writeHermesMcpServer(opts);
3810
+ const gateway = writeHermesGatewaySkillDirectory(opts);
3765
3811
  logVerbose(`${C.grey}+ Hermes config updated: ${configPath}${C.reset}`);
3766
- return { installed: true, configPath, skillsRoot };
3812
+ return {
3813
+ installed: true,
3814
+ configPath,
3815
+ skillsRoot,
3816
+ gatewayConfigPath: gateway?.configPath || null,
3817
+ gatewayExternalSkillsDir: gateway?.skillDir || null,
3818
+ };
3767
3819
  } catch (err) {
3768
3820
  if (skillsRoot && !opts.dryRun) {
3769
3821
  rmSync(skillsRoot, { recursive: true, force: true });
@@ -4001,6 +4053,7 @@ function runHermesProfileBootstrap(argv) {
4001
4053
  sellableConfigPath,
4002
4054
  sellableConfigsDir,
4003
4055
  skillsRoot: hermesSkillsRoot(opts),
4056
+ gatewayConfigPath: hermesGatewayConfigPath(opts),
4004
4057
  },
4005
4058
  auth: {
4006
4059
  apiUrl: opts.apiUrl,
@@ -4085,6 +4138,8 @@ function runHermesProfileBootstrap(argv) {
4085
4138
  }
4086
4139
 
4087
4140
  const hermesConfigExisted = existsSync(hermesConfigPath(opts));
4141
+ const gatewayConfigPath = hermesGatewayConfigPath(opts);
4142
+ const gatewayConfigExisted = gatewayConfigPath ? existsSync(gatewayConfigPath) : false;
4088
4143
  const skillsExisted = existsSync(hermesSkillsRoot(opts));
4089
4144
  const authExisted = existsSync(sellableConfigPath);
4090
4145
  const configsDirExisted = existsSync(sellableConfigsDir);
@@ -4138,6 +4193,11 @@ function runHermesProfileBootstrap(argv) {
4138
4193
  (hermesConfigExisted ? summary.updated : summary.created).push(
4139
4194
  hermesConfigPath(opts)
4140
4195
  );
4196
+ if (gatewayConfigPath) {
4197
+ (gatewayConfigExisted ? summary.updated : summary.created).push(
4198
+ gatewayConfigPath
4199
+ );
4200
+ }
4141
4201
  (skillsExisted ? summary.updated : summary.created).push(hermesSkillsRoot(opts));
4142
4202
  if (Object.values(slackTokens).some(Boolean)) {
4143
4203
  (envExisted ? summary.updated : summary.created).push(envPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.308",
3
+ "version": "0.1.309",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {