hacklab 0.22.3 → 26.921.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/README.md +23 -430
  2. package/dist/index.js +132 -23
  3. package/package.json +20 -42
  4. package/LICENSE +0 -21
  5. package/dist/agent-handoff.d.ts +0 -51
  6. package/dist/agent-handoff.d.ts.map +0 -1
  7. package/dist/agent-handoff.js +0 -91
  8. package/dist/agent-handoff.js.map +0 -1
  9. package/dist/api-client.d.ts +0 -18
  10. package/dist/api-client.d.ts.map +0 -1
  11. package/dist/api-client.js +0 -40
  12. package/dist/api-client.js.map +0 -1
  13. package/dist/belt.d.ts +0 -25
  14. package/dist/belt.d.ts.map +0 -1
  15. package/dist/belt.js +0 -113
  16. package/dist/belt.js.map +0 -1
  17. package/dist/card.d.ts +0 -66
  18. package/dist/card.d.ts.map +0 -1
  19. package/dist/card.js +0 -151
  20. package/dist/card.js.map +0 -1
  21. package/dist/cli.d.ts +0 -2
  22. package/dist/cli.d.ts.map +0 -1
  23. package/dist/cli.js +0 -238
  24. package/dist/cli.js.map +0 -1
  25. package/dist/commands/book.d.ts +0 -14
  26. package/dist/commands/book.d.ts.map +0 -1
  27. package/dist/commands/book.js +0 -90
  28. package/dist/commands/book.js.map +0 -1
  29. package/dist/commands/chat.d.ts +0 -36
  30. package/dist/commands/chat.d.ts.map +0 -1
  31. package/dist/commands/chat.js +0 -642
  32. package/dist/commands/chat.js.map +0 -1
  33. package/dist/commands/config.d.ts +0 -2
  34. package/dist/commands/config.d.ts.map +0 -1
  35. package/dist/commands/config.js +0 -84
  36. package/dist/commands/config.js.map +0 -1
  37. package/dist/commands/daemon.d.ts +0 -19
  38. package/dist/commands/daemon.d.ts.map +0 -1
  39. package/dist/commands/daemon.js +0 -72
  40. package/dist/commands/daemon.js.map +0 -1
  41. package/dist/commands/drop.d.ts +0 -12
  42. package/dist/commands/drop.d.ts.map +0 -1
  43. package/dist/commands/drop.js +0 -103
  44. package/dist/commands/drop.js.map +0 -1
  45. package/dist/commands/drops.d.ts +0 -9
  46. package/dist/commands/drops.d.ts.map +0 -1
  47. package/dist/commands/drops.js +0 -96
  48. package/dist/commands/drops.js.map +0 -1
  49. package/dist/commands/essay.d.ts +0 -40
  50. package/dist/commands/essay.d.ts.map +0 -1
  51. package/dist/commands/essay.js +0 -651
  52. package/dist/commands/essay.js.map +0 -1
  53. package/dist/commands/hackathon.d.ts +0 -2
  54. package/dist/commands/hackathon.d.ts.map +0 -1
  55. package/dist/commands/hackathon.js +0 -893
  56. package/dist/commands/hackathon.js.map +0 -1
  57. package/dist/commands/hacker.d.ts +0 -2
  58. package/dist/commands/hacker.d.ts.map +0 -1
  59. package/dist/commands/hacker.js +0 -87
  60. package/dist/commands/hacker.js.map +0 -1
  61. package/dist/commands/jobs.d.ts +0 -18
  62. package/dist/commands/jobs.d.ts.map +0 -1
  63. package/dist/commands/jobs.js +0 -138
  64. package/dist/commands/jobs.js.map +0 -1
  65. package/dist/commands/keys.d.ts +0 -2
  66. package/dist/commands/keys.d.ts.map +0 -1
  67. package/dist/commands/keys.js +0 -138
  68. package/dist/commands/keys.js.map +0 -1
  69. package/dist/commands/login.d.ts +0 -83
  70. package/dist/commands/login.d.ts.map +0 -1
  71. package/dist/commands/login.js +0 -236
  72. package/dist/commands/login.js.map +0 -1
  73. package/dist/commands/logout.d.ts +0 -2
  74. package/dist/commands/logout.d.ts.map +0 -1
  75. package/dist/commands/logout.js +0 -22
  76. package/dist/commands/logout.js.map +0 -1
  77. package/dist/commands/org-jobs.d.ts +0 -50
  78. package/dist/commands/org-jobs.d.ts.map +0 -1
  79. package/dist/commands/org-jobs.js +0 -383
  80. package/dist/commands/org-jobs.js.map +0 -1
  81. package/dist/commands/org.d.ts +0 -67
  82. package/dist/commands/org.d.ts.map +0 -1
  83. package/dist/commands/org.js +0 -1244
  84. package/dist/commands/org.js.map +0 -1
  85. package/dist/commands/ping.d.ts +0 -2
  86. package/dist/commands/ping.d.ts.map +0 -1
  87. package/dist/commands/ping.js +0 -50
  88. package/dist/commands/ping.js.map +0 -1
  89. package/dist/commands/profile.d.ts +0 -72
  90. package/dist/commands/profile.d.ts.map +0 -1
  91. package/dist/commands/profile.js +0 -672
  92. package/dist/commands/profile.js.map +0 -1
  93. package/dist/commands/project.d.ts +0 -2
  94. package/dist/commands/project.d.ts.map +0 -1
  95. package/dist/commands/project.js +0 -583
  96. package/dist/commands/project.js.map +0 -1
  97. package/dist/commands/referral.d.ts +0 -6
  98. package/dist/commands/referral.d.ts.map +0 -1
  99. package/dist/commands/referral.js +0 -93
  100. package/dist/commands/referral.js.map +0 -1
  101. package/dist/commands/rtfm.d.ts +0 -9
  102. package/dist/commands/rtfm.d.ts.map +0 -1
  103. package/dist/commands/rtfm.js +0 -114
  104. package/dist/commands/rtfm.js.map +0 -1
  105. package/dist/commands/scan.d.ts +0 -13
  106. package/dist/commands/scan.d.ts.map +0 -1
  107. package/dist/commands/scan.js +0 -286
  108. package/dist/commands/scan.js.map +0 -1
  109. package/dist/commands/scout.d.ts +0 -18
  110. package/dist/commands/scout.d.ts.map +0 -1
  111. package/dist/commands/scout.js +0 -232
  112. package/dist/commands/scout.js.map +0 -1
  113. package/dist/commands/setup-rail.d.ts +0 -58
  114. package/dist/commands/setup-rail.d.ts.map +0 -1
  115. package/dist/commands/setup-rail.js +0 -135
  116. package/dist/commands/setup-rail.js.map +0 -1
  117. package/dist/commands/setup.d.ts +0 -29
  118. package/dist/commands/setup.d.ts.map +0 -1
  119. package/dist/commands/setup.js +0 -442
  120. package/dist/commands/setup.js.map +0 -1
  121. package/dist/commands/sync.d.ts +0 -9
  122. package/dist/commands/sync.d.ts.map +0 -1
  123. package/dist/commands/sync.js +0 -345
  124. package/dist/commands/sync.js.map +0 -1
  125. package/dist/commands/update.d.ts +0 -2
  126. package/dist/commands/update.d.ts.map +0 -1
  127. package/dist/commands/update.js +0 -92
  128. package/dist/commands/update.js.map +0 -1
  129. package/dist/commands/whoami.d.ts +0 -2
  130. package/dist/commands/whoami.d.ts.map +0 -1
  131. package/dist/commands/whoami.js +0 -41
  132. package/dist/commands/whoami.js.map +0 -1
  133. package/dist/config.d.ts +0 -78
  134. package/dist/config.d.ts.map +0 -1
  135. package/dist/config.js +0 -93
  136. package/dist/config.js.map +0 -1
  137. package/dist/daily-sync.d.ts +0 -150
  138. package/dist/daily-sync.d.ts.map +0 -1
  139. package/dist/daily-sync.js +0 -665
  140. package/dist/daily-sync.js.map +0 -1
  141. package/dist/dossier.d.ts +0 -73
  142. package/dist/dossier.d.ts.map +0 -1
  143. package/dist/dossier.js +0 -141
  144. package/dist/dossier.js.map +0 -1
  145. package/dist/field-normalize.d.ts +0 -4
  146. package/dist/field-normalize.d.ts.map +0 -1
  147. package/dist/field-normalize.js +0 -8
  148. package/dist/field-normalize.js.map +0 -1
  149. package/dist/index.d.ts +0 -3
  150. package/dist/index.d.ts.map +0 -1
  151. package/dist/index.js.map +0 -1
  152. package/dist/job-fields.d.ts +0 -47
  153. package/dist/job-fields.d.ts.map +0 -1
  154. package/dist/job-fields.js +0 -148
  155. package/dist/job-fields.js.map +0 -1
  156. package/dist/machine.d.ts +0 -15
  157. package/dist/machine.d.ts.map +0 -1
  158. package/dist/machine.js +0 -64
  159. package/dist/machine.js.map +0 -1
  160. package/dist/org-fields.d.ts +0 -57
  161. package/dist/org-fields.d.ts.map +0 -1
  162. package/dist/org-fields.js +0 -167
  163. package/dist/org-fields.js.map +0 -1
  164. package/dist/posthog.d.ts +0 -38
  165. package/dist/posthog.d.ts.map +0 -1
  166. package/dist/posthog.js +0 -224
  167. package/dist/posthog.js.map +0 -1
  168. package/dist/project-fields.d.ts +0 -40
  169. package/dist/project-fields.d.ts.map +0 -1
  170. package/dist/project-fields.js +0 -79
  171. package/dist/project-fields.js.map +0 -1
  172. package/dist/prompt-consent.d.ts +0 -73
  173. package/dist/prompt-consent.d.ts.map +0 -1
  174. package/dist/prompt-consent.js +0 -191
  175. package/dist/prompt-consent.js.map +0 -1
  176. package/dist/prompt-stats.d.ts +0 -226
  177. package/dist/prompt-stats.d.ts.map +0 -1
  178. package/dist/prompt-stats.js +0 -561
  179. package/dist/prompt-stats.js.map +0 -1
  180. package/dist/referral.d.ts +0 -14
  181. package/dist/referral.d.ts.map +0 -1
  182. package/dist/referral.js +0 -28
  183. package/dist/referral.js.map +0 -1
  184. package/dist/registry.d.ts +0 -15
  185. package/dist/registry.d.ts.map +0 -1
  186. package/dist/registry.js +0 -180
  187. package/dist/registry.js.map +0 -1
  188. package/dist/resolve-command.d.ts +0 -20
  189. package/dist/resolve-command.d.ts.map +0 -1
  190. package/dist/resolve-command.js +0 -21
  191. package/dist/resolve-command.js.map +0 -1
  192. package/dist/scanners/antigravity.d.ts +0 -41
  193. package/dist/scanners/antigravity.d.ts.map +0 -1
  194. package/dist/scanners/antigravity.js +0 -210
  195. package/dist/scanners/antigravity.js.map +0 -1
  196. package/dist/scanners/github-copilot-telemetry.d.ts +0 -23
  197. package/dist/scanners/github-copilot-telemetry.d.ts.map +0 -1
  198. package/dist/scanners/github-copilot-telemetry.js +0 -220
  199. package/dist/scanners/github-copilot-telemetry.js.map +0 -1
  200. package/dist/scanners/github-copilot.d.ts +0 -68
  201. package/dist/scanners/github-copilot.d.ts.map +0 -1
  202. package/dist/scanners/github-copilot.js +0 -262
  203. package/dist/scanners/github-copilot.js.map +0 -1
  204. package/dist/scanners/incremental.d.ts +0 -282
  205. package/dist/scanners/incremental.d.ts.map +0 -1
  206. package/dist/scanners/incremental.js +0 -788
  207. package/dist/scanners/incremental.js.map +0 -1
  208. package/dist/scanners/index.d.ts +0 -116
  209. package/dist/scanners/index.d.ts.map +0 -1
  210. package/dist/scanners/index.js +0 -726
  211. package/dist/scanners/index.js.map +0 -1
  212. package/dist/scanners/sqlite.d.ts +0 -18
  213. package/dist/scanners/sqlite.d.ts.map +0 -1
  214. package/dist/scanners/sqlite.js +0 -63
  215. package/dist/scanners/sqlite.js.map +0 -1
  216. package/dist/scanners/util.d.ts +0 -112
  217. package/dist/scanners/util.d.ts.map +0 -1
  218. package/dist/scanners/util.js +0 -167
  219. package/dist/scanners/util.js.map +0 -1
  220. package/dist/session.d.ts +0 -106
  221. package/dist/session.d.ts.map +0 -1
  222. package/dist/session.js +0 -191
  223. package/dist/session.js.map +0 -1
  224. package/dist/share-card-colors.d.ts +0 -19
  225. package/dist/share-card-colors.d.ts.map +0 -1
  226. package/dist/share-card-colors.js +0 -36
  227. package/dist/share-card-colors.js.map +0 -1
  228. package/dist/share-card.d.ts +0 -31
  229. package/dist/share-card.d.ts.map +0 -1
  230. package/dist/share-card.js +0 -380
  231. package/dist/share-card.js.map +0 -1
  232. package/dist/share.d.ts +0 -17
  233. package/dist/share.d.ts.map +0 -1
  234. package/dist/share.js +0 -75
  235. package/dist/share.js.map +0 -1
  236. package/dist/sync.d.ts +0 -100
  237. package/dist/sync.d.ts.map +0 -1
  238. package/dist/sync.js +0 -304
  239. package/dist/sync.js.map +0 -1
  240. package/dist/ui.d.ts +0 -33
  241. package/dist/ui.d.ts.map +0 -1
  242. package/dist/ui.js +0 -127
  243. package/dist/ui.js.map +0 -1
  244. package/dist/utils/npmGlobal.d.ts +0 -30
  245. package/dist/utils/npmGlobal.d.ts.map +0 -1
  246. package/dist/utils/npmGlobal.js +0 -69
  247. package/dist/utils/npmGlobal.js.map +0 -1
  248. package/dist/utils/openBrowser.d.ts +0 -14
  249. package/dist/utils/openBrowser.d.ts.map +0 -1
  250. package/dist/utils/openBrowser.js +0 -47
  251. package/dist/utils/openBrowser.js.map +0 -1
  252. package/dist/utils/updateCheck.d.ts +0 -16
  253. package/dist/utils/updateCheck.d.ts.map +0 -1
  254. package/dist/utils/updateCheck.js +0 -111
  255. package/dist/utils/updateCheck.js.map +0 -1
  256. package/dist/utils/waitForEnter.d.ts +0 -12
  257. package/dist/utils/waitForEnter.d.ts.map +0 -1
  258. package/dist/utils/waitForEnter.js +0 -43
  259. package/dist/utils/waitForEnter.js.map +0 -1
