readable-cli 0.1.2 → 0.1.3
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 +44 -15
- package/dist/index.js +3574 -13
- package/package.json +9 -6
- package/dist/api.d.ts +0 -13
- package/dist/api.js +0 -43
- package/dist/commands/auth.d.ts +0 -2
- package/dist/commands/auth.js +0 -86
- package/dist/commands/pages.d.ts +0 -2
- package/dist/commands/pages.js +0 -99
- package/dist/commands/publish.d.ts +0 -2
- package/dist/commands/publish.js +0 -128
- package/dist/config.d.ts +0 -9
- package/dist/config.js +0 -34
- package/dist/fmt.d.ts +0 -14
- package/dist/fmt.js +0 -41
- package/dist/index.d.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "readable-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "CLI for Readable — publish Markdown from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,17 +11,20 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "
|
|
15
|
-
"dev": "
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsup --watch",
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"commander": "^12.1.0"
|
|
20
|
-
},
|
|
18
|
+
"dependencies": {},
|
|
21
19
|
"devDependencies": {
|
|
22
20
|
"@types/node": "^20.19.0",
|
|
21
|
+
"commander": "^12.1.0",
|
|
22
|
+
"tsup": "^8.5.1",
|
|
23
23
|
"typescript": "^5.7.0"
|
|
24
24
|
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
25
28
|
"engines": {
|
|
26
29
|
"node": ">=18.0.0"
|
|
27
30
|
},
|
package/dist/api.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export type ApiResponse<T> = {
|
|
2
|
-
ok: true;
|
|
3
|
-
status: number;
|
|
4
|
-
data: T;
|
|
5
|
-
} | {
|
|
6
|
-
ok: false;
|
|
7
|
-
status: number;
|
|
8
|
-
error: string;
|
|
9
|
-
};
|
|
10
|
-
export declare function apiRequest<T>(path: string, options?: {
|
|
11
|
-
method?: string;
|
|
12
|
-
body?: unknown;
|
|
13
|
-
}): Promise<ApiResponse<T>>;
|
package/dist/api.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { getApiKey, getApiBase } from "./config.js";
|
|
2
|
-
export async function apiRequest(path, options = {}) {
|
|
3
|
-
const key = await getApiKey();
|
|
4
|
-
if (!key) {
|
|
5
|
-
return { ok: false, status: 401, error: "Not authenticated. Run `readable login` to set your API key." };
|
|
6
|
-
}
|
|
7
|
-
const base = await getApiBase();
|
|
8
|
-
const url = `${base}${path}`;
|
|
9
|
-
const headers = {
|
|
10
|
-
Authorization: `Bearer ${key}`,
|
|
11
|
-
"X-Readable-Source": "cli",
|
|
12
|
-
};
|
|
13
|
-
if (options.body !== undefined) {
|
|
14
|
-
headers["Content-Type"] = "application/json";
|
|
15
|
-
}
|
|
16
|
-
let res;
|
|
17
|
-
try {
|
|
18
|
-
res = await fetch(url, {
|
|
19
|
-
method: options.method ?? "GET",
|
|
20
|
-
headers,
|
|
21
|
-
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
catch (e) {
|
|
25
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
26
|
-
return { ok: false, status: 0, error: `Network error: ${msg}` };
|
|
27
|
-
}
|
|
28
|
-
let data;
|
|
29
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
30
|
-
try {
|
|
31
|
-
data = ct.includes("application/json") ? await res.json() : await res.text();
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
data = null;
|
|
35
|
-
}
|
|
36
|
-
if (!res.ok) {
|
|
37
|
-
const errMsg = data && typeof data === "object" && "error" in data
|
|
38
|
-
? String(data.error)
|
|
39
|
-
: `HTTP ${res.status}`;
|
|
40
|
-
return { ok: false, status: res.status, error: errMsg };
|
|
41
|
-
}
|
|
42
|
-
return { ok: true, status: res.status, data: data };
|
|
43
|
-
}
|
package/dist/commands/auth.d.ts
DELETED
package/dist/commands/auth.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { readConfig, writeConfig, getApiKey, getApiBase } from "../config.js";
|
|
2
|
-
import { apiRequest } from "../api.js";
|
|
3
|
-
import { success, error, info, bold, dim, gray } from "../fmt.js";
|
|
4
|
-
import { createInterface } from "readline";
|
|
5
|
-
function prompt(question) {
|
|
6
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7
|
-
return new Promise((resolve) => {
|
|
8
|
-
rl.question(question, (answer) => {
|
|
9
|
-
rl.close();
|
|
10
|
-
resolve(answer.trim());
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
export function registerAuthCommands(program) {
|
|
15
|
-
const auth = program
|
|
16
|
-
.command("login")
|
|
17
|
-
.description("Save your Readable API key")
|
|
18
|
-
.option("--key <key>", "API key (skip interactive prompt)")
|
|
19
|
-
.option("--api-url <url>", "Override API base URL")
|
|
20
|
-
.action(async (opts) => {
|
|
21
|
-
let key = opts.key;
|
|
22
|
-
if (!key) {
|
|
23
|
-
const base = opts.apiUrl ?? (await (async () => {
|
|
24
|
-
const cfg = await readConfig();
|
|
25
|
-
return cfg.apiBase;
|
|
26
|
-
})());
|
|
27
|
-
console.log();
|
|
28
|
-
console.log(`${bold("Readable CLI")} — authenticate`);
|
|
29
|
-
console.log(dim(`API: ${base}`));
|
|
30
|
-
console.log();
|
|
31
|
-
console.log(`Get your key at: ${bold(`${base}/my-pages`)} → Settings → API Keys`);
|
|
32
|
-
console.log();
|
|
33
|
-
key = await prompt("Paste your API key: ");
|
|
34
|
-
}
|
|
35
|
-
if (!key) {
|
|
36
|
-
error("No key provided.");
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
// Validate the key by hitting the pages list endpoint
|
|
40
|
-
const existing = await readConfig();
|
|
41
|
-
const testBase = opts.apiUrl ?? existing.apiBase;
|
|
42
|
-
const testConfig = { ...existing, apiKey: key, apiBase: testBase };
|
|
43
|
-
// Temporarily write so apiRequest can pick it up
|
|
44
|
-
await writeConfig(testConfig);
|
|
45
|
-
const res = await apiRequest("/api/v1/pages");
|
|
46
|
-
if (!res.ok) {
|
|
47
|
-
error(`Invalid key or unreachable server: ${res.error}`);
|
|
48
|
-
// Roll back
|
|
49
|
-
await writeConfig(existing);
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
success(`Authenticated. Key saved to ~/.readable/config.json`);
|
|
53
|
-
info(`You have ${res.data.pages.length} page(s).`);
|
|
54
|
-
});
|
|
55
|
-
program
|
|
56
|
-
.command("logout")
|
|
57
|
-
.description("Remove saved API key")
|
|
58
|
-
.action(async () => {
|
|
59
|
-
const config = await readConfig();
|
|
60
|
-
if (!config.apiKey) {
|
|
61
|
-
info("No key stored.");
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const { apiKey: _removed, ...rest } = config;
|
|
65
|
-
await writeConfig(rest);
|
|
66
|
-
success("Logged out. API key removed.");
|
|
67
|
-
});
|
|
68
|
-
program
|
|
69
|
-
.command("whoami")
|
|
70
|
-
.description("Show the active API key and base URL")
|
|
71
|
-
.action(async () => {
|
|
72
|
-
const key = await getApiKey();
|
|
73
|
-
const base = await getApiBase();
|
|
74
|
-
if (!key) {
|
|
75
|
-
info("Not authenticated. Run `readable login` to set your API key.");
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
const masked = key.length > 8
|
|
79
|
-
? `${key.slice(0, 4)}${"•".repeat(key.length - 8)}${key.slice(-4)}`
|
|
80
|
-
: "•".repeat(key.length);
|
|
81
|
-
console.log(`${bold("Key:")} ${masked}`);
|
|
82
|
-
console.log(`${bold("Base:")} ${gray(base)}`);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
return auth;
|
|
86
|
-
}
|
package/dist/commands/pages.d.ts
DELETED
package/dist/commands/pages.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { apiRequest } from "../api.js";
|
|
2
|
-
import { success, error, info, bold, dim, gray } from "../fmt.js";
|
|
3
|
-
import { table } from "../fmt.js";
|
|
4
|
-
import { createInterface } from "readline";
|
|
5
|
-
function formatDate(iso) {
|
|
6
|
-
try {
|
|
7
|
-
return new Date(iso).toLocaleDateString("en-GB", {
|
|
8
|
-
year: "numeric",
|
|
9
|
-
month: "short",
|
|
10
|
-
day: "numeric",
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return iso;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
async function confirm(question) {
|
|
18
|
-
if (!process.stdin.isTTY)
|
|
19
|
-
return false;
|
|
20
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
rl.question(`${question} [y/N] `, (ans) => {
|
|
23
|
-
rl.close();
|
|
24
|
-
resolve(ans.trim().toLowerCase() === "y");
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function registerPagesCommand(program) {
|
|
29
|
-
const pages = program
|
|
30
|
-
.command("pages")
|
|
31
|
-
.description("Manage your Readable pages");
|
|
32
|
-
pages
|
|
33
|
-
.command("list")
|
|
34
|
-
.description("List all your pages")
|
|
35
|
-
.option("--json", "Output raw JSON")
|
|
36
|
-
.action(async (opts) => {
|
|
37
|
-
const res = await apiRequest("/api/v1/pages");
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
error(res.error);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
const items = res.data.pages;
|
|
43
|
-
if (opts.json) {
|
|
44
|
-
console.log(JSON.stringify(items, null, 2));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (items.length === 0) {
|
|
48
|
-
info("No pages yet. Run `readable publish <file>` to create one.");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
console.log();
|
|
52
|
-
table(["Title", "ID / Slug", "Visibility", "Views", "Updated"], items.map((p) => [
|
|
53
|
-
p.title ?? dim("Untitled"),
|
|
54
|
-
p.slug ?? gray(p.id),
|
|
55
|
-
p.visibility,
|
|
56
|
-
p.view_count,
|
|
57
|
-
formatDate(p.updated_at),
|
|
58
|
-
]));
|
|
59
|
-
console.log();
|
|
60
|
-
info(`${items.length} page${items.length === 1 ? "" : "s"}`);
|
|
61
|
-
});
|
|
62
|
-
pages
|
|
63
|
-
.command("open <id>")
|
|
64
|
-
.description("Print the URL of a page")
|
|
65
|
-
.action(async (id) => {
|
|
66
|
-
const res = await apiRequest("/api/v1/pages");
|
|
67
|
-
if (!res.ok) {
|
|
68
|
-
error(res.error);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
const page = res.data.pages.find((p) => p.id === id || p.slug === id);
|
|
72
|
-
if (!page) {
|
|
73
|
-
error(`Page not found: ${id}`);
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
console.log(bold(page.url));
|
|
77
|
-
});
|
|
78
|
-
pages
|
|
79
|
-
.command("delete <id>")
|
|
80
|
-
.description("Delete a page by ID")
|
|
81
|
-
.option("-y, --yes", "Skip confirmation prompt")
|
|
82
|
-
.action(async (id, opts) => {
|
|
83
|
-
if (!opts.yes) {
|
|
84
|
-
const ok = await confirm(`Delete page ${bold(id)}?`);
|
|
85
|
-
if (!ok) {
|
|
86
|
-
info("Aborted.");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const res = await apiRequest(`/api/v1/pages/${id}`, {
|
|
91
|
-
method: "DELETE",
|
|
92
|
-
});
|
|
93
|
-
if (!res.ok) {
|
|
94
|
-
error(`Delete failed: ${res.error}`);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
success(`Deleted page ${id}`);
|
|
98
|
-
});
|
|
99
|
-
}
|
package/dist/commands/publish.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { readFile, watch } from "fs/promises";
|
|
2
|
-
import { apiRequest } from "../api.js";
|
|
3
|
-
import { success, error, info, warn, bold, dim } from "../fmt.js";
|
|
4
|
-
async function readInput(filePath) {
|
|
5
|
-
if (filePath === "-") {
|
|
6
|
-
// Read from stdin
|
|
7
|
-
const chunks = [];
|
|
8
|
-
for await (const chunk of process.stdin) {
|
|
9
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
10
|
-
}
|
|
11
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
12
|
-
}
|
|
13
|
-
return readFile(filePath, "utf8");
|
|
14
|
-
}
|
|
15
|
-
async function doPublish(raw, opts, isWatch = false) {
|
|
16
|
-
const body = { raw };
|
|
17
|
-
if (opts.visibility)
|
|
18
|
-
body.settings = { visibility: opts.visibility };
|
|
19
|
-
if (opts.update) {
|
|
20
|
-
// PATCH existing page
|
|
21
|
-
const patchBody = { raw };
|
|
22
|
-
if (opts.slug)
|
|
23
|
-
patchBody.slug = opts.slug;
|
|
24
|
-
if (opts.visibility)
|
|
25
|
-
patchBody.visibility = opts.visibility;
|
|
26
|
-
const res = await apiRequest(`/api/v1/pages/${opts.update}`, {
|
|
27
|
-
method: "PATCH",
|
|
28
|
-
body: patchBody,
|
|
29
|
-
});
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
error(`Update failed: ${res.error}`);
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
if (isWatch) {
|
|
35
|
-
info(`Updated → ${bold(res.data.url)}`);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
success(`Updated: ${bold(res.data.url)}`);
|
|
39
|
-
}
|
|
40
|
-
return res.data;
|
|
41
|
-
}
|
|
42
|
-
// POST new page
|
|
43
|
-
const res = await apiRequest("/api/v1/publish", {
|
|
44
|
-
method: "POST",
|
|
45
|
-
body: { raw },
|
|
46
|
-
});
|
|
47
|
-
if (!res.ok) {
|
|
48
|
-
error(`Publish failed: ${res.error}`);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
const { id, url } = res.data;
|
|
52
|
-
// Apply post-publish metadata patches
|
|
53
|
-
if (opts.slug || opts.visibility) {
|
|
54
|
-
const metaPatch = {};
|
|
55
|
-
if (opts.slug)
|
|
56
|
-
metaPatch.slug = opts.slug;
|
|
57
|
-
if (opts.visibility)
|
|
58
|
-
metaPatch.visibility = opts.visibility;
|
|
59
|
-
const patchRes = await apiRequest(`/api/v1/pages/${id}`, {
|
|
60
|
-
method: "PATCH",
|
|
61
|
-
body: metaPatch,
|
|
62
|
-
});
|
|
63
|
-
if (!patchRes.ok) {
|
|
64
|
-
warn(`Published but metadata patch failed: ${patchRes.error}`);
|
|
65
|
-
success(`Published: ${bold(url)}`);
|
|
66
|
-
return { id, url };
|
|
67
|
-
}
|
|
68
|
-
success(`Published: ${bold(patchRes.data.url)}`);
|
|
69
|
-
return { id, url: patchRes.data.url };
|
|
70
|
-
}
|
|
71
|
-
success(`Published: ${bold(url)}`);
|
|
72
|
-
console.log(dim(` ID: ${id}`));
|
|
73
|
-
return { id, url };
|
|
74
|
-
}
|
|
75
|
-
export function registerPublishCommand(program) {
|
|
76
|
-
program
|
|
77
|
-
.command("publish [file]")
|
|
78
|
-
.description("Publish a Markdown file (use - for stdin)")
|
|
79
|
-
.option("--slug <slug>", "Set a custom URL slug")
|
|
80
|
-
.option("--visibility <v>", "public or unlisted (default: public)", "public")
|
|
81
|
-
.option("--update <id>", "Update an existing page by ID instead of creating new")
|
|
82
|
-
.option("--watch", "Watch file for changes and re-publish automatically")
|
|
83
|
-
.action(async (file, opts) => {
|
|
84
|
-
const filePath = file ?? "-";
|
|
85
|
-
if (opts.watch && filePath === "-") {
|
|
86
|
-
error("--watch cannot be used with stdin.");
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
// Initial publish
|
|
90
|
-
let raw;
|
|
91
|
-
try {
|
|
92
|
-
raw = await readInput(filePath);
|
|
93
|
-
}
|
|
94
|
-
catch (e) {
|
|
95
|
-
error(`Cannot read "${filePath}": ${e instanceof Error ? e.message : String(e)}`);
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
const result = await doPublish(raw, opts);
|
|
99
|
-
if (!result)
|
|
100
|
-
process.exit(1);
|
|
101
|
-
if (!opts.watch)
|
|
102
|
-
return;
|
|
103
|
-
// --watch mode: watch file for changes
|
|
104
|
-
const pageId = opts.update ?? result.id;
|
|
105
|
-
info(`Watching ${bold(filePath)} for changes… (Ctrl+C to stop)`);
|
|
106
|
-
console.log();
|
|
107
|
-
try {
|
|
108
|
-
const watcher = watch(filePath);
|
|
109
|
-
for await (const event of watcher) {
|
|
110
|
-
if (event.eventType !== "change")
|
|
111
|
-
continue;
|
|
112
|
-
// Debounce: small delay to let the write finish
|
|
113
|
-
await new Promise((r) => setTimeout(r, 80));
|
|
114
|
-
try {
|
|
115
|
-
const updated = await readFile(filePath, "utf8");
|
|
116
|
-
await doPublish(updated, { ...opts, update: pageId }, true);
|
|
117
|
-
}
|
|
118
|
-
catch (readErr) {
|
|
119
|
-
warn(`Read error: ${readErr instanceof Error ? readErr.message : String(readErr)}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
error(`Watch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
package/dist/config.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const DEFAULT_API_BASE = "https://readable.ashwinsathian.com";
|
|
2
|
-
export type Config = {
|
|
3
|
-
apiKey?: string;
|
|
4
|
-
apiBase: string;
|
|
5
|
-
};
|
|
6
|
-
export declare function readConfig(): Promise<Config>;
|
|
7
|
-
export declare function writeConfig(config: Config): Promise<void>;
|
|
8
|
-
export declare function getApiKey(): Promise<string | null>;
|
|
9
|
-
export declare function getApiBase(): Promise<string>;
|
package/dist/config.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
4
|
-
const CONFIG_DIR = join(homedir(), ".readable");
|
|
5
|
-
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
6
|
-
export const DEFAULT_API_BASE = "https://readable.ashwinsathian.com";
|
|
7
|
-
export async function readConfig() {
|
|
8
|
-
try {
|
|
9
|
-
const raw = await readFile(CONFIG_PATH, "utf8");
|
|
10
|
-
const parsed = JSON.parse(raw);
|
|
11
|
-
return { apiBase: DEFAULT_API_BASE, ...parsed };
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return { apiBase: DEFAULT_API_BASE };
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export async function writeConfig(config) {
|
|
18
|
-
await mkdir(CONFIG_DIR, { recursive: true });
|
|
19
|
-
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
20
|
-
}
|
|
21
|
-
export async function getApiKey() {
|
|
22
|
-
const env = process.env.READABLE_API_KEY;
|
|
23
|
-
if (env)
|
|
24
|
-
return env;
|
|
25
|
-
const config = await readConfig();
|
|
26
|
-
return config.apiKey ?? null;
|
|
27
|
-
}
|
|
28
|
-
export async function getApiBase() {
|
|
29
|
-
const env = process.env.READABLE_API_URL;
|
|
30
|
-
if (env)
|
|
31
|
-
return env.replace(/\/$/, "");
|
|
32
|
-
const config = await readConfig();
|
|
33
|
-
return config.apiBase.replace(/\/$/, "");
|
|
34
|
-
}
|
package/dist/fmt.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export declare const dim: (s: string) => string;
|
|
2
|
-
export declare const bold: (s: string) => string;
|
|
3
|
-
export declare const green: (s: string) => string;
|
|
4
|
-
export declare const red: (s: string) => string;
|
|
5
|
-
export declare const yellow: (s: string) => string;
|
|
6
|
-
export declare const cyan: (s: string) => string;
|
|
7
|
-
export declare const gray: (s: string) => string;
|
|
8
|
-
export declare function success(msg: string): void;
|
|
9
|
-
export declare function info(msg: string): void;
|
|
10
|
-
export declare function warn(msg: string): void;
|
|
11
|
-
export declare function error(msg: string): void;
|
|
12
|
-
type Row = (string | number | null | undefined)[];
|
|
13
|
-
export declare function table(headers: string[], rows: Row[]): void;
|
|
14
|
-
export {};
|
package/dist/fmt.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// ANSI colour helpers — fall back gracefully when NO_COLOR is set
|
|
2
|
-
const NO_COLOR = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
3
|
-
const c = (code, s) => (NO_COLOR ? s : `\x1b[${code}m${s}\x1b[0m`);
|
|
4
|
-
export const dim = (s) => c(2, s);
|
|
5
|
-
export const bold = (s) => c(1, s);
|
|
6
|
-
export const green = (s) => c(32, s);
|
|
7
|
-
export const red = (s) => c(31, s);
|
|
8
|
-
export const yellow = (s) => c(33, s);
|
|
9
|
-
export const cyan = (s) => c(36, s);
|
|
10
|
-
export const gray = (s) => c(90, s);
|
|
11
|
-
export function success(msg) {
|
|
12
|
-
console.log(`${green("✓")} ${msg}`);
|
|
13
|
-
}
|
|
14
|
-
export function info(msg) {
|
|
15
|
-
console.log(`${cyan("→")} ${msg}`);
|
|
16
|
-
}
|
|
17
|
-
export function warn(msg) {
|
|
18
|
-
console.warn(`${yellow("!")} ${msg}`);
|
|
19
|
-
}
|
|
20
|
-
export function error(msg) {
|
|
21
|
-
console.error(`${red("✗")} ${msg}`);
|
|
22
|
-
}
|
|
23
|
-
export function table(headers, rows) {
|
|
24
|
-
if (rows.length === 0) {
|
|
25
|
-
console.log(dim("(no results)"));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
// Compute column widths
|
|
29
|
-
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => String(r[i] ?? "").length)));
|
|
30
|
-
const divider = widths.map((w) => "─".repeat(w + 2)).join("┼");
|
|
31
|
-
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
32
|
-
const headerRow = widths
|
|
33
|
-
.map((w, i) => ` ${bold(pad(headers[i], w))} `)
|
|
34
|
-
.join("│");
|
|
35
|
-
const dataRows = rows.map((row) => widths.map((w, i) => ` ${pad(String(row[i] ?? ""), w)} `).join("│"));
|
|
36
|
-
console.log(headerRow);
|
|
37
|
-
console.log(dim(divider));
|
|
38
|
-
for (const row of dataRows) {
|
|
39
|
-
console.log(row);
|
|
40
|
-
}
|
|
41
|
-
}
|
package/dist/index.d.ts
DELETED