terminalhire 0.4.6 → 0.6.0

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.
@@ -10,6 +10,10 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/open-url.js
13
+ var open_url_exports = {};
14
+ __export(open_url_exports, {
15
+ openInBrowser: () => openInBrowser
16
+ });
13
17
  import { spawn } from "child_process";
14
18
  function openInBrowser(url) {
15
19
  let cmd;
@@ -923,6 +927,52 @@ function githubToFingerprint(p) {
923
927
  const seniorityBand = inferSeniority(p);
924
928
  return { skillTags, seniorityBand };
925
929
  }
930
+ async function fetchOwnedRepoTraction(login, token) {
931
+ const computedAt = (/* @__PURE__ */ new Date()).toISOString();
932
+ const empty = (status) => ({
933
+ status,
934
+ totalStars: 0,
935
+ totalForks: 0,
936
+ reposWithStars: 0,
937
+ top: [],
938
+ computedAt
939
+ });
940
+ let repos;
941
+ try {
942
+ repos = await ghFetch(
943
+ `/users/${login}/repos?sort=pushed&per_page=100`,
944
+ token
945
+ );
946
+ } catch (err) {
947
+ const msg = err instanceof Error ? err.message : String(err);
948
+ const status = /HTTP 403|HTTP 429|rate limit/i.test(msg) ? "rate-limited" : "failed";
949
+ console.warn(`[github] ${login}: traction fetch failed (${status}) \u2014`, msg);
950
+ return empty(status);
951
+ }
952
+ if (!Array.isArray(repos)) return empty("failed");
953
+ const owned = repos.filter((r) => r && !r.fork && !r.archived);
954
+ let totalStars = 0;
955
+ let totalForks = 0;
956
+ let reposWithStars = 0;
957
+ const ranked = [];
958
+ for (const r of owned) {
959
+ const stars = typeof r.stargazers_count === "number" ? r.stargazers_count : 0;
960
+ const forks = typeof r.forks_count === "number" ? r.forks_count : 0;
961
+ totalStars += stars;
962
+ totalForks += forks;
963
+ if (stars >= 1) reposWithStars++;
964
+ ranked.push({ name: r.name, stars, forks });
965
+ }
966
+ ranked.sort((a, b) => b.stars - a.stars || b.forks - a.forks);
967
+ return {
968
+ status: "ok",
969
+ totalStars,
970
+ totalForks,
971
+ reposWithStars,
972
+ top: ranked.slice(0, TRACTION_TOP_N),
973
+ computedAt
974
+ };
975
+ }
926
976
  async function ghFetchRaw(path, token) {
927
977
  return fetch(`https://api.github.com${path}`, { headers: ghHeaders(token) });
928
978
  }
@@ -1131,11 +1181,12 @@ function deriveResumeTrend(cred, repoRecency, now = Date.now()) {
1131
1181
  }
1132
1182
  return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
1133
1183
  }
