@timurtekb/tekjobs 0.31.0 → 0.31.1

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/CHANGELOG.md CHANGED
@@ -2,15 +2,14 @@
2
2
 
3
3
  Every release, newest first. Written by semantic-release from the commit messages.
4
4
 
5
- ## 0.31.0 (2026-10-07)
5
+ ## <small>0.31.1 (2026-10-07)</small>
6
6
 
7
- * fix(cli): servers stay up until you stop them; tekjobs up, down and ps ([4cb10b6](https://github.com/Timurtek/tekjobs/commit/4cb10b6))
8
- * chore(release): v0.31.0 [skip ci] ([650d966](https://github.com/Timurtek/tekjobs/commit/650d966))
9
- * feat(cli): tekjobs serve opens the app and leaves when the app is closed ([91a584d](https://github.com/Timurtek/tekjobs/commit/91a584d))
10
- * docs(site): a square cut of the showreel ([26a4f3c](https://github.com/Timurtek/tekjobs/commit/26a4f3c))
7
+ * fix(mail): a recruiter's "401(k)" no longer reads as a signed-out CLI ([8610854](https://github.com/Timurtek/tekjobs/commit/8610854))
8
+ * chore(changelog): one entry for 0.31.0, not the one a failed run left behind ([538df22](https://github.com/Timurtek/tekjobs/commit/538df22))
11
9
 
12
10
  ## 0.31.0 (2026-10-07)
13
11
 
12
+ * fix(cli): servers stay up until you stop them; tekjobs up, down and ps ([4cb10b6](https://github.com/Timurtek/tekjobs/commit/4cb10b6))
14
13
  * feat(cli): tekjobs serve opens the app and leaves when the app is closed ([91a584d](https://github.com/Timurtek/tekjobs/commit/91a584d))
15
14
  * docs(site): a square cut of the showreel ([26a4f3c](https://github.com/Timurtek/tekjobs/commit/26a4f3c))
16
15
 
@@ -214,17 +214,37 @@ export function runLLM(prompt, { timeoutMs = 240000, extraArgs = [] } = {}) {
214
214
  child.on('error', (e) => { clearTimeout(timer); reject(new Error(`Could not start "${command}": ${e.message}`)); });
215
215
  child.on('close', (code) => {
216
216
  clearTimeout(timer);
217
- const all = `${out}\n${err}`;
218
- if (/not recognized as|command not found|ENOENT/i.test(all)) return reject(Object.assign(new Error(`"${command}" is not installed or not on PATH. Install Claude Code, or set another CLI under "llm" in ${CONFIG_FILE}.`), { kind: 'missing' }));
219
- if (/failed to authenticate|oauth|not logged in|please log in|login required|unauthorized|401/i.test(all)) return reject(Object.assign(new Error(`${command} is signed out. Open a terminal, run "${command}", sign in, then try again. (The desktop app's login is separate from the terminal's.)`), { kind: 'auth' }));
220
- if (/requires a newer version|please upgrade/i.test(all)) return reject(Object.assign(new Error(`${command} is out of date and its model refused the request. Update it (npm i -g, or its own updater), then try again.`), { kind: 'outdated' }));
221
- if (code !== 0 && !out.trim()) return reject(new Error(`${command} exited with code ${code}: ${err.trim().split('\n').slice(-2).join(' ') || 'no output'}`));
217
+ const failure = classifyRun({ code, out, err, command });
218
+ if (failure) return reject(failure);
222
219
  resolve(out.replace(/^```[a-z]*\n|\n```\s*$/g, '').trim());
223
220
  });
224
221
  child.stdin.write(prompt); child.stdin.end();
225
222
  });
226
223
  }
227
224
 
225
+ /**
226
+ * What a finished CLI run means: null when it answered, otherwise an Error with a `kind` (missing, auth,
227
+ * outdated, failed) and the CLI's last words. A run that exited 0 with output answered, whatever words the
228
+ * answer contains: the earlier version read "401" and "OAuth" anywhere in the output as a sign-out, and a
229
+ * recruiter's "401(k) match" turned a healthy morning mail check into "claude is signed out".
230
+ */
231
+ export function classifyRun({ code, out = '', err = '', command = 'claude' }) {
232
+ const tail = err.trim().split('\n').slice(-3).join(' ').slice(0, 300);
233
+ if (/not recognized as an internal|command not found|ENOENT|is not recognized/i.test(err) && !out.trim()) {
234
+ return Object.assign(new Error(`"${command}" is not installed or not on PATH. Install Claude Code, or set another CLI under "llm" in ${CONFIG_FILE}.`), { kind: 'missing' });
235
+ }
236
+ if (code === 0 && out.trim()) return null;
237
+ const signedOut = /not logged in|please run \/login|run `?\/login|failed to authenticate|authentication[_ ]error|invalid[_ ]api[_ ]key|token (has )?expired|(^|\W)unauthorized(\W|$)|\b(http|status|error)\s*:?\s*401\b/i;
238
+ if (signedOut.test(err) || (!out.trim() && signedOut.test(out))) {
239
+ return Object.assign(new Error(`${command} is signed out. Open a terminal, run "${command}", sign in, then try again. (The desktop app's login is separate from the terminal's.) It said: ${tail || 'nothing'}`), { kind: 'auth' });
240
+ }
241
+ if (/requires a newer version|please upgrade/i.test(`${out}\n${err}`)) {
242
+ return Object.assign(new Error(`${command} is out of date and its model refused the request. Update it (npm i -g, or its own updater), then try again.`), { kind: 'outdated' });
243
+ }
244
+ if (!out.trim()) return Object.assign(new Error(`${command} exited with code ${code}: ${tail || 'no output'}`), { kind: 'failed' });
245
+ return null;
246
+ }
247
+
228
248
  const drafts = new Map(); // id → { running, startedAt, finishedAt, error, errorKind }
229
249
  export function state(id) {
230
250
  const d = drafts.get(id) || { running: false, startedAt: null, finishedAt: null, error: '', errorKind: '' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurtekb/tekjobs",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "A local-first job-search machine: watches hundreds of company job boards, scores every posting against your profile, and files matches into a folder of markdown notes you own. Bring your own LLM through MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",