@hasna/hooks 0.10.6 → 0.10.8

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.
@@ -5,7 +5,13 @@
5
5
  // resolver data root once adopted (HASNA_DATA_HOME set or hooks.db already
6
6
  // present there), then the legacy ~/.hasna/hooks default. Best-effort — the
7
7
  // package must never fail to install because this script cannot run.
8
+ //
9
+ // It also provisions the required Sumi native-safety binding for the installing
10
+ // user (see the second block below). Same best-effort contract.
8
11
  import { existsSync, mkdirSync } from "node:fs";
12
+ import { spawnSync } from "node:child_process";
13
+ import { fileURLToPath } from "node:url";
14
+ import { dirname } from "node:path";
9
15
  // --- Local path resolver -------------------------------------------------
10
16
  // @hasna/paths was deleted (hasna/apps#1535, 2026-09-03); this in-package
11
17
  // implementation preserves the resolver contract (XDG / macOS home layout
@@ -58,7 +64,7 @@ function dataDir(options) {
58
64
  return pathsResolverResolve("data", options);
59
65
  }
60
66
  import { homedir } from "node:os";
61
- import { join, resolve } from "node:path";
67
+ import { join, resolve, sep } from "node:path";
62
68
 
63
69
  const HOME = process.env.HOME || process.env.USERPROFILE || homedir();
64
70
 
@@ -97,3 +103,61 @@ try {
97
103
  } catch {
98
104
  // ignore
99
105
  }
106
+
107
+ /**
108
+ * The bun runtime that must execute the provisioning entry: the package's own
109
+ * bin targets are bun bundles (`#!/usr/bin/env bun`) and `installedNativeSafetyCommand`
110
+ * pins `process.execPath`, so the runtime that runs this decides the binding.
111
+ * Well-known install locations first, then PATH; null when neither exists.
112
+ */
113
+ function resolveBun() {
114
+ const wellKnown = [
115
+ process.env.BUN_INSTALL ? join(process.env.BUN_INSTALL, "bin", "bun") : null,
116
+ join(HOME, ".bun", "bin", "bun"),
117
+ ];
118
+ for (const candidate of wellKnown) {
119
+ if (candidate && existsSync(candidate)) return candidate;
120
+ }
121
+ return spawnSync("bun", ["--version"], { encoding: "utf8" }).status === 0 ? "bun" : null;
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Required Sumi native-safety binding (hasna/apps incidents 798053).
126
+ //
127
+ // Sumi refuses EVERY shell command while
128
+ // `~/.hasna/hooks/native/sumi-trash-guard.json` is absent, and neither a package
129
+ // install nor an upgrade used to create it: a rollout could install this package
130
+ // and still leave every Sumi seat on the station without a shell.
131
+ //
132
+ // The packaged CLI owns the policy (`hooks safety provision`, also applied by
133
+ // `hooks install` and `hooks upgrade`): idempotent, a healthy existing binding is
134
+ // left intact, a stale one is replaced with its predecessor preserved beside it,
135
+ // and an unrecognized one is refused rather than overwritten. This lifecycle
136
+ // surface only reaches installs whose scripts actually run — bun blocks
137
+ // lifecycle scripts for untrusted dependencies and `--ignore-scripts` skips
138
+ // them, which is why the CLI surfaces apply the same policy.
139
+ //
140
+ // Two gates keep a source checkout from touching the operator's home: the
141
+ // package must be an INSTALLED copy (inside a node_modules directory) and its
142
+ // built CLI must exist. Best-effort — install never fails on a native guard.
143
+ try {
144
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
145
+ const cli = join(packageRoot, "bin", "index.js");
146
+ if (packageRoot.includes(`${sep}node_modules${sep}`) && existsSync(cli)) {
147
+ const bun = resolveBun();
148
+ if (!bun) {
149
+ process.stderr.write("[@hasna/hooks] Sumi native-safety binding not provisioned: no bun runtime found; run `hooks safety provision`.\n");
150
+ } else {
151
+ const provisioned = spawnSync(bun, [cli, "safety", "provision", "--target", "sumi"], {
152
+ encoding: "utf8", timeout: 60_000, env: { ...process.env, HOME },
153
+ });
154
+ let result = null;
155
+ try { result = JSON.parse(provisioned.stdout?.trim() ?? ""); } catch { /* reported below */ }
156
+ if (!result?.ok) {
157
+ process.stderr.write(`[@hasna/hooks] Sumi native-safety binding not provisioned (${result?.code ?? "provision_failed"}); run \`hooks safety provision\` on this station before a Sumi seat needs its shell.\n`);
158
+ }
159
+ }
160
+ }
161
+ } catch {
162
+ // ignore
163
+ }