@stats-forge/github-stats-forge-cli 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Anurag Hazra, Abhijit Gupta, martin-mfg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # @stats-forge/github-stats-forge-cli
2
+
3
+ Render a [GitHub Stats Forge](https://github.com/stats-forge/github-stats-forge) card
4
+ to a local SVG file, one prompt at a time.
5
+
6
+ Run it without installing anything:
7
+
8
+ ```sh
9
+ npx @stats-forge/github-stats-forge-cli
10
+ ```
11
+
12
+ or keep it around as `github-stats-forge`:
13
+
14
+ ```sh
15
+ pnpm add -g @stats-forge/github-stats-forge-cli
16
+ github-stats-forge
17
+ ```
18
+
19
+ Requires Node 24 or newer. The cards themselves come from
20
+ [`@stats-forge/github-stats-forge-core`](https://www.npmjs.com/package/@stats-forge/github-stats-forge-core),
21
+ so every option below is the one that package's api accepts — the prompts read the
22
+ accepted values off it rather than keeping their own copy.
23
+
24
+ It asks which card you want, then the params that card cannot render without, then
25
+ puts you in a menu of every other option it accepts — pick one, answer it, and the
26
+ menu comes back with the answer beside it. `Generate the card` writes the SVG.
27
+
28
+ The menu stays open afterwards, with every answer still on it and the cursor where
29
+ you left it, so tuning a card is: generate, look at it, change one option, generate
30
+ again. `Quit` (or Ctrl-C) ends the session.
31
+
32
+ ```
33
+ ? Which card? Stats — commits, PRs, issues, reviews and a rank
34
+ ? GitHub username anuraghazra
35
+ ? Stats — wrote stats-anuraghazra.svg — edit an option and generate again
36
+ ❯ Generate the card
37
+ Quit
38
+ Extra stats to show reviews,prs_merged
39
+ Stats to hide —
40
+ Show the stat icons yes
41
+ Theme tokyonight
42
+ ```
43
+
44
+ ## Saving a card
45
+
46
+ `Save these options` in the menu writes the card and its params to a JSON file, and
47
+ `--config` loads one back:
48
+
49
+ ```sh
50
+ github-stats-forge --config my-stats.json # picks up where the file left off
51
+ ```
52
+
53
+ ```json
54
+ {
55
+ "card": "stats",
56
+ "params": {
57
+ "username": "anuraghazra",
58
+ "theme": "tokyonight",
59
+ "show_icons": "true"
60
+ }
61
+ }
62
+ ```
63
+
64
+ The file holds what a query string holds, so it reads like the URL it stands for and
65
+ can be edited by hand. Loading one skips both the card prompt and the required
66
+ params, landing you straight on the menu — and `--config` doubles as the save target,
67
+ so the next `Save these options` writes back to it without asking.
68
+
69
+ `--generate` skips the menu too, rendering exactly what the file holds:
70
+
71
+ ```sh
72
+ github-stats-forge --config my-stats.json --generate
73
+ ```
74
+
75
+ Nothing is asked, so this is the form for a script or a scheduled job — refresh a
76
+ card on a cron, or regenerate a directory of them. It exits non-zero if the card
77
+ could not be rendered, and needs its token from `--pat`, an env file or the
78
+ environment, since there is nobody to ask.
79
+
80
+ While it fetches, a spinner runs on stderr — so `github-stats-forge > card.svg` still
81
+ pipes only the card, and a CI log gets one line instead of an animation.
82
+
83
+ ## The token
84
+
85
+ Every card except WakaTime reads the GitHub API, so it needs a personal access token
86
+ ([how to create one](https://github.com/settings/personal-access-tokens)). It can come
87
+ from any of these, in this order:
88
+
89
+ ```sh
90
+ github-stats-forge --pat ghp_yourtoken # the flag wins
91
+ echo "PAT_1=ghp_yourtoken" > .env # read from ./.env by default
92
+ export PAT_1=ghp_yourtoken # or straight from the environment
93
+ ```
94
+
95
+ `PAT_1`, `PAT_2`, … are the names core reads, so an env file you already use with a
96
+ self-hosted instance works unchanged. With none of them set, the CLI asks for the
97
+ token and hides what you type.
98
+
99
+ ## Options
100
+
101
+ ```
102
+ -c, --card <id> Skip the card prompt: stats, top-langs, pin, gist, wakatime
103
+ -o, --out <file> Where to write the card (default: named after the card)
104
+ --config <file> Options to load, and where "Save these options" writes
105
+ -g, --generate Render what --config holds and exit, without the menu
106
+ --pat <token> GitHub token; repeat for several
107
+ --env-file <file> Env file to read PAT_1, PAT_2, … from (default: .env)
108
+ -h, --help Show the help
109
+ -v, --version Show the version
110
+ ```
111
+
112
+ A card that cannot be rendered prints why, including the code and the param at
113
+ fault, and exits non-zero:
114
+
115
+ ```
116
+ Could not render the stats card.
117
+ Something went wrong: Invalid number input for parameter "border_radius"
118
+ code: invalid_param, param: border_radius
119
+ ```
@@ -0,0 +1,47 @@
1
+ import type { ApiResult, CardConfig } from '@stats-forge/github-stats-forge-core';
2
+ /**
3
+ * @file What each card accepts, in the order the prompts walk it.
4
+ *
5
+ * The core schemas validate these params;
6
+ * this catalog is what makes them navigable, so it carries the prose and the choices a schema has no room for.
7
+ */
8
+ /** How a param is asked for, and how the answer becomes a query string value. */
9
+ type OptionKind = 'text' | 'boolean' | 'number' | 'list' | 'choice';
10
+ /** One param of one card. */
11
+ export interface CardOption {
12
+ /** Query param the answer is written to. */
13
+ name: string;
14
+ /** What the prompt asks. */
15
+ label: string;
16
+ kind: OptionKind;
17
+ /** The accepted values, for `choice`. */
18
+ choices?: ReadonlyArray<string>;
19
+ /** Shown under the prompt, for anything the label cannot say. */
20
+ hint?: string;
21
+ }
22
+ /** A card, its params, and the core handler that renders it. */
23
+ export interface CardKind {
24
+ id: string;
25
+ label: string;
26
+ /** Whether rendering it calls the GitHub API, and so needs a token. */
27
+ needsToken: boolean;
28
+ /** Asked first: the card renders nothing without them. */
29
+ required: ReadonlyArray<CardOption>;
30
+ /** Everything else, navigable in any order. */
31
+ options: ReadonlyArray<CardOption>;
32
+ /**
33
+ * @param query The answers, as a query string would carry them.
34
+ * @param config Tokens the fetchers use.
35
+ * @returns The rendered card, or the rendered error.
36
+ */
37
+ render: (query: Record<string, string>, config: CardConfig) => Promise<ApiResult>;
38
+ }
39
+ /** Every card, with the options every card shares appended to its own. */
40
+ export declare const cards: ReadonlyArray<CardKind>;
41
+ /**
42
+ * @param id The card's id, as `--card` takes it.
43
+ * @returns The card, or `undefined` when nothing renders under that name.
44
+ */
45
+ export declare const findCard: (id: string) => CardKind | undefined;
46
+ export {};
47
+ //# sourceMappingURL=cards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAElF;;;;;GAKG;AAEH,iFAAiF;AACjF,KAAK,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEpE,6BAA6B;AAC7B,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,yCAAyC;IACzC,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACpC,+CAA+C;IAC/C,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACnC;;;;OAIG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;CACnF;AAkSD,0EAA0E;AAC1E,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,QAAQ,CAGvC,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,IAAI,MAAM,KAAG,QAAQ,GAAG,SAAiD,CAAC"}
package/build/cards.js ADDED
@@ -0,0 +1,295 @@
1
+ import { gist, pin, stats, themes, topLangs, wakatime } from '@stats-forge/github-stats-forge-core';
2
+ const THEME_NAMES = Object.keys(themes);
3
+ /** Colors and the theme, which every card accepts. */
4
+ const COMMON_OPTIONS = [
5
+ {
6
+ name: 'theme',
7
+ label: 'Theme',
8
+ kind: 'choice',
9
+ choices: THEME_NAMES,
10
+ hint: 'An unknown name falls back to the default theme',
11
+ },
12
+ {
13
+ name: 'title_color',
14
+ label: 'Title color',
15
+ kind: 'text',
16
+ hint: 'Hex, no #',
17
+ },
18
+ { name: 'text_color', label: 'Text color', kind: 'text', hint: 'Hex, no #' },
19
+ { name: 'icon_color', label: 'Icon color', kind: 'text', hint: 'Hex, no #' },
20
+ {
21
+ name: 'bg_color',
22
+ label: 'Background color',
23
+ kind: 'text',
24
+ hint: 'Hex, no #, or a gradient: angle,color,color',
25
+ },
26
+ {
27
+ name: 'border_color',
28
+ label: 'Border color',
29
+ kind: 'text',
30
+ hint: 'Hex, no #',
31
+ },
32
+ { name: 'border_radius', label: 'Border radius', kind: 'number' },
33
+ { name: 'hide_border', label: 'Hide the border', kind: 'boolean' },
34
+ ];
35
+ const LOCALE_OPTION = {
36
+ name: 'locale',
37
+ label: 'Locale',
38
+ kind: 'text',
39
+ hint: 'Two-letter code, e.g. es',
40
+ };
41
+ const CARDS = [
42
+ {
43
+ id: 'stats',
44
+ label: 'Stats — commits, PRs, issues, reviews and a rank',
45
+ needsToken: true,
46
+ required: [{ name: 'username', label: 'GitHub username', kind: 'text' }],
47
+ options: [
48
+ {
49
+ name: 'show',
50
+ label: 'Extra stats to show',
51
+ kind: 'list',
52
+ hint: 'e.g. reviews,discussions_started,prs_merged,contributions',
53
+ },
54
+ {
55
+ name: 'hide',
56
+ label: 'Stats to hide',
57
+ kind: 'list',
58
+ hint: 'e.g. stars,commits,prs,issues,contribs',
59
+ },
60
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
61
+ { name: 'hide_rank', label: 'Hide the rank circle', kind: 'boolean' },
62
+ {
63
+ name: 'rank_icon',
64
+ label: 'Rank indicator',
65
+ kind: 'choice',
66
+ choices: stats.RANK_ICONS,
67
+ },
68
+ {
69
+ name: 'include_all_commits',
70
+ label: 'Count commits of all time',
71
+ kind: 'boolean',
72
+ },
73
+ {
74
+ name: 'commits_year',
75
+ label: 'Count commits for one year',
76
+ kind: 'number',
77
+ hint: 'Four digits, e.g. 2025',
78
+ },
79
+ {
80
+ name: 'exclude_repo',
81
+ label: 'Repositories to exclude',
82
+ kind: 'list',
83
+ },
84
+ {
85
+ name: 'repo',
86
+ label: 'Repositories the search-based stats are scoped to',
87
+ kind: 'list',
88
+ },
89
+ {
90
+ name: 'owner',
91
+ label: 'Owners the search-based stats are scoped to',
92
+ kind: 'list',
93
+ },
94
+ {
95
+ name: 'role',
96
+ label: 'Owner affiliations to include',
97
+ kind: 'list',
98
+ hint: 'OWNER, COLLABORATOR, ORGANIZATION_MEMBER',
99
+ },
100
+ {
101
+ name: 'contribs_include_own_repos',
102
+ label: 'Count contributions to your own repositories',
103
+ kind: 'boolean',
104
+ },
105
+ { name: 'custom_title', label: 'Card title', kind: 'text' },
106
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
107
+ { name: 'card_width', label: 'Card width', kind: 'number' },
108
+ { name: 'line_height', label: 'Line height', kind: 'number' },
109
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
110
+ {
111
+ name: 'number_format',
112
+ label: 'Number format',
113
+ kind: 'choice',
114
+ choices: ['short', 'long'],
115
+ },
116
+ {
117
+ name: 'number_precision',
118
+ label: 'Decimals kept when abbreviating',
119
+ kind: 'number',
120
+ },
121
+ {
122
+ name: 'disable_animations',
123
+ label: 'Disable the animations',
124
+ kind: 'boolean',
125
+ },
126
+ { name: 'ring_color', label: 'Rank ring color', kind: 'text' },
127
+ LOCALE_OPTION,
128
+ ],
129
+ render: (query, config) => stats(query, config),
130
+ },
131
+ {
132
+ id: 'top-langs',
133
+ label: 'Top languages — the languages you write most',
134
+ needsToken: true,
135
+ required: [{ name: 'username', label: 'GitHub username', kind: 'text' }],
136
+ options: [
137
+ {
138
+ name: 'layout',
139
+ label: 'Layout',
140
+ kind: 'choice',
141
+ choices: topLangs.LAYOUTS,
142
+ },
143
+ { name: 'langs_count', label: 'Languages to show', kind: 'number' },
144
+ { name: 'hide', label: 'Languages to hide', kind: 'list' },
145
+ { name: 'exclude_repo', label: 'Repositories to exclude', kind: 'list' },
146
+ {
147
+ name: 'size_weight',
148
+ label: "Weight given to a language's size",
149
+ kind: 'number',
150
+ },
151
+ {
152
+ name: 'count_weight',
153
+ label: 'Weight given to its repository count',
154
+ kind: 'number',
155
+ },
156
+ {
157
+ name: 'stats_format',
158
+ label: 'Show values as',
159
+ kind: 'choice',
160
+ choices: topLangs.STATS_FORMATS,
161
+ },
162
+ {
163
+ name: 'hide_progress',
164
+ label: 'Hide the progress bars',
165
+ kind: 'boolean',
166
+ },
167
+ { name: 'hide_values', label: 'Hide the values', kind: 'boolean' },
168
+ {
169
+ name: 'prog_bar_bg_color',
170
+ label: 'Progress bar background color',
171
+ kind: 'text',
172
+ },
173
+ { name: 'role', label: 'Owner affiliations to include', kind: 'list' },
174
+ { name: 'custom_title', label: 'Card title', kind: 'text' },
175
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
176
+ { name: 'card_width', label: 'Card width', kind: 'number' },
177
+ {
178
+ name: 'disable_animations',
179
+ label: 'Disable the animations',
180
+ kind: 'boolean',
181
+ },
182
+ LOCALE_OPTION,
183
+ ],
184
+ render: (query, config) => topLangs(query, config),
185
+ },
186
+ {
187
+ id: 'pin',
188
+ label: 'Repository pin — one repository',
189
+ needsToken: true,
190
+ required: [
191
+ { name: 'username', label: 'GitHub username', kind: 'text' },
192
+ { name: 'repo', label: 'Repository name', kind: 'text' },
193
+ ],
194
+ options: [
195
+ { name: 'show_owner', label: 'Show the owner', kind: 'boolean' },
196
+ {
197
+ name: 'show',
198
+ label: 'Extra stats to show',
199
+ kind: 'list',
200
+ hint: 'e.g. prs_authored,issues_commented',
201
+ },
202
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
203
+ {
204
+ name: 'description_lines_count',
205
+ label: 'Lines the description wraps to',
206
+ kind: 'number',
207
+ },
208
+ {
209
+ name: 'browser_rendering',
210
+ label: 'Let the browser wrap the description',
211
+ kind: 'boolean',
212
+ },
213
+ { name: 'card_width', label: 'Card width', kind: 'number' },
214
+ { name: 'line_height', label: 'Line height', kind: 'number' },
215
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
216
+ {
217
+ name: 'number_format',
218
+ label: 'Number format',
219
+ kind: 'choice',
220
+ choices: ['short', 'long'],
221
+ },
222
+ LOCALE_OPTION,
223
+ ],
224
+ render: (query, config) => pin(query, config),
225
+ },
226
+ {
227
+ id: 'gist',
228
+ label: 'Gist pin — one gist',
229
+ needsToken: true,
230
+ required: [{ name: 'id', label: 'Gist ID', kind: 'text' }],
231
+ options: [
232
+ { name: 'show_owner', label: 'Show the owner', kind: 'boolean' },
233
+ {
234
+ name: 'browser_rendering',
235
+ label: 'Let the browser wrap the description',
236
+ kind: 'boolean',
237
+ },
238
+ ],
239
+ render: (query, config) => gist(query, config),
240
+ },
241
+ {
242
+ id: 'wakatime',
243
+ label: 'WakaTime — coding time per language',
244
+ needsToken: false,
245
+ required: [{ name: 'username', label: 'WakaTime username', kind: 'text' }],
246
+ options: [
247
+ {
248
+ name: 'layout',
249
+ label: 'Layout',
250
+ kind: 'choice',
251
+ choices: wakatime.LAYOUTS,
252
+ },
253
+ {
254
+ name: 'display_format',
255
+ label: 'Show values as',
256
+ kind: 'choice',
257
+ choices: wakatime.DISPLAY_FORMATS,
258
+ },
259
+ { name: 'langs_count', label: 'Languages to show', kind: 'number' },
260
+ { name: 'hide', label: 'Languages to hide', kind: 'list' },
261
+ {
262
+ name: 'hide_progress',
263
+ label: 'Hide the progress bars',
264
+ kind: 'boolean',
265
+ },
266
+ { name: 'custom_title', label: 'Card title', kind: 'text' },
267
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
268
+ { name: 'card_width', label: 'Card width', kind: 'number' },
269
+ { name: 'line_height', label: 'Line height', kind: 'number' },
270
+ {
271
+ name: 'disable_animations',
272
+ label: 'Disable the animations',
273
+ kind: 'boolean',
274
+ },
275
+ {
276
+ name: 'api_domain',
277
+ label: 'WakaTime instance',
278
+ kind: 'text',
279
+ hint: 'Defaults to wakatime.com',
280
+ },
281
+ LOCALE_OPTION,
282
+ ],
283
+ render: (query, config) => wakatime(query, config),
284
+ },
285
+ ];
286
+ /** Every card, with the options every card shares appended to its own. */
287
+ export const cards = CARDS.map((card) => ({
288
+ ...card,
289
+ options: [...card.options, ...COMMON_OPTIONS],
290
+ }));
291
+ /**
292
+ * @param id The card's id, as `--card` takes it.
293
+ * @returns The card, or `undefined` when nothing renders under that name.
294
+ */
295
+ export const findCard = (id) => cards.find((card) => card.id === id);
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}