@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,155 @@
1
+ /**
2
+ * The directory: which instances exist.
3
+ *
4
+ * An instance announces itself by sending nothing but its own URL. The
5
+ * directory then fetches that URL's descriptor itself and records what it
6
+ * actually read. nixamp's stream directory trusts the announcement body, which
7
+ * is right for a stream whose only claim is "I am playing something"; here an
8
+ * announcement carries a name, a topic list and a job count, and a board that
9
+ * could announce someone else's name with its own URL underneath would be a
10
+ * phishing primitive on day one.
11
+ */
12
+ import { MAX_FAILURES, ONLINE_TTL_MS, publishable, } from "../schema/instance.js";
13
+ import { fetchDescriptor, FetchProblem } from "./fetch.js";
14
+ function toListing(row, now = Date.now()) {
15
+ return {
16
+ id: row.id,
17
+ url: row.url,
18
+ descriptor: row.descriptor,
19
+ updatedAt: row.checked_at,
20
+ firstSeenAt: row.first_seen_at,
21
+ failures: row.failures,
22
+ online: now - Date.parse(row.checked_at) < ONLINE_TTL_MS && row.failures === 0,
23
+ };
24
+ }
25
+ export class Blocked extends Error {
26
+ }
27
+ /**
28
+ * Record an announcement.
29
+ *
30
+ * Idempotent per origin: an instance that heartbeats every ten minutes updates
31
+ * one row forever rather than accumulating a history of itself.
32
+ */
33
+ export async function announce(pool, rawUrl) {
34
+ const origin = publishable(rawUrl);
35
+ if (origin === null)
36
+ throw new FetchProblem(`${rawUrl} is not an address we will list`);
37
+ const url = origin.origin;
38
+ const existing = await pool.query(`select blocked, blocked_reason from instances where url = $1`, [url]);
39
+ const row = existing.rows[0];
40
+ if (row?.blocked === true) {
41
+ throw new Blocked(row.blocked_reason ?? 'This instance is not accepted here.');
42
+ }
43
+ const descriptor = await fetchDescriptor(url);
44
+ const saved = await pool.query(`insert into instances (url, descriptor, checked_at, failures)
45
+ values ($1, $2::jsonb, now(), 0)
46
+ on conflict (url) do update
47
+ set descriptor = excluded.descriptor,
48
+ checked_at = now(),
49
+ failures = 0
50
+ returning id, url, descriptor, first_seen_at, checked_at, failures, blocked`, [url, JSON.stringify(descriptor)]);
51
+ const result = saved.rows[0];
52
+ if (result === undefined)
53
+ throw new Error('instance upsert returned no row');
54
+ return toListing(result);
55
+ }
56
+ export async function listInstances(pool, options = {}) {
57
+ const params = [];
58
+ const where = ['blocked = false'];
59
+ if (options.onlineOnly === true) {
60
+ params.push(String(ONLINE_TTL_MS));
61
+ where.push(`checked_at > now() - ($${params.length} || ' milliseconds')::interval`);
62
+ where.push('failures = 0');
63
+ }
64
+ if (options.topic) {
65
+ params.push(options.topic.toLowerCase());
66
+ where.push(`descriptor -> 'topics' ? $${params.length}`);
67
+ }
68
+ if (options.q) {
69
+ params.push(`%${options.q.toLowerCase()}%`);
70
+ where.push(`(lower(descriptor ->> 'name') like $${params.length} or lower(descriptor ->> 'tagline') like $${params.length} or url like $${params.length})`);
71
+ }
72
+ params.push(Math.min(500, Math.max(1, options.limit ?? 100)));
73
+ params.push(Math.max(0, options.offset ?? 0));
74
+ const result = await pool.query(`select id, url, descriptor, first_seen_at, checked_at, failures, blocked
75
+ from instances
76
+ where ${where.join(' and ')}
77
+ order by failures asc,
78
+ (descriptor -> 'jobs' ->> 'open')::int desc nulls last,
79
+ checked_at desc
80
+ limit $${params.length - 1} offset $${params.length}`, params);
81
+ const now = Date.now();
82
+ return result.rows.map((row) => toListing(row, now));
83
+ }
84
+ export async function getInstance(pool, url) {
85
+ const origin = publishable(url);
86
+ if (origin === null)
87
+ return null;
88
+ const result = await pool.query(`select id, url, descriptor, first_seen_at, checked_at, failures, blocked
89
+ from instances where url = $1`, [origin.origin]);
90
+ const row = result.rows[0];
91
+ return row === undefined ? null : toListing(row);
92
+ }
93
+ /** Every topic the directory knows about, most-used first. */
94
+ export async function listTopics(pool) {
95
+ const result = await pool.query(`select topic, count(*)::bigint as instances
96
+ from instances, jsonb_array_elements_text(descriptor -> 'topics') as topic
97
+ where blocked = false
98
+ group by topic
99
+ order by instances desc, topic asc
100
+ limit 100`);
101
+ return result.rows;
102
+ }
103
+ /**
104
+ * Re-read every instance the directory knows about.
105
+ *
106
+ * A failure raises a counter rather than deleting the row: an instance is
107
+ * allowed to be down for a redeploy without losing its place, and the URL only
108
+ * goes away after MAX_FAILURES consecutive misses, which at the sweep interval
109
+ * is the better part of a day.
110
+ */
111
+ export async function sweep(pool, options = {}) {
112
+ const due = await pool.query(`select url from instances
113
+ where blocked = false
114
+ order by checked_at asc
115
+ limit $1`, [Math.min(500, Math.max(1, options.limit ?? 100))]);
116
+ const urls = due.rows.map((row) => row.url);
117
+ const result = { checked: urls.length, ok: 0, failed: 0, dropped: 0 };
118
+ const concurrency = Math.min(16, Math.max(1, options.concurrency ?? 8));
119
+ let cursor = 0;
120
+ const workers = Array.from({ length: Math.min(concurrency, urls.length) }, async () => {
121
+ for (;;) {
122
+ const index = cursor;
123
+ cursor += 1;
124
+ const url = urls[index];
125
+ if (url === undefined)
126
+ return;
127
+ try {
128
+ const descriptor = await fetchDescriptor(url);
129
+ await pool.query(`update instances set descriptor = $2::jsonb, checked_at = now(), failures = 0
130
+ where url = $1`, [url, JSON.stringify(descriptor)]);
131
+ result.ok += 1;
132
+ }
133
+ catch {
134
+ const bumped = await pool.query(`update instances set failures = failures + 1 where url = $1 returning failures`, [url]);
135
+ result.failed += 1;
136
+ if ((bumped.rows[0]?.failures ?? 0) >= MAX_FAILURES) {
137
+ await pool.query(`delete from instances where url = $1`, [url]);
138
+ result.dropped += 1;
139
+ }
140
+ }
141
+ }
142
+ });
143
+ await Promise.all(workers);
144
+ return result;
145
+ }
146
+ export async function blockInstance(pool, url, reason) {
147
+ const origin = publishable(url);
148
+ if (origin === null)
149
+ return false;
150
+ const result = await pool.query(`insert into instances (url, descriptor, blocked, blocked_reason)
151
+ values ($1, '{}'::jsonb, true, $2)
152
+ on conflict (url) do update set blocked = true, blocked_reason = $2`, [origin.origin, reason.slice(0, 300)]);
153
+ return (result.rowCount ?? 0) > 0;
154
+ }
155
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/directory/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The package's public surface.
3
+ *
4
+ * Kept to the parts another program has a reason to import: the schema (so a
5
+ * tool can speak OpenJob without this board), the client (so it can talk to
6
+ * one), and the markup pipeline. The server half is reachable by path but is
7
+ * not part of the promise.
8
+ */
9
+ export * from './schema/index.ts';
10
+ export * from './client/index.ts';
11
+ export { renderMarkdown, renderInline, toPlainText } from './markup/markdown.ts';
12
+ export { parseResume, resumeTemplate, resumeSearchText } from './markup/resume.ts';
13
+ export type { OpenResume, ResumeEntry, ResumeSection } from './markup/resume.ts';
14
+ export { VERSION, SOFTWARE_NAME } from './config.ts';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The package's public surface.
3
+ *
4
+ * Kept to the parts another program has a reason to import: the schema (so a
5
+ * tool can speak OpenJob without this board), the client (so it can talk to
6
+ * one), and the markup pipeline. The server half is reachable by path but is
7
+ * not part of the promise.
8
+ */
9
+ export * from "./schema/index.js";
10
+ export * from "./client/index.js";
11
+ export { renderMarkdown, renderInline, toPlainText } from "./markup/markdown.js";
12
+ export { parseResume, resumeTemplate, resumeSearchText } from "./markup/resume.js";
13
+ export { VERSION, SOFTWARE_NAME } from "./config.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEnF,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The markup pipeline is safe by construction: source text is escaped as it is
3
+ * emitted, and the only tags that can appear in the output are ones this
4
+ * module writes literally. Raw HTML in a job description, a cover letter or a
5
+ * resume is content, not markup.
6
+ *
7
+ * Do not "improve" this by adding a sanitiser or a raw-HTML passthrough. A
8
+ * sanitiser is a denylist with a long history of bypasses; this is an
9
+ * allowlist that cannot be bypassed, because there is no path from input to a
10
+ * tag at all.
11
+ */
12
+ export declare function escapeHtml(input: string): string;
13
+ export declare function safeUrl(input: string): string | null;
14
+ /**
15
+ * Anything interpolated into a <script> element goes through this. A bare
16
+ * JSON.stringify of text containing `</script>` closes the element and the
17
+ * rest of the document parses as markup, which is stored XSS on a field as
18
+ * ordinary as a job title.
19
+ */
20
+ export declare function jsonForScript(value: unknown): string;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The markup pipeline is safe by construction: source text is escaped as it is
3
+ * emitted, and the only tags that can appear in the output are ones this
4
+ * module writes literally. Raw HTML in a job description, a cover letter or a
5
+ * resume is content, not markup.
6
+ *
7
+ * Do not "improve" this by adding a sanitiser or a raw-HTML passthrough. A
8
+ * sanitiser is a denylist with a long history of bypasses; this is an
9
+ * allowlist that cannot be bypassed, because there is no path from input to a
10
+ * tag at all.
11
+ */
12
+ const HTML_ESCAPES = {
13
+ '&': '&amp;',
14
+ '<': '&lt;',
15
+ '>': '&gt;',
16
+ '"': '&quot;',
17
+ "'": '&#39;',
18
+ };
19
+ export function escapeHtml(input) {
20
+ return input.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character] ?? character);
21
+ }
22
+ /** Only these schemes ever reach an href or a src. Everything else is dropped. */
23
+ const SAFE_SCHEME = /^(https?:|mailto:|\/|#)/i;
24
+ export function safeUrl(input) {
25
+ const trimmed = input.trim();
26
+ if (trimmed === '')
27
+ return null;
28
+ // A control character in the middle of a URL is how `javascript:` gets past
29
+ // a scheme check: `java\nscript:` fails a naive test and is still executed
30
+ // by the parser.
31
+ for (const character of trimmed) {
32
+ const code = character.codePointAt(0) ?? 0;
33
+ if (code < 0x20 || code === 0x7f)
34
+ return null;
35
+ }
36
+ if (!SAFE_SCHEME.test(trimmed))
37
+ return null;
38
+ return trimmed;
39
+ }
40
+ /**
41
+ * Anything interpolated into a <script> element goes through this. A bare
42
+ * JSON.stringify of text containing `</script>` closes the element and the
43
+ * rest of the document parses as markup, which is stored XSS on a field as
44
+ * ordinary as a job title.
45
+ */
46
+ export function jsonForScript(value) {
47
+ return JSON.stringify(value)
48
+ .replace(/</g, '\\u003c')
49
+ .replace(/>/g, '\\u003e')
50
+ .replace(/&/g, '\\u0026')
51
+ .split(String.fromCharCode(0x2028))
52
+ .join('\\u2028')
53
+ .split(String.fromCharCode(0x2029))
54
+ .join('\\u2029');
55
+ }
56
+ //# sourceMappingURL=escape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/markup/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,YAAY,GAA2B;IAC3C,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC;AACxF,CAAC;AAED,kFAAkF;AAClF,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAChC,4EAA4E;IAC5E,2EAA2E;IAC3E,iBAAiB;IACjB,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACzB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;SAClC,IAAI,CAAC,SAAS,CAAC;SACf,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;SAClC,IAAI,CAAC,SAAS,CAAC,CAAC;AACrB,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Markdown, rendered to HTML without ever passing raw HTML through.
3
+ *
4
+ * Everything a person or an agent writes on this board is Markdown: job
5
+ * descriptions, employer profiles, cover letters and whole resumes. One
6
+ * renderer serves all of them, so a table in a resume and a table in a job
7
+ * description behave the same, and neither can introduce a tag.
8
+ *
9
+ * The grammar is CommonMark's common subset plus GitHub tables and
10
+ * strikethrough. It is not a complete CommonMark implementation and does not
11
+ * try to be: the constructs left out are either unsafe here (raw HTML) or
12
+ * unused by the documents this board actually stores.
13
+ */
14
+ export interface MarkdownOptions {
15
+ /**
16
+ * Added to every heading level, so a document embedded in a page whose h1
17
+ * is the job title does not emit a second h1.
18
+ */
19
+ headingOffset?: number;
20
+ /** Rendered as links but never as images. Resumes are read by strangers. */
21
+ noImages?: boolean;
22
+ /** Appended to every link. */
23
+ linkRel?: string;
24
+ }
25
+ export declare function renderMarkdown(source: string, options?: MarkdownOptions): string;
26
+ /**
27
+ * Inline markup.
28
+ *
29
+ * Code spans are pulled out first and put back last, so a backticked
30
+ * `**not bold**` stays literal and an asterisk inside code cannot open
31
+ * emphasis that then runs off through the rest of the paragraph.
32
+ */
33
+ export declare function renderInline(source: string, options?: MarkdownOptions): string;
34
+ /** Plain text, for meta descriptions, feeds, the TUI and search snippets. */
35
+ export declare function toPlainText(source: string, limit?: number): string;
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Markdown, rendered to HTML without ever passing raw HTML through.
3
+ *
4
+ * Everything a person or an agent writes on this board is Markdown: job
5
+ * descriptions, employer profiles, cover letters and whole resumes. One
6
+ * renderer serves all of them, so a table in a resume and a table in a job
7
+ * description behave the same, and neither can introduce a tag.
8
+ *
9
+ * The grammar is CommonMark's common subset plus GitHub tables and
10
+ * strikethrough. It is not a complete CommonMark implementation and does not
11
+ * try to be: the constructs left out are either unsafe here (raw HTML) or
12
+ * unused by the documents this board actually stores.
13
+ */
14
+ import { escapeHtml, safeUrl } from "./escape.js";
15
+ const DEFAULT_REL = 'nofollow ugc noopener noreferrer';
16
+ export function renderMarkdown(source, options = {}) {
17
+ const lines = source.replace(/\r\n?/g, '\n').split('\n');
18
+ const out = [];
19
+ let index = 0;
20
+ while (index < lines.length) {
21
+ const line = lines[index] ?? '';
22
+ if (line.trim() === '') {
23
+ index += 1;
24
+ continue;
25
+ }
26
+ // Fenced code, taken first so nothing inside a fence is parsed as markup.
27
+ const fence = /^\s{0,3}(`{3,}|~{3,})\s*([A-Za-z0-9_+-]*)\s*$/.exec(line);
28
+ if (fence !== null) {
29
+ const marker = (fence[1] ?? '```').startsWith('`') ? '`{3,}' : '~{3,}';
30
+ const language = fence[2] ?? '';
31
+ const closing = new RegExp(`^\\s{0,3}${marker}\\s*$`);
32
+ const body = [];
33
+ index += 1;
34
+ while (index < lines.length) {
35
+ const current = lines[index] ?? '';
36
+ if (closing.test(current)) {
37
+ index += 1;
38
+ break;
39
+ }
40
+ body.push(current);
41
+ index += 1;
42
+ }
43
+ const className = language === '' ? '' : ` class="language-${escapeHtml(language)}"`;
44
+ out.push(`<pre><code${className}>${escapeHtml(body.join('\n'))}\n</code></pre>`);
45
+ continue;
46
+ }
47
+ const heading = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line);
48
+ if (heading !== null) {
49
+ const raw = (heading[1] ?? '#').length + (options.headingOffset ?? 0);
50
+ const level = Math.min(6, Math.max(1, raw));
51
+ out.push(`<h${level}>${renderInline(heading[2] ?? '', options)}</h${level}>`);
52
+ index += 1;
53
+ continue;
54
+ }
55
+ if (/^\s{0,3}([-*_])\s*(\1\s*){2,}$/.test(line)) {
56
+ out.push('<hr />');
57
+ index += 1;
58
+ continue;
59
+ }
60
+ if (/^\s{0,3}>/.test(line)) {
61
+ const body = [];
62
+ while (index < lines.length && /^\s{0,3}>/.test(lines[index] ?? '')) {
63
+ body.push((lines[index] ?? '').replace(/^\s{0,3}>\s?/, ''));
64
+ index += 1;
65
+ }
66
+ // Recursing means a quote can hold a list or a code block, which is what
67
+ // people actually paste into one.
68
+ out.push(`<blockquote>${renderMarkdown(body.join('\n'), options)}</blockquote>`);
69
+ continue;
70
+ }
71
+ const table = tryTable(lines, index, options);
72
+ if (table !== null) {
73
+ out.push(table.html);
74
+ index = table.next;
75
+ continue;
76
+ }
77
+ const list = tryList(lines, index, options);
78
+ if (list !== null) {
79
+ out.push(list.html);
80
+ index = list.next;
81
+ continue;
82
+ }
83
+ const paragraph = [];
84
+ while (index < lines.length) {
85
+ const current = lines[index] ?? '';
86
+ if (current.trim() === '' || startsBlock(current))
87
+ break;
88
+ paragraph.push(current);
89
+ index += 1;
90
+ }
91
+ if (paragraph.length > 0) {
92
+ // A single newline inside a paragraph becomes a break. GitHub does this,
93
+ // and every resume in the wild is written assuming it.
94
+ out.push(`<p>${paragraph.map((text) => renderInline(text, options)).join('<br />')}</p>`);
95
+ }
96
+ }
97
+ return out.join('\n');
98
+ }
99
+ function startsBlock(line) {
100
+ return (/^\s{0,3}(`{3,}|~{3,})/.test(line) ||
101
+ /^#{1,6}\s/.test(line) ||
102
+ /^\s{0,3}>/.test(line) ||
103
+ /^\s{0,3}([-*_])\s*(\1\s*){2,}$/.test(line) ||
104
+ /^\s{0,3}([-*+]|\d{1,9}[.)])\s+/.test(line));
105
+ }
106
+ /** GitHub-style tables. Resumes and compensation bands both use them. */
107
+ function tryTable(lines, start, options) {
108
+ const header = lines[start] ?? '';
109
+ const divider = lines[start + 1] ?? '';
110
+ if (!header.includes('|'))
111
+ return null;
112
+ if (!/^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(divider))
113
+ return null;
114
+ const aligns = splitRow(divider).map((cell) => {
115
+ const left = cell.startsWith(':');
116
+ const right = cell.endsWith(':');
117
+ if (left && right)
118
+ return ' style="text-align:center"';
119
+ if (right)
120
+ return ' style="text-align:right"';
121
+ return '';
122
+ });
123
+ const head = splitRow(header);
124
+ const rows = [];
125
+ let index = start + 2;
126
+ while (index < lines.length) {
127
+ const line = lines[index] ?? '';
128
+ if (line.trim() === '' || !line.includes('|'))
129
+ break;
130
+ rows.push(splitRow(line));
131
+ index += 1;
132
+ }
133
+ const th = head
134
+ .map((cell, column) => `<th${aligns[column] ?? ''}>${renderInline(cell, options)}</th>`)
135
+ .join('');
136
+ const tbody = rows
137
+ .map((row) => {
138
+ const cells = head
139
+ .map((_, column) => `<td${aligns[column] ?? ''}>${renderInline(row[column] ?? '', options)}</td>`)
140
+ .join('');
141
+ return `<tr>${cells}</tr>`;
142
+ })
143
+ .join('');
144
+ // Wrapped so a wide table scrolls inside itself instead of making the whole
145
+ // page scroll sideways on a phone.
146
+ return {
147
+ html: `<div class="table-wrap"><table><thead><tr>${th}</tr></thead><tbody>${tbody}</tbody></table></div>`,
148
+ next: index,
149
+ };
150
+ }
151
+ function splitRow(line) {
152
+ return line
153
+ .trim()
154
+ .replace(/^\|/, '')
155
+ .replace(/\|$/, '')
156
+ .split('|')
157
+ .map((cell) => cell.trim());
158
+ }
159
+ function tryList(lines, start, options) {
160
+ const first = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/.exec(lines[start] ?? '');
161
+ if (first === null)
162
+ return null;
163
+ const ordered = /\d/.test(first[2] ?? '');
164
+ const indent = (first[1] ?? '').length;
165
+ const items = [];
166
+ let index = start;
167
+ while (index < lines.length) {
168
+ const line = lines[index] ?? '';
169
+ const match = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/.exec(line);
170
+ if (match !== null && (match[1] ?? '').length <= indent) {
171
+ // A different marker type at the same level starts a new list rather
172
+ // than silently continuing this one.
173
+ if (/\d/.test(match[2] ?? '') !== ordered)
174
+ break;
175
+ items.push([match[3] ?? '']);
176
+ index += 1;
177
+ continue;
178
+ }
179
+ const current = items[items.length - 1];
180
+ if (current === undefined)
181
+ break;
182
+ // A deeper marker, or a plain continuation line, belongs to the item above.
183
+ if (match !== null || /^\s+\S/.test(line)) {
184
+ const strip = Math.min(line.length - line.trimStart().length, indent + 2);
185
+ current.push(line.slice(strip));
186
+ index += 1;
187
+ continue;
188
+ }
189
+ break;
190
+ }
191
+ if (items.length === 0)
192
+ return null;
193
+ const rendered = items
194
+ .map((item) => {
195
+ const body = item.join('\n');
196
+ // A one-line item stays inline, so a bullet list does not gain a
197
+ // paragraph's worth of vertical space per bullet.
198
+ const inner = item.length === 1
199
+ ? renderInline(body, options)
200
+ : renderMarkdown(body, options).replace(/^<p>([\s\S]*)<\/p>$/, '$1');
201
+ return `<li>${inner}</li>`;
202
+ })
203
+ .join('');
204
+ const tag = ordered ? 'ol' : 'ul';
205
+ return { html: `<${tag}>${rendered}</${tag}>`, next: index };
206
+ }
207
+ /** A sentinel that cannot survive escapeHtml, so it cannot be forged in input. */
208
+ const MARK = String.fromCharCode(0);
209
+ /**
210
+ * Inline markup.
211
+ *
212
+ * Code spans are pulled out first and put back last, so a backticked
213
+ * `**not bold**` stays literal and an asterisk inside code cannot open
214
+ * emphasis that then runs off through the rest of the paragraph.
215
+ */
216
+ export function renderInline(source, options = {}) {
217
+ const codes = [];
218
+ const rel = options.linkRel ?? DEFAULT_REL;
219
+ let text = source.replace(/(`+)([\s\S]*?)\1/g, (_whole, _ticks, body) => {
220
+ codes.push(`<code>${escapeHtml(body.trim())}</code>`);
221
+ return `${MARK}${codes.length - 1}${MARK}`;
222
+ });
223
+ text = escapeHtml(text);
224
+ // Images before links: the syntax differs only by the leading character.
225
+ text = text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+&quot;[^&]*&quot;)?\)/g, (whole, alt, href) => {
226
+ const url = safeUrl(unescapeUrl(href));
227
+ if (url === null)
228
+ return whole;
229
+ if (options.noImages === true) {
230
+ return `<a href="${escapeHtml(url)}" rel="${rel}">${alt === '' ? escapeHtml(url) : alt}</a>`;
231
+ }
232
+ return `<img src="${escapeHtml(url)}" alt="${alt}" loading="lazy" />`;
233
+ });
234
+ text = text.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+&quot;[^&]*&quot;)?\)/g, (whole, label, href) => {
235
+ const url = safeUrl(unescapeUrl(href));
236
+ if (url === null)
237
+ return whole;
238
+ return `<a href="${escapeHtml(url)}" rel="${rel}">${label}</a>`;
239
+ });
240
+ // Bare URLs. Trailing punctuation is left outside the link, because a URL at
241
+ // the end of a sentence is the common case and the full stop is not part of
242
+ // it.
243
+ text = text.replace(/(^|[\s(])(https?:\/\/[^\s<>"']+)/g, (_whole, lead, href) => {
244
+ const trimmed = href.replace(/[.,;:!?)]+$/, '');
245
+ const tail = href.slice(trimmed.length);
246
+ const url = safeUrl(trimmed);
247
+ if (url === null)
248
+ return `${lead}${href}`;
249
+ return `${lead}<a href="${escapeHtml(url)}" rel="${rel}">${escapeHtml(trimmed)}</a>${tail}`;
250
+ });
251
+ text = text
252
+ .replace(/\*\*\*([^*]+)\*\*\*/g, '<strong><em>$1</em></strong>')
253
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
254
+ .replace(/(^|[^\w*])\*([^*\n]+)\*(?!\w)/g, '$1<em>$2</em>')
255
+ .replace(/(^|[^\w_])__([^_\n]+)__(?!\w)/g, '$1<strong>$2</strong>')
256
+ .replace(/(^|[^\w_])_([^_\n]+)_(?!\w)/g, '$1<em>$2</em>')
257
+ .replace(/~~([^~\n]+)~~/g, '<del>$1</del>');
258
+ return text.replace(new RegExp(`${MARK}(\\d+)${MARK}`, 'g'), (_whole, id) => {
259
+ return codes[Number(id)] ?? '';
260
+ });
261
+ }
262
+ /** Only `\(` and `\)` matter inside a link target. */
263
+ function unescapeUrl(href) {
264
+ return href.replace(/\\([()])/g, '$1');
265
+ }
266
+ /** Plain text, for meta descriptions, feeds, the TUI and search snippets. */
267
+ export function toPlainText(source, limit = 300) {
268
+ const text = source
269
+ .replace(/```[\s\S]*?```/g, ' ')
270
+ .replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
271
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
272
+ .replace(/[#>*_~`|]/g, ' ')
273
+ .replace(/\s+/g, ' ')
274
+ .trim();
275
+ if (text.length <= limit)
276
+ return text;
277
+ return `${text.slice(0, limit - 1).replace(/\s+\S*$/, '')}...`;
278
+ }
279
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/markup/markdown.ts"],"names":[],"mappings":"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"}