adsinagents 0.1.5 → 0.1.8
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 +279 -43
- package/daemon/dist/main.js +65 -23
- package/native/build.sh +17 -0
- package/package.json +1 -1
- package/statusline/dist/index.js +7 -2
package/cli/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// ../cli/src/index.ts
|
|
11
|
-
import { dirname as dirname4, join as
|
|
11
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
12
12
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13
13
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
|
|
14
14
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
@@ -19,6 +19,7 @@ import { hostname } from "node:os";
|
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
var ADLINE_DIR = join(homedir(), ".adsinagents");
|
|
22
|
+
var HOOK_PORT = 8473;
|
|
22
23
|
var PATHS = {
|
|
23
24
|
root: ADLINE_DIR,
|
|
24
25
|
config: join(ADLINE_DIR, "config.json"),
|
|
@@ -4097,6 +4098,7 @@ function isCategory(value) {
|
|
|
4097
4098
|
}
|
|
4098
4099
|
|
|
4099
4100
|
// ../../shared/dist/schemas.js
|
|
4101
|
+
var AdSourceSchema = external_exports.enum(["gravity", "direct"]);
|
|
4100
4102
|
var AdSchema = external_exports.object({
|
|
4101
4103
|
/** Stable impression id for this rotation; idempotency key for firing. */
|
|
4102
4104
|
id: external_exports.string().min(1),
|
|
@@ -4113,7 +4115,7 @@ var AdSchema = external_exports.object({
|
|
|
4113
4115
|
/** Gravity click pixel (GET). Empty for direct campaigns. */
|
|
4114
4116
|
clickUrl: external_exports.string().default(""),
|
|
4115
4117
|
/** Source of demand. */
|
|
4116
|
-
source:
|
|
4118
|
+
source: AdSourceSchema,
|
|
4117
4119
|
/** Unix ms when this ad was fetched into cache. */
|
|
4118
4120
|
fetchedAt: external_exports.number().int(),
|
|
4119
4121
|
/**
|
|
@@ -4216,7 +4218,11 @@ var ImpressionSchema = external_exports.object({
|
|
|
4216
4218
|
verifiedAt: external_exports.number().int().nullable(),
|
|
4217
4219
|
impFired: external_exports.boolean(),
|
|
4218
4220
|
clicked: external_exports.boolean(),
|
|
4219
|
-
source:
|
|
4221
|
+
source: AdSourceSchema,
|
|
4222
|
+
/** Render surface the ad painted on. Only "statusline" is billable today
|
|
4223
|
+
* (spinner reach is un-billable and never writes a row). Recorded so the
|
|
4224
|
+
* publisher log can show *where* the ad showed. Older daemons omit it. */
|
|
4225
|
+
slot: external_exports.enum(["statusline", "spinner"]).default("statusline"),
|
|
4220
4226
|
/** Server serve-proof echoed back on sync. Empty for standalone/unverified. */
|
|
4221
4227
|
nonce: external_exports.string().default("")
|
|
4222
4228
|
});
|
|
@@ -4536,8 +4542,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4536
4542
|
else
|
|
4537
4543
|
priorValues.hooks = cur.hooks;
|
|
4538
4544
|
const mk = (path) => ({
|
|
4539
|
-
type: "
|
|
4540
|
-
|
|
4545
|
+
type: "command",
|
|
4546
|
+
command: hookCommand(path)
|
|
4541
4547
|
});
|
|
4542
4548
|
next.hooks = {
|
|
4543
4549
|
...hooks,
|
|
@@ -4562,8 +4568,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4562
4568
|
const backup = this.backup();
|
|
4563
4569
|
const { next, diff } = this.merge(plan);
|
|
4564
4570
|
this.write(next);
|
|
4565
|
-
this.writeSlashCommand();
|
|
4566
|
-
return { backup, diff };
|
|
4571
|
+
const slashCommand = this.writeSlashCommand();
|
|
4572
|
+
return { backup, diff, slashCommand };
|
|
4567
4573
|
}
|
|
4568
4574
|
/** Path of the in-session settings command, e.g. ~/.claude/commands/adsinagents.md. */
|
|
4569
4575
|
slashCommandPath() {
|
|
@@ -4573,14 +4579,22 @@ var ClaudeCodeAdapter = class {
|
|
|
4573
4579
|
* Install /adsinagents — the status line itself is display-only, so this is
|
|
4574
4580
|
* the in-session settings surface: the user types /adsinagents and Claude
|
|
4575
4581
|
* drives the CLI. File is fully ours (sentinel comment), safe to delete on
|
|
4576
|
-
* remove.
|
|
4582
|
+
* remove. Reconciles: writes if missing or stale (content drift), skips if
|
|
4583
|
+
* already correct. Returns the outcome so init can REPORT failures instead of
|
|
4584
|
+
* silently swallowing them (the old try/catch hid a real install bug — the
|
|
4585
|
+
* file never wrote and doctor was the only thing that ever noticed).
|
|
4577
4586
|
*/
|
|
4578
4587
|
writeSlashCommand() {
|
|
4588
|
+
const p = this.slashCommandPath();
|
|
4579
4589
|
try {
|
|
4580
|
-
|
|
4590
|
+
if (existsSync2(p) && readFileSync2(p, "utf8") === SLASH_COMMAND_MD) {
|
|
4591
|
+
return { ok: true };
|
|
4592
|
+
}
|
|
4581
4593
|
mkdirSync2(dirname2(p), { recursive: true });
|
|
4582
4594
|
writeFileSync2(p, SLASH_COMMAND_MD);
|
|
4583
|
-
|
|
4595
|
+
return { ok: true };
|
|
4596
|
+
} catch (e) {
|
|
4597
|
+
return { ok: false, reason: e.message };
|
|
4584
4598
|
}
|
|
4585
4599
|
}
|
|
4586
4600
|
removeInstall() {
|
|
@@ -4710,10 +4724,18 @@ function spinnerVerbsFor(ad) {
|
|
|
4710
4724
|
`Brought to you by ${brand}`
|
|
4711
4725
|
];
|
|
4712
4726
|
}
|
|
4727
|
+
function hookCommand(path) {
|
|
4728
|
+
return [
|
|
4729
|
+
`p=$(node -e 'try{process.stdout.write(String(require(process.env.HOME+"/.adsinagents/daemon.json").port||""))}catch(e){}' 2>/dev/null)`,
|
|
4730
|
+
`[ -n "$p" ] && curl -s --max-time 2 -X POST "http://127.0.0.1:$p${path}" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1`,
|
|
4731
|
+
`true`
|
|
4732
|
+
].join("; ");
|
|
4733
|
+
}
|
|
4713
4734
|
function appendHook(existing, entry) {
|
|
4714
4735
|
const arr = Array.isArray(existing) ? existing.slice() : existing ? [existing] : [];
|
|
4715
|
-
arr.
|
|
4716
|
-
|
|
4736
|
+
const userOwned = arr.filter((e) => !(e && typeof e === "object" && e[ADLINE_SENTINEL]));
|
|
4737
|
+
userOwned.push({ hooks: [entry], [ADLINE_SENTINEL]: true });
|
|
4738
|
+
return userOwned;
|
|
4717
4739
|
}
|
|
4718
4740
|
function renderDiff(before, after) {
|
|
4719
4741
|
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
@@ -4755,6 +4777,9 @@ function getAgent(name = "claude-code") {
|
|
|
4755
4777
|
|
|
4756
4778
|
// ../cli/src/settings.ts
|
|
4757
4779
|
var cc = () => getAgent("claude-code");
|
|
4780
|
+
function isInstalled() {
|
|
4781
|
+
return cc().isInstalled();
|
|
4782
|
+
}
|
|
4758
4783
|
function planMerge(plan) {
|
|
4759
4784
|
return cc().planInstall(plan);
|
|
4760
4785
|
}
|
|
@@ -4798,6 +4823,10 @@ function buildAndInstallNative() {
|
|
|
4798
4823
|
if (existsSync3(src)) {
|
|
4799
4824
|
copyFileSync2(src, dest);
|
|
4800
4825
|
chmodSync2(dest, 493);
|
|
4826
|
+
try {
|
|
4827
|
+
execFileSync3("codesign", ["-f", "--sign", "-", dest], { stdio: "pipe" });
|
|
4828
|
+
} catch {
|
|
4829
|
+
}
|
|
4801
4830
|
installed.push(dest);
|
|
4802
4831
|
}
|
|
4803
4832
|
}
|
|
@@ -4806,6 +4835,9 @@ function buildAndInstallNative() {
|
|
|
4806
4835
|
|
|
4807
4836
|
// ../cli/src/doctor.ts
|
|
4808
4837
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
4838
|
+
import { spawnSync } from "node:child_process";
|
|
4839
|
+
import { homedir as homedir3 } from "node:os";
|
|
4840
|
+
import { join as join5 } from "node:path";
|
|
4809
4841
|
function daemonAlive() {
|
|
4810
4842
|
if (!existsSync4(PATHS.daemonInfo)) return { ok: false, detail: "no daemon.json (not started)" };
|
|
4811
4843
|
try {
|
|
@@ -4857,8 +4889,133 @@ async function runDoctor() {
|
|
|
4857
4889
|
ok: key !== null,
|
|
4858
4890
|
detail: key ? "present (Keychain/file)" : "none \u2014 run `adsinagents login`"
|
|
4859
4891
|
});
|
|
4892
|
+
checks.push(checkStatusLine());
|
|
4893
|
+
checks.push(checkHookReachable());
|
|
4894
|
+
checks.push(checkSlashCommand());
|
|
4860
4895
|
return checks;
|
|
4861
4896
|
}
|
|
4897
|
+
function checkStatusLine() {
|
|
4898
|
+
const name = "Status line renders";
|
|
4899
|
+
const settingsPath = join5(claudeConfigDir(), "settings.json");
|
|
4900
|
+
if (!existsSync4(settingsPath)) {
|
|
4901
|
+
return { name, ok: true, detail: "no statusLine command configured" };
|
|
4902
|
+
}
|
|
4903
|
+
let settings;
|
|
4904
|
+
try {
|
|
4905
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf8"));
|
|
4906
|
+
} catch {
|
|
4907
|
+
return { name, ok: true, detail: "settings.json unreadable (skipped)" };
|
|
4908
|
+
}
|
|
4909
|
+
const cmd = settings.statusLine?.command;
|
|
4910
|
+
if (!cmd) {
|
|
4911
|
+
return { name, ok: true, detail: "no statusLine command configured" };
|
|
4912
|
+
}
|
|
4913
|
+
let hasAd = false;
|
|
4914
|
+
try {
|
|
4915
|
+
if (existsSync4(PATHS.currentAd)) {
|
|
4916
|
+
const ad = JSON.parse(readFileSync3(PATHS.currentAd, "utf8"));
|
|
4917
|
+
hasAd = !!(ad.adId && String(ad.adId).trim().length > 0);
|
|
4918
|
+
}
|
|
4919
|
+
} catch {
|
|
4920
|
+
hasAd = false;
|
|
4921
|
+
}
|
|
4922
|
+
const parts = cmd.split(" ");
|
|
4923
|
+
const exe = parts[0];
|
|
4924
|
+
const args = parts.slice(1);
|
|
4925
|
+
const sessionJson = JSON.stringify({
|
|
4926
|
+
session_id: "doctor",
|
|
4927
|
+
workspace: { current_dir: "/tmp" },
|
|
4928
|
+
model: { display_name: "x" }
|
|
4929
|
+
});
|
|
4930
|
+
const result = spawnSync(exe, args, {
|
|
4931
|
+
input: sessionJson,
|
|
4932
|
+
encoding: "utf8",
|
|
4933
|
+
timeout: 3e3
|
|
4934
|
+
});
|
|
4935
|
+
if (result.error) {
|
|
4936
|
+
const errCode = result.error.code;
|
|
4937
|
+
const msg = errCode === "ENOENT" ? "not found" : result.error.message;
|
|
4938
|
+
return { name, ok: false, detail: `statusLine command not found or not executable: ${exe} (${msg})` };
|
|
4939
|
+
}
|
|
4940
|
+
if (result.status !== 0) {
|
|
4941
|
+
return { name, ok: false, detail: `statusLine command exited with error (code ${result.status ?? "?"})` };
|
|
4942
|
+
}
|
|
4943
|
+
const stdout = (result.stdout ?? "").trim();
|
|
4944
|
+
if (hasAd && stdout.length === 0) {
|
|
4945
|
+
return { name, ok: false, detail: "statusLine command produced no output (ad exists but nothing rendered)" };
|
|
4946
|
+
}
|
|
4947
|
+
if (stdout.length > 0) {
|
|
4948
|
+
return { name, ok: true, detail: `rendered ${stdout.length} chars` };
|
|
4949
|
+
}
|
|
4950
|
+
return { name, ok: true, detail: "ok (no ad to render right now)" };
|
|
4951
|
+
}
|
|
4952
|
+
function checkHookReachable() {
|
|
4953
|
+
const name = "Hook reachable";
|
|
4954
|
+
let daemonPort = null;
|
|
4955
|
+
if (existsSync4(PATHS.daemonInfo)) {
|
|
4956
|
+
try {
|
|
4957
|
+
const info = JSON.parse(readFileSync3(PATHS.daemonInfo, "utf8"));
|
|
4958
|
+
daemonPort = info.port ?? null;
|
|
4959
|
+
} catch {
|
|
4960
|
+
daemonPort = null;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
if (daemonPort === null) {
|
|
4964
|
+
return { name, ok: true, detail: "daemon not running (skipped)" };
|
|
4965
|
+
}
|
|
4966
|
+
const settingsPath = join5(claudeConfigDir(), "settings.json");
|
|
4967
|
+
let hookPort = null;
|
|
4968
|
+
if (existsSync4(settingsPath)) {
|
|
4969
|
+
try {
|
|
4970
|
+
const s = JSON.parse(readFileSync3(settingsPath, "utf8"));
|
|
4971
|
+
const hooksStr = JSON.stringify(s.hooks ?? {});
|
|
4972
|
+
const m = /http:\/\/127\.0\.0\.1:(\d+)/.exec(hooksStr);
|
|
4973
|
+
if (m) {
|
|
4974
|
+
hookPort = Number(m[1]);
|
|
4975
|
+
} else {
|
|
4976
|
+
hookPort = daemonPort;
|
|
4977
|
+
}
|
|
4978
|
+
} catch {
|
|
4979
|
+
hookPort = daemonPort;
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
const result = spawnSync(
|
|
4983
|
+
"node",
|
|
4984
|
+
[
|
|
4985
|
+
"-e",
|
|
4986
|
+
`const h=require("node:http");const r=h.get("http://127.0.0.1:${daemonPort}/health",res=>{let b="";res.on("data",d=>b+=d);res.on("end",()=>{process.stdout.write(b);process.exit(0)});});r.on("error",e=>{process.exit(1)});r.setTimeout(2000,()=>{r.destroy();process.exit(2)});`
|
|
4987
|
+
],
|
|
4988
|
+
{ encoding: "utf8", timeout: 4e3 }
|
|
4989
|
+
);
|
|
4990
|
+
if (result.status !== 0) {
|
|
4991
|
+
return { name, ok: false, detail: `daemon unreachable on port ${daemonPort}` };
|
|
4992
|
+
}
|
|
4993
|
+
if (hookPort !== null && hookPort !== daemonPort) {
|
|
4994
|
+
return {
|
|
4995
|
+
name,
|
|
4996
|
+
ok: false,
|
|
4997
|
+
detail: `hooks point at port ${hookPort} but daemon is on ${daemonPort}`
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
return { name, ok: true, detail: `daemon OK on port ${daemonPort}` };
|
|
5001
|
+
}
|
|
5002
|
+
function checkSlashCommand() {
|
|
5003
|
+
const name = "Slash command installed";
|
|
5004
|
+
const SENTINEL = "<!-- adsinagents:managed -->";
|
|
5005
|
+
const cmdPath = join5(claudeConfigDir(), "commands", "adsinagents.md");
|
|
5006
|
+
if (!existsSync4(cmdPath)) {
|
|
5007
|
+
return { name, ok: false, detail: "run `adsinagents init` to reinstall the /adsinagents command" };
|
|
5008
|
+
}
|
|
5009
|
+
try {
|
|
5010
|
+
const contents = readFileSync3(cmdPath, "utf8");
|
|
5011
|
+
if (!contents.includes(SENTINEL)) {
|
|
5012
|
+
return { name, ok: false, detail: "run `adsinagents init` to reinstall the /adsinagents command" };
|
|
5013
|
+
}
|
|
5014
|
+
} catch {
|
|
5015
|
+
return { name, ok: false, detail: "run `adsinagents init` to reinstall the /adsinagents command" };
|
|
5016
|
+
}
|
|
5017
|
+
return { name, ok: true, detail: `present at ${cmdPath.replace(homedir3(), "~")}` };
|
|
5018
|
+
}
|
|
4862
5019
|
function printDoctor(checks) {
|
|
4863
5020
|
for (const c of checks) {
|
|
4864
5021
|
const mark = c.ok ? "\u2713" : "\u2717";
|
|
@@ -4867,19 +5024,73 @@ function printDoctor(checks) {
|
|
|
4867
5024
|
}
|
|
4868
5025
|
}
|
|
4869
5026
|
|
|
5027
|
+
// ../cli/src/init-ui.ts
|
|
5028
|
+
var useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
5029
|
+
var ID = (s) => s;
|
|
5030
|
+
var dim = useColor ? (s) => `\x1B[2m${s}\x1B[0m` : ID;
|
|
5031
|
+
var bold = useColor ? (s) => `\x1B[1m${s}\x1B[0m` : ID;
|
|
5032
|
+
var green = useColor ? (s) => `\x1B[32m${s}\x1B[0m` : ID;
|
|
5033
|
+
var amber = useColor ? (s) => `\x1B[38;5;215m${s}\x1B[0m` : ID;
|
|
5034
|
+
function renderInitSummary(data) {
|
|
5035
|
+
const lines = [];
|
|
5036
|
+
const tick = green("\u2713");
|
|
5037
|
+
const warn = amber("!");
|
|
5038
|
+
const dot = dim("\xB7");
|
|
5039
|
+
if (data.firstRun) {
|
|
5040
|
+
lines.push(`${amber("\u258C")} ${bold("AdsInAgents")} ${dim("\u2014 sponsored status line for Claude Code")}`);
|
|
5041
|
+
lines.push("");
|
|
5042
|
+
const labelW = maxLabelWidth(data.steps);
|
|
5043
|
+
for (const step of data.steps) {
|
|
5044
|
+
const icon = step.ok ? tick : warn;
|
|
5045
|
+
const label = step.label.padEnd(labelW);
|
|
5046
|
+
const note = step.note ? ` ${dim("(" + step.note + ")")}` : "";
|
|
5047
|
+
lines.push(` ${icon} ${label} ${dot} ${step.detail}${note}`);
|
|
5048
|
+
}
|
|
5049
|
+
lines.push("");
|
|
5050
|
+
lines.push(` ${bold("You're live.")} Restart Claude Code to see your first ad.`);
|
|
5051
|
+
lines.push("");
|
|
5052
|
+
lines.push(` ${dim("Verify ")} adsinagents doctor`);
|
|
5053
|
+
if (data.claimUrl) {
|
|
5054
|
+
lines.push(` ${dim("Get paid")} ${amber(data.claimUrl)}`);
|
|
5055
|
+
}
|
|
5056
|
+
lines.push("");
|
|
5057
|
+
lines.push(` ${dim("Ads are test-only for now.")}`);
|
|
5058
|
+
} else {
|
|
5059
|
+
lines.push(`${amber("\u258C")} ${bold("AdsInAgents")} ${dim("\u2014 already set up, refreshed")}`);
|
|
5060
|
+
lines.push("");
|
|
5061
|
+
const labelW = maxLabelWidth(data.steps);
|
|
5062
|
+
for (const step of data.steps) {
|
|
5063
|
+
const icon = step.ok ? tick : warn;
|
|
5064
|
+
const label = step.label.padEnd(labelW);
|
|
5065
|
+
let detail = step.detail;
|
|
5066
|
+
if (step.label === "Device" && data.balanceCents !== void 0) {
|
|
5067
|
+
const dollars = `$${(data.balanceCents / 100).toFixed(2)}`;
|
|
5068
|
+
detail = `${detail} ${dot} balance ${green(dollars)}`;
|
|
5069
|
+
}
|
|
5070
|
+
lines.push(` ${icon} ${label} ${dot} ${detail}`);
|
|
5071
|
+
}
|
|
5072
|
+
lines.push("");
|
|
5073
|
+
lines.push(` ${dim("Nothing changed.")} Run ${bold("adsinagents doctor")} to check status.`);
|
|
5074
|
+
}
|
|
5075
|
+
return lines.join("\n") + "\n";
|
|
5076
|
+
}
|
|
5077
|
+
function maxLabelWidth(steps) {
|
|
5078
|
+
return steps.reduce((m, s) => Math.max(m, s.label.length), 0);
|
|
5079
|
+
}
|
|
5080
|
+
|
|
4870
5081
|
// ../cli/src/index.ts
|
|
4871
5082
|
var HERE2 = dirname4(fileURLToPath2(import.meta.url));
|
|
4872
5083
|
function readVersion() {
|
|
4873
5084
|
for (const rel of ["../../package.json", "../package.json"]) {
|
|
4874
5085
|
try {
|
|
4875
|
-
return JSON.parse(readFileSync4(
|
|
5086
|
+
return JSON.parse(readFileSync4(join6(HERE2, rel), "utf8")).version ?? "0.0.0";
|
|
4876
5087
|
} catch {
|
|
4877
5088
|
}
|
|
4878
5089
|
}
|
|
4879
5090
|
return "0.0.0";
|
|
4880
5091
|
}
|
|
4881
5092
|
var VERSION = readVersion();
|
|
4882
|
-
var DAEMON_ENTRY =
|
|
5093
|
+
var DAEMON_ENTRY = join6(HERE2, "..", "..", "daemon", "dist", "main.js");
|
|
4883
5094
|
function parseFlags(argv) {
|
|
4884
5095
|
const positional = [];
|
|
4885
5096
|
const flags = /* @__PURE__ */ new Map();
|
|
@@ -4895,11 +5106,11 @@ function parseFlags(argv) {
|
|
|
4895
5106
|
return { positional, flags };
|
|
4896
5107
|
}
|
|
4897
5108
|
function buildPlan(placement, spinnerVerbs) {
|
|
4898
|
-
const nativeStatusline =
|
|
4899
|
-
const statuslineCommand = existsSync5(nativeStatusline) ? nativeStatusline : `node ${
|
|
5109
|
+
const nativeStatusline = join6(dirname4(PATHS.frontmostBin), "adsinagents-statusline");
|
|
5110
|
+
const statuslineCommand = existsSync5(nativeStatusline) ? nativeStatusline : `node ${join6(HERE2, "..", "..", "statusline", "dist", "index.js")}`;
|
|
4900
5111
|
return {
|
|
4901
5112
|
statuslineCommand,
|
|
4902
|
-
hookBaseUrl:
|
|
5113
|
+
hookBaseUrl: `http://127.0.0.1:${HOOK_PORT}`,
|
|
4903
5114
|
placement,
|
|
4904
5115
|
spinnerVerbs,
|
|
4905
5116
|
version: VERSION
|
|
@@ -4916,17 +5127,33 @@ async function cmdInit(flags) {
|
|
|
4916
5127
|
process.stdout.write(diff + "\n");
|
|
4917
5128
|
return;
|
|
4918
5129
|
}
|
|
5130
|
+
const firstRun = !isInstalled();
|
|
5131
|
+
const steps = [];
|
|
4919
5132
|
const native = buildAndInstallNative();
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
`
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
5133
|
+
steps.push({
|
|
5134
|
+
ok: native.ok,
|
|
5135
|
+
label: "Native helpers",
|
|
5136
|
+
detail: native.ok ? `${native.installed.length} installed` : `degraded mode`,
|
|
5137
|
+
note: native.ok ? void 0 : native.reason
|
|
5138
|
+
});
|
|
5139
|
+
const { backup, slashCommand } = applyMerge(plan);
|
|
5140
|
+
steps.push({
|
|
5141
|
+
ok: true,
|
|
5142
|
+
label: "Settings",
|
|
5143
|
+
detail: "merged",
|
|
5144
|
+
note: backup ? "backup saved" : void 0
|
|
5145
|
+
});
|
|
5146
|
+
steps.push({
|
|
5147
|
+
ok: slashCommand.ok,
|
|
5148
|
+
label: "Slash command",
|
|
5149
|
+
detail: slashCommand.ok ? "/adsinagents ready" : "not installed",
|
|
5150
|
+
note: slashCommand.ok ? void 0 : slashCommand.reason
|
|
5151
|
+
});
|
|
4928
5152
|
const cfg = loadConfigSafe();
|
|
4929
5153
|
let claimUrl = "";
|
|
5154
|
+
let deviceStepOk = true;
|
|
5155
|
+
let deviceDetail = "";
|
|
5156
|
+
let deviceNote;
|
|
4930
5157
|
if (!cfg.deviceToken) {
|
|
4931
5158
|
try {
|
|
4932
5159
|
const deviceId = `dev_${randomBytes(8).toString("hex")}`;
|
|
@@ -4939,34 +5166,43 @@ async function cmdInit(flags) {
|
|
|
4939
5166
|
const reg = await res.json();
|
|
4940
5167
|
writeConfigPatch({ deviceId: reg.deviceId, deviceToken: reg.deviceToken, claimToken: reg.claimToken });
|
|
4941
5168
|
claimUrl = reg.claimUrl;
|
|
4942
|
-
|
|
5169
|
+
deviceDetail = "registered, earning anonymously";
|
|
4943
5170
|
} catch (e) {
|
|
4944
|
-
|
|
4945
|
-
|
|
5171
|
+
deviceStepOk = false;
|
|
5172
|
+
deviceDetail = "registration skipped";
|
|
5173
|
+
deviceNote = e.message;
|
|
4946
5174
|
}
|
|
4947
5175
|
} else {
|
|
4948
|
-
|
|
5176
|
+
deviceDetail = "registered";
|
|
5177
|
+
if (cfg.claimToken) claimUrl = `${cfg.serverUrl}/claim?t=${cfg.claimToken}`;
|
|
4949
5178
|
}
|
|
5179
|
+
steps.push({ ok: deviceStepOk, label: "Device", detail: deviceDetail, note: deviceNote });
|
|
4950
5180
|
const platform = getPlatform();
|
|
4951
5181
|
const nodeBin = process.execPath;
|
|
4952
|
-
|
|
4953
|
-
|
|
5182
|
+
let daemonOk = true;
|
|
5183
|
+
let daemonNote;
|
|
5184
|
+
try {
|
|
5185
|
+
await platform.installAutostart(nodeBin, [DAEMON_ENTRY]);
|
|
5186
|
+
} catch (e) {
|
|
5187
|
+
daemonOk = false;
|
|
5188
|
+
daemonNote = e.message;
|
|
5189
|
+
}
|
|
5190
|
+
steps.push({ ok: daemonOk, label: "Daemon", detail: daemonOk ? "running" : "not loaded", note: daemonNote });
|
|
4954
5191
|
if (flags.has("show-earnings")) {
|
|
4955
5192
|
writeConfigPatch({ showEarnings: flags.get("show-earnings") !== "false" });
|
|
4956
|
-
process.stdout.write("\u2713 live earnings segment enabled\n");
|
|
4957
5193
|
}
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
${claimUrl}
|
|
4966
|
-
(or run \`adsinagents claim\` anytime to reprint the link)
|
|
4967
|
-
`
|
|
4968
|
-
);
|
|
5194
|
+
let balanceCents;
|
|
5195
|
+
if (!firstRun && existsSync5(PATHS.daemonState)) {
|
|
5196
|
+
try {
|
|
5197
|
+
const state = JSON.parse(readFileSync4(PATHS.daemonState, "utf8"));
|
|
5198
|
+
if (typeof state.balanceCents === "number") balanceCents = state.balanceCents;
|
|
5199
|
+
} catch {
|
|
5200
|
+
}
|
|
4969
5201
|
}
|
|
5202
|
+
const summarySteps = firstRun ? steps : steps.filter((s) => s.label !== "Native helpers");
|
|
5203
|
+
process.stdout.write(
|
|
5204
|
+
renderInitSummary({ firstRun, steps: summarySteps, claimUrl: claimUrl || void 0, balanceCents })
|
|
5205
|
+
);
|
|
4970
5206
|
}
|
|
4971
5207
|
async function cmdRemove() {
|
|
4972
5208
|
const platform = getPlatform();
|
package/daemon/dist/main.js
CHANGED
|
@@ -14,6 +14,7 @@ import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
var ADLINE_DIR = join(homedir(), ".adsinagents");
|
|
17
|
+
var HOOK_PORT = 8473;
|
|
17
18
|
var PATHS = {
|
|
18
19
|
root: ADLINE_DIR,
|
|
19
20
|
config: join(ADLINE_DIR, "config.json"),
|
|
@@ -4102,6 +4103,9 @@ function matchesBlocked(ad, blocked) {
|
|
|
4102
4103
|
}
|
|
4103
4104
|
|
|
4104
4105
|
// ../../shared/dist/schemas.js
|
|
4106
|
+
var AdSourceSchema = external_exports.enum(["gravity", "direct"]);
|
|
4107
|
+
var GRAVITY_CAMPAIGN_ID = "gravity";
|
|
4108
|
+
var FALLBACK_AD_URL = "https://trygravity.ai";
|
|
4105
4109
|
var AdSchema = external_exports.object({
|
|
4106
4110
|
/** Stable impression id for this rotation; idempotency key for firing. */
|
|
4107
4111
|
id: external_exports.string().min(1),
|
|
@@ -4118,7 +4122,7 @@ var AdSchema = external_exports.object({
|
|
|
4118
4122
|
/** Gravity click pixel (GET). Empty for direct campaigns. */
|
|
4119
4123
|
clickUrl: external_exports.string().default(""),
|
|
4120
4124
|
/** Source of demand. */
|
|
4121
|
-
source:
|
|
4125
|
+
source: AdSourceSchema,
|
|
4122
4126
|
/** Unix ms when this ad was fetched into cache. */
|
|
4123
4127
|
fetchedAt: external_exports.number().int(),
|
|
4124
4128
|
/**
|
|
@@ -4222,7 +4226,11 @@ var ImpressionSchema = external_exports.object({
|
|
|
4222
4226
|
verifiedAt: external_exports.number().int().nullable(),
|
|
4223
4227
|
impFired: external_exports.boolean(),
|
|
4224
4228
|
clicked: external_exports.boolean(),
|
|
4225
|
-
source:
|
|
4229
|
+
source: AdSourceSchema,
|
|
4230
|
+
/** Render surface the ad painted on. Only "statusline" is billable today
|
|
4231
|
+
* (spinner reach is un-billable and never writes a row). Recorded so the
|
|
4232
|
+
* publisher log can show *where* the ad showed. Older daemons omit it. */
|
|
4233
|
+
slot: external_exports.enum(["statusline", "spinner"]).default("statusline"),
|
|
4226
4234
|
/** Server serve-proof echoed back on sync. Empty for standalone/unverified. */
|
|
4227
4235
|
nonce: external_exports.string().default("")
|
|
4228
4236
|
});
|
|
@@ -4542,8 +4550,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4542
4550
|
else
|
|
4543
4551
|
priorValues.hooks = cur.hooks;
|
|
4544
4552
|
const mk = (path) => ({
|
|
4545
|
-
type: "
|
|
4546
|
-
|
|
4553
|
+
type: "command",
|
|
4554
|
+
command: hookCommand(path)
|
|
4547
4555
|
});
|
|
4548
4556
|
next.hooks = {
|
|
4549
4557
|
...hooks,
|
|
@@ -4568,8 +4576,8 @@ var ClaudeCodeAdapter = class {
|
|
|
4568
4576
|
const backup = this.backup();
|
|
4569
4577
|
const { next, diff } = this.merge(plan);
|
|
4570
4578
|
this.write(next);
|
|
4571
|
-
this.writeSlashCommand();
|
|
4572
|
-
return { backup, diff };
|
|
4579
|
+
const slashCommand = this.writeSlashCommand();
|
|
4580
|
+
return { backup, diff, slashCommand };
|
|
4573
4581
|
}
|
|
4574
4582
|
/** Path of the in-session settings command, e.g. ~/.claude/commands/adsinagents.md. */
|
|
4575
4583
|
slashCommandPath() {
|
|
@@ -4579,14 +4587,22 @@ var ClaudeCodeAdapter = class {
|
|
|
4579
4587
|
* Install /adsinagents — the status line itself is display-only, so this is
|
|
4580
4588
|
* the in-session settings surface: the user types /adsinagents and Claude
|
|
4581
4589
|
* drives the CLI. File is fully ours (sentinel comment), safe to delete on
|
|
4582
|
-
* remove.
|
|
4590
|
+
* remove. Reconciles: writes if missing or stale (content drift), skips if
|
|
4591
|
+
* already correct. Returns the outcome so init can REPORT failures instead of
|
|
4592
|
+
* silently swallowing them (the old try/catch hid a real install bug — the
|
|
4593
|
+
* file never wrote and doctor was the only thing that ever noticed).
|
|
4583
4594
|
*/
|
|
4584
4595
|
writeSlashCommand() {
|
|
4596
|
+
const p = this.slashCommandPath();
|
|
4585
4597
|
try {
|
|
4586
|
-
|
|
4598
|
+
if (existsSync2(p) && readFileSync2(p, "utf8") === SLASH_COMMAND_MD) {
|
|
4599
|
+
return { ok: true };
|
|
4600
|
+
}
|
|
4587
4601
|
mkdirSync2(dirname2(p), { recursive: true });
|
|
4588
4602
|
writeFileSync2(p, SLASH_COMMAND_MD);
|
|
4589
|
-
|
|
4603
|
+
return { ok: true };
|
|
4604
|
+
} catch (e) {
|
|
4605
|
+
return { ok: false, reason: e.message };
|
|
4590
4606
|
}
|
|
4591
4607
|
}
|
|
4592
4608
|
removeInstall() {
|
|
@@ -4716,10 +4732,18 @@ function spinnerVerbsFor(ad) {
|
|
|
4716
4732
|
`Brought to you by ${brand}`
|
|
4717
4733
|
];
|
|
4718
4734
|
}
|
|
4735
|
+
function hookCommand(path) {
|
|
4736
|
+
return [
|
|
4737
|
+
`p=$(node -e 'try{process.stdout.write(String(require(process.env.HOME+"/.adsinagents/daemon.json").port||""))}catch(e){}' 2>/dev/null)`,
|
|
4738
|
+
`[ -n "$p" ] && curl -s --max-time 2 -X POST "http://127.0.0.1:$p${path}" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1`,
|
|
4739
|
+
`true`
|
|
4740
|
+
].join("; ");
|
|
4741
|
+
}
|
|
4719
4742
|
function appendHook(existing, entry) {
|
|
4720
4743
|
const arr = Array.isArray(existing) ? existing.slice() : existing ? [existing] : [];
|
|
4721
|
-
arr.
|
|
4722
|
-
|
|
4744
|
+
const userOwned = arr.filter((e) => !(e && typeof e === "object" && e[ADLINE_SENTINEL]));
|
|
4745
|
+
userOwned.push({ hooks: [entry], [ADLINE_SENTINEL]: true });
|
|
4746
|
+
return userOwned;
|
|
4723
4747
|
}
|
|
4724
4748
|
function renderDiff(before, after) {
|
|
4725
4749
|
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
@@ -4818,6 +4842,7 @@ var Ledger = class {
|
|
|
4818
4842
|
imp_fired INTEGER NOT NULL DEFAULT 0,
|
|
4819
4843
|
clicked INTEGER NOT NULL DEFAULT 0,
|
|
4820
4844
|
nonce TEXT NOT NULL DEFAULT '',
|
|
4845
|
+
slot TEXT NOT NULL DEFAULT 'statusline',
|
|
4821
4846
|
synced INTEGER NOT NULL DEFAULT 0
|
|
4822
4847
|
);
|
|
4823
4848
|
CREATE INDEX IF NOT EXISTS idx_imp_unsynced ON impressions(synced);
|
|
@@ -4827,14 +4852,17 @@ var Ledger = class {
|
|
|
4827
4852
|
if (!cols.some((c) => c.name === "nonce")) {
|
|
4828
4853
|
this.db.exec(`ALTER TABLE impressions ADD COLUMN nonce TEXT NOT NULL DEFAULT ''`);
|
|
4829
4854
|
}
|
|
4855
|
+
if (!cols.some((c) => c.name === "slot")) {
|
|
4856
|
+
this.db.exec(`ALTER TABLE impressions ADD COLUMN slot TEXT NOT NULL DEFAULT 'statusline'`);
|
|
4857
|
+
}
|
|
4830
4858
|
}
|
|
4831
4859
|
/** Record an impression starting (idempotent — ignore if id already present). */
|
|
4832
4860
|
startImpression(row) {
|
|
4833
4861
|
this.db.prepare(
|
|
4834
4862
|
`INSERT OR IGNORE INTO impressions
|
|
4835
|
-
(id, ad_id, campaign_id, session_id, source, started_at, nonce)
|
|
4836
|
-
VALUES (@id, @adId, @campaignId, @sessionId, @source, @startedAt, @nonce)`
|
|
4837
|
-
).run({ ...row, nonce: row.nonce ?? "" });
|
|
4863
|
+
(id, ad_id, campaign_id, session_id, source, started_at, nonce, slot)
|
|
4864
|
+
VALUES (@id, @adId, @campaignId, @sessionId, @source, @startedAt, @nonce, @slot)`
|
|
4865
|
+
).run({ ...row, nonce: row.nonce ?? "", slot: row.slot ?? "statusline" });
|
|
4838
4866
|
}
|
|
4839
4867
|
/**
|
|
4840
4868
|
* Mark verified + fired. Persist-before-fire: call this, then fire impUrl.
|
|
@@ -4874,7 +4902,7 @@ var Ledger = class {
|
|
|
4874
4902
|
const rows = this.db.prepare(
|
|
4875
4903
|
`SELECT id, ad_id as adId, campaign_id as campaignId, session_id as sessionId,
|
|
4876
4904
|
source, started_at as startedAt, verified_at as verifiedAt,
|
|
4877
|
-
imp_fired as impFired, clicked, nonce
|
|
4905
|
+
imp_fired as impFired, clicked, nonce, slot
|
|
4878
4906
|
FROM impressions WHERE synced = 0 ORDER BY started_at LIMIT @limit`
|
|
4879
4907
|
).all({ limit });
|
|
4880
4908
|
return rows.map((r) => ({
|
|
@@ -4887,6 +4915,7 @@ var Ledger = class {
|
|
|
4887
4915
|
verifiedAt: r.verifiedAt ?? null,
|
|
4888
4916
|
impFired: !!r.impFired,
|
|
4889
4917
|
clicked: !!r.clicked,
|
|
4918
|
+
slot: r.slot ?? "statusline",
|
|
4890
4919
|
nonce: r.nonce ?? ""
|
|
4891
4920
|
}));
|
|
4892
4921
|
}
|
|
@@ -5027,7 +5056,7 @@ function newImpId(nowMs) {
|
|
|
5027
5056
|
}
|
|
5028
5057
|
var BUILTIN_TEST_ADS = [
|
|
5029
5058
|
{
|
|
5030
|
-
campaignId:
|
|
5059
|
+
campaignId: GRAVITY_CAMPAIGN_ID,
|
|
5031
5060
|
text: "Ship faster \u2014 catch errors before users do",
|
|
5032
5061
|
brandName: "Sentry",
|
|
5033
5062
|
url: "https://sentry.io",
|
|
@@ -5036,7 +5065,7 @@ var BUILTIN_TEST_ADS = [
|
|
|
5036
5065
|
source: "gravity"
|
|
5037
5066
|
},
|
|
5038
5067
|
{
|
|
5039
|
-
campaignId:
|
|
5068
|
+
campaignId: GRAVITY_CAMPAIGN_ID,
|
|
5040
5069
|
text: "Postgres without the ops \u2014 serverless branches",
|
|
5041
5070
|
brandName: "Neon",
|
|
5042
5071
|
url: "https://neon.tech",
|
|
@@ -5045,7 +5074,7 @@ var BUILTIN_TEST_ADS = [
|
|
|
5045
5074
|
source: "gravity"
|
|
5046
5075
|
},
|
|
5047
5076
|
{
|
|
5048
|
-
campaignId:
|
|
5077
|
+
campaignId: GRAVITY_CAMPAIGN_ID,
|
|
5049
5078
|
text: "Deploy in seconds, scale to zero",
|
|
5050
5079
|
brandName: "Vercel",
|
|
5051
5080
|
url: "https://vercel.com",
|
|
@@ -5133,7 +5162,7 @@ function startServer(deps) {
|
|
|
5133
5162
|
const ad = readCurrentAd();
|
|
5134
5163
|
deps.ledger.markClicked(impId);
|
|
5135
5164
|
if (ad && ad.id === impId && ad.clickUrl) deps.firePixel(ad.clickUrl);
|
|
5136
|
-
const dest = ad?.url ||
|
|
5165
|
+
const dest = ad?.url || FALLBACK_AD_URL;
|
|
5137
5166
|
res.writeHead(302, { location: dest });
|
|
5138
5167
|
res.end();
|
|
5139
5168
|
return;
|
|
@@ -5172,12 +5201,22 @@ function startServer(deps) {
|
|
|
5172
5201
|
res.end("error");
|
|
5173
5202
|
}
|
|
5174
5203
|
});
|
|
5175
|
-
return new Promise((resolve) => {
|
|
5176
|
-
|
|
5204
|
+
return new Promise((resolve, reject) => {
|
|
5205
|
+
const onListening = () => {
|
|
5177
5206
|
const addr = server.address();
|
|
5178
5207
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
5179
5208
|
resolve({ server, port });
|
|
5180
|
-
}
|
|
5209
|
+
};
|
|
5210
|
+
const onError = (err) => {
|
|
5211
|
+
if (err.code === "EADDRINUSE") {
|
|
5212
|
+
server.once("error", reject);
|
|
5213
|
+
server.listen(0, "127.0.0.1", onListening);
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
reject(err);
|
|
5217
|
+
};
|
|
5218
|
+
server.once("error", onError);
|
|
5219
|
+
server.listen(HOOK_PORT, "127.0.0.1", onListening);
|
|
5181
5220
|
});
|
|
5182
5221
|
}
|
|
5183
5222
|
|
|
@@ -5361,7 +5400,10 @@ async function main() {
|
|
|
5361
5400
|
sessionId: sid,
|
|
5362
5401
|
source: ad.source,
|
|
5363
5402
|
startedAt: nowMs,
|
|
5364
|
-
nonce: ad.nonce
|
|
5403
|
+
nonce: ad.nonce,
|
|
5404
|
+
// Only the status line writes a billable impression — spinner reach
|
|
5405
|
+
// is un-billable and never reaches this path. Recorded for the log.
|
|
5406
|
+
slot: "statusline"
|
|
5365
5407
|
});
|
|
5366
5408
|
}
|
|
5367
5409
|
} catch (e) {
|
package/native/build.sh
CHANGED
|
@@ -7,6 +7,13 @@ set -euo pipefail
|
|
|
7
7
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
8
8
|
OUT="${1:-$HERE/build}"
|
|
9
9
|
|
|
10
|
+
# macOS-only: these helpers import AppKit. On any non-Darwin host (Linux CI),
|
|
11
|
+
# skip cleanly — the binary is compiled on the user's Mac at runtime, never in CI.
|
|
12
|
+
if [ "$(uname -s)" != "Darwin" ]; then
|
|
13
|
+
echo "native: skipping Swift build on non-macOS ($(uname -s))" >&2
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
10
17
|
if ! xcrun --find swiftc >/dev/null 2>&1 && ! command -v swiftc >/dev/null 2>&1; then
|
|
11
18
|
echo "swiftc not found (install Xcode Command Line Tools: xcode-select --install)" >&2
|
|
12
19
|
exit 3
|
|
@@ -20,4 +27,14 @@ swiftc -O "$HERE/statusline.swift" -o "$OUT/adsinagents-statusline"
|
|
|
20
27
|
echo "Compiling frontmost..." >&2
|
|
21
28
|
swiftc -O "$HERE/frontmost.swift" -o "$OUT/frontmost"
|
|
22
29
|
|
|
30
|
+
# Re-sign with ad-hoc signature. macOS 26 (Tahoe) Taskgated invalidates the
|
|
31
|
+
# linker-signed signature when the binary is copied across paths (copyFileSync /
|
|
32
|
+
# install step). A fresh codesign -f after compile ensures the installed copy
|
|
33
|
+
# passes Taskgated validation without needing a Developer ID.
|
|
34
|
+
if command -v codesign >/dev/null 2>&1; then
|
|
35
|
+
echo "Signing binaries (ad-hoc)..." >&2
|
|
36
|
+
codesign -f --sign - "$OUT/adsinagents-statusline" >/dev/null 2>&1 || true
|
|
37
|
+
codesign -f --sign - "$OUT/frontmost" >/dev/null 2>&1 || true
|
|
38
|
+
fi
|
|
39
|
+
|
|
23
40
|
echo "Built: $OUT/adsinagents-statusline, $OUT/frontmost" >&2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adsinagents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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
|
@@ -4082,6 +4082,7 @@ var CATEGORY_KEYS = Object.keys(CATEGORIES);
|
|
|
4082
4082
|
var CategorySchema = external_exports.enum(CATEGORY_KEYS);
|
|
4083
4083
|
|
|
4084
4084
|
// ../../shared/dist/schemas.js
|
|
4085
|
+
var AdSourceSchema = external_exports.enum(["gravity", "direct"]);
|
|
4085
4086
|
var AdSchema = external_exports.object({
|
|
4086
4087
|
/** Stable impression id for this rotation; idempotency key for firing. */
|
|
4087
4088
|
id: external_exports.string().min(1),
|
|
@@ -4098,7 +4099,7 @@ var AdSchema = external_exports.object({
|
|
|
4098
4099
|
/** Gravity click pixel (GET). Empty for direct campaigns. */
|
|
4099
4100
|
clickUrl: external_exports.string().default(""),
|
|
4100
4101
|
/** Source of demand. */
|
|
4101
|
-
source:
|
|
4102
|
+
source: AdSourceSchema,
|
|
4102
4103
|
/** Unix ms when this ad was fetched into cache. */
|
|
4103
4104
|
fetchedAt: external_exports.number().int(),
|
|
4104
4105
|
/**
|
|
@@ -4201,7 +4202,11 @@ var ImpressionSchema = external_exports.object({
|
|
|
4201
4202
|
verifiedAt: external_exports.number().int().nullable(),
|
|
4202
4203
|
impFired: external_exports.boolean(),
|
|
4203
4204
|
clicked: external_exports.boolean(),
|
|
4204
|
-
source:
|
|
4205
|
+
source: AdSourceSchema,
|
|
4206
|
+
/** Render surface the ad painted on. Only "statusline" is billable today
|
|
4207
|
+
* (spinner reach is un-billable and never writes a row). Recorded so the
|
|
4208
|
+
* publisher log can show *where* the ad showed. Older daemons omit it. */
|
|
4209
|
+
slot: external_exports.enum(["statusline", "spinner"]).default("statusline"),
|
|
4205
4210
|
/** Server serve-proof echoed back on sync. Empty for standalone/unverified. */
|
|
4206
4211
|
nonce: external_exports.string().default("")
|
|
4207
4212
|
});
|