@sellable/install 0.1.309 → 0.1.310

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
@@ -119,6 +119,7 @@ It creates:
119
119
  /srv/hermes/profiles/acme/sellable/config.json
120
120
  /srv/hermes/profiles/acme/sellable/configs/
121
121
  /srv/hermes/profiles/acme/skills/sellable/
122
+ /srv/hermes/bin/entrypoint-acme.sh
122
123
  /srv/hermes/profiles/acme/.env # only when Slack token inputs are supplied
123
124
  ```
124
125
 
@@ -129,6 +130,19 @@ For profile paths under a gateway root such as
129
130
  Hermes Desktop command autocomplete scans the gateway/root skill config, not
130
131
  only the selected profile's local skill tree.
131
132
 
133
+ Bootstrap also writes `/srv/hermes/bin/entrypoint-acme.sh`. Point the Hermes
134
+ Docker service at that script when this profile should be the Desktop/dashboard
135
+ default:
136
+
137
+ ```yaml
138
+ entrypoint: ["/opt/data/bin/entrypoint-acme.sh"]
139
+ ```
140
+
141
+ The wrapper starts `hermes gateway run` when needed, then intentionally runs
142
+ `hermes -p acme dashboard --isolated`. Without `--isolated`, Hermes OS chat can
143
+ see `/sellable-*` commands while Hermes Desktop autocomplete misses them because
144
+ the dashboard backend is not scoped to the selected profile.
145
+
132
146
  If no token is supplied, bootstrap still creates a pending
133
147
  `sellable/config.json` so the profile path is concrete from the first MCP
134
148
  launch. Finish auth with:
@@ -3676,6 +3676,74 @@ function hermesGatewayConfigPath(opts = {}) {
3676
3676
  return join(root, "config.yaml");
3677
3677
  }
3678
3678
 
3679
+ function hermesDashboardEntrypointPath(opts = {}, profile = "") {
3680
+ const root = hermesGatewayRoot(opts);
3681
+ if (!root) return null;
3682
+ const safeProfile = String(profile || basenameSafe(hermesHome(opts))).replace(
3683
+ /[^A-Za-z0-9_.-]/g,
3684
+ "_"
3685
+ );
3686
+ return join(root, "bin", `entrypoint-${safeProfile}.sh`);
3687
+ }
3688
+
3689
+ function hermesDashboardCommand(profile) {
3690
+ return `hermes -p ${profile} dashboard --isolated --host 0.0.0.0 --port 4860 --no-open --skip-build`;
3691
+ }
3692
+
3693
+ function hermesDashboardEntrypointContent(opts, profile) {
3694
+ const quotedProfile = shellQuote(profile);
3695
+ const quotedProfileRoot = shellQuote(hermesHome(opts));
3696
+ const gatewayRoot = hermesGatewayRoot(opts);
3697
+ const quotedGatewayRoot = shellQuote(gatewayRoot || dirname(hermesHome(opts)));
3698
+ const quotedDashboardLog = shellQuote(join(hermesHome(opts), "logs", "dashboard.log"));
3699
+ return `#!/usr/bin/env bash
3700
+ set -euo pipefail
3701
+
3702
+ # This file is generated by sellable hermes profile bootstrap.
3703
+ # It preserves the Hostinger image startup shape, but launches the dashboard in
3704
+ # an isolated Sellable profile. Without --isolated, Hermes OS chat can see
3705
+ # profile skills while Desktop slash autocomplete stays on the default profile.
3706
+ profile=${quotedProfile}
3707
+ profile_root=${quotedProfileRoot}
3708
+ gateway_root=${quotedGatewayRoot}
3709
+ dashboard_log=${quotedDashboardLog}
3710
+ dashboard_port="\${HERMES_DASHBOARD_PORT:-4860}"
3711
+ run_as=()
3712
+ if command -v gosu >/dev/null 2>&1; then
3713
+ run_as=(gosu hermes)
3714
+ fi
3715
+
3716
+ chown -R hermes:hermes "$gateway_root" 2>/dev/null || true
3717
+ mkdir -p "$gateway_root/logs" "$profile_root/logs"
3718
+ chown -R hermes:hermes "$gateway_root/logs" "$profile_root/logs" 2>/dev/null || true
3719
+
3720
+ if [[ -f "$gateway_root/config.yaml" ]]; then
3721
+ if ! pgrep -f "hermes gateway run" >/dev/null 2>&1; then
3722
+ "\${run_as[@]}" nohup hermes gateway run >>"$gateway_root/logs/gateway.log" 2>&1 </dev/null &
3723
+ fi
3724
+ fi
3725
+
3726
+ for v in ADMIN_USERNAME ADMIN_PASSWORD; do
3727
+ if [[ -z "\${!v:-}" ]]; then
3728
+ echo "missing required environment variable $v" >&2
3729
+ exit 1
3730
+ fi
3731
+ done
3732
+
3733
+ export HERMES_DASHBOARD_BASIC_AUTH_USERNAME="$ADMIN_USERNAME"
3734
+ export HERMES_DASHBOARD_BASIC_AUTH_PASSWORD="$ADMIN_PASSWORD"
3735
+
3736
+ exec "\${run_as[@]}" hermes -p "$profile" dashboard --isolated --host 0.0.0.0 --port "$dashboard_port" --no-open --skip-build >>"$dashboard_log" 2>&1
3737
+ `;
3738
+ }
3739
+
3740
+ function writeHermesDashboardEntrypoint(opts, profile) {
3741
+ const path = hermesDashboardEntrypointPath(opts, profile);
3742
+ if (!path) return null;
3743
+ writeFile(path, hermesDashboardEntrypointContent(opts, profile), opts, 0o755);
3744
+ return path;
3745
+ }
3746
+
3679
3747
  function hermesLikelyInstalled() {
3680
3748
  return (
3681
3749
  Boolean(process.env.HERMES_HOME?.trim()) ||
@@ -4054,6 +4122,7 @@ function runHermesProfileBootstrap(argv) {
4054
4122
  sellableConfigsDir,
4055
4123
  skillsRoot: hermesSkillsRoot(opts),
4056
4124
  gatewayConfigPath: hermesGatewayConfigPath(opts),
4125
+ hermesDashboardEntrypointPath: hermesDashboardEntrypointPath(opts, profile),
4057
4126
  },
4058
4127
  auth: {
4059
4128
  apiUrl: opts.apiUrl,
@@ -4073,6 +4142,12 @@ function runHermesProfileBootstrap(argv) {
4073
4142
  .map(([key, value]) => [key, tokenFingerprint(value)])
4074
4143
  ),
4075
4144
  },
4145
+ dashboard: {
4146
+ command: hermesDashboardCommand(profile),
4147
+ composeEntrypoint: hermesDashboardEntrypointPath(opts, profile)
4148
+ ? `entrypoint: ["${hermesDashboardEntrypointPath(opts, profile)}"]`
4149
+ : null,
4150
+ },
4076
4151
  created: [],
4077
4152
  updated: [],
4078
4153
  preserved: [],
@@ -4140,6 +4215,10 @@ function runHermesProfileBootstrap(argv) {
4140
4215
  const hermesConfigExisted = existsSync(hermesConfigPath(opts));
4141
4216
  const gatewayConfigPath = hermesGatewayConfigPath(opts);
4142
4217
  const gatewayConfigExisted = gatewayConfigPath ? existsSync(gatewayConfigPath) : false;
4218
+ const dashboardEntrypointPath = hermesDashboardEntrypointPath(opts, profile);
4219
+ const dashboardEntrypointExisted = dashboardEntrypointPath
4220
+ ? existsSync(dashboardEntrypointPath)
4221
+ : false;
4143
4222
  const skillsExisted = existsSync(hermesSkillsRoot(opts));
4144
4223
  const authExisted = existsSync(sellableConfigPath);
4145
4224
  const configsDirExisted = existsSync(sellableConfigsDir);
@@ -4178,6 +4257,7 @@ function runHermesProfileBootstrap(argv) {
4178
4257
  mkdirSync(sellableConfigsDir, { recursive: true, mode: 0o700 });
4179
4258
  writeHermesProfileSellableConfig(sellableConfigPath, nextAuth, opts);
4180
4259
  installHermes(opts);
4260
+ writeHermesDashboardEntrypoint(opts, profile);
4181
4261
  if (Object.values(slackTokens).some(Boolean)) {
4182
4262
  for (const [key, value] of Object.entries(slackTokens)) {
4183
4263
  if (value) envState.entries.set(key, value);
@@ -4198,6 +4278,15 @@ function runHermesProfileBootstrap(argv) {
4198
4278
  gatewayConfigPath
4199
4279
  );
4200
4280
  }
4281
+ if (dashboardEntrypointPath) {
4282
+ (dashboardEntrypointExisted ? summary.updated : summary.created).push(
4283
+ dashboardEntrypointPath
4284
+ );
4285
+ } else {
4286
+ summary.skipped.push(
4287
+ "Hermes dashboard entrypoint: profile root is not under a gateway profiles directory"
4288
+ );
4289
+ }
4201
4290
  (skillsExisted ? summary.updated : summary.created).push(hermesSkillsRoot(opts));
4202
4291
  if (Object.values(slackTokens).some(Boolean)) {
4203
4292
  (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.309",
3
+ "version": "0.1.310",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {