terminalhire 0.42.4 → 0.42.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,117 @@
1
+ // bin/founder-bounty-notify.js
2
+ import { spawn } from "child_process";
3
+
4
+ // bin/founder-pin.js
5
+ function isPinnedFounderBounty(j) {
6
+ return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
7
+ }
8
+
9
+ // bin/founder-paid-badge.js
10
+ function openPaidIds(index) {
11
+ const jobs = index && index.jobs || [];
12
+ const ids = [];
13
+ for (const j of jobs) {
14
+ if (!j || typeof j.id !== "string") continue;
15
+ if (isPinnedFounderBounty(j)) ids.push(j.id);
16
+ }
17
+ return [...new Set(ids)].sort();
18
+ }
19
+
20
+ // bin/founder-bounty-notify.js
21
+ function nextFounderBountyNotifyState(index, previous) {
22
+ const open = openPaidIds(index);
23
+ const prior = previous && Array.isArray(previous.ids) ? previous.ids : null;
24
+ if (prior === null) {
25
+ return { ids: open, fire: [], seeded: true };
26
+ }
27
+ const seen = new Set(prior);
28
+ const fire = open.filter((id) => !seen.has(id));
29
+ const ids = [.../* @__PURE__ */ new Set([...prior.filter((id) => open.includes(id)), ...fire])].sort();
30
+ return { ids, fire, seeded: false };
31
+ }
32
+ function formatFounderBountyNotifyBody(index, fireIds) {
33
+ const jobs = index && index.jobs || [];
34
+ const byId = /* @__PURE__ */ new Map();
35
+ for (const j of jobs) {
36
+ if (j && typeof j.id === "string") byId.set(j.id, j);
37
+ }
38
+ if (fireIds.length === 1) {
39
+ const j = byId.get(fireIds[0]);
40
+ const amount = j && j.bounty && typeof j.bounty.amountUSD === "number" ? j.bounty.amountUSD : null;
41
+ const price = typeof amount === "number" && Number.isFinite(amount) && amount > 0 ? `$${Math.round(amount)} ` : "";
42
+ return `${price}founder bounty available \u2014 run: terminalhire bounties`;
43
+ }
44
+ return `${fireIds.length} founder bounties available \u2014 run: terminalhire bounties`;
45
+ }
46
+ function escapeAppleScriptString(s) {
47
+ return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
48
+ }
49
+ function displayLocalNotification({
50
+ title = "Terminalhire",
51
+ body,
52
+ platform = process.platform,
53
+ spawnFn = spawn
54
+ } = {}) {
55
+ if (!body || typeof body !== "string") return false;
56
+ try {
57
+ if (platform === "darwin") {
58
+ const t = escapeAppleScriptString(title);
59
+ const b = escapeAppleScriptString(body);
60
+ const child = spawnFn("osascript", ["-e", `display notification "${b}" with title "${t}"`], {
61
+ detached: true,
62
+ stdio: "ignore"
63
+ });
64
+ if (child && typeof child.unref === "function") child.unref();
65
+ return true;
66
+ }
67
+ if (platform === "win32") {
68
+ const safeTitle = String(title).replace(/'/g, "''");
69
+ const safeBody = String(body).replace(/'/g, "''");
70
+ const ps = `
71
+ Add-Type -AssemblyName System.Windows.Forms
72
+ $n = New-Object System.Windows.Forms.NotifyIcon
73
+ $n.Icon = [System.Drawing.SystemIcons]::Information
74
+ $n.Visible = $true
75
+ $n.ShowBalloonTip(8000, '${safeTitle}', '${safeBody}', [System.Windows.Forms.ToolTipIcon]::Info)
76
+ Start-Sleep -Seconds 9
77
+ $n.Dispose()
78
+ `.trim();
79
+ const child = spawnFn("powershell.exe", ["-NoProfile", "-Command", ps], {
80
+ detached: true,
81
+ stdio: "ignore",
82
+ windowsHide: true
83
+ });
84
+ if (child && typeof child.unref === "function") child.unref();
85
+ return true;
86
+ }
87
+ return false;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+ function maybeNotifyFounderBounties({
93
+ index,
94
+ previous,
95
+ optedIn,
96
+ display = displayLocalNotification
97
+ } = {}) {
98
+ if (!optedIn) {
99
+ const ids = previous && Array.isArray(previous.ids) ? [...previous.ids].sort() : [];
100
+ return { state: { ids }, fired: [] };
101
+ }
102
+ const next = nextFounderBountyNotifyState(index, previous);
103
+ if (next.fire.length > 0) {
104
+ display({
105
+ title: "Terminalhire",
106
+ body: formatFounderBountyNotifyBody(index, next.fire)
107
+ });
108
+ }
109
+ return { state: { ids: next.ids }, fired: next.fire };
110
+ }
111
+ export {
112
+ displayLocalNotification,
113
+ escapeAppleScriptString,
114
+ formatFounderBountyNotifyBody,
115
+ maybeNotifyFounderBounties,
116
+ nextFounderBountyNotifyState
117
+ };
@@ -11546,6 +11546,7 @@ __export(config_exports, {
11546
11546
  getSurfaceMix: () => getSurfaceMix,
11547
11547
  isBetaOptIn: () => isBetaOptIn,
11548
11548
  isContributeEnabled: () => isContributeEnabled,
11549
+ isFounderBountyNotifyEnabled: () => isFounderBountyNotifyEnabled,
11549
11550
  isInboundNudgeMuted: () => isInboundNudgeMuted,
11550
11551
  isPeerConnectEnabled: () => isPeerConnectEnabled,
11551
11552
  parseNudgeMode: () => parseNudgeMode,
@@ -11630,6 +11631,9 @@ function isContributeEnabled() {
11630
11631
  function isBetaOptIn() {
11631
11632
  return readConfig().betaOptIn === true;
11632
11633
  }
11634
+ function isFounderBountyNotifyEnabled() {
11635
+ return readConfig().founderBountyNotify === true;
11636
+ }
11633
11637
  var TERMINALHIRE_DIR6, CONFIG_FILE, DEFAULT_CONFIG;
11634
11638
  var init_config = __esm({
11635
11639
  "src/config.ts"() {
@@ -11651,7 +11655,8 @@ var init_config = __esm({
11651
11655
  lastFullFeedbackAt: null,
11652
11656
  lastPulseAskAt: null,
11653
11657
  pulseDisclosed: false,
11654
- mix: "balanced"
11658
+ mix: "balanced",
11659
+ founderBountyNotify: false
11655
11660
  };
11656
11661
  }
11657
11662
  });
@@ -11880,6 +11885,23 @@ ${founderClaimBlurb(job.bounty?.amountUSD, job.bounty?.paidWork)}
11880
11885
  terminalhire claim ${ref}
11881
11886
  ${sanitizeText(job.bounty?.claimUrl ?? job.url)}`;
11882
11887
  }
11888
+ function wrapIndented(text, indent, width) {
11889
+ const pad = " ".repeat(indent);
11890
+ const words2 = String(text).split(/\s+/).filter(Boolean);
11891
+ if (words2.length === 0) return [];
11892
+ const lines = [];
11893
+ let line = "";
11894
+ for (const w of words2) {
11895
+ if (line === "") line = w;
11896
+ else if (line.length + 1 + w.length <= width) line += ` ${w}`;
11897
+ else {
11898
+ lines.push(pad + line);
11899
+ line = w;
11900
+ }
11901
+ }
11902
+ lines.push(pad + line);
11903
+ return lines;
11904
+ }
11883
11905
  function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set(), continuityNote = null) {
11884
11906
  const b = job.bounty ?? {};
11885
11907
  const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
@@ -11897,6 +11919,9 @@ ${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
11897
11919
  );
11898
11920
  if (reason) console.log(` ${reason}`);
11899
11921
  if (continuityNote) console.log(` ${continuityNote}`);
11922
+ if (b.publicSummary) {
11923
+ for (const line of wrapIndented(sanitizeText(b.publicSummary), 3, 76)) console.log(line);
11924
+ }
11900
11925
  if (b.bountySource === "founder" && b.specProvenance) {
11901
11926
  console.log(
11902
11927
  ` Spec: ${b.specProvenance === "agent_drafted_human_confirmed" ? "agent drafted \xB7 founder confirmed" : "founder authored"}`
@@ -12135,7 +12160,8 @@ export {
12135
12160
  isPinnedFounderBounty,
12136
12161
  printBounty,
12137
12162
  rankBounties,
12138
- run
12163
+ run,
12164
+ wrapIndented
12139
12165
  };
12140
12166
  /*! Bundled license information:
12141
12167
 
@@ -4911,7 +4911,8 @@ var init_config = __esm({
4911
4911
  lastFullFeedbackAt: null,
4912
4912
  lastPulseAskAt: null,
4913
4913
  pulseDisclosed: false,
4914
- mix: "balanced"
4914
+ mix: "balanced",
4915
+ founderBountyNotify: false
4915
4916
  };
4916
4917
  }
4917
4918
  });
@@ -4940,7 +4940,8 @@ var init_config = __esm({
4940
4940
  lastFullFeedbackAt: null,
4941
4941
  lastPulseAskAt: null,
4942
4942
  pulseDisclosed: false,
4943
- mix: "balanced"
4943
+ mix: "balanced",
4944
+ founderBountyNotify: false
4944
4945
  };
4945
4946
  }
4946
4947
  });
@@ -1,21 +1,16 @@
1
1
  #!/usr/bin/env node
2
-
3
- // bin/jpi-config.js
4
- import { join as join2 } from "path";
5
- import { homedir as homedir2 } from "os";
6
-
7
- // src/config.ts
8
- import { readFileSync, writeFileSync, existsSync } from "fs";
9
- import { join } from "path";
10
- import { homedir } from "os";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
11
 
12
12
  // src/state-dir.ts
13
13
  import { closeSync, constants, fchmodSync, fstatSync, mkdirSync, openSync } from "fs";
14
- var STATE_DIR_MODE = 448;
15
- var STATE_DIR_OK = "ok";
16
- var STATE_DIR_SYMLINK = "symlink";
17
- var STATE_DIR_UNVERIFIED = "unverified";
18
- var warnedDirs = /* @__PURE__ */ new Set();
19
14
  function warnStateDirOnce(dir, message) {
20
15
  if (warnedDirs.has(dir)) return;
21
16
  warnedDirs.add(dir);
@@ -56,8 +51,116 @@ function ensureStateDir(dir) {
56
51
  }
57
52
  }
58
53
  }
54
+ var STATE_DIR_MODE, STATE_DIR_OK, STATE_DIR_SYMLINK, STATE_DIR_UNVERIFIED, warnedDirs;
55
+ var init_state_dir = __esm({
56
+ "src/state-dir.ts"() {
57
+ "use strict";
58
+ STATE_DIR_MODE = 448;
59
+ STATE_DIR_OK = "ok";
60
+ STATE_DIR_SYMLINK = "symlink";
61
+ STATE_DIR_UNVERIFIED = "unverified";
62
+ warnedDirs = /* @__PURE__ */ new Set();
63
+ }
64
+ });
65
+
66
+ // bin/founder-pin.js
67
+ function isPinnedFounderBounty(j) {
68
+ return j?.bounty?.bountySource === "founder" && j?.bounty?.claimable === true;
69
+ }
70
+ var init_founder_pin = __esm({
71
+ "bin/founder-pin.js"() {
72
+ "use strict";
73
+ }
74
+ });
75
+
76
+ // bin/founder-paid-badge.js
77
+ var founder_paid_badge_exports = {};
78
+ __export(founder_paid_badge_exports, {
79
+ acknowledgeFounderPaid: () => acknowledgeFounderPaid,
80
+ computeFounderPaid: () => computeFounderPaid,
81
+ openPaidIds: () => openPaidIds
82
+ });
83
+ function openPaidIds(index) {
84
+ const jobs = index && index.jobs || [];
85
+ const ids = [];
86
+ for (const j of jobs) {
87
+ if (!j || typeof j.id !== "string") continue;
88
+ if (isPinnedFounderBounty(j)) ids.push(j.id);
89
+ }
90
+ return [...new Set(ids)].sort();
91
+ }
92
+ function gate(openIds, seenIds) {
93
+ const seen = new Set(seenIds);
94
+ const acknowledged = openIds.filter((id) => seen.has(id));
95
+ return { count: openIds.length - acknowledged.length, acknowledged };
96
+ }
97
+ function computeFounderPaid(index, previous) {
98
+ const open = openPaidIds(index);
99
+ const prior = previous && previous.acknowledged;
100
+ return gate(open, Array.isArray(prior) ? prior : []);
101
+ }
102
+ function acknowledgeFounderPaid({ shown = [], open = [], previous } = {}) {
103
+ const prior = previous && Array.isArray(previous.acknowledged) ? previous.acknowledged : [];
104
+ return gate(openPaidIds({ jobs: open }), [...prior, ...openPaidIds({ jobs: shown })]);
105
+ }
106
+ var init_founder_paid_badge = __esm({
107
+ "bin/founder-paid-badge.js"() {
108
+ "use strict";
109
+ init_founder_pin();
110
+ }
111
+ });
112
+
113
+ // bin/cache-store.js
114
+ var cache_store_exports = {};
115
+ __export(cache_store_exports, {
116
+ readCacheEntry: () => readCacheEntry,
117
+ updateIndexCache: () => updateIndexCache
118
+ });
119
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync } from "fs";
120
+ import { join as join2 } from "path";
121
+ import { homedir as homedir2 } from "os";
122
+ function readCacheEntry() {
123
+ try {
124
+ return JSON.parse(readFileSync2(INDEX_CACHE_FILE, "utf8"));
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+ function updateIndexCache(patch) {
130
+ ensureStateDir(TERMINALHIRE_DIR2);
131
+ const existing = readCacheEntry() ?? {};
132
+ const entry = {
133
+ ...existing,
134
+ ...patch,
135
+ schemaVersion: SCHEMA_VERSION,
136
+ ts: Date.now()
137
+ };
138
+ const tmp = `${INDEX_CACHE_FILE}.${process.pid}.${tmpCounter++}.tmp`;
139
+ writeFileSync2(tmp, JSON.stringify(entry), "utf8");
140
+ renameSync(tmp, INDEX_CACHE_FILE);
141
+ return entry;
142
+ }
143
+ var TERMINALHIRE_DIR2, INDEX_CACHE_FILE, SCHEMA_VERSION, tmpCounter;
144
+ var init_cache_store = __esm({
145
+ "bin/cache-store.js"() {
146
+ "use strict";
147
+ init_state_dir();
148
+ TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join2(homedir2(), ".terminalhire");
149
+ INDEX_CACHE_FILE = join2(TERMINALHIRE_DIR2, "index-cache.json");
150
+ SCHEMA_VERSION = 1;
151
+ tmpCounter = 0;
152
+ }
153
+ });
154
+
155
+ // bin/jpi-config.js
156
+ import { join as join3 } from "path";
157
+ import { homedir as homedir3 } from "os";
59
158
 
60
159
  // src/config.ts
160
+ init_state_dir();
161
+ import { readFileSync, writeFileSync, existsSync } from "fs";
162
+ import { join } from "path";
163
+ import { homedir } from "os";
61
164
  var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join(homedir(), ".terminalhire");
62
165
  var CONFIG_FILE = join(TERMINALHIRE_DIR, "config.json");
63
166
  var DEFAULT_CONFIG = {
@@ -74,7 +177,8 @@ var DEFAULT_CONFIG = {
74
177
  lastFullFeedbackAt: null,
75
178
  lastPulseAskAt: null,
76
179
  pulseDisclosed: false,
77
- mix: "balanced"
180
+ mix: "balanced",
181
+ founderBountyNotify: false
78
182
  };
79
183
  function readConfig() {
80
184
  try {
@@ -107,8 +211,8 @@ function parseSurfaceLead(raw) {
107
211
  }
108
212
 
109
213
  // bin/jpi-config.js
110
- var TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join2(homedir2(), ".terminalhire");
111
- var CONFIG_FILE2 = join2(TERMINALHIRE_DIR2, "config.json");
214
+ var TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join3(homedir3(), ".terminalhire");
215
+ var CONFIG_FILE2 = join3(TERMINALHIRE_DIR3, "config.json");
112
216
  function parseNudgeMode(raw) {
113
217
  if (raw === "session" || raw === "always") return raw;
114
218
  const m = /^every:(\d+)$/.exec(raw);
@@ -186,13 +290,20 @@ async function run() {
186
290
  if (envOverride) {
187
291
  console.log(` (overridden by TERMINALHIRE_NUDGE=${envOverride} at runtime)`);
188
292
  }
189
- console.log(` peer-connect: ${cfg.peerConnect ? "on" : "off"} (ambient peer & founder surfacing; default off)`);
293
+ console.log(
294
+ ` peer-connect: ${cfg.peerConnect ? "on" : "off"} (ambient peer & founder surfacing; default off)`
295
+ );
190
296
  const mixEnv = process.env["TH_MIX"];
191
- console.log(` mix: ${cfg.mix} (roles vs. contributions on the ambient surface; default balanced)`);
297
+ console.log(
298
+ ` mix: ${cfg.mix} (roles vs. contributions on the ambient surface; default balanced)`
299
+ );
192
300
  if (mixEnv) {
193
301
  console.log(` (mix overridden by TH_MIX=${mixEnv} at runtime)`);
194
302
  }
195
303
  console.log(` lead: ${cfg.surfaceLead ?? "auto"} (auto derives from your open postings)`);
304
+ console.log(
305
+ ` founder-notify: ${cfg.founderBountyNotify ? "on" : "off"} (OS ping when a paid founder bounty drops; default off)`
306
+ );
196
307
  console.log(` config file: ${CONFIG_FILE2}`);
197
308
  console.log("");
198
309
  console.log(" Valid nudge values:");
@@ -204,9 +315,15 @@ async function run() {
204
315
  console.log(" (set with: config set mix <value> \xB7 read with: config get mix)");
205
316
  console.log("");
206
317
  console.log(" Peer-connect (--connect on|off):");
207
- console.log(" on \u2014 surface peers & founders in the spinner + send an anonymous matched signal");
318
+ console.log(
319
+ " on \u2014 surface peers & founders in the spinner + send an anonymous matched signal"
320
+ );
208
321
  console.log(" off \u2014 no peer matching, no directory fetch, no signal (default)");
209
322
  console.log("");
323
+ console.log(" Founder bounty OS notify (--founder-notify on|off):");
324
+ console.log(" on \u2014 ping when a NEW claimable founder bounty appears (TERM-228)");
325
+ console.log(" off \u2014 no OS toast for founder supply (default)");
326
+ console.log("");
210
327
  return;
211
328
  }
212
329
  const nudgeIdx = filtered.indexOf("--nudge");
@@ -238,8 +355,38 @@ async function run() {
238
355
  console.log(` (saved to ${CONFIG_FILE2})`);
239
356
  return;
240
357
  }
358
+ const founderNotifyIdx = filtered.indexOf("--founder-notify");
359
+ if (founderNotifyIdx !== -1) {
360
+ const value = filtered[founderNotifyIdx + 1];
361
+ if (value !== "on" && value !== "off") {
362
+ console.error("Error: --founder-notify requires a value: on | off");
363
+ process.exit(1);
364
+ }
365
+ writeConfig({ founderBountyNotify: value === "on" });
366
+ if (value === "on") {
367
+ try {
368
+ const { openPaidIds: openPaidIds2 } = await Promise.resolve().then(() => (init_founder_paid_badge(), founder_paid_badge_exports));
369
+ const { readCacheEntry: readCacheEntry2, updateIndexCache: updateIndexCache2 } = await Promise.resolve().then(() => (init_cache_store(), cache_store_exports));
370
+ const entry = readCacheEntry2() ?? {};
371
+ const ids = openPaidIds2(entry.index);
372
+ updateIndexCache2({ founderPaidOsNotified: { ids } });
373
+ console.log(` founder-notify set to: on`);
374
+ console.log(
375
+ ` (seeded ${ids.length} existing open bounty id(s) \u2014 only NEW ones will ping)`
376
+ );
377
+ } catch {
378
+ console.log(` founder-notify set to: on`);
379
+ console.log(" (could not seed from cache \u2014 first refresh will silent-seed)");
380
+ }
381
+ } else {
382
+ console.log(` founder-notify set to: off`);
383
+ }
384
+ console.log(` (saved to ${CONFIG_FILE2})`);
385
+ return;
386
+ }
241
387
  console.error("Usage: terminalhire config --nudge <session|always|every:N>");
242
388
  console.error(" terminalhire config --connect <on|off>");
389
+ console.error(" terminalhire config --founder-notify <on|off>");
243
390
  console.error(" terminalhire config set mix <jobs|balanced|credential>");
244
391
  console.error(" terminalhire config get mix");
245
392
  console.error(" terminalhire config set lead <auto|dev|founder>");
@@ -3876,7 +3876,8 @@ var DEFAULT_CONFIG = {
3876
3876
  lastFullFeedbackAt: null,
3877
3877
  lastPulseAskAt: null,
3878
3878
  pulseDisclosed: false,
3879
- mix: "balanced"
3879
+ mix: "balanced",
3880
+ founderBountyNotify: false
3880
3881
  };
3881
3882
  function readConfig() {
3882
3883
  try {