@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.
@@ -0,0 +1,36 @@
1
+ export { ensureAuth, runLogin, runLogout, runWhoami, runHealth, runCircuits, runAuthCheck, runProve, runVerify, } from "./commands-core.js";
2
+ export function printUsage() {
3
+ console.log(`Usage: affix [command]
4
+
5
+ Auth
6
+ login Sign in via affix-io.com/cli/verify (browser)
7
+ logout Remove local credentials
8
+ whoami Show signed-in account
9
+
10
+ API
11
+ health API health check
12
+ circuits List available circuits
13
+ auth Check API key status
14
+ prove [--circuit <id>]
15
+ verify <proof> [--circuit <id>]
16
+
17
+ Output locations (~/.affix/settings.json)
18
+ location list List saved output directories
19
+ location add <name> <path> Register an output directory
20
+ location default <name> Set default output location
21
+ location rm <name> Remove a saved location
22
+
23
+ Codes (Merkle-backed; index in ~/.affix/codes/)
24
+ qr <text|url> [--scans N --event <slug>] [--location <name>] [--out <path>] [--short]
25
+ qr ticket --event <slug> [--scans N] [--holder <id>]
26
+ barcode <data> [--type code128] [--scans N --event <slug>] [--location <name>]
27
+ scan <id|token|code> [--event <slug>] [--gate <id>]
28
+ codes List local vault
29
+ show <id> Show stored code
30
+ open <id> Open file in default app
31
+ rm <id> Remove from vault
32
+ symbologies List barcode types
33
+
34
+ Run affix with no arguments for interactive mode.
35
+ `);
36
+ }
@@ -0,0 +1,19 @@
1
+ export interface AffixCredentials {
2
+ api_key: string;
3
+ email: string;
4
+ api_base: string;
5
+ hub_base: string;
6
+ activated_at: string;
7
+ client_name?: string;
8
+ }
9
+ export declare const DEFAULT_HUB_BASE = "https://www.affix-io.com";
10
+ export declare const DEFAULT_API_BASE = "https://api.affix-io.com";
11
+ export declare function affixDir(): string;
12
+ export declare function codesDir(): string;
13
+ export declare function credentialsPath(): string;
14
+ /** Local placeholder credentials for offline QR/barcode rendering. */
15
+ export declare function localCredentials(): AffixCredentials;
16
+ export declare function loadCredentials(): AffixCredentials | null;
17
+ export declare function hasApiKey(creds: AffixCredentials | null | undefined): boolean;
18
+ export declare function saveCredentials(creds: AffixCredentials): void;
19
+ export declare function clearCredentials(): void;
package/dist/config.js ADDED
@@ -0,0 +1,78 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, chmodSync, existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const AFFIX_DIR = join(homedir(), ".affix");
5
+ const CREDENTIALS_PATH = join(AFFIX_DIR, "credentials.json");
6
+ const CODES_DIR = join(AFFIX_DIR, "codes");
7
+ export const DEFAULT_HUB_BASE = "https://www.affix-io.com";
8
+ export const DEFAULT_API_BASE = "https://api.affix-io.com";
9
+ export function affixDir() {
10
+ return AFFIX_DIR;
11
+ }
12
+ export function codesDir() {
13
+ return CODES_DIR;
14
+ }
15
+ export function credentialsPath() {
16
+ return CREDENTIALS_PATH;
17
+ }
18
+ function loadFileCredentials() {
19
+ if (!existsSync(CREDENTIALS_PATH))
20
+ return null;
21
+ try {
22
+ const raw = readFileSync(CREDENTIALS_PATH, "utf8");
23
+ const parsed = JSON.parse(raw);
24
+ if (!parsed.api_key || !parsed.email)
25
+ return null;
26
+ return {
27
+ ...parsed,
28
+ api_base: parsed.api_base ?? DEFAULT_API_BASE,
29
+ hub_base: parsed.hub_base ?? DEFAULT_HUB_BASE,
30
+ };
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /** Local placeholder credentials for offline QR/barcode rendering. */
37
+ export function localCredentials() {
38
+ return {
39
+ api_key: "",
40
+ email: "local",
41
+ api_base: process.env.AFFIX_API_BASE?.trim() || DEFAULT_API_BASE,
42
+ hub_base: process.env.AFFIX_HUB_BASE?.trim() || DEFAULT_HUB_BASE,
43
+ activated_at: new Date().toISOString(),
44
+ client_name: "affix-cli",
45
+ };
46
+ }
47
+ export function loadCredentials() {
48
+ const file = loadFileCredentials();
49
+ const envKey = process.env.AFFIX_API_KEY?.trim();
50
+ if (envKey) {
51
+ return {
52
+ api_key: envKey,
53
+ email: process.env.AFFIX_EMAIL?.trim() || file?.email || "env",
54
+ api_base: process.env.AFFIX_API_BASE?.trim() || file?.api_base || DEFAULT_API_BASE,
55
+ hub_base: process.env.AFFIX_HUB_BASE?.trim() || file?.hub_base || DEFAULT_HUB_BASE,
56
+ activated_at: file?.activated_at || new Date().toISOString(),
57
+ client_name: file?.client_name || "affix-cli",
58
+ };
59
+ }
60
+ return file;
61
+ }
62
+ export function hasApiKey(creds) {
63
+ return Boolean(creds?.api_key?.trim());
64
+ }
65
+ export function saveCredentials(creds) {
66
+ mkdirSync(AFFIX_DIR, { recursive: true, mode: 0o700 });
67
+ writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2) + "\n", {
68
+ encoding: "utf8",
69
+ mode: 0o600,
70
+ });
71
+ chmodSync(AFFIX_DIR, 0o700);
72
+ chmodSync(CREDENTIALS_PATH, 0o600);
73
+ }
74
+ export function clearCredentials() {
75
+ if (!existsSync(CREDENTIALS_PATH))
76
+ return;
77
+ writeFileSync(CREDENTIALS_PATH, "", { mode: 0o600 });
78
+ }
@@ -0,0 +1,5 @@
1
+ export declare function renderQrSvg(content: string): Promise<string>;
2
+ export declare function renderQrPng(content: string): Promise<Buffer>;
3
+ export declare function renderBarcodeSvg(symbology: string, content: string): Promise<string>;
4
+ export declare function renderBarcodePng(symbology: string, content: string): Promise<Buffer>;
5
+ export declare const DEFAULT_SYMBOLOGIES: string[];
@@ -0,0 +1,47 @@
1
+ import QRCode from "qrcode";
2
+ import bwipjs from "bwip-js";
3
+ export async function renderQrSvg(content) {
4
+ return QRCode.toString(content, {
5
+ type: "svg",
6
+ errorCorrectionLevel: "M",
7
+ margin: 2,
8
+ width: 256,
9
+ });
10
+ }
11
+ export async function renderQrPng(content) {
12
+ return QRCode.toBuffer(content, {
13
+ type: "png",
14
+ errorCorrectionLevel: "M",
15
+ margin: 2,
16
+ width: 512,
17
+ });
18
+ }
19
+ export async function renderBarcodeSvg(symbology, content) {
20
+ return bwipjs.toSVG({
21
+ bcid: symbology,
22
+ text: content,
23
+ scale: 3,
24
+ height: 12,
25
+ includetext: true,
26
+ textxalign: "center",
27
+ });
28
+ }
29
+ export async function renderBarcodePng(symbology, content) {
30
+ return bwipjs.toBuffer({
31
+ bcid: symbology,
32
+ text: content,
33
+ scale: 3,
34
+ height: 12,
35
+ includetext: true,
36
+ textxalign: "center",
37
+ });
38
+ }
39
+ export const DEFAULT_SYMBOLOGIES = [
40
+ "code128",
41
+ "ean13",
42
+ "ean8",
43
+ "upca",
44
+ "qrcode",
45
+ "datamatrix",
46
+ "pdf417",
47
+ ];
package/dist/http.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type JsonResponse = {
2
+ status: number;
3
+ ok: boolean;
4
+ json(): Promise<Record<string, unknown>>;
5
+ };
6
+ export declare function requestJson(url: string, options?: {
7
+ method?: string;
8
+ headers?: Record<string, string>;
9
+ body?: string;
10
+ timeoutMs?: number;
11
+ }): Promise<JsonResponse>;
package/dist/http.js ADDED
@@ -0,0 +1,62 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import http from "node:http";
3
+ import https from "node:https";
4
+ import { dirname, join } from "node:path";
5
+ import tls from "node:tls";
6
+ import { fileURLToPath } from "node:url";
7
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
8
+ const EXTRA_CA_PATH = join(packageRoot, "certs", "sectigo-r36.pem");
9
+ let caBundle;
10
+ function getCaBundle() {
11
+ if (caBundle)
12
+ return caBundle;
13
+ caBundle = [...tls.rootCertificates];
14
+ if (existsSync(EXTRA_CA_PATH)) {
15
+ caBundle.push(readFileSync(EXTRA_CA_PATH, "utf8"));
16
+ }
17
+ return caBundle;
18
+ }
19
+ export async function requestJson(url, options = {}) {
20
+ const parsed = new URL(url);
21
+ const isHttps = parsed.protocol === "https:";
22
+ const lib = isHttps ? https : http;
23
+ return new Promise((resolve, reject) => {
24
+ const req = lib.request({
25
+ protocol: parsed.protocol,
26
+ hostname: parsed.hostname,
27
+ port: parsed.port || (isHttps ? 443 : 80),
28
+ path: `${parsed.pathname}${parsed.search}`,
29
+ method: options.method ?? "GET",
30
+ headers: options.headers,
31
+ ca: isHttps ? getCaBundle() : undefined,
32
+ }, (res) => {
33
+ const chunks = [];
34
+ res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
35
+ res.on("end", () => {
36
+ const text = Buffer.concat(chunks).toString("utf8");
37
+ const status = res.statusCode ?? 0;
38
+ resolve({
39
+ status,
40
+ ok: status >= 200 && status < 300,
41
+ async json() {
42
+ try {
43
+ return JSON.parse(text);
44
+ }
45
+ catch {
46
+ return { error: "invalid_json", raw: text };
47
+ }
48
+ },
49
+ });
50
+ });
51
+ });
52
+ req.on("error", reject);
53
+ if (options.timeoutMs) {
54
+ req.setTimeout(options.timeoutMs, () => {
55
+ req.destroy(new Error("request_timeout"));
56
+ });
57
+ }
58
+ if (options.body)
59
+ req.write(options.body);
60
+ req.end();
61
+ });
62
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ import { printUsage, runAuthCheck, runCircuits, runHealth, runLogin, runLogout, runProve, runVerify, runWhoami, } from "./commands.js";
3
+ import { hasApiKey, loadCredentials, localCredentials } from "./config.js";
4
+ import { AffixApi } from "./api.js";
5
+ import { runRepl } from "./repl.js";
6
+ import { generateBarcode, generateQr, generateTicketCode, listSymbologies, openCode, printCodeEntry, printCodesList, removeCode, scanCode, showCode, } from "./codes.js";
7
+ import { addOutputLocation, printLocations, removeOutputLocation, setDefaultOutputLocation, } from "./settings.js";
8
+ function arg(name) {
9
+ const idx = process.argv.indexOf(name);
10
+ if (idx === -1)
11
+ return undefined;
12
+ return process.argv[idx + 1];
13
+ }
14
+ function hasFlag(name) {
15
+ return process.argv.includes(name);
16
+ }
17
+ function parseScans() {
18
+ const raw = arg("--scans") ?? arg("--max-uses");
19
+ if (raw == null)
20
+ return undefined;
21
+ const n = Number(raw);
22
+ if (!Number.isFinite(n) || n < 1) {
23
+ console.error("--scans must be a positive integer.");
24
+ process.exit(1);
25
+ }
26
+ return Math.floor(n);
27
+ }
28
+ function positionalArgs(argv) {
29
+ const out = [];
30
+ for (let i = 0; i < argv.length; i++) {
31
+ if (argv[i].startsWith("--")) {
32
+ if (argv[i + 1] && !argv[i + 1].startsWith("--"))
33
+ i++;
34
+ continue;
35
+ }
36
+ out.push(argv[i]);
37
+ }
38
+ return out.join(" ");
39
+ }
40
+ function outputOpts() {
41
+ return {
42
+ location: arg("--location"),
43
+ out: arg("--out"),
44
+ maxUses: parseScans(),
45
+ event: arg("--event"),
46
+ holder: arg("--holder"),
47
+ seat: arg("--seat"),
48
+ tier: arg("--tier"),
49
+ };
50
+ }
51
+ function requireApi(creds) {
52
+ if (!hasApiKey(creds)) {
53
+ console.error("Not signed in. Run: affix login");
54
+ console.error("Or set AFFIX_API_KEY for API commands.");
55
+ process.exit(1);
56
+ }
57
+ return creds;
58
+ }
59
+ async function main() {
60
+ const command = process.argv[2];
61
+ if (!command || command === "chat" || command === "repl") {
62
+ const creds = loadCredentials() ?? localCredentials();
63
+ if (!hasApiKey(creds)) {
64
+ console.log("Local mode. Sign in for Merkle-backed codes and prove/verify.\n");
65
+ console.log("Run: affix login or set AFFIX_API_KEY\n");
66
+ }
67
+ await runRepl(creds, new AffixApi(creds));
68
+ return;
69
+ }
70
+ if (command === "help" || command === "--help" || command === "-h") {
71
+ printUsage();
72
+ return;
73
+ }
74
+ if (command === "login") {
75
+ await runLogin();
76
+ return;
77
+ }
78
+ if (command === "logout") {
79
+ runLogout();
80
+ return;
81
+ }
82
+ if (command === "location" || command === "locations") {
83
+ const sub = process.argv[3];
84
+ if (!sub || sub === "list") {
85
+ printLocations();
86
+ return;
87
+ }
88
+ if (sub === "add") {
89
+ const name = process.argv[4];
90
+ const path = process.argv[5];
91
+ if (!name || !path) {
92
+ console.error("Usage: affix location add <name> <path>");
93
+ process.exit(1);
94
+ }
95
+ const entry = addOutputLocation(name, path);
96
+ console.log(`Added output location "${entry.name}" → ${entry.path}`);
97
+ return;
98
+ }
99
+ if (sub === "default") {
100
+ const name = process.argv[4];
101
+ if (!name) {
102
+ console.error("Usage: affix location default <name>");
103
+ process.exit(1);
104
+ }
105
+ setDefaultOutputLocation(name);
106
+ console.log(`Default output location set to "${name}".`);
107
+ return;
108
+ }
109
+ if (sub === "rm" || sub === "remove") {
110
+ const name = process.argv[4];
111
+ if (!name) {
112
+ console.error("Usage: affix location rm <name>");
113
+ process.exit(1);
114
+ }
115
+ removeOutputLocation(name);
116
+ console.log(`Removed location "${name}".`);
117
+ return;
118
+ }
119
+ printLocations();
120
+ return;
121
+ }
122
+ if (command === "codes" || command === "ls") {
123
+ printCodesList();
124
+ return;
125
+ }
126
+ if (command === "show") {
127
+ const id = process.argv[3];
128
+ if (!id) {
129
+ console.error("Usage: affix show <id>");
130
+ process.exit(1);
131
+ }
132
+ showCode(id);
133
+ return;
134
+ }
135
+ if (command === "open") {
136
+ const id = process.argv[3];
137
+ if (!id) {
138
+ console.error("Usage: affix open <id>");
139
+ process.exit(1);
140
+ }
141
+ openCode(id);
142
+ return;
143
+ }
144
+ if (command === "rm" || command === "delete") {
145
+ const id = process.argv[3];
146
+ if (!id) {
147
+ console.error("Usage: affix rm <id>");
148
+ process.exit(1);
149
+ }
150
+ removeCode(id);
151
+ return;
152
+ }
153
+ const creds = loadCredentials() ?? localCredentials();
154
+ if (command === "qr") {
155
+ const sub = process.argv[3];
156
+ const opts = {
157
+ ...outputOpts(),
158
+ both: hasFlag("--png"),
159
+ label: arg("--label"),
160
+ shortLink: hasFlag("--short"),
161
+ };
162
+ if (sub === "ticket") {
163
+ requireApi(creds);
164
+ const event = arg("--event");
165
+ if (!event) {
166
+ console.error("Usage: affix qr ticket --event <slug> [--scans N] [--holder <id>] [--location <name>]");
167
+ process.exit(1);
168
+ }
169
+ const entry = await generateTicketCode(creds, {
170
+ event,
171
+ holder: arg("--holder"),
172
+ seat: arg("--seat"),
173
+ tier: arg("--tier"),
174
+ maxUses: opts.maxUses,
175
+ }, opts);
176
+ printCodeEntry(entry);
177
+ return;
178
+ }
179
+ const content = positionalArgs(process.argv.slice(3));
180
+ if (!content) {
181
+ console.error("Usage: affix qr <text|url> [--scans N --event <slug>] [--png] [--short] [--location <name>] [--out <path>]");
182
+ process.exit(1);
183
+ }
184
+ const entry = await generateQr(creds, content, opts);
185
+ printCodeEntry(entry);
186
+ return;
187
+ }
188
+ if (command === "barcode") {
189
+ const content = positionalArgs(process.argv.slice(3));
190
+ const sym = arg("--type") ?? "code128";
191
+ if (!content) {
192
+ console.error("Usage: affix barcode <data> [--type code128] [--scans N --event <slug>] [--location <name>]");
193
+ process.exit(1);
194
+ }
195
+ const entry = await generateBarcode(creds, content, {
196
+ ...outputOpts(),
197
+ symbology: sym,
198
+ both: hasFlag("--png"),
199
+ label: arg("--label"),
200
+ });
201
+ printCodeEntry(entry);
202
+ return;
203
+ }
204
+ if (command === "symbologies") {
205
+ const list = await listSymbologies(creds);
206
+ console.log(list.join(", "));
207
+ return;
208
+ }
209
+ const apiCreds = requireApi(creds);
210
+ const api = new AffixApi(apiCreds);
211
+ if (command === "whoami") {
212
+ runWhoami(apiCreds);
213
+ return;
214
+ }
215
+ if (command === "health") {
216
+ await runHealth(api);
217
+ return;
218
+ }
219
+ if (command === "circuits") {
220
+ await runCircuits(api);
221
+ return;
222
+ }
223
+ if (command === "auth") {
224
+ await runAuthCheck(api);
225
+ return;
226
+ }
227
+ if (command === "prove") {
228
+ const circuit = arg("--circuit") ?? "ticket_local_resident";
229
+ await runProve(api, circuit);
230
+ return;
231
+ }
232
+ if (command === "verify") {
233
+ const proof = process.argv[3];
234
+ if (!proof) {
235
+ console.error("Usage: affix verify <proof> [--circuit <id>]");
236
+ process.exit(1);
237
+ }
238
+ const circuit = arg("--circuit") ?? "ticket_local_resident";
239
+ await runVerify(api, circuit, proof);
240
+ return;
241
+ }
242
+ if (command === "scan") {
243
+ const target = process.argv[3];
244
+ if (!target) {
245
+ console.error("Usage: affix scan <id|token|code> [--event <slug>] [--gate <id>]");
246
+ process.exit(1);
247
+ }
248
+ const result = await scanCode(apiCreds, target, {
249
+ event: arg("--event"),
250
+ entryPoint: arg("--gate") ?? arg("--entry"),
251
+ id: getStoredCodeId(target),
252
+ });
253
+ console.log(JSON.stringify(result, null, 2));
254
+ return;
255
+ }
256
+ printUsage();
257
+ process.exit(1);
258
+ }
259
+ function getStoredCodeId(target) {
260
+ if (/^[a-f0-9]{6,}$/i.test(target))
261
+ return target;
262
+ return undefined;
263
+ }
264
+ main().catch((err) => {
265
+ console.error(err instanceof Error ? err.message : String(err));
266
+ process.exit(1);
267
+ });
package/dist/repl.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { AffixCredentials } from "./config.js";
2
+ import type { AffixApi } from "./api.js";
3
+ export declare const HELP: string;
4
+ export declare function runRepl(creds: AffixCredentials, api: AffixApi): Promise<void>;