@timurtekb/tekjobs 0.29.3 → 0.31.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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
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)
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))
11
+
12
+ ## 0.31.0 (2026-10-07)
13
+
14
+ * feat(cli): tekjobs serve opens the app and leaves when the app is closed ([91a584d](https://github.com/Timurtek/tekjobs/commit/91a584d))
15
+ * docs(site): a square cut of the showreel ([26a4f3c](https://github.com/Timurtek/tekjobs/commit/26a4f3c))
16
+
17
+ ## 0.30.0 (2026-09-27)
18
+
19
+ * feat(site): a 45-second showreel, on the landing and in the README ([ac1aa62](https://github.com/Timurtek/tekjobs/commit/ac1aa62))
20
+ * chore(app): let the site preview take any free port ([36bef43](https://github.com/Timurtek/tekjobs/commit/36bef43))
21
+ * test: the API, the MCP tools, the CLI and scoring, against the sample vault ([732ff2f](https://github.com/Timurtek/tekjobs/commit/732ff2f))
22
+ * test(app): a browser run of the built app on the sample vault, in CI ([8399912](https://github.com/Timurtek/tekjobs/commit/8399912))
23
+
5
24
  ## <small>0.29.3 (2026-09-25)</small>
6
25
 
7
26
  * fix(app): scrollbars in the brand, and the copy panel scrolls one way ([a87b43c](https://github.com/Timurtek/tekjobs/commit/a87b43c))
package/README.md CHANGED
@@ -12,9 +12,9 @@ A local career memory for you and your AI. TekJobs scans hundreds of public comp
12
12
 
13
13
  [Website](https://tekjobs.timurtek.com) · [Getting started](https://tekjobs.timurtek.com/docs/getting-started) · [Connect your AI client](https://tekjobs.timurtek.com/docs/ai-clients) · [npm](https://www.npmjs.com/package/@timurtekb/tekjobs) · [Releases](https://github.com/Timurtek/tekjobs/releases)
14
14
 
15
- [![Fifty-seven seconds: the init command, the interview in Claude Code, the first scan, and the Today page it produced](site/public/demo/tekjobs-demo-poster.png)](https://tekjobs.timurtek.com/#demo)
15
+ [![The TekJobs showreel: the morning scan, the cut to the few that fit, every point written down, the Today list, the pipeline, your AI over MCP, and the submit button it leaves to you](site/public/showreel/tekjobs-showreel-readme.jpg)](https://tekjobs.timurtek.com/#showreel)
16
16
 
17
- *Fifty-seven seconds, recorded from one real run on the fictional sample resume: install, interview, first scan, first Today. [Watch it on the site.](https://tekjobs.timurtek.com/#demo)*
17
+ *What it does, in forty-five seconds. [Watch the showreel](https://tekjobs.timurtek.com/#showreel) ([mp4](https://tekjobs.timurtek.com/showreel/tekjobs-showreel.mp4), [vertical](https://tekjobs.timurtek.com/showreel/tekjobs-showreel-vertical.mp4) and [square](https://tekjobs.timurtek.com/showreel/tekjobs-showreel-square.mp4) cuts). For one real, unedited run on the fictional sample resume (install, interview, first scan, first Today), [watch the 57-second demo](https://tekjobs.timurtek.com/#demo).*
18
18
 
19
19
  ## Why it exists
20
20
 
@@ -63,7 +63,7 @@ It does not fit if you want a hosted service with nothing to install (TekJobs is
63
63
  4. Run the app, and schedule the mornings.
64
64
 
65
65
  ```
66
- tekjobs serve # the app on http://127.0.0.1:8787
66
+ tekjobs up # the app on http://127.0.0.1:8787, detached: it stays up until `tekjobs down`
67
67
  tekjobs schedule # the scan and the read-only mail pass, daily at 07:30 (--time to change it)
68
68
  ```
69
69
 
@@ -170,7 +170,10 @@ tekjobs add <url> [<url>...] [--dry] add postings you found yourself
170
170
  tekjobs mail [--days N] the read-only mail pass
171
171
  tekjobs rescore [--full] [--dry] what existing notes score under the current criteria
172
172
  tekjobs schedule [--time HH:MM] [--print] the morning task, for Windows, macOS or Linux
173
- tekjobs serve the app + API on http://127.0.0.1:8787
173
+ tekjobs up [--dev] [--port N] start the API (and with --dev, Vite) detached; they stay up until `down`
174
+ tekjobs down stop what `up` started
175
+ tekjobs ps what `up` has running
176
+ tekjobs serve the app + API on http://127.0.0.1:8787, in this terminal
174
177
  tekjobs mcp the MCP server on stdio
175
178
  tekjobs --version
176
179
  ```
@@ -57,7 +57,7 @@ Defaulting to \`null\`.`}md(Ad,`getInvalidValueError`);var jd=xd,Md=Cd;(0,b.forw
57
57
  `)});continue}let a=/^(#{1,6})\s+(.*)$/.exec(e);if(a){n.push({kind:`heading`,level:a[1].length,children:ug(a[2])}),r++;continue}if(/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)){n.push({kind:`rule`}),r++;continue}if(sg(e)&&r+1<t.length&&cg(t[r+1])){let i=og(e).map(ug);r+=2;let a=[];for(;r<t.length&&sg(t[r]);)a.push(og(t[r++]).map(ug));n.push({kind:`table`,header:i,rows:a});continue}if(/^>\s?/.test(e)){let e=[];for(;r<t.length&&/^>\s?/.test(t[r]);)e.push(t[r++].replace(/^>\s?/,``));n.push({kind:`quote`,children:ug(e.join(` `))});continue}let o=/^\s*([-*+]|\d+[.)])\s+/.exec(e);if(o){let e=/\d/.test(o[1]),i=[];for(;r<t.length;){let e=/^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(t[r]);if(!e)break;let n=e[2];for(r++;r<t.length&&/^\s{2,}\S/.test(t[r])&&!/^\s*([-*+]|\d+[.)])\s+/.test(t[r]);)n+=` `+t[r++].trim();i.push(ug(n))}n.push({kind:`list`,ordered:e,items:i});continue}let s=[];for(;r<t.length&&t[r].trim()!==``&&!/^```/.test(t[r])&&!/^(#{1,6})\s/.test(t[r])&&!/^\s*([-*+]|\d+[.)])\s+/.test(t[r])&&!/^>\s?/.test(t[r])&&!(sg(t[r])&&r+1<t.length&&cg(t[r+1]));)s.push(t[r++].trim());n.push({kind:`paragraph`,children:ug(s.join(` `))})}return n}function ug(e){let t=[],n=e,r=e=>{let n=t[t.length-1];e.kind===`text`&&n?.kind===`text`?n.text+=e.text:t.push(e)};for(;n.length;){let e=/^`([^`]+)`/.exec(n);if(e){r({kind:`code`,text:e[1]}),n=n.slice(e[0].length);continue}let t=/^\[([^\]]+)\]\((https?:\/\/[^\s)]+|\/[^\s)]*|#[^\s)]*)\)/.exec(n);if(t){r({kind:`link`,href:t[2],children:ug(t[1])}),n=n.slice(t[0].length);continue}let i=/^(\*\*|__)(.+?)\1/.exec(n);if(i){r({kind:`strong`,children:ug(i[2])}),n=n.slice(i[0].length);continue}let a=/^(\*|_)([^*_]+?)\1/.exec(n);if(a){r({kind:`em`,children:ug(a[2])}),n=n.slice(a[0].length);continue}let o=n.slice(1).search(/[`*_[]/),s=o===-1?n.length:o+1;r({kind:`text`,text:n.slice(0,s)}),n=n.slice(s)}return t}var dg=(0,b.forwardRef)(function({text:e,streaming:t=!1,className:n,...r},i){let a=lg(e);return(0,x.jsxs)(`div`,{ref:i,className:R(`z-markdown`,n),"data-streaming":t||void 0,...r,children:[a.map((e,n)=>(0,x.jsx)(fg,{block:e,last:n===a.length-1,streaming:t},n)),t&&a.length===0&&(0,x.jsx)(`span`,{className:`z-markdown__cursor`,"aria-hidden":`true`})]})});function fg({block:e,last:t,streaming:n}){let r=t&&n?(0,x.jsx)(`span`,{className:`z-markdown__cursor`,"aria-hidden":`true`}):null;switch(e.kind){case`paragraph`:return(0,x.jsxs)(`p`,{children:[(0,x.jsx)(pg,{nodes:e.children}),r]});case`heading`:{let t=`h${e.level}`;return(0,x.jsxs)(t,{children:[(0,x.jsx)(pg,{nodes:e.children}),r]})}case`list`:{let t=e.ordered?`ol`:`ul`;return(0,x.jsx)(t,{children:e.items.map((t,n)=>(0,x.jsxs)(`li`,{children:[(0,x.jsx)(pg,{nodes:t}),n===e.items.length-1?r:null]},n))})}case`code`:return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ag,{code:e.code,language:e.language||void 0}),r]});case`quote`:return(0,x.jsxs)(`blockquote`,{children:[(0,x.jsx)(pg,{nodes:e.children}),r]});case`table`:return(0,x.jsxs)(Y,{density:`sm`,children:[(0,x.jsx)(Y.Head,{children:(0,x.jsx)(Y.Row,{children:e.header.map((e,t)=>(0,x.jsx)(Y.HeadCell,{children:(0,x.jsx)(pg,{nodes:e})},t))})}),(0,x.jsx)(Y.Body,{children:e.rows.map((e,t)=>(0,x.jsx)(Y.Row,{children:e.map((e,t)=>(0,x.jsx)(Y.Cell,{children:(0,x.jsx)(pg,{nodes:e})},t))},t))})]});case`rule`:return(0,x.jsx)(`hr`,{})}}function pg({nodes:e}){return e.map((e,t)=>{switch(e.kind){case`text`:return e.text;case`code`:return(0,x.jsx)(`code`,{children:e.text},t);case`strong`:return(0,x.jsx)(`strong`,{children:(0,x.jsx)(pg,{nodes:e.children})},t);case`em`:return(0,x.jsx)(`em`,{children:(0,x.jsx)(pg,{nodes:e.children})},t);case`link`:return(0,x.jsx)(`a`,{href:e.href,target:e.href.startsWith(`http`)?`_blank`:void 0,rel:e.href.startsWith(`http`)?`noreferrer`:void 0,children:(0,x.jsx)(pg,{nodes:e.children})},t)}})}var mg=Object.defineProperty,hg=(e,t)=>mg(e,`name`,{value:t,configurable:!0}),gg=`Tabs`,[_g,vg]=T(gg,[rc]),yg=rc(),[bg,xg]=_g(gg),Sg=b.forwardRef(hg(function(e,t){let{__scopeTabs:n,value:r,onValueChange:i,defaultValue:a,orientation:o=`horizontal`,dir:s,activationMode:c=`automatic`,...l}=e,u=G(s),[d,f]=kt({prop:r,onChange:i,defaultProp:a??``,caller:gg});return(0,x.jsx)(bg,{scope:n,baseId:bt(),value:d,onValueChange:f,orientation:o,dir:u,activationMode:c,children:(0,x.jsx)(L.div,{dir:u,"data-orientation":o,...l,ref:t})})},`Tabs`)),Cg=`TabsList`,wg=b.forwardRef(hg(function(e,t){let{__scopeTabs:n,loop:r=!0,...i}=e,a=xg(Cg,n),o=yg(n);return(0,x.jsx)(hc,{asChild:!0,...o,orientation:a.orientation,dir:a.dir,loop:r,children:(0,x.jsx)(L.div,{role:`tablist`,"aria-orientation":a.orientation,...i,ref:t})})},`TabsList`)),Tg=`TabsTrigger`,Eg=b.forwardRef(hg(function(e,t){let{__scopeTabs:n,value:r,disabled:i=!1,...a}=e,o=xg(Tg,n),s=yg(n),c=kg(o.baseId,r),l=Ag(o.baseId,r),u=r===o.value;return(0,x.jsx)(gc,{asChild:!0,...s,focusable:!i,active:u,children:(0,x.jsx)(L.button,{type:`button`,role:`tab`,"aria-selected":u,"aria-controls":l,"data-state":u?`active`:`inactive`,"data-disabled":i?``:void 0,disabled:i,id:c,...a,ref:t,onMouseDown:U(e.onMouseDown,e=>{!i&&e.button===0&&e.ctrlKey===!1?o.onValueChange(r):e.preventDefault()}),onKeyDown:U(e.onKeyDown,e=>{i||e.target!==e.currentTarget||[` `,`Enter`].includes(e.key)&&o.onValueChange(r)}),onFocus:U(e.onFocus,()=>{let e=o.activationMode!==`manual`;!u&&!i&&e&&o.onValueChange(r)})})})},`TabsTrigger`)),Dg=`TabsContent`,Og=b.forwardRef(hg(function(e,t){let{__scopeTabs:n,value:r,forceMount:i,children:a,...o}=e,s=xg(Dg,n),c=kg(s.baseId,r),l=Ag(s.baseId,r),u=r===s.value,d=b.useRef(u);return b.useEffect(()=>{let e=requestAnimationFrame(()=>d.current=!1);return()=>cancelAnimationFrame(e)},[]),(0,x.jsx)(bn,{present:i||u,children:({present:n})=>(0,x.jsx)(L.div,{"data-state":u?`active`:`inactive`,"data-orientation":s.orientation,role:`tabpanel`,"aria-labelledby":c,hidden:!n,id:l,tabIndex:0,...o,ref:t,style:{...e.style,animationDuration:d.current?`0s`:void 0},children:n&&a})})},`TabsContent`));function kg(e,t){return`${e}-trigger-${t}`}hg(kg,`makeTriggerId`);function Ag(e,t){return`${e}-content-${t}`}hg(Ag,`makeContentId`);var jg=Sg,Mg=wg,Ng=Eg,Pg=Og,Fg=(0,b.forwardRef)(function({variant:e=`line`,size:t=`md`,className:n,...r},i){return(0,x.jsx)(jg,{ref:i,className:R(`z-tabs`,n),"data-variant":e,"data-size":t,...r})}),Ig=(0,b.forwardRef)(function({className:e,...t},n){return(0,x.jsx)(Mg,{ref:n,className:R(`z-tabs__list`,e),...t})}),Lg=(0,b.forwardRef)(function({className:e,...t},n){return(0,x.jsx)(Ng,{ref:n,className:R(`z-tabs__trigger z-focusable`,e),...t})}),Rg=(0,b.forwardRef)(function({className:e,...t},n){return(0,x.jsx)(Pg,{ref:n,className:R(`z-tabs__content z-focusable`,e),...t})}),Z=Object.assign(Fg,{List:Ig,Trigger:Lg,Content:Rg}),zg=(0,b.forwardRef)(function({variant:e=`text`,width:t,height:n,lines:r=1,className:i,style:a,...o},s){return e===`text`&&r>1?(0,x.jsx)(`span`,{ref:s,className:R(`z-skeleton-lines`,i),"aria-hidden":`true`,style:a,...o,children:Array.from({length:r},(e,n)=>(0,x.jsx)(`span`,{className:`z-skeleton`,"data-variant":`text`,style:{width:n===r-1?`60%`:t}},n))}):(0,x.jsx)(`span`,{ref:s,className:R(`z-skeleton`,i),"data-variant":e,"aria-hidden":`true`,style:{width:t,height:n,...a},...o})}),Bg=(0,b.forwardRef)(function({label:e=`Thinking`,showLabel:t=!1,size:n=`md`,className:r,...i},a){return(0,x.jsxs)(`span`,{ref:a,className:R(`z-loader`,r),"data-size":n,role:`status`,"aria-label":e,...i,children:[(0,x.jsxs)(`span`,{className:`z-loader__dots`,"aria-hidden":`true`,children:[(0,x.jsx)(`span`,{}),(0,x.jsx)(`span`,{}),(0,x.jsx)(`span`,{})]}),t&&(0,x.jsx)(`span`,{className:`z-loader__label`,children:e})]})}),Vg=(0,b.forwardRef)(function({title:e,description:t,icon:n,action:r,size:i=`md`,tone:a=`neutral`,className:o,...s},c){return(0,x.jsxs)(`div`,{ref:c,className:R(`z-empty`,o),"data-size":i,"data-tone":a,...s,children:[n&&(0,x.jsx)(`span`,{className:`z-empty__icon`,"aria-hidden":`true`,children:n}),(0,x.jsx)(`p`,{className:`z-empty__title`,children:e}),t&&(0,x.jsx)(`p`,{className:`z-empty__description`,children:t}),r&&(0,x.jsx)(`div`,{className:`z-empty__action`,children:r})]})});function Hg({columns:e,cards:t,cardId:n,cardColumn:r,renderCard:i,cardLabel:a,onMove:o,density:s=`md`,label:c,className:l}){let[u,d]=(0,b.useState)(null),[f,p]=(0,b.useState)(null),[m,h]=(0,b.useState)(``),g=(0,b.useId)(),_=(0,b.useRef)(new Map),v=(0,b.useCallback)(e=>t.filter(t=>r(t)===e),[t,r]),y=(0,b.useCallback)((e,t,r,i)=>{(t!==r||v(t).findIndex(t=>n(t)===e)!==i)&&o?.({cardId:e,from:t,to:r,index:i})},[v,n,o]);(0,b.useEffect)(()=>{u&&_.current.get(u.id)?.focus()},[u,t]);let S=(e,t)=>{u&&(y(u.id,u.column,e,t),d(null),p(null))},C=(t,i,o,s)=>{if(t.target!==t.currentTarget)return;let c=n(i),l=t.key;if(l===` `||l===`Enter`){if(t.preventDefault(),!u)d({id:c,column:r(i),index:s}),h(`${a(i)} lifted. Use the arrow keys to move it, Enter to drop, Escape to cancel.`);else{let t=f??{column:u.column,index:u.index},n=e.find(e=>e.id===t.column);y(u.id,u.column,t.column,t.index),h(`${a(i)} moved to ${typeof n?.title==`string`?n.title:t.column}, position ${t.index+1}.`),d(null),p(null)}return}if(l===`Escape`&&u){t.preventDefault(),h(`${a(i)} put back.`),d(null),p(null);return}if(!u)return;let m=f??{column:u.column,index:u.index},g=e.findIndex(e=>e.id===m.column);if(l===`ArrowLeft`||l===`ArrowRight`){t.preventDefault();let r=e[g+(l===`ArrowRight`?1:-1)];if(!r)return;let i=v(r.id).filter(e=>n(e)!==u.id).length,a=Math.min(m.index,i);p({column:r.id,index:a}),h(`Over ${typeof r.title==`string`?r.title:r.id}, position ${a+1}.`);return}if(l===`ArrowUp`||l===`ArrowDown`){t.preventDefault();let e=v(m.column).filter(e=>n(e)!==u.id).length,r=Math.max(0,Math.min(e,m.index+(l===`ArrowDown`?1:-1)));p({column:m.column,index:r}),h(`Position ${r+1}.`)}};return(0,x.jsxs)(`div`,{className:R(`z-kanban`,l),"data-density":s,role:`group`,"aria-label":c,children:[(0,x.jsx)(`p`,{className:`z-sr-only`,role:`status`,"aria-live":`polite`,children:m}),e.map((e,t)=>{let r=v(e.id),o=f?.column===e.id,s=e.limit!==void 0&&r.length>e.limit;return(0,x.jsxs)(`section`,{className:`z-kanban__column`,"data-over":o||void 0,"aria-labelledby":`${g}-${e.id}`,onDragOver:t=>{u&&(t.preventDefault(),p({column:e.id,index:r.filter(e=>n(e)!==u.id).length}))},onDrop:t=>{t.preventDefault(),S(e.id,f?.column===e.id?f.index:r.length)},children:[(0,x.jsxs)(`header`,{className:`z-kanban__head`,children:[(0,x.jsx)(`h3`,{className:`z-kanban__title`,id:`${g}-${e.id}`,children:e.title}),(0,x.jsx)(z,{size:`sm`,tone:s?`warning`:`neutral`,variant:`outline`,children:e.limit===void 0?r.length:`${r.length}/${e.limit}`})]}),(0,x.jsxs)(`ul`,{className:`z-kanban__list`,children:[r.map((r,s)=>{let c=n(r),l=u?.id===c;return(0,x.jsx)(`li`,{className:`z-kanban__slot`,"data-drop":o&&f?.index===s?``:void 0,children:(0,x.jsx)(`article`,{ref:e=>{e?_.current.set(c,e):_.current.delete(c)},className:`z-kanban__card z-focusable`,tabIndex:0,draggable:!0,"data-lifted":l||void 0,"aria-roledescription":`Draggable card`,"aria-label":a(r),"aria-grabbed":l||void 0,onKeyDown:e=>C(e,r,t,s),onDragStart:()=>d({id:c,column:e.id,index:s}),onDragEnd:()=>{d(null),p(null)},children:i(r)})},c)}),r.length===0&&(0,x.jsx)(`li`,{className:`z-kanban__slot`,"data-drop":o?``:void 0,children:e.empty??(0,x.jsx)(Vg,{size:`sm`,title:`Nothing here`})})]})]},e.id)})]})}function Ug(e,t){return`${e}-option-${t}`}function Wg(e){let{options:t,label:n,description:r,error:i,placeholder:a,size:o=`md`,disabled:s,emptyMessage:c=`No matches`,className:l,id:u}=e,d=e.multiple===!0,f=(0,b.useId)(),p=u??f,m=`${p}-list`,[h,g]=(0,b.useState)(!1),[_,v]=(0,b.useState)(``),[y,S]=(0,b.useState)(0),C=(0,b.useRef)(null),w=(0,b.useRef)(null),T=(0,b.useRef)(null),E=(0,b.useMemo)(()=>{if(d)return e.value??[];let t=e.value;return t?[t]:[]},[d,e]),D=(0,b.useMemo)(()=>new Map(t.map(e=>[e.value,e])),[t]),O=(0,b.useMemo)(()=>{let e=_.trim().toLowerCase();return e?t.filter(t=>t.label.toLowerCase().includes(e)||(t.hint??``).toLowerCase().includes(e)):t},[t,_,d]);(0,b.useEffect)(()=>S(0),[_,h]),(0,b.useEffect)(()=>{if(!h)return;let e=e=>{T.current?.contains(e.target)||g(!1)};return document.addEventListener(`pointerdown`,e),()=>document.removeEventListener(`pointerdown`,e)},[h]),(0,b.useEffect)(()=>{h&&w.current?.querySelector(`[data-active="true"]`)?.scrollIntoView({block:`nearest`})},[y,h]);let k=t=>{if(!t.disabled){if(d){let n=E.includes(t.value)?E.filter(e=>e!==t.value):[...E,t.value];e.onValueChange?.(n),v(``),C.current?.focus();return}e.onValueChange?.(t.value),v(``),g(!1)}},A=t=>{d&&(e.onValueChange?.(E.filter(e=>e!==t)),C.current?.focus())},j=e=>{let t=e.key;if(t===`ArrowDown`||t===`ArrowUp`){if(e.preventDefault(),!h){g(!0);return}let n=t===`ArrowDown`?1:-1;for(let e=1;e<=O.length;e++){let t=(y+n*e+O.length*e)%O.length;if(!O[t]?.disabled){S(t);return}}return}if(t===`Enter`&&h){let t=O[y];t&&(e.preventDefault(),k(t));return}if(t===`Escape`){h&&(e.preventDefault(),g(!1));return}t===`Backspace`&&d&&_===``&&E.length&&A(E[E.length-1])},ee=(0,b.useMemo)(()=>{let e=[];for(let t of O){let n=e[e.length-1];n&&n.name===t.group?n.options.push(t):e.push({name:t.group,options:[t]})}return e},[O]),M=-1,N=[r?`${p}-description`:null,i?`${p}-error`:null].filter(Boolean).join(` `)||void 0;return(0,x.jsxs)(`div`,{ref:T,className:R(`z-combobox`,l),"data-size":o,"data-invalid":i?``:void 0,"data-disabled":s||void 0,children:[(0,x.jsx)(`label`,{className:`z-combobox__label`,htmlFor:p,children:n}),(0,x.jsxs)(`div`,{className:`z-combobox__control`,children:[d&&E.map(e=>(0,x.jsxs)(z,{size:`sm`,tone:`neutral`,variant:`soft`,className:`z-combobox__chip`,children:[D.get(e)?.label??e,(0,x.jsx)(`button`,{type:`button`,className:`z-combobox__remove z-focusable`,"aria-label":`Remove ${D.get(e)?.label??e}`,onClick:()=>A(e),disabled:s,children:(0,x.jsx)(H.Close,{size:12})})]},e)),(0,x.jsx)(`input`,{ref:C,id:p,className:`z-combobox__input z-focusable`,role:`combobox`,type:`text`,autoComplete:`off`,"aria-expanded":h,"aria-controls":m,"aria-autocomplete":`list`,"aria-activedescendant":h&&O[y]?Ug(p,y):void 0,"aria-describedby":N,"aria-invalid":i?!0:void 0,disabled:s,placeholder:d||!E.length?a:D.get(E[0])?.label,value:_,onChange:e=>{v(e.target.value),g(!0)},onFocus:()=>g(!0),onKeyDown:j}),(0,x.jsx)(`span`,{className:`z-combobox__chevron`,"aria-hidden":`true`,children:(0,x.jsx)(H.ChevronDown,{size:16})})]}),h&&(0,x.jsxs)(`ul`,{ref:w,className:`z-combobox__list`,id:m,role:`listbox`,"aria-label":typeof n==`string`?n:void 0,"aria-multiselectable":d||void 0,children:[ee.map((e,t)=>(0,x.jsxs)(`li`,{className:`z-combobox__group`,role:`presentation`,children:[e.name&&(0,x.jsx)(`p`,{className:`z-combobox__group-name`,children:e.name}),(0,x.jsx)(`ul`,{role:`presentation`,children:e.options.map(e=>{M+=1;let t=M,n=E.includes(e.value);return(0,x.jsxs)(`li`,{id:Ug(p,t),"data-value":e.value,className:`z-combobox__option`,role:`option`,"aria-selected":n,"aria-disabled":e.disabled||void 0,"data-active":t===y,"data-selected":n||void 0,onPointerDown:t=>{t.preventDefault(),k(e)},onPointerEnter:()=>S(t),children:[(0,x.jsx)(`span`,{className:`z-combobox__option-label`,children:e.label}),e.hint&&(0,x.jsx)(`span`,{className:`z-combobox__option-hint`,children:e.hint}),n&&(0,x.jsx)(`span`,{className:`z-combobox__tick`,"aria-hidden":`true`,children:(0,x.jsx)(H.Check,{size:14})})]},e.value)})})]},e.name??`g${t}`)),O.length===0&&(0,x.jsx)(`li`,{className:`z-combobox__empty`,role:`presentation`,children:c})]}),r&&!i&&(0,x.jsx)(`p`,{className:`z-combobox__description`,id:`${p}-description`,children:r}),i&&(0,x.jsx)(`p`,{className:`z-combobox__error`,id:`${p}-error`,children:i})]})}var Gg=[`new`,`reviewing`,`applying`,`ready`,`applied`,`interviewing`,`offer`,`rejected`,`passed`],Kg=[`wrong role shape`,`weak evidence match`,`compensation`,`location or authorisation`,`too managerial`,`too visual`,`too engineering`,`stale or duplicate`,`company`],qg=[`recruiter`,`hiring-manager`,`interviewer`,`referral`,`other`];async function Q(e,t){let n=await fetch(e,{headers:{"content-type":`application/json`},...t}),r=await n.json().catch(()=>({}));if(!n.ok)throw Error(r.error||`${n.status} ${n.statusText}`);return r}var $={summary:()=>Q(`/api/summary`),jobs:e=>{let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!==void 0&&r!==``&&r!==null&&t.set(n,String(r));return Q(`/api/jobs?${t}`)},facets:e=>{let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!==void 0&&r!==``&&r!==null&&t.set(n,String(r));return Q(`/api/jobs/facets?${t}`)},job:e=>Q(`/api/jobs/${encodeURIComponent(e)}`),attachPosting:(e,t,n=!1)=>Q(`/api/jobs/${encodeURIComponent(e)}/attach`,{method:`POST`,body:JSON.stringify({url:t,linkOnly:n})}),settings:()=>Q(`/api/settings`),saveSettings:e=>Q(`/api/settings`,{method:`PUT`,body:JSON.stringify(e)}),resumes:()=>Q(`/api/resumes`),useResume:e=>Q(`/api/resumes/use`,{method:`POST`,body:JSON.stringify({name:e})}),openResume:e=>Q(`/api/resumes/open`,{method:`POST`,body:JSON.stringify({name:e})}),revealJob:e=>Q(`/api/jobs/${encodeURIComponent(e)}/reveal`,{method:`POST`}),importLinks:e=>Q(`/api/jobs/import`,{method:`POST`,body:JSON.stringify({urls:e})}),today:(e=7)=>Q(`/api/today?cap=${e}`),setStatus:(e,t,n)=>Q(`/api/jobs/${encodeURIComponent(e)}`,{method:`PATCH`,body:JSON.stringify({status:t,reason:n})}),addNote:(e,t)=>Q(`/api/jobs/${encodeURIComponent(e)}`,{method:`PATCH`,body:JSON.stringify({note:t})}),packet:e=>Q(`/api/jobs/${encodeURIComponent(e)}/packet`),coverLetter:e=>Q(`/api/jobs/${encodeURIComponent(e)}/cover-letter`),writeCoverLetter:(e,t)=>Q(`/api/jobs/${encodeURIComponent(e)}/cover-letter`,{method:`POST`,body:JSON.stringify(t)}),tailoredResume:e=>Q(`/api/jobs/${encodeURIComponent(e)}/resume`),writeTailoredResume:(e,t)=>Q(`/api/jobs/${encodeURIComponent(e)}/resume`,{method:`POST`,body:JSON.stringify(t)}),saveTailoredResume:(e,t)=>Q(`/api/jobs/${encodeURIComponent(e)}/resume`,{method:`PUT`,body:JSON.stringify({text:t})}),resumePrintUrl:e=>`/api/jobs/${encodeURIComponent(e)}/resume.html`,saveCoverLetter:(e,t)=>Q(`/api/jobs/${encodeURIComponent(e)}/cover-letter`,{method:`PUT`,body:JSON.stringify({text:t})}),saveApplication:(e,t,n)=>Q(`/api/jobs/${encodeURIComponent(e)}`,{method:`PATCH`,body:JSON.stringify({application:{field:t,value:n}})}),runs:()=>Q(`/api/runs`),scan:()=>Q(`/api/scan`),startScan:(e=!1,t,n=!1)=>Q(`/api/scan`,{method:`POST`,body:JSON.stringify({dry:e,criteria:t||``,retryFailed:n})}),criteria:()=>Q(`/api/criteria`),saveCriteria:e=>Q(`/api/criteria`,{method:`PUT`,body:JSON.stringify({raw:e})}),outcomes:()=>Q(`/api/outcomes`),views:()=>Q(`/api/views`),defaultViews:()=>Q(`/api/views/defaults`),saveViews:e=>Q(`/api/views`,{method:`PUT`,body:JSON.stringify({views:e})}),mail:()=>Q(`/api/mail`),mailCheck:e=>Q(`/api/mail/check`,{method:`POST`,body:JSON.stringify({sinceDays:e})}),mailConfirm:(e,t)=>Q(`/api/mail/${encodeURIComponent(e)}/confirm`,{method:`POST`,body:JSON.stringify({noteId:t})}),mailConfirmSafe:()=>Q(`/api/mail/confirm-safe`,{method:`POST`}),mailDismiss:e=>Q(`/api/mail/${encodeURIComponent(e)}/dismiss`,{method:`POST`}),previewCriteria:e=>Q(`/api/criteria/preview`,{method:`POST`,body:JSON.stringify({raw:e})}),rescoreNotes:(e=!1)=>Q(`/api/rescore`,{method:`POST`,body:JSON.stringify({dry:e})}),criteriaPresets:()=>Q(`/api/criteria/presets`),criteriaPreset:e=>Q(`/api/criteria/presets/${encodeURIComponent(e)}`),saveCriteriaPreset:(e,t)=>Q(`/api/criteria/presets/${encodeURIComponent(e)}`,{method:`PUT`,body:JSON.stringify({raw:t})}),deleteCriteriaPreset:e=>Q(`/api/criteria/presets/${encodeURIComponent(e)}`,{method:`DELETE`}),activateCriteriaPreset:e=>Q(`/api/criteria/presets/${encodeURIComponent(e)}/activate`,{method:`POST`}),profileNotes:()=>Q(`/api/profile`),profileSummary:()=>Q(`/api/profile/summary`),saveProfileNote:(e,t)=>Q(`/api/profile`,{method:`PUT`,body:JSON.stringify({note:e,markdown:t})}),snippets:()=>Q(`/api/snippets`),saveSnippets:e=>Q(`/api/snippets`,{method:`PUT`,body:JSON.stringify({items:e})}),people:()=>Q(`/api/people`),person:e=>Q(`/api/people/${encodeURIComponent(e)}`),addPerson:e=>Q(`/api/people`,{method:`POST`,body:JSON.stringify(e)}),attachPerson:(e,t,n,r)=>Q(`/api/people/${encodeURIComponent(e)}/attach`,{method:`POST`,body:JSON.stringify({jobId:t,role:n,context:r})}),logContact:(e,t,n)=>Q(`/api/people/${encodeURIComponent(e)}/log`,{method:`POST`,body:JSON.stringify({text:t,date:n})}),jobPeople:e=>Q(`/api/jobs/${encodeURIComponent(e)}/people`),jobConnections:e=>Q(`/api/jobs/${encodeURIComponent(e)}/connections`),linkedinStatus:()=>Q(`/api/linkedin`),linkedinPreview:(e,t,n)=>Q(`/api/linkedin/preview`,{method:`POST`,body:JSON.stringify({source:e,since:t,everyone:n})}),linkedinImport:(e,t={})=>Q(`/api/linkedin/import`,{method:`POST`,body:JSON.stringify({source:e,...t})}),companies:()=>Q(`/api/companies`),addCompany:e=>Q(`/api/companies`,{method:`POST`,body:JSON.stringify(e)}),feeds:()=>Q(`/api/feeds`),onboarding:()=>Q(`/api/onboarding`),initProfile:()=>Q(`/api/onboarding/init`,{method:`POST`,body:`{}`}),importResume:e=>Q(`/api/onboarding/resume`,{method:`POST`,body:JSON.stringify({path:e})})},Jg=e=>`$${Math.round(e/1e3)}k`,Yg=e=>e.replace(/\s*[•·].*$/,``).replace(/\s+/g,` `).trim(),Xg=e=>{if(!e)return``;let t=Math.floor((Date.now()-new Date(e).getTime())/864e5);return Number.isFinite(t)?t<=0?`today`:`${t}d ago`:``},Zg={floor:`Floor`,stretch:`Stretch`,below:`Below`,unknown:`Not stated`},Qg={floor:`success`,stretch:`warning`,below:`neutral`,unknown:`neutral`},$g={new:`neutral`,reviewing:`primary`,applying:`primary`,ready:`warning`,applied:`primary`,interviewing:`warning`,offer:`success`,rejected:`danger`,passed:`neutral`};function e_(e){let[t,n]=(0,b.useState)(()=>typeof window<`u`&&window.matchMedia(e).matches);return(0,b.useEffect)(()=>{let t=window.matchMedia(e),r=()=>n(t.matches);return r(),t.addEventListener(`change`,r),()=>t.removeEventListener(`change`,r)},[e]),t}var t_={name:`TekJobs`,logo:`/brand/tekjobs-avatar-192.png`};function n_({href:e=`/`}){return(0,x.jsxs)(`a`,{className:`brand-mark`,href:e,"aria-label":t_.name,children:[t_.logo&&(0,x.jsx)(`img`,{className:`brand-mark__logo`,src:t_.logo,alt:``}),(0,x.jsxs)(`span`,{"aria-hidden":`true`,children:[`Tek`,(0,x.jsx)(`span`,{className:`brand-mark__accent`,children:`Jobs`})]})]})}function r_(){let[e,t]=(0,b.useState)(!1),[n,r]=(0,b.useState)(null),[i,a]=(0,b.useState)(``),[o,s]=(0,b.useState)(null),[c,l]=(0,b.useState)(!1),u=(0,b.useRef)(null),d=()=>$.snippets().then(r).catch(e=>X({title:`Could not load the copy panel`,description:e.message,tone:`danger`}));(0,b.useEffect)(()=>{e&&!n&&d()},[e]),(0,b.useEffect)(()=>{e?setTimeout(()=>u.current?.focus(),30):a(``)},[e]);let f=(0,b.useMemo)(()=>{let e=i.trim().toLowerCase(),t=(n?.items??[]).filter(t=>t.value&&(!e||`${t.group} ${t.label} ${t.value}`.toLowerCase().includes(e))),r=new Map;for(let e of t){let t=e.group||`Other`;r.has(t)||r.set(t,[]),r.get(t).push(e)}return[...r.entries()]},[n,i]),p=async e=>{try{await navigator.clipboard.writeText(e.value),s(e.label),setTimeout(()=>s(t=>t===e.label?null:t),1600)}catch(e){X({title:`Could not copy`,description:e.message,tone:`danger`})}},m=f[0]?.[1][0];return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(fd,{open:e,onOpenChange:t,size:`lg`,children:[(0,x.jsx)(Bh,{content:`Copy panel`,children:(0,x.jsx)(fd.Trigger,{asChild:!0,children:(0,x.jsx)(B,{variant:`ghost`,size:`sm`,"aria-label":`Copy panel`,leadingIcon:(0,x.jsx)(H.Copy,{})})})}),(0,x.jsx)(fd.Content,{align:`end`,showArrow:!1,children:(0,x.jsxs)(`div`,{className:`copy`,children:[(0,x.jsx)(`form`,{className:`copy__search`,onSubmit:e=>{e.preventDefault(),m&&p(m)},children:(0,x.jsx)(Zp,{ref:u,size:`sm`,"aria-label":`Find a snippet`,placeholder:`Find, then Enter copies the first`,leadingIcon:(0,x.jsx)(H.Search,{}),value:i,onChange:e=>a(e.target.value)})}),n?f.length===0?(0,x.jsx)(`p`,{className:`muted copy__empty`,children:n.items.length===0?`Nothing here yet. Customize adds lines.`:i?`Nothing matches.`:`Every snippet is empty. Customize fills them in.`}):(0,x.jsx)(`div`,{className:`copy__groups`,children:f.map(([e,t])=>(0,x.jsxs)(`div`,{className:`copy__group`,children:[(0,x.jsx)(`div`,{className:`microlabel`,children:e}),t.map(t=>(0,x.jsx)(B,{asChild:!0,variant:`ghost`,tone:`neutral`,size:`sm`,align:`start`,className:`copy__row`,children:(0,x.jsxs)(`button`,{type:`button`,onClick:()=>p(t),title:t.value,children:[(0,x.jsx)(`span`,{className:`copy__label`,children:t.label}),(0,x.jsxs)(`span`,{className:`copy__value num`,children:[o===t.label?`Copied`:t.value.split(`
58
58
  `)[0],t.value.includes(`
59
59
  `)&&o!==t.label?` …`:``]})]})},`${e}:${t.label}`))]},e))}):(0,x.jsx)(zg,{lines:4}),(0,x.jsxs)(`div`,{className:`copy__foot`,children:[(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`neutral`,leadingIcon:(0,x.jsx)(H.Edit,{}),onClick:()=>{t(!1),l(!0)},children:`Customize`}),n&&!n.exists&&(0,x.jsx)(`span`,{className:`muted`,children:`Read from your profile until you save once.`})]})]})})]}),c&&n&&(0,x.jsx)(i_,{initial:n.items,path:n.path,onClose:()=>l(!1),onSaved:e=>{r(e),l(!1)}})]})}function i_({initial:e,path:t,onClose:n,onSaved:r}){let[i,a]=(0,b.useState)(e.length?e:[{group:``,label:``,value:``}]),[o,s]=(0,b.useState)(!1),c=(e,t)=>a(n=>n.map((n,r)=>r===e?{...n,...t}:n)),l=(e,t)=>a(n=>{let r=e+t;if(r<0||r>=n.length)return n;let i=[...n];return[i[e],i[r]]=[i[r],i[e]],i}),u=e=>a(t=>t.filter((t,n)=>n!==e));return(0,x.jsx)(Sp,{open:!0,onOpenChange:e=>!e&&n(),side:`right`,size:`md`,children:(0,x.jsx)(Sp.Content,{children:(0,x.jsxs)(`div`,{className:`detail`,children:[(0,x.jsx)(Sp.Title,{children:`Customize the copy panel`}),(0,x.jsxs)(Sp.Description,{children:[`Label is the button, group is its heading, value is what lands on the clipboard. Lines with no label are dropped. Saved to `,(0,x.jsx)(`code`,{className:`mono`,children:t.split(/[\\/]/).slice(-2).join(`/`)}),`, so Obsidian works too.`]}),(0,x.jsx)(`div`,{className:`copy__rows`,children:i.map((e,t)=>(0,x.jsxs)(`div`,{className:`copy__edit`,children:[(0,x.jsxs)(`div`,{className:`form__row`,children:[(0,x.jsx)(Zp,{size:`sm`,label:`Label`,value:e.label,onChange:e=>c(t,{label:e.target.value})}),(0,x.jsx)(Zp,{size:`sm`,label:`Group`,value:e.group,onChange:e=>c(t,{group:e.target.value})})]}),(0,x.jsx)(Xp,{label:`Value`,rows:e.value.includes(`
60
- `)?4:1,value:e.value,onChange:e=>c(t,{value:e.target.value})}),(0,x.jsxs)(`div`,{className:`copy__edit-actions`,children:[(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`neutral`,"aria-label":`Move up`,leadingIcon:(0,x.jsx)(H.ArrowUp,{}),disabled:t===0,onClick:()=>l(t,-1)}),(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`neutral`,"aria-label":`Move down`,leadingIcon:(0,x.jsx)(H.ArrowDown,{}),disabled:t===i.length-1,onClick:()=>l(t,1)}),(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`danger`,"aria-label":`Remove`,leadingIcon:(0,x.jsx)(H.Trash,{}),onClick:()=>u(t)})]})]},t))}),(0,x.jsxs)(`div`,{className:`form__actions`,children:[(0,x.jsx)(B,{size:`sm`,variant:`soft`,tone:`neutral`,leadingIcon:(0,x.jsx)(H.Plus,{}),onClick:()=>a(e=>[...e,{group:e[e.length-1]?.group??``,label:``,value:``}]),children:`Add a line`}),(0,x.jsx)(`span`,{className:`toolbar__spacer`}),(0,x.jsx)(B,{size:`sm`,variant:`ghost`,onClick:n,children:`Cancel`}),(0,x.jsx)(B,{size:`sm`,tone:`primary`,loading:o,onClick:async()=>{s(!0);try{let e=await $.saveSnippets(i);X({title:`Copy panel saved`,description:`Profile/Snippets.md`,tone:`success`}),r(e)}catch(e){X({title:`Not saved`,description:e.message,tone:`danger`})}s(!1)},children:`Save`})]})]})})})}var a_=[{page:`onboarding`,label:`Get started`,icon:H.Sparkles},{page:`today`,label:`Today`,icon:H.Star},{page:`overview`,label:`Overview`,icon:H.Home},{page:`jobs`,label:`Jobs`,icon:H.Inbox},{page:`pipeline`,label:`Pipeline`,icon:H.Layers},{page:`mail`,label:`Mail`,icon:H.Mail},{page:`people`,label:`People`,icon:H.Users},{page:`companies`,label:`Sources`,icon:H.Globe},{page:`criteria`,label:`Criteria`,icon:H.Filter},{page:`profile`,label:`Profile`,icon:H.User},{page:`runs`,label:`Runs`,icon:H.Refresh},{page:`agent`,label:`Agent access`,icon:H.Terminal},{page:`settings`,label:`Settings`,icon:H.Settings}],o_={onboarding:`Get started`,today:`Today`,overview:`Overview`,jobs:`Jobs`,pipeline:`Pipeline`,mail:`Mail`,people:`People`,companies:`Sources`,criteria:`Criteria`,profile:`Profile`,runs:`Runs`,agent:`Agent access`,settings:`Settings`},s_=[{title:`Work`,pages:[`today`,`jobs`,`pipeline`,`mail`,`people`]},{title:`Insights`,pages:[`overview`]},{title:`Setup`,pages:[`profile`,`criteria`,`companies`]},{title:`System`,pages:[`runs`,`agent`,`settings`,`onboarding`]}];function c_({page:e,onNavigate:t,onSearch:n,theme:r,onToggleTheme:i,onboarded:a=!0,counts:o={},lastScan:s=``,children:c}){let[l,u]=(0,b.useState)(!1),d=e_(`(max-width: 40rem)`),[f,p]=(0,b.useState)(``),[m,h]=(0,b.useState)(()=>{try{return localStorage.getItem(`tekjobs.sidebar`)===`collapsed`}catch{return!1}}),g=()=>h(e=>{try{localStorage.setItem(`tekjobs.sidebar`,e?`open`:`collapsed`)}catch{}return!e}),_=e=>{t(e),u(!1)};return(0,x.jsxs)(`div`,{className:`shell${m?` shell--collapsed`:``}${d?` shell--strip`:``}`,children:[(0,x.jsx)(`aside`,{className:`sidebar`,"aria-label":`Primary`,children:(0,x.jsx)(l_,{page:e,onNavigate:_,onboarded:a,counts:o,lastScan:s,collapsed:m||d,onToggleCollapsed:d?void 0:g,tipSide:d?`bottom`:`right`})}),(0,x.jsxs)(`div`,{className:`main`,children:[(0,x.jsxs)(`header`,{className:`topbar`,children:[(0,x.jsxs)(Sp,{open:l,onOpenChange:u,side:`left`,size:`sm`,children:[(0,x.jsx)(Sp.Trigger,{asChild:!0,children:(0,x.jsx)(B,{className:`topbar__menu`,variant:`ghost`,size:`sm`,"aria-label":`Open navigation`,leadingIcon:(0,x.jsx)(H.Menu,{})})}),(0,x.jsxs)(Sp.Content,{children:[(0,x.jsx)(Sp.Title,{children:`Navigation`}),(0,x.jsx)(`div`,{className:`sidebar sidebar--sheet`,children:(0,x.jsx)(l_,{page:e,onNavigate:_,onboarded:a,counts:o,lastScan:s,collapsed:!1})})]})]}),(0,x.jsx)(`h1`,{className:`topbar__title`,children:o_[e]}),(0,x.jsx)(`form`,{className:`topbar__search`,onSubmit:e=>{e.preventDefault(),n(f.trim())},children:(0,x.jsx)(Zp,{size:`sm`,placeholder:`Search company or role`,"aria-label":`Search jobs`,leadingIcon:(0,x.jsx)(H.Search,{}),trailingIcon:(0,x.jsx)(`span`,{className:`num`,children:`⌘K`}),value:f,onChange:e=>p(e.target.value)})}),(0,x.jsxs)(`div`,{className:`topbar__actions`,children:[(0,x.jsx)(r_,{}),(0,x.jsx)(Bh,{content:r===`light`?`Switch to dark`:`Switch to light`,children:(0,x.jsx)(B,{variant:`ghost`,size:`sm`,onClick:i,"aria-label":`Toggle theme`,leadingIcon:r===`light`?(0,x.jsx)(H.Moon,{}):(0,x.jsx)(H.Sun,{})})})]})]}),c]})]})}function l_({page:e,onNavigate:t,onboarded:n,counts:r,lastScan:i,collapsed:a,onToggleCollapsed:o,tipSide:s=`right`}){let c=new Map(a_.map(e=>[e.page,e])),l=n?s_:[{title:`Start`,pages:[`onboarding`]},...s_.map(e=>({...e,pages:e.pages.filter(e=>e!==`onboarding`)}))];return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`div`,{className:`brand`,children:[(0,x.jsx)(n_,{}),o&&(0,x.jsx)(Bh,{content:a?`Expand the sidebar`:`Collapse the sidebar`,children:(0,x.jsx)(B,{className:`sidebar__collapse`,variant:`ghost`,size:`sm`,tone:`neutral`,"aria-label":a?`Expand the sidebar`:`Collapse the sidebar`,leadingIcon:a?(0,x.jsx)(H.ChevronRight,{}):(0,x.jsx)(H.ChevronLeft,{}),onClick:o})})]}),(0,x.jsx)(`nav`,{className:`nav`,"aria-label":`Pages`,children:l.map(n=>(0,x.jsxs)(`div`,{className:`nav__group`,children:[!a&&(0,x.jsx)(`div`,{className:`nav__group-title`,children:n.title}),n.pages.map(e=>c.get(e)).filter(e=>!!e).map(n=>{let i=(0,x.jsx)(B,{className:`nav__item`,align:`start`,variant:e===n.page?`soft`:`ghost`,tone:e===n.page?`primary`:`neutral`,size:`sm`,leadingIcon:(0,x.jsx)(n.icon,{}),trailingIcon:!a&&r[n.page]!=null?(0,x.jsx)(`span`,{className:`nav__count`,children:r[n.page]}):void 0,"aria-current":e===n.page?`page`:void 0,"aria-label":a?n.label:void 0,onClick:()=>t(n.page),children:a?null:n.label},n.page);return a?(0,x.jsx)(Bh,{content:r[n.page]==null?n.label:`${n.label} · ${r[n.page]}`,side:s,children:i},n.page):i})]},n.title))}),!a&&(0,x.jsx)(`div`,{className:`sidebar__foot`,children:(0,x.jsxs)(`div`,{children:[`Notes are the record.`,i&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`br`,{}),(0,x.jsx)(`span`,{className:`num`,children:i})]}),(0,x.jsx)(`br`,{}),(0,x.jsxs)(`span`,{className:`num`,children:[`v`,`0.29.2`]}),(0,x.jsx)(`br`,{}),(0,x.jsx)(`a`,{className:`sidebar__legal`,href:`https://tekjobs.timurtek.com/legal/terms`,target:`_blank`,rel:`noreferrer`,children:`Terms`}),` · `,(0,x.jsx)(`a`,{className:`sidebar__legal`,href:`https://tekjobs.timurtek.com/legal/privacy`,target:`_blank`,rel:`noreferrer`,children:`Privacy`})]})})]})}var u_=`{
60
+ `)?4:1,value:e.value,onChange:e=>c(t,{value:e.target.value})}),(0,x.jsxs)(`div`,{className:`copy__edit-actions`,children:[(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`neutral`,"aria-label":`Move up`,leadingIcon:(0,x.jsx)(H.ArrowUp,{}),disabled:t===0,onClick:()=>l(t,-1)}),(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`neutral`,"aria-label":`Move down`,leadingIcon:(0,x.jsx)(H.ArrowDown,{}),disabled:t===i.length-1,onClick:()=>l(t,1)}),(0,x.jsx)(B,{size:`sm`,variant:`ghost`,tone:`danger`,"aria-label":`Remove`,leadingIcon:(0,x.jsx)(H.Trash,{}),onClick:()=>u(t)})]})]},t))}),(0,x.jsxs)(`div`,{className:`form__actions`,children:[(0,x.jsx)(B,{size:`sm`,variant:`soft`,tone:`neutral`,leadingIcon:(0,x.jsx)(H.Plus,{}),onClick:()=>a(e=>[...e,{group:e[e.length-1]?.group??``,label:``,value:``}]),children:`Add a line`}),(0,x.jsx)(`span`,{className:`toolbar__spacer`}),(0,x.jsx)(B,{size:`sm`,variant:`ghost`,onClick:n,children:`Cancel`}),(0,x.jsx)(B,{size:`sm`,tone:`primary`,loading:o,onClick:async()=>{s(!0);try{let e=await $.saveSnippets(i);X({title:`Copy panel saved`,description:`Profile/Snippets.md`,tone:`success`}),r(e)}catch(e){X({title:`Not saved`,description:e.message,tone:`danger`})}s(!1)},children:`Save`})]})]})})})}var a_=[{page:`onboarding`,label:`Get started`,icon:H.Sparkles},{page:`today`,label:`Today`,icon:H.Star},{page:`overview`,label:`Overview`,icon:H.Home},{page:`jobs`,label:`Jobs`,icon:H.Inbox},{page:`pipeline`,label:`Pipeline`,icon:H.Layers},{page:`mail`,label:`Mail`,icon:H.Mail},{page:`people`,label:`People`,icon:H.Users},{page:`companies`,label:`Sources`,icon:H.Globe},{page:`criteria`,label:`Criteria`,icon:H.Filter},{page:`profile`,label:`Profile`,icon:H.User},{page:`runs`,label:`Runs`,icon:H.Refresh},{page:`agent`,label:`Agent access`,icon:H.Terminal},{page:`settings`,label:`Settings`,icon:H.Settings}],o_={onboarding:`Get started`,today:`Today`,overview:`Overview`,jobs:`Jobs`,pipeline:`Pipeline`,mail:`Mail`,people:`People`,companies:`Sources`,criteria:`Criteria`,profile:`Profile`,runs:`Runs`,agent:`Agent access`,settings:`Settings`},s_=[{title:`Work`,pages:[`today`,`jobs`,`pipeline`,`mail`,`people`]},{title:`Insights`,pages:[`overview`]},{title:`Setup`,pages:[`profile`,`criteria`,`companies`]},{title:`System`,pages:[`runs`,`agent`,`settings`,`onboarding`]}];function c_({page:e,onNavigate:t,onSearch:n,theme:r,onToggleTheme:i,onboarded:a=!0,counts:o={},lastScan:s=``,children:c}){let[l,u]=(0,b.useState)(!1),d=e_(`(max-width: 40rem)`),[f,p]=(0,b.useState)(``),[m,h]=(0,b.useState)(()=>{try{return localStorage.getItem(`tekjobs.sidebar`)===`collapsed`}catch{return!1}}),g=()=>h(e=>{try{localStorage.setItem(`tekjobs.sidebar`,e?`open`:`collapsed`)}catch{}return!e}),_=e=>{t(e),u(!1)};return(0,x.jsxs)(`div`,{className:`shell${m?` shell--collapsed`:``}${d?` shell--strip`:``}`,children:[(0,x.jsx)(`aside`,{className:`sidebar`,"aria-label":`Primary`,children:(0,x.jsx)(l_,{page:e,onNavigate:_,onboarded:a,counts:o,lastScan:s,collapsed:m||d,onToggleCollapsed:d?void 0:g,tipSide:d?`bottom`:`right`})}),(0,x.jsxs)(`div`,{className:`main`,children:[(0,x.jsxs)(`header`,{className:`topbar`,children:[(0,x.jsxs)(Sp,{open:l,onOpenChange:u,side:`left`,size:`sm`,children:[(0,x.jsx)(Sp.Trigger,{asChild:!0,children:(0,x.jsx)(B,{className:`topbar__menu`,variant:`ghost`,size:`sm`,"aria-label":`Open navigation`,leadingIcon:(0,x.jsx)(H.Menu,{})})}),(0,x.jsxs)(Sp.Content,{children:[(0,x.jsx)(Sp.Title,{children:`Navigation`}),(0,x.jsx)(`div`,{className:`sidebar sidebar--sheet`,children:(0,x.jsx)(l_,{page:e,onNavigate:_,onboarded:a,counts:o,lastScan:s,collapsed:!1})})]})]}),(0,x.jsx)(`h1`,{className:`topbar__title`,children:o_[e]}),(0,x.jsx)(`form`,{className:`topbar__search`,onSubmit:e=>{e.preventDefault(),n(f.trim())},children:(0,x.jsx)(Zp,{size:`sm`,placeholder:`Search company or role`,"aria-label":`Search jobs`,leadingIcon:(0,x.jsx)(H.Search,{}),trailingIcon:(0,x.jsx)(`span`,{className:`num`,children:`⌘K`}),value:f,onChange:e=>p(e.target.value)})}),(0,x.jsxs)(`div`,{className:`topbar__actions`,children:[(0,x.jsx)(r_,{}),(0,x.jsx)(Bh,{content:r===`light`?`Switch to dark`:`Switch to light`,children:(0,x.jsx)(B,{variant:`ghost`,size:`sm`,onClick:i,"aria-label":`Toggle theme`,leadingIcon:r===`light`?(0,x.jsx)(H.Moon,{}):(0,x.jsx)(H.Sun,{})})})]})]}),c]})]})}function l_({page:e,onNavigate:t,onboarded:n,counts:r,lastScan:i,collapsed:a,onToggleCollapsed:o,tipSide:s=`right`}){let c=new Map(a_.map(e=>[e.page,e])),l=n?s_:[{title:`Start`,pages:[`onboarding`]},...s_.map(e=>({...e,pages:e.pages.filter(e=>e!==`onboarding`)}))];return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`div`,{className:`brand`,children:[(0,x.jsx)(n_,{}),o&&(0,x.jsx)(Bh,{content:a?`Expand the sidebar`:`Collapse the sidebar`,children:(0,x.jsx)(B,{className:`sidebar__collapse`,variant:`ghost`,size:`sm`,tone:`neutral`,"aria-label":a?`Expand the sidebar`:`Collapse the sidebar`,leadingIcon:a?(0,x.jsx)(H.ChevronRight,{}):(0,x.jsx)(H.ChevronLeft,{}),onClick:o})})]}),(0,x.jsx)(`nav`,{className:`nav`,"aria-label":`Pages`,children:l.map(n=>(0,x.jsxs)(`div`,{className:`nav__group`,children:[!a&&(0,x.jsx)(`div`,{className:`nav__group-title`,children:n.title}),n.pages.map(e=>c.get(e)).filter(e=>!!e).map(n=>{let i=(0,x.jsx)(B,{className:`nav__item`,align:`start`,variant:e===n.page?`soft`:`ghost`,tone:e===n.page?`primary`:`neutral`,size:`sm`,leadingIcon:(0,x.jsx)(n.icon,{}),trailingIcon:!a&&r[n.page]!=null?(0,x.jsx)(`span`,{className:`nav__count`,children:r[n.page]}):void 0,"aria-current":e===n.page?`page`:void 0,"aria-label":a?n.label:void 0,onClick:()=>t(n.page),children:a?null:n.label},n.page);return a?(0,x.jsx)(Bh,{content:r[n.page]==null?n.label:`${n.label} · ${r[n.page]}`,side:s,children:i},n.page):i})]},n.title))}),!a&&(0,x.jsx)(`div`,{className:`sidebar__foot`,children:(0,x.jsxs)(`div`,{children:[`Notes are the record.`,i&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`br`,{}),(0,x.jsx)(`span`,{className:`num`,children:i})]}),(0,x.jsx)(`br`,{}),(0,x.jsxs)(`span`,{className:`num`,children:[`v`,`0.31.0`]}),(0,x.jsx)(`br`,{}),(0,x.jsx)(`a`,{className:`sidebar__legal`,href:`https://tekjobs.timurtek.com/legal/terms`,target:`_blank`,rel:`noreferrer`,children:`Terms`}),` · `,(0,x.jsx)(`a`,{className:`sidebar__legal`,href:`https://tekjobs.timurtek.com/legal/privacy`,target:`_blank`,rel:`noreferrer`,children:`Privacy`})]})})]})}var u_=`{
61
61
  "mcpServers": {
62
62
  "tekjobs": {
63
63
  "command": "node",
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/brand/tekjobs-avatar-192.png" type="image/png" sizes="192x192" />
9
9
  <link rel="apple-touch-icon" href="/brand/tekjobs-avatar-192.png" />
10
10
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@500;600&family=Hanken+Grotesk:wght@400;500;600&family=Azeret+Mono:wght@400;500&display=swap" data-zengin="fonts" />
11
- <script type="module" crossorigin src="/assets/index-Cl1o8hT9.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-Dnu8dxG5.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-CbvdnTFY.css">
13
13
  </head>
14
14
  <body>
@@ -2,6 +2,8 @@
2
2
  // TekJobs MCP server (stdio, JSON-RPC 2.0). Lets Claude Code / Claude Desktop run the job search without the UI:
3
3
  // search and read matches, move them through the pipeline, add notes, edit criteria, add boards, trigger a scan,
4
4
  // and pull the materials needed to tailor an application. Hand-rolled: no SDK dependency.
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
5
7
  import * as store from './store.mjs';
6
8
  import * as letter from './cover-letter.mjs';
7
9
  import * as tailored from './tailored-resume.mjs';
@@ -9,7 +11,7 @@ import * as mail from './mail-check.mjs';
9
11
  import * as people from './people.mjs';
10
12
  import * as linkedin from './linkedin-import.mjs';
11
13
 
12
- const TOOLS = [
14
+ export const TOOLS = [
13
15
  { name: 'list_snippets', description: 'The copy panel: the person\'s standard answers for application forms (name, email, phone, links, availability, salary answer, anything they added), grouped, from Profile/Snippets.md. Use these verbatim when drafting form answers; never invent a value that is empty here.', inputSchema: { type: 'object', properties: {} } },
14
16
  { name: 'list_people', description: 'The people in the search: recruiters, hiring managers, interviewers and referrals, one note each under People/, with role, company, email, last contact and the job notes they are on. Newest contact first.', inputSchema: { type: 'object', properties: {} } },
15
17
  { name: 'get_person', description: 'One person in full: the row plus their About text and dated Log of contacts.', inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
@@ -56,7 +58,7 @@ const TOOLS = [
56
58
  { name: 'add_company', description: 'Append a board to the watchlist. ats is the platform (greenhouse, lever, ashby, workday, rippling, smartrecruiters, workable, bamboohr, breezy, personio, teamtailor, eightfold, atlassian, github, spotify, amazon); slug is the board token.', inputSchema: { type: 'object', properties: { name: { type: 'string' }, ats: { type: 'string' }, slug: { type: 'string' }, tier: { type: 'string' }, notes: { type: 'string' } }, required: ['name', 'ats', 'slug'] } },
57
59
  ];
58
60
 
59
- async function call(name, a = {}) {
61
+ export async function call(name, a = {}) {
60
62
  switch (name) {
61
63
  case 'onboarding_status': return store.onboardingStatus();
62
64
  case 'onboarding_materials': return store.onboardingMaterials();
@@ -108,8 +110,9 @@ async function call(name, a = {}) {
108
110
 
109
111
  const write = (o) => process.stdout.write(JSON.stringify(o) + '\n');
110
112
  let buf = '';
111
- process.stdin.setEncoding('utf8');
112
- process.stdin.on('data', (chunk) => {
113
+ const isMain = !!process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
114
+ if (isMain) process.stdin.setEncoding('utf8');
115
+ if (isMain) process.stdin.on('data', (chunk) => {
113
116
  buf += chunk; let i;
114
117
  while ((i = buf.indexOf('\n')) >= 0) {
115
118
  const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1);
@@ -131,4 +134,4 @@ function handle(msg) {
131
134
  }
132
135
  if (id !== undefined) write({ jsonrpc: '2.0', id, error: { code: -32601, message: `method not found: ${method}` } });
133
136
  }
134
- process.stderr.write(`tekjobs-mcp: vault ${store.VAULT}\n`);
137
+ if (isMain) process.stderr.write(`tekjobs-mcp: vault ${store.VAULT}\n`);
package/cli.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  // tekjobs CLI: init a profile folder, import a resume, check onboarding, scan, serve, mcp.
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
+ import os from 'node:os';
5
6
  import { spawn, spawnSync } from 'node:child_process';
6
7
  import { fileURLToPath } from 'node:url';
7
8
 
@@ -37,7 +38,10 @@ const HELP = `tekjobs — a local job-search machine
37
38
  tekjobs import linkedin <zip|folder> your LinkedIn data export into the search: who you know at each
38
39
  company, and the recruiters who wrote
39
40
  [--since YYYY-MM-DD] [--everyone] [--no-people] [--preview] [--dry]
40
- tekjobs serve the app + API on http://127.0.0.1:8787
41
+ tekjobs up [--dev] [--port N] start the API (and with --dev, Vite) detached: they stay up until \`down\`
42
+ tekjobs down stop what \`up\` started
43
+ tekjobs ps what \`up\` has running
44
+ tekjobs serve the app + API on http://127.0.0.1:8787, in this terminal
41
45
  tekjobs mcp the MCP server on stdio (Claude Code, Codex, Cursor, Claude Desktop)
42
46
 
43
47
  The profile folder is resolved from TEKJOBS_PROFILE, then ~/.tekjobs/config.json, then ~/.tekjobs/profile.
@@ -167,6 +171,9 @@ async function main() {
167
171
  if (!s.dry) console.log(` Index: ${s.indexPath}. Job notes now show who you know at each company.`);
168
172
  return;
169
173
  }
174
+ if (cmd === 'up') return up();
175
+ if (cmd === 'down') return down();
176
+ if (cmd === 'ps') return ps();
170
177
  if (cmd === 'serve') return run(process.execPath, [path.join(ROOT, 'app', 'server', 'index.mjs')]);
171
178
  if (cmd === 'mcp') return run(process.execPath, [path.join(ROOT, 'app', 'server', 'mcp.mjs')]);
172
179
  console.error(`unknown command "${cmd}"\n`); console.log(HELP); process.exit(1);
@@ -177,6 +184,69 @@ function printStatus(s) {
177
184
  console.log(s.complete ? '\nAll set. The daily scan takes it from here.' : '\nNext: the first unchecked line above.');
178
185
  }
179
186
  function run(bin, args) { const p = spawn(bin, args, { stdio: 'inherit', env: process.env }); p.on('close', (c) => process.exit(c ?? 0)); }
187
+
188
+ // ---- up / down / ps: the servers, started once, detached from this terminal, left running until `down` ----
189
+ // A server started in a terminal dies with the tab, and one started by an editor's preview dies with the session;
190
+ // both meant starting them again every morning. These start the API (and with --dev, Vite) as detached processes
191
+ // with their output in ~/.tekjobs/run/*.log and their pids in servers.json, and nothing stops them but `down`.
192
+ const RUN_DIR = path.join(os.homedir(), '.tekjobs', 'run');
193
+ const RUN_FILE = path.join(RUN_DIR, 'servers.json');
194
+ const alive = (pid) => { if (!pid) return false; try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; } };
195
+ const readRun = () => { try { return JSON.parse(fs.readFileSync(RUN_FILE, 'utf8')); } catch { return {}; } };
196
+ const writeRun = (o) => { fs.mkdirSync(RUN_DIR, { recursive: true }); fs.writeFileSync(RUN_FILE, JSON.stringify(o, null, 2)); };
197
+ function startDetached(name, bin, args, env, cwd) {
198
+ fs.mkdirSync(RUN_DIR, { recursive: true });
199
+ const log = path.join(RUN_DIR, `${name}.log`);
200
+ const fd = fs.openSync(log, 'a');
201
+ fs.writeSync(fd, `\n===== ${new Date().toISOString()} start =====\n`);
202
+ const p = spawn(bin, args, { cwd, env: { ...process.env, ...env }, detached: true, stdio: ['ignore', fd, fd], windowsHide: true });
203
+ fs.closeSync(fd);
204
+ p.unref();
205
+ return { pid: p.pid, log, started: new Date().toISOString() };
206
+ }
207
+ function killTree(pid) {
208
+ if (process.platform === 'win32') { spawnSync('taskkill', ['/T', '/F', '/PID', String(pid)], { stdio: 'ignore' }); return; }
209
+ try { process.kill(-pid, 'SIGTERM'); } catch { try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ } }
210
+ }
211
+ async function up() {
212
+ const port = Number(opt('--port') || process.env.PORT || 8787);
213
+ const vitePort = Number(opt('--vite-port') || 5173);
214
+ const dev = rest.includes('--dev');
215
+ const state = readRun();
216
+ const next = { ...state };
217
+ if (state.api && alive(state.api.pid)) console.log(`API already up: pid ${state.api.pid}, http://127.0.0.1:${state.api.port}`);
218
+ else {
219
+ next.api = { ...startDetached('api', process.execPath, [path.join(ROOT, 'app', 'server', 'index.mjs')], { PORT: String(port) }, ROOT), port };
220
+ console.log(`API up: pid ${next.api.pid}, http://127.0.0.1:${port} (log: ${next.api.log})`);
221
+ }
222
+ if (dev) {
223
+ if (state.vite && alive(state.vite.pid)) console.log(`Vite already up: pid ${state.vite.pid}, http://localhost:${state.vite.port}`);
224
+ else {
225
+ const viteArgs = ['run', 'dev', '--', '--port', String(vitePort), '--strictPort'];
226
+ const [bin, args] = process.platform === 'win32' ? ['cmd', ['/c', 'npm', ...viteArgs]] : ['npm', viteArgs];
227
+ next.vite = { ...startDetached('vite', bin, args, {}, path.join(ROOT, 'app')), port: vitePort };
228
+ console.log(`Vite up: pid ${next.vite.pid}, http://localhost:${vitePort} (log: ${next.vite.log})`);
229
+ }
230
+ } else if (state.vite && !alive(state.vite.pid)) delete next.vite;
231
+ writeRun(next);
232
+ for (let i = 0; i < 40; i++) { try { if ((await fetch(`http://127.0.0.1:${port}/api/summary`)).ok) break; } catch { /* not yet */ } await new Promise((r) => setTimeout(r, 250)); }
233
+ console.log('They stay up until `tekjobs down`; closing this terminal does not stop them. `tekjobs ps` shows them.');
234
+ }
235
+ function down() {
236
+ const state = readRun();
237
+ let n = 0;
238
+ for (const [name, s] of Object.entries(state)) {
239
+ if (s?.pid && alive(s.pid)) { killTree(s.pid); n++; console.log(`${name} stopped (pid ${s.pid})`); }
240
+ }
241
+ try { fs.unlinkSync(RUN_FILE); } catch { /* nothing recorded */ }
242
+ if (!n) console.log('Nothing was up.');
243
+ }
244
+ function ps() {
245
+ const rows = Object.entries(readRun());
246
+ if (!rows.length) return console.log('Nothing up. `tekjobs up` starts the API; `tekjobs up --dev` the API and Vite.');
247
+ for (const [name, s] of rows) console.log(`${name.padEnd(5)} ${alive(s.pid) ? 'up ' : 'gone'} pid ${String(s.pid).padEnd(6)} http://127.0.0.1:${s.port} since ${s.started} log: ${s.log}`);
248
+ }
249
+
180
250
  main().catch((e) => { console.error(e.message); process.exit(1); });
181
251
 
182
252
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurtekb/tekjobs",
3
- "version": "0.29.3",
3
+ "version": "0.31.0",
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",
@@ -54,7 +54,11 @@
54
54
  "scraper",
55
55
  "greenhouse",
56
56
  "lever",
57
- "ashby"
57
+ "ashby",
58
+ "claude-code",
59
+ "ai-agent",
60
+ "job-tracker",
61
+ "resume"
58
62
  ],
59
63
  "files": [
60
64
  "cli.mjs",