@trygocode/notify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +237 -0
- package/assets/README.md +16 -0
- package/assets/icon.svg +8 -0
- package/dist/src/agent.js +33 -0
- package/dist/src/claude.js +287 -0
- package/dist/src/cli.js +444 -0
- package/dist/src/commit_message.js +321 -0
- package/dist/src/config.js +348 -0
- package/dist/src/creds.js +158 -0
- package/dist/src/cursor.js +273 -0
- package/dist/src/detect.js +109 -0
- package/dist/src/login.js +152 -0
- package/dist/src/mcp.js +215 -0
- package/dist/src/outbox.js +150 -0
- package/dist/src/push.js +278 -0
- package/dist/src/repo_key.js +98 -0
- package/dist/src/rule-content.js +71 -0
- package/dist/src/send.js +141 -0
- package/dist/src/settings.js +213 -0
- package/dist/src/setup.js +148 -0
- package/dist/src/status.js +150 -0
- package/dist/src/uninstall.js +39 -0
- package/dist/src/version.js +2 -0
- package/package.json +61 -0
- package/snippets/ralph-homer.sh +21 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gocode-notify CLI entrypoint.
|
|
3
|
+
//
|
|
4
|
+
// This is the SCAFFOLD only (Milestone A, task 1). Individual subcommands
|
|
5
|
+
// (login / send / test / setup / status / mcp / uninstall) are implemented in
|
|
6
|
+
// later milestones. For now the entrypoint resolves help/version and reports
|
|
7
|
+
// not-yet-implemented for the known commands so the bin is wired and testable.
|
|
8
|
+
import { realpathSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { VERSION } from "./version.js";
|
|
11
|
+
import { login } from "./login.js";
|
|
12
|
+
import { resolveServerUrl } from "./creds.js";
|
|
13
|
+
import { send, isNotifyKind, NOTIFY_KINDS, } from "./send.js";
|
|
14
|
+
import { enqueue, flush } from "./outbox.js";
|
|
15
|
+
import { gatherStatus, formatStatus, formatStatusSteps } from "./status.js";
|
|
16
|
+
import { isAgentDriven, stdoutSink } from "./agent.js";
|
|
17
|
+
import { serveStdio } from "./mcp.js";
|
|
18
|
+
import { setup } from "./setup.js";
|
|
19
|
+
import { uninstall } from "./uninstall.js";
|
|
20
|
+
import { cmdConfig } from "./config.js";
|
|
21
|
+
/** Subcommands the finished CLI will expose (see PRD §4.1). */
|
|
22
|
+
export const COMMANDS = [
|
|
23
|
+
"login",
|
|
24
|
+
"send",
|
|
25
|
+
"test",
|
|
26
|
+
"setup",
|
|
27
|
+
"status",
|
|
28
|
+
"mcp",
|
|
29
|
+
"uninstall",
|
|
30
|
+
"config",
|
|
31
|
+
];
|
|
32
|
+
export function printHelp() {
|
|
33
|
+
console.log([
|
|
34
|
+
`gocode-notify v${VERSION}`,
|
|
35
|
+
"",
|
|
36
|
+
"Free phone notifications for any coding agent via the GoCode app.",
|
|
37
|
+
"",
|
|
38
|
+
"Usage: gocode-notify <command> [options]",
|
|
39
|
+
"",
|
|
40
|
+
"Commands:",
|
|
41
|
+
" login Pair this machine with your GoCode account",
|
|
42
|
+
" send Send a single push notification",
|
|
43
|
+
" test Send a canned test push",
|
|
44
|
+
" setup Pair + detect runtimes + write configs",
|
|
45
|
+
" status Report credentials / server / detected runtimes",
|
|
46
|
+
" mcp Run as an MCP server over stdio",
|
|
47
|
+
" uninstall Remove entries this tool added",
|
|
48
|
+
" config Get/set Notify settings (get | set <key> <value> | pull)",
|
|
49
|
+
"",
|
|
50
|
+
" -h, --help Show this help",
|
|
51
|
+
" -v, --version Print the version",
|
|
52
|
+
" --agent-driven Emit one JSON line per step (machine-readable)",
|
|
53
|
+
].join("\n"));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse `--flag value` / `--flag=value` pairs from an argv slice. A flag with no
|
|
57
|
+
* following value (end of argv or another `--flag` next) becomes a boolean
|
|
58
|
+
* `true`. Bare positionals are ignored — the CLI is flag-driven. Used by
|
|
59
|
+
* subcommand handlers; kept tiny to honour the zero-dep rule (no arg library).
|
|
60
|
+
*/
|
|
61
|
+
export function parseFlags(argv) {
|
|
62
|
+
const flags = new Map();
|
|
63
|
+
for (let i = 0; i < argv.length; i++) {
|
|
64
|
+
const arg = argv[i];
|
|
65
|
+
if (!arg.startsWith("--"))
|
|
66
|
+
continue;
|
|
67
|
+
const eq = arg.indexOf("=");
|
|
68
|
+
if (eq !== -1) {
|
|
69
|
+
flags.set(arg.slice(2, eq), arg.slice(eq + 1));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const key = arg.slice(2);
|
|
73
|
+
const next = argv[i + 1];
|
|
74
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
75
|
+
flags.set(key, next);
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
flags.set(key, true);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return flags;
|
|
83
|
+
}
|
|
84
|
+
/** Read a flag as a string, or undefined when absent / passed as a bare boolean. */
|
|
85
|
+
function flagString(flags, name) {
|
|
86
|
+
const v = flags.get(name);
|
|
87
|
+
return typeof v === "string" ? v : undefined;
|
|
88
|
+
}
|
|
89
|
+
/** Handle `gocode-notify login [--code N] [--label "..."] [--server URL] [--agent-driven]`. */
|
|
90
|
+
export async function cmdLogin(args, deps = {}) {
|
|
91
|
+
const flags = parseFlags(args);
|
|
92
|
+
const agent = isAgentDriven(flags);
|
|
93
|
+
const sink = deps.sink ?? stdoutSink;
|
|
94
|
+
// In agent-driven mode there is no human at the terminal: an absent --code is
|
|
95
|
+
// an error, not a cue to prompt. Inject a prompt that fails fast so login()
|
|
96
|
+
// returns a clean ok:false step instead of blocking on stdin (PRD §2.4).
|
|
97
|
+
const promptCode = deps.promptCode ??
|
|
98
|
+
(agent
|
|
99
|
+
? async () => {
|
|
100
|
+
throw new Error("--agent-driven requires --code (no interactive prompt)");
|
|
101
|
+
}
|
|
102
|
+
: undefined);
|
|
103
|
+
const result = await login({
|
|
104
|
+
code: flagString(flags, "code"),
|
|
105
|
+
label: flagString(flags, "label"),
|
|
106
|
+
server: flagString(flags, "server"),
|
|
107
|
+
home: deps.home,
|
|
108
|
+
fetchImpl: deps.fetchImpl,
|
|
109
|
+
timeoutMs: deps.timeoutMs,
|
|
110
|
+
promptCode,
|
|
111
|
+
});
|
|
112
|
+
if (result.ok && result.credentials) {
|
|
113
|
+
if (agent) {
|
|
114
|
+
sink({
|
|
115
|
+
step: "login",
|
|
116
|
+
ok: true,
|
|
117
|
+
detail: `paired as ${result.credentials.user_id} (${result.credentials.label})`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.log(`✓ Paired as ${result.credentials.user_id} (${result.credentials.label}).`);
|
|
122
|
+
console.log(" Credentials saved to ~/.gocode/credentials");
|
|
123
|
+
}
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
const error = result.error ?? "pairing failed";
|
|
127
|
+
if (agent) {
|
|
128
|
+
sink({ step: "login", ok: false, detail: error });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.error(`gocode-notify login: ${error}`);
|
|
132
|
+
}
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Handle `gocode-notify send --kind K [--title T] [--body B] [--source S]
|
|
137
|
+
* [--project P] [--dedupe-key D] [--server URL]`.
|
|
138
|
+
*
|
|
139
|
+
* Validates `--kind` against the canonical enum (a usage error → non-zero exit,
|
|
140
|
+
* since it is caught before any network and signals a misconfigured hook). Every
|
|
141
|
+
* VALID send then follows the PRD §4.4 non-blocking contract: it ALWAYS exits 0,
|
|
142
|
+
* even on network/server failure, so a hook never blocks the agent's turn. On a
|
|
143
|
+
* delivery failure the payload is queued to the offline outbox; on a successful
|
|
144
|
+
* send the backlog is flushed opportunistically (server is known reachable).
|
|
145
|
+
*/
|
|
146
|
+
export async function cmdSend(args, deps = {}) {
|
|
147
|
+
const flags = parseFlags(args);
|
|
148
|
+
const agent = isAgentDriven(flags);
|
|
149
|
+
const sink = deps.sink ?? stdoutSink;
|
|
150
|
+
const kind = flagString(flags, "kind");
|
|
151
|
+
if (kind === undefined) {
|
|
152
|
+
const detail = `--kind is required (one of: ${NOTIFY_KINDS.join(", ")})`;
|
|
153
|
+
if (agent)
|
|
154
|
+
sink({ step: "validate", ok: false, detail });
|
|
155
|
+
else
|
|
156
|
+
console.error(`gocode-notify send: ${detail}`);
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
if (!isNotifyKind(kind)) {
|
|
160
|
+
const detail = `invalid --kind "${kind}" (expected one of: ${NOTIFY_KINDS.join(", ")})`;
|
|
161
|
+
if (agent)
|
|
162
|
+
sink({ step: "validate", ok: false, detail });
|
|
163
|
+
else
|
|
164
|
+
console.error(`gocode-notify send: ${detail}`);
|
|
165
|
+
return 2;
|
|
166
|
+
}
|
|
167
|
+
const payload = { kind };
|
|
168
|
+
const title = flagString(flags, "title");
|
|
169
|
+
if (title)
|
|
170
|
+
payload.title = title;
|
|
171
|
+
const body = flagString(flags, "body");
|
|
172
|
+
if (body)
|
|
173
|
+
payload.body = body;
|
|
174
|
+
const source = flagString(flags, "source");
|
|
175
|
+
if (source)
|
|
176
|
+
payload.source = source;
|
|
177
|
+
const project = flagString(flags, "project");
|
|
178
|
+
if (project)
|
|
179
|
+
payload.project = project;
|
|
180
|
+
const dedupeKey = flagString(flags, "dedupe-key");
|
|
181
|
+
if (dedupeKey)
|
|
182
|
+
payload.dedupe_key = dedupeKey;
|
|
183
|
+
const server = await resolveServerUrl(flagString(flags, "server"), deps);
|
|
184
|
+
const sendOpts = {
|
|
185
|
+
home: deps.home,
|
|
186
|
+
fetchImpl: deps.fetchImpl,
|
|
187
|
+
timeoutMs: deps.timeoutMs,
|
|
188
|
+
timestamp: deps.timestamp,
|
|
189
|
+
server,
|
|
190
|
+
};
|
|
191
|
+
const result = await send(payload, sendOpts);
|
|
192
|
+
if (result.ok) {
|
|
193
|
+
// Server is reachable → drain any notifications queued while offline.
|
|
194
|
+
await flush((p) => send(p, sendOpts), deps);
|
|
195
|
+
const where = typeof result.sent === "number"
|
|
196
|
+
? ` (delivered to ${result.sent} device${result.sent === 1 ? "" : "s"})`
|
|
197
|
+
: "";
|
|
198
|
+
if (agent)
|
|
199
|
+
sink({ step: "send", ok: true, detail: `sent ${payload.kind}${where}` });
|
|
200
|
+
else
|
|
201
|
+
console.log(`✓ Sent ${payload.kind}${where}.`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Offline / failed → queue for the next send. Still exit 0 (non-blocking).
|
|
205
|
+
await enqueue(payload, deps);
|
|
206
|
+
const detail = `${payload.kind} not delivered (${result.error}); queued for retry`;
|
|
207
|
+
if (agent)
|
|
208
|
+
sink({ step: "send", ok: false, detail });
|
|
209
|
+
else
|
|
210
|
+
console.error(`gocode-notify send: ${detail}.`);
|
|
211
|
+
}
|
|
212
|
+
// PRD §4.4: a failed notification must NEVER block a hook — always exit 0.
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
/** The canned payload `gocode-notify test` sends (PRD §4.1). */
|
|
216
|
+
export const TEST_PAYLOAD = {
|
|
217
|
+
kind: "finished",
|
|
218
|
+
title: "GoCode test",
|
|
219
|
+
body: "If you can read this on your phone, gocode-notify is working.",
|
|
220
|
+
source: "manual",
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Handle `gocode-notify test [--server URL]`. Sends the canned {@link TEST_PAYLOAD}
|
|
224
|
+
* push so a user (or an agent installer, per PRD §2 Path 2) can verify the phone
|
|
225
|
+
* receives it post-install.
|
|
226
|
+
*
|
|
227
|
+
* Unlike {@link cmdSend} — which is wired into fire-and-forget hooks and so MUST
|
|
228
|
+
* always exit 0 — `test` is an interactive diagnostic. It reports the outcome and
|
|
229
|
+
* exits NON-zero on failure so the human/agent running it sees that the push did
|
|
230
|
+
* not reach the server. It does not queue to the outbox (a stale "GoCode test"
|
|
231
|
+
* surfacing later would be confusing); it is a clean one-shot probe.
|
|
232
|
+
*/
|
|
233
|
+
export async function cmdTest(args, deps = {}) {
|
|
234
|
+
const flags = parseFlags(args);
|
|
235
|
+
const agent = isAgentDriven(flags);
|
|
236
|
+
const sink = deps.sink ?? stdoutSink;
|
|
237
|
+
const server = await resolveServerUrl(flagString(flags, "server"), deps);
|
|
238
|
+
const sendOpts = {
|
|
239
|
+
home: deps.home,
|
|
240
|
+
fetchImpl: deps.fetchImpl,
|
|
241
|
+
timeoutMs: deps.timeoutMs,
|
|
242
|
+
timestamp: deps.timestamp,
|
|
243
|
+
server,
|
|
244
|
+
};
|
|
245
|
+
const result = await send(TEST_PAYLOAD, sendOpts);
|
|
246
|
+
if (result.ok) {
|
|
247
|
+
const where = typeof result.sent === "number"
|
|
248
|
+
? ` (delivered to ${result.sent} device${result.sent === 1 ? "" : "s"})`
|
|
249
|
+
: "";
|
|
250
|
+
if (agent)
|
|
251
|
+
sink({ step: "test", ok: true, detail: `test push sent${where}` });
|
|
252
|
+
else
|
|
253
|
+
console.log(`✓ Test push sent${where}. Check your phone for the GoCode notification.`);
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
const detail = `test push not delivered (${result.error})`;
|
|
257
|
+
if (agent)
|
|
258
|
+
sink({ step: "test", ok: false, detail });
|
|
259
|
+
else
|
|
260
|
+
console.error(`gocode-notify test: ${detail}.`);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Handle `gocode-notify status [--server URL]`. Prints a coherent report of
|
|
265
|
+
* credentials / server reachability / detected runtimes (PRD §4.1, §8). Purely
|
|
266
|
+
* informational — always exits 0, even when nothing is paired or reachable.
|
|
267
|
+
*/
|
|
268
|
+
export async function cmdStatus(args, deps = {}) {
|
|
269
|
+
const flags = parseFlags(args);
|
|
270
|
+
const agent = isAgentDriven(flags);
|
|
271
|
+
const sink = deps.sink ?? stdoutSink;
|
|
272
|
+
const report = await gatherStatus({
|
|
273
|
+
home: deps.home,
|
|
274
|
+
serverFlag: flagString(flags, "server"),
|
|
275
|
+
fetchImpl: deps.fetchImpl,
|
|
276
|
+
timeoutMs: deps.timeoutMs,
|
|
277
|
+
});
|
|
278
|
+
if (agent) {
|
|
279
|
+
for (const step of formatStatusSteps(report))
|
|
280
|
+
sink(step);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
for (const line of formatStatus(report))
|
|
284
|
+
console.log(line);
|
|
285
|
+
}
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
/** True when a flag is present as a bare boolean or explicit `=true`. */
|
|
289
|
+
function flagBool(flags, name) {
|
|
290
|
+
const v = flags.get(name);
|
|
291
|
+
return v === true || v === "true";
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Handle `gocode-notify setup [--pair-code N] [--label "..."] [--server URL]
|
|
295
|
+
* [--force] [--agent-driven]`. Runs the installer orchestration (PRD §5): pair →
|
|
296
|
+
* detect runtimes → write configs → summary. Idempotent; `--force` re-pairs.
|
|
297
|
+
*
|
|
298
|
+
* In `--agent-driven` mode each step is emitted as one JSON line and an absent
|
|
299
|
+
* `--pair-code` is an error (no interactive prompt — there is no human at the
|
|
300
|
+
* terminal), mirroring `cmdLogin`. Exit 0 when setup succeeded, else 1.
|
|
301
|
+
*/
|
|
302
|
+
export async function cmdSetup(args, deps = {}) {
|
|
303
|
+
const flags = parseFlags(args);
|
|
304
|
+
const agent = isAgentDriven(flags);
|
|
305
|
+
const sink = deps.sink ?? stdoutSink;
|
|
306
|
+
// No human at the terminal in agent-driven mode: an absent --pair-code is an
|
|
307
|
+
// error, not a cue to prompt. Inject a prompt that fails fast (same contract
|
|
308
|
+
// as cmdLogin) so the pair step returns a clean ok:false instead of hanging.
|
|
309
|
+
const promptCode = deps.promptCode ??
|
|
310
|
+
(agent
|
|
311
|
+
? async () => {
|
|
312
|
+
throw new Error("--agent-driven requires --pair-code (no interactive prompt)");
|
|
313
|
+
}
|
|
314
|
+
: undefined);
|
|
315
|
+
const result = await setup({
|
|
316
|
+
pairCode: flagString(flags, "pair-code"),
|
|
317
|
+
label: flagString(flags, "label"),
|
|
318
|
+
server: flagString(flags, "server"),
|
|
319
|
+
force: flagBool(flags, "force"),
|
|
320
|
+
home: deps.home,
|
|
321
|
+
fetchImpl: deps.fetchImpl,
|
|
322
|
+
timeoutMs: deps.timeoutMs,
|
|
323
|
+
promptCode,
|
|
324
|
+
pairImpl: deps.pairImpl,
|
|
325
|
+
detectImpl: deps.detectImpl,
|
|
326
|
+
writeConfig: deps.writeConfig,
|
|
327
|
+
});
|
|
328
|
+
if (agent) {
|
|
329
|
+
for (const step of result.steps)
|
|
330
|
+
sink(step);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
console.log("gocode-notify setup");
|
|
334
|
+
console.log("");
|
|
335
|
+
for (const step of result.steps) {
|
|
336
|
+
console.log(` ${step.ok ? "✓" : "✗"} ${step.step}: ${step.detail}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return result.ok ? 0 : 1;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Handle `gocode-notify uninstall [--agent-driven]`. Removes EXACTLY the entries
|
|
343
|
+
* this tool added to each runtime — our hooks, MCP entry, and rule/skill — while
|
|
344
|
+
* preserving the user's own config (PRD §4.1, §11). Idempotent and never
|
|
345
|
+
* destructive of credentials. In `--agent-driven` mode each step is emitted as
|
|
346
|
+
* one JSON line. Exit 0 when every runtime uninstall succeeded (or was a clean
|
|
347
|
+
* no-op), else 1.
|
|
348
|
+
*/
|
|
349
|
+
export async function cmdUninstall(args, deps = {}) {
|
|
350
|
+
const flags = parseFlags(args);
|
|
351
|
+
const agent = isAgentDriven(flags);
|
|
352
|
+
const sink = deps.sink ?? stdoutSink;
|
|
353
|
+
const result = await uninstall({ home: deps.home, uninstallers: deps.uninstallers });
|
|
354
|
+
if (agent) {
|
|
355
|
+
for (const step of result.steps)
|
|
356
|
+
sink(step);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
console.log("gocode-notify uninstall");
|
|
360
|
+
console.log("");
|
|
361
|
+
for (const step of result.steps) {
|
|
362
|
+
console.log(` ${step.ok ? "✓" : "✗"} ${step.step}: ${step.detail}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return result.ok ? 0 : 1;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Handle `gocode-notify mcp [--server URL]`. Runs the stdio MCP server (PRD
|
|
369
|
+
* §4.3) until the client disconnects. Resolves to 0 once the connection closes
|
|
370
|
+
* cleanly. Note: this is NOT `--agent-driven`-aware — MCP speaks JSON-RPC on
|
|
371
|
+
* stdout, so the stream must be left untouched for the protocol.
|
|
372
|
+
*/
|
|
373
|
+
export async function cmdMcp(args, deps = {}) {
|
|
374
|
+
const flags = parseFlags(args);
|
|
375
|
+
await serveStdio({ ...deps, serverFlag: flagString(flags, "server") });
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
/** Synchronous dispatcher: help/version/unknown + not-yet-implemented commands. */
|
|
379
|
+
export function run(argv) {
|
|
380
|
+
const cmd = argv[0];
|
|
381
|
+
if (cmd === undefined || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
382
|
+
printHelp();
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
if (cmd === "version" || cmd === "--version" || cmd === "-v") {
|
|
386
|
+
console.log(VERSION);
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
if (COMMANDS.includes(cmd)) {
|
|
390
|
+
console.error(`gocode-notify: command "${cmd}" is not implemented yet.`);
|
|
391
|
+
return 1;
|
|
392
|
+
}
|
|
393
|
+
console.error(`gocode-notify: unknown command "${cmd}". Run "gocode-notify --help".`);
|
|
394
|
+
return 2;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Async dispatcher used by the bin entrypoint. Routes commands that perform I/O
|
|
398
|
+
* (currently `login`) to their async handlers and delegates everything else to
|
|
399
|
+
* the synchronous {@link run}.
|
|
400
|
+
*/
|
|
401
|
+
export async function runAsync(argv) {
|
|
402
|
+
const cmd = argv[0];
|
|
403
|
+
if (cmd === "login")
|
|
404
|
+
return cmdLogin(argv.slice(1));
|
|
405
|
+
if (cmd === "send")
|
|
406
|
+
return cmdSend(argv.slice(1));
|
|
407
|
+
if (cmd === "test")
|
|
408
|
+
return cmdTest(argv.slice(1));
|
|
409
|
+
if (cmd === "status")
|
|
410
|
+
return cmdStatus(argv.slice(1));
|
|
411
|
+
if (cmd === "setup")
|
|
412
|
+
return cmdSetup(argv.slice(1));
|
|
413
|
+
if (cmd === "mcp")
|
|
414
|
+
return cmdMcp(argv.slice(1));
|
|
415
|
+
if (cmd === "uninstall")
|
|
416
|
+
return cmdUninstall(argv.slice(1));
|
|
417
|
+
if (cmd === "config")
|
|
418
|
+
return cmdConfig(argv.slice(1));
|
|
419
|
+
return run(argv);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* True when this module is the process entrypoint (run as the `gocode-notify`
|
|
423
|
+
* bin), false when imported (e.g. by tests). Resolves symlinks (npm installs
|
|
424
|
+
* the bin as a `.bin/` symlink) and uses `fileURLToPath` so paths with spaces
|
|
425
|
+
* and Windows drive paths compare correctly.
|
|
426
|
+
*/
|
|
427
|
+
export function isMainModule() {
|
|
428
|
+
const entry = process.argv[1];
|
|
429
|
+
if (entry === undefined)
|
|
430
|
+
return false;
|
|
431
|
+
try {
|
|
432
|
+
return realpathSync(entry) === fileURLToPath(import.meta.url);
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Only run when invoked as a binary, not when imported by tests.
|
|
439
|
+
if (isMainModule()) {
|
|
440
|
+
runAsync(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
|
|
441
|
+
console.error(`gocode-notify: ${err instanceof Error ? err.message : String(err)}`);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
});
|
|
444
|
+
}
|