@involvex/prompt-enhancer 0.0.2

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.
Files changed (60) hide show
  1. package/.github/FUNDING.yml +9 -0
  2. package/LICENSE +21 -0
  3. package/dist/app.d.ts +5 -0
  4. package/dist/app.js +39 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +53 -0
  7. package/dist/commands/about.d.ts +1 -0
  8. package/dist/commands/about.js +6 -0
  9. package/dist/commands/direct-enhance.d.ts +5 -0
  10. package/dist/commands/direct-enhance.js +52 -0
  11. package/dist/commands/enhanceprompt.d.ts +1 -0
  12. package/dist/commands/enhanceprompt.js +6 -0
  13. package/dist/commands/help.d.ts +1 -0
  14. package/dist/commands/help.js +6 -0
  15. package/dist/commands/settings.d.ts +1 -0
  16. package/dist/commands/settings.js +1 -0
  17. package/dist/commands/version.d.ts +1 -0
  18. package/dist/commands/version.js +6 -0
  19. package/dist/components/enhance-prompt.d.ts +5 -0
  20. package/dist/components/enhance-prompt.js +82 -0
  21. package/dist/components/history-viewer.d.ts +5 -0
  22. package/dist/components/history-viewer.js +64 -0
  23. package/dist/components/menu.d.ts +11 -0
  24. package/dist/components/menu.js +6 -0
  25. package/dist/components/select-input.d.ts +19 -0
  26. package/dist/components/select-input.js +51 -0
  27. package/dist/components/settings.d.ts +5 -0
  28. package/dist/components/settings.js +246 -0
  29. package/dist/lib/config/manager.d.ts +49 -0
  30. package/dist/lib/config/manager.js +152 -0
  31. package/dist/lib/config/schema.d.ts +69 -0
  32. package/dist/lib/config/schema.js +37 -0
  33. package/dist/lib/config/types.d.ts +34 -0
  34. package/dist/lib/config/types.js +4 -0
  35. package/dist/lib/enhancement/engine.d.ts +41 -0
  36. package/dist/lib/enhancement/engine.js +163 -0
  37. package/dist/lib/history/manager.d.ts +45 -0
  38. package/dist/lib/history/manager.js +120 -0
  39. package/dist/lib/providers/base.d.ts +47 -0
  40. package/dist/lib/providers/base.js +30 -0
  41. package/dist/lib/providers/copilot.d.ts +18 -0
  42. package/dist/lib/providers/copilot.js +112 -0
  43. package/dist/lib/providers/gemini.d.ts +13 -0
  44. package/dist/lib/providers/gemini.js +86 -0
  45. package/dist/lib/providers/index.d.ts +26 -0
  46. package/dist/lib/providers/index.js +86 -0
  47. package/dist/lib/providers/kilo.d.ts +22 -0
  48. package/dist/lib/providers/kilo.js +174 -0
  49. package/dist/lib/providers/opencode.d.ts +22 -0
  50. package/dist/lib/providers/opencode.js +174 -0
  51. package/dist/lib/types/index.d.ts +26 -0
  52. package/dist/lib/types/index.js +4 -0
  53. package/dist/lib/utils/copilot-auth.d.ts +14 -0
  54. package/dist/lib/utils/copilot-auth.js +84 -0
  55. package/dist/lib/utils/models-cache.d.ts +12 -0
  56. package/dist/lib/utils/models-cache.js +139 -0
  57. package/dist/lib/utils/paths.d.ts +10 -0
  58. package/dist/lib/utils/paths.js +18 -0
  59. package/package.json +101 -0
  60. package/readme.md +271 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Provider factory and registry
3
+ * Manages creation and registration of provider instances
4
+ */
5
+ import { GeminiProvider } from './gemini.js';
6
+ import { CopilotProvider } from './copilot.js';
7
+ import { KiloProvider } from './kilo.js';
8
+ import { OpenCodeProvider } from './opencode.js';
9
+ class ProviderRegistry {
10
+ providers = new Map();
11
+ register(name, provider) {
12
+ this.providers.set(name.toLowerCase(), provider);
13
+ }
14
+ get(name) {
15
+ return this.providers.get(name.toLowerCase());
16
+ }
17
+ getAll() {
18
+ return Array.from(this.providers.values());
19
+ }
20
+ has(name) {
21
+ return this.providers.has(name.toLowerCase());
22
+ }
23
+ list() {
24
+ return Array.from(this.providers.keys());
25
+ }
26
+ clear() {
27
+ this.providers.clear();
28
+ }
29
+ }
30
+ export class ProviderFactory {
31
+ registry = new ProviderRegistry();
32
+ createProvider(type, credentials) {
33
+ const lowerType = type.toLowerCase();
34
+ switch (lowerType) {
35
+ case 'gemini': {
36
+ return new GeminiProvider(credentials);
37
+ }
38
+ case 'copilot': {
39
+ return new CopilotProvider(credentials);
40
+ }
41
+ case 'kilo': {
42
+ return new KiloProvider(credentials);
43
+ }
44
+ case 'opencode': {
45
+ return new OpenCodeProvider(credentials);
46
+ }
47
+ default: {
48
+ throw new Error(`Unknown provider type: ${type}`);
49
+ }
50
+ }
51
+ }
52
+ registerProvider(name, provider) {
53
+ this.registry.register(name, provider);
54
+ }
55
+ getProvider(name) {
56
+ return this.registry.get(name);
57
+ }
58
+ getAllProviders() {
59
+ return this.registry.getAll();
60
+ }
61
+ hasProvider(name) {
62
+ return this.registry.has(name);
63
+ }
64
+ listProviders() {
65
+ return this.registry.list();
66
+ }
67
+ clearProviders() {
68
+ this.registry.clear();
69
+ }
70
+ }
71
+ // Export singleton instance
72
+ export const providerFactory = new ProviderFactory();
73
+ /**
74
+ * Get or create a provider by type with credentials
75
+ */
76
+ export function getProvider(type, credentials) {
77
+ // If no credentials provided, create with empty credentials
78
+ const creds = credentials || { apiKey: '' };
79
+ return providerFactory.createProvider(type, creds);
80
+ }
81
+ /**
82
+ * Get list of supported provider types
83
+ */
84
+ export function getSupportedProviders() {
85
+ return ['gemini', 'copilot', 'kilo', 'opencode'];
86
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Kilo-gateway provider implementation
3
+ * Kilo is an LLM router with free and paid models
4
+ * API: https://kilo.ai/ — free models still require an API key from https://app.kilo.ai
5
+ */
6
+ import { Provider } from './base.js';
7
+ import type { EnhancementOptions, ProviderCredentials } from '../types/index.js';
8
+ export declare const KILO_MODELS: {
9
+ id: string;
10
+ name: string;
11
+ }[];
12
+ export declare const KILO_DEFAULT_ENDPOINT = "https://api.kilo.ai/api/gateway/chat/completions";
13
+ export declare const KILO_DEFAULT_MODEL = "minimax/minimax-m2.5";
14
+ export declare class KiloProvider extends Provider {
15
+ private endpoint;
16
+ constructor(credentials: ProviderCredentials);
17
+ private buildHeaders;
18
+ enhance(prompt: string, options?: EnhancementOptions): Promise<string>;
19
+ enhanceStream(prompt: string, options?: EnhancementOptions): AsyncGenerator<string, void, unknown>;
20
+ getAvailableModels(): Promise<string[]>;
21
+ validateCredentials(): Promise<boolean>;
22
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Kilo-gateway provider implementation
3
+ * Kilo is an LLM router with free and paid models
4
+ * API: https://kilo.ai/ — free models still require an API key from https://app.kilo.ai
5
+ */
6
+ import { Provider } from './base.js';
7
+ export const KILO_MODELS = [
8
+ {
9
+ id: 'openrouter/free',
10
+ name: 'OpenRouter Free Router (auto-select free model)',
11
+ },
12
+ { id: 'minimax/minimax-m2.5', name: 'MiniMax M2.5 (recommended)' },
13
+ { id: 'minimax/minimax-m2.1', name: 'MiniMax M2.1' },
14
+ { id: 'minimax/minimax', name: 'MiniMax' },
15
+ { id: 'arcee-ai/trinity-large', name: 'Arcee AI Trinity Large' },
16
+ { id: 'z-ai/glm-4-7-flash-preview', name: 'Z.ai GLM 4.7 Flash Preview (free)' },
17
+ { id: 'qwen/qwen3-coder-next', name: 'Qwen3 Coder Next' },
18
+ { id: 'qwen/qwen3-coder-480b-a35b', name: 'Qwen3 Coder 480B A35B' },
19
+ { id: 'xai/grok-code-fast-1', name: 'xAI Grok Code Fast 1' },
20
+ { id: 'kwaipilot/kat-coder-pro-v1', name: 'KwaiPilot KAT-Coder-Pro V1' },
21
+ { id: 'deepseek/deepseek-chat-v3.1', name: 'DeepSeek V3.1 Terminus' },
22
+ ];
23
+ export const KILO_DEFAULT_ENDPOINT = 'https://api.kilo.ai/api/gateway/chat/completions';
24
+ export const KILO_DEFAULT_MODEL = 'minimax/minimax-m2.5';
25
+ const SYSTEM_PROMPT = 'You are an expert at enhancing and improving user prompts for LLMs. Analyze the given prompt and return an improved version that is clearer, more specific, and more likely to produce better results. Return ONLY the enhanced prompt, no explanations.';
26
+ async function throwOnError(response) {
27
+ let body = '';
28
+ try {
29
+ body = await response.text();
30
+ }
31
+ catch {
32
+ // ignore
33
+ }
34
+ if (body) {
35
+ try {
36
+ const parsed = JSON.parse(body);
37
+ const errorMsg = parsed.error?.message ?? parsed.message;
38
+ if (response.status === 429) {
39
+ throw new Error(`Kilo: Rate limited${errorMsg ? ` — ${errorMsg}` : ''}. Wait a moment or use a different model.`);
40
+ }
41
+ if (errorMsg) {
42
+ throw new Error(`Kilo error: ${errorMsg}`);
43
+ }
44
+ }
45
+ catch (e) {
46
+ if (!(e instanceof SyntaxError))
47
+ throw e;
48
+ }
49
+ }
50
+ throw new Error(`Kilo API error: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`);
51
+ }
52
+ export class KiloProvider extends Provider {
53
+ endpoint;
54
+ constructor(credentials) {
55
+ super('kilo', credentials, KILO_DEFAULT_MODEL);
56
+ this.endpoint = credentials.endpoint || KILO_DEFAULT_ENDPOINT;
57
+ }
58
+ buildHeaders() {
59
+ const headers = {
60
+ 'Content-Type': 'application/json',
61
+ };
62
+ if (this.credentials.apiKey) {
63
+ headers['Authorization'] = `Bearer ${this.credentials.apiKey}`;
64
+ }
65
+ return headers;
66
+ }
67
+ async enhance(prompt, options) {
68
+ const body = {
69
+ messages: [
70
+ { role: 'system', content: options?.systemPrompt || SYSTEM_PROMPT },
71
+ { role: 'user', content: `Original prompt:\n${prompt}` },
72
+ ],
73
+ model: options?.model || this.defaultModel,
74
+ temperature: options?.temperature ?? 0.7,
75
+ max_tokens: options?.maxTokens ?? 1000,
76
+ stream: false,
77
+ };
78
+ const response = await fetch(this.endpoint, {
79
+ method: 'POST',
80
+ headers: this.buildHeaders(),
81
+ body: JSON.stringify(body),
82
+ });
83
+ if (!response.ok) {
84
+ await throwOnError(response);
85
+ }
86
+ const data = (await response.json());
87
+ const content = data.choices[0]?.message?.content;
88
+ if (!content)
89
+ throw new Error('No response received from Kilo');
90
+ return content.trim();
91
+ }
92
+ async *enhanceStream(prompt, options) {
93
+ const body = {
94
+ messages: [
95
+ { role: 'system', content: options?.systemPrompt || SYSTEM_PROMPT },
96
+ { role: 'user', content: `Original prompt:\n${prompt}` },
97
+ ],
98
+ model: options?.model || this.defaultModel,
99
+ temperature: options?.temperature ?? 0.7,
100
+ max_tokens: options?.maxTokens ?? 1000,
101
+ stream: true,
102
+ };
103
+ const response = await fetch(this.endpoint, {
104
+ method: 'POST',
105
+ headers: this.buildHeaders(),
106
+ body: JSON.stringify(body),
107
+ });
108
+ if (!response.ok) {
109
+ await throwOnError(response);
110
+ }
111
+ if (!response.body)
112
+ throw new Error('No response stream from Kilo');
113
+ const reader = response.body.getReader();
114
+ const decoder = new TextDecoder();
115
+ let buffer = '';
116
+ try {
117
+ while (true) {
118
+ const { done, value } = await reader.read();
119
+ if (done)
120
+ break;
121
+ buffer += decoder.decode(value, { stream: true });
122
+ const lines = buffer.split('\n');
123
+ for (let i = 0; i < lines.length - 1; i++) {
124
+ const line = lines[i];
125
+ if (line.startsWith('data: ')) {
126
+ const data = line.slice(6).trim();
127
+ if (data === '[DONE]')
128
+ return;
129
+ try {
130
+ const parsed = JSON.parse(data);
131
+ if (parsed.error) {
132
+ const msg = parsed.error.message ?? JSON.stringify(parsed.error);
133
+ throw new Error(`Kilo stream error: ${msg}`);
134
+ }
135
+ const content = parsed.choices?.[0]?.delta?.content;
136
+ if (content)
137
+ yield content;
138
+ }
139
+ catch (e) {
140
+ if (e instanceof SyntaxError)
141
+ continue;
142
+ throw e;
143
+ }
144
+ }
145
+ }
146
+ buffer = lines[lines.length - 1];
147
+ }
148
+ }
149
+ finally {
150
+ reader.releaseLock();
151
+ }
152
+ }
153
+ async getAvailableModels() {
154
+ return KILO_MODELS.map(m => m.id);
155
+ }
156
+ async validateCredentials() {
157
+ try {
158
+ const response = await fetch(this.endpoint, {
159
+ method: 'POST',
160
+ headers: this.buildHeaders(),
161
+ body: JSON.stringify({
162
+ messages: [{ role: 'user', content: 'test' }],
163
+ model: KILO_DEFAULT_MODEL,
164
+ max_tokens: 1,
165
+ stream: false,
166
+ }),
167
+ });
168
+ return response.ok;
169
+ }
170
+ catch {
171
+ return false;
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * OpenCode provider implementation
3
+ * OpenCode is an AI gateway with an OpenAI-compatible API
4
+ * Models endpoint: https://opencode.ai/zen/v1/models
5
+ * Chat completions: https://opencode.ai/zen/v1/chat/completions
6
+ */
7
+ import { Provider } from './base.js';
8
+ import type { EnhancementOptions, ProviderCredentials } from '../types/index.js';
9
+ export declare const OPENCODE_BASE_URL = "https://opencode.ai/zen/v1";
10
+ export declare const OPENCODE_MODELS_ENDPOINT = "https://opencode.ai/zen/v1/models";
11
+ export declare const OPENCODE_CHAT_ENDPOINT = "https://opencode.ai/zen/v1/chat/completions";
12
+ export declare const OPENCODE_DEFAULT_MODEL = "anthropic/claude-sonnet-4-5";
13
+ export declare class OpenCodeProvider extends Provider {
14
+ private chatEndpoint;
15
+ private modelsEndpoint;
16
+ constructor(credentials: ProviderCredentials);
17
+ private buildHeaders;
18
+ enhance(prompt: string, options?: EnhancementOptions): Promise<string>;
19
+ enhanceStream(prompt: string, options?: EnhancementOptions): AsyncGenerator<string, void, unknown>;
20
+ getAvailableModels(): Promise<string[]>;
21
+ validateCredentials(): Promise<boolean>;
22
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * OpenCode provider implementation
3
+ * OpenCode is an AI gateway with an OpenAI-compatible API
4
+ * Models endpoint: https://opencode.ai/zen/v1/models
5
+ * Chat completions: https://opencode.ai/zen/v1/chat/completions
6
+ */
7
+ import { Provider } from './base.js';
8
+ export const OPENCODE_BASE_URL = 'https://opencode.ai/zen/v1';
9
+ export const OPENCODE_MODELS_ENDPOINT = `${OPENCODE_BASE_URL}/models`;
10
+ export const OPENCODE_CHAT_ENDPOINT = `${OPENCODE_BASE_URL}/chat/completions`;
11
+ export const OPENCODE_DEFAULT_MODEL = 'anthropic/claude-sonnet-4-5';
12
+ const SYSTEM_PROMPT = 'You are an expert at enhancing and improving user prompts for LLMs. Analyze the given prompt and return an improved version that is clearer, more specific, and more likely to produce better results. Return ONLY the enhanced prompt, no explanations.';
13
+ async function throwOnError(response, provider = 'OpenCode') {
14
+ let body = '';
15
+ try {
16
+ body = await response.text();
17
+ }
18
+ catch {
19
+ // ignore
20
+ }
21
+ if (body) {
22
+ try {
23
+ const parsed = JSON.parse(body);
24
+ const errorType = parsed.error?.type;
25
+ const errorMsg = parsed.error?.message ?? parsed.message;
26
+ if (errorType === 'FreeUsageLimitError') {
27
+ throw new Error(`${provider}: Free usage limit reached. Switch to a paid model or try a different provider.`);
28
+ }
29
+ if (response.status === 429) {
30
+ throw new Error(`${provider}: Rate limited${errorMsg ? ` — ${errorMsg}` : ''}. Wait a moment or use a different model.`);
31
+ }
32
+ if (errorMsg) {
33
+ throw new Error(`${provider} error: ${errorMsg}`);
34
+ }
35
+ }
36
+ catch (e) {
37
+ if (!(e instanceof SyntaxError))
38
+ throw e;
39
+ }
40
+ }
41
+ throw new Error(`${provider} API error: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`);
42
+ }
43
+ export class OpenCodeProvider extends Provider {
44
+ chatEndpoint;
45
+ modelsEndpoint;
46
+ constructor(credentials) {
47
+ super('opencode', credentials, OPENCODE_DEFAULT_MODEL);
48
+ const base = credentials.endpoint
49
+ ? credentials.endpoint.replace(/\/chat\/completions$/, '')
50
+ : OPENCODE_BASE_URL;
51
+ this.chatEndpoint = `${base}/chat/completions`;
52
+ this.modelsEndpoint = `${base}/models`;
53
+ }
54
+ buildHeaders() {
55
+ const headers = {
56
+ 'Content-Type': 'application/json',
57
+ };
58
+ if (this.credentials.apiKey) {
59
+ headers['Authorization'] = `Bearer ${this.credentials.apiKey}`;
60
+ }
61
+ return headers;
62
+ }
63
+ async enhance(prompt, options) {
64
+ const body = {
65
+ messages: [
66
+ { role: 'system', content: options?.systemPrompt || SYSTEM_PROMPT },
67
+ { role: 'user', content: `Original prompt:\n${prompt}` },
68
+ ],
69
+ model: options?.model || this.defaultModel,
70
+ temperature: options?.temperature ?? 0.7,
71
+ max_tokens: options?.maxTokens ?? 1000,
72
+ stream: false,
73
+ };
74
+ const response = await fetch(this.chatEndpoint, {
75
+ method: 'POST',
76
+ headers: this.buildHeaders(),
77
+ body: JSON.stringify(body),
78
+ });
79
+ if (!response.ok) {
80
+ await throwOnError(response);
81
+ }
82
+ const data = (await response.json());
83
+ const content = data.choices[0]?.message?.content;
84
+ if (!content)
85
+ throw new Error('No response received from OpenCode');
86
+ return content.trim();
87
+ }
88
+ async *enhanceStream(prompt, options) {
89
+ const body = {
90
+ messages: [
91
+ { role: 'system', content: options?.systemPrompt || SYSTEM_PROMPT },
92
+ { role: 'user', content: `Original prompt:\n${prompt}` },
93
+ ],
94
+ model: options?.model || this.defaultModel,
95
+ temperature: options?.temperature ?? 0.7,
96
+ max_tokens: options?.maxTokens ?? 1000,
97
+ stream: true,
98
+ };
99
+ const response = await fetch(this.chatEndpoint, {
100
+ method: 'POST',
101
+ headers: this.buildHeaders(),
102
+ body: JSON.stringify(body),
103
+ });
104
+ if (!response.ok) {
105
+ await throwOnError(response);
106
+ }
107
+ if (!response.body)
108
+ throw new Error('No response stream from OpenCode');
109
+ const reader = response.body.getReader();
110
+ const decoder = new TextDecoder();
111
+ let buffer = '';
112
+ try {
113
+ while (true) {
114
+ const { done, value } = await reader.read();
115
+ if (done)
116
+ break;
117
+ buffer += decoder.decode(value, { stream: true });
118
+ const lines = buffer.split('\n');
119
+ for (let i = 0; i < lines.length - 1; i++) {
120
+ const line = lines[i];
121
+ if (line.startsWith('data: ')) {
122
+ const data = line.slice(6).trim();
123
+ if (data === '[DONE]')
124
+ return;
125
+ try {
126
+ const parsed = JSON.parse(data);
127
+ if (parsed.error) {
128
+ const msg = parsed.error.message ?? JSON.stringify(parsed.error);
129
+ const type = parsed.error.type;
130
+ if (type === 'FreeUsageLimitError') {
131
+ throw new Error('OpenCode: Free usage limit reached. Switch to a paid model or try a different provider.');
132
+ }
133
+ throw new Error(`OpenCode stream error: ${msg}`);
134
+ }
135
+ const content = parsed.choices?.[0]?.delta?.content;
136
+ if (content)
137
+ yield content;
138
+ }
139
+ catch (e) {
140
+ if (e instanceof SyntaxError)
141
+ continue;
142
+ throw e;
143
+ }
144
+ }
145
+ }
146
+ buffer = lines[lines.length - 1];
147
+ }
148
+ }
149
+ finally {
150
+ reader.releaseLock();
151
+ }
152
+ }
153
+ async getAvailableModels() {
154
+ const response = await fetch(this.modelsEndpoint, {
155
+ headers: this.buildHeaders(),
156
+ });
157
+ if (!response.ok) {
158
+ await throwOnError(response);
159
+ }
160
+ const data = (await response.json());
161
+ return data.data.map(m => m.id).filter(Boolean);
162
+ }
163
+ async validateCredentials() {
164
+ try {
165
+ const response = await fetch(this.modelsEndpoint, {
166
+ headers: this.buildHeaders(),
167
+ });
168
+ return response.ok;
169
+ }
170
+ catch {
171
+ return false;
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared types for prompt-enhancer
3
+ */
4
+ export interface EnhancementOptions {
5
+ model?: string;
6
+ temperature?: number;
7
+ maxTokens?: number;
8
+ systemPrompt?: string;
9
+ }
10
+ export interface EnhancementResult {
11
+ enhanced: string;
12
+ model: string;
13
+ provider: string;
14
+ tokensUsed?: number;
15
+ duration?: number;
16
+ }
17
+ export interface StreamChunk {
18
+ content: string;
19
+ done: boolean;
20
+ }
21
+ export interface ProviderCredentials {
22
+ apiKey: string;
23
+ endpoint?: string;
24
+ project?: string;
25
+ useOAuth?: boolean;
26
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared types for prompt-enhancer
3
+ */
4
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Auto-detects Copilot / GitHub OAuth credentials from well-known filesystem paths.
3
+ *
4
+ * Checked in order:
5
+ * 1. ~/.copilot/hosts.json (Copilot CLI)
6
+ * 2. ~/.config/github-copilot/hosts.json (Linux/Mac VSCode extension)
7
+ * 3. %APPDATA%\GitHub Copilot\hosts.json (Windows VSCode extension)
8
+ * 4. ~/.config/gh/hosts.yml (GitHub CLI — requires `js-yaml` or manual parse)
9
+ */
10
+ /**
11
+ * Attempts to auto-detect a GitHub/Copilot OAuth token from the local machine.
12
+ * Returns `null` if no token can be found.
13
+ */
14
+ export declare function getCopilotToken(): Promise<string | null>;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Auto-detects Copilot / GitHub OAuth credentials from well-known filesystem paths.
3
+ *
4
+ * Checked in order:
5
+ * 1. ~/.copilot/hosts.json (Copilot CLI)
6
+ * 2. ~/.config/github-copilot/hosts.json (Linux/Mac VSCode extension)
7
+ * 3. %APPDATA%\GitHub Copilot\hosts.json (Windows VSCode extension)
8
+ * 4. ~/.config/gh/hosts.yml (GitHub CLI — requires `js-yaml` or manual parse)
9
+ */
10
+ import { readFile } from 'fs/promises';
11
+ import { existsSync } from 'fs';
12
+ import { homedir } from 'os';
13
+ import { join } from 'path';
14
+ async function readJsonToken(filePath) {
15
+ try {
16
+ const raw = await readFile(filePath, 'utf-8');
17
+ const parsed = JSON.parse(raw);
18
+ const ghEntry = parsed['github.com'] ?? parsed['api.github.com'];
19
+ const token = ghEntry?.oauth_token ?? ghEntry?.token ?? null;
20
+ return typeof token === 'string' && token.length > 0 ? token : null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /** Minimal YAML-line parser — only handles `oauth_token: <value>` under a `github.com:` block. */
27
+ async function readYamlToken(filePath) {
28
+ try {
29
+ const raw = await readFile(filePath, 'utf-8');
30
+ let inGithubBlock = false;
31
+ for (const line of raw.split('\n')) {
32
+ if (/^github\.com:/.test(line)) {
33
+ inGithubBlock = true;
34
+ continue;
35
+ }
36
+ if (inGithubBlock) {
37
+ // Exit block on a new top-level key
38
+ if (/^\S/.test(line) &&
39
+ !line.startsWith(' ') &&
40
+ !line.startsWith('\t')) {
41
+ break;
42
+ }
43
+ const match = /oauth_token:\s*(.+)/.exec(line);
44
+ if (match) {
45
+ return match[1].trim();
46
+ }
47
+ }
48
+ }
49
+ }
50
+ catch {
51
+ // Ignore
52
+ }
53
+ return null;
54
+ }
55
+ /**
56
+ * Attempts to auto-detect a GitHub/Copilot OAuth token from the local machine.
57
+ * Returns `null` if no token can be found.
58
+ */
59
+ export async function getCopilotToken() {
60
+ const home = homedir();
61
+ const jsonPaths = [
62
+ join(home, '.copilot', 'hosts.json'),
63
+ join(home, '.config', 'github-copilot', 'hosts.json'),
64
+ ];
65
+ // Windows: %APPDATA%\GitHub Copilot\hosts.json
66
+ if (process.env['APPDATA']) {
67
+ jsonPaths.push(join(process.env['APPDATA'], 'GitHub Copilot', 'hosts.json'));
68
+ }
69
+ for (const p of jsonPaths) {
70
+ if (existsSync(p)) {
71
+ const token = await readJsonToken(p);
72
+ if (token)
73
+ return token;
74
+ }
75
+ }
76
+ // GitHub CLI fallback: ~/.config/gh/hosts.yml
77
+ const yamlPath = join(home, '.config', 'gh', 'hosts.yml');
78
+ if (existsSync(yamlPath)) {
79
+ const token = await readYamlToken(yamlPath);
80
+ if (token)
81
+ return token;
82
+ }
83
+ return null;
84
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Fetches and caches the models list from https://models.dev/api.json
3
+ * Cache TTL is 24 hours, stored at ~/.prompt-enhancer/models-cache.json
4
+ */
5
+ export interface ModelEntry {
6
+ id: string;
7
+ name: string;
8
+ }
9
+ /**
10
+ * Falls back to a hardcoded list if network is unavailable.
11
+ */
12
+ export declare function getModelsForProvider(provider: 'gemini' | 'copilot' | 'kilo' | 'opencode', apiKey?: string): Promise<ModelEntry[]>;