@adriangalilea/utils 2.27.0 → 2.29.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 CHANGED
@@ -123,6 +123,54 @@ money(100, 'EUR') // "€100.00"
123
123
  usdIntlOptions(0.43387) // { style: 'currency', currency: 'USD', minimumFractionDigits: 3, ... }
124
124
  ```
125
125
 
126
+ ### ANSI primitives (`ansi`)
127
+
128
+ Raw escape codes, ALWAYS ON — the vocabulary under the TTY-aware layers. The
129
+ logger and `cli` palettes decide *whether* to color (TTY, `NO_COLOR`,
130
+ `FORCE_COLOR`); this module is for output whose consumer renders ANSI
131
+ regardless of what stdout is: Claude Code status lines, tmux status strings,
132
+ files a terminal will cat. 256-color first (`fg`/`bg` — stable across
133
+ terminals and multiplexers); `rgb` for known-truecolor consumers. Out-of-range
134
+ indices panic.
135
+
136
+ ```typescript
137
+ import { fg, bg, rgb, BOLD, RESET, stripAnsi, ANSI_RE } from '@adriangalilea/utils/ansi'
138
+
139
+ `${BOLD}${fg(171)}Fable 5${RESET}` // bold bright violet
140
+ fg(196) // red foreground
141
+ bg(238) // dark gray background
142
+ rgb(173, 127, 168) // 24-bit, when the consumer renders it
143
+ stripAnsi(styled) // the string minus its escapes
144
+ ```
145
+
146
+ `cli`'s ANSI-aware `width()` / `clip()` measure strings styled with these
147
+ (they share `ANSI_RE`).
148
+
149
+ ### Time (`time`)
150
+
151
+ Humanized time — three questions, three functions: how long is a duration
152
+ (`span`), how long ago was an instant (`ago`, raw-duration twin `since`), how
153
+ long until an instant (`until`). `ago` degrades by distance instead of
154
+ stacking units: fresh instants stay relative, older ones snap to the clock the
155
+ reader would check. Accepts `Date | ISO string | epoch ms | null | undefined`
156
+ (null renders `"—"`); epoch numbers are milliseconds only, by design — a
157
+ seconds heuristic would silently shift dates near the boundary.
158
+
159
+ ```typescript
160
+ import { span, since, ago, until } from '@adriangalilea/utils/time'
161
+
162
+ span(6_000_000) // "1h 40m" (also "45s", "47m", "2d 3h")
163
+ since(sessionStart) // "47m" — elapsed, no "ago"
164
+ ago(twoMinutesAgo) // "2m ago" ("now" under a minute)
165
+ ago(fiveHoursAgo) // "5h 12m ago" (relative up to 6h)
166
+ ago(thisMorning) // "10:30" (clock time, today)
167
+ ago(lastNight) // "yst 22:15"
168
+ ago(fourDaysAgo) // "Mon 09:00" (within a week)
169
+ ago(lastMonth) // "07/28"
170
+ until(weeklyReset) // "in 3d 13h" ("now" if passed)
171
+ ago(null) // "—" (every function)
172
+ ```
173
+
126
174
  ### URL (`url`)
127
175
 
128
176
  URLs, from user-typed text to cache identity. The whole funnel — *is there a
@@ -25,12 +25,16 @@ export type ChatIdCtx = {
25
25
  };
26
26
  /** Minimum ctx shape `isGroupAdmin` reads. `bot` is the running gramio Bot — typed
27
27
  * `unknown` so MenuCtx and every real ctx assign without a cast; its
28
- * `api.getChatMember` is asserted at call time. */
28
+ * `api.getChatAdministrators` is asserted at call time. `senderChat` is the
29
+ * anonymous-admin identity on message ctxs (callback ctxs have none). */
29
30
  export type GroupAdminCtx = ChatIdCtx & {
30
31
  bot: unknown;
31
32
  from?: {
32
33
  id?: number;
33
34
  };
35
+ senderChat?: {
36
+ id?: number;
37
+ };
34
38
  };
35
39
  /** The ctx's chat id, whatever the event spelling. `undefined` only when the event has
36
40
  * no chat at all (inline queries, some service events). */
@@ -40,10 +44,12 @@ export declare const isGroupChat: (ctx: ChatTypeCtx) => boolean;
40
44
  /** True for a 1:1 private chat. */
41
45
  export declare const isPrivateChat: (ctx: ChatTypeCtx) => boolean;
42
46
  /**
43
- * Whether a user is a creator/administrator of a group, per `getChatMember`. Chat and
44
- * user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` — gramio's own
45
- * parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat checks. Missing
46
- * ids (an actor-less service event) or an API rejection answer `false`.
47
+ * Whether a user is a creator/administrator of a group, per `getChatAdministrators`
48
+ * membership. Chat and user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` —
49
+ * gramio's own parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat
50
+ * checks. An anonymous admin's own message (`senderChat` = the chat itself) answers
51
+ * `true` without an API call — unless an explicit `userId` asks about someone else.
52
+ * Missing ids (an actor-less service event) or an API rejection answer `false`.
47
53
  */
48
54
  export declare function isGroupAdmin(ctx: GroupAdminCtx, opts?: {
49
55
  chatId?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAgDA;8DAC8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF;sCACsC;AACtC,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG;IACvC,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvB,CAAC;AAcF;4DAC4D;AAC5D,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,KAAG,MAAM,GAAG,SACC,CAAC;AAMrD,2CAA2C;AAC3C,eAAO,MAAM,WAAW,GAAI,KAAK,WAAW,KAAG,OAG9C,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,aAAa,GAAI,KAAK,WAAW,KAAG,OACnB,CAAC;AAE/B;;;;;GAKG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,aAAa,EAClB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,OAAO,CAAC,OAAO,CAAC,CAkBlB"}
1
+ {"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAsDA;8DAC8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACzB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF;sCACsC;AACtC,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE;YAAE,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF;;;0EAG0E;AAC1E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG;IACvC,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,UAAU,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,CAAC;AAUF;4DAC4D;AAC5D,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,KAAG,MAAM,GAAG,SACC,CAAC;AAMrD,2CAA2C;AAC3C,eAAO,MAAM,WAAW,GAAI,KAAK,WAAW,KAAG,OAG9C,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,aAAa,GAAI,KAAK,WAAW,KAAG,OACnB,CAAC;AAE/B;;;;;;;GAOG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,aAAa,EAClB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,OAAO,CAAC,OAAO,CAAC,CAmBlB"}
@@ -3,9 +3,12 @@
3
3
  * check. Worker-safe (no env, no OS).
4
4
  *
5
5
  * Telegram's Bot API has no "is this user a group admin?" primitive and neither does
6
- * gramio: the raw material is `getChatMember` returning a member whose `status` may be
7
- * `"creator"` or `"administrator"`. Every bot with an admin-gated group setting re-rolls
8
- * that check; this module rolls it ONCE.
6
+ * gramio. The raw material is `getChatAdministrators` — NOT `getChatMember`: in a group
7
+ * whose member list is hidden, `getChatMember` on another user rejects with
8
+ * `CHAT_ADMIN_REQUIRED` for a non-admin bot, so a gate built on it denies EVERY real
9
+ * admin (creator included) while the admin list itself stays readable. Every bot with an
10
+ * admin-gated group setting re-rolls that check; this module rolls it ONCE, on the call
11
+ * that works everywhere the bot is present.
9
12
  *
10
13
  * ## The two ctx spellings (the trap this module absorbs)
11
14
  *
@@ -21,19 +24,22 @@
21
24
  * These take minimum structural ctx shapes (the `bot/ctx.ts` philosophy) that real
22
25
  * gramio contexts AND `bot/menu`'s `MenuCtx` satisfy by duck typing — a menu action
23
26
  * gates with `isGroupAdmin(ctx)`, no cast. `ctx.bot` stays `unknown` for exactly that
24
- * assignability (MenuCtx's own choice); a ctx whose bot has no `api.getChatMember`
25
- * PANICS — a miswire screams, it doesn't read as "not admin".
27
+ * assignability (MenuCtx's own choice); a ctx whose bot has no
28
+ * `api.getChatAdministrators` PANICS — a miswire screams, it doesn't read as "not
29
+ * admin".
26
30
  *
27
- * `isGroupAdmin` FAILS CLOSED on the API call itself: a `getChatMember` rejection (user
28
- * never seen in the chat, network, the bot removed mid-tap) answers `false`, never
29
- * throws — it is a permission gate, and a gate that throws breaks the surface it guards
30
- * (a menu tap's single answerCallbackQuery, a command reply). That rejection is the
31
- * messy real world; denial is the only honest answer to "I couldn't verify".
31
+ * `isGroupAdmin` FAILS CLOSED on the API call itself: a `getChatAdministrators`
32
+ * rejection (the bot removed mid-tap, network) answers `false`, never throws — it is a
33
+ * permission gate, and a gate that throws breaks the surface it guards (a menu tap's
34
+ * single answerCallbackQuery, a command reply). That rejection is the messy real world;
35
+ * denial is the only honest answer to "I couldn't verify".
32
36
  *
33
- * Anonymous-admin caveat: an admin posting anonymously wears the group's own identity
34
- * in `message.from` (the GroupAnonymousBot), so a MESSAGE from them fails this check.
35
- * Callback taps always carry the real user, so inline-button gates (the settings case)
36
- * are unaffected.
37
+ * Anonymous admins: a message posted "as the group" carries the chat itself in
38
+ * `sender_chat`, and Telegram offers that identity only to the chat's own admins — so
39
+ * `sender_chat.id === chat.id` IS the proof, no API call (a linked channel's auto-forward
40
+ * wears the CHANNEL's id, so the equality never false-positives). Callback taps always
41
+ * carry the real user, and anonymous admins appear in the admin list under their real
42
+ * user, so inline-button gates resolve them too.
37
43
  *
38
44
  * @example
39
45
  * // Gate a group-wide toggle: the bot's own admins always may, otherwise the
@@ -45,9 +51,6 @@
45
51
  * await isGroupAdmin(ctx, { chatId: -100123, userId: 42 })
46
52
  */
47
53
  import { assert } from "../offensive.js";
48
- // The two chat-member statuses that hold power over a group. Not exported: the
49
- // contract is the question `isGroupAdmin` answers, never status comparison.
50
- const GROUP_ADMIN_STATUSES = new Set(["creator", "administrator"]);
51
54
  /** The ctx's chat id, whatever the event spelling. `undefined` only when the event has
52
55
  * no chat at all (inline queries, some service events). */
53
56
  export const chatIdOf = (ctx) => ctx.chat?.id ?? ctx.chatId ?? ctx.message?.chat?.id;
@@ -61,24 +64,27 @@ export const isGroupChat = (ctx) => {
61
64
  /** True for a 1:1 private chat. */
62
65
  export const isPrivateChat = (ctx) => chatTypeOf(ctx) === "private";
63
66
  /**
64
- * Whether a user is a creator/administrator of a group, per `getChatMember`. Chat and
65
- * user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` — gramio's own
66
- * parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat checks. Missing
67
- * ids (an actor-less service event) or an API rejection answer `false`.
67
+ * Whether a user is a creator/administrator of a group, per `getChatAdministrators`
68
+ * membership. Chat and user default to the ctx's own ({@link chatIdOf}, `ctx.from.id` —
69
+ * gramio's own parameter-defaulting idiom); pass `chatId` / `userId` for cross-chat
70
+ * checks. An anonymous admin's own message (`senderChat` = the chat itself) answers
71
+ * `true` without an API call — unless an explicit `userId` asks about someone else.
72
+ * Missing ids (an actor-less service event) or an API rejection answer `false`.
68
73
  */
