opencrater 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/cli.js +59 -4
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -43,9 +43,21 @@ const CODEX_EVENTS = [
43
43
  ];
44
44
  const MARKER = "--package " + PKG + " "; // identifies OUR hook entries only
45
45
 
46
- const cmdFor = (event, host) =>
47
- "npx -y -p @opencrater/sdk opencrater-hook --placement " + event +
48
- " --key " + KEY + " --package " + PKG + " --host " + host;
46
+ // Resilient hook command: prefer the pre-installed local runtime (no npx on
47
+ // the hot path concurrent fires racing a cold npx cache caused
48
+ // "opencrater-hook: command not found" noise in the host); fall back to npx;
49
+ // ALWAYS exit 0 with stderr silenced — a sponsor hook must never surface an
50
+ // error inside someone's session.
51
+ const cmdFor = (event, host) => {
52
+ const args = "--placement " + event + " --key " + KEY +
53
+ " --package " + PKG + " --host " + host;
54
+ const runtime = '"$HOME/.config/opencrater/runtime/node_modules/@opencrater/sdk/dist/hook.js"';
55
+ return (
56
+ "{ if [ -f " + runtime + " ]; then node " + runtime + " " + args +
57
+ "; else npx -y -p @opencrater/sdk opencrater-hook " + args +
58
+ "; fi; } 2>/dev/null || true"
59
+ );
60
+ };
49
61
 
50
62
  const CLAUDE_SETTINGS = path.join(os.homedir(), ".claude", "settings.json");
51
63
  const CODEX_HOOKS = path.join(
@@ -133,10 +145,50 @@ function statusIn(file) {
133
145
  );
134
146
  }
135
147
 
148
+ /* Dismiss the sponsor card currently on screen — no browser needed.
149
+ Writes the painter's local dismiss signal, records the dismissal against
150
+ the API (idempotent), and SIGTERMs the painter so the card clears NOW. */
151
+ async function dismissNow() {
152
+ const dir = path.join(os.homedir(), ".config", "opencrater");
153
+ let lock = null;
154
+ try {
155
+ lock = JSON.parse(fs.readFileSync(path.join(dir, "painter.lock"), "utf8"));
156
+ } catch {
157
+ console.log("opencrater: no sponsor card is on screen.");
158
+ return;
159
+ }
160
+ try {
161
+ fs.mkdirSync(dir, { recursive: true });
162
+ fs.writeFileSync(
163
+ path.join(dir, "dismiss.signal"),
164
+ JSON.stringify({ impressionId: lock.impressionId, at: Date.now() }),
165
+ );
166
+ } catch {}
167
+ if (lock.impressionId) {
168
+ const origin = process.env.OPENCRATER_API_ORIGIN || "https://api.opencrater.to";
169
+ try {
170
+ await fetch(origin + "/x/" + lock.impressionId, { signal: AbortSignal.timeout(2500) });
171
+ } catch {}
172
+ }
173
+ if (lock.pid && lock.pid > 1) {
174
+ try { process.kill(lock.pid, "SIGTERM"); } catch {}
175
+ }
176
+ console.log("opencrater: ad dismissed — feedback recorded.");
177
+ }
178
+
136
179
  const cmd = process.argv[2] || "status";
137
180
  if (cmd === "on" || cmd === "enable") {
138
181
  enableIn(CLAUDE_SETTINGS, "claude_code");
139
182
  enableIn(CODEX_HOOKS, "codex");
183
+ // Pre-install the local hook runtime (background): hooks then run via
184
+ // plain node — no npx resolution latency, no cold-cache races.
185
+ try {
186
+ const { spawn } = require("node:child_process");
187
+ spawn("npm", ["install", "--prefix",
188
+ path.join(os.homedir(), ".config", "opencrater", "runtime"),
189
+ "@opencrater/sdk@latest", "--silent", "--no-audit", "--no-fund"],
190
+ { detached: true, stdio: "ignore" }).unref();
191
+ } catch {}
140
192
  console.log("OpenCrater ads enabled for Claude Code + Codex.");
141
193
  console.log("All hooks registered as triggers; cards render only at session edges");
142
194
  console.log("(SessionStart, Stop, SessionEnd, Notification) with a machine-wide frequency cap.");
@@ -147,12 +199,15 @@ if (cmd === "on" || cmd === "enable") {
147
199
  const a = disableIn(CLAUDE_SETTINGS);
148
200
  const b = disableIn(CODEX_HOOKS);
149
201
  console.log(a || b ? "OpenCrater ads disabled. Publisher hooks (if any) were not touched." : "Nothing to remove — OpenCrater ads were not enabled.");
202
+ } else if (cmd === "x" || cmd === "dismiss") {
203
+ dismissNow();
150
204
  } else if (cmd === "status") {
151
205
  const cc = statusIn(CLAUDE_SETTINGS);
152
206
  const cx = statusIn(CODEX_HOOKS);
153
207
  console.log("Claude Code:", cc.length ? "enabled on " + cc.join(", ") : "not enabled");
154
208
  console.log("Codex: ", cx.length ? "enabled on " + cx.join(", ") : "not enabled");
155
209
  } else {
156
- console.log("Usage: npx opencrater <on|off|status>");
210
+ console.log("Usage: npx opencrater <on|off|status|x>");
211
+ console.log(" x dismiss the sponsor card currently on screen");
157
212
  process.exitCode = 1;
158
213
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrater",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "OpenCrater — sponsor cards in Claude Code and Codex. Free, one command, opt out anytime.",
5
5
  "keywords": [
6
6
  "opencrater",