adsinagents 0.1.7 → 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 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 join5 } from "node:path";
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";
@@ -4542,8 +4542,8 @@ var ClaudeCodeAdapter = class {
4542
4542
  else
4543
4543
  priorValues.hooks = cur.hooks;
4544
4544
  const mk = (path) => ({
4545
- type: "http",
4546
- url: `${plan.hookBaseUrl}${path}`
4545
+ type: "command",
4546
+ command: hookCommand(path)
4547
4547
  });
4548
4548
  next.hooks = {
4549
4549
  ...hooks,
@@ -4568,8 +4568,8 @@ var ClaudeCodeAdapter = class {
4568
4568
  const backup = this.backup();
4569
4569
  const { next, diff } = this.merge(plan);
4570
4570
  this.write(next);
4571
- this.writeSlashCommand();
4572
- return { backup, diff };
4571
+ const slashCommand = this.writeSlashCommand();
4572
+ return { backup, diff, slashCommand };
4573
4573
  }
4574
4574
  /** Path of the in-session settings command, e.g. ~/.claude/commands/adsinagents.md. */
4575
4575
  slashCommandPath() {
@@ -4579,14 +4579,22 @@ var ClaudeCodeAdapter = class {
4579
4579
  * Install /adsinagents — the status line itself is display-only, so this is
4580
4580
  * the in-session settings surface: the user types /adsinagents and Claude
4581
4581
  * drives the CLI. File is fully ours (sentinel comment), safe to delete on
4582
- * remove. Best-effort: a failure here never fails install.
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).
4583
4586
  */
4584
4587
  writeSlashCommand() {
4588
+ const p = this.slashCommandPath();
4585
4589
  try {
4586
- const p = this.slashCommandPath();
4590
+ if (existsSync2(p) && readFileSync2(p, "utf8") === SLASH_COMMAND_MD) {
4591
+ return { ok: true };
4592
+ }
4587
4593
  mkdirSync2(dirname2(p), { recursive: true });
4588
4594
  writeFileSync2(p, SLASH_COMMAND_MD);
4589
- } catch {
4595
+ return { ok: true };
4596
+ } catch (e) {
4597
+ return { ok: false, reason: e.message };
4590
4598
  }
4591
4599
  }
4592
4600
  removeInstall() {
@@ -4716,10 +4724,18 @@ function spinnerVerbsFor(ad) {
4716
4724
  `Brought to you by ${brand}`
4717
4725
  ];
4718
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
+ }
4719
4734
  function appendHook(existing, entry) {
4720
4735
  const arr = Array.isArray(existing) ? existing.slice() : existing ? [existing] : [];
4721
- arr.push({ hooks: [entry], [ADLINE_SENTINEL]: true });
4722
- return arr;
4736
+ const userOwned = arr.filter((e) => !(e && typeof e === "object" && e[ADLINE_SENTINEL]));
4737
+ userOwned.push({ hooks: [entry], [ADLINE_SENTINEL]: true });
4738
+ return userOwned;
4723
4739
  }
4724
4740
  function renderDiff(before, after) {
4725
4741
  const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
@@ -4761,6 +4777,9 @@ function getAgent(name = "claude-code") {
4761
4777
 
4762
4778
  // ../cli/src/settings.ts
4763
4779
  var cc = () => getAgent("claude-code");
4780
+ function isInstalled() {
4781
+ return cc().isInstalled();
4782
+ }
4764
4783
  function planMerge(plan) {
4765
4784
  return cc().planInstall(plan);
4766
4785
  }
@@ -4804,6 +4823,10 @@ function buildAndInstallNative() {
4804
4823
  if (existsSync3(src)) {
4805
4824
  copyFileSync2(src, dest);
4806
4825
  chmodSync2(dest, 493);
4826
+ try {
4827
+ execFileSync3("codesign", ["-f", "--sign", "-", dest], { stdio: "pipe" });
4828
+ } catch {
4829
+ }
4807
4830
  installed.push(dest);
4808
4831
  }
4809
4832
  }
@@ -4812,6 +4835,9 @@ function buildAndInstallNative() {
4812
4835
 
4813
4836
  // ../cli/src/doctor.ts
4814
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";
4815
4841
  function daemonAlive() {
4816
4842
  if (!existsSync4(PATHS.daemonInfo)) return { ok: false, detail: "no daemon.json (not started)" };
4817
4843
  try {
@@ -4863,8 +4889,133 @@ async function runDoctor() {
4863
4889
  ok: key !== null,
4864
4890
  detail: key ? "present (Keychain/file)" : "none \u2014 run `adsinagents login`"
4865
4891
  });
4892
+ checks.push(checkStatusLine());
4893
+ checks.push(checkHookReachable());
4894
+ checks.push(checkSlashCommand());
4866
4895
  return checks;
4867
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
+ }
4868
5019
  function printDoctor(checks) {
4869
5020
  for (const c of checks) {
4870
5021
  const mark = c.ok ? "\u2713" : "\u2717";
@@ -4873,19 +5024,73 @@ function printDoctor(checks) {
4873
5024
  }
4874
5025
  }
4875
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
+
4876
5081
  // ../cli/src/index.ts
4877
5082
  var HERE2 = dirname4(fileURLToPath2(import.meta.url));
4878
5083
  function readVersion() {
4879
5084
  for (const rel of ["../../package.json", "../package.json"]) {
4880
5085
  try {
4881
- return JSON.parse(readFileSync4(join5(HERE2, rel), "utf8")).version ?? "0.0.0";
5086
+ return JSON.parse(readFileSync4(join6(HERE2, rel), "utf8")).version ?? "0.0.0";
4882
5087
  } catch {
4883
5088
  }
4884
5089
  }
4885
5090
  return "0.0.0";
4886
5091
  }
4887
5092
  var VERSION = readVersion();
4888
- var DAEMON_ENTRY = join5(HERE2, "..", "..", "daemon", "dist", "main.js");
5093
+ var DAEMON_ENTRY = join6(HERE2, "..", "..", "daemon", "dist", "main.js");
4889
5094
  function parseFlags(argv) {
4890
5095
  const positional = [];
4891
5096
  const flags = /* @__PURE__ */ new Map();
@@ -4901,8 +5106,8 @@ function parseFlags(argv) {
4901
5106
  return { positional, flags };
4902
5107
  }
4903
5108
  function buildPlan(placement, spinnerVerbs) {
4904
- const nativeStatusline = join5(dirname4(PATHS.frontmostBin), "adsinagents-statusline");
4905
- const statuslineCommand = existsSync5(nativeStatusline) ? nativeStatusline : `node ${join5(HERE2, "..", "..", "statusline", "dist", "index.js")}`;
5109
+ const nativeStatusline = join6(dirname4(PATHS.frontmostBin), "adsinagents-statusline");
5110
+ const statuslineCommand = existsSync5(nativeStatusline) ? nativeStatusline : `node ${join6(HERE2, "..", "..", "statusline", "dist", "index.js")}`;
4906
5111
  return {
4907
5112
  statuslineCommand,
4908
5113
  hookBaseUrl: `http://127.0.0.1:${HOOK_PORT}`,
@@ -4922,17 +5127,33 @@ async function cmdInit(flags) {
4922
5127
  process.stdout.write(diff + "\n");
4923
5128
  return;
4924
5129
  }
5130
+ const firstRun = !isInstalled();
5131
+ const steps = [];
4925
5132
  const native = buildAndInstallNative();
4926
- process.stdout.write(
4927
- native.ok ? `\u2713 native helpers installed (${native.installed.length})
4928
- ` : `\u26A0 ${native.reason} (degraded mode)
4929
- `
4930
- );
4931
- const { backup } = applyMerge(plan);
4932
- process.stdout.write(`\u2713 settings merged${backup ? ` (backup: ${backup})` : ""}
4933
- `);
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
+ });
4934
5152
  const cfg = loadConfigSafe();
4935
5153
  let claimUrl = "";
5154
+ let deviceStepOk = true;
5155
+ let deviceDetail = "";
5156
+ let deviceNote;
4936
5157
  if (!cfg.deviceToken) {
4937
5158
  try {
4938
5159
  const deviceId = `dev_${randomBytes(8).toString("hex")}`;
@@ -4945,36 +5166,43 @@ async function cmdInit(flags) {
4945
5166
  const reg = await res.json();
4946
5167
  writeConfigPatch({ deviceId: reg.deviceId, deviceToken: reg.deviceToken, claimToken: reg.claimToken });
4947
5168
  claimUrl = reg.claimUrl;
4948
- process.stdout.write("\u2713 device registered (earning anonymously)\n");
5169
+ deviceDetail = "registered, earning anonymously";
4949
5170
  } catch (e) {
4950
- process.stdout.write(`\u26A0 device registration skipped (${e.message}); run \`adsinagents login\` later
4951
- `);
5171
+ deviceStepOk = false;
5172
+ deviceDetail = "registration skipped";
5173
+ deviceNote = e.message;
4952
5174
  }
4953
5175
  } else {
4954
- process.stdout.write("\u2022 device already registered\n");
5176
+ deviceDetail = "registered";
4955
5177
  if (cfg.claimToken) claimUrl = `${cfg.serverUrl}/claim?t=${cfg.claimToken}`;
4956
5178
  }
5179
+ steps.push({ ok: deviceStepOk, label: "Device", detail: deviceDetail, note: deviceNote });
4957
5180
  const platform = getPlatform();
4958
5181
  const nodeBin = process.execPath;
4959
- await platform.installAutostart(nodeBin, [DAEMON_ENTRY]);
4960
- process.stdout.write("\u2713 daemon autostart installed + loaded\n");
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 });
4961
5191
  if (flags.has("show-earnings")) {
4962
5192
  writeConfigPatch({ showEarnings: flags.get("show-earnings") !== "false" });
4963
- process.stdout.write("\u2713 live earnings segment enabled\n");
4964
5193
  }
4965
- process.stdout.write(`
4966
- AdsInAgents is live (placement=${placement}). Run \`adsinagents doctor\` to verify.
4967
- `);
4968
- process.stdout.write("Restart Claude Code (quit + reopen) so the status line picks up the ad.\n");
4969
- if (claimUrl) {
4970
- process.stdout.write(
4971
- `
4972
- You're earning anonymously. To see earnings + get paid, claim this install:
4973
- ${claimUrl}
4974
- (or run \`adsinagents claim\` anytime to reprint the link)
4975
- `
4976
- );
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
+ }
4977
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
+ );
4978
5206
  }
4979
5207
  async function cmdRemove() {
4980
5208
  const platform = getPlatform();
@@ -4550,8 +4550,8 @@ var ClaudeCodeAdapter = class {
4550
4550
  else
4551
4551
  priorValues.hooks = cur.hooks;
4552
4552
  const mk = (path) => ({
4553
- type: "http",
4554
- url: `${plan.hookBaseUrl}${path}`
4553
+ type: "command",
4554
+ command: hookCommand(path)
4555
4555
  });
4556
4556
  next.hooks = {
4557
4557
  ...hooks,
@@ -4576,8 +4576,8 @@ var ClaudeCodeAdapter = class {
4576
4576
  const backup = this.backup();
4577
4577
  const { next, diff } = this.merge(plan);
4578
4578
  this.write(next);
4579
- this.writeSlashCommand();
4580
- return { backup, diff };
4579
+ const slashCommand = this.writeSlashCommand();
4580
+ return { backup, diff, slashCommand };
4581
4581
  }
4582
4582
  /** Path of the in-session settings command, e.g. ~/.claude/commands/adsinagents.md. */
4583
4583
  slashCommandPath() {
@@ -4587,14 +4587,22 @@ var ClaudeCodeAdapter = class {
4587
4587
  * Install /adsinagents — the status line itself is display-only, so this is
4588
4588
  * the in-session settings surface: the user types /adsinagents and Claude
4589
4589
  * drives the CLI. File is fully ours (sentinel comment), safe to delete on
4590
- * remove. Best-effort: a failure here never fails install.
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).
4591
4594
  */
4592
4595
  writeSlashCommand() {
4596
+ const p = this.slashCommandPath();
4593
4597
  try {
4594
- const p = this.slashCommandPath();
4598
+ if (existsSync2(p) && readFileSync2(p, "utf8") === SLASH_COMMAND_MD) {
4599
+ return { ok: true };
4600
+ }
4595
4601
  mkdirSync2(dirname2(p), { recursive: true });
4596
4602
  writeFileSync2(p, SLASH_COMMAND_MD);
4597
- } catch {
4603
+ return { ok: true };
4604
+ } catch (e) {
4605
+ return { ok: false, reason: e.message };
4598
4606
  }
4599
4607
  }
4600
4608
  removeInstall() {
@@ -4724,10 +4732,18 @@ function spinnerVerbsFor(ad) {
4724
4732
  `Brought to you by ${brand}`
4725
4733
  ];
4726
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
+ }
4727
4742
  function appendHook(existing, entry) {
4728
4743
  const arr = Array.isArray(existing) ? existing.slice() : existing ? [existing] : [];
4729
- arr.push({ hooks: [entry], [ADLINE_SENTINEL]: true });
4730
- return arr;
4744
+ const userOwned = arr.filter((e) => !(e && typeof e === "object" && e[ADLINE_SENTINEL]));
4745
+ userOwned.push({ hooks: [entry], [ADLINE_SENTINEL]: true });
4746
+ return userOwned;
4731
4747
  }
4732
4748
  function renderDiff(before, after) {
4733
4749
  const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
package/native/build.sh CHANGED
@@ -27,4 +27,14 @@ swiftc -O "$HERE/statusline.swift" -o "$OUT/adsinagents-statusline"
27
27
  echo "Compiling frontmost..." >&2
28
28
  swiftc -O "$HERE/frontmost.swift" -o "$OUT/frontmost"
29
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
+
30
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.7",
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",