terminalhire 0.9.0 → 0.9.2
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-chat-read.js +74 -39
- package/dist/bin/jpi-chat.js +75 -44
- package/dist/bin/jpi-dispatch.js +452 -210
- package/dist/bin/jpi-intro.js +72 -45
- package/dist/bin/jpi-link.js +285 -0
- package/dist/bin/jpi-trajectory.js +83 -44
- package/dist/src/chat-client.js +48 -15
- package/dist/src/intro.js +68 -45
- package/dist/src/link.js +253 -0
- package/dist/src/trajectory.js +77 -44
- package/dist/src/web-session.js +55 -0
- package/package.json +1 -1
package/dist/src/link.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/open-url.js
|
|
12
|
+
var open_url_exports = {};
|
|
13
|
+
__export(open_url_exports, {
|
|
14
|
+
openInBrowser: () => openInBrowser
|
|
15
|
+
});
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
function openInBrowser(url) {
|
|
18
|
+
let cmd;
|
|
19
|
+
let args;
|
|
20
|
+
if (process.platform === "darwin") {
|
|
21
|
+
cmd = "open";
|
|
22
|
+
args = [url];
|
|
23
|
+
} else if (process.platform === "win32") {
|
|
24
|
+
cmd = "cmd";
|
|
25
|
+
args = ["/c", "start", "", url];
|
|
26
|
+
} else {
|
|
27
|
+
cmd = "xdg-open";
|
|
28
|
+
args = [url];
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
32
|
+
child.on("error", () => {
|
|
33
|
+
});
|
|
34
|
+
child.unref();
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
var init_open_url = __esm({
|
|
39
|
+
"src/open-url.js"() {
|
|
40
|
+
"use strict";
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// src/link.ts
|
|
45
|
+
import { createServer } from "http";
|
|
46
|
+
import { randomBytes } from "crypto";
|
|
47
|
+
|
|
48
|
+
// src/web-session.ts
|
|
49
|
+
import {
|
|
50
|
+
chmodSync,
|
|
51
|
+
existsSync,
|
|
52
|
+
mkdirSync,
|
|
53
|
+
readFileSync,
|
|
54
|
+
rmSync,
|
|
55
|
+
writeFileSync
|
|
56
|
+
} from "fs";
|
|
57
|
+
import { homedir } from "os";
|
|
58
|
+
import { join } from "path";
|
|
59
|
+
function terminalhireDir() {
|
|
60
|
+
return join(homedir(), ".terminalhire");
|
|
61
|
+
}
|
|
62
|
+
function webSessionFilePath() {
|
|
63
|
+
return join(terminalhireDir(), "web-session");
|
|
64
|
+
}
|
|
65
|
+
function readWebSessionFile() {
|
|
66
|
+
try {
|
|
67
|
+
const path = webSessionFilePath();
|
|
68
|
+
if (!existsSync(path)) return null;
|
|
69
|
+
const v = readFileSync(path, "utf8").trim();
|
|
70
|
+
return v.length > 0 ? v : null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function writeWebSessionFile(token) {
|
|
76
|
+
mkdirSync(terminalhireDir(), { recursive: true });
|
|
77
|
+
const path = webSessionFilePath();
|
|
78
|
+
writeFileSync(path, token, { mode: 384, encoding: "utf8" });
|
|
79
|
+
try {
|
|
80
|
+
chmodSync(path, 384);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function clearWebSessionFile() {
|
|
85
|
+
try {
|
|
86
|
+
rmSync(webSessionFilePath());
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/link.ts
|
|
92
|
+
var LINK_BASE = "https://www.terminalhire.com";
|
|
93
|
+
var GH_SESSION_COOKIE = "__jpi_gh_session";
|
|
94
|
+
var LINK_TIMEOUT_MS = 12e4;
|
|
95
|
+
function resolveLoopbackRequest(rawUrl, expectedNonce) {
|
|
96
|
+
let u;
|
|
97
|
+
try {
|
|
98
|
+
u = new URL(rawUrl, "http://127.0.0.1");
|
|
99
|
+
} catch {
|
|
100
|
+
return { ok: false, reason: "bad_url" };
|
|
101
|
+
}
|
|
102
|
+
const nonce = u.searchParams.get("nonce");
|
|
103
|
+
if (!nonce || nonce !== expectedNonce) return { ok: false, reason: "nonce_mismatch" };
|
|
104
|
+
const token = u.searchParams.get("token");
|
|
105
|
+
if (!token) return { ok: false, reason: "missing_token" };
|
|
106
|
+
return { ok: true, token };
|
|
107
|
+
}
|
|
108
|
+
var LINKED_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>terminalhire</title></head>
|
|
109
|
+
<body style="font-family:system-ui;padding:2rem;background:#0b0d10;color:#e6e6e6">
|
|
110
|
+
<script>history.replaceState({},'','/');</script>
|
|
111
|
+
<p>CLI linked \u2014 you can close this tab.</p>
|
|
112
|
+
<script>setTimeout(function(){window.close();},400);</script>
|
|
113
|
+
</body></html>`;
|
|
114
|
+
var FAILED_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>terminalhire</title></head>
|
|
115
|
+
<body style="font-family:system-ui;padding:2rem;background:#0b0d10;color:#e6e6e6">
|
|
116
|
+
<script>history.replaceState({},'','/');</script>
|
|
117
|
+
<p>Link failed \u2014 return to your terminal and run <code>terminalhire link</code> again.</p>
|
|
118
|
+
</body></html>`;
|
|
119
|
+
function defaultStartLoopback(expectedNonce, timeoutMs) {
|
|
120
|
+
return new Promise((resolveHandle) => {
|
|
121
|
+
let settle;
|
|
122
|
+
const result = new Promise((res) => {
|
|
123
|
+
settle = res;
|
|
124
|
+
});
|
|
125
|
+
let done = false;
|
|
126
|
+
const finish = (r) => {
|
|
127
|
+
if (done) return;
|
|
128
|
+
done = true;
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
settle(r);
|
|
131
|
+
setImmediate(() => {
|
|
132
|
+
try {
|
|
133
|
+
server.close();
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const server = createServer((req, res) => {
|
|
139
|
+
const outcome = resolveLoopbackRequest(req.url ?? "", expectedNonce);
|
|
140
|
+
res.writeHead(outcome.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
|
|
141
|
+
res.end(outcome.ok ? LINKED_HTML : FAILED_HTML);
|
|
142
|
+
finish(outcome);
|
|
143
|
+
});
|
|
144
|
+
const timer = setTimeout(() => finish({ ok: false, reason: "timeout" }), timeoutMs);
|
|
145
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
146
|
+
server.on("error", () => finish({ ok: false, reason: "listen_error" }));
|
|
147
|
+
server.listen(0, "127.0.0.1", () => {
|
|
148
|
+
const addr = server.address();
|
|
149
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
150
|
+
resolveHandle({
|
|
151
|
+
port,
|
|
152
|
+
result,
|
|
153
|
+
close: () => {
|
|
154
|
+
try {
|
|
155
|
+
server.close();
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function defaultLinkDeps() {
|
|
164
|
+
return {
|
|
165
|
+
startLoopback: defaultStartLoopback,
|
|
166
|
+
openBrowser: (url) => {
|
|
167
|
+
void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
generateNonce: () => randomBytes(16).toString("hex"),
|
|
171
|
+
persistToken: (token) => writeWebSessionFile(token),
|
|
172
|
+
log: (msg) => console.log(msg),
|
|
173
|
+
errorLog: (msg) => console.error(msg),
|
|
174
|
+
exit: (code) => process.exit(code)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function runLink(overrides) {
|
|
178
|
+
const deps = { ...defaultLinkDeps(), ...overrides };
|
|
179
|
+
const nonce = deps.generateNonce();
|
|
180
|
+
const handle = await deps.startLoopback(nonce, LINK_TIMEOUT_MS);
|
|
181
|
+
const url = `${LINK_BASE}/api/auth/link?port=${handle.port}&nonce=${encodeURIComponent(nonce)}`;
|
|
182
|
+
deps.log("");
|
|
183
|
+
deps.log(" terminalhire \u2014 link this terminal to your account");
|
|
184
|
+
deps.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");
|
|
185
|
+
deps.log(" Opening your browser to approve. If it does not open, paste this URL:");
|
|
186
|
+
deps.log(` \u2192 ${url}`);
|
|
187
|
+
deps.log(" Waiting for approval (this tab closes itself once you approve)\u2026");
|
|
188
|
+
deps.openBrowser(url);
|
|
189
|
+
let outcome;
|
|
190
|
+
try {
|
|
191
|
+
outcome = await handle.result;
|
|
192
|
+
} finally {
|
|
193
|
+
handle.close();
|
|
194
|
+
}
|
|
195
|
+
if (!outcome.ok || !outcome.token) {
|
|
196
|
+
if (outcome.reason === "timeout") {
|
|
197
|
+
deps.errorLog("\n Link timed out \u2014 run `terminalhire link` again.\n");
|
|
198
|
+
} else if (outcome.reason === "nonce_mismatch") {
|
|
199
|
+
deps.errorLog("\n Link rejected (nonce did not match) \u2014 run `terminalhire link` again.\n");
|
|
200
|
+
} else {
|
|
201
|
+
deps.errorLog("\n Link failed \u2014 run `terminalhire link` again.\n");
|
|
202
|
+
}
|
|
203
|
+
deps.exit(1);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
deps.persistToken(outcome.token);
|
|
207
|
+
deps.log("\n This terminal is now linked to your terminalhire account.");
|
|
208
|
+
deps.log(" Try `terminalhire intro <login>`, `terminalhire chat`, or `terminalhire trajectory --push`.");
|
|
209
|
+
deps.log(" Unlink any time with `terminalhire link --logout`.\n");
|
|
210
|
+
deps.exit(0);
|
|
211
|
+
}
|
|
212
|
+
function defaultLinkLogoutDeps() {
|
|
213
|
+
return {
|
|
214
|
+
fetchImpl: (...args) => globalThis.fetch(...args),
|
|
215
|
+
readSessionFile: () => readWebSessionFile(),
|
|
216
|
+
clearSessionFile: () => clearWebSessionFile(),
|
|
217
|
+
log: (msg) => console.log(msg),
|
|
218
|
+
errorLog: (msg) => console.error(msg),
|
|
219
|
+
exit: (code) => process.exit(code)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function runLinkLogout(overrides) {
|
|
223
|
+
const deps = { ...defaultLinkLogoutDeps(), ...overrides };
|
|
224
|
+
const token = deps.readSessionFile();
|
|
225
|
+
if (!token) {
|
|
226
|
+
deps.log("\n No linked web session on this machine \u2014 nothing to unlink.\n");
|
|
227
|
+
deps.exit(0);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let revoked = false;
|
|
231
|
+
try {
|
|
232
|
+
const res = await deps.fetchImpl(`${LINK_BASE}/api/auth/session`, {
|
|
233
|
+
method: "DELETE",
|
|
234
|
+
headers: { Cookie: `${GH_SESSION_COOKIE}=${token}` },
|
|
235
|
+
signal: AbortSignal.timeout(1e4)
|
|
236
|
+
});
|
|
237
|
+
revoked = res.ok;
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
deps.clearSessionFile();
|
|
241
|
+
if (revoked) {
|
|
242
|
+
deps.log("\n Unlinked \u2014 the session was revoked server-side and removed from this machine.\n");
|
|
243
|
+
} else {
|
|
244
|
+
deps.log("\n Removed the local session from this machine.");
|
|
245
|
+
deps.log(" (Could not reach the server to revoke it \u2014 it expires on its own.)\n");
|
|
246
|
+
}
|
|
247
|
+
deps.exit(0);
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
resolveLoopbackRequest,
|
|
251
|
+
runLink,
|
|
252
|
+
runLinkLogout
|
|
253
|
+
};
|
package/dist/src/trajectory.js
CHANGED
|
@@ -683,8 +683,8 @@ var init_feeds = __esm({
|
|
|
683
683
|
});
|
|
684
684
|
|
|
685
685
|
// ../../packages/core/src/partners.ts
|
|
686
|
-
import { readFileSync } from "fs";
|
|
687
|
-
import { join } from "path";
|
|
686
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
687
|
+
import { join as join2 } from "path";
|
|
688
688
|
import { fileURLToPath } from "url";
|
|
689
689
|
var EXAMPLE_BUYER, BUYER_REGISTRY;
|
|
690
690
|
var init_partners = __esm({
|
|
@@ -782,13 +782,13 @@ import {
|
|
|
782
782
|
randomBytes as randomBytes2
|
|
783
783
|
} from "crypto";
|
|
784
784
|
import {
|
|
785
|
-
readFileSync as
|
|
786
|
-
writeFileSync,
|
|
787
|
-
mkdirSync,
|
|
788
|
-
existsSync
|
|
785
|
+
readFileSync as readFileSync3,
|
|
786
|
+
writeFileSync as writeFileSync2,
|
|
787
|
+
mkdirSync as mkdirSync2,
|
|
788
|
+
existsSync as existsSync2
|
|
789
789
|
} from "fs";
|
|
790
|
-
import { join as
|
|
791
|
-
import { homedir } from "os";
|
|
790
|
+
import { join as join3 } from "path";
|
|
791
|
+
import { homedir as homedir2 } from "os";
|
|
792
792
|
async function loadKey() {
|
|
793
793
|
try {
|
|
794
794
|
const kt = await import("keytar");
|
|
@@ -801,12 +801,12 @@ async function loadKey() {
|
|
|
801
801
|
return key2;
|
|
802
802
|
} catch {
|
|
803
803
|
}
|
|
804
|
-
|
|
805
|
-
if (
|
|
806
|
-
return Buffer.from(
|
|
804
|
+
mkdirSync2(TERMINALHIRE_DIR, { recursive: true });
|
|
805
|
+
if (existsSync2(KEY_FILE)) {
|
|
806
|
+
return Buffer.from(readFileSync3(KEY_FILE, "utf8").trim(), "hex");
|
|
807
807
|
}
|
|
808
808
|
const key = randomBytes2(KEY_BYTES);
|
|
809
|
-
|
|
809
|
+
writeFileSync2(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
810
810
|
return key;
|
|
811
811
|
}
|
|
812
812
|
function encrypt(plaintext, key) {
|
|
@@ -864,10 +864,10 @@ function migrateTagWeights(profile) {
|
|
|
864
864
|
}
|
|
865
865
|
}
|
|
866
866
|
async function readProfile() {
|
|
867
|
-
if (!
|
|
867
|
+
if (!existsSync2(PROFILE_FILE)) return blankProfile();
|
|
868
868
|
try {
|
|
869
869
|
const key = await loadKey();
|
|
870
|
-
const raw =
|
|
870
|
+
const raw = readFileSync3(PROFILE_FILE, "utf8");
|
|
871
871
|
const blob = JSON.parse(raw);
|
|
872
872
|
const plaintext = decrypt(blob, key);
|
|
873
873
|
const parsed = JSON.parse(plaintext);
|
|
@@ -878,12 +878,12 @@ async function readProfile() {
|
|
|
878
878
|
}
|
|
879
879
|
}
|
|
880
880
|
async function writeProfile(profile) {
|
|
881
|
-
|
|
881
|
+
mkdirSync2(TERMINALHIRE_DIR, { recursive: true });
|
|
882
882
|
const key = await loadKey();
|
|
883
883
|
profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
884
884
|
profile.skillTags = deriveSkillTags(profile.tagWeights);
|
|
885
885
|
const blob = encrypt(JSON.stringify(profile), key);
|
|
886
|
-
|
|
886
|
+
writeFileSync2(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
887
887
|
}
|
|
888
888
|
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
889
889
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -944,13 +944,13 @@ async function removeSavedJob(id) {
|
|
|
944
944
|
return true;
|
|
945
945
|
}
|
|
946
946
|
async function deleteProfile() {
|
|
947
|
-
const { rmSync } = await import("fs");
|
|
947
|
+
const { rmSync: rmSync2 } = await import("fs");
|
|
948
948
|
try {
|
|
949
|
-
|
|
949
|
+
rmSync2(PROFILE_FILE);
|
|
950
950
|
} catch {
|
|
951
951
|
}
|
|
952
952
|
try {
|
|
953
|
-
|
|
953
|
+
rmSync2(KEY_FILE);
|
|
954
954
|
} catch {
|
|
955
955
|
}
|
|
956
956
|
}
|
|
@@ -972,9 +972,9 @@ var init_profile = __esm({
|
|
|
972
972
|
"src/profile.ts"() {
|
|
973
973
|
"use strict";
|
|
974
974
|
init_src();
|
|
975
|
-
TERMINALHIRE_DIR =
|
|
976
|
-
PROFILE_FILE =
|
|
977
|
-
KEY_FILE =
|
|
975
|
+
TERMINALHIRE_DIR = join3(homedir2(), ".terminalhire");
|
|
976
|
+
PROFILE_FILE = join3(TERMINALHIRE_DIR, "profile.enc");
|
|
977
|
+
KEY_FILE = join3(TERMINALHIRE_DIR, "key");
|
|
978
978
|
ALGO = "aes-256-gcm";
|
|
979
979
|
KEY_BYTES = 32;
|
|
980
980
|
IV_BYTES = 12;
|
|
@@ -1037,14 +1037,14 @@ var init_open_url = __esm({
|
|
|
1037
1037
|
|
|
1038
1038
|
// src/trajectory.ts
|
|
1039
1039
|
import {
|
|
1040
|
-
existsSync as
|
|
1041
|
-
mkdirSync as
|
|
1042
|
-
readFileSync as
|
|
1040
|
+
existsSync as existsSync3,
|
|
1041
|
+
mkdirSync as mkdirSync3,
|
|
1042
|
+
readFileSync as readFileSync4,
|
|
1043
1043
|
readdirSync,
|
|
1044
|
-
writeFileSync as
|
|
1044
|
+
writeFileSync as writeFileSync3
|
|
1045
1045
|
} from "fs";
|
|
1046
|
-
import { homedir as
|
|
1047
|
-
import { join as
|
|
1046
|
+
import { homedir as homedir3 } from "os";
|
|
1047
|
+
import { join as join4 } from "path";
|
|
1048
1048
|
|
|
1049
1049
|
// ../../packages/core/src/episodes/node-model.ts
|
|
1050
1050
|
function isRecord(value) {
|
|
@@ -1306,7 +1306,7 @@ function finalize(build) {
|
|
|
1306
1306
|
};
|
|
1307
1307
|
}
|
|
1308
1308
|
function reconstruct(files, opts = {}) {
|
|
1309
|
-
const
|
|
1309
|
+
const join5 = opts.joinSidechains !== false;
|
|
1310
1310
|
const mains = [];
|
|
1311
1311
|
const sidechains = [];
|
|
1312
1312
|
for (const file of files) {
|
|
@@ -1331,7 +1331,7 @@ function reconstruct(files, opts = {}) {
|
|
|
1331
1331
|
}
|
|
1332
1332
|
const orphanedSidechainPaths = [];
|
|
1333
1333
|
const joinedPaths = /* @__PURE__ */ new Set();
|
|
1334
|
-
if (
|
|
1334
|
+
if (join5) {
|
|
1335
1335
|
const sidechainsBySession = /* @__PURE__ */ new Map();
|
|
1336
1336
|
for (const sc of sidechains) {
|
|
1337
1337
|
const acc = sidechainsBySession.get(sc.sessionId) ?? [];
|
|
@@ -1776,6 +1776,40 @@ function deriveRecoveryDepth(episodes, nodesByUuid) {
|
|
|
1776
1776
|
return inwardMetric({ maxConsecutiveErrors, meanRecoveryDepth, recoveryChains, spansConsidered, spansSkipped });
|
|
1777
1777
|
}
|
|
1778
1778
|
|
|
1779
|
+
// src/web-session.ts
|
|
1780
|
+
import {
|
|
1781
|
+
chmodSync,
|
|
1782
|
+
existsSync,
|
|
1783
|
+
mkdirSync,
|
|
1784
|
+
readFileSync,
|
|
1785
|
+
rmSync,
|
|
1786
|
+
writeFileSync
|
|
1787
|
+
} from "fs";
|
|
1788
|
+
import { homedir } from "os";
|
|
1789
|
+
import { join } from "path";
|
|
1790
|
+
function terminalhireDir() {
|
|
1791
|
+
return join(homedir(), ".terminalhire");
|
|
1792
|
+
}
|
|
1793
|
+
function webSessionFilePath() {
|
|
1794
|
+
return join(terminalhireDir(), "web-session");
|
|
1795
|
+
}
|
|
1796
|
+
function readWebSessionFile() {
|
|
1797
|
+
try {
|
|
1798
|
+
const path = webSessionFilePath();
|
|
1799
|
+
if (!existsSync(path)) return null;
|
|
1800
|
+
const v = readFileSync(path, "utf8").trim();
|
|
1801
|
+
return v.length > 0 ? v : null;
|
|
1802
|
+
} catch {
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
function readWebSessionCookie() {
|
|
1807
|
+
const fromFile = readWebSessionFile();
|
|
1808
|
+
if (fromFile) return fromFile;
|
|
1809
|
+
const env = process.env["TERMINALHIRE_WEB_SESSION"];
|
|
1810
|
+
return typeof env === "string" && env.length > 0 ? env : null;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1779
1813
|
// src/trajectory.ts
|
|
1780
1814
|
function isRecord4(value) {
|
|
1781
1815
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1808,7 +1842,7 @@ function findJsonlFiles(dir) {
|
|
|
1808
1842
|
return out;
|
|
1809
1843
|
}
|
|
1810
1844
|
for (const entry of entries) {
|
|
1811
|
-
const full =
|
|
1845
|
+
const full = join4(dir, entry.name);
|
|
1812
1846
|
if (entry.isDirectory()) {
|
|
1813
1847
|
out.push(...findJsonlFiles(full));
|
|
1814
1848
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -1822,7 +1856,7 @@ function loadCorpus(paths) {
|
|
|
1822
1856
|
for (const path of paths) {
|
|
1823
1857
|
let text;
|
|
1824
1858
|
try {
|
|
1825
|
-
text =
|
|
1859
|
+
text = readFileSync4(path, "utf8");
|
|
1826
1860
|
} catch {
|
|
1827
1861
|
continue;
|
|
1828
1862
|
}
|
|
@@ -1932,12 +1966,12 @@ function renderMarkdown(view) {
|
|
|
1932
1966
|
return lines.join("\n");
|
|
1933
1967
|
}
|
|
1934
1968
|
function writeExportArtifacts(score, markdown) {
|
|
1935
|
-
const dir =
|
|
1936
|
-
|
|
1937
|
-
const jsonPath =
|
|
1938
|
-
const mdPath =
|
|
1939
|
-
|
|
1940
|
-
|
|
1969
|
+
const dir = join4(homedir3(), ".terminalhire");
|
|
1970
|
+
mkdirSync3(dir, { recursive: true });
|
|
1971
|
+
const jsonPath = join4(dir, "trajectory-export.json");
|
|
1972
|
+
const mdPath = join4(dir, "trajectory-export.md");
|
|
1973
|
+
writeFileSync3(jsonPath, JSON.stringify(score, null, 2) + "\n", "utf8");
|
|
1974
|
+
writeFileSync3(mdPath, markdown, "utf8");
|
|
1941
1975
|
return { jsonPath, mdPath };
|
|
1942
1976
|
}
|
|
1943
1977
|
function renderInward(allNodes, view, files) {
|
|
@@ -1956,8 +1990,8 @@ function renderInward(allNodes, view, files) {
|
|
|
1956
1990
|
console.log("");
|
|
1957
1991
|
}
|
|
1958
1992
|
function buildTrajectory() {
|
|
1959
|
-
const projectsDir =
|
|
1960
|
-
if (!
|
|
1993
|
+
const projectsDir = join4(homedir3(), ".claude", "projects");
|
|
1994
|
+
if (!existsSync3(projectsDir)) return null;
|
|
1961
1995
|
const paths = findJsonlFiles(projectsDir);
|
|
1962
1996
|
if (paths.length === 0) return null;
|
|
1963
1997
|
const files = loadCorpus(paths);
|
|
@@ -2048,10 +2082,9 @@ function defaultPushDeps() {
|
|
|
2048
2082
|
void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
|
|
2049
2083
|
});
|
|
2050
2084
|
},
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
},
|
|
2085
|
+
// Session source priority: persisted file (`terminalhire link`) FIRST, then the
|
|
2086
|
+
// legacy TERMINALHIRE_WEB_SESSION env, then none.
|
|
2087
|
+
sessionCookie: () => readWebSessionCookie(),
|
|
2055
2088
|
log: (msg) => console.log(msg),
|
|
2056
2089
|
errorLog: (msg) => console.error(msg),
|
|
2057
2090
|
exit: (code) => process.exit(code)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/web-session.ts
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync
|
|
9
|
+
} from "fs";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
function terminalhireDir() {
|
|
13
|
+
return join(homedir(), ".terminalhire");
|
|
14
|
+
}
|
|
15
|
+
function webSessionFilePath() {
|
|
16
|
+
return join(terminalhireDir(), "web-session");
|
|
17
|
+
}
|
|
18
|
+
function readWebSessionFile() {
|
|
19
|
+
try {
|
|
20
|
+
const path = webSessionFilePath();
|
|
21
|
+
if (!existsSync(path)) return null;
|
|
22
|
+
const v = readFileSync(path, "utf8").trim();
|
|
23
|
+
return v.length > 0 ? v : null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function readWebSessionCookie() {
|
|
29
|
+
const fromFile = readWebSessionFile();
|
|
30
|
+
if (fromFile) return fromFile;
|
|
31
|
+
const env = process.env["TERMINALHIRE_WEB_SESSION"];
|
|
32
|
+
return typeof env === "string" && env.length > 0 ? env : null;
|
|
33
|
+
}
|
|
34
|
+
function writeWebSessionFile(token) {
|
|
35
|
+
mkdirSync(terminalhireDir(), { recursive: true });
|
|
36
|
+
const path = webSessionFilePath();
|
|
37
|
+
writeFileSync(path, token, { mode: 384, encoding: "utf8" });
|
|
38
|
+
try {
|
|
39
|
+
chmodSync(path, 384);
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function clearWebSessionFile() {
|
|
44
|
+
try {
|
|
45
|
+
rmSync(webSessionFilePath());
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
clearWebSessionFile,
|
|
51
|
+
readWebSessionCookie,
|
|
52
|
+
readWebSessionFile,
|
|
53
|
+
webSessionFilePath,
|
|
54
|
+
writeWebSessionFile
|
|
55
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|