terminalhire 0.42.2 → 0.42.3
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-dispatch.js +92 -29
- package/dist/bin/jpi-protocol.js +114 -30
- package/dist/bin/jpi-update.js +114 -30
- package/dist/src/protocol.js +113 -29
- package/package.json +2 -2
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -294,10 +294,12 @@ __export(protocol_exports, {
|
|
|
294
294
|
handleUrl: () => handleUrl,
|
|
295
295
|
healStaleHandler: () => healStaleHandler,
|
|
296
296
|
parseClaimUrl: () => parseClaimUrl,
|
|
297
|
+
preferredTerminalApp: () => preferredTerminalApp,
|
|
297
298
|
printClaimCommand: () => printClaimCommand,
|
|
298
299
|
registerScheme: () => registerScheme,
|
|
299
300
|
schemeStatus: () => schemeStatus,
|
|
300
|
-
unregisterScheme: () => unregisterScheme
|
|
301
|
+
unregisterScheme: () => unregisterScheme,
|
|
302
|
+
writeClaimLauncher: () => writeClaimLauncher
|
|
301
303
|
});
|
|
302
304
|
import { spawn, spawnSync } from "child_process";
|
|
303
305
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
@@ -329,8 +331,11 @@ function defaultProtocolDeps() {
|
|
|
329
331
|
ensureStateDir: (path6) => {
|
|
330
332
|
ensureStateDir(path6);
|
|
331
333
|
},
|
|
332
|
-
|
|
333
|
-
|
|
334
|
+
ensureStateDirForSecret: (path6) => {
|
|
335
|
+
ensureStateDirForSecret(path6);
|
|
336
|
+
},
|
|
337
|
+
writeFileSync: (path6, contents, mode) => {
|
|
338
|
+
writeFileSync3(path6, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode });
|
|
334
339
|
},
|
|
335
340
|
readFileSync: (path6) => readFileSync3(path6, "utf8"),
|
|
336
341
|
rmSync: (path6) => {
|
|
@@ -370,30 +375,52 @@ function appleScriptStringLiteral(s) {
|
|
|
370
375
|
function shellQuoteSingle(s) {
|
|
371
376
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
372
377
|
}
|
|
373
|
-
function
|
|
378
|
+
function preferredTerminalApp(env) {
|
|
379
|
+
const raw = typeof env.TERM_PROGRAM === "string" ? env.TERM_PROGRAM.trim() : "";
|
|
380
|
+
if (!raw) return null;
|
|
381
|
+
const known = {
|
|
382
|
+
apple_terminal: "Terminal",
|
|
383
|
+
"iterm.app": "iTerm"
|
|
384
|
+
};
|
|
385
|
+
return known[raw.toLowerCase()] ?? null;
|
|
386
|
+
}
|
|
387
|
+
function buildAppleScriptHandler(execPath, dispatchPath, terminalApp) {
|
|
374
388
|
const execLit = appleScriptStringLiteral(execPath);
|
|
375
389
|
const dispatchLit = appleScriptStringLiteral(dispatchPath);
|
|
390
|
+
const fallback = (indent) => [
|
|
391
|
+
`${indent}display notification "Couldn't open your terminal. Run: " & launcher with title "Terminalhire"`,
|
|
392
|
+
`${indent}try`,
|
|
393
|
+
`${indent} do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
394
|
+
`${indent}end try`
|
|
395
|
+
];
|
|
396
|
+
const launch = terminalApp ? [
|
|
397
|
+
" try",
|
|
398
|
+
` do shell script "open -a " & quoted form of ${appleScriptStringLiteral(terminalApp)} & " " & quoted form of launcher`,
|
|
399
|
+
" on error",
|
|
400
|
+
" try",
|
|
401
|
+
' do shell script "open " & quoted form of launcher',
|
|
402
|
+
" on error",
|
|
403
|
+
...fallback(" "),
|
|
404
|
+
" end try",
|
|
405
|
+
" end try"
|
|
406
|
+
] : [
|
|
407
|
+
" try",
|
|
408
|
+
' do shell script "open " & quoted form of launcher',
|
|
409
|
+
" on error",
|
|
410
|
+
...fallback(" "),
|
|
411
|
+
" end try"
|
|
412
|
+
];
|
|
376
413
|
return [
|
|
377
414
|
"on open location theURL",
|
|
378
|
-
' set
|
|
415
|
+
' set launcher to ""',
|
|
379
416
|
" try",
|
|
380
|
-
` set
|
|
417
|
+
` set launcher to do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " write-claim-launcher " & quoted form of theURL`,
|
|
381
418
|
" end try",
|
|
382
|
-
' if
|
|
419
|
+
' if launcher is "" then',
|
|
383
420
|
` display notification "That isn't a valid Terminalhire claim link." with title "Terminalhire"`,
|
|
384
421
|
" return",
|
|
385
422
|
" end if",
|
|
386
|
-
|
|
387
|
-
' tell application "Terminal"',
|
|
388
|
-
" activate",
|
|
389
|
-
" do script claimCmd",
|
|
390
|
-
" end tell",
|
|
391
|
-
" on error",
|
|
392
|
-
` display notification "Couldn't open Terminal automatically. Run: " & claimCmd with title "Terminalhire"`,
|
|
393
|
-
" try",
|
|
394
|
-
` do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
395
|
-
" end try",
|
|
396
|
-
" end try",
|
|
423
|
+
...launch,
|
|
397
424
|
"end open location",
|
|
398
425
|
""
|
|
399
426
|
].join("\n");
|
|
@@ -404,7 +431,12 @@ function buildPreviewShellCommand(token, deps) {
|
|
|
404
431
|
shellQuoteSingle(deps.dispatchPath),
|
|
405
432
|
"claim",
|
|
406
433
|
"preview",
|
|
407
|
-
|
|
434
|
+
// Quoted like the paths beside it, though parseClaimUrl's `[A-Za-z0-9_-]{8}` already
|
|
435
|
+
// forbids every shell metacharacter. Leaving the ONE value that came from a URL bare
|
|
436
|
+
// while quoting the two that never did is backwards: it makes the safety depend on a
|
|
437
|
+
// regex two files away rather than on this line, and a future loosening of that regex
|
|
438
|
+
// would open an injection here silently.
|
|
439
|
+
shellQuoteSingle(token)
|
|
408
440
|
].join(" ");
|
|
409
441
|
}
|
|
410
442
|
function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
@@ -416,6 +448,28 @@ function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
|
416
448
|
deps.log(buildPreviewShellCommand(parsed.token, deps));
|
|
417
449
|
deps.exit(0);
|
|
418
450
|
}
|
|
451
|
+
function launcherPath(token, deps) {
|
|
452
|
+
return join3(stateDir(deps), `claim-${token}.command`);
|
|
453
|
+
}
|
|
454
|
+
function buildLauncherScript(token, deps) {
|
|
455
|
+
return ["#!/bin/sh", `exec ${buildPreviewShellCommand(token, deps)}`, ""].join("\n");
|
|
456
|
+
}
|
|
457
|
+
function writeLauncherFile(token, deps) {
|
|
458
|
+
deps.ensureStateDirForSecret(stateDir(deps));
|
|
459
|
+
const path6 = launcherPath(token, deps);
|
|
460
|
+
deps.rmSync(path6);
|
|
461
|
+
deps.writeFileSync(path6, buildLauncherScript(token, deps), 448);
|
|
462
|
+
return path6;
|
|
463
|
+
}
|
|
464
|
+
function writeClaimLauncher(raw, deps = defaultProtocolDeps()) {
|
|
465
|
+
const parsed = parseClaimUrl(raw);
|
|
466
|
+
if (!parsed) {
|
|
467
|
+
deps.exit(1);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
deps.log(writeLauncherFile(parsed.token, deps));
|
|
471
|
+
deps.exit(0);
|
|
472
|
+
}
|
|
419
473
|
function darwinAppPaths(deps) {
|
|
420
474
|
const appDir = join3(deps.homedir(), "Applications");
|
|
421
475
|
const appPath = join3(appDir, "Terminalhire Handler.app");
|
|
@@ -428,7 +482,16 @@ function darwinRegister(deps) {
|
|
|
428
482
|
const { appDir, appPath, plistPath } = darwinAppPaths(deps);
|
|
429
483
|
deps.mkdirSync(appDir);
|
|
430
484
|
const scriptPath = join3(dir, "handler.applescript");
|
|
431
|
-
deps.writeFileSync(
|
|
485
|
+
deps.writeFileSync(
|
|
486
|
+
scriptPath,
|
|
487
|
+
buildAppleScriptHandler(
|
|
488
|
+
deps.execPath,
|
|
489
|
+
deps.dispatchPath,
|
|
490
|
+
// Resolved at REGISTER time, from the shell the user ran `protocol register` in.
|
|
491
|
+
// `open location` runs with no TERM_PROGRAM at all, so this cannot be deferred.
|
|
492
|
+
preferredTerminalApp(deps.env)
|
|
493
|
+
)
|
|
494
|
+
);
|
|
432
495
|
deps.rmSync(appPath);
|
|
433
496
|
const compile = deps.spawnSync("osacompile", ["-o", appPath, scriptPath]);
|
|
434
497
|
if (compile.status !== 0) {
|
|
@@ -496,14 +559,10 @@ function darwinStatus(deps) {
|
|
|
496
559
|
return { registered: exists && registered, appExists: exists };
|
|
497
560
|
}
|
|
498
561
|
function darwinOpenPreviewTerminal(token, deps) {
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
"-e",
|
|
504
|
-
doScript
|
|
505
|
-
]);
|
|
506
|
-
return res.status === 0;
|
|
562
|
+
const path6 = writeLauncherFile(token, deps);
|
|
563
|
+
const app = preferredTerminalApp(deps.env);
|
|
564
|
+
if (app && deps.spawnSync("open", ["-a", app, path6]).status === 0) return true;
|
|
565
|
+
return deps.spawnSync("open", [path6]).status === 0;
|
|
507
566
|
}
|
|
508
567
|
function win32Register(deps) {
|
|
509
568
|
for (const scheme of WIN32_SCHEMES) {
|
|
@@ -792,7 +851,7 @@ var init_protocol = __esm({
|
|
|
792
851
|
["konsole", ["-e"]],
|
|
793
852
|
["xterm", ["-e"]]
|
|
794
853
|
];
|
|
795
|
-
HANDLER_TEMPLATE_VERSION =
|
|
854
|
+
HANDLER_TEMPLATE_VERSION = 3;
|
|
796
855
|
PENDING_CLAIMS_CAP = 20;
|
|
797
856
|
PENDING_TOKEN_RE = /^[A-Za-z0-9_-]{8}$/;
|
|
798
857
|
}
|
|
@@ -67272,6 +67331,10 @@ if (firstArg === "print-claim-command") {
|
|
|
67272
67331
|
const { printClaimCommand: printClaimCommand2 } = await Promise.resolve().then(() => (init_protocol(), protocol_exports));
|
|
67273
67332
|
printClaimCommand2(process.argv[3]);
|
|
67274
67333
|
}
|
|
67334
|
+
if (firstArg === "write-claim-launcher") {
|
|
67335
|
+
const { writeClaimLauncher: writeClaimLauncher2 } = await Promise.resolve().then(() => (init_protocol(), protocol_exports));
|
|
67336
|
+
writeClaimLauncher2(process.argv[3]);
|
|
67337
|
+
}
|
|
67275
67338
|
if (firstArg === "profile") {
|
|
67276
67339
|
const mod2 = await Promise.resolve().then(() => (init_jpi_profile(), jpi_profile_exports));
|
|
67277
67340
|
await mod2.run();
|
package/dist/bin/jpi-protocol.js
CHANGED
|
@@ -51,7 +51,31 @@ function ensureStateDir(dir) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
function applyStateDirSecretPolicy(dir, status) {
|
|
55
|
+
if (status === STATE_DIR_SYMLINK) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`terminalhire: refusing to write key material into ${dir} \u2014 it is a symlink, not a directory.
|
|
58
|
+
A write through it would FOLLOW THE LINK and place key/token material wherever the symlink points, outside our control and outside the "owner-only" (0700) guarantee this directory is supposed to carry.
|
|
59
|
+
Fix: remove the symlink so terminalhire can recreate it as a real directory \u2014
|
|
60
|
+
rm ${dir}
|
|
61
|
+
then re-run the command. If the symlink is intentional, point TERMINALHIRE_DIR at a real directory instead of routing it through this one.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (status === STATE_DIR_UNVERIFIED && !warnedUnverifiedSecretWriteThisProcess) {
|
|
65
|
+
warnedUnverifiedSecretWriteThisProcess = true;
|
|
66
|
+
try {
|
|
67
|
+
process.stderr.write(
|
|
68
|
+
`terminalhire: could not verify ${dir}'s permissions (expected on Windows \u2014 POSIX mode bits do not apply there) \u2014 proceeding, but the "owner-only" guarantee on key/token storage is NOT enforced on this platform.
|
|
69
|
+
`
|
|
70
|
+
);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function ensureStateDirForSecret(dir) {
|
|
76
|
+
applyStateDirSecretPolicy(dir, ensureStateDir(dir));
|
|
77
|
+
}
|
|
78
|
+
var STATE_DIR_MODE, STATE_DIR_OK, STATE_DIR_SYMLINK, STATE_DIR_UNVERIFIED, warnedDirs, warnedUnverifiedSecretWriteThisProcess;
|
|
55
79
|
var init_state_dir = __esm({
|
|
56
80
|
"src/state-dir.ts"() {
|
|
57
81
|
"use strict";
|
|
@@ -60,6 +84,7 @@ var init_state_dir = __esm({
|
|
|
60
84
|
STATE_DIR_SYMLINK = "symlink";
|
|
61
85
|
STATE_DIR_UNVERIFIED = "unverified";
|
|
62
86
|
warnedDirs = /* @__PURE__ */ new Set();
|
|
87
|
+
warnedUnverifiedSecretWriteThisProcess = false;
|
|
63
88
|
}
|
|
64
89
|
});
|
|
65
90
|
|
|
@@ -72,10 +97,12 @@ __export(protocol_exports, {
|
|
|
72
97
|
handleUrl: () => handleUrl,
|
|
73
98
|
healStaleHandler: () => healStaleHandler,
|
|
74
99
|
parseClaimUrl: () => parseClaimUrl,
|
|
100
|
+
preferredTerminalApp: () => preferredTerminalApp,
|
|
75
101
|
printClaimCommand: () => printClaimCommand,
|
|
76
102
|
registerScheme: () => registerScheme,
|
|
77
103
|
schemeStatus: () => schemeStatus,
|
|
78
|
-
unregisterScheme: () => unregisterScheme
|
|
104
|
+
unregisterScheme: () => unregisterScheme,
|
|
105
|
+
writeClaimLauncher: () => writeClaimLauncher
|
|
79
106
|
});
|
|
80
107
|
import { spawn, spawnSync } from "child_process";
|
|
81
108
|
import { existsSync, mkdirSync as mkdirSync2, readFileSync, rmSync, writeFileSync, renameSync } from "fs";
|
|
@@ -107,8 +134,11 @@ function defaultProtocolDeps() {
|
|
|
107
134
|
ensureStateDir: (path) => {
|
|
108
135
|
ensureStateDir(path);
|
|
109
136
|
},
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
ensureStateDirForSecret: (path) => {
|
|
138
|
+
ensureStateDirForSecret(path);
|
|
139
|
+
},
|
|
140
|
+
writeFileSync: (path, contents, mode) => {
|
|
141
|
+
writeFileSync(path, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode });
|
|
112
142
|
},
|
|
113
143
|
readFileSync: (path) => readFileSync(path, "utf8"),
|
|
114
144
|
rmSync: (path) => {
|
|
@@ -148,30 +178,52 @@ function appleScriptStringLiteral(s) {
|
|
|
148
178
|
function shellQuoteSingle(s) {
|
|
149
179
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
150
180
|
}
|
|
151
|
-
function
|
|
181
|
+
function preferredTerminalApp(env) {
|
|
182
|
+
const raw = typeof env.TERM_PROGRAM === "string" ? env.TERM_PROGRAM.trim() : "";
|
|
183
|
+
if (!raw) return null;
|
|
184
|
+
const known = {
|
|
185
|
+
apple_terminal: "Terminal",
|
|
186
|
+
"iterm.app": "iTerm"
|
|
187
|
+
};
|
|
188
|
+
return known[raw.toLowerCase()] ?? null;
|
|
189
|
+
}
|
|
190
|
+
function buildAppleScriptHandler(execPath, dispatchPath, terminalApp) {
|
|
152
191
|
const execLit = appleScriptStringLiteral(execPath);
|
|
153
192
|
const dispatchLit = appleScriptStringLiteral(dispatchPath);
|
|
193
|
+
const fallback = (indent) => [
|
|
194
|
+
`${indent}display notification "Couldn't open your terminal. Run: " & launcher with title "Terminalhire"`,
|
|
195
|
+
`${indent}try`,
|
|
196
|
+
`${indent} do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
197
|
+
`${indent}end try`
|
|
198
|
+
];
|
|
199
|
+
const launch = terminalApp ? [
|
|
200
|
+
" try",
|
|
201
|
+
` do shell script "open -a " & quoted form of ${appleScriptStringLiteral(terminalApp)} & " " & quoted form of launcher`,
|
|
202
|
+
" on error",
|
|
203
|
+
" try",
|
|
204
|
+
' do shell script "open " & quoted form of launcher',
|
|
205
|
+
" on error",
|
|
206
|
+
...fallback(" "),
|
|
207
|
+
" end try",
|
|
208
|
+
" end try"
|
|
209
|
+
] : [
|
|
210
|
+
" try",
|
|
211
|
+
' do shell script "open " & quoted form of launcher',
|
|
212
|
+
" on error",
|
|
213
|
+
...fallback(" "),
|
|
214
|
+
" end try"
|
|
215
|
+
];
|
|
154
216
|
return [
|
|
155
217
|
"on open location theURL",
|
|
156
|
-
' set
|
|
218
|
+
' set launcher to ""',
|
|
157
219
|
" try",
|
|
158
|
-
` set
|
|
220
|
+
` set launcher to do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " write-claim-launcher " & quoted form of theURL`,
|
|
159
221
|
" end try",
|
|
160
|
-
' if
|
|
222
|
+
' if launcher is "" then',
|
|
161
223
|
` display notification "That isn't a valid Terminalhire claim link." with title "Terminalhire"`,
|
|
162
224
|
" return",
|
|
163
225
|
" end if",
|
|
164
|
-
|
|
165
|
-
' tell application "Terminal"',
|
|
166
|
-
" activate",
|
|
167
|
-
" do script claimCmd",
|
|
168
|
-
" end tell",
|
|
169
|
-
" on error",
|
|
170
|
-
` display notification "Couldn't open Terminal automatically. Run: " & claimCmd with title "Terminalhire"`,
|
|
171
|
-
" try",
|
|
172
|
-
` do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
173
|
-
" end try",
|
|
174
|
-
" end try",
|
|
226
|
+
...launch,
|
|
175
227
|
"end open location",
|
|
176
228
|
""
|
|
177
229
|
].join("\n");
|
|
@@ -182,7 +234,12 @@ function buildPreviewShellCommand(token, deps) {
|
|
|
182
234
|
shellQuoteSingle(deps.dispatchPath),
|
|
183
235
|
"claim",
|
|
184
236
|
"preview",
|
|
185
|
-
|
|
237
|
+
// Quoted like the paths beside it, though parseClaimUrl's `[A-Za-z0-9_-]{8}` already
|
|
238
|
+
// forbids every shell metacharacter. Leaving the ONE value that came from a URL bare
|
|
239
|
+
// while quoting the two that never did is backwards: it makes the safety depend on a
|
|
240
|
+
// regex two files away rather than on this line, and a future loosening of that regex
|
|
241
|
+
// would open an injection here silently.
|
|
242
|
+
shellQuoteSingle(token)
|
|
186
243
|
].join(" ");
|
|
187
244
|
}
|
|
188
245
|
function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
@@ -194,6 +251,28 @@ function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
|
194
251
|
deps.log(buildPreviewShellCommand(parsed.token, deps));
|
|
195
252
|
deps.exit(0);
|
|
196
253
|
}
|
|
254
|
+
function launcherPath(token, deps) {
|
|
255
|
+
return join(stateDir(deps), `claim-${token}.command`);
|
|
256
|
+
}
|
|
257
|
+
function buildLauncherScript(token, deps) {
|
|
258
|
+
return ["#!/bin/sh", `exec ${buildPreviewShellCommand(token, deps)}`, ""].join("\n");
|
|
259
|
+
}
|
|
260
|
+
function writeLauncherFile(token, deps) {
|
|
261
|
+
deps.ensureStateDirForSecret(stateDir(deps));
|
|
262
|
+
const path = launcherPath(token, deps);
|
|
263
|
+
deps.rmSync(path);
|
|
264
|
+
deps.writeFileSync(path, buildLauncherScript(token, deps), 448);
|
|
265
|
+
return path;
|
|
266
|
+
}
|
|
267
|
+
function writeClaimLauncher(raw, deps = defaultProtocolDeps()) {
|
|
268
|
+
const parsed = parseClaimUrl(raw);
|
|
269
|
+
if (!parsed) {
|
|
270
|
+
deps.exit(1);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
deps.log(writeLauncherFile(parsed.token, deps));
|
|
274
|
+
deps.exit(0);
|
|
275
|
+
}
|
|
197
276
|
function darwinAppPaths(deps) {
|
|
198
277
|
const appDir = join(deps.homedir(), "Applications");
|
|
199
278
|
const appPath = join(appDir, "Terminalhire Handler.app");
|
|
@@ -206,7 +285,16 @@ function darwinRegister(deps) {
|
|
|
206
285
|
const { appDir, appPath, plistPath } = darwinAppPaths(deps);
|
|
207
286
|
deps.mkdirSync(appDir);
|
|
208
287
|
const scriptPath = join(dir, "handler.applescript");
|
|
209
|
-
deps.writeFileSync(
|
|
288
|
+
deps.writeFileSync(
|
|
289
|
+
scriptPath,
|
|
290
|
+
buildAppleScriptHandler(
|
|
291
|
+
deps.execPath,
|
|
292
|
+
deps.dispatchPath,
|
|
293
|
+
// Resolved at REGISTER time, from the shell the user ran `protocol register` in.
|
|
294
|
+
// `open location` runs with no TERM_PROGRAM at all, so this cannot be deferred.
|
|
295
|
+
preferredTerminalApp(deps.env)
|
|
296
|
+
)
|
|
297
|
+
);
|
|
210
298
|
deps.rmSync(appPath);
|
|
211
299
|
const compile = deps.spawnSync("osacompile", ["-o", appPath, scriptPath]);
|
|
212
300
|
if (compile.status !== 0) {
|
|
@@ -274,14 +362,10 @@ function darwinStatus(deps) {
|
|
|
274
362
|
return { registered: exists && registered, appExists: exists };
|
|
275
363
|
}
|
|
276
364
|
function darwinOpenPreviewTerminal(token, deps) {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"-e",
|
|
282
|
-
doScript
|
|
283
|
-
]);
|
|
284
|
-
return res.status === 0;
|
|
365
|
+
const path = writeLauncherFile(token, deps);
|
|
366
|
+
const app = preferredTerminalApp(deps.env);
|
|
367
|
+
if (app && deps.spawnSync("open", ["-a", app, path]).status === 0) return true;
|
|
368
|
+
return deps.spawnSync("open", [path]).status === 0;
|
|
285
369
|
}
|
|
286
370
|
function win32Register(deps) {
|
|
287
371
|
for (const scheme of WIN32_SCHEMES) {
|
|
@@ -570,7 +654,7 @@ var init_protocol = __esm({
|
|
|
570
654
|
["konsole", ["-e"]],
|
|
571
655
|
["xterm", ["-e"]]
|
|
572
656
|
];
|
|
573
|
-
HANDLER_TEMPLATE_VERSION =
|
|
657
|
+
HANDLER_TEMPLATE_VERSION = 3;
|
|
574
658
|
PENDING_CLAIMS_CAP = 20;
|
|
575
659
|
PENDING_TOKEN_RE = /^[A-Za-z0-9_-]{8}$/;
|
|
576
660
|
}
|
package/dist/bin/jpi-update.js
CHANGED
|
@@ -51,7 +51,31 @@ function ensureStateDir(dir) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
function applyStateDirSecretPolicy(dir, status) {
|
|
55
|
+
if (status === STATE_DIR_SYMLINK) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`terminalhire: refusing to write key material into ${dir} \u2014 it is a symlink, not a directory.
|
|
58
|
+
A write through it would FOLLOW THE LINK and place key/token material wherever the symlink points, outside our control and outside the "owner-only" (0700) guarantee this directory is supposed to carry.
|
|
59
|
+
Fix: remove the symlink so terminalhire can recreate it as a real directory \u2014
|
|
60
|
+
rm ${dir}
|
|
61
|
+
then re-run the command. If the symlink is intentional, point TERMINALHIRE_DIR at a real directory instead of routing it through this one.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (status === STATE_DIR_UNVERIFIED && !warnedUnverifiedSecretWriteThisProcess) {
|
|
65
|
+
warnedUnverifiedSecretWriteThisProcess = true;
|
|
66
|
+
try {
|
|
67
|
+
process.stderr.write(
|
|
68
|
+
`terminalhire: could not verify ${dir}'s permissions (expected on Windows \u2014 POSIX mode bits do not apply there) \u2014 proceeding, but the "owner-only" guarantee on key/token storage is NOT enforced on this platform.
|
|
69
|
+
`
|
|
70
|
+
);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function ensureStateDirForSecret(dir) {
|
|
76
|
+
applyStateDirSecretPolicy(dir, ensureStateDir(dir));
|
|
77
|
+
}
|
|
78
|
+
var STATE_DIR_MODE, STATE_DIR_OK, STATE_DIR_SYMLINK, STATE_DIR_UNVERIFIED, warnedDirs, warnedUnverifiedSecretWriteThisProcess;
|
|
55
79
|
var init_state_dir = __esm({
|
|
56
80
|
"src/state-dir.ts"() {
|
|
57
81
|
"use strict";
|
|
@@ -60,6 +84,7 @@ var init_state_dir = __esm({
|
|
|
60
84
|
STATE_DIR_SYMLINK = "symlink";
|
|
61
85
|
STATE_DIR_UNVERIFIED = "unverified";
|
|
62
86
|
warnedDirs = /* @__PURE__ */ new Set();
|
|
87
|
+
warnedUnverifiedSecretWriteThisProcess = false;
|
|
63
88
|
}
|
|
64
89
|
});
|
|
65
90
|
|
|
@@ -72,10 +97,12 @@ __export(protocol_exports, {
|
|
|
72
97
|
handleUrl: () => handleUrl,
|
|
73
98
|
healStaleHandler: () => healStaleHandler,
|
|
74
99
|
parseClaimUrl: () => parseClaimUrl,
|
|
100
|
+
preferredTerminalApp: () => preferredTerminalApp,
|
|
75
101
|
printClaimCommand: () => printClaimCommand,
|
|
76
102
|
registerScheme: () => registerScheme,
|
|
77
103
|
schemeStatus: () => schemeStatus,
|
|
78
|
-
unregisterScheme: () => unregisterScheme
|
|
104
|
+
unregisterScheme: () => unregisterScheme,
|
|
105
|
+
writeClaimLauncher: () => writeClaimLauncher
|
|
79
106
|
});
|
|
80
107
|
import { spawn, spawnSync } from "child_process";
|
|
81
108
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
@@ -107,8 +134,11 @@ function defaultProtocolDeps() {
|
|
|
107
134
|
ensureStateDir: (path2) => {
|
|
108
135
|
ensureStateDir(path2);
|
|
109
136
|
},
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
ensureStateDirForSecret: (path2) => {
|
|
138
|
+
ensureStateDirForSecret(path2);
|
|
139
|
+
},
|
|
140
|
+
writeFileSync: (path2, contents, mode) => {
|
|
141
|
+
writeFileSync2(path2, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode });
|
|
112
142
|
},
|
|
113
143
|
readFileSync: (path2) => readFileSync2(path2, "utf8"),
|
|
114
144
|
rmSync: (path2) => {
|
|
@@ -148,30 +178,52 @@ function appleScriptStringLiteral(s) {
|
|
|
148
178
|
function shellQuoteSingle(s) {
|
|
149
179
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
150
180
|
}
|
|
151
|
-
function
|
|
181
|
+
function preferredTerminalApp(env) {
|
|
182
|
+
const raw = typeof env.TERM_PROGRAM === "string" ? env.TERM_PROGRAM.trim() : "";
|
|
183
|
+
if (!raw) return null;
|
|
184
|
+
const known = {
|
|
185
|
+
apple_terminal: "Terminal",
|
|
186
|
+
"iterm.app": "iTerm"
|
|
187
|
+
};
|
|
188
|
+
return known[raw.toLowerCase()] ?? null;
|
|
189
|
+
}
|
|
190
|
+
function buildAppleScriptHandler(execPath, dispatchPath, terminalApp) {
|
|
152
191
|
const execLit = appleScriptStringLiteral(execPath);
|
|
153
192
|
const dispatchLit = appleScriptStringLiteral(dispatchPath);
|
|
193
|
+
const fallback = (indent) => [
|
|
194
|
+
`${indent}display notification "Couldn't open your terminal. Run: " & launcher with title "Terminalhire"`,
|
|
195
|
+
`${indent}try`,
|
|
196
|
+
`${indent} do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
197
|
+
`${indent}end try`
|
|
198
|
+
];
|
|
199
|
+
const launch = terminalApp ? [
|
|
200
|
+
" try",
|
|
201
|
+
` do shell script "open -a " & quoted form of ${appleScriptStringLiteral(terminalApp)} & " " & quoted form of launcher`,
|
|
202
|
+
" on error",
|
|
203
|
+
" try",
|
|
204
|
+
' do shell script "open " & quoted form of launcher',
|
|
205
|
+
" on error",
|
|
206
|
+
...fallback(" "),
|
|
207
|
+
" end try",
|
|
208
|
+
" end try"
|
|
209
|
+
] : [
|
|
210
|
+
" try",
|
|
211
|
+
' do shell script "open " & quoted form of launcher',
|
|
212
|
+
" on error",
|
|
213
|
+
...fallback(" "),
|
|
214
|
+
" end try"
|
|
215
|
+
];
|
|
154
216
|
return [
|
|
155
217
|
"on open location theURL",
|
|
156
|
-
' set
|
|
218
|
+
' set launcher to ""',
|
|
157
219
|
" try",
|
|
158
|
-
` set
|
|
220
|
+
` set launcher to do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " write-claim-launcher " & quoted form of theURL`,
|
|
159
221
|
" end try",
|
|
160
|
-
' if
|
|
222
|
+
' if launcher is "" then',
|
|
161
223
|
` display notification "That isn't a valid Terminalhire claim link." with title "Terminalhire"`,
|
|
162
224
|
" return",
|
|
163
225
|
" end if",
|
|
164
|
-
|
|
165
|
-
' tell application "Terminal"',
|
|
166
|
-
" activate",
|
|
167
|
-
" do script claimCmd",
|
|
168
|
-
" end tell",
|
|
169
|
-
" on error",
|
|
170
|
-
` display notification "Couldn't open Terminal automatically. Run: " & claimCmd with title "Terminalhire"`,
|
|
171
|
-
" try",
|
|
172
|
-
` do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
173
|
-
" end try",
|
|
174
|
-
" end try",
|
|
226
|
+
...launch,
|
|
175
227
|
"end open location",
|
|
176
228
|
""
|
|
177
229
|
].join("\n");
|
|
@@ -182,7 +234,12 @@ function buildPreviewShellCommand(token, deps) {
|
|
|
182
234
|
shellQuoteSingle(deps.dispatchPath),
|
|
183
235
|
"claim",
|
|
184
236
|
"preview",
|
|
185
|
-
|
|
237
|
+
// Quoted like the paths beside it, though parseClaimUrl's `[A-Za-z0-9_-]{8}` already
|
|
238
|
+
// forbids every shell metacharacter. Leaving the ONE value that came from a URL bare
|
|
239
|
+
// while quoting the two that never did is backwards: it makes the safety depend on a
|
|
240
|
+
// regex two files away rather than on this line, and a future loosening of that regex
|
|
241
|
+
// would open an injection here silently.
|
|
242
|
+
shellQuoteSingle(token)
|
|
186
243
|
].join(" ");
|
|
187
244
|
}
|
|
188
245
|
function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
@@ -194,6 +251,28 @@ function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
|
194
251
|
deps.log(buildPreviewShellCommand(parsed.token, deps));
|
|
195
252
|
deps.exit(0);
|
|
196
253
|
}
|
|
254
|
+
function launcherPath(token, deps) {
|
|
255
|
+
return join2(stateDir2(deps), `claim-${token}.command`);
|
|
256
|
+
}
|
|
257
|
+
function buildLauncherScript(token, deps) {
|
|
258
|
+
return ["#!/bin/sh", `exec ${buildPreviewShellCommand(token, deps)}`, ""].join("\n");
|
|
259
|
+
}
|
|
260
|
+
function writeLauncherFile(token, deps) {
|
|
261
|
+
deps.ensureStateDirForSecret(stateDir2(deps));
|
|
262
|
+
const path2 = launcherPath(token, deps);
|
|
263
|
+
deps.rmSync(path2);
|
|
264
|
+
deps.writeFileSync(path2, buildLauncherScript(token, deps), 448);
|
|
265
|
+
return path2;
|
|
266
|
+
}
|
|
267
|
+
function writeClaimLauncher(raw, deps = defaultProtocolDeps()) {
|
|
268
|
+
const parsed = parseClaimUrl(raw);
|
|
269
|
+
if (!parsed) {
|
|
270
|
+
deps.exit(1);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
deps.log(writeLauncherFile(parsed.token, deps));
|
|
274
|
+
deps.exit(0);
|
|
275
|
+
}
|
|
197
276
|
function darwinAppPaths(deps) {
|
|
198
277
|
const appDir = join2(deps.homedir(), "Applications");
|
|
199
278
|
const appPath = join2(appDir, "Terminalhire Handler.app");
|
|
@@ -206,7 +285,16 @@ function darwinRegister(deps) {
|
|
|
206
285
|
const { appDir, appPath, plistPath } = darwinAppPaths(deps);
|
|
207
286
|
deps.mkdirSync(appDir);
|
|
208
287
|
const scriptPath = join2(dir, "handler.applescript");
|
|
209
|
-
deps.writeFileSync(
|
|
288
|
+
deps.writeFileSync(
|
|
289
|
+
scriptPath,
|
|
290
|
+
buildAppleScriptHandler(
|
|
291
|
+
deps.execPath,
|
|
292
|
+
deps.dispatchPath,
|
|
293
|
+
// Resolved at REGISTER time, from the shell the user ran `protocol register` in.
|
|
294
|
+
// `open location` runs with no TERM_PROGRAM at all, so this cannot be deferred.
|
|
295
|
+
preferredTerminalApp(deps.env)
|
|
296
|
+
)
|
|
297
|
+
);
|
|
210
298
|
deps.rmSync(appPath);
|
|
211
299
|
const compile = deps.spawnSync("osacompile", ["-o", appPath, scriptPath]);
|
|
212
300
|
if (compile.status !== 0) {
|
|
@@ -274,14 +362,10 @@ function darwinStatus(deps) {
|
|
|
274
362
|
return { registered: exists && registered, appExists: exists };
|
|
275
363
|
}
|
|
276
364
|
function darwinOpenPreviewTerminal(token, deps) {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"-e",
|
|
282
|
-
doScript
|
|
283
|
-
]);
|
|
284
|
-
return res.status === 0;
|
|
365
|
+
const path2 = writeLauncherFile(token, deps);
|
|
366
|
+
const app = preferredTerminalApp(deps.env);
|
|
367
|
+
if (app && deps.spawnSync("open", ["-a", app, path2]).status === 0) return true;
|
|
368
|
+
return deps.spawnSync("open", [path2]).status === 0;
|
|
285
369
|
}
|
|
286
370
|
function win32Register(deps) {
|
|
287
371
|
for (const scheme of WIN32_SCHEMES) {
|
|
@@ -570,7 +654,7 @@ var init_protocol = __esm({
|
|
|
570
654
|
["konsole", ["-e"]],
|
|
571
655
|
["xterm", ["-e"]]
|
|
572
656
|
];
|
|
573
|
-
HANDLER_TEMPLATE_VERSION =
|
|
657
|
+
HANDLER_TEMPLATE_VERSION = 3;
|
|
574
658
|
PENDING_CLAIMS_CAP = 20;
|
|
575
659
|
PENDING_TOKEN_RE = /^[A-Za-z0-9_-]{8}$/;
|
|
576
660
|
}
|
package/dist/src/protocol.js
CHANGED
|
@@ -52,6 +52,31 @@ function ensureStateDir(dir) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
var warnedUnverifiedSecretWriteThisProcess = false;
|
|
56
|
+
function applyStateDirSecretPolicy(dir, status) {
|
|
57
|
+
if (status === STATE_DIR_SYMLINK) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`terminalhire: refusing to write key material into ${dir} \u2014 it is a symlink, not a directory.
|
|
60
|
+
A write through it would FOLLOW THE LINK and place key/token material wherever the symlink points, outside our control and outside the "owner-only" (0700) guarantee this directory is supposed to carry.
|
|
61
|
+
Fix: remove the symlink so terminalhire can recreate it as a real directory \u2014
|
|
62
|
+
rm ${dir}
|
|
63
|
+
then re-run the command. If the symlink is intentional, point TERMINALHIRE_DIR at a real directory instead of routing it through this one.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (status === STATE_DIR_UNVERIFIED && !warnedUnverifiedSecretWriteThisProcess) {
|
|
67
|
+
warnedUnverifiedSecretWriteThisProcess = true;
|
|
68
|
+
try {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
`terminalhire: could not verify ${dir}'s permissions (expected on Windows \u2014 POSIX mode bits do not apply there) \u2014 proceeding, but the "owner-only" guarantee on key/token storage is NOT enforced on this platform.
|
|
71
|
+
`
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function ensureStateDirForSecret(dir) {
|
|
78
|
+
applyStateDirSecretPolicy(dir, ensureStateDir(dir));
|
|
79
|
+
}
|
|
55
80
|
|
|
56
81
|
// src/protocol.ts
|
|
57
82
|
var CLAIM_URL_RE = /^(th|terminalhire):\/\/claim\/([A-Za-z0-9_-]{8})\/?$/i;
|
|
@@ -80,8 +105,11 @@ function defaultProtocolDeps() {
|
|
|
80
105
|
ensureStateDir: (path) => {
|
|
81
106
|
ensureStateDir(path);
|
|
82
107
|
},
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
ensureStateDirForSecret: (path) => {
|
|
109
|
+
ensureStateDirForSecret(path);
|
|
110
|
+
},
|
|
111
|
+
writeFileSync: (path, contents, mode) => {
|
|
112
|
+
writeFileSync(path, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode });
|
|
85
113
|
},
|
|
86
114
|
readFileSync: (path) => readFileSync(path, "utf8"),
|
|
87
115
|
rmSync: (path) => {
|
|
@@ -123,30 +151,52 @@ function appleScriptStringLiteral(s) {
|
|
|
123
151
|
function shellQuoteSingle(s) {
|
|
124
152
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
125
153
|
}
|
|
126
|
-
function
|
|
154
|
+
function preferredTerminalApp(env) {
|
|
155
|
+
const raw = typeof env.TERM_PROGRAM === "string" ? env.TERM_PROGRAM.trim() : "";
|
|
156
|
+
if (!raw) return null;
|
|
157
|
+
const known = {
|
|
158
|
+
apple_terminal: "Terminal",
|
|
159
|
+
"iterm.app": "iTerm"
|
|
160
|
+
};
|
|
161
|
+
return known[raw.toLowerCase()] ?? null;
|
|
162
|
+
}
|
|
163
|
+
function buildAppleScriptHandler(execPath, dispatchPath, terminalApp) {
|
|
127
164
|
const execLit = appleScriptStringLiteral(execPath);
|
|
128
165
|
const dispatchLit = appleScriptStringLiteral(dispatchPath);
|
|
166
|
+
const fallback = (indent) => [
|
|
167
|
+
`${indent}display notification "Couldn't open your terminal. Run: " & launcher with title "Terminalhire"`,
|
|
168
|
+
`${indent}try`,
|
|
169
|
+
`${indent} do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
170
|
+
`${indent}end try`
|
|
171
|
+
];
|
|
172
|
+
const launch = terminalApp ? [
|
|
173
|
+
" try",
|
|
174
|
+
` do shell script "open -a " & quoted form of ${appleScriptStringLiteral(terminalApp)} & " " & quoted form of launcher`,
|
|
175
|
+
" on error",
|
|
176
|
+
" try",
|
|
177
|
+
' do shell script "open " & quoted form of launcher',
|
|
178
|
+
" on error",
|
|
179
|
+
...fallback(" "),
|
|
180
|
+
" end try",
|
|
181
|
+
" end try"
|
|
182
|
+
] : [
|
|
183
|
+
" try",
|
|
184
|
+
' do shell script "open " & quoted form of launcher',
|
|
185
|
+
" on error",
|
|
186
|
+
...fallback(" "),
|
|
187
|
+
" end try"
|
|
188
|
+
];
|
|
129
189
|
return [
|
|
130
190
|
"on open location theURL",
|
|
131
|
-
' set
|
|
191
|
+
' set launcher to ""',
|
|
132
192
|
" try",
|
|
133
|
-
` set
|
|
193
|
+
` set launcher to do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " write-claim-launcher " & quoted form of theURL`,
|
|
134
194
|
" end try",
|
|
135
|
-
' if
|
|
195
|
+
' if launcher is "" then',
|
|
136
196
|
` display notification "That isn't a valid Terminalhire claim link." with title "Terminalhire"`,
|
|
137
197
|
" return",
|
|
138
198
|
" end if",
|
|
139
|
-
|
|
140
|
-
' tell application "Terminal"',
|
|
141
|
-
" activate",
|
|
142
|
-
" do script claimCmd",
|
|
143
|
-
" end tell",
|
|
144
|
-
" on error",
|
|
145
|
-
` display notification "Couldn't open Terminal automatically. Run: " & claimCmd with title "Terminalhire"`,
|
|
146
|
-
" try",
|
|
147
|
-
` do shell script quoted form of ${execLit} & " " & quoted form of ${dispatchLit} & " handle-url " & quoted form of theURL & " > /dev/null 2>&1 &"`,
|
|
148
|
-
" end try",
|
|
149
|
-
" end try",
|
|
199
|
+
...launch,
|
|
150
200
|
"end open location",
|
|
151
201
|
""
|
|
152
202
|
].join("\n");
|
|
@@ -157,7 +207,12 @@ function buildPreviewShellCommand(token, deps) {
|
|
|
157
207
|
shellQuoteSingle(deps.dispatchPath),
|
|
158
208
|
"claim",
|
|
159
209
|
"preview",
|
|
160
|
-
|
|
210
|
+
// Quoted like the paths beside it, though parseClaimUrl's `[A-Za-z0-9_-]{8}` already
|
|
211
|
+
// forbids every shell metacharacter. Leaving the ONE value that came from a URL bare
|
|
212
|
+
// while quoting the two that never did is backwards: it makes the safety depend on a
|
|
213
|
+
// regex two files away rather than on this line, and a future loosening of that regex
|
|
214
|
+
// would open an injection here silently.
|
|
215
|
+
shellQuoteSingle(token)
|
|
161
216
|
].join(" ");
|
|
162
217
|
}
|
|
163
218
|
function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
@@ -169,6 +224,28 @@ function printClaimCommand(raw, deps = defaultProtocolDeps()) {
|
|
|
169
224
|
deps.log(buildPreviewShellCommand(parsed.token, deps));
|
|
170
225
|
deps.exit(0);
|
|
171
226
|
}
|
|
227
|
+
function launcherPath(token, deps) {
|
|
228
|
+
return join(stateDir(deps), `claim-${token}.command`);
|
|
229
|
+
}
|
|
230
|
+
function buildLauncherScript(token, deps) {
|
|
231
|
+
return ["#!/bin/sh", `exec ${buildPreviewShellCommand(token, deps)}`, ""].join("\n");
|
|
232
|
+
}
|
|
233
|
+
function writeLauncherFile(token, deps) {
|
|
234
|
+
deps.ensureStateDirForSecret(stateDir(deps));
|
|
235
|
+
const path = launcherPath(token, deps);
|
|
236
|
+
deps.rmSync(path);
|
|
237
|
+
deps.writeFileSync(path, buildLauncherScript(token, deps), 448);
|
|
238
|
+
return path;
|
|
239
|
+
}
|
|
240
|
+
function writeClaimLauncher(raw, deps = defaultProtocolDeps()) {
|
|
241
|
+
const parsed = parseClaimUrl(raw);
|
|
242
|
+
if (!parsed) {
|
|
243
|
+
deps.exit(1);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
deps.log(writeLauncherFile(parsed.token, deps));
|
|
247
|
+
deps.exit(0);
|
|
248
|
+
}
|
|
172
249
|
function darwinAppPaths(deps) {
|
|
173
250
|
const appDir = join(deps.homedir(), "Applications");
|
|
174
251
|
const appPath = join(appDir, "Terminalhire Handler.app");
|
|
@@ -181,7 +258,16 @@ function darwinRegister(deps) {
|
|
|
181
258
|
const { appDir, appPath, plistPath } = darwinAppPaths(deps);
|
|
182
259
|
deps.mkdirSync(appDir);
|
|
183
260
|
const scriptPath = join(dir, "handler.applescript");
|
|
184
|
-
deps.writeFileSync(
|
|
261
|
+
deps.writeFileSync(
|
|
262
|
+
scriptPath,
|
|
263
|
+
buildAppleScriptHandler(
|
|
264
|
+
deps.execPath,
|
|
265
|
+
deps.dispatchPath,
|
|
266
|
+
// Resolved at REGISTER time, from the shell the user ran `protocol register` in.
|
|
267
|
+
// `open location` runs with no TERM_PROGRAM at all, so this cannot be deferred.
|
|
268
|
+
preferredTerminalApp(deps.env)
|
|
269
|
+
)
|
|
270
|
+
);
|
|
185
271
|
deps.rmSync(appPath);
|
|
186
272
|
const compile = deps.spawnSync("osacompile", ["-o", appPath, scriptPath]);
|
|
187
273
|
if (compile.status !== 0) {
|
|
@@ -249,14 +335,10 @@ function darwinStatus(deps) {
|
|
|
249
335
|
return { registered: exists && registered, appExists: exists };
|
|
250
336
|
}
|
|
251
337
|
function darwinOpenPreviewTerminal(token, deps) {
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"-e",
|
|
257
|
-
doScript
|
|
258
|
-
]);
|
|
259
|
-
return res.status === 0;
|
|
338
|
+
const path = writeLauncherFile(token, deps);
|
|
339
|
+
const app = preferredTerminalApp(deps.env);
|
|
340
|
+
if (app && deps.spawnSync("open", ["-a", app, path]).status === 0) return true;
|
|
341
|
+
return deps.spawnSync("open", [path]).status === 0;
|
|
260
342
|
}
|
|
261
343
|
var WIN32_SCHEMES = ["th", "terminalhire"];
|
|
262
344
|
function win32Register(deps) {
|
|
@@ -348,7 +430,7 @@ var LINUX_TERMINAL_CANDIDATES = [
|
|
|
348
430
|
["konsole", ["-e"]],
|
|
349
431
|
["xterm", ["-e"]]
|
|
350
432
|
];
|
|
351
|
-
var HANDLER_TEMPLATE_VERSION =
|
|
433
|
+
var HANDLER_TEMPLATE_VERSION = 3;
|
|
352
434
|
function handlerTemplateVersionPath(deps) {
|
|
353
435
|
return join(stateDir(deps), "handler-template-version");
|
|
354
436
|
}
|
|
@@ -547,8 +629,10 @@ export {
|
|
|
547
629
|
handleUrl,
|
|
548
630
|
healStaleHandler,
|
|
549
631
|
parseClaimUrl,
|
|
632
|
+
preferredTerminalApp,
|
|
550
633
|
printClaimCommand,
|
|
551
634
|
registerScheme,
|
|
552
635
|
schemeStatus,
|
|
553
|
-
unregisterScheme
|
|
636
|
+
unregisterScheme,
|
|
637
|
+
writeClaimLauncher
|
|
554
638
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.3",
|
|
4
4
|
"description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Your profile never leaves your machine.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"test": "node scripts/run-tests.mjs test",
|
|
32
32
|
"build": "tsup",
|
|
33
33
|
"bundle:plugin": "npm run build && rm -rf ../../plugins/terminalhire/dist && cp -R dist ../../plugins/terminalhire/dist && cp package.json ../../plugins/terminalhire/dist/package.json && cp install.js ../../plugins/terminalhire/dist/install.js && cp statusline-install.js ../../plugins/terminalhire/dist/statusline-install.js && cp postinstall.js ../../plugins/terminalhire/dist/postinstall.js && node ../../scripts/write-bundle-manifest.mjs",
|
|
34
|
-
"prepublishOnly": "npm run build",
|
|
34
|
+
"prepublishOnly": "node ../../scripts/check-publish-preconditions.mjs && npm run build",
|
|
35
35
|
"install-hook": "node install.js",
|
|
36
36
|
"postinstall": "node ./postinstall.js"
|
|
37
37
|
},
|