terminalhire 0.5.0 → 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;
@@ -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,
@@ -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;
@@ -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,6 +4346,8 @@ 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
 
@@ -4532,21 +4823,23 @@ var init_events = __esm({
4532
4823
  });
4533
4824
 
4534
4825
  // ../../packages/core/src/episodes/derivers/signals.ts
4535
- function normalizeToolName(name) {
4536
- if (!name.toLowerCase().startsWith("mcp__")) {
4537
- return name;
4538
- }
4826
+ function mcpToolSignal(name) {
4539
4827
  const rest = name.slice("mcp__".length);
4540
4828
  const sep = rest.indexOf("__");
4541
- if (sep <= 0) {
4542
- return "mcp:custom";
4543
- }
4829
+ if (sep <= 0) return "mcp:custom";
4544
4830
  const server = rest.slice(0, sep).toLowerCase();
4545
4831
  const leaf = rest.slice(sep + 2);
4546
- if (leaf.length === 0 || !PUBLIC_MCP_SERVERS.has(server)) {
4547
- return "mcp:custom";
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);
4548
4839
  }
4549
- return `mcp:${leaf}`;
4840
+ const key = name.toLowerCase();
4841
+ if (ORCHESTRATION_TOOLS.has(key)) return AGENTIC_WORKFLOW_SIGNAL;
4842
+ return null;
4550
4843
  }
