terminalhire 0.2.2 → 0.2.4

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/install.js CHANGED
@@ -1,26 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * install.js — v3.1 installer for terminalhire
3
+ * install.js — installer for terminalhire (ambient spinner job surface)
4
4
  *
5
5
  * ToS / safety guardrails (binding — see CONSTITUTION.md Rule 7 and Rule 12):
6
- * - ONLY uses the official Claude Code `statusLine` settings.json hook.
6
+ * - ONLY uses the official Claude Code spinner settings (spinnerVerbs +
7
+ * spinnerTipsOverride) via the spinner module. NEVER writes settings.statusLine.
7
8
  * - NEVER silently modifies ~/.claude/settings.json.
8
9
  * - Always backs up settings.json (timestamped) before any write.
9
- * - Prints a clear disclosure of exactly what it changed.
10
- * - Provides one-command uninstall that restores the backup.
11
- * - CHAIN, never clobber: if a statusLine already exists that is NOT ours,
12
- * creates a wrapper script that runs BOTH commands, then points settings.json
13
- * at the wrapper. Idempotent — detects existing wrapper and does not double-wrap.
10
+ * - Prints a clear disclosure of exactly what it changes.
11
+ * - Requires an explicit typed "yes" before touching any system file.
12
+ * - Provides one-command uninstall that clears our spinner verbs + tips.
14
13
  * - postinstall (not this file) is print-only and never calls this file.
15
14
  *
16
15
  * What it does:
17
- * 1. Prints full v3 disclosure
16
+ * 1. Prints full disclosure (ambient spinner job surface only)
18
17
  * 2. Requires explicit "yes" before touching any system file
19
18
  * 3. Backs up ~/.claude/settings.json (timestamped)
20
- * 4. If no existing statusLine sets terminalhire's nudge directly
21
- * If existing statusLine (not ours) builds a wrapper that chains both
22
- * 5. Supports --uninstall (restores the backup / removes wrapper)
23
- * 7. Idempotent: safe to run multiple times
19
+ * 4. Enables the ambient spinner job surface (spinnerVerbs + spinnerTipsOverride)
20
+ * 5. Supports --uninstall (clears our spinner verbs + tips, disables spinner)
21
+ * 6. Idempotent: safe to run multiple times
24
22
  *
25
23
  * Usage:
26
24
  * node install.js
@@ -28,29 +26,22 @@
28
26
  */
29
27
 
30
28
  import {
31
- readFileSync, writeFileSync, copyFileSync, existsSync,
32
- mkdirSync, chmodSync, readdirSync,
29
+ readFileSync, writeFileSync, copyFileSync, existsSync, mkdirSync,
33
30
  } from 'node:fs';
34
31
  import { homedir } from 'node:os';
35
32
  import { join, resolve, dirname } from 'node:path';
36
33
  import { fileURLToPath, pathToFileURL } from 'node:url';
37
34
  import { createInterface } from 'node:readline';
38
- import { spawnSync } from 'node:child_process';
39
35
 
40
36
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
41
37
 
42
- // Resolve the nudge bin robustly: prefer the bundled dist output (published package),
43
- // fall back to the legacy bin/ path for in-workspace / development installs.
44
- const _distBin = resolve(join(__dirname, 'dist', 'bin', 'jpi.js'));
45
- const _legacyBin = resolve(join(__dirname, 'bin', 'jpi.js'));
46
- const BIN_PATH = existsSync(_distBin) ? _distBin : _legacyBin;
47
-
48
38
  const SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
49
39
  const SETTINGS_DIR = dirname(SETTINGS_PATH);
50
40
  const TERMINALHIRE_DIR = join(homedir(), '.terminalhire');
51
- const WRAPPER_PATH = join(TERMINALHIRE_DIR, 'statusline-wrapper.sh');
52
41
  const CONFIG_FILE = join(TERMINALHIRE_DIR, 'config.json');
53
42
 
43
+ const UNINSTALL = process.argv.includes('--uninstall');
44
+
54
45
  // Resolve the spinner module (dist preferred; bin fallback for the dev workspace).
