@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,31 @@
1
+ /**
2
+ * What this instance says about itself at /.well-known/agenticjobs.
3
+ *
4
+ * Built from live counts rather than from configuration, so an instance cannot
5
+ * advertise a hundred openings it does not have. The directory re-reads this
6
+ * on its own schedule; nothing here is cached longer than a request.
7
+ */
8
+ import { SOFTWARE_NAME } from "../../config.js";
9
+ import { countJobs } from "../../core/jobs.js";
10
+ import { PROTOCOL_VERSION } from "../../schema/instance.js";
11
+ export async function descriptorFor(pool, config) {
12
+ const jobs = await countJobs(pool);
13
+ return {
14
+ protocol: PROTOCOL_VERSION,
15
+ url: config.publicUrl,
16
+ name: config.boardName,
17
+ tagline: config.boardTagline,
18
+ topics: config.topics,
19
+ software: { name: SOFTWARE_NAME, version: config.version },
20
+ jobs,
21
+ endpoints: {
22
+ search: `${config.publicUrl}/api/v1/jobs`,
23
+ openapi: `${config.publicUrl}/api/v1/openapi.json`,
24
+ mcp: `${config.publicUrl}/api/mcp`,
25
+ feed: `${config.publicUrl}/jobs.json`,
26
+ },
27
+ directory: config.isDirectory,
28
+ contact: null,
29
+ };
30
+ }
31
+ //# sourceMappingURL=descriptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptor.js","sourceRoot":"","sources":["../../../src/server/routes/descriptor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAA2B,MAAM,0BAA0B,CAAC;AAErF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAa,EAAE,MAAc;IAC/D,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO;QACL,QAAQ,EAAE,gBAAgB;QAC1B,GAAG,EAAE,MAAM,CAAC,SAAS;QACrB,IAAI,EAAE,MAAM,CAAC,SAAS;QACtB,OAAO,EAAE,MAAM,CAAC,YAAY;QAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE;QAC1D,IAAI;QACJ,SAAS,EAAE;YACT,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,cAAc;YACzC,OAAO,EAAE,GAAG,MAAM,CAAC,SAAS,sBAAsB;YAClD,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,UAAU;YAClC,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,YAAY;SACtC;QACD,SAAS,EAAE,MAAM,CAAC,WAAW;QAC7B,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Everything a machine reads without being told to: the instance descriptor,
3
+ * feeds, the sitemap, robots.txt, llms.txt, the web app manifest and the
4
+ * service worker.
5
+ *
6
+ * These are not an afterthought on a board whose whole pitch is being
7
+ * readable. The descriptor is the federation contract; llms.txt is the one
8
+ * file a model fetches when it has no other instructions.
9
+ */
10
+ import { Hono } from 'hono';
11
+ import type { AppEnv } from '../deps.ts';
12
+ export declare function discoveryRoutes(): Hono<AppEnv>;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Everything a machine reads without being told to: the instance descriptor,
3
+ * feeds, the sitemap, robots.txt, llms.txt, the web app manifest and the
4
+ * service worker.
5
+ *
6
+ * These are not an afterthought on a board whose whole pitch is being
7
+ * readable. The descriptor is the federation contract; llms.txt is the one
8
+ * file a model fetches when it has no other instructions.
9
+ */
10
+ import { Hono } from 'hono';
11
+ import { readFile } from 'node:fs/promises';
12
+ import { dirname, extname, join, normalize } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { countJobs, searchJobs } from "../../core/jobs.js";
15
+ import { parseQuery } from "../../schema/query.js";
16
+ import { WELL_KNOWN_PATH } from "../../schema/instance.js";
17
+ import { jobPostingJsonLd } from "../../schema/jsonld.js";
18
+ import { escapeHtml } from "../../markup/escape.js";
19
+ import { toPlainText } from "../../markup/markdown.js";
20
+ import { descriptorFor } from "./descriptor.js";
21
+ function publicDir() {
22
+ // dist/server/routes/discovery.js -> package root -> web/public
23
+ return join(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))), 'web', 'public');
24
+ }
25
+ const MIME = {
26
+ '.css': 'text/css; charset=utf-8',
27
+ '.js': 'text/javascript; charset=utf-8',
28
+ '.svg': 'image/svg+xml',
29
+ '.png': 'image/png',
30
+ '.webmanifest': 'application/manifest+json',
31
+ '.txt': 'text/plain; charset=utf-8',
32
+ '.json': 'application/json',
33
+ };
34
+ export function discoveryRoutes() {
35
+ const routes = new Hono();
36
+ routes.get('/assets/:file', async (c) => {
37
+ const requested = c.req.param('file');
38
+ // normalize collapses any traversal before it is joined, so a name like
39
+ // "..%2f..%2fetc%2fpasswd" resolves to a bare filename and misses.
40
+ const name = normalize(requested).replace(/^(\.\.[/\\])+/, '');
41
+ if (name.includes('/') || name.includes('\\'))
42
+ return c.notFound();
43
+ try {
44
+ const body = await readFile(join(publicDir(), name));
45
+ return c.body(body, 200, {
46
+ 'content-type': MIME[extname(name)] ?? 'application/octet-stream',
47
+ 'cache-control': 'public, max-age=3600',
48
+ });
49
+ }
50
+ catch {
51
+ return c.notFound();
52
+ }
53
+ });
54
+ routes.get(WELL_KNOWN_PATH, async (c) => {
55
+ const { pool, config } = c.get('deps');
56
+ return c.json(await descriptorFor(pool, config), 200, {
57
+ // Read by other instances on a schedule, so a short cache is polite and
58
+ // a long one would make a new job invisible to the network for an hour.
59
+ 'cache-control': 'public, max-age=60',
60
+ 'access-control-allow-origin': '*',
61
+ });
62
+ });
63
+ routes.get('/jobs.json', async (c) => {
64
+ const { pool, config } = c.get('deps');
65
+ const query = { ...parseQuery(new URL(c.req.url).searchParams), limit: 100 };
66
+ const page = await searchJobs(pool, query);
67
+ // JSON Feed, so an ordinary feed reader can follow a job board.
68
+ return c.json({
69
+ version: 'https://jsonfeed.org/version/1.1',
70
+ title: `${config.boardName} - open roles`,
71
+ home_page_url: config.publicUrl,
72
+ feed_url: `${config.publicUrl}/jobs.json`,
73
+ description: config.boardTagline,
74
+ items: page.items.map((job) => ({
75
+ id: `${config.publicUrl}/jobs/${job.slug}`,
76
+ url: `${config.publicUrl}/jobs/${job.slug}`,
77
+ title: `${job.title} at ${job.org.name}`,
78
+ content_text: toPlainText(job.description, 600),
79
+ date_published: job.publishedAt ?? job.createdAt,
80
+ tags: [...job.tags, ...job.stack, job.workplace, job.employmentType],
81
+ authors: [{ name: job.org.name }],
82
+ _agenticjobs: {
83
+ agentPolicy: job.agentPolicy,
84
+ applyVia: job.apply.via,
85
+ jsonld: jobPostingJsonLd(job, config.publicUrl),
86
+ },
87
+ })),
88
+ }, 200, { 'access-control-allow-origin': '*' });
89
+ });
90
+ routes.get('/jobs.rss', async (c) => {
91
+ const { pool, config } = c.get('deps');
92
+ const page = await searchJobs(pool, { ...parseQuery(new URLSearchParams()), limit: 100 });
93
+ const items = page.items
94
+ .map((job) => {
95
+ const url = `${config.publicUrl}/jobs/${job.slug}`;
96
+ return [
97
+ ' <item>',
98
+ ` <title>${escapeHtml(`${job.title} at ${job.org.name}`)}</title>`,
99
+ ` <link>${escapeHtml(url)}</link>`,
100
+ ` <guid isPermaLink="true">${escapeHtml(url)}</guid>`,
101
+ ` <pubDate>${new Date(job.publishedAt ?? job.createdAt).toUTCString()}</pubDate>`,
102
+ ` <description>${escapeHtml(toPlainText(job.description, 500))}</description>`,
103
+ ' </item>',
104
+ ].join('\n');
105
+ })
106
+ .join('\n');
107
+ const xml = [
108
+ '<?xml version="1.0" encoding="UTF-8"?>',
109
+ '<rss version="2.0">',
110
+ ' <channel>',
111
+ ` <title>${escapeHtml(config.boardName)}</title>`,
112
+ ` <link>${escapeHtml(config.publicUrl)}</link>`,
113
+ ` <description>${escapeHtml(config.boardTagline)}</description>`,
114
+ items,
115
+ ' </channel>',
116
+ '</rss>',
117
+ ].join('\n');
118
+ return c.body(xml, 200, { 'content-type': 'application/rss+xml; charset=utf-8' });
119
+ });
120
+ /**
121
+ * A sitemap index plus monthly chunks, even for a small board.
122
+ *
123
+ * The shape does not change as the board grows, so nobody has to migrate a
124
+ * flat sitemap the week it passes 50,000 URLs.
125
+ */
126
+ routes.get('/sitemap.xml', async (c) => {
127
+ const { pool, config } = c.get('deps');
128
+ const months = await pool.query(`select to_char(published_at, 'YYYY-MM') as month, max(published_at)::text as latest
129
+ from jobs
130
+ where status = 'published' and published_at is not null
131
+ group by 1 order by 1 desc`);
132
+ const entries = months.rows
133
+ .map((row) => ` <sitemap><loc>${config.publicUrl}/sitemaps/${row.month}.xml</loc><lastmod>${new Date(row.latest).toISOString()}</lastmod></sitemap>`)
134
+ .join('\n');
135
+ const xml = [
136
+ '<?xml version="1.0" encoding="UTF-8"?>',
137
+ '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
138
+ ` <sitemap><loc>${config.publicUrl}/sitemaps/pages.xml</loc></sitemap>`,
139
+ entries,
140
+ '</sitemapindex>',
141
+ ].join('\n');
142
+ return c.body(xml, 200, { 'content-type': 'application/xml; charset=utf-8' });
143
+ });
144
+ routes.get('/sitemaps/:name', async (c) => {
145
+ const { pool, config } = c.get('deps');
146
+ const name = c.req.param('name').replace(/\.xml$/, '');
147
+ if (name === 'pages') {
148
+ const paths = ['/', '/employers', '/docs', '/docs/openresume', '/docs/openjob'];
149
+ if (c.get('deps').config.isDirectory)
150
+ paths.push('/network');
151
+ return c.body(urlset(paths.map((path) => ({ loc: `${config.publicUrl}${path}` }))), 200, {
152
+ 'content-type': 'application/xml; charset=utf-8',
153
+ });
154
+ }
155
+ if (!/^\d{4}-\d{2}$/.test(name))
156
+ return c.notFound();
157
+ const jobs = await pool.query(`select slug, updated_at from jobs
158
+ where status = 'published' and published_at is not null
159
+ and to_char(published_at, 'YYYY-MM') = $1
160
+ order by published_at desc limit 50000`, [name]);
161
+ return c.body(urlset(jobs.rows.map((row) => ({
162
+ loc: `${config.publicUrl}/jobs/${row.slug}`,
163
+ lastmod: new Date(row.updated_at).toISOString(),
164
+ }))), 200, { 'content-type': 'application/xml; charset=utf-8' });
165
+ });
166
+ routes.get('/robots.txt', (c) => {
167
+ const { config } = c.get('deps');
168
+ return c.text([
169
+ 'User-agent: *',
170
+ 'Allow: /',
171
+ // The dashboards hold one person's own data and are noindex anyway;
172
+ // saying so here saves the crawl.
173
+ 'Disallow: /me',
174
+ 'Disallow: /device',
175
+ 'Disallow: /auth/',
176
+ '',
177
+ `Sitemap: ${config.publicUrl}/sitemap.xml`,
178
+ '',
179
+ ].join('\n'));
180
+ });
181
+ /**
182
+ * llms.txt.
183
+ *
184
+ * The point of this board is that a model does not have to guess, so this
185
+ * says plainly what is here, what is not, and which two endpoints matter.
186
+ */
187
+ routes.get('/llms.txt', async (c) => {
188
+ const { pool, config } = c.get('deps');
189
+ const counts = await countJobs(pool);
190
+ return c.text([
191
+ `# ${config.boardName}`,
192
+ '',
193
+ `> ${config.boardTagline}`,
194
+ '',
195
+ `An agent-friendly job board with ${counts.open} open roles. Every listing was posted`,
196
+ 'here by the employer. Nothing is scraped from anywhere else, so an empty search means',
197
+ 'nobody has posted that job rather than that a crawler missed it.',
198
+ '',
199
+ 'Reads need no credentials.',
200
+ '',
201
+ '## Start here',
202
+ '',
203
+ `- [Instance descriptor](${config.publicUrl}${WELL_KNOWN_PATH}): what this board is and where everything lives`,
204
+ `- [OpenAPI](${config.publicUrl}/api/v1/openapi.json): every endpoint`,
205
+ `- [Search](${config.publicUrl}/api/v1/jobs?q=): q, workplace, employmentType, seniority, agentPolicy, tag, salaryMin`,
206
+ `- [Feed](${config.publicUrl}/jobs.json): the 100 most recent, as JSON Feed`,
207
+ '',
208
+ '## Applying',
209
+ '',
210
+ `GET ${config.publicUrl}/api/v1/jobs/{slug}/apply-schema for the exact fields, then POST`,
211
+ `them to ${config.publicUrl}/api/v1/jobs/{slug}/apply.`,
212
+ '',
213
+ 'Every listing declares an agent policy: welcome, disclose or human-only. If it is',
214
+ '"disclose", send an `agent` object saying which agent wrote the application. Disclosing',
215
+ 'is not held against a candidate on a board that asked for it.',
216
+ '',
217
+ 'Resumes are Markdown in the OpenResume.md convention:',
218
+ `${config.publicUrl}/docs/openresume`,
219
+ '',
220
+ '## MCP',
221
+ '',
222
+ `${config.publicUrl}/api/mcp (streamable HTTP)`,
223
+ '',
224
+ ...(config.isDirectory
225
+ ? [
226
+ '## The network',
227
+ '',
228
+ `This instance is also a directory. ${config.publicUrl}/api/v1/directory/search asks`,
229
+ 'every listed board one question at once.',
230
+ '',
231
+ ]
232
+ : []),
233
+ '## Software',
234
+ '',
235
+ 'MIT licensed, self-hostable: https://github.com/profullstack/agenticjobs',
236
+ '',
237
+ ].join('\n'));
238
+ });
239
+ routes.get('/manifest.webmanifest', (c) => {
240
+ const { config } = c.get('deps');
241
+ return c.json({
242
+ name: config.boardName,
243
+ short_name: config.boardName.slice(0, 12),
244
+ description: config.boardTagline,
245
+ start_url: '/',
246
+ scope: '/',
247
+ display: 'standalone',
248
+ background_color: '#ffffff',
249
+ theme_color: '#111318',
250
+ icons: [
251
+ { src: '/assets/icon.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any' },
252
+ { src: '/assets/icon-192.png', sizes: '192x192', type: 'image/png' },
253
+ { src: '/assets/icon-512.png', sizes: '512x512', type: 'image/png' },
254
+ ],
255
+ shortcuts: [
256
+ { name: 'Post a job', url: '/post' },
257
+ { name: 'Your resumes', url: '/me' },
258
+ ],
259
+ }, 200, { 'content-type': 'application/manifest+json' });
260
+ });
261
+ /**
262
+ * The service worker, served from the root so its scope is the whole site.
263
+ *
264
+ * A worker served from /assets/ can only ever control /assets/, which is the
265
+ * single most common reason a PWA installs and then does nothing offline.
266
+ */
267
+ routes.get('/sw.js', async (c) => {
268
+ try {
269
+ const body = await readFile(join(publicDir(), 'sw.js'), 'utf8');
270
+ return c.body(body, 200, {
271
+ 'content-type': 'text/javascript; charset=utf-8',
272
+ // Never cached: a stale worker is a site that cannot be updated.
273
+ 'cache-control': 'no-cache',
274
+ 'service-worker-allowed': '/',
275
+ });
276
+ }
277
+ catch {
278
+ return c.notFound();
279
+ }
280
+ });
281
+ routes.get('/.well-known/security.txt', (c) => {
282
+ const { config } = c.get('deps');
283
+ const expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();
284
+ return c.text([
285
+ `Contact: ${config.publicUrl}/docs`,
286
+ `Expires: ${expires}`,
287
+ 'Preferred-Languages: en',
288
+ `Canonical: ${config.publicUrl}/.well-known/security.txt`,
289
+ '',
290
+ ].join('\n'));
291
+ });
292
+ return routes;
293
+ }
294
+ function urlset(entries) {
295
+ const body = entries
296
+ .map((entry) => ` <url><loc>${escapeHtml(entry.loc)}</loc>${entry.lastmod === undefined ? '' : `<lastmod>${entry.lastmod}</lastmod>`}</url>`)
297
+ .join('\n');
298
+ return [
299
+ '<?xml version="1.0" encoding="UTF-8"?>',
300
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
301
+ body,
302
+ '</urlset>',
303
+ ].join('\n');
304
+ }
305
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/server/routes/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * MCP over streamable HTTP.
3
+ *
4
+ * The tools dispatch back through this same app rather than reaching into the
5
+ * database, so an assistant gets the same code and the same permission checks
6
+ * a browser does. The app has to dispatch into itself, which is a cycle the
7
+ * module graph cannot express - so createApp passes a getter that is filled in
8
+ * once the app exists.
9
+ *
10
+ * Bearer tokens are honoured and cookies are deliberately ignored. Browsers
11
+ * attach cookies to cross-origin POSTs, so honouring one would turn this
12
+ * endpoint into a write primitive for any page on the web.
13
+ */
14
+ import { Hono } from 'hono';
15
+ import type { AppEnv } from '../deps.ts';
16
+ export interface Dispatcher {
17
+ fetch(request: Request): Response | Promise<Response>;
18
+ }
19
+ export declare function mcpRoutes(getApp: () => Dispatcher): Hono<AppEnv>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * MCP over streamable HTTP.
3
+ *
4
+ * The tools dispatch back through this same app rather than reaching into the
5
+ * database, so an assistant gets the same code and the same permission checks
6
+ * a browser does. The app has to dispatch into itself, which is a cycle the
7
+ * module graph cannot express - so createApp passes a getter that is filled in
8
+ * once the app exists.
9
+ *
10
+ * Bearer tokens are honoured and cookies are deliberately ignored. Browsers
11
+ * attach cookies to cross-origin POSTs, so honouring one would turn this
12
+ * endpoint into a write primitive for any page on the web.
13
+ */
14
+ import { Hono } from 'hono';
15
+ import { SOFTWARE_NAME } from "../../config.js";
16
+ import { handleMessage } from "../../mcp/server.js";
17
+ export function mcpRoutes(getApp) {
18
+ const routes = new Hono();
19
+ routes.post('/', async (c) => {
20
+ const { config } = c.get('deps');
21
+ let payload;
22
+ try {
23
+ payload = await c.req.json();
24
+ }
25
+ catch {
26
+ return c.json({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Invalid JSON.' } }, 400);
27
+ }
28
+ const authorization = c.req.header('authorization');
29
+ // Built from the request's own origin rather than PUBLIC_URL, so loopback
30
+ // works behind a proxy, on a laptop and in tests, where the configured
31
+ // public URL is not necessarily the one answering.
32
+ const origin = new URL(c.req.url).origin;
33
+ const caller = {
34
+ server: config.publicUrl,
35
+ authenticated: authorization !== undefined,
36
+ call: async (method, path, body) => {
37
+ const headers = { accept: 'application/json' };
38
+ if (authorization !== undefined)
39
+ headers['authorization'] = authorization;
40
+ if (body !== undefined)
41
+ headers['content-type'] = 'application/json';
42
+ const response = await getApp().fetch(new Request(new URL(path, origin), {
43
+ method,
44
+ headers,
45
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
46
+ }));
47
+ const raw = await response.text();
48
+ try {
49
+ return { status: response.status, body: JSON.parse(raw) };
50
+ }
51
+ catch {
52
+ // A non-JSON body from our own API means a route returned HTML,
53
+ // which is worth surfacing verbatim rather than hiding.
54
+ return { status: response.status, body: raw };
55
+ }
56
+ },
57
+ };
58
+ const info = { name: `${SOFTWARE_NAME}-mcp`, version: config.version };
59
+ // A batch is an array. Notifications inside it produce no reply, and a
60
+ // batch of nothing but notifications produces no body at all.
61
+ if (Array.isArray(payload)) {
62
+ const responses = [];
63
+ for (const item of payload) {
64
+ const answer = await handleMessage(item, caller, info);
65
+ if (answer !== null)
66
+ responses.push(answer);
67
+ }
68
+ return responses.length === 0 ? c.body(null, 202) : c.json(responses);
69
+ }
70
+ const answer = await handleMessage(payload, caller, info);
71
+ return answer === null ? c.body(null, 202) : c.json(answer);
72
+ });
73
+ routes.get('/', (c) => {
74
+ const { config } = c.get('deps');
75
+ return c.json({
76
+ name: `${SOFTWARE_NAME}-mcp`,
77
+ version: config.version,
78
+ transport: 'streamable-http',
79
+ board: config.publicUrl,
80
+ note: 'POST JSON-RPC 2.0 here. Send a bearer token to act as an account; cookies are ignored on purpose.',
81
+ });
82
+ });
83
+ return routes;
84
+ }
85
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../../src/server/routes/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The OpenAPI document, written by hand.
3
+ *
4
+ * Hand-written because it is read by people and by models deciding what this
5
+ * board can do, and a generated one describes the router rather than the
6
+ * board. test/api.test.ts walks every documented GET against the real routes,
7
+ * so a path that drifts out of this file fails the suite rather than quietly
8
+ * lying to whoever reads it.
9
+ */
10
+ import type { Config } from '../../config.ts';
11
+ export declare function openApiDocument(config: Config): Record<string, unknown>;