terminalhire 0.3.3 → 0.3.5

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.
@@ -905,10 +905,13 @@ function parseRss(xml) {
905
905
  return decodeEntities(plainMatch?.[1].trim() ?? "");
906
906
  };
907
907
  const rawTitle = get("title");
908
- const colonIdx = rawTitle.indexOf(":");
909
- const company = colonIdx !== -1 ? rawTitle.slice(0, colonIdx).trim() : "Unknown";
910
- const titleAfterColon = colonIdx !== -1 ? rawTitle.slice(colonIdx + 1).trim() : rawTitle;
908
+ const m = rawTitle.match(/^(.*?):\s+(.*)$/);
909
+ let company = m ? m[1].trim() : "Unknown";
910
+ const titleAfterColon = m ? m[2].trim() : rawTitle;
911
911
  const title = titleAfterColon.replace(/\s*\([^)]*\)\s*$/, "").trim();
912
+ if (/^https?:\/\//i.test(company)) {
913
+ company = company.replace(/^https?:\/\//i, "").replace(/\/.*$/, "").trim() || "Unknown";
914
+ }
912
915
  items.push({
913
916
  title,
914
917
  link: get("link") || get("guid"),
@@ -1119,10 +1119,13 @@ function parseRss(xml) {
1119
1119
  return decodeEntities(plainMatch?.[1].trim() ?? "");
1120
1120
  };
1121
1121
  const rawTitle = get("title");
1122
- const colonIdx = rawTitle.indexOf(":");
1123
- const company = colonIdx !== -1 ? rawTitle.slice(0, colonIdx).trim() : "Unknown";
1124
- const titleAfterColon = colonIdx !== -1 ? rawTitle.slice(colonIdx + 1).trim() : rawTitle;
1122
+ const m = rawTitle.match(/^(.*?):\s+(.*)$/);
1123
+ let company = m ? m[1].trim() : "Unknown";
1124
+ const titleAfterColon = m ? m[2].trim() : rawTitle;
1125
1125
  const title = titleAfterColon.replace(/\s*\([^)]*\)\s*$/, "").trim();
1126
+ if (/^https?:\/\//i.test(company)) {
1127
+ company = company.replace(/^https?:\/\//i, "").replace(/\/.*$/, "").trim() || "Unknown";
1128
+ }
1126
1129
  items.push({
1127
1130
  title,
1128
1131
  link: get("link") || get("guid"),
@@ -3181,19 +3184,16 @@ function buildContextVerbs(topMatches, sessionTags) {
3181
3184
  if (overlap.length >= 2) {
3182
3185
  const a = titleCase(overlap[0]);
3183
3186
  const b = titleCase(overlap[1]);
3184
- headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A role matching what you're building`];
3187
+ headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A match for what you're building \u2014 link below`];
3185
3188
  } else if (overlap.length === 1) {
3186
3189
  const a = titleCase(overlap[0]);
3187
- headers = [`\u2726 A role matching your ${a} work`, `\u2726 Your ${a} work \u2014 link in the tip below`];
3190
+ headers = [`\u2726 Work in your ${a} stack \u2014 link below`, `\u2726 Your ${a} work \u2014 link in the tip below`];
3188
3191
  } else {
3189
- headers = [`\u2726 A role that fits your work`, `\u2726 Job match for you \u2014 link in the tip below`];
3192
+ headers = [`\u2726 Work that fits your stack`, `\u2726 A match for you \u2014 link in the tip below`];
3190
3193
  }
3191
3194
  const list = Array.isArray(topMatches) ? topMatches : [];
3192
- const bounty = list.find((m) => m && m.source === "bounty" && m.amountUSD != null) || list.find((m) => m && m.source === "bounty");
3193
- if (bounty) {
3194
- const money = bounty.amountUSD != null ? `$${bounty.amountUSD.toLocaleString()} ` : "";
3195
- headers.unshift(`\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`);
3196
- }
3195
+ const hasBounty = list.some((m) => m && m.source === "bounty");
3196
+ if (hasBounty) headers.unshift(`\u2726 Roles + \u{1F48E} paid bounties in your stack \u2014 link below`);
3197
3197
  return headers;
3198
3198
  }
3199
3199
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -3279,8 +3279,15 @@ function buildTips(topMatches, baseUrl, max = 8) {
3279
3279
  const perCompany = /* @__PURE__ */ new Map();
3280
3280
  const COMPANY_CAP = 2;
3281
3281
  const all = Array.isArray(topMatches) ? topMatches : [];
3282
- const leadBounty = all.find((m) => m && m.source === "bounty");
3283
- const ordered = leadBounty ? [leadBounty, ...interleaveBySource(all.filter((m) => m !== leadBounty))] : interleaveBySource(all);
3282
+ const bountyQ = all.filter((m) => m && m.source === "bounty");
3283
+ const roleQ = interleaveBySource(all.filter((m) => m && m.source !== "bounty"));
3284
+ const ordered = [];
3285
+ let bi = 0;
3286
+ let ri = 0;
3287
+ while (bi < bountyQ.length || ri < roleQ.length) {
3288
+ if (bi < bountyQ.length) ordered.push(bountyQ[bi++]);
3289
+ if (ri < roleQ.length) ordered.push(roleQ[ri++]);
3290
+ }
3284
3291
  for (const m of ordered) {
3285
3292
  if (!m || !m.title || !m.company || !m.id) continue;
3286
3293
  const idx = String(m.id).indexOf(":");
@@ -4108,7 +4115,9 @@ async function run10() {
4108
4115
  const results = match2(fp, jobs, jobs.length);
4109
4116
  matchCount = results.length;
4110
4117
  const BOUNTY_SLOTS = 5;
4111
- const bountyTop = results.filter((r) => r.job.source === "bounty").slice(0, BOUNTY_SLOTS);
4118
+ const bountyMatches = results.filter((r) => r.job.source === "bounty");
4119
+ const rot = bountyMatches.length > 0 ? Math.floor(Date.now() / (5 * 60 * 1e3)) % bountyMatches.length : 0;
4120
+ const bountyTop = [...bountyMatches.slice(rot), ...bountyMatches.slice(0, rot)].slice(0, BOUNTY_SLOTS);
4112
4121
  const roleTop = results.filter((r) => r.job.source !== "bounty").slice(0, 25 - bountyTop.length);
4113
4122
  topMatches = [...roleTop, ...bountyTop].map((r) => ({
4114
4123
  id: r.job.id,
@@ -905,10 +905,13 @@ function parseRss(xml) {
905
905
  return decodeEntities(plainMatch?.[1].trim() ?? "");
906
906
  };
907
907
  const rawTitle = get("title");
908
- const colonIdx = rawTitle.indexOf(":");
909
- const company = colonIdx !== -1 ? rawTitle.slice(0, colonIdx).trim() : "Unknown";
910
- const titleAfterColon = colonIdx !== -1 ? rawTitle.slice(colonIdx + 1).trim() : rawTitle;
908
+ const m = rawTitle.match(/^(.*?):\s+(.*)$/);
909
+ let company = m ? m[1].trim() : "Unknown";
910
+ const titleAfterColon = m ? m[2].trim() : rawTitle;
911
911
  const title = titleAfterColon.replace(/\s*\([^)]*\)\s*$/, "").trim();
912
+ if (/^https?:\/\//i.test(company)) {
913
+ company = company.replace(/^https?:\/\//i, "").replace(/\/.*$/, "").trim() || "Unknown";
914
+ }
912
915
  items.push({
913
916
  title,
914
917
  link: get("link") || get("guid"),
@@ -1119,10 +1119,13 @@ function parseRss(xml) {
1119
1119
  return decodeEntities(plainMatch?.[1].trim() ?? "");
1120
1120
  };
1121
1121
  const rawTitle = get("title");
1122
- const colonIdx = rawTitle.indexOf(":");
1123
- const company = colonIdx !== -1 ? rawTitle.slice(0, colonIdx).trim() : "Unknown";
1124
- const titleAfterColon = colonIdx !== -1 ? rawTitle.slice(colonIdx + 1).trim() : rawTitle;
1122
+ const m = rawTitle.match(/^(.*?):\s+(.*)$/);
1123
+ let company = m ? m[1].trim() : "Unknown";
1124
+ const titleAfterColon = m ? m[2].trim() : rawTitle;
1125
1125
  const title = titleAfterColon.replace(/\s*\([^)]*\)\s*$/, "").trim();
1126
+ if (/^https?:\/\//i.test(company)) {
1127
+ company = company.replace(/^https?:\/\//i, "").replace(/\/.*$/, "").trim() || "Unknown";
1128
+ }
1126
1129
  items.push({
1127
1130
  title,
1128
1131
  link: get("link") || get("guid"),
@@ -905,10 +905,13 @@ function parseRss(xml) {
905
905
  return decodeEntities(plainMatch?.[1].trim() ?? "");
906
906
  };
907
907
  const rawTitle = get("title");
908
- const colonIdx = rawTitle.indexOf(":");
909
- const company = colonIdx !== -1 ? rawTitle.slice(0, colonIdx).trim() : "Unknown";
910
- const titleAfterColon = colonIdx !== -1 ? rawTitle.slice(colonIdx + 1).trim() : rawTitle;
908
+ const m = rawTitle.match(/^(.*?):\s+(.*)$/);
909
+ let company = m ? m[1].trim() : "Unknown";
910
+ const titleAfterColon = m ? m[2].trim() : rawTitle;
911
911
  const title = titleAfterColon.replace(/\s*\([^)]*\)\s*$/, "").trim();
912
+ if (/^https?:\/\//i.test(company)) {
913
+ company = company.replace(/^https?:\/\//i, "").replace(/\/.*$/, "").trim() || "Unknown";
914
+ }
912
915
  items.push({
913
916
  title,
914
917
  link: get("link") || get("guid"),
@@ -2010,19 +2013,16 @@ function buildContextVerbs(topMatches, sessionTags) {
2010
2013
  if (overlap.length >= 2) {
2011
2014
  const a = titleCase(overlap[0]);
2012
2015
  const b = titleCase(overlap[1]);
2013
- headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A role matching what you're building`];
2016
+ headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A match for what you're building \u2014 link below`];
2014
2017
  } else if (overlap.length === 1) {
2015
2018
  const a = titleCase(overlap[0]);
2016
- headers = [`\u2726 A role matching your ${a} work`, `\u2726 Your ${a} work \u2014 link in the tip below`];
2019
+ headers = [`\u2726 Work in your ${a} stack \u2014 link below`, `\u2726 Your ${a} work \u2014 link in the tip below`];
2017
2020
  } else {
2018
- headers = [`\u2726 A role that fits your work`, `\u2726 Job match for you \u2014 link in the tip below`];
2021
+ headers = [`\u2726 Work that fits your stack`, `\u2726 A match for you \u2014 link in the tip below`];
2019
2022
  }
2020
2023
  const list = Array.isArray(topMatches) ? topMatches : [];
2021
- const bounty = list.find((m) => m && m.source === "bounty" && m.amountUSD != null) || list.find((m) => m && m.source === "bounty");
2022
- if (bounty) {
2023
- const money = bounty.amountUSD != null ? `$${bounty.amountUSD.toLocaleString()} ` : "";
2024
- headers.unshift(`\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`);
2025
- }
2024
+ const hasBounty = list.some((m) => m && m.source === "bounty");
2025
+ if (hasBounty) headers.unshift(`\u2726 Roles + \u{1F48E} paid bounties in your stack \u2014 link below`);
2026
2026
  return headers;
2027
2027
  }
2028
2028
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -2108,8 +2108,15 @@ function buildTips(topMatches, baseUrl, max = 8) {
2108
2108
  const perCompany = /* @__PURE__ */ new Map();
2109
2109
  const COMPANY_CAP = 2;
2110
2110
  const all = Array.isArray(topMatches) ? topMatches : [];
2111
- const leadBounty = all.find((m) => m && m.source === "bounty");
2112
- const ordered = leadBounty ? [leadBounty, ...interleaveBySource(all.filter((m) => m !== leadBounty))] : interleaveBySource(all);
2111
+ const bountyQ = all.filter((m) => m && m.source === "bounty");
2112
+ const roleQ = interleaveBySource(all.filter((m) => m && m.source !== "bounty"));
2113
+ const ordered = [];
2114
+ let bi = 0;
2115
+ let ri = 0;
2116
+ while (bi < bountyQ.length || ri < roleQ.length) {
2117
+ if (bi < bountyQ.length) ordered.push(bountyQ[bi++]);
2118
+ if (ri < roleQ.length) ordered.push(roleQ[ri++]);
2119
+ }
2113
2120
  for (const m of ordered) {
2114
2121
  if (!m || !m.title || !m.company || !m.id) continue;
2115
2122
  const idx = String(m.id).indexOf(":");
@@ -2495,7 +2502,9 @@ async function run() {
2495
2502
  const results = match2(fp, jobs, jobs.length);
2496
2503
  matchCount = results.length;
2497
2504
  const BOUNTY_SLOTS = 5;
2498
- const bountyTop = results.filter((r) => r.job.source === "bounty").slice(0, BOUNTY_SLOTS);
2505
+ const bountyMatches = results.filter((r) => r.job.source === "bounty");
2506
+ const rot = bountyMatches.length > 0 ? Math.floor(Date.now() / (5 * 60 * 1e3)) % bountyMatches.length : 0;
2507
+ const bountyTop = [...bountyMatches.slice(rot), ...bountyMatches.slice(0, rot)].slice(0, BOUNTY_SLOTS);
2499
2508
  const roleTop = results.filter((r) => r.job.source !== "bounty").slice(0, 25 - bountyTop.length);
2500
2509
  topMatches = [...roleTop, ...bountyTop].map((r) => ({
2501
2510
  id: r.job.id,
@@ -94,19 +94,16 @@ function buildContextVerbs(topMatches, sessionTags) {
94
94
  if (overlap.length >= 2) {
95
95
  const a = titleCase(overlap[0]);
96
96
  const b = titleCase(overlap[1]);
97
- headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A role matching what you're building`];
97
+ headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A match for what you're building \u2014 link below`];
98
98
  } else if (overlap.length === 1) {
99
99
  const a = titleCase(overlap[0]);
100
- headers = [`\u2726 A role matching your ${a} work`, `\u2726 Your ${a} work \u2014 link in the tip below`];
100
+ headers = [`\u2726 Work in your ${a} stack \u2014 link below`, `\u2726 Your ${a} work \u2014 link in the tip below`];
101
101
  } else {
102
- headers = [`\u2726 A role that fits your work`, `\u2726 Job match for you \u2014 link in the tip below`];
102
+ headers = [`\u2726 Work that fits your stack`, `\u2726 A match for you \u2014 link in the tip below`];
103
103
  }
104
104
  const list = Array.isArray(topMatches) ? topMatches : [];
105
- const bounty = list.find((m) => m && m.source === "bounty" && m.amountUSD != null) || list.find((m) => m && m.source === "bounty");
106
- if (bounty) {
107
- const money = bounty.amountUSD != null ? `$${bounty.amountUSD.toLocaleString()} ` : "";
108
- headers.unshift(`\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`);
109
- }
105
+ const hasBounty = list.some((m) => m && m.source === "bounty");
106
+ if (hasBounty) headers.unshift(`\u2726 Roles + \u{1F48E} paid bounties in your stack \u2014 link below`);
110
107
  return headers;
111
108
  }
112
109
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -101,19 +101,16 @@ function buildContextVerbs(topMatches, sessionTags) {
101
101
  if (overlap.length >= 2) {
102
102
  const a = titleCase(overlap[0]);
103
103
  const b = titleCase(overlap[1]);
104
- headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A role matching what you're building`];
104
+ headers = [`\u2726 Fits your ${a} + ${b} work`, `\u2726 A match for what you're building \u2014 link below`];
105
105
  } else if (overlap.length === 1) {
106
106
  const a = titleCase(overlap[0]);
107
- headers = [`\u2726 A role matching your ${a} work`, `\u2726 Your ${a} work \u2014 link in the tip below`];
107
+ headers = [`\u2726 Work in your ${a} stack \u2014 link below`, `\u2726 Your ${a} work \u2014 link in the tip below`];
108
108
  } else {
109
- headers = [`\u2726 A role that fits your work`, `\u2726 Job match for you \u2014 link in the tip below`];
109
+ headers = [`\u2726 Work that fits your stack`, `\u2726 A match for you \u2014 link in the tip below`];
110
110
  }
111
111
  const list = Array.isArray(topMatches) ? topMatches : [];
112
- const bounty = list.find((m) => m && m.source === "bounty" && m.amountUSD != null) || list.find((m) => m && m.source === "bounty");
113
- if (bounty) {
114
- const money = bounty.amountUSD != null ? `$${bounty.amountUSD.toLocaleString()} ` : "";
115
- headers.unshift(`\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`);
116
- }
112
+ const hasBounty = list.some((m) => m && m.source === "bounty");
113
+ if (hasBounty) headers.unshift(`\u2726 Roles + \u{1F48E} paid bounties in your stack \u2014 link below`);
117
114
  return headers;
118
115
  }
119
116
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -199,8 +196,15 @@ function buildTips(topMatches, baseUrl, max = 8) {
199
196
  const perCompany = /* @__PURE__ */ new Map();
200
197
  const COMPANY_CAP = 2;
201
198
  const all = Array.isArray(topMatches) ? topMatches : [];
202
- const leadBounty = all.find((m) => m && m.source === "bounty");
203
- const ordered = leadBounty ? [leadBounty, ...interleaveBySource(all.filter((m) => m !== leadBounty))] : interleaveBySource(all);
199
+ const bountyQ = all.filter((m) => m && m.source === "bounty");
200
+ const roleQ = interleaveBySource(all.filter((m) => m && m.source !== "bounty"));
201
+ const ordered = [];
202
+ let bi = 0;
203
+ let ri = 0;
204
+ while (bi < bountyQ.length || ri < roleQ.length) {
205
+ if (bi < bountyQ.length) ordered.push(bountyQ[bi++]);
206
+ if (ri < roleQ.length) ordered.push(roleQ[ri++]);
207
+ }
204
208
  for (const m of ordered) {
205
209
  if (!m || !m.title || !m.company || !m.id) continue;
206
210
  const idx = String(m.id).indexOf(":");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
5
5
  "repository": {
6
6
  "type": "git",