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/bin/jpi-intro.js
CHANGED
|
@@ -774,6 +774,45 @@ var init_src = __esm({
|
|
|
774
774
|
}
|
|
775
775
|
});
|
|
776
776
|
|
|
777
|
+
// src/web-session.ts
|
|
778
|
+
import {
|
|
779
|
+
chmodSync,
|
|
780
|
+
existsSync,
|
|
781
|
+
mkdirSync,
|
|
782
|
+
readFileSync as readFileSync2,
|
|
783
|
+
rmSync,
|
|
784
|
+
writeFileSync
|
|
785
|
+
} from "fs";
|
|
786
|
+
import { homedir } from "os";
|
|
787
|
+
import { join as join2 } from "path";
|
|
788
|
+
function terminalhireDir() {
|
|
789
|
+
return join2(homedir(), ".terminalhire");
|
|
790
|
+
}
|
|
791
|
+
function webSessionFilePath() {
|
|
792
|
+
return join2(terminalhireDir(), "web-session");
|
|
793
|
+
}
|
|
794
|
+
function readWebSessionFile() {
|
|
795
|
+
try {
|
|
796
|
+
const path = webSessionFilePath();
|
|
797
|
+
if (!existsSync(path)) return null;
|
|
798
|
+
const v = readFileSync2(path, "utf8").trim();
|
|
799
|
+
return v.length > 0 ? v : null;
|
|
800
|
+
} catch {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function readWebSessionCookie() {
|
|
805
|
+
const fromFile = readWebSessionFile();
|
|
806
|
+
if (fromFile) return fromFile;
|
|
807
|
+
const env = process.env["TERMINALHIRE_WEB_SESSION"];
|
|
808
|
+
return typeof env === "string" && env.length > 0 ? env : null;
|
|
809
|
+
}
|
|
810
|
+
var init_web_session = __esm({
|
|
811
|
+
"src/web-session.ts"() {
|
|
812
|
+
"use strict";
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
777
816
|
// src/profile.ts
|
|
778
817
|
var profile_exports = {};
|
|
779
818
|
__export(profile_exports, {
|
|
@@ -794,13 +833,13 @@ import {
|
|
|
794
833
|
randomBytes as randomBytes2
|
|
795
834
|
} from "crypto";
|
|
796
835
|
import {
|
|
797
|
-
readFileSync as
|
|
798
|
-
writeFileSync,
|
|
799
|
-
mkdirSync,
|
|
800
|
-
existsSync
|
|
836
|
+
readFileSync as readFileSync3,
|
|
837
|
+
writeFileSync as writeFileSync2,
|
|
838
|
+
mkdirSync as mkdirSync2,
|
|
839
|
+
existsSync as existsSync2
|
|
801
840
|
} from "fs";
|
|
802
|
-
import { join as
|
|
803
|
-
import { homedir } from "os";
|
|
841
|
+
import { join as join3 } from "path";
|
|
842
|
+
import { homedir as homedir2 } from "os";
|
|
804
843
|
async function loadKey() {
|
|
805
844
|
try {
|
|
806
845
|
const kt = await import("keytar");
|
|
@@ -813,12 +852,12 @@ async function loadKey() {
|
|
|
813
852
|
return key2;
|
|
814
853
|
} catch {
|
|
815
854
|
}
|
|
816
|
-
|
|
817
|
-
if (
|
|
818
|
-
return Buffer.from(
|
|
855
|
+
mkdirSync2(TERMINALHIRE_DIR, { recursive: true });
|
|
856
|
+
if (existsSync2(KEY_FILE)) {
|
|
857
|
+
return Buffer.from(readFileSync3(KEY_FILE, "utf8").trim(), "hex");
|
|
819
858
|
}
|
|
820
859
|
const key = randomBytes2(KEY_BYTES);
|
|
821
|
-
|
|
860
|
+
writeFileSync2(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
822
861
|
return key;
|
|
823
862
|
}
|
|
824
863
|
function encrypt(plaintext, key) {
|
|
@@ -876,10 +915,10 @@ function migrateTagWeights(profile) {
|
|
|
876
915
|
}
|
|
877
916
|
}
|
|
878
917
|
async function readProfile() {
|
|
879
|
-
if (!
|
|
918
|
+
if (!existsSync2(PROFILE_FILE)) return blankProfile();
|
|
880
919
|
try {
|
|
881
920
|
const key = await loadKey();
|
|
882
|
-
const raw =
|
|
921
|
+
const raw = readFileSync3(PROFILE_FILE, "utf8");
|
|
883
922
|
const blob = JSON.parse(raw);
|
|
884
923
|
const plaintext = decrypt(blob, key);
|
|
885
924
|
const parsed = JSON.parse(plaintext);
|
|
@@ -890,12 +929,12 @@ async function readProfile() {
|
|
|
890
929
|
}
|
|
891
930
|
}
|
|
892
931
|
async function writeProfile(profile) {
|
|
893
|
-
|
|
932
|
+
mkdirSync2(TERMINALHIRE_DIR, { recursive: true });
|
|
894
933
|
const key = await loadKey();
|
|
895
934
|
profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
896
935
|
profile.skillTags = deriveSkillTags(profile.tagWeights);
|
|
897
936
|
const blob = encrypt(JSON.stringify(profile), key);
|
|
898
|
-
|
|
937
|
+
writeFileSync2(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
899
938
|
}
|
|
900
939
|
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
901
940
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -956,13 +995,13 @@ async function removeSavedJob(id) {
|
|
|
956
995
|
return true;
|
|
957
996
|
}
|
|
958
997
|
async function deleteProfile() {
|
|
959
|
-
const { rmSync } = await import("fs");
|
|
998
|
+
const { rmSync: rmSync2 } = await import("fs");
|
|
960
999
|
try {
|
|
961
|
-
|
|
1000
|
+
rmSync2(PROFILE_FILE);
|
|
962
1001
|
} catch {
|
|
963
1002
|
}
|
|
964
1003
|
try {
|
|
965
|
-
|
|
1004
|
+
rmSync2(KEY_FILE);
|
|
966
1005
|
} catch {
|
|
967
1006
|
}
|
|
968
1007
|
}
|
|
@@ -984,9 +1023,9 @@ var init_profile = __esm({
|
|
|
984
1023
|
"src/profile.ts"() {
|
|
985
1024
|
"use strict";
|
|
986
1025
|
init_src();
|
|
987
|
-
TERMINALHIRE_DIR =
|
|
988
|
-
PROFILE_FILE =
|
|
989
|
-
KEY_FILE =
|
|
1026
|
+
TERMINALHIRE_DIR = join3(homedir2(), ".terminalhire");
|
|
1027
|
+
PROFILE_FILE = join3(TERMINALHIRE_DIR, "profile.enc");
|
|
1028
|
+
KEY_FILE = join3(TERMINALHIRE_DIR, "key");
|
|
990
1029
|
ALGO = "aes-256-gcm";
|
|
991
1030
|
KEY_BYTES = 32;
|
|
992
1031
|
IV_BYTES = 12;
|
|
@@ -1089,10 +1128,9 @@ function defaultIntroDeps() {
|
|
|
1089
1128
|
void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
|
|
1090
1129
|
});
|
|
1091
1130
|
},
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
},
|
|
1131
|
+
// Session source priority: persisted file (`terminalhire link`) FIRST, then the
|
|
1132
|
+
// legacy TERMINALHIRE_WEB_SESSION env, then none.
|
|
1133
|
+
sessionCookie: () => readWebSessionCookie(),
|
|
1096
1134
|
log: (msg) => console.log(msg),
|
|
1097
1135
|
errorLog: (msg) => console.error(msg),
|
|
1098
1136
|
exit: (code) => process.exit(code)
|
|
@@ -1164,10 +1202,7 @@ async function runIntroRequest(args, overrides) {
|
|
|
1164
1202
|
const cookie = deps.sessionCookie();
|
|
1165
1203
|
if (!cookie) {
|
|
1166
1204
|
deps.log("\n No linked web session found on this machine.");
|
|
1167
|
-
deps.log("
|
|
1168
|
-
deps.log(` \u2192 ${LINK_BASE}/dashboard
|
|
1169
|
-
`);
|
|
1170
|
-
deps.openBrowser(`${LINK_BASE}/dashboard`);
|
|
1205
|
+
deps.log(" Run `terminalhire link` to connect this terminal to your account, then re-run.\n");
|
|
1171
1206
|
deps.exit(0);
|
|
1172
1207
|
return;
|
|
1173
1208
|
}
|
|
@@ -1187,9 +1222,8 @@ async function runIntroRequest(args, overrides) {
|
|
|
1187
1222
|
return;
|
|
1188
1223
|
}
|
|
1189
1224
|
if (res.status === 401) {
|
|
1190
|
-
deps.log("\n Your web session expired
|
|
1191
|
-
deps.log(
|
|
1192
|
-
`);
|
|
1225
|
+
deps.log("\n Your linked web session expired.");
|
|
1226
|
+
deps.log(" Run `terminalhire link` to reconnect this terminal, then re-run.\n");
|
|
1193
1227
|
deps.exit(1);
|
|
1194
1228
|
return;
|
|
1195
1229
|
}
|
|
@@ -1220,10 +1254,7 @@ async function runIntroDecision(args, overrides) {
|
|
|
1220
1254
|
const cookie = deps.sessionCookie();
|
|
1221
1255
|
if (!cookie) {
|
|
1222
1256
|
deps.log("\n No linked web session found on this machine.");
|
|
1223
|
-
deps.log("
|
|
1224
|
-
deps.log(` \u2192 ${LINK_BASE}/dashboard
|
|
1225
|
-
`);
|
|
1226
|
-
deps.openBrowser(`${LINK_BASE}/dashboard`);
|
|
1257
|
+
deps.log(" Run `terminalhire link` to connect this terminal to your account, then re-run.\n");
|
|
1227
1258
|
deps.exit(0);
|
|
1228
1259
|
return;
|
|
1229
1260
|
}
|
|
@@ -1266,9 +1297,8 @@ async function runIntroDecision(args, overrides) {
|
|
|
1266
1297
|
return;
|
|
1267
1298
|
}
|
|
1268
1299
|
if (res.status === 401) {
|
|
1269
|
-
deps.log("\n Your web session expired
|
|
1270
|
-
deps.log(
|
|
1271
|
-
`);
|
|
1300
|
+
deps.log("\n Your linked web session expired.");
|
|
1301
|
+
deps.log(" Run `terminalhire link` to reconnect this terminal, then re-run.\n");
|
|
1272
1302
|
deps.exit(1);
|
|
1273
1303
|
return;
|
|
1274
1304
|
}
|
|
@@ -1307,10 +1337,7 @@ async function runIntroList(overrides) {
|
|
|
1307
1337
|
const cookie = deps.sessionCookie();
|
|
1308
1338
|
if (!cookie) {
|
|
1309
1339
|
deps.log("\n No linked web session found on this machine.");
|
|
1310
|
-
deps.log("
|
|
1311
|
-
deps.log(` \u2192 ${LINK_BASE}/dashboard
|
|
1312
|
-
`);
|
|
1313
|
-
deps.openBrowser(`${LINK_BASE}/dashboard`);
|
|
1340
|
+
deps.log(" Run `terminalhire link` to connect this terminal to your account, then re-run.\n");
|
|
1314
1341
|
deps.exit(0);
|
|
1315
1342
|
return;
|
|
1316
1343
|
}
|
|
@@ -1329,9 +1356,8 @@ async function runIntroList(overrides) {
|
|
|
1329
1356
|
return;
|
|
1330
1357
|
}
|
|
1331
1358
|
if (res.status === 401) {
|
|
1332
|
-
deps.log("\n Your web session expired
|
|
1333
|
-
deps.log(
|
|
1334
|
-
`);
|
|
1359
|
+
deps.log("\n Your linked web session expired.");
|
|
1360
|
+
deps.log(" Run `terminalhire link` to reconnect this terminal, then re-run.\n");
|
|
1335
1361
|
deps.exit(1);
|
|
1336
1362
|
return;
|
|
1337
1363
|
}
|
|
@@ -1369,6 +1395,7 @@ var init_intro2 = __esm({
|
|
|
1369
1395
|
"src/intro.ts"() {
|
|
1370
1396
|
"use strict";
|
|
1371
1397
|
init_src();
|
|
1398
|
+
init_web_session();
|
|
1372
1399
|
LINK_BASE = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
|
|
1373
1400
|
GH_SESSION_COOKIE = "__jpi_gh_session";
|
|
1374
1401
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/web-session.ts
|
|
13
|
+
import {
|
|
14
|
+
chmodSync,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
writeFileSync
|
|
20
|
+
} from "fs";
|
|
21
|
+
import { homedir } from "os";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
function terminalhireDir() {
|
|
24
|
+
return join(homedir(), ".terminalhire");
|
|
25
|
+
}
|
|
26
|
+
function webSessionFilePath() {
|
|
27
|
+
return join(terminalhireDir(), "web-session");
|
|
28
|
+
}
|
|
29
|
+
function readWebSessionFile() {
|
|
30
|
+
try {
|
|
31
|
+
const path = webSessionFilePath();
|
|
32
|
+
if (!existsSync(path)) return null;
|
|
33
|
+
const v = readFileSync(path, "utf8").trim();
|
|
34
|
+
return v.length > 0 ? v : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeWebSessionFile(token) {
|
|
40
|
+
mkdirSync(terminalhireDir(), { recursive: true });
|
|
41
|
+
const path = webSessionFilePath();
|
|
42
|
+
writeFileSync(path, token, { mode: 384, encoding: "utf8" });
|
|
43
|
+
try {
|
|
44
|
+
chmodSync(path, 384);
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function clearWebSessionFile() {
|
|
49
|
+
try {
|
|
50
|
+
rmSync(webSessionFilePath());
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
var init_web_session = __esm({
|
|
55
|
+
"src/web-session.ts"() {
|
|
56
|
+
"use strict";
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// src/open-url.js
|
|
61
|
+
var open_url_exports = {};
|
|
62
|
+
__export(open_url_exports, {
|
|
63
|
+
openInBrowser: () => openInBrowser
|
|
64
|
+
});
|
|
65
|
+
import { spawn } from "child_process";
|
|
66
|
+
function openInBrowser(url) {
|
|
67
|
+
let cmd;
|
|
68
|
+
let args;
|
|
69
|
+
if (process.platform === "darwin") {
|
|
70
|
+
cmd = "open";
|
|
71
|
+
args = [url];
|
|
72
|
+
} else if (process.platform === "win32") {
|
|
73
|
+
cmd = "cmd";
|
|
74
|
+
args = ["/c", "start", "", url];
|
|
75
|
+
} else {
|
|
76
|
+
cmd = "xdg-open";
|
|
77
|
+
args = [url];
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
81
|
+
child.on("error", () => {
|
|
82
|
+
});
|
|
83
|
+
child.unref();
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
var init_open_url = __esm({
|
|
88
|
+
"src/open-url.js"() {
|
|
89
|
+
"use strict";
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// src/link.ts
|
|
94
|
+
var link_exports = {};
|
|
95
|
+
__export(link_exports, {
|
|
96
|
+
resolveLoopbackRequest: () => resolveLoopbackRequest,
|
|
97
|
+
runLink: () => runLink,
|
|
98
|
+
runLinkLogout: () => runLinkLogout
|
|
99
|
+
});
|
|
100
|
+
import { createServer } from "http";
|
|
101
|
+
import { randomBytes } from "crypto";
|
|
102
|
+
function resolveLoopbackRequest(rawUrl, expectedNonce) {
|
|
103
|
+
let u;
|
|
104
|
+
try {
|
|
105
|
+
u = new URL(rawUrl, "http://127.0.0.1");
|
|
106
|
+
} catch {
|
|
107
|
+
return { ok: false, reason: "bad_url" };
|
|
108
|
+
}
|
|
109
|
+
const nonce = u.searchParams.get("nonce");
|
|
110
|
+
if (!nonce || nonce !== expectedNonce) return { ok: false, reason: "nonce_mismatch" };
|
|
111
|
+
const token = u.searchParams.get("token");
|
|
112
|
+
if (!token) return { ok: false, reason: "missing_token" };
|
|
113
|
+
return { ok: true, token };
|
|
114
|
+
}
|
|
115
|
+
function defaultStartLoopback(expectedNonce, timeoutMs) {
|
|
116
|
+
return new Promise((resolveHandle) => {
|
|
117
|
+
let settle;
|
|
118
|
+
const result = new Promise((res) => {
|
|
119
|
+
settle = res;
|
|
120
|
+
});
|
|
121
|
+
let done = false;
|
|
122
|
+
const finish = (r) => {
|
|
123
|
+
if (done) return;
|
|
124
|
+
done = true;
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
settle(r);
|
|
127
|
+
setImmediate(() => {
|
|
128
|
+
try {
|
|
129
|
+
server.close();
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const server = createServer((req, res) => {
|
|
135
|
+
const outcome = resolveLoopbackRequest(req.url ?? "", expectedNonce);
|
|
136
|
+
res.writeHead(outcome.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
|
|
137
|
+
res.end(outcome.ok ? LINKED_HTML : FAILED_HTML);
|
|
138
|
+
finish(outcome);
|
|
139
|
+
});
|
|
140
|
+
const timer = setTimeout(() => finish({ ok: false, reason: "timeout" }), timeoutMs);
|
|
141
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
142
|
+
server.on("error", () => finish({ ok: false, reason: "listen_error" }));
|
|
143
|
+
server.listen(0, "127.0.0.1", () => {
|
|
144
|
+
const addr = server.address();
|
|
145
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
146
|
+
resolveHandle({
|
|
147
|
+
port,
|
|
148
|
+
result,
|
|
149
|
+
close: () => {
|
|
150
|
+
try {
|
|
151
|
+
server.close();
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function defaultLinkDeps() {
|
|
160
|
+
return {
|
|
161
|
+
startLoopback: defaultStartLoopback,
|
|
162
|
+
openBrowser: (url) => {
|
|
163
|
+
void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
generateNonce: () => randomBytes(16).toString("hex"),
|
|
167
|
+
persistToken: (token) => writeWebSessionFile(token),
|
|
168
|
+
log: (msg) => console.log(msg),
|
|
169
|
+
errorLog: (msg) => console.error(msg),
|
|
170
|
+
exit: (code) => process.exit(code)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function runLink(overrides) {
|
|
174
|
+
const deps = { ...defaultLinkDeps(), ...overrides };
|
|
175
|
+
const nonce = deps.generateNonce();
|
|
176
|
+
const handle = await deps.startLoopback(nonce, LINK_TIMEOUT_MS);
|
|
177
|
+
const url = `${LINK_BASE}/api/auth/link?port=${handle.port}&nonce=${encodeURIComponent(nonce)}`;
|
|
178
|
+
deps.log("");
|
|
179
|
+
deps.log(" terminalhire \u2014 link this terminal to your account");
|
|
180
|
+
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");
|
|
181
|
+
deps.log(" Opening your browser to approve. If it does not open, paste this URL:");
|
|
182
|
+
deps.log(` \u2192 ${url}`);
|
|
183
|
+
deps.log(" Waiting for approval (this tab closes itself once you approve)\u2026");
|
|
184
|
+
deps.openBrowser(url);
|
|
185
|
+
let outcome;
|
|
186
|
+
try {
|
|
187
|
+
outcome = await handle.result;
|
|
188
|
+
} finally {
|
|
189
|
+
handle.close();
|
|
190
|
+
}
|
|
191
|
+
if (!outcome.ok || !outcome.token) {
|
|
192
|
+
if (outcome.reason === "timeout") {
|
|
193
|
+
deps.errorLog("\n Link timed out \u2014 run `terminalhire link` again.\n");
|
|
194
|
+
} else if (outcome.reason === "nonce_mismatch") {
|
|
195
|
+
deps.errorLog("\n Link rejected (nonce did not match) \u2014 run `terminalhire link` again.\n");
|
|
196
|
+
} else {
|
|
197
|
+
deps.errorLog("\n Link failed \u2014 run `terminalhire link` again.\n");
|
|
198
|
+
}
|
|
199
|
+
deps.exit(1);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
deps.persistToken(outcome.token);
|
|
203
|
+
deps.log("\n This terminal is now linked to your terminalhire account.");
|
|
204
|
+
deps.log(" Try `terminalhire intro <login>`, `terminalhire chat`, or `terminalhire trajectory --push`.");
|
|
205
|
+
deps.log(" Unlink any time with `terminalhire link --logout`.\n");
|
|
206
|
+
deps.exit(0);
|
|
207
|
+
}
|
|
208
|
+
function defaultLinkLogoutDeps() {
|
|
209
|
+
return {
|
|
210
|
+
fetchImpl: (...args) => globalThis.fetch(...args),
|
|
211
|
+
readSessionFile: () => readWebSessionFile(),
|
|
212
|
+
clearSessionFile: () => clearWebSessionFile(),
|
|
213
|
+
log: (msg) => console.log(msg),
|
|
214
|
+
errorLog: (msg) => console.error(msg),
|
|
215
|
+
exit: (code) => process.exit(code)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async function runLinkLogout(overrides) {
|
|
219
|
+
const deps = { ...defaultLinkLogoutDeps(), ...overrides };
|
|
220
|
+
const token = deps.readSessionFile();
|
|
221
|
+
if (!token) {
|
|
222
|
+
deps.log("\n No linked web session on this machine \u2014 nothing to unlink.\n");
|
|
223
|
+
deps.exit(0);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let revoked = false;
|
|
227
|
+
try {
|
|
228
|
+
const res = await deps.fetchImpl(`${LINK_BASE}/api/auth/session`, {
|
|
229
|
+
method: "DELETE",
|
|
230
|
+
headers: { Cookie: `${GH_SESSION_COOKIE}=${token}` },
|
|
231
|
+
signal: AbortSignal.timeout(1e4)
|
|
232
|
+
});
|
|
233
|
+
revoked = res.ok;
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
deps.clearSessionFile();
|
|
237
|
+
if (revoked) {
|
|
238
|
+
deps.log("\n Unlinked \u2014 the session was revoked server-side and removed from this machine.\n");
|
|
239
|
+
} else {
|
|
240
|
+
deps.log("\n Removed the local session from this machine.");
|
|
241
|
+
deps.log(" (Could not reach the server to revoke it \u2014 it expires on its own.)\n");
|
|
242
|
+
}
|
|
243
|
+
deps.exit(0);
|
|
244
|
+
}
|
|
245
|
+
var LINK_BASE, GH_SESSION_COOKIE, LINK_TIMEOUT_MS, LINKED_HTML, FAILED_HTML;
|
|
246
|
+
var init_link = __esm({
|
|
247
|
+
"src/link.ts"() {
|
|
248
|
+
"use strict";
|
|
249
|
+
init_web_session();
|
|
250
|
+
LINK_BASE = "https://www.terminalhire.com";
|
|
251
|
+
GH_SESSION_COOKIE = "__jpi_gh_session";
|
|
252
|
+
LINK_TIMEOUT_MS = 12e4;
|
|
253
|
+
LINKED_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>terminalhire</title></head>
|
|
254
|
+
<body style="font-family:system-ui;padding:2rem;background:#0b0d10;color:#e6e6e6">
|
|
255
|
+
<script>history.replaceState({},'','/');</script>
|
|
256
|
+
<p>CLI linked \u2014 you can close this tab.</p>
|
|
257
|
+
<script>setTimeout(function(){window.close();},400);</script>
|
|
258
|
+
</body></html>`;
|
|
259
|
+
FAILED_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>terminalhire</title></head>
|
|
260
|
+
<body style="font-family:system-ui;padding:2rem;background:#0b0d10;color:#e6e6e6">
|
|
261
|
+
<script>history.replaceState({},'','/');</script>
|
|
262
|
+
<p>Link failed \u2014 return to your terminal and run <code>terminalhire link</code> again.</p>
|
|
263
|
+
</body></html>`;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// bin/jpi-link.js
|
|
268
|
+
async function run() {
|
|
269
|
+
try {
|
|
270
|
+
const args = process.argv.slice(2);
|
|
271
|
+
if (args.includes("--logout")) {
|
|
272
|
+
const { runLinkLogout: runLinkLogout2 } = await Promise.resolve().then(() => (init_link(), link_exports));
|
|
273
|
+
await runLinkLogout2();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const { runLink: runLink2 } = await Promise.resolve().then(() => (init_link(), link_exports));
|
|
277
|
+
await runLink2();
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.error("terminalhire link error:", err?.message ?? err);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
export {
|
|
284
|
+
run
|
|
285
|
+
};
|