1134
- var MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
1184
+ var TRACTION_TOP_N, MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
1135
1185
  var init_github = __esm({
1136
1186
  "../../packages/core/src/github.ts"() {
1137
1187
  "use strict";
1138
1188
  init_vocabulary();
1189
+ TRACTION_TOP_N = 6;
1139
1190
  MIN_STARS = 50;
1140
1191
  MIN_CONTRIBUTORS = 10;
1141
1192
  CANDIDATE_PR_PAGE = 50;
@@ -1889,14 +1940,14 @@ async function mapWithConcurrency(items, limit, fn) {
1889
1940
  if (items.length === 0) return results;
1890
1941
  const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
1891
1942
  let next = 0;
1892
- async function run13() {
1943
+ async function run14() {
1893
1944
  for (; ; ) {
1894
1945
  const i = next++;
1895
1946
  if (i >= items.length) return;
1896
1947
  results[i] = await fn(items[i], i);
1897
1948
  }
1898
1949
  }
1899
- await Promise.all(Array.from({ length: workers }, run13));
1950
+ await Promise.all(Array.from({ length: workers }, run14));
1900
1951
  return results;
1901
1952
  }
1902
1953
  var init_concurrency = __esm({
@@ -2814,6 +2865,7 @@ __export(src_exports, {
2814
2865
  expandWeighted: () => expandWeighted,
2815
2866
  extractSkillTags: () => extractSkillTags,
2816
2867
  fetchGitHubProfile: () => fetchGitHubProfile,
2868
+ fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
2817
2869
  fetchRepoRecency: () => fetchRepoRecency,
2818
2870
  flattenTiers: () => flattenTiers,
2819
2871
  getBuyer: () => getBuyer,
@@ -3122,11 +3174,11 @@ async function runLogin() {
3122
3174
  if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") {
3123
3175
  const { createRequire: createRequire2 } = await import("module");
3124
3176
  const { fileURLToPath: fileURLToPath7 } = await import("url");
3125
- const { join: join17, dirname: dirname3 } = await import("path");
3177
+ const { join: join18, dirname: dirname3 } = await import("path");
3126
3178
  const __dirname6 = fileURLToPath7(new URL(".", import.meta.url));
3127
- const fixturePath = join17(__dirname6, "../../fixtures/github-sample.json");
3128
- const { readFileSync: readFileSync16 } = await import("fs");
3129
- ghProfile = JSON.parse(readFileSync16(fixturePath, "utf8"));
3179
+ const fixturePath = join18(__dirname6, "../../fixtures/github-sample.json");
3180
+ const { readFileSync: readFileSync17 } = await import("fs");
3181
+ ghProfile = JSON.parse(readFileSync17(fixturePath, "utf8"));
3130
3182
  } else {
3131
3183
  ghProfile = await fetchGitHubProfile2(login, token);
3132
3184
  }
@@ -3180,8 +3232,8 @@ async function runLogin() {
3180
3232
  const skipWeb = process.argv.includes("--no-web");
3181
3233
  if (!isMock && !skipWeb) {
3182
3234
  try {
3183
- const OAUTH_BASE = "https://www.terminalhire.com";
3184
- const webUrl = `${OAUTH_BASE}/api/auth/github?next=/dashboard`;
3235
+ const OAUTH_BASE2 = "https://www.terminalhire.com";
3236
+ const webUrl = `${OAUTH_BASE2}/api/auth/github?next=/dashboard`;
3185
3237
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
3186
3238
  console.log(" Your web profile & r\xE9sum\xE9 \u2014 public GitHub data only,");
3187
3239
  console.log(" your local profile is NOT uploaded.");
@@ -3735,6 +3787,50 @@ __export(jpi_claim_exports, {
3735
3787
  import { readFileSync as readFileSync7, existsSync as existsSync5 } from "fs";
3736
3788
  import { join as join7 } from "path";
3737
3789
  import { homedir as homedir6 } from "os";
3790
+ import { execFile } from "child_process";
3791
+ import { promisify } from "util";
3792
+ import { createInterface as createInterface3 } from "readline";
3793
+ async function sh(cmd, args3, opts = {}) {
3794
+ const { stdout } = await pExecFile(cmd, args3, { ...opts, shell: false, maxBuffer: 16 * 1024 * 1024 });
3795
+ return String(stdout).trim();
3796
+ }
3797
+ async function confirm(question) {
3798
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
3799
+ try {
3800
+ const ans = await new Promise((resolve2) => rl.question(question, resolve2));
3801
+ return /^y(es)?$/i.test(String(ans).trim());
3802
+ } finally {
3803
+ rl.close();
3804
+ }
3805
+ }
3806
+ function parseArgs(argv) {
3807
+ const flags = {};
3808
+ const positional = [];
3809
+ for (let i = 0; i < argv.length; i++) {
3810
+ const a = argv[i];
3811
+ if (a.startsWith("--")) {
3812
+ const key = a.slice(2);
3813
+ if (VALUE_FLAGS.has(key)) {
3814
+ const val = argv[i + 1];
3815
+ if (val === void 0 || val.startsWith("--")) {
3816
+ console.error(`terminalhire claim: --${key} requires a value.`);
3817
+ process.exit(1);
3818
+ }
3819
+ flags[key] = val;
3820
+ i++;
3821
+ } else {
3822
+ flags[key] = true;
3823
+ }
3824
+ } else {
3825
+ positional.push(a);
3826
+ }
3827
+ }
3828
+ return { flags, positional };
3829
+ }
3830
+ function parseRepoFromRemote(url) {
3831
+ const m = String(url ?? "").trim().match(/github\.com[:/]+([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
3832
+ return m ? `${m[1]}/${m[2]}` : null;
3833
+ }
3738
3834
  function findBountyInCache(bountyId) {
3739
3835
  try {
3740
3836
  if (!existsSync5(INDEX_CACHE_FILE3)) return null;
@@ -3801,9 +3897,9 @@ function fmtAmount(a) {
3801
3897
  return a != null ? "$" + a.toLocaleString() : "$\u2014";
3802
3898
  }
3803
3899
  function printMetric(rate) {
3804
- const pct = Math.round(rate.rate * 100);
3900
+ const pct2 = Math.round(rate.rate * 100);
3805
3901
  console.log(`
3806
- \u{1F4CA} Accepted-PR rate: ${rate.merged}/${rate.total} claims merged (${pct}%)`);
3902
+ \u{1F4CA} Accepted-PR rate: ${rate.merged}/${rate.total} claims merged (${pct2}%)`);
3807
3903
  }
3808
3904
  async function resolveBounty(arg) {
3809
3905
  let bountyId, title, repoFullName, issueUrl, amountUSD;
@@ -3889,7 +3985,10 @@ async function cmdRecord(arg) {
3889
3985
  console.log(" \u2022 MUST NOT `git push` or `gh pr` \u2014 pushing happens only via `terminalhire submit`");
3890
3986
  console.log(" \u2022 clone + static analysis + patch only; NO test/build execution without explicit approval");
3891
3987
  console.log(" \u2022 no access to ~/.terminalhire (the executor never needs your profile)");
3892
- console.log("\n Next: do the work, then `terminalhire claim update " + claim.id + " <state>` as you progress.");
3988
+ console.log("\n Next:");
3989
+ console.log(" 1. record the worktree: terminalhire claim attach " + claim.id + " --worktree <path> --branch <branch>");
3990
+ console.log(" 2. do the work + review, then mark it cleared: terminalhire claim update " + claim.id + " ready");
3991
+ console.log(" 3. publish (pushes to your fork + opens the PR): terminalhire claim submit " + claim.id);
3893
3992
  }
3894
3993
  async function cmdPreview(arg, { json } = {}) {
3895
3994
  if (!arg) {
@@ -4015,33 +4114,223 @@ async function cmdRelease(id) {
4015
4114
  console.log(removed ? `Released claim: ${id}` : `terminalhire claim: no claim with id '${id}'.`);
4016
4115
  if (!removed) process.exit(1);
4017
4116
  }
4117
+ async function cmdAttach(id, worktree, branch) {
4118
+ const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
4119
+ if (!id || !worktree || !branch) {
4120
+ console.error("Usage: terminalhire claim attach <id> --worktree <path> --branch <branchName>");
4121
+ console.error(" Records the worktree + branch so `terminalhire claim submit` can verify identity before pushing.");
4122
+ process.exit(1);
4123
+ }
4124
+ if (!claims.findClaim(id)) {
4125
+ console.error(`terminalhire claim: no claim with id '${id}'.`);
4126
+ process.exit(1);
4127
+ }
4128
+ let toplevel;
4129
+ try {
4130
+ toplevel = await sh("git", ["-C", worktree, "rev-parse", "--show-toplevel"]);
4131
+ } catch {
4132
+ console.error(`terminalhire claim: '${worktree}' is not a git work tree.`);
4133
+ process.exit(1);
4134
+ }
4135
+ claims.updateClaim(id, { worktreePath: toplevel, branch });
4136
+ console.log(`Attached ${id}: worktree=${toplevel} branch=${branch}`);
4137
+ }
4138
+ async function cmdSubmit(id, worktreeOverride) {
4139
+ const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
4140
+ if (!id) {
4141
+ console.error("Usage: terminalhire claim submit <id> [--worktree <path>]");
4142
+ process.exit(1);
4143
+ }
4144
+ const claim = claims.findClaim(id);
4145
+ if (!claim) {
4146
+ console.error(`terminalhire claim: no claim with id '${id}'.`);
4147
+ process.exit(1);
4148
+ }
4149
+ if (claim.state !== "ready") {
4150
+ console.error(
4151
+ `terminalhire claim: ${id} is '${claim.state}', not 'ready'. Submit only runs after the review gate clears it:
4152
+ terminalhire claim update ${id} ready`
4153
+ );
4154
+ process.exit(1);
4155
+ }
4156
+ if (claim.review && claim.review.verdict === "revise") {
4157
+ console.error(`terminalhire claim: ${id} review verdict is 'revise' \u2014 the gate said do not submit. Resolve blockers and re-review first.`);
4158
+ process.exit(1);
4159
+ }
4160
+ if (!claim.worktreePath || !claim.branch) {
4161
+ console.error(
4162
+ `terminalhire claim: ${id} has no recorded worktree/branch \u2014 cannot verify what to push. Run:
4163
+ terminalhire claim attach ${id} --worktree <path> --branch <branch>`
4164
+ );
4165
+ process.exit(1);
4166
+ }
4167
+ const inspectDir = worktreeOverride || process.cwd();
4168
+ let toplevel;
4169
+ try {
4170
+ toplevel = await sh("git", ["-C", inspectDir, "rev-parse", "--show-toplevel"]);
4171
+ } catch {
4172
+ console.error(
4173
+ `terminalhire claim: '${inspectDir}' is not a git work tree.
4174
+ Run submit from inside the claim's worktree (or pass --worktree <path>).`
4175
+ );
4176
+ process.exit(1);
4177
+ }
4178
+ if (toplevel !== claim.worktreePath) {
4179
+ console.error(
4180
+ `terminalhire claim: worktree mismatch \u2014 refusing to push.
4181
+ expected: ${claim.worktreePath}
4182
+ found: ${toplevel}
4183
+ Run submit from inside the claim's worktree (or pass --worktree <path>).`
4184
+ );
4185
+ process.exit(1);
4186
+ }
4187
+ const wt = toplevel;
4188
+ const curBranch = await sh("git", ["-C", wt, "rev-parse", "--abbrev-ref", "HEAD"]);
4189
+ if (curBranch !== claim.branch) {
4190
+ console.error(
4191
+ `terminalhire claim: branch mismatch \u2014 refusing to push.
4192
+ expected: ${claim.branch}
4193
+ found: ${curBranch}
4194
+ Check out the claim's branch first.`
4195
+ );
4196
+ process.exit(1);
4197
+ }
4198
+ let defaultBranch = null;
4199
+ try {
4200
+ defaultBranch = (await sh("git", ["-C", wt, "symbolic-ref", "--short", "refs/remotes/origin/HEAD"])).replace(/^origin\//, "");
4201
+ } catch {
4202
+ }
4203
+ if (defaultBranch && claim.branch === defaultBranch) {
4204
+ console.error(`terminalhire claim: '${claim.branch}' is the default branch \u2014 open the PR from a feature branch.`);
4205
+ process.exit(1);
4206
+ }
4207
+ if (defaultBranch) {
4208
+ let ahead = "0";
4209
+ try {
4210
+ ahead = await sh("git", ["-C", wt, "rev-list", "--count", `origin/${defaultBranch}..HEAD`]);
4211
+ } catch {
4212
+ ahead = "1";
4213
+ }
4214
+ if (ahead === "0") {
4215
+ console.error(`terminalhire claim: branch has no commits ahead of origin/${defaultBranch} \u2014 nothing to submit.`);
4216
+ process.exit(1);
4217
+ }
4218
+ }
4219
+ const dirty = await sh("git", ["-C", wt, "status", "--porcelain"]);
4220
+ if (dirty) {
4221
+ console.error("terminalhire claim: working tree is not clean \u2014 commit or stash before submitting (submit pushes what was reviewed).");
4222
+ process.exit(1);
4223
+ }
4224
+ let originUrl;
4225
+ try {
4226
+ originUrl = await sh("git", ["-C", wt, "remote", "get-url", "origin"]);
4227
+ } catch {
4228
+ console.error("terminalhire claim: no 'origin' remote in the worktree.");
4229
+ process.exit(1);
4230
+ }
4231
+ const originRepo = parseRepoFromRemote(originUrl);
4232
+ if (!originRepo) {
4233
+ console.error(`terminalhire claim: could not parse owner/repo from origin (${originUrl}).`);
4234
+ process.exit(1);
4235
+ }
4236
+ if (originRepo.toLowerCase() === claim.repoFullName.toLowerCase()) {
4237
+ console.error(
4238
+ `terminalhire claim: origin points at the UPSTREAM bounty repo (${claim.repoFullName}), not a fork.
4239
+ Pushing would create a branch directly on the target repo. Fork first:
4240
+ gh repo fork ${claim.repoFullName} --clone=false
4241
+ set your fork as 'origin' (or push it there), then retry.`
4242
+ );
4243
+ process.exit(1);
4244
+ }
4245
+ let ghUser;
4246
+ try {
4247
+ ghUser = await sh("gh", ["api", "user", "-q", ".login"]);
4248
+ } catch {
4249
+ console.error("terminalhire claim: 'gh' CLI not available or not authenticated. Run 'gh auth login'.");
4250
+ process.exit(1);
4251
+ }
4252
+ const upstream = claim.repoFullName;
4253
+ const head = `${ghUser}:${claim.branch}`;
4254
+ console.log(`
4255
+ SUBMIT \xB7 ${claim.title}`);
4256
+ console.log(` upstream: ${upstream}`);
4257
+ console.log(` fork: ${originRepo}`);
4258
+ console.log(` branch: ${claim.branch}`);
4259
+ console.log(` head: ${head}`);
4260
+ console.log(` issue: ${claim.issueUrl}`);
4261
+ const ok = await confirm(`
4262
+ Push '${claim.branch}' to ${originRepo} and open a PR against ${upstream}? (y/N) `);
4263
+ if (!ok) {
4264
+ console.log("Aborted \u2014 nothing pushed.");
4265
+ return;
4266
+ }
4267
+ try {
4268
+ await sh("git", ["-C", wt, "push", "origin", claim.branch]);
4269
+ } catch (err) {
4270
+ console.error(`terminalhire claim: git push failed (NOT force-pushed). ${err.stderr || err.message || err}`);
4271
+ console.error(` Resolve and retry, or open the PR manually then: terminalhire claim update ${id} submitted <prUrl>`);
4272
+ process.exit(1);
4273
+ }
4274
+ let prUrl = null;
4275
+ try {
4276
+ const existing = await sh("gh", ["pr", "list", "--repo", upstream, "--head", head, "--state", "open", "--json", "url", "-q", ".[0].url // empty"]);
4277
+ if (existing) prUrl = existing;
4278
+ } catch {
4279
+ }
4280
+ if (!prUrl) {
4281
+ const issueNum = (parseGitHubUrl(claim.issueUrl) || {}).number;
4282
+ const body = issueNum ? `Closes #${issueNum}` : "";
4283
+ try {
4284
+ const out = await sh("gh", ["pr", "create", "--repo", upstream, "--head", head, "--title", claim.title, "--body", body]);
4285
+ prUrl = out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("https://github.com/")).pop() || null;
4286
+ } catch (err) {
4287
+ console.error(`terminalhire claim: branch pushed, but 'gh pr create' failed. ${err.stderr || err.message || err}`);
4288
+ console.error(` Open the PR manually (gh pr create / web UI), then: terminalhire claim update ${id} submitted <prUrl>`);
4289
+ process.exit(1);
4290
+ }
4291
+ }
4292
+ if (!prUrl || !parseGitHubUrl(prUrl)) {
4293
+ console.error(`terminalhire claim: could not determine the PR URL. Set it manually: terminalhire claim update ${id} submitted <prUrl>`);
4294
+ process.exit(1);
4295
+ }
4296
+ claims.updateClaim(id, { state: "submitted", prUrl });
4297
+ console.log(`
4298
+ \u2713 Submitted ${id} \u2192 ${prUrl}`);
4299
+ console.log(` Run 'terminalhire claim status ${id}' after the maintainer acts to fold the merge into your accepted-PR rate.`);
4300
+ }
4018
4301
  async function run4() {
4019
4302
  const verb = process.argv[2];
4020
- const rest = process.argv.slice(3).filter((a) => !a.startsWith("--"));
4021
- const active = process.argv.includes("--active");
4022
- const json = process.argv.includes("--json");
4303
+ const { flags, positional } = parseArgs(process.argv.slice(3));
4304
+ const active = Boolean(flags.active);
4305
+ const json = Boolean(flags.json);
4023
4306
  try {
4024
4307
  switch (verb) {
4025
4308
  case "preview":
4026
- await cmdPreview(rest[0], { json });
4309
+ await cmdPreview(positional[0], { json });
4027
4310
  break;
4028
4311
  case "record":
4029
- await cmdRecord(rest[0]);
4312
+ await cmdRecord(positional[0]);
4030
4313
  break;
4031
4314
  case "list":
4032
4315
  await cmdList(active);
4033
4316
  break;
4034
4317
  case "status":
4035
- await cmdStatus(rest[0]);
4318
+ await cmdStatus(positional[0]);
4036
4319
  break;
4037
4320
  case "update":
4038
- await cmdUpdate(rest[0], rest[1], rest[2]);
4321
+ await cmdUpdate(positional[0], positional[1], positional[2]);
4322
+ break;
4323
+ case "attach":
4324
+ await cmdAttach(positional[0], flags.worktree, flags.branch);
4325
+ break;
4326
+ case "submit":
4327
+ await cmdSubmit(positional[0], flags.worktree);
4039
4328
  break;
4040
4329
  case "release":
4041
- await cmdRelease(rest[0]);
4330
+ await cmdRelease(positional[0]);
4042
4331
  break;
4043
4332
  default:
4044
- console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | list | status | update | release`);
4333
+ console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | attach | list | status | update | submit | release`);
4045
4334
  process.exit(1);
4046
4335
  }
4047
4336
  } catch (err) {
@@ -4049,7 +4338,7 @@ async function run4() {
4049
4338
  process.exit(1);
4050
4339
  }
4051
4340
  }
4052
- var TERMINALHIRE_DIR6, INDEX_CACHE_FILE3, GH_API, GH_HEADERS;
4341
+ var TERMINALHIRE_DIR6, INDEX_CACHE_FILE3, GH_API, GH_HEADERS, pExecFile, VALUE_FLAGS;
4053
4342
  var init_jpi_claim = __esm({
4054
4343
  "bin/jpi-claim.js"() {
4055
4344
  "use strict";
@@ -4057,141 +4346,1471 @@ var init_jpi_claim = __esm({
4057
4346
  INDEX_CACHE_FILE3 = join7(TERMINALHIRE_DIR6, "index-cache.json");
4058
4347
  GH_API = "https://api.github.com";
4059
4348
  GH_HEADERS = { "User-Agent": "terminalhire-claim", Accept: "application/vnd.github+json" };
4349
+ pExecFile = promisify(execFile);
4350
+ VALUE_FLAGS = /* @__PURE__ */ new Set(["worktree", "branch"]);
4060
4351
  }
4061
4352
  });
4062
4353
 
4063
- // bin/jpi-profile.js
4064
- var jpi_profile_exports = {};
4065
- __export(jpi_profile_exports, {
4066
- run: () => run5
4067
- });
4068
- import { createInterface as createInterface3 } from "readline";
4069
- function prompt3(question) {
4070
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
4071
- return new Promise((resolve2) => {
4072
- rl.question(question, (answer) => {
4073
- rl.close();
4074
- resolve2(answer.trim());
4075
- });
4076
- });
4354
+ // ../../packages/core/src/episodes/node-model.ts
4355
+ function isRecord(value) {
4356
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4077
4357
  }
4078
- async function run5() {
4079
- const { readProfile: readProfile2, writeProfile: writeProfile2, deleteProfile: deleteProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
4080
- const args3 = process.argv.slice(2);
4081
- if (args3.includes("--show")) {
4082
- const profile = await readProfile2();
4083
- console.log("\n\u2726 terminalhire local profile (encrypted at rest \u2014 shown here for your review only)\n");
4084
- console.log(" Skill tags: " + (profile.skillTags.length > 0 ? profile.skillTags.join(", ") : "(none yet)"));
4085
- console.log(" Seniority: " + (profile.seniority ?? "(not set)"));
4086
- if (profile.displayName) console.log(" Display name: " + profile.displayName);
4087
- if (profile.contactEmail) console.log(" Contact email: " + profile.contactEmail);
4088
- if (profile.remoteOnly !== void 0) console.log(" Remote only: " + profile.remoteOnly);
4089
- if (profile.compFloorUsd !== void 0) console.log(" Comp floor USD: $" + profile.compFloorUsd);
4090
- console.log(" Employer sessions contributed: " + profile.hasEmployerSessions);
4091
- if (profile.github) {
4092
- console.log("");
4093
- console.log(" GitHub (public data only, scope: read:user):");
4094
- console.log(" Login: @" + profile.github.login);
4095
- console.log(" Profile URL: " + profile.github.profileUrl);
4096
- console.log(" Top languages: " + profile.github.topLanguages.join(", "));
4097
- console.log(" Public repos: " + profile.github.publicRepos);
4098
- console.log("");
4099
- console.log(' GitHub fields are included in a lead ONLY when you consent "yes".');
4100
- console.log(" To disconnect GitHub: terminalhire logout");
4101
- } else {
4102
- console.log("");
4103
- console.log(" GitHub: not connected (run: terminalhire login for instant enrichment)");
4358
+ function asString(value, fallback = "") {
4359
+ return typeof value === "string" ? value : fallback;
4360
+ }
4361
+ function asStringOrNull(value) {
4362
+ return typeof value === "string" ? value : null;
4363
+ }
4364
+ function asBool(value) {
4365
+ return value === true;
4366
+ }
4367
+ function parseBlock(raw) {
4368
+ if (!isRecord(raw)) {
4369
+ return { type: "unknown", rawType: typeof raw };
4370
+ }
4371
+ const t = raw.type;
4372
+ if (t === "text") {
4373
+ return { type: "text", text: asString(raw.text) };
4374
+ }
4375
+ if (t === "tool_use") {
4376
+ return { type: "tool_use", id: asString(raw.id), name: asString(raw.name), input: raw.input };
4377
+ }
4378
+ if (t === "tool_result") {
4379
+ return {
4380
+ type: "tool_result",
4381
+ toolUseId: asString(raw.tool_use_id),
4382
+ content: raw.content,
4383
+ isError: asBool(raw.is_error)
4384
+ };
4385
+ }
4386
+ return { type: "unknown", rawType: typeof t === "string" ? t : String(t) };
4387
+ }
4388
+ function parseContent(content) {
4389
+ if (typeof content === "string") {
4390
+ return content.length > 0 ? [{ type: "text", text: content }] : [];
4391
+ }
4392
+ if (Array.isArray(content)) {
4393
+ return content.map(parseBlock);
4394
+ }
4395
+ return [];
4396
+ }
4397
+ function toNode(raw, opts = {}) {
4398
+ if (!isRecord(raw)) {
4399
+ if (opts.strict) {
4400
+ throw new Error(`toNode: node is not an object (got ${typeof raw})`);
4104
4401
  }
4105
- console.log("");
4106
- console.log(" Raw profile JSON:");
4107
- console.log(JSON.stringify(profile, null, 2));
4108
- console.log("\nThis profile NEVER leaves your machine except in a consented lead payload.");
4109
- return;
4402
+ return makeUnknown(emptyBase(), typeof raw);
4403
+ }
4404
+ const message = isRecord(raw.message) ? raw.message : void 0;
4405
+ const content = parseContent(message?.content);
4406
+ const base = {
4407
+ uuid: asString(raw.uuid),
4408
+ parentUuid: asStringOrNull(raw.parentUuid),
4409
+ sessionId: asString(raw.sessionId),
4410
+ cwd: asString(raw.cwd),
4411
+ gitBranch: asString(raw.gitBranch),
4412
+ timestamp: asString(raw.timestamp),
4413
+ isSidechain: asBool(raw.isSidechain),
4414
+ userType: asString(raw.userType),
4415
+ role: asString(message?.role),
4416
+ content,
4417
+ text: content.reduce((acc, b) => b.type === "text" ? [...acc, b.text] : acc, []).join("\n")
4418
+ };
4419
+ const type = raw.type;
4420
+ if (type === "summary" || asBool(raw.isCompactSummary) || isRecord(raw.compactMetadata)) {
4421
+ return { ...base, kind: "summary" /* Summary */, toolName: null, toolUseId: null, isError: false, rawType: asString(type, "summary") };
4110
4422
  }
4111
- if (args3.includes("--delete")) {
4112
- console.log("\nThis will permanently delete your local terminalhire profile and encryption key.");
4113
- const answer = await prompt3('Type "yes" to confirm: ');
4114
- if (answer !== "yes") {
4115
- console.log("Aborted.");
4116
- process.exit(0);
4423
+ if (type === "system") {
4424
+ return { ...base, kind: "system" /* System */, toolName: null, toolUseId: null, isError: false, rawType: "system" };
4425
+ }
4426
+ if (type === "user") {
4427
+ const tr = content.find((b) => b.type === "tool_result");
4428
+ if (tr) {
4429
+ return { ...base, kind: "tool_result" /* ToolResult */, toolName: null, toolUseId: tr.toolUseId, isError: tr.isError, rawType: "user" };
4117
4430
  }
4118
- await deleteProfile2();
4119
- console.log("Profile and key deleted from ~/.terminalhire/");
4120
- return;
4431
+ return { ...base, kind: "user" /* User */, toolName: null, toolUseId: null, isError: false, rawType: "user" };
4121
4432
  }
4122
- if (args3.includes("--edit")) {
4123
- const profile = await readProfile2();
4124
- console.log("\n\u2726 terminalhire profile editor (press Enter to keep current value)\n");
4125
- const name = await prompt3(`Display name [${profile.displayName ?? "not set"}]: `);
4126
- if (name) profile.displayName = name;
4127
- const email = await prompt3(`Contact email [${profile.contactEmail ?? "not set"}]: `);
4128
- if (email) profile.contactEmail = email;
4129
- const remote = await prompt3(`Remote only? (y/n) [${profile.remoteOnly ? "y" : "n"}]: `);
4130
- if (remote === "y") profile.remoteOnly = true;
4131
- if (remote === "n") profile.remoteOnly = false;
4132
- const floor = await prompt3(`Comp floor USD [${profile.compFloorUsd ?? "not set"}]: `);
4133
- if (floor && !isNaN(parseInt(floor, 10))) profile.compFloorUsd = parseInt(floor, 10);
4134
- await writeProfile2(profile);
4135
- console.log("\nProfile updated (encrypted at ~/.terminalhire/profile.enc)");
4136
- return;
4433
+ if (type === "assistant") {
4434
+ const tu = content.find((b) => b.type === "tool_use");
4435
+ if (tu) {
4436
+ return { ...base, kind: "tool_use" /* ToolUse */, toolName: tu.name, toolUseId: tu.id, isError: false, rawType: "assistant" };
4437
+ }
4438
+ return { ...base, kind: "assistant" /* Assistant */, toolName: null, toolUseId: null, isError: false, rawType: "assistant" };
4137
4439
  }
4138
- console.log("Usage: terminalhire profile --show | --edit | --delete");
4440
+ const rawType = typeof type === "string" ? type : String(type);
4441
+ if (opts.strict) {
4442
+ throw new Error(`toNode: unrecognized node kind: ${rawType}`);
4443
+ }
4444
+ return makeUnknown(base, rawType);
4139
4445
  }
4140
- var init_jpi_profile = __esm({
4141
- "bin/jpi-profile.js"() {
4446
+ function makeUnknown(base, rawType) {
4447
+ return { ...base, kind: "unknown" /* Unknown */, toolName: null, toolUseId: null, isError: false, rawType };
4448
+ }
4449
+ function emptyBase() {
4450
+ return {
4451
+ uuid: "",
4452
+ parentUuid: null,
4453
+ sessionId: "",
4454
+ cwd: "",
4455
+ gitBranch: "",
4456
+ timestamp: "",
4457
+ isSidechain: false,
4458
+ userType: "",
4459
+ role: "",
4460
+ content: [],
4461
+ text: ""
4462
+ };
4463
+ }
4464
+ var init_node_model = __esm({
4465
+ "../../packages/core/src/episodes/node-model.ts"() {
4142
4466
  "use strict";
4143
4467
  }
4144
4468
  });
4145
4469
 
4146
- // src/signal.ts
4147
- var signal_exports = {};
4148
- __export(signal_exports, {
4149
- extractFingerprint: () => extractFingerprint
4150
- });
4151
- import { readFileSync as readFileSync8, readdirSync } from "fs";
4152
- import { execFileSync } from "child_process";
4153
- import { join as join8 } from "path";
4154
- function safeGit(args3, cwd) {
4155
- try {
4156
- return execFileSync("git", ["-C", cwd, ...args3], {
4157
- timeout: 2e3,
4158
- stdio: ["ignore", "pipe", "ignore"]
4159
- }).toString().trim();
4160
- } catch {
4161
- return "";
4470
+ // ../../packages/core/src/episodes/parser.ts
4471
+ function parseTranscript(text, opts = {}) {
4472
+ const nodes = [];
4473
+ const unknownKinds = /* @__PURE__ */ new Set();
4474
+ let malformedCount = 0;
4475
+ for (const line of text.split(/\r?\n/)) {
4476
+ const trimmed = line.trim();
4477
+ if (trimmed.length === 0) {
4478
+ continue;
4479
+ }
4480
+ let raw;
4481
+ try {
4482
+ raw = JSON.parse(trimmed);
4483
+ } catch {
4484
+ malformedCount++;
4485
+ continue;
4486
+ }
4487
+ const node = toNode(raw, { strict: opts.strict });
4488
+ if (node.kind === "unknown" /* Unknown */) {
4489
+ unknownKinds.add(node.rawType);
4490
+ }
4491
+ nodes.push(node);
4162
4492
  }
4493
+ return { nodes, malformedCount, unknownKinds: [...unknownKinds] };
4163
4494
  }
4164
- function isEmployerContext(cwd) {
4165
- const inRepo = safeGit(["rev-parse", "--is-inside-work-tree"], cwd);
4166
- if (inRepo !== "true") return false;
4167
- const remote = safeGit(["remote", "get-url", "origin"], cwd);
4168
- if (remote) {
4169
- const sshMatch = remote.match(/^git@([^:]+):/);
4170
- const httpsMatch = remote.match(/^https?:\/\/([^/]+)/);
4171
- const host = (sshMatch?.[1] ?? httpsMatch?.[1] ?? "").toLowerCase();
4172
- if (host) return !PERSONAL_GIT_HOSTS.has(host);
4495
+ var init_parser = __esm({
4496
+ "../../packages/core/src/episodes/parser.ts"() {
4497
+ "use strict";
4498
+ init_node_model();
4173
4499
  }
4174
- const email = safeGit(["config", "user.email"], cwd);
4175
- const domain = email.split("@")[1]?.toLowerCase() ?? "";
4176
- if (domain) return !PERSONAL_EMAIL_DOMAINS.has(domain);
4177
- return false;
4178
- }
4179
- function readJsonSafe(path) {
4180
- try {
4181
- return JSON.parse(readFileSync8(path, "utf8"));
4182
- } catch {
4183
- return null;
4500
+ });
4501
+
4502
+ // ../../packages/core/src/episodes/episode.ts
4503
+ var init_episode = __esm({
4504
+ "../../packages/core/src/episodes/episode.ts"() {
4505
+ "use strict";
4184
4506
  }
4185
- }
4186
- function readFileSafe(path) {
4507
+ });
4508
+
4509
+ // ../../packages/core/src/episodes/reconstructor.ts
4510
+ function sidechainRatio(nodes) {
4511
+ if (nodes.length === 0) {
4512
+ return 0;
4513
+ }
4514
+ const n = nodes.reduce((acc, node) => node.isSidechain ? acc + 1 : acc, 0);
4515
+ return n / nodes.length;
4516
+ }
4517
+ function fileSessionId(nodes) {
4518
+ return nodes.length > 0 ? nodes[0].sessionId : "";
4519
+ }
4520
+ function byTimestamp(a, b) {
4521
+ return a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0;
4522
+ }
4523
+ function isGenuineUserTurn(node) {
4524
+ return node.kind === "user" /* User */ && !node.isSidechain;
4525
+ }
4526
+ function boundMainSession(sessionId, nodes) {
4527
+ const ordered = [...nodes].sort(byTimestamp);
4528
+ const episodes = [];
4529
+ const leading = [];
4530
+ let current = null;
4531
+ for (const node of ordered) {
4532
+ if (isGenuineUserTurn(node)) {
4533
+ current = {
4534
+ sessionId,
4535
+ rootUuid: node.uuid,
4536
+ mainNodes: [...leading, node],
4537
+ sidechainNodes: [],
4538
+ joinedSidechainPaths: []
4539
+ };
4540
+ leading.length = 0;
4541
+ episodes.push(current);
4542
+ } else if (current) {
4543
+ current.mainNodes.push(node);
4544
+ } else {
4545
+ leading.push(node);
4546
+ }
4547
+ }
4548
+ if (leading.length > 0) {
4549
+ if (episodes.length > 0) {
4550
+ episodes[0].mainNodes.unshift(...leading);
4551
+ } else {
4552
+ episodes.push({
4553
+ sessionId,
4554
+ rootUuid: leading[0].uuid,
4555
+ mainNodes: [...leading],
4556
+ sidechainNodes: [],
4557
+ joinedSidechainPaths: []
4558
+ });
4559
+ }
4560
+ }
4561
+ return episodes;
4562
+ }
4563
+ function isCompaction(node) {
4564
+ return node.kind === "summary" /* Summary */;
4565
+ }
4566
+ function deriveLifecycle(mainNodes) {
4567
+ const meaningful = mainNodes.filter((n) => !isCompaction(n)).sort(byTimestamp);
4568
+ const hasWork = meaningful.some((n) => n.kind !== "user" /* User */);
4569
+ if (!hasWork) {
4570
+ return "open";
4571
+ }
4572
+ const last = meaningful[meaningful.length - 1];
4573
+ if (last.kind === "tool_result" /* ToolResult */ && last.isError) {
4574
+ return "abandoned";
4575
+ }
4576
+ if (last.kind === "assistant" /* Assistant */) {
4577
+ return "resolved";
4578
+ }
4579
+ return "updated";
4580
+ }
4581
+ function findAgentDispatchEpisodes(episodes) {
4582
+ const out = [];
4583
+ for (const ep of episodes) {
4584
+ for (const node of ep.mainNodes) {
4585
+ if (node.kind === "tool_use" /* ToolUse */ && node.toolName === AGENT_TOOL) {
4586
+ out.push({ episode: ep, ts: node.timestamp });
4587
+ }
4588
+ }
4589
+ }
4590
+ return out.sort((a, b) => a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0);
4591
+ }
4592
+ function episodeContainingTs(episodes, ts) {
4593
+ let chosen = null;
4594
+ for (const ep of episodes) {
4595
+ const sorted = [...ep.mainNodes].sort(byTimestamp);
4596
+ const start = sorted[0]?.timestamp ?? "";
4597
+ const end = sorted[sorted.length - 1]?.timestamp ?? "";
4598
+ if (ts >= start && ts <= end) {
4599
+ return ep;
4600
+ }
4601
+ if (ts >= start) {
4602
+ chosen = ep;
4603
+ }
4604
+ }
4605
+ return chosen ?? (episodes.length > 0 ? episodes[episodes.length - 1] : null);
4606
+ }
4607
+ function finalize(build) {
4608
+ const mainSorted = [...build.mainNodes].sort(byTimestamp);
4609
+ const mainNodeUuids = mainSorted.map((n) => n.uuid);
4610
+ const sidechainNodeUuids = [...build.sidechainNodes].sort(byTimestamp).map((n) => n.uuid);
4611
+ const compactedNodeUuids = mainSorted.filter(isCompaction).map((n) => n.uuid);
4612
+ const start = mainSorted[0]?.timestamp ?? "";
4613
+ const end = mainSorted[mainSorted.length - 1]?.timestamp ?? "";
4614
+ return {
4615
+ id: build.rootUuid,
4616
+ sessionId: build.sessionId,
4617
+ rootUuid: build.rootUuid,
4618
+ lifecycle: deriveLifecycle(build.mainNodes),
4619
+ mainNodeUuids,
4620
+ sidechainNodeUuids,
4621
+ nodeUuids: [...mainNodeUuids, ...sidechainNodeUuids],
4622
+ joinedSidechainPaths: [...build.joinedSidechainPaths],
4623
+ compacted: compactedNodeUuids.length > 0,
4624
+ compactedNodeUuids,
4625
+ startTimestamp: start,
4626
+ endTimestamp: end
4627
+ };
4628
+ }
4629
+ function reconstruct(files, opts = {}) {
4630
+ const join18 = opts.joinSidechains !== false;
4631
+ const mains = [];
4632
+ const sidechains = [];
4633
+ for (const file of files) {
4634
+ const sessionId = fileSessionId(file.nodes);
4635
+ if (sidechainRatio(file.nodes) >= SIDECHAIN_FILE_THRESHOLD) {
4636
+ const firstTs = [...file.nodes].sort(byTimestamp)[0]?.timestamp ?? "";
4637
+ sidechains.push({ path: file.path, sessionId, nodes: file.nodes, firstTs });
4638
+ } else {
4639
+ mains.push(file);
4640
+ }
4641
+ }
4642
+ const mainNodesBySession = /* @__PURE__ */ new Map();
4643
+ for (const file of mains) {
4644
+ const sessionId = fileSessionId(file.nodes);
4645
+ const acc = mainNodesBySession.get(sessionId) ?? [];
4646
+ acc.push(...file.nodes);
4647
+ mainNodesBySession.set(sessionId, acc);
4648
+ }
4649
+ const episodesBySession = /* @__PURE__ */ new Map();
4650
+ for (const [sessionId, nodes] of mainNodesBySession) {
4651
+ episodesBySession.set(sessionId, boundMainSession(sessionId, nodes));
4652
+ }
4653
+ const orphanedSidechainPaths = [];
4654
+ const joinedPaths = /* @__PURE__ */ new Set();
4655
+ if (join18) {
4656
+ const sidechainsBySession = /* @__PURE__ */ new Map();
4657
+ for (const sc of sidechains) {
4658
+ const acc = sidechainsBySession.get(sc.sessionId) ?? [];
4659
+ acc.push(sc);
4660
+ sidechainsBySession.set(sc.sessionId, acc);
4661
+ }
4662
+ for (const [sessionId, group] of sidechainsBySession) {
4663
+ const parentEpisodes = episodesBySession.get(sessionId);
4664
+ if (!parentEpisodes || parentEpisodes.length === 0) {
4665
+ for (const sc of group) {
4666
+ orphanedSidechainPaths.push(sc.path);
4667
+ }
4668
+ continue;
4669
+ }
4670
+ const dispatches = findAgentDispatchEpisodes(parentEpisodes);
4671
+ const orderedScs = [...group].sort((a, b) => a.firstTs < b.firstTs ? -1 : a.firstTs > b.firstTs ? 1 : 0);
4672
+ orderedScs.forEach((sc, i) => {
4673
+ let target = null;
4674
+ if (dispatches.length > 0) {
4675
+ target = dispatches[Math.min(i, dispatches.length - 1)].episode;
4676
+ } else {
4677
+ target = episodeContainingTs(parentEpisodes, sc.firstTs);
4678
+ }
4679
+ if (target) {
4680
+ target.sidechainNodes.push(...sc.nodes);
4681
+ target.joinedSidechainPaths.push(sc.path);
4682
+ joinedPaths.add(sc.path);
4683
+ } else {
4684
+ orphanedSidechainPaths.push(sc.path);
4685
+ }
4686
+ });
4687
+ }
4688
+ } else {
4689
+ for (const sc of sidechains) {
4690
+ orphanedSidechainPaths.push(sc.path);
4691
+ }
4692
+ }
4693
+ const episodes = [];
4694
+ const compactedNodeUuids = [];
4695
+ for (const builds of episodesBySession.values()) {
4696
+ for (const build of builds) {
4697
+ const ep = finalize(build);
4698
+ episodes.push(ep);
4699
+ compactedNodeUuids.push(...ep.compactedNodeUuids);
4700
+ }
4701
+ }
4702
+ episodes.sort(
4703
+ (a, b) => a.startTimestamp < b.startTimestamp ? -1 : a.startTimestamp > b.startTimestamp ? 1 : 0
4704
+ );
4705
+ const classification = [
4706
+ ...mains.map((f) => ({
4707
+ path: f.path,
4708
+ sessionId: fileSessionId(f.nodes),
4709
+ role: "main",
4710
+ nodeCount: f.nodes.length,
4711
+ joined: false
4712
+ })),
4713
+ ...sidechains.map((sc) => ({
4714
+ path: sc.path,
4715
+ sessionId: sc.sessionId,
4716
+ role: "sidechain",
4717
+ nodeCount: sc.nodes.length,
4718
+ joined: joinedPaths.has(sc.path)
4719
+ }))
4720
+ ];
4721
+ return {
4722
+ episodes,
4723
+ files: classification,
4724
+ orphanedSidechainPaths,
4725
+ compactedNodeUuids
4726
+ };
4727
+ }
4728
+ var AGENT_TOOL, SIDECHAIN_FILE_THRESHOLD;
4729
+ var init_reconstructor = __esm({
4730
+ "../../packages/core/src/episodes/reconstructor.ts"() {
4731
+ "use strict";
4732
+ init_node_model();
4733
+ AGENT_TOOL = "Agent";
4734
+ SIDECHAIN_FILE_THRESHOLD = 0.5;
4735
+ }
4736
+ });
4737
+
4738
+ // ../../packages/core/src/episodes/coverage.ts
4739
+ function pct(part, whole) {
4740
+ return whole === 0 ? 0 : part / whole * 100;
4741
+ }
4742
+ function computeCoverage(files, result) {
4743
+ const totalNodes = files.reduce((acc, f) => acc + f.nodes.length, 0);
4744
+ const nodeCountByPath = /* @__PURE__ */ new Map();
4745
+ for (const f of files) {
4746
+ nodeCountByPath.set(f.path, f.nodes.length);
4747
+ }
4748
+ const orphanedNodes = result.orphanedSidechainPaths.reduce(
4749
+ (acc, path) => acc + (nodeCountByPath.get(path) ?? 0),
4750
+ 0
4751
+ );
4752
+ const attributedNodes = totalNodes - orphanedNodes;
4753
+ const compactedNodes = result.compactedNodeUuids.length;
4754
+ return {
4755
+ totalNodes,
4756
+ attributedNodes,
4757
+ orphanedNodes,
4758
+ compactedNodes,
4759
+ attributedPct: pct(attributedNodes, totalNodes),
4760
+ orphanedPct: pct(orphanedNodes, totalNodes),
4761
+ compactedPct: pct(compactedNodes, totalNodes)
4762
+ };
4763
+ }
4764
+ var init_coverage = __esm({
4765
+ "../../packages/core/src/episodes/coverage.ts"() {
4766
+ "use strict";
4767
+ }
4768
+ });
4769
+
4770
+ // ../../packages/core/src/episodes/schema.ts
4771
+ var SCHEMA_VERSION;
4772
+ var init_schema = __esm({
4773
+ "../../packages/core/src/episodes/schema.ts"() {
4774
+ "use strict";
4775
+ SCHEMA_VERSION = 1;
4776
+ }
4777
+ });
4778
+
4779
+ // ../../packages/core/src/episodes/doors.ts
4780
+ function recruiterMetric(value) {
4781
+ return { value };
4782
+ }
4783
+ function inwardMetric(value) {
4784
+ return { value };
4785
+ }
4786
+ function metricValue(metric) {
4787
+ return metric.value;
4788
+ }
4789
+ function declassify(metrics, coveragePct = 0) {
4790
+ const velocity = metrics.skillAdoptionVelocity.value;
4791
+ const drift = metrics.distributionDrift.value;
4792
+ const recency = metrics.recencySplit.value;
4793
+ return {
4794
+ schemaVersion: SCHEMA_VERSION,
4795
+ headline: {
4796
+ distinctSignalCount: velocity.distinctSignalCount,
4797
+ recentAdoptionCount: velocity.recentAdoptionCount,
4798
+ velocityPerWeek: velocity.velocityPerWeek,
4799
+ rising: drift.rising.map((entry) => ({ signal: entry.signal, delta: entry.delta })),
4800
+ falling: drift.falling.map((entry) => ({ signal: entry.signal, delta: entry.delta }))
4801
+ },
4802
+ liveStack: [...recency.live],
4803
+ dormantStack: [...recency.dormant],
4804
+ coveragePct
4805
+ };
4806
+ }
4807
+ function buildExport(metrics, coveragePct = 0) {
4808
+ return declassify(metrics, coveragePct);
4809
+ }
4810
+ var init_doors = __esm({
4811
+ "../../packages/core/src/episodes/doors.ts"() {
4812
+ "use strict";
4813
+ init_schema();
4814
+ }
4815
+ });
4816
+
4817
+ // ../../packages/core/src/episodes/events.ts
4818
+ var init_events = __esm({
4819
+ "../../packages/core/src/episodes/events.ts"() {
4820
+ "use strict";
4821
+ init_schema();
4822
+ }
4823
+ });
4824
+
4825
+ // ../../packages/core/src/episodes/derivers/signals.ts
4826
+ function mcpToolSignal(name) {
4827
+ const rest = name.slice("mcp__".length);
4828
+ const sep = rest.indexOf("__");
4829
+ if (sep <= 0) return "mcp:custom";
4830
+ const server = rest.slice(0, sep).toLowerCase();
4831
+ const leaf = rest.slice(sep + 2);
4832
+ if (leaf.length === 0) return "mcp:custom";
4833
+ if (MCP_SERVER_CAPABILITY.has(server)) return MCP_SERVER_CAPABILITY.get(server) ?? null;
4834
+ return "mcp:custom";
4835
+ }
4836
+ function classifyToolSignal(name) {
4837
+ if (name.toLowerCase().startsWith("mcp__")) {
4838
+ return mcpToolSignal(name);
4839
+ }
4840
+ const key = name.toLowerCase();
4841
+ if (ORCHESTRATION_TOOLS.has(key)) return AGENTIC_WORKFLOW_SIGNAL;
4842
+ return null;
4843
+ }
4844
+ function isRecord2(value) {
4845
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4846
+ }
4847
+ function fileExtLang(input) {
4848
+ if (!isRecord2(input)) {
4849
+ return null;
4850
+ }
4851
+ const filePath = input.file_path;
4852
+ if (typeof filePath !== "string") {
4853
+ return null;
4854
+ }
4855
+ const dot = filePath.lastIndexOf(".");
4856
+ if (dot < 0 || dot === filePath.length - 1) {
4857
+ return null;
4858
+ }
4859
+ const ext = filePath.slice(dot + 1).toLowerCase();
4860
+ return EXT_LANG[ext] ?? null;
4861
+ }
4862
+ function toolUseInput(node) {
4863
+ const block = node.content.find((b) => b.type === "tool_use");
4864
+ return block?.input;
4865
+ }
4866
+ function compareSignal(a, b) {
4867
+ if (a.ts !== b.ts) {
4868
+ return a.ts < b.ts ? -1 : 1;
4869
+ }
4870
+ if (a.signal !== b.signal) {
4871
+ return a.signal < b.signal ? -1 : 1;
4872
+ }
4873
+ return 0;
4874
+ }
4875
+ function extractToolSignals(nodes) {
4876
+ const out = [];
4877
+ for (const node of nodes) {
4878
+ if (node.kind !== "tool_use" /* ToolUse */ || node.toolName === null) {
4879
+ continue;
4880
+ }
4881
+ const toolSignal = classifyToolSignal(node.toolName);
4882
+ if (toolSignal !== null) {
4883
+ out.push({ signal: toolSignal, ts: node.timestamp });
4884
+ }
4885
+ const lang = fileExtLang(toolUseInput(node));
4886
+ if (lang !== null) {
4887
+ out.push({ signal: `lang:${lang}`, ts: node.timestamp });
4888
+ }
4889
+ }
4890
+ out.sort(compareSignal);
4891
+ return out;
4892
+ }
4893
+ var EXT_LANG, MCP_SERVER_CAPABILITY, ORCHESTRATION_TOOLS, AGENTIC_WORKFLOW_SIGNAL;
4894
+ var init_signals = __esm({
4895
+ "../../packages/core/src/episodes/derivers/signals.ts"() {
4896
+ "use strict";
4897
+ init_node_model();
4898
+ EXT_LANG = {
4899
+ ts: "ts",
4900
+ tsx: "ts",
4901
+ js: "js",
4902
+ jsx: "js",
4903
+ mjs: "js",
4904
+ cjs: "js",
4905
+ py: "py",
4906
+ rs: "rs",
4907
+ go: "go",
4908
+ rb: "rb",
4909
+ java: "java",
4910
+ kt: "kt",
4911
+ sql: "sql",
4912
+ md: "md"
4913
+ };
4914
+ MCP_SERVER_CAPABILITY = /* @__PURE__ */ new Map([
4915
+ // Coding-relevant public servers → a capability.
4916
+ ["plugin_playwright_playwright", "cap:ui-automation"],
4917
+ ["playwright", "cap:ui-automation"],
4918
+ ["claude_ai_linear", "cap:project-mgmt"],
4919
+ ["claude_ai_vercel", "cap:deploys"],
4920
+ ["plugin_vercel_vercel", "cap:deploys"],
4921
+ ["claude_ai_figma", "cap:design"],
4922
+ ["figma", "cap:design"],
4923
+ ["pencil", "cap:design"],
4924
+ ["claude_ai_ideabrowser", "cap:product-research"],
4925
+ ["ideabrowser", "cap:product-research"],
4926
+ ["blender", "cap:3d-modeling"],
4927
+ ["n8n-knowledge", "cap:workflow-automation"],
4928
+ // Public but NOT a coding skill → dropped from the trajectory.
4929
+ ["plugin_context-mode_context-mode", null],
4930
+ // agent context plumbing
4931
+ ["claude_ai_gmail", null],
4932
+ ["claude_ai_google_calendar", null],
4933
+ ["claude_ai_google_drive", null]
4934
+ ]);
4935
+ ORCHESTRATION_TOOLS = /* @__PURE__ */ new Set([
4936
+ "task",
4937
+ "agent",
4938
+ "workflow",
4939
+ "enterworktree",
4940
+ "exitworktree",
4941
+ "schedulewakeup",
4942
+ "monitor",
4943
+ "sendmessage",
4944
+ "taskcreate",
4945
+ "tasklist",
4946
+ "taskstop",
4947
+ "taskupdate",
4948
+ "taskget",
4949
+ "taskoutput",
4950
+ "croncreate",
4951
+ "cronlist",
4952
+ "crondelete"
4953
+ ]);
4954
+ AGENTIC_WORKFLOW_SIGNAL = "cap:agentic-workflow";
4955
+ }
4956
+ });
4957
+
4958
+ // ../../packages/core/src/episodes/derivers/skill-adoption-velocity.ts
4959
+ function deriveSkillAdoptionVelocity(nodes, opts = {}) {
4960
+ const signals = extractToolSignals(nodes);
4961
+ const firstSeen = /* @__PURE__ */ new Map();
4962
+ for (const s of signals) {
4963
+ const cur = firstSeen.get(s.signal);
4964
+ if (cur === void 0 || s.ts < cur) {
4965
+ firstSeen.set(s.signal, s.ts);
4966
+ }
4967
+ }
4968
+ const firstAppearances = [...firstSeen.entries()].map(([signal, ts]) => ({ signal, ts })).sort((a, b) => a.ts !== b.ts ? a.ts < b.ts ? -1 : 1 : a.signal < b.signal ? -1 : 1);
4969
+ const minTs = signals.length > 0 ? signals[0].ts : "";
4970
+ const maxTs = signals.length > 0 ? signals[signals.length - 1].ts : "";
4971
+ const now = opts.now ?? maxTs;
4972
+ const windowDays = opts.recentWindowDays ?? RECENT_WINDOW_DAYS;
4973
+ const spanDays = minTs !== "" && maxTs !== "" ? (Date.parse(maxTs) - Date.parse(minTs)) / DAY_MS : 0;
4974
+ const nowMs = now !== "" ? Date.parse(now) : 0;
4975
+ const recentAdoptionCount = now === "" ? 0 : firstAppearances.filter((f) => nowMs - Date.parse(f.ts) <= windowDays * DAY_MS).length;
4976
+ const spanWeeks = spanDays / 7;
4977
+ const velocityPerWeek = spanWeeks > 0 ? firstSeen.size / spanWeeks : firstSeen.size;
4978
+ return recruiterMetric({
4979
+ distinctSignalCount: firstSeen.size,
4980
+ firstAppearances,
4981
+ recentAdoptionCount,
4982
+ spanDays,
4983
+ velocityPerWeek
4984
+ });
4985
+ }
4986
+ var DAY_MS, RECENT_WINDOW_DAYS;
4987
+ var init_skill_adoption_velocity = __esm({
4988
+ "../../packages/core/src/episodes/derivers/skill-adoption-velocity.ts"() {
4989
+ "use strict";
4990
+ init_doors();
4991
+ init_signals();
4992
+ DAY_MS = 864e5;
4993
+ RECENT_WINDOW_DAYS = 90;
4994
+ }
4995
+ });
4996
+
4997
+ // ../../packages/core/src/episodes/derivers/distribution-drift.ts
4998
+ function shareMap(signals) {
4999
+ const counts = /* @__PURE__ */ new Map();
5000
+ for (const s of signals) {
5001
+ counts.set(s.signal, (counts.get(s.signal) ?? 0) + 1);
5002
+ }
5003
+ const total = signals.length > 0 ? signals.length : 1;
5004
+ const shares = /* @__PURE__ */ new Map();
5005
+ for (const [signal, count] of counts) {
5006
+ shares.set(signal, count / total);
5007
+ }
5008
+ return shares;
5009
+ }
5010
+ function deriveDistributionDrift(nodes) {
5011
+ const signals = extractToolSignals(nodes);
5012
+ const mid = Math.floor(signals.length / 2);
5013
+ const early = signals.slice(0, mid);
5014
+ const late = signals.slice(mid);
5015
+ const earlyShares = shareMap(early);
5016
+ const lateShares = shareMap(late);
5017
+ const all = /* @__PURE__ */ new Set([...earlyShares.keys(), ...lateShares.keys()]);
5018
+ const entries = [...all].map((signal) => {
5019
+ const earlyShare = earlyShares.get(signal) ?? 0;
5020
+ const lateShare = lateShares.get(signal) ?? 0;
5021
+ return { signal, earlyShare, lateShare, delta: lateShare - earlyShare };
5022
+ });
5023
+ const rising = entries.filter((e) => e.delta > 0).sort((a, b) => b.delta !== a.delta ? b.delta - a.delta : a.signal < b.signal ? -1 : 1);
5024
+ const falling = entries.filter((e) => e.delta < 0).sort((a, b) => a.delta !== b.delta ? a.delta - b.delta : a.signal < b.signal ? -1 : 1);
5025
+ return recruiterMetric({ rising, falling });
5026
+ }
5027
+ var init_distribution_drift = __esm({
5028
+ "../../packages/core/src/episodes/derivers/distribution-drift.ts"() {
5029
+ "use strict";
5030
+ init_doors();
5031
+ init_signals();
5032
+ }
5033
+ });
5034
+
5035
+ // ../../packages/core/src/episodes/derivers/recency-split.ts
5036
+ function deriveRecencySplit(nodes, opts = {}) {
5037
+ const signals = extractToolSignals(nodes);
5038
+ const lastSeen = /* @__PURE__ */ new Map();
5039
+ for (const s of signals) {
5040
+ const cur = lastSeen.get(s.signal);
5041
+ if (cur === void 0 || s.ts > cur) {
5042
+ lastSeen.set(s.signal, s.ts);
5043
+ }
5044
+ }
5045
+ const maxTs = signals.length > 0 ? signals[signals.length - 1].ts : "";
5046
+ const now = opts.now ?? maxTs;
5047
+ const thresholdDays = opts.thresholdDays ?? DORMANT_THRESHOLD_DAYS;
5048
+ const nowMs = now !== "" ? Date.parse(now) : 0;
5049
+ const live = [];
5050
+ const dormant = [];
5051
+ for (const [signal, ts] of [...lastSeen.entries()].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
5052
+ const ageDays2 = now !== "" ? (nowMs - Date.parse(ts)) / DAY_MS2 : 0;
5053
+ if (ageDays2 <= thresholdDays) {
5054
+ live.push(signal);
5055
+ } else {
5056
+ dormant.push(signal);
5057
+ }
5058
+ }
5059
+ return recruiterMetric({ now, thresholdDays, live, dormant });
5060
+ }
5061
+ var DAY_MS2, DORMANT_THRESHOLD_DAYS;
5062
+ var init_recency_split = __esm({
5063
+ "../../packages/core/src/episodes/derivers/recency-split.ts"() {
5064
+ "use strict";
5065
+ init_doors();
5066
+ init_signals();
5067
+ DAY_MS2 = 864e5;
5068
+ DORMANT_THRESHOLD_DAYS = 90;
5069
+ }
5070
+ });
5071
+
5072
+ // ../../packages/core/src/episodes/derivers/rework-density.ts
5073
+ function isRecord3(value) {
5074
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5075
+ }
5076
+ function filePathOf(node) {
5077
+ const block = node.content.find((b) => b.type === "tool_use");
5078
+ const input = block?.input;
5079
+ if (isRecord3(input) && typeof input.file_path === "string") {
5080
+ return input.file_path;
5081
+ }
5082
+ return null;
5083
+ }
5084
+ function deriveReworkDensity(episodes, nodesByUuid) {
5085
+ let totalEdits = 0;
5086
+ let reworkEdits = 0;
5087
+ let spansConsidered = 0;
5088
+ let spansSkipped = 0;
5089
+ for (const episode of episodes) {
5090
+ if (episode.compacted) {
5091
+ spansSkipped++;
5092
+ continue;
5093
+ }
5094
+ spansConsidered++;
5095
+ const seen = /* @__PURE__ */ new Set();
5096
+ for (const uuid of episode.mainNodeUuids) {
5097
+ const node = nodesByUuid.get(uuid);
5098
+ if (node === void 0 || node.kind !== "tool_use" /* ToolUse */ || node.toolName === null) {
5099
+ continue;
5100
+ }
5101
+ if (!EDIT_TOOLS.has(node.toolName)) {
5102
+ continue;
5103
+ }
5104
+ const filePath = filePathOf(node);
5105
+ if (filePath === null) {
5106
+ continue;
5107
+ }
5108
+ totalEdits++;
5109
+ if (seen.has(filePath)) {
5110
+ reworkEdits++;
5111
+ } else {
5112
+ seen.add(filePath);
5113
+ }
5114
+ }
5115
+ }
5116
+ const reworkRatio = totalEdits > 0 ? reworkEdits / totalEdits : 0;
5117
+ return inwardMetric({ totalEdits, reworkEdits, reworkRatio, spansConsidered, spansSkipped });
5118
+ }
5119
+ var EDIT_TOOLS;
5120
+ var init_rework_density = __esm({
5121
+ "../../packages/core/src/episodes/derivers/rework-density.ts"() {
5122
+ "use strict";
5123
+ init_node_model();
5124
+ init_doors();
5125
+ EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit"]);
5126
+ }
5127
+ });
5128
+
5129
+ // ../../packages/core/src/episodes/derivers/recovery-depth.ts
5130
+ function deriveRecoveryDepth(episodes, nodesByUuid) {
5131
+ let maxConsecutiveErrors = 0;
5132
+ let totalDepth = 0;
5133
+ let recoveryChains = 0;
5134
+ let spansConsidered = 0;
5135
+ let spansSkipped = 0;
5136
+ for (const episode of episodes) {
5137
+ if (episode.compacted) {
5138
+ spansSkipped++;
5139
+ continue;
5140
+ }
5141
+ spansConsidered++;
5142
+ let run14 = 0;
5143
+ const closeChain = () => {
5144
+ if (run14 > 0) {
5145
+ recoveryChains++;
5146
+ totalDepth += run14;
5147
+ run14 = 0;
5148
+ }
5149
+ };
5150
+ for (const uuid of episode.mainNodeUuids) {
5151
+ const node = nodesByUuid.get(uuid);
5152
+ if (node === void 0) {
5153
+ continue;
5154
+ }
5155
+ if (node.kind === "tool_result" /* ToolResult */ && node.isError) {
5156
+ run14++;
5157
+ if (run14 > maxConsecutiveErrors) {
5158
+ maxConsecutiveErrors = run14;
5159
+ }
5160
+ } else if (node.kind === "tool_result" /* ToolResult */ && !node.isError) {
5161
+ closeChain();
5162
+ } else if (node.kind === "assistant" /* Assistant */) {
5163
+ closeChain();
5164
+ }
5165
+ }
5166
+ closeChain();
5167
+ }
5168
+ const meanRecoveryDepth = recoveryChains > 0 ? totalDepth / recoveryChains : 0;
5169
+ return inwardMetric({ maxConsecutiveErrors, meanRecoveryDepth, recoveryChains, spansConsidered, spansSkipped });
5170
+ }
5171
+ var init_recovery_depth = __esm({
5172
+ "../../packages/core/src/episodes/derivers/recovery-depth.ts"() {
5173
+ "use strict";
5174
+ init_node_model();
5175
+ init_doors();
5176
+ }
5177
+ });
5178
+
5179
+ // ../../packages/core/src/episodes/index.ts
5180
+ var init_episodes = __esm({
5181
+ "../../packages/core/src/episodes/index.ts"() {
5182
+ "use strict";
5183
+ init_node_model();
5184
+ init_parser();
5185
+ init_episode();
5186
+ init_reconstructor();
5187
+ init_coverage();
5188
+ init_schema();
5189
+ init_doors();
5190
+ init_events();
5191
+ init_signals();
5192
+ init_skill_adoption_velocity();
5193
+ init_distribution_drift();
5194
+ init_recency_split();
5195
+ init_rework_density();
5196
+ init_recovery_depth();
5197
+ }
5198
+ });
5199
+
5200
+ // src/trajectory.ts
5201
+ var trajectory_exports = {};
5202
+ __export(trajectory_exports, {
5203
+ PUSH_DENYLIST: () => PUSH_DENYLIST,
5204
+ computeDerivedScore: () => computeDerivedScore,
5205
+ runTrajectory: () => runTrajectory,
5206
+ runTrajectoryPush: () => runTrajectoryPush
5207
+ });
5208
+ import {
5209
+ existsSync as existsSync6,
5210
+ mkdirSync as mkdirSync6,
5211
+ readFileSync as readFileSync8,
5212
+ readdirSync,
5213
+ writeFileSync as writeFileSync6
5214
+ } from "fs";
5215
+ import { homedir as homedir7 } from "os";
5216
+ import { join as join8 } from "path";
5217
+ function isRecord4(value) {
5218
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5219
+ }
5220
+ function filePathOf2(node) {
5221
+ const block = node.content.find((b) => b.type === "tool_use");
5222
+ const input = block?.input;
5223
+ if (isRecord4(input) && typeof input.file_path === "string") {
5224
+ return input.file_path;
5225
+ }
5226
+ return null;
5227
+ }
5228
+ function slimNode(node) {
5229
+ if (node.kind === "tool_use" /* ToolUse */) {
5230
+ const fp = filePathOf2(node);
5231
+ const input = fp === null ? {} : { file_path: fp };
5232
+ const content = [
5233
+ { type: "tool_use", id: node.toolUseId ?? "", name: node.toolName ?? "", input }
5234
+ ];
5235
+ return { ...node, content, text: "" };
5236
+ }
5237
+ return { ...node, content: [], text: "" };
5238
+ }
5239
+ function findJsonlFiles(dir) {
5240
+ const out = [];
5241
+ let entries;
5242
+ try {
5243
+ entries = readdirSync(dir, { withFileTypes: true, encoding: "utf8" });
5244
+ } catch {
5245
+ return out;
5246
+ }
5247
+ for (const entry of entries) {
5248
+ const full = join8(dir, entry.name);
5249
+ if (entry.isDirectory()) {
5250
+ out.push(...findJsonlFiles(full));
5251
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
5252
+ out.push(full);
5253
+ }
5254
+ }
5255
+ return out;
5256
+ }
5257
+ function loadCorpus(paths) {
5258
+ const files = [];
5259
+ for (const path of paths) {
5260
+ let text;
5261
+ try {
5262
+ text = readFileSync8(path, "utf8");
5263
+ } catch {
5264
+ continue;
5265
+ }
5266
+ const parsed = parseTranscript(text);
5267
+ const nodes = parsed.nodes.map(slimNode);
5268
+ files.push({ path, nodes });
5269
+ }
5270
+ return files;
5271
+ }
5272
+ function isLang(signal) {
5273
+ return signal.startsWith("lang:");
5274
+ }
5275
+ function prettySignal(signal) {
5276
+ if (signal.startsWith("lang:")) return `.${signal.slice("lang:".length)}`;
5277
+ if (CAP_LABELS[signal]) return CAP_LABELS[signal];
5278
+ if (signal.startsWith("cap:")) return signal.slice("cap:".length).replace(/-/g, " ");
5279
+ return signal;
5280
+ }
5281
+ function prettyList(signals, max = 12) {
5282
+ if (signals.length === 0) return "(none)";
5283
+ const shown = signals.slice(0, max).map(prettySignal).join(", ");
5284
+ const extra = signals.length - max;
5285
+ return extra > 0 ? `${shown}, +${extra} more` : shown;
5286
+ }
5287
+ function round(n) {
5288
+ return Math.round(n);
5289
+ }
5290
+ function oneDecimal(n) {
5291
+ return (Math.round(n * 10) / 10).toFixed(1);
5292
+ }
5293
+ function coverageLine(view) {
5294
+ const attr = round(view.coverage.attributedPct);
5295
+ const sub = round(view.subagentPct);
5296
+ const comp = round(view.coverage.compactedPct);
5297
+ return `Built from ${view.sessions} session${view.sessions === 1 ? "" : "s"}; ${attr}% attributed, ${sub}% in subagent dispatches, ${comp}% compacted. Covers stack currency + trajectory \u2014 not seniority, impact, or judgment. Pair with a conversation.`;
5298
+ }
5299
+ function renderTerminal(view) {
5300
+ const h = view.score.headline;
5301
+ const rising = h.rising.map((e) => prettySignal(e.signal)).slice(0, 6);
5302
+ const falling = h.falling.map((e) => prettySignal(e.signal)).slice(0, 6);
5303
+ console.log("");
5304
+ console.log(" Trajectory \u2014 your code is your r\xE9sum\xE9");
5305
+ console.log(" " + "\u2500".repeat(68));
5306
+ console.log("");
5307
+ console.log(" \u258C Trajectory");
5308
+ console.log(
5309
+ ` ${h.distinctSignalCount} distinct tools/languages \xB7 ${h.recentAdoptionCount} adopted in the last 90d \xB7 ${oneDecimal(h.velocityPerWeek)} new/wk`
5310
+ );
5311
+ if (rising.length > 0) console.log(` \u2191 rising: ${rising.join(", ")}`);
5312
+ if (falling.length > 0) console.log(` \u2193 falling: ${falling.join(", ")}`);
5313
+ console.log("");
5314
+ const liveLangs = view.score.liveStack.filter(isLang);
5315
+ const liveCaps = view.score.liveStack.filter((s) => !isLang(s));
5316
+ const dormantLangs = view.score.dormantStack.filter(isLang);
5317
+ console.log(" \u258C Stack (languages)");
5318
+ console.log(` live (this quarter): ${prettyList(liveLangs)}`);
5319
+ console.log(` dormant (90d+ untouched): ${prettyList(dormantLangs)}`);
5320
+ console.log("");
5321
+ console.log(" \u258C Capabilities (tools & integrations)");
5322
+ console.log(` ${prettyList(liveCaps)}`);
5323
+ console.log("");
5324
+ console.log(" \u258C Coverage / scope");
5325
+ console.log(` ${coverageLine(view)}`);
5326
+ console.log("");
5327
+ }
5328
+ function renderMarkdown(view) {
5329
+ const h = view.score.headline;
5330
+ const rising = h.rising.map((e) => prettySignal(e.signal)).slice(0, 8);
5331
+ const falling = h.falling.map((e) => prettySignal(e.signal)).slice(0, 8);
5332
+ const lines = [];
5333
+ lines.push("# Trajectory");
5334
+ lines.push("");
5335
+ lines.push("> Your code is your r\xE9sum\xE9. Local-first, derived from your own Claude Code corpus.");
5336
+ lines.push("");
5337
+ lines.push("## Trajectory");
5338
+ lines.push("");
5339
+ lines.push(`- Distinct tools/languages: **${h.distinctSignalCount}**`);
5340
+ lines.push(`- Adopted in the last 90 days: **${h.recentAdoptionCount}**`);
5341
+ lines.push(`- Adoption velocity: **${oneDecimal(h.velocityPerWeek)} new/week**`);
5342
+ lines.push(`- Rising: ${rising.length > 0 ? rising.join(", ") : "_none_"}`);
5343
+ lines.push(`- Falling: ${falling.length > 0 ? falling.join(", ") : "_none_"}`);
5344
+ lines.push("");
5345
+ lines.push("## Stack (languages)");
5346
+ lines.push("");
5347
+ lines.push(`- **Live** (used this quarter): ${prettyList(view.score.liveStack.filter(isLang), 64)}`);
5348
+ lines.push(`- **Dormant** (90d+ untouched): ${prettyList(view.score.dormantStack.filter(isLang), 64)}`);
5349
+ lines.push("");
5350
+ lines.push("## Capabilities (tools & integrations)");
5351
+ lines.push("");
5352
+ lines.push(`- ${prettyList(view.score.liveStack.filter((s) => !isLang(s)), 64)}`);
5353
+ lines.push("");
5354
+ lines.push("## Coverage / scope");
5355
+ lines.push("");
5356
+ lines.push(coverageLine(view));
5357
+ lines.push("");
5358
+ return lines.join("\n");
5359
+ }
5360
+ function writeExportArtifacts(score, markdown) {
5361
+ const dir = join8(homedir7(), ".terminalhire");
5362
+ mkdirSync6(dir, { recursive: true });
5363
+ const jsonPath = join8(dir, "trajectory-export.json");
5364
+ const mdPath = join8(dir, "trajectory-export.md");
5365
+ writeFileSync6(jsonPath, JSON.stringify(score, null, 2) + "\n", "utf8");
5366
+ writeFileSync6(mdPath, markdown, "utf8");
5367
+ return { jsonPath, mdPath };
5368
+ }
5369
+ function renderInward(allNodes, view, files) {
5370
+ const result = reconstruct(files);
5371
+ const nodesByUuid = /* @__PURE__ */ new Map();
5372
+ for (const n of allNodes) nodesByUuid.set(n.uuid, n);
5373
+ const rework = metricValue(deriveReworkDensity(result.episodes, nodesByUuid));
5374
+ const recovery = metricValue(deriveRecoveryDepth(result.episodes, nodesByUuid));
5375
+ console.log(" \u258C Inward (private \u2014 never exported)");
5376
+ console.log(
5377
+ ` rework: ${rework.reworkEdits}/${rework.totalEdits} edits revisited (ratio ${oneDecimal(rework.reworkRatio * 100)}%) over ${rework.spansConsidered} spans (${rework.spansSkipped} compacted skipped)`
5378
+ );
5379
+ console.log(
5380
+ ` recovery: max chain ${recovery.maxConsecutiveErrors}, mean depth ${oneDecimal(recovery.meanRecoveryDepth)} over ${recovery.recoveryChains} chains (${recovery.spansConsidered} spans, ${recovery.spansSkipped} compacted skipped)`
5381
+ );
5382
+ console.log("");
5383
+ }
5384
+ function buildTrajectory() {
5385
+ const projectsDir = join8(homedir7(), ".claude", "projects");
5386
+ if (!existsSync6(projectsDir)) return null;
5387
+ const paths = findJsonlFiles(projectsDir);
5388
+ if (paths.length === 0) return null;
5389
+ const files = loadCorpus(paths);
5390
+ const allNodes = [];
5391
+ for (const f of files) {
5392
+ for (const n of f.nodes) allNodes.push(n);
5393
+ }
5394
+ const reconstructed = reconstruct(files);
5395
+ const coverage = computeCoverage(files, reconstructed);
5396
+ const velocity = deriveSkillAdoptionVelocity(allNodes);
5397
+ const drift = deriveDistributionDrift(allNodes);
5398
+ const recency = deriveRecencySplit(allNodes);
5399
+ const score = buildExport(
5400
+ {
5401
+ skillAdoptionVelocity: velocity,
5402
+ distributionDrift: drift,
5403
+ recencySplit: recency
5404
+ },
5405
+ round(coverage.attributedPct)
5406
+ );
5407
+ let sidechainNodes = 0;
5408
+ for (const n of allNodes) if (n.isSidechain) sidechainNodes++;
5409
+ const subagentPct = allNodes.length > 0 ? sidechainNodes / allNodes.length * 100 : 0;
5410
+ const sessions = new Set(reconstructed.files.map((f) => f.sessionId)).size;
5411
+ const view = { score, coverage, sessions, subagentPct, fileCount: files.length };
5412
+ return { view, allNodes, files };
5413
+ }
5414
+ function computeDerivedScore() {
5415
+ return buildTrajectory()?.view.score ?? null;
5416
+ }
5417
+ async function runTrajectory(opts) {
5418
+ const built = buildTrajectory();
5419
+ if (!built) {
5420
+ console.log("terminalhire trajectory: no transcripts found under ~/.claude/projects.");
5421
+ console.log(" Start a Claude Code session and your trajectory will appear here.");
5422
+ return;
5423
+ }
5424
+ const { view, allNodes, files } = built;
5425
+ renderTerminal(view);
5426
+ if (opts.inward) {
5427
+ renderInward(allNodes, view, files);
5428
+ }
5429
+ if (opts.export) {
5430
+ const markdown = renderMarkdown(view);
5431
+ const { jsonPath, mdPath } = writeExportArtifacts(view.score, markdown);
5432
+ console.log(" Export written:");
5433
+ console.log(` ${jsonPath}`);
5434
+ console.log(` ${mdPath}`);
5435
+ console.log("");
5436
+ }
5437
+ }
5438
+ function dashboardLinkUrl(serialized) {
5439
+ const payload = Buffer.from(serialized, "utf8").toString("base64url");
5440
+ return `${OAUTH_BASE}/dashboard#link=${payload}`;
5441
+ }
5442
+ function scanDenylist(serialized) {
5443
+ const haystack = serialized.toLowerCase();
5444
+ return PUSH_DENYLIST.filter((needle) => haystack.includes(needle));
5445
+ }
5446
+ function defaultPushDeps() {
5447
+ return {
5448
+ computeScore: computeDerivedScore,
5449
+ readGithubLogin: async () => {
5450
+ try {
5451
+ const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
5452
+ const profile = await readProfile2();
5453
+ return profile?.github?.login ?? null;
5454
+ } catch {
5455
+ return null;
5456
+ }
5457
+ },
5458
+ prompt: async (question) => {
5459
+ const { createInterface: createInterface8 } = await import("readline");
5460
+ const rl = createInterface8({ input: process.stdin, output: process.stdout });
5461
+ return new Promise((res) => {
5462
+ rl.question(question, (answer) => {
5463
+ rl.close();
5464
+ res(answer.trim().toLowerCase());
5465
+ });
5466
+ });
5467
+ },
5468
+ fetchImpl: (...args3) => globalThis.fetch(...args3),
5469
+ openBrowser: (url) => {
5470
+ void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
5471
+ });
5472
+ },
5473
+ sessionCookie: () => {
5474
+ const v = process.env["TERMINALHIRE_WEB_SESSION"];
5475
+ return typeof v === "string" && v.length > 0 ? v : null;
5476
+ },
5477
+ log: (msg) => console.log(msg),
5478
+ errorLog: (msg) => console.error(msg),
5479
+ exit: (code) => process.exit(code)
5480
+ };
5481
+ }
5482
+ function renderConsentCard(score, login, log) {
5483
+ const h = score.headline;
5484
+ const rising = h.rising.map((e) => prettySignal(e.signal)).slice(0, 6);
5485
+ const falling = h.falling.map((e) => prettySignal(e.signal)).slice(0, 6);
5486
+ log("");
5487
+ log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
5488
+ log("\u2502 terminalhire \u2014 link your trajectory (opt-in) \u2502");
5489
+ log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
5490
+ log("");
5491
+ log(` This shares your DERIVED trajectory for @${login} with your`);
5492
+ log(" terminalhire.com dashboard, shown next to your GitHub r\xE9sum\xE9:");
5493
+ log("");
5494
+ log(` Distinct tools/languages : ${h.distinctSignalCount}`);
5495
+ log(` Adopted in last 90d : ${h.recentAdoptionCount}`);
5496
+ log(` Adoption velocity : ${oneDecimal(h.velocityPerWeek)} new/week`);
5497
+ log(` Rising : ${rising.length ? rising.join(", ") : "(none)"}`);
5498
+ log(` Falling : ${falling.length ? falling.join(", ") : "(none)"}`);
5499
+ log(` Live stack : ${prettyList(score.liveStack)}`);
5500
+ log(` Dormant stack : ${prettyList(score.dormantStack)}`);
5501
+ log(` Coverage : ${score.coveragePct}%`);
5502
+ log("");
5503
+ log(" What is NEVER sent:");
5504
+ log(" - Raw transcripts, file contents, code, project/file names");
5505
+ log(" - Inward metrics (rework / recovery / fatigue) \u2014 local-only");
5506
+ log("");
5507
+ log(" This is the SAME derived score `terminalhire trajectory --export` writes.");
5508
+ log(" Not required to use terminalhire. Revoke any time: trajectory --push --delete");
5509
+ log("");
5510
+ }
5511
+ async function runTrajectoryPush(opts, overrides) {
5512
+ const deps = { ...defaultPushDeps(), ...overrides };
5513
+ const login = await deps.readGithubLogin();
5514
+ if (!login) {
5515
+ deps.log("");
5516
+ deps.log(" Not signed in. Run `terminalhire login` first, then re-run this command.");
5517
+ deps.log("");
5518
+ deps.exit(1);
5519
+ return;
5520
+ }
5521
+ const cookie = deps.sessionCookie();
5522
+ if (opts.delete) {
5523
+ const answer2 = await deps.prompt(' Unlink your trajectory from terminalhire.com? Type "yes" to confirm: ');
5524
+ if (answer2 !== "yes") {
5525
+ deps.log("\n Aborted \u2014 nothing was changed.\n");
5526
+ deps.exit(0);
5527
+ return;
5528
+ }
5529
+ if (!cookie) {
5530
+ deps.log('\n Open your dashboard and use "remove trajectory" there, or set a bridged');
5531
+ deps.log(" session. Nothing was sent.\n");
5532
+ deps.openBrowser(`${LINK_BASE}/dashboard`);
5533
+ deps.exit(0);
5534
+ return;
5535
+ }
5536
+ let res2;
5537
+ try {
5538
+ res2 = await deps.fetchImpl(`${LINK_BASE}/api/trajectory-sync`, {
5539
+ method: "DELETE",
5540
+ headers: { Cookie: `${GH_SESSION_COOKIE}=${cookie}` },
5541
+ signal: AbortSignal.timeout(1e4)
5542
+ });
5543
+ } catch (err) {
5544
+ deps.errorLog(`
5545
+ Unlink failed: ${err instanceof Error ? err.message : String(err)}
5546
+ `);
5547
+ deps.exit(1);
5548
+ return;
5549
+ }
5550
+ if (!res2.ok) {
5551
+ deps.errorLog(`
5552
+ Unlink failed: /api/trajectory-sync returned ${res2.status}.
5553
+ `);
5554
+ deps.exit(1);
5555
+ return;
5556
+ }
5557
+ deps.log("\n Trajectory unlinked from terminalhire.com.\n");
5558
+ return;
5559
+ }
5560
+ const score = deps.computeScore();
5561
+ if (!score) {
5562
+ deps.log("");
5563
+ deps.log(" No trajectory to link yet \u2014 no transcripts under ~/.claude/projects.");
5564
+ deps.log(" Start a Claude Code session, then re-run `terminalhire trajectory --push`.");
5565
+ deps.log("");
5566
+ deps.exit(1);
5567
+ return;
5568
+ }
5569
+ renderConsentCard(score, login, deps.log);
5570
+ const answer = await deps.prompt(' Type "yes" to link your trajectory (anything else cancels): ');
5571
+ const consented = answer === "yes";
5572
+ if (!consented) {
5573
+ deps.log("\n Cancelled \u2014 nothing was sent.\n");
5574
+ deps.exit(0);
5575
+ return;
5576
+ }
5577
+ const serialized = JSON.stringify(score);
5578
+ const hits = scanDenylist(serialized);
5579
+ if (hits.length > 0) {
5580
+ deps.errorLog(`
5581
+ Aborted: the derived score unexpectedly contains a private field (${hits.join(", ")}).`);
5582
+ deps.errorLog(" This should never happen \u2014 nothing was sent. Please report this.\n");
5583
+ deps.exit(1);
5584
+ return;
5585
+ }
5586
+ if (!cookie) {
5587
+ const url = dashboardLinkUrl(serialized);
5588
+ deps.log(" Opening your browser to finish linking\u2026");
5589
+ deps.log(` \u2192 ${url}`);
5590
+ deps.openBrowser(url);
5591
+ deps.log("");
5592
+ deps.log(" (Sign in if prompted \u2014 your trajectory then appears on your dashboard.)");
5593
+ deps.log("");
5594
+ return;
5595
+ }
5596
+ let res;
5597
+ try {
5598
+ res = await deps.fetchImpl(`${LINK_BASE}/api/trajectory-sync`, {
5599
+ method: "POST",
5600
+ headers: { "Content-Type": "application/json", Cookie: `${GH_SESSION_COOKIE}=${cookie}` },
5601
+ body: serialized,
5602
+ signal: AbortSignal.timeout(1e4)
5603
+ });
5604
+ } catch (err) {
5605
+ deps.errorLog(`
5606
+ Link failed: ${err instanceof Error ? err.message : String(err)}
5607
+ `);
5608
+ deps.exit(1);
5609
+ return;
5610
+ }
5611
+ if (res.status === 401) {
5612
+ const url = dashboardLinkUrl(serialized);
5613
+ deps.log("\n Your web session expired \u2014 opening your browser to re-auth and finish linking\u2026");
5614
+ deps.log(` \u2192 ${url}
5615
+ `);
5616
+ deps.openBrowser(url);
5617
+ return;
5618
+ }
5619
+ if (!res.ok) {
5620
+ deps.errorLog(`
5621
+ Link failed: /api/trajectory-sync returned ${res.status}.
5622
+ `);
5623
+ deps.exit(1);
5624
+ return;
5625
+ }
5626
+ deps.log("\n Trajectory linked. It now appears next to your r\xE9sum\xE9 on your dashboard.");
5627
+ deps.log(` \u2192 ${LINK_BASE}/dashboard
5628
+ `);
5629
+ }
5630
+ var CAP_LABELS, LINK_BASE, OAUTH_BASE, GH_SESSION_COOKIE, PUSH_DENYLIST;
5631
+ var init_trajectory = __esm({
5632
+ "src/trajectory.ts"() {
5633
+ "use strict";
5634
+ init_episodes();
5635
+ CAP_LABELS = {
5636
+ "cap:ui-automation": "UI automation",
5637
+ "cap:deploys": "Deployment",
5638
+ "cap:project-mgmt": "Project tracking",
5639
+ "cap:product-research": "Product research",
5640
+ "cap:design": "Design",
5641
+ "cap:3d-modeling": "3D modeling",
5642
+ "cap:workflow-automation": "Workflow automation",
5643
+ "cap:agentic-workflow": "Agent orchestration",
5644
+ "mcp:custom": "Private integrations"
5645
+ };
5646
+ LINK_BASE = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
5647
+ OAUTH_BASE = "https://www.terminalhire.com";
5648
+ GH_SESSION_COOKIE = "__jpi_gh_session";
5649
+ PUSH_DENYLIST = ["rework", "recovery", "within_session", "fatigue", "vector", "mcp__"];
5650
+ }
5651
+ });
5652
+
5653
+ // bin/jpi-trajectory.js
5654
+ var jpi_trajectory_exports = {};
5655
+ __export(jpi_trajectory_exports, {
5656
+ run: () => run5
5657
+ });
5658
+ async function run5() {
5659
+ try {
5660
+ const args3 = process.argv.slice(2);
5661
+ if (args3.includes("--push")) {
5662
+ const doDelete = args3.includes("--delete") || args3.includes("--unlink");
5663
+ const { runTrajectoryPush: runTrajectoryPush2 } = await Promise.resolve().then(() => (init_trajectory(), trajectory_exports));
5664
+ await runTrajectoryPush2({ delete: doDelete });
5665
+ return;
5666
+ }
5667
+ const doExport = args3.includes("--export");
5668
+ const inward = args3.includes("--inward");
5669
+ const { runTrajectory: runTrajectory2 } = await Promise.resolve().then(() => (init_trajectory(), trajectory_exports));
5670
+ await runTrajectory2({ export: doExport, inward });
5671
+ } catch (err) {
5672
+ console.error("terminalhire trajectory error:", err?.message ?? err);
5673
+ process.exit(1);
5674
+ }
5675
+ }
5676
+ var init_jpi_trajectory = __esm({
5677
+ "bin/jpi-trajectory.js"() {
5678
+ "use strict";
5679
+ }
5680
+ });
5681
+
5682
+ // bin/jpi-profile.js
5683
+ var jpi_profile_exports = {};
5684
+ __export(jpi_profile_exports, {
5685
+ run: () => run6
5686
+ });
5687
+ import { createInterface as createInterface4 } from "readline";
5688
+ function prompt3(question) {
5689
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
5690
+ return new Promise((resolve2) => {
5691
+ rl.question(question, (answer) => {
5692
+ rl.close();
5693
+ resolve2(answer.trim());
5694
+ });
5695
+ });
5696
+ }
5697
+ async function run6() {
5698
+ const { readProfile: readProfile2, writeProfile: writeProfile2, deleteProfile: deleteProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
5699
+ const args3 = process.argv.slice(2);
5700
+ if (args3.includes("--show")) {
5701
+ const profile = await readProfile2();
5702
+ console.log("\n\u2726 terminalhire local profile (encrypted at rest \u2014 shown here for your review only)\n");
5703
+ console.log(" Skill tags: " + (profile.skillTags.length > 0 ? profile.skillTags.join(", ") : "(none yet)"));
5704
+ console.log(" Seniority: " + (profile.seniority ?? "(not set)"));
5705
+ if (profile.displayName) console.log(" Display name: " + profile.displayName);
5706
+ if (profile.contactEmail) console.log(" Contact email: " + profile.contactEmail);
5707
+ if (profile.remoteOnly !== void 0) console.log(" Remote only: " + profile.remoteOnly);
5708
+ if (profile.compFloorUsd !== void 0) console.log(" Comp floor USD: $" + profile.compFloorUsd);
5709
+ console.log(" Employer sessions contributed: " + profile.hasEmployerSessions);
5710
+ if (profile.github) {
5711
+ console.log("");
5712
+ console.log(" GitHub (public data only, scope: read:user):");
5713
+ console.log(" Login: @" + profile.github.login);
5714
+ console.log(" Profile URL: " + profile.github.profileUrl);
5715
+ console.log(" Top languages: " + profile.github.topLanguages.join(", "));
5716
+ console.log(" Public repos: " + profile.github.publicRepos);
5717
+ console.log("");
5718
+ console.log(' GitHub fields are included in a lead ONLY when you consent "yes".');
5719
+ console.log(" To disconnect GitHub: terminalhire logout");
5720
+ } else {
5721
+ console.log("");
5722
+ console.log(" GitHub: not connected (run: terminalhire login for instant enrichment)");
5723
+ }
5724
+ console.log("");
5725
+ console.log(" Raw profile JSON:");
5726
+ console.log(JSON.stringify(profile, null, 2));
5727
+ console.log("\nThis profile NEVER leaves your machine except in a consented lead payload.");
5728
+ return;
5729
+ }
5730
+ if (args3.includes("--delete")) {
5731
+ console.log("\nThis will permanently delete your local terminalhire profile and encryption key.");
5732
+ const answer = await prompt3('Type "yes" to confirm: ');
5733
+ if (answer !== "yes") {
5734
+ console.log("Aborted.");
5735
+ process.exit(0);
5736
+ }
5737
+ await deleteProfile2();
5738
+ console.log("Profile and key deleted from ~/.terminalhire/");
5739
+ return;
5740
+ }
5741
+ if (args3.includes("--edit")) {
5742
+ const profile = await readProfile2();
5743
+ console.log("\n\u2726 terminalhire profile editor (press Enter to keep current value)\n");
5744
+ const name = await prompt3(`Display name [${profile.displayName ?? "not set"}]: `);
5745
+ if (name) profile.displayName = name;
5746
+ const email = await prompt3(`Contact email [${profile.contactEmail ?? "not set"}]: `);
5747
+ if (email) profile.contactEmail = email;
5748
+ const remote = await prompt3(`Remote only? (y/n) [${profile.remoteOnly ? "y" : "n"}]: `);
5749
+ if (remote === "y") profile.remoteOnly = true;
5750
+ if (remote === "n") profile.remoteOnly = false;
5751
+ const floor = await prompt3(`Comp floor USD [${profile.compFloorUsd ?? "not set"}]: `);
5752
+ if (floor && !isNaN(parseInt(floor, 10))) profile.compFloorUsd = parseInt(floor, 10);
5753
+ await writeProfile2(profile);
5754
+ console.log("\nProfile updated (encrypted at ~/.terminalhire/profile.enc)");
5755
+ return;
5756
+ }
5757
+ console.log("Usage: terminalhire profile --show | --edit | --delete");
5758
+ }
5759
+ var init_jpi_profile = __esm({
5760
+ "bin/jpi-profile.js"() {
5761
+ "use strict";
5762
+ }
5763
+ });
5764
+
5765
+ // src/signal.ts
5766
+ var signal_exports = {};
5767
+ __export(signal_exports, {
5768
+ extractFingerprint: () => extractFingerprint
5769
+ });
5770
+ import { readFileSync as readFileSync9, readdirSync as readdirSync2 } from "fs";
5771
+ import { execFileSync } from "child_process";
5772
+ import { join as join9 } from "path";
5773
+ function safeGit(args3, cwd) {
5774
+ try {
5775
+ return execFileSync("git", ["-C", cwd, ...args3], {
5776
+ timeout: 2e3,
5777
+ stdio: ["ignore", "pipe", "ignore"]
5778
+ }).toString().trim();
5779
+ } catch {
5780
+ return "";
5781
+ }
5782
+ }
5783
+ function isEmployerContext(cwd) {
5784
+ const inRepo = safeGit(["rev-parse", "--is-inside-work-tree"], cwd);
5785
+ if (inRepo !== "true") return false;
5786
+ const remote = safeGit(["remote", "get-url", "origin"], cwd);
5787
+ if (remote) {
5788
+ const sshMatch = remote.match(/^git@([^:]+):/);
5789
+ const httpsMatch = remote.match(/^https?:\/\/([^/]+)/);
5790
+ const host = (sshMatch?.[1] ?? httpsMatch?.[1] ?? "").toLowerCase();
5791
+ if (host) return !PERSONAL_GIT_HOSTS.has(host);
5792
+ }
5793
+ const email = safeGit(["config", "user.email"], cwd);
5794
+ const domain = email.split("@")[1]?.toLowerCase() ?? "";
5795
+ if (domain) return !PERSONAL_EMAIL_DOMAINS.has(domain);
5796
+ return false;
5797
+ }
5798
+ function readJsonSafe(path) {
5799
+ try {
5800
+ return JSON.parse(readFileSync9(path, "utf8"));
5801
+ } catch {
5802
+ return null;
5803
+ }
5804
+ }
5805
+ function readFileSafe(path) {
4187
5806
  try {
4188
- return readFileSync8(path, "utf8");
5807
+ return readFileSync9(path, "utf8");
4189
5808
  } catch {
4190
5809
  return "";
4191
5810
  }
4192
5811
  }
4193
5812
  function tokensFromPackageJson(cwd) {
4194
- const pkg = readJsonSafe(join8(cwd, "package.json"));
5813
+ const pkg = readJsonSafe(join9(cwd, "package.json"));
4195
5814
  if (!pkg || typeof pkg !== "object") return [];
4196
5815
  const p = pkg;
4197
5816
  const deps = {
@@ -4205,9 +5824,9 @@ function workspaceMemberDirs(cwd) {
4205
5824
  const dirs = [cwd];
4206
5825
  for (const group of ["apps", "packages"]) {
4207
5826
  try {
4208
- const groupDir = join8(cwd, group);
4209
- for (const e of readdirSync(groupDir, { withFileTypes: true })) {
4210
- if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(join8(groupDir, e.name));
5827
+ const groupDir = join9(cwd, group);
5828
+ for (const e of readdirSync2(groupDir, { withFileTypes: true })) {
5829
+ if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(join9(groupDir, e.name));
4211
5830
  }
4212
5831
  } catch {
4213
5832
  }
@@ -4215,18 +5834,18 @@ function workspaceMemberDirs(cwd) {
4215
5834
  return dirs;
4216
5835
  }
4217
5836
  function tokensFromRequirementsTxt(cwd) {
4218
- const content = readFileSafe(join8(cwd, "requirements.txt"));
5837
+ const content = readFileSafe(join9(cwd, "requirements.txt"));
4219
5838
  if (!content) return [];
4220
5839
  return content.split("\n").map((l) => l.trim().split(/[>=<!\[;]/)[0].trim().toLowerCase()).filter(Boolean);
4221
5840
  }
4222
5841
  function tokensFromGoMod(cwd) {
4223
- const content = readFileSafe(join8(cwd, "go.mod"));
5842
+ const content = readFileSafe(join9(cwd, "go.mod"));
4224
5843
  if (!content) return [];
4225
5844
  const requires = Array.from(content.matchAll(/^\s+([^\s]+)\s+v/gm)).map((m) => m[1].split("/").pop() ?? "").filter(Boolean);
4226
5845
  return ["go", ...requires];
4227
5846
  }
4228
5847
  function tokensFromCargoToml(cwd) {
4229
- const content = readFileSafe(join8(cwd, "Cargo.toml"));
5848
+ const content = readFileSafe(join9(cwd, "Cargo.toml"));
4230
5849
  if (!content) return [];
4231
5850
  const deps = [];
4232
5851
  let inDeps = false;
@@ -4247,14 +5866,14 @@ function tokensFromFileExtensions(cwd) {
4247
5866
  const tokens = [];
4248
5867
  const scanDirs = [cwd];
4249
5868
  try {
4250
- const srcDir = join8(cwd, "src");
4251
- readdirSync(srcDir);
5869
+ const srcDir = join9(cwd, "src");
5870
+ readdirSync2(srcDir);
4252
5871
  scanDirs.push(srcDir);
4253
5872
  } catch {
4254
5873
  }
4255
5874
  for (const dir of scanDirs) {
4256
5875
  try {
4257
- const entries = readdirSync(dir, { withFileTypes: true });
5876
+ const entries = readdirSync2(dir, { withFileTypes: true });
4258
5877
  for (const e of entries) {
4259
5878
  if (!e.isFile()) continue;
4260
5879
  const dotIdx = e.name.lastIndexOf(".");
@@ -4408,9 +6027,9 @@ var init_signal = __esm({
4408
6027
  // bin/jpi-learn.js
4409
6028
  var jpi_learn_exports = {};
4410
6029
  __export(jpi_learn_exports, {
4411
- run: () => run6
6030
+ run: () => run7
4412
6031
  });
4413
- async function run6() {
6032
+ async function run7() {
4414
6033
  try {
4415
6034
  const args3 = process.argv.slice(2);
4416
6035
  const cwdIdx = args3.indexOf("--cwd");
@@ -4437,7 +6056,7 @@ var init_jpi_learn = __esm({
4437
6056
  "use strict";
4438
6057
  isMain = process.argv[1]?.endsWith("jpi-learn.js") || process.argv[1]?.endsWith("jpi-learn");
4439
6058
  if (isMain) {
4440
- run6();
6059
+ run7();
4441
6060
  }
4442
6061
  }
4443
6062
  });
@@ -4445,23 +6064,23 @@ var init_jpi_learn = __esm({
4445
6064
  // bin/jpi-config.js
4446
6065
  var jpi_config_exports = {};
4447
6066
  __export(jpi_config_exports, {
4448
- run: () => run7
6067
+ run: () => run8
4449
6068
  });
4450
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync6 } from "fs";
4451
- import { join as join9 } from "path";
4452
- import { homedir as homedir7 } from "os";
6069
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
6070
+ import { join as join10 } from "path";
6071
+ import { homedir as homedir8 } from "os";
4453
6072
  function readConfig() {
4454
6073
  try {
4455
- if (!existsSync6(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
4456
- return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync9(CONFIG_FILE, "utf8")) };
6074
+ if (!existsSync7(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
6075
+ return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync10(CONFIG_FILE, "utf8")) };
4457
6076
  } catch {
4458
6077
  return { ...DEFAULT_CONFIG };
4459
6078
  }
4460
6079
  }
4461
6080
  function writeConfig(patch) {
4462
- mkdirSync6(TERMINALHIRE_DIR7, { recursive: true });
6081
+ mkdirSync7(TERMINALHIRE_DIR7, { recursive: true });
4463
6082
  const merged = { ...readConfig(), ...patch };
4464
- writeFileSync6(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
6083
+ writeFileSync7(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
4465
6084
  }
4466
6085
  function parseNudgeMode(raw) {
4467
6086
  if (raw === "session" || raw === "always") return raw;
@@ -4469,7 +6088,7 @@ function parseNudgeMode(raw) {
4469
6088
  if (m && parseInt(m[1], 10) >= 1) return raw;
4470
6089
  return null;
4471
6090
  }
4472
- async function run7() {
6091
+ async function run8() {
4473
6092
  const args3 = process.argv.slice(2);
4474
6093
  const filtered = args3[0] === "config" ? args3.slice(1) : args3;
4475
6094
  if (filtered.includes("--show") || filtered.length === 0) {
@@ -4516,8 +6135,8 @@ var TERMINALHIRE_DIR7, CONFIG_FILE, DEFAULT_CONFIG;
4516
6135
  var init_jpi_config = __esm({
4517
6136
  "bin/jpi-config.js"() {
4518
6137
  "use strict";
4519
- TERMINALHIRE_DIR7 = join9(homedir7(), ".terminalhire");
4520
- CONFIG_FILE = join9(TERMINALHIRE_DIR7, "config.json");
6138
+ TERMINALHIRE_DIR7 = join10(homedir8(), ".terminalhire");
6139
+ CONFIG_FILE = join10(TERMINALHIRE_DIR7, "config.json");
4521
6140
  DEFAULT_CONFIG = { nudge: "session" };
4522
6141
  }
4523
6142
  });
@@ -4540,25 +6159,25 @@ __export(spinner_exports, {
4540
6159
  readSpinnerConfig: () => readSpinnerConfig
4541
6160
  });
4542
6161
  import {
4543
- readFileSync as readFileSync10,
4544
- writeFileSync as writeFileSync7,
4545
- existsSync as existsSync7,
4546
- mkdirSync as mkdirSync7,
6162
+ readFileSync as readFileSync11,
6163
+ writeFileSync as writeFileSync8,
6164
+ existsSync as existsSync8,
6165
+ mkdirSync as mkdirSync8,
4547
6166
  renameSync as renameSync2
4548
6167
  } from "fs";
4549
- import { join as join10, dirname } from "path";
4550
- import { homedir as homedir8 } from "os";
6168
+ import { join as join11, dirname } from "path";
6169
+ import { homedir as homedir9 } from "os";
4551
6170
  function readJson(path, fallback) {
4552
6171
  try {
4553
- return existsSync7(path) ? JSON.parse(readFileSync10(path, "utf8")) : fallback;
6172
+ return existsSync8(path) ? JSON.parse(readFileSync11(path, "utf8")) : fallback;
4554
6173
  } catch {
4555
6174
  return fallback;
4556
6175
  }
4557
6176
  }
4558
6177
  function atomicWriteJson(path, obj) {
4559
- mkdirSync7(dirname(path), { recursive: true });
6178
+ mkdirSync8(dirname(path), { recursive: true });
4560
6179
  const tmp = `${path}.tmp-${process.pid}`;
4561
- writeFileSync7(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
6180
+ writeFileSync8(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
4562
6181
  renameSync2(tmp, path);
4563
6182
  }
4564
6183
  function titleCase(s) {
@@ -4587,12 +6206,12 @@ function formatVerbs(topMatches, max = 6) {
4587
6206
  let title = String(m.title).trim().replace(/\s+/g, " ");
4588
6207
  if (title.length > 32) title = title.slice(0, 31).trimEnd() + "\u2026";
4589
6208
  const company = titleCase(String(m.company).trim().replace(/\s+/g, " "));
4590
- const pct = Math.max(1, Math.min(99, Math.round((Number(m.score) || 0) * 100)));
6209
+ const pct2 = Math.max(1, Math.min(99, Math.round((Number(m.score) || 0) * 100)));
4591
6210
  const key = `${title.toLowerCase()}@${company.toLowerCase()}`;
4592
6211
  if (seen.has(key)) continue;
4593
6212
  seen.add(key);
4594
6213
  const intro = VERB_INTROS[out.length % VERB_INTROS.length];
4595
- out.push(`${intro} ${title} @ ${company} \xB7 ${pct}% match`);
6214
+ out.push(`${intro} ${title} @ ${company} \xB7 ${pct2}% match`);
4596
6215
  if (out.length >= max) break;
4597
6216
  }
4598
6217
  return out;
@@ -4755,15 +6374,15 @@ function buildTips(topMatches, baseUrl, max = 8) {
4755
6374
  let title = titleRaw;
4756
6375
  if (title.length > 34) title = title.slice(0, 33).trimEnd() + "\u2026";
4757
6376
  const company = titleCase(companyRaw);
4758
- const pct = Math.max(1, Math.min(99, Math.round((Number(m.score) || 0) * 100)));
6377
+ const pct2 = Math.max(1, Math.min(99, Math.round((Number(m.score) || 0) * 100)));
4759
6378
  const token = Buffer.from(String(m.id)).toString("base64url");
4760
6379
  const url = `${base}/j/${token}`;
4761
6380
  if (source === "bounty") {
4762
6381
  const money = m.amountUSD != null ? `$${Number(m.amountUSD).toLocaleString()}` : "$\u2014";
4763
6382
  const repo = m.repo || companyRaw;
4764
- out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct}% \u2014 ${url}`);
6383
+ out.push(`\u{1F48E} ${money} \xB7 ${title} \xB7 ${repo} \xB7 ${pct2}% \u2014 ${url}`);
4765
6384
  } else {
4766
- out.push(`\u2197 ${title} @ ${company} \xB7 ${pct}% \u2014 ${url}`);
6385
+ out.push(`\u2197 ${title} @ ${company} \xB7 ${pct2}% \u2014 ${url}`);
4767
6386
  }
4768
6387
  if (out.length >= max) break;
4769
6388
  }
@@ -4810,10 +6429,10 @@ var TH_DIR, CLAUDE_SETTINGS, CONFIG_FILE2, SPINNER_STATE_FILE, SPINNER_DEFAULTS,
4810
6429
  var init_spinner = __esm({
4811
6430
  "bin/spinner.js"() {
4812
6431
  "use strict";
4813
- TH_DIR = process.env["TERMINALHIRE_DIR"] || join10(homedir8(), ".terminalhire");
4814
- CLAUDE_SETTINGS = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join10(homedir8(), ".claude", "settings.json");
4815
- CONFIG_FILE2 = join10(TH_DIR, "config.json");
4816
- SPINNER_STATE_FILE = join10(TH_DIR, "spinner-state.json");
6432
+ TH_DIR = process.env["TERMINALHIRE_DIR"] || join11(homedir9(), ".terminalhire");
6433
+ CLAUDE_SETTINGS = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join11(homedir9(), ".claude", "settings.json");
6434
+ CONFIG_FILE2 = join11(TH_DIR, "config.json");
6435
+ SPINNER_STATE_FILE = join11(TH_DIR, "spinner-state.json");
4817
6436
  SPINNER_DEFAULTS = { enabled: false, mode: "append", max: 6, frequency: "sometimes" };
4818
6437
  VERB_INTROS = ["Matched:", "You\u2019d fit:", "Worth a look:", "On your radar:", "Fits your stack:"];
4819
6438
  }
@@ -4822,39 +6441,39 @@ var init_spinner = __esm({
4822
6441
  // bin/jpi-spinner.js
4823
6442
  var jpi_spinner_exports = {};
4824
6443
  __export(jpi_spinner_exports, {
4825
- run: () => run8
6444
+ run: () => run9
4826
6445
  });
4827
6446
  import {
4828
- readFileSync as readFileSync11,
4829
- writeFileSync as writeFileSync8,
6447
+ readFileSync as readFileSync12,
6448
+ writeFileSync as writeFileSync9,
4830
6449
  copyFileSync,
4831
- existsSync as existsSync8,
4832
- mkdirSync as mkdirSync8
6450
+ existsSync as existsSync9,
6451
+ mkdirSync as mkdirSync9
4833
6452
  } from "fs";
4834
- import { join as join11 } from "path";
4835
- import { homedir as homedir9 } from "os";
4836
- import { createInterface as createInterface4 } from "readline";
6453
+ import { join as join12 } from "path";
6454
+ import { homedir as homedir10 } from "os";
6455
+ import { createInterface as createInterface5 } from "readline";
4837
6456
  function readConfig2() {
4838
6457
  try {
4839
- return existsSync8(CONFIG_FILE3) ? JSON.parse(readFileSync11(CONFIG_FILE3, "utf8")) : {};
6458
+ return existsSync9(CONFIG_FILE3) ? JSON.parse(readFileSync12(CONFIG_FILE3, "utf8")) : {};
4840
6459
  } catch {
4841
6460
  return {};
4842
6461
  }
4843
6462
  }
4844
6463
  function writeConfig2(patch) {
4845
- mkdirSync8(TH_DIR2, { recursive: true });
6464
+ mkdirSync9(TH_DIR2, { recursive: true });
4846
6465
  const merged = { ...readConfig2(), ...patch };
4847
- writeFileSync8(CONFIG_FILE3, JSON.stringify(merged, null, 2) + "\n", "utf8");
6466
+ writeFileSync9(CONFIG_FILE3, JSON.stringify(merged, null, 2) + "\n", "utf8");
4848
6467
  }
4849
6468
  function backupSettings() {
4850
- if (!existsSync8(SETTINGS_PATH)) return null;
6469
+ if (!existsSync9(SETTINGS_PATH)) return null;
4851
6470
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4852
6471
  const backupPath = `${SETTINGS_PATH}.terminalhire-backup-${ts}`;
4853
6472
  copyFileSync(SETTINGS_PATH, backupPath);
4854
6473
  return backupPath;
4855
6474
  }
4856
6475
  function ask(question) {
4857
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
6476
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
4858
6477
  return new Promise((res) => {
4859
6478
  rl.question(question, (answer) => {
4860
6479
  rl.close();
@@ -4864,13 +6483,13 @@ function ask(question) {
4864
6483
  }
4865
6484
  function readTopMatches() {
4866
6485
  try {
4867
- const c = JSON.parse(readFileSync11(CACHE_FILE, "utf8"));
6486
+ const c = JSON.parse(readFileSync12(CACHE_FILE, "utf8"));
4868
6487
  return Array.isArray(c.topMatches) ? c.topMatches : [];
4869
6488
  } catch {
4870
6489
  return [];
4871
6490
  }
4872
6491
  }
4873
- async function run8() {
6492
+ async function run9() {
4874
6493
  const args3 = process.argv.slice(2).filter((a) => a !== "spinner");
4875
6494
  const has = (f) => args3.includes(f);
4876
6495
  const val = (f) => {
@@ -5007,24 +6626,24 @@ var init_jpi_spinner = __esm({
5007
6626
  "bin/jpi-spinner.js"() {
5008
6627
  "use strict";
5009
6628
  init_spinner();
5010
- TH_DIR2 = process.env["TERMINALHIRE_DIR"] || join11(homedir9(), ".terminalhire");
5011
- CONFIG_FILE3 = join11(TH_DIR2, "config.json");
5012
- SETTINGS_PATH = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join11(homedir9(), ".claude", "settings.json");
5013
- CACHE_FILE = join11(TH_DIR2, "index-cache.json");
6629
+ TH_DIR2 = process.env["TERMINALHIRE_DIR"] || join12(homedir10(), ".terminalhire");
6630
+ CONFIG_FILE3 = join12(TH_DIR2, "config.json");
6631
+ SETTINGS_PATH = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join12(homedir10(), ".claude", "settings.json");
6632
+ CACHE_FILE = join12(TH_DIR2, "index-cache.json");
5014
6633
  }
5015
6634
  });
5016
6635
 
5017
6636
  // bin/jpi-sync.js
5018
6637
  var jpi_sync_exports = {};
5019
6638
  __export(jpi_sync_exports, {
5020
- run: () => run9
6639
+ run: () => run10
5021
6640
  });
5022
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync9, rmSync as rmSync2 } from "fs";
5023
- import { join as join12 } from "path";
5024
- import { homedir as homedir10, hostname as osHostname } from "os";
5025
- import { createInterface as createInterface5 } from "readline";
6641
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, existsSync as existsSync10, rmSync as rmSync2 } from "fs";
6642
+ import { join as join13 } from "path";
6643
+ import { homedir as homedir11, hostname as osHostname } from "os";
6644
+ import { createInterface as createInterface6 } from "readline";
5026
6645
  function ask2(question) {
5027
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
6646
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
5028
6647
  return new Promise((res) => {
5029
6648
  rl.question(question, (answer) => {
5030
6649
  rl.close();
@@ -5034,14 +6653,14 @@ function ask2(question) {
5034
6653
  }
5035
6654
  function readMarker() {
5036
6655
  try {
5037
- return existsSync9(TIER1_MARKER) ? JSON.parse(readFileSync12(TIER1_MARKER, "utf8")) : null;
6656
+ return existsSync10(TIER1_MARKER) ? JSON.parse(readFileSync13(TIER1_MARKER, "utf8")) : null;
5038
6657
  } catch {
5039
6658
  return null;
5040
6659
  }
5041
6660
  }
5042
6661
  function writeMarker(marker) {
5043
- mkdirSync9(TH_DIR3, { recursive: true });
5044
- writeFileSync9(TIER1_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
6662
+ mkdirSync10(TH_DIR3, { recursive: true });
6663
+ writeFileSync10(TIER1_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
5045
6664
  }
5046
6665
  function clearMarker() {
5047
6666
  try {
@@ -5107,7 +6726,7 @@ async function runPush() {
5107
6726
  const fields = buildConsentFields(profile);
5108
6727
  renderPreview(fields);
5109
6728
  await new Promise((resolve2) => {
5110
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
6729
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
5111
6730
  rl.question(
5112
6731
  " Press Enter to open your browser to authorize + consent (or Ctrl-C to cancel)... ",
5113
6732
  () => {
@@ -5329,7 +6948,7 @@ async function runDelete() {
5329
6948
  clearMarker();
5330
6949
  console.log("\n Synced profile deleted and local marker cleared.\n");
5331
6950
  }
5332
- async function run9() {
6951
+ async function run10() {
5333
6952
  const args3 = process.argv.slice(2).filter((a) => a !== "sync");
5334
6953
  const has = (f) => args3.includes(f);
5335
6954
  if (has("--push") || has("--enable")) {
@@ -5360,8 +6979,8 @@ var init_jpi_sync = __esm({
5360
6979
  "bin/jpi-sync.js"() {
5361
6980
  "use strict";
5362
6981
  init_open_url();
5363
- TH_DIR3 = process.env["TERMINALHIRE_DIR"] || join12(homedir10(), ".terminalhire");
5364
- TIER1_MARKER = join12(TH_DIR3, "tier1.json");
6982
+ TH_DIR3 = process.env["TERMINALHIRE_DIR"] || join13(homedir11(), ".terminalhire");
6983
+ TIER1_MARKER = join13(TH_DIR3, "tier1.json");
5365
6984
  API_URL3 = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
5366
6985
  SYNC_BASE = "https://www.terminalhire.com";
5367
6986
  POLL_INTERVAL_MS = 2e3;
@@ -5373,16 +6992,16 @@ var init_jpi_sync = __esm({
5373
6992
  // bin/jpi-init.js
5374
6993
  var jpi_init_exports = {};
5375
6994
  __export(jpi_init_exports, {
5376
- run: () => run10
6995
+ run: () => run11
5377
6996
  });
5378
- import { existsSync as existsSync10 } from "fs";
5379
- import { join as join13, resolve } from "path";
6997
+ import { existsSync as existsSync11 } from "fs";
6998
+ import { join as join14, resolve } from "path";
5380
6999
  import { fileURLToPath as fileURLToPath3 } from "url";
5381
- import { createInterface as createInterface6 } from "readline";
7000
+ import { createInterface as createInterface7 } from "readline";
5382
7001
  import { spawnSync, spawn as spawn2 } from "child_process";
5383
- import { homedir as homedir11 } from "os";
7002
+ import { homedir as homedir12 } from "os";
5384
7003
  function ask3(question) {
5385
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
7004
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
5386
7005
  return new Promise((resolve2) => {
5387
7006
  rl.question(question, (answer) => {
5388
7007
  rl.close();
@@ -5391,18 +7010,18 @@ function ask3(question) {
5391
7010
  });
5392
7011
  }
5393
7012
  function resolveScript(name) {
5394
- const distPath = resolve(join13(__dirname2, "..", "..", "dist", "bin", `${name}.js`));
5395
- const legacyPath = resolve(join13(__dirname2, `${name}.js`));
5396
- return existsSync10(distPath) ? distPath : legacyPath;
7013
+ const distPath = resolve(join14(__dirname2, "..", "..", "dist", "bin", `${name}.js`));
7014
+ const legacyPath = resolve(join14(__dirname2, `${name}.js`));
7015
+ return existsSync11(distPath) ? distPath : legacyPath;
5397
7016
  }
5398
7017
  function resolveInstallJs() {
5399
- const fromDist = resolve(join13(__dirname2, "..", "..", "install.js"));
5400
- const fromBin = resolve(join13(__dirname2, "..", "install.js"));
5401
- if (existsSync10(fromDist)) return fromDist;
5402
- if (existsSync10(fromBin)) return fromBin;
7018
+ const fromDist = resolve(join14(__dirname2, "..", "..", "install.js"));
7019
+ const fromBin = resolve(join14(__dirname2, "..", "install.js"));
7020
+ if (existsSync11(fromDist)) return fromDist;
7021
+ if (existsSync11(fromBin)) return fromBin;
5403
7022
  return fromBin;
5404
7023
  }
5405
- async function run10() {
7024
+ async function run11() {
5406
7025
  console.log("");
5407
7026
  console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
5408
7027
  console.log("\u2502 terminalhire init \u2014 one-command onboarding \u2502");
@@ -5505,13 +7124,13 @@ var init_jpi_init = __esm({
5505
7124
  // bin/jpi-refresh.js
5506
7125
  var jpi_refresh_exports = {};
5507
7126
  __export(jpi_refresh_exports, {
5508
- run: () => run11
7127
+ run: () => run12
5509
7128
  });
5510
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync11, mkdirSync as mkdirSync10 } from "fs";
5511
- import { join as join14 } from "path";
5512
- import { homedir as homedir12 } from "os";
7129
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync12, mkdirSync as mkdirSync11 } from "fs";
7130
+ import { join as join15 } from "path";
7131
+ import { homedir as homedir13 } from "os";
5513
7132
  import { fileURLToPath as fileURLToPath4 } from "url";
5514
- async function run11() {
7133
+ async function run12() {
5515
7134
  try {
5516
7135
  let index;
5517
7136
  try {
@@ -5563,14 +7182,14 @@ async function run11() {
5563
7182
  }
5564
7183
  } catch {
5565
7184
  }
5566
- mkdirSync10(TERMINALHIRE_DIR8, { recursive: true });
7185
+ mkdirSync11(TERMINALHIRE_DIR8, { recursive: true });
5567
7186
  const cacheEntry = {
5568
7187
  ts: Date.now(),
5569
7188
  index,
5570
7189
  matchCount,
5571
7190
  topMatches
5572
7191
  };
5573
- writeFileSync10(INDEX_CACHE_FILE4, JSON.stringify(cacheEntry), "utf8");
7192
+ writeFileSync11(INDEX_CACHE_FILE4, JSON.stringify(cacheEntry), "utf8");
5574
7193
  try {
5575
7194
  const {
5576
7195
  readSpinnerConfig: readSpinnerConfig2,
@@ -5619,8 +7238,8 @@ var init_jpi_refresh = __esm({
5619
7238
  "bin/jpi-refresh.js"() {
5620
7239
  "use strict";
5621
7240
  __dirname3 = fileURLToPath4(new URL(".", import.meta.url));
5622
- TERMINALHIRE_DIR8 = join14(homedir12(), ".terminalhire");
5623
- INDEX_CACHE_FILE4 = join14(TERMINALHIRE_DIR8, "index-cache.json");
7241
+ TERMINALHIRE_DIR8 = join15(homedir13(), ".terminalhire");
7242
+ INDEX_CACHE_FILE4 = join15(TERMINALHIRE_DIR8, "index-cache.json");
5624
7243
  API_URL4 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
5625
7244
  }
5626
7245
  });
@@ -5628,16 +7247,16 @@ var init_jpi_refresh = __esm({
5628
7247
  // bin/jpi-save.js
5629
7248
  var jpi_save_exports = {};
5630
7249
  __export(jpi_save_exports, {
5631
- run: () => run12
7250
+ run: () => run13
5632
7251
  });
5633
- import { readFileSync as readFileSync14, existsSync as existsSync12 } from "fs";
5634
- import { join as join15 } from "path";
5635
- import { homedir as homedir13 } from "os";
7252
+ import { readFileSync as readFileSync15, existsSync as existsSync13 } from "fs";
7253
+ import { join as join16 } from "path";
7254
+ import { homedir as homedir14 } from "os";
5636
7255
  import { fileURLToPath as fileURLToPath5 } from "url";
5637
7256
  function findJobInCache(jobId) {
5638
7257
  try {
5639
- if (!existsSync12(INDEX_CACHE_FILE5)) return null;
5640
- const raw = readFileSync14(INDEX_CACHE_FILE5, "utf8");
7258
+ if (!existsSync13(INDEX_CACHE_FILE5)) return null;
7259
+ const raw = readFileSync15(INDEX_CACHE_FILE5, "utf8");
5641
7260
  const entry = JSON.parse(raw);
5642
7261
  const jobs = entry?.index?.jobs ?? [];
5643
7262
  return jobs.find((j) => j.id === jobId) ?? null;
@@ -5706,7 +7325,7 @@ async function cmdUnsave(jobId) {
5706
7325
  process.exit(1);
5707
7326
  }
5708
7327
  }
5709
- async function run12() {
7328
+ async function run13() {
5710
7329
  const verb = process.argv[2];
5711
7330
  const jobId = process.argv[3];
5712
7331
  try {
@@ -5730,26 +7349,26 @@ var init_jpi_save = __esm({
5730
7349
  "bin/jpi-save.js"() {
5731
7350
  "use strict";
5732
7351
  __dirname4 = fileURLToPath5(new URL(".", import.meta.url));
5733
- TERMINALHIRE_DIR9 = join15(homedir13(), ".terminalhire");
5734
- INDEX_CACHE_FILE5 = join15(TERMINALHIRE_DIR9, "index-cache.json");
7352
+ TERMINALHIRE_DIR9 = join16(homedir14(), ".terminalhire");
7353
+ INDEX_CACHE_FILE5 = join16(TERMINALHIRE_DIR9, "index-cache.json");
5735
7354
  }
5736
7355
  });
5737
7356
 
5738
7357
  // bin/jpi-dispatch.js
5739
7358
  import { fileURLToPath as fileURLToPath6 } from "url";
5740
- import { join as join16, dirname as dirname2 } from "path";
5741
- import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
7359
+ import { join as join17, dirname as dirname2 } from "path";
7360
+ import { existsSync as existsSync14, readFileSync as readFileSync16 } from "fs";
5742
7361
  import { createRequire } from "module";
5743
7362
  var __dirname5 = fileURLToPath6(new URL(".", import.meta.url));
5744
7363
  function readPackageVersion() {
5745
7364
  try {
5746
7365
  const candidates = [
5747
- join16(__dirname5, "..", "..", "package.json"),
5748
- join16(__dirname5, "..", "package.json")
7366
+ join17(__dirname5, "..", "..", "package.json"),
7367
+ join17(__dirname5, "..", "package.json")
5749
7368
  ];
5750
7369
  for (const p of candidates) {
5751
- if (existsSync13(p)) {
5752
- const pkg = JSON.parse(readFileSync15(p, "utf8"));
7370
+ if (existsSync14(p)) {
7371
+ const pkg = JSON.parse(readFileSync16(p, "utf8"));
5753
7372
  if (pkg.version) return pkg.version;
5754
7373
  }
5755
7374
  }
@@ -5760,7 +7379,7 @@ function readPackageVersion() {
5760
7379
  var firstArg = process.argv[2];
5761
7380
  if (!firstArg && !process.stdin.isTTY) {
5762
7381
  const { default: childProcess } = await import("child_process");
5763
- const nudgeScript = join16(__dirname5, "jpi.js");
7382
+ const nudgeScript = join17(__dirname5, "jpi.js");
5764
7383
  const child = childProcess.spawnSync(process.execPath, [nudgeScript], {
5765
7384
  stdio: ["inherit", "inherit", "inherit"]
5766
7385
  });
@@ -5782,6 +7401,11 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
5782
7401
  console.log(" terminalhire claim record <id|issueUrl> Claim a bounty locally + print the executor brief");
5783
7402
  console.log(" terminalhire claim list [--active] List your claims + accepted-PR rate");
5784
7403
  console.log(" terminalhire claim status [<id>] Poll source PR merge state (updates the metric)");
7404
+ console.log(" terminalhire trajectory Trajectory from your local Claude Code corpus");
7405
+ console.log(" terminalhire trajectory --export Write a derived score + Markdown to ~/.terminalhire/");
7406
+ console.log(" terminalhire trajectory --inward Also show private rework/recovery (never exported)");
7407
+ console.log(" terminalhire trajectory --push Opt-in: link your derived trajectory to your dashboard (typed-yes)");
7408
+ console.log(" terminalhire trajectory --push --delete Unlink (revoke) your trajectory from the dashboard");
5785
7409
  console.log(" terminalhire profile --show Display your encrypted local profile");
5786
7410
  console.log(" terminalhire profile --edit Set displayName, contactEmail, prefs");
5787
7411
  console.log(" terminalhire profile --delete Wipe profile and encryption key from disk");
@@ -5840,6 +7464,12 @@ if (firstArg === "claim") {
5840
7464
  await mod.run();
5841
7465
  process.exit(0);
5842
7466
  }
7467
+ if (firstArg === "trajectory" || firstArg === "mirror") {
7468
+ process.argv.splice(2, 1);
7469
+ const mod = await Promise.resolve().then(() => (init_jpi_trajectory(), jpi_trajectory_exports));
7470
+ await mod.run();
7471
+ process.exit(0);
7472
+ }
5843
7473
  if (firstArg === "profile") {
5844
7474
  const mod = await Promise.resolve().then(() => (init_jpi_profile(), jpi_profile_exports));
5845
7475
  await mod.run();