69
74
  export async function isGroupAdmin(ctx, opts = {}) {
70
75
  const chatId = opts.chatId ?? chatIdOf(ctx);
76
+ if (chatId === undefined)
77
+ return false;
78
+ if (opts.userId === undefined && ctx.senderChat?.id === chatId)
79
+ return true;
71
80
  const userId = opts.userId ?? ctx.from?.id;
72
- if (chatId === undefined || userId === undefined)
81
+ if (userId === undefined)
73
82
  return false;
74
83
  const bot = ctx.bot;
75
- assert(typeof bot?.api?.getChatMember === "function", "isGroupAdmin: ctx.bot.api.getChatMember missing — not a gramio ctx?");
84
+ assert(typeof bot?.api?.getChatAdministrators === "function", "isGroupAdmin: ctx.bot.api.getChatAdministrators missing — not a gramio ctx?");
76
85
  try {
77
- const member = await bot.api.getChatMember({
78
- chat_id: chatId,
79
- user_id: userId,
80
- });
81
- return GROUP_ADMIN_STATUSES.has(member.status ?? "");
86
+ const admins = await bot.api.getChatAdministrators({ chat_id: chatId });
87
+ return admins.some((m) => m.user?.id === userId);
82
88
  }
83
89
  catch {
84
90
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAiCzC,+EAA+E;AAC/E,4EAA4E;AAC5E,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC;AAEnE;4DAC4D;AAC5D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAc,EAAsB,EAAE,CAC9D,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAErD,mEAAmE;AACnE,MAAM,UAAU,GAAG,CAAC,GAAgB,EAAsB,EAAE,CAC3D,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAgB,EAAW,EAAE;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC;AAClD,CAAC,CAAC;AAEF,mCAAmC;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAgB,EAAW,EAAE,CAC1D,UAAU,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;AAE/B;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,GAAkB,EAClB,OAA6C,EAAE;IAE/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IAC3C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC/D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAsD,CAAC;IACvE,MAAM,CACL,OAAO,GAAG,EAAE,GAAG,EAAE,aAAa,KAAK,UAAU,EAC7C,qEAAqE,CACrE,CAAC;IACF,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC;YAC1C,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,MAAM;SACf,CAAC,CAAC;QACH,OAAO,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/bot/groups.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAmCzC;4DAC4D;AAC5D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAc,EAAsB,EAAE,CAC9D,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAErD,mEAAmE;AACnE,MAAM,UAAU,GAAG,CAAC,GAAgB,EAAsB,EAAE,CAC3D,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAgB,EAAW,EAAE;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC;AAClD,CAAC,CAAC;AAEF,mCAAmC;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAgB,EAAW,EAAE,CAC1D,UAAU,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,GAAkB,EAClB,OAA6C,EAAE;IAE/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,EAAE,EAAE,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IAC3C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,GAAG,GAAG,GAAG,CAAC,GAEJ,CAAC;IACb,MAAM,CACL,OAAO,GAAG,EAAE,GAAG,EAAE,qBAAqB,KAAK,UAAU,EACrD,6EAA6E,CAC7E,CAAC;IACF,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,qBAAqB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC"}
@@ -4,7 +4,8 @@
4
4
  * (`cli/index` — table/kv/tree) and the live layer (`cli/live` — pinned
5
5
  * region/spinner) build on these; keeping them here avoids an import cycle.
6
6
  */
7
- export declare const ANSI_RE: RegExp;
7
+ import { ANSI_RE } from "../universal/ansi.js";
8
+ export { ANSI_RE };
8
9
  /** Visible width of a string — ANSI escapes stripped, counted by grapheme cluster. */
9
10
  export declare const width: (s: string) => number;
10
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/cli/text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,eAAO,MAAM,OAAO,QAAoB,CAAC;AAazC,sFAAsF;AACtF,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,KAAG,MACoB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAwBjD;AAED,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MACL,CAAC;AAC3C,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MACP,CAAC;AAE3C,gFAAgF;AAChF,eAAO,MAAM,EAAE;cACJ,MAAM;gBACJ,MAAM;eACP,MAAM;YACT,MAAM;cACJ,MAAM;aACP,MAAM;IACf,+CAA+C;aACtC,MAAM;CACf,CAAC"}
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/cli/text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,CAAC;AAanB,sFAAsF;AACtF,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,KAAG,MACoB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAwBjD;AAED,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MACL,CAAC;AAC3C,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MACP,CAAC;AAE3C,gFAAgF;AAChF,eAAO,MAAM,EAAE;cACJ,MAAM;gBACJ,MAAM;eACP,MAAM;YACT,MAAM;cACJ,MAAM;aACP,MAAM;IACf,+CAA+C;aACtC,MAAM;CACf,CAAC"}
package/dist/cli/text.js CHANGED
@@ -4,9 +4,9 @@
4
4
  * (`cli/index` — table/kv/tree) and the live layer (`cli/live` — pinned
5
5
  * region/spinner) build on these; keeping them here avoids an import cycle.
6
6
  */
7
+ import { ANSI_RE } from "../universal/ansi.js";
7
8
  import { bold, cyan, dim, gray, green, red, yellow } from "../universal/log.js";
8
- // biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escapes is the point
9
- export const ANSI_RE = /\x1b\[[0-9;]*m/g;
9
+ export { ANSI_RE };
10
10
  // biome-ignore lint/suspicious/noControlCharactersInRegex: split on ANSI escapes, keeping them
11
11
  const ANSI_SPLIT_RE = /(\x1b\[[0-9;]*m)/;
12
12
  // Grapheme clusters, not code points: what the terminal renders as ONE symbol
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/cli/text.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/cli/text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAEhF,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,+FAA+F;AAC/F,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEzC,8EAA8E;AAC9E,iFAAiF;AACjF,2BAA2B;AAC3B,4EAA4E;AAC5E,4EAA4E;AAC5E,eAAe;AACf,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;AAEvC,sFAAsF;AACtF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAC1C,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAEvD;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,CAAS,EAAE,CAAS;IACxC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3C,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QAC1B,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,GAAG,IAAI,IAAI,CAAC;YACZ,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACV,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjB,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC;gBACjB,GAAG,EAAE,CAAC;YACP,CAAC;iBAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,GAAG,IAAI,GAAG,CAAC;gBACX,GAAG,EAAE,CAAC;YACP,CAAC;QACF,CAAC;IACF,CAAC;IACD,2EAA2E;IAC3E,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CACvD,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CACzD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAE3C,gFAAgF;AAChF,MAAM,CAAC,MAAM,EAAE,GAAG;IACjB,IAAI,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,EAAE,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9B,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,+CAA+C;IAC/C,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;CAC3B,CAAC"}
package/dist/index.d.ts CHANGED
@@ -13,8 +13,10 @@ export * from "./platform/xdg.js";
13
13
  export type { RuntimeCapabilities } from "./runtime.js";
14
14
  export { ProcessExitError, runtime } from "./runtime.js";
15
15
  export * from "./say/index.js";
16
+ export * from "./universal/ansi.js";
16
17
  export * from "./universal/currency/index.js";
17
18
  export * from "./universal/format.js";
18
19
  export * from "./universal/log.js";
20
+ export * from "./universal/time.js";
19
21
  export * from "./universal/url/index.js";
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEzD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AAEtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEzD,cAAc,gBAAgB,CAAC;AAE/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
package/dist/index.js CHANGED
@@ -17,9 +17,11 @@ export * from "./platform/xdg.js";
17
17
  export { ProcessExitError, runtime } from "./runtime.js";
18
18
  // Polyglot strings — TS-enforced multi-language values
19
19
  export * from "./say/index.js";
20
+ // Universal utilities - work everywhere
21
+ export * from "./universal/ansi.js";
20
22
  export * from "./universal/currency/index.js";
21
23
  export * from "./universal/format.js";
22
- // Universal utilities - work everywhere
23
24
  export * from "./universal/log.js";
25
+ export * from "./universal/time.js";
24
26
  export * from "./universal/url/index.js";
25
27
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,8BAA8B;AAC9B,mEAAmE;AACnE,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,8CAA8C;AAC9C,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACzD,uDAAuD;AACvD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,wCAAwC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,8BAA8B;AAC9B,mEAAmE;AACnE,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,8CAA8C;AAC9C,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACzD,uDAAuD;AACvD,cAAc,gBAAgB,CAAC;AAC/B,wCAAwC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Raw ANSI escape primitives — ALWAYS ON, no TTY detection, no env checks.
3
+ * The vocabulary, not the policy: ./log and ../cli decide WHETHER to color
4
+ * (TTY, NO_COLOR, FORCE_COLOR) and build semantics on top; this module is for
5
+ * output whose consumer renders ANSI regardless of what stdout is — Claude
6
+ * Code status lines, tmux status strings, files a terminal will cat. A
7
+ * TTY-gated palette emits nothing through those pipes, which is exactly wrong.
8
+ *
9
+ * 256-color escapes (`fg`/`bg`) over 24-bit as the house default: the xterm
10
+ * cube is stable across every terminal/multiplexer, while 24-bit gets
11
+ * quantized or dropped by tmux/screen configs. `rgb` exists for when the
12
+ * consumer is known to render truecolor.
13
+ */
14
+ export declare const RESET = "\u001B[0m";
15
+ export declare const BOLD = "\u001B[1m";
16
+ export declare const DIM = "\u001B[2m";
17
+ export declare const ITALIC = "\u001B[3m";
18
+ export declare const UNDERLINE = "\u001B[4m";
19
+ /** Foreground, xterm-256 palette index. */
20
+ export declare function fg(n: number): string;
21
+ /** Background, xterm-256 palette index. */
22
+ export declare function bg(n: number): string;
23
+ /** Foreground, 24-bit — only when the consumer is known to render truecolor. */
24
+ export declare function rgb(r: number, g: number, b: number): string;
25
+ export declare const ANSI_RE: RegExp;
26
+ /** The string minus its ANSI style escapes. */
27
+ export declare const stripAnsi: (s: string) => string;
28
+ //# sourceMappingURL=ansi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ansi.d.ts","sourceRoot":"","sources":["../../src/universal/ansi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,eAAO,MAAM,KAAK,cAAY,CAAC;AAC/B,eAAO,MAAM,IAAI,cAAY,CAAC;AAC9B,eAAO,MAAM,GAAG,cAAY,CAAC;AAC7B,eAAO,MAAM,MAAM,cAAY,CAAC;AAChC,eAAO,MAAM,SAAS,cAAY,CAAC;AAEnC,2CAA2C;AAC3C,wBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGpC;AAED,2CAA2C;AAC3C,wBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGpC;AAED,gFAAgF;AAChF,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS3D;AAGD,eAAO,MAAM,OAAO,QAAoB,CAAC;AAEzC,+CAA+C;AAC/C,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAgC,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Raw ANSI escape primitives — ALWAYS ON, no TTY detection, no env checks.
3
+ * The vocabulary, not the policy: ./log and ../cli decide WHETHER to color
4
+ * (TTY, NO_COLOR, FORCE_COLOR) and build semantics on top; this module is for
5
+ * output whose consumer renders ANSI regardless of what stdout is — Claude
6
+ * Code status lines, tmux status strings, files a terminal will cat. A
7
+ * TTY-gated palette emits nothing through those pipes, which is exactly wrong.
8
+ *
9
+ * 256-color escapes (`fg`/`bg`) over 24-bit as the house default: the xterm
10
+ * cube is stable across every terminal/multiplexer, while 24-bit gets
11
+ * quantized or dropped by tmux/screen configs. `rgb` exists for when the
12
+ * consumer is known to render truecolor.
13
+ */
14
+ import { assert } from "../offensive.js";
15
+ export const RESET = "\x1b[0m";
16
+ export const BOLD = "\x1b[1m";
17
+ export const DIM = "\x1b[2m";
18
+ export const ITALIC = "\x1b[3m";
19
+ export const UNDERLINE = "\x1b[4m";
20
+ /** Foreground, xterm-256 palette index. */
21
+ export function fg(n) {
22
+ assert(Number.isInteger(n) && n >= 0 && n <= 255, "xterm-256 index:", n);
23
+ return `\x1b[38;5;${n}m`;
24
+ }
25
+ /** Background, xterm-256 palette index. */
26
+ export function bg(n) {
27
+ assert(Number.isInteger(n) && n >= 0 && n <= 255, "xterm-256 index:", n);
28
+ return `\x1b[48;5;${n}m`;
29
+ }
30
+ /** Foreground, 24-bit — only when the consumer is known to render truecolor. */
31
+ export function rgb(r, g, b) {
32
+ assert([r, g, b].every((v) => Number.isInteger(v) && v >= 0 && v <= 255), "rgb components:", r, g, b);
33
+ return `\x1b[38;2;${r};${g};${b}m`;
34
+ }
35
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escapes is the point
36
+ export const ANSI_RE = /\x1b\[[0-9;]*m/g;
37
+ /** The string minus its ANSI style escapes. */
38
+ export const stripAnsi = (s) => s.replace(ANSI_RE, "");
39
+ //# sourceMappingURL=ansi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ansi.js","sourceRoot":"","sources":["../../src/universal/ansi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,CAAC,MAAM,KAAK,GAAG,SAAS,CAAC;AAC/B,MAAM,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC;AAC9B,MAAM,CAAC,MAAM,GAAG,GAAG,SAAS,CAAC;AAC7B,MAAM,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC;AAChC,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC;AAEnC,2CAA2C;AAC3C,MAAM,UAAU,EAAE,CAAC,CAAS;IAC3B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC;IACzE,OAAO,aAAa,CAAC,GAAG,CAAC;AAC1B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,EAAE,CAAC,CAAS;IAC3B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC;IACzE,OAAO,aAAa,CAAC,GAAG,CAAC;AAC1B,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAClD,MAAM,CACL,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,EACjE,iBAAiB,EACjB,CAAC,EACD,CAAC,EACD,CAAC,CACD,CAAC;IACF,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACpC,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAEzC,+CAA+C;AAC/C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Humanized time. Three questions, three functions: how long is this
3
+ * duration (`span`), how long ago was this instant (`ago` — and its raw-
4
+ * duration twin `since`), how long until this instant (`until`).
5
+ *
6
+ * `ago` degrades by distance instead of stacking units: fresh instants are
7
+ * relative ("now", "3m ago", "2h 15m ago"), older ones snap to the clock the
8
+ * reader would check — "10:30" earlier today, "yst 22:15" yesterday,
9
+ * "Mon 09:00" within a week, "07/28" beyond. Relative wording past a few
10
+ * hours ("19h ago") makes the reader do calendar math; a clock time doesn't.
11
+ *
12
+ * Accepts `Date | ISO string | epoch ms | null | undefined` and renders "—"
13
+ * for null/undefined, same contract as ./format. Epoch numbers are
14
+ * MILLISECONDS only — a seconds heuristic would silently misread dates near
15
+ * the boundary, and a wrong timestamp should scream, not shift by 1000×.
16
+ * Weekday/date fallbacks are en-pinned like every formatter here.
17
+ */
18
+ export type Dateish = Date | string | number | null | undefined;
19
+ /** Duration in ms → "45s", "47m", "1h 40m", "2d 3h". Negative clamps to "0s". */
20
+ export declare function span(ms: number): string;
21
+ /** Elapsed since an instant, as a bare duration: "47m", "1h 40m" (no "ago"). */
22
+ export declare function since(d: Dateish, now?: Dateish): string;
23
+ /**
24
+ * Past instant, humanized: "now" (<1m), "3m ago", "2h 15m ago" (<6h), then
25
+ * clock time — "10:30" today, "yst 22:15" yesterday, "Mon 09:00" within 7
26
+ * days, "07/28" beyond. Future inputs render "now".
27
+ */
28
+ export declare function ago(d: Dateish, now?: Dateish): string;
29
+ /** Future instant, humanized: "now", "in 40m", "in 5h 12m", "in 3d 13h". */
30
+ export declare function until(d: Dateish, now?: Dateish): string;
31
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/universal/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAuBhE,iFAAiF;AACjF,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAOvC;AAED,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,GAAE,OAAoB,GAAG,MAAM,CAKnE;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,GAAE,OAAoB,GAAG,MAAM,CAejE;AAED,4EAA4E;AAC5E,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,GAAE,OAAoB,GAAG,MAAM,CAOnE"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Humanized time. Three questions, three functions: how long is this
3
+ * duration (`span`), how long ago was this instant (`ago` — and its raw-
4
+ * duration twin `since`), how long until this instant (`until`).
5
+ *
6
+ * `ago` degrades by distance instead of stacking units: fresh instants are
7
+ * relative ("now", "3m ago", "2h 15m ago"), older ones snap to the clock the
8
+ * reader would check — "10:30" earlier today, "yst 22:15" yesterday,
9
+ * "Mon 09:00" within a week, "07/28" beyond. Relative wording past a few
10
+ * hours ("19h ago") makes the reader do calendar math; a clock time doesn't.
11
+ *
12
+ * Accepts `Date | ISO string | epoch ms | null | undefined` and renders "—"
13
+ * for null/undefined, same contract as ./format. Epoch numbers are
14
+ * MILLISECONDS only — a seconds heuristic would silently misread dates near
15
+ * the boundary, and a wrong timestamp should scream, not shift by 1000×.
16
+ * Weekday/date fallbacks are en-pinned like every formatter here.
17
+ */
18
+ import { NO_DATA } from "./format.js";
19
+ const MIN = 60_000;
20
+ const HOUR = 3_600_000;
21
+ const DAY = 86_400_000;
22
+ const WEEK = 604_800_000;
23
+ const WEEKDAY = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
24
+ function toMs(d) {
25
+ if (d == null)
26
+ return null;
27
+ if (d instanceof Date)
28
+ return d.getTime();
29
+ const ms = typeof d === "number" ? d : Date.parse(d);
30
+ return Number.isNaN(ms) ? null : ms;
31
+ }
32
+ const pad = (n) => String(n).padStart(2, "0");
33
+ const clock = (d) => `${pad(d.getHours())}:${pad(d.getMinutes())}`;
34
+ const sameDay = (a, b) => a.getFullYear() === b.getFullYear() &&
35
+ a.getMonth() === b.getMonth() &&
36
+ a.getDate() === b.getDate();
37
+ /** Duration in ms → "45s", "47m", "1h 40m", "2d 3h". Negative clamps to "0s". */
38
+ export function span(ms) {
39
+ if (ms < 0)
40
+ ms = 0;
41
+ if (ms < MIN)
42
+ return `${Math.floor(ms / 1000)}s`;
43
+ if (ms < HOUR)
44
+ return `${Math.floor(ms / MIN)}m`;
45
+ if (ms < DAY)
46
+ return `${Math.floor(ms / HOUR)}h ${Math.floor((ms % HOUR) / MIN)}m`;
47
+ return `${Math.floor(ms / DAY)}d ${Math.floor((ms % DAY) / HOUR)}h`;
48
+ }
49
+ /** Elapsed since an instant, as a bare duration: "47m", "1h 40m" (no "ago"). */
50
+ export function since(d, now = Date.now()) {
51
+ const ms = toMs(d);
52
+ const ref = toMs(now);
53
+ if (ms == null || ref == null)
54
+ return NO_DATA;
55
+ return span(ref - ms);
56
+ }
57
+ /**
58
+ * Past instant, humanized: "now" (<1m), "3m ago", "2h 15m ago" (<6h), then
59
+ * clock time — "10:30" today, "yst 22:15" yesterday, "Mon 09:00" within 7
60
+ * days, "07/28" beyond. Future inputs render "now".
61
+ */
62
+ export function ago(d, now = Date.now()) {
63
+ const ms = toMs(d);
64
+ const ref = toMs(now);
65
+ if (ms == null || ref == null)
66
+ return NO_DATA;
67
+ const delta = ref - ms;
68
+ if (delta < MIN)
69
+ return "now";
70
+ if (delta < HOUR)
71
+ return `${Math.floor(delta / MIN)}m ago`;
72
+ if (delta < 6 * HOUR)
73
+ return `${Math.floor(delta / HOUR)}h ${Math.floor((delta % HOUR) / MIN)}m ago`;
74
+ const date = new Date(ms);
75
+ const refDate = new Date(ref);
76
+ if (sameDay(date, refDate))
77
+ return clock(date);
78
+ if (sameDay(date, new Date(ref - DAY)))
79
+ return `yst ${clock(date)}`;
80
+ if (delta < WEEK)
81
+ return `${WEEKDAY[date.getDay()]} ${clock(date)}`;
82
+ return `${pad(date.getMonth() + 1)}/${pad(date.getDate())}`;
83
+ }
84
+ /** Future instant, humanized: "now", "in 40m", "in 5h 12m", "in 3d 13h". */
85
+ export function until(d, now = Date.now()) {
86
+ const ms = toMs(d);
87
+ const ref = toMs(now);
88
+ if (ms == null || ref == null)
89
+ return NO_DATA;
90
+ const delta = ms - ref;
91
+ if (delta < MIN)
92
+ return "now";
93
+ return `in ${span(delta)}`;
94
+ }
95
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/universal/time.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "2.27.0",
3
+ "version": "2.29.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",
@@ -20,10 +20,18 @@
20
20
  "types": "./dist/runtime.d.ts",
21
21
  "default": "./dist/runtime.js"
22
22
  },
23
+ "./ansi": {
24
+ "types": "./dist/universal/ansi.d.ts",
25
+ "default": "./dist/universal/ansi.js"
26
+ },
23
27
  "./format": {
24
28
  "types": "./dist/universal/format.d.ts",
25
29
  "default": "./dist/universal/format.js"
26
30
  },
31
+ "./time": {
32
+ "types": "./dist/universal/time.d.ts",
33
+ "default": "./dist/universal/time.js"
34
+ },
27
35
  "./url": {
28
36
  "types": "./dist/universal/url/index.d.ts",
29
37
  "default": "./dist/universal/url/index.js"