@shorten-dev/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/dist/index.js ADDED
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+
6
+ // ../shared/src/constants.ts
7
+ var MAX_TAGS_PER_LINK = 3;
8
+ var MAX_TAG_LENGTH = 50;
9
+
10
+ // src/api/client.ts
11
+ var BASE_URL = "https://shorten.dev/api/v1";
12
+ var ShortenApiError = class extends Error {
13
+ constructor(status, body) {
14
+ super(body.message);
15
+ this.status = status;
16
+ this.body = body;
17
+ this.name = "ShortenApiError";
18
+ }
19
+ };
20
+ var ApiClient = class {
21
+ apiKey;
22
+ constructor(apiKey) {
23
+ this.apiKey = apiKey;
24
+ }
25
+ async get(path, params) {
26
+ const url = new URL(`${BASE_URL}${path}`);
27
+ if (params) {
28
+ for (const [k, v] of Object.entries(params)) {
29
+ if (v !== void 0 && v !== "") url.searchParams.set(k, v);
30
+ }
31
+ }
32
+ return this.request(url, { method: "GET" });
33
+ }
34
+ async post(path, body) {
35
+ return this.request(new URL(`${BASE_URL}${path}`), {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/json" },
38
+ body: body ? JSON.stringify(body) : void 0
39
+ });
40
+ }
41
+ async patch(path, body) {
42
+ return this.request(new URL(`${BASE_URL}${path}`), {
43
+ method: "PATCH",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify(body)
46
+ });
47
+ }
48
+ async del(path) {
49
+ const res = await fetch(new URL(`${BASE_URL}${path}`), {
50
+ method: "DELETE",
51
+ headers: { Authorization: `Bearer ${this.apiKey}` }
52
+ });
53
+ if (!res.ok) {
54
+ const body = await res.json();
55
+ throw new ShortenApiError(res.status, body);
56
+ }
57
+ }
58
+ async request(url, init) {
59
+ const res = await fetch(url, {
60
+ ...init,
61
+ headers: {
62
+ ...init.headers ?? {},
63
+ Authorization: `Bearer ${this.apiKey}`
64
+ }
65
+ });
66
+ if (!res.ok) {
67
+ const body = await res.json();
68
+ throw new ShortenApiError(res.status, body);
69
+ }
70
+ return await res.json();
71
+ }
72
+ };
73
+
74
+ // src/config.ts
75
+ import { readFileSync } from "fs";
76
+ import { join } from "path";
77
+ import { homedir } from "os";
78
+ var CONFIG_PATH = join(homedir(), ".shorten.json");
79
+ function loadConfig() {
80
+ try {
81
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
82
+ return JSON.parse(raw);
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
87
+ function resolveApiKey(flagKey) {
88
+ const key = flagKey ?? process.env["SHORTEN_API_KEY"] ?? loadConfig().api_key;
89
+ if (!key) {
90
+ console.error(
91
+ "No API key found. Set SHORTEN_API_KEY or pass --key.\nGenerate one at https://shorten.dev/api-keys"
92
+ );
93
+ process.exit(1);
94
+ }
95
+ return key;
96
+ }
97
+
98
+ // src/utils/clipboard.ts
99
+ async function copyToClipboard(text) {
100
+ try {
101
+ const { default: clipboardy } = await import("clipboardy");
102
+ await clipboardy.write(text);
103
+ return true;
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ // src/utils/output.ts
110
+ import pc from "picocolors";
111
+ function resolveFormat(opts) {
112
+ if (opts.json) return "json";
113
+ if (opts.quiet) return "quiet";
114
+ if (opts.configDefault === "json") return "json";
115
+ if (opts.configDefault === "short") return "quiet";
116
+ return "pretty";
117
+ }
118
+ function success(msg) {
119
+ console.log(pc.green("\u2713") + " " + msg);
120
+ }
121
+ function error(msg) {
122
+ console.error(pc.red("\u2717") + " " + msg);
123
+ }
124
+ function info(msg) {
125
+ console.log(pc.dim(msg));
126
+ }
127
+ function json(data) {
128
+ console.log(JSON.stringify(data, null, 2));
129
+ }
130
+ function table(headers, rows, columnWidths) {
131
+ const widths = columnWidths ?? headers.map(
132
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
133
+ );
134
+ const header = headers.map((h, i) => h.padEnd(widths[i] ?? h.length)).join(" ");
135
+ console.log(pc.bold(header));
136
+ console.log(pc.dim("\u2500".repeat(header.length)));
137
+ for (const row of rows) {
138
+ console.log(
139
+ row.map((c, i) => c.padEnd(widths[i] ?? c.length)).join(" ")
140
+ );
141
+ }
142
+ }
143
+ function formatNumber(n) {
144
+ return n.toLocaleString("en-US");
145
+ }
146
+ function formatDate(iso) {
147
+ return new Date(iso).toLocaleDateString("en-US", {
148
+ month: "short",
149
+ day: "numeric",
150
+ year: "numeric"
151
+ });
152
+ }
153
+
154
+ // src/commands/shorten.ts
155
+ function registerShortenCommand(program2) {
156
+ const cmd = program2.command("create", { isDefault: true, hidden: true }).argument("<url>", "URL to shorten").option("-s, --slug <slug>", "Custom slug").option("-t, --tag <tag>", "Add tags (repeatable)", (val, acc) => [...acc, val], []).option("-q, --quiet", "Output only the short URL").option("-j, --json", "Output as JSON").option("--no-copy", "Don't copy to clipboard").action(async (url, opts) => {
157
+ const config = loadConfig();
158
+ const globalKey = cmd.optsWithGlobals()["key"];
159
+ const apiKey = resolveApiKey(globalKey);
160
+ const client = new ApiClient(apiKey);
161
+ const format = resolveFormat({
162
+ json: opts.json,
163
+ quiet: opts.quiet,
164
+ configDefault: config.default_format
165
+ });
166
+ const tags = opts.tag;
167
+ if (tags && tags.length > MAX_TAGS_PER_LINK) {
168
+ error(`Too many tags: got ${tags.length}, max is ${MAX_TAGS_PER_LINK}`);
169
+ process.exit(1);
170
+ return;
171
+ }
172
+ const longTag = tags?.find((t) => t.length > MAX_TAG_LENGTH);
173
+ if (longTag) {
174
+ error(`Tag "${longTag}" exceeds ${MAX_TAG_LENGTH} characters`);
175
+ process.exit(1);
176
+ return;
177
+ }
178
+ const body = {
179
+ destination_url: url,
180
+ custom_slug: opts.slug,
181
+ tags
182
+ };
183
+ try {
184
+ const res = await client.post("/links", body);
185
+ if (format === "json") {
186
+ json(res);
187
+ return;
188
+ }
189
+ if (format === "quiet") {
190
+ console.log(res.short_url);
191
+ return;
192
+ }
193
+ const shouldCopy = opts.copy !== false && (config.copy_to_clipboard ?? true);
194
+ let suffix = "";
195
+ if (shouldCopy) {
196
+ const copied = await copyToClipboard(res.short_url);
197
+ if (copied) suffix = " (copied to clipboard)";
198
+ }
199
+ success(`${res.short_url}${suffix}`);
200
+ if (res.link.tags.length > 0) {
201
+ info(` tags: ${res.link.tags.join(", ")}`);
202
+ }
203
+ } catch (err) {
204
+ if (err instanceof ShortenApiError) {
205
+ error(err.message);
206
+ process.exit(1);
207
+ return;
208
+ }
209
+ throw err;
210
+ }
211
+ });
212
+ }
213
+
214
+ // src/commands/list.ts
215
+ function registerListCommand(program2) {
216
+ const cmd = program2.command("list").description("List your links").option("-n, --limit <n>", "Number of results (default: 10, max: 100)").option("--status <status>", "Filter by active or flagged").option("--search <query>", "Search in slug, URL, and tags").option(
217
+ "--sort <field>",
218
+ "Sort by created_at or slug"
219
+ ).option("--order <dir>", "asc or desc (default: desc)").option("-j, --json", "Output as JSON").action(async (opts) => {
220
+ const globalKey = cmd.optsWithGlobals()["key"];
221
+ const apiKey = resolveApiKey(globalKey);
222
+ const client = new ApiClient(apiKey);
223
+ const params = {};
224
+ if (opts.limit) params["limit"] = opts.limit;
225
+ if (opts.status) params["status"] = opts.status;
226
+ if (opts.search) params["search"] = opts.search;
227
+ if (opts.sort) params["sort"] = opts.sort;
228
+ if (opts.order) params["order"] = opts.order;
229
+ try {
230
+ const res = await client.get(
231
+ "/links",
232
+ params
233
+ );
234
+ if (opts.json) {
235
+ json(res);
236
+ return;
237
+ }
238
+ if (res.data.length === 0) {
239
+ info("No links found.");
240
+ return;
241
+ }
242
+ table(
243
+ ["Slug", "Destination", "Status", "Created"],
244
+ res.data.map((link) => [
245
+ link.slug,
246
+ truncate(link.destination_url, 40),
247
+ link.status,
248
+ formatDate(link.created_at)
249
+ ])
250
+ );
251
+ info(
252
+ `
253
+ Showing ${res.data.length} of ${res.total} links (page ${res.page}/${res.total_pages})`
254
+ );
255
+ } catch (err) {
256
+ if (err instanceof ShortenApiError) {
257
+ error(err.message);
258
+ process.exit(1);
259
+ return;
260
+ }
261
+ throw err;
262
+ }
263
+ });
264
+ }
265
+ function truncate(str, maxLen) {
266
+ if (str.length <= maxLen) return str;
267
+ return str.slice(0, maxLen - 1) + "\u2026";
268
+ }
269
+
270
+ // src/commands/stats.ts
271
+ import pc2 from "picocolors";
272
+ function registerStatsCommand(program2) {
273
+ const cmd = program2.command("stats <slug>").description("View click analytics for a link").option(
274
+ "-p, --period <period>",
275
+ "Time window: 7d, 30d, 90d, or all (default: 7d)"
276
+ ).option("-j, --json", "Output as JSON").action(async (slug, opts) => {
277
+ const globalKey = cmd.optsWithGlobals()["key"];
278
+ const apiKey = resolveApiKey(globalKey);
279
+ const client = new ApiClient(apiKey);
280
+ const params = {};
281
+ if (opts.period) params["period"] = opts.period;
282
+ try {
283
+ const res = await client.get(
284
+ `/links/${slug}/analytics`,
285
+ params
286
+ );
287
+ if (opts.json) {
288
+ json(res);
289
+ return;
290
+ }
291
+ const period = res.period === "all" ? "all time" : res.period;
292
+ console.log(
293
+ `
294
+ ${pc2.bold(`shorten.dev/${res.slug}`)} \u2014 ${pc2.cyan(formatNumber(res.total_clicks))} clicks (${period})`
295
+ );
296
+ console.log(
297
+ pc2.dim(
298
+ ` ${formatNumber(res.unique_visitors)} unique visitors`
299
+ )
300
+ );
301
+ console.log();
302
+ const colWidth = 24;
303
+ const maxRows = 5;
304
+ const countries = res.top_countries.slice(0, maxRows);
305
+ const referrers = res.top_referrers.slice(0, maxRows);
306
+ const totalForPct = res.total_clicks || 1;
307
+ console.log(
308
+ pc2.bold(" Top countries".padEnd(colWidth)) + pc2.bold("Top referrers".padEnd(colWidth)) + pc2.bold("Devices")
309
+ );
310
+ const deviceEntries = [
311
+ ["Desktop", res.devices.desktop],
312
+ ["Mobile", res.devices.mobile],
313
+ ["Tablet", res.devices.tablet]
314
+ ];
315
+ for (let i = 0; i < maxRows; i++) {
316
+ let line = " ";
317
+ const country = countries[i];
318
+ if (country) {
319
+ const pct = Math.round(
320
+ country.count / totalForPct * 100
321
+ );
322
+ line += `${country.country_code.padEnd(6)}${String(pct).padStart(3)}%`.padEnd(
323
+ colWidth
324
+ );
325
+ } else {
326
+ line += "".padEnd(colWidth);
327
+ }
328
+ const referrer = referrers[i];
329
+ if (referrer) {
330
+ const pct = Math.round(
331
+ referrer.count / totalForPct * 100
332
+ );
333
+ line += `${referrer.referrer.padEnd(16)}${String(pct).padStart(3)}%`.padEnd(
334
+ colWidth
335
+ );
336
+ } else {
337
+ line += "".padEnd(colWidth);
338
+ }
339
+ const device = deviceEntries[i];
340
+ if (device) {
341
+ const pct = Math.round(
342
+ device[1] / totalForPct * 100
343
+ );
344
+ line += `${device[0].padEnd(10)}${String(pct).padStart(3)}%`;
345
+ }
346
+ console.log(line);
347
+ }
348
+ console.log();
349
+ } catch (err) {
350
+ if (err instanceof ShortenApiError) {
351
+ error(err.message);
352
+ process.exit(1);
353
+ return;
354
+ }
355
+ throw err;
356
+ }
357
+ });
358
+ }
359
+
360
+ // src/commands/whoami.ts
361
+ import pc3 from "picocolors";
362
+ function registerWhoamiCommand(program2) {
363
+ const cmd = program2.command("whoami").description("Display authenticated user and API key info").option("-j, --json", "Output as JSON").action(async (opts) => {
364
+ const globalKey = cmd.optsWithGlobals()["key"];
365
+ const apiKey = resolveApiKey(globalKey);
366
+ const client = new ApiClient(apiKey);
367
+ try {
368
+ const usage = await client.get("/usage");
369
+ if (opts.json) {
370
+ json({
371
+ key_prefix: maskKey(apiKey),
372
+ rate_limit: usage
373
+ });
374
+ return;
375
+ }
376
+ console.log(
377
+ ` Key: ${pc3.cyan(maskKey(apiKey))}`
378
+ );
379
+ console.log(
380
+ ` Rate limit: ${pc3.bold(String(usage.remaining))}/${usage.limit} remaining`
381
+ );
382
+ console.log(
383
+ ` Resets: ${formatDate(usage.reset_at)}`
384
+ );
385
+ } catch (err) {
386
+ if (err instanceof ShortenApiError) {
387
+ error(err.message);
388
+ process.exit(1);
389
+ return;
390
+ }
391
+ throw err;
392
+ }
393
+ });
394
+ }
395
+ function maskKey(key) {
396
+ if (key.length <= 8) return key;
397
+ return key.slice(0, 3) + "\u2026" + key.slice(-4);
398
+ }
399
+
400
+ // src/cli.ts
401
+ function createProgram() {
402
+ const program2 = new Command();
403
+ program2.name("shorten").description("Shorten URLs from your terminal").version("0.1.0").option("-k, --key <key>", "API key (overrides SHORTEN_API_KEY)").option("--no-color", "Disable colored output").configureOutput({ writeErr: (str) => process.stderr.write(str) });
404
+ registerShortenCommand(program2);
405
+ registerListCommand(program2);
406
+ registerStatsCommand(program2);
407
+ registerWhoamiCommand(program2);
408
+ return program2;
409
+ }
410
+
411
+ // src/index.ts
412
+ var program = createProgram();
413
+ program.parseAsync(process.argv);
414
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../../shared/src/constants.ts","../src/api/client.ts","../src/config.ts","../src/utils/clipboard.ts","../src/utils/output.ts","../src/commands/shorten.ts","../src/commands/list.ts","../src/commands/stats.ts","../src/commands/whoami.ts","../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { registerShortenCommand } from \"./commands/shorten.js\";\nimport { registerListCommand } from \"./commands/list.js\";\nimport { registerStatsCommand } from \"./commands/stats.js\";\nimport { registerWhoamiCommand } from \"./commands/whoami.js\";\n\nexport function createProgram(): Command {\n const program = new Command();\n\n program\n .name(\"shorten\")\n .description(\"Shorten URLs from your terminal\")\n .version(\"0.1.0\")\n .option(\"-k, --key <key>\", \"API key (overrides SHORTEN_API_KEY)\")\n .option(\"--no-color\", \"Disable colored output\")\n .configureOutput({ writeErr: (str) => process.stderr.write(str) });\n\n registerShortenCommand(program);\n registerListCommand(program);\n registerStatsCommand(program);\n registerWhoamiCommand(program);\n\n return program;\n}\n","export const SLUG_LENGTH = 7;\nexport const BASE_URL = \"https://shorten.dev\";\n\nexport const MAX_TAGS_PER_LINK = 3;\nexport const MAX_TAG_LENGTH = 50;\n\nexport const RATE_LIMIT = {\n requests_per_hour: 300,\n} as const;\n\nexport const LINK_STATUSES = [\"active\", \"flagged\"] as const;\n\nexport const API_KEY_SCOPES = [\"read\", \"write\", \"admin\"] as const;\n\nexport const ANALYTICS_PERIODS = [\"7d\", \"30d\", \"90d\", \"all\"] as const;\n\n\nexport const TIMEZONES = [\n \"America/New_York\",\n \"America/Chicago\",\n \"America/Denver\",\n \"America/Los_Angeles\",\n \"America/Anchorage\",\n \"Pacific/Honolulu\",\n \"Europe/London\",\n \"Europe/Paris\",\n \"Europe/Berlin\",\n \"Asia/Tokyo\",\n \"Asia/Shanghai\",\n \"Asia/Kolkata\",\n \"Australia/Sydney\",\n \"UTC\",\n] as const;\n","import type { ApiError } from \"@shorten/shared\";\n\nconst BASE_URL = \"https://shorten.dev/api/v1\";\n\nexport class ShortenApiError extends Error {\n constructor(\n public readonly status: number,\n public readonly body: ApiError,\n ) {\n super(body.message);\n this.name = \"ShortenApiError\";\n }\n}\n\nexport class ApiClient {\n private apiKey: string;\n\n constructor(apiKey: string) {\n this.apiKey = apiKey;\n }\n\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${BASE_URL}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== \"\") url.searchParams.set(k, v);\n }\n }\n return this.request(url, { method: \"GET\" });\n }\n\n async post<T>(path: string, body?: unknown): Promise<T> {\n return this.request(new URL(`${BASE_URL}${path}`), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n async patch<T>(path: string, body: unknown): Promise<T> {\n return this.request(new URL(`${BASE_URL}${path}`), {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n }\n\n async del(path: string): Promise<void> {\n const res = await fetch(new URL(`${BASE_URL}${path}`), {\n method: \"DELETE\",\n headers: { Authorization: `Bearer ${this.apiKey}` },\n });\n if (!res.ok) {\n const body = (await res.json()) as ApiError;\n throw new ShortenApiError(res.status, body);\n }\n }\n\n private async request<T>(url: URL, init: RequestInit): Promise<T> {\n const res = await fetch(url, {\n ...init,\n headers: {\n ...((init.headers as Record<string, string>) ?? {}),\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n\n if (!res.ok) {\n const body = (await res.json()) as ApiError;\n throw new ShortenApiError(res.status, body);\n }\n\n return (await res.json()) as T;\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface ShortenConfig {\n api_key?: string;\n default_format?: \"pretty\" | \"short\" | \"json\";\n copy_to_clipboard?: boolean;\n}\n\nconst CONFIG_PATH = join(homedir(), \".shorten.json\");\n\nexport function loadConfig(): ShortenConfig {\n try {\n const raw = readFileSync(CONFIG_PATH, \"utf-8\");\n return JSON.parse(raw) as ShortenConfig;\n } catch {\n return {};\n }\n}\n\nexport function resolveApiKey(flagKey?: string): string {\n const key = flagKey ?? process.env[\"SHORTEN_API_KEY\"] ?? loadConfig().api_key;\n\n if (!key) {\n console.error(\n \"No API key found. Set SHORTEN_API_KEY or pass --key.\\n\" +\n \"Generate one at https://shorten.dev/api-keys\",\n );\n process.exit(1);\n }\n\n return key;\n}\n","export async function copyToClipboard(text: string): Promise<boolean> {\n try {\n const { default: clipboardy } = await import(\"clipboardy\");\n await clipboardy.write(text);\n return true;\n } catch {\n return false;\n }\n}\n","import pc from \"picocolors\";\n\nexport type OutputFormat = \"pretty\" | \"json\" | \"quiet\";\n\nexport function resolveFormat(opts: {\n json?: boolean;\n quiet?: boolean;\n configDefault?: string;\n}): OutputFormat {\n if (opts.json) return \"json\";\n if (opts.quiet) return \"quiet\";\n if (opts.configDefault === \"json\") return \"json\";\n if (opts.configDefault === \"short\") return \"quiet\";\n return \"pretty\";\n}\n\nexport function success(msg: string): void {\n console.log(pc.green(\"✓\") + \" \" + msg);\n}\n\nexport function error(msg: string): void {\n console.error(pc.red(\"✗\") + \" \" + msg);\n}\n\nexport function info(msg: string): void {\n console.log(pc.dim(msg));\n}\n\nexport function json(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\nexport function table(\n headers: string[],\n rows: string[][],\n columnWidths?: number[],\n): void {\n const widths =\n columnWidths ??\n headers.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => (r[i] ?? \"\").length)),\n );\n\n const header = headers\n .map((h, i) => h.padEnd(widths[i] ?? h.length))\n .join(\" \");\n console.log(pc.bold(header));\n console.log(pc.dim(\"─\".repeat(header.length)));\n\n for (const row of rows) {\n console.log(\n row.map((c, i) => c.padEnd(widths[i] ?? c.length)).join(\" \"),\n );\n }\n}\n\nexport function formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\nexport function formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n });\n}\n\nexport function percentBar(pct: number, width = 10): string {\n const filled = Math.round((pct / 100) * width);\n return pc.cyan(\"█\".repeat(filled)) + pc.dim(\"░\".repeat(width - filled));\n}\n","import type { Command } from \"commander\";\nimport { MAX_TAGS_PER_LINK, MAX_TAG_LENGTH } from \"@shorten/shared\";\nimport type { CreateLinkRequest, CreateLinkResponse } from \"@shorten/shared\";\nimport { ApiClient, ShortenApiError } from \"../api/client.js\";\nimport { loadConfig, resolveApiKey } from \"../config.js\";\nimport { copyToClipboard } from \"../utils/clipboard.js\";\nimport * as out from \"../utils/output.js\";\n\ninterface ShortenOptions {\n slug?: string;\n tag?: string[];\n quiet?: boolean;\n json?: boolean;\n copy?: boolean;\n}\n\nexport function registerShortenCommand(program: Command): void {\n const cmd = program\n .command(\"create\", { isDefault: true, hidden: true })\n .argument(\"<url>\", \"URL to shorten\")\n .option(\"-s, --slug <slug>\", \"Custom slug\")\n .option(\"-t, --tag <tag>\", \"Add tags (repeatable)\", (val: string, acc: string[]) => [...acc, val], [] as string[])\n .option(\"-q, --quiet\", \"Output only the short URL\")\n .option(\"-j, --json\", \"Output as JSON\")\n .option(\"--no-copy\", \"Don't copy to clipboard\")\n .action(async (url: string, opts: ShortenOptions) => {\n const config = loadConfig();\n const globalKey = cmd.optsWithGlobals()[\"key\"] as string | undefined;\n const apiKey = resolveApiKey(globalKey);\n const client = new ApiClient(apiKey);\n const format = out.resolveFormat({\n json: opts.json,\n quiet: opts.quiet,\n configDefault: config.default_format,\n });\n\n const tags = opts.tag;\n if (tags && tags.length > MAX_TAGS_PER_LINK) {\n out.error(`Too many tags: got ${tags.length}, max is ${MAX_TAGS_PER_LINK}`);\n process.exit(1);\n return;\n }\n const longTag = tags?.find((t) => t.length > MAX_TAG_LENGTH);\n if (longTag) {\n out.error(`Tag \"${longTag}\" exceeds ${MAX_TAG_LENGTH} characters`);\n process.exit(1);\n return;\n }\n\n const body: CreateLinkRequest = {\n destination_url: url,\n custom_slug: opts.slug,\n tags,\n };\n\n try {\n const res = await client.post<CreateLinkResponse>(\"/links\", body);\n\n if (format === \"json\") {\n out.json(res);\n return;\n }\n\n if (format === \"quiet\") {\n console.log(res.short_url);\n return;\n }\n\n const shouldCopy =\n opts.copy !== false && (config.copy_to_clipboard ?? true);\n\n let suffix = \"\";\n if (shouldCopy) {\n const copied = await copyToClipboard(res.short_url);\n if (copied) suffix = \" (copied to clipboard)\";\n }\n\n out.success(`${res.short_url}${suffix}`);\n\n if (res.link.tags.length > 0) {\n out.info(` tags: ${res.link.tags.join(\", \")}`);\n }\n } catch (err) {\n if (err instanceof ShortenApiError) {\n out.error(err.message);\n process.exit(1);\n return;\n }\n throw err;\n }\n });\n}\n","import type { Command } from \"commander\";\nimport type { Link, PaginatedResponse } from \"@shorten/shared\";\nimport { ApiClient, ShortenApiError } from \"../api/client.js\";\nimport { resolveApiKey } from \"../config.js\";\nimport * as out from \"../utils/output.js\";\n\ninterface ListOptions {\n limit?: string;\n status?: string;\n search?: string;\n sort?: string;\n order?: string;\n json?: boolean;\n}\n\nexport function registerListCommand(program: Command): void {\n const cmd = program\n .command(\"list\")\n .description(\"List your links\")\n .option(\"-n, --limit <n>\", \"Number of results (default: 10, max: 100)\")\n .option(\"--status <status>\", \"Filter by active or flagged\")\n .option(\"--search <query>\", \"Search in slug, URL, and tags\")\n .option(\n \"--sort <field>\",\n \"Sort by created_at or slug\",\n )\n .option(\"--order <dir>\", \"asc or desc (default: desc)\")\n .option(\"-j, --json\", \"Output as JSON\")\n .action(async (opts: ListOptions) => {\n const globalKey = cmd.optsWithGlobals()[\"key\"] as string | undefined;\n const apiKey = resolveApiKey(globalKey);\n const client = new ApiClient(apiKey);\n\n const params: Record<string, string> = {};\n if (opts.limit) params[\"limit\"] = opts.limit;\n if (opts.status) params[\"status\"] = opts.status;\n if (opts.search) params[\"search\"] = opts.search;\n if (opts.sort) params[\"sort\"] = opts.sort;\n if (opts.order) params[\"order\"] = opts.order;\n\n try {\n const res = await client.get<PaginatedResponse<Link>>(\n \"/links\",\n params,\n );\n\n if (opts.json) {\n out.json(res);\n return;\n }\n\n if (res.data.length === 0) {\n out.info(\"No links found.\");\n return;\n }\n\n out.table(\n [\"Slug\", \"Destination\", \"Status\", \"Created\"],\n res.data.map((link) => [\n link.slug,\n truncate(link.destination_url, 40),\n link.status,\n out.formatDate(link.created_at),\n ]),\n );\n\n out.info(\n `\\nShowing ${res.data.length} of ${res.total} links (page ${res.page}/${res.total_pages})`,\n );\n } catch (err) {\n if (err instanceof ShortenApiError) {\n out.error(err.message);\n process.exit(1);\n return;\n }\n throw err;\n }\n });\n}\n\nfunction truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen - 1) + \"…\";\n}\n","import type { Command } from \"commander\";\nimport type { AnalyticsResponse } from \"@shorten/shared\";\nimport { ApiClient, ShortenApiError } from \"../api/client.js\";\nimport { resolveApiKey } from \"../config.js\";\nimport * as out from \"../utils/output.js\";\nimport pc from \"picocolors\";\n\ninterface StatsOptions {\n period?: string;\n json?: boolean;\n}\n\nexport function registerStatsCommand(program: Command): void {\n const cmd = program\n .command(\"stats <slug>\")\n .description(\"View click analytics for a link\")\n .option(\n \"-p, --period <period>\",\n \"Time window: 7d, 30d, 90d, or all (default: 7d)\",\n )\n .option(\"-j, --json\", \"Output as JSON\")\n .action(async (slug: string, opts: StatsOptions) => {\n const globalKey = cmd.optsWithGlobals()[\"key\"] as string | undefined;\n const apiKey = resolveApiKey(globalKey);\n const client = new ApiClient(apiKey);\n\n const params: Record<string, string> = {};\n if (opts.period) params[\"period\"] = opts.period;\n\n try {\n const res = await client.get<AnalyticsResponse>(\n `/links/${slug}/analytics`,\n params,\n );\n\n if (opts.json) {\n out.json(res);\n return;\n }\n\n const period = res.period === \"all\" ? \"all time\" : res.period;\n console.log(\n `\\n${pc.bold(`shorten.dev/${res.slug}`)} — ${pc.cyan(out.formatNumber(res.total_clicks))} clicks (${period})`,\n );\n console.log(\n pc.dim(\n ` ${out.formatNumber(res.unique_visitors)} unique visitors`,\n ),\n );\n\n console.log();\n\n const colWidth = 24;\n const maxRows = 5;\n\n // Build columns\n const countries = res.top_countries.slice(0, maxRows);\n const referrers = res.top_referrers.slice(0, maxRows);\n\n const totalForPct = res.total_clicks || 1;\n\n // Header\n console.log(\n pc.bold(\" Top countries\".padEnd(colWidth)) +\n pc.bold(\"Top referrers\".padEnd(colWidth)) +\n pc.bold(\"Devices\"),\n );\n\n // Rows\n const deviceEntries = [\n [\"Desktop\", res.devices.desktop],\n [\"Mobile\", res.devices.mobile],\n [\"Tablet\", res.devices.tablet],\n ] as const;\n\n for (let i = 0; i < maxRows; i++) {\n let line = \" \";\n\n // Country column\n const country = countries[i];\n if (country) {\n const pct = Math.round(\n (country.count / totalForPct) * 100,\n );\n line += `${country.country_code.padEnd(6)}${String(pct).padStart(3)}%`.padEnd(\n colWidth,\n );\n } else {\n line += \"\".padEnd(colWidth);\n }\n\n // Referrer column\n const referrer = referrers[i];\n if (referrer) {\n const pct = Math.round(\n (referrer.count / totalForPct) * 100,\n );\n line += `${referrer.referrer.padEnd(16)}${String(pct).padStart(3)}%`.padEnd(\n colWidth,\n );\n } else {\n line += \"\".padEnd(colWidth);\n }\n\n // Device column\n const device = deviceEntries[i];\n if (device) {\n const pct = Math.round(\n (device[1] / totalForPct) * 100,\n );\n line += `${device[0].padEnd(10)}${String(pct).padStart(3)}%`;\n }\n\n console.log(line);\n }\n\n console.log();\n } catch (err) {\n if (err instanceof ShortenApiError) {\n out.error(err.message);\n process.exit(1);\n return;\n }\n throw err;\n }\n });\n}\n","import type { Command } from \"commander\";\nimport type { UsageResponse } from \"@shorten/shared\";\nimport { ApiClient, ShortenApiError } from \"../api/client.js\";\nimport { resolveApiKey } from \"../config.js\";\nimport * as out from \"../utils/output.js\";\nimport pc from \"picocolors\";\n\ninterface WhoamiOptions {\n json?: boolean;\n}\n\nexport function registerWhoamiCommand(program: Command): void {\n const cmd = program\n .command(\"whoami\")\n .description(\"Display authenticated user and API key info\")\n .option(\"-j, --json\", \"Output as JSON\")\n .action(async (opts: WhoamiOptions) => {\n const globalKey = cmd.optsWithGlobals()[\"key\"] as string | undefined;\n const apiKey = resolveApiKey(globalKey);\n const client = new ApiClient(apiKey);\n\n try {\n const usage = await client.get<UsageResponse>(\"/usage\");\n\n if (opts.json) {\n out.json({\n key_prefix: maskKey(apiKey),\n rate_limit: usage,\n });\n return;\n }\n\n console.log(\n ` Key: ${pc.cyan(maskKey(apiKey))}`,\n );\n console.log(\n ` Rate limit: ${pc.bold(String(usage.remaining))}/${usage.limit} remaining`,\n );\n console.log(\n ` Resets: ${out.formatDate(usage.reset_at)}`,\n );\n } catch (err) {\n if (err instanceof ShortenApiError) {\n out.error(err.message);\n process.exit(1);\n return;\n }\n throw err;\n }\n });\n}\n\nfunction maskKey(key: string): string {\n if (key.length <= 8) return key;\n return key.slice(0, 3) + \"…\" + key.slice(-4);\n}\n","import { createProgram } from \"./cli.js\";\n\nconst program = createProgram();\nprogram.parseAsync(process.argv);\n"],"mappings":"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","names":["program","program","pc","program","pc","program","program"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@shorten-dev/cli",
3
+ "version": "0.1.0",
4
+ "description": "Shorten URLs from your terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "shorten": "./dist/index.js"
8
+ },
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup --watch",
13
+ "lint": "eslint",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest"
17
+ },
18
+ "dependencies": {
19
+ "clipboardy": "^4.0.0",
20
+ "commander": "^13.1.0",
21
+ "picocolors": "^1.1.1"
22
+ },
23
+ "devDependencies": {
24
+ "@shorten/shared": "*",
25
+ "tsup": "^8.4.0",
26
+ "typescript": "^5.8.3",
27
+ "vitest": "^2.1.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/shorten-dev/shorten.dev",
36
+ "directory": "packages/cli"
37
+ },
38
+ "keywords": ["url-shortener", "cli", "shorten"]
39
+ }