@korl3one/ccode 2.3.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,37 +30,39 @@ npm install -g @korl3one/ccode
30
30
 
31
31
  Every time you switch sessions, models, or tools when working with AI, **you lose your project context**. You end up re-explaining the architecture, previous decisions, and current state over and over again.
32
32
 
33
- ## The solution
33
+ And if you use multiple AI tools — Claude Code, Cursor, Gemini CLI, Copilot — you have to configure context files for each one manually.
34
34
 
35
- CCODE stores your project context **inside the repository**. One command generates professional documentation, architecture, rules, and a verifiable task checklist — all adapted to your project's actual complexity.
35
+ ## The solution
36
36
 
37
- Any developer or AI can read `.ccode/` and understand the project instantly.
37
+ CCODE generates and maintains your project context **inside the repository**. One command produces professional documentation, architecture, rules, and a verifiable task checklist — all adapted to your project's actual complexity.
38
38
 
39
- ---
39
+ Then it **syncs that context to every major AI tool** automatically.
40
40
 
41
- ## Demo
41
+ Any developer or AI can read your project and understand it instantly.
42
42
 
43
- ### `ccode init` — Generate full project context
44
-
45
- <div align="center">
46
- <img src="assets/demo-init.svg" alt="ccode init demo" width="700"/>
47
- </div>
43
+ ---
48
44
 
49
- <br/>
45
+ ## Universal Context Sync
50
46
 
51
- ### Persistent session CCODE watches while you code
47
+ This is what makes CCODE different. One command generates context files for **every major AI tool**:
52
48
 
53
- <div align="center">
54
- <img src="assets/demo-session.svg" alt="ccode session demo" width="700"/>
55
- </div>
49
+ ```bash
50
+ ccode sync
51
+ ```
56
52
 
57
- <br/>
53
+ ```
54
+ project/
55
+ ├── AGENTS.md ← Open Standard (60K+ repos)
56
+ ├── CLAUDE.md ← Claude Code
57
+ ├── GEMINI.md ← Gemini CLI
58
+ ├── .cursorrules ← Cursor
59
+ ├── .github/copilot-instructions.md ← GitHub Copilot
60
+ └── .ccode/context-export.md ← Universal (copy/paste to any AI chat)
61
+ ```
58
62
 
59
- ### `ccode doctor`Project health check
63
+ Each file is adapted to the tool's expected format. Your project context architecture, rules, tasks, decisions — synced everywhere, from a single source of truth.
60
64
 
61
- <div align="center">
62
- <img src="assets/demo-doctor.svg" alt="ccode doctor demo" width="700"/>
63
- </div>
65
+ No manual setup per tool. No copy-pasting between files. Context stays in sync automatically with `ccode init`, `ccode update`, and `ccode sync`.
64
66
 
65
67
  ---
66
68
 
@@ -71,12 +73,13 @@ Any developer or AI can read `.ccode/` and understand the project instantly.
71
73
  | | CCODE | Manual prompts |
72
74
  |---|:---:|:---:|
73
75
  | Persistent project context | ✅ | ❌ |
76
+ | Universal context sync (5+ AI tools) | ✅ | ❌ |
74
77
  | Architecture adapted to complexity | ✅ | ❌ |
75
78
  | AI-ready documentation | ✅ | ❌ |
76
79
  | Verifiable task checklist | ✅ | ❌ |
77
80
  | Auto-detect file changes | ✅ | ❌ |
78
81
  | AI-powered task verification | ✅ | ❌ |
79
- | Works with 6 AI providers | ✅ | ❌ |
82
+ | Zero-config AI provider detection | ✅ | ❌ |
80
83
  | Context lives in the repo (Git) | ✅ | ❌ |
81
84
 
82
85
  </div>
@@ -102,6 +105,8 @@ A step-by-step wizard asks about your project and an AI generates the full conte
102
105
 
103
106
  A simple login doesn't need microservice diagrams. CCODE is smart about it.
104
107
 
108
+ After generation, context is automatically synced to all AI tools.
109
+
105
110
  ### 2. Persistent session
106
111
 
107
112
  After init, CCODE **stays active** — watching your project in real time:
@@ -129,21 +134,29 @@ CCODE compares **acceptance criteria** against **actual project files**:
129
134
 
130
135
  It doesn't guess — it verifies.
131
136
 
