@profullstack/agenticjobs 0.1.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.
Files changed (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/bin/agenticjobs-mcp.mjs +2 -0
  4. package/bin/agenticjobs.mjs +2 -0
  5. package/dist/cli/args.d.ts +17 -0
  6. package/dist/cli/args.js +103 -0
  7. package/dist/cli/args.js.map +1 -0
  8. package/dist/cli/format.d.ts +15 -0
  9. package/dist/cli/format.js +24 -0
  10. package/dist/cli/format.js.map +1 -0
  11. package/dist/cli/index.d.ts +13 -0
  12. package/dist/cli/index.js +620 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/jobfile.d.ts +15 -0
  15. package/dist/cli/jobfile.js +94 -0
  16. package/dist/cli/jobfile.js.map +1 -0
  17. package/dist/client/client.d.ts +105 -0
  18. package/dist/client/client.js +182 -0
  19. package/dist/client/client.js.map +1 -0
  20. package/dist/client/config.d.ts +33 -0
  21. package/dist/client/config.js +92 -0
  22. package/dist/client/config.js.map +1 -0
  23. package/dist/client/fanout.d.ts +35 -0
  24. package/dist/client/fanout.js +77 -0
  25. package/dist/client/fanout.js.map +1 -0
  26. package/dist/client/index.d.ts +4 -0
  27. package/dist/client/index.js +5 -0
  28. package/dist/client/index.js.map +1 -0
  29. package/dist/client/login.d.ts +23 -0
  30. package/dist/client/login.js +45 -0
  31. package/dist/client/login.js.map +1 -0
  32. package/dist/config.d.ts +40 -0
  33. package/dist/config.js +64 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/core/applications.d.ts +67 -0
  36. package/dist/core/applications.js +187 -0
  37. package/dist/core/applications.js.map +1 -0
  38. package/dist/core/auth.d.ts +120 -0
  39. package/dist/core/auth.js +246 -0
  40. package/dist/core/auth.js.map +1 -0
  41. package/dist/core/import.d.ts +52 -0
  42. package/dist/core/import.js +311 -0
  43. package/dist/core/import.js.map +1 -0
  44. package/dist/core/jobs.d.ts +91 -0
  45. package/dist/core/jobs.js +381 -0
  46. package/dist/core/jobs.js.map +1 -0
  47. package/dist/core/orgs.d.ts +21 -0
  48. package/dist/core/orgs.js +111 -0
  49. package/dist/core/orgs.js.map +1 -0
  50. package/dist/core/resumes.d.ts +51 -0
  51. package/dist/core/resumes.js +144 -0
  52. package/dist/core/resumes.js.map +1 -0
  53. package/dist/core/zip.d.ts +17 -0
  54. package/dist/core/zip.js +77 -0
  55. package/dist/core/zip.js.map +1 -0
  56. package/dist/db/index.d.ts +3 -0
  57. package/dist/db/index.js +3 -0
  58. package/dist/db/index.js.map +1 -0
  59. package/dist/db/migrate.d.ts +15 -0
  60. package/dist/db/migrate.js +67 -0
  61. package/dist/db/migrate.js.map +1 -0
  62. package/dist/db/pool.d.ts +13 -0
  63. package/dist/db/pool.js +68 -0
  64. package/dist/db/pool.js.map +1 -0
  65. package/dist/db/seed.d.ts +9 -0
  66. package/dist/db/seed.js +120 -0
  67. package/dist/db/seed.js.map +1 -0
  68. package/dist/directory/announce.d.ts +32 -0
  69. package/dist/directory/announce.js +59 -0
  70. package/dist/directory/announce.js.map +1 -0
  71. package/dist/directory/federate.d.ts +59 -0
  72. package/dist/directory/federate.js +126 -0
  73. package/dist/directory/federate.js.map +1 -0
  74. package/dist/directory/fetch.d.ts +35 -0
  75. package/dist/directory/fetch.js +138 -0
  76. package/dist/directory/fetch.js.map +1 -0
  77. package/dist/directory/index.d.ts +4 -0
  78. package/dist/directory/index.js +5 -0
  79. package/dist/directory/index.js.map +1 -0
  80. package/dist/directory/registry.d.ts +55 -0
  81. package/dist/directory/registry.js +155 -0
  82. package/dist/directory/registry.js.map +1 -0
  83. package/dist/index.d.ts +14 -0
  84. package/dist/index.js +14 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/markup/escape.d.ts +20 -0
  87. package/dist/markup/escape.js +56 -0
  88. package/dist/markup/escape.js.map +1 -0
  89. package/dist/markup/markdown.d.ts +35 -0
  90. package/dist/markup/markdown.js +279 -0
  91. package/dist/markup/markdown.js.map +1 -0
  92. package/dist/markup/resume.d.ts +72 -0
  93. package/dist/markup/resume.js +260 -0
  94. package/dist/markup/resume.js.map +1 -0
  95. package/dist/mcp/protocol.d.ts +56 -0
  96. package/dist/mcp/protocol.js +48 -0
  97. package/dist/mcp/protocol.js.map +1 -0
  98. package/dist/mcp/server.d.ts +21 -0
  99. package/dist/mcp/server.js +73 -0
  100. package/dist/mcp/server.js.map +1 -0
  101. package/dist/mcp/stdio.d.ts +13 -0
  102. package/dist/mcp/stdio.js +85 -0
  103. package/dist/mcp/stdio.js.map +1 -0
  104. package/dist/mcp/tools.d.ts +26 -0
  105. package/dist/mcp/tools.js +351 -0
  106. package/dist/mcp/tools.js.map +1 -0
  107. package/dist/schema/index.d.ts +5 -0
  108. package/dist/schema/index.js +6 -0
  109. package/dist/schema/index.js.map +1 -0
  110. package/dist/schema/instance.d.ts +85 -0
  111. package/dist/schema/instance.js +147 -0
  112. package/dist/schema/instance.js.map +1 -0
  113. package/dist/schema/job.d.ts +151 -0
  114. package/dist/schema/job.js +73 -0
  115. package/dist/schema/job.js.map +1 -0
  116. package/dist/schema/jsonld.d.ts +18 -0
  117. package/dist/schema/jsonld.js +120 -0
  118. package/dist/schema/jsonld.js.map +1 -0
  119. package/dist/schema/query.d.ts +47 -0
  120. package/dist/schema/query.js +103 -0
  121. package/dist/schema/query.js.map +1 -0
  122. package/dist/schema/text.d.ts +27 -0
  123. package/dist/schema/text.js +117 -0
  124. package/dist/schema/text.js.map +1 -0
  125. package/dist/server/app.d.ts +13 -0
  126. package/dist/server/app.js +57 -0
  127. package/dist/server/app.js.map +1 -0
  128. package/dist/server/deps.d.ts +20 -0
  129. package/dist/server/deps.js +2 -0
  130. package/dist/server/deps.js.map +1 -0
  131. package/dist/server/middleware.d.ts +39 -0
  132. package/dist/server/middleware.js +100 -0
  133. package/dist/server/middleware.js.map +1 -0
  134. package/dist/server/routes/api.d.ts +20 -0
  135. package/dist/server/routes/api.js +584 -0
  136. package/dist/server/routes/api.js.map +1 -0
  137. package/dist/server/routes/descriptor.d.ts +11 -0
  138. package/dist/server/routes/descriptor.js +31 -0
  139. package/dist/server/routes/descriptor.js.map +1 -0
  140. package/dist/server/routes/discovery.d.ts +12 -0
  141. package/dist/server/routes/discovery.js +305 -0
  142. package/dist/server/routes/discovery.js.map +1 -0
  143. package/dist/server/routes/mcp.d.ts +19 -0
  144. package/dist/server/routes/mcp.js +85 -0
  145. package/dist/server/routes/mcp.js.map +1 -0
  146. package/dist/server/routes/openapi.d.ts +11 -0
  147. package/dist/server/routes/openapi.js +297 -0
  148. package/dist/server/routes/openapi.js.map +1 -0
  149. package/dist/server/routes/pages.d.ts +11 -0
  150. package/dist/server/routes/pages.js +445 -0
  151. package/dist/server/routes/pages.js.map +1 -0
  152. package/dist/server/routes/passkey.d.ts +20 -0
  153. package/dist/server/routes/passkey.js +198 -0
  154. package/dist/server/routes/passkey.js.map +1 -0
  155. package/dist/server/routes/specs.d.ts +10 -0
  156. package/dist/server/routes/specs.js +42 -0
  157. package/dist/server/routes/specs.js.map +1 -0
  158. package/dist/server/serve.d.ts +11 -0
  159. package/dist/server/serve.js +79 -0
  160. package/dist/server/serve.js.map +1 -0
  161. package/dist/tui/index.d.ts +13 -0
  162. package/dist/tui/index.js +266 -0
  163. package/dist/tui/index.js.map +1 -0
  164. package/dist/tui/state.d.ts +63 -0
  165. package/dist/tui/state.js +136 -0
  166. package/dist/tui/state.js.map +1 -0
  167. package/dist/tui/types.d.ts +2 -0
  168. package/dist/tui/types.js +2 -0
  169. package/dist/tui/types.js.map +1 -0
  170. package/dist/tui/views.d.ts +11 -0
  171. package/dist/tui/views.js +197 -0
  172. package/dist/tui/views.js.map +1 -0
  173. package/dist/views/auth.d.ts +21 -0
  174. package/dist/views/auth.js +5 -0
  175. package/dist/views/auth.js.map +1 -0
  176. package/dist/views/docs.d.ts +17 -0
  177. package/dist/views/docs.js +40 -0
  178. package/dist/views/docs.js.map +1 -0
  179. package/dist/views/jobs.d.ts +49 -0
  180. package/dist/views/jobs.js +44 -0
  181. package/dist/views/jobs.js.map +1 -0
  182. package/dist/views/layout.d.ts +58 -0
  183. package/dist/views/layout.js +36 -0
  184. package/dist/views/layout.js.map +1 -0
  185. package/dist/views/me.d.ts +27 -0
  186. package/dist/views/me.js +6 -0
  187. package/dist/views/me.js.map +1 -0
  188. package/dist/views/network.d.ts +21 -0
  189. package/dist/views/network.js +17 -0
  190. package/dist/views/network.js.map +1 -0
  191. package/dist/views/post.d.ts +22 -0
  192. package/dist/views/post.js +20 -0
  193. package/dist/views/post.js.map +1 -0
  194. package/docs/openjob.md +200 -0
  195. package/docs/openresume.md +143 -0
  196. package/migrations/0001_init.sql +109 -0
  197. package/migrations/0002_auth.sql +82 -0
  198. package/migrations/0003_directory.sql +27 -0
  199. package/migrations/0004_resumes.sql +63 -0
  200. package/migrations/0005_application_drafts.sql +20 -0
  201. package/package.json +92 -0
  202. package/web/public/app.css +731 -0
  203. package/web/public/app.js +173 -0
  204. package/web/public/icon.svg +5 -0
  205. package/web/public/sw.js +64 -0
  206. package/web/public/tokens.css +153 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * OpenResume.md.
3
+ *
4
+ * A resume is a Markdown document, and the Markdown is the canonical copy.
5
+ * Everything else - the structured object below, the HTML on the page, a PDF
6
+ * someone exports - is derived from it and regenerated, never edited beside
7
+ * it. That is the whole point of choosing Markdown over a form: the candidate
8
+ * owns a file they can read, diff, and paste into any other tool, and an agent
9
+ * can write one without a schema-shaped API.
10
+ *
11
+ * The convention is deliberately thin, because a resume that fails to parse
12
+ * must still be a usable resume. Every rule here degrades: an unrecognised
13
+ * section is kept verbatim, a missing date is simply absent, and a document
14
+ * with nothing but an h1 and some prose is valid.
15
+ *
16
+ * # Name <- exactly one h1, the person
17
+ * - **Email**: a@b.com <- the list right after it is the contact block
18
+ * ## Experience <- h2 opens a section
19
+ * ### Company | Location <- h3 opens an entry
20
+ * Role (Mar 2020 - Present) <- the line under an entry is its subtitle
21
+ * - Did a thing <- bullets are highlights
22
+ *
23
+ * The full specification is in docs/openresume.md.
24
+ */
25
+ export interface ResumeContact {
26
+ key: string;
27
+ value: string;
28
+ /** Set when the value is a URL or an email we can link. */
29
+ href: string | null;
30
+ }
31
+ export interface ResumeEntry {
32
+ /** The h3 text, with a `|` split into title and place. */
33
+ title: string;
34
+ place: string | null;
35
+ /** The line under the heading: a role, a degree, a one-line description. */
36
+ subtitle: string | null;
37
+ /** Parsed out of the subtitle when it ends in a bracketed range. */
38
+ start: string | null;
39
+ end: string | null;
40
+ /** True when the range ends in "present", "now" or "current". */
41
+ current: boolean;
42
+ highlights: string[];
43
+ /** Everything under the entry, verbatim, so nothing is ever lost. */
44
+ markdown: string;
45
+ }
46
+ export interface ResumeSection {
47
+ /** The h2 text as written. */
48
+ title: string;
49
+ /** Lowercased and normalised, for matching. */
50
+ kind: string;
51
+ entries: ResumeEntry[];
52
+ /** Prose and bullets that sat directly under the h2. */
53
+ markdown: string;
54
+ }
55
+ export interface OpenResume {
56
+ name: string | null;
57
+ headline: string | null;
58
+ contact: ResumeContact[];
59
+ sections: ResumeSection[];
60
+ /** The document as supplied. Always the source of truth. */
61
+ markdown: string;
62
+ /** Things a candidate would probably want to fix. Never fatal. */
63
+ warnings: string[];
64
+ }
65
+ export declare function parseResume(source: string): OpenResume;
66
+ /**
67
+ * Everything an employer would filter on, flattened for the search index.
68
+ * Derived, never stored as the truth: regenerated from the Markdown on save.
69
+ */
70
+ export declare function resumeSearchText(resume: OpenResume): string;
71
+ /** A starting document, so nobody faces an empty box. */
72
+ export declare function resumeTemplate(name?: string): string;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * OpenResume.md.
3
+ *
4
+ * A resume is a Markdown document, and the Markdown is the canonical copy.
5
+ * Everything else - the structured object below, the HTML on the page, a PDF
6
+ * someone exports - is derived from it and regenerated, never edited beside
7
+ * it. That is the whole point of choosing Markdown over a form: the candidate
8
+ * owns a file they can read, diff, and paste into any other tool, and an agent
9
+ * can write one without a schema-shaped API.
10
+ *
11
+ * The convention is deliberately thin, because a resume that fails to parse
12
+ * must still be a usable resume. Every rule here degrades: an unrecognised
13
+ * section is kept verbatim, a missing date is simply absent, and a document
14
+ * with nothing but an h1 and some prose is valid.
15
+ *
16
+ * # Name <- exactly one h1, the person
17
+ * - **Email**: a@b.com <- the list right after it is the contact block
18
+ * ## Experience <- h2 opens a section
19
+ * ### Company | Location <- h3 opens an entry
20
+ * Role (Mar 2020 - Present) <- the line under an entry is its subtitle
21
+ * - Did a thing <- bullets are highlights
22
+ *
23
+ * The full specification is in docs/openresume.md.
24
+ */
25
+ /** Section names we normalise, so "Work Experience" and "Experience" match. */
26
+ const KINDS = [
27
+ [/^(work\s+)?experience$|^employment$|^work\s+history$/i, 'experience'],
28
+ [/^education$|^academic/i, 'education'],
29
+ [/^skills?$|^technical\s+skills?$|^areas?\s+of\s+expertise$/i, 'skills'],
30
+ [/^projects?$|^open\s+source$/i, 'projects'],
31
+ [/^summary$|^about$|^profile$|^objective$/i, 'summary'],
32
+ [/^links?$|^elsewhere$|^profiles?$/i, 'links'],
33
+ [/^certification|^licen[cs]e/i, 'certifications'],
34
+ [/^languages?$/i, 'languages'],
35
+ [/^publications?$|^talks?$|^writing$/i, 'publications'],
36
+ [/^awards?$|^honou?rs?$/i, 'awards'],
37
+ ];
38
+ function kindOf(title) {
39
+ for (const [pattern, kind] of KINDS) {
40
+ if (pattern.test(title.trim()))
41
+ return kind;
42
+ }
43
+ return title.trim().toLowerCase().replace(/\s+/g, '-').slice(0, 40) || 'section';
44
+ }
45
+ const PRESENT = /\b(present|now|current|ongoing)\b/i;
46
+ export function parseResume(source) {
47
+ const markdown = source.replace(/\r\n?/g, '\n').trim();
48
+ const lines = markdown.split('\n');
49
+ const warnings = [];
50
+ let name = null;
51
+ let headline = null;
52
+ const contact = [];
53
+ const sections = [];
54
+ let section = null;
55
+ let entry = null;
56
+ let seenH1 = false;
57
+ let inPreamble = false;
58
+ const flushEntry = () => {
59
+ if (entry !== null && section !== null) {
60
+ entry.markdown = entry.markdown.trim();
61
+ section.entries.push(entry);
62
+ }
63
+ entry = null;
64
+ };
65
+ const flushSection = () => {
66
+ flushEntry();
67
+ if (section !== null) {
68
+ section.markdown = section.markdown.trim();
69
+ sections.push(section);
70
+ }
71
+ section = null;
72
+ };
73
+ for (const line of lines) {
74
+ const h1 = /^#\s+(.+?)\s*#*\s*$/.exec(line);
75
+ if (h1 !== null) {
76
+ if (seenH1) {
77
+ warnings.push('More than one top-level heading; the first one is used as the name.');
78
+ }
79
+ else {
80
+ name = (h1[1] ?? '').trim();
81
+ seenH1 = true;
82
+ inPreamble = true;
83
+ }
84
+ continue;
85
+ }
86
+ const h2 = /^##\s+(.+?)\s*#*\s*$/.exec(line);
87
+ if (h2 !== null) {
88
+ flushSection();
89
+ inPreamble = false;
90
+ const title = (h2[1] ?? '').trim();
91
+ section = { title, kind: kindOf(title), entries: [], markdown: '' };
92
+ continue;
93
+ }
94
+ const h3 = /^###\s+(.+?)\s*#*\s*$/.exec(line);
95
+ if (h3 !== null && section !== null) {
96
+ flushEntry();
97
+ const raw = (h3[1] ?? '').trim();
98
+ const parts = raw.split('|').map((part) => part.trim());
99
+ entry = {
100
+ title: parts[0] ?? raw,
101
+ place: parts.length > 1 ? parts.slice(1).join(' | ') : null,
102
+ subtitle: null,
103
+ start: null,
104
+ end: null,
105
+ current: false,
106
+ highlights: [],
107
+ markdown: '',
108
+ };
109
+ continue;
110
+ }
111
+ if (inPreamble) {
112
+ const bullet = /^\s*[-*+]\s+(.*)$/.exec(line);
113
+ if (bullet !== null) {
114
+ const field = parseContact(bullet[1] ?? '');
115
+ if (field !== null)
116
+ contact.push(field);
117
+ continue;
118
+ }
119
+ if (line.trim() !== '' && headline === null && !line.startsWith('#')) {
120
+ // A single prose line under the name, before any section, reads as a
121
+ // headline on every resume that has one.
122
+ headline = line.trim().replace(/^[*_]+|[*_]+$/g, '');
123
+ }
124
+ continue;
125
+ }
126
+ if (entry !== null) {
127
+ const bullet = /^\s*[-*+]\s+(.*)$/.exec(line);
128
+ if (bullet !== null) {
129
+ entry.highlights.push((bullet[1] ?? '').trim());
130
+ }
131
+ else if (entry.subtitle === null && line.trim() !== '') {
132
+ const subtitle = line.trim();
133
+ entry.subtitle = subtitle;
134
+ const range = parseRange(subtitle);
135
+ entry.start = range.start;
136
+ entry.end = range.end;
137
+ entry.current = range.current;
138
+ }
139
+ entry.markdown += `${line}\n`;
140
+ continue;
141
+ }
142
+ if (section !== null)
143
+ section.markdown += `${line}\n`;
144
+ }
145
+ flushSection();
146
+ if (name === null)
147
+ warnings.push('No name: the document should start with "# Your Name".');
148
+ if (contact.length === 0) {
149
+ warnings.push('No contact details: add a bullet list under the name, e.g. "- Email: you@example.com".');
150
+ }
151
+ if (!sections.some((item) => item.kind === 'experience')) {
152
+ warnings.push('No experience section found. Employers filter on it.');
153
+ }
154
+ return { name, headline, contact, sections, markdown, warnings };
155
+ }
156
+ /** `- **Email**: a@b.com`, `- Email: a@b.com`, `- [GitHub](https://...)`. */
157
+ function parseContact(raw) {
158
+ const text = raw.trim();
159
+ if (text === '')
160
+ return null;
161
+ const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(text);
162
+ if (link !== null) {
163
+ return { key: (link[1] ?? '').trim(), value: (link[1] ?? '').trim(), href: link[2] ?? null };
164
+ }
165
+ const pair = /^\*{0,2}([^:*]{1,40})\*{0,2}\s*:\s*(.+)$/.exec(text);
166
+ if (pair === null) {
167
+ return { key: 'note', value: stripMarkdown(text), href: hrefFor(stripMarkdown(text)) };
168
+ }
169
+ const key = (pair[1] ?? '').trim();
170
+ const rawValue = (pair[2] ?? '').trim();
171
+ const inner = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(rawValue);
172
+ if (inner !== null) {
173
+ return { key, value: (inner[1] ?? '').trim(), href: inner[2] ?? null };
174
+ }
175
+ const value = stripMarkdown(rawValue);
176
+ return { key, value, href: hrefFor(value) };
177
+ }
178
+ function stripMarkdown(value) {
179
+ return value.replace(/[*_`]/g, '').trim();
180
+ }
181
+ function hrefFor(value) {
182
+ if (/^https?:\/\//i.test(value))
183
+ return value;
184
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
185
+ return `mailto:${value}`;
186
+ if (/^\+?[\d\s().-]{7,}$/.test(value))
187
+ return `tel:${value.replace(/[^\d+]/g, '')}`;
188
+ // A bare domain, with or without a path. "Web: example.com" is how most
189
+ // people write it, and an earlier version of this required a trailing slash
190
+ // and so linked none of them.
191
+ if (/^[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,24}(\/\S*)?$/i.test(value))
192
+ return `https://${value}`;
193
+ return null;
194
+ }
195
+ /** "Full Stack Engineer (Mar 2020 - Present)" -> start, end, current. */
196
+ function parseRange(subtitle) {
197
+ const bracketed = /\(([^)]*)\)\s*$/.exec(subtitle);
198
+ const candidate = bracketed?.[1] ?? subtitle;
199
+ // An en dash, an em dash and a hyphen all show up in real resumes.
200
+ const split = candidate.split(/\s+(?:to|-|–|—)\s+/i);
201
+ if (split.length < 2) {
202
+ if (bracketed !== null && candidate.trim() !== '') {
203
+ return { start: candidate.trim(), end: null, current: PRESENT.test(candidate) };
204
+ }
205
+ return { start: null, end: null, current: false };
206
+ }
207
+ const start = (split[0] ?? '').trim();
208
+ const end = (split[1] ?? '').trim();
209
+ return {
210
+ start: start === '' ? null : start,
211
+ end: end === '' ? null : end,
212
+ current: PRESENT.test(end),
213
+ };
214
+ }
215
+ /**
216
+ * Everything an employer would filter on, flattened for the search index.
217
+ * Derived, never stored as the truth: regenerated from the Markdown on save.
218
+ */
219
+ export function resumeSearchText(resume) {
220
+ const parts = [resume.name ?? '', resume.headline ?? ''];
221
+ for (const section of resume.sections) {
222
+ parts.push(section.title, section.markdown);
223
+ for (const entry of section.entries) {
224
+ parts.push(entry.title, entry.place ?? '', entry.subtitle ?? '', ...entry.highlights);
225
+ }
226
+ }
227
+ return parts.filter((part) => part !== '').join('\n');
228
+ }
229
+ /** A starting document, so nobody faces an empty box. */
230
+ export function resumeTemplate(name = 'Your Name') {
231
+ return [
232
+ `# ${name}`,
233
+ '',
234
+ '- **Email**: you@example.com',
235
+ '- **Location**: City, Country',
236
+ '- **Web**: https://example.com',
237
+ '',
238
+ 'One line on what you do and what you are looking for.',
239
+ '',
240
+ '## Experience',
241
+ '',
242
+ '### Company | Remote',
243
+ 'Your Role (Jan 2024 - Present)',
244
+ '',
245
+ '- What you built, and what changed because you built it.',
246
+ '- Numbers where you have them.',
247
+ '',
248
+ '## Skills',
249
+ '',
250
+ '- Languages: ',
251
+ '- Tools: ',
252
+ '',
253
+ '## Education',
254
+ '',
255
+ '### School',
256
+ 'Degree (Year)',
257
+ '',
258
+ ].join('\n');
259
+ }
260
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../src/markup/resume.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Just enough MCP to be one.
3
+ *
4
+ * The protocol is JSON-RPC 2.0 with a small set of methods, and implementing
5
+ * those directly keeps this package dependency-free and runnable in both
6
+ * places it has to run: inside the server behind /api/mcp, and inside the
7
+ * stdio binary that talks to a remote board.
8
+ */
9
+ export declare const PROTOCOL_VERSION = "2025-06-18";
10
+ export interface JsonRpcRequest {
11
+ jsonrpc: '2.0';
12
+ id?: string | number | null;
13
+ method: string;
14
+ params?: Record<string, unknown>;
15
+ }
16
+ export interface JsonRpcResponse {
17
+ jsonrpc: '2.0';
18
+ id: string | number | null;
19
+ result?: unknown;
20
+ error?: {
21
+ code: number;
22
+ message: string;
23
+ data?: unknown;
24
+ };
25
+ }
26
+ export declare const PARSE_ERROR = -32700;
27
+ export declare const INVALID_REQUEST = -32600;
28
+ export declare const METHOD_NOT_FOUND = -32601;
29
+ export declare const INVALID_PARAMS = -32602;
30
+ export declare const INTERNAL_ERROR = -32603;
31
+ export declare function result(id: string | number | null, value: unknown): JsonRpcResponse;
32
+ export declare function failure(id: string | number | null, code: number, message: string, data?: unknown): JsonRpcResponse;
33
+ export declare function isRequest(value: unknown): value is JsonRpcRequest;
34
+ /**
35
+ * A notification has no id and takes no reply.
36
+ *
37
+ * Answering one is a protocol error that some clients tolerate and others
38
+ * treat as a fatal desync, so it is checked rather than assumed.
39
+ */
40
+ export declare function isNotification(request: JsonRpcRequest): boolean;
41
+ export interface ToolDefinition {
42
+ name: string;
43
+ title: string;
44
+ description: string;
45
+ inputSchema: Record<string, unknown>;
46
+ }
47
+ export interface ToolResult {
48
+ content: {
49
+ type: 'text';
50
+ text: string;
51
+ }[];
52
+ isError?: boolean;
53
+ structuredContent?: unknown;
54
+ }
55
+ export declare function text(value: string, structured?: unknown): ToolResult;
56
+ export declare function toolError(message: string): ToolResult;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Just enough MCP to be one.
3
+ *
4
+ * The protocol is JSON-RPC 2.0 with a small set of methods, and implementing
5
+ * those directly keeps this package dependency-free and runnable in both
6
+ * places it has to run: inside the server behind /api/mcp, and inside the
7
+ * stdio binary that talks to a remote board.
8
+ */
9
+ export const PROTOCOL_VERSION = '2025-06-18';
10
+ export const PARSE_ERROR = -32700;
11
+ export const INVALID_REQUEST = -32600;
12
+ export const METHOD_NOT_FOUND = -32601;
13
+ export const INVALID_PARAMS = -32602;
14
+ export const INTERNAL_ERROR = -32603;
15
+ export function result(id, value) {
16
+ return { jsonrpc: '2.0', id, result: value };
17
+ }
18
+ export function failure(id, code, message, data) {
19
+ return { jsonrpc: '2.0', id, error: { code, message, ...(data === undefined ? {} : { data }) } };
20
+ }
21
+ export function isRequest(value) {
22
+ if (typeof value !== 'object' || value === null)
23
+ return false;
24
+ const record = value;
25
+ return record['jsonrpc'] === '2.0' && typeof record['method'] === 'string';
26
+ }
27
+ /**
28
+ * A notification has no id and takes no reply.
29
+ *
30
+ * Answering one is a protocol error that some clients tolerate and others
31
+ * treat as a fatal desync, so it is checked rather than assumed.
32
+ */
33
+ export function isNotification(request) {
34
+ return request.id === undefined || request.id === null;
35
+ }
36
+ export function text(value, structured) {
37
+ return {
38
+ content: [{ type: 'text', text: value }],
39
+ ...(structured === undefined ? {} : { structuredContent: structured }),
40
+ };
41
+ }
42
+ export function toolError(message) {
43
+ // Reported as a tool result rather than a JSON-RPC error on purpose: the
44
+ // model is supposed to read it and try something else, and a transport-level
45
+ // error is not shown to the model by most clients.
46
+ return { content: [{ type: 'text', text: message }], isError: true };
47
+ }
48
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/mcp/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAgB7C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAK,CAAC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAK,CAAC;AACtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAK,CAAC;AACvC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC;AACrC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC;AAErC,MAAM,UAAU,MAAM,CAAC,EAA0B,EAAE,KAAc;IAC/D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,EAA0B,EAC1B,IAAY,EACZ,OAAe,EACf,IAAc;IAEd,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;AACnG,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,KAAK,IAAI,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAuB;IACpD,OAAO,OAAO,CAAC,EAAE,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC;AACzD,CAAC;AAeD,MAAM,UAAU,IAAI,CAAC,KAAa,EAAE,UAAoB;IACtD,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACxC,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;KACvE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,yEAAyE;IACzE,6EAA6E;IAC7E,mDAAmD;IACnD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The MCP request handler, independent of transport.
3
+ *
4
+ * Used by both hosts: the board's own /api/mcp endpoint and the stdio binary.
5
+ */
6
+ import { type JsonRpcResponse } from './protocol.ts';
7
+ import { callTool, TOOLS, type Caller } from './tools.ts';
8
+ export interface ServerInfo {
9
+ name: string;
10
+ version: string;
11
+ }
12
+ /**
13
+ * Handle one message.
14
+ *
15
+ * Returns null for a notification, which the caller must then not answer:
16
+ * some clients treat a reply to a notification as a fatal desync rather than
17
+ * as noise to ignore.
18
+ */
19
+ export declare function handleMessage(payload: unknown, caller: Caller, info: ServerInfo): Promise<JsonRpcResponse | null>;
20
+ export { TOOLS, callTool };
21
+ export type { Caller };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The MCP request handler, independent of transport.
3
+ *
4
+ * Used by both hosts: the board's own /api/mcp endpoint and the stdio binary.
5
+ */
6
+ import { failure, INTERNAL_ERROR, INVALID_PARAMS, isNotification, isRequest, METHOD_NOT_FOUND, PROTOCOL_VERSION, result, } from "./protocol.js";
7
+ import { callTool, TOOLS } from "./tools.js";
8
+ /**
9
+ * Handle one message.
10
+ *
11
+ * Returns null for a notification, which the caller must then not answer:
12
+ * some clients treat a reply to a notification as a fatal desync rather than
13
+ * as noise to ignore.
14
+ */
15
+ export async function handleMessage(payload, caller, info) {
16
+ if (!isRequest(payload)) {
17
+ return failure(null, INVALID_PARAMS, 'Not a JSON-RPC 2.0 request.');
18
+ }
19
+ const id = payload.id ?? null;
20
+ const notification = isNotification(payload);
21
+ try {
22
+ switch (payload.method) {
23
+ case 'initialize':
24
+ return result(id, {
25
+ protocolVersion: PROTOCOL_VERSION,
26
+ capabilities: { tools: { listChanged: false } },
27
+ serverInfo: info,
28
+ instructions: [
29
+ `This is ${caller.server}, an agenticjobs board.`,
30
+ '',
31
+ 'Every listing here was posted by its employer. Nothing is scraped, so an empty',
32
+ 'search means nobody posted that job rather than that a crawler missed it.',
33
+ '',
34
+ 'To apply: call get_apply_schema first, then apply_to_job with exactly the fields it',
35
+ 'listed. Listings declare an agent policy of welcome, disclose or human-only; when it',
36
+ 'is "disclose", pass agentName. Disclosing is not held against a candidate on a board',
37
+ 'that asked for it.',
38
+ '',
39
+ 'Resumes are Markdown in the OpenResume.md convention.',
40
+ ].join('\n'),
41
+ });
42
+ case 'notifications/initialized':
43
+ case 'notifications/cancelled':
44
+ return null;
45
+ case 'ping':
46
+ return notification ? null : result(id, {});
47
+ case 'tools/list':
48
+ return result(id, { tools: TOOLS });
49
+ case 'tools/call': {
50
+ const params = payload.params ?? {};
51
+ const name = params['name'];
52
+ if (typeof name !== 'string') {
53
+ return failure(id, INVALID_PARAMS, 'tools/call needs a "name".');
54
+ }
55
+ const args = typeof params['arguments'] === 'object' && params['arguments'] !== null
56
+ ? params['arguments']
57
+ : {};
58
+ return result(id, await callTool(caller, name, args));
59
+ }
60
+ case 'resources/list':
61
+ return result(id, { resources: [] });
62
+ case 'prompts/list':
63
+ return result(id, { prompts: [] });
64
+ default:
65
+ return notification ? null : failure(id, METHOD_NOT_FOUND, `No method ${payload.method}.`);
66
+ }
67
+ }
68
+ catch (error) {
69
+ return failure(id, INTERNAL_ERROR, error instanceof Error ? error.message : 'Something went wrong.');
70
+ }
71
+ }
72
+ export { TOOLS, callTool };
73
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MCP over stdio, pointed at a board.
3
+ *
4
+ * The same tools the board serves at /api/mcp, hosted in a local process so a
5
+ * client that only speaks stdio can use a remote board. Credentials come from
6
+ * the terminal's own config, so `agenticjobs login` is the whole setup.
7
+ *
8
+ * Nothing may ever be written to stdout except protocol frames: a stray
9
+ * console.log corrupts the stream and the client reports a parse error with no
10
+ * indication of where it came from. Diagnostics go to stderr.
11
+ */
12
+ import type { BoardClient } from '../client/client.ts';
13
+ export declare function startStdio(client: BoardClient): Promise<void>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * MCP over stdio, pointed at a board.
3
+ *
4
+ * The same tools the board serves at /api/mcp, hosted in a local process so a
5
+ * client that only speaks stdio can use a remote board. Credentials come from
6
+ * the terminal's own config, so `agenticjobs login` is the whole setup.
7
+ *
8
+ * Nothing may ever be written to stdout except protocol frames: a stray
9
+ * console.log corrupts the stream and the client reports a parse error with no
10
+ * indication of where it came from. Diagnostics go to stderr.
11
+ */
12
+ import process from 'node:process';
13
+ import { VERSION, SOFTWARE_NAME } from "../config.js";
14
+ import { ApiError } from "../client/client.js";
15
+ import { handleMessage } from "./server.js";
16
+ export async function startStdio(client) {
17
+ const caller = {
18
+ server: client.server,
19
+ authenticated: client.hasToken(),
20
+ call: async (method, path, body) => {
21
+ try {
22
+ const result = await client.request(method, path, body);
23
+ return { status: 200, body: result };
24
+ }
25
+ catch (error) {
26
+ if (error instanceof ApiError) {
27
+ return {
28
+ status: error.status,
29
+ body: { error: { message: error.message, code: error.code, fields: error.fields } },
30
+ };
31
+ }
32
+ throw error;
33
+ }
34
+ },
35
+ };
36
+ const info = { name: `${SOFTWARE_NAME}-mcp`, version: VERSION };
37
+ process.stderr.write(`${SOFTWARE_NAME}-mcp ${VERSION} -> ${client.server}\n`);
38
+ let buffer = '';
39
+ process.stdin.setEncoding('utf8');
40
+ for await (const chunk of process.stdin) {
41
+ buffer += chunk;
42
+ // Line-delimited JSON. A partial line stays in the buffer until the rest
43
+ // of it arrives, which is the normal case on a busy pipe.
44
+ let newline = buffer.indexOf('\n');
45
+ while (newline >= 0) {
46
+ const line = buffer.slice(0, newline).trim();
47
+ buffer = buffer.slice(newline + 1);
48
+ newline = buffer.indexOf('\n');
49
+ if (line === '')
50
+ continue;
51
+ let payload;
52
+ try {
53
+ payload = JSON.parse(line);
54
+ }
55
+ catch {
56
+ process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Invalid JSON.' } })}\n`);
57
+ continue;
58
+ }
59
+ const answer = await handleMessage(payload, caller, info);
60
+ if (answer !== null)
61
+ process.stdout.write(`${JSON.stringify(answer)}\n`);
62
+ }
63
+ }
64
+ }
65
+ // Run directly when this module is the entry point, so the published binary
66
+ // works without the CLI in front of it.
67
+ if (process.argv[1]?.endsWith('agenticjobs-mcp.mjs') === true) {
68
+ const { BoardClient: Client } = await import("../client/client.js");
69
+ const { currentBoard, DEFAULT_SERVER, loadConfig, normaliseServer } = await import("../client/config.js");
70
+ const index = process.argv.indexOf('--server');
71
+ const explicit = index >= 0 ? process.argv[index + 1] : undefined;
72
+ let server;
73
+ let token = null;
74
+ if (explicit !== undefined) {
75
+ server = normaliseServer(explicit);
76
+ token = loadConfig().boards[server]?.token ?? null;
77
+ }
78
+ else {
79
+ const board = currentBoard();
80
+ server = board?.server ?? DEFAULT_SERVER;
81
+ token = board?.token ?? null;
82
+ }
83
+ await startStdio(new Client(server, { token, userAgent: `agenticjobs-mcp/${VERSION}` }));
84
+ }
85
+ //# sourceMappingURL=stdio.js.map