terminalhire 0.7.1 → 0.8.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 +11 -0
- package/dist/bin/jpi-config.js +39 -6
- package/dist/bin/jpi-devs.js +57 -41
- package/dist/bin/jpi-dispatch.js +372 -202
- package/dist/bin/jpi-intro.js +8 -0
- package/dist/bin/jpi-jobs.js +11 -0
- package/dist/bin/jpi-learn.js +8 -0
- package/dist/bin/jpi-login.js +106 -4
- package/dist/bin/jpi-profile.js +8 -0
- package/dist/bin/jpi-project.js +11 -0
- package/dist/bin/jpi-refresh.js +604 -447
- package/dist/bin/jpi-save.js +8 -0
- package/dist/bin/jpi-spinner.js +10 -2
- package/dist/bin/jpi-sync.js +8 -0
- package/dist/bin/jpi-trajectory.js +8 -0
- package/dist/bin/peer-connect-prompt.js +75 -0
- package/dist/bin/spinner.js +11 -2
- package/dist/src/config.js +7 -1
- package/dist/src/intro.js +8 -0
- package/dist/src/trajectory.js +8 -0
- package/package.json +1 -1
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -2800,6 +2800,15 @@ var init_intro = __esm({
|
|
|
2800
2800
|
}
|
|
2801
2801
|
});
|
|
2802
2802
|
|
|
2803
|
+
// ../../packages/core/src/directoryThreshold.ts
|
|
2804
|
+
var STRONG_MATCH_THRESHOLD;
|
|
2805
|
+
var init_directoryThreshold = __esm({
|
|
2806
|
+
"../../packages/core/src/directoryThreshold.ts"() {
|
|
2807
|
+
"use strict";
|
|
2808
|
+
STRONG_MATCH_THRESHOLD = 0.58;
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2803
2812
|
// ../../packages/core/src/index.ts
|
|
2804
2813
|
var src_exports = {};
|
|
2805
2814
|
__export(src_exports, {
|
|
@@ -2819,6 +2828,7 @@ __export(src_exports, {
|
|
|
2819
2828
|
INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
|
|
2820
2829
|
INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
|
|
2821
2830
|
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
2831
|
+
STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
|
|
2822
2832
|
SYNONYMS: () => SYNONYMS,
|
|
2823
2833
|
VOCABULARY: () => VOCABULARY,
|
|
2824
2834
|
VOCAB_NODES: () => VOCAB_NODES,
|
|
@@ -2887,6 +2897,7 @@ var init_src = __esm({
|
|
|
2887
2897
|
init_partners();
|
|
2888
2898
|
init_github();
|
|
2889
2899
|
init_intro();
|
|
2900
|
+
init_directoryThreshold();
|
|
2890
2901
|
}
|
|
2891
2902
|
});
|
|
2892
2903
|
|
package/dist/bin/jpi-config.js
CHANGED
|
@@ -1,25 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/jpi-config.js
|
|
4
|
+
import { join as join2 } from "path";
|
|
5
|
+
import { homedir as homedir2 } from "os";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
4
8
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
5
9
|
import { join } from "path";
|
|
6
10
|
import { homedir } from "os";
|
|
7
11
|
var TERMINALHIRE_DIR = join(homedir(), ".terminalhire");
|
|
8
12
|
var CONFIG_FILE = join(TERMINALHIRE_DIR, "config.json");
|
|
9
|
-
var DEFAULT_CONFIG = {
|
|
13
|
+
var DEFAULT_CONFIG = {
|
|
14
|
+
nudge: "session",
|
|
15
|
+
peerConnect: false,
|
|
16
|
+
peerConnectPrompted: false
|
|
17
|
+
};
|
|
10
18
|
function readConfig() {
|
|
11
19
|
try {
|
|
12
20
|
if (!existsSync(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
|
|
13
|
-
|
|
21
|
+
const raw = readFileSync(CONFIG_FILE, "utf8");
|
|
22
|
+
const parsed = JSON.parse(raw);
|
|
23
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
14
24
|
} catch {
|
|
15
25
|
return { ...DEFAULT_CONFIG };
|
|
16
26
|
}
|
|
17
27
|
}
|
|
18
|
-
function writeConfig(
|
|
28
|
+
function writeConfig(config) {
|
|
19
29
|
mkdirSync(TERMINALHIRE_DIR, { recursive: true });
|
|
20
|
-
const
|
|
30
|
+
const current = readConfig();
|
|
31
|
+
const merged = { ...current, ...config };
|
|
21
32
|
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
22
33
|
}
|
|
34
|
+
|
|
35
|
+
// bin/jpi-config.js
|
|
36
|
+
var TERMINALHIRE_DIR2 = join2(homedir2(), ".terminalhire");
|
|
37
|
+
var CONFIG_FILE2 = join2(TERMINALHIRE_DIR2, "config.json");
|
|
23
38
|
function parseNudgeMode(raw) {
|
|
24
39
|
if (raw === "session" || raw === "always") return raw;
|
|
25
40
|
const m = /^every:(\d+)$/.exec(raw);
|
|
@@ -39,13 +54,18 @@ async function run() {
|
|
|
39
54
|
if (envOverride) {
|
|
40
55
|
console.log(` (overridden by TERMINALHIRE_NUDGE=${envOverride} at runtime)`);
|
|
41
56
|
}
|
|
42
|
-
console.log(`
|
|
57
|
+
console.log(` peer-connect: ${cfg.peerConnect ? "on" : "off"} (ambient peer & founder surfacing; default off)`);
|
|
58
|
+
console.log(` config file: ${CONFIG_FILE2}`);
|
|
43
59
|
console.log("");
|
|
44
60
|
console.log(" Valid nudge values:");
|
|
45
61
|
console.log(" session \u2014 print at most once per Claude Code session (default)");
|
|
46
62
|
console.log(" always \u2014 print every statusLine render when matches exist");
|
|
47
63
|
console.log(" every:N \u2014 print every Nth render (e.g. every:3)");
|
|
48
64
|
console.log("");
|
|
65
|
+
console.log(" Peer-connect (--connect on|off):");
|
|
66
|
+
console.log(" on \u2014 surface peers & founders in the spinner + send an anonymous matched signal");
|
|
67
|
+
console.log(" off \u2014 no peer matching, no directory fetch, no signal (default)");
|
|
68
|
+
console.log("");
|
|
49
69
|
return;
|
|
50
70
|
}
|
|
51
71
|
const nudgeIdx = filtered.indexOf("--nudge");
|
|
@@ -62,10 +82,23 @@ async function run() {
|
|
|
62
82
|
}
|
|
63
83
|
writeConfig({ nudge: parsed });
|
|
64
84
|
console.log(` nudge set to: ${parsed}`);
|
|
65
|
-
console.log(` (saved to ${
|
|
85
|
+
console.log(` (saved to ${CONFIG_FILE2})`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const connectIdx = filtered.indexOf("--connect");
|
|
89
|
+
if (connectIdx !== -1) {
|
|
90
|
+
const value = filtered[connectIdx + 1];
|
|
91
|
+
if (value !== "on" && value !== "off") {
|
|
92
|
+
console.error("Error: --connect requires a value: on | off");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
writeConfig({ peerConnect: value === "on", peerConnectPrompted: true });
|
|
96
|
+
console.log(` peer-connect set to: ${value}`);
|
|
97
|
+
console.log(` (saved to ${CONFIG_FILE2})`);
|
|
66
98
|
return;
|
|
67
99
|
}
|
|
68
100
|
console.error("Usage: terminalhire config --nudge <session|always|every:N>");
|
|
101
|
+
console.error(" terminalhire config --connect <on|off>");
|
|
69
102
|
console.error(" terminalhire config --show");
|
|
70
103
|
process.exit(1);
|
|
71
104
|
}
|
package/dist/bin/jpi-devs.js
CHANGED
|
@@ -2543,21 +2543,21 @@ var init_feeds = __esm({
|
|
|
2543
2543
|
});
|
|
2544
2544
|
|
|
2545
2545
|
// ../../packages/core/src/partners.ts
|
|
2546
|
-
import { readFileSync } from "fs";
|
|
2547
|
-
import { join } from "path";
|
|
2546
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2547
|
+
import { join as join2 } from "path";
|
|
2548
2548
|
import { fileURLToPath } from "url";
|
|
2549
2549
|
function resolveDataPath() {
|
|
2550
2550
|
try {
|
|
2551
2551
|
const dir = fileURLToPath(new URL("../../../data", import.meta.url));
|
|
2552
|
-
return
|
|
2552
|
+
return join2(dir, "partner-roles.json");
|
|
2553
2553
|
} catch {
|
|
2554
|
-
return
|
|
2554
|
+
return join2(process.cwd(), "data", "partner-roles.json");
|
|
2555
2555
|
}
|
|
2556
2556
|
}
|
|
2557
2557
|
function loadPartnerRoles() {
|
|
2558
2558
|
const filePath = resolveDataPath();
|
|
2559
2559
|
try {
|
|
2560
|
-
const raw =
|
|
2560
|
+
const raw = readFileSync2(filePath, "utf-8");
|
|
2561
2561
|
const parsed = JSON.parse(raw);
|
|
2562
2562
|
if (!Array.isArray(parsed)) {
|
|
2563
2563
|
console.warn("[partners] partner-roles.json is not an array \u2014 skipping");
|
|
@@ -2800,6 +2800,15 @@ var init_intro = __esm({
|
|
|
2800
2800
|
}
|
|
2801
2801
|
});
|
|
2802
2802
|
|
|
2803
|
+
// ../../packages/core/src/directoryThreshold.ts
|
|
2804
|
+
var STRONG_MATCH_THRESHOLD;
|
|
2805
|
+
var init_directoryThreshold = __esm({
|
|
2806
|
+
"../../packages/core/src/directoryThreshold.ts"() {
|
|
2807
|
+
"use strict";
|
|
2808
|
+
STRONG_MATCH_THRESHOLD = 0.58;
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2803
2812
|
// ../../packages/core/src/index.ts
|
|
2804
2813
|
var src_exports = {};
|
|
2805
2814
|
__export(src_exports, {
|
|
@@ -2819,6 +2828,7 @@ __export(src_exports, {
|
|
|
2819
2828
|
INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
|
|
2820
2829
|
INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
|
|
2821
2830
|
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
2831
|
+
STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
|
|
2822
2832
|
SYNONYMS: () => SYNONYMS,
|
|
2823
2833
|
VOCABULARY: () => VOCABULARY,
|
|
2824
2834
|
VOCAB_NODES: () => VOCAB_NODES,
|
|
@@ -2887,6 +2897,7 @@ var init_src = __esm({
|
|
|
2887
2897
|
init_partners();
|
|
2888
2898
|
init_github();
|
|
2889
2899
|
init_intro();
|
|
2900
|
+
init_directoryThreshold();
|
|
2890
2901
|
}
|
|
2891
2902
|
});
|
|
2892
2903
|
|
|
@@ -2910,13 +2921,13 @@ import {
|
|
|
2910
2921
|
randomBytes
|
|
2911
2922
|
} from "crypto";
|
|
2912
2923
|
import {
|
|
2913
|
-
readFileSync as
|
|
2914
|
-
writeFileSync,
|
|
2915
|
-
mkdirSync,
|
|
2924
|
+
readFileSync as readFileSync3,
|
|
2925
|
+
writeFileSync as writeFileSync2,
|
|
2926
|
+
mkdirSync as mkdirSync2,
|
|
2916
2927
|
existsSync
|
|
2917
2928
|
} from "fs";
|
|
2918
|
-
import { join as
|
|
2919
|
-
import { homedir } from "os";
|
|
2929
|
+
import { join as join3 } from "path";
|
|
2930
|
+
import { homedir as homedir2 } from "os";
|
|
2920
2931
|
async function loadKey() {
|
|
2921
2932
|
try {
|
|
2922
2933
|
const kt = await import("keytar");
|
|
@@ -2929,12 +2940,12 @@ async function loadKey() {
|
|
|
2929
2940
|
return key2;
|
|
2930
2941
|
} catch {
|
|
2931
2942
|
}
|
|
2932
|
-
|
|
2943
|
+
mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
|
|
2933
2944
|
if (existsSync(KEY_FILE)) {
|
|
2934
|
-
return Buffer.from(
|
|
2945
|
+
return Buffer.from(readFileSync3(KEY_FILE, "utf8").trim(), "hex");
|
|
2935
2946
|
}
|
|
2936
2947
|
const key = randomBytes(KEY_BYTES);
|
|
2937
|
-
|
|
2948
|
+
writeFileSync2(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
2938
2949
|
return key;
|
|
2939
2950
|
}
|
|
2940
2951
|
function encrypt(plaintext, key) {
|
|
@@ -2995,7 +3006,7 @@ async function readProfile() {
|
|
|
2995
3006
|
if (!existsSync(PROFILE_FILE)) return blankProfile();
|
|
2996
3007
|
try {
|
|
2997
3008
|
const key = await loadKey();
|
|
2998
|
-
const raw =
|
|
3009
|
+
const raw = readFileSync3(PROFILE_FILE, "utf8");
|
|
2999
3010
|
const blob = JSON.parse(raw);
|
|
3000
3011
|
const plaintext = decrypt(blob, key);
|
|
3001
3012
|
const parsed = JSON.parse(plaintext);
|
|
@@ -3006,12 +3017,12 @@ async function readProfile() {
|
|
|
3006
3017
|
}
|
|
3007
3018
|
}
|
|
3008
3019
|
async function writeProfile(profile) {
|
|
3009
|
-
|
|
3020
|
+
mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
|
|
3010
3021
|
const key = await loadKey();
|
|
3011
3022
|
profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3012
3023
|
profile.skillTags = deriveSkillTags(profile.tagWeights);
|
|
3013
3024
|
const blob = encrypt(JSON.stringify(profile), key);
|
|
3014
|
-
|
|
3025
|
+
writeFileSync2(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
3015
3026
|
}
|
|
3016
3027
|
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
3017
3028
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3095,14 +3106,14 @@ function profileToFingerprint(profile) {
|
|
|
3095
3106
|
}
|
|
3096
3107
|
};
|
|
3097
3108
|
}
|
|
3098
|
-
var
|
|
3109
|
+
var TERMINALHIRE_DIR2, PROFILE_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, DECAY_HALF_LIFE_MS, LANGUAGE_TAGS, MIN_FINGERPRINT_SCORE;
|
|
3099
3110
|
var init_profile = __esm({
|
|
3100
3111
|
"src/profile.ts"() {
|
|
3101
3112
|
"use strict";
|
|
3102
3113
|
init_src();
|
|
3103
|
-
|
|
3104
|
-
PROFILE_FILE =
|
|
3105
|
-
KEY_FILE =
|
|
3114
|
+
TERMINALHIRE_DIR2 = join3(homedir2(), ".terminalhire");
|
|
3115
|
+
PROFILE_FILE = join3(TERMINALHIRE_DIR2, "profile.enc");
|
|
3116
|
+
KEY_FILE = join3(TERMINALHIRE_DIR2, "key");
|
|
3106
3117
|
ALGO = "aes-256-gcm";
|
|
3107
3118
|
KEY_BYTES = 32;
|
|
3108
3119
|
IV_BYTES = 12;
|
|
@@ -3131,24 +3142,20 @@ var init_profile = __esm({
|
|
|
3131
3142
|
});
|
|
3132
3143
|
|
|
3133
3144
|
// bin/jpi-devs.js
|
|
3134
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3135
|
-
import { join as join3 } from "path";
|
|
3136
|
-
import { homedir as homedir2 } from "os";
|
|
3137
3145
|
import { createInterface } from "readline";
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3146
|
+
|
|
3147
|
+
// bin/directory.js
|
|
3148
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3149
|
+
import { join } from "path";
|
|
3150
|
+
import { homedir } from "os";
|
|
3151
|
+
var TERMINALHIRE_DIR = join(homedir(), ".terminalhire");
|
|
3152
|
+
var DIRECTORY_CACHE_FILE = join(TERMINALHIRE_DIR, "directory-cache.json");
|
|
3153
|
+
var PROJECT_FILE = join(TERMINALHIRE_DIR, "project.json");
|
|
3141
3154
|
var INDEX_TTL_MS = 15 * 60 * 1e3;
|
|
3142
3155
|
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
3143
|
-
var DEFAULT_LIMIT = 10;
|
|
3144
|
-
var args = process.argv.slice(2);
|
|
3145
|
-
var limitArg = args.indexOf("--limit");
|
|
3146
|
-
var LIMIT = limitArg !== -1 ? parseInt(args[limitArg + 1] ?? "10", 10) : DEFAULT_LIMIT;
|
|
3147
|
-
var SHOW_ALL = args.includes("--all");
|
|
3148
|
-
var AS_PROJECT = args.includes("--as-project");
|
|
3149
3156
|
function readDirectoryCache() {
|
|
3150
3157
|
try {
|
|
3151
|
-
const entry = JSON.parse(
|
|
3158
|
+
const entry = JSON.parse(readFileSync(DIRECTORY_CACHE_FILE, "utf8"));
|
|
3152
3159
|
if (typeof entry.ts === "number" && Number.isFinite(entry.ts) && Date.now() - entry.ts < INDEX_TTL_MS) {
|
|
3153
3160
|
return { index: entry.index, ts: entry.ts };
|
|
3154
3161
|
}
|
|
@@ -3158,12 +3165,12 @@ function readDirectoryCache() {
|
|
|
3158
3165
|
}
|
|
3159
3166
|
}
|
|
3160
3167
|
function writeDirectoryCache(index) {
|
|
3161
|
-
|
|
3162
|
-
|
|
3168
|
+
mkdirSync(TERMINALHIRE_DIR, { recursive: true });
|
|
3169
|
+
writeFileSync(DIRECTORY_CACHE_FILE, JSON.stringify({ ts: Date.now(), index }), "utf8");
|
|
3163
3170
|
}
|
|
3164
3171
|
function readProject() {
|
|
3165
3172
|
try {
|
|
3166
|
-
return JSON.parse(
|
|
3173
|
+
return JSON.parse(readFileSync(PROJECT_FILE, "utf8"));
|
|
3167
3174
|
} catch {
|
|
3168
3175
|
return null;
|
|
3169
3176
|
}
|
|
@@ -3174,13 +3181,13 @@ function relativeTime(ts) {
|
|
|
3174
3181
|
const mins = Math.round(secs / 60);
|
|
3175
3182
|
return mins < 60 ? `${mins}m ago` : `${Math.round(mins / 60)}h ago`;
|
|
3176
3183
|
}
|
|
3177
|
-
async function fetchDirectory() {
|
|
3184
|
+
async function fetchDirectory({ quiet = false } = {}) {
|
|
3178
3185
|
const cached = readDirectoryCache();
|
|
3179
3186
|
if (cached) {
|
|
3180
|
-
console.log(`\u2713 Using cached directory (updated ${relativeTime(cached.ts)})`);
|
|
3187
|
+
if (!quiet) console.log(`\u2713 Using cached directory (updated ${relativeTime(cached.ts)})`);
|
|
3181
3188
|
return cached.index;
|
|
3182
3189
|
}
|
|
3183
|
-
console.log(`\u21BB Refreshing builder directory from ${API_URL}/api/directory...`);
|
|
3190
|
+
if (!quiet) console.log(`\u21BB Refreshing builder directory from ${API_URL}/api/directory...`);
|
|
3184
3191
|
const res = await fetch(`${API_URL}/api/directory`, { signal: AbortSignal.timeout(1e4) });
|
|
3185
3192
|
if (!res.ok) throw new Error(`/api/directory returned ${res.status}`);
|
|
3186
3193
|
const index = await res.json();
|
|
@@ -3195,7 +3202,7 @@ function reportMatched(results, fetchImpl = fetch) {
|
|
|
3195
3202
|
)
|
|
3196
3203
|
];
|
|
3197
3204
|
if (logins.length === 0) return;
|
|
3198
|
-
Promise.resolve(
|
|
3205
|
+
return Promise.resolve(
|
|
3199
3206
|
fetchImpl(`${API_URL}/api/directory/matched`, {
|
|
3200
3207
|
method: "POST",
|
|
3201
3208
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3207,6 +3214,15 @@ function reportMatched(results, fetchImpl = fetch) {
|
|
|
3207
3214
|
} catch {
|
|
3208
3215
|
}
|
|
3209
3216
|
}
|
|
3217
|
+
|
|
3218
|
+
// bin/jpi-devs.js
|
|
3219
|
+
var API_URL2 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
3220
|
+
var DEFAULT_LIMIT = 10;
|
|
3221
|
+
var args = process.argv.slice(2);
|
|
3222
|
+
var limitArg = args.indexOf("--limit");
|
|
3223
|
+
var LIMIT = limitArg !== -1 ? parseInt(args[limitArg + 1] ?? "10", 10) : DEFAULT_LIMIT;
|
|
3224
|
+
var SHOW_ALL = args.includes("--all");
|
|
3225
|
+
var AS_PROJECT = args.includes("--as-project");
|
|
3210
3226
|
function prompt(question) {
|
|
3211
3227
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3212
3228
|
return new Promise((resolve) => {
|
|
@@ -3218,7 +3234,7 @@ function prompt(question) {
|
|
|
3218
3234
|
}
|
|
3219
3235
|
function credentialUrl(job) {
|
|
3220
3236
|
const u = job.url ?? "";
|
|
3221
|
-
return u.startsWith("http") ? u : `${
|
|
3237
|
+
return u.startsWith("http") ? u : `${API_URL2}${u}`;
|
|
3222
3238
|
}
|
|
3223
3239
|
function linkTitle(title, url) {
|
|
3224
3240
|
const isTTY = process.stdout.isTTY;
|