waitspin 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/README.md +84 -0
- package/assets/waitspin-vscode/out/extension.js +446 -0
- package/assets/waitspin-vscode/out/extension.js.map +1 -0
- package/assets/waitspin-vscode/package.json +64 -0
- package/dist/cli.js +675 -0
- package/dist/cli.js.map +1 -0
- package/package.json +39 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { constants as fsConstants } from "node:fs";
|
|
5
|
+
import { access, cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
const DEFAULT_BASE_URL = process.env.WAITSPIN_BASE_URL?.trim() || "https://api.waitspin.com";
|
|
10
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
11
|
+
const extensionTargets = {
|
|
12
|
+
vscode: "vscode",
|
|
13
|
+
};
|
|
14
|
+
export function usageText() {
|
|
15
|
+
return ([
|
|
16
|
+
"Usage:",
|
|
17
|
+
" waitspin init --email you@example.com [--code CODE] [--key-profile control|publisher-extension] [--base-url URL]",
|
|
18
|
+
" waitspin bid create --line TEXT --url https://example.com --price-per-block CENTS --blocks N [--base-url URL] [--api-key KEY]",
|
|
19
|
+
" waitspin bids list [--base-url URL] [--api-key KEY]",
|
|
20
|
+
" waitspin bid checkout <campaign-id> [--base-url URL] [--api-key KEY]",
|
|
21
|
+
" waitspin market [--base-url URL]",
|
|
22
|
+
" waitspin wallet status [--base-url URL] [--api-key KEY]",
|
|
23
|
+
" waitspin wallet connect [--base-url URL] [--api-key KEY]",
|
|
24
|
+
" waitspin wallet ledger [--limit N] [--base-url URL] [--api-key KEY]",
|
|
25
|
+
" waitspin wallet payout --dry-run [--base-url URL] [--api-key KEY]",
|
|
26
|
+
" waitspin wallet payout --confirm-test-transfer [--base-url URL] [--api-key KEY]",
|
|
27
|
+
" waitspin extension install [--target vscode] [--base-url URL] [--api-key KEY] [--dry-run]",
|
|
28
|
+
" waitspin extension status [--target vscode]",
|
|
29
|
+
" waitspin extension uninstall [--target vscode] [--dry-run]",
|
|
30
|
+
"",
|
|
31
|
+
"Defaults:",
|
|
32
|
+
" API base: https://api.waitspin.com",
|
|
33
|
+
" API key: WAITSPIN_API_KEY env var",
|
|
34
|
+
" Public extension target: VS Code status-bar fallback",
|
|
35
|
+
].join("\n") + "\n");
|
|
36
|
+
}
|
|
37
|
+
function usage(exitCode = 1) {
|
|
38
|
+
const output = usageText();
|
|
39
|
+
if (exitCode === 0) {
|
|
40
|
+
process.stdout.write(output);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
process.stderr.write(output);
|
|
44
|
+
}
|
|
45
|
+
process.exit(exitCode);
|
|
46
|
+
}
|
|
47
|
+
function parseArgs(argv) {
|
|
48
|
+
if (argv.length === 0) {
|
|
49
|
+
usage();
|
|
50
|
+
}
|
|
51
|
+
const [command, ...rest] = argv;
|
|
52
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
53
|
+
usage(0);
|
|
54
|
+
}
|
|
55
|
+
const flags = new Map();
|
|
56
|
+
const positionals = [];
|
|
57
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
58
|
+
const token = rest[index];
|
|
59
|
+
if (!token)
|
|
60
|
+
continue;
|
|
61
|
+
if (!token.startsWith("--")) {
|
|
62
|
+
positionals.push(token);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const key = token.slice(2);
|
|
66
|
+
if (key === "help") {
|
|
67
|
+
usage(0);
|
|
68
|
+
}
|
|
69
|
+
if (key === "dry-run" ||
|
|
70
|
+
key === "allow-debug-auto-verify" ||
|
|
71
|
+
key === "confirm-test-transfer") {
|
|
72
|
+
flags.set(key, ["true"]);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const next = rest[index + 1];
|
|
76
|
+
if (next === undefined) {
|
|
77
|
+
throw new Error(`Missing value for --${key}`);
|
|
78
|
+
}
|
|
79
|
+
const existing = flags.get(key) || [];
|
|
80
|
+
existing.push(next);
|
|
81
|
+
flags.set(key, existing);
|
|
82
|
+
index += 1;
|
|
83
|
+
}
|
|
84
|
+
return { command, flags, positionals };
|
|
85
|
+
}
|
|
86
|
+
function requireFlag(flags, name) {
|
|
87
|
+
const value = flags.get(name)?.[0]?.trim();
|
|
88
|
+
if (!value) {
|
|
89
|
+
throw new Error(`Missing required flag --${name}`);
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
function optionalFlag(flags, name) {
|
|
94
|
+
const value = flags.get(name)?.[0]?.trim();
|
|
95
|
+
return value || undefined;
|
|
96
|
+
}
|
|
97
|
+
function booleanFlag(flags, name) {
|
|
98
|
+
return flags.get(name)?.[0] === "true";
|
|
99
|
+
}
|
|
100
|
+
function resolveBaseUrl(flags) {
|
|
101
|
+
return optionalFlag(flags, "base-url") || DEFAULT_BASE_URL;
|
|
102
|
+
}
|
|
103
|
+
function resolveApiKey(flags) {
|
|
104
|
+
return optionalFlag(flags, "api-key") || process.env.WAITSPIN_API_KEY?.trim();
|
|
105
|
+
}
|
|
106
|
+
function resolveKeyIntendedUse(flags) {
|
|
107
|
+
const explicitProfile = optionalFlag(flags, "key-profile");
|
|
108
|
+
if (!explicitProfile) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (explicitProfile === "publisher-extension") {
|
|
112
|
+
return "key_profile:publisher_extension";
|
|
113
|
+
}
|
|
114
|
+
if (explicitProfile === "control") {
|
|
115
|
+
return "key_profile:control";
|
|
116
|
+
}
|
|
117
|
+
throw new Error("--key-profile must be control or publisher-extension");
|
|
118
|
+
}
|
|
119
|
+
function requireApiKey(flags) {
|
|
120
|
+
const apiKey = resolveApiKey(flags);
|
|
121
|
+
if (!apiKey) {
|
|
122
|
+
throw new Error("Missing API key. Set WAITSPIN_API_KEY or pass --api-key");
|
|
123
|
+
}
|
|
124
|
+
return apiKey;
|
|
125
|
+
}
|
|
126
|
+
function timeoutSignal() {
|
|
127
|
+
return AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
128
|
+
}
|
|
129
|
+
class WaitSpinCliHttpError extends Error {
|
|
130
|
+
status;
|
|
131
|
+
constructor(status, message) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.status = status;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function requestJson(input, init) {
|
|
137
|
+
const response = await fetch(input, { ...init, signal: timeoutSignal() });
|
|
138
|
+
const text = await response.text();
|
|
139
|
+
if (response.status === 204 || !text) {
|
|
140
|
+
return { status: response.status, body: null };
|
|
141
|
+
}
|
|
142
|
+
let payload;
|
|
143
|
+
try {
|
|
144
|
+
payload = JSON.parse(text);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new WaitSpinCliHttpError(response.status, `HTTP ${response.status}: upstream returned a non-JSON error response`);
|
|
149
|
+
}
|
|
150
|
+
throw new Error("Invalid JSON response from WaitSpin API");
|
|
151
|
+
}
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new WaitSpinCliHttpError(response.status, `HTTP ${response.status}: ${JSON.stringify(payload).slice(0, 500)}`);
|
|
154
|
+
}
|
|
155
|
+
return { status: response.status, body: payload };
|
|
156
|
+
}
|
|
157
|
+
function printJson(value) {
|
|
158
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
159
|
+
}
|
|
160
|
+
async function runInit(flags) {
|
|
161
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
162
|
+
const email = requireFlag(flags, "email");
|
|
163
|
+
const intendedUse = resolveKeyIntendedUse(flags);
|
|
164
|
+
const providedCode = optionalFlag(flags, "code") ||
|
|
165
|
+
process.env.WAITSPIN_VERIFICATION_CODE?.trim();
|
|
166
|
+
if (providedCode) {
|
|
167
|
+
const { body } = await requestJson(`${baseUrl}/v1/keys/verify`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: { "Content-Type": "application/json" },
|
|
170
|
+
body: JSON.stringify({
|
|
171
|
+
email,
|
|
172
|
+
code: providedCode,
|
|
173
|
+
...(intendedUse ? { intended_use: intendedUse } : {}),
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
printJson({ ok: true, base_url: baseUrl, ...body });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const { body: requestResult } = await requestJson(`${baseUrl}/v1/keys/request`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "Content-Type": "application/json" },
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
email,
|
|
184
|
+
...(intendedUse ? { intended_use: intendedUse } : {}),
|
|
185
|
+
}),
|
|
186
|
+
});
|
|
187
|
+
const code = requestResult?.verification_debug_code;
|
|
188
|
+
const allowDebugAutoVerify = booleanFlag(flags, "allow-debug-auto-verify") ||
|
|
189
|
+
process.env.WAITSPIN_ALLOW_DEBUG_CODE_AUTO_VERIFY === "1";
|
|
190
|
+
if (!code || !allowDebugAutoVerify) {
|
|
191
|
+
printJson({
|
|
192
|
+
ok: true,
|
|
193
|
+
next: "check_email_for_code",
|
|
194
|
+
delivery: requestResult?.delivery,
|
|
195
|
+
email,
|
|
196
|
+
debug_code_available: Boolean(code),
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const { body: verifyResult } = await requestJson(`${baseUrl}/v1/keys/verify`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
email,
|
|
205
|
+
code,
|
|
206
|
+
...(intendedUse ? { intended_use: intendedUse } : {}),
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
printJson({ ok: true, base_url: baseUrl, ...verifyResult });
|
|
210
|
+
}
|
|
211
|
+
async function runBidCreate(flags) {
|
|
212
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
213
|
+
const apiKey = requireApiKey(flags);
|
|
214
|
+
const adLine = requireFlag(flags, "line");
|
|
215
|
+
const destinationUrl = requireFlag(flags, "url");
|
|
216
|
+
const pricePerBlockCents = Number(requireFlag(flags, "price-per-block"));
|
|
217
|
+
const blocks = Number(requireFlag(flags, "blocks"));
|
|
218
|
+
if (!Number.isFinite(pricePerBlockCents) || pricePerBlockCents < 100) {
|
|
219
|
+
throw new Error("--price-per-block must be at least 100 cents");
|
|
220
|
+
}
|
|
221
|
+
if (!Number.isFinite(blocks) || blocks < 1) {
|
|
222
|
+
throw new Error("--blocks must be at least 1");
|
|
223
|
+
}
|
|
224
|
+
const { body } = await requestJson(`${baseUrl}/v1/campaigns`, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
"Content-Type": "application/json",
|
|
228
|
+
Authorization: `Bearer ${apiKey}`,
|
|
229
|
+
"Idempotency-Key": randomUUID(),
|
|
230
|
+
},
|
|
231
|
+
body: JSON.stringify({
|
|
232
|
+
ad_line: adLine,
|
|
233
|
+
destination_url: destinationUrl,
|
|
234
|
+
price_per_block_cents: pricePerBlockCents,
|
|
235
|
+
blocks,
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
printJson({ ok: true, ...body });
|
|
239
|
+
}
|
|
240
|
+
async function runBidsList(flags) {
|
|
241
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
242
|
+
const apiKey = requireApiKey(flags);
|
|
243
|
+
const { body } = await requestJson(`${baseUrl}/v1/campaigns`, {
|
|
244
|
+
method: "GET",
|
|
245
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
246
|
+
});
|
|
247
|
+
printJson({ ok: true, campaigns: body?.campaigns ?? [] });
|
|
248
|
+
}
|
|
249
|
+
async function runBidCheckout(flags, positionals) {
|
|
250
|
+
const campaignId = positionals[0]?.trim();
|
|
251
|
+
if (!campaignId) {
|
|
252
|
+
throw new Error("Usage: waitspin bid checkout <campaign-id>");
|
|
253
|
+
}
|
|
254
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
255
|
+
const apiKey = requireApiKey(flags);
|
|
256
|
+
const { body } = await requestJson(`${baseUrl}/v1/blocks/checkout`, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: {
|
|
259
|
+
"Content-Type": "application/json",
|
|
260
|
+
Authorization: `Bearer ${apiKey}`,
|
|
261
|
+
},
|
|
262
|
+
body: JSON.stringify({ campaign_id: campaignId }),
|
|
263
|
+
});
|
|
264
|
+
printJson({
|
|
265
|
+
ok: true,
|
|
266
|
+
...body,
|
|
267
|
+
checkout_disclosure: {
|
|
268
|
+
terms_url: "https://waitspin.com/waitspin/terms",
|
|
269
|
+
privacy_url: "https://waitspin.com/waitspin/privacy",
|
|
270
|
+
refund_policy: "Unused prepaid blocks are account credit unless a cash refund is required by law or approved through support review. Self-serve cash refund requests are not shipped.",
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
async function runMarket(flags) {
|
|
275
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
276
|
+
const { body } = await requestJson(`${baseUrl}/v1/market`, { method: "GET" });
|
|
277
|
+
printJson({ ok: true, ...body });
|
|
278
|
+
}
|
|
279
|
+
async function runWalletStatus(flags) {
|
|
280
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
281
|
+
const apiKey = requireApiKey(flags);
|
|
282
|
+
const { body } = await requestJson(`${baseUrl}/v1/wallet/status`, {
|
|
283
|
+
method: "GET",
|
|
284
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
285
|
+
});
|
|
286
|
+
printJson({ ok: true, ...body });
|
|
287
|
+
}
|
|
288
|
+
async function runWalletConnect(flags) {
|
|
289
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
290
|
+
const apiKey = requireApiKey(flags);
|
|
291
|
+
const { body } = await requestJson(`${baseUrl}/v1/wallet/connect`, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
294
|
+
});
|
|
295
|
+
printJson({ ok: true, ...body });
|
|
296
|
+
}
|
|
297
|
+
async function runWalletLedger(flags) {
|
|
298
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
299
|
+
const apiKey = requireApiKey(flags);
|
|
300
|
+
const limit = optionalFlag(flags, "limit");
|
|
301
|
+
const url = new URL(`${baseUrl}/v1/wallet/ledger`);
|
|
302
|
+
if (limit) {
|
|
303
|
+
url.searchParams.set("limit", limit);
|
|
304
|
+
}
|
|
305
|
+
const { body } = await requestJson(url.toString(), {
|
|
306
|
+
method: "GET",
|
|
307
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
308
|
+
});
|
|
309
|
+
printJson({ ok: true, ...body });
|
|
310
|
+
}
|
|
311
|
+
async function runWalletPayout(flags) {
|
|
312
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
313
|
+
const apiKey = requireApiKey(flags);
|
|
314
|
+
const dryRun = booleanFlag(flags, "dry-run");
|
|
315
|
+
const confirmTestTransfer = booleanFlag(flags, "confirm-test-transfer");
|
|
316
|
+
if (!dryRun && !confirmTestTransfer) {
|
|
317
|
+
throw new Error("Use --dry-run first, then --confirm-test-transfer for a guarded test payout");
|
|
318
|
+
}
|
|
319
|
+
const headers = {
|
|
320
|
+
"Content-Type": "application/json",
|
|
321
|
+
Authorization: `Bearer ${apiKey}`,
|
|
322
|
+
};
|
|
323
|
+
if (!dryRun) {
|
|
324
|
+
headers["Idempotency-Key"] = randomUUID();
|
|
325
|
+
}
|
|
326
|
+
const { body } = await requestJson(`${baseUrl}/v1/wallet/payouts`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers,
|
|
329
|
+
body: JSON.stringify({
|
|
330
|
+
dry_run: dryRun,
|
|
331
|
+
confirm_test_transfer: confirmTestTransfer,
|
|
332
|
+
}),
|
|
333
|
+
});
|
|
334
|
+
printJson({ ok: true, ...body });
|
|
335
|
+
}
|
|
336
|
+
export function generateInstallId() {
|
|
337
|
+
return `wins_${randomUUID().replace(/-/g, "")}`;
|
|
338
|
+
}
|
|
339
|
+
export function publisherTargetForExtension(target) {
|
|
340
|
+
return target === "vscode" ? "status-bar-fallback" : target;
|
|
341
|
+
}
|
|
342
|
+
function extensionInstallDir(_target) {
|
|
343
|
+
return path.join(os.homedir(), ".vscode", "extensions");
|
|
344
|
+
}
|
|
345
|
+
function vscodeExtensionInstallDir(manifest) {
|
|
346
|
+
return path.join(extensionInstallDir("vscode"), `waitspin.${manifest.name}-${manifest.version}`);
|
|
347
|
+
}
|
|
348
|
+
function installStatePath(target) {
|
|
349
|
+
return path.join(os.homedir(), ".waitspin", `${target}-install.json`);
|
|
350
|
+
}
|
|
351
|
+
function extensionMarkerPath(target) {
|
|
352
|
+
return path.join(extensionInstallDir(target), ".waitspin-install.json");
|
|
353
|
+
}
|
|
354
|
+
function resolveExtensionTarget(flags) {
|
|
355
|
+
const target = optionalFlag(flags, "target") || "vscode";
|
|
356
|
+
if (!(target in extensionTargets)) {
|
|
357
|
+
throw new Error(`Unsupported extension target: ${target}. Public WaitSpin installs currently support --target vscode only.`);
|
|
358
|
+
}
|
|
359
|
+
return target;
|
|
360
|
+
}
|
|
361
|
+
async function loadInstallState(statePath) {
|
|
362
|
+
try {
|
|
363
|
+
const raw = await readFile(statePath, "utf8");
|
|
364
|
+
const parsed = JSON.parse(raw);
|
|
365
|
+
if (!parsed.install_id?.trim()) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return parsed;
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function loadInstallMarker(markerPath) {
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(await readFile(markerPath, "utf8"));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function assertManagedVscodeExtensionPath(input) {
|
|
383
|
+
const extensionRoot = path.resolve(extensionInstallDir("vscode"));
|
|
384
|
+
const resolved = path.resolve(input);
|
|
385
|
+
const name = path.basename(resolved);
|
|
386
|
+
if (!resolved.startsWith(`${extensionRoot}${path.sep}`) ||
|
|
387
|
+
!name.startsWith("waitspin.")) {
|
|
388
|
+
throw new Error("Refusing to manage an extension path outside the WaitSpin VS Code install directory.");
|
|
389
|
+
}
|
|
390
|
+
return resolved;
|
|
391
|
+
}
|
|
392
|
+
async function registerPublisherInstall(input) {
|
|
393
|
+
const { body } = await requestJson(`${input.baseUrl}/v1/publishers/register`, {
|
|
394
|
+
method: "POST",
|
|
395
|
+
headers: {
|
|
396
|
+
"Content-Type": "application/json",
|
|
397
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
398
|
+
},
|
|
399
|
+
body: JSON.stringify({
|
|
400
|
+
install_id: input.installId,
|
|
401
|
+
target: input.target,
|
|
402
|
+
}),
|
|
403
|
+
});
|
|
404
|
+
if (!body) {
|
|
405
|
+
throw new Error("Publisher registration returned empty body");
|
|
406
|
+
}
|
|
407
|
+
return body;
|
|
408
|
+
}
|
|
409
|
+
async function resolveExtensionDir() {
|
|
410
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
411
|
+
const candidates = [
|
|
412
|
+
path.join(process.cwd(), "packages/waitspin/assets/waitspin-vscode"),
|
|
413
|
+
path.join(process.cwd(), "extensions/waitspin-vscode"),
|
|
414
|
+
path.join(packageRoot, "assets", "waitspin-vscode"),
|
|
415
|
+
path.join(packageRoot, "../../extensions/waitspin-vscode"),
|
|
416
|
+
];
|
|
417
|
+
for (const candidate of candidates) {
|
|
418
|
+
try {
|
|
419
|
+
await access(path.join(candidate, "package.json"), fsConstants.F_OK);
|
|
420
|
+
return candidate;
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
throw new Error("WaitSpin extension package not found. Install from the monorepo checkout or use a build that ships assets/waitspin-vscode.");
|
|
427
|
+
}
|
|
428
|
+
async function installVscodeExtensionRuntime(input) {
|
|
429
|
+
const targetDir = vscodeExtensionInstallDir(input.manifest);
|
|
430
|
+
await access(path.join(input.sourceDir, "out", "extension.js"), fsConstants.F_OK);
|
|
431
|
+
await mkdir(targetDir, { recursive: true });
|
|
432
|
+
await cp(path.join(input.sourceDir, "package.json"), path.join(targetDir, "package.json"), {
|
|
433
|
+
force: true,
|
|
434
|
+
});
|
|
435
|
+
await cp(path.join(input.sourceDir, "out"), path.join(targetDir, "out"), {
|
|
436
|
+
recursive: true,
|
|
437
|
+
force: true,
|
|
438
|
+
});
|
|
439
|
+
return targetDir;
|
|
440
|
+
}
|
|
441
|
+
export async function runExtensionInstall(flags) {
|
|
442
|
+
const target = resolveExtensionTarget(flags);
|
|
443
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
444
|
+
const publisherTarget = publisherTargetForExtension(target);
|
|
445
|
+
const extensionDir = await resolveExtensionDir();
|
|
446
|
+
const manifestPath = path.join(extensionDir, "package.json");
|
|
447
|
+
let manifest;
|
|
448
|
+
try {
|
|
449
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
throw new Error(`Extension package not found at ${extensionDir}.`);
|
|
453
|
+
}
|
|
454
|
+
const installDir = extensionInstallDir(target);
|
|
455
|
+
const statePath = installStatePath(target);
|
|
456
|
+
const existingState = await loadInstallState(statePath);
|
|
457
|
+
const installId = existingState?.install_id || generateInstallId();
|
|
458
|
+
const summary = {
|
|
459
|
+
ok: true,
|
|
460
|
+
target,
|
|
461
|
+
extension: manifest.name,
|
|
462
|
+
version: manifest.version,
|
|
463
|
+
source: extensionDir,
|
|
464
|
+
install_hint: installDir,
|
|
465
|
+
install_id: installId,
|
|
466
|
+
publisher_target: publisherTarget,
|
|
467
|
+
state_path: statePath,
|
|
468
|
+
note: "Installs the WaitSpin VS Code status-bar fallback extension.",
|
|
469
|
+
next: {
|
|
470
|
+
create_publisher_key: "waitspin init --email you@example.com --key-profile publisher-extension",
|
|
471
|
+
set_vscode_settings: {
|
|
472
|
+
"waitspin.installId": installId,
|
|
473
|
+
},
|
|
474
|
+
credential_storage: "The VS Code extension migrates waitspin.apiKey user settings into SecretStorage; WAITSPIN_API_KEY remains env-only.",
|
|
475
|
+
optional_bootstrap_env: {
|
|
476
|
+
WAITSPIN_INSTALL_ID: installId,
|
|
477
|
+
WAITSPIN_API_KEY: "<temporary publisher-extension key for first activation>",
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
if (booleanFlag(flags, "dry-run")) {
|
|
482
|
+
printJson({ ...summary, dry_run: true, publisher_registered: false });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const apiKey = requireApiKey(flags);
|
|
486
|
+
const registration = await registerPublisherInstall({
|
|
487
|
+
baseUrl,
|
|
488
|
+
apiKey,
|
|
489
|
+
installId,
|
|
490
|
+
target: publisherTarget,
|
|
491
|
+
});
|
|
492
|
+
const installState = {
|
|
493
|
+
install_id: registration.install_id,
|
|
494
|
+
publisher_id: registration.publisher_id,
|
|
495
|
+
publisher_target: registration.target,
|
|
496
|
+
registered_at: new Date().toISOString(),
|
|
497
|
+
};
|
|
498
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
499
|
+
await writeFile(statePath, `${JSON.stringify(installState, null, 2)}\n`, "utf8");
|
|
500
|
+
await mkdir(installDir, { recursive: true });
|
|
501
|
+
const markerPath = extensionMarkerPath(target);
|
|
502
|
+
const installedExtensionPath = await installVscodeExtensionRuntime({
|
|
503
|
+
sourceDir: extensionDir,
|
|
504
|
+
manifest,
|
|
505
|
+
});
|
|
506
|
+
await writeFile(markerPath, `${JSON.stringify({
|
|
507
|
+
...summary,
|
|
508
|
+
...installState,
|
|
509
|
+
extension_installed: Boolean(installedExtensionPath),
|
|
510
|
+
installed_extension_path: installedExtensionPath,
|
|
511
|
+
publisher_registered: true,
|
|
512
|
+
}, null, 2)}\n`, "utf8");
|
|
513
|
+
printJson({
|
|
514
|
+
...summary,
|
|
515
|
+
...installState,
|
|
516
|
+
extension_installed: Boolean(installedExtensionPath),
|
|
517
|
+
installed_extension_path: installedExtensionPath,
|
|
518
|
+
publisher_registered: true,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
async function pathExists(filePath) {
|
|
522
|
+
try {
|
|
523
|
+
await access(filePath, fsConstants.F_OK);
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function managedInstalledPath(marker) {
|
|
531
|
+
const markerPath = marker?.installed_extension_path?.trim();
|
|
532
|
+
return markerPath ? assertManagedVscodeExtensionPath(markerPath) : null;
|
|
533
|
+
}
|
|
534
|
+
function managedInstalledPathStatus(marker) {
|
|
535
|
+
try {
|
|
536
|
+
return { path: managedInstalledPath(marker), error: null };
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return { path: null, error: "invalid_managed_extension_path" };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
export async function runExtensionStatus(flags) {
|
|
543
|
+
const target = resolveExtensionTarget(flags);
|
|
544
|
+
const statePath = installStatePath(target);
|
|
545
|
+
const markerPath = extensionMarkerPath(target);
|
|
546
|
+
const [state, marker] = await Promise.all([
|
|
547
|
+
loadInstallState(statePath),
|
|
548
|
+
loadInstallMarker(markerPath),
|
|
549
|
+
]);
|
|
550
|
+
const installedPathStatus = managedInstalledPathStatus(marker);
|
|
551
|
+
const installedExtensionPath = installedPathStatus.path;
|
|
552
|
+
const extensionInstalled = installedExtensionPath
|
|
553
|
+
? await pathExists(path.join(installedExtensionPath, "package.json"))
|
|
554
|
+
: false;
|
|
555
|
+
printJson({
|
|
556
|
+
ok: true,
|
|
557
|
+
target,
|
|
558
|
+
mode: "status-bar-fallback",
|
|
559
|
+
installed: extensionInstalled,
|
|
560
|
+
publisher_registered: Boolean(state?.publisher_id || marker?.publisher_id),
|
|
561
|
+
install_id: state?.install_id || marker?.install_id || null,
|
|
562
|
+
publisher_id: state?.publisher_id || marker?.publisher_id || null,
|
|
563
|
+
publisher_target: state?.publisher_target ||
|
|
564
|
+
marker?.publisher_target ||
|
|
565
|
+
publisherTargetForExtension(target),
|
|
566
|
+
extension: marker?.extension || null,
|
|
567
|
+
version: marker?.version || null,
|
|
568
|
+
state_path: statePath,
|
|
569
|
+
marker_path: markerPath,
|
|
570
|
+
installed_extension_path: installedExtensionPath,
|
|
571
|
+
install_marker_error: installedPathStatus.error,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
export async function runExtensionUninstall(flags) {
|
|
575
|
+
const target = resolveExtensionTarget(flags);
|
|
576
|
+
const statePath = installStatePath(target);
|
|
577
|
+
const markerPath = extensionMarkerPath(target);
|
|
578
|
+
const marker = await loadInstallMarker(markerPath);
|
|
579
|
+
const installedPathStatus = managedInstalledPathStatus(marker);
|
|
580
|
+
const installedExtensionPath = installedPathStatus.path;
|
|
581
|
+
const removePaths = [statePath, markerPath];
|
|
582
|
+
if (installedExtensionPath) {
|
|
583
|
+
removePaths.unshift(installedExtensionPath);
|
|
584
|
+
}
|
|
585
|
+
if (booleanFlag(flags, "dry-run")) {
|
|
586
|
+
printJson({
|
|
587
|
+
ok: true,
|
|
588
|
+
target,
|
|
589
|
+
dry_run: true,
|
|
590
|
+
would_remove: removePaths,
|
|
591
|
+
install_marker_error: installedPathStatus.error,
|
|
592
|
+
});
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
await Promise.all(removePaths.map((filePath) => rm(filePath, { force: true, recursive: true })));
|
|
596
|
+
printJson({
|
|
597
|
+
ok: true,
|
|
598
|
+
target,
|
|
599
|
+
uninstalled: true,
|
|
600
|
+
removed: removePaths,
|
|
601
|
+
install_marker_error: installedPathStatus.error,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
605
|
+
const { command, flags, positionals } = parseArgs(argv);
|
|
606
|
+
if (command === "init") {
|
|
607
|
+
await runInit(flags);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (command === "bid" && positionals[0] === "create") {
|
|
611
|
+
await runBidCreate(flags);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (command === "bids" && positionals[0] === "list") {
|
|
615
|
+
await runBidsList(flags);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (command === "bid" && positionals[0] === "checkout") {
|
|
619
|
+
await runBidCheckout(flags, positionals.slice(1));
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (command === "market") {
|
|
623
|
+
await runMarket(flags);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (command === "wallet" && positionals[0] === "status") {
|
|
627
|
+
await runWalletStatus(flags);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (command === "wallet" && positionals[0] === "connect") {
|
|
631
|
+
await runWalletConnect(flags);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (command === "wallet" && positionals[0] === "ledger") {
|
|
635
|
+
await runWalletLedger(flags);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (command === "wallet" && positionals[0] === "payout") {
|
|
639
|
+
await runWalletPayout(flags);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (command === "extension" && positionals[0] === "install") {
|
|
643
|
+
await runExtensionInstall(flags);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (command === "extension" && positionals[0] === "status") {
|
|
647
|
+
await runExtensionStatus(flags);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (command === "extension" && positionals[0] === "uninstall") {
|
|
651
|
+
await runExtensionUninstall(flags);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
usage();
|
|
655
|
+
}
|
|
656
|
+
function isDirectEntrypoint() {
|
|
657
|
+
if (!process.argv[1]) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
661
|
+
try {
|
|
662
|
+
return realpathSync(modulePath) === realpathSync(process.argv[1]);
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
return modulePath === process.argv[1];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (isDirectEntrypoint()) {
|
|
669
|
+
main().catch((error) => {
|
|
670
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
671
|
+
process.stderr.write(usageText());
|
|
672
|
+
process.exit(1);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
//# sourceMappingURL=cli.js.map
|