@adriangalilea/utils 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/bot/allow-list.d.ts +38 -0
- package/dist/bot/allow-list.d.ts.map +1 -0
- package/dist/bot/allow-list.js +41 -0
- package/dist/bot/allow-list.js.map +1 -0
- package/dist/cli/index.d.ts +66 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +92 -0
- package/dist/cli/index.js.map +1 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -304,6 +304,7 @@ Then `pnpm install`. Every `ctx.send` / `ctx.sendDocument` / `ctx.reply` / etc.
|
|
|
304
304
|
|---|---|
|
|
305
305
|
| `@adriangalilea/utils/bot/kit` | `gracefulStart(bot, opts?)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs. DMs the admin `@<bot> started.` / `@<bot> shutting down.` by default when `KEV.TELEGRAM_ADMIN_ID` is set (graceful only — crashes don't trigger `onStop`); pass `notifyAdmin: false` to disable or `notifyAdmin: 12345` for an explicit chat id.<br>`adminContext({ adminId? })` — reads `TELEGRAM_ADMIN_ID` from `kev` (with optional hardcoded fallback), decorates `ctx.adminId` + `ctx.isAdmin`.<br>`botSession(opts)` — **drop-in replacement for `@gramio/session`'s `session()`** that auto-namespaces every key as `bot-<id>:<senderId>` using `ctx.bot.info.id` (populated by `getMe()` at startup). Use this instead of `session()` — full stop. Multiple bots sharing one Redis stay isolated by construction; every plugin in this package derives the same prefix internally via `botStorageKey(ctx, userId)` / `botSubKey(ctx, sub)`. No regex, no manual prefix argument, no way to forget.<br>`prefixStorage(storage, prefix)` — escape hatch for adding a top-level prefix on top of the bot-id namespace; almost never needed. |
|
|
306
306
|
| `@adriangalilea/utils/bot/access-control` | Personal-bot ACL — gates non-admin/non-default users; admin gets DM with `[✅ Aprobar][❌ Denegar]` on first attempt; `/access` opens a persistent menu (revoke / reapprove / list pending). Backed by `@gramio/session` per-user + a small index. **Native alternative**: BotFather → Bot Settings → Access → "Restrict bot usage" — flat allow-list at Telegram. Use this plugin when you want in-bot approval flow instead of a BotFather round-trip; both can coexist. |
|
|
307
|
+
| `@adriangalilea/utils/bot/allow-list` | Static allow-list by **id and/or @username** — stateless, no session/storage. `allowList({ ids?, usernames? })` is a plugin that decorates `ctx.allowed` (boolean); gate in your handlers (`if (!ctx.allowed) return`). `makeAllowList(...)` is the pure framework-free predicate. The light counterpart to `access-control` (which adds an approve/deny flow + revocable store, needing session+storage). Username caveat: a `@username` is optional and mutable, and the Bot API can't resolve username→id ahead of time — prefer `ids` when known, `usernames` is the pragmatic fallback. |
|
|
307
308
|
| `@adriangalilea/utils/bot/coalesce` | Joins client-split inbound messages back into one. When a user pastes >4096 chars, Telegram clients fragment it into separate `message` updates with no marker. Middleware detects the burst and emits one combined event. |
|
|
308
309
|
| `@adriangalilea/utils/bot/llm` | The full LLM-chatbot pipeline in one module. **Input:** `streamChat(response)` parses OpenAI-compatible SSE (OpenAI, vllm, mlx-lm, llama.cpp, Together, Groq, …) into a typed `AsyncGenerator<{type: 'content' \| 'reasoning', text}>`. **Output:** `ctx.startStream()` (low-level: debounced markdown to Telegram, 4000-char split, exposes `wasPartial` after `.end()`). `ctx.startChatStream(response)` (high-level: consumes the stream, renders reasoning as a Telegram `expandable_blockquote` entity + content as streamed markdown — both go through `markdownToFormattable` with graceful degradation — returns `{ content, reasoning }`). **History:** `llmHistory({...})` returns `.plugin` (decorates `ctx.llm` with `.add() / .get() / .clear() / .all() / .clearAll()`, per-(user, thread) OpenAI `ChatMessage` shape, persisted in the shared session record so the menu's 🗑 Forget wipes it automatically) AND `.menuItem` (drop-in "🗑 Delete this thread" for `botMenu` — wipes the LLM history AND calls `deleteForumTopic` so the Telegram thread + all its messages disappear from the chat; falls back to Redis-only clear when no `threadId` is present). |
|
|
309
310
|
| `@adriangalilea/utils/bot/menu` | `botMenu({ command, description, items, privacy?, personalData?, adminContact })` — `/settings` command + InlineKeyboard router. Root view always renders a `🔒 Privacy & data` submenu button that wraps the privacy policy link plus (if `personalData: { storage }`) 🗑 Forget + 📥 Export buttons. `toggleMenuItem({ id, read, write, label: { off, on }, toast? })` — convenience factory for boolean-toggle items with dynamic label + optional toast, storage-agnostic via `read`/`write` closures. |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static allow-list by id AND/OR username — stateless, no storage/session.
|
|
3
|
+
*
|
|
4
|
+
* The heavyweight counterpart is `bot/access-control` (approve/deny flow,
|
|
5
|
+
* revocable store, admin menu — needs session + storage). This is the light
|
|
6
|
+
* version: a hardcoded allow-list you control in code, gating by Telegram user
|
|
7
|
+
* id and/or @username. Decorates every message/callback context with
|
|
8
|
+
* `ctx.allowed` (boolean); you gate in your handlers (`if (!ctx.allowed) return`).
|
|
9
|
+
*
|
|
10
|
+
* Caveat on usernames: a @username is optional and can change, and the Bot API
|
|
11
|
+
* has no way to resolve a username → id ahead of time. Prefer `ids` when you
|
|
12
|
+
* know them; `usernames` is the pragmatic fallback when you only have the handle.
|
|
13
|
+
*
|
|
14
|
+
* import { allowList } from '@adriangalilea/utils/bot/allow-list'
|
|
15
|
+
*
|
|
16
|
+
* bot
|
|
17
|
+
* .extend(allowList({ ids: [190202471], usernames: ['mrwagecuck'] }))
|
|
18
|
+
* .on('message', (ctx) => { if (!ctx.allowed) return; ... })
|
|
19
|
+
*/
|
|
20
|
+
import { Plugin } from "gramio";
|
|
21
|
+
export type AllowListOptions = {
|
|
22
|
+
/** Allowed Telegram user ids — exact and robust. */
|
|
23
|
+
ids?: ReadonlyArray<number>;
|
|
24
|
+
/** Allowed @usernames — case-insensitive, leading `@` optional. Less robust (usernames change). */
|
|
25
|
+
usernames?: ReadonlyArray<string>;
|
|
26
|
+
};
|
|
27
|
+
/** Returns whether a user (by id/username) is on the allow-list — pure, framework-free. */
|
|
28
|
+
export declare const makeAllowList: (opts?: AllowListOptions) => (user?: {
|
|
29
|
+
id?: number;
|
|
30
|
+
username?: string;
|
|
31
|
+
}) => boolean;
|
|
32
|
+
/** gramio plugin: decorates `ctx.allowed` (boolean) for message/callback updates. */
|
|
33
|
+
export declare const allowList: (opts?: AllowListOptions) => Plugin<{}, import("gramio").DeriveDefinitions & {
|
|
34
|
+
global: {
|
|
35
|
+
allowed: boolean;
|
|
36
|
+
};
|
|
37
|
+
}, {}>;
|
|
38
|
+
//# sourceMappingURL=allow-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allow-list.d.ts","sourceRoot":"","sources":["../../src/bot/allow-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG;IAC9B,oDAAoD;IACpD,GAAG,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,mGAAmG;IACnG,SAAS,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAClC,CAAC;AAIF,2FAA2F;AAC3F,eAAO,MAAM,aAAa,GAAI,OAAM,gBAAqB,MAGhD,OAAO;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,YAIjD,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,SAAS,GAAI,OAAM,gBAAqB;;;;MAUpD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static allow-list by id AND/OR username — stateless, no storage/session.
|
|
3
|
+
*
|
|
4
|
+
* The heavyweight counterpart is `bot/access-control` (approve/deny flow,
|
|
5
|
+
* revocable store, admin menu — needs session + storage). This is the light
|
|
6
|
+
* version: a hardcoded allow-list you control in code, gating by Telegram user
|
|
7
|
+
* id and/or @username. Decorates every message/callback context with
|
|
8
|
+
* `ctx.allowed` (boolean); you gate in your handlers (`if (!ctx.allowed) return`).
|
|
9
|
+
*
|
|
10
|
+
* Caveat on usernames: a @username is optional and can change, and the Bot API
|
|
11
|
+
* has no way to resolve a username → id ahead of time. Prefer `ids` when you
|
|
12
|
+
* know them; `usernames` is the pragmatic fallback when you only have the handle.
|
|
13
|
+
*
|
|
14
|
+
* import { allowList } from '@adriangalilea/utils/bot/allow-list'
|
|
15
|
+
*
|
|
16
|
+
* bot
|
|
17
|
+
* .extend(allowList({ ids: [190202471], usernames: ['mrwagecuck'] }))
|
|
18
|
+
* .on('message', (ctx) => { if (!ctx.allowed) return; ... })
|
|
19
|
+
*/
|
|
20
|
+
import { Plugin } from "gramio";
|
|
21
|
+
const normalize = (u) => u.replace(/^@/, "").toLowerCase();
|
|
22
|
+
/** Returns whether a user (by id/username) is on the allow-list — pure, framework-free. */
|
|
23
|
+
export const makeAllowList = (opts = {}) => {
|
|
24
|
+
const ids = new Set(opts.ids ?? []);
|
|
25
|
+
const usernames = new Set((opts.usernames ?? []).map(normalize));
|
|
26
|
+
return (user) => !!user &&
|
|
27
|
+
((user.id !== undefined && ids.has(user.id)) ||
|
|
28
|
+
(user.username !== undefined && usernames.has(normalize(user.username))));
|
|
29
|
+
};
|
|
30
|
+
/** gramio plugin: decorates `ctx.allowed` (boolean) for message/callback updates. */
|
|
31
|
+
export const allowList = (opts = {}) => {
|
|
32
|
+
const isAllowed = makeAllowList(opts);
|
|
33
|
+
return new Plugin("@adriangalilea/utils/bot/allow-list").derive((ctx) => {
|
|
34
|
+
const id = "senderId" in ctx && typeof ctx.senderId === "number"
|
|
35
|
+
? ctx.senderId
|
|
36
|
+
: undefined;
|
|
37
|
+
const username = ctx.from?.username;
|
|
38
|
+
return { allowed: isAllowed({ id, username }) };
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=allow-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allow-list.js","sourceRoot":"","sources":["../../src/bot/allow-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAShC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAEnE,2FAA2F;AAC3F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAyB,EAAE,EAAE,EAAE;IAC5D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IACjE,OAAO,CAAC,IAAyC,EAAE,EAAE,CACpD,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3C,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7E,CAAC,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAyB,EAAE,EAAE,EAAE;IACxD,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,IAAI,MAAM,CAAC,qCAAqC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QACvE,MAAM,EAAE,GACP,UAAU,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;YACpD,CAAC,CAAC,GAAG,CAAC,QAAQ;YACd,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,QAAQ,GAAI,GAAwC,CAAC,IAAI,EAAE,QAAQ,CAAC;QAC1E,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cli` — terminal presentation: aligned tables, key/value blocks, trees, a
|
|
3
|
+
* semantic palette. Separate from `format` (which is universal value formatting,
|
|
4
|
+
* for every layer) — this is terminal-scoped output.
|
|
5
|
+
*
|
|
6
|
+
* Colors come from `universal/log` and auto-disable on non-TTY / NO_COLOR, so a
|
|
7
|
+
* `table()` renders colored in a terminal and as plain aligned text in a pipe,
|
|
8
|
+
* log file, or a bot's monospace block — correct everywhere.
|
|
9
|
+
*
|
|
10
|
+
* Alignment is ANSI-aware: padding is computed on the *visible* width (escape
|
|
11
|
+
* codes stripped), so colored cells line up.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { table, kv, ui, indent } from '@adriangalilea/utils/cli'
|
|
15
|
+
*
|
|
16
|
+
* console.log(table(
|
|
17
|
+
* [['Ada', ui.accent('ada@x.com'), ui.muted('work')],
|
|
18
|
+
* ['Bo', ui.accent('bo@y.com'), ui.muted('home')]],
|
|
19
|
+
* { head: ['name', 'email', 'label'] },
|
|
20
|
+
* ))
|
|
21
|
+
*/
|
|
22
|
+
/** Visible width of a string — ANSI escape codes stripped, counted by code point. */
|
|
23
|
+
export declare const width: (s: string) => number;
|
|
24
|
+
/** Truncate to `n` visible chars with an ellipsis. Assumes `s` is unstyled. */
|
|
25
|
+
export declare const truncate: (s: string, n: number) => string;
|
|
26
|
+
/** Semantic palette — use these, not raw colors, so intent stays consistent. */
|
|
27
|
+
export declare const ui: {
|
|
28
|
+
head: (s: string) => string;
|
|
29
|
+
accent: (s: string) => string;
|
|
30
|
+
muted: (s: string) => string;
|
|
31
|
+
ok: (s: string) => string;
|
|
32
|
+
warn: (s: string) => string;
|
|
33
|
+
bad: (s: string) => string;
|
|
34
|
+
/** ids / refs — de-emphasized monospace-ish */
|
|
35
|
+
ref: (s: string) => string;
|
|
36
|
+
};
|
|
37
|
+
export interface TableOpts {
|
|
38
|
+
/** Column headers (bolded). */
|
|
39
|
+
head?: string[];
|
|
40
|
+
/** Per-column alignment; default all left. */
|
|
41
|
+
align?: ("l" | "r")[];
|
|
42
|
+
/** Spaces between columns (default 2). */
|
|
43
|
+
gap?: number;
|
|
44
|
+
/** Left indent for the whole table (default 0). */
|
|
45
|
+
indent?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Render rows as aligned columns. Cells may be pre-colored — widths use visible
|
|
49
|
+
* length so colored columns still line up. The last left-aligned column isn't
|
|
50
|
+
* right-padded (no trailing whitespace).
|
|
51
|
+
*/
|
|
52
|
+
export declare function table(rows: string[][], opts?: TableOpts): string;
|
|
53
|
+
/** Key/value block (aligned keys), e.g. for a detail view. */
|
|
54
|
+
export declare function kv(pairs: [string, string][], opts?: {
|
|
55
|
+
indent?: number;
|
|
56
|
+
gap?: number;
|
|
57
|
+
}): string;
|
|
58
|
+
/** Indent every line of a (possibly multi-line) string by `n` spaces. */
|
|
59
|
+
export declare const indent: (s: string, n: number) => string;
|
|
60
|
+
/** A labeled node with child lines drawn under it. */
|
|
61
|
+
export declare function tree(label: string, children: string[], opts?: {
|
|
62
|
+
indent?: number;
|
|
63
|
+
}): string;
|
|
64
|
+
/** A small inline badge, e.g. `badge('golden', ui.ok)`. */
|
|
65
|
+
export declare const badge: (text: string, style?: (s: string) => string) => string;
|
|
66
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,qFAAqF;AACrF,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,KAAG,MAA4C,CAAC;AAE/E,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MACsB,CAAC;AAOvE,gFAAgF;AAChF,eAAO,MAAM,EAAE;cACJ,MAAM;gBACJ,MAAM;eACP,MAAM;YACT,MAAM;cACJ,MAAM;aACP,MAAM;IACf,+CAA+C;aACtC,MAAM;CACf,CAAC;AAEF,MAAM,WAAW,SAAS;IACzB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;IACtB,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAE,SAAc,GAAG,MAAM,CAwBpE;AAED,8DAA8D;AAC9D,wBAAgB,EAAE,CACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACzB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,MAAM,CAOR;AAED,yEAAyE;AACzE,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAIjC,CAAC;AAEd,sDAAsD;AACtD,wBAAgB,IAAI,CACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAAE,EAClB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,MAAM,CAGR;AAED,2DAA2D;AAC3D,eAAO,MAAM,KAAK,GACjB,MAAM,MAAM,EACZ,QAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAiB,KACrC,MAAqB,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cli` — terminal presentation: aligned tables, key/value blocks, trees, a
|
|
3
|
+
* semantic palette. Separate from `format` (which is universal value formatting,
|
|
4
|
+
* for every layer) — this is terminal-scoped output.
|
|
5
|
+
*
|
|
6
|
+
* Colors come from `universal/log` and auto-disable on non-TTY / NO_COLOR, so a
|
|
7
|
+
* `table()` renders colored in a terminal and as plain aligned text in a pipe,
|
|
8
|
+
* log file, or a bot's monospace block — correct everywhere.
|
|
9
|
+
*
|
|
10
|
+
* Alignment is ANSI-aware: padding is computed on the *visible* width (escape
|
|
11
|
+
* codes stripped), so colored cells line up.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { table, kv, ui, indent } from '@adriangalilea/utils/cli'
|
|
15
|
+
*
|
|
16
|
+
* console.log(table(
|
|
17
|
+
* [['Ada', ui.accent('ada@x.com'), ui.muted('work')],
|
|
18
|
+
* ['Bo', ui.accent('bo@y.com'), ui.muted('home')]],
|
|
19
|
+
* { head: ['name', 'email', 'label'] },
|
|
20
|
+
* ))
|
|
21
|
+
*/
|
|
22
|
+
import { bold, cyan, dim, gray, green, red, yellow } from "../universal/log.js";
|
|
23
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escapes is the point
|
|
24
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
25
|
+
/** Visible width of a string — ANSI escape codes stripped, counted by code point. */
|
|
26
|
+
export const width = (s) => [...s.replace(ANSI_RE, "")].length;
|
|
27
|
+
/** Truncate to `n` visible chars with an ellipsis. Assumes `s` is unstyled. */
|
|
28
|
+
export const truncate = (s, n) => width(s) > n ? `${[...s].slice(0, Math.max(0, n - 1)).join("")}…` : s;
|
|
29
|
+
const padEndV = (s, n) => s + " ".repeat(Math.max(0, n - width(s)));
|
|
30
|
+
const padStartV = (s, n) => " ".repeat(Math.max(0, n - width(s))) + s;
|
|
31
|
+
/** Semantic palette — use these, not raw colors, so intent stays consistent. */
|
|
32
|
+
export const ui = {
|
|
33
|
+
head: (s) => bold(s),
|
|
34
|
+
accent: (s) => cyan(s),
|
|
35
|
+
muted: (s) => dim(s),
|
|
36
|
+
ok: (s) => green(s),
|
|
37
|
+
warn: (s) => yellow(s),
|
|
38
|
+
bad: (s) => red(s),
|
|
39
|
+
/** ids / refs — de-emphasized monospace-ish */
|
|
40
|
+
ref: (s) => gray(s),
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Render rows as aligned columns. Cells may be pre-colored — widths use visible
|
|
44
|
+
* length so colored columns still line up. The last left-aligned column isn't
|
|
45
|
+
* right-padded (no trailing whitespace).
|
|
46
|
+
*/
|
|
47
|
+
export function table(rows, opts = {}) {
|
|
48
|
+
const gap = " ".repeat(opts.gap ?? 2);
|
|
49
|
+
const lead = " ".repeat(opts.indent ?? 0);
|
|
50
|
+
const body = opts.head ? [opts.head, ...rows] : rows;
|
|
51
|
+
const cols = Math.max(0, ...body.map((r) => r.length));
|
|
52
|
+
const w = Array.from({ length: cols }, (_, c) => Math.max(0, ...body.map((r) => width(r[c] ?? ""))));
|
|
53
|
+
const line = (r) => {
|
|
54
|
+
const cells = Array.from({ length: cols }, (_, c) => {
|
|
55
|
+
const cell = r[c] ?? "";
|
|
56
|
+
const isLast = c === cols - 1;
|
|
57
|
+
const right = opts.align?.[c] === "r";
|
|
58
|
+
if (right)
|
|
59
|
+
return padStartV(cell, w[c]);
|
|
60
|
+
return isLast ? cell : padEndV(cell, w[c]);
|
|
61
|
+
});
|
|
62
|
+
return lead + cells.join(gap).replace(/\s+$/, "");
|
|
63
|
+
};
|
|
64
|
+
const out = [];
|
|
65
|
+
if (opts.head)
|
|
66
|
+
out.push(line(opts.head.map((h) => ui.head(h))));
|
|
67
|
+
for (const r of rows)
|
|
68
|
+
out.push(line(r));
|
|
69
|
+
return out.join("\n");
|
|
70
|
+
}
|
|
71
|
+
/** Key/value block (aligned keys), e.g. for a detail view. */
|
|
72
|
+
export function kv(pairs, opts = {}) {
|
|
73
|
+
const lead = " ".repeat(opts.indent ?? 0);
|
|
74
|
+
const gap = " ".repeat(opts.gap ?? 2);
|
|
75
|
+
const w = Math.max(0, ...pairs.map(([k]) => width(k)));
|
|
76
|
+
return pairs
|
|
77
|
+
.map(([k, v]) => `${lead}${ui.muted(padEndV(k, w))}${gap}${v}`)
|
|
78
|
+
.join("\n");
|
|
79
|
+
}
|
|
80
|
+
/** Indent every line of a (possibly multi-line) string by `n` spaces. */
|
|
81
|
+
export const indent = (s, n) => s
|
|
82
|
+
.split("\n")
|
|
83
|
+
.map((l) => " ".repeat(n) + l)
|
|
84
|
+
.join("\n");
|
|
85
|
+
/** A labeled node with child lines drawn under it. */
|
|
86
|
+
export function tree(label, children, opts = {}) {
|
|
87
|
+
const lead = " ".repeat(opts.indent ?? 0);
|
|
88
|
+
return [lead + label, ...children.map((c) => `${lead} ${c}`)].join("\n");
|
|
89
|
+
}
|
|
90
|
+
/** A small inline badge, e.g. `badge('golden', ui.ok)`. */
|
|
91
|
+
export const badge = (text, style = ui.muted) => style(text);
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adriangalilea/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "TypeScript utilities - logger, currency, formatter, GramIO bot plugins, and more",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"types": "./dist/universal/format.d.ts",
|
|
25
25
|
"default": "./dist/universal/format.js"
|
|
26
26
|
},
|
|
27
|
+
"./cli": {
|
|
28
|
+
"types": "./dist/cli/index.d.ts",
|
|
29
|
+
"default": "./dist/cli/index.js"
|
|
30
|
+
},
|
|
27
31
|
"./currency": {
|
|
28
32
|
"types": "./dist/universal/currency/index.d.ts",
|
|
29
33
|
"default": "./dist/universal/currency/index.js"
|
|
@@ -40,6 +44,10 @@
|
|
|
40
44
|
"types": "./dist/bot/access-control.d.ts",
|
|
41
45
|
"default": "./dist/bot/access-control.js"
|
|
42
46
|
},
|
|
47
|
+
"./bot/allow-list": {
|
|
48
|
+
"types": "./dist/bot/allow-list.d.ts",
|
|
49
|
+
"default": "./dist/bot/allow-list.js"
|
|
50
|
+
},
|
|
43
51
|
"./bot/coalesce": {
|
|
44
52
|
"types": "./dist/bot/coalesce.d.ts",
|
|
45
53
|
"default": "./dist/bot/coalesce.js"
|