opencrater 0.1.3 → 0.1.5

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 +61 -5
  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(
@@ -152,6 +164,9 @@ async function dismissNow() {
152
164
  JSON.stringify({ impressionId: lock.impressionId, at: Date.now() }),
153
165
  );
154
166
  } catch {}
167
+ if (lock.dismissUrl) {
168
+ try { await fetch(lock.dismissUrl, { signal: AbortSignal.timeout(1000) }); } catch {}
169
+ }
155
170
  if (lock.impressionId) {
156
171
  const origin = process.env.OPENCRATER_API_ORIGIN || "https://api.opencrater.to";
157
172
  try {
@@ -162,12 +177,50 @@ async function dismissNow() {
162
177
  try { process.kill(lock.pid, "SIGTERM"); } catch {}
163
178
  }
164
179
  console.log("opencrater: ad dismissed — feedback recorded.");
180
+ console.log(" changed your mind? npx opencrater show");
181
+ }
182
+
183
+ /* Repaint the last sponsor card ("show ad again"). Reads the payload the
184
+ painter saved, resolves THIS terminal's tty, and spawns the local-runtime
185
+ painter detached — exactly how the hook does it. */
186
+ function showAgain() {
187
+ const dir = path.join(os.homedir(), ".config", "opencrater");
188
+ let saved = null;
189
+ try {
190
+ saved = JSON.parse(fs.readFileSync(path.join(dir, "last-ad.json"), "utf8"));
191
+ } catch {
192
+ console.log("opencrater: no recent ad to show.");
193
+ return;
194
+ }
195
+ const painter = path.join(dir, "runtime", "node_modules", "@opencrater", "sdk", "dist", "paint.js");
196
+ if (!fs.existsSync(painter)) {
197
+ console.log("opencrater: hook runtime not installed yet — run: npx opencrater on");
198
+ return;
199
+ }
200
+ const { spawnSync, spawn } = require("node:child_process");
201
+ // our own terminal's device (we are running interactively)
202
+ const tty = spawnSync("tty", { stdio: ["inherit", "pipe", "ignore"], encoding: "utf8" })
203
+ .stdout?.trim();
204
+ const env = { ...process.env, OPENCRATER_PAINT: JSON.stringify(saved.payload) };
205
+ if (tty && tty.startsWith("/dev/")) env.OPENCRATER_TTY = tty;
206
+ const child = spawn(process.execPath, [painter], { detached: true, stdio: "ignore", env });
207
+ child.unref();
208
+ console.log("opencrater: showing the last ad again.");
165
209
  }
166
210
 
167
211
  const cmd = process.argv[2] || "status";
168
212
  if (cmd === "on" || cmd === "enable") {
169
213
  enableIn(CLAUDE_SETTINGS, "claude_code");
170
214
  enableIn(CODEX_HOOKS, "codex");
215
+ // Pre-install the local hook runtime (background): hooks then run via
216
+ // plain node — no npx resolution latency, no cold-cache races.
217
+ try {
218
+ const { spawn } = require("node:child_process");
219
+ spawn("npm", ["install", "--prefix",
220
+ path.join(os.homedir(), ".config", "opencrater", "runtime"),
221
+ "@opencrater/sdk@latest", "--silent", "--no-audit", "--no-fund"],
222
+ { detached: true, stdio: "ignore" }).unref();
223
+ } catch {}
171
224
  console.log("OpenCrater ads enabled for Claude Code + Codex.");
172
225
  console.log("All hooks registered as triggers; cards render only at session edges");
173
226
  console.log("(SessionStart, Stop, SessionEnd, Notification) with a machine-wide frequency cap.");
@@ -180,13 +233,16 @@ if (cmd === "on" || cmd === "enable") {
180
233
  console.log(a || b ? "OpenCrater ads disabled. Publisher hooks (if any) were not touched." : "Nothing to remove — OpenCrater ads were not enabled.");
181
234
  } else if (cmd === "x" || cmd === "dismiss") {
182
235
  dismissNow();
236
+ } else if (cmd === "show" || cmd === "again") {
237
+ showAgain();
183
238
  } else if (cmd === "status") {
184
239
  const cc = statusIn(CLAUDE_SETTINGS);
185
240
  const cx = statusIn(CODEX_HOOKS);
186
241
  console.log("Claude Code:", cc.length ? "enabled on " + cc.join(", ") : "not enabled");
187
242
  console.log("Codex: ", cx.length ? "enabled on " + cx.join(", ") : "not enabled");
188
243
  } else {
189
- console.log("Usage: npx opencrater <on|off|status|x>");
190
- console.log(" x dismiss the sponsor card currently on screen");
244
+ console.log("Usage: npx opencrater <on|off|status|x|show>");
245
+ console.log(" x dismiss the sponsor card currently on screen");
246
+ console.log(" show bring the last dismissed ad back");
191
247
  process.exitCode = 1;
192
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrater",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenCrater — sponsor cards in Claude Code and Codex. Free, one command, opt out anytime.",
5
5
  "keywords": [
6
6
  "opencrater",