geeto 0.6.6 → 0.9.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/README.md +23 -9
- package/lib/api/copilot-adapter.d.ts +14 -5
- package/lib/api/copilot-adapter.d.ts.map +1 -1
- package/lib/api/copilot-adapter.js +15 -21
- package/lib/api/copilot-adapter.js.map +1 -1
- package/lib/api/copilot-sdk.d.ts +3 -16
- package/lib/api/copilot-sdk.d.ts.map +1 -1
- package/lib/api/copilot-sdk.js +186 -454
- package/lib/api/copilot-sdk.js.map +1 -1
- package/lib/api/copilot.d.ts +3 -4
- package/lib/api/copilot.d.ts.map +1 -1
- package/lib/api/copilot.js +28 -28
- package/lib/api/copilot.js.map +1 -1
- package/lib/api/gemini-sdk.d.ts.map +1 -1
- package/lib/api/gemini-sdk.js +11 -77
- package/lib/api/gemini-sdk.js.map +1 -1
- package/lib/api/gemini.d.ts +2 -2
- package/lib/api/gemini.d.ts.map +1 -1
- package/lib/api/gemini.js +24 -19
- package/lib/api/gemini.js.map +1 -1
- package/lib/api/gitlab.d.ts +80 -0
- package/lib/api/gitlab.d.ts.map +1 -0
- package/lib/api/gitlab.js +192 -0
- package/lib/api/gitlab.js.map +1 -0
- package/lib/api/openrouter-sdk.d.ts.map +1 -1
- package/lib/api/openrouter-sdk.js +11 -76
- package/lib/api/openrouter-sdk.js.map +1 -1
- package/lib/api/openrouter.d.ts.map +1 -1
- package/lib/api/openrouter.js +2 -16
- package/lib/api/openrouter.js.map +1 -1
- package/lib/api/platform.d.ts +78 -0
- package/lib/api/platform.d.ts.map +1 -0
- package/lib/api/platform.js +218 -0
- package/lib/api/platform.js.map +1 -0
- package/lib/cli/input.d.ts +2 -2
- package/lib/cli/input.d.ts.map +1 -1
- package/lib/cli/input.js +23 -27
- package/lib/cli/input.js.map +1 -1
- package/lib/cli/menu.d.ts +1 -1
- package/lib/cli/menu.d.ts.map +1 -1
- package/lib/cli/menu.js +123 -100
- package/lib/cli/menu.js.map +1 -1
- package/lib/core/copilot-setup.d.ts +9 -8
- package/lib/core/copilot-setup.d.ts.map +1 -1
- package/lib/core/copilot-setup.js +81 -264
- package/lib/core/copilot-setup.js.map +1 -1
- package/lib/core/gemini-setup.js +7 -7
- package/lib/core/gemini-setup.js.map +1 -1
- package/lib/core/gitlab-setup.d.ts +5 -0
- package/lib/core/gitlab-setup.d.ts.map +1 -0
- package/lib/core/gitlab-setup.js +85 -0
- package/lib/core/gitlab-setup.js.map +1 -0
- package/lib/core/openrouter-setup.d.ts.map +1 -1
- package/lib/core/openrouter-setup.js +17 -0
- package/lib/core/openrouter-setup.js.map +1 -1
- package/lib/index.js +518 -704
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +10 -6
- package/lib/types/index.d.ts.map +1 -1
- package/lib/utils/ai-provider-helpers.d.ts +5 -0
- package/lib/utils/ai-provider-helpers.d.ts.map +1 -0
- package/lib/utils/ai-provider-helpers.js +23 -0
- package/lib/utils/ai-provider-helpers.js.map +1 -0
- package/lib/utils/ai-text.d.ts +23 -0
- package/lib/utils/ai-text.d.ts.map +1 -0
- package/lib/utils/ai-text.js +57 -0
- package/lib/utils/ai-text.js.map +1 -0
- package/lib/utils/ai-workflow.d.ts +18 -0
- package/lib/utils/ai-workflow.d.ts.map +1 -0
- package/lib/utils/ai-workflow.js +66 -0
- package/lib/utils/ai-workflow.js.map +1 -0
- package/lib/utils/branch-naming.d.ts.map +1 -1
- package/lib/utils/branch-naming.js +1 -3
- package/lib/utils/branch-naming.js.map +1 -1
- package/lib/utils/config.d.ts +13 -1
- package/lib/utils/config.d.ts.map +1 -1
- package/lib/utils/config.js +38 -1
- package/lib/utils/config.js.map +1 -1
- package/lib/utils/display.d.ts.map +1 -1
- package/lib/utils/display.js +4 -3
- package/lib/utils/display.js.map +1 -1
- package/lib/utils/exec.d.ts.map +1 -1
- package/lib/utils/exec.js +10 -2
- package/lib/utils/exec.js.map +1 -1
- package/lib/utils/git-ai.js +13 -13
- package/lib/utils/git-ai.js.map +1 -1
- package/lib/utils/git-errors.d.ts.map +1 -1
- package/lib/utils/git-errors.js +2 -6
- package/lib/utils/git-errors.js.map +1 -1
- package/lib/utils/git.d.ts.map +1 -1
- package/lib/utils/git.js +5 -0
- package/lib/utils/git.js.map +1 -1
- package/lib/utils/github-helpers.d.ts +33 -0
- package/lib/utils/github-helpers.d.ts.map +1 -0
- package/lib/utils/github-helpers.js +101 -0
- package/lib/utils/github-helpers.js.map +1 -0
- package/lib/utils/prompt-loader.d.ts +9 -0
- package/lib/utils/prompt-loader.d.ts.map +1 -0
- package/lib/utils/prompt-loader.js +42 -0
- package/lib/utils/prompt-loader.js.map +1 -0
- package/lib/utils/prompts-embedded.d.ts +2 -0
- package/lib/utils/prompts-embedded.d.ts.map +1 -0
- package/lib/utils/prompts-embedded.js +255 -0
- package/lib/utils/prompts-embedded.js.map +1 -0
- package/lib/utils/scramble.d.ts +9 -86
- package/lib/utils/scramble.d.ts.map +1 -1
- package/lib/utils/scramble.js +27 -279
- package/lib/utils/scramble.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/workflows/alias.d.ts.map +1 -1
- package/lib/workflows/alias.js +1 -0
- package/lib/workflows/alias.js.map +1 -1
- package/lib/workflows/amend.d.ts.map +1 -1
- package/lib/workflows/amend.js +1 -5
- package/lib/workflows/amend.js.map +1 -1
- package/lib/workflows/branch-helpers.d.ts.map +1 -1
- package/lib/workflows/branch-helpers.js +0 -1
- package/lib/workflows/branch-helpers.js.map +1 -1
- package/lib/workflows/commit.d.ts.map +1 -1
- package/lib/workflows/commit.js +160 -187
- package/lib/workflows/commit.js.map +1 -1
- package/lib/workflows/doctor.d.ts +7 -0
- package/lib/workflows/doctor.d.ts.map +1 -0
- package/lib/workflows/doctor.js +284 -0
- package/lib/workflows/doctor.js.map +1 -0
- package/lib/workflows/issue.d.ts +1 -1
- package/lib/workflows/issue.d.ts.map +1 -1
- package/lib/workflows/issue.js +28 -115
- package/lib/workflows/issue.js.map +1 -1
- package/lib/workflows/main-helpers.d.ts +34 -0
- package/lib/workflows/main-helpers.d.ts.map +1 -0
- package/lib/workflows/main-helpers.js +346 -0
- package/lib/workflows/main-helpers.js.map +1 -0
- package/lib/workflows/main-steps.d.ts.map +1 -1
- package/lib/workflows/main-steps.js +9 -134
- package/lib/workflows/main-steps.js.map +1 -1
- package/lib/workflows/main.d.ts +2 -6
- package/lib/workflows/main.d.ts.map +1 -1
- package/lib/workflows/main.js +44 -381
- package/lib/workflows/main.js.map +1 -1
- package/lib/workflows/pr.d.ts +2 -2
- package/lib/workflows/pr.d.ts.map +1 -1
- package/lib/workflows/pr.js +49 -137
- package/lib/workflows/pr.js.map +1 -1
- package/lib/workflows/prune.d.ts.map +1 -1
- package/lib/workflows/prune.js +2 -10
- package/lib/workflows/prune.js.map +1 -1
- package/lib/workflows/pull.d.ts.map +1 -1
- package/lib/workflows/pull.js +2 -24
- package/lib/workflows/pull.js.map +1 -1
- package/lib/workflows/release-merge.d.ts +12 -0
- package/lib/workflows/release-merge.d.ts.map +1 -0
- package/lib/workflows/release-merge.js +593 -0
- package/lib/workflows/release-merge.js.map +1 -0
- package/lib/workflows/release-notes.d.ts +13 -0
- package/lib/workflows/release-notes.d.ts.map +1 -0
- package/lib/workflows/release-notes.js +141 -0
- package/lib/workflows/release-notes.js.map +1 -0
- package/lib/workflows/release-recover.d.ts +5 -0
- package/lib/workflows/release-recover.d.ts.map +1 -0
- package/lib/workflows/release-recover.js +137 -0
- package/lib/workflows/release-recover.js.map +1 -0
- package/lib/workflows/release-sync.d.ts +7 -0
- package/lib/workflows/release-sync.d.ts.map +1 -0
- package/lib/workflows/release-sync.js +378 -0
- package/lib/workflows/release-sync.js.map +1 -0
- package/lib/workflows/release-utils.d.ts +36 -0
- package/lib/workflows/release-utils.d.ts.map +1 -0
- package/lib/workflows/release-utils.js +150 -0
- package/lib/workflows/release-utils.js.map +1 -0
- package/lib/workflows/release.d.ts.map +1 -1
- package/lib/workflows/release.js +92 -719
- package/lib/workflows/release.js.map +1 -1
- package/lib/workflows/repo-settings.d.ts +2 -2
- package/lib/workflows/repo-settings.d.ts.map +1 -1
- package/lib/workflows/repo-settings.js +33 -24
- package/lib/workflows/repo-settings.js.map +1 -1
- package/lib/workflows/reword.d.ts.map +1 -1
- package/lib/workflows/reword.js +154 -151
- package/lib/workflows/reword.js.map +1 -1
- package/lib/workflows/security-gate.d.ts.map +1 -1
- package/lib/workflows/security-gate.js +15 -75
- package/lib/workflows/security-gate.js.map +1 -1
- package/lib/workflows/settings.d.ts +3 -1
- package/lib/workflows/settings.d.ts.map +1 -1
- package/lib/workflows/settings.js +319 -19
- package/lib/workflows/settings.js.map +1 -1
- package/lib/workflows/submodules.d.ts +6 -0
- package/lib/workflows/submodules.d.ts.map +1 -0
- package/lib/workflows/submodules.js +344 -0
- package/lib/workflows/submodules.js.map +1 -0
- package/lib/workflows/trello-menu.d.ts +2 -5
- package/lib/workflows/trello-menu.d.ts.map +1 -1
- package/lib/workflows/trello-menu.js +67 -228
- package/lib/workflows/trello-menu.js.map +1 -1
- package/package.json +4 -6
- package/prompts/branch-name-prompt.md +4 -0
- package/prompts/commit-message-prompt.md +12 -0
- package/prompts/issue-prompt.md +19 -0
- package/prompts/issue-review.with-context.prompt.yml +77 -0
- package/prompts/pr-prompt.md +14 -0
- package/prompts/release-notes-prompt.md +35 -0
- package/prompts/repo-description-prompt.md +1 -0
- package/prompts/security-gate-prompt.md +80 -0
package/lib/api/copilot-sdk.js
CHANGED
|
@@ -1,426 +1,211 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { CopilotClient } from '@github/copilot-sdk';
|
|
8
|
-
import { exec } from '../utils/exec.js';
|
|
9
|
-
import { log } from '../utils/logging.js';
|
|
10
|
-
// Copilot SDK wrapper (lazy-load, optional)
|
|
11
|
-
// Minimum Copilot CLI version required for SDK compatibility
|
|
12
|
-
export const MIN_COPILOT_VERSION = '0.0.400';
|
|
13
|
-
// Cache file path for storing copilot binary info
|
|
14
|
-
const CACHE_FILE = path.join(os.homedir(), '.cache', 'geeto', 'copilot-bin.json');
|
|
15
|
-
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
16
4
|
/**
|
|
17
|
-
*
|
|
5
|
+
* Copilot AI provider — direct REST API client.
|
|
6
|
+
*
|
|
7
|
+
* Uses GitHub Copilot Chat Completions API (api.individual.githubcopilot.com)
|
|
8
|
+
* instead of the @github/copilot-sdk. This removes the dependency on the
|
|
9
|
+
* Copilot CLI binary and avoids protocol version mismatch issues.
|
|
10
|
+
*
|
|
11
|
+
* Auth: uses `gh auth token` (GitHub CLI) or GITHUB_TOKEN env var.
|
|
18
12
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
import { buildPromptWithCorrection, buildReleaseNotesPrompt, cleanAIContent, MIN_AI_RESPONSE_LENGTH, normalizeBranchName, } from '../utils/ai-text.js';
|
|
14
|
+
import { exec } from '../utils/exec.js';
|
|
15
|
+
import { log } from '../utils/logging.js';
|
|
16
|
+
// ── API Configuration ───────────────────────────────────────────────────
|
|
17
|
+
const COPILOT_API_BASE = 'https://api.individual.githubcopilot.com';
|
|
18
|
+
const CHAT_ENDPOINT = `${COPILOT_API_BASE}/chat/completions`;
|
|
19
|
+
const MODELS_ENDPOINT = `${COPILOT_API_BASE}/models`;
|
|
20
|
+
// Required by the Copilot API — requests without these headers get 403
|
|
21
|
+
const COPILOT_HEADERS = {
|
|
22
|
+
'Copilot-Integration-Id': 'vscode-chat',
|
|
23
|
+
'Editor-Version': 'vscode/1.96.0',
|
|
24
|
+
'Editor-Plugin-Version': 'copilot-chat/0.24.0',
|
|
25
|
+
'User-Agent': 'GitHubCopilotChat/0.24.0',
|
|
26
|
+
'Openai-Intent': 'conversation-panel',
|
|
23
27
|
};
|
|
28
|
+
// ── Token Management ────────────────────────────────────────────────────
|
|
29
|
+
let cachedToken = null;
|
|
24
30
|
/**
|
|
25
|
-
*
|
|
31
|
+
* Get GitHub auth token for Copilot API access.
|
|
32
|
+
* Priority: 1) cached token, 2) GITHUB_TOKEN env, 3) `gh auth token`
|
|
26
33
|
*/
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
34
|
+
const getToken = () => {
|
|
35
|
+
if (cachedToken)
|
|
36
|
+
return cachedToken;
|
|
37
|
+
// Try env vars first (fast path)
|
|
38
|
+
const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null;
|
|
39
|
+
if (envToken) {
|
|
40
|
+
cachedToken = envToken;
|
|
41
|
+
return envToken;
|
|
36
42
|
}
|
|
37
|
-
|
|
38
|
-
// ignore
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Write copilot binary info to cache file.
|
|
44
|
-
*/
|
|
45
|
-
const writeCache = (info) => {
|
|
43
|
+
// Fall back to gh CLI
|
|
46
44
|
try {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
|
|
45
|
+
const token = exec('gh auth token', true).trim();
|
|
46
|
+
if (token) {
|
|
47
|
+
cachedToken = token;
|
|
48
|
+
return token;
|
|
50
49
|
}
|
|
51
|
-
fs.writeFileSync(CACHE_FILE, JSON.stringify({ ...info, timestamp: Date.now() }));
|
|
52
50
|
}
|
|
53
51
|
catch {
|
|
54
|
-
//
|
|
52
|
+
// gh CLI not available or not authenticated
|
|
55
53
|
}
|
|
54
|
+
return null;
|
|
56
55
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const verOut = exec(`"${binPath}" --version`, true);
|
|
63
|
-
const m = verOut.match(/(\d+\.\d+\.\d+)/);
|
|
64
|
-
return m?.[1] ?? null;
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
56
|
+
const chatCompletion = async (messages, model) => {
|
|
57
|
+
const token = getToken();
|
|
58
|
+
if (!token) {
|
|
59
|
+
log.warn('No GitHub token available. Run `gh auth login` to authenticate.');
|
|
67
60
|
return null;
|
|
68
61
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
62
|
+
const body = {
|
|
63
|
+
model: model ?? 'gpt-5-mini',
|
|
64
|
+
messages,
|
|
65
|
+
};
|
|
66
|
+
const maxRetries = 2;
|
|
67
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(CHAT_ENDPOINT, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
...COPILOT_HEADERS,
|
|
73
|
+
'Authorization': `Bearer ${token}`,
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'Accept': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const errorText = await res.text().catch(() => '');
|
|
81
|
+
// Retry on 403 (Copilot intermittent rate-limit)
|
|
82
|
+
if (res.status === 403 && attempt < maxRetries) {
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (res.status === 401 || res.status === 403) {
|
|
87
|
+
cachedToken = null;
|
|
88
|
+
log.clearLine();
|
|
89
|
+
log.warn('GitHub token expired or unauthorized. Run `gh auth login` to re-authenticate.');
|
|
95
90
|
}
|
|
91
|
+
else {
|
|
92
|
+
log.clearLine();
|
|
93
|
+
log.warn(`Copilot API error (${res.status}): ${errorText.slice(0, 200)}`);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
96
|
}
|
|
97
|
+
const data = (await res.json());
|
|
98
|
+
const content = data.choices?.[0]?.message?.content;
|
|
99
|
+
return content ?? null;
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// PATH version is outdated or not found - scan known locations
|
|
103
|
-
const home = os.homedir();
|
|
104
|
-
const knownPaths = isWin
|
|
105
|
-
? [
|
|
106
|
-
// Windows install locations (matches copilot-setup.ts)
|
|
107
|
-
path.join(process.env.LOCALAPPDATA ?? '', 'Programs', 'GitHub CLI', copilotBin),
|
|
108
|
-
'C:\\Program Files\\GitHub CLI\\' + copilotBin,
|
|
109
|
-
'C:\\Program Files (x86)\\GitHub CLI\\' + copilotBin,
|
|
110
|
-
path.join(home, 'AppData', 'Roaming', 'npm', copilotBin),
|
|
111
|
-
path.join(home, 'scoop', 'shims', copilotBin),
|
|
112
|
-
path.join(home, '.config', 'Code', 'User', 'globalStorage', 'github.copilot-chat', 'copilotCli', copilotBin),
|
|
113
|
-
]
|
|
114
|
-
: [
|
|
115
|
-
// macOS / Linux install locations (matches copilot-setup.ts)
|
|
116
|
-
'/opt/homebrew/bin/copilot',
|
|
117
|
-
'/usr/local/bin/copilot',
|
|
118
|
-
'/usr/bin/copilot',
|
|
119
|
-
path.join(home, '.config/Code/User/globalStorage/github.copilot-chat/copilotCli/copilot'),
|
|
120
|
-
path.join(home, '.npm-global/bin/copilot'),
|
|
121
|
-
path.join(home, '.local/bin/copilot'),
|
|
122
|
-
];
|
|
123
|
-
for (const binPath of knownPaths) {
|
|
124
|
-
if (fs.existsSync(binPath)) {
|
|
125
|
-
const ver = getVersionFromPath(binPath);
|
|
126
|
-
if (ver) {
|
|
127
|
-
const num = parseParts(ver);
|
|
128
|
-
if (num >= minNum) {
|
|
129
|
-
const result = { path: binPath, version: ver };
|
|
130
|
-
writeCache(result);
|
|
131
|
-
return result;
|
|
132
|
-
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (attempt < maxRetries) {
|
|
103
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
104
|
+
continue;
|
|
133
105
|
}
|
|
106
|
+
log.clearLine();
|
|
107
|
+
log.error('Copilot API request failed: ' + String(error));
|
|
108
|
+
return null;
|
|
134
109
|
}
|
|
135
110
|
}
|
|
136
111
|
return null;
|
|
137
112
|
};
|
|
113
|
+
// ── Public API (same exports as before) ─────────────────────────────────
|
|
138
114
|
/**
|
|
139
|
-
* Check if Copilot
|
|
140
|
-
* The standalone CLI is used as fallback when the bundled CLI is unavailable
|
|
141
|
-
* (e.g. in bun-compiled binaries). Also used for setup guidance.
|
|
115
|
+
* Check if Copilot API is accessible (has valid token).
|
|
142
116
|
*/
|
|
143
|
-
export const
|
|
144
|
-
const
|
|
145
|
-
if (!
|
|
117
|
+
export const isAvailable = async () => {
|
|
118
|
+
const token = getToken();
|
|
119
|
+
if (!token)
|
|
120
|
+
return false;
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(MODELS_ENDPOINT, {
|
|
123
|
+
method: 'GET',
|
|
124
|
+
headers: { ...COPILOT_HEADERS, Authorization: `Bearer ${token}` },
|
|
125
|
+
});
|
|
126
|
+
return res.ok;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
146
129
|
return false;
|
|
147
130
|
}
|
|
148
|
-
return parseParts(best.version) >= parseParts(MIN_COPILOT_VERSION);
|
|
149
131
|
};
|
|
150
|
-
let client = null;
|
|
151
132
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* Resolution order:
|
|
155
|
-
* 1. Bundled CLI – SDK resolves @github/copilot from node_modules (npm/bun install).
|
|
156
|
-
* 2. System CLI – Standalone Copilot CLI binary found on PATH or known locations
|
|
157
|
-
* (used when bundled CLI is unavailable, e.g. Bun-compiled binary).
|
|
133
|
+
* No-op for backwards compatibility. REST API is stateless.
|
|
158
134
|
*/
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
// Suppress Node.js experimental warnings from copilot subprocess
|
|
164
|
-
process.env.NODE_NO_WARNINGS = '1';
|
|
165
|
-
// ── Attempt 1: bundled CLI (default SDK behaviour) ────────────────────
|
|
166
|
-
try {
|
|
167
|
-
client = new CopilotClient({ autoStart: true });
|
|
168
|
-
await client.start();
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
catch (bundledError) {
|
|
172
|
-
const bundledMsg = bundledError instanceof Error ? bundledError.message : String(bundledError);
|
|
173
|
-
// Only fall through to system CLI when the bundled CLI cannot be resolved
|
|
174
|
-
// (e.g. running inside a bun-compiled binary where node_modules don't exist).
|
|
175
|
-
const isMissingModule = bundledMsg.includes('Cannot find module') ||
|
|
176
|
-
bundledMsg.includes('ResolveMessage') ||
|
|
177
|
-
bundledMsg.includes('ENOENT');
|
|
178
|
-
if (!isMissingModule) {
|
|
179
|
-
// Not a resolution error — report and bail out
|
|
180
|
-
log.clearLine();
|
|
181
|
-
log.gap();
|
|
182
|
-
if (bundledMsg.includes('headless') || bundledMsg.includes('Unknown flag')) {
|
|
183
|
-
log.info('Copilot SDK: bundled CLI does not support --headless (Bun compat issue).');
|
|
184
|
-
log.info('Upgrade SDK: bun add @github/copilot-sdk@latest');
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
log.info(`Copilot SDK unavailable: ${bundledMsg}`);
|
|
188
|
-
}
|
|
189
|
-
log.gap();
|
|
190
|
-
client = null;
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
// ── Attempt 2: system Copilot CLI (standalone native binary) ────────
|
|
194
|
-
const systemCli = findBestCopilotBinary();
|
|
195
|
-
if (!systemCli) {
|
|
196
|
-
log.clearLine();
|
|
197
|
-
log.gap();
|
|
198
|
-
log.info('Copilot SDK: bundled CLI not found and no system Copilot CLI available.');
|
|
199
|
-
log.info('Install Copilot CLI: brew install copilot-cli');
|
|
200
|
-
log.gap();
|
|
201
|
-
client = null;
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
log.clearLine();
|
|
206
|
-
log.gap();
|
|
207
|
-
log.info(`Using system Copilot CLI (v${systemCli.version})`);
|
|
208
|
-
log.gap();
|
|
209
|
-
client = new CopilotClient({ cliPath: systemCli.path, autoStart: true });
|
|
210
|
-
await client.start();
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
catch (systemError) {
|
|
214
|
-
const sysMsg = systemError instanceof Error ? systemError.message : String(systemError);
|
|
215
|
-
log.clearLine();
|
|
216
|
-
log.gap();
|
|
217
|
-
log.info(`System Copilot CLI failed: ${sysMsg}`);
|
|
218
|
-
log.gap();
|
|
219
|
-
client = null;
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
135
|
+
export const stopClient = async () => {
|
|
136
|
+
// REST API is stateless — nothing to stop
|
|
223
137
|
};
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
if (!ok || !client) {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
138
|
+
export const generateBranchName = async (text, correction, model) => {
|
|
139
|
+
const prompt = buildPromptWithCorrection('branch-name-prompt.md', text, 'Input', correction);
|
|
229
140
|
try {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// ignore
|
|
239
|
-
}
|
|
240
|
-
return res;
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
try {
|
|
244
|
-
await session.destroy();
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
// ignore
|
|
248
|
-
}
|
|
249
|
-
throw error;
|
|
250
|
-
}
|
|
141
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
142
|
+
if (!content)
|
|
143
|
+
return null;
|
|
144
|
+
const first = content
|
|
145
|
+
.trim()
|
|
146
|
+
.split('\n')
|
|
147
|
+
.find((l) => !!l) ?? '';
|
|
148
|
+
return normalizeBranchName(first) || null;
|
|
251
149
|
}
|
|
252
150
|
catch (error) {
|
|
253
|
-
log.
|
|
151
|
+
log.clearLine();
|
|
152
|
+
log.gap();
|
|
153
|
+
log.error('Copilot Error: ' + String(error));
|
|
254
154
|
return null;
|
|
255
155
|
}
|
|
256
156
|
};
|
|
257
|
-
export const
|
|
258
|
-
|
|
259
|
-
};
|
|
260
|
-
export const stopClient = async () => {
|
|
261
|
-
if (!client) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
157
|
+
export const generateCommitMessage = async (diff, correction, model) => {
|
|
158
|
+
const prompt = buildPromptWithCorrection('commit-message-prompt.md', diff, 'Diff', correction);
|
|
264
159
|
try {
|
|
265
|
-
await
|
|
160
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
161
|
+
if (!content)
|
|
162
|
+
return null;
|
|
163
|
+
return cleanAIContent(content, {
|
|
164
|
+
normalizeBlankLines: true,
|
|
165
|
+
minLength: MIN_AI_RESPONSE_LENGTH,
|
|
166
|
+
});
|
|
266
167
|
}
|
|
267
|
-
catch {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
catch {
|
|
273
|
-
/* ignore */
|
|
274
|
-
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
log.clearLine();
|
|
170
|
+
log.gap();
|
|
171
|
+
log.error('Copilot Error: ' + String(error));
|
|
172
|
+
return null;
|
|
275
173
|
}
|
|
276
|
-
client = null;
|
|
277
|
-
};
|
|
278
|
-
export const generateBranchName = async (text, correction, model) => {
|
|
279
|
-
const promptBase = `Generate a git branch name suffix from this input. Output ONLY the kebab-case suffix (lowercase-with-hyphens), 3-50 chars, nothing else.`;
|
|
280
|
-
const prompt = correction
|
|
281
|
-
? `${promptBase}\n\nInput:\n${text}\n\nAdjustment: ${correction}`
|
|
282
|
-
: `${promptBase}\n\nInput:\n${text}`;
|
|
283
|
-
const result = await withSession(model, async (session) => {
|
|
284
|
-
// sendAndWait returns the assistant message event with data.content
|
|
285
|
-
try {
|
|
286
|
-
const response = await session.sendAndWait({ prompt });
|
|
287
|
-
const content = response?.data?.content ?? '';
|
|
288
|
-
const first = String(content)
|
|
289
|
-
.trim()
|
|
290
|
-
.split('\n')
|
|
291
|
-
.find((l) => !!l) ?? '';
|
|
292
|
-
// sanitize to kebab-case
|
|
293
|
-
const cleaned = String(first)
|
|
294
|
-
.toLowerCase()
|
|
295
|
-
.replaceAll(/[^\d\sa-z-]/g, ' ')
|
|
296
|
-
.trim()
|
|
297
|
-
.replaceAll(/\s+/g, '-')
|
|
298
|
-
.replaceAll(/-+/g, '-')
|
|
299
|
-
.replaceAll(/^-|-$/g, '');
|
|
300
|
-
return cleaned || null;
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
log.clearLine();
|
|
304
|
-
log.gap();
|
|
305
|
-
log.error('Copilot Error: ' + String(error));
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
return result;
|
|
310
|
-
};
|
|
311
|
-
export const generateCommitMessage = async (diff, correction, model) => {
|
|
312
|
-
const promptBase = `Generate a conventional commit message from this git diff. Output ONLY the commit message in this format:\n\n<type>(<scope>): <short summary>\n\n<Detailed multi-line body explaining the change. Wrap lines at ~72 characters. LIMITS: subject max 100 chars; body max 360 chars. Include why the change was made and any important notes. Separate subject and body by a single blank line. Do not include any extraneous commentary or markers. Use imperative mood.
|
|
313
|
-
|
|
314
|
-
Example:
|
|
315
|
-
refactor(ai): migrate providers to SDKs
|
|
316
|
-
|
|
317
|
-
Replaces direct API/CLI calls for Copilot and Gemini with SDK integrations.
|
|
318
|
-
This simplifies code, improves maintainability, and adds dynamic model
|
|
319
|
-
fetching. Updates .gitignore for geeto binaries.`;
|
|
320
|
-
const prompt = correction
|
|
321
|
-
? `${promptBase}\n\nDiff:\n${diff}\n\nAdjustment: ${correction}`
|
|
322
|
-
: `${promptBase}\n\nDiff:\n${diff}`;
|
|
323
|
-
const result = await withSession(model, async (session) => {
|
|
324
|
-
try {
|
|
325
|
-
const response = await session.sendAndWait({ prompt });
|
|
326
|
-
const content = response?.data?.content ?? '';
|
|
327
|
-
// Normalize full response: remove fenced blocks, trim surrounding quotes, collapse extra blank lines
|
|
328
|
-
const cleaned = String(content)
|
|
329
|
-
.replaceAll(/```[\S\s]*?```/g, '')
|
|
330
|
-
.replaceAll(/^"+|"+$/g, '')
|
|
331
|
-
.trim();
|
|
332
|
-
const normalized = cleaned.replaceAll(/\n\s*\n+/g, '\n\n').trim();
|
|
333
|
-
return normalized && normalized.length >= 8 ? normalized : null;
|
|
334
|
-
}
|
|
335
|
-
catch (error) {
|
|
336
|
-
log.clearLine();
|
|
337
|
-
log.gap();
|
|
338
|
-
log.error('Copilot Error: ' + String(error));
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
return result;
|
|
343
174
|
};
|
|
344
175
|
export const getAvailableModelsDetailed = async () => {
|
|
176
|
+
const token = getToken();
|
|
177
|
+
if (!token)
|
|
178
|
+
return null;
|
|
345
179
|
try {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
else if (typeof runtime.getModels === 'function') {
|
|
352
|
-
list = await runtime.getModels();
|
|
353
|
-
}
|
|
354
|
-
else if (Array.isArray(runtime.models)) {
|
|
355
|
-
list = runtime.models;
|
|
356
|
-
}
|
|
357
|
-
if (!list || !Array.isArray(list)) {
|
|
180
|
+
const res = await fetch(MODELS_ENDPOINT, {
|
|
181
|
+
headers: { ...COPILOT_HEADERS, Authorization: `Bearer ${token}` },
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok)
|
|
358
184
|
return null;
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
const isPremium = Boolean(billing?.is_premium ?? billing?.premium ?? false);
|
|
382
|
-
const multiplierRaw = billing?.multiplier ?? billing?.cost_multiplier ?? null;
|
|
185
|
+
const data = (await res.json());
|
|
186
|
+
const list = data.data;
|
|
187
|
+
if (!Array.isArray(list))
|
|
188
|
+
return null;
|
|
189
|
+
const result = [];
|
|
190
|
+
for (const m of list) {
|
|
191
|
+
if (!m?.id)
|
|
192
|
+
continue;
|
|
193
|
+
// Skip non-chat models (embeddings, etc.)
|
|
194
|
+
const type = m.capabilities?.type;
|
|
195
|
+
if (type && type !== 'chat')
|
|
196
|
+
continue;
|
|
197
|
+
const id = String(m.id);
|
|
198
|
+
const name = String(m.name ?? id);
|
|
199
|
+
const limits = m.capabilities?.limits;
|
|
200
|
+
const inputTokenLimit = typeof limits?.max_prompt_tokens === 'number' ? limits.max_prompt_tokens : null;
|
|
201
|
+
const outputTokenLimit = typeof limits?.max_output_tokens === 'number' ? limits.max_output_tokens : null;
|
|
202
|
+
const policyState = m.policy?.state ?? null;
|
|
203
|
+
const needsEnable = Boolean(policyState && String(policyState).toLowerCase() !== 'enabled');
|
|
204
|
+
const billing = m.capabilities?.supports?.billing ?? null;
|
|
205
|
+
const isPremium = Boolean(billing?.premium_billing);
|
|
206
|
+
const multiplierRaw = billing?.copilot_premium_request_multiplier ?? null;
|
|
383
207
|
const multiplier = typeof multiplierRaw === 'number' ? multiplierRaw : null;
|
|
384
|
-
|
|
385
|
-
// If the policy state is unknown, try to ping the model via adapter to check enablement
|
|
386
|
-
if (policyState === null) {
|
|
387
|
-
try {
|
|
388
|
-
const adapter = await import('./copilot-adapter.js');
|
|
389
|
-
if (adapter && typeof adapter.pingModel === 'function') {
|
|
390
|
-
try {
|
|
391
|
-
const pingOk = await adapter.pingModel(id || name);
|
|
392
|
-
needsEnable = !pingOk;
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
// If ping fails, leave needsEnable as false to avoid blocking users
|
|
396
|
-
needsEnable = false;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
catch {
|
|
401
|
-
// adapter import failed; ignore and proceed
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// capabilities.limits may contain token limits for the runtime
|
|
405
|
-
let inputTokenLimit = null;
|
|
406
|
-
let outputTokenLimit = null;
|
|
407
|
-
try {
|
|
408
|
-
const limits = m.capabilities?.limits;
|
|
409
|
-
if (limits) {
|
|
410
|
-
if (typeof limits.max_prompt_tokens === 'number') {
|
|
411
|
-
inputTokenLimit = limits.max_prompt_tokens;
|
|
412
|
-
}
|
|
413
|
-
if (typeof limits.max_output_tokens === 'number') {
|
|
414
|
-
outputTokenLimit = limits.max_output_tokens;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
catch {
|
|
419
|
-
// ignore parsing errors; leave as null
|
|
420
|
-
}
|
|
421
|
-
const label = `${name}`;
|
|
422
|
-
const value = id || label;
|
|
423
|
-
return {
|
|
208
|
+
result.push({
|
|
424
209
|
id,
|
|
425
210
|
name,
|
|
426
211
|
inputTokenLimit,
|
|
@@ -428,15 +213,11 @@ export const getAvailableModelsDetailed = async () => {
|
|
|
428
213
|
needsEnable,
|
|
429
214
|
isPremium,
|
|
430
215
|
multiplier,
|
|
431
|
-
label,
|
|
432
|
-
value,
|
|
433
|
-
};
|
|
434
|
-
}));
|
|
435
|
-
const out = mapped.filter(Boolean);
|
|
436
|
-
if (out.length > 0) {
|
|
437
|
-
return out;
|
|
216
|
+
label: name,
|
|
217
|
+
value: id,
|
|
218
|
+
});
|
|
438
219
|
}
|
|
439
|
-
return null;
|
|
220
|
+
return result.length > 0 ? result : null;
|
|
440
221
|
}
|
|
441
222
|
catch {
|
|
442
223
|
return null;
|
|
@@ -444,7 +225,6 @@ export const getAvailableModelsDetailed = async () => {
|
|
|
444
225
|
};
|
|
445
226
|
export const getAvailableModelChoices = async (defaultModelId) => {
|
|
446
227
|
const detailed = (await getAvailableModelsDetailed()) ?? [];
|
|
447
|
-
// compute max lengths using simple loops (avoid reduce)
|
|
448
228
|
let maxNameLen = 0;
|
|
449
229
|
let maxIoLen = 0;
|
|
450
230
|
let maxMultLen = 0;
|
|
@@ -487,79 +267,31 @@ export const getAvailableModelChoices = async (defaultModelId) => {
|
|
|
487
267
|
return choices;
|
|
488
268
|
};
|
|
489
269
|
export const generateReleaseNotes = async (commits, language, correction, model) => {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
- Start with "### What's New?" as the top-level section
|
|
495
|
-
- Group changes into subsections: "#### New Features", "#### Bug Fixes", "#### Other Improvements"
|
|
496
|
-
- Only include subsections that have items (skip empty ones)
|
|
497
|
-
- Use simple, non-technical language that end users can understand
|
|
498
|
-
- Each item should be a bullet point starting with "-"
|
|
499
|
-
- Strip conventional commit prefixes (feat:, fix:, chore:, etc.)
|
|
500
|
-
- Keep it concise but informative
|
|
501
|
-
- If there are breaking changes, add a "#### Breaking Changes" subsection at the top
|
|
502
|
-
- Do NOT include commit hashes or author names
|
|
503
|
-
|
|
504
|
-
Formatting (follow EXACTLY — this is markdownlint-compliant):
|
|
505
|
-
- Always put ONE blank line after EVERY heading (### or ####) before the first bullet
|
|
506
|
-
- Always put ONE blank line after the last bullet in a section before the next #### heading
|
|
507
|
-
- Never have more than one consecutive blank line
|
|
508
|
-
- Example output:
|
|
509
|
-
|
|
510
|
-
### What's New?
|
|
511
|
-
|
|
512
|
-
#### New Features
|
|
513
|
-
|
|
514
|
-
- Feature description here
|
|
515
|
-
- Another feature
|
|
516
|
-
|
|
517
|
-
#### Bug Fixes
|
|
518
|
-
|
|
519
|
-
- Fix description here
|
|
520
|
-
|
|
521
|
-
#### Other Improvements
|
|
522
|
-
|
|
523
|
-
- Improvement here`;
|
|
524
|
-
const prompt = correction
|
|
525
|
-
? `${promptBase}\n\nCommits:\n${commits}\n\nAdjustment: ${correction}`
|
|
526
|
-
: `${promptBase}\n\nCommits:\n${commits}`;
|
|
527
|
-
const result = await withSession(model, async (session) => {
|
|
528
|
-
try {
|
|
529
|
-
const response = await session.sendAndWait({ prompt });
|
|
530
|
-
const content = response?.data?.content ?? '';
|
|
531
|
-
const cleaned = String(content)
|
|
532
|
-
.replaceAll(/```[\S\s]*?```/g, '')
|
|
533
|
-
.replaceAll(/^"+|"+$/g, '')
|
|
534
|
-
.trim();
|
|
535
|
-
return cleaned || null;
|
|
536
|
-
}
|
|
537
|
-
catch (error) {
|
|
538
|
-
log.clearLine();
|
|
539
|
-
log.gap();
|
|
540
|
-
log.error('Copilot Error: ' + String(error));
|
|
270
|
+
const prompt = buildReleaseNotesPrompt(commits, language, correction);
|
|
271
|
+
try {
|
|
272
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
273
|
+
if (!content)
|
|
541
274
|
return null;
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
275
|
+
return cleanAIContent(content);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
log.clearLine();
|
|
279
|
+
log.gap();
|
|
280
|
+
log.error('Copilot Error: ' + String(error));
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
545
283
|
};
|
|
546
284
|
/** Send a raw prompt to Copilot and return the text response. */
|
|
547
285
|
export const generateText = async (prompt, model) => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const content = response?.data?.content ?? '';
|
|
552
|
-
const cleaned = String(content)
|
|
553
|
-
.replaceAll(/```[\S\s]*?```/g, '')
|
|
554
|
-
.replaceAll(/^"+|"+$/g, '')
|
|
555
|
-
.trim();
|
|
556
|
-
return cleaned || null;
|
|
557
|
-
}
|
|
558
|
-
catch {
|
|
286
|
+
try {
|
|
287
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
288
|
+
if (!content)
|
|
559
289
|
return null;
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
290
|
+
return cleanAIContent(content);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
563
295
|
};
|
|
564
296
|
export default {
|
|
565
297
|
generateBranchName,
|