55
46
  async function loadSpinnerModule() {
56
47
  const candidates = [
@@ -77,27 +68,8 @@ function patchConfig(patch) {
77
68
  writeFileSync(CONFIG_FILE, JSON.stringify({ ...cfg, ...patch }, null, 2) + '\n', 'utf8');
78
69
  }
79
70
 
80
- // The existing statusLine command on the user's machine that we must preserve
81
- const KNOWN_EXISTING_STATUSLINE = 'bash /Users/ericgang/.claude/statusline-command.sh';
82
-
83
- const UNINSTALL = process.argv.includes('--uninstall');
84
-
85
- // ── Sentinel comment embedded in the wrapper so we can detect our own wrappers
86
- const WRAPPER_SENTINEL = '# terminalhire-wrapper-v1';
87
-
88
71
  // ── Helpers ───────────────────────────────────────────────────────────────────
89
72
 
90
- function readSettings() {
91
- if (!existsSync(SETTINGS_PATH)) return {};
92
- try {
93
- return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8'));
94
- } catch {
95
- console.error('Error: ~/.claude/settings.json exists but is not valid JSON.');
96
- console.error('Please fix it manually before running this installer.');
97
- process.exit(1);
98
- }
99
- }
100
-
101
73
  function backupSettings() {
102
74
  if (!existsSync(SETTINGS_PATH)) return null;
103
75
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
@@ -107,11 +79,6 @@ function backupSettings() {
107
79
  return backupPath;
108
80
  }
109
81
 
110
- function writeSettings(settings) {
111
- mkdirSync(SETTINGS_DIR, { recursive: true });
112
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n', 'utf8');
113
- }
114
-
115
82
  function ask(question) {
116
83
  const rl = createInterface({ input: process.stdin, output: process.stdout });
117
84
  return new Promise(res => {
@@ -122,124 +89,32 @@ function ask(question) {
122
89
  });
123
90
  }
124
91
 
125
- /** Build the direct (no-chain) statusLine entry for terminalhire. */
126
- function buildDirectEntry(binPath) {
127
- return `node ${binPath}`;
128
- }
129
-
130
- /**
131
- * Returns true if the given statusLine value is terminalhire's own entry:
132
- * either the direct node entry or our wrapper script.
133
- */
134
- function isOurEntry(statusLine) {
135
- if (!statusLine) return false;
136
- // Direct entry
137
- if (statusLine === buildDirectEntry(BIN_PATH)) return true;
138
- // Points at our wrapper
139
- if (statusLine === `bash ${WRAPPER_PATH}`) return true;
140
- // Wrapper exists and contains our sentinel
141
- if (existsSync(WRAPPER_PATH)) {
142
- try {
143
- const wrapperContent = readFileSync(WRAPPER_PATH, 'utf8');
144
- return wrapperContent.includes(WRAPPER_SENTINEL);
145
- } catch { /* fall through */ }
146
- }
147
- return false;
148
- }
149
-
150
- /**
151
- * Normalize a statusLine value that may be a string or an object
152
- * (e.g. {type:"command", command:"..."} from Claude Code's settings).
153
- * Returns the shell command string, or null if the value is unusable.
154
- */
155
- function extractCommand(statusLine) {
156
- if (!statusLine) return null;
157
- if (typeof statusLine === 'string') return statusLine;
158
- // Object form: {type: "command", command: "..."}
159
- if (typeof statusLine === 'object' && typeof statusLine.command === 'string') {
160
- return statusLine.command;
161
- }
162
- return null;
163
- }
164
-
165
- /**
166
- * Build the chaining wrapper shell script.
167
- * Both the existing command and the terminalhire nudge receive the SAME stdin JSON.
168
- * Output: existing command's output first, then terminalhire's output.
169
- * existingCmd MUST be a plain string (call extractCommand first).
170
- */
171
- function buildWrapper(existingCmd) {
172
- return `#!/usr/bin/env bash
173
- ${WRAPPER_SENTINEL}
174
- # Chains the original statusLine command with terminalhire's nudge.
175
- # Both receive the same stdin JSON. Original output prints first, then terminalhire.
176
- # Generated by: node install.js
177
- # Existing command: ${existingCmd}
178
- # terminalhire nudge: node ${BIN_PATH}
179
-
180
- # Read stdin once into a variable
181
- INPUT=$(cat)
182
-
183
- # Run the existing statusLine command
184
- EXISTING_OUT=$(printf '%s' "$INPUT" | ${existingCmd} 2>/dev/null || true)
185
-
186
- # Run terminalhire's nudge
187
- TH_OUT=$(printf '%s' "$INPUT" | node ${BIN_PATH} 2>/dev/null || true)
188
-
189
- # Print existing output (if any), then terminalhire output (if any)
190
- if [ -n "$EXISTING_OUT" ]; then
191
- printf '%s\\n' "$EXISTING_OUT"
192
- fi
193
- if [ -n "$TH_OUT" ]; then
194
- printf '%s\\n' "$TH_OUT"
195
- fi
196
- `;
197
- }
198
-
199
- /**
200
- * Find the most recent terminalhire backup of settings.json.
201
- * Returns the path or null.
202
- */
203
- function findLatestBackup() {
204
- try {
205
- const files = readdirSync(SETTINGS_DIR)
206
- .filter(f => f.startsWith('settings.json.terminalhire-backup-'))
207
- .sort()
208
- .reverse();
209
- if (files.length === 0) return null;
210
- return join(SETTINGS_DIR, files[0]);
211
- } catch {
212
- return null;
213
- }
214
- }
215
-
216
92
  // ── Install ───────────────────────────────────────────────────────────────────
217
93
 
218
94
  async function install() {
219
95
  console.log('');
220
96
  console.log('┌─────────────────────────────────────────────────────────────────┐');
221
- console.log('│ terminalhire v0.1.1 install statusLine hook │');
222
- console.log('│ Pull your matches. Your profile stays on-device. │');
97
+ console.log('│ terminalhire — enable the ambient spinner job surface │');
98
+ console.log('│ Pull your matches. Your profile stays on-device. │');
223
99
  console.log('└─────────────────────────────────────────────────────────────────┘');
224
100
  console.log('');
225
101
  console.log('DISCLOSURE — read before installing');
226
102
  console.log('');
227
- console.log('HOW IT WORKS (v3 pull model):');
103
+ console.log('HOW IT WORKS (pull model):');
228
104
  console.log(' 1. `terminalhire jobs` downloads an anonymous job index from the server');
229
105
  console.log(' (GET /api/index — no dev data in the request).');
230
106
  console.log(' 2. Matching runs LOCALLY against an encrypted profile on your device.');
231
- console.log(' 3. The status bar shows a nudge when matches exist.');
107
+ console.log(' 3. The ambient spinner surfaces your top matches while Claude works.');
232
108
  console.log(' It reads only local files and makes zero network calls.');
233
- console.log(' 4. Nudge frequency: configurable via `terminalhire config --nudge`.');
234
- console.log(' Default: once per session. Options: always | every:N.');
235
109
  console.log('');
236
- console.log(' 5. SPINNER JOB SURFACE (enabled by this install):');
110
+ console.log(' AMBIENT SPINNER JOB SURFACE (enabled by this install):');
237
111
  console.log(' While Claude is working, the spinner line shows your top LOCAL job');
238
112
  console.log(' matches, e.g. Senior Backend Engineer @ Stripe · 82% …');
239
113
  console.log(' The "tip" line below it shows a ⌘-clickable link to open the listing');
240
114
  console.log(' (a terminalhire.com/j/… redirect — clicks are logged anonymously, no');
241
115
  console.log(' profile data). Uses official `spinnerVerbs`/`spinnerTipsOverride` settings');
242
116
  console.log(' (no patching, Rule 7). Computed locally, zero egress — only public job text.');
117
+ console.log(' This install does NOT write any statusLine — the spinner is the only surface.');
243
118
  console.log(' Turn it off any time: terminalhire spinner --off');
244
119
  console.log('');
245
120
  console.log('YOUR LOCAL PROFILE (~/.terminalhire/profile.enc):');
@@ -263,44 +138,20 @@ async function install() {
263
138
  console.log(' • GitHub data enriches your LOCAL profile — no data leaves the machine');
264
139
  console.log(' unless you consent to include GitHub fields in a specific terminalhire lead.');
265
140
  console.log('');
266
- console.log('HOW TO DISABLE / DELETE:');
267
- console.log(' • Uninstall statusLine: node install.js --uninstall');
268
- console.log(' Delete local profile: terminalhire profile --delete');
269
- console.log(' • Clear GitHub token: terminalhire logout');
270
- console.log(' • Wipe everything: rm -rf ~/.terminalhire');
141
+ console.log('WHAT THIS INSTALL CHANGES:');
142
+ console.log(' • ~/.claude/settings.json enables the spinner job surface only');
143
+ console.log(' (spinnerVerbs + spinnerTipsOverride). It does NOT write statusLine.');
144
+ console.log(' • A timestamped backup is created before any change.');
271
145
  console.log('');
272
-
273
- const settings = readSettings();
274
- // Normalize: settings.statusLine may be a string or an object {type,command}
275
- const currentStatusLine = extractCommand(settings.statusLine);
276
-
277
- // Already installed?
278
- if (isOurEntry(currentStatusLine)) {
279
- console.log('Already installed (statusLine is already terminalhire or our wrapper).');
280
- console.log('');
281
- console.log('To change nudge frequency: terminalhire config --nudge <session|always|every:N>');
282
- console.log('To uninstall: node install.js --uninstall');
283
- console.log('');
284
- return;
285
- }
286
-
287
- // Determine install strategy
288
- let installStrategy;
289
- if (!currentStatusLine) {
290
- installStrategy = 'direct';
291
- console.log(' No existing statusLine found.');
292
- console.log(` Will set: statusLine = "node ${BIN_PATH}"`);
293
- } else {
294
- installStrategy = 'chain';
295
- console.log(` Existing statusLine detected: ${currentStatusLine}`);
296
- console.log(' Will create a wrapper at: ~/.terminalhire/statusline-wrapper.sh');
297
- console.log(' The wrapper runs BOTH commands (existing first, terminalhire second).');
298
- console.log(' Both receive the same stdin JSON from Claude Code.');
299
- console.log(` Will set: statusLine = "bash ${WRAPPER_PATH}"`);
300
- }
146
+ console.log('HOW TO DISABLE / DELETE:');
147
+ console.log(' • Disable spinner surface: node install.js --uninstall');
148
+ console.log(' • (or) terminalhire spinner --off');
149
+ console.log(' • Delete local profile: terminalhire profile --delete');
150
+ console.log(' • Clear GitHub token: terminalhire logout');
151
+ console.log(' • Wipe everything: rm -rf ~/.terminalhire');
301
152
  console.log('');
302
153
 
303
- const installAnswer = await ask('Install terminalhire statusLine hook? Type "yes" to continue: ');
154
+ const installAnswer = await ask('Enable the terminalhire ambient spinner job surface? Type "yes" to continue: ');
304
155
  if (installAnswer !== 'yes') {
305
156
  console.log('\nAborted — nothing was changed.');
306
157
  process.exit(0);
@@ -309,35 +160,10 @@ async function install() {
309
160
  console.log('');
310
161
  const backupPath = backupSettings();
311
162
 
312
- if (installStrategy === 'direct') {
313
- // Claude Code requires statusLine as an object { type, command } — NOT a bare
314
- // string (a string fails schema validation and makes Claude skip ALL settings).
315
- settings.statusLine = { type: 'command', command: buildDirectEntry(BIN_PATH) };
316
- writeSettings(settings);
317
- console.log(' Set statusLine in ~/.claude/settings.json');
318
- console.log(` → node ${BIN_PATH}`);
319
- } else {
320
- // Write wrapper
321
- mkdirSync(TERMINALHIRE_DIR, { recursive: true });
322
- const wrapperContent = buildWrapper(currentStatusLine);
323
- writeFileSync(WRAPPER_PATH, wrapperContent, 'utf8');
324
- chmodSync(WRAPPER_PATH, 0o755);
325
- console.log(` Created wrapper: ${WRAPPER_PATH}`);
326
-
327
- // Object form { type, command } — a bare string breaks Claude Code's settings schema.
328
- settings.statusLine = { type: 'command', command: `bash ${WRAPPER_PATH}` };
329
- writeSettings(settings);
330
- console.log(' Updated statusLine in ~/.claude/settings.json');
331
- console.log(` → bash ${WRAPPER_PATH}`);
332
- console.log(' (chains existing command + terminalhire nudge)');
333
- }
334
-
335
- if (backupPath) {
336
- console.log(` Backup: ${backupPath}`);
337
- }
338
-
339
163
  // Enable the spinner job surface as part of this single consented install
340
- // (Rule 10, amended: install IS the opt-in). Best-effort never blocks install.
164
+ // (Rule 12: disclosure + timestamped backup + typed "yes" above). The spinner
165
+ // module is the ONLY writer of ~/.claude/settings.json here — verbs + tips only.
166
+ let enabled = false;
341
167
  try {
342
168
  patchConfig({ spinner: { enabled: true, mode: 'replace', max: 6 } });
343
169
  const spinnerMod = await loadSpinnerModule();
@@ -347,33 +173,55 @@ async function install() {
347
173
  const cache = JSON.parse(readFileSync(join(TERMINALHIRE_DIR, 'index-cache.json'), 'utf8'));
348
174
  if (Array.isArray(cache.topMatches)) topMatches = cache.topMatches;
349
175
  } catch { /* no cache yet — monitor will populate it */ }
176
+
350
177
  const verbs = spinnerMod.buildSpinnerPool(topMatches, 6);
351
178
  if (verbs.length > 0) spinnerMod.applySpinnerVerbs(verbs, 'replace');
179
+
180
+ // Tips line: ⌘-clickable listing links for the same top matches.
181
+ let tipsCount = 0;
182
+ try {
183
+ const tips = spinnerMod.buildTips(topMatches, 'https://terminalhire.com', 8);
184
+ if (Array.isArray(tips) && tips.length > 0) {
185
+ spinnerMod.applySpinnerTips(tips);
186
+ tipsCount = tips.length;
187
+ }
188
+ } catch { /* tips are best-effort */ }
189
+
190
+ enabled = true;
352
191
  console.log(
353
192
  ' Spinner job surface: ENABLED' +
354
193
  (verbs.length
355
194
  ? ` (${verbs.length} match${verbs.length === 1 ? '' : 'es'} live now)`
356
195
  : ' (matches appear after the first background refresh)')
357
196
  );
197
+ if (tipsCount > 0) {
198
+ console.log(` Tip links: ${tipsCount} ⌘-clickable listing${tipsCount === 1 ? '' : 's'}`);
199
+ }
358
200
  console.log(' Turn off any time: terminalhire spinner --off');
359
201
  }
360
202
  } catch { /* spinner is best-effort; never block the install */ }
361
203
 
204
+ if (backupPath) {
205
+ console.log(` Backup: ${backupPath}`);
206
+ }
207
+
208
+ if (!enabled) {
209
+ console.log(' Could not load the spinner module — nothing was enabled.');
210
+ console.log(' Run `terminalhire spinner --on` after building, or reinstall the package.');
211
+ }
212
+
362
213
  console.log('');
363
- console.log('Done. Restart Claude Code to activate the status bar nudge.');
364
- console.log('');
365
- console.log(' Status bar format (default: once per session, only when matches exist):');
366
- console.log(' ✦ N roles match your current work — run: terminalhire jobs');
214
+ console.log('Done. Restart Claude Code to activate the ambient spinner job surface.');
367
215
  console.log('');
368
- console.log(' Nudge frequency:');
369
- console.log(' terminalhire config --nudge session (default once per session)');
370
- console.log(' terminalhire config --nudge always (every statusLine render)');
371
- console.log(' terminalhire config --nudge every:N (every Nth render)');
216
+ console.log(' While Claude works, the spinner shows your top LOCAL matches, e.g.');
217
+ console.log(' Senior Backend Engineer @ Stripe · 82% ');
218
+ console.log(' with a ⌘-clickable tip link to open the listing.');
372
219
  console.log('');
373
220
  console.log(' Other commands:');
374
221
  console.log(' terminalhire login — sign in with GitHub (enriches profile instantly)');
375
222
  console.log(' terminalhire logout — clear GitHub token');
376
223
  console.log(' terminalhire jobs — fetch index, match locally, browse roles');
224
+ console.log(' terminalhire spinner --off — disable the ambient spinner surface');
377
225
  console.log(' terminalhire profile --show — inspect your encrypted profile');
378
226
  console.log(' terminalhire profile --edit — set displayName, contactEmail, prefs');
379
227
  console.log(' terminalhire profile --delete — wipe profile and key');
@@ -386,68 +234,41 @@ async function uninstall() {
386
234
  console.log('');
387
235
  console.log('terminalhire uninstall');
388
236
  console.log('');
389
-
390
- const settings = readSettings();
391
-
392
- if (!settings.statusLine) {
393
- console.log('No statusLine entry found — nothing to remove.');
394
- process.exit(0);
395
- }
396
-
397
- console.log(` Current statusLine: ${settings.statusLine}`);
398
-
399
- // Check if there is a backup to restore
400
- const latestBackup = findLatestBackup();
401
- if (latestBackup) {
402
- console.log(` Backup found: ${latestBackup}`);
403
- console.log(' Uninstalling will restore this backup (restoring original statusLine).');
404
- } else {
405
- console.log(' No backup found — statusLine entry will be deleted (set to none).');
406
- }
237
+ console.log(' This disables the ambient spinner job surface and clears the spinner');
238
+ console.log(' verbs + tips terminalhire added (any verbs/tips you set yourself are kept).');
239
+ console.log(' It does NOT touch settings.statusLine.');
407
240
  console.log('');
408
241
 
409
- const answer = await ask('Uninstall terminalhire hook? Type "yes" to continue: ');
242
+ const answer = await ask('Disable the terminalhire spinner job surface? Type "yes" to continue: ');
410
243
  if (answer !== 'yes') {
411
244
  console.log('\nAborted — nothing was changed.');
412
245
  process.exit(0);
413
246
  }
414
247
 
415
248
  console.log('');
416
-
417
- if (latestBackup) {
418
- // Restore the backup
419
- copyFileSync(latestBackup, SETTINGS_PATH);
420
- console.log(` Restored settings from backup: ${latestBackup}`);
421
- } else {
422
- // Just remove the statusLine key
423
- backupSettings();
424
- delete settings.statusLine;
425
- writeSettings(settings);
426
- console.log(' Removed statusLine from ~/.claude/settings.json');
427
- }
428
-
429
- // Remove wrapper if it exists and is ours
430
- if (existsSync(WRAPPER_PATH)) {
431
- try {
432
- const wrapperContent = readFileSync(WRAPPER_PATH, 'utf8');
433
- if (wrapperContent.includes(WRAPPER_SENTINEL)) {
434
- // We don't delete it automatically — user may have customized it
435
- console.log(` Wrapper file left in place: ${WRAPPER_PATH}`);
436
- console.log(' (Remove manually if desired: rm ~/.terminalhire/statusline-wrapper.sh)');
437
- }
438
- } catch { /* ignore */ }
249
+ const backupPath = backupSettings();
250
+ if (backupPath) {
251
+ console.log(` Backup: ${backupPath}`);
439
252
  }
440
253
 
441
- // Remove the spinner job verbs we injected (preserving any the user set themselves).
254
+ // Remove the spinner job verbs + tips we injected (preserving any the user set
255
+ // themselves) and disable the surface in our config.
442
256
  try {
443
257
  patchConfig({ spinner: { enabled: false } });
444
258
  const spinnerMod = await loadSpinnerModule();
445
259
  if (spinnerMod) {
446
- const res = spinnerMod.clearSpinnerVerbs();
260
+ const vres = spinnerMod.clearSpinnerVerbs();
447
261
  console.log(
448
262
  ' Removed spinner job verbs' +
449
- (res && res.keptUserVerbs ? ` (kept ${res.keptUserVerbs} of your own)` : '') + '.'
263
+ (vres && vres.keptUserVerbs ? ` (kept ${vres.keptUserVerbs} of your own)` : '') + '.'
450
264
  );
265
+ try {
266
+ const tres = spinnerMod.clearSpinnerTips();
267
+ console.log(
268
+ ' Removed spinner tip links' +
269
+ (tres && tres.keptUserTips ? ` (kept ${tres.keptUserTips} of your own)` : '') + '.'
270
+ );
271
+ } catch { /* tips clear is best-effort */ }
451
272
  }
452
273
  } catch { /* best-effort */ }
453
274
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.2.2",
4
- "description": "Local-first job matching for developers — Claude Code statusLine nudge and ambient spinner job surface",
3
+ "version": "0.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",
7
7
  "url": "git+https://github.com/staqsIO/terminalhire.git"
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "scripts": {
29
29
  "build": "tsup",
30
- "bundle:plugin": "npm run build && rm -rf ../../plugins/terminalhire/dist && cp -R dist ../../plugins/terminalhire/dist && cp package.json ../../plugins/terminalhire/dist/package.json",
30
+ "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 postinstall.js ../../plugins/terminalhire/dist/postinstall.js",
31
31
  "prepublishOnly": "npm run build",
32
32
  "install-hook": "node install.js",
33
33
  "postinstall": "node ./postinstall.js"