hacklab 0.22.2 → 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.
- package/README.md +23 -430
- package/dist/index.js +132 -23
- package/package.json +20 -42
- package/LICENSE +0 -21
- package/dist/agent-handoff.d.ts +0 -51
- package/dist/agent-handoff.d.ts.map +0 -1
- package/dist/agent-handoff.js +0 -91
- package/dist/agent-handoff.js.map +0 -1
- package/dist/api-client.d.ts +0 -18
- package/dist/api-client.d.ts.map +0 -1
- package/dist/api-client.js +0 -40
- package/dist/api-client.js.map +0 -1
- package/dist/belt.d.ts +0 -25
- package/dist/belt.d.ts.map +0 -1
- package/dist/belt.js +0 -113
- package/dist/belt.js.map +0 -1
- package/dist/card.d.ts +0 -66
- package/dist/card.d.ts.map +0 -1
- package/dist/card.js +0 -151
- package/dist/card.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -238
- package/dist/cli.js.map +0 -1
- package/dist/commands/book.d.ts +0 -14
- package/dist/commands/book.d.ts.map +0 -1
- package/dist/commands/book.js +0 -90
- package/dist/commands/book.js.map +0 -1
- package/dist/commands/chat.d.ts +0 -36
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js +0 -642
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js +0 -84
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/daemon.d.ts +0 -19
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/daemon.js +0 -72
- package/dist/commands/daemon.js.map +0 -1
- package/dist/commands/drop.d.ts +0 -12
- package/dist/commands/drop.d.ts.map +0 -1
- package/dist/commands/drop.js +0 -103
- package/dist/commands/drop.js.map +0 -1
- package/dist/commands/drops.d.ts +0 -9
- package/dist/commands/drops.d.ts.map +0 -1
- package/dist/commands/drops.js +0 -96
- package/dist/commands/drops.js.map +0 -1
- package/dist/commands/essay.d.ts +0 -40
- package/dist/commands/essay.d.ts.map +0 -1
- package/dist/commands/essay.js +0 -651
- package/dist/commands/essay.js.map +0 -1
- package/dist/commands/hackathon.d.ts +0 -2
- package/dist/commands/hackathon.d.ts.map +0 -1
- package/dist/commands/hackathon.js +0 -893
- package/dist/commands/hackathon.js.map +0 -1
- package/dist/commands/hacker.d.ts +0 -2
- package/dist/commands/hacker.d.ts.map +0 -1
- package/dist/commands/hacker.js +0 -87
- package/dist/commands/hacker.js.map +0 -1
- package/dist/commands/jobs.d.ts +0 -18
- package/dist/commands/jobs.d.ts.map +0 -1
- package/dist/commands/jobs.js +0 -138
- package/dist/commands/jobs.js.map +0 -1
- package/dist/commands/keys.d.ts +0 -2
- package/dist/commands/keys.d.ts.map +0 -1
- package/dist/commands/keys.js +0 -138
- package/dist/commands/keys.js.map +0 -1
- package/dist/commands/login.d.ts +0 -83
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -236
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -2
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js +0 -22
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/org-jobs.d.ts +0 -50
- package/dist/commands/org-jobs.d.ts.map +0 -1
- package/dist/commands/org-jobs.js +0 -383
- package/dist/commands/org-jobs.js.map +0 -1
- package/dist/commands/org.d.ts +0 -67
- package/dist/commands/org.d.ts.map +0 -1
- package/dist/commands/org.js +0 -1244
- package/dist/commands/org.js.map +0 -1
- package/dist/commands/ping.d.ts +0 -2
- package/dist/commands/ping.d.ts.map +0 -1
- package/dist/commands/ping.js +0 -50
- package/dist/commands/ping.js.map +0 -1
- package/dist/commands/profile.d.ts +0 -72
- package/dist/commands/profile.d.ts.map +0 -1
- package/dist/commands/profile.js +0 -672
- package/dist/commands/profile.js.map +0 -1
- package/dist/commands/project.d.ts +0 -2
- package/dist/commands/project.d.ts.map +0 -1
- package/dist/commands/project.js +0 -583
- package/dist/commands/project.js.map +0 -1
- package/dist/commands/referral.d.ts +0 -6
- package/dist/commands/referral.d.ts.map +0 -1
- package/dist/commands/referral.js +0 -93
- package/dist/commands/referral.js.map +0 -1
- package/dist/commands/rtfm.d.ts +0 -9
- package/dist/commands/rtfm.d.ts.map +0 -1
- package/dist/commands/rtfm.js +0 -114
- package/dist/commands/rtfm.js.map +0 -1
- package/dist/commands/scan.d.ts +0 -13
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js +0 -286
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/scout.d.ts +0 -18
- package/dist/commands/scout.d.ts.map +0 -1
- package/dist/commands/scout.js +0 -232
- package/dist/commands/scout.js.map +0 -1
- package/dist/commands/setup-rail.d.ts +0 -58
- package/dist/commands/setup-rail.d.ts.map +0 -1
- package/dist/commands/setup-rail.js +0 -135
- package/dist/commands/setup-rail.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -442
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/sync.d.ts +0 -9
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js +0 -345
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/update.d.ts +0 -2
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -92
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -2
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js +0 -41
- package/dist/commands/whoami.js.map +0 -1
- package/dist/config.d.ts +0 -78
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -93
- package/dist/config.js.map +0 -1
- package/dist/daily-sync.d.ts +0 -150
- package/dist/daily-sync.d.ts.map +0 -1
- package/dist/daily-sync.js +0 -665
- package/dist/daily-sync.js.map +0 -1
- package/dist/dossier.d.ts +0 -73
- package/dist/dossier.d.ts.map +0 -1
- package/dist/dossier.js +0 -141
- package/dist/dossier.js.map +0 -1
- package/dist/field-normalize.d.ts +0 -4
- package/dist/field-normalize.d.ts.map +0 -1
- package/dist/field-normalize.js +0 -8
- package/dist/field-normalize.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/job-fields.d.ts +0 -47
- package/dist/job-fields.d.ts.map +0 -1
- package/dist/job-fields.js +0 -148
- package/dist/job-fields.js.map +0 -1
- package/dist/machine.d.ts +0 -15
- package/dist/machine.d.ts.map +0 -1
- package/dist/machine.js +0 -64
- package/dist/machine.js.map +0 -1
- package/dist/org-fields.d.ts +0 -57
- package/dist/org-fields.d.ts.map +0 -1
- package/dist/org-fields.js +0 -167
- package/dist/org-fields.js.map +0 -1
- package/dist/posthog.d.ts +0 -38
- package/dist/posthog.d.ts.map +0 -1
- package/dist/posthog.js +0 -224
- package/dist/posthog.js.map +0 -1
- package/dist/project-fields.d.ts +0 -40
- package/dist/project-fields.d.ts.map +0 -1
- package/dist/project-fields.js +0 -79
- package/dist/project-fields.js.map +0 -1
- package/dist/prompt-consent.d.ts +0 -73
- package/dist/prompt-consent.d.ts.map +0 -1
- package/dist/prompt-consent.js +0 -191
- package/dist/prompt-consent.js.map +0 -1
- package/dist/prompt-stats.d.ts +0 -226
- package/dist/prompt-stats.d.ts.map +0 -1
- package/dist/prompt-stats.js +0 -561
- package/dist/prompt-stats.js.map +0 -1
- package/dist/referral.d.ts +0 -14
- package/dist/referral.d.ts.map +0 -1
- package/dist/referral.js +0 -28
- package/dist/referral.js.map +0 -1
- package/dist/registry.d.ts +0 -15
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -180
- package/dist/registry.js.map +0 -1
- package/dist/resolve-command.d.ts +0 -20
- package/dist/resolve-command.d.ts.map +0 -1
- package/dist/resolve-command.js +0 -21
- package/dist/resolve-command.js.map +0 -1
- package/dist/scanners/antigravity.d.ts +0 -41
- package/dist/scanners/antigravity.d.ts.map +0 -1
- package/dist/scanners/antigravity.js +0 -210
- package/dist/scanners/antigravity.js.map +0 -1
- package/dist/scanners/github-copilot-telemetry.d.ts +0 -23
- package/dist/scanners/github-copilot-telemetry.d.ts.map +0 -1
- package/dist/scanners/github-copilot-telemetry.js +0 -220
- package/dist/scanners/github-copilot-telemetry.js.map +0 -1
- package/dist/scanners/github-copilot.d.ts +0 -68
- package/dist/scanners/github-copilot.d.ts.map +0 -1
- package/dist/scanners/github-copilot.js +0 -262
- package/dist/scanners/github-copilot.js.map +0 -1
- package/dist/scanners/incremental.d.ts +0 -282
- package/dist/scanners/incremental.d.ts.map +0 -1
- package/dist/scanners/incremental.js +0 -788
- package/dist/scanners/incremental.js.map +0 -1
- package/dist/scanners/index.d.ts +0 -116
- package/dist/scanners/index.d.ts.map +0 -1
- package/dist/scanners/index.js +0 -726
- package/dist/scanners/index.js.map +0 -1
- package/dist/scanners/sqlite.d.ts +0 -18
- package/dist/scanners/sqlite.d.ts.map +0 -1
- package/dist/scanners/sqlite.js +0 -63
- package/dist/scanners/sqlite.js.map +0 -1
- package/dist/scanners/util.d.ts +0 -112
- package/dist/scanners/util.d.ts.map +0 -1
- package/dist/scanners/util.js +0 -167
- package/dist/scanners/util.js.map +0 -1
- package/dist/session.d.ts +0 -106
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -191
- package/dist/session.js.map +0 -1
- package/dist/share-card-colors.d.ts +0 -19
- package/dist/share-card-colors.d.ts.map +0 -1
- package/dist/share-card-colors.js +0 -36
- package/dist/share-card-colors.js.map +0 -1
- package/dist/share-card.d.ts +0 -32
- package/dist/share-card.d.ts.map +0 -1
- package/dist/share-card.js +0 -420
- package/dist/share-card.js.map +0 -1
- package/dist/share.d.ts +0 -17
- package/dist/share.d.ts.map +0 -1
- package/dist/share.js +0 -77
- package/dist/share.js.map +0 -1
- package/dist/sync.d.ts +0 -100
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -304
- package/dist/sync.js.map +0 -1
- package/dist/ui.d.ts +0 -33
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js +0 -127
- package/dist/ui.js.map +0 -1
- package/dist/utils/npmGlobal.d.ts +0 -30
- package/dist/utils/npmGlobal.d.ts.map +0 -1
- package/dist/utils/npmGlobal.js +0 -69
- package/dist/utils/npmGlobal.js.map +0 -1
- package/dist/utils/openBrowser.d.ts +0 -14
- package/dist/utils/openBrowser.d.ts.map +0 -1
- package/dist/utils/openBrowser.js +0 -47
- package/dist/utils/openBrowser.js.map +0 -1
- package/dist/utils/updateCheck.d.ts +0 -16
- package/dist/utils/updateCheck.d.ts.map +0 -1
- package/dist/utils/updateCheck.js +0 -111
- package/dist/utils/updateCheck.js.map +0 -1
- package/dist/utils/waitForEnter.d.ts +0 -12
- package/dist/utils/waitForEnter.d.ts.map +0 -1
- package/dist/utils/waitForEnter.js +0 -43
- package/dist/utils/waitForEnter.js.map +0 -1
package/dist/commands/essay.js
DELETED
|
@@ -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
|