@@ -1,651 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import * as clack from '@clack/prompts';
3
- import { emitJsonError, requireSession } from '../api-client.js';
4
- import { captureEvent } from '../posthog.js';
5
- import { resolveCommand } from '../resolve-command.js';
6
- import { loadSession, resolveAppUrl, unauthorizedHint, } from '../session.js';
7
- import { bold, dim, error, info, link, stripControl, success } from '../ui.js';
8
- // `hacklab essay` — agent help on the bare command. `post` publishes markdown
9
- // the agent already has (`--content`) or a file on disk (`--file`, or a bare
10
- // path). `update` replaces the body at a stable URL. `view` reads one essay by
11
- // id. `list` reads yours, a hacker's, or an org's. `delete` removes yours.
12
- const SUBCOMMANDS = ['post', 'update', 'view', 'list', 'delete'];
13
- const ESSAYS_BASE = '/api/essays';
14
- // Same rule as the server: 4..36 chars of a lowercased uuid / prefix.
15
- const ID_PREFIX_RE = /^[0-9a-f][0-9a-f-]{3,35}$/;
16
- /**
17
- * `view` takes an essay id only. Anything that can't be an id prefix is a
18
- * usage error pointing at `essay list <handle>` — a handle and a short hex id
19
- * are indistinguishable, so guessing between them silently reads the wrong
20
- * thing.
21
- */
22
- export function parseViewTarget(token) {
23
- if (!token)
24
- return { kind: 'missing' };
25
- const raw = token.replace(/^@/, '');
26
- if (!raw)
27
- return { kind: 'missing' };
28
- if (ID_PREFIX_RE.test(raw.toLowerCase())) {
29
- return { kind: 'one', id: raw.toLowerCase() };
30
- }
31
- return { kind: 'not-an-id', token: raw };
32
- }
33
- /**
34
- * The `essay list` target grammar, decided by argument count so a user
35
- * literally named "org" still works as the bare one-arg form:
36
- * (none) → your essays
37
- * <handle> → that user's essays
38
- * org <slug> → that org's essays
39
- * org/<slug> → same, mirroring the web URL /org/<slug>
40
- */
41
- export function parseListTarget(positionals) {
42
- if (positionals.length === 0)
43
- return { kind: 'self' };
44
- if (positionals.length === 1) {
45
- const arg = positionals[0];
46
- if (arg.startsWith('org/')) {
47
- const slug = arg.slice('org/'.length);
48
- return slug ? { kind: 'org', slug } : { kind: 'invalid' };
49
- }
50
- return { kind: 'user', handle: arg.replace(/^@/, '') };
51
- }
52
- if (positionals.length === 2 && positionals[0] === 'org') {
53
- return { kind: 'org', slug: positionals[1] };
54
- }
55
- return { kind: 'invalid' };
56
- }
57
- function printJson(data) {
58
- console.log(JSON.stringify(data, null, 2));
59
- }
60
- function printHelp() {
61
- console.log(`hacklab essay post --title <t> --content <md> [--json]`);
62
- console.log(dim(' or --file <path.md>, or a bare <path.md>'));
63
- console.log(`hacklab essay update <id> --content <md> [--json]`);
64
- console.log(dim(' same URL'));
65
- console.log('');
66
- console.log(`hacklab essay list [<handle> | org <slug>] [--page N] [--json]`);
67
- console.log(dim(' yours with no argument'));
68
- console.log(`hacklab essay view <id> [--json]`);
69
- console.log(dim(' one essay, full text'));
70
- console.log('');
71
- console.log(`hacklab essay delete <id> [--yes] [--json]`);
72
- console.log(dim(' yours only; --yes skips the confirm'));
73
- }
74
- function usage(exitCode = 1) {
75
- printHelp();
76
- process.exit(exitCode);
77
- }
78
- function flagValue(args, ...names) {
79
- for (const name of names) {
80
- const i = args.indexOf(name);
81
- if (i !== -1 && args[i + 1])
82
- return args[i + 1];
83
- const eq = args.find((a) => a.startsWith(`${name}=`));
84
- if (eq)
85
- return eq.slice(name.length + 1);
86
- }
87
- return undefined;
88
- }
89
- // Flags that take a following value. Every arg-walker has to skip that value,
90
- // or a legitimate `--content "- bullet"` reads as a flag of its own.
91
- function unknownFlag(args, allowed, valueFlags) {
92
- for (let i = 0; i < args.length; i++) {
93
- const arg = args[i];
94
- if (!arg)
95
- continue;
96
- if (valueFlags.has(arg)) {
97
- i++;
98
- continue;
99
- }
100
- if (!arg.startsWith('-'))
101
- continue;
102
- const name = arg.split('=')[0];
103
- if (!name)
104
- continue;
105
- if (!allowed.has(name))
106
- return name;
107
- }
108
- return undefined;
109
- }
110
- /** Non-flag arguments, in order, skipping the values that belong to a flag. */
111
- function positionals(args, valueFlags) {
112
- const found = [];
113
- for (let i = 0; i < args.length; i++) {
114
- const arg = args[i];
115
- if (!arg || arg === '--json')
116
- continue;
117
- if (valueFlags.has(arg)) {
118
- i++;
119
- continue;
120
- }
121
- if ([...valueFlags].some((name) => arg.startsWith(`${name}=`)))
122
- continue;
123
- if (arg.startsWith('-'))
124
- continue;
125
- found.push(arg);
126
- }
127
- return found;
128
- }
129
- export async function readError(res, session) {
130
- if (res.status === 401 && session)
131
- return unauthorizedHint(session);
132
- const data = (await res.json().catch(() => null));
133
- return data?.error ?? `request failed (${res.status})`;
134
- }
135
- const MONTHS = [
136
- 'jan',
137
- 'feb',
138
- 'mar',
139
- 'apr',
140
- 'may',
141
- 'jun',
142
- 'jul',
143
- 'aug',
144
- 'sep',
145
- 'oct',
146
- 'nov',
147
- 'dec',
148
- ];
149
- export function formatEssayDate(iso) {
150
- const d = new Date(iso);
151
- if (Number.isNaN(d.getTime()))
152
- return iso;
153
- return `${MONTHS[d.getMonth()]} ${d.getDate()} ${d.getFullYear()}`;
154
- }
155
- function shortId(id) {
156
- return id.slice(0, 8);
157
- }
158
- async function readMarkdownFile(path, json) {
159
- if (!/\.(md|markdown)$/i.test(path)) {
160
- const message = `expected a markdown file (.md), got: ${path}`;
161
- if (json)
162
- emitJsonError('invalid_fields', message);
163
- error(message);
164
- process.exit(1);
165
- }
166
- try {
167
- return await readFile(path, 'utf8');
168
- }
169
- catch (err) {
170
- const code = err.code;
171
- const message = code === 'ENOENT' ? `file not found: ${path}` : `could not read ${path}`;
172
- if (json)
173
- emitJsonError('read_failed', message);
174
- error(message);
175
- process.exit(1);
176
- }
177
- }
178
- /**
179
- * The markdown body, from `--content`, `--file`, or the positional path the
180
- * pre-flag CLI took (`essay post note.md`). Exactly one source.
181
- */
182
- async function resolveMarkdown(args, json, file) {
183
- const content = flagValue(args, '--content');
184
- const fileFlag = flagValue(args, '--file');
185
- const sources = [content, fileFlag, file].filter((v) => v !== undefined);
186
- if (sources.length > 1) {
187
- const message = content !== undefined && fileFlag !== undefined
188
- ? 'use either --content or --file, not both'
189
- : 'use one markdown source: --content, --file, or a <file.md> path';
190
- if (json)
191
- emitJsonError('invalid_fields', message);
192
- error(message);
193
- process.exit(1);
194
- }
195
- if (content !== undefined) {
196
- if (!content.trim()) {
197
- const message = 'an essay needs --content or --file';
198
- if (json)
199
- emitJsonError('missing_content', message);
200
- error(message);
201
- process.exit(1);
202
- }
203
- return content;
204
- }
205
- const path = fileFlag ?? file;
206
- if (path)
207
- return readMarkdownFile(path, json);
208
- const message = 'an essay needs --content or --file';
209
- if (json)
210
- emitJsonError('missing_content', message);
211
- error(message);
212
- process.exit(1);
213
- }
214
- const BODY_FLAGS = new Set(['--title', '--content', '--file', '--json']);
215
- const BODY_VALUE_FLAGS = new Set(['--title', '--content', '--file']);
216
- async function essayPost(args) {
217
- const json = args.includes('--json');
218
- const unknown = unknownFlag(args, BODY_FLAGS, BODY_VALUE_FLAGS);
219
- if (unknown) {
220
- if (json)
221
- emitJsonError('invalid_fields', `unknown flag: ${unknown}`);
222
- error(`unknown flag: ${unknown}`);
223
- process.exit(1);
224
- }
225
- const rest = positionals(args, BODY_VALUE_FLAGS);
226
- if (rest.length > 1) {
227
- const message = 'usage: hacklab essay post --title <t> --content <md>';
228
- if (json)
229
- emitJsonError('usage', message);
230
- error(message);
231
- process.exit(1);
232
- }
233
- const title = flagValue(args, '--title');
234
- if (!title) {
235
- const message = 'an essay needs a title: --title "Why I built this"';
236
- if (json)
237
- emitJsonError('missing_title', message);
238
- error(message);
239
- process.exit(1);
240
- }
241
- const markdown = await resolveMarkdown(args, json, rest[0]);
242
- const session = await requireSession(json);
243
- let res;
244
- try {
245
- res = await fetch(`${resolveAppUrl(session)}${ESSAYS_BASE}`, {
246
- method: 'POST',
247
- headers: {
248
- 'Content-Type': 'application/json',
249
- Authorization: `Bearer ${session.token}`,
250
- },
251
- body: JSON.stringify({ markdown, title }),
252
- });
253
- }
254
- catch (err) {
255
- const message = err instanceof Error ? err.message : String(err);
256
- if (json)
257
- emitJsonError('network', message);
258
- error(message);
259
- process.exit(1);
260
- }
261
- if (!res.ok) {
262
- const message = await readError(res, session);
263
- if (json)
264
- emitJsonError('error', message);
265
- error(message);
266
- process.exit(1);
267
- }
268
- const data = (await res.json());
269
- const url = `${resolveAppUrl(session)}${data.path}`;
270
- await captureEvent(session.handle, 'cli_essay_published', {
271
- essay_id: data.id,
272
- has_custom_title: true,
273
- });
274
- if (json) {
275
- printJson({ schemaVersion: 1, ...data, url });
276
- return;
277
- }
278
- success(`published ${bold(stripControl(data.title))}`);
279
- info(`${dim('id')} ${bold(shortId(data.id))}`);
280
- info(link(url));
281
- }
282
- async function essayUpdate(args) {
283
- const json = args.includes('--json');
284
- const unknown = unknownFlag(args, BODY_FLAGS, BODY_VALUE_FLAGS);
285
- if (unknown) {
286
- if (json)
287
- emitJsonError('invalid_fields', `unknown flag: ${unknown}`);
288
- error(`unknown flag: ${unknown}`);
289
- process.exit(1);
290
- }
291
- const rest = positionals(args, BODY_VALUE_FLAGS);
292
- const id = rest[0];
293
- if (!id || rest.length > 2) {
294
- const message = 'usage: hacklab essay update <id> --content <md>';
295
- if (json)
296
- emitJsonError('usage', message);
297
- error(message);
298
- process.exit(1);
299
- }
300
- const title = flagValue(args, '--title');
301
- const markdown = await resolveMarkdown(args, json, rest[1]);
302
- const session = await requireSession(json);
303
- let res;
304
- try {
305
- res = await fetch(`${resolveAppUrl(session)}${ESSAYS_BASE}/${encodeURIComponent(id)}`, {
306
- method: 'PATCH',
307
- headers: {
308
- 'Content-Type': 'application/json',
309
- Authorization: `Bearer ${session.token}`,
310
- },
311
- body: JSON.stringify({ markdown, ...(title ? { title } : {}) }),
312
- });
313
- }
314
- catch (err) {
315
- const message = err instanceof Error ? err.message : String(err);
316
- if (json)
317
- emitJsonError('network', message);
318
- error(message);
319
- process.exit(1);
320
- }
321
- if (!res.ok) {
322
- const message = await readError(res, session);
323
- if (json)
324
- emitJsonError('error', message);
325
- error(message);
326
- process.exit(1);
327
- }
328
- const data = (await res.json());
329
- const url = `${resolveAppUrl(session)}${data.path}`;
330
- await captureEvent(session.handle, 'cli_essay_updated', {
331
- essay_id: data.id,
332
- has_custom_title: title !== undefined,
333
- });
334
- if (json) {
335
- printJson({ schemaVersion: 1, ...data, url });
336
- return;
337
- }
338
- success(`updated ${bold(stripControl(data.title))}`);
339
- info(link(url));
340
- }
341
- async function essayDelete(args) {
342
- const json = args.includes('--json');
343
- const unknown = unknownFlag(args, new Set(['--json', '--yes', '-y']), new Set());
344
- if (unknown) {
345
- if (json)
346
- emitJsonError('invalid_fields', `unknown flag: ${unknown}`);
347
- error(`unknown flag: ${unknown}`);
348
- process.exit(1);
349
- }
350
- const yes = args.includes('--yes') || args.includes('-y');
351
- const id = args.find((a) => !a.startsWith('-'));
352
- if (!id) {
353
- const message = 'usage: hacklab essay delete <id> [--yes]';
354
- if (json)
355
- emitJsonError('usage', message);
356
- error(message);
357
- process.exit(1);
358
- }
359
- const session = await requireSession(json);
360
- const base = resolveAppUrl(session);
361
- // Deletion is irreversible and `essay d <id>` resolves here by prefix, so a
362
- // typo must not be enough on its own. The prompt names the essay, which
363
- // costs one read — skipped entirely on the --yes and agent paths.
364
- if (!yes) {
365
- if (json || !process.stdin.isTTY) {
366
- const message = 'refusing to delete without confirmation — pass --yes';
367
- if (json)
368
- emitJsonError('confirm', message);
369
- error(message);
370
- process.exit(1);
371
- }
372
- const preview = await fetch(`${base}${ESSAYS_BASE}/${encodeURIComponent(id)}`);
373
- if (!preview.ok) {
374
- error(await readError(preview, session));
375
- process.exit(1);
376
- }
377
- const { essay: found } = (await preview.json());
378
- const ok = await clack.confirm({
379
- message: `delete ${bold(stripControl(found.title))}? this cannot be undone.`,
380
- initialValue: false,
381
- });
382
- if (clack.isCancel(ok) || !ok) {
383
- info('kept.');
384
- return;
385
- }
386
- }
387
- let res;
388
- try {
389
- res = await fetch(`${base}${ESSAYS_BASE}/${encodeURIComponent(id)}`, {
390
- method: 'DELETE',
391
- headers: { Authorization: `Bearer ${session.token}` },
392
- });
393
- }
394
- catch (err) {
395
- const message = err instanceof Error ? err.message : String(err);
396
- if (json)
397
- emitJsonError('network', message);
398
- error(message);
399
- process.exit(1);
400
- }
401
- if (!res.ok) {
402
- const message = await readError(res, session);
403
- if (json)
404
- emitJsonError('error', message);
405
- error(message);
406
- process.exit(1);
407
- }
408
- const data = (await res.json());
409
- await captureEvent(session.handle, 'cli_essay_deleted', {
410
- essay_id: data.id,
411
- });
412
- if (json) {
413
- printJson({ schemaVersion: 1, deleted: true, id: data.id });
414
- return;
415
- }
416
- success(`deleted ${bold(stripControl(data.title))}`);
417
- }
418
- function renderEssay(essay, url) {
419
- const byline = [
420
- essay.authorDisplayName ?? essay.authorHandle,
421
- formatEssayDate(essay.publishedAt),
422
- essay.readingTimeMinutes ? `${essay.readingTimeMinutes} min` : null,
423
- ].filter(Boolean);
424
- console.log(` ${bold(stripControl(essay.title))}`);
425
- console.log(dim(` ${byline.join(' · ')}`));
426
- if (essay.contentText) {
427
- console.log('');
428
- console.log(stripControl(essay.contentText).trimEnd());
429
- }
430
- console.log('');
431
- info(link(url));
432
- }
433
- /** Meta line under a list entry: (by author ·) id · date · reading time (· synced). */
434
- function metaLine(item, withAuthor) {
435
- const parts = [];
436
- if (withAuthor && item.authorHandle)
437
- parts.push(`by ${item.authorHandle}`);
438
- parts.push(shortId(item.id));
439
- parts.push(formatEssayDate(item.publishedAt));
440
- if (item.readingTimeMinutes)
441
- parts.push(`${item.readingTimeMinutes} min`);
442
- if (item.source === 'sync')
443
- parts.push('synced');
444
- return dim(` ${stripControl(parts.join(' · '))}`);
445
- }
446
- function renderList(data, subject, appUrl, self) {
447
- const isOrg = data.kind === 'org';
448
- if (data.items.length === 0) {
449
- info(isOrg ? `no essays on ${subject}` : `no essays on @${subject}`);
450
- if (self) {
451
- info(`run ${dim('hacklab essay post --title "…" --content <md>')} to publish one`);
452
- }
453
- return;
454
- }
455
- for (const item of data.items) {
456
- console.log(` ${bold(stripControl(item.title))}`);
457
- console.log(metaLine(item, isOrg));
458
- }
459
- console.log('');
460
- if (data.page < data.totalPages) {
461
- const target = isOrg ? `org ${subject}` : subject;
462
- console.log(dim(` next → hacklab essay list ${target} --page ${data.page + 1}`));
463
- console.log('');
464
- }
465
- info(link(`${appUrl}${isOrg ? `/org/${subject}` : `/${subject}`}`));
466
- }
467
- async function essayView(args) {
468
- const json = args.includes('--json');
469
- const unknown = unknownFlag(args, new Set(['--json']), new Set());
470
- if (unknown) {
471
- if (json)
472
- emitJsonError('invalid_fields', `unknown flag: ${unknown}`);
473
- error(`unknown flag: ${unknown}`);
474
- process.exit(1);
475
- }
476
- const rest = positionals(args, new Set());
477
- const target = parseViewTarget(rest.length === 1 ? rest[0] : undefined);
478
- if (target.kind === 'missing') {
479
- const message = 'usage: hacklab essay view <id>';
480
- if (json)
481
- emitJsonError('usage', message);
482
- error(message);
483
- process.exit(1);
484
- }
485
- if (target.kind === 'not-an-id') {
486
- const message = `not an essay id: "${target.token}" — for a hacker's essays run: hacklab essay list ${target.token}`;
487
- if (json)
488
- emitJsonError('usage', message);
489
- error(message);
490
- process.exit(1);
491
- }
492
- // Viewing is public — a session only improves the base URL resolution.
493
- const session = await loadSession();
494
- const appUrl = resolveAppUrl(session);
495
- let res;
496
- try {
497
- res = await fetch(`${appUrl}${ESSAYS_BASE}/${encodeURIComponent(target.id)}`);
498
- }
499
- catch (err) {
500
- const message = err instanceof Error ? err.message : String(err);
501
- if (json)
502
- emitJsonError('network', message);
503
- error(message);
504
- process.exit(1);
505
- }
506
- if (!res.ok) {
507
- if (res.status === 404) {
508
- const message = `no essay named "${target.id}"`;
509
- if (json)
510
- emitJsonError('not_found', message);
511
- error(message);
512
- info(`if that's a handle, run ${dim(`hacklab essay list ${target.id}`)} instead`);
513
- process.exit(1);
514
- }
515
- const message = await readError(res);
516
- if (json)
517
- emitJsonError('error', message);
518
- error(message);
519
- process.exit(1);
520
- }
521
- const body = (await res.json());
522
- const url = `${appUrl}${body.essay.path}`;
523
- if (json) {
524
- printJson({ schemaVersion: 1, essay: { ...body.essay, url } });
525
- return;
526
- }
527
- renderEssay(body.essay, url);
528
- }
529
- async function essayList(args) {
530
- const json = args.includes('--json');
531
- const unknown = unknownFlag(args, new Set(['--json', '--page']), new Set(['--page']));
532
- if (unknown) {
533
- if (json)
534
- emitJsonError('invalid_fields', `unknown flag: ${unknown}`);
535
- error(`unknown flag: ${unknown}`);
536
- process.exit(1);
537
- }
538
- const rawPage = flagValue(args, '--page');
539
- const page = rawPage ? Number.parseInt(rawPage, 10) : 1;
540
- if (rawPage !== undefined && (!Number.isInteger(page) || page < 1)) {
541
- const message = `--page must be a positive integer, got: ${rawPage}`;
542
- if (json)
543
- emitJsonError('invalid_fields', message);
544
- error(message);
545
- process.exit(1);
546
- }
547
- const target = parseListTarget(positionals(args, new Set(['--page'])));
548
- if (target.kind === 'invalid') {
549
- const message = 'usage: hacklab essay list [<handle> | org <slug>] [--page N]';
550
- if (json)
551
- emitJsonError('usage', message);
552
- error(message);
553
- process.exit(1);
554
- }
555
- let session;
556
- let subject;
557
- let query;
558
- if (target.kind === 'self') {
559
- session = await requireSession(json);
560
- if (!session.handle) {
561
- const message = 'no username on this session';
562
- if (json)
563
- emitJsonError('unauthorized', message);
564
- error(message);
565
- info(`run ${dim('hacklab login')} (or pass a handle explicitly)`);
566
- process.exit(1);
567
- }
568
- subject = session.handle;
569
- query = { key: 'user', value: session.handle };
570
- }
571
- else {
572
- session = await loadSession();
573
- subject = target.kind === 'user' ? target.handle : target.slug;
574
- query = { key: target.kind === 'user' ? 'user' : 'org', value: subject };
575
- }
576
- const appUrl = resolveAppUrl(session);
577
- const url = new URL(`${appUrl}${ESSAYS_BASE}`);
578
- url.searchParams.set(query.key, query.value);
579
- if (page > 1)
580
- url.searchParams.set('page', String(page));
581
- let res;
582
- try {
583
- res = await fetch(url);
584
- }
585
- catch (err) {
586
- const message = err instanceof Error ? err.message : String(err);
587
- if (json)
588
- emitJsonError('network', message);
589
- error(message);
590
- process.exit(1);
591
- }
592
- if (!res.ok) {
593
- if (res.status === 404) {
594
- const message = query.key === 'org'
595
- ? `no org named "${subject}"`
596
- : `no hacker named "${subject}"`;
597
- if (json)
598
- emitJsonError('not_found', message);
599
- error(message);
600
- process.exit(1);
601
- }
602
- const message = await readError(res, session);
603
- if (json)
604
- emitJsonError('error', message);
605
- error(message);
606
- process.exit(1);
607
- }
608
- const data = (await res.json());
609
- if (json) {
610
- printJson({ schemaVersion: 1, ...data });
611
- return;
612
- }
613
- renderList(data, data.kind === 'org'
614
- ? (data.org?.slug ?? subject)
615
- : (data.author?.handle ?? subject), appUrl, target.kind === 'self');
616
- }
617
- export async function essay(args) {
618
- const json = args.includes('--json');
619
- const rest = args.filter((a) => a !== '--json');
620
- const [subToken, ...subArgs] = rest;
621
- if (json)
622
- subArgs.push('--json');
623
- if (!subToken ||
624
- subToken === '--help' ||
625
- subToken === '-h' ||
626
- subToken === 'help') {
627
- usage(0);
628
- }
629
- if (subToken.startsWith('-'))
630
- usage();
631
- const resolved = resolveCommand(subToken, SUBCOMMANDS);
632
- if (resolved.kind === 'ambiguous') {
633
- error(`ambiguous: essay ${subToken} (${resolved.matches.join(', ')})`);
634
- process.exit(1);
635
- }
636
- if (resolved.kind === 'unknown') {
637
- error(`unknown subcommand: essay ${subToken}`);
638
- usage();
639
- }
640
- if (resolved.name === 'post')
641
- return essayPost(subArgs);
642
- if (resolved.name === 'update')
643
- return essayUpdate(subArgs);
644
- if (resolved.name === 'view')
645
- return essayView(subArgs);
646
- if (resolved.name === 'list')
647
- return essayList(subArgs);
648
- if (resolved.name === 'delete')
649
- return essayDelete(subArgs);
650
- }
651
- //# sourceMappingURL=essay.js.map