@radjay/kit-analytics 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/dist/api.d.ts ADDED
@@ -0,0 +1,115 @@
1
+ export interface KitClientOptions {
2
+ apiKey: string;
3
+ }
4
+ export interface Pagination {
5
+ has_previous_page: boolean;
6
+ has_next_page: boolean;
7
+ start_cursor: string | null;
8
+ end_cursor: string | null;
9
+ per_page: number;
10
+ }
11
+ export interface AccountUser {
12
+ id: number;
13
+ email: string;
14
+ }
15
+ export interface AccountInfo {
16
+ id: number;
17
+ name: string;
18
+ plan_type: string;
19
+ primary_email_address: string;
20
+ created_at: string;
21
+ }
22
+ export interface Subscriber {
23
+ id: number;
24
+ email_address: string;
25
+ first_name: string | null;
26
+ state: string;
27
+ created_at: string;
28
+ }
29
+ export interface BroadcastStats {
30
+ recipients: number;
31
+ open_rate: number;
32
+ click_rate: number;
33
+ unsubscribes: number;
34
+ total_clicks: number;
35
+ status: string;
36
+ }
37
+ export interface Broadcast {
38
+ id: number;
39
+ subject: string;
40
+ created_at: string;
41
+ published_at?: string | null;
42
+ send_at?: string | null;
43
+ description?: string | null;
44
+ }
45
+ export interface Tag {
46
+ id: number;
47
+ name: string;
48
+ created_at: string;
49
+ }
50
+ export interface Form {
51
+ id: number;
52
+ name: string;
53
+ type: string;
54
+ format: string | null;
55
+ created_at: string;
56
+ archived: boolean;
57
+ }
58
+ export interface Sequence {
59
+ id: number;
60
+ name: string;
61
+ created_at: string;
62
+ }
63
+ export declare class KitClient {
64
+ private apiKey;
65
+ constructor(opts: KitClientOptions);
66
+ private request;
67
+ getAccount(): Promise<{
68
+ user: AccountUser;
69
+ account: AccountInfo;
70
+ }>;
71
+ listSubscribers(opts?: {
72
+ status?: string;
73
+ tagId?: string;
74
+ since?: string;
75
+ until?: string;
76
+ limit?: string;
77
+ cursor?: string;
78
+ }): Promise<{
79
+ subscribers: Subscriber[];
80
+ pagination: Pagination;
81
+ }>;
82
+ listBroadcasts(opts?: {
83
+ status?: string;
84
+ limit?: string;
85
+ cursor?: string;
86
+ }): Promise<{
87
+ broadcasts: Broadcast[];
88
+ pagination: Pagination;
89
+ }>;
90
+ getBroadcastStats(id: number): Promise<{
91
+ broadcast: {
92
+ id: number;
93
+ stats: BroadcastStats;
94
+ };
95
+ }>;
96
+ listTags(opts?: {
97
+ cursor?: string;
98
+ }): Promise<{
99
+ tags: Tag[];
100
+ pagination: Pagination;
101
+ }>;
102
+ listForms(opts?: {
103
+ status?: string;
104
+ cursor?: string;
105
+ }): Promise<{
106
+ forms: Form[];
107
+ pagination: Pagination;
108
+ }>;
109
+ listSequences(opts?: {
110
+ cursor?: string;
111
+ }): Promise<{
112
+ sequences: Sequence[];
113
+ pagination: Pagination;
114
+ }>;
115
+ }
package/dist/api.js ADDED
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KitClient = void 0;
4
+ const BASE_URL = "https://api.kit.com/v4";
5
+ class KitClient {
6
+ apiKey;
7
+ constructor(opts) {
8
+ this.apiKey = opts.apiKey;
9
+ }
10
+ async request(path, params) {
11
+ const url = new URL(`${BASE_URL}${path}`);
12
+ if (params) {
13
+ for (const [key, value] of Object.entries(params)) {
14
+ if (value !== undefined && value !== "") {
15
+ url.searchParams.set(key, value);
16
+ }
17
+ }
18
+ }
19
+ const res = await fetch(url.toString(), {
20
+ headers: {
21
+ "X-Kit-Api-Key": this.apiKey,
22
+ Accept: "application/json",
23
+ },
24
+ });
25
+ if (!res.ok) {
26
+ const body = await res.text();
27
+ throw new Error(`Kit API error ${res.status}: ${body}`);
28
+ }
29
+ return res.json();
30
+ }
31
+ async getAccount() {
32
+ return this.request("/account");
33
+ }
34
+ async listSubscribers(opts) {
35
+ const params = {};
36
+ if (opts?.status)
37
+ params["status"] = opts.status;
38
+ if (opts?.tagId)
39
+ params["tag_id"] = opts.tagId;
40
+ if (opts?.since)
41
+ params["created_after"] = opts.since;
42
+ if (opts?.until)
43
+ params["created_before"] = opts.until;
44
+ if (opts?.limit)
45
+ params["per_page"] = opts.limit;
46
+ if (opts?.cursor)
47
+ params["after"] = opts.cursor;
48
+ return this.request("/subscribers", params);
49
+ }
50
+ async listBroadcasts(opts) {
51
+ const params = {};
52
+ if (opts?.status)
53
+ params["status"] = opts.status;
54
+ if (opts?.limit)
55
+ params["per_page"] = opts.limit;
56
+ if (opts?.cursor)
57
+ params["after"] = opts.cursor;
58
+ return this.request("/broadcasts", params);
59
+ }
60
+ async getBroadcastStats(id) {
61
+ return this.request(`/broadcasts/${id}/stats`);
62
+ }
63
+ async listTags(opts) {
64
+ const params = {};
65
+ if (opts?.cursor)
66
+ params["after"] = opts.cursor;
67
+ return this.request("/tags", params);
68
+ }
69
+ async listForms(opts) {
70
+ const params = {};
71
+ if (opts?.status)
72
+ params["status"] = opts.status;
73
+ if (opts?.cursor)
74
+ params["after"] = opts.cursor;
75
+ return this.request("/forms", params);
76
+ }
77
+ async listSequences(opts) {
78
+ const params = {};
79
+ if (opts?.cursor)
80
+ params["after"] = opts.cursor;
81
+ return this.request("/sequences", params);
82
+ }
83
+ }
84
+ exports.KitClient = KitClient;
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function accountCommand(): Command;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.accountCommand = accountCommand;
4
+ const commander_1 = require("commander");
5
+ const formatter_js_1 = require("../formatter.js");
6
+ function accountCommand() {
7
+ return new commander_1.Command("account")
8
+ .description("Display account info")
9
+ .action(async function () {
10
+ const client = this.opts()["__client"];
11
+ const format = this.opts()["__format"];
12
+ const res = await client.getAccount();
13
+ (0, formatter_js_1.output)({
14
+ name: res.account.name,
15
+ plan: res.account.plan_type,
16
+ email: res.account.primary_email_address,
17
+ created_at: res.account.created_at,
18
+ }, format);
19
+ });
20
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function broadcastsCommand(): Command;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.broadcastsCommand = broadcastsCommand;
4
+ const commander_1 = require("commander");
5
+ const formatter_js_1 = require("../formatter.js");
6
+ function broadcastsCommand() {
7
+ return new commander_1.Command("broadcasts")
8
+ .description("List broadcasts with stats (defaults to completed only)")
9
+ .option("--status <status>", "Filter by stats status (completed, scheduled, all)", "completed")
10
+ .option("--limit <n>", "Max results per page")
11
+ .action(async function () {
12
+ const client = this.opts()["__client"];
13
+ const format = this.opts()["__format"];
14
+ const opts = this.opts();
15
+ const res = await client.listBroadcasts({ limit: opts.limit });
16
+ const rows = [];
17
+ for (const b of res.broadcasts) {
18
+ const statsRes = await client.getBroadcastStats(b.id);
19
+ const s = statsRes.broadcast.stats;
20
+ if (opts.status !== "all" && s.status !== opts.status)
21
+ continue;
22
+ rows.push({
23
+ id: b.id,
24
+ subject: b.subject ?? "",
25
+ created_at: b.created_at,
26
+ recipients: s.recipients,
27
+ open_rate: `${s.open_rate.toFixed(1)}%`,
28
+ click_rate: `${s.click_rate.toFixed(1)}%`,
29
+ unsubscribes: s.unsubscribes,
30
+ status: s.status,
31
+ });
32
+ }
33
+ (0, formatter_js_1.output)(rows, format);
34
+ });
35
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function formsCommand(): Command;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formsCommand = formsCommand;
4
+ const commander_1 = require("commander");
5
+ const formatter_js_1 = require("../formatter.js");
6
+ function formsCommand() {
7
+ return new commander_1.Command("forms")
8
+ .description("List all forms")
9
+ .action(async function () {
10
+ const client = this.opts()["__client"];
11
+ const format = this.opts()["__format"];
12
+ const res = await client.listForms();
13
+ const rows = res.forms.map((f) => ({
14
+ id: f.id,
15
+ name: f.name,
16
+ type: f.type,
17
+ archived: f.archived,
18
+ created_at: f.created_at,
19
+ }));
20
+ (0, formatter_js_1.output)(rows, format);
21
+ });
22
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function sequencesCommand(): Command;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sequencesCommand = sequencesCommand;
4
+ const commander_1 = require("commander");
5
+ const formatter_js_1 = require("../formatter.js");
6
+ function sequencesCommand() {
7
+ return new commander_1.Command("sequences")
8
+ .description("List all sequences")
9
+ .action(async function () {
10
+ const client = this.opts()["__client"];
11
+ const format = this.opts()["__format"];
12
+ const res = await client.listSequences();
13
+ const rows = res.sequences.map((s) => ({
14
+ id: s.id,
15
+ name: s.name,
16
+ created_at: s.created_at,
17
+ }));
18
+ (0, formatter_js_1.output)(rows, format);
19
+ });
20
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function subscribersCommand(): Command;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.subscribersCommand = subscribersCommand;
4
+ const commander_1 = require("commander");
5
+ const formatter_js_1 = require("../formatter.js");
6
+ function subscribersCommand() {
7
+ return new commander_1.Command("subscribers")
8
+ .description("List subscribers")
9
+ .option("--status <status>", "Filter by status (active, cancelled, bounced, complained)")
10
+ .option("--tag <tag_id>", "Filter by tag ID")
11
+ .option("--since <date>", "Created after date (ISO 8601)")
12
+ .option("--until <date>", "Created before date (ISO 8601)")
13
+ .option("--limit <n>", "Max results per page")
14
+ .action(async function () {
15
+ const client = this.opts()["__client"];
16
+ const format = this.opts()["__format"];
17
+ const opts = this.opts();
18
+ const res = await client.listSubscribers({
19
+ status: opts.status,
20
+ tagId: opts.tag,
21
+ since: opts.since,
22
+ until: opts.until,
23
+ limit: opts.limit,
24
+ });
25
+ const rows = res.subscribers.map((s) => ({
26
+ id: s.id,
27
+ email: s.email_address,
28
+ first_name: s.first_name ?? "",
29
+ state: s.state,
30
+ created_at: s.created_at,
31
+ }));
32
+ (0, formatter_js_1.output)(rows, format);
33
+ });
34
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function tagsCommand(): Command;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tagsCommand = tagsCommand;
4
+ const commander_1 = require("commander");
5
+ const formatter_js_1 = require("../formatter.js");
6
+ function tagsCommand() {
7
+ return new commander_1.Command("tags")
8
+ .description("List all tags")
9
+ .action(async function () {
10
+ const client = this.opts()["__client"];
11
+ const format = this.opts()["__format"];
12
+ const res = await client.listTags();
13
+ const rows = res.tags.map((t) => ({
14
+ id: t.id,
15
+ name: t.name,
16
+ created_at: t.created_at,
17
+ }));
18
+ (0, formatter_js_1.output)(rows, format);
19
+ });
20
+ }
@@ -0,0 +1,2 @@
1
+ export declare function formatTable(rows: Record<string, unknown>[]): string;
2
+ export declare function output(data: unknown, format: string): void;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTable = formatTable;
4
+ exports.output = output;
5
+ function formatTable(rows) {
6
+ if (rows.length === 0)
7
+ return "(no data)";
8
+ const keys = Object.keys(rows[0]);
9
+ const widths = keys.map((k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length)));
10
+ const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
11
+ const separator = widths.map((w) => "-".repeat(w)).join(" ");
12
+ const body = rows
13
+ .map((r) => keys.map((k, i) => String(r[k] ?? "").padEnd(widths[i])).join(" "))
14
+ .join("\n");
15
+ return `${header}\n${separator}\n${body}`;
16
+ }
17
+ function output(data, format) {
18
+ if (format === "json") {
19
+ console.log(JSON.stringify(data, null, 2));
20
+ }
21
+ else {
22
+ if (Array.isArray(data)) {
23
+ console.log(formatTable(data));
24
+ }
25
+ else {
26
+ console.log(formatTable([data]));
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const api_js_1 = require("./api.js");
6
+ const account_js_1 = require("./commands/account.js");
7
+ const subscribers_js_1 = require("./commands/subscribers.js");
8
+ const broadcasts_js_1 = require("./commands/broadcasts.js");
9
+ const tags_js_1 = require("./commands/tags.js");
10
+ const forms_js_1 = require("./commands/forms.js");
11
+ const sequences_js_1 = require("./commands/sequences.js");
12
+ const program = new commander_1.Command();
13
+ program
14
+ .name("kit-analytics")
15
+ .description("Pull Kit.com newsletter analytics via Kit API v4")
16
+ .version("0.0.1")
17
+ .option("--api-key <key>", "Kit API key (overrides KIT_API_KEY env var)")
18
+ .option("--format <format>", "Output format: json or table", "table");
19
+ program.hook("preAction", () => {
20
+ const opts = program.opts();
21
+ const apiKey = opts.apiKey || process.env["KIT_API_KEY"];
22
+ if (!apiKey) {
23
+ console.error("Error: Kit API key is required. Set KIT_API_KEY env var or pass --api-key.");
24
+ process.exit(1);
25
+ }
26
+ const client = new api_js_1.KitClient({ apiKey });
27
+ const format = opts.format;
28
+ // Bind the client to all subcommands via metadata
29
+ for (const cmd of program.commands) {
30
+ cmd.setOptionValue("__client", client);
31
+ cmd.setOptionValue("__format", format);
32
+ }
33
+ });
34
+ // Register commands — each command pulls client/format from its own opts
35
+ program.addCommand((0, account_js_1.accountCommand)());
36
+ program.addCommand((0, subscribers_js_1.subscribersCommand)());
37
+ program.addCommand((0, broadcasts_js_1.broadcastsCommand)());
38
+ program.addCommand((0, tags_js_1.tagsCommand)());
39
+ program.addCommand((0, forms_js_1.formsCommand)());
40
+ program.addCommand((0, sequences_js_1.sequencesCommand)());
41
+ program.parseAsync(process.argv).catch((err) => {
42
+ console.error(`Error: ${err.message}`);
43
+ process.exit(1);
44
+ });
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@radjay/kit-analytics",
3
+ "version": "0.0.1",
4
+ "description": "CLI tool to pull Kit.com newsletter analytics via Kit API v4",
5
+ "bin": {
6
+ "kit-analytics": "./dist/index.js"
7
+ },
8
+ "files": ["dist"],
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "clean": "rm -rf dist"
12
+ },
13
+ "dependencies": {
14
+ "commander": "^13.1.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.13.4",
18
+ "typescript": "^5.7.3"
19
+ }
20
+ }