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.
- package/dist/bin/jpi-bounties.js +49 -1
- package/dist/bin/jpi-claim.js +249 -10
- package/dist/bin/jpi-dispatch.js +630 -81
- package/dist/bin/jpi-jobs.js +49 -1
- package/dist/bin/jpi-login.js +49 -1
- package/dist/bin/jpi-refresh.js +49 -1
- package/dist/bin/jpi-trajectory.js +1312 -75
- package/dist/src/trajectory.js +1314 -74
- package/package.json +1 -1
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -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
|
|
3184
|
-
const webUrl = `${
|
|
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:
|
|
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
|
|
4021
|
-
const active =
|
|
4022
|
-
const 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(
|
|
4309
|
+
await cmdPreview(positional[0], { json });
|
|
4027
4310
|
break;
|
|
4028
4311
|
case "record":
|
|
4029
|
-
await cmdRecord(
|
|
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(
|
|
4318
|
+
await cmdStatus(positional[0]);
|
|
4036
4319
|
break;
|
|
4037
4320
|
case "update":
|
|
4038
|
-
await cmdUpdate(
|
|
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(
|
|
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
|
|
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
|
|
4547
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
4619
|
-
//
|
|
4620
|
-
"
|
|
4621
|
-
"
|
|
4622
|
-
"
|
|
4623
|
-
"
|
|
4624
|
-
"
|
|
4625
|
-
"
|
|
4626
|
-
"
|
|
4627
|
-
|
|
4628
|
-
"
|
|
4629
|
-
"
|
|
4630
|
-
"
|
|
4631
|
-
"
|
|
4632
|
-
|
|
4633
|
-
"
|
|
4634
|
-
|
|
4635
|
-
"
|
|
4636
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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
|
|
5687
|
+
import { createInterface as createInterface4 } from "readline";
|
|
5141
5688
|
function prompt3(question) {
|
|
5142
|
-
const rl =
|
|
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
|
|
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 =
|
|
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
|
|
6644
|
+
import { createInterface as createInterface6 } from "readline";
|
|
6098
6645
|
function ask2(question) {
|
|
6099
|
-
const rl =
|
|
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 =
|
|
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
|
|
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 =
|
|
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");
|