n8n-nodes-github-copilot 3.30.1 → 3.31.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.
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from "n8n-workflow";
2
+ export declare class GitHubCopilotAuthHelper implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,575 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubCopilotAuthHelper = void 0;
4
+ class GitHubCopilotAuthHelper {
5
+ constructor() {
6
+ this.description = {
7
+ displayName: "GitHub Copilot Auth Helper",
8
+ name: "githubCopilotAuthHelper",
9
+ icon: "file:../../shared/icons/copilot.svg",
10
+ group: ["transform"],
11
+ version: 1,
12
+ description: "Interactive OAuth Device Flow authentication helper - generates HTML page for easy token generation",
13
+ defaults: {
14
+ name: "GitHub Copilot Auth Helper",
15
+ },
16
+ inputs: ["main"],
17
+ outputs: ["main"],
18
+ properties: [
19
+ {
20
+ displayName: "📋 Como Usar",
21
+ name: "instructions",
22
+ type: "notice",
23
+ default: "",
24
+ description: `
25
+ <div style="background: #e3f2fd; padding: 15px; border-left: 4px solid #2196F3; border-radius: 4px;">
26
+ <h3 style="margin-top: 0;">🎯 Este node gera uma página HTML interativa!</h3>
27
+ <ol>
28
+ <li>Execute este node</li>
29
+ <li>Copie o HTML do output</li>
30
+ <li>Salve como arquivo .html e abra no navegador</li>
31
+ <li>OU use um node "Send Email" para enviar para você</li>
32
+ <li>Siga as instruções na página para obter seu token</li>
33
+ </ol>
34
+ <p><strong>A página faz tudo automaticamente:</strong></p>
35
+ <ul>
36
+ <li>✅ Solicita device code do GitHub</li>
37
+ <li>✅ Mostra código para copiar</li>
38
+ <li>✅ Abre GitHub automaticamente</li>
39
+ <li>✅ Faz polling até você autorizar</li>
40
+ <li>✅ Exibe token pronto para copiar</li>
41
+ </ul>
42
+ </div>
43
+ `,
44
+ },
45
+ {
46
+ displayName: "Client ID",
47
+ name: "clientId",
48
+ type: "string",
49
+ default: "01ab8ac9400c4e429b23",
50
+ required: true,
51
+ description: "GitHub OAuth Client ID (VS Code official)",
52
+ },
53
+ {
54
+ displayName: "Scopes",
55
+ name: "scopes",
56
+ type: "string",
57
+ default: "repo user:email",
58
+ required: true,
59
+ description: "OAuth scopes required for GitHub Copilot",
60
+ },
61
+ {
62
+ displayName: "Output Format",
63
+ name: "outputFormat",
64
+ type: "options",
65
+ options: [
66
+ {
67
+ name: "📄 Binary File (Download Ready)",
68
+ value: "binary",
69
+ description: "HTML as binary data ready to download as .html file",
70
+ },
71
+ {
72
+ name: "📋 HTML Text + Instructions",
73
+ value: "htmlWithInstructions",
74
+ description: "HTML text with usage instructions (copy & paste)",
75
+ },
76
+ {
77
+ name: "📝 HTML Text Only",
78
+ value: "html",
79
+ description: "Just the HTML code as text",
80
+ },
81
+ ],
82
+ default: "binary",
83
+ description: "How to output the authentication page",
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ async execute() {
89
+ const items = this.getInputData();
90
+ const returnData = [];
91
+ for (let i = 0; i < items.length; i++) {
92
+ const clientId = this.getNodeParameter("clientId", i);
93
+ const scopes = this.getNodeParameter("scopes", i);
94
+ const outputFormat = this.getNodeParameter("outputFormat", i);
95
+ const html = generateAuthPage(clientId, scopes);
96
+ let output;
97
+ if (outputFormat === "binary") {
98
+ const buffer = Buffer.from(html, "utf-8");
99
+ output = {
100
+ json: {
101
+ success: true,
102
+ message: "✅ Authentication page generated successfully!",
103
+ instructions: "Download the binary file below and open it in your browser",
104
+ fileName: "github-copilot-auth.html",
105
+ fileSize: `${(buffer.length / 1024).toFixed(2)} KB`,
106
+ },
107
+ binary: {
108
+ authPage: {
109
+ data: buffer.toString("base64"),
110
+ mimeType: "text/html",
111
+ fileName: "github-copilot-auth.html",
112
+ },
113
+ },
114
+ pairedItem: i,
115
+ };
116
+ }
117
+ else {
118
+ const jsonOutput = {
119
+ html,
120
+ clientId,
121
+ scopes,
122
+ };
123
+ if (outputFormat === "htmlWithInstructions") {
124
+ jsonOutput.instructions = [
125
+ "1. Copy the HTML content from the 'html' field",
126
+ "2. Save as 'github-copilot-auth.html'",
127
+ "3. Open the file in your browser",
128
+ "4. Follow the on-screen instructions",
129
+ "5. Copy the token when it appears",
130
+ "6. Use the token in your GitHub Copilot API credential in n8n",
131
+ ].join("\n");
132
+ }
133
+ output = {
134
+ json: jsonOutput,
135
+ pairedItem: i,
136
+ };
137
+ }
138
+ returnData.push(output);
139
+ }
140
+ return [returnData];
141
+ }
142
+ }
143
+ exports.GitHubCopilotAuthHelper = GitHubCopilotAuthHelper;
144
+ function generateAuthPage(clientId, scopes) {
145
+ return `<!DOCTYPE html>
146
+ <html lang="pt-BR">
147
+ <head>
148
+ <meta charset="UTF-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
150
+ <title>GitHub Copilot Authentication</title>
151
+ <style>
152
+ * {
153
+ margin: 0;
154
+ padding: 0;
155
+ box-sizing: border-box;
156
+ }
157
+
158
+ body {
159
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
160
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
161
+ min-height: 100vh;
162
+ display: flex;
163
+ justify-content: center;
164
+ align-items: center;
165
+ padding: 20px;
166
+ }
167
+
168
+ .container {
169
+ background: white;
170
+ border-radius: 16px;
171
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
172
+ max-width: 600px;
173
+ width: 100%;
174
+ padding: 40px;
175
+ }
176
+
177
+ h1 {
178
+ color: #333;
179
+ margin-bottom: 10px;
180
+ font-size: 28px;
181
+ }
182
+
183
+ .subtitle {
184
+ color: #666;
185
+ margin-bottom: 30px;
186
+ font-size: 14px;
187
+ }
188
+
189
+ .step {
190
+ background: #f8f9fa;
191
+ border-radius: 12px;
192
+ padding: 25px;
193
+ margin-bottom: 20px;
194
+ border: 2px solid #e9ecef;
195
+ }
196
+
197
+ .step.active {
198
+ border-color: #667eea;
199
+ background: #f0f4ff;
200
+ }
201
+
202
+ .step h2 {
203
+ color: #333;
204
+ font-size: 18px;
205
+ margin-bottom: 15px;
206
+ display: flex;
207
+ align-items: center;
208
+ gap: 10px;
209
+ }
210
+
211
+ .step-number {
212
+ background: #667eea;
213
+ color: white;
214
+ width: 30px;
215
+ height: 30px;
216
+ border-radius: 50%;
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ font-size: 14px;
221
+ font-weight: bold;
222
+ }
223
+
224
+ .code-box {
225
+ background: #2d3748;
226
+ color: #fff;
227
+ padding: 20px;
228
+ border-radius: 8px;
229
+ font-size: 32px;
230
+ text-align: center;
231
+ letter-spacing: 8px;
232
+ font-family: 'Courier New', monospace;
233
+ margin: 15px 0;
234
+ user-select: all;
235
+ cursor: pointer;
236
+ transition: all 0.3s;
237
+ }
238
+
239
+ .code-box:hover {
240
+ background: #1a202c;
241
+ transform: scale(1.02);
242
+ }
243
+
244
+ .btn {
245
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
246
+ color: white;
247
+ padding: 15px 30px;
248
+ border: none;
249
+ border-radius: 8px;
250
+ font-size: 16px;
251
+ font-weight: 600;
252
+ cursor: pointer;
253
+ width: 100%;
254
+ transition: all 0.3s;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ gap: 10px;
259
+ }
260
+
261
+ .btn:hover {
262
+ transform: translateY(-2px);
263
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
264
+ }
265
+
266
+ .btn:disabled {
267
+ opacity: 0.6;
268
+ cursor: not-allowed;
269
+ }
270
+
271
+ .status {
272
+ text-align: center;
273
+ margin-top: 30px;
274
+ padding: 20px;
275
+ background: #f8f9fa;
276
+ border-radius: 8px;
277
+ }
278
+
279
+ .status h3 {
280
+ color: #333;
281
+ margin-bottom: 15px;
282
+ font-size: 16px;
283
+ }
284
+
285
+ .spinner {
286
+ border: 4px solid #f3f3f3;
287
+ border-top: 4px solid #667eea;
288
+ border-radius: 50%;
289
+ width: 40px;
290
+ height: 40px;
291
+ animation: spin 1s linear infinite;
292
+ margin: 0 auto;
293
+ }
294
+
295
+ @keyframes spin {
296
+ 0% { transform: rotate(0deg); }
297
+ 100% { transform: rotate(360deg); }
298
+ }
299
+
300
+ .success-box {
301
+ background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
302
+ padding: 30px;
303
+ border-radius: 12px;
304
+ margin-top: 20px;
305
+ }
306
+
307
+ .success-box h2 {
308
+ color: #155724;
309
+ margin-bottom: 15px;
310
+ }
311
+
312
+ .token-display {
313
+ background: white;
314
+ padding: 15px;
315
+ border-radius: 8px;
316
+ word-break: break-all;
317
+ font-family: 'Courier New', monospace;
318
+ font-size: 14px;
319
+ color: #333;
320
+ margin: 15px 0;
321
+ border: 2px solid #28a745;
322
+ max-height: 150px;
323
+ overflow-y: auto;
324
+ }
325
+
326
+ .error-box {
327
+ background: #f8d7da;
328
+ color: #721c24;
329
+ padding: 20px;
330
+ border-radius: 8px;
331
+ margin-top: 20px;
332
+ border: 2px solid #f5c6cb;
333
+ }
334
+
335
+ .hidden {
336
+ display: none;
337
+ }
338
+
339
+ .info-text {
340
+ color: #666;
341
+ font-size: 14px;
342
+ margin-top: 10px;
343
+ line-height: 1.6;
344
+ }
345
+ </style>
346
+ </head>
347
+ <body>
348
+ <div class="container">
349
+ <h1>🚀 GitHub Copilot Authentication</h1>
350
+ <p class="subtitle">OAuth Device Flow - Obtenha seu token de autenticação</p>
351
+
352
+ <div id="step1" class="step active">
353
+ <h2><span class="step-number">1</span> Iniciar Autenticação</h2>
354
+ <button class="btn" onclick="startDeviceFlow()">
355
+ <span>▶️</span>
356
+ <span>Começar</span>
357
+ </button>
358
+ <p class="info-text">
359
+ Clique para solicitar um código de autorização do GitHub
360
+ </p>
361
+ </div>
362
+
363
+ <div id="step2" class="step hidden">
364
+ <h2><span class="step-number">2</span> Copie o Código</h2>
365
+ <div class="code-box" id="userCode" onclick="copyCode()">
366
+ Carregando...
367
+ </div>
368
+ <button class="btn" onclick="copyCode()">
369
+ <span>📋</span>
370
+ <span>Copiar Código</span>
371
+ </button>
372
+ <p class="info-text">
373
+ Clique no código ou no botão para copiar
374
+ </p>
375
+ </div>
376
+
377
+ <div id="step3" class="step hidden">
378
+ <h2><span class="step-number">3</span> Autorize no GitHub</h2>
379
+ <button class="btn" onclick="openGitHub()">
380
+ <span>🌐</span>
381
+ <span>Abrir GitHub</span>
382
+ </button>
383
+ <p class="info-text">
384
+ Cole o código copiado e autorize a aplicação
385
+ </p>
386
+ </div>
387
+
388
+ <div id="statusSection" class="status hidden">
389
+ <h3>Status: <span id="statusText">Aguardando...</span></h3>
390
+ <div id="spinner" class="spinner"></div>
391
+ </div>
392
+
393
+ <div id="successSection" class="success-box hidden">
394
+ <h2>✅ Token Obtido com Sucesso!</h2>
395
+ <p>Copie o token abaixo e use na credencial n8n:</p>
396
+ <div class="token-display" id="tokenDisplay"></div>
397
+ <button class="btn" onclick="copyToken()">
398
+ <span>📋</span>
399
+ <span>Copiar Token</span>
400
+ </button>
401
+ <p class="info-text" style="color: #155724; margin-top: 15px;">
402
+ ✨ Cole este token na credencial "GitHub Copilot OAuth2 (with Helper)" no n8n
403
+ </p>
404
+ </div>
405
+
406
+ <div id="errorSection" class="error-box hidden">
407
+ <h3>❌ Erro</h3>
408
+ <p id="errorText"></p>
409
+ </div>
410
+ </div>
411
+
412
+ <script>
413
+ const CLIENT_ID = "${clientId}";
414
+ const SCOPES = "${scopes}";
415
+ const DEVICE_CODE_URL = "https://github.com/login/device/code";
416
+ const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
417
+
418
+ let deviceCode = "";
419
+ let userCode = "";
420
+ let verificationUri = "";
421
+ let accessToken = "";
422
+
423
+ async function startDeviceFlow() {
424
+ try {
425
+ document.getElementById("step1").querySelector(".btn").disabled = true;
426
+ document.getElementById("step1").querySelector(".btn").innerHTML = '<span>⏳</span><span>Solicitando...</span>';
427
+
428
+ // Request device code
429
+ const response = await fetch(DEVICE_CODE_URL, {
430
+ method: "POST",
431
+ headers: {
432
+ "Accept": "application/json",
433
+ "Content-Type": "application/x-www-form-urlencoded",
434
+ },
435
+ body: new URLSearchParams({
436
+ client_id: CLIENT_ID,
437
+ scope: SCOPES
438
+ })
439
+ });
440
+
441
+ if (!response.ok) {
442
+ throw new Error(\`Erro ao solicitar device code: \${response.status}\`);
443
+ }
444
+
445
+ const data = await response.json();
446
+ deviceCode = data.device_code;
447
+ userCode = data.user_code;
448
+ verificationUri = data.verification_uri_complete || data.verification_uri;
449
+
450
+ // Show step 2
451
+ document.getElementById("step1").classList.remove("active");
452
+ document.getElementById("step2").classList.remove("hidden");
453
+ document.getElementById("step2").classList.add("active");
454
+ document.getElementById("userCode").textContent = userCode;
455
+
456
+ // Show step 3
457
+ document.getElementById("step3").classList.remove("hidden");
458
+ document.getElementById("step3").classList.add("active");
459
+
460
+ } catch (error) {
461
+ showError(error.message);
462
+ }
463
+ }
464
+
465
+ function copyCode() {
466
+ navigator.clipboard.writeText(userCode);
467
+ const btn = document.getElementById("step2").querySelector(".btn");
468
+ const originalText = btn.innerHTML;
469
+ btn.innerHTML = '<span>✅</span><span>Copiado!</span>';
470
+ setTimeout(() => {
471
+ btn.innerHTML = originalText;
472
+ }, 2000);
473
+ }
474
+
475
+ function openGitHub() {
476
+ window.open(verificationUri, "_blank");
477
+ startPolling();
478
+ }
479
+
480
+ async function startPolling() {
481
+ document.getElementById("statusSection").classList.remove("hidden");
482
+ document.getElementById("statusText").textContent = "Verificando autorização...";
483
+
484
+ const maxAttempts = 180; // 15 minutes
485
+ let interval = 5000; // 5 seconds
486
+
487
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
488
+ await sleep(interval);
489
+
490
+ document.getElementById("statusText").textContent = \`Verificando... (tentativa \${attempt}/\${maxAttempts})\`;
491
+
492
+ try {
493
+ const response = await fetch(ACCESS_TOKEN_URL, {
494
+ method: "POST",
495
+ headers: {
496
+ "Accept": "application/json",
497
+ "Content-Type": "application/x-www-form-urlencoded",
498
+ },
499
+ body: new URLSearchParams({
500
+ client_id: CLIENT_ID,
501
+ device_code: deviceCode,
502
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
503
+ })
504
+ });
505
+
506
+ const data = await response.json();
507
+
508
+ if (data.access_token) {
509
+ accessToken = data.access_token;
510
+ showSuccess();
511
+ return;
512
+ }
513
+
514
+ if (data.error === "authorization_pending") {
515
+ continue;
516
+ }
517
+
518
+ if (data.error === "slow_down") {
519
+ interval += 5000; // Increase interval by 5 seconds
520
+ continue;
521
+ }
522
+
523
+ if (data.error === "expired_token") {
524
+ throw new Error("Código expirado. Por favor, recomece o processo.");
525
+ }
526
+
527
+ if (data.error === "access_denied") {
528
+ throw new Error("Autorização negada pelo usuário.");
529
+ }
530
+
531
+ if (data.error) {
532
+ throw new Error(\`Erro OAuth: \${data.error}\`);
533
+ }
534
+
535
+ } catch (error) {
536
+ showError(error.message);
537
+ return;
538
+ }
539
+ }
540
+
541
+ showError("Tempo esgotado. A autorização demorou muito. Por favor, tente novamente.");
542
+ }
543
+
544
+ function showSuccess() {
545
+ document.getElementById("statusSection").classList.add("hidden");
546
+ document.getElementById("successSection").classList.remove("hidden");
547
+ document.getElementById("tokenDisplay").textContent = accessToken;
548
+
549
+ // Scroll to success section
550
+ document.getElementById("successSection").scrollIntoView({ behavior: "smooth" });
551
+ }
552
+
553
+ function showError(message) {
554
+ document.getElementById("statusSection").classList.add("hidden");
555
+ document.getElementById("errorSection").classList.remove("hidden");
556
+ document.getElementById("errorText").textContent = message;
557
+ }
558
+
559
+ function copyToken() {
560
+ navigator.clipboard.writeText(accessToken);
561
+ const btn = document.getElementById("successSection").querySelector(".btn");
562
+ const originalText = btn.innerHTML;
563
+ btn.innerHTML = '<span>✅</span><span>Token Copiado!</span>';
564
+ setTimeout(() => {
565
+ btn.innerHTML = originalText;
566
+ }, 2000);
567
+ }
568
+
569
+ function sleep(ms) {
570
+ return new Promise(resolve => setTimeout(resolve, ms));
571
+ }
572
+ </script>
573
+ </body>
574
+ </html>`;
575
+ }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-github-copilot",
3
- "version": "3.30.1",
3
+ "version": "3.31.1",
4
4
  "description": "n8n community node for GitHub Copilot with CLI integration, Chat API access, and AI Chat Model for workflows - access GPT-5, Claude, Gemini and more using your Copilot subscription",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/sufficit/n8n-nodes-github-copilot",
@@ -26,12 +26,11 @@
26
26
  "n8n": {
27
27
  "n8nNodesApiVersion": 1,
28
28
  "credentials": [
29
- "dist/credentials/GitHubCopilotApi.credentials.js",
30
- "dist/credentials/GitHubCopilotOAuth2Api.credentials.js",
31
- "dist/credentials/GitHubCopilotOAuth2Api.credentials.oauth.js"
29
+ "dist/credentials/GitHubCopilotApi.credentials.js"
32
30
  ],
33
31
  "nodes": [
34
32
  "dist/nodes/GitHubCopilot/GitHubCopilot.node.js",
33
+ "dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.js",
35
34
  "dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js",
36
35
  "dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js",
37
36
  "dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js",
@@ -0,0 +1,42 @@
1
+ interface DeviceCodeResponse {
2
+ device_code: string;
3
+ user_code: string;
4
+ verification_uri: string;
5
+ verification_uri_complete?: string;
6
+ expires_in: number;
7
+ interval: number;
8
+ }
9
+ interface CopilotTokenResponse {
10
+ token: string;
11
+ expires_at: number;
12
+ refresh_in: number;
13
+ sku?: string;
14
+ chat_enabled?: boolean;
15
+ }
16
+ export declare function requestDeviceCode(clientId: string, scopes: string, deviceCodeUrl: string): Promise<DeviceCodeResponse>;
17
+ export declare function pollForAccessToken(clientId: string, deviceCode: string, accessTokenUrl: string, interval?: number, maxAttempts?: number): Promise<string>;
18
+ export declare function convertToCopilotToken(githubToken: string, copilotTokenUrl: string): Promise<CopilotTokenResponse>;
19
+ export declare function executeDeviceFlow(clientId: string, scopes: string, deviceCodeUrl: string, accessTokenUrl: string, copilotTokenUrl: string, onProgress?: (status: DeviceFlowStatus) => void): Promise<DeviceFlowResult>;
20
+ export interface DeviceFlowStatus {
21
+ step: number;
22
+ status: "requesting_device_code" | "awaiting_authorization" | "token_obtained" | "complete" | "error";
23
+ message: string;
24
+ deviceData?: {
25
+ userCode: string;
26
+ verificationUri: string;
27
+ verificationUriComplete?: string;
28
+ expiresIn: number;
29
+ };
30
+ }
31
+ export interface DeviceFlowResult {
32
+ success: boolean;
33
+ accessToken?: string;
34
+ expiresAt?: Date;
35
+ metadata?: {
36
+ sku?: string;
37
+ chatEnabled?: boolean;
38
+ refreshIn?: number;
39
+ };
40
+ error?: string;
41
+ }
42
+ export {};