@brozarti/spincome 0.1.4 → 0.1.6

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/dist/animate.js CHANGED
@@ -14,7 +14,7 @@ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇",
14
14
  const COIN = ["◐", "◓", "◑", "◒"];
15
15
  const LOGO = `${BOLD}${GREEN}$${R}${BOLD} spincome${R}`;
16
16
  async function animateWhileLoading(work) {
17
- process.stdout.write(HIDE_CURSOR);
17
+ process.stderr.write(HIDE_CURSOR);
18
18
  let frame = 0;
19
19
  let dots = 0;
20
20
  let done = false;
@@ -26,7 +26,7 @@ async function animateWhileLoading(work) {
26
26
  const coin = GREEN + COIN[frame % COIN.length] + R;
27
27
  dots = (dots + 1) % 4;
28
28
  const dotStr = DIM + ".".repeat(dots).padEnd(3) + R;
29
- process.stdout.write(CLEAR_LINE +
29
+ process.stderr.write(CLEAR_LINE +
30
30
  ` ${spinner} ${LOGO} ${coin} ${DIM}earning${R}${dotStr}`);
31
31
  frame++;
32
32
  }, 80);
@@ -36,9 +36,9 @@ async function animateWhileLoading(work) {
36
36
  finally {
37
37
  done = true;
38
38
  clearInterval(tick);
39
- process.stdout.write(CLEAR_LINE);
40
- process.stdout.write(UP_ONE + CLEAR_LINE);
41
- process.stdout.write(SHOW_CURSOR);
39
+ process.stderr.write(CLEAR_LINE);
40
+ process.stderr.write(UP_ONE + CLEAR_LINE);
41
+ process.stderr.write(SHOW_CURSOR);
42
42
  }
43
43
  return result;
44
44
  }
package/dist/display.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderEarnings = renderEarnings;
3
4
  exports.renderAd = renderAd;
4
5
  const R = "\x1b[0m";
5
6
  const B = "\x1b[1m";
@@ -7,7 +8,6 @@ const DIM = "\x1b[2m";
7
8
  const GREEN = "\x1b[32m";
8
9
  const BRIGHT_GREEN = "\x1b[92m";
9
10
  const CYAN = "\x1b[36m";
10
- const YELLOW = "\x1b[33m";
11
11
  const WHITE = "\x1b[97m";
12
12
  const BG = "\x1b[48;5;234m";
13
13
  const W = 62;
@@ -21,9 +21,15 @@ function box(content, visibleLen) {
21
21
  function ruler() {
22
22
  return `${DIM}${"─".repeat(W)}${R}`;
23
23
  }
24
+ // Compact earnings ticker -- shown every tool call
25
+ function renderEarnings(sessionCents) {
26
+ const sessionDollars = (sessionCents / 100000).toFixed(4);
27
+ return `\n ${B}${GREEN}$${R} ${B}spincome${R} ${DIM}→${R} ${BRIGHT_GREEN}$${sessionDollars}${R} ${DIM}this session${R}\n`;
28
+ }
29
+ // Full ad box -- shown every Nth call
24
30
  function renderAd(ad, earnedCents, sessionCents, context) {
25
- const earnedDollars = (earnedCents / 100).toFixed(4);
26
- const sessionDollars = (sessionCents / 100).toFixed(4);
31
+ const earnedDollars = (earnedCents / 100000).toFixed(4);
32
+ const sessionDollars = (sessionCents / 100000).toFixed(4);
27
33
  const contextTag = context?.fileExt
28
34
  ? ` · ${context.fileExt.toUpperCase()} dev`
29
35
  : context?.toolName
@@ -33,13 +39,13 @@ function renderAd(ad, earnedCents, sessionCents, context) {
33
39
  const headlineLine = `${B}${WHITE}${ad.headline}${R}`;
34
40
  const bodyLine = `${DIM}${ad.body}${R}`;
35
41
  const ctaLine = `${CYAN}${ad.cta}${R} ${DIM}${ad.clickUrl}${R}`;
36
- const earnLine = `${BRIGHT_GREEN}+$${earnedDollars} earned${R} ${DIM}session total: ${GREEN}$${sessionDollars}${R}`;
42
+ const earnLine = `${BRIGHT_GREEN}+$${earnedDollars} earned${R} ${DIM}session: ${GREEN}$${sessionDollars}${R}`;
37
43
  const footerLine = `${DIM}spincome · /disable to opt out${R}`;
38
44
  const sponsorLen = `Sponsored · ${ad.advertiser}${contextTag}`.length;
39
45
  const headlineLen = ad.headline.length;
40
46
  const bodyLen = ad.body.length;
41
47
  const ctaLen = `${ad.cta} ${ad.clickUrl}`.length;
42
- const earnLen = `+$${earnedDollars} earned session total: $${sessionDollars}`.length;
48
+ const earnLen = `+$${earnedDollars} earned session: $${sessionDollars}`.length;
43
49
  const footerLen = `spincome · /disable to opt out`.length;
44
50
  return [
45
51
  "",
package/dist/hook.js CHANGED
@@ -10,6 +10,7 @@ const config_js_1 = require("./config.js");
10
10
  const session_js_1 = require("./session.js");
11
11
  const animate_js_1 = require("./animate.js");
12
12
  const path_1 = __importDefault(require("path"));
13
+ const AD_EVERY_N = 10; // show full ad every 10th impression
13
14
  function extractContext(payload) {
14
15
  const toolName = payload.tool_name ?? undefined;
15
16
  const filePath = payload.tool_input?.file_path ??
@@ -35,12 +36,17 @@ async function main() {
35
36
  // empty or malformed stdin
36
37
  }
37
38
  const context = extractContext(payload);
38
- // Fetch ad and record impression concurrently, animate while waiting
39
39
  const ad = await (0, ad_js_1.fetchAd)(context);
40
40
  if (!ad)
41
41
  process.exit(0);
42
42
  const earnedCents = await (0, animate_js_1.animateWhileLoading)((0, ad_js_1.recordImpression)(ad.id, config.developerKey, ad.actualCpmCents, context));
43
43
  const session = (0, session_js_1.addToSession)(earnedCents);
44
- process.stdout.write((0, display_js_1.renderAd)(ad, earnedCents, session.totalCents, context));
44
+ // Every 10th impression show the full ad, otherwise just the earnings ticker
45
+ if (session.impressions % AD_EVERY_N === 0) {
46
+ process.stdout.write((0, display_js_1.renderAd)(ad, earnedCents, session.totalCents, context));
47
+ }
48
+ else {
49
+ process.stdout.write((0, display_js_1.renderEarnings)(session.totalCents));
50
+ }
45
51
  }
46
52
  main().catch(() => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brozarti/spincome",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Earn passive income from your Claude Code sessions",
5
5
  "bin": {
6
6
  "spincome": "./dist/cli.js"
package/src/animate.ts CHANGED
@@ -15,7 +15,7 @@ const COIN = ["◐","◓","◑","◒"];
15
15
  const LOGO = `${BOLD}${GREEN}$${R}${BOLD} spincome${R}`;
16
16
 
17
17
  export async function animateWhileLoading<T>(work: Promise<T>): Promise<T> {
18
- process.stdout.write(HIDE_CURSOR);
18
+ process.stderr.write(HIDE_CURSOR);
19
19
 
20
20
  let frame = 0;
21
21
  let dots = 0;
@@ -28,7 +28,7 @@ export async function animateWhileLoading<T>(work: Promise<T>): Promise<T> {
28
28
  const coin = GREEN + COIN[frame % COIN.length] + R;
29
29
  dots = (dots + 1) % 4;
30
30
  const dotStr = DIM + ".".repeat(dots).padEnd(3) + R;
31
- process.stdout.write(
31
+ process.stderr.write(
32
32
  CLEAR_LINE +
33
33
  ` ${spinner} ${LOGO} ${coin} ${DIM}earning${R}${dotStr}`
34
34
  );
@@ -40,9 +40,9 @@ export async function animateWhileLoading<T>(work: Promise<T>): Promise<T> {
40
40
  } finally {
41
41
  done = true;
42
42
  clearInterval(tick);
43
- process.stdout.write(CLEAR_LINE);
44
- process.stdout.write(UP_ONE + CLEAR_LINE);
45
- process.stdout.write(SHOW_CURSOR);
43
+ process.stderr.write(CLEAR_LINE);
44
+ process.stderr.write(UP_ONE + CLEAR_LINE);
45
+ process.stderr.write(SHOW_CURSOR);
46
46
  }
47
47
 
48
48
  return result!;
package/src/display.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import type { Ad } from "./ad.js";
2
2
 
3
- const R = "\x1b[0m";
4
- const B = "\x1b[1m";
5
- const DIM = "\x1b[2m";
6
- const GREEN = "\x1b[32m";
3
+ const R = "\x1b[0m";
4
+ const B = "\x1b[1m";
5
+ const DIM = "\x1b[2m";
6
+ const GREEN = "\x1b[32m";
7
7
  const BRIGHT_GREEN = "\x1b[92m";
8
- const CYAN = "\x1b[36m";
9
- const YELLOW = "\x1b[33m";
10
- const WHITE = "\x1b[97m";
11
- const BG = "\x1b[48;5;234m";
8
+ const CYAN = "\x1b[36m";
9
+ const WHITE = "\x1b[97m";
10
+ const BG = "\x1b[48;5;234m";
12
11
 
13
12
  const W = 62;
14
13
 
@@ -25,9 +24,16 @@ function ruler(): string {
25
24
  return `${DIM}${"─".repeat(W)}${R}`;
26
25
  }
27
26
 
27
+ // Compact earnings ticker -- shown every tool call
28
+ export function renderEarnings(sessionCents: number): string {
29
+ const sessionDollars = (sessionCents / 100000).toFixed(4);
30
+ return `\n ${B}${GREEN}$${R} ${B}spincome${R} ${DIM}→${R} ${BRIGHT_GREEN}$${sessionDollars}${R} ${DIM}this session${R}\n`;
31
+ }
32
+
33
+ // Full ad box -- shown every Nth call
28
34
  export function renderAd(ad: Ad, earnedCents: number, sessionCents: number, context?: { toolName?: string; fileExt?: string }): string {
29
- const earnedDollars = (earnedCents / 100).toFixed(4);
30
- const sessionDollars = (sessionCents / 100).toFixed(4);
35
+ const earnedDollars = (earnedCents / 100000).toFixed(4);
36
+ const sessionDollars = (sessionCents / 100000).toFixed(4);
31
37
 
32
38
  const contextTag = context?.fileExt
33
39
  ? ` · ${context.fileExt.toUpperCase()} dev`
@@ -35,19 +41,19 @@ export function renderAd(ad: Ad, earnedCents: number, sessionCents: number, cont
35
41
  ? ` · ${context.toolName}`
36
42
  : "";
37
43
 
38
- const sponsorLine = `${DIM}Sponsored · ${ad.advertiser}${contextTag}${R}`;
39
- const headlineLine = `${B}${WHITE}${ad.headline}${R}`;
40
- const bodyLine = `${DIM}${ad.body}${R}`;
41
- const ctaLine = `${CYAN}${ad.cta}${R} ${DIM}${ad.clickUrl}${R}`;
42
- const earnLine = `${BRIGHT_GREEN}+$${earnedDollars} earned${R} ${DIM}session total: ${GREEN}$${sessionDollars}${R}`;
43
- const footerLine = `${DIM}spincome · /disable to opt out${R}`;
44
+ const sponsorLine = `${DIM}Sponsored · ${ad.advertiser}${contextTag}${R}`;
45
+ const headlineLine = `${B}${WHITE}${ad.headline}${R}`;
46
+ const bodyLine = `${DIM}${ad.body}${R}`;
47
+ const ctaLine = `${CYAN}${ad.cta}${R} ${DIM}${ad.clickUrl}${R}`;
48
+ const earnLine = `${BRIGHT_GREEN}+$${earnedDollars} earned${R} ${DIM}session: ${GREEN}$${sessionDollars}${R}`;
49
+ const footerLine = `${DIM}spincome · /disable to opt out${R}`;
44
50
 
45
- const sponsorLen = `Sponsored · ${ad.advertiser}${contextTag}`.length;
46
- const headlineLen = ad.headline.length;
47
- const bodyLen = ad.body.length;
48
- const ctaLen = `${ad.cta} ${ad.clickUrl}`.length;
49
- const earnLen = `+$${earnedDollars} earned session total: $${sessionDollars}`.length;
50
- const footerLen = `spincome · /disable to opt out`.length;
51
+ const sponsorLen = `Sponsored · ${ad.advertiser}${contextTag}`.length;
52
+ const headlineLen = ad.headline.length;
53
+ const bodyLen = ad.body.length;
54
+ const ctaLen = `${ad.cta} ${ad.clickUrl}`.length;
55
+ const earnLen = `+$${earnedDollars} earned session: $${sessionDollars}`.length;
56
+ const footerLen = `spincome · /disable to opt out`.length;
51
57
 
52
58
  return [
53
59
  "",
package/src/hook.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { fetchAd, recordImpression, type AdContext } from "./ad.js";
4
- import { renderAd } from "./display.js";
4
+ import { renderEarnings, renderAd } from "./display.js";
5
5
  import { readConfig } from "./config.js";
6
6
  import { addToSession } from "./session.js";
7
7
  import { animateWhileLoading } from "./animate.js";
8
8
  import path from "path";
9
9
 
10
+ const AD_EVERY_N = 10; // show full ad every 10th impression
11
+
10
12
  interface HookPayload {
11
13
  tool_name?: string;
12
14
  tool_input?: {
@@ -43,8 +45,6 @@ async function main() {
43
45
  }
44
46
 
45
47
  const context = extractContext(payload);
46
-
47
- // Fetch ad and record impression concurrently, animate while waiting
48
48
  const ad = await fetchAd(context);
49
49
  if (!ad) process.exit(0);
50
50
 
@@ -53,7 +53,13 @@ async function main() {
53
53
  );
54
54
 
55
55
  const session = addToSession(earnedCents);
56
- process.stdout.write(renderAd(ad, earnedCents, session.totalCents, context));
56
+
57
+ // Every 10th impression show the full ad, otherwise just the earnings ticker
58
+ if (session.impressions % AD_EVERY_N === 0) {
59
+ process.stdout.write(renderAd(ad, earnedCents, session.totalCents, context));
60
+ } else {
61
+ process.stdout.write(renderEarnings(session.totalCents));
62
+ }
57
63
  }
58
64
 
59
65
  main().catch(() => process.exit(0));