terminalhire 0.3.2 → 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,21 +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
- const bountyHeader = `\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`;
3196
- if (list[0] && list[0].source === "bounty") headers.unshift(bountyHeader);
3197
- else headers.push(bountyHeader);
3198
- }
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`);
3199
3197
  return headers;
3200
3198
  }
3201
3199
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -3280,7 +3278,17 @@ function buildTips(topMatches, baseUrl, max = 8) {
3280
3278
  const seenRole = /* @__PURE__ */ new Set();
3281
3279
  const perCompany = /* @__PURE__ */ new Map();
3282
3280
  const COMPANY_CAP = 2;
3283
- for (const m of interleaveBySource(Array.isArray(topMatches) ? topMatches : [])) {
3281
+ const all = Array.isArray(topMatches) ? topMatches : [];
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
+ }
3291
+ for (const m of ordered) {
3284
3292
  if (!m || !m.title || !m.company || !m.id) continue;
3285
3293
  const idx = String(m.id).indexOf(":");
3286
3294
  if (idx <= 0) continue;
@@ -4107,7 +4115,9 @@ async function run10() {
4107
4115
  const results = match2(fp, jobs, jobs.length);
4108
4116
  matchCount = results.length;
4109
4117
  const BOUNTY_SLOTS = 5;
4110
- 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);
4111
4121
  const roleTop = results.filter((r) => r.job.source !== "bounty").slice(0, 25 - bountyTop.length);
4112
4122
  topMatches = [...roleTop, ...bountyTop].map((r) => ({
4113
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,21 +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
- const bountyHeader = `\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`;
2025
- if (list[0] && list[0].source === "bounty") headers.unshift(bountyHeader);
2026
- else headers.push(bountyHeader);
2027
- }
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`);
2028
2026
  return headers;
2029
2027
  }
2030
2028
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -2109,7 +2107,17 @@ function buildTips(topMatches, baseUrl, max = 8) {
2109
2107
  const seenRole = /* @__PURE__ */ new Set();
2110
2108
  const perCompany = /* @__PURE__ */ new Map();
2111
2109
  const COMPANY_CAP = 2;
2112
- for (const m of interleaveBySource(Array.isArray(topMatches) ? topMatches : [])) {
2110
+ const all = Array.isArray(topMatches) ? topMatches : [];
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
+ }
2120
+ for (const m of ordered) {
2113
2121
  if (!m || !m.title || !m.company || !m.id) continue;
2114
2122
  const idx = String(m.id).indexOf(":");
2115
2123
  if (idx <= 0) continue;
@@ -2494,7 +2502,9 @@ async function run() {
2494
2502
  const results = match2(fp, jobs, jobs.length);
2495
2503
  matchCount = results.length;
2496
2504
  const BOUNTY_SLOTS = 5;
2497
- 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);
2498
2508
  const roleTop = results.filter((r) => r.job.source !== "bounty").slice(0, 25 - bountyTop.length);
2499
2509
  topMatches = [...roleTop, ...bountyTop].map((r) => ({
2500
2510
  id: r.job.id,
@@ -94,21 +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
- const bountyHeader = `\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`;
109
- if (list[0] && list[0].source === "bounty") headers.unshift(bountyHeader);
110
- else headers.push(bountyHeader);
111
- }
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`);
112
107
  return headers;
113
108
  }
114
109
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -101,21 +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
- const bountyHeader = `\u2726 \u{1F48E} A ${money}bounty in your stack \u2014 link below`;
116
- if (list[0] && list[0].source === "bounty") headers.unshift(bountyHeader);
117
- else headers.push(bountyHeader);
118
- }
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`);
119
114
  return headers;
120
115
  }
121
116
  function buildSpinnerPool(topMatches, max = 6, opts = {}) {
@@ -200,7 +195,17 @@ function buildTips(topMatches, baseUrl, max = 8) {
200
195
  const seenRole = /* @__PURE__ */ new Set();
201
196
  const perCompany = /* @__PURE__ */ new Map();
202
197
  const COMPANY_CAP = 2;
203
- for (const m of interleaveBySource(Array.isArray(topMatches) ? topMatches : [])) {
198
+ const all = Array.isArray(topMatches) ? topMatches : [];
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
+ }
208
+ for (const m of ordered) {
204
209
  if (!m || !m.title || !m.company || !m.id) continue;
205
210
  const idx = String(m.id).indexOf(":");
206
211
  if (idx <= 0) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.3.2",
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",