@affix-io/cli 1.2.4
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 +48 -0
- package/certs/sectigo-r36.pem +36 -0
- package/dist/api.d.ts +11 -0
- package/dist/api.js +49 -0
- package/dist/auth.d.ts +17 -0
- package/dist/auth.js +93 -0
- package/dist/codes.d.ts +31 -0
- package/dist/codes.js +321 -0
- package/dist/commands-core.d.ts +11 -0
- package/dist/commands-core.js +55 -0
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +36 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +78 -0
- package/dist/generate-local.d.ts +5 -0
- package/dist/generate-local.js +47 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.js +62 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +267 -0
- package/dist/repl.d.ts +4 -0
- package/dist/repl.js +347 -0
- package/dist/settings.d.ts +23 -0
- package/dist/settings.js +116 -0
- package/dist/site-api.d.ts +8 -0
- package/dist/site-api.js +57 -0
- package/dist/store.d.ts +43 -0
- package/dist/store.js +96 -0
- package/package.json +42 -0
package/dist/repl.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { generateBarcode, generateQr, generateTicketCode, listSymbologies, openCode, printCodeEntry, printCodesList, removeCode, scanCode, showCode, } from "./codes.js";
|
|
4
|
+
import { addOutputLocation, printLocations, removeOutputLocation, setDefaultOutputLocation, } from "./settings.js";
|
|
5
|
+
import { runAuthCheck, runCircuits, runHealth, runLogin, runLogout, runProve, runVerify, runWhoami, } from "./commands-core.js";
|
|
6
|
+
const BANNER = `
|
|
7
|
+
AffixIO CLI
|
|
8
|
+
───────────
|
|
9
|
+
Merkle-backed QR · barcode · scan · vault
|
|
10
|
+
Type help for commands, exit to quit.
|
|
11
|
+
`.trim();
|
|
12
|
+
export const HELP = `
|
|
13
|
+
Commands
|
|
14
|
+
help Show this list
|
|
15
|
+
whoami Account on this machine
|
|
16
|
+
health API health check
|
|
17
|
+
auth Verify API key
|
|
18
|
+
circuits List circuits
|
|
19
|
+
prove [circuit] Demo prove
|
|
20
|
+
verify <proof> [circuit] Verify a proof
|
|
21
|
+
|
|
22
|
+
location list Output directories
|
|
23
|
+
location add <name> <path> Add output directory
|
|
24
|
+
location default <name> Set default output
|
|
25
|
+
|
|
26
|
+
qr <text|url> [--scans N --event <slug>] [--location <name>] [--short]
|
|
27
|
+
qr ticket --event <slug> [--scans N]
|
|
28
|
+
barcode <data> [--type sym] [--scans N --event <slug>]
|
|
29
|
+
scan <id|token|code> Merkle-audited scan
|
|
30
|
+
codes List local vault
|
|
31
|
+
show <id> Show stored code metadata
|
|
32
|
+
open <id> Open SVG in default app
|
|
33
|
+
rm <id> Remove from local vault
|
|
34
|
+
symbologies List barcode types
|
|
35
|
+
|
|
36
|
+
login Re-authenticate via cli/verify
|
|
37
|
+
logout Sign out
|
|
38
|
+
exit Quit
|
|
39
|
+
`.trim();
|
|
40
|
+
function parseFlags(args) {
|
|
41
|
+
const positional = [];
|
|
42
|
+
const flags = {};
|
|
43
|
+
for (let i = 0; i < args.length; i++) {
|
|
44
|
+
const a = args[i];
|
|
45
|
+
if (a.startsWith("--")) {
|
|
46
|
+
const key = a.slice(2);
|
|
47
|
+
const next = args[i + 1];
|
|
48
|
+
if (next && !next.startsWith("--")) {
|
|
49
|
+
flags[key] = next;
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
flags[key] = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
positional.push(a);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { positional, flags };
|
|
61
|
+
}
|
|
62
|
+
function genOpts(flags) {
|
|
63
|
+
const scansRaw = flags.scans ?? flags["max-uses"];
|
|
64
|
+
let maxUses;
|
|
65
|
+
if (typeof scansRaw === "string") {
|
|
66
|
+
const n = Number(scansRaw);
|
|
67
|
+
if (Number.isFinite(n) && n >= 1)
|
|
68
|
+
maxUses = Math.floor(n);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
both: Boolean(flags.png),
|
|
72
|
+
label: typeof flags.label === "string" ? flags.label : undefined,
|
|
73
|
+
location: typeof flags.location === "string" ? flags.location : undefined,
|
|
74
|
+
out: typeof flags.out === "string" ? flags.out : undefined,
|
|
75
|
+
maxUses,
|
|
76
|
+
event: typeof flags.event === "string" ? flags.event : undefined,
|
|
77
|
+
holder: typeof flags.holder === "string" ? flags.holder : undefined,
|
|
78
|
+
seat: typeof flags.seat === "string" ? flags.seat : undefined,
|
|
79
|
+
tier: typeof flags.tier === "string" ? flags.tier : undefined,
|
|
80
|
+
shortLink: Boolean(flags.short),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function requireSignedIn(localMode) {
|
|
84
|
+
if (localMode) {
|
|
85
|
+
console.error("Sign in required. Run: login");
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
async function prompt(rl, label, fallback = "") {
|
|
91
|
+
const answer = (await rl.question(`${label}: `)).trim();
|
|
92
|
+
return answer || fallback;
|
|
93
|
+
}
|
|
94
|
+
async function interactiveQr(rl, creds) {
|
|
95
|
+
const content = await prompt(rl, "QR content (text or URL)");
|
|
96
|
+
if (!content) {
|
|
97
|
+
console.error("Content required.");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const entry = await generateQr(creds, content, {});
|
|
101
|
+
printCodeEntry(entry);
|
|
102
|
+
}
|
|
103
|
+
async function interactiveBarcode(rl, creds) {
|
|
104
|
+
const content = await prompt(rl, "Barcode data");
|
|
105
|
+
if (!content) {
|
|
106
|
+
console.error("Data required.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const sym = await prompt(rl, "Symbology", "code128");
|
|
110
|
+
const entry = await generateBarcode(creds, content, { symbology: sym });
|
|
111
|
+
printCodeEntry(entry);
|
|
112
|
+
}
|
|
113
|
+
async function dispatchLine(line, creds, api, rl) {
|
|
114
|
+
const trimmed = line.trim();
|
|
115
|
+
if (!trimmed)
|
|
116
|
+
return "continue";
|
|
117
|
+
const parts = trimmed.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
|
|
118
|
+
const tokens = parts.map((p) => p.replace(/^"|"$/g, ""));
|
|
119
|
+
const [cmd, ...rest] = tokens;
|
|
120
|
+
const localMode = creds.email === "local" || !creds.api_key;
|
|
121
|
+
switch (cmd.toLowerCase()) {
|
|
122
|
+
case "help":
|
|
123
|
+
case "?":
|
|
124
|
+
console.log(HELP);
|
|
125
|
+
break;
|
|
126
|
+
case "exit":
|
|
127
|
+
case "quit":
|
|
128
|
+
return "exit";
|
|
129
|
+
case "whoami":
|
|
130
|
+
if (localMode) {
|
|
131
|
+
console.error("Not signed in. Run: login");
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
runWhoami(creds);
|
|
135
|
+
break;
|
|
136
|
+
case "health":
|
|
137
|
+
if (localMode) {
|
|
138
|
+
console.error("Sign in required. Run: login");
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
await runHealth(api);
|
|
142
|
+
break;
|
|
143
|
+
case "auth":
|
|
144
|
+
if (localMode) {
|
|
145
|
+
console.error("Sign in required. Run: login");
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
await runAuthCheck(api);
|
|
149
|
+
break;
|
|
150
|
+
case "circuits":
|
|
151
|
+
if (localMode) {
|
|
152
|
+
console.error("Sign in required. Run: login");
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
await runCircuits(api);
|
|
156
|
+
break;
|
|
157
|
+
case "prove":
|
|
158
|
+
if (localMode) {
|
|
159
|
+
console.error("Sign in required. Run: login");
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
await runProve(api, rest[0] ?? "ticket_local_resident");
|
|
163
|
+
break;
|
|
164
|
+
case "verify":
|
|
165
|
+
if (localMode) {
|
|
166
|
+
console.error("Sign in required. Run: login");
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
if (!rest[0]) {
|
|
170
|
+
console.error("Usage: verify <proof> [circuit]");
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
await runVerify(api, rest[1] ?? "ticket_local_resident", rest[0]);
|
|
174
|
+
break;
|
|
175
|
+
case "location":
|
|
176
|
+
case "locations": {
|
|
177
|
+
const sub = rest[0]?.toLowerCase();
|
|
178
|
+
if (!sub || sub === "list") {
|
|
179
|
+
printLocations();
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (sub === "add" && rest[1] && rest[2]) {
|
|
183
|
+
const entry = addOutputLocation(rest[1], rest[2]);
|
|
184
|
+
console.log(`Added "${entry.name}" → ${entry.path}`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (sub === "default" && rest[1]) {
|
|
188
|
+
setDefaultOutputLocation(rest[1]);
|
|
189
|
+
console.log(`Default output: ${rest[1]}`);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
if ((sub === "rm" || sub === "remove") && rest[1]) {
|
|
193
|
+
removeOutputLocation(rest[1]);
|
|
194
|
+
console.log(`Removed location "${rest[1]}".`);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
printLocations();
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "scan": {
|
|
201
|
+
if (!requireSignedIn(localMode))
|
|
202
|
+
break;
|
|
203
|
+
if (!rest[0]) {
|
|
204
|
+
console.error("Usage: scan <id|token|code>");
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
const { flags } = parseFlags(rest.slice(1));
|
|
208
|
+
const result = await scanCode(creds, rest[0], {
|
|
209
|
+
event: typeof flags.event === "string" ? flags.event : undefined,
|
|
210
|
+
entryPoint: typeof flags.gate === "string"
|
|
211
|
+
? flags.gate
|
|
212
|
+
: typeof flags.entry === "string"
|
|
213
|
+
? flags.entry
|
|
214
|
+
: undefined,
|
|
215
|
+
id: /^[a-f0-9]{6,}$/i.test(rest[0]) ? rest[0] : undefined,
|
|
216
|
+
});
|
|
217
|
+
console.log(JSON.stringify(result, null, 2));
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "qr": {
|
|
221
|
+
if (!requireSignedIn(localMode))
|
|
222
|
+
break;
|
|
223
|
+
const { positional, flags } = parseFlags(rest);
|
|
224
|
+
const opts = genOpts(flags);
|
|
225
|
+
if (positional[0] === "ticket") {
|
|
226
|
+
const event = String(flags.event ?? positional[1] ?? "");
|
|
227
|
+
if (!event) {
|
|
228
|
+
const slug = await prompt(rl, "Event slug");
|
|
229
|
+
if (!slug)
|
|
230
|
+
break;
|
|
231
|
+
const entry = await generateTicketCode(creds, {
|
|
232
|
+
event: slug,
|
|
233
|
+
holder: opts.holder,
|
|
234
|
+
seat: opts.seat,
|
|
235
|
+
tier: opts.tier,
|
|
236
|
+
maxUses: opts.maxUses,
|
|
237
|
+
}, opts);
|
|
238
|
+
printCodeEntry(entry);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
const entry = await generateTicketCode(creds, {
|
|
242
|
+
event,
|
|
243
|
+
holder: opts.holder,
|
|
244
|
+
seat: opts.seat,
|
|
245
|
+
tier: opts.tier,
|
|
246
|
+
maxUses: opts.maxUses,
|
|
247
|
+
}, opts);
|
|
248
|
+
printCodeEntry(entry);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
const content = positional.join(" ");
|
|
252
|
+
if (!content) {
|
|
253
|
+
await interactiveQr(rl, creds);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
const entry = await generateQr(creds, content, opts);
|
|
257
|
+
printCodeEntry(entry);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case "barcode": {
|
|
261
|
+
if (!requireSignedIn(localMode))
|
|
262
|
+
break;
|
|
263
|
+
const { positional, flags } = parseFlags(rest);
|
|
264
|
+
const sym = typeof flags.type === "string" ? flags.type : "code128";
|
|
265
|
+
const content = positional.join(" ");
|
|
266
|
+
if (!content) {
|
|
267
|
+
await interactiveBarcode(rl, creds);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
const entry = await generateBarcode(creds, content, {
|
|
271
|
+
...genOpts(flags),
|
|
272
|
+
symbology: sym,
|
|
273
|
+
});
|
|
274
|
+
printCodeEntry(entry);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "codes":
|
|
278
|
+
case "ls":
|
|
279
|
+
printCodesList();
|
|
280
|
+
break;
|
|
281
|
+
case "show":
|
|
282
|
+
if (!rest[0]) {
|
|
283
|
+
console.error("Usage: show <id>");
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
showCode(rest[0]);
|
|
287
|
+
break;
|
|
288
|
+
case "open":
|
|
289
|
+
if (!rest[0]) {
|
|
290
|
+
console.error("Usage: open <id>");
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
openCode(rest[0]);
|
|
294
|
+
break;
|
|
295
|
+
case "rm":
|
|
296
|
+
case "delete":
|
|
297
|
+
if (!rest[0]) {
|
|
298
|
+
console.error("Usage: rm <id>");
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
removeCode(rest[0]);
|
|
302
|
+
break;
|
|
303
|
+
case "symbologies": {
|
|
304
|
+
if (!requireSignedIn(localMode))
|
|
305
|
+
break;
|
|
306
|
+
const list = await listSymbologies(creds);
|
|
307
|
+
console.log(list.join(", "));
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "login":
|
|
311
|
+
await runLogin();
|
|
312
|
+
break;
|
|
313
|
+
case "logout":
|
|
314
|
+
runLogout();
|
|
315
|
+
return "exit";
|
|
316
|
+
default:
|
|
317
|
+
console.log(`Unknown command: ${cmd}. Type help.`);
|
|
318
|
+
}
|
|
319
|
+
return "continue";
|
|
320
|
+
}
|
|
321
|
+
export async function runRepl(creds, api) {
|
|
322
|
+
const rl = createInterface({ input, output, terminal: true });
|
|
323
|
+
const localMode = creds.email === "local" || !creds.api_key;
|
|
324
|
+
console.log(BANNER);
|
|
325
|
+
if (localMode) {
|
|
326
|
+
console.log("Not signed in · run login for Merkle-backed codes\n");
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
console.log(`Signed in as ${creds.email}\n`);
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
while (true) {
|
|
333
|
+
const line = await rl.question("affix› ");
|
|
334
|
+
try {
|
|
335
|
+
const next = await dispatchLine(line, creds, api, rl);
|
|
336
|
+
if (next === "exit")
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
rl.close();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface OutputLocation {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
export interface AffixSettings {
|
|
6
|
+
output_locations: OutputLocation[];
|
|
7
|
+
default_output?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function settingsPath(): string;
|
|
10
|
+
export declare function loadSettings(): AffixSettings;
|
|
11
|
+
export declare function saveSettings(settings: AffixSettings): void;
|
|
12
|
+
export declare function listOutputLocations(): OutputLocation[];
|
|
13
|
+
export declare function addOutputLocation(name: string, dirPath: string): OutputLocation;
|
|
14
|
+
export declare function removeOutputLocation(name: string): void;
|
|
15
|
+
export declare function setDefaultOutputLocation(name: string): void;
|
|
16
|
+
export declare function resolveOutputDir(opts?: {
|
|
17
|
+
location?: string;
|
|
18
|
+
out?: string;
|
|
19
|
+
}): {
|
|
20
|
+
name: string;
|
|
21
|
+
path: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function printLocations(): void;
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { affixDir, codesDir } from "./config.js";
|
|
4
|
+
const SETTINGS_PATH = join(affixDir(), "settings.json");
|
|
5
|
+
const DEFAULT_LOCATION = "default";
|
|
6
|
+
function defaultSettings() {
|
|
7
|
+
return {
|
|
8
|
+
output_locations: [{ name: DEFAULT_LOCATION, path: codesDir() }],
|
|
9
|
+
default_output: DEFAULT_LOCATION,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function settingsPath() {
|
|
13
|
+
return SETTINGS_PATH;
|
|
14
|
+
}
|
|
15
|
+
export function loadSettings() {
|
|
16
|
+
mkdirSync(affixDir(), { recursive: true, mode: 0o700 });
|
|
17
|
+
if (!existsSync(SETTINGS_PATH)) {
|
|
18
|
+
const defaults = defaultSettings();
|
|
19
|
+
saveSettings(defaults);
|
|
20
|
+
return defaults;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const raw = readFileSync(SETTINGS_PATH, "utf8");
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
if (!Array.isArray(parsed.output_locations) || !parsed.output_locations.length) {
|
|
26
|
+
return defaultSettings();
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return defaultSettings();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function saveSettings(settings) {
|
|
35
|
+
mkdirSync(affixDir(), { recursive: true, mode: 0o700 });
|
|
36
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", {
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
mode: 0o600,
|
|
39
|
+
});
|
|
40
|
+
chmodSync(affixDir(), 0o700);
|
|
41
|
+
}
|
|
42
|
+
export function listOutputLocations() {
|
|
43
|
+
return loadSettings().output_locations;
|
|
44
|
+
}
|
|
45
|
+
export function addOutputLocation(name, dirPath) {
|
|
46
|
+
const trimmedName = name.trim().toLowerCase();
|
|
47
|
+
const abs = resolve(dirPath.trim());
|
|
48
|
+
if (!trimmedName)
|
|
49
|
+
throw new Error("Location name is required.");
|
|
50
|
+
if (!abs)
|
|
51
|
+
throw new Error("Path is required.");
|
|
52
|
+
const settings = loadSettings();
|
|
53
|
+
if (settings.output_locations.some((l) => l.name === trimmedName)) {
|
|
54
|
+
throw new Error(`Location "${trimmedName}" already exists.`);
|
|
55
|
+
}
|
|
56
|
+
mkdirSync(abs, { recursive: true, mode: 0o700 });
|
|
57
|
+
chmodSync(abs, 0o700);
|
|
58
|
+
const entry = { name: trimmedName, path: abs };
|
|
59
|
+
settings.output_locations.push(entry);
|
|
60
|
+
if (!settings.default_output) {
|
|
61
|
+
settings.default_output = trimmedName;
|
|
62
|
+
}
|
|
63
|
+
saveSettings(settings);
|
|
64
|
+
return entry;
|
|
65
|
+
}
|
|
66
|
+
export function removeOutputLocation(name) {
|
|
67
|
+
const trimmedName = name.trim().toLowerCase();
|
|
68
|
+
const settings = loadSettings();
|
|
69
|
+
const remaining = settings.output_locations.filter((l) => l.name !== trimmedName);
|
|
70
|
+
if (remaining.length === settings.output_locations.length) {
|
|
71
|
+
throw new Error(`No location named "${trimmedName}".`);
|
|
72
|
+
}
|
|
73
|
+
if (remaining.length === 0) {
|
|
74
|
+
throw new Error("Cannot remove the last output location.");
|
|
75
|
+
}
|
|
76
|
+
if (settings.default_output === trimmedName) {
|
|
77
|
+
settings.default_output = remaining[0]?.name;
|
|
78
|
+
}
|
|
79
|
+
settings.output_locations = remaining;
|
|
80
|
+
saveSettings(settings);
|
|
81
|
+
}
|
|
82
|
+
export function setDefaultOutputLocation(name) {
|
|
83
|
+
const trimmedName = name.trim().toLowerCase();
|
|
84
|
+
const settings = loadSettings();
|
|
85
|
+
if (!settings.output_locations.some((l) => l.name === trimmedName)) {
|
|
86
|
+
throw new Error(`No location named "${trimmedName}".`);
|
|
87
|
+
}
|
|
88
|
+
settings.default_output = trimmedName;
|
|
89
|
+
saveSettings(settings);
|
|
90
|
+
}
|
|
91
|
+
export function resolveOutputDir(opts) {
|
|
92
|
+
if (opts?.out) {
|
|
93
|
+
const abs = resolve(opts.out.trim());
|
|
94
|
+
mkdirSync(abs, { recursive: true, mode: 0o700 });
|
|
95
|
+
chmodSync(abs, 0o700);
|
|
96
|
+
return { name: "_custom", path: abs };
|
|
97
|
+
}
|
|
98
|
+
const settings = loadSettings();
|
|
99
|
+
const wanted = (opts?.location ?? settings.default_output ?? DEFAULT_LOCATION).trim().toLowerCase();
|
|
100
|
+
const found = settings.output_locations.find((l) => l.name === wanted);
|
|
101
|
+
if (!found) {
|
|
102
|
+
throw new Error(`Unknown output location "${wanted}". Add one with: affix location add <name> <path>`);
|
|
103
|
+
}
|
|
104
|
+
mkdirSync(found.path, { recursive: true, mode: 0o700 });
|
|
105
|
+
chmodSync(found.path, 0o700);
|
|
106
|
+
return found;
|
|
107
|
+
}
|
|
108
|
+
export function printLocations() {
|
|
109
|
+
const settings = loadSettings();
|
|
110
|
+
console.log(`Settings: ${SETTINGS_PATH}\n`);
|
|
111
|
+
for (const loc of settings.output_locations) {
|
|
112
|
+
const mark = loc.name === settings.default_output ? " (default)" : "";
|
|
113
|
+
console.log(` ${loc.name}${mark}`);
|
|
114
|
+
console.log(` ${loc.path}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AffixCredentials } from "./config.js";
|
|
2
|
+
export declare function cliApiBase(creds: AffixCredentials): string;
|
|
3
|
+
export declare function generateQrMerkle(creds: AffixCredentials, body: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
4
|
+
export declare function generateBarcodeMerkle(creds: AffixCredentials, body: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
5
|
+
export declare function performCliScan(creds: AffixCredentials, body: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
6
|
+
export declare function fetchBarcodeSymbologies(creds: AffixCredentials): Promise<string[]>;
|
|
7
|
+
/** @deprecated Use generateQrMerkle via hub proxy */
|
|
8
|
+
export declare function generateTicketQr(creds: AffixCredentials, body: Record<string, unknown>): Promise<Record<string, unknown>>;
|
package/dist/site-api.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { DEFAULT_HUB_BASE } from "./config.js";
|
|
2
|
+
import { requestJson } from "./http.js";
|
|
3
|
+
export function cliApiBase(creds) {
|
|
4
|
+
const base = (creds.hub_base || DEFAULT_HUB_BASE).replace(/\/$/, "");
|
|
5
|
+
return `${base}/cli/v1`;
|
|
6
|
+
}
|
|
7
|
+
async function cliPost(creds, path, body) {
|
|
8
|
+
const res = await requestJson(`${cliApiBase(creds)}${path}`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${creds.api_key}`,
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
Accept: "application/json",
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify(body),
|
|
16
|
+
timeoutMs: 120_000,
|
|
17
|
+
});
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
throw new Error(data.error || data.message || `Request failed (${res.status})`);
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
async function cliGet(creds, path) {
|
|
25
|
+
const res = await requestJson(`${cliApiBase(creds)}${path}`, {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${creds.api_key}`,
|
|
28
|
+
Accept: "application/json",
|
|
29
|
+
},
|
|
30
|
+
timeoutMs: 60_000,
|
|
31
|
+
});
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(data.error || data.message || `Request failed (${res.status})`);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
export async function generateQrMerkle(creds, body) {
|
|
39
|
+
return cliPost(creds, "/qr/generate", body);
|
|
40
|
+
}
|
|
41
|
+
export async function generateBarcodeMerkle(creds, body) {
|
|
42
|
+
return cliPost(creds, "/barcode/generate", body);
|
|
43
|
+
}
|
|
44
|
+
export async function performCliScan(creds, body) {
|
|
45
|
+
return cliPost(creds, "/scan", body);
|
|
46
|
+
}
|
|
47
|
+
export async function fetchBarcodeSymbologies(creds) {
|
|
48
|
+
const data = await cliGet(creds, "/barcode/symbologies");
|
|
49
|
+
const list = data.symbologies;
|
|
50
|
+
if (!Array.isArray(list))
|
|
51
|
+
return ["code128", "ean13", "qrcode"];
|
|
52
|
+
return list.map((s) => s.bcid || s.id || "").filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
/** @deprecated Use generateQrMerkle via hub proxy */
|
|
55
|
+
export async function generateTicketQr(creds, body) {
|
|
56
|
+
return generateQrMerkle(creds, body);
|
|
57
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type CodeKind = "qr" | "barcode";
|
|
2
|
+
export type CodeFormat = "svg" | "png";
|
|
3
|
+
export interface StoredCode {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: CodeKind;
|
|
6
|
+
label: string;
|
|
7
|
+
content: string;
|
|
8
|
+
symbology?: string;
|
|
9
|
+
format: CodeFormat;
|
|
10
|
+
created_at: string;
|
|
11
|
+
output_location?: string;
|
|
12
|
+
output_path?: string;
|
|
13
|
+
scans_total?: number | null;
|
|
14
|
+
scans_remaining?: number | null;
|
|
15
|
+
files: {
|
|
16
|
+
svg?: string;
|
|
17
|
+
png?: string;
|
|
18
|
+
};
|
|
19
|
+
meta?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export declare function newCodeId(): string;
|
|
22
|
+
export declare function codeFilePath(id: string, ext: "svg" | "png", outputDir?: string): string;
|
|
23
|
+
export declare function listStoredCodes(): StoredCode[];
|
|
24
|
+
export declare function getStoredCode(id: string): StoredCode | null;
|
|
25
|
+
export declare function saveStoredCode(entry: StoredCode): StoredCode;
|
|
26
|
+
export declare function deleteStoredCode(id: string): boolean;
|
|
27
|
+
export declare function vaultSummary(): {
|
|
28
|
+
total: number;
|
|
29
|
+
qr: number;
|
|
30
|
+
barcode: number;
|
|
31
|
+
dir: string;
|
|
32
|
+
};
|
|
33
|
+
export interface WriteCodeOptions {
|
|
34
|
+
location?: string;
|
|
35
|
+
out?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function writeCodeFile(id: string, ext: "svg" | "png", body: Buffer | string, opts?: WriteCodeOptions): {
|
|
38
|
+
path: string;
|
|
39
|
+
locationName: string;
|
|
40
|
+
locationPath: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function listOrphanFiles(): string[];
|
|
43
|
+
export declare function updateScanCounts(id: string, scansRemaining: number | null, scansTotal?: number | null): StoredCode | null;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { codesDir } from "./config.js";
|
|
5
|
+
import { resolveOutputDir } from "./settings.js";
|
|
6
|
+
const INDEX_FILE = "index.json";
|
|
7
|
+
function indexPath() {
|
|
8
|
+
return join(codesDir(), INDEX_FILE);
|
|
9
|
+
}
|
|
10
|
+
function ensureVault() {
|
|
11
|
+
const dir = codesDir();
|
|
12
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
13
|
+
chmodSync(dir, 0o700);
|
|
14
|
+
if (!existsSync(indexPath())) {
|
|
15
|
+
writeFileSync(indexPath(), "[]\n", { mode: 0o600 });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function readIndex() {
|
|
19
|
+
ensureVault();
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(indexPath(), "utf8");
|
|
22
|
+
const parsed = JSON.parse(raw);
|
|
23
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writeIndex(entries) {
|
|
30
|
+
ensureVault();
|
|
31
|
+
writeFileSync(indexPath(), JSON.stringify(entries, null, 2) + "\n", { mode: 0o600 });
|
|
32
|
+
}
|
|
33
|
+
export function newCodeId() {
|
|
34
|
+
return randomBytes(6).toString("hex");
|
|
35
|
+
}
|
|
36
|
+
export function codeFilePath(id, ext, outputDir) {
|
|
37
|
+
return join(outputDir ?? codesDir(), `${id}.${ext}`);
|
|
38
|
+
}
|
|
39
|
+
export function listStoredCodes() {
|
|
40
|
+
return readIndex().sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
41
|
+
}
|
|
42
|
+
export function getStoredCode(id) {
|
|
43
|
+
const prefix = id.trim().toLowerCase();
|
|
44
|
+
return (readIndex().find((e) => e.id === prefix || e.id.startsWith(prefix)) ?? null);
|
|
45
|
+
}
|
|
46
|
+
export function saveStoredCode(entry) {
|
|
47
|
+
const items = readIndex().filter((e) => e.id !== entry.id);
|
|
48
|
+
items.unshift(entry);
|
|
49
|
+
writeIndex(items);
|
|
50
|
+
return entry;
|
|
51
|
+
}
|
|
52
|
+
export function deleteStoredCode(id) {
|
|
53
|
+
const entry = getStoredCode(id);
|
|
54
|
+
if (!entry)
|
|
55
|
+
return false;
|
|
56
|
+
const items = readIndex().filter((e) => e.id !== entry.id);
|
|
57
|
+
writeIndex(items);
|
|
58
|
+
for (const path of Object.values(entry.files)) {
|
|
59
|
+
if (path && existsSync(path))
|
|
60
|
+
unlinkSync(path);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
export function vaultSummary() {
|
|
65
|
+
const items = listStoredCodes();
|
|
66
|
+
return {
|
|
67
|
+
total: items.length,
|
|
68
|
+
qr: items.filter((i) => i.kind === "qr").length,
|
|
69
|
+
barcode: items.filter((i) => i.kind === "barcode").length,
|
|
70
|
+
dir: codesDir(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function writeCodeFile(id, ext, body, opts) {
|
|
74
|
+
ensureVault();
|
|
75
|
+
const resolved = resolveOutputDir(opts);
|
|
76
|
+
const path = codeFilePath(id, ext, resolved.path);
|
|
77
|
+
writeFileSync(path, body, ext === "svg" ? { encoding: "utf8", mode: 0o600 } : { mode: 0o600 });
|
|
78
|
+
return { path, locationName: resolved.name, locationPath: resolved.path };
|
|
79
|
+
}
|
|
80
|
+
export function listOrphanFiles() {
|
|
81
|
+
ensureVault();
|
|
82
|
+
const indexed = new Set(readIndex().flatMap((e) => Object.values(e.files).filter(Boolean)));
|
|
83
|
+
return readdirSync(codesDir())
|
|
84
|
+
.filter((f) => f.endsWith(".svg") || f.endsWith(".png"))
|
|
85
|
+
.map((f) => join(codesDir(), f))
|
|
86
|
+
.filter((p) => !indexed.has(p));
|
|
87
|
+
}
|
|
88
|
+
export function updateScanCounts(id, scansRemaining, scansTotal) {
|
|
89
|
+
const entry = getStoredCode(id);
|
|
90
|
+
if (!entry)
|
|
91
|
+
return null;
|
|
92
|
+
entry.scans_remaining = scansRemaining;
|
|
93
|
+
if (scansTotal !== undefined)
|
|
94
|
+
entry.scans_total = scansTotal;
|
|
95
|
+
return saveStoredCode(entry);
|
|
96
|
+
}
|