@webhookey/cli 0.0.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/bin/run.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const oclif = require('@oclif/core')
4
+
5
+ oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Config extends Command {
3
+ static description: string;
4
+ static args: {
5
+ action: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ value: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const config_1 = require("../lib/config");
5
+ class Config extends core_1.Command {
6
+ static description = 'Configure the CLI';
7
+ static args = {
8
+ action: core_1.Args.string({
9
+ description: 'Action to perform',
10
+ options: ['set-url', 'get-url'],
11
+ required: true,
12
+ }),
13
+ value: core_1.Args.string({ description: 'Value for set-url' }),
14
+ };
15
+ async run() {
16
+ const { args } = await this.parse(Config);
17
+ if (args.action === 'set-url') {
18
+ if (!args.value) {
19
+ this.error('URL required for set-url');
20
+ return;
21
+ }
22
+ (0, config_1.setApiUrl)(args.value);
23
+ this.log(`API URL set to: ${args.value}`);
24
+ }
25
+ else if (args.action === 'get-url') {
26
+ this.log((0, config_1.getApiUrl)());
27
+ }
28
+ }
29
+ }
30
+ exports.default = Config;
@@ -0,0 +1,13 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Listen extends Command {
3
+ static description: string;
4
+ static args: {
5
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static flags: {
8
+ parallel: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ 'allow-unverified': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ static strict: boolean;
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const core_1 = require("@oclif/core");
37
+ const child_process_1 = require("child_process");
38
+ const api_1 = require("../lib/api");
39
+ const config_1 = require("../lib/config");
40
+ class Listen extends core_1.Command {
41
+ static description = 'Listen for webhooks on a channel and execute commands';
42
+ static args = {
43
+ name: core_1.Args.string({ description: 'Channel name', required: true }),
44
+ };
45
+ static flags = {
46
+ parallel: core_1.Flags.boolean({ description: 'Allow parallel command execution' }),
47
+ 'allow-unverified': core_1.Flags.boolean({ description: 'Execute commands even for unverified webhooks' }),
48
+ };
49
+ static strict = false;
50
+ async run() {
51
+ const { args, flags } = await this.parse(Listen);
52
+ const commandStart = process.argv.indexOf('--');
53
+ const command = commandStart >= 0 ? process.argv.slice(commandStart + 1) : [];
54
+ if (command.length === 0) {
55
+ this.error('No command specified. Usage: webhookey listen <name> -- <command>');
56
+ return;
57
+ }
58
+ // Find channel by name
59
+ const channels = await api_1.api.getChannels();
60
+ const channel = channels.find((c) => c.name === args.name);
61
+ if (!channel) {
62
+ this.error(`Channel "${args.name}" not found`);
63
+ return;
64
+ }
65
+ const apiUrl = (0, config_1.getApiUrl)();
66
+ let accessToken = await (0, config_1.getAccessToken)();
67
+ if (!accessToken) {
68
+ this.error('Not logged in. Run "webhookey login" first.');
69
+ return;
70
+ }
71
+ this.log(`Listening on channel "${args.name}"...`);
72
+ this.log(`Executing: ${command.join(' ')}`);
73
+ // Dynamically import eventsource
74
+ const { default: EventSource } = await Promise.resolve().then(() => __importStar(require('eventsource')));
75
+ let eventSource;
76
+ const queue = [];
77
+ let currentProcess = null;
78
+ const maxQueueDepth = 10;
79
+ let reconnectAttempts = 0;
80
+ const maxReconnectAttempts = 10;
81
+ const baseReconnectDelay = 1000;
82
+ let generation = 0; // incremented on each new connection; stale handlers check this
83
+ let isRefreshing = false; // prevents concurrent token refresh calls
84
+ let reconnectTimer = null;
85
+ const connect = () => {
86
+ const myGeneration = ++generation;
87
+ // Cancel any pending reconnect scheduled by a previous generation
88
+ if (reconnectTimer !== null) {
89
+ clearTimeout(reconnectTimer);
90
+ reconnectTimer = null;
91
+ }
92
+ eventSource = new EventSource(`${apiUrl}/hooks/${channel.slug}/events`, {
93
+ headers: { Authorization: `Bearer ${accessToken}` },
94
+ });
95
+ eventSource.onopen = () => {
96
+ if (myGeneration !== generation)
97
+ return;
98
+ reconnectAttempts = 0;
99
+ };
100
+ eventSource.onmessage = (event) => {
101
+ if (myGeneration !== generation)
102
+ return;
103
+ const data = JSON.parse(event.data);
104
+ if (data.type === 'heartbeat') {
105
+ return;
106
+ }
107
+ if (!data.verified) {
108
+ if (!flags['allow-unverified']) {
109
+ this.warn('Received unverified webhook, skipping execution');
110
+ return;
111
+ }
112
+ }
113
+ if (queue.length >= maxQueueDepth) {
114
+ this.warn('Queue full, dropping event');
115
+ return;
116
+ }
117
+ queue.push(data.payload);
118
+ processQueue();
119
+ };
120
+ eventSource.onerror = async (err) => {
121
+ if (myGeneration !== generation)
122
+ return;
123
+ const status = err?.status;
124
+ // Only attempt token refresh on 401
125
+ if (status === 401) {
126
+ if (isRefreshing)
127
+ return;
128
+ isRefreshing = true;
129
+ const refresh = await (0, config_1.getRefreshToken)();
130
+ if (refresh) {
131
+ try {
132
+ const res = await fetch(`${apiUrl}/auth/refresh`, {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({ refreshToken: refresh }),
136
+ });
137
+ if (res.ok) {
138
+ const tokens = await res.json();
139
+ await (0, config_1.setAccessToken)(tokens.access_token);
140
+ await (0, config_1.setRefreshToken)(tokens.refresh_token);
141
+ accessToken = tokens.access_token;
142
+ isRefreshing = false;
143
+ eventSource?.close();
144
+ reconnectAttempts = 0;
145
+ connect();
146
+ return;
147
+ }
148
+ }
149
+ catch {
150
+ // Refresh failed, fall through
151
+ }
152
+ }
153
+ // Tokens remain in keyring — subsequent commands can still attempt refresh
154
+ this.error('Session expired — run webhookey login');
155
+ process.exit(1);
156
+ return;
157
+ }
158
+ // Transient error (connection reset, server restart, network issue)
159
+ eventSource?.close();
160
+ if (reconnectAttempts >= maxReconnectAttempts) {
161
+ this.error('Lost connection to server after multiple retries');
162
+ process.exit(1);
163
+ return;
164
+ }
165
+ const delay = Math.min(baseReconnectDelay * 2 ** reconnectAttempts, 60000);
166
+ reconnectAttempts++;
167
+ this.warn(`Connection lost, reconnecting in ${Math.round(delay / 1000)}s (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);
168
+ reconnectTimer = setTimeout(connect, delay);
169
+ };
170
+ };
171
+ const processQueue = () => {
172
+ if (queue.length === 0)
173
+ return;
174
+ if (currentProcess && !flags.parallel)
175
+ return;
176
+ const payload = queue.shift();
177
+ const payloadJson = JSON.stringify(payload);
178
+ currentProcess = (0, child_process_1.spawn)(command[0], command.slice(1), {
179
+ env: { ...process.env, WEBHOOKEY_PAYLOAD: payloadJson },
180
+ stdio: ['pipe', 'inherit', 'inherit'],
181
+ });
182
+ currentProcess.stdin?.write(payloadJson);
183
+ currentProcess.stdin?.end();
184
+ currentProcess.on('close', () => {
185
+ currentProcess = null;
186
+ processQueue();
187
+ });
188
+ };
189
+ connect();
190
+ // Handle SIGINT gracefully
191
+ process.on('SIGINT', () => {
192
+ if (reconnectTimer !== null)
193
+ clearTimeout(reconnectTimer);
194
+ eventSource?.close();
195
+ if (currentProcess) {
196
+ currentProcess.kill('SIGTERM');
197
+ }
198
+ process.exit(0);
199
+ });
200
+ // Keep process running
201
+ await new Promise(() => { });
202
+ }
203
+ }
204
+ exports.default = Listen;
@@ -0,0 +1,5 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Login extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const api_1 = require("../lib/api");
5
+ const config_1 = require("../lib/config");
6
+ class Login extends core_1.Command {
7
+ static description = 'Authenticate via device flow';
8
+ async run() {
9
+ const deviceRes = await api_1.api.deviceCode();
10
+ this.log(`User code: ${deviceRes.user_code}`);
11
+ this.log(`Open ${deviceRes.verification_uri} to activate`);
12
+ this.log('Waiting for activation...');
13
+ let interval = deviceRes.interval * 1000;
14
+ while (true) {
15
+ await new Promise((r) => setTimeout(r, interval));
16
+ const tokenRes = await api_1.api.token(deviceRes.device_code);
17
+ if (tokenRes.error === 'slow_down') {
18
+ interval += 5000;
19
+ this.log('Server requested slow_down, increasing interval...');
20
+ continue;
21
+ }
22
+ if (tokenRes.error === 'authorization_pending') {
23
+ continue;
24
+ }
25
+ if (tokenRes.error) {
26
+ this.error(`Device flow failed: ${tokenRes.error}`);
27
+ return;
28
+ }
29
+ if (tokenRes.access_token && tokenRes.refresh_token) {
30
+ await (0, config_1.setAccessToken)(tokenRes.access_token);
31
+ await (0, config_1.setRefreshToken)(tokenRes.refresh_token);
32
+ this.log('Logged in successfully!');
33
+ return;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ exports.default = Login;
@@ -0,0 +1,5 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Logout extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const api_1 = require("../lib/api");
5
+ const config_1 = require("../lib/config");
6
+ class Logout extends core_1.Command {
7
+ static description = 'Log out and clear stored credentials';
8
+ async run() {
9
+ const refreshToken = await (0, config_1.getRefreshToken)();
10
+ if (refreshToken) {
11
+ try {
12
+ await api_1.api.logout(refreshToken);
13
+ }
14
+ catch (e) {
15
+ // Ignore errors - still clear local tokens
16
+ }
17
+ }
18
+ await (0, config_1.clearTokens)();
19
+ this.log('Logged out successfully');
20
+ }
21
+ }
22
+ exports.default = Logout;
@@ -0,0 +1,5 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class List extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const api_1 = require("../lib/api");
5
+ class List extends core_1.Command {
6
+ static description = 'List your webhook channels';
7
+ async run() {
8
+ const channels = await api_1.api.getChannels();
9
+ if (channels.length === 0) {
10
+ this.log('No channels found. Run "webhookey new <name>" to create one.');
11
+ return;
12
+ }
13
+ this.log('Channels:');
14
+ for (const c of channels) {
15
+ this.log(` ${c.name}`);
16
+ this.log(` URL: ${c.webhookUrl}`);
17
+ this.log(` Created: ${new Date(c.createdAt).toLocaleDateString()}`);
18
+ }
19
+ }
20
+ }
21
+ exports.default = List;
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class New extends Command {
3
+ static description: string;
4
+ static args: {
5
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static flags: {
8
+ 'no-secret': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ 'retention-days': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const api_1 = require("../lib/api");
5
+ class New extends core_1.Command {
6
+ static description = 'Create a new webhook channel';
7
+ static args = {
8
+ name: core_1.Args.string({ description: 'Channel name', required: true }),
9
+ };
10
+ static flags = {
11
+ 'no-secret': core_1.Flags.boolean({ description: 'Create channel without HMAC secret' }),
12
+ 'retention-days': core_1.Flags.integer({ description: 'Number of days to retain webhook events (1-365)' }),
13
+ };
14
+ async run() {
15
+ const { args, flags } = await this.parse(New);
16
+ const channel = await api_1.api.createChannel(args.name, !flags['no-secret'], flags['retention-days']);
17
+ this.log(`Channel created: ${channel.name}`);
18
+ this.log(`Webhook URL: ${channel.webhookUrl}`);
19
+ if (channel.secret) {
20
+ this.log(`Secret: ${channel.secret}`);
21
+ this.log('⚠️ Save your secret — it won\'t be shown again!');
22
+ }
23
+ else {
24
+ this.log('ℹ️ Channel has no secret — all incoming webhooks will be treated as verified');
25
+ }
26
+ }
27
+ }
28
+ exports.default = New;
@@ -0,0 +1,8 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Remove extends Command {
3
+ static description: string;
4
+ static args: {
5
+ name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const api_1 = require("../lib/api");
5
+ const prompts_1 = require("@inquirer/prompts");
6
+ class Remove extends core_1.Command {
7
+ static description = 'Remove a webhook channel and all its events';
8
+ static args = {
9
+ name: core_1.Args.string({ description: 'Channel name to remove', required: true }),
10
+ };
11
+ async run() {
12
+ const { args } = await this.parse(Remove);
13
+ // Find channel by name
14
+ const channels = await api_1.api.getChannels();
15
+ const channel = channels.find(c => c.name === args.name);
16
+ if (!channel) {
17
+ this.error(`Channel "${args.name}" not found`);
18
+ return;
19
+ }
20
+ // Confirm deletion
21
+ const confirmed = await (0, prompts_1.confirm)({
22
+ message: `Delete channel "${args.name}" and all its events?`,
23
+ default: false,
24
+ });
25
+ if (!confirmed) {
26
+ this.log('Deletion cancelled');
27
+ return;
28
+ }
29
+ // Delete the channel
30
+ await api_1.api.deleteChannel(channel.id);
31
+ this.log(`Channel "${args.name}" deleted successfully`);
32
+ }
33
+ }
34
+ exports.default = Remove;
@@ -0,0 +1,5 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Whoami extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const api_1 = require("../lib/api");
5
+ class Whoami extends core_1.Command {
6
+ static description = 'Show current user information';
7
+ async run() {
8
+ const user = await api_1.api.me();
9
+ this.log(`Email: ${user.email}`);
10
+ this.log(`User ID: ${user.id}`);
11
+ }
12
+ }
13
+ exports.default = Whoami;
@@ -0,0 +1,36 @@
1
+ export declare const api: {
2
+ deviceCode: () => Promise<{
3
+ device_code: string;
4
+ user_code: string;
5
+ verification_uri: string;
6
+ interval: number;
7
+ }>;
8
+ token: (deviceCode: string) => Promise<{
9
+ error?: string;
10
+ access_token?: string;
11
+ refresh_token?: string;
12
+ }>;
13
+ logout: (refreshToken: string) => Promise<unknown>;
14
+ me: () => Promise<{
15
+ id: string;
16
+ email: string;
17
+ name: string;
18
+ }>;
19
+ getChannels: () => Promise<{
20
+ id: string;
21
+ slug: string;
22
+ name: string;
23
+ webhookUrl: string;
24
+ createdAt: string;
25
+ }[]>;
26
+ createChannel: (name: string, generateSecret?: boolean, retentionDays?: number) => Promise<{
27
+ id: string;
28
+ slug: string;
29
+ name: string;
30
+ webhookUrl: string;
31
+ secret?: string;
32
+ }>;
33
+ deleteChannel: (id: string) => Promise<{
34
+ success: boolean;
35
+ }>;
36
+ };
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.api = void 0;
4
+ const config_1 = require("./config");
5
+ async function request(path, options = {}) {
6
+ const apiUrl = (0, config_1.getApiUrl)();
7
+ let token = await (0, config_1.getAccessToken)();
8
+ const doRequest = async (accessToken) => {
9
+ const headers = {
10
+ 'Content-Type': 'application/json',
11
+ };
12
+ if (options.headers) {
13
+ const otherHeaders = options.headers;
14
+ Object.assign(headers, otherHeaders);
15
+ }
16
+ if (accessToken) {
17
+ headers['Authorization'] = `Bearer ${accessToken}`;
18
+ }
19
+ return fetch(`${apiUrl}${path}`, {
20
+ ...options,
21
+ headers,
22
+ });
23
+ };
24
+ let res = await doRequest(token);
25
+ // Handle 401 - try refresh
26
+ if (res.status === 401) {
27
+ const refresh = await (0, config_1.getRefreshToken)();
28
+ if (refresh) {
29
+ const refreshRes = await fetch(`${apiUrl}/auth/refresh`, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ body: JSON.stringify({ refreshToken: refresh }),
35
+ });
36
+ if (refreshRes.ok) {
37
+ const tokens = await refreshRes.json();
38
+ await (0, config_1.setAccessToken)(tokens.access_token);
39
+ await (0, config_1.setRefreshToken)(tokens.refresh_token);
40
+ res = await doRequest(tokens.access_token);
41
+ }
42
+ else {
43
+ await (0, config_1.clearTokens)();
44
+ throw new Error('Session expired — run webhookey login');
45
+ }
46
+ }
47
+ else {
48
+ throw new Error('Session expired — run webhookey login');
49
+ }
50
+ }
51
+ if (!res.ok) {
52
+ const error = await res.json().catch(() => ({ message: 'Request failed' }));
53
+ throw new Error(error.error || error.message || 'Request failed');
54
+ }
55
+ return res.json();
56
+ }
57
+ exports.api = {
58
+ deviceCode: () => request('/auth/device', { method: 'POST' }),
59
+ token: (deviceCode) => request('/auth/token', {
60
+ method: 'POST',
61
+ body: JSON.stringify({ device_code: deviceCode }),
62
+ }),
63
+ logout: (refreshToken) => request('/auth/logout', {
64
+ method: 'POST',
65
+ body: JSON.stringify({ refreshToken }),
66
+ }),
67
+ me: () => request('/auth/me'),
68
+ getChannels: () => request('/channels'),
69
+ createChannel: (name, generateSecret = true, retentionDays) => request('/channels', {
70
+ method: 'POST',
71
+ body: JSON.stringify({ name, generateSecret, retentionDays }),
72
+ }),
73
+ deleteChannel: (id) => request(`/channels/${id}`, {
74
+ method: 'DELETE',
75
+ }),
76
+ };
@@ -0,0 +1,7 @@
1
+ export declare function getApiUrl(): string;
2
+ export declare function setApiUrl(url: string): void;
3
+ export declare function getAccessToken(): Promise<string | null>;
4
+ export declare function setAccessToken(token: string): Promise<void>;
5
+ export declare function getRefreshToken(): Promise<string | null>;
6
+ export declare function setRefreshToken(token: string): Promise<void>;
7
+ export declare function clearTokens(): Promise<void>;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getApiUrl = getApiUrl;
37
+ exports.setApiUrl = setApiUrl;
38
+ exports.getAccessToken = getAccessToken;
39
+ exports.setAccessToken = setAccessToken;
40
+ exports.getRefreshToken = getRefreshToken;
41
+ exports.setRefreshToken = setRefreshToken;
42
+ exports.clearTokens = clearTokens;
43
+ const keyring_1 = require("@napi-rs/keyring");
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
47
+ const SERVICE_NAME = 'webhookey';
48
+ const configDir = path.join(os.homedir(), '.config', 'webhookey');
49
+ const configPath = path.join(configDir, 'config.json');
50
+ const defaultConfig = {
51
+ apiUrl: 'http://localhost:3000',
52
+ };
53
+ function loadConfig() {
54
+ try {
55
+ if (fs.existsSync(configPath)) {
56
+ const data = fs.readFileSync(configPath, 'utf-8');
57
+ return { ...defaultConfig, ...JSON.parse(data) };
58
+ }
59
+ }
60
+ catch {
61
+ // Ignore errors, use defaults
62
+ }
63
+ return { ...defaultConfig };
64
+ }
65
+ function saveConfig(config) {
66
+ try {
67
+ if (!fs.existsSync(configDir)) {
68
+ fs.mkdirSync(configDir, { recursive: true });
69
+ }
70
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
71
+ }
72
+ catch (e) {
73
+ throw new Error(`Failed to save config: ${e}`);
74
+ }
75
+ }
76
+ let cachedConfig = null;
77
+ function getConfig() {
78
+ if (!cachedConfig) {
79
+ cachedConfig = loadConfig();
80
+ }
81
+ return cachedConfig;
82
+ }
83
+ function getApiUrl() {
84
+ return process.env.WEBHOOKEY_API_URL || getConfig().apiUrl;
85
+ }
86
+ function setApiUrl(url) {
87
+ // Validate URL
88
+ try {
89
+ new URL(url);
90
+ }
91
+ catch {
92
+ throw new Error(`Invalid URL: ${url}`);
93
+ }
94
+ const config = getConfig();
95
+ config.apiUrl = url;
96
+ saveConfig(config);
97
+ }
98
+ async function getAccessToken() {
99
+ const entry = new keyring_1.Entry(SERVICE_NAME, 'access_token');
100
+ try {
101
+ const password = await entry.getPassword();
102
+ return password || null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ async function setAccessToken(token) {
109
+ const entry = new keyring_1.Entry(SERVICE_NAME, 'access_token');
110
+ await entry.setPassword(token);
111
+ }
112
+ async function getRefreshToken() {
113
+ const entry = new keyring_1.Entry(SERVICE_NAME, 'refresh_token');
114
+ try {
115
+ const password = await entry.getPassword();
116
+ return password || null;
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ async function setRefreshToken(token) {
123
+ const entry = new keyring_1.Entry(SERVICE_NAME, 'refresh_token');
124
+ await entry.setPassword(token);
125
+ }
126
+ async function clearTokens() {
127
+ const accessEntry = new keyring_1.Entry(SERVICE_NAME, 'access_token');
128
+ const refreshEntry = new keyring_1.Entry(SERVICE_NAME, 'refresh_token');
129
+ try {
130
+ await accessEntry.deletePassword();
131
+ }
132
+ catch { }
133
+ try {
134
+ await refreshEntry.deletePassword();
135
+ }
136
+ catch { }
137
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@webhookey/cli",
3
+ "version": "0.0.0",
4
+ "description": "CLI for webhookey — self-hosted webhook proxy with SSE streaming",
5
+ "keywords": ["webhookey", "webhook", "proxy", "cli", "sse"],
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org"
10
+ },
11
+ "bin": {
12
+ "webhookey": "./bin/run.js"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "lint": "eslint \"src/**/*.ts\"",
22
+ "test": "vitest run --passWithNoTests",
23
+ "clean": "rm -rf dist",
24
+ "prepublishOnly": "yarn build"
25
+ },
26
+ "dependencies": {
27
+ "@inquirer/prompts": "^8.4.1",
28
+ "@napi-rs/keyring": "^1.0.0",
29
+ "@oclif/core": "^3.0.0",
30
+ "conf": "^12.0.0",
31
+ "eventsource": "^2.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/eventsource": "^1.0.0",
35
+ "@types/node": "^20.0.0",
36
+ "@webhookey/types": "*",
37
+ "typescript": "^5.0.0",
38
+ "vitest": "^1.0.0"
39
+ },
40
+ "oclif": {
41
+ "bin": "webhookey",
42
+ "dirname": "webhookey",
43
+ "commands": "./dist/commands",
44
+ "topicSeparator": " "
45
+ }
46
+ }