myvillage-clients-cli 0.1.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/cli.js ADDED
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const readline = require('readline');
5
+
6
+ const DEFAULT_WEBHOOK_URL = 'https://myvillageproject.app.n8n.cloud/webhook/7c7b9591-54f4-47eb-8b81-6464339a497d';
7
+
8
+ /**
9
+ * Pretty print the report data
10
+ */
11
+ function prettyPrintReport(data) {
12
+ if (Array.isArray(data)) {
13
+ printUpdates(data);
14
+ return;
15
+ }
16
+
17
+ if (typeof data === 'object' && data !== null) {
18
+ // Handle simple output response
19
+ if (data.output && typeof data.output === 'string') {
20
+ console.log(data.output);
21
+ return;
22
+ }
23
+
24
+ const updates = data.updates || data.items || null;
25
+ const meta = Object.fromEntries(
26
+ Object.entries(data).filter(([k]) => !['updates', 'items'].includes(k))
27
+ );
28
+
29
+ if (Object.keys(meta).length > 0) {
30
+ console.log('== Meta ==');
31
+ for (const [k, v] of Object.entries(meta)) {
32
+ console.log(`- ${k}: ${v}`);
33
+ }
34
+ console.log();
35
+ }
36
+
37
+ if (!updates) {
38
+ console.log('No updates returned.');
39
+ return;
40
+ }
41
+
42
+ if (!Array.isArray(updates)) {
43
+ console.log(typeof updates === 'object' ? JSON.stringify(updates, null, 2) : String(updates));
44
+ return;
45
+ }
46
+
47
+ printUpdates(updates);
48
+ return;
49
+ }
50
+
51
+ console.log(String(data));
52
+ }
53
+
54
+ function printUpdates(updates) {
55
+ console.log('== GitHub Updates ==');
56
+ updates.forEach((u, i) => {
57
+ const idx = i + 1;
58
+ if (typeof u !== 'object' || u === null) {
59
+ console.log(`${idx}. ${u}`);
60
+ return;
61
+ }
62
+
63
+ const repo = u.repo || u.repository || u.full_name || 'Unknown repo';
64
+ const title = u.title || u.message || u.summary || '';
65
+ const author = u.author || u.user || u.actor || '';
66
+ const url = u.url || u.html_url || u.link || '';
67
+ const ts = u.timestamp || u.time || u.created_at || '';
68
+
69
+ let line = `${idx}. ${repo}`;
70
+ if (title) line += ` — ${title}`;
71
+ console.log(line);
72
+
73
+ const extra = [];
74
+ if (author) extra.push(`by ${author}`);
75
+ if (ts) extra.push(`at ${ts}`);
76
+ if (extra.length > 0) console.log(' ' + extra.join(' | '));
77
+ if (url) console.log(` ${url}`);
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Fetch updates from the webhook
83
+ */
84
+ async function getUpdates(options) {
85
+ const url = new URL(options.url || DEFAULT_WEBHOOK_URL);
86
+
87
+ if (options.client) url.searchParams.set('client', options.client);
88
+ if (options.since) url.searchParams.set('since', options.since);
89
+ if (options.limit) url.searchParams.set('limit', options.limit);
90
+
91
+ try {
92
+ const controller = new AbortController();
93
+ const timeout = setTimeout(() => controller.abort(), (options.timeout || 30) * 1000);
94
+
95
+ const resp = await fetch(url.toString(), {
96
+ method: 'GET',
97
+ signal: controller.signal,
98
+ });
99
+ clearTimeout(timeout);
100
+
101
+ if (resp.status >= 400) {
102
+ const text = await resp.text();
103
+ console.error(`❌ HTTP ${resp.status}`);
104
+ console.error(text.slice(0, 2000));
105
+ process.exit(3);
106
+ }
107
+
108
+ const text = await resp.text();
109
+ let data;
110
+ try {
111
+ data = JSON.parse(text);
112
+ } catch {
113
+ if (options.json) {
114
+ console.log(JSON.stringify({ raw: text.trim() }));
115
+ } else {
116
+ console.log(text.trim());
117
+ }
118
+ return;
119
+ }
120
+
121
+ if (options.json) {
122
+ console.log(JSON.stringify(data, null, 2));
123
+ } else {
124
+ prettyPrintReport(data);
125
+ }
126
+ } catch (err) {
127
+ if (err.name === 'AbortError') {
128
+ console.error('❌ Request timed out');
129
+ } else {
130
+ console.error(`❌ Request failed: ${err.message}`);
131
+ }
132
+ process.exit(2);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Interactive REPL mode
138
+ */
139
+ async function interactiveMode() {
140
+ console.log('MyVillage CLI Client Updates - Interactive Mode');
141
+ console.log("Type 'help' for commands, 'exit' to quit.\n");
142
+
143
+ const settings = {
144
+ client: null,
145
+ since: null,
146
+ url: DEFAULT_WEBHOOK_URL,
147
+ timeout: 30,
148
+ };
149
+
150
+ const rl = readline.createInterface({
151
+ input: process.stdin,
152
+ output: process.stdout,
153
+ });
154
+
155
+ const prompt = () => {
156
+ rl.question('myvillage-client-updates> ', async (input) => {
157
+ const userInput = input.trim();
158
+ if (!userInput) {
159
+ prompt();
160
+ return;
161
+ }
162
+
163
+ const parts = userInput.split(/\s+/);
164
+ const cmd = parts[0].toLowerCase();
165
+ const arg = parts.slice(1).join(' ');
166
+
167
+ switch (cmd) {
168
+ case 'exit':
169
+ case 'quit':
170
+ case 'q':
171
+ console.log('Goodbye!');
172
+ rl.close();
173
+ return;
174
+
175
+ case 'help':
176
+ console.log('Commands:');
177
+ console.log(' updates [client] - Get updates (uses current client if not specified)');
178
+ console.log(' client <name> - Set the current client');
179
+ console.log(' since <date> - Set the since date filter (e.g., 2026-02-01)');
180
+ console.log(' status - Show current settings');
181
+ console.log(' clear - Clear current settings');
182
+ console.log(' exit / quit / q - Exit the CLI');
183
+ break;
184
+
185
+ case 'client':
186
+ if (arg) {
187
+ settings.client = arg;
188
+ console.log(`Client set to: ${arg}`);
189
+ } else {
190
+ console.log(`Current client: ${settings.client || '(not set)'}`);
191
+ }
192
+ break;
193
+
194
+ case 'since':
195
+ if (arg) {
196
+ settings.since = arg;
197
+ console.log(`Since date set to: ${arg}`);
198
+ } else {
199
+ console.log(`Current since: ${settings.since || '(not set)'}`);
200
+ }
201
+ break;
202
+
203
+ case 'status':
204
+ console.log(` Client: ${settings.client || '(not set)'}`);
205
+ console.log(` Since: ${settings.since || '(not set)'}`);
206
+ console.log(` URL: ${settings.url}`);
207
+ console.log(` Timeout: ${settings.timeout}s`);
208
+ break;
209
+
210
+ case 'clear':
211
+ settings.client = null;
212
+ settings.since = null;
213
+ console.log('Settings cleared.');
214
+ break;
215
+
216
+ case 'updates':
217
+ const client = arg || settings.client;
218
+ if (!client) {
219
+ console.log("No client specified. Use 'client <name>' or 'updates <client>'");
220
+ break;
221
+ }
222
+ await getUpdates({
223
+ url: settings.url,
224
+ client,
225
+ since: settings.since,
226
+ timeout: settings.timeout,
227
+ json: false,
228
+ });
229
+ break;
230
+
231
+ default:
232
+ console.log(`Unknown command: ${cmd}. Type 'help' for available commands.`);
233
+ }
234
+
235
+ prompt();
236
+ });
237
+ };
238
+
239
+ rl.on('close', () => {
240
+ process.exit(0);
241
+ });
242
+
243
+ prompt();
244
+ }
245
+
246
+ // CLI setup with commander
247
+ program
248
+ .name('myvillage-clients-cli')
249
+ .description('MyVillage CLI - report client GitHub updates via n8n webhook')
250
+ .version('0.1.0');
251
+
252
+ program
253
+ .command('get-updates')
254
+ .description('Fetch and print client GitHub updates (via n8n webhook)')
255
+ .option('--url <url>', 'Override webhook URL', DEFAULT_WEBHOOK_URL)
256
+ .option('--client <name>', 'Client name/id used by your n8n workflow')
257
+ .option('--since <date>', 'ISO date/time filter, e.g. "2026-02-01"')
258
+ .option('--limit <number>', 'Limit number of updates returned', parseInt)
259
+ .option('--timeout <seconds>', 'Request timeout (seconds)', parseInt, 30)
260
+ .option('--json', 'Print raw JSON instead of a formatted report', false)
261
+ .action(getUpdates);
262
+
263
+ // If no arguments, run interactive mode
264
+ if (process.argv.length <= 2) {
265
+ interactiveMode();
266
+ } else {
267
+ program.parse();
268
+ }