geeto 0.6.6 → 0.9.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/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 +166 -456
- 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 +8 -7
- package/lib/core/copilot-setup.d.ts.map +1 -1
- package/lib/core/copilot-setup.js +59 -230
- 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 +501 -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/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 +569 -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 +321 -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 +2 -1
- package/lib/workflows/settings.d.ts.map +1 -1
- package/lib/workflows/settings.js +289 -19
- package/lib/workflows/settings.js.map +1 -1
- 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 +3 -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,189 @@
|
|
|
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
|
-
|
|
23
|
-
|
|
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
|
+
// ── Token Management ────────────────────────────────────────────────────
|
|
21
|
+
let cachedToken = null;
|
|
24
22
|
/**
|
|
25
|
-
*
|
|
23
|
+
* Get GitHub auth token for Copilot API access.
|
|
24
|
+
* Priority: 1) cached token, 2) GITHUB_TOKEN env, 3) `gh auth token`
|
|
26
25
|
*/
|
|
27
|
-
const
|
|
26
|
+
const getToken = () => {
|
|
27
|
+
if (cachedToken)
|
|
28
|
+
return cachedToken;
|
|
29
|
+
// Try env vars first (fast path)
|
|
30
|
+
const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null;
|
|
31
|
+
if (envToken) {
|
|
32
|
+
cachedToken = envToken;
|
|
33
|
+
return envToken;
|
|
34
|
+
}
|
|
35
|
+
// Fall back to gh CLI
|
|
28
36
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (Date.now() - data.timestamp < CACHE_TTL_MS && fs.existsSync(data.path)) {
|
|
34
|
-
return data;
|
|
37
|
+
const token = exec('gh auth token', true).trim();
|
|
38
|
+
if (token) {
|
|
39
|
+
cachedToken = token;
|
|
40
|
+
return token;
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
catch {
|
|
38
|
-
//
|
|
44
|
+
// gh CLI not available or not authenticated
|
|
39
45
|
}
|
|
40
46
|
return null;
|
|
41
47
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const cacheDir = path.dirname(CACHE_FILE);
|
|
48
|
-
if (!fs.existsSync(cacheDir)) {
|
|
49
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
50
|
-
}
|
|
51
|
-
fs.writeFileSync(CACHE_FILE, JSON.stringify({ ...info, timestamp: Date.now() }));
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
// ignore cache write failures
|
|
48
|
+
const chatCompletion = async (messages, model) => {
|
|
49
|
+
const token = getToken();
|
|
50
|
+
if (!token) {
|
|
51
|
+
log.warn('No GitHub token available. Run `gh auth login` to authenticate.');
|
|
52
|
+
return null;
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const getVersionFromPath = (binPath) => {
|
|
54
|
+
const body = {
|
|
55
|
+
model: model ?? 'gpt-5-mini',
|
|
56
|
+
messages,
|
|
57
|
+
};
|
|
61
58
|
try {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
const res = await fetch(CHAT_ENDPOINT, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Authorization': `Bearer ${token}`,
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'Accept': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const errorText = await res.text().catch(() => '');
|
|
70
|
+
if (res.status === 401 || res.status === 403) {
|
|
71
|
+
cachedToken = null;
|
|
72
|
+
log.clearLine();
|
|
73
|
+
log.warn('GitHub token expired or unauthorized. Run `gh auth login` to re-authenticate.');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log.clearLine();
|
|
77
|
+
log.warn(`Copilot API error (${res.status}): ${errorText.slice(0, 200)}`);
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const data = (await res.json());
|
|
82
|
+
const content = data.choices?.[0]?.message?.content;
|
|
83
|
+
return content ?? null;
|
|
65
84
|
}
|
|
66
|
-
catch {
|
|
85
|
+
catch (error) {
|
|
86
|
+
log.clearLine();
|
|
87
|
+
log.error('Copilot API request failed: ' + String(error));
|
|
67
88
|
return null;
|
|
68
89
|
}
|
|
69
90
|
};
|
|
91
|
+
// ── Public API (same exports as before) ─────────────────────────────────
|
|
70
92
|
/**
|
|
71
|
-
*
|
|
72
|
-
* Uses file-based caching to avoid slow exec calls on every startup.
|
|
93
|
+
* Check if Copilot API is accessible (has valid token).
|
|
73
94
|
*/
|
|
74
|
-
export const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Super fast path: check cache first
|
|
79
|
-
const cached = readCache();
|
|
80
|
-
if (cached && fs.existsSync(cached.path) && parseParts(cached.version) >= minNum) {
|
|
81
|
-
return { path: cached.path, version: cached.version };
|
|
82
|
-
}
|
|
83
|
-
// Check PATH first (most common case)
|
|
95
|
+
export const isAvailable = async () => {
|
|
96
|
+
const token = getToken();
|
|
97
|
+
if (!token)
|
|
98
|
+
return false;
|
|
84
99
|
try {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const num = parseParts(ver);
|
|
91
|
-
if (num >= minNum) {
|
|
92
|
-
const result = { path: pathBin, version: ver };
|
|
93
|
-
writeCache(result);
|
|
94
|
-
return result;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
100
|
+
const res = await fetch(MODELS_ENDPOINT, {
|
|
101
|
+
method: 'GET',
|
|
102
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
103
|
+
});
|
|
104
|
+
return res.ok;
|
|
98
105
|
}
|
|
99
106
|
catch {
|
|
100
|
-
// ignore, will check other locations
|
|
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
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
137
|
-
};
|
|
138
|
-
/**
|
|
139
|
-
* Check if Copilot CLI version is compatible with SDK.
|
|
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.
|
|
142
|
-
*/
|
|
143
|
-
export const checkCopilotCliVersion = () => {
|
|
144
|
-
const best = findBestCopilotBinary();
|
|
145
|
-
if (!best) {
|
|
146
107
|
return false;
|
|
147
108
|
}
|
|
148
|
-
return parseParts(best.version) >= parseParts(MIN_COPILOT_VERSION);
|
|
149
109
|
};
|
|
150
|
-
let client = null;
|
|
151
110
|
/**
|
|
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).
|
|
111
|
+
* No-op for backwards compatibility. REST API is stateless.
|
|
158
112
|
*/
|
|
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
|
-
}
|
|
113
|
+
export const stopClient = async () => {
|
|
114
|
+
// REST API is stateless — nothing to stop
|
|
223
115
|
};
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
if (!ok || !client) {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
116
|
+
export const generateBranchName = async (text, correction, model) => {
|
|
117
|
+
const prompt = buildPromptWithCorrection('branch-name-prompt.md', text, 'Input', correction);
|
|
229
118
|
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
|
-
}
|
|
119
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
120
|
+
if (!content)
|
|
121
|
+
return null;
|
|
122
|
+
const first = content
|
|
123
|
+
.trim()
|
|
124
|
+
.split('\n')
|
|
125
|
+
.find((l) => !!l) ?? '';
|
|
126
|
+
return normalizeBranchName(first) || null;
|
|
251
127
|
}
|
|
252
128
|
catch (error) {
|
|
253
|
-
log.
|
|
129
|
+
log.clearLine();
|
|
130
|
+
log.gap();
|
|
131
|
+
log.error('Copilot Error: ' + String(error));
|
|
254
132
|
return null;
|
|
255
133
|
}
|
|
256
134
|
};
|
|
257
|
-
export const
|
|
258
|
-
|
|
259
|
-
};
|
|
260
|
-
export const stopClient = async () => {
|
|
261
|
-
if (!client) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
135
|
+
export const generateCommitMessage = async (diff, correction, model) => {
|
|
136
|
+
const prompt = buildPromptWithCorrection('commit-message-prompt.md', diff, 'Diff', correction);
|
|
264
137
|
try {
|
|
265
|
-
await
|
|
138
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
139
|
+
if (!content)
|
|
140
|
+
return null;
|
|
141
|
+
return cleanAIContent(content, {
|
|
142
|
+
normalizeBlankLines: true,
|
|
143
|
+
minLength: MIN_AI_RESPONSE_LENGTH,
|
|
144
|
+
});
|
|
266
145
|
}
|
|
267
|
-
catch {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
catch {
|
|
273
|
-
/* ignore */
|
|
274
|
-
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
log.clearLine();
|
|
148
|
+
log.gap();
|
|
149
|
+
log.error('Copilot Error: ' + String(error));
|
|
150
|
+
return null;
|
|
275
151
|
}
|
|
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
152
|
};
|
|
344
153
|
export const getAvailableModelsDetailed = async () => {
|
|
154
|
+
const token = getToken();
|
|
155
|
+
if (!token)
|
|
156
|
+
return null;
|
|
345
157
|
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)) {
|
|
158
|
+
const res = await fetch(MODELS_ENDPOINT, {
|
|
159
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
160
|
+
});
|
|
161
|
+
if (!res.ok)
|
|
358
162
|
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;
|
|
163
|
+
const data = (await res.json());
|
|
164
|
+
const list = data.data;
|
|
165
|
+
if (!Array.isArray(list))
|
|
166
|
+
return null;
|
|
167
|
+
const result = [];
|
|
168
|
+
for (const m of list) {
|
|
169
|
+
if (!m?.id)
|
|
170
|
+
continue;
|
|
171
|
+
// Skip non-chat models (embeddings, etc.)
|
|
172
|
+
const type = m.capabilities?.type;
|
|
173
|
+
if (type && type !== 'chat')
|
|
174
|
+
continue;
|
|
175
|
+
const id = String(m.id);
|
|
176
|
+
const name = String(m.name ?? id);
|
|
177
|
+
const limits = m.capabilities?.limits;
|
|
178
|
+
const inputTokenLimit = typeof limits?.max_prompt_tokens === 'number' ? limits.max_prompt_tokens : null;
|
|
179
|
+
const outputTokenLimit = typeof limits?.max_output_tokens === 'number' ? limits.max_output_tokens : null;
|
|
180
|
+
const policyState = m.policy?.state ?? null;
|
|
181
|
+
const needsEnable = Boolean(policyState && String(policyState).toLowerCase() !== 'enabled');
|
|
182
|
+
const billing = m.capabilities?.supports?.billing ?? null;
|
|
183
|
+
const isPremium = Boolean(billing?.premium_billing);
|
|
184
|
+
const multiplierRaw = billing?.copilot_premium_request_multiplier ?? null;
|
|
383
185
|
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 {
|
|
186
|
+
result.push({
|
|
424
187
|
id,
|
|
425
188
|
name,
|
|
426
189
|
inputTokenLimit,
|
|
@@ -428,15 +191,11 @@ export const getAvailableModelsDetailed = async () => {
|
|
|
428
191
|
needsEnable,
|
|
429
192
|
isPremium,
|
|
430
193
|
multiplier,
|
|
431
|
-
label,
|
|
432
|
-
value,
|
|
433
|
-
};
|
|
434
|
-
}));
|
|
435
|
-
const out = mapped.filter(Boolean);
|
|
436
|
-
if (out.length > 0) {
|
|
437
|
-
return out;
|
|
194
|
+
label: name,
|
|
195
|
+
value: id,
|
|
196
|
+
});
|
|
438
197
|
}
|
|
439
|
-
return null;
|
|
198
|
+
return result.length > 0 ? result : null;
|
|
440
199
|
}
|
|
441
200
|
catch {
|
|
442
201
|
return null;
|
|
@@ -444,7 +203,6 @@ export const getAvailableModelsDetailed = async () => {
|
|
|
444
203
|
};
|
|
445
204
|
export const getAvailableModelChoices = async (defaultModelId) => {
|
|
446
205
|
const detailed = (await getAvailableModelsDetailed()) ?? [];
|
|
447
|
-
// compute max lengths using simple loops (avoid reduce)
|
|
448
206
|
let maxNameLen = 0;
|
|
449
207
|
let maxIoLen = 0;
|
|
450
208
|
let maxMultLen = 0;
|
|
@@ -487,79 +245,31 @@ export const getAvailableModelChoices = async (defaultModelId) => {
|
|
|
487
245
|
return choices;
|
|
488
246
|
};
|
|
489
247
|
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));
|
|
248
|
+
const prompt = buildReleaseNotesPrompt(commits, language, correction);
|
|
249
|
+
try {
|
|
250
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
251
|
+
if (!content)
|
|
541
252
|
return null;
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
253
|
+
return cleanAIContent(content);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
log.clearLine();
|
|
257
|
+
log.gap();
|
|
258
|
+
log.error('Copilot Error: ' + String(error));
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
545
261
|
};
|
|
546
262
|
/** Send a raw prompt to Copilot and return the text response. */
|
|
547
263
|
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 {
|
|
264
|
+
try {
|
|
265
|
+
const content = await chatCompletion([{ role: 'user', content: prompt }], model);
|
|
266
|
+
if (!content)
|
|
559
267
|
return null;
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
268
|
+
return cleanAIContent(content);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
563
273
|
};
|
|
564
274
|
export default {
|
|
565
275
|
generateBranchName,
|