hacklab 0.22.3 → 26.921.2
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 +39 -415
- package/dist/index.js +317 -23
- package/package.json +22 -40
- 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 -31
- package/dist/share-card.d.ts.map +0 -1
- package/dist/share-card.js +0 -380
- 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 -75
- 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/chat.js
DELETED
|
@@ -1,642 +0,0 @@
|
|
|
1
|
-
import readline from 'node:readline';
|
|
2
|
-
import { renderCompact } from '../card.js';
|
|
3
|
-
import { captureEvent } from '../posthog.js';
|
|
4
|
-
import { loadSession, resolveAppUrl } from '../session.js';
|
|
5
|
-
import { bold, dim, error, green, info, rankColor, red, stripControl, success, } from '../ui.js';
|
|
6
|
-
// Re-exported for existing importers (chat.test.ts); the implementation moved
|
|
7
|
-
// to ui.ts so any command can strip control chars from untrusted output.
|
|
8
|
-
export { stripControl } from '../ui.js';
|
|
9
|
-
// The chat command is CLI-first: every subcommand takes --json so an AI agent
|
|
10
|
-
// can drive the channel and DMs programmatically, while the default output is a
|
|
11
|
-
// human-readable terminal view. Thin wrappers over the /api/chat/* routes.
|
|
12
|
-
const CHANNEL_BASE = '/api/chat/channel/main';
|
|
13
|
-
function printJson(data) {
|
|
14
|
-
console.log(JSON.stringify(data, null, 2));
|
|
15
|
-
}
|
|
16
|
-
// The session-aware backend resolver (see resolveAppUrl for the precedence).
|
|
17
|
-
// Kept as a thin named export so the chat call sites and tests read clearly.
|
|
18
|
-
export function baseUrl(session) {
|
|
19
|
-
return resolveAppUrl(session);
|
|
20
|
-
}
|
|
21
|
-
async function requireSession() {
|
|
22
|
-
const session = await loadSession();
|
|
23
|
-
if (!session) {
|
|
24
|
-
error('not logged in');
|
|
25
|
-
info(`run ${dim('hacklab login')} first`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
return session;
|
|
29
|
-
}
|
|
30
|
-
async function readError(res) {
|
|
31
|
-
const data = (await res.json().catch(() => null));
|
|
32
|
-
return data?.error ?? `request failed (${res.status})`;
|
|
33
|
-
}
|
|
34
|
-
function flagValue(args, name) {
|
|
35
|
-
const i = args.indexOf(name);
|
|
36
|
-
if (i !== -1 && args[i + 1])
|
|
37
|
-
return args[i + 1];
|
|
38
|
-
const eq = args.find((a) => a.startsWith(`${name}=`));
|
|
39
|
-
return eq ? eq.slice(name.length + 1) : undefined;
|
|
40
|
-
}
|
|
41
|
-
// Universal, locale-independent labels — we never want American month/day
|
|
42
|
-
// ordering or localized names sneaking in. Lowercase to match the terminal's
|
|
43
|
-
// quiet aesthetic. Indexed by Date.getDay() (0 = Sunday) / Date.getMonth().
|
|
44
|
-
const WEEKDAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
|
45
|
-
const MONTHS = [
|
|
46
|
-
'jan',
|
|
47
|
-
'feb',
|
|
48
|
-
'mar',
|
|
49
|
-
'apr',
|
|
50
|
-
'may',
|
|
51
|
-
'jun',
|
|
52
|
-
'jul',
|
|
53
|
-
'aug',
|
|
54
|
-
'sep',
|
|
55
|
-
'oct',
|
|
56
|
-
'nov',
|
|
57
|
-
'dec',
|
|
58
|
-
];
|
|
59
|
-
function pad2(n) {
|
|
60
|
-
return String(n).padStart(2, '0');
|
|
61
|
-
}
|
|
62
|
-
// A calendar-day index in *local* time: the count of days since the epoch using
|
|
63
|
-
// the date's local Y/M/D. Computing it via Date.UTC on the local components
|
|
64
|
-
// strips the time-of-day entirely, so subtracting two of these gives an exact
|
|
65
|
-
// whole-day difference that is immune to DST (a local "day" can be 23 or 25
|
|
66
|
-
// hours, but we never look at hours) and to leap years (Date.UTC normalizes
|
|
67
|
-
// real calendar dates). This is the basis for "today vs yesterday vs N days
|
|
68
|
-
// ago" — never a raw millisecond/24h subtraction.
|
|
69
|
-
function localDayNumber(d) {
|
|
70
|
-
return Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / 86_400_000);
|
|
71
|
-
}
|
|
72
|
-
// The date prefix shown before a message's HH:MM, by how many *local calendar
|
|
73
|
-
// days* ago it was sent (relative to `now`):
|
|
74
|
-
// - today (or future/clock-skew): no prefix
|
|
75
|
-
// - 1 day ago: "yest"
|
|
76
|
-
// - 2–6 days ago: short weekday ("mon".."sun"). This range can never collide
|
|
77
|
-
// with today's weekday or with yesterday, so the day name is unambiguous.
|
|
78
|
-
// - 7+ days ago: a date. "the same weekday last week" is 7 days ago, so it
|
|
79
|
-
// lands here (a date, not a weekday) — exactly the ambiguity we want to
|
|
80
|
-
// avoid. This-year dates render "mmm-dd"; other years render "yyyy-mm-dd".
|
|
81
|
-
export function datePrefix(when, now) {
|
|
82
|
-
const diff = localDayNumber(now) - localDayNumber(when);
|
|
83
|
-
if (diff <= 0)
|
|
84
|
-
return '';
|
|
85
|
-
if (diff === 1)
|
|
86
|
-
return 'yest';
|
|
87
|
-
if (diff <= 6)
|
|
88
|
-
return WEEKDAYS[when.getDay()];
|
|
89
|
-
if (when.getFullYear() === now.getFullYear()) {
|
|
90
|
-
return `${MONTHS[when.getMonth()]}-${pad2(when.getDate())}`;
|
|
91
|
-
}
|
|
92
|
-
return `${when.getFullYear()}-${pad2(when.getMonth() + 1)}-${pad2(when.getDate())}`;
|
|
93
|
-
}
|
|
94
|
-
// A message timestamp: 24h local HH:MM, with a relative date prefix once the
|
|
95
|
-
// message is no longer from today. `now` is injectable so the live view can
|
|
96
|
-
// re-derive every prefix when the local day rolls over.
|
|
97
|
-
export function formatTimestamp(when, now = new Date()) {
|
|
98
|
-
const d = typeof when === 'string' ? new Date(when) : when;
|
|
99
|
-
const time = `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
100
|
-
const prefix = datePrefix(d, now);
|
|
101
|
-
return prefix ? `${prefix} ${time}` : time;
|
|
102
|
-
}
|
|
103
|
-
export function formatLine(m, now = new Date()) {
|
|
104
|
-
const lvl = m.senderLevel != null ? dim(` lv${m.senderLevel}`) : '';
|
|
105
|
-
// Colour the author's handle in their belt/rank colour (see rankColor).
|
|
106
|
-
// Strip control chars from the handle too, not just content: it's untrusted
|
|
107
|
-
// (user-registered) and gets wrapped in ANSI colour codes before printing, so
|
|
108
|
-
// an embedded escape sequence would otherwise hijack the viewer's terminal.
|
|
109
|
-
const handle = rankColor(m.senderLevel, stripControl(m.senderHandle));
|
|
110
|
-
return `${dim(formatTimestamp(m.createdAt, now))} ${handle}${lvl}: ${stripControl(m.content)}`;
|
|
111
|
-
}
|
|
112
|
-
// Optimistic "sending" line: the just-typed message rendered faded with a
|
|
113
|
-
// status tag, shown the instant you hit enter and rewritten in place once the
|
|
114
|
-
// server confirms it (or it fails). Handle/time are best-effort — we don't know
|
|
115
|
-
// our own rank level locally, so the confirmed line from the server is what
|
|
116
|
-
// applies the proper belt colour. `tag` is passed pre-styled by the caller
|
|
117
|
-
// (dim for sending, red for failed).
|
|
118
|
-
export function formatPendingLine(handle, content, tag, at = new Date().toISOString(), now = new Date()) {
|
|
119
|
-
const ts = formatTimestamp(at, now);
|
|
120
|
-
const base = `${ts} ${stripControl(handle)}: ${stripControl(content)}`;
|
|
121
|
-
return `${dim(base)} ${tag}`;
|
|
122
|
-
}
|
|
123
|
-
// API returns newest-first; print oldest-first like a chat log.
|
|
124
|
-
function renderMessages(messages) {
|
|
125
|
-
if (messages.length === 0) {
|
|
126
|
-
console.log(dim('(no messages yet)'));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
for (const m of [...messages].reverse()) {
|
|
130
|
-
console.log(formatLine(m));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
async function chatTail(json) {
|
|
134
|
-
// Public endpoint — no auth required (the whole point of the cached tail), but
|
|
135
|
-
// still resolve the backend the same way so --env and your logged-in env work.
|
|
136
|
-
const session = await loadSession();
|
|
137
|
-
const res = await fetch(`${baseUrl(session)}${CHANNEL_BASE}/tail`);
|
|
138
|
-
if (!res.ok) {
|
|
139
|
-
error(await readError(res));
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
const data = (await res.json());
|
|
143
|
-
if (json) {
|
|
144
|
-
printJson(data);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
renderMessages(data.messages);
|
|
148
|
-
}
|
|
149
|
-
async function chatPost(words, json) {
|
|
150
|
-
const content = words.join(' ').trim();
|
|
151
|
-
if (!content) {
|
|
152
|
-
error('usage: hacklab chat post <message>');
|
|
153
|
-
process.exit(1);
|
|
154
|
-
}
|
|
155
|
-
const session = await requireSession();
|
|
156
|
-
const res = await fetch(`${baseUrl(session)}${CHANNEL_BASE}`, {
|
|
157
|
-
method: 'POST',
|
|
158
|
-
headers: {
|
|
159
|
-
'Content-Type': 'application/json',
|
|
160
|
-
Authorization: `Bearer ${session.token}`,
|
|
161
|
-
},
|
|
162
|
-
body: JSON.stringify({ content }),
|
|
163
|
-
});
|
|
164
|
-
if (!res.ok) {
|
|
165
|
-
error(await readError(res));
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
const data = await res.json();
|
|
169
|
-
if (json) {
|
|
170
|
-
printJson(data);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
success('posted.');
|
|
174
|
-
await captureEvent(session.handle, 'cli_chat_message_posted', {
|
|
175
|
-
content_length: content.length,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
async function chatHistory(args, json) {
|
|
179
|
-
const before = flagValue(args, '--before');
|
|
180
|
-
const session = await requireSession();
|
|
181
|
-
const url = new URL(`${baseUrl(session)}${CHANNEL_BASE}/history`);
|
|
182
|
-
if (before)
|
|
183
|
-
url.searchParams.set('before', before);
|
|
184
|
-
const res = await fetch(url, {
|
|
185
|
-
headers: { Authorization: `Bearer ${session.token}` },
|
|
186
|
-
});
|
|
187
|
-
if (!res.ok) {
|
|
188
|
-
error(await readError(res));
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
const data = (await res.json());
|
|
192
|
-
if (json) {
|
|
193
|
-
printJson(data);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
renderMessages(data.messages);
|
|
197
|
-
if (data.nextCursor) {
|
|
198
|
-
console.log(dim(`\nolder → hacklab chat history --before ${data.nextCursor}`));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async function chatDms(json) {
|
|
202
|
-
const session = await requireSession();
|
|
203
|
-
const res = await fetch(`${baseUrl(session)}/api/chat/dms`, {
|
|
204
|
-
headers: { Authorization: `Bearer ${session.token}` },
|
|
205
|
-
});
|
|
206
|
-
if (!res.ok) {
|
|
207
|
-
error(await readError(res));
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
const data = (await res.json());
|
|
211
|
-
if (json) {
|
|
212
|
-
printJson(data);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (data.conversations.length === 0) {
|
|
216
|
-
console.log(dim('(no conversations yet)'));
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
for (const c of data.conversations) {
|
|
220
|
-
const unread = c.unread_count > 0 ? ` ${green(`(${c.unread_count} new)`)}` : '';
|
|
221
|
-
// Both the partner handle and the last-message preview are untrusted user
|
|
222
|
-
// input wrapped in ANSI before printing — strip control chars so a crafted
|
|
223
|
-
// handle or DM body can't inject escape sequences into the viewer's terminal.
|
|
224
|
-
const preview = c.last_message_content
|
|
225
|
-
? dim(` — ${stripControl(c.last_message_content)}`)
|
|
226
|
-
: '';
|
|
227
|
-
console.log(`${bold(`@${stripControl(c.partner_handle)}`)}${unread}${preview}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
async function chatDm(args, json) {
|
|
231
|
-
const handle = args[0];
|
|
232
|
-
if (!handle) {
|
|
233
|
-
error('usage: hacklab chat dm <handle> [message]');
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
const message = args.slice(1).join(' ').trim();
|
|
237
|
-
const session = await requireSession();
|
|
238
|
-
const base = `${baseUrl(session)}/api/chat/dm/${encodeURIComponent(handle)}`;
|
|
239
|
-
if (message) {
|
|
240
|
-
const res = await fetch(base, {
|
|
241
|
-
method: 'POST',
|
|
242
|
-
headers: {
|
|
243
|
-
'Content-Type': 'application/json',
|
|
244
|
-
Authorization: `Bearer ${session.token}`,
|
|
245
|
-
},
|
|
246
|
-
body: JSON.stringify({ content: message }),
|
|
247
|
-
});
|
|
248
|
-
if (!res.ok) {
|
|
249
|
-
error(await readError(res));
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
const data = await res.json();
|
|
253
|
-
if (json) {
|
|
254
|
-
printJson(data);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
success(`sent to @${handle}.`);
|
|
258
|
-
await captureEvent(session.handle, 'cli_dm_sent', {
|
|
259
|
-
content_length: message.length,
|
|
260
|
-
});
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
const res = await fetch(base, {
|
|
264
|
-
headers: { Authorization: `Bearer ${session.token}` },
|
|
265
|
-
});
|
|
266
|
-
if (!res.ok) {
|
|
267
|
-
error(await readError(res));
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
const data = (await res.json());
|
|
271
|
-
if (json) {
|
|
272
|
-
printJson(data);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
renderMessages(data.messages);
|
|
276
|
-
}
|
|
277
|
-
async function chatFlag(args, json) {
|
|
278
|
-
const targetId = args[0];
|
|
279
|
-
const reason = args[1];
|
|
280
|
-
if (!targetId || !reason) {
|
|
281
|
-
error('usage: hacklab chat flag <messageId> <off_topic|personal_attack|spam|other>');
|
|
282
|
-
process.exit(1);
|
|
283
|
-
}
|
|
284
|
-
const session = await requireSession();
|
|
285
|
-
const res = await fetch(`${baseUrl(session)}/api/chat/flag`, {
|
|
286
|
-
method: 'POST',
|
|
287
|
-
headers: {
|
|
288
|
-
'Content-Type': 'application/json',
|
|
289
|
-
Authorization: `Bearer ${session.token}`,
|
|
290
|
-
},
|
|
291
|
-
body: JSON.stringify({ targetId, reason }),
|
|
292
|
-
});
|
|
293
|
-
if (!res.ok) {
|
|
294
|
-
error(await readError(res));
|
|
295
|
-
process.exit(1);
|
|
296
|
-
}
|
|
297
|
-
const data = await res.json();
|
|
298
|
-
if (json) {
|
|
299
|
-
printJson(data);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
success('flagged for review.');
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Decide what a typed line means. A single leading slash is a local command and
|
|
306
|
-
* never posts. A DOUBLED leading slash is how you post a message that starts with
|
|
307
|
-
* a slash: it posts (and displays) verbatim, both slashes intact. Nothing is
|
|
308
|
-
* stripped, so the optimistic placeholder, the POST body, and the server echo all
|
|
309
|
-
* agree (reconcile-by-content) — and the server can reject single-leading-slash
|
|
310
|
-
* content (a stale client posting a command it doesn't know is local) without
|
|
311
|
-
* catching the doubled form. Pure + exported so these semantics are unit-tested
|
|
312
|
-
* without driving readline.
|
|
313
|
-
*/
|
|
314
|
-
export function parseLine(input) {
|
|
315
|
-
const content = input.trim();
|
|
316
|
-
if (!content)
|
|
317
|
-
return { kind: 'empty' };
|
|
318
|
-
if (content.startsWith('//'))
|
|
319
|
-
return { kind: 'message', content };
|
|
320
|
-
if (content.startsWith('/')) {
|
|
321
|
-
const [cmd, ...args] = content.slice(1).trim().split(/\s+/);
|
|
322
|
-
return { kind: 'command', cmd: cmd ?? '', args };
|
|
323
|
-
}
|
|
324
|
-
return { kind: 'message', content };
|
|
325
|
-
}
|
|
326
|
-
// Interactive live channel: short-poll the public tail and print new messages
|
|
327
|
-
// above a readline prompt; press enter to post. CLI-first design means this is a
|
|
328
|
-
// thin loop over the same endpoints the agent subcommands use.
|
|
329
|
-
async function chatLive() {
|
|
330
|
-
const session = await requireSession();
|
|
331
|
-
const base = baseUrl(session);
|
|
332
|
-
const seen = new Set();
|
|
333
|
-
let started = false;
|
|
334
|
-
// We learn our own handle from the first confirmed send (or the session) and
|
|
335
|
-
// use it to match incoming messages back to our optimistic placeholders.
|
|
336
|
-
let myHandle = session.handle;
|
|
337
|
-
// The local calendar day we last rendered against. When it changes (midnight,
|
|
338
|
-
// or waking from sleep across midnight), every message's relative date prefix
|
|
339
|
-
// may be stale, so we repaint the whole transcript.
|
|
340
|
-
let dayKey = localDayNumber(new Date());
|
|
341
|
-
const log = [];
|
|
342
|
-
const rl = readline.createInterface({
|
|
343
|
-
input: process.stdin,
|
|
344
|
-
output: process.stdout,
|
|
345
|
-
prompt: dim('> '),
|
|
346
|
-
});
|
|
347
|
-
function renderEntry(e, now) {
|
|
348
|
-
if (e.kind === 'msg')
|
|
349
|
-
return formatLine(e.m, now);
|
|
350
|
-
if (e.kind === 'text')
|
|
351
|
-
return e.text;
|
|
352
|
-
const tag = e.failed ? red(`[failed: ${e.failed}]`) : dim('[sending]');
|
|
353
|
-
return formatPendingLine(myHandle ?? 'you', e.content, tag, e.at, now);
|
|
354
|
-
}
|
|
355
|
-
// How many terminal rows above the prompt an entry sits (1 = directly above).
|
|
356
|
-
// Derived from its index in `log`: the last entry is one row up, and every
|
|
357
|
-
// later line pushes earlier ones further up. Assumes one row per entry, as the
|
|
358
|
-
// rest of this view does.
|
|
359
|
-
function rowsUpOf(e) {
|
|
360
|
-
const i = log.indexOf(e);
|
|
361
|
-
return i === -1 ? 0 : log.length - i;
|
|
362
|
-
}
|
|
363
|
-
// Append a new entry as a line above the prompt without clobbering whatever
|
|
364
|
-
// the user is mid-typing: clear the prompt line, write the entry, then redraw
|
|
365
|
-
// the prompt + the in-progress input buffer.
|
|
366
|
-
function appendEntry(e) {
|
|
367
|
-
log.push(e);
|
|
368
|
-
const inProgress = rl.line;
|
|
369
|
-
readline.cursorTo(process.stdout, 0);
|
|
370
|
-
readline.clearLine(process.stdout, 0);
|
|
371
|
-
process.stdout.write(`${renderEntry(e, new Date())}\n`);
|
|
372
|
-
process.stdout.write(rl.getPrompt() + inProgress);
|
|
373
|
-
}
|
|
374
|
-
// Append a local one-row line (slash-command output). Goes through appendEntry
|
|
375
|
-
// as a `text` entry, so it counts as exactly one log row.
|
|
376
|
-
function appendText(text) {
|
|
377
|
-
appendEntry({ kind: 'text', text });
|
|
378
|
-
}
|
|
379
|
-
const SLASH_HELP = [
|
|
380
|
-
dim('/who <handle> look up a hacker'),
|
|
381
|
-
dim('/help this list'),
|
|
382
|
-
dim('//<text> post a message that starts with a slash (shown as //)'),
|
|
383
|
-
dim('(dm someone with `hacklab chat dm <handle> <message>`)'),
|
|
384
|
-
];
|
|
385
|
-
// Dispatch a `/command`. Runs locally, never posts. Async commands (/who)
|
|
386
|
-
// append their rows when the fetch lands — the prompt was already restored by
|
|
387
|
-
// the caller, so the user keeps typing while the lookup is in flight.
|
|
388
|
-
async function handleSlash(cmd, args) {
|
|
389
|
-
if (cmd === 'who')
|
|
390
|
-
return whoLookup(args[0]);
|
|
391
|
-
if (cmd === 'help') {
|
|
392
|
-
for (const line of SLASH_HELP)
|
|
393
|
-
appendText(line);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
appendText(dim(`unknown command: /${stripControl(cmd)}. try /help`));
|
|
397
|
-
}
|
|
398
|
-
// `/who <handle>` — the compact card, appended one row per line. Prints
|
|
399
|
-
// nothing while the fetch is in flight (no placeholder to rewrite).
|
|
400
|
-
async function whoLookup(rawHandle) {
|
|
401
|
-
const handle = rawHandle?.replace(/^@/, '');
|
|
402
|
-
if (!handle) {
|
|
403
|
-
appendText(dim('usage: /who <handle>'));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const safe = stripControl(handle);
|
|
407
|
-
try {
|
|
408
|
-
const res = await fetch(`${base}/api/hackers/${encodeURIComponent(handle)}?src=chat`, { headers: { Authorization: `Bearer ${session.token}` } });
|
|
409
|
-
if (res.status === 404) {
|
|
410
|
-
appendText(dim(`no hacker named "${safe}"`));
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (!res.ok) {
|
|
414
|
-
appendText(dim(`could not look up ${safe} (${res.status})`));
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
const data = (await res.json());
|
|
418
|
-
if (!data.hacker) {
|
|
419
|
-
appendText(dim(`no hacker named "${safe}"`));
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
for (const line of renderCompact(data.hacker))
|
|
423
|
-
appendText(line);
|
|
424
|
-
}
|
|
425
|
-
catch {
|
|
426
|
-
appendText(dim('could not reach hacklab'));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
// Rewrite an entry's existing line in place (it must still be on screen), then
|
|
430
|
-
// restore the prompt + in-progress input. Used to swap an optimistic
|
|
431
|
-
// placeholder for the confirmed (or failed) message with no second line.
|
|
432
|
-
function rewriteEntry(e) {
|
|
433
|
-
const rowsUp = rowsUpOf(e);
|
|
434
|
-
if (rowsUp < 1)
|
|
435
|
-
return;
|
|
436
|
-
const inProgress = rl.line;
|
|
437
|
-
readline.moveCursor(process.stdout, 0, -rowsUp);
|
|
438
|
-
readline.cursorTo(process.stdout, 0);
|
|
439
|
-
readline.clearLine(process.stdout, 0);
|
|
440
|
-
process.stdout.write(renderEntry(e, new Date()));
|
|
441
|
-
readline.cursorTo(process.stdout, 0);
|
|
442
|
-
readline.moveCursor(process.stdout, 0, rowsUp);
|
|
443
|
-
readline.clearLine(process.stdout, 0);
|
|
444
|
-
process.stdout.write(rl.getPrompt() + inProgress);
|
|
445
|
-
}
|
|
446
|
-
// Repaint the entire transcript with fresh date prefixes. Clears the screen
|
|
447
|
-
// *and* scrollback (\x1b[3J) so the rolled-over timestamps don't leave a stale
|
|
448
|
-
// duplicate above. Rare — at most once per local-day change.
|
|
449
|
-
function repaint() {
|
|
450
|
-
const now = new Date();
|
|
451
|
-
const inProgress = rl.line;
|
|
452
|
-
process.stdout.write('\x1b[3J\x1b[2J\x1b[H');
|
|
453
|
-
for (const e of log)
|
|
454
|
-
process.stdout.write(`${renderEntry(e, now)}\n`);
|
|
455
|
-
process.stdout.write(rl.getPrompt() + inProgress);
|
|
456
|
-
}
|
|
457
|
-
// Render a confirmed message exactly once. If it matches one of our pending
|
|
458
|
-
// optimistic sends (same author + content), swap that placeholder in place;
|
|
459
|
-
// otherwise append it. Dedupes whether the message arrives via the POST
|
|
460
|
-
// response or the poll, whichever wins the race.
|
|
461
|
-
function commitMessage(m) {
|
|
462
|
-
if (seen.has(m.id))
|
|
463
|
-
return;
|
|
464
|
-
seen.add(m.id);
|
|
465
|
-
const idx = log.findIndex((e) => e.kind === 'pending' &&
|
|
466
|
-
e.content === m.content &&
|
|
467
|
-
(myHandle ? m.senderHandle === myHandle : true));
|
|
468
|
-
if (idx !== -1) {
|
|
469
|
-
log[idx] = { kind: 'msg', m };
|
|
470
|
-
rewriteEntry(log[idx]);
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
appendEntry({ kind: 'msg', m });
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// A send failed: flip its placeholder to a red [failed] tag instead of leaving
|
|
477
|
-
// it stuck on [sending]. No-op if the poll already confirmed it (the
|
|
478
|
-
// placeholder object is no longer in `log`).
|
|
479
|
-
function failPending(placeholder, reason) {
|
|
480
|
-
if (placeholder.kind !== 'pending' || placeholder.failed)
|
|
481
|
-
return;
|
|
482
|
-
if (!log.includes(placeholder))
|
|
483
|
-
return;
|
|
484
|
-
placeholder.failed = reason;
|
|
485
|
-
rewriteEntry(placeholder);
|
|
486
|
-
}
|
|
487
|
-
async function poll() {
|
|
488
|
-
// Day-rollover check runs every tick, independent of the network, so the
|
|
489
|
-
// relative prefixes advance even while the fetch is failing.
|
|
490
|
-
const k = localDayNumber(new Date());
|
|
491
|
-
if (started && k !== dayKey) {
|
|
492
|
-
dayKey = k;
|
|
493
|
-
repaint();
|
|
494
|
-
}
|
|
495
|
-
try {
|
|
496
|
-
const res = await fetch(`${base}${CHANNEL_BASE}/tail`);
|
|
497
|
-
if (!res.ok)
|
|
498
|
-
return;
|
|
499
|
-
const data = (await res.json());
|
|
500
|
-
const ordered = [...data.messages].reverse();
|
|
501
|
-
if (!started) {
|
|
502
|
-
const now = new Date();
|
|
503
|
-
for (const m of ordered) {
|
|
504
|
-
seen.add(m.id);
|
|
505
|
-
const e = { kind: 'msg', m };
|
|
506
|
-
log.push(e);
|
|
507
|
-
console.log(renderEntry(e, now));
|
|
508
|
-
}
|
|
509
|
-
started = true;
|
|
510
|
-
rl.prompt();
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
for (const m of ordered)
|
|
514
|
-
commitMessage(m);
|
|
515
|
-
}
|
|
516
|
-
catch {
|
|
517
|
-
// Transient network error — keep polling silently.
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
console.log(dim('hacklab channel — live. type a message + enter to post, ctrl-c to quit.'));
|
|
521
|
-
await poll();
|
|
522
|
-
const timer = setInterval(poll, 1500);
|
|
523
|
-
rl.on('line', async (input) => {
|
|
524
|
-
const intent = parseLine(input);
|
|
525
|
-
if (intent.kind === 'empty') {
|
|
526
|
-
rl.prompt();
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
// Replace readline's echo of the typed line ("> message") with a faded
|
|
530
|
-
// optimistic line. Without this the echo plus the confirmed message read as
|
|
531
|
-
// a duplicate. clearScreenDown also handles a long line that wrapped.
|
|
532
|
-
const PROMPT_WIDTH = 2; // visible width of "> "
|
|
533
|
-
const cols = process.stdout.columns ?? 80;
|
|
534
|
-
const echoRows = Math.max(1, Math.ceil((PROMPT_WIDTH + input.length) / cols));
|
|
535
|
-
readline.moveCursor(process.stdout, 0, -echoRows);
|
|
536
|
-
readline.cursorTo(process.stdout, 0);
|
|
537
|
-
readline.clearScreenDown(process.stdout);
|
|
538
|
-
// A local command never posts. Restore the prompt first, then let the
|
|
539
|
-
// (possibly async) command append its rows via appendEntry — one row per
|
|
540
|
-
// entry, so rowsUpOf stays correct even mid optimistic-send.
|
|
541
|
-
if (intent.kind === 'command') {
|
|
542
|
-
rl.prompt();
|
|
543
|
-
void handleSlash(intent.cmd, intent.args);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
// parseLine already stripped the escaping slash from a `//` message, so the
|
|
547
|
-
// placeholder and the POST body agree — the poll reconciles optimistic sends
|
|
548
|
-
// by exact content match, and a mismatch sticks the placeholder on [sending].
|
|
549
|
-
const postContent = intent.content;
|
|
550
|
-
// Faded placeholder, one row above the freshly drawn prompt. Written inline
|
|
551
|
-
// (not via appendEntry) so we can hand control back with rl.prompt(), which
|
|
552
|
-
// keeps readline's cursor model correct for the next keystroke.
|
|
553
|
-
const placeholder = {
|
|
554
|
-
kind: 'pending',
|
|
555
|
-
content: postContent,
|
|
556
|
-
at: new Date().toISOString(),
|
|
557
|
-
};
|
|
558
|
-
log.push(placeholder);
|
|
559
|
-
process.stdout.write(`${renderEntry(placeholder, new Date())}\n`);
|
|
560
|
-
rl.prompt();
|
|
561
|
-
try {
|
|
562
|
-
const res = await fetch(`${base}${CHANNEL_BASE}`, {
|
|
563
|
-
method: 'POST',
|
|
564
|
-
headers: {
|
|
565
|
-
'Content-Type': 'application/json',
|
|
566
|
-
Authorization: `Bearer ${session.token}`,
|
|
567
|
-
},
|
|
568
|
-
body: JSON.stringify({ content: postContent }),
|
|
569
|
-
});
|
|
570
|
-
if (!res.ok) {
|
|
571
|
-
failPending(placeholder, await readError(res));
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const data = (await res.json());
|
|
575
|
-
const m = data.message;
|
|
576
|
-
if (m?.id) {
|
|
577
|
-
myHandle ??= m.senderHandle;
|
|
578
|
-
commitMessage(m);
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
failPending(placeholder, 'no message returned');
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
catch {
|
|
585
|
-
failPending(placeholder, 'network error');
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
rl.on('SIGINT', () => {
|
|
589
|
-
clearInterval(timer);
|
|
590
|
-
rl.close();
|
|
591
|
-
process.stdout.write('\n');
|
|
592
|
-
process.exit(0);
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
function printChatHelp() {
|
|
596
|
-
console.log(bold('hacklab chat'));
|
|
597
|
-
console.log('');
|
|
598
|
-
console.log(` ${dim('(no args)')} open the live channel (type to post)`);
|
|
599
|
-
console.log(` ${dim('live')} open the live channel (same as no args)`);
|
|
600
|
-
console.log(` ${dim('tail')} read the latest channel messages`);
|
|
601
|
-
console.log(` ${dim('post <message>')} post to the channel`);
|
|
602
|
-
console.log(` ${dim('history [--before C]')} older channel messages`);
|
|
603
|
-
console.log(` ${dim('dms')} list your direct-message threads`);
|
|
604
|
-
console.log(` ${dim('dm <handle> [msg]')} read a DM thread, or send if msg given`);
|
|
605
|
-
console.log(` ${dim('flag <id> <reason>')} report a message to moderation`);
|
|
606
|
-
console.log('');
|
|
607
|
-
console.log(` ${dim('--json')} machine-readable output (non-interactive subcommands)`);
|
|
608
|
-
}
|
|
609
|
-
export async function chat(args) {
|
|
610
|
-
const json = args.includes('--json');
|
|
611
|
-
const rest = args.filter((a) => a !== '--json');
|
|
612
|
-
const sub = rest[0];
|
|
613
|
-
const subArgs = rest.slice(1);
|
|
614
|
-
// Explicit help only — a bare `hacklab chat` is the human entry point and
|
|
615
|
-
// drops you straight into the live channel. The tail/post/history/dms/dm/flag
|
|
616
|
-
// subcommands stay for scripting and agents (pair them with --json).
|
|
617
|
-
if (sub === 'help' || sub === '--help' || sub === '-h') {
|
|
618
|
-
printChatHelp();
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
switch (sub) {
|
|
622
|
-
case undefined:
|
|
623
|
-
case 'live':
|
|
624
|
-
return chatLive();
|
|
625
|
-
case 'tail':
|
|
626
|
-
return chatTail(json);
|
|
627
|
-
case 'post':
|
|
628
|
-
return chatPost(subArgs, json);
|
|
629
|
-
case 'history':
|
|
630
|
-
return chatHistory(subArgs, json);
|
|
631
|
-
case 'dms':
|
|
632
|
-
return chatDms(json);
|
|
633
|
-
case 'dm':
|
|
634
|
-
return chatDm(subArgs, json);
|
|
635
|
-
case 'flag':
|
|
636
|
-
return chatFlag(subArgs, json);
|
|
637
|
-
default:
|
|
638
|
-
printChatHelp();
|
|
639
|
-
process.exit(1);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
//# sourceMappingURL=chat.js.map
|