4551
4844
  function isRecord2(value) {
4552
4845
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -4585,7 +4878,10 @@ function extractToolSignals(nodes) {
4585
4878
  if (node.kind !== "tool_use" /* ToolUse */ || node.toolName === null) {
4586
4879
  continue;
4587
4880
  }
4588
- out.push({ signal: `tool:${normalizeToolName(node.toolName)}`, ts: node.timestamp });
4881
+ const toolSignal = classifyToolSignal(node.toolName);
4882
+ if (toolSignal !== null) {
4883
+ out.push({ signal: toolSignal, ts: node.timestamp });
4884
+ }
4589
4885
  const lang = fileExtLang(toolUseInput(node));
4590
4886
  if (lang !== null) {
4591
4887
  out.push({ signal: `lang:${lang}`, ts: node.timestamp });
@@ -4594,7 +4890,7 @@ function extractToolSignals(nodes) {
4594
4890
  out.sort(compareSignal);
4595
4891
  return out;
4596
4892
  }
4597
- var EXT_LANG, PUBLIC_MCP_SERVERS;
4893
+ var EXT_LANG, MCP_SERVER_CAPABILITY, ORCHESTRATION_TOOLS, AGENTIC_WORKFLOW_SIGNAL;
4598
4894
  var init_signals = __esm({
4599
4895
  "../../packages/core/src/episodes/derivers/signals.ts"() {
4600
4896
  "use strict";
@@ -4615,26 +4911,47 @@ var init_signals = __esm({
4615
4911
  sql: "sql",
4616
4912
  md: "md"
4617
4913
  };
4618
- PUBLIC_MCP_SERVERS = /* @__PURE__ */ new Set([
4619
- // Anthropic first-party connectors (claude.ai)
4620
- "claude_ai_linear",
4621
- "claude_ai_gmail",
4622
- "claude_ai_google_calendar",
4623
- "claude_ai_google_drive",
4624
- "claude_ai_figma",
4625
- "claude_ai_vercel",
4626
- "claude_ai_ideabrowser",
4627
- // Common public Claude Code plugins
4628
- "plugin_playwright_playwright",
4629
- "plugin_context-mode_context-mode",
4630
- "plugin_vercel_vercel",
4631
- "playwright",
4632
- "ideabrowser",
4633
- "blender",
4634
- "figma",
4635
- "n8n-knowledge",
4636
- "pencil"
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]
4637
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";
4638
4955
  }
4639
4956
  });
4640
4957
 
@@ -4883,7 +5200,10 @@ var init_episodes = __esm({
4883
5200
  // src/trajectory.ts
4884
5201
  var trajectory_exports = {};
4885
5202
  __export(trajectory_exports, {
4886
- runTrajectory: () => runTrajectory
5203
+ PUSH_DENYLIST: () => PUSH_DENYLIST,
5204
+ computeDerivedScore: () => computeDerivedScore,
5205
+ runTrajectory: () => runTrajectory,
5206
+ runTrajectoryPush: () => runTrajectoryPush
4887
5207
  });
4888
5208
  import {
4889
5209
  existsSync as existsSync6,
@@ -4949,9 +5269,13 @@ function loadCorpus(paths) {
4949
5269
  }
4950
5270
  return files;
4951
5271
  }
5272
+ function isLang(signal) {
5273
+ return signal.startsWith("lang:");
5274
+ }
4952
5275
  function prettySignal(signal) {
4953
- if (signal.startsWith("tool:")) return signal.slice("tool:".length);
4954
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, " ");
4955
5279
  return signal;
4956
5280
  }
4957
5281
  function prettyList(signals, max = 12) {
@@ -4987,9 +5311,15 @@ function renderTerminal(view) {
4987
5311
  if (rising.length > 0) console.log(` \u2191 rising: ${rising.join(", ")}`);
4988
5312
  if (falling.length > 0) console.log(` \u2193 falling: ${falling.join(", ")}`);
4989
5313
  console.log("");
4990
- console.log(" \u258C Stack");
4991
- console.log(` live (this quarter): ${prettyList(view.score.liveStack)}`);
4992
- console.log(` dormant (90d+ untouched): ${prettyList(view.score.dormantStack)}`);
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)}`);
4993
5323
  console.log("");
4994
5324
  console.log(" \u258C Coverage / scope");
4995
5325
  console.log(` ${coverageLine(view)}`);
@@ -5012,10 +5342,14 @@ function renderMarkdown(view) {
5012
5342
  lines.push(`- Rising: ${rising.length > 0 ? rising.join(", ") : "_none_"}`);
5013
5343
  lines.push(`- Falling: ${falling.length > 0 ? falling.join(", ") : "_none_"}`);
5014
5344
  lines.push("");
5015
- lines.push("## Stack");
5345
+ lines.push("## Stack (languages)");
5016
5346
  lines.push("");
5017
- lines.push(`- **Live** (used this quarter): ${prettyList(view.score.liveStack, 64)}`);
5018
- lines.push(`- **Dormant** (90d+ untouched): ${prettyList(view.score.dormantStack, 64)}`);
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)}`);
5019
5353
  lines.push("");
5020
5354
  lines.push("## Coverage / scope");
5021
5355
  lines.push("");
@@ -5047,19 +5381,11 @@ function renderInward(allNodes, view, files) {
5047
5381
  );
5048
5382
  console.log("");
5049
5383
  }
5050
- async function runTrajectory(opts) {
5384
+ function buildTrajectory() {
5051
5385
  const projectsDir = join8(homedir7(), ".claude", "projects");
5052
- if (!existsSync6(projectsDir)) {
5053
- console.log("terminalhire trajectory: no ~/.claude/projects directory found.");
5054
- console.log(" Start a Claude Code session and your trajectory will appear here.");
5055
- return;
5056
- }
5386
+ if (!existsSync6(projectsDir)) return null;
5057
5387
  const paths = findJsonlFiles(projectsDir);
5058
- if (paths.length === 0) {
5059
- console.log("terminalhire trajectory: no transcripts found under ~/.claude/projects.");
5060
- console.log(" Start a Claude Code session and your trajectory will appear here.");
5061
- return;
5062
- }
5388
+ if (paths.length === 0) return null;
5063
5389
  const files = loadCorpus(paths);
5064
5390
  const allNodes = [];
5065
5391
  for (const f of files) {
@@ -5082,30 +5408,245 @@ async function runTrajectory(opts) {
5082
5408
  for (const n of allNodes) if (n.isSidechain) sidechainNodes++;
5083
5409
  const subagentPct = allNodes.length > 0 ? sidechainNodes / allNodes.length * 100 : 0;
5084
5410
  const sessions = new Set(reconstructed.files.map((f) => f.sessionId)).size;
5085
- const view = {
5086
- score,
5087
- coverage,
5088
- sessions,
5089
- subagentPct,
5090
- fileCount: files.length
5091
- };
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;
5092
5425
  renderTerminal(view);
5093
5426
  if (opts.inward) {
5094
5427
  renderInward(allNodes, view, files);
5095
5428
  }
5096
5429
  if (opts.export) {
5097
5430
  const markdown = renderMarkdown(view);
5098
- const { jsonPath, mdPath } = writeExportArtifacts(score, markdown);
5431
+ const { jsonPath, mdPath } = writeExportArtifacts(view.score, markdown);
5099
5432
  console.log(" Export written:");
5100
5433
  console.log(` ${jsonPath}`);
5101
5434
  console.log(` ${mdPath}`);
5102
5435
  console.log("");
5103
5436
  }
5104
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;
5105
5631
  var init_trajectory = __esm({
5106
5632
  "src/trajectory.ts"() {
5107
5633
  "use strict";
5108
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__"];
5109
5650
  }
5110
5651
  });
5111
5652
 
@@ -5117,6 +5658,12 @@ __export(jpi_trajectory_exports, {
5117
5658
  async function run5() {
5118
5659
  try {
5119
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
+ }
5120
5667
  const doExport = args3.includes("--export");
5121
5668
  const inward = args3.includes("--inward");
5122
5669
  const { runTrajectory: runTrajectory2 } = await Promise.resolve().then(() => (init_trajectory(), trajectory_exports));
@@ -5137,9 +5684,9 @@ var jpi_profile_exports = {};
5137
5684
  __export(jpi_profile_exports, {
5138
5685
  run: () => run6
5139
5686
  });
5140
- import { createInterface as createInterface3 } from "readline";
5687
+ import { createInterface as createInterface4 } from "readline";
5141
5688
  function prompt3(question) {
5142
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
5689
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
5143
5690
  return new Promise((resolve2) => {
5144
5691
  rl.question(question, (answer) => {
5145
5692
  rl.close();
@@ -5905,7 +6452,7 @@ import {
5905
6452
  } from "fs";
5906
6453
  import { join as join12 } from "path";
5907
6454
  import { homedir as homedir10 } from "os";
5908
- import { createInterface as createInterface4 } from "readline";
6455
+ import { createInterface as createInterface5 } from "readline";
5909
6456
  function readConfig2() {
5910
6457
  try {
5911
6458
  return existsSync9(CONFIG_FILE3) ? JSON.parse(readFileSync12(CONFIG_FILE3, "utf8")) : {};
@@ -5926,7 +6473,7 @@ function backupSettings() {
5926
6473
  return backupPath;
5927
6474
  }
5928
6475
  function ask(question) {
5929
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
6476
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
5930
6477
  return new Promise((res) => {
5931
6478
  rl.question(question, (answer) => {
5932
6479
  rl.close();
@@ -6094,9 +6641,9 @@ __export(jpi_sync_exports, {
6094
6641
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, existsSync as existsSync10, rmSync as rmSync2 } from "fs";
6095
6642
  import { join as join13 } from "path";
6096
6643
  import { homedir as homedir11, hostname as osHostname } from "os";
6097
- import { createInterface as createInterface5 } from "readline";
6644
+ import { createInterface as createInterface6 } from "readline";
6098
6645
  function ask2(question) {
6099
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
6646
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
6100
6647
  return new Promise((res) => {
6101
6648
  rl.question(question, (answer) => {
6102
6649
  rl.close();
@@ -6179,7 +6726,7 @@ async function runPush() {
6179
6726
  const fields = buildConsentFields(profile);
6180
6727
  renderPreview(fields);
6181
6728
  await new Promise((resolve2) => {
6182
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
6729
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
6183
6730
  rl.question(
6184
6731
  " Press Enter to open your browser to authorize + consent (or Ctrl-C to cancel)... ",
6185
6732
  () => {
@@ -6450,11 +6997,11 @@ __export(jpi_init_exports, {
6450
6997
  import { existsSync as existsSync11 } from "fs";
6451
6998
  import { join as join14, resolve } from "path";
6452
6999
  import { fileURLToPath as fileURLToPath3 } from "url";
6453
- import { createInterface as createInterface6 } from "readline";
7000
+ import { createInterface as createInterface7 } from "readline";
6454
7001
  import { spawnSync, spawn as spawn2 } from "child_process";
6455
7002
  import { homedir as homedir12 } from "os";
6456
7003
  function ask3(question) {
6457
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
7004
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
6458
7005
  return new Promise((resolve2) => {
6459
7006
  rl.question(question, (answer) => {
6460
7007
  rl.close();
@@ -6857,6 +7404,8 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
6857
7404
  console.log(" terminalhire trajectory Trajectory from your local Claude Code corpus");
6858
7405
  console.log(" terminalhire trajectory --export Write a derived score + Markdown to ~/.terminalhire/");
6859
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");
6860
7409
  console.log(" terminalhire profile --show Display your encrypted local profile");
6861
7410
  console.log(" terminalhire profile --edit Set displayName, contactEmail, prefs");
6862
7411
  console.log(" terminalhire profile --delete Wipe profile and encryption key from disk");