adsinagents 0.1.1 → 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/cli/dist/index.js +132 -6
- package/daemon/dist/main.js +128 -12
- package/package.json +1 -1
- package/statusline/dist/index.js +109 -25
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,6 +4148,12 @@ 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),
|
|
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([]),
|
|
4136
4157
|
/** AdLine server base URL the daemon polls for ads / sync / flags.
|
|
4137
4158
|
* Defaults to production — a fresh `npm i -g adsinagents` install must work
|
|
4138
4159
|
* with zero config. Local dev: set serverUrl in ~/.adsinagents/config.json. */
|
|
@@ -4151,10 +4172,18 @@ var ConfigSchema = external_exports.object({
|
|
|
4151
4172
|
rateCapSec: external_exports.number().int().positive().default(60),
|
|
4152
4173
|
/** Continuous focus seconds required before an impression verifies. */
|
|
4153
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),
|
|
4154
4181
|
/** Remote kill-switch flag URL (optional; falls back to serverUrl/api/flags). */
|
|
4155
4182
|
killSwitchUrl: external_exports.string().default(""),
|
|
4156
4183
|
/** Show a live earnings segment in the status line (separate from the ad). Off by default. */
|
|
4157
|
-
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")
|
|
4158
4187
|
});
|
|
4159
4188
|
var DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
4160
4189
|
var DaemonStateSchema = external_exports.object({
|
|
@@ -4173,7 +4202,10 @@ var DaemonStateSchema = external_exports.object({
|
|
|
4173
4202
|
/** Publisher lifetime balance, cents. From the last sync response. Optional. */
|
|
4174
4203
|
balanceCents: external_exports.number().int().nonnegative().default(0),
|
|
4175
4204
|
/** Earnings today, cents. From the last sync response. Optional. */
|
|
4176
|
-
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)
|
|
4177
4209
|
});
|
|
4178
4210
|
var ImpressionSchema = external_exports.object({
|
|
4179
4211
|
id: external_exports.string(),
|
|
@@ -4422,7 +4454,7 @@ function getPlatform() {
|
|
|
4422
4454
|
}
|
|
4423
4455
|
|
|
4424
4456
|
// ../../shared/dist/agent/claude-code.js
|
|
4425
|
-
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";
|
|
4426
4458
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4427
4459
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4428
4460
|
var ClaudeCodeAdapter = class {
|
|
@@ -4430,6 +4462,9 @@ var ClaudeCodeAdapter = class {
|
|
|
4430
4462
|
hasStatusLine: true,
|
|
4431
4463
|
hasSpinner: true,
|
|
4432
4464
|
hasHttpHooks: true,
|
|
4465
|
+
// CC strips OSC 8 in the status line (anthropics/claude-code#21586).
|
|
4466
|
+
statuslineHyperlinks: false,
|
|
4467
|
+
hasSlashCommands: true,
|
|
4433
4468
|
displayName: "Claude Code",
|
|
4434
4469
|
binaryName: "claude"
|
|
4435
4470
|
};
|
|
@@ -4484,7 +4519,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4484
4519
|
next.statusLine = {
|
|
4485
4520
|
type: "command",
|
|
4486
4521
|
command: plan.statuslineCommand,
|
|
4487
|
-
|
|
4522
|
+
// 1s so the working glyph animates at 1 frame/sec (was 2).
|
|
4523
|
+
refreshInterval: 1
|
|
4488
4524
|
};
|
|
4489
4525
|
}
|
|
4490
4526
|
if (plan.placement === "spinner" || plan.placement === "both") {
|
|
@@ -4526,8 +4562,27 @@ var ClaudeCodeAdapter = class {
|
|
|
4526
4562
|
const backup = this.backup();
|
|
4527
4563
|
const { next, diff } = this.merge(plan);
|
|
4528
4564
|
this.write(next);
|
|
4565
|
+
this.writeSlashCommand();
|
|
4529
4566
|
return { backup, diff };
|
|
4530
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
|
+
}
|
|
4531
4586
|
removeInstall() {
|
|
4532
4587
|
const cur = this.read();
|
|
4533
4588
|
const marker = cur[ADLINE_SENTINEL];
|
|
@@ -4552,6 +4607,13 @@ var ClaudeCodeAdapter = class {
|
|
|
4552
4607
|
}
|
|
4553
4608
|
delete next[ADLINE_SENTINEL];
|
|
4554
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
|
+
}
|
|
4555
4617
|
return { restored: true };
|
|
4556
4618
|
}
|
|
4557
4619
|
/**
|
|
@@ -4614,6 +4676,30 @@ var ClaudeCodeAdapter = class {
|
|
|
4614
4676
|
return rows;
|
|
4615
4677
|
}
|
|
4616
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
|
+
`;
|
|
4617
4703
|
function spinnerVerbsFor(ad) {
|
|
4618
4704
|
if (!ad)
|
|
4619
4705
|
return [];
|
|
@@ -4946,11 +5032,49 @@ function writeConfigPatch(patch) {
|
|
|
4946
5032
|
writeFileSync3(PATHS.config, JSON.stringify(next, null, 2) + "\n");
|
|
4947
5033
|
return next;
|
|
4948
5034
|
}
|
|
5035
|
+
var THEME_VALUES = ["plain", "dim", "accent"];
|
|
4949
5036
|
function cmdConfig(positional, flags) {
|
|
4950
5037
|
if (positional[0] === "set" && positional[1] === "show-earnings") {
|
|
4951
5038
|
const on = positional[2] !== "false";
|
|
4952
5039
|
writeConfigPatch({ showEarnings: on });
|
|
4953
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}
|
|
4954
5078
|
`);
|
|
4955
5079
|
return;
|
|
4956
5080
|
}
|
|
@@ -4959,7 +5083,9 @@ function cmdConfig(positional, flags) {
|
|
|
4959
5083
|
process.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
|
|
4960
5084
|
return;
|
|
4961
5085
|
}
|
|
4962
|
-
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
|
+
);
|
|
4963
5089
|
}
|
|
4964
5090
|
async function main() {
|
|
4965
5091
|
const [, , cmd, ...rest] = process.argv;
|
|
@@ -4990,7 +5116,7 @@ async function main() {
|
|
|
4990
5116
|
doctor
|
|
4991
5117
|
stats
|
|
4992
5118
|
claim print the URL to attach a login + get paid
|
|
4993
|
-
config get | config set show-earnings <true|false>
|
|
5119
|
+
config get | config set show-earnings <true|false> | config set theme <plain|dim|accent>
|
|
4994
5120
|
`
|
|
4995
5121
|
);
|
|
4996
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,6 +4153,12 @@ 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),
|
|
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([]),
|
|
4131
4162
|
/** AdLine server base URL the daemon polls for ads / sync / flags.
|
|
4132
4163
|
* Defaults to production — a fresh `npm i -g adsinagents` install must work
|
|
4133
4164
|
* with zero config. Local dev: set serverUrl in ~/.adsinagents/config.json. */
|
|
@@ -4146,10 +4177,18 @@ var ConfigSchema = external_exports.object({
|
|
|
4146
4177
|
rateCapSec: external_exports.number().int().positive().default(60),
|
|
4147
4178
|
/** Continuous focus seconds required before an impression verifies. */
|
|
4148
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),
|
|
4149
4186
|
/** Remote kill-switch flag URL (optional; falls back to serverUrl/api/flags). */
|
|
4150
4187
|
killSwitchUrl: external_exports.string().default(""),
|
|
4151
4188
|
/** Show a live earnings segment in the status line (separate from the ad). Off by default. */
|
|
4152
|
-
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")
|
|
4153
4192
|
});
|
|
4154
4193
|
var DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
4155
4194
|
var DAEMON_STATE_SCHEMA_VERSION = 1;
|
|
@@ -4169,7 +4208,10 @@ var DaemonStateSchema = external_exports.object({
|
|
|
4169
4208
|
/** Publisher lifetime balance, cents. From the last sync response. Optional. */
|
|
4170
4209
|
balanceCents: external_exports.number().int().nonnegative().default(0),
|
|
4171
4210
|
/** Earnings today, cents. From the last sync response. Optional. */
|
|
4172
|
-
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)
|
|
4173
4215
|
});
|
|
4174
4216
|
var ImpressionSchema = external_exports.object({
|
|
4175
4217
|
id: external_exports.string(),
|
|
@@ -4418,7 +4460,7 @@ function getPlatform() {
|
|
|
4418
4460
|
}
|
|
4419
4461
|
|
|
4420
4462
|
// ../../shared/dist/agent/claude-code.js
|
|
4421
|
-
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";
|
|
4422
4464
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4423
4465
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4424
4466
|
var ClaudeCodeAdapter = class {
|
|
@@ -4426,6 +4468,9 @@ var ClaudeCodeAdapter = class {
|
|
|
4426
4468
|
hasStatusLine: true,
|
|
4427
4469
|
hasSpinner: true,
|
|
4428
4470
|
hasHttpHooks: true,
|
|
4471
|
+
// CC strips OSC 8 in the status line (anthropics/claude-code#21586).
|
|
4472
|
+
statuslineHyperlinks: false,
|
|
4473
|
+
hasSlashCommands: true,
|
|
4429
4474
|
displayName: "Claude Code",
|
|
4430
4475
|
binaryName: "claude"
|
|
4431
4476
|
};
|
|
@@ -4480,7 +4525,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4480
4525
|
next.statusLine = {
|
|
4481
4526
|
type: "command",
|
|
4482
4527
|
command: plan.statuslineCommand,
|
|
4483
|
-
|
|
4528
|
+
// 1s so the working glyph animates at 1 frame/sec (was 2).
|
|
4529
|
+
refreshInterval: 1
|
|
4484
4530
|
};
|
|
4485
4531
|
}
|
|
4486
4532
|
if (plan.placement === "spinner" || plan.placement === "both") {
|
|
@@ -4522,8 +4568,27 @@ var ClaudeCodeAdapter = class {
|
|
|
4522
4568
|
const backup = this.backup();
|
|
4523
4569
|
const { next, diff } = this.merge(plan);
|
|
4524
4570
|
this.write(next);
|
|
4571
|
+
this.writeSlashCommand();
|
|
4525
4572
|
return { backup, diff };
|
|
4526
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
|
+
}
|
|
4527
4592
|
removeInstall() {
|
|
4528
4593
|
const cur = this.read();
|
|
4529
4594
|
const marker = cur[ADLINE_SENTINEL];
|
|
@@ -4548,6 +4613,13 @@ var ClaudeCodeAdapter = class {
|
|
|
4548
4613
|
}
|
|
4549
4614
|
delete next[ADLINE_SENTINEL];
|
|
4550
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
|
+
}
|
|
4551
4623
|
return { restored: true };
|
|
4552
4624
|
}
|
|
4553
4625
|
/**
|
|
@@ -4610,6 +4682,30 @@ var ClaudeCodeAdapter = class {
|
|
|
4610
4682
|
return rows;
|
|
4611
4683
|
}
|
|
4612
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
|
+
`;
|
|
4613
4709
|
function spinnerVerbsFor(ad) {
|
|
4614
4710
|
if (!ad)
|
|
4615
4711
|
return [];
|
|
@@ -4847,6 +4943,7 @@ var SessionTracker = class {
|
|
|
4847
4943
|
/** session_id -> startedAt ms. A session is live while present here. */
|
|
4848
4944
|
live = /* @__PURE__ */ new Map();
|
|
4849
4945
|
lastPromptAtMs = null;
|
|
4946
|
+
lastStopAtMs = null;
|
|
4850
4947
|
onSessionStart(sessionId, nowMs) {
|
|
4851
4948
|
this.live.set(sessionId, nowMs);
|
|
4852
4949
|
}
|
|
@@ -4858,7 +4955,16 @@ var SessionTracker = class {
|
|
|
4858
4955
|
this.lastPromptAtMs = nowMs;
|
|
4859
4956
|
}
|
|
4860
4957
|
/** Stop hook: assistant turn ended. Session stays live (only End closes it). */
|
|
4861
|
-
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;
|
|
4862
4968
|
}
|
|
4863
4969
|
sessionLive() {
|
|
4864
4970
|
return this.live.size > 0;
|
|
@@ -4987,15 +5093,21 @@ var AdSource = class {
|
|
|
4987
5093
|
id: json.id ?? newImpId(nowMs),
|
|
4988
5094
|
fetchedAt: nowMs
|
|
4989
5095
|
});
|
|
4990
|
-
|
|
5096
|
+
if (!parsed.success) return void 0;
|
|
5097
|
+
if (matchesBlocked(parsed.data, this.cfg.blockedCategories)) return null;
|
|
5098
|
+
return parsed.data;
|
|
4991
5099
|
} catch {
|
|
4992
5100
|
return void 0;
|
|
4993
5101
|
}
|
|
4994
5102
|
}
|
|
4995
5103
|
builtinTestAd(nowMs) {
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
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;
|
|
4999
5111
|
}
|
|
5000
5112
|
};
|
|
5001
5113
|
|
|
@@ -5256,6 +5368,9 @@ async function main() {
|
|
|
5256
5368
|
await fileLog(`ad fetch error: ${String(e)}`);
|
|
5257
5369
|
}
|
|
5258
5370
|
}
|
|
5371
|
+
const working = sessions.working();
|
|
5372
|
+
const lastStop = sessions.lastStop();
|
|
5373
|
+
const adDisplayable = working || lastStop !== null && nowMs - lastStop <= cfg.idleGraceSec * 1e3;
|
|
5259
5374
|
const probe = focus.current();
|
|
5260
5375
|
const sample = {
|
|
5261
5376
|
nowMs,
|
|
@@ -5264,7 +5379,7 @@ async function main() {
|
|
|
5264
5379
|
screenUnlocked: !probe.locked,
|
|
5265
5380
|
displayAwake: probe.screenAwake,
|
|
5266
5381
|
disabled,
|
|
5267
|
-
adPresent: currentAd !== null,
|
|
5382
|
+
adPresent: currentAd !== null && adDisplayable,
|
|
5268
5383
|
lastPromptAtMs: sessions.lastPrompt(),
|
|
5269
5384
|
adId: currentAd?.id ?? null,
|
|
5270
5385
|
focusDwellSec: cfg.focusDwellSec,
|
|
@@ -5285,11 +5400,12 @@ async function main() {
|
|
|
5285
5400
|
schemaVersion: DAEMON_STATE_SCHEMA_VERSION,
|
|
5286
5401
|
sessionLive,
|
|
5287
5402
|
disabled,
|
|
5288
|
-
adVisible: !disabled && sessionLive && currentAd !== null,
|
|
5403
|
+
adVisible: !disabled && sessionLive && currentAd !== null && adDisplayable,
|
|
5289
5404
|
port,
|
|
5290
5405
|
updatedAt: nowMs,
|
|
5291
5406
|
balanceCents: earnings.balanceCents,
|
|
5292
|
-
todayCents: earnings.todayCents
|
|
5407
|
+
todayCents: earnings.todayCents,
|
|
5408
|
+
working
|
|
5293
5409
|
};
|
|
5294
5410
|
writeDaemonState(state);
|
|
5295
5411
|
if (tickCount % SYNC_EVERY_TICKS === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adsinagents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
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",
|
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,6 +4133,12 @@ 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),
|
|
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([]),
|
|
4124
4142
|
/** AdLine server base URL the daemon polls for ads / sync / flags.
|
|
4125
4143
|
* Defaults to production — a fresh `npm i -g adsinagents` install must work
|
|
4126
4144
|
* with zero config. Local dev: set serverUrl in ~/.adsinagents/config.json. */
|
|
@@ -4139,10 +4157,18 @@ var ConfigSchema = external_exports.object({
|
|
|
4139
4157
|
rateCapSec: external_exports.number().int().positive().default(60),
|
|
4140
4158
|
/** Continuous focus seconds required before an impression verifies. */
|
|
4141
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),
|
|
4142
4166
|
/** Remote kill-switch flag URL (optional; falls back to serverUrl/api/flags). */
|
|
4143
4167
|
killSwitchUrl: external_exports.string().default(""),
|
|
4144
4168
|
/** Show a live earnings segment in the status line (separate from the ad). Off by default. */
|
|
4145
|
-
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")
|
|
4146
4172
|
});
|
|
4147
4173
|
var DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
4148
4174
|
var DaemonStateSchema = external_exports.object({
|
|
@@ -4161,7 +4187,10 @@ var DaemonStateSchema = external_exports.object({
|
|
|
4161
4187
|
/** Publisher lifetime balance, cents. From the last sync response. Optional. */
|
|
4162
4188
|
balanceCents: external_exports.number().int().nonnegative().default(0),
|
|
4163
4189
|
/** Earnings today, cents. From the last sync response. Optional. */
|
|
4164
|
-
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)
|
|
4165
4194
|
});
|
|
4166
4195
|
var ImpressionSchema = external_exports.object({
|
|
4167
4196
|
id: external_exports.string(),
|
|
@@ -4191,19 +4220,41 @@ var FocusProbeSchema = external_exports.object({
|
|
|
4191
4220
|
function osc8(url, text) {
|
|
4192
4221
|
return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
|
|
4193
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;
|
|
4194
4234
|
function renderAdSegment(ad, opts) {
|
|
4195
|
-
const
|
|
4196
|
-
const
|
|
4197
|
-
const
|
|
4198
|
-
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;
|
|
4199
4240
|
const clickUrl = `${opts.clickBase}/click/${encodeURIComponent(ad.id)}`;
|
|
4200
|
-
const linked = opts.supportsOsc8 ? osc8(clickUrl,
|
|
4201
|
-
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")}`;
|
|
4202
4243
|
}
|
|
4203
|
-
function renderEarningsSegment(balanceCents, todayCents) {
|
|
4204
|
-
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)}`);
|
|
4205
4247
|
return `${d(todayCents)} today \xB7 ${d(balanceCents)}`;
|
|
4206
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
|
+
}
|
|
4207
4258
|
function composeStatusLine(priorOutput, ...segments) {
|
|
4208
4259
|
const parts = [priorOutput.trim(), ...segments.map((s) => s.trim())].filter(Boolean);
|
|
4209
4260
|
return parts.join(" \u2502 ");
|
|
@@ -4217,6 +4268,32 @@ import { execFile, execFileSync } from "node:child_process";
|
|
|
4217
4268
|
import { promisify } from "node:util";
|
|
4218
4269
|
var pExecFile = promisify(execFile);
|
|
4219
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
|
+
|
|
4220
4297
|
// ../statusline/src/index.ts
|
|
4221
4298
|
var STATE_STALE_MS = 3e4;
|
|
4222
4299
|
function readJson(path) {
|
|
@@ -4227,11 +4304,15 @@ function readJson(path) {
|
|
|
4227
4304
|
return null;
|
|
4228
4305
|
}
|
|
4229
4306
|
}
|
|
4230
|
-
function
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
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)}`;
|
|
4235
4316
|
}
|
|
4236
4317
|
function priorStatusline(stdinJson) {
|
|
4237
4318
|
const cmd = process.env.ADSINAGENTS_PRIOR_STATUSLINE;
|
|
@@ -4265,20 +4346,23 @@ function main() {
|
|
|
4265
4346
|
const stale = Date.now() - state.updatedAt > STATE_STALE_MS;
|
|
4266
4347
|
const cfg = ConfigSchema.safeParse(readJson(PATHS.config));
|
|
4267
4348
|
const showEarnings = cfg.success && cfg.data.showEarnings;
|
|
4268
|
-
const
|
|
4269
|
-
|
|
4270
|
-
process.stdout.write(composeStatusLine(prior, earningsSeg) + "\n");
|
|
4271
|
-
return;
|
|
4272
|
-
}
|
|
4349
|
+
const theme = cfg.success ? cfg.data.theme : "plain";
|
|
4350
|
+
const earningsSeg = showEarnings && state.sessionLive && !stale ? renderEarningsSegment(state.balanceCents, state.todayCents, theme) : "";
|
|
4273
4351
|
const adParsed = AdSchema.safeParse(readJson(PATHS.currentAd));
|
|
4274
|
-
if (!adParsed.success) {
|
|
4275
|
-
|
|
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");
|
|
4276
4355
|
return;
|
|
4277
4356
|
}
|
|
4357
|
+
const clickBase = `http://127.0.0.1:${state.port}`;
|
|
4358
|
+
const serverUrl = cfg.success ? cfg.data.serverUrl : "";
|
|
4278
4359
|
const adSeg = renderAdSegment(adParsed.data, {
|
|
4279
|
-
clickBase
|
|
4280
|
-
supportsOsc8:
|
|
4360
|
+
clickBase,
|
|
4361
|
+
supportsOsc8: hostPreservesOsc8(),
|
|
4362
|
+
plainLink: plainClickLink(adParsed.data, serverUrl, clickBase),
|
|
4363
|
+
theme
|
|
4281
4364
|
});
|
|
4282
|
-
|
|
4365
|
+
const seg = state.working ? `${workingGlyph(Date.now(), theme)} ${adSeg}` : adSeg;
|
|
4366
|
+
process.stdout.write(composeStatusLine(prior, seg, earningsSeg) + "\n");
|
|
4283
4367
|
}
|
|
4284
4368
|
main();
|