@binaryandthread/cli 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 +83 -0
- package/dist/bin.js +1678 -0
- package/dist/bin.js.map +1 -0
- package/dist/brand-TMOYK6QE.js +35 -0
- package/dist/brand-TMOYK6QE.js.map +1 -0
- package/dist/browser-EO5EQUXQ.js +50 -0
- package/dist/browser-EO5EQUXQ.js.map +1 -0
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/chunk-DWTH7CNI.js +1126 -0
- package/dist/chunk-DWTH7CNI.js.map +1 -0
- package/dist/chunk-IM3HF7NN.js +32 -0
- package/dist/chunk-IM3HF7NN.js.map +1 -0
- package/dist/chunk-OVG37IIX.js +66 -0
- package/dist/chunk-OVG37IIX.js.map +1 -0
- package/dist/chunk-P3LSUCEO.js +120 -0
- package/dist/chunk-P3LSUCEO.js.map +1 -0
- package/dist/config-FXGCWC6Y.js +17 -0
- package/dist/config-FXGCWC6Y.js.map +1 -0
- package/dist/interactive-TFZOY3JU.js +109 -0
- package/dist/interactive-TFZOY3JU.js.map +1 -0
- package/dist/poll-O3VUD5WV.js +39 -0
- package/dist/poll-O3VUD5WV.js.map +1 -0
- package/dist/prompts-GTC5VYM6.js +9 -0
- package/dist/prompts-GTC5VYM6.js.map +1 -0
- package/man/bt.1 +87 -0
- package/package.json +47 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,1678 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configPath,
|
|
4
|
+
forgetConfig,
|
|
5
|
+
loadConfig,
|
|
6
|
+
saveConfig
|
|
7
|
+
} from "./chunk-OVG37IIX.js";
|
|
8
|
+
import {
|
|
9
|
+
makePrompts
|
|
10
|
+
} from "./chunk-IM3HF7NN.js";
|
|
11
|
+
import {
|
|
12
|
+
BT_DEFAULT_PUBLISHABLE_KEY,
|
|
13
|
+
BT_DEFAULT_STORE_URL,
|
|
14
|
+
GLYPH,
|
|
15
|
+
RecoverableCartError,
|
|
16
|
+
StoreApiError,
|
|
17
|
+
StoreClient,
|
|
18
|
+
brandOf,
|
|
19
|
+
displayName,
|
|
20
|
+
empty,
|
|
21
|
+
fail,
|
|
22
|
+
firstPrice,
|
|
23
|
+
fmtPrice,
|
|
24
|
+
formatCartLine,
|
|
25
|
+
lineColour,
|
|
26
|
+
ok,
|
|
27
|
+
printResult,
|
|
28
|
+
productColours,
|
|
29
|
+
redact,
|
|
30
|
+
registerBrowse,
|
|
31
|
+
registerStage,
|
|
32
|
+
registerView,
|
|
33
|
+
resolveLineItemRef,
|
|
34
|
+
runStage,
|
|
35
|
+
stageMarker,
|
|
36
|
+
versionLine,
|
|
37
|
+
warn,
|
|
38
|
+
working
|
|
39
|
+
} from "./chunk-DWTH7CNI.js";
|
|
40
|
+
import {
|
|
41
|
+
box,
|
|
42
|
+
colourMarker,
|
|
43
|
+
contributionBar,
|
|
44
|
+
cyan,
|
|
45
|
+
dim,
|
|
46
|
+
green,
|
|
47
|
+
header,
|
|
48
|
+
makeBrand,
|
|
49
|
+
rule
|
|
50
|
+
} from "./chunk-P3LSUCEO.js";
|
|
51
|
+
import {
|
|
52
|
+
__require
|
|
53
|
+
} from "./chunk-7OCVIDC7.js";
|
|
54
|
+
|
|
55
|
+
// src/bin.ts
|
|
56
|
+
import { spawn } from "child_process";
|
|
57
|
+
import { Command } from "commander";
|
|
58
|
+
|
|
59
|
+
// src/client-config.ts
|
|
60
|
+
function resolveClientConfig(env = process.env) {
|
|
61
|
+
const baseUrl = env.BT_API_URL || env.MEDUSA_BACKEND_URL || BT_DEFAULT_STORE_URL;
|
|
62
|
+
const publishableKey = env.BT_PUBLISHABLE_KEY || env.MEDUSA_PUBLISHABLE_KEY || BT_DEFAULT_PUBLISHABLE_KEY;
|
|
63
|
+
return { baseUrl, publishableKey };
|
|
64
|
+
}
|
|
65
|
+
function makeLogger(debug, write = (s) => process.stderr.write(s)) {
|
|
66
|
+
if (!debug) {
|
|
67
|
+
const noop = () => {
|
|
68
|
+
};
|
|
69
|
+
return { info: noop, warn: noop, error: noop };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
info: (...a) => write("\xB7 " + JSON.stringify(redact(a)) + "\n"),
|
|
73
|
+
warn: (...a) => write("\u26A0 " + JSON.stringify(redact(a)) + "\n"),
|
|
74
|
+
error: (...a) => write("\u2717 " + JSON.stringify(redact(a)) + "\n")
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/eggs.ts
|
|
79
|
+
function teapot() {
|
|
80
|
+
return `${GLYPH.fail} 418 i'm a teapot. we make clothing, not coffee. (RFC 2324)`;
|
|
81
|
+
}
|
|
82
|
+
function sudo() {
|
|
83
|
+
return "nice try. you don't have root on fashion.";
|
|
84
|
+
}
|
|
85
|
+
function vim() {
|
|
86
|
+
return "to exit, commit a piece. (:q won't save you here.)";
|
|
87
|
+
}
|
|
88
|
+
function panic() {
|
|
89
|
+
return "don't. nothing's on fire. your orders are safe. take a walk; the wardrobe will still be here.";
|
|
90
|
+
}
|
|
91
|
+
function deploy() {
|
|
92
|
+
return `${GLYPH.fail} command not found: deploy. you mean \`bt commit\`?
|
|
93
|
+
we ship clothing, not services. did you fat-finger your day job?`;
|
|
94
|
+
}
|
|
95
|
+
function shipIt() {
|
|
96
|
+
return `that's commit's job. ${GLYPH.arrow} bt commit`;
|
|
97
|
+
}
|
|
98
|
+
function rmWardrobe() {
|
|
99
|
+
return `${GLYPH.warn} this would delete your entire order history. it won't, obviously \u2014
|
|
100
|
+
we don't have rm on your memories. (and you can't unship a delivered piece.)`;
|
|
101
|
+
}
|
|
102
|
+
function force() {
|
|
103
|
+
return "--force-pushing to prod, are we. there's no --force here \u2014\n a commit to your wardrobe is already that decisive.";
|
|
104
|
+
}
|
|
105
|
+
var FORTUNES = [
|
|
106
|
+
'// "there are only two hard things in computer science: cache invalidation and naming things." \u2014 phil karlton',
|
|
107
|
+
'// "premature optimization is the root of all evil." \u2014 donald knuth',
|
|
108
|
+
'// "weeks of programming can save you hours of planning."',
|
|
109
|
+
'// "make it work, make it right, make it fast." \u2014 kent beck',
|
|
110
|
+
'// "talk is cheap. show me the code." \u2014 linus torvalds',
|
|
111
|
+
`// "given enough eyeballs, all bugs are shallow." \u2014 linus's law`,
|
|
112
|
+
'// "always code as if the person maintaining it is a violent psychopath who knows where you live." \u2014 john woods',
|
|
113
|
+
'// "it works on my machine."'
|
|
114
|
+
];
|
|
115
|
+
function fortune(seed = 0) {
|
|
116
|
+
const n = Number.isFinite(seed) ? Math.abs(Math.floor(seed)) : 0;
|
|
117
|
+
return FORTUNES[n % FORTUNES.length];
|
|
118
|
+
}
|
|
119
|
+
function blame(slug) {
|
|
120
|
+
const subject = slug ? slug.trim() : "";
|
|
121
|
+
if (subject) {
|
|
122
|
+
return `blame ${subject}
|
|
123
|
+
motif a style \xD7 an idea, collided on purpose \u2014 then curated by hand, not shipped raw.
|
|
124
|
+
crew the people at Binary & Thread. (per-designer credit is coming \u2014 bt drops to watch.)`;
|
|
125
|
+
}
|
|
126
|
+
return "blame \u2014 who's responsible for all this?\n every piece here is a style collided with an idea, curated by hand at Binary & Thread.\n nothing auto-shipped. someone looked at each one. that's the whole point.";
|
|
127
|
+
}
|
|
128
|
+
function manPage(version) {
|
|
129
|
+
return [
|
|
130
|
+
"BT(1) Binary & Thread BT(1)",
|
|
131
|
+
"",
|
|
132
|
+
"NAME",
|
|
133
|
+
" bt \u2014 the Binary & Thread terminal store",
|
|
134
|
+
"",
|
|
135
|
+
"SYNOPSIS",
|
|
136
|
+
" bt <command> [options]",
|
|
137
|
+
" npx @binaryandthread/cli <command> [options]",
|
|
138
|
+
"",
|
|
139
|
+
"DESCRIPTION",
|
|
140
|
+
" Binary & Thread is a techno-minimalist clothing & lifestyle",
|
|
141
|
+
" company. bt is its terminal store: you browse, stage, and commit",
|
|
142
|
+
" pieces with the ceremony of a deploy. Payment opens in your",
|
|
143
|
+
" browser \u2014 cards never touch the terminal.",
|
|
144
|
+
"",
|
|
145
|
+
"COMMANDS",
|
|
146
|
+
" init first-run welcome + the happy path",
|
|
147
|
+
" browse list what's in stock",
|
|
148
|
+
" view render one piece (image or ASCII) + specs",
|
|
149
|
+
" stage reserve stock (like git add)",
|
|
150
|
+
" commit turn your staged items into an order",
|
|
151
|
+
" buy stage + commit in one",
|
|
152
|
+
" log your order history as a git log (--graph, --oneline)",
|
|
153
|
+
" order track one order through its lifecycle",
|
|
154
|
+
" whoami your contribution profile + standing",
|
|
155
|
+
" drops what's on next, before it hits the web",
|
|
156
|
+
" login passwordless magic-link sign-in",
|
|
157
|
+
" report file a bug report (you become a contributor)",
|
|
158
|
+
"",
|
|
159
|
+
"ENVIRONMENT",
|
|
160
|
+
" NO_COLOR disable ANSI colour + inline images",
|
|
161
|
+
" BT_TOKEN a customer JWT for non-interactive sessions",
|
|
162
|
+
" BT_STORE_URL override the store base URL (defaults baked in)",
|
|
163
|
+
"",
|
|
164
|
+
"EXIT STATUS",
|
|
165
|
+
" 0 success 1 general error",
|
|
166
|
+
" 75 EX_TEMPFAIL \u2014 a transient failure; nothing was charged, retry",
|
|
167
|
+
"",
|
|
168
|
+
"BUGS",
|
|
169
|
+
" none known. if you find one, you're now a contributor.",
|
|
170
|
+
" file it: bt report (or bugs@binaryandthread.com)",
|
|
171
|
+
"",
|
|
172
|
+
"AUTHORS",
|
|
173
|
+
" The crew at Binary & Thread, and every contributor who committed",
|
|
174
|
+
" a fit. earned, not bought.",
|
|
175
|
+
"",
|
|
176
|
+
"SEE ALSO",
|
|
177
|
+
" git(1), curl(1), a mirror. go build something.",
|
|
178
|
+
"",
|
|
179
|
+
`Binary & Thread bt ${version} BT(1)`
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/errors.ts
|
|
184
|
+
var EXIT = {
|
|
185
|
+
OK: 0,
|
|
186
|
+
GENERIC: 1,
|
|
187
|
+
DATAERR: 65,
|
|
188
|
+
// EX_DATAERR — bad request / unprocessable / stale cart (400/409/422)
|
|
189
|
+
UNAVAILABLE: 69,
|
|
190
|
+
// EX_UNAVAILABLE — not found (404)
|
|
191
|
+
TEMPFAIL: 75,
|
|
192
|
+
// EX_TEMPFAIL — temporary: 5xx, network/exhausted, rate-limited (429)
|
|
193
|
+
NOPERM: 77
|
|
194
|
+
// EX_NOPERM — auth required / forbidden (401/403)
|
|
195
|
+
};
|
|
196
|
+
function messageOf(e) {
|
|
197
|
+
if (e instanceof Error && e.message) {
|
|
198
|
+
return e.message;
|
|
199
|
+
}
|
|
200
|
+
const s = String(e ?? "").trim();
|
|
201
|
+
return s || "unexpected error";
|
|
202
|
+
}
|
|
203
|
+
function classifyError(e) {
|
|
204
|
+
const message = String(redact(messageOf(e)));
|
|
205
|
+
if (e instanceof RecoverableCartError) {
|
|
206
|
+
return { code: EXIT.DATAERR, type: "stale_cart", status: e.status, message };
|
|
207
|
+
}
|
|
208
|
+
if (e instanceof StoreApiError) {
|
|
209
|
+
const s = e.status;
|
|
210
|
+
if (s === 401 || s === 403) return { code: EXIT.NOPERM, type: "auth", status: s, message };
|
|
211
|
+
if (s === 404) return { code: EXIT.UNAVAILABLE, type: "not_found", status: s, message };
|
|
212
|
+
if (s === 429) return { code: EXIT.TEMPFAIL, type: "rate_limited", status: s, message };
|
|
213
|
+
if (s === 0 || s === 408 || s >= 500) return { code: EXIT.TEMPFAIL, type: "upstream", status: s, message };
|
|
214
|
+
if (s >= 400) return { code: EXIT.DATAERR, type: "client_error", status: s, message };
|
|
215
|
+
}
|
|
216
|
+
return { code: EXIT.GENERIC, type: "error", message };
|
|
217
|
+
}
|
|
218
|
+
function formatFatal(e, json) {
|
|
219
|
+
const info = classifyError(e);
|
|
220
|
+
if (json) {
|
|
221
|
+
const envelope = { error: { code: info.code, type: info.type, message: info.message } };
|
|
222
|
+
return { out: JSON.stringify(envelope, null, 2), err: null, code: info.code };
|
|
223
|
+
}
|
|
224
|
+
return { out: null, err: `\u2717 ${info.message}`, code: info.code };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/keychain.ts
|
|
228
|
+
import * as fs from "fs";
|
|
229
|
+
import * as os from "os";
|
|
230
|
+
import * as path from "path";
|
|
231
|
+
var KEYCHAIN_SERVICE = "binaryandthread/bt";
|
|
232
|
+
var KEYCHAIN_ACCOUNT = "jwt";
|
|
233
|
+
function fallbackTokenPath() {
|
|
234
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
235
|
+
const base = xdg && xdg.trim() ? xdg : path.join(os.homedir(), ".config");
|
|
236
|
+
return path.join(base, "bt", "token");
|
|
237
|
+
}
|
|
238
|
+
function defaultMakeEntry(service, account) {
|
|
239
|
+
try {
|
|
240
|
+
const { Entry } = __require("@napi-rs/keyring");
|
|
241
|
+
return new Entry(service, account);
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function setToken(jwt, deps = {}) {
|
|
247
|
+
const make = deps.makeEntry ?? defaultMakeEntry;
|
|
248
|
+
const entry = make(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
249
|
+
if (entry) {
|
|
250
|
+
try {
|
|
251
|
+
entry.setPassword(jwt);
|
|
252
|
+
try {
|
|
253
|
+
fs.rmSync(fallbackTokenPath(), { force: true });
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const target = fallbackTokenPath();
|
|
261
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
262
|
+
const tmp = target + ".tmp";
|
|
263
|
+
fs.writeFileSync(tmp, jwt, { encoding: "utf8", mode: 384 });
|
|
264
|
+
fs.renameSync(tmp, target);
|
|
265
|
+
}
|
|
266
|
+
function getToken(deps = {}) {
|
|
267
|
+
const make = deps.makeEntry ?? defaultMakeEntry;
|
|
268
|
+
const entry = make(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
269
|
+
if (entry) {
|
|
270
|
+
try {
|
|
271
|
+
const pw = entry.getPassword();
|
|
272
|
+
if (pw) {
|
|
273
|
+
return pw;
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const raw = fs.readFileSync(fallbackTokenPath(), "utf8").trim();
|
|
280
|
+
return raw || null;
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function deleteToken(deps = {}) {
|
|
286
|
+
const make = deps.makeEntry ?? defaultMakeEntry;
|
|
287
|
+
const entry = make(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
288
|
+
if (entry) {
|
|
289
|
+
try {
|
|
290
|
+
entry.deletePassword();
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
fs.rmSync(fallbackTokenPath(), { force: true });
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/commands/search.ts
|
|
301
|
+
function matches(p, q) {
|
|
302
|
+
const needle = q.toLowerCase();
|
|
303
|
+
return (p.handle ?? "").toLowerCase().includes(needle) || (p.title ?? "").toLowerCase().includes(needle) || (p.description ?? "").toLowerCase().includes(needle);
|
|
304
|
+
}
|
|
305
|
+
async function runSearch(client, config, args) {
|
|
306
|
+
const style = brandOf(args);
|
|
307
|
+
const region = await client.resolveRegion(config.region);
|
|
308
|
+
const products = await client.listProducts({ region });
|
|
309
|
+
const filtered = products.filter((p) => matches(p, args.query));
|
|
310
|
+
const lines = [];
|
|
311
|
+
if (filtered.length === 0) {
|
|
312
|
+
lines.push(empty(`no matches for "${args.query}". 0 results \u2014 try a looser query.`));
|
|
313
|
+
lines.push(" browse all \u2192 bt browse");
|
|
314
|
+
} else {
|
|
315
|
+
lines.push(header(style, `bt search "${args.query}"`));
|
|
316
|
+
for (const p of filtered) {
|
|
317
|
+
const { amount, currency } = firstPrice(p);
|
|
318
|
+
const { title, handle } = displayName(p);
|
|
319
|
+
const stage = p.metadata?.["channel_stage"] ?? "main";
|
|
320
|
+
const colours = colourMarker(productColours(p));
|
|
321
|
+
const row = [
|
|
322
|
+
title.padEnd(22),
|
|
323
|
+
cyan(style, fmtPrice(amount, currency).padEnd(11)),
|
|
324
|
+
dim(style, handle.padEnd(34)),
|
|
325
|
+
stageMarker(stage)
|
|
326
|
+
].join(" ").trimEnd();
|
|
327
|
+
lines.push(colours ? `${row}
|
|
328
|
+
${dim(style, colours)}` : row);
|
|
329
|
+
}
|
|
330
|
+
lines.push(" view one \u2192 bt view <handle>");
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
lines,
|
|
334
|
+
json: args.json ? filtered.map((p) => ({
|
|
335
|
+
id: p.id,
|
|
336
|
+
handle: p.handle,
|
|
337
|
+
title: p.title,
|
|
338
|
+
price: firstPrice(p).amount,
|
|
339
|
+
currency: firstPrice(p).currency,
|
|
340
|
+
colours: productColours(p)
|
|
341
|
+
})) : void 0
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function registerSearch(program, makeClient2, loadConfig2) {
|
|
345
|
+
program.command("search").argument("<query>", "what to look for").description("filter the catalog by a query.").action(async (query) => {
|
|
346
|
+
const gOpts = program.opts();
|
|
347
|
+
const json = Boolean(gOpts.json);
|
|
348
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
349
|
+
const res = await runSearch(makeClient2(), loadConfig2(), { query, json, brand });
|
|
350
|
+
printResult(res, json);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/commands/cart.ts
|
|
355
|
+
var EMPTY_STATE = "no commits yet. your wardrobe is a fresh repo.";
|
|
356
|
+
async function runCart(client, config, args) {
|
|
357
|
+
if (!config.cart_id) {
|
|
358
|
+
return {
|
|
359
|
+
lines: [empty(EMPTY_STATE), " start one \u2192 bt browse"],
|
|
360
|
+
json: args.json ? { cart_id: null, items: [] } : void 0
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const style = brandOf(args);
|
|
364
|
+
try {
|
|
365
|
+
const cart = await client.getCart(config.cart_id);
|
|
366
|
+
const lines = [];
|
|
367
|
+
if (!cart.items || cart.items.length === 0) {
|
|
368
|
+
lines.push(empty(EMPTY_STATE));
|
|
369
|
+
lines.push(" start one \u2192 bt browse");
|
|
370
|
+
} else {
|
|
371
|
+
lines.push(header(style, "bt cart \u2014 staged for commit"));
|
|
372
|
+
cart.items.forEach((item, i) => {
|
|
373
|
+
lines.push(formatCartLine(item, i, cart.currency_code, style));
|
|
374
|
+
});
|
|
375
|
+
if (cart.total != null) {
|
|
376
|
+
lines.push(` total ${cyan(style, fmtPrice(cart.total, cart.currency_code))} incl. VAT`);
|
|
377
|
+
}
|
|
378
|
+
lines.push(" remove one \u2192 bt unstage <#> \xB7 commit \u2192 bt commit");
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
lines,
|
|
382
|
+
json: args.json ? { cart_id: cart.id, items: cart.items ?? [], total: cart.total ?? null } : void 0
|
|
383
|
+
};
|
|
384
|
+
} catch (e) {
|
|
385
|
+
if (e instanceof RecoverableCartError) {
|
|
386
|
+
return {
|
|
387
|
+
lines: [
|
|
388
|
+
warn("that cart is no longer valid (expired or already committed)."),
|
|
389
|
+
empty(EMPTY_STATE),
|
|
390
|
+
" start a fresh one \u2192 bt browse"
|
|
391
|
+
],
|
|
392
|
+
json: args.json ? { cart_id: null, items: [], stale: true } : void 0
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
throw e;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function registerCart(program, makeClient2, loadConfig2) {
|
|
399
|
+
program.command("cart").description("show your current cart \u2014 the authoritative server state.").action(async () => {
|
|
400
|
+
const gOpts = program.opts();
|
|
401
|
+
const json = Boolean(gOpts.json);
|
|
402
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
403
|
+
const res = await runCart(makeClient2(), loadConfig2(), { json, brand });
|
|
404
|
+
printResult(res, json);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/commands/checkout.ts
|
|
409
|
+
function supportedCountries(region) {
|
|
410
|
+
return (region.countries ?? []).map((c) => (c.iso_2 ?? "").toLowerCase()).filter((c) => c.length > 0);
|
|
411
|
+
}
|
|
412
|
+
async function runCheckout(client, config, args, deps) {
|
|
413
|
+
const cartId = config.cart_id;
|
|
414
|
+
if (!cartId) {
|
|
415
|
+
if (args.resume) {
|
|
416
|
+
return {
|
|
417
|
+
lines: [
|
|
418
|
+
empty("nothing to resume \u2014 no cart on record."),
|
|
419
|
+
" stage something first \u2192 bt browse then bt stage <slug>"
|
|
420
|
+
]
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
lines: [
|
|
425
|
+
empty("nothing staged. your wardrobe is a fresh repo."),
|
|
426
|
+
" start one \u2192 bt browse"
|
|
427
|
+
]
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
let cart;
|
|
431
|
+
try {
|
|
432
|
+
cart = await client.getCart(cartId);
|
|
433
|
+
} catch (e) {
|
|
434
|
+
if (e instanceof RecoverableCartError) {
|
|
435
|
+
return {
|
|
436
|
+
lines: [
|
|
437
|
+
warn("that cart is no longer valid (expired or already committed)."),
|
|
438
|
+
" re-stage \u2192 bt browse then bt stage <slug>"
|
|
439
|
+
]
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
throw e;
|
|
443
|
+
}
|
|
444
|
+
if (!cart.items || cart.items.length === 0) {
|
|
445
|
+
return {
|
|
446
|
+
lines: [
|
|
447
|
+
empty("nothing staged. your wardrobe is a fresh repo."),
|
|
448
|
+
" start one \u2192 bt browse"
|
|
449
|
+
]
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const region = await client.resolveRegion(config.region);
|
|
453
|
+
const style = brandOf(args);
|
|
454
|
+
const prompts = deps.prompts;
|
|
455
|
+
if (!prompts) {
|
|
456
|
+
throw new Error("checkout requires a prompter");
|
|
457
|
+
}
|
|
458
|
+
const itemCount = cart.items.reduce((n, li) => n + (li.quantity ?? 1), 0);
|
|
459
|
+
const totalLine = fmtPrice(cart.total, cart.currency_code ?? region.currency_code);
|
|
460
|
+
const reviewLines = [header(style, "bt checkout \u2014 staged for commit")];
|
|
461
|
+
for (const li of cart.items) {
|
|
462
|
+
const colour = lineColour(li);
|
|
463
|
+
const colourTag = colour ? ` \xB7 ${colour}` : "";
|
|
464
|
+
reviewLines.push(
|
|
465
|
+
` ${(li.title ?? li.product_title ?? "item").toString()}${colourTag} \xB7 qty ${li.quantity}` + (li.total != null ? ` ${cyan(style, fmtPrice(li.total, cart.currency_code ?? region.currency_code))}` : "")
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
reviewLines.push(` total ${cyan(style, totalLine)} incl. VAT`);
|
|
469
|
+
const print = deps.print ?? ((line) => process.stdout.write(line + "\n"));
|
|
470
|
+
for (const line of reviewLines) {
|
|
471
|
+
print(line);
|
|
472
|
+
}
|
|
473
|
+
const autoProceed = Boolean(args.json) || Boolean(args.yes);
|
|
474
|
+
if (!autoProceed) {
|
|
475
|
+
const proceed = await prompts.confirm(
|
|
476
|
+
`proceed to checkout? ${itemCount} item${itemCount === 1 ? "" : "s"} \xB7 ${totalLine} incl. VAT (y/n):`
|
|
477
|
+
);
|
|
478
|
+
if (!proceed) {
|
|
479
|
+
return {
|
|
480
|
+
lines: [empty("aborted. nothing was committed \u2014 your stage is still held.")]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const allowed = supportedCountries(region);
|
|
485
|
+
const shipsTo = (allowed[0] ?? "").toUpperCase();
|
|
486
|
+
try {
|
|
487
|
+
await client.ensureCartShippingMethod(cartId);
|
|
488
|
+
} catch (e) {
|
|
489
|
+
if (e instanceof RecoverableCartError) {
|
|
490
|
+
return {
|
|
491
|
+
lines: [
|
|
492
|
+
warn("that cart is no longer valid (expired or already committed)."),
|
|
493
|
+
" re-stage \u2192 bt browse then bt stage <slug>"
|
|
494
|
+
]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
lines: [
|
|
499
|
+
fail({
|
|
500
|
+
code: 1,
|
|
501
|
+
what: "couldn't attach a shipping method to your cart.",
|
|
502
|
+
fix: "this region may not have shipping configured yet \u2014 try again shortly, or re-stage."
|
|
503
|
+
})
|
|
504
|
+
]
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const email = (config.email ?? "").trim();
|
|
508
|
+
const { checkout_url, session_id } = await client.mintCheckout(cartId, email || void 0, "cli");
|
|
509
|
+
const save = email ? { email } : {};
|
|
510
|
+
const opened = await deps.open(checkout_url);
|
|
511
|
+
print(" enter your details on the page \u2014 address + card. cards never touch the terminal.");
|
|
512
|
+
const outcome = await deps.poll(session_id);
|
|
513
|
+
const lines = [...opened.lines];
|
|
514
|
+
if (outcome.status === "placed") {
|
|
515
|
+
const displayId = await resolveDisplayId(client, outcome.order_id, email);
|
|
516
|
+
lines.push("");
|
|
517
|
+
lines.push(ok("deploy successful."));
|
|
518
|
+
lines.push(` committed to wardrobe@main`);
|
|
519
|
+
lines.push(` order #${displayId}`);
|
|
520
|
+
lines.push(` total ${cyan(style, totalLine)} (incl. VAT)`);
|
|
521
|
+
if (shipsTo) {
|
|
522
|
+
lines.push(` ships to ${shipsTo} \xB7 ~3\u20136 working days`);
|
|
523
|
+
} else {
|
|
524
|
+
lines.push(` ships ~3\u20136 working days`);
|
|
525
|
+
}
|
|
526
|
+
lines.push(` track bt order ${displayId}`);
|
|
527
|
+
if (email) {
|
|
528
|
+
lines.push(` receipt sent to ${maskEmail(email)}`);
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
lines,
|
|
532
|
+
save,
|
|
533
|
+
json: args.json ? { status: "placed", order_id: outcome.order_id, display_id: displayId } : void 0
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (outcome.status === "expired") {
|
|
537
|
+
lines.push("");
|
|
538
|
+
lines.push(
|
|
539
|
+
warn("your stage expired before checkout completed. nothing was charged.")
|
|
540
|
+
);
|
|
541
|
+
lines.push(" re-stage \u2192 bt stage <slug> then bt checkout");
|
|
542
|
+
return { lines, save, json: args.json ? { status: "expired" } : void 0 };
|
|
543
|
+
}
|
|
544
|
+
lines.push("");
|
|
545
|
+
lines.push(
|
|
546
|
+
fail({
|
|
547
|
+
code: 75,
|
|
548
|
+
what: "checkout handoff timed out \u2014 we didn't see the order complete.",
|
|
549
|
+
money: "nothing was charged; your stage is still held.",
|
|
550
|
+
fix: "bt checkout --resume (or open the link below manually)",
|
|
551
|
+
link: checkout_url
|
|
552
|
+
})
|
|
553
|
+
);
|
|
554
|
+
return { lines, save, json: args.json ? { status: "timeout", checkout_url } : void 0 };
|
|
555
|
+
}
|
|
556
|
+
async function resolveDisplayId(client, orderId, email) {
|
|
557
|
+
if (!orderId) {
|
|
558
|
+
return "\u2014";
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const looked = await client.getOrder(orderId, email);
|
|
562
|
+
const display = looked?.order?.display_id ?? looked?.display_id;
|
|
563
|
+
return display ?? orderId;
|
|
564
|
+
} catch {
|
|
565
|
+
return orderId;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function maskEmail(email) {
|
|
569
|
+
const at = email.indexOf("@");
|
|
570
|
+
if (at <= 0) {
|
|
571
|
+
return "***";
|
|
572
|
+
}
|
|
573
|
+
const local = email.slice(0, at);
|
|
574
|
+
const domain = email.slice(at + 1);
|
|
575
|
+
const head = local[0] ?? "";
|
|
576
|
+
return `${head}***@${domain}`;
|
|
577
|
+
}
|
|
578
|
+
function registerCheckout(program, makeClient2, loadConfig2) {
|
|
579
|
+
const action = async (opts) => {
|
|
580
|
+
const gOpts = program.opts();
|
|
581
|
+
const json = Boolean(gOpts.json);
|
|
582
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
583
|
+
const { makePrompts: makePrompts2 } = await import("./prompts-GTC5VYM6.js");
|
|
584
|
+
const { openCheckout } = await import("./browser-EO5EQUXQ.js");
|
|
585
|
+
const { pollUntilPlaced } = await import("./poll-O3VUD5WV.js");
|
|
586
|
+
const { saveConfig: saveConfig2 } = await import("./config-FXGCWC6Y.js");
|
|
587
|
+
const client = makeClient2();
|
|
588
|
+
const res = await runCheckout(
|
|
589
|
+
client,
|
|
590
|
+
loadConfig2(),
|
|
591
|
+
{ resume: opts.resume, json, brand, yes: opts.yes },
|
|
592
|
+
{
|
|
593
|
+
prompts: makePrompts2(),
|
|
594
|
+
open: (url) => openCheckout(url),
|
|
595
|
+
poll: (sessionId) => pollUntilPlaced(client, sessionId),
|
|
596
|
+
saveConfig: saveConfig2,
|
|
597
|
+
// In --json mode the review is suppressed (machine output only); otherwise print eagerly.
|
|
598
|
+
print: json ? () => {
|
|
599
|
+
} : (line) => process.stdout.write(line + "\n")
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
if (res.save && Object.keys(res.save).length > 0) {
|
|
603
|
+
saveConfig2(res.save);
|
|
604
|
+
}
|
|
605
|
+
printResult(res, json);
|
|
606
|
+
};
|
|
607
|
+
program.command("checkout").alias("commit").description(
|
|
608
|
+
"turn your staged items into an order. opens browser checkout, polls, never touches cards."
|
|
609
|
+
).option("--resume", "resume an in-progress checkout from your existing cart").option("--yes", "skip the proceed confirmation (scripts / CI / agents); implied by --json").action(action);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/commands/order.ts
|
|
613
|
+
var STAGES = ["staged", "committed", "printed", "shipped", "delivered"];
|
|
614
|
+
function stageIndex(o) {
|
|
615
|
+
const f = (o.fulfillment_status ?? "").toLowerCase();
|
|
616
|
+
if (f.includes("delivered")) return 4;
|
|
617
|
+
if (f.includes("shipped")) return 3;
|
|
618
|
+
if (f.includes("fulfilled") || f.includes("printed") || f.includes("partially_shipped")) return 2;
|
|
619
|
+
return 1;
|
|
620
|
+
}
|
|
621
|
+
function timelineGraph(idx) {
|
|
622
|
+
const dots = STAGES.map((_s, i) => i <= idx ? "\u25CF" : "\u25CB").join("\u2500\u2500");
|
|
623
|
+
return dots;
|
|
624
|
+
}
|
|
625
|
+
async function runOrder(client, config, args) {
|
|
626
|
+
const email = config.email;
|
|
627
|
+
if (!email) {
|
|
628
|
+
return {
|
|
629
|
+
lines: [
|
|
630
|
+
fail({
|
|
631
|
+
code: 1,
|
|
632
|
+
what: "order lookup needs your email.",
|
|
633
|
+
fix: "bt config email <you@example.com> (then retry)"
|
|
634
|
+
})
|
|
635
|
+
]
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
const looked = await client.getOrder(args.displayId, email);
|
|
639
|
+
if (!looked) {
|
|
640
|
+
return {
|
|
641
|
+
lines: [
|
|
642
|
+
fail({
|
|
643
|
+
code: 1,
|
|
644
|
+
what: `order ${args.displayId} not found for that email.`,
|
|
645
|
+
fix: "check the order number + the email it was placed with."
|
|
646
|
+
})
|
|
647
|
+
]
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const o = looked.order ?? looked;
|
|
651
|
+
const idx = stageIndex(o);
|
|
652
|
+
const display = o.display_id ?? args.displayId;
|
|
653
|
+
const total = fmtPrice(o.total ?? null, o.currency_code ?? null);
|
|
654
|
+
const items = o.items ?? [];
|
|
655
|
+
const first = items[0];
|
|
656
|
+
const itemLabel = first ? `${first.title ?? "item"}${first.variant_title ? ` \xB7 ${first.variant_title}` : ""}` : "your order";
|
|
657
|
+
const style = brandOf(args);
|
|
658
|
+
const lines = [];
|
|
659
|
+
lines.push(`${display} ${itemLabel} ${cyan(style, timelineGraph(idx))} ${STAGES[idx]}`);
|
|
660
|
+
lines.push(` ${STAGES.join(" \u2192 ")}${idx === 4 ? " \u{1F389}" : ""}`);
|
|
661
|
+
lines.push(` total ${cyan(style, total)} incl. VAT`);
|
|
662
|
+
return {
|
|
663
|
+
lines,
|
|
664
|
+
json: args.json ? { display_id: display, stage: STAGES[idx], total: o.total ?? null, currency: o.currency_code ?? null } : void 0
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function registerOrder(program, makeClient2, loadConfig2) {
|
|
668
|
+
program.command("order").argument("<id>", "the order number (display id)").description("track one order: staged \u2192 committed \u2192 printed \u2192 shipped \u2192 delivered.").action(async (id) => {
|
|
669
|
+
const gOpts = program.opts();
|
|
670
|
+
const json = Boolean(gOpts.json);
|
|
671
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
672
|
+
const res = await runOrder(makeClient2(), loadConfig2(), { displayId: id, json, brand });
|
|
673
|
+
printResult(res, json);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/jwt.ts
|
|
678
|
+
function jwtExpiry(jwt, now = Date.now()) {
|
|
679
|
+
if (!jwt) {
|
|
680
|
+
return { expiresAt: null, expired: null };
|
|
681
|
+
}
|
|
682
|
+
const parts = jwt.split(".");
|
|
683
|
+
if (parts.length < 2 || !parts[1]) {
|
|
684
|
+
return { expiresAt: null, expired: null };
|
|
685
|
+
}
|
|
686
|
+
try {
|
|
687
|
+
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
688
|
+
const payload = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
689
|
+
const exp = typeof payload.exp === "number" ? payload.exp : null;
|
|
690
|
+
if (exp == null) {
|
|
691
|
+
return { expiresAt: null, expired: null };
|
|
692
|
+
}
|
|
693
|
+
const expMs = exp * 1e3;
|
|
694
|
+
return { expiresAt: new Date(expMs).toISOString(), expired: expMs <= now };
|
|
695
|
+
} catch {
|
|
696
|
+
return { expiresAt: null, expired: null };
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/commands/token.ts
|
|
701
|
+
function resolveBridgeToken(env, keychainToken) {
|
|
702
|
+
const fromEnv = env.BT_TOKEN?.trim();
|
|
703
|
+
return fromEnv || keychainToken || null;
|
|
704
|
+
}
|
|
705
|
+
function registerToken(program) {
|
|
706
|
+
program.command("token").description("print your keychain JWT for an MCP config (env BT_TOKEN). a bearer credential \u2014 handle with care.").action(() => {
|
|
707
|
+
const json = Boolean(program.opts().json);
|
|
708
|
+
const token = resolveBridgeToken(process.env, getToken());
|
|
709
|
+
if (!token) {
|
|
710
|
+
process.stderr.write(empty("not logged in \u2014 run bt login first.") + "\n");
|
|
711
|
+
process.exitCode = 1;
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (jwtExpiry(token).expired) {
|
|
715
|
+
process.stderr.write(warn("this token is expired \u2014 run bt login for a fresh one.") + "\n");
|
|
716
|
+
}
|
|
717
|
+
process.stderr.write("\u26A0 bearer credential \u2014 paste only into your own MCP config (env BT_TOKEN), never anywhere public.\n");
|
|
718
|
+
process.stdout.write((json ? JSON.stringify({ token }) : token) + "\n");
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/commands/log-graph.ts
|
|
723
|
+
function orderStage(o) {
|
|
724
|
+
const status = (o.status ?? "").toLowerCase();
|
|
725
|
+
if (status.includes("cancel") || status.includes("refund") || status.includes("revert")) {
|
|
726
|
+
return "reverted";
|
|
727
|
+
}
|
|
728
|
+
const f = (o.fulfillment_status ?? "").toLowerCase();
|
|
729
|
+
if (f.includes("delivered")) return "delivered";
|
|
730
|
+
if (f.includes("shipped")) return "shipped";
|
|
731
|
+
if (f.includes("fulfilled") || f.includes("printed") || f.includes("partially")) return "printed";
|
|
732
|
+
return "committed";
|
|
733
|
+
}
|
|
734
|
+
function shortHash(o) {
|
|
735
|
+
const seed = `${o.display_id ?? ""}:${o.created_at ?? ""}`;
|
|
736
|
+
let h = 2166136261;
|
|
737
|
+
for (let i = 0; i < seed.length; i++) {
|
|
738
|
+
h ^= seed.charCodeAt(i);
|
|
739
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
740
|
+
}
|
|
741
|
+
return h.toString(16).padStart(8, "0").slice(0, 7);
|
|
742
|
+
}
|
|
743
|
+
function label(o) {
|
|
744
|
+
const items = o.items ?? [];
|
|
745
|
+
const first = items[0];
|
|
746
|
+
if (!first) return "order";
|
|
747
|
+
return `${first.title ?? "order"}${first.variant_title ? ` \xB7 ${first.variant_title}` : ""}`;
|
|
748
|
+
}
|
|
749
|
+
function hasLiveHead(orders) {
|
|
750
|
+
return orders.some((o) => orderStage(o) !== "reverted");
|
|
751
|
+
}
|
|
752
|
+
function renderOneline(orders, style) {
|
|
753
|
+
if (orders.length === 0) return [];
|
|
754
|
+
const live = hasLiveHead(orders);
|
|
755
|
+
const headIdx = live ? orders.findIndex((o) => orderStage(o) !== "reverted") : -1;
|
|
756
|
+
const lines = [];
|
|
757
|
+
if (!live) {
|
|
758
|
+
lines.push(dim(style, "(detached HEAD) you have no committed fits. literally nothing to wear. we don't judge."));
|
|
759
|
+
}
|
|
760
|
+
orders.forEach((o, i) => {
|
|
761
|
+
const hash = shortHash(o);
|
|
762
|
+
const stage = orderStage(o);
|
|
763
|
+
const ref = i === headIdx ? "(HEAD -> main) " : "";
|
|
764
|
+
const tail = stage === "delivered" ? "delivered \u{1F389}" : stage;
|
|
765
|
+
lines.push(`${hash} ${ref}${label(o).padEnd(34)} ${tail}`);
|
|
766
|
+
});
|
|
767
|
+
return lines;
|
|
768
|
+
}
|
|
769
|
+
function renderGraph(orders, style) {
|
|
770
|
+
if (orders.length === 0) return [];
|
|
771
|
+
const live = hasLiveHead(orders);
|
|
772
|
+
const headIdx = live ? orders.findIndex((o) => orderStage(o) !== "reverted") : -1;
|
|
773
|
+
const lines = [];
|
|
774
|
+
if (!live) {
|
|
775
|
+
lines.push(dim(style, "(detached HEAD) no committed fits. literally nothing to wear. we don't judge."));
|
|
776
|
+
}
|
|
777
|
+
orders.forEach((o, i) => {
|
|
778
|
+
const hash = shortHash(o);
|
|
779
|
+
const stage = orderStage(o);
|
|
780
|
+
const ref = i === headIdx ? " (HEAD -> main)" : "";
|
|
781
|
+
const node = stage === "delivered" ? green(style, "*") : "*";
|
|
782
|
+
const tail = stage === "delivered" ? `${stage} ${green(style, "\u{1F389}")}` : stage;
|
|
783
|
+
lines.push(`${node} ${hash}${ref} ${label(o).padEnd(32)} ${tail}`);
|
|
784
|
+
if (i < orders.length - 1) {
|
|
785
|
+
lines.push("|");
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
return lines;
|
|
789
|
+
}
|
|
790
|
+
function renderFlat(orders) {
|
|
791
|
+
return orders.map((o) => {
|
|
792
|
+
const display = String(o.display_id ?? "");
|
|
793
|
+
return `${display.padEnd(8)} ${label(o).padEnd(36)} ${orderStage(o)}`;
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/commands/log.ts
|
|
798
|
+
var EMPTY_STATE2 = "no commits yet. your wardrobe is a fresh repo.";
|
|
799
|
+
async function runLog(client, config, args) {
|
|
800
|
+
const history = client;
|
|
801
|
+
const loggedIn = args.loggedIn ?? Boolean(config.email);
|
|
802
|
+
if (!loggedIn || typeof history.listOrders !== "function") {
|
|
803
|
+
return {
|
|
804
|
+
lines: [empty(EMPTY_STATE2), " start one \u2192 bt browse"],
|
|
805
|
+
json: args.json ? { commits: [] } : void 0
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const orders = await history.listOrders();
|
|
809
|
+
if (!orders || orders.length === 0) {
|
|
810
|
+
return {
|
|
811
|
+
lines: [empty(EMPTY_STATE2), " start one \u2192 bt browse"],
|
|
812
|
+
json: args.json ? { commits: [] } : void 0
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
const style = brandOf(args);
|
|
816
|
+
const lines = args.graph ? renderGraph(orders, style) : args.oneline ? renderOneline(orders, style) : renderFlat(orders);
|
|
817
|
+
return {
|
|
818
|
+
lines,
|
|
819
|
+
json: args.json ? { commits: orders } : void 0
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function registerLog(program, makeClient2, loadConfig2) {
|
|
823
|
+
program.command("log").option("--oneline", "compact one-line-per-order format").option("--graph", "graph format").description("your order history as a git log.").action(async (opts) => {
|
|
824
|
+
const gOpts = program.opts();
|
|
825
|
+
const json = Boolean(gOpts.json);
|
|
826
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
827
|
+
const loggedIn = Boolean(resolveBridgeToken(process.env, getToken()));
|
|
828
|
+
const res = await runLog(makeClient2(), loadConfig2(), { oneline: opts.oneline, graph: opts.graph, json, loggedIn, brand });
|
|
829
|
+
printResult(res, json);
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/commands/status.ts
|
|
834
|
+
var FRESH_REPO = "no commits yet. your wardrobe is a fresh repo.";
|
|
835
|
+
async function runStatus(client, config, args) {
|
|
836
|
+
if (!config.cart_id) {
|
|
837
|
+
return {
|
|
838
|
+
lines: [
|
|
839
|
+
"on branch main",
|
|
840
|
+
empty(FRESH_REPO),
|
|
841
|
+
" nothing staged. clean tree \u2192 bt browse"
|
|
842
|
+
],
|
|
843
|
+
json: args.json ? { branch: "main", staged: [], clean: true } : void 0
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const style = brandOf(args);
|
|
847
|
+
try {
|
|
848
|
+
const cart = await client.getCart(config.cart_id);
|
|
849
|
+
const items = cart.items ?? [];
|
|
850
|
+
const lines = ["on branch main"];
|
|
851
|
+
if (items.length === 0) {
|
|
852
|
+
lines.push(empty(FRESH_REPO));
|
|
853
|
+
lines.push(" nothing staged. clean tree \u2192 bt browse");
|
|
854
|
+
} else {
|
|
855
|
+
lines.push("staged for commit:");
|
|
856
|
+
items.forEach((li, i) => {
|
|
857
|
+
lines.push(formatCartLine(li, i, cart.currency_code, style));
|
|
858
|
+
});
|
|
859
|
+
if (cart.total != null) {
|
|
860
|
+
lines.push(` total ${cyan(style, fmtPrice(cart.total, cart.currency_code))} incl. VAT`);
|
|
861
|
+
}
|
|
862
|
+
lines.push(" remove one \u2192 bt unstage <#> \xB7 commit \u2192 bt commit");
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
lines,
|
|
866
|
+
json: args.json ? { branch: "main", staged: items, total: cart.total ?? null, clean: items.length === 0 } : void 0
|
|
867
|
+
};
|
|
868
|
+
} catch (e) {
|
|
869
|
+
if (e instanceof RecoverableCartError) {
|
|
870
|
+
return {
|
|
871
|
+
lines: [
|
|
872
|
+
"on branch main",
|
|
873
|
+
warn("your staged cart is no longer valid (expired or already committed)."),
|
|
874
|
+
empty(FRESH_REPO),
|
|
875
|
+
" re-stage \u2192 bt browse"
|
|
876
|
+
],
|
|
877
|
+
json: args.json ? { branch: "main", staged: [], clean: true, stale: true } : void 0
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
throw e;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function registerStatus(program, makeClient2, loadConfig2) {
|
|
884
|
+
program.command("status").description("working-tree state: what's staged, what's in flight.").action(async () => {
|
|
885
|
+
const gOpts = program.opts();
|
|
886
|
+
const json = Boolean(gOpts.json);
|
|
887
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
888
|
+
const res = await runStatus(makeClient2(), loadConfig2(), { json, brand });
|
|
889
|
+
printResult(res, json);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/commands/unstage.ts
|
|
894
|
+
var FRESH_REPO2 = "no commits yet. your wardrobe is a fresh repo.";
|
|
895
|
+
async function runUnstage(client, config, args) {
|
|
896
|
+
if (!config.cart_id) {
|
|
897
|
+
return {
|
|
898
|
+
lines: [empty(FRESH_REPO2), " nothing staged to unstage \u2192 bt browse"],
|
|
899
|
+
json: args.json ? { cart_id: null, items: [] } : void 0
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
const remover = client;
|
|
903
|
+
if (typeof remover.removeLineItem !== "function") {
|
|
904
|
+
return {
|
|
905
|
+
lines: [
|
|
906
|
+
warn("unstage isn't wired to this client build yet."),
|
|
907
|
+
" for now: bt forget clears your local cart, or re-stage to overwrite."
|
|
908
|
+
]
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const before = await client.getCart(config.cart_id);
|
|
913
|
+
const beforeItems = before.items ?? [];
|
|
914
|
+
if (beforeItems.length === 0) {
|
|
915
|
+
return {
|
|
916
|
+
lines: [empty(FRESH_REPO2), " clean tree \u2192 bt browse"],
|
|
917
|
+
json: args.json ? { cart_id: before.id, items: [] } : void 0
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
const target = resolveLineItemRef(beforeItems, args.itemId);
|
|
921
|
+
if (!target) {
|
|
922
|
+
return {
|
|
923
|
+
lines: [
|
|
924
|
+
warn(`no such item in your cart: "${args.itemId}"`),
|
|
925
|
+
" see what's staged (with its #) \u2192 bt cart"
|
|
926
|
+
],
|
|
927
|
+
json: args.json ? { cart_id: before.id, removed: null, items: beforeItems } : void 0
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
await remover.removeLineItem(config.cart_id, target.id);
|
|
931
|
+
const after = await client.getCart(config.cart_id);
|
|
932
|
+
const afterItems = after.items ?? [];
|
|
933
|
+
const stillThere = afterItems.some((li) => li.id === target.id);
|
|
934
|
+
if (stillThere) {
|
|
935
|
+
return {
|
|
936
|
+
lines: [
|
|
937
|
+
fail({
|
|
938
|
+
code: 1,
|
|
939
|
+
what: `couldn't unstage "${(target.title ?? target.product_title ?? args.itemId).toString()}" \u2014 it's still in your cart.`,
|
|
940
|
+
fix: "check what's staged \u2192 bt cart (then try the # shown, or bt forget to clear)"
|
|
941
|
+
})
|
|
942
|
+
],
|
|
943
|
+
json: args.json ? { cart_id: after.id, removed: null, items: afterItems } : void 0
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
const removedTitle = (target.title ?? target.product_title ?? "item").toString();
|
|
947
|
+
const lines = [ok(`unstaged ${removedTitle}.`)];
|
|
948
|
+
if (afterItems.length === 0) {
|
|
949
|
+
lines.push(empty(FRESH_REPO2));
|
|
950
|
+
lines.push(" clean tree \u2192 bt browse");
|
|
951
|
+
} else {
|
|
952
|
+
lines.push("still staged:");
|
|
953
|
+
afterItems.forEach((li, i) => {
|
|
954
|
+
lines.push(formatCartLine(li, i, after.currency_code));
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
lines,
|
|
959
|
+
json: args.json ? { cart_id: after.id, removed: target.id, items: afterItems } : void 0
|
|
960
|
+
};
|
|
961
|
+
} catch (e) {
|
|
962
|
+
if (e instanceof RecoverableCartError) {
|
|
963
|
+
return {
|
|
964
|
+
lines: [
|
|
965
|
+
warn("that cart is no longer valid (expired or already committed)."),
|
|
966
|
+
empty(FRESH_REPO2),
|
|
967
|
+
" re-stage \u2192 bt browse"
|
|
968
|
+
]
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
throw e;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function registerUnstage(program, makeClient2, loadConfig2) {
|
|
975
|
+
program.command("unstage").alias("reset").argument("<ref>", "the staged item: its # (from bt cart), its line-item id, or its name").description("remove a staged item from the cart (git reset).").action(async (ref) => {
|
|
976
|
+
const json = Boolean(program.opts().json);
|
|
977
|
+
const res = await runUnstage(makeClient2(), loadConfig2(), { itemId: ref, json });
|
|
978
|
+
printResult(res, json);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/commands/whoami.ts
|
|
983
|
+
var NEXT_TIER = {
|
|
984
|
+
contributor: { at: 5, name: "maintainer", pct: 10 },
|
|
985
|
+
maintainer: { at: 20, name: "core", pct: 15 },
|
|
986
|
+
core: { at: 0, name: "core", pct: 20 }
|
|
987
|
+
};
|
|
988
|
+
var GRAPH_CAP = 20;
|
|
989
|
+
var CARD_WIDTH = 58;
|
|
990
|
+
function profileCard(p, style, handle) {
|
|
991
|
+
const tier = p.bt_tier ?? "contributor";
|
|
992
|
+
const commits = p.bt_commit_count ?? 0;
|
|
993
|
+
const next = NEXT_TIER[tier];
|
|
994
|
+
const isMax = tier === "core" || !next;
|
|
995
|
+
const toNext = next && !isMax ? Math.max(0, next.at - commits) : 0;
|
|
996
|
+
const standing = p.standing_pct ?? NEXT_TIER[tier]?.pct ?? null;
|
|
997
|
+
const inner = [];
|
|
998
|
+
inner.push(`${handle ? `${handle} \xB7 ` : ""}${cyan(style, tier)}`);
|
|
999
|
+
inner.push(rule(CARD_WIDTH - 1));
|
|
1000
|
+
if (isMax) {
|
|
1001
|
+
inner.push(`tier ${tier.padEnd(13)}max standing \u2014 ${standing ?? 20}%`);
|
|
1002
|
+
} else {
|
|
1003
|
+
inner.push(`tier ${tier.padEnd(13)}${toNext} more commit${toNext === 1 ? "" : "s"} to ${next.name}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (commits === 0) {
|
|
1006
|
+
inner.push(`commits ${String(commits).padEnd(13)}your repo's empty \u2014 everyone starts at 0`);
|
|
1007
|
+
} else {
|
|
1008
|
+
inner.push(`commits ${String(commits).padEnd(13)}order${commits === 1 ? "" : "s"} committed`);
|
|
1009
|
+
}
|
|
1010
|
+
if (standing != null) {
|
|
1011
|
+
inner.push(`standing ${cyan(style, `${standing}%`.padEnd(13))}applied at every commit`);
|
|
1012
|
+
}
|
|
1013
|
+
const bar = contributionBar(style, commits, GRAPH_CAP);
|
|
1014
|
+
const graphLabel = commits === 0 ? "no commits yet" : `${commits} contribution${commits === 1 ? "" : "s"}`;
|
|
1015
|
+
inner.push(`graph ${bar} ${graphLabel}`);
|
|
1016
|
+
if (p.bt_badges && p.bt_badges.length > 0) {
|
|
1017
|
+
inner.push(`badges ${p.bt_badges.join(" \xB7 ")}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (p.bt_connected_at) {
|
|
1020
|
+
inner.push(`since contributor since ${p.bt_connected_at.slice(0, 10)}`);
|
|
1021
|
+
}
|
|
1022
|
+
inner.push(rule(CARD_WIDTH - 1));
|
|
1023
|
+
if (isMax) {
|
|
1024
|
+
inner.push(`in ${green(style, "AUTHORS")}. earned, not bought.`);
|
|
1025
|
+
} else if (commits === 0) {
|
|
1026
|
+
inner.push("your first commit makes this worth screenshotting \u2192 bt browse");
|
|
1027
|
+
} else {
|
|
1028
|
+
inner.push(`${toNext} more to ${next.name} \u2192 bt browse \xB7 earned, not bought`);
|
|
1029
|
+
}
|
|
1030
|
+
return box(inner, { width: CARD_WIDTH, title: "bt whoami", style });
|
|
1031
|
+
}
|
|
1032
|
+
function handleFromConfig(config) {
|
|
1033
|
+
const email = config.email;
|
|
1034
|
+
if (!email || typeof email !== "string") return void 0;
|
|
1035
|
+
const local = email.split("@")[0]?.trim();
|
|
1036
|
+
return local || void 0;
|
|
1037
|
+
}
|
|
1038
|
+
async function runWhoami(client, config, args, token) {
|
|
1039
|
+
const style = brandOf(args);
|
|
1040
|
+
const handle = args.handle ?? handleFromConfig(config);
|
|
1041
|
+
const { expiresAt, expired } = jwtExpiry(token);
|
|
1042
|
+
try {
|
|
1043
|
+
const me = await client.whoami();
|
|
1044
|
+
if (args.json) {
|
|
1045
|
+
return {
|
|
1046
|
+
lines: profileCard(me, style, handle),
|
|
1047
|
+
json: {
|
|
1048
|
+
authenticated: true,
|
|
1049
|
+
tier: me.bt_tier,
|
|
1050
|
+
commits: me.bt_commit_count,
|
|
1051
|
+
badges: me.bt_badges,
|
|
1052
|
+
standing_pct: me.standing_pct ?? null,
|
|
1053
|
+
connected_at: me.bt_connected_at,
|
|
1054
|
+
token_expires_at: expiresAt,
|
|
1055
|
+
token_expired: expired
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return { lines: profileCard(me, style, handle) };
|
|
1060
|
+
} catch (e) {
|
|
1061
|
+
if (e instanceof StoreApiError && (e.status === 401 || e.status === 403)) {
|
|
1062
|
+
return {
|
|
1063
|
+
lines: [
|
|
1064
|
+
empty("you're browsing as a guest \u2014 not connected yet."),
|
|
1065
|
+
" connect \u2192 bt login (the contributor on-ramp for this channel)"
|
|
1066
|
+
],
|
|
1067
|
+
json: args.json ? {
|
|
1068
|
+
authenticated: false,
|
|
1069
|
+
tier: null,
|
|
1070
|
+
connected: false,
|
|
1071
|
+
token_expires_at: expiresAt,
|
|
1072
|
+
token_expired: expired
|
|
1073
|
+
} : void 0
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
throw e;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
function registerWhoami(program, makeClient2, loadConfig2) {
|
|
1080
|
+
program.command("whoami").description("your contribution profile + standing.").action(async () => {
|
|
1081
|
+
const gOpts = program.opts();
|
|
1082
|
+
const json = Boolean(gOpts.json);
|
|
1083
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
1084
|
+
const res = await runWhoami(makeClient2(), loadConfig2(), { json, brand }, getToken());
|
|
1085
|
+
printResult(res, json);
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/commands/drops.ts
|
|
1090
|
+
function mergesIn(nextUntil) {
|
|
1091
|
+
if (!nextUntil) return "on next";
|
|
1092
|
+
const until = Date.parse(nextUntil);
|
|
1093
|
+
if (Number.isNaN(until)) return "on next";
|
|
1094
|
+
const ms = until - Date.now();
|
|
1095
|
+
if (ms <= 0) return "merging now";
|
|
1096
|
+
const hours = Math.round(ms / (60 * 60 * 1e3));
|
|
1097
|
+
if (hours < 48) return `merges to main in ${hours}h`;
|
|
1098
|
+
return `merges to main in ${Math.round(hours / 24)}d`;
|
|
1099
|
+
}
|
|
1100
|
+
async function runDrops(client, _config, args) {
|
|
1101
|
+
const drops = await client.listDrops();
|
|
1102
|
+
if (!drops || drops.length === 0) {
|
|
1103
|
+
return {
|
|
1104
|
+
lines: [
|
|
1105
|
+
empty("nothing on next right now. main is the latest."),
|
|
1106
|
+
" browse main \u2192 bt browse"
|
|
1107
|
+
],
|
|
1108
|
+
json: args.json ? { drops: [] } : void 0
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
const lines = ["on next \u2014 terminal-only, not yet on the web:"];
|
|
1112
|
+
for (const d of drops) {
|
|
1113
|
+
lines.push(
|
|
1114
|
+
[
|
|
1115
|
+
` ${(d.handle ?? d.id).padEnd(32)}`,
|
|
1116
|
+
fmtPrice(d.price, null).padEnd(10),
|
|
1117
|
+
mergesIn(d.next_until)
|
|
1118
|
+
].join(" ").trimEnd()
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
lines.push(" first dibs \u2192 bt stage <slug> then bt checkout");
|
|
1122
|
+
return {
|
|
1123
|
+
lines,
|
|
1124
|
+
json: args.json ? drops.map((d) => ({
|
|
1125
|
+
id: d.id,
|
|
1126
|
+
handle: d.handle,
|
|
1127
|
+
title: d.title,
|
|
1128
|
+
price: d.price,
|
|
1129
|
+
next_until: d.next_until
|
|
1130
|
+
})) : void 0
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
function registerDrops(program, makeClient2, loadConfig2) {
|
|
1134
|
+
program.command("drops").alias("next").description("what's on next before it hits the web.").action(async () => {
|
|
1135
|
+
const json = Boolean(program.opts().json);
|
|
1136
|
+
const res = await runDrops(makeClient2(), loadConfig2(), { json });
|
|
1137
|
+
printResult(res, json);
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/commands/connect.ts
|
|
1142
|
+
async function runConnect(args) {
|
|
1143
|
+
const lines = [
|
|
1144
|
+
"connect your wardrobe \u2014 become a contributor.",
|
|
1145
|
+
" connecting authenticates you and unlocks:",
|
|
1146
|
+
" \xB7 10% standing (applied at every commit)",
|
|
1147
|
+
" \xB7 access to next (drops before the web sees them)",
|
|
1148
|
+
" \xB7 your wardrobe repo + the contributor badge",
|
|
1149
|
+
"",
|
|
1150
|
+
" connect now \u2192 bt login",
|
|
1151
|
+
" (we email you a token to paste; it lives in your OS keychain, never in plaintext config.)"
|
|
1152
|
+
];
|
|
1153
|
+
return {
|
|
1154
|
+
lines,
|
|
1155
|
+
json: args.json ? { connected: false, login_command: "bt login" } : void 0
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function registerConnect(program) {
|
|
1159
|
+
program.command("connect").description("the contributor on-ramp \u2014 what connecting unlocks. run bt login to connect.").action(async () => {
|
|
1160
|
+
const json = Boolean(program.opts().json);
|
|
1161
|
+
const res = await runConnect({ json });
|
|
1162
|
+
printResult(res, json);
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/commands/login.ts
|
|
1167
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1168
|
+
async function runLogin(client, prompts, keychain, config, args) {
|
|
1169
|
+
let email = (args.email ?? "").trim();
|
|
1170
|
+
if (!email) {
|
|
1171
|
+
email = (await prompts.ask("email", { default: config.email })).trim();
|
|
1172
|
+
}
|
|
1173
|
+
if (!email || !EMAIL_RE.test(email)) {
|
|
1174
|
+
return {
|
|
1175
|
+
lines: [warn("that doesn't look like an email. try: bt login --email you@dev.to")]
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
await client.requestMagicLink(email);
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
if (e instanceof StoreApiError) {
|
|
1182
|
+
return { lines: [warn("couldn't start login right now. try again in a moment.")] };
|
|
1183
|
+
}
|
|
1184
|
+
throw e;
|
|
1185
|
+
}
|
|
1186
|
+
const waitLines = [
|
|
1187
|
+
ok("check your inbox \u2014 a single-use login token is on its way."),
|
|
1188
|
+
empty(" no password. the token expires shortly. i'll wait."),
|
|
1189
|
+
empty(" (we email a token to paste, not a clickable link \u2014 so it never lands in your shell history or a browser log.)")
|
|
1190
|
+
];
|
|
1191
|
+
let token = (args.token ?? "").trim();
|
|
1192
|
+
if (!token) {
|
|
1193
|
+
token = (await prompts.ask("paste the token from the email")).trim();
|
|
1194
|
+
}
|
|
1195
|
+
if (!token) {
|
|
1196
|
+
return { lines: [...waitLines, "", warn("no token entered \u2014 run bt login again when you have the email.")] };
|
|
1197
|
+
}
|
|
1198
|
+
let result;
|
|
1199
|
+
try {
|
|
1200
|
+
result = await client.verifyMagicLink(token);
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
if (e instanceof StoreApiError && (e.status === 401 || e.status === 403)) {
|
|
1203
|
+
return {
|
|
1204
|
+
lines: [...waitLines, "", warn("that link is invalid or expired. run bt login to get a fresh one.")]
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
if (e instanceof StoreApiError) {
|
|
1208
|
+
return { lines: [...waitLines, "", warn("login couldn't complete. try again.")] };
|
|
1209
|
+
}
|
|
1210
|
+
throw e;
|
|
1211
|
+
}
|
|
1212
|
+
keychain.setToken(result.jwt);
|
|
1213
|
+
return {
|
|
1214
|
+
lines: [
|
|
1215
|
+
ok("authenticated. welcome \u2014 you're a contributor now."),
|
|
1216
|
+
` ${GLYPH.arrow} your token is in your OS keychain (never plaintext).`,
|
|
1217
|
+
` ${GLYPH.arrow} see your standing \u2192 bt whoami`
|
|
1218
|
+
],
|
|
1219
|
+
save: { email },
|
|
1220
|
+
json: args.json ? { connected: true, customer_id: result.customer.id } : void 0
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function registerLogin(program, makeClient2, loadConfig2, prompts, keychain, persist) {
|
|
1224
|
+
program.command("login").description("connect your wardrobe with a passwordless magic-link (no password, ever).").option("--email <email>", "your email (skips the prompt)").action(async (opts) => {
|
|
1225
|
+
const json = Boolean(program.opts().json);
|
|
1226
|
+
const envToken = process.env.BT_MAGIC_TOKEN?.trim() || void 0;
|
|
1227
|
+
const res = await runLogin(makeClient2(), prompts, keychain, loadConfig2(), {
|
|
1228
|
+
email: opts.email,
|
|
1229
|
+
token: envToken,
|
|
1230
|
+
json
|
|
1231
|
+
});
|
|
1232
|
+
if (res.save) {
|
|
1233
|
+
persist(res.save);
|
|
1234
|
+
}
|
|
1235
|
+
if (json && res.json !== void 0) {
|
|
1236
|
+
process.stdout.write(JSON.stringify(res.json) + "\n");
|
|
1237
|
+
} else {
|
|
1238
|
+
process.stdout.write(res.lines.join("\n") + "\n");
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// src/commands/logout.ts
|
|
1244
|
+
async function runLogout(keychain) {
|
|
1245
|
+
keychain.deleteToken();
|
|
1246
|
+
return {
|
|
1247
|
+
lines: [
|
|
1248
|
+
ok("logged out. your token is wiped from this machine's keychain."),
|
|
1249
|
+
empty(" (server tokens are short-lived by design \u2014 they expire on their own.)")
|
|
1250
|
+
]
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
function registerLogout(program, keychain) {
|
|
1254
|
+
program.command("logout").description("clear your local session (wipes the keychain token).").action(async () => {
|
|
1255
|
+
const res = await runLogout(keychain);
|
|
1256
|
+
process.stdout.write(res.lines.join("\n") + "\n");
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/commands/drop.ts
|
|
1261
|
+
var SCRIPT = {
|
|
1262
|
+
create: "drop:create",
|
|
1263
|
+
verify: "drop:verify",
|
|
1264
|
+
promote: "drop:promote",
|
|
1265
|
+
retire: "drop:retire"
|
|
1266
|
+
};
|
|
1267
|
+
async function runDrop(args, exec) {
|
|
1268
|
+
if (!args.operator) {
|
|
1269
|
+
return {
|
|
1270
|
+
lines: [
|
|
1271
|
+
warn("bt drop is an operator command (catalog mutation)."),
|
|
1272
|
+
empty(" run it from the backend with BT_OPERATOR=1, or use the backend npm scripts directly.")
|
|
1273
|
+
]
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
const sub = (args.sub ?? "").trim();
|
|
1277
|
+
if (!sub || !SCRIPT[sub]) {
|
|
1278
|
+
return { lines: [warn("usage: bt drop <create|verify|promote|retire> --drop-id <id> [--motif <slug>]")] };
|
|
1279
|
+
}
|
|
1280
|
+
if (!args.dropId || !args.dropId.trim()) {
|
|
1281
|
+
return { lines: [warn(`bt drop ${sub}: --drop-id <id> is required.`)] };
|
|
1282
|
+
}
|
|
1283
|
+
if (sub === "create" && (!args.motif || !args.motif.trim())) {
|
|
1284
|
+
return { lines: [warn("bt drop create: --motif <slug> is required.")] };
|
|
1285
|
+
}
|
|
1286
|
+
const env = { DROP_ID: args.dropId.trim() };
|
|
1287
|
+
if (sub === "create" && args.motif) {
|
|
1288
|
+
env.MOTIF_SLUG = args.motif.trim();
|
|
1289
|
+
}
|
|
1290
|
+
if (sub === "verify" && args.stage) {
|
|
1291
|
+
env.DROP_EXPECT_STAGE = args.stage.trim();
|
|
1292
|
+
}
|
|
1293
|
+
const code = await exec.run(SCRIPT[sub], env);
|
|
1294
|
+
if (code !== 0) {
|
|
1295
|
+
return {
|
|
1296
|
+
lines: [warn(`drop ${sub} reported a problem (exit ${code}). check the backend logs / ops alert.`)],
|
|
1297
|
+
json: args.json ? { sub, drop_id: args.dropId, exit: code } : void 0
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
const done = {
|
|
1301
|
+
create: "drop created on next (terminal-only ~48h).",
|
|
1302
|
+
verify: "drop verified (visible to the channel, invisible to the web).",
|
|
1303
|
+
promote: "drop promoted next \u2192 main (it's on the web now).",
|
|
1304
|
+
retire: "drop retired (archived)."
|
|
1305
|
+
};
|
|
1306
|
+
return {
|
|
1307
|
+
lines: [ok(done[sub])],
|
|
1308
|
+
json: args.json ? { sub, drop_id: args.dropId, exit: 0 } : void 0
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function registerDrop(program, exec) {
|
|
1312
|
+
program.command("drop", { hidden: true }).argument("<sub>", "create | verify | promote | retire").option("--motif <slug>", "motif slug (create)").option("--drop-id <id>", "the drop id").option("--stage <stage>", "expected stage for verify (next|main)").description("operator: manage a drop's lifecycle (create/verify/promote/retire).").action(async (sub, opts) => {
|
|
1313
|
+
const json = Boolean(program.opts().json);
|
|
1314
|
+
const operator = process.env.BT_OPERATOR === "1";
|
|
1315
|
+
process.stdout.write(working(`drop ${sub}\u2026`) + "\n");
|
|
1316
|
+
const res = await runDrop(
|
|
1317
|
+
{ sub, motif: opts.motif, dropId: opts.dropId, stage: opts.stage, operator, json },
|
|
1318
|
+
exec
|
|
1319
|
+
);
|
|
1320
|
+
if (json && res.json !== void 0) {
|
|
1321
|
+
process.stdout.write(JSON.stringify(res.json) + "\n");
|
|
1322
|
+
} else {
|
|
1323
|
+
process.stdout.write(res.lines.join("\n") + "\n");
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/commands/init.ts
|
|
1329
|
+
var HAPPY_PATH = ["bt browse", "bt stage <handle> --size L", "bt checkout"];
|
|
1330
|
+
var DOCS_URL = "https://binaryandthread.com/docs";
|
|
1331
|
+
function runInit(args) {
|
|
1332
|
+
const style = brandOf(args);
|
|
1333
|
+
const lines = [
|
|
1334
|
+
header(style, "bt \u2014 binary & thread, the terminal store"),
|
|
1335
|
+
" buy heavyweight pieces from your terminal. zero setup \u2014 you're already pointed at the live store.",
|
|
1336
|
+
"",
|
|
1337
|
+
" the happy path:",
|
|
1338
|
+
" bt browse # the catalog (arrow keys to pick)",
|
|
1339
|
+
" bt stage <handle> --size L # add to your cart",
|
|
1340
|
+
" bt checkout # pay in your browser \u2014 cards never touch the terminal",
|
|
1341
|
+
"",
|
|
1342
|
+
" optional:",
|
|
1343
|
+
" bt login # contributor standing + order history",
|
|
1344
|
+
" bt whoami # your profile",
|
|
1345
|
+
` docs \u2192 ${DOCS_URL}`
|
|
1346
|
+
];
|
|
1347
|
+
return {
|
|
1348
|
+
lines,
|
|
1349
|
+
json: args.json ? { happy_path: HAPPY_PATH, docs: DOCS_URL, zero_setup: true } : void 0
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function registerInit(program) {
|
|
1353
|
+
program.command("init").description("first-run welcome + the 3-command happy path.").action(async () => {
|
|
1354
|
+
const gOpts = program.opts();
|
|
1355
|
+
const json = Boolean(gOpts.json);
|
|
1356
|
+
const { makeBrand: makeBrand2 } = await import("./brand-TMOYK6QE.js");
|
|
1357
|
+
const brand = json ? makeBrand2({ noColor: true }) : makeBrand2({ noColor: gOpts.color === false });
|
|
1358
|
+
printResult(runInit({ json, brand }), json);
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/commands/buy.ts
|
|
1363
|
+
async function runBuy(client, config, args, deps) {
|
|
1364
|
+
const staged = await runStage(client, config, {
|
|
1365
|
+
handle: args.handle,
|
|
1366
|
+
size: args.size,
|
|
1367
|
+
colour: args.colour,
|
|
1368
|
+
json: args.json,
|
|
1369
|
+
brand: args.brand
|
|
1370
|
+
});
|
|
1371
|
+
const cartId = staged.save?.cart_id;
|
|
1372
|
+
if (!cartId) {
|
|
1373
|
+
return staged;
|
|
1374
|
+
}
|
|
1375
|
+
const checked = await runCheckout(
|
|
1376
|
+
client,
|
|
1377
|
+
{ ...config, cart_id: cartId },
|
|
1378
|
+
{ json: args.json, brand: args.brand, yes: true },
|
|
1379
|
+
deps
|
|
1380
|
+
);
|
|
1381
|
+
return {
|
|
1382
|
+
lines: [...staged.lines, "", ...checked.lines],
|
|
1383
|
+
save: { ...staged.save ?? {}, ...checked.save ?? {} },
|
|
1384
|
+
json: args.json ? { cart_id: cartId, checkout: checked.json ?? null } : void 0
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function registerBuy(program, makeClient2, loadConfig2) {
|
|
1388
|
+
program.command("buy").argument("<handle>", "the product handle/slug").option("--size <size>", "garment size").option("--colour <colour>", "colour, e.g. white / black").description("stage + checkout in one \u2014 reserve, then open browser checkout. cards never touch the terminal.").action(async (handle, opts) => {
|
|
1389
|
+
const gOpts = program.opts();
|
|
1390
|
+
const json = Boolean(gOpts.json);
|
|
1391
|
+
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: gOpts.color === false });
|
|
1392
|
+
const { makePrompts: makePrompts2 } = await import("./prompts-GTC5VYM6.js");
|
|
1393
|
+
const { openCheckout } = await import("./browser-EO5EQUXQ.js");
|
|
1394
|
+
const { pollUntilPlaced } = await import("./poll-O3VUD5WV.js");
|
|
1395
|
+
const { saveConfig: saveConfig2 } = await import("./config-FXGCWC6Y.js");
|
|
1396
|
+
const client = makeClient2();
|
|
1397
|
+
const res = await runBuy(client, loadConfig2(), { handle, size: opts.size, colour: opts.colour, json, brand }, {
|
|
1398
|
+
prompts: makePrompts2(),
|
|
1399
|
+
open: (url) => openCheckout(url),
|
|
1400
|
+
poll: (sessionId) => pollUntilPlaced(client, sessionId),
|
|
1401
|
+
saveConfig: saveConfig2,
|
|
1402
|
+
print: json ? () => {
|
|
1403
|
+
} : (line) => process.stdout.write(line + "\n")
|
|
1404
|
+
});
|
|
1405
|
+
if (res.save && Object.keys(res.save).length > 0) {
|
|
1406
|
+
saveConfig2(res.save);
|
|
1407
|
+
}
|
|
1408
|
+
printResult(res, json);
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/commands/config.ts
|
|
1413
|
+
var SETTABLE = ["email", "region", "defaultSize", "defaultColour", "defaultBranch"];
|
|
1414
|
+
function runConfig(args, deps) {
|
|
1415
|
+
const cfg = deps.load();
|
|
1416
|
+
const sub = (args.sub ?? "get").toLowerCase();
|
|
1417
|
+
if (sub === "get") {
|
|
1418
|
+
if (args.key) {
|
|
1419
|
+
const v = cfg[args.key];
|
|
1420
|
+
return {
|
|
1421
|
+
lines: [v != null ? `${args.key} = ${String(v)}` : empty(`${args.key} is not set.`)],
|
|
1422
|
+
json: args.json ? { [args.key]: v ?? null } : void 0
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
const entries = Object.entries(cfg);
|
|
1426
|
+
const lines = entries.length ? entries.map(([k, v]) => `${k} = ${String(v)}`) : [empty("no local config set. (your auth token lives in the keychain, not here.)")];
|
|
1427
|
+
return { lines, json: args.json ? cfg : void 0 };
|
|
1428
|
+
}
|
|
1429
|
+
if (sub === "set") {
|
|
1430
|
+
if (!args.key || args.value == null || args.value === "") {
|
|
1431
|
+
return { lines: [warn("usage: bt config set <key> <value>")] };
|
|
1432
|
+
}
|
|
1433
|
+
if (!SETTABLE.includes(args.key)) {
|
|
1434
|
+
return {
|
|
1435
|
+
lines: [
|
|
1436
|
+
warn(
|
|
1437
|
+
`can't set "${args.key}". settable keys: ${SETTABLE.join(", ")}. (the auth token is never stored in config \u2014 use bt login.)`
|
|
1438
|
+
)
|
|
1439
|
+
]
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
deps.save({ [args.key]: args.value });
|
|
1443
|
+
return { lines: [ok(`set ${args.key} = ${args.value}`)], json: args.json ? { [args.key]: args.value } : void 0 };
|
|
1444
|
+
}
|
|
1445
|
+
return { lines: [warn("usage: bt config get [key] | bt config set <key> <value>")] };
|
|
1446
|
+
}
|
|
1447
|
+
function registerConfig(program) {
|
|
1448
|
+
program.command("config").argument("[sub]", "get | set", "get").argument("[key]", "config key").argument("[value]", "value (for set)").description("view or set local prefs (email, region, defaults). the auth token is never stored here.").action(async (sub, key, value) => {
|
|
1449
|
+
const json = Boolean(program.opts().json);
|
|
1450
|
+
const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await import("./config-FXGCWC6Y.js");
|
|
1451
|
+
const res = runConfig({ sub, key, value, json }, { load: loadConfig2, save: saveConfig2 });
|
|
1452
|
+
printResult(res, json);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// src/commands/doctor.ts
|
|
1457
|
+
import * as fs2 from "fs";
|
|
1458
|
+
function runDoctor(s, json) {
|
|
1459
|
+
const auth = !s.loggedIn ? "guest (not logged in)" : s.tokenExpired ? `logged in but token EXPIRED${s.tokenExpiresAt ? ` (${s.tokenExpiresAt})` : ""} \u2014 run bt login` : `logged in${s.tokenExpiresAt ? ` \xB7 token valid until ${s.tokenExpiresAt}` : ""}`;
|
|
1460
|
+
const lines = [
|
|
1461
|
+
ok("bt doctor \u2014 local checkup (nothing was sent)"),
|
|
1462
|
+
` bt ${s.btVersion}`,
|
|
1463
|
+
` node ${s.node} \xB7 ${s.platform}`,
|
|
1464
|
+
` store ${s.storeUrl}`,
|
|
1465
|
+
` key set (${s.keySource})`,
|
|
1466
|
+
` auth ${auth}`,
|
|
1467
|
+
` terminal ${s.tty ? "tty" : "non-tty"} \xB7 colour ${s.color ? "on" : "off"}`,
|
|
1468
|
+
` config ${s.configExists ? configPath() : "none yet"}`,
|
|
1469
|
+
empty(" paste this into bt report or an issue. re-run with --debug for request tracing.")
|
|
1470
|
+
];
|
|
1471
|
+
return { lines, json: json ? { ...s } : void 0 };
|
|
1472
|
+
}
|
|
1473
|
+
function gatherDoctorSnapshot(opts = {}) {
|
|
1474
|
+
const { baseUrl } = resolveClientConfig();
|
|
1475
|
+
const token = resolveBridgeToken(process.env, getToken());
|
|
1476
|
+
const exp = jwtExpiry(token);
|
|
1477
|
+
let configExists = false;
|
|
1478
|
+
try {
|
|
1479
|
+
configExists = fs2.existsSync(configPath());
|
|
1480
|
+
} catch {
|
|
1481
|
+
}
|
|
1482
|
+
return {
|
|
1483
|
+
btVersion: "0.1.0",
|
|
1484
|
+
node: process.versions.node,
|
|
1485
|
+
platform: `${process.platform}/${process.arch}`,
|
|
1486
|
+
storeUrl: baseUrl,
|
|
1487
|
+
keySource: process.env.BT_PUBLISHABLE_KEY || process.env.MEDUSA_PUBLISHABLE_KEY ? "env" : "baked",
|
|
1488
|
+
loggedIn: Boolean(token),
|
|
1489
|
+
tokenExpiresAt: exp.expiresAt,
|
|
1490
|
+
tokenExpired: exp.expired,
|
|
1491
|
+
tty: Boolean(process.stdout.isTTY),
|
|
1492
|
+
color: opts.color !== false && !process.env.NO_COLOR,
|
|
1493
|
+
configExists
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function registerDoctor(program) {
|
|
1497
|
+
program.command("doctor").description("local diagnostics for a bug report (sends nothing).").action(() => {
|
|
1498
|
+
const gOpts = program.opts();
|
|
1499
|
+
const json = Boolean(gOpts.json);
|
|
1500
|
+
const snapshot = gatherDoctorSnapshot({ color: gOpts.color });
|
|
1501
|
+
printResult(runDoctor(snapshot, json), json);
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/commands/report.ts
|
|
1506
|
+
async function runReport(client, args, deps) {
|
|
1507
|
+
let message = (args.message ?? "").trim();
|
|
1508
|
+
if (!message && !args.json) {
|
|
1509
|
+
message = (await deps.prompts.ask("what went wrong? (one line is fine)")).trim();
|
|
1510
|
+
}
|
|
1511
|
+
if (!message) {
|
|
1512
|
+
return { lines: [empty("nothing to report \u2014 the message was empty. nothing was sent.")] };
|
|
1513
|
+
}
|
|
1514
|
+
const context = { ...deps.snapshot };
|
|
1515
|
+
const print = deps.print ?? ((line) => process.stdout.write(line + "\n"));
|
|
1516
|
+
const autoProceed = Boolean(args.json) || Boolean(args.yes);
|
|
1517
|
+
if (!autoProceed) {
|
|
1518
|
+
print(ok("bt report \u2014 about to send (tokens + emails are redacted server-side):"));
|
|
1519
|
+
print(` message ${message}`);
|
|
1520
|
+
if (args.debug) {
|
|
1521
|
+
for (const [k, v] of Object.entries(context)) {
|
|
1522
|
+
print(` ${k.padEnd(11)} ${String(v)}`);
|
|
1523
|
+
}
|
|
1524
|
+
} else {
|
|
1525
|
+
print(
|
|
1526
|
+
` context bt ${context.btVersion} \xB7 node ${context.node} \xB7 ${context.platform} \xB7 ${context.storeUrl}`
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
const proceed = await deps.prompts.confirm("send this report? (y/n):");
|
|
1530
|
+
if (!proceed) {
|
|
1531
|
+
return { lines: [empty("cancelled. nothing was sent.")] };
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
const { reference } = await client.report(message, context);
|
|
1535
|
+
const lines = [ok("report filed. thank you \u2014 you're a contributor now."), ` ref ${reference}`];
|
|
1536
|
+
if (args.debug) {
|
|
1537
|
+
lines.push(empty(" context attached:"));
|
|
1538
|
+
for (const [k, v] of Object.entries(context)) {
|
|
1539
|
+
lines.push(` ${k.padEnd(11)} ${String(v)}`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return { lines, json: args.json ? { reference } : void 0 };
|
|
1543
|
+
}
|
|
1544
|
+
function registerReport(program, makeClient2) {
|
|
1545
|
+
program.command("report").argument("[message]", "what went wrong (omit to be prompted)").description("file a bug report \u2014 sends a redacted snapshot; never cards or tokens.").option("--yes", "skip the confirm (scripts / CI / agents); implied by --json").action(async (message, opts) => {
|
|
1546
|
+
const gOpts = program.opts();
|
|
1547
|
+
const json = Boolean(gOpts.json);
|
|
1548
|
+
const debug = process.argv.includes("--debug") || process.argv.includes("--verbose");
|
|
1549
|
+
const { makePrompts: makePrompts2 } = await import("./prompts-GTC5VYM6.js");
|
|
1550
|
+
const snapshot = gatherDoctorSnapshot({ color: gOpts.color });
|
|
1551
|
+
const res = await runReport(
|
|
1552
|
+
makeClient2(),
|
|
1553
|
+
{ message, json, yes: opts.yes, debug },
|
|
1554
|
+
{ prompts: makePrompts2(), snapshot }
|
|
1555
|
+
);
|
|
1556
|
+
printResult(res, json);
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/bin.ts
|
|
1561
|
+
var VERSION = "0.1.0";
|
|
1562
|
+
function makeClient() {
|
|
1563
|
+
const { baseUrl, publishableKey } = resolveClientConfig();
|
|
1564
|
+
const jwt = resolveBridgeToken(process.env, getToken()) ?? void 0;
|
|
1565
|
+
const debug = process.argv.includes("--debug") || process.argv.includes("--verbose");
|
|
1566
|
+
return new StoreClient({
|
|
1567
|
+
baseUrl,
|
|
1568
|
+
publishableKey,
|
|
1569
|
+
jwt,
|
|
1570
|
+
logger: makeLogger(debug)
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
function buildProgram() {
|
|
1574
|
+
const program = new Command();
|
|
1575
|
+
program.name("bt").description(
|
|
1576
|
+
"the binary & thread terminal store. techno-minimalist clothing & lifestyle, bought with the ceremony of a deploy.\nprimary invocation: npx @binaryandthread/cli (a bare `bt` may collide with another binary)"
|
|
1577
|
+
).version(versionLine(VERSION), "-v, --version", "print the version").option("--json", "machine-readable output", false).option("--no-color", "disable ANSI colour + inline images (also honours NO_COLOR)").option("--debug, --verbose", "trace requests to stderr (redacted) \u2014 for bug reports (d15)");
|
|
1578
|
+
program.addHelpText("beforeAll", "\n bt \u2014 the binary & thread terminal store. new here? \u2192 bt init\n");
|
|
1579
|
+
program.command("brew").argument("[what]", "what to brew").description("we make clothing, not coffee.").action(() => {
|
|
1580
|
+
process.stdout.write(teapot() + "\n");
|
|
1581
|
+
});
|
|
1582
|
+
registerEggs(program);
|
|
1583
|
+
program.command("forget").description("wipe your local session + config (keychain token, cart, email, defaults).").action(() => {
|
|
1584
|
+
deleteToken();
|
|
1585
|
+
forgetConfig();
|
|
1586
|
+
process.stdout.write("\u2713 local session + config wiped. (your wardrobe on the server is untouched.)\n");
|
|
1587
|
+
});
|
|
1588
|
+
registerCommands(program);
|
|
1589
|
+
return program;
|
|
1590
|
+
}
|
|
1591
|
+
var dropExec = {
|
|
1592
|
+
run(script, env) {
|
|
1593
|
+
return new Promise((resolve) => {
|
|
1594
|
+
const cwd = process.env.BT_BACKEND_DIR || process.cwd();
|
|
1595
|
+
const child = spawn("npm", ["run", script], {
|
|
1596
|
+
cwd,
|
|
1597
|
+
env: { ...process.env, ...env },
|
|
1598
|
+
stdio: "inherit"
|
|
1599
|
+
});
|
|
1600
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
1601
|
+
child.on("error", () => resolve(1));
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
function registerCommands(program) {
|
|
1606
|
+
registerInit(program);
|
|
1607
|
+
registerBrowse(program, makeClient, loadConfig);
|
|
1608
|
+
registerSearch(program, makeClient, loadConfig);
|
|
1609
|
+
registerView(program, makeClient, loadConfig);
|
|
1610
|
+
registerStage(program, makeClient, loadConfig);
|
|
1611
|
+
registerBuy(program, makeClient, loadConfig);
|
|
1612
|
+
registerConfig(program);
|
|
1613
|
+
registerToken(program);
|
|
1614
|
+
registerDoctor(program);
|
|
1615
|
+
registerReport(program, makeClient);
|
|
1616
|
+
registerCart(program, makeClient, loadConfig);
|
|
1617
|
+
registerCheckout(program, makeClient, loadConfig);
|
|
1618
|
+
registerOrder(program, makeClient, loadConfig);
|
|
1619
|
+
registerLog(program, makeClient, loadConfig);
|
|
1620
|
+
registerStatus(program, makeClient, loadConfig);
|
|
1621
|
+
registerWhoami(program, makeClient, loadConfig);
|
|
1622
|
+
registerDrops(program, makeClient, loadConfig);
|
|
1623
|
+
registerConnect(program);
|
|
1624
|
+
registerUnstage(program, makeClient, loadConfig);
|
|
1625
|
+
const prompts = makePrompts();
|
|
1626
|
+
const persist = (patch) => {
|
|
1627
|
+
saveConfig(patch);
|
|
1628
|
+
};
|
|
1629
|
+
registerLogin(
|
|
1630
|
+
program,
|
|
1631
|
+
makeClient,
|
|
1632
|
+
loadConfig,
|
|
1633
|
+
prompts,
|
|
1634
|
+
{ setToken: (jwt) => setToken(jwt) },
|
|
1635
|
+
persist
|
|
1636
|
+
);
|
|
1637
|
+
registerLogout(program, { deleteToken: () => deleteToken() });
|
|
1638
|
+
registerDrop(program, dropExec);
|
|
1639
|
+
}
|
|
1640
|
+
function registerEggs(program) {
|
|
1641
|
+
const print = (s) => {
|
|
1642
|
+
process.stdout.write(s + "\n");
|
|
1643
|
+
};
|
|
1644
|
+
program.command("sudo", { hidden: true }).allowUnknownOption().argument("[args...]").action(() => print(sudo()));
|
|
1645
|
+
program.command("vim", { hidden: true }).action(() => print(vim()));
|
|
1646
|
+
program.command("panic", { hidden: true }).action(() => print(panic()));
|
|
1647
|
+
program.command("deploy", { hidden: true }).allowUnknownOption().argument("[args...]").action(() => print(deploy()));
|
|
1648
|
+
program.command("ship", { hidden: true }).argument("[what]").action(() => print(shipIt()));
|
|
1649
|
+
program.command("rm", { hidden: true }).allowUnknownOption().argument("[args...]").action(() => print(rmWardrobe()));
|
|
1650
|
+
program.command("force", { hidden: true }).allowUnknownOption().argument("[args...]").action(() => print(force()));
|
|
1651
|
+
program.command("blame", { hidden: true }).argument("[slug]", "a design slug to blame").description("who's responsible for all this \u2014 provenance + lore.").action((slug) => print(blame(slug)));
|
|
1652
|
+
program.command("fortune").description("a coding fortune. real aphorisms only.").action(() => {
|
|
1653
|
+
const seed = Math.floor(Date.now() / 1e3) % FORTUNES.length;
|
|
1654
|
+
print(fortune(seed));
|
|
1655
|
+
});
|
|
1656
|
+
program.command("man", { hidden: true }).description("the bt man page (also installed for the system man(1)).").action(() => print(manPage(VERSION).join("\n")));
|
|
1657
|
+
}
|
|
1658
|
+
var isMain = (() => {
|
|
1659
|
+
try {
|
|
1660
|
+
return typeof process.argv[1] === "string" && (process.argv[1].endsWith("bin.js") || process.argv[1].endsWith("bin.ts"));
|
|
1661
|
+
} catch {
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1664
|
+
})();
|
|
1665
|
+
if (isMain) {
|
|
1666
|
+
buildProgram().parseAsync(process.argv).catch((e) => {
|
|
1667
|
+
const json = process.argv.includes("--json");
|
|
1668
|
+
const { out, err, code } = formatFatal(e, json);
|
|
1669
|
+
if (out) process.stdout.write(out + "\n");
|
|
1670
|
+
if (err) process.stderr.write(err + "\n");
|
|
1671
|
+
process.exitCode = code;
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
export {
|
|
1675
|
+
buildProgram,
|
|
1676
|
+
makeClient
|
|
1677
|
+
};
|
|
1678
|
+
//# sourceMappingURL=bin.js.map
|