132
- ### 4. Context export
137
+ ### 4. Context sync
138
+
139
+ ```bash
140
+ ccode sync
141
+ ```
142
+
143
+ Regenerates context files for every AI tool. Run it after making significant changes, or let `ccode update` handle it when you re-analyze the project.
144
+
145
+ ### 5. Context export
133
146
 
134
147
  ```bash
135
148
  ccode export
136
149
  ```
137
150
 
138
- Generates a single `.md` file with your full project context ready to paste into **any AI chat** (ChatGPT, Claude, Gemini) without connecting an API.
151
+ Three options: sync to all tools, export a universal `.md` for copy/paste into any AI chat, or pick specific tools.
139
152
 
140
- ### 5. Project health check
153
+ ### 6. Project health check
141
154
 
142
155
  ```bash
143
156
  ccode doctor
144
157
  ```
145
158
 
146
- Like a linter, but for your project context. Checks files, AI connection, task status, and tells you what needs attention.
159
+ Like a linter, but for your project context. Checks context files, AI connection, task status, export state, and tells you what needs attention.
147
160
 
148
161
  ---
149
162
 
@@ -153,35 +166,41 @@ Like a linter, but for your project context. Checks files, AI connection, task s
153
166
 
154
167
  | Provider | Models | Note |
155
168
  |----------|--------|------|
156
- | **Claude** (Anthropic) | Sonnet 4, Haiku 3.5, Opus 4 | Recommended |
157
- | **OpenAI** (ChatGPT) | GPT-4o, GPT-4o mini, GPT-4.1, o3-mini | Most popular |
158
- | **Google Gemini** | 2.5 Flash, 2.5 Pro, 2.0 Flash | Free tier available |
159
- | **DeepSeek** | Chat, Reasoner | Budget-friendly |
160
- | **Groq** | Llama 3.3 70B, Llama 3.1 8B, Mixtral | Ultra-fast, free tier |
161
- | **Ollama** | Any local model | Offline, no API key |
169
+ | **Google Gemini** | 2.5 Flash, 2.5 Pro, 2.0 Flash | Free — just a Google account |
170
+ | **Claude** (Anthropic) | Sonnet 4, Haiku 3.5, Opus 4 | Best quality |
162
171
 
163
172
  </div>
164
173
 
174
+ CCODE auto-detects your provider:
175
+
176
+ 1. **Gemini CLI OAuth** — If you have `gemini` CLI installed and authenticated, it works instantly. Zero config.
177
+ 2. **Environment variables** — `GOOGLE_API_KEY` or `ANTHROPIC_API_KEY` detected automatically.
178
+ 3. **Manual setup** — Guided wizard with browser-based key generation as fallback.
179
+
165
180
  ---
166
181
 
167
182
  ## Available commands
168
183
 
169
184
  | Command | What it does |
170
185
  |---------|-------------|
171
- | `ccode init` | Interactive wizard — generates full project context |
172
- | `ccode update` | Re-analyze project and refresh context with AI |
173
- | `ccode export` | Export context as a single `.md` for any AI chat |
174
- | `ccode explain` | Quick project summary for onboarding |
175
- | `ccode doctor` | Health check what's good, what's missing |
176
- | `ccode connect` | Configure AI provider and model |
186
+ | `ccode init` | Interactive wizard — generates full project context + syncs to all AI tools |
187
+ | `ccode sync` | Sync context to all AI tools (AGENTS.md, CLAUDE.md, .cursorrules, ...) |
188
+ | `ccode update` | Re-analyze project with AI and refresh context |
189
+ | `ccode export` | Export context all tools, universal .md, or pick specific |
190
+ | `ccode verify` | AI-powered task verification against actual project files |
177
191
  | `ccode status` | Dashboard with progress bar and stats |
178
- | `ccode verify` | AI-powered task verification |
192
+ | `ccode doctor` | Health check — context files, AI connection, exports, tasks |
193
+ | `ccode connect` | Configure or reconfigure AI provider |
194
+ | `ccode explain` | Quick project summary for onboarding |
195
+ | `ccode plan` | Generate or regenerate task checklist |
196
+ | `ccode next` | Show and start the next pending task |
197
+ | `ccode complete` | Mark active task as completed |
179
198
 
180
199
  ---
181
200
 
182
201
  ## What gets generated
183
202
 
184
- Everything lives in `.ccode/` inside your repository:
203
+ ### Source of truth (`.ccode/`)
185
204
 
