adsinagents 0.1.0 → 0.1.2
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 +2 -2
- package/cli/dist/index.js +136 -8
- package/daemon/dist/main.js +132 -14
- package/package.json +2 -2
- package/statusline/dist/index.js +113 -27
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AdsInAgents
|
|
2
2
|
|
|
3
3
|
Get paid while you build. AdsInAgents puts a single sponsored line in your
|
|
4
|
-
|
|
4
|
+
coding agent's status line and pays you for every verified impression.
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
npm install -g adsinagents
|
|
@@ -17,6 +17,6 @@ to attach a login and cash out.
|
|
|
17
17
|
|
|
18
18
|
The ad is always disclosed (`· sponsored`), never injected into model context,
|
|
19
19
|
and the daemon only counts an impression when the session is real, focused, and
|
|
20
|
-
on-screen. macOS today; Windows next.
|
|
20
|
+
on-screen. Claude Code on macOS today; more agents and Windows next.
|
|
21
21
|
|
|
22
22
|
[adsinagents.com](https://adsinagents.com) · advertisers: [adsinagents.com/advertise](https://adsinagents.com/advertise)
|
package/cli/dist/index.js
CHANGED
|
@@ -4082,6 +4082,20 @@ var coerce = {
|
|
|
4082
4082
|
};
|
|
4083
4083
|
var NEVER = INVALID;
|
|
4084
4084
|
|
|
4085
|
+
// ../../shared/dist/categories.js
|
|
4086
|
+
var CATEGORIES = {
|
|
4087
|
+
crypto: ["crypto", "web3", "blockchain", "nft", "token", "defi", "bitcoin", "ethereum", "wallet"],
|
|
4088
|
+
gambling: ["casino", "betting", "sportsbook", "poker", "lottery", "wager", "slots"],
|
|
4089
|
+
alcohol: ["beer", "whiskey", "whisky", "vodka", "wine", "tequila", "liquor", "cocktail"],
|
|
4090
|
+
dating: ["dating", "hookup", "singles", "match with", "find love"],
|
|
4091
|
+
politics: ["campaign", "vote for", "super pac", "ballot", "elect "]
|
|
4092
|
+
};
|
|
4093
|
+
var CATEGORY_KEYS = Object.keys(CATEGORIES);
|
|
4094
|
+
var CategorySchema = external_exports.enum(CATEGORY_KEYS);
|
|
4095
|
+
function isCategory(value) {
|
|
4096
|
+
return CATEGORY_KEYS.includes(value);
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4085
4099
|
// ../../shared/dist/schemas.js
|
|
4086
4100
|
var AdSchema = external_exports.object({
|
|
4087
4101
|
/** Stable impression id for this rotation; idempotency key for firing. */
|
|
@@ -4124,6 +4138,7 @@ var GravityAdSchema = external_exports.object({
|
|
|
4124
4138
|
});
|
|
4125
4139
|
var GravityResponseSchema = external_exports.array(GravityAdSchema);
|
|
4126
4140
|
var PlacementSchema = external_exports.enum(["statusline", "spinner", "both", "off"]);
|
|
4141
|
+
var ThemeSchema = external_exports.enum(["plain", "dim", "accent"]);
|
|
4127
4142
|
var AgentNameSchema = external_exports.enum(["claude-code"]);
|
|
4128
4143
|
var ConfigSchema = external_exports.object({
|
|
4129
4144
|
placement: PlacementSchema.default("statusline"),
|
|
@@ -4133,8 +4148,16 @@ var ConfigSchema = external_exports.object({
|
|
|
4133
4148
|
production: external_exports.boolean().default(false),
|
|
4134
4149
|
/** Send prompt text to Gravity for contextual matching. PRIVACY: off by default. */
|
|
4135
4150
|
contextualAds: external_exports.boolean().default(false),
|
|
4136
|
-
/**
|
|
4137
|
-
|
|
4151
|
+
/**
|
|
4152
|
+
* Ad categories the user opted out of. Best-effort keyword match on the ad's
|
|
4153
|
+
* own text/brand, applied client-side in the daemon — a matched ad is dropped
|
|
4154
|
+
* before it paints (no impression, not billed). See shared/src/categories.ts.
|
|
4155
|
+
*/
|
|
4156
|
+
blockedCategories: external_exports.array(CategorySchema).default([]),
|
|
4157
|
+
/** AdLine server base URL the daemon polls for ads / sync / flags.
|
|
4158
|
+
* Defaults to production — a fresh `npm i -g adsinagents` install must work
|
|
4159
|
+
* with zero config. Local dev: set serverUrl in ~/.adsinagents/config.json. */
|
|
4160
|
+
serverUrl: external_exports.string().default("https://adsinagents.com"),
|
|
4138
4161
|
/** Device bearer token minted by `adsinagents init` (server /api/register). */
|
|
4139
4162
|
deviceToken: external_exports.string().default(""),
|
|
4140
4163
|
/** Stable per-install device id. */
|
|
@@ -4149,10 +4172,18 @@ var ConfigSchema = external_exports.object({
|
|
|
4149
4172
|
rateCapSec: external_exports.number().int().positive().default(60),
|
|
4150
4173
|
/** Continuous focus seconds required before an impression verifies. */
|
|
4151
4174
|
focusDwellSec: external_exports.number().int().positive().default(5),
|
|
4175
|
+
/**
|
|
4176
|
+
* Seconds the ad stays painted after a turn ends (Stop hook). Past the grace
|
|
4177
|
+
* the status line goes clean until the next prompt. 0 = hide on Stop.
|
|
4178
|
+
* Display AND billing follow this gate together (billed ⇒ on screen).
|
|
4179
|
+
*/
|
|
4180
|
+
idleGraceSec: external_exports.number().int().nonnegative().default(120),
|
|
4152
4181
|
/** Remote kill-switch flag URL (optional; falls back to serverUrl/api/flags). */
|
|
4153
4182
|
killSwitchUrl: external_exports.string().default(""),
|
|
4154
4183
|
/** Show a live earnings segment in the status line (separate from the ad). Off by default. */
|
|
4155
|
-
showEarnings: external_exports.boolean().default(false)
|
|
4184
|
+
showEarnings: external_exports.boolean().default(false),
|
|
4185
|
+
/** Terminal styling for ad/earnings segments. Plain (no ANSI) by default. */
|
|
4186
|
+
theme: ThemeSchema.default("plain")
|
|
4156
4187
|
});
|
|
4157
4188
|
var DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
4158
4189
|
var DaemonStateSchema = external_exports.object({
|
|
@@ -4171,7 +4202,10 @@ var DaemonStateSchema = external_exports.object({
|
|
|
4171
4202
|
/** Publisher lifetime balance, cents. From the last sync response. Optional. */
|
|
4172
4203
|
balanceCents: external_exports.number().int().nonnegative().default(0),
|
|
4173
4204
|
/** Earnings today, cents. From the last sync response. Optional. */
|
|
4174
|
-
todayCents: external_exports.number().int().nonnegative().default(0)
|
|
4205
|
+
todayCents: external_exports.number().int().nonnegative().default(0),
|
|
4206
|
+
/** Claude is mid-turn (prompt received, Stop not yet). Drives the statusline
|
|
4207
|
+
* working glyph. Additive — older daemons omit it. */
|
|
4208
|
+
working: external_exports.boolean().default(false)
|
|
4175
4209
|
});
|
|
4176
4210
|
var ImpressionSchema = external_exports.object({
|
|
4177
4211
|
id: external_exports.string(),
|
|
@@ -4420,7 +4454,7 @@ function getPlatform() {
|
|
|
4420
4454
|
}
|
|
4421
4455
|
|
|
4422
4456
|
// ../../shared/dist/agent/claude-code.js
|
|
4423
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync, renameSync } from "node:fs";
|
|
4457
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync, renameSync, rmSync } from "node:fs";
|
|
4424
4458
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4425
4459
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4426
4460
|
var ClaudeCodeAdapter = class {
|
|
@@ -4428,6 +4462,9 @@ var ClaudeCodeAdapter = class {
|
|
|
4428
4462
|
hasStatusLine: true,
|
|
4429
4463
|
hasSpinner: true,
|
|
4430
4464
|
hasHttpHooks: true,
|
|
4465
|
+
// CC strips OSC 8 in the status line (anthropics/claude-code#21586).
|
|
4466
|
+
statuslineHyperlinks: false,
|
|
4467
|
+
hasSlashCommands: true,
|
|
4431
4468
|
displayName: "Claude Code",
|
|
4432
4469
|
binaryName: "claude"
|
|
4433
4470
|
};
|
|
@@ -4482,7 +4519,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4482
4519
|
next.statusLine = {
|
|
4483
4520
|
type: "command",
|
|
4484
4521
|
command: plan.statuslineCommand,
|
|
4485
|
-
|
|
4522
|
+
// 1s so the working glyph animates at 1 frame/sec (was 2).
|
|
4523
|
+
refreshInterval: 1
|
|
4486
4524
|
};
|
|
4487
4525
|
}
|
|
4488
4526
|
if (plan.placement === "spinner" || plan.placement === "both") {
|
|
@@ -4524,8 +4562,27 @@ var ClaudeCodeAdapter = class {
|
|
|
4524
4562
|
const backup = this.backup();
|
|
4525
4563
|
const { next, diff } = this.merge(plan);
|
|
4526
4564
|
this.write(next);
|
|
4565
|
+
this.writeSlashCommand();
|
|
4527
4566
|
return { backup, diff };
|
|
4528
4567
|
}
|
|
4568
|
+
/** Path of the in-session settings command, e.g. ~/.claude/commands/adsinagents.md. */
|
|
4569
|
+
slashCommandPath() {
|
|
4570
|
+
return join3(claudeConfigDir(), "commands", "adsinagents.md");
|
|
4571
|
+
}
|
|
4572
|
+
/**
|
|
4573
|
+
* Install /adsinagents — the status line itself is display-only, so this is
|
|
4574
|
+
* the in-session settings surface: the user types /adsinagents and Claude
|
|
4575
|
+
* drives the CLI. File is fully ours (sentinel comment), safe to delete on
|
|
4576
|
+
* remove. Best-effort: a failure here never fails install.
|
|
4577
|
+
*/
|
|
4578
|
+
writeSlashCommand() {
|
|
4579
|
+
try {
|
|
4580
|
+
const p = this.slashCommandPath();
|
|
4581
|
+
mkdirSync2(dirname2(p), { recursive: true });
|
|
4582
|
+
writeFileSync2(p, SLASH_COMMAND_MD);
|
|
4583
|
+
} catch {
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4529
4586
|
removeInstall() {
|
|
4530
4587
|
const cur = this.read();
|
|
4531
4588
|
const marker = cur[ADLINE_SENTINEL];
|
|
@@ -4550,6 +4607,13 @@ var ClaudeCodeAdapter = class {
|
|
|
4550
4607
|
}
|
|
4551
4608
|
delete next[ADLINE_SENTINEL];
|
|
4552
4609
|
this.write(next);
|
|
4610
|
+
try {
|
|
4611
|
+
const p = this.slashCommandPath();
|
|
4612
|
+
if (existsSync2(p) && readFileSync2(p, "utf8").includes(SLASH_SENTINEL)) {
|
|
4613
|
+
rmSync(p);
|
|
4614
|
+
}
|
|
4615
|
+
} catch {
|
|
4616
|
+
}
|
|
4553
4617
|
return { restored: true };
|
|
4554
4618
|
}
|
|
4555
4619
|
/**
|
|
@@ -4612,6 +4676,30 @@ var ClaudeCodeAdapter = class {
|
|
|
4612
4676
|
return rows;
|
|
4613
4677
|
}
|
|
4614
4678
|
};
|
|
4679
|
+
var SLASH_SENTINEL = "<!-- adsinagents:managed -->";
|
|
4680
|
+
var SLASH_COMMAND_MD = `---
|
|
4681
|
+
description: AdsInAgents \u2014 earnings, settings, doctor for the sponsored status line
|
|
4682
|
+
allowed-tools: Bash(adsinagents:*)
|
|
4683
|
+
---
|
|
4684
|
+
${SLASH_SENTINEL}
|
|
4685
|
+
|
|
4686
|
+
You manage AdsInAgents (the sponsored status line) for the user via its CLI.
|
|
4687
|
+
|
|
4688
|
+
Current state:
|
|
4689
|
+
- Config: !\`adsinagents config get\`
|
|
4690
|
+
- Stats: !\`adsinagents stats\`
|
|
4691
|
+
|
|
4692
|
+
User request: $ARGUMENTS
|
|
4693
|
+
|
|
4694
|
+
If the request is empty, show current config + stats and list what can be changed.
|
|
4695
|
+
Apply changes with the CLI (never edit config files directly):
|
|
4696
|
+
- \`adsinagents config set show-earnings <true|false>\` \u2014 toggle the earnings readout
|
|
4697
|
+
- \`adsinagents config set theme <plain|dim|accent>\` \u2014 status line colors (accent = amber mark, bold brand, green earnings; note: Claude Code currently dims all status line colors upstream)
|
|
4698
|
+
- \`adsinagents config block <category>\` / \`config unblock <category>\` \u2014 opt out of ad categories (crypto, gambling, alcohol, dating, politics). Best-effort keyword match on the ad's text; a blocked ad is dropped before it shows and is never billed. Run \`config blocked\` to list. Map natural requests like "no crypto ads" to \`config block crypto\`.
|
|
4699
|
+
- \`adsinagents doctor\` \u2014 diagnose setup issues
|
|
4700
|
+
- \`adsinagents stats\` \u2014 impressions, clicks, balance
|
|
4701
|
+
- \`adsinagents remove\` \u2014 uninstall (confirm with the user first)
|
|
4702
|
+
`;
|
|
4615
4703
|
function spinnerVerbsFor(ad) {
|
|
4616
4704
|
if (!ad)
|
|
4617
4705
|
return [];
|
|
@@ -4944,11 +5032,49 @@ function writeConfigPatch(patch) {
|
|
|
4944
5032
|
writeFileSync3(PATHS.config, JSON.stringify(next, null, 2) + "\n");
|
|
4945
5033
|
return next;
|
|
4946
5034
|
}
|
|
5035
|
+
var THEME_VALUES = ["plain", "dim", "accent"];
|
|
4947
5036
|
function cmdConfig(positional, flags) {
|
|
4948
5037
|
if (positional[0] === "set" && positional[1] === "show-earnings") {
|
|
4949
5038
|
const on = positional[2] !== "false";
|
|
4950
5039
|
writeConfigPatch({ showEarnings: on });
|
|
4951
5040
|
process.stdout.write(`\u2713 showEarnings = ${on}
|
|
5041
|
+
`);
|
|
5042
|
+
return;
|
|
5043
|
+
}
|
|
5044
|
+
if (positional[0] === "block" || positional[0] === "unblock") {
|
|
5045
|
+
const cat = positional[1];
|
|
5046
|
+
if (!cat || !isCategory(cat)) {
|
|
5047
|
+
process.stdout.write(`category must be one of: ${CATEGORY_KEYS.join(" | ")}
|
|
5048
|
+
`);
|
|
5049
|
+
process.exitCode = 1;
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
const cur = loadConfigSafe().blockedCategories;
|
|
5053
|
+
const next = positional[0] === "block" ? Array.from(/* @__PURE__ */ new Set([...cur, cat])) : cur.filter((c) => c !== cat);
|
|
5054
|
+
writeConfigPatch({ blockedCategories: next });
|
|
5055
|
+
const verb = positional[0] === "block" ? "blocking" : "showing";
|
|
5056
|
+
process.stdout.write(`\u2713 now ${verb} ${cat} ads \xB7 blocked: ${next.join(", ") || "(none)"}
|
|
5057
|
+
`);
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5060
|
+
if (positional[0] === "blocked") {
|
|
5061
|
+
const cur = loadConfigSafe().blockedCategories;
|
|
5062
|
+
process.stdout.write(`blocked: ${cur.join(", ") || "(none)"}
|
|
5063
|
+
`);
|
|
5064
|
+
process.stdout.write(`available: ${CATEGORY_KEYS.join(", ")}
|
|
5065
|
+
`);
|
|
5066
|
+
return;
|
|
5067
|
+
}
|
|
5068
|
+
if (positional[0] === "set" && positional[1] === "theme") {
|
|
5069
|
+
const t = positional[2];
|
|
5070
|
+
if (!THEME_VALUES.includes(t)) {
|
|
5071
|
+
process.stdout.write(`theme must be one of: ${THEME_VALUES.join(" | ")}
|
|
5072
|
+
`);
|
|
5073
|
+
process.exitCode = 1;
|
|
5074
|
+
return;
|
|
5075
|
+
}
|
|
5076
|
+
writeConfigPatch({ theme: t });
|
|
5077
|
+
process.stdout.write(`\u2713 theme = ${t}
|
|
4952
5078
|
`);
|
|
4953
5079
|
return;
|
|
4954
5080
|
}
|
|
@@ -4957,7 +5083,9 @@ function cmdConfig(positional, flags) {
|
|
|
4957
5083
|
process.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
|
|
4958
5084
|
return;
|
|
4959
5085
|
}
|
|
4960
|
-
process.stdout.write(
|
|
5086
|
+
process.stdout.write(
|
|
5087
|
+
"usage: adsinagents config get | config set show-earnings <true|false> | config set theme <plain|dim|accent> | config block <category> | config unblock <category> | config blocked\n"
|
|
5088
|
+
);
|
|
4961
5089
|
}
|
|
4962
5090
|
async function main() {
|
|
4963
5091
|
const [, , cmd, ...rest] = process.argv;
|
|
@@ -4988,7 +5116,7 @@ async function main() {
|
|
|
4988
5116
|
doctor
|
|
4989
5117
|
stats
|
|
4990
5118
|
claim print the URL to attach a login + get paid
|
|
4991
|
-
config get | config set show-earnings <true|false>
|
|
5119
|
+
config get | config set show-earnings <true|false> | config set theme <plain|dim|accent>
|
|
4992
5120
|
`
|
|
4993
5121
|
);
|
|
4994
5122
|
return;
|
package/daemon/dist/main.js
CHANGED
|
@@ -4077,6 +4077,30 @@ var coerce = {
|
|
|
4077
4077
|
};
|
|
4078
4078
|
var NEVER = INVALID;
|
|
4079
4079
|
|
|
4080
|
+
// ../../shared/dist/categories.js
|
|
4081
|
+
var CATEGORIES = {
|
|
4082
|
+
crypto: ["crypto", "web3", "blockchain", "nft", "token", "defi", "bitcoin", "ethereum", "wallet"],
|
|
4083
|
+
gambling: ["casino", "betting", "sportsbook", "poker", "lottery", "wager", "slots"],
|
|
4084
|
+
alcohol: ["beer", "whiskey", "whisky", "vodka", "wine", "tequila", "liquor", "cocktail"],
|
|
4085
|
+
dating: ["dating", "hookup", "singles", "match with", "find love"],
|
|
4086
|
+
politics: ["campaign", "vote for", "super pac", "ballot", "elect "]
|
|
4087
|
+
};
|
|
4088
|
+
var CATEGORY_KEYS = Object.keys(CATEGORIES);
|
|
4089
|
+
var CategorySchema = external_exports.enum(CATEGORY_KEYS);
|
|
4090
|
+
function matchesBlocked(ad, blocked) {
|
|
4091
|
+
if (blocked.length === 0)
|
|
4092
|
+
return false;
|
|
4093
|
+
const haystack = `${ad.text} ${ad.brandName}`.toLowerCase();
|
|
4094
|
+
for (const cat of blocked) {
|
|
4095
|
+
const keywords = CATEGORIES[cat];
|
|
4096
|
+
if (!keywords)
|
|
4097
|
+
continue;
|
|
4098
|
+
if (keywords.some((kw) => haystack.includes(kw)))
|
|
4099
|
+
return true;
|
|
4100
|
+
}
|
|
4101
|
+
return false;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4080
4104
|
// ../../shared/dist/schemas.js
|
|
4081
4105
|
var AdSchema = external_exports.object({
|
|
4082
4106
|
/** Stable impression id for this rotation; idempotency key for firing. */
|
|
@@ -4119,6 +4143,7 @@ var GravityAdSchema = external_exports.object({
|
|
|
4119
4143
|
});
|
|
4120
4144
|
var GravityResponseSchema = external_exports.array(GravityAdSchema);
|
|
4121
4145
|
var PlacementSchema = external_exports.enum(["statusline", "spinner", "both", "off"]);
|
|
4146
|
+
var ThemeSchema = external_exports.enum(["plain", "dim", "accent"]);
|
|
4122
4147
|
var AgentNameSchema = external_exports.enum(["claude-code"]);
|
|
4123
4148
|
var ConfigSchema = external_exports.object({
|
|
4124
4149
|
placement: PlacementSchema.default("statusline"),
|
|
@@ -4128,8 +4153,16 @@ var ConfigSchema = external_exports.object({
|
|
|
4128
4153
|
production: external_exports.boolean().default(false),
|
|
4129
4154
|
/** Send prompt text to Gravity for contextual matching. PRIVACY: off by default. */
|
|
4130
4155
|
contextualAds: external_exports.boolean().default(false),
|
|
4131
|
-
/**
|
|
4132
|
-
|
|
4156
|
+
/**
|
|
4157
|
+
* Ad categories the user opted out of. Best-effort keyword match on the ad's
|
|
4158
|
+
* own text/brand, applied client-side in the daemon — a matched ad is dropped
|
|
4159
|
+
* before it paints (no impression, not billed). See shared/src/categories.ts.
|
|
4160
|
+
*/
|
|
4161
|
+
blockedCategories: external_exports.array(CategorySchema).default([]),
|
|
4162
|
+
/** AdLine server base URL the daemon polls for ads / sync / flags.
|
|
4163
|
+
* Defaults to production — a fresh `npm i -g adsinagents` install must work
|
|
4164
|
+
* with zero config. Local dev: set serverUrl in ~/.adsinagents/config.json. */
|
|
4165
|
+
serverUrl: external_exports.string().default("https://adsinagents.com"),
|
|
4133
4166
|
/** Device bearer token minted by `adsinagents init` (server /api/register). */
|
|
4134
4167
|
deviceToken: external_exports.string().default(""),
|
|
4135
4168
|
/** Stable per-install device id. */
|
|
@@ -4144,10 +4177,18 @@ var ConfigSchema = external_exports.object({
|
|
|
4144
4177
|
rateCapSec: external_exports.number().int().positive().default(60),
|
|
4145
4178
|
/** Continuous focus seconds required before an impression verifies. */
|
|
4146
4179
|
focusDwellSec: external_exports.number().int().positive().default(5),
|
|
4180
|
+
/**
|
|
4181
|
+
* Seconds the ad stays painted after a turn ends (Stop hook). Past the grace
|
|
4182
|
+
* the status line goes clean until the next prompt. 0 = hide on Stop.
|
|
4183
|
+
* Display AND billing follow this gate together (billed ⇒ on screen).
|
|
4184
|
+
*/
|
|
4185
|
+
idleGraceSec: external_exports.number().int().nonnegative().default(120),
|
|
4147
4186
|
/** Remote kill-switch flag URL (optional; falls back to serverUrl/api/flags). */
|
|
4148
4187
|
killSwitchUrl: external_exports.string().default(""),
|
|
4149
4188
|
/** Show a live earnings segment in the status line (separate from the ad). Off by default. */
|
|
4150
|
-
showEarnings: external_exports.boolean().default(false)
|
|
4189
|
+
showEarnings: external_exports.boolean().default(false),
|
|
4190
|
+
/** Terminal styling for ad/earnings segments. Plain (no ANSI) by default. */
|
|
4191
|
+
theme: ThemeSchema.default("plain")
|
|
4151
4192
|
});
|
|
4152
4193
|
var DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
4153
4194
|
var DAEMON_STATE_SCHEMA_VERSION = 1;
|
|
@@ -4167,7 +4208,10 @@ var DaemonStateSchema = external_exports.object({
|
|
|
4167
4208
|
/** Publisher lifetime balance, cents. From the last sync response. Optional. */
|
|
4168
4209
|
balanceCents: external_exports.number().int().nonnegative().default(0),
|
|
4169
4210
|
/** Earnings today, cents. From the last sync response. Optional. */
|
|
4170
|
-
todayCents: external_exports.number().int().nonnegative().default(0)
|
|
4211
|
+
todayCents: external_exports.number().int().nonnegative().default(0),
|
|
4212
|
+
/** Claude is mid-turn (prompt received, Stop not yet). Drives the statusline
|
|
4213
|
+
* working glyph. Additive — older daemons omit it. */
|
|
4214
|
+
working: external_exports.boolean().default(false)
|
|
4171
4215
|
});
|
|
4172
4216
|
var ImpressionSchema = external_exports.object({
|
|
4173
4217
|
id: external_exports.string(),
|
|
@@ -4416,7 +4460,7 @@ function getPlatform() {
|
|
|
4416
4460
|
}
|
|
4417
4461
|
|
|
4418
4462
|
// ../../shared/dist/agent/claude-code.js
|
|
4419
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync, renameSync } from "node:fs";
|
|
4463
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync, renameSync, rmSync } from "node:fs";
|
|
4420
4464
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4421
4465
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4422
4466
|
var ClaudeCodeAdapter = class {
|
|
@@ -4424,6 +4468,9 @@ var ClaudeCodeAdapter = class {
|
|
|
4424
4468
|
hasStatusLine: true,
|
|
4425
4469
|
hasSpinner: true,
|
|
4426
4470
|
hasHttpHooks: true,
|
|
4471
|
+
// CC strips OSC 8 in the status line (anthropics/claude-code#21586).
|
|
4472
|
+
statuslineHyperlinks: false,
|
|
4473
|
+
hasSlashCommands: true,
|
|
4427
4474
|
displayName: "Claude Code",
|
|
4428
4475
|
binaryName: "claude"
|
|
4429
4476
|
};
|
|
@@ -4478,7 +4525,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4478
4525
|
next.statusLine = {
|
|
4479
4526
|
type: "command",
|
|
4480
4527
|
command: plan.statuslineCommand,
|
|
4481
|
-
|
|
4528
|
+
// 1s so the working glyph animates at 1 frame/sec (was 2).
|
|
4529
|
+
refreshInterval: 1
|
|
4482
4530
|
};
|
|
4483
4531
|
}
|
|
4484
4532
|
if (plan.placement === "spinner" || plan.placement === "both") {
|
|
@@ -4520,8 +4568,27 @@ var ClaudeCodeAdapter = class {
|
|
|
4520
4568
|
const backup = this.backup();
|
|
4521
4569
|
const { next, diff } = this.merge(plan);
|
|
4522
4570
|
this.write(next);
|
|
4571
|
+
this.writeSlashCommand();
|
|
4523
4572
|
return { backup, diff };
|
|
4524
4573
|
}
|
|
4574
|
+
/** Path of the in-session settings command, e.g. ~/.claude/commands/adsinagents.md. */
|
|
4575
|
+
slashCommandPath() {
|
|
4576
|
+
return join3(claudeConfigDir(), "commands", "adsinagents.md");
|
|
4577
|
+
}
|
|
4578
|
+
/**
|
|
4579
|
+
* Install /adsinagents — the status line itself is display-only, so this is
|
|
4580
|
+
* the in-session settings surface: the user types /adsinagents and Claude
|
|
4581
|
+
* drives the CLI. File is fully ours (sentinel comment), safe to delete on
|
|
4582
|
+
* remove. Best-effort: a failure here never fails install.
|
|
4583
|
+
*/
|
|
4584
|
+
writeSlashCommand() {
|
|
4585
|
+
try {
|
|
4586
|
+
const p = this.slashCommandPath();
|
|
4587
|
+
mkdirSync2(dirname2(p), { recursive: true });
|
|
4588
|
+
writeFileSync2(p, SLASH_COMMAND_MD);
|
|
4589
|
+
} catch {
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4525
4592
|
removeInstall() {
|
|
4526
4593
|
const cur = this.read();
|
|
4527
4594
|
const marker = cur[ADLINE_SENTINEL];
|
|
@@ -4546,6 +4613,13 @@ var ClaudeCodeAdapter = class {
|
|
|
4546
4613
|
}
|
|
4547
4614
|
delete next[ADLINE_SENTINEL];
|
|
4548
4615
|
this.write(next);
|
|
4616
|
+
try {
|
|
4617
|
+
const p = this.slashCommandPath();
|
|
4618
|
+
if (existsSync2(p) && readFileSync2(p, "utf8").includes(SLASH_SENTINEL)) {
|
|
4619
|
+
rmSync(p);
|
|
4620
|
+
}
|
|
4621
|
+
} catch {
|
|
4622
|
+
}
|
|
4549
4623
|
return { restored: true };
|
|
4550
4624
|
}
|
|
4551
4625
|
/**
|
|
@@ -4608,6 +4682,30 @@ var ClaudeCodeAdapter = class {
|
|
|
4608
4682
|
return rows;
|
|
4609
4683
|
}
|
|
4610
4684
|
};
|
|
4685
|
+
var SLASH_SENTINEL = "<!-- adsinagents:managed -->";
|
|
4686
|
+
var SLASH_COMMAND_MD = `---
|
|
4687
|
+
description: AdsInAgents \u2014 earnings, settings, doctor for the sponsored status line
|
|
4688
|
+
allowed-tools: Bash(adsinagents:*)
|
|
4689
|
+
---
|
|
4690
|
+
${SLASH_SENTINEL}
|
|
4691
|
+
|
|
4692
|
+
You manage AdsInAgents (the sponsored status line) for the user via its CLI.
|
|
4693
|
+
|
|
4694
|
+
Current state:
|
|
4695
|
+
- Config: !\`adsinagents config get\`
|
|
4696
|
+
- Stats: !\`adsinagents stats\`
|
|
4697
|
+
|
|
4698
|
+
User request: $ARGUMENTS
|
|
4699
|
+
|
|
4700
|
+
If the request is empty, show current config + stats and list what can be changed.
|
|
4701
|
+
Apply changes with the CLI (never edit config files directly):
|
|
4702
|
+
- \`adsinagents config set show-earnings <true|false>\` \u2014 toggle the earnings readout
|
|
4703
|
+
- \`adsinagents config set theme <plain|dim|accent>\` \u2014 status line colors (accent = amber mark, bold brand, green earnings; note: Claude Code currently dims all status line colors upstream)
|
|
4704
|
+
- \`adsinagents config block <category>\` / \`config unblock <category>\` \u2014 opt out of ad categories (crypto, gambling, alcohol, dating, politics). Best-effort keyword match on the ad's text; a blocked ad is dropped before it shows and is never billed. Run \`config blocked\` to list. Map natural requests like "no crypto ads" to \`config block crypto\`.
|
|
4705
|
+
- \`adsinagents doctor\` \u2014 diagnose setup issues
|
|
4706
|
+
- \`adsinagents stats\` \u2014 impressions, clicks, balance
|
|
4707
|
+
- \`adsinagents remove\` \u2014 uninstall (confirm with the user first)
|
|
4708
|
+
`;
|
|
4611
4709
|
function spinnerVerbsFor(ad) {
|
|
4612
4710
|
if (!ad)
|
|
4613
4711
|
return [];
|
|
@@ -4845,6 +4943,7 @@ var SessionTracker = class {
|
|
|
4845
4943
|
/** session_id -> startedAt ms. A session is live while present here. */
|
|
4846
4944
|
live = /* @__PURE__ */ new Map();
|
|
4847
4945
|
lastPromptAtMs = null;
|
|
4946
|
+
lastStopAtMs = null;
|
|
4848
4947
|
onSessionStart(sessionId, nowMs) {
|
|
4849
4948
|
this.live.set(sessionId, nowMs);
|
|
4850
4949
|
}
|
|
@@ -4856,7 +4955,16 @@ var SessionTracker = class {
|
|
|
4856
4955
|
this.lastPromptAtMs = nowMs;
|
|
4857
4956
|
}
|
|
4858
4957
|
/** Stop hook: assistant turn ended. Session stays live (only End closes it). */
|
|
4859
|
-
onStop(_sessionId,
|
|
4958
|
+
onStop(_sessionId, nowMs) {
|
|
4959
|
+
this.lastStopAtMs = nowMs;
|
|
4960
|
+
}
|
|
4961
|
+
/** Mid-turn: a prompt arrived after the last Stop (Claude is "working"). */
|
|
4962
|
+
working() {
|
|
4963
|
+
if (!this.sessionLive() || this.lastPromptAtMs === null) return false;
|
|
4964
|
+
return this.lastStopAtMs === null || this.lastPromptAtMs > this.lastStopAtMs;
|
|
4965
|
+
}
|
|
4966
|
+
lastStop() {
|
|
4967
|
+
return this.lastStopAtMs;
|
|
4860
4968
|
}
|
|
4861
4969
|
sessionLive() {
|
|
4862
4970
|
return this.live.size > 0;
|
|
@@ -4985,15 +5093,21 @@ var AdSource = class {
|
|
|
4985
5093
|
id: json.id ?? newImpId(nowMs),
|
|
4986
5094
|
fetchedAt: nowMs
|
|
4987
5095
|
});
|
|
4988
|
-
|
|
5096
|
+
if (!parsed.success) return void 0;
|
|
5097
|
+
if (matchesBlocked(parsed.data, this.cfg.blockedCategories)) return null;
|
|
5098
|
+
return parsed.data;
|
|
4989
5099
|
} catch {
|
|
4990
5100
|
return void 0;
|
|
4991
5101
|
}
|
|
4992
5102
|
}
|
|
4993
5103
|
builtinTestAd(nowMs) {
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5104
|
+
for (let i = 0; i < BUILTIN_TEST_ADS.length; i++) {
|
|
5105
|
+
const base = BUILTIN_TEST_ADS[this.rotation % BUILTIN_TEST_ADS.length];
|
|
5106
|
+
this.rotation += 1;
|
|
5107
|
+
const ad = AdSchema.parse({ ...base, id: newImpId(nowMs), fetchedAt: nowMs, nonce: "" });
|
|
5108
|
+
if (!matchesBlocked(ad, this.cfg.blockedCategories)) return ad;
|
|
5109
|
+
}
|
|
5110
|
+
return null;
|
|
4997
5111
|
}
|
|
4998
5112
|
};
|
|
4999
5113
|
|
|
@@ -5254,6 +5368,9 @@ async function main() {
|
|
|
5254
5368
|
await fileLog(`ad fetch error: ${String(e)}`);
|
|
5255
5369
|
}
|
|
5256
5370
|
}
|
|
5371
|
+
const working = sessions.working();
|
|
5372
|
+
const lastStop = sessions.lastStop();
|
|
5373
|
+
const adDisplayable = working || lastStop !== null && nowMs - lastStop <= cfg.idleGraceSec * 1e3;
|
|
5257
5374
|
const probe = focus.current();
|
|
5258
5375
|
const sample = {
|
|
5259
5376
|
nowMs,
|
|
@@ -5262,7 +5379,7 @@ async function main() {
|
|
|
5262
5379
|
screenUnlocked: !probe.locked,
|
|
5263
5380
|
displayAwake: probe.screenAwake,
|
|
5264
5381
|
disabled,
|
|
5265
|
-
adPresent: currentAd !== null,
|
|
5382
|
+
adPresent: currentAd !== null && adDisplayable,
|
|
5266
5383
|
lastPromptAtMs: sessions.lastPrompt(),
|
|
5267
5384
|
adId: currentAd?.id ?? null,
|
|
5268
5385
|
focusDwellSec: cfg.focusDwellSec,
|
|
@@ -5283,11 +5400,12 @@ async function main() {
|
|
|
5283
5400
|
schemaVersion: DAEMON_STATE_SCHEMA_VERSION,
|
|
5284
5401
|
sessionLive,
|
|
5285
5402
|
disabled,
|
|
5286
|
-
adVisible: !disabled && sessionLive && currentAd !== null,
|
|
5403
|
+
adVisible: !disabled && sessionLive && currentAd !== null && adDisplayable,
|
|
5287
5404
|
port,
|
|
5288
5405
|
updatedAt: nowMs,
|
|
5289
5406
|
balanceCents: earnings.balanceCents,
|
|
5290
|
-
todayCents: earnings.todayCents
|
|
5407
|
+
todayCents: earnings.todayCents,
|
|
5408
|
+
working
|
|
5291
5409
|
};
|
|
5292
5410
|
writeDaemonState(state);
|
|
5293
5411
|
if (tickCount % SYNC_EVERY_TICKS === 0) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adsinagents",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Get paid while you build.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Get paid while you build. AdsInAgents is the terminal-native ad layer for AI coding agents. Verified impressions pay you.",
|
|
5
5
|
"homepage": "https://adsinagents.com",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
package/statusline/dist/index.js
CHANGED
|
@@ -4070,6 +4070,17 @@ var coerce = {
|
|
|
4070
4070
|
};
|
|
4071
4071
|
var NEVER = INVALID;
|
|
4072
4072
|
|
|
4073
|
+
// ../../shared/dist/categories.js
|
|
4074
|
+
var CATEGORIES = {
|
|
4075
|
+
crypto: ["crypto", "web3", "blockchain", "nft", "token", "defi", "bitcoin", "ethereum", "wallet"],
|
|
4076
|
+
gambling: ["casino", "betting", "sportsbook", "poker", "lottery", "wager", "slots"],
|
|
4077
|
+
alcohol: ["beer", "whiskey", "whisky", "vodka", "wine", "tequila", "liquor", "cocktail"],
|
|
4078
|
+
dating: ["dating", "hookup", "singles", "match with", "find love"],
|
|
4079
|
+
politics: ["campaign", "vote for", "super pac", "ballot", "elect "]
|
|
4080
|
+
};
|
|
4081
|
+
var CATEGORY_KEYS = Object.keys(CATEGORIES);
|
|
4082
|
+
var CategorySchema = external_exports.enum(CATEGORY_KEYS);
|
|
4083
|
+
|
|
4073
4084
|
// ../../shared/dist/schemas.js
|
|
4074
4085
|
var AdSchema = external_exports.object({
|
|
4075
4086
|
/** Stable impression id for this rotation; idempotency key for firing. */
|
|
@@ -4112,6 +4123,7 @@ var GravityAdSchema = external_exports.object({
|
|
|
4112
4123
|
});
|
|
4113
4124
|
var GravityResponseSchema = external_exports.array(GravityAdSchema);
|
|
4114
4125
|
var PlacementSchema = external_exports.enum(["statusline", "spinner", "both", "off"]);
|
|
4126
|
+
var ThemeSchema = external_exports.enum(["plain", "dim", "accent"]);
|
|
4115
4127
|
var AgentNameSchema = external_exports.enum(["claude-code"]);
|
|
4116
4128
|
var ConfigSchema = external_exports.object({
|
|
4117
4129
|
placement: PlacementSchema.default("statusline"),
|
|
@@ -4121,8 +4133,16 @@ var ConfigSchema = external_exports.object({
|
|
|
4121
4133
|
production: external_exports.boolean().default(false),
|
|
4122
4134
|
/** Send prompt text to Gravity for contextual matching. PRIVACY: off by default. */
|
|
4123
4135
|
contextualAds: external_exports.boolean().default(false),
|
|
4124
|
-
/**
|
|
4125
|
-
|
|
4136
|
+
/**
|
|
4137
|
+
* Ad categories the user opted out of. Best-effort keyword match on the ad's
|
|
4138
|
+
* own text/brand, applied client-side in the daemon — a matched ad is dropped
|
|
4139
|
+
* before it paints (no impression, not billed). See shared/src/categories.ts.
|
|
4140
|
+
*/
|
|
4141
|
+
blockedCategories: external_exports.array(CategorySchema).default([]),
|
|
4142
|
+
/** AdLine server base URL the daemon polls for ads / sync / flags.
|
|
4143
|
+
* Defaults to production — a fresh `npm i -g adsinagents` install must work
|
|
4144
|
+
* with zero config. Local dev: set serverUrl in ~/.adsinagents/config.json. */
|
|
4145
|
+
serverUrl: external_exports.string().default("https://adsinagents.com"),
|
|
4126
4146
|
/** Device bearer token minted by `adsinagents init` (server /api/register). */
|
|
4127
4147
|
deviceToken: external_exports.string().default(""),
|
|
4128
4148
|
/** Stable per-install device id. */
|
|
@@ -4137,10 +4157,18 @@ var ConfigSchema = external_exports.object({
|
|
|
4137
4157
|
rateCapSec: external_exports.number().int().positive().default(60),
|
|
4138
4158
|
/** Continuous focus seconds required before an impression verifies. */
|
|
4139
4159
|
focusDwellSec: external_exports.number().int().positive().default(5),
|
|
4160
|
+
/**
|
|
4161
|
+
* Seconds the ad stays painted after a turn ends (Stop hook). Past the grace
|
|
4162
|
+
* the status line goes clean until the next prompt. 0 = hide on Stop.
|
|
4163
|
+
* Display AND billing follow this gate together (billed ⇒ on screen).
|
|
4164
|
+
*/
|
|
4165
|
+
idleGraceSec: external_exports.number().int().nonnegative().default(120),
|
|
4140
4166
|
/** Remote kill-switch flag URL (optional; falls back to serverUrl/api/flags). */
|
|
4141
4167
|
killSwitchUrl: external_exports.string().default(""),
|
|
4142
4168
|
/** Show a live earnings segment in the status line (separate from the ad). Off by default. */
|
|
4143
|
-
showEarnings: external_exports.boolean().default(false)
|
|
4169
|
+
showEarnings: external_exports.boolean().default(false),
|
|
4170
|
+
/** Terminal styling for ad/earnings segments. Plain (no ANSI) by default. */
|
|
4171
|
+
theme: ThemeSchema.default("plain")
|
|
4144
4172
|
});
|
|
4145
4173
|
var DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
4146
4174
|
var DaemonStateSchema = external_exports.object({
|
|
@@ -4159,7 +4187,10 @@ var DaemonStateSchema = external_exports.object({
|
|
|
4159
4187
|
/** Publisher lifetime balance, cents. From the last sync response. Optional. */
|
|
4160
4188
|
balanceCents: external_exports.number().int().nonnegative().default(0),
|
|
4161
4189
|
/** Earnings today, cents. From the last sync response. Optional. */
|
|
4162
|
-
todayCents: external_exports.number().int().nonnegative().default(0)
|
|
4190
|
+
todayCents: external_exports.number().int().nonnegative().default(0),
|
|
4191
|
+
/** Claude is mid-turn (prompt received, Stop not yet). Drives the statusline
|
|
4192
|
+
* working glyph. Additive — older daemons omit it. */
|
|
4193
|
+
working: external_exports.boolean().default(false)
|
|
4163
4194
|
});
|
|
4164
4195
|
var ImpressionSchema = external_exports.object({
|
|
4165
4196
|
id: external_exports.string(),
|
|
@@ -4189,19 +4220,41 @@ var FocusProbeSchema = external_exports.object({
|
|
|
4189
4220
|
function osc8(url, text) {
|
|
4190
4221
|
return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
|
|
4191
4222
|
}
|
|
4223
|
+
var ID = (s) => s;
|
|
4224
|
+
var DIM = (s) => `\x1B[2m${s}\x1B[22m`;
|
|
4225
|
+
var BOLD = (s) => `\x1B[1m${s}\x1B[22m`;
|
|
4226
|
+
var GREEN = (s) => `\x1B[32m${s}\x1B[39m`;
|
|
4227
|
+
var AMBER = (s) => `\x1B[38;5;215m${s}\x1B[39m`;
|
|
4228
|
+
var THEMES = {
|
|
4229
|
+
plain: { disclosure: ID, mark: ID, brand: ID, money: ID },
|
|
4230
|
+
dim: { disclosure: DIM, mark: ID, brand: ID, money: ID },
|
|
4231
|
+
accent: { disclosure: DIM, mark: AMBER, brand: BOLD, money: GREEN }
|
|
4232
|
+
};
|
|
4233
|
+
var MAX_AD_TEXT = 60;
|
|
4192
4234
|
function renderAdSegment(ad, opts) {
|
|
4193
|
-
const
|
|
4194
|
-
const
|
|
4195
|
-
const
|
|
4196
|
-
const
|
|
4235
|
+
const t = THEMES[opts.theme ?? "plain"];
|
|
4236
|
+
const star = t.mark("\u2736");
|
|
4237
|
+
const arrow = t.mark("\u2197");
|
|
4238
|
+
const text = ad.text.length > MAX_AD_TEXT ? ad.text.slice(0, MAX_AD_TEXT - 1).trimEnd() + "\u2026" : ad.text;
|
|
4239
|
+
const label = ad.brandName ? `${text} \xB7 ${t.brand(ad.brandName)}` : text;
|
|
4197
4240
|
const clickUrl = `${opts.clickBase}/click/${encodeURIComponent(ad.id)}`;
|
|
4198
|
-
const linked = opts.supportsOsc8 ? osc8(clickUrl,
|
|
4199
|
-
return
|
|
4241
|
+
const linked = opts.supportsOsc8 ? osc8(clickUrl, `${star} ${label} ${arrow}`) : opts.plainLink ? `${star} ${label} \xB7 ${opts.plainLink} ${arrow}` : `${star} ${label} ${arrow}`;
|
|
4242
|
+
return `${t.disclosure("Ad:")} ${linked} ${t.disclosure("\xB7 sponsored")}`;
|
|
4200
4243
|
}
|
|
4201
|
-
function renderEarningsSegment(balanceCents, todayCents) {
|
|
4202
|
-
const
|
|
4244
|
+
function renderEarningsSegment(balanceCents, todayCents, theme = "plain") {
|
|
4245
|
+
const t = THEMES[theme];
|
|
4246
|
+
const d = (c) => t.money(`$${(Math.max(0, c) / 100).toFixed(2)}`);
|
|
4203
4247
|
return `${d(todayCents)} today \xB7 ${d(balanceCents)}`;
|
|
4204
4248
|
}
|
|
4249
|
+
function renderLoadingSegment(theme = "plain") {
|
|
4250
|
+
const s = "Ad: loading\u2026";
|
|
4251
|
+
return theme === "plain" ? s : DIM(s);
|
|
4252
|
+
}
|
|
4253
|
+
var WORKING_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4254
|
+
function workingGlyph(nowMs, theme = "plain") {
|
|
4255
|
+
const frame = WORKING_FRAMES[Math.floor(nowMs / 1e3) % WORKING_FRAMES.length];
|
|
4256
|
+
return THEMES[theme ?? "plain"].mark(frame);
|
|
4257
|
+
}
|
|
4205
4258
|
function composeStatusLine(priorOutput, ...segments) {
|
|
4206
4259
|
const parts = [priorOutput.trim(), ...segments.map((s) => s.trim())].filter(Boolean);
|
|
4207
4260
|
return parts.join(" \u2502 ");
|
|
@@ -4215,6 +4268,32 @@ import { execFile, execFileSync } from "node:child_process";
|
|
|
4215
4268
|
import { promisify } from "node:util";
|
|
4216
4269
|
var pExecFile = promisify(execFile);
|
|
4217
4270
|
|
|
4271
|
+
// ../../shared/dist/agent/claude-code.js
|
|
4272
|
+
var SLASH_SENTINEL = "<!-- adsinagents:managed -->";
|
|
4273
|
+
var SLASH_COMMAND_MD = `---
|
|
4274
|
+
description: AdsInAgents \u2014 earnings, settings, doctor for the sponsored status line
|
|
4275
|
+
allowed-tools: Bash(adsinagents:*)
|
|
4276
|
+
---
|
|
4277
|
+
${SLASH_SENTINEL}
|
|
4278
|
+
|
|
4279
|
+
You manage AdsInAgents (the sponsored status line) for the user via its CLI.
|
|
4280
|
+
|
|
4281
|
+
Current state:
|
|
4282
|
+
- Config: !\`adsinagents config get\`
|
|
4283
|
+
- Stats: !\`adsinagents stats\`
|
|
4284
|
+
|
|
4285
|
+
User request: $ARGUMENTS
|
|
4286
|
+
|
|
4287
|
+
If the request is empty, show current config + stats and list what can be changed.
|
|
4288
|
+
Apply changes with the CLI (never edit config files directly):
|
|
4289
|
+
- \`adsinagents config set show-earnings <true|false>\` \u2014 toggle the earnings readout
|
|
4290
|
+
- \`adsinagents config set theme <plain|dim|accent>\` \u2014 status line colors (accent = amber mark, bold brand, green earnings; note: Claude Code currently dims all status line colors upstream)
|
|
4291
|
+
- \`adsinagents config block <category>\` / \`config unblock <category>\` \u2014 opt out of ad categories (crypto, gambling, alcohol, dating, politics). Best-effort keyword match on the ad's text; a blocked ad is dropped before it shows and is never billed. Run \`config blocked\` to list. Map natural requests like "no crypto ads" to \`config block crypto\`.
|
|
4292
|
+
- \`adsinagents doctor\` \u2014 diagnose setup issues
|
|
4293
|
+
- \`adsinagents stats\` \u2014 impressions, clicks, balance
|
|
4294
|
+
- \`adsinagents remove\` \u2014 uninstall (confirm with the user first)
|
|
4295
|
+
`;
|
|
4296
|
+
|
|
4218
4297
|
// ../statusline/src/index.ts
|
|
4219
4298
|
var STATE_STALE_MS = 3e4;
|
|
4220
4299
|
function readJson(path) {
|
|
@@ -4225,11 +4304,15 @@ function readJson(path) {
|
|
|
4225
4304
|
return null;
|
|
4226
4305
|
}
|
|
4227
4306
|
}
|
|
4228
|
-
function
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4307
|
+
function hostPreservesOsc8() {
|
|
4308
|
+
return process.env.FORCE_HYPERLINK === "1";
|
|
4309
|
+
}
|
|
4310
|
+
function plainClickLink(ad, serverUrl, clickBase) {
|
|
4311
|
+
const localServer = /127\.0\.0\.1|localhost/.test(serverUrl);
|
|
4312
|
+
if (ad.source === "direct" && !localServer) {
|
|
4313
|
+
return `${serverUrl.replace(/\/$/, "")}/c/${encodeURIComponent(ad.campaignId)}`;
|
|
4314
|
+
}
|
|
4315
|
+
return `${clickBase}/click/${encodeURIComponent(ad.id)}`;
|
|
4233
4316
|
}
|
|
4234
4317
|
function priorStatusline(stdinJson) {
|
|
4235
4318
|
const cmd = process.env.ADSINAGENTS_PRIOR_STATUSLINE;
|
|
@@ -4263,20 +4346,23 @@ function main() {
|
|
|
4263
4346
|
const stale = Date.now() - state.updatedAt > STATE_STALE_MS;
|
|
4264
4347
|
const cfg = ConfigSchema.safeParse(readJson(PATHS.config));
|
|
4265
4348
|
const showEarnings = cfg.success && cfg.data.showEarnings;
|
|
4266
|
-
const
|
|
4267
|
-
|
|
4268
|
-
process.stdout.write(composeStatusLine(prior, earningsSeg) + "\n");
|
|
4269
|
-
return;
|
|
4270
|
-
}
|
|
4349
|
+
const theme = cfg.success ? cfg.data.theme : "plain";
|
|
4350
|
+
const earningsSeg = showEarnings && state.sessionLive && !stale ? renderEarningsSegment(state.balanceCents, state.todayCents, theme) : "";
|
|
4271
4351
|
const adParsed = AdSchema.safeParse(readJson(PATHS.currentAd));
|
|
4272
|
-
if (!adParsed.success) {
|
|
4273
|
-
|
|
4352
|
+
if (stale || state.disabled || !state.adVisible || !adParsed.success) {
|
|
4353
|
+
const loadingSeg = !stale && !state.disabled && state.working && !adParsed.success ? renderLoadingSegment(theme) : "";
|
|
4354
|
+
process.stdout.write(composeStatusLine(prior, loadingSeg, earningsSeg) + "\n");
|
|
4274
4355
|
return;
|
|
4275
4356
|
}
|
|
4357
|
+
const clickBase = `http://127.0.0.1:${state.port}`;
|
|
4358
|
+
const serverUrl = cfg.success ? cfg.data.serverUrl : "";
|
|
4276
4359
|
const adSeg = renderAdSegment(adParsed.data, {
|
|
4277
|
-
clickBase
|
|
4278
|
-
supportsOsc8:
|
|
4360
|
+
clickBase,
|
|
4361
|
+
supportsOsc8: hostPreservesOsc8(),
|
|
4362
|
+
plainLink: plainClickLink(adParsed.data, serverUrl, clickBase),
|
|
4363
|
+
theme
|
|
4279
4364
|
});
|
|
4280
|
-
|
|
4365
|
+
const seg = state.working ? `${workingGlyph(Date.now(), theme)} ${adSeg}` : adSeg;
|
|
4366
|
+
process.stdout.write(composeStatusLine(prior, seg, earningsSeg) + "\n");
|
|
4281
4367
|
}
|
|
4282
4368
|
main();
|