186
205
  ```
187
206
  .ccode/
@@ -192,8 +211,21 @@ Everything lives in `.ccode/` inside your repository:
192
211
  ├── state.json Active task, workflow stage
193
212
  ├── context.json Project configuration
194
213
  ├── memory.md Decision history
195
- └── config.json AI provider config
214
+ ├── config.json AI provider config
215
+ └── context-export.md Universal export
216
+ ```
217
+
218
+ ### Synced context files (project root)
219
+
196
220
  ```
221
+ AGENTS.md Open Standard
222
+ CLAUDE.md Claude Code
223
+ GEMINI.md Gemini CLI
224
+ .cursorrules Cursor
225
+ .github/copilot-instructions.md GitHub Copilot
226
+ ```
227
+
228
+ All derived from `.ccode/`. One source, multiple outputs.
197
229
 
198
230
  ---
199
231
 
@@ -202,8 +234,8 @@ Everything lives in `.ccode/` inside your repository:
202
234
  ```
203
235
  src/
204
236
  ├── cli/ Session, branding, file watcher
205
- ├── core/ Context engine, tasks, prompt builder
206
- ├── ai/ 6 provider adapters (Adapter pattern)
237
+ ├── core/ Context engine, tasks, prompt builder, exports
238
+ ├── ai/ Provider adapters (Adapter pattern)
207
239
  └── utils/ File system abstraction
208
240
  ```
209
241
 
@@ -215,7 +247,11 @@ src/
215
247
 
216
248
  See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions.
217
249
 
218
- Adding a new AI provider? Just implement `IAIProvider`, add it to the manager switch, done. Zero changes to the rest of the system.
250
+ Adding a new AI provider? Implement `IAIProvider`, add it to the manager, done.
251
+
252
+ Adding a new export format? Add a method to `ContextExporter`, register the format, done.
253
+
254
+ Zero changes to the rest of the system in both cases.
219
255
 
220
256
  ---
221
257
 
@@ -223,16 +259,15 @@ Adding a new AI provider? Just implement `IAIProvider`, add it to the manager sw
223
259
 
224
260
  | Resource | Link |
225
261
  |----------|------|
226
- | Learning guide (6 modules, QP2C) | [docs/learning/](docs/learning/README.md) |
227
262
  | Engineering roles | [AGENTS.md](AGENTS.md) |
228
263
  | Technical competencies | [SKILLS.md](SKILLS.md) |
229
- | YouTube tutorials | [@CreativeCode25](https://www.youtube.com/@CreativeCode25) |
264
+ | YouTube | [@CreativeCode25](https://www.youtube.com/@CreativeCode25) |
230
265
 
231
266
  ---
232
267
 
233
268
  <div align="center">
234
269
 
235
- ### If CCODE helps you, consider giving it a star
270
+ ### If CCODE helps you, consider giving it a star
236
271
 
237
272
  It helps the project grow and reach more developers.
238
273
 
@@ -3,7 +3,8 @@ import { IAIProvider, IAIConfig } from './provider.js';
3
3
  * Adaptador para Claude (Anthropic).
4
4
  */
5
5
  export declare class ClaudeAdapter implements IAIProvider {
6
- private config;
6
+ private apiKey;
7
+ private model;
7
8
  constructor(config: IAIConfig);
8
9
  getName(): string;
9
10
  generate(prompt: string): Promise<string>;
package/dist/ai/claude.js CHANGED
@@ -3,32 +3,35 @@ import axios from 'axios';
3
3
  * Adaptador para Claude (Anthropic).
4
4
  */
5
5
  export class ClaudeAdapter {
6
- config;
6
+ apiKey;
7
+ model;
7
8
  constructor(config) {
8
- this.config = {
9
- apiKey: config.apiKey || '',
10
- model: config.model || 'claude-sonnet-4-20250514',
11
- baseUrl: config.baseUrl || 'https://api.anthropic.com/v1/messages',
12
- };
9
+ this.apiKey = config.apiKey || '';
10
+ this.model = config.model || 'claude-sonnet-4-20250514';
13
11
  }
14
12
  getName() {
15
- return `Claude (${this.config.model})`;
13
+ return `Claude (${this.model})`;
16
14
  }
17
15
  async generate(prompt) {
18
- if (!this.config.apiKey) {
16
+ if (!this.apiKey) {
19
17
  throw new Error('API Key de Anthropic no configurada.');
20
18
  }
21
- const response = await axios.post(this.config.baseUrl, {
22
- model: this.config.model,
19
+ const response = await axios.post('https://api.anthropic.com/v1/messages', {
20
+ model: this.model,
23
21
  max_tokens: 8096,
24
22
  messages: [{ role: 'user', content: prompt }],
25
23
  }, {
26
24
  headers: {
27
- 'x-api-key': this.config.apiKey,
25
+ 'x-api-key': this.apiKey,
28
26
  'anthropic-version': '2023-06-01',
29
27
  'content-type': 'application/json',
30
28
  },
29
+ timeout: 120000,
31
30
  });
32
- return response.data.content[0].text;
31
+ const text = response.data?.content?.[0]?.text;
32
+ if (!text) {
33
+ throw new Error('Claude respondio pero sin contenido. Intenta de nuevo.');
34
+ }
35
+ return text;
33
36
  }
34
37
  }
@@ -1,10 +1,22 @@
1
1
  import { IAIProvider, IAIConfig } from './provider.js';
2
2
  /**
3
3
  * Adaptador para Google Gemini.
4
+ * Soporta API Key y OAuth token (de Gemini CLI).
4
5
  */
5
6
  export declare class GeminiAdapter implements IAIProvider {
6
- private config;
7
+ private apiKey;
8
+ private oauthToken;
9
+ private model;
7
10
  constructor(config: IAIConfig);
8
11
  getName(): string;
12
+ /**
13
+ * Try to read OAuth token from Gemini CLI installation.
14
+ */
15
+ static readOAuthToken(): string | null;
16
+ /**
17
+ * Check if Gemini CLI is installed and authenticated.
18
+ */
19
+ static isAvailable(): boolean;
20
+ setOAuthToken(token: string): void;
9
21
  generate(prompt: string): Promise<string>;
10
22
  }
package/dist/ai/gemini.js CHANGED
@@ -1,31 +1,78 @@
1
1
  import axios from 'axios';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
2
5
  /**
3
6
  * Adaptador para Google Gemini.
7
+ * Soporta API Key y OAuth token (de Gemini CLI).
4
8
  */
5
9
  export class GeminiAdapter {
6
- config;
10
+ apiKey;
11
+ oauthToken;
12
+ model;
7
13
  constructor(config) {
8
- this.config = {
9
- apiKey: config.apiKey || '',
10
- model: config.model || 'gemini-2.5-flash',
11
- baseUrl: config.baseUrl || 'https://generativelanguage.googleapis.com/v1beta/models',
12
- };
14
+ this.apiKey = config.apiKey || '';
15
+ this.oauthToken = '';
16
+ this.model = config.model || 'gemini-2.5-flash';
13
17
  }
14
18
  getName() {
15
- return `Gemini (${this.config.model})`;
19
+ return `Gemini (${this.model})`;
20
+ }
21
+ /**
22
+ * Try to read OAuth token from Gemini CLI installation.
23
+ */
24
+ static readOAuthToken() {
25
+ try {
26
+ const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
27
+ if (!fs.existsSync(credsPath))
28
+ return null;
29
+ const creds = JSON.parse(fs.readFileSync(credsPath, 'utf-8'));
30
+ if (creds.access_token && typeof creds.access_token === 'string') {
31
+ return creds.access_token;
32
+ }
33
+ }
34
+ catch {
35
+ // ignore
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Check if Gemini CLI is installed and authenticated.
41
+ */
42
+ static isAvailable() {
43
+ return GeminiAdapter.readOAuthToken() !== null;
44
+ }
45
+ setOAuthToken(token) {
46
+ this.oauthToken = token;
16
47
  }
17
48
  async generate(prompt) {
18
- if (!this.config.apiKey) {
19
- throw new Error('API Key de Google AI no configurada.');
49
+ const baseUrl = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent`;
50
+ let url;
51
+ let headers;
52
+ if (this.oauthToken) {
53
+ // Use OAuth token from Gemini CLI
54
+ url = baseUrl;
55
+ headers = {
56
+ 'Content-Type': 'application/json',
57
+ 'Authorization': `Bearer ${this.oauthToken}`,
58
+ };
59
+ }
60
+ else if (this.apiKey) {
61
+ // Use API key
62
+ url = `${baseUrl}?key=${this.apiKey}`;
63
+ headers = { 'Content-Type': 'application/json' };
64
+ }
65
+ else {
66
+ throw new Error('No hay credenciales de Gemini configuradas.');
20
67
  }
21
- const url = `${this.config.baseUrl}/${this.config.model}:generateContent?key=${this.config.apiKey}`;
22
68
  const response = await axios.post(url, {
23
69
  contents: [{ parts: [{ text: prompt }] }],
24
70
  generationConfig: { maxOutputTokens: 8096 },
25
- }, {
26
- headers: { 'Content-Type': 'application/json' },
27
- timeout: 120000,
28
- });
29
- return response.data.candidates[0].content.parts[0].text;
71
+ }, { headers, timeout: 120000 });
72
+ const text = response.data?.candidates?.[0]?.content?.parts?.[0]?.text;
73
+ if (!text) {
74
+ throw new Error('Gemini respondio pero sin contenido. Intenta de nuevo.');
75
+ }
76
+ return text;
30
77
  }
31
78
  }
@@ -1,18 +1,37 @@
1
1
  import { IAIProvider } from './provider.js';
2
- export type ProviderName = 'claude' | 'openai' | 'gemini' | 'deepseek' | 'groq' | 'ollama';
2
+ export type ProviderName = 'claude' | 'gemini';
3
3
  export interface ICCODEConfig {
4
4
  provider: ProviderName;
5
5
  apiKey?: string;
6
6
  model?: string;
7
- baseUrl?: string;
7
+ authType?: 'api-key' | 'oauth';
8
8
  }
9
- /**
10
- * Gestiona la configuración y conexión con proveedores de IA.
11
- */
9
+ export declare const PROVIDER_INFO: Record<ProviderName, {
10
+ name: string;
11
+ keyUrl: string;
12
+ envVars: string[];
13
+ models: Array<{
14
+ name: string;
15
+ value: string;
16
+ }>;
17
+ }>;
12
18
  export declare class AIManager {
13
19
  private static readonly CONFIG_FILE;
14
20
  static loadConfig(): Promise<ICCODEConfig | null>;
15
21
  static saveConfig(config: ICCODEConfig): Promise<void>;
16
22
  static getProvider(config: ICCODEConfig): IAIProvider;
17
- static testConnection(config: ICCODEConfig): Promise<boolean>;
23
+ static testConnection(config: ICCODEConfig): Promise<{
24
+ ok: boolean;
25
+ error?: string;
26
+ }>;
27
+ /**
28
+ * Auto-detect the best available provider.
29
+ * Priority: 1) Gemini CLI OAuth, 2) env vars, 3) null
30
+ */
31
+ static autoDetect(): {
32
+ provider: ProviderName;
33
+ authType: 'oauth' | 'api-key';
34
+ apiKey?: string;
35
+ source: string;
36
+ } | null;
18
37
  }
@@ -1,14 +1,30 @@
1
1
  import * as path from 'path';
2
+ import axios from 'axios';
2
3
  import { ClaudeAdapter } from './claude.js';
3
- import { OpenAIAdapter } from './openai.js';
4
4
  import { GeminiAdapter } from './gemini.js';
5
- import { DeepSeekAdapter } from './deepseek.js';
6
- import { GroqAdapter } from './groq.js';
7
- import { OllamaAdapter } from './ollama.js';
8
5
  import { FileUtils } from '../utils/files.js';
9
- /**
10
- * Gestiona la configuración y conexión con proveedores de IA.
11
- */
6
+ export const PROVIDER_INFO = {
7
+ gemini: {
8
+ name: 'Google Gemini',
9
+ keyUrl: 'https://aistudio.google.com/apikey',
10
+ envVars: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
11
+ models: [
12
+ { name: 'Gemini 2.5 Flash (recomendado, gratis)', value: 'gemini-2.5-flash' },
13
+ { name: 'Gemini 2.5 Pro (maxima calidad)', value: 'gemini-2.5-pro' },
14
+ { name: 'Gemini 2.0 Flash (rapido)', value: 'gemini-2.0-flash' },
15
+ ],
16
+ },
17
+ claude: {
18
+ name: 'Claude (Anthropic)',
19
+ keyUrl: 'https://console.anthropic.com/settings/keys',
20
+ envVars: ['ANTHROPIC_API_KEY'],
21
+ models: [
22
+ { name: 'Claude Sonnet 4 (recomendado)', value: 'claude-sonnet-4-20250514' },
23
+ { name: 'Claude Haiku 3.5 (rapido)', value: 'claude-haiku-4-5-20251001' },
24
+ { name: 'Claude Opus 4 (maxima calidad)', value: 'claude-opus-4-20250514' },
25
+ ],
26
+ },
27
+ };
12
28
  export class AIManager {
13
29
  static CONFIG_FILE = '.ccode/config.json';
14
30
  static async loadConfig() {
@@ -24,18 +40,18 @@ export class AIManager {
24
40
  }
25
41
  static getProvider(config) {
26
42
  switch (config.provider) {
43
+ case 'gemini': {
44
+ const adapter = new GeminiAdapter({ apiKey: config.apiKey, model: config.model });
45
+ // If using OAuth from Gemini CLI
46
+ if (config.authType === 'oauth') {
47
+ const token = GeminiAdapter.readOAuthToken();
48
+ if (token)
49
+ adapter.setOAuthToken(token);
50
+ }
51
+ return adapter;
52
+ }
27
53
  case 'claude':
28
54
  return new ClaudeAdapter({ apiKey: config.apiKey, model: config.model });
29
- case 'openai':
30
- return new OpenAIAdapter({ apiKey: config.apiKey, model: config.model });
31
- case 'gemini':
32
- return new GeminiAdapter({ apiKey: config.apiKey, model: config.model });
33
- case 'deepseek':
34
- return new DeepSeekAdapter({ apiKey: config.apiKey, model: config.model });
35
- case 'groq':
36
- return new GroqAdapter({ apiKey: config.apiKey, model: config.model });
37
- case 'ollama':
38
- return new OllamaAdapter({ model: config.model, baseUrl: config.baseUrl });
39
55
  default:
40
56
  throw new Error(`Proveedor desconocido: ${config.provider}`);
41
57
  }
@@ -43,11 +59,57 @@ export class AIManager {
43
59
  static async testConnection(config) {
44
60
  try {
45
61
  const provider = this.getProvider(config);
46
- await provider.generate('Responde únicamente con la palabra "ok".');
47
- return true;
62
+ const result = await provider.generate('Responde unicamente con la palabra "ok".');
63
+ if (!result || result.trim().length === 0) {
64
+ return { ok: false, error: 'La IA respondio pero el contenido esta vacio.' };
65
+ }
66
+ return { ok: true };
48
67
  }
49
- catch {
50
- return false;
68
+ catch (err) {
69
+ if (axios.isAxiosError(err)) {
70
+ const status = err.response?.status;
71
+ const data = err.response?.data;
72
+ if (status === 401 || status === 403) {
73
+ return { ok: false, error: `Credenciales invalidas (HTTP ${status}). Verifica tu autenticacion.` };
74
+ }
75
+ if (status === 404) {
76
+ return { ok: false, error: 'Modelo no encontrado. Verifica el nombre del modelo.' };
77
+ }
78
+ if (status === 429) {
79
+ return { ok: false, error: 'Rate limit. Espera unos segundos e intenta de nuevo.' };
80
+ }
81
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
82
+ return { ok: false, error: 'Sin conexion a internet.' };
83
+ }
84
+ if (err.code === 'ETIMEDOUT' || err.message?.includes('timeout')) {
85
+ return { ok: false, error: 'Timeout: la IA no respondio a tiempo.' };
86
+ }
87
+ const msg = typeof data === 'object' && data?.error?.message
88
+ ? data.error.message
89
+ : `Error HTTP ${status || 'desconocido'}`;
90
+ return { ok: false, error: msg };
91
+ }
92
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
51
93
  }
52
94
  }
95
+ /**
96
+ * Auto-detect the best available provider.
97
+ * Priority: 1) Gemini CLI OAuth, 2) env vars, 3) null
98
+ */
99
+ static autoDetect() {
100
+ // 1. Gemini CLI installed and authenticated
101
+ if (GeminiAdapter.isAvailable()) {
102
+ return { provider: 'gemini', authType: 'oauth', source: 'Gemini CLI' };
103
+ }
104
+ // 2. Environment variables
105
+ for (const [provider, info] of Object.entries(PROVIDER_INFO)) {
106
+ for (const envVar of info.envVars) {
107
+ const value = process.env[envVar];
108
+ if (value && value.length > 10) {
109
+ return { provider: provider, authType: 'api-key', apiKey: value, source: envVar };
110
+ }
111
+ }
112
+ }
113
+ return null;
114
+ }
53
115
  }
package/dist/cli/brand.js CHANGED
@@ -27,7 +27,7 @@ export function showLogo() {
27
27
  LOGO_LINES.forEach((line, i) => {
28
28
  console.log(chalk.hex(GRADIENT[i])(line));
29
29
  });
30
- console.log(c.dim(' Context-Persistent AI Development v2.0'));
30
+ console.log(c.dim(' Context-Persistent AI Development v3.0'));
31
31
  console.log('');
32
32
  }
33
33
  // ─── Componentes UI ─────────────────────────────────────────────────