ai-code-connect 1.0.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 +280 -0
- package/dist/adapters/base.d.ts +43 -0
- package/dist/adapters/base.d.ts.map +1 -0
- package/dist/adapters/base.js +28 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude.d.ts +23 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +68 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/gemini.d.ts +24 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +59 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +104 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk-session.d.ts +52 -0
- package/dist/sdk-session.d.ts.map +1 -0
- package/dist/sdk-session.js +1341 -0
- package/dist/sdk-session.js.map +1 -0
- package/dist/utils.d.ts +43 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +138 -0
- package/dist/utils.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# AIC² - AI Code Connect
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
█████╗ ██╗ ██████╗ ^2
|
|
5
|
+
██╔══██╗██║██╔════╝
|
|
6
|
+
███████║██║██║
|
|
7
|
+
██╔══██║██║██║
|
|
8
|
+
██║ ██║██║╚██████╗
|
|
9
|
+
╚═╝ ╚═╝╚═╝ ╚═════╝
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
A CLI tool that connects **Claude Code** and **Gemini CLI**, eliminating manual copy-paste between AI coding assistants.
|
|
13
|
+
|
|
14
|
+
**AIC²** = **A**I **C**ode **C**onnect (the two C's = ²)
|
|
15
|
+
|
|
16
|
+
## The Problem
|
|
17
|
+
|
|
18
|
+
When working with multiple AI coding tools:
|
|
19
|
+
1. Ask Gemini for a proposal
|
|
20
|
+
2. Copy the response
|
|
21
|
+
3. Paste into Claude for review
|
|
22
|
+
4. Copy Claude's feedback
|
|
23
|
+
5. Paste back to Gemini...
|
|
24
|
+
|
|
25
|
+
This is tedious and breaks your flow.
|
|
26
|
+
|
|
27
|
+
## The Solution
|
|
28
|
+
|
|
29
|
+
`aic` bridges both tools in a single interactive session with:
|
|
30
|
+
- **Persistent sessions** - Both tools remember context
|
|
31
|
+
- **One-command forwarding** - Send responses between tools instantly
|
|
32
|
+
- **Interactive mode** - Full access to slash commands and approvals
|
|
33
|
+
- **Detach/reattach** - Keep tools running in background
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Clone and install
|
|
39
|
+
git clone https://github.com/jacob-bd/ai-code-connect.git
|
|
40
|
+
cd ai-code-connect
|
|
41
|
+
npm install
|
|
42
|
+
npm run build
|
|
43
|
+
|
|
44
|
+
# Link globally
|
|
45
|
+
npm link
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Prerequisites
|
|
49
|
+
|
|
50
|
+
Install both AI CLI tools:
|
|
51
|
+
|
|
52
|
+
- **Claude Code**: `npm install -g @anthropic-ai/claude-code`
|
|
53
|
+
- **Gemini CLI**: `npm install -g @google/gemini-cli`
|
|
54
|
+
|
|
55
|
+
Verify:
|
|
56
|
+
```bash
|
|
57
|
+
aic tools
|
|
58
|
+
# Should show both as "✓ available"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
aic
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That's it! This launches the interactive session.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
### Basic Commands
|
|
72
|
+
|
|
73
|
+
| Command | Description |
|
|
74
|
+
|---------|-------------|
|
|
75
|
+
| `/claude` | Switch to Claude Code |
|
|
76
|
+
| `/gemini` | Switch to Gemini CLI |
|
|
77
|
+
| `/i` | Enter interactive mode (full tool access) |
|
|
78
|
+
| `/forward` | Forward last response to other tool (auto-selects if 2 tools) |
|
|
79
|
+
| `/forward [tool]` | Forward to specific tool (required if 3+ tools) |
|
|
80
|
+
| `/forward [tool] [msg]` | Forward with additional context |
|
|
81
|
+
| `/history` | Show conversation history |
|
|
82
|
+
| `/status` | Show running processes |
|
|
83
|
+
| `/clear` | Clear sessions and history |
|
|
84
|
+
| `/quit` or `/cya` | Exit |
|
|
85
|
+
|
|
86
|
+
### Tool Slash Commands
|
|
87
|
+
|
|
88
|
+
Use double slash (`//`) to run tool-specific slash commands:
|
|
89
|
+
|
|
90
|
+
| Input | What Happens |
|
|
91
|
+
|-------|--------------|
|
|
92
|
+
| `//cost` | Opens interactive mode, runs `/cost`, you see output |
|
|
93
|
+
| `//status` | Opens interactive mode, runs `/status`, you can interact |
|
|
94
|
+
| `//config` | Opens interactive mode, runs `/config`, full control |
|
|
95
|
+
|
|
96
|
+
When you type `//command`:
|
|
97
|
+
1. AIC enters interactive mode with the tool
|
|
98
|
+
2. Sends the `/command` for you
|
|
99
|
+
3. You see the full output and can interact
|
|
100
|
+
4. Press `Ctrl+]` when done to return to AIC
|
|
101
|
+
|
|
102
|
+
This approach ensures you can fully view and interact with commands like `/status` that show interactive UIs.
|
|
103
|
+
|
|
104
|
+
### Command Menu
|
|
105
|
+
|
|
106
|
+
Type `/` to see a command menu. Use ↓ arrow to select, or keep typing.
|
|
107
|
+
|
|
108
|
+
### Example Session
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
❯ claude → How should I implement caching for this API?
|
|
112
|
+
|
|
113
|
+
⠹ Claude is thinking...
|
|
114
|
+
I suggest implementing a Redis-based caching layer...
|
|
115
|
+
|
|
116
|
+
❯ claude → /forward What do you think of this approach?
|
|
117
|
+
|
|
118
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
119
|
+
↗ Forwarding from Claude Code → Gemini CLI
|
|
120
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
121
|
+
Gemini CLI responds:
|
|
122
|
+
|
|
123
|
+
The Redis approach is solid. I'd also consider...
|
|
124
|
+
|
|
125
|
+
❯ gemini → /claude
|
|
126
|
+
● Switched to Claude Code
|
|
127
|
+
|
|
128
|
+
❯ claude → Can you implement that?
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Interactive Mode
|
|
132
|
+
|
|
133
|
+
For full tool access (approvals, multi-turn interactions, etc.):
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
❯ claude → /i
|
|
137
|
+
|
|
138
|
+
▶ Starting Claude Code interactive mode...
|
|
139
|
+
Press Ctrl+] to detach • /exit to terminate
|
|
140
|
+
|
|
141
|
+
> (interact with Claude directly)
|
|
142
|
+
> (press Ctrl+]) # Detach back to aic
|
|
143
|
+
|
|
144
|
+
⏸ Detached from Claude Code (still running)
|
|
145
|
+
Use /i to re-attach
|
|
146
|
+
|
|
147
|
+
❯ claude → /i # Re-attach to same session
|
|
148
|
+
↩ Re-attaching to Claude Code...
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Key bindings in interactive mode:**
|
|
152
|
+
- `Ctrl+]` - Detach (tool keeps running)
|
|
153
|
+
- `/exit` - Terminate the tool session
|
|
154
|
+
|
|
155
|
+
> **Tip:** Use `//status` or `//cost` to quickly run tool commands—AIC will enter interactive mode, run the command, and you press `Ctrl+]` when done.
|
|
156
|
+
|
|
157
|
+
> **Note:** Messages exchanged while in interactive mode (after `/i`) are not captured for forwarding. Use regular mode for conversations you want to forward between tools.
|
|
158
|
+
|
|
159
|
+
### Session Persistence
|
|
160
|
+
|
|
161
|
+
Sessions persist automatically:
|
|
162
|
+
- **Claude**: Uses `--continue` flag
|
|
163
|
+
- **Gemini**: Uses `--resume latest` flag
|
|
164
|
+
|
|
165
|
+
Your conversation context is maintained across messages.
|
|
166
|
+
|
|
167
|
+
## CLI Options
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
aic # Launch interactive session
|
|
171
|
+
aic tools # List available AI tools
|
|
172
|
+
aic config default # Show current default tool
|
|
173
|
+
aic config default gemini # Set Gemini as default tool
|
|
174
|
+
aic --version # Show version
|
|
175
|
+
aic --help # Show help
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Configuration
|
|
179
|
+
|
|
180
|
+
### Default Tool
|
|
181
|
+
|
|
182
|
+
Set which tool loads by default when you start AIC²:
|
|
183
|
+
|
|
184
|
+
**Option 1: CLI command**
|
|
185
|
+
```bash
|
|
186
|
+
aic config default gemini
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Option 2: Inside AIC²**
|
|
190
|
+
```
|
|
191
|
+
❯ claude → /default gemini
|
|
192
|
+
✓ Default tool set to "gemini". Will be used on next launch.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Option 3: Environment variable (temporary override)**
|
|
196
|
+
```bash
|
|
197
|
+
AIC_DEFAULT_TOOL=gemini aic
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Configuration is stored in `~/.aic/config.json`.
|
|
201
|
+
|
|
202
|
+
## Architecture
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
src/
|
|
206
|
+
├── adapters/
|
|
207
|
+
│ ├── base.ts # ToolAdapter interface
|
|
208
|
+
│ ├── claude.ts # Claude Code adapter
|
|
209
|
+
│ ├── gemini.ts # Gemini CLI adapter
|
|
210
|
+
│ └── template.ts.example # Template for new adapters
|
|
211
|
+
├── sdk-session.ts # Interactive session logic
|
|
212
|
+
├── index.ts # CLI entry point
|
|
213
|
+
└── utils.ts # Utilities
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Adding New Tools
|
|
217
|
+
|
|
218
|
+
AIC² is modular. To add a new AI CLI (e.g., OpenAI Codex):
|
|
219
|
+
|
|
220
|
+
1. Copy the template: `cp src/adapters/template.ts.example src/adapters/codex.ts`
|
|
221
|
+
2. Implement the `ToolAdapter` interface
|
|
222
|
+
3. Register in `src/adapters/index.ts` and `src/index.ts`
|
|
223
|
+
4. Add to `src/sdk-session.ts`
|
|
224
|
+
|
|
225
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed instructions.
|
|
226
|
+
|
|
227
|
+
## Features
|
|
228
|
+
|
|
229
|
+
- ✅ **Colorful UI** - ASCII banner, colored prompts, status indicators
|
|
230
|
+
- ✅ **Rainbow animations** - Animated rainbow effect on slash commands
|
|
231
|
+
- ✅ **Spinner** - Visual feedback while waiting for responses
|
|
232
|
+
- ✅ **Session persistence** - Context maintained across messages
|
|
233
|
+
- ✅ **Interactive mode** - Full tool access with detach/reattach
|
|
234
|
+
- ✅ **Command menu** - Type `/` for autocomplete suggestions
|
|
235
|
+
- ✅ **Forward responses** - One command to send between tools
|
|
236
|
+
- ✅ **Modular adapters** - Easy to add new AI tools
|
|
237
|
+
|
|
238
|
+
## Development
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Development mode
|
|
242
|
+
npm run dev
|
|
243
|
+
|
|
244
|
+
# Build
|
|
245
|
+
npm run build
|
|
246
|
+
|
|
247
|
+
# Run
|
|
248
|
+
aic
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Testing
|
|
252
|
+
|
|
253
|
+
AIC² uses [Vitest](https://vitest.dev/) for testing.
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# Run tests once
|
|
257
|
+
npm test
|
|
258
|
+
|
|
259
|
+
# Run tests in watch mode (re-runs on file changes)
|
|
260
|
+
npm run test:watch
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### What's Tested
|
|
264
|
+
|
|
265
|
+
| File | Tests | Description |
|
|
266
|
+
|------|-------|-------------|
|
|
267
|
+
| `src/utils.test.ts` | 17 | Pure utility functions: `stripAnsi`, `truncate`, `formatResponse` |
|
|
268
|
+
| `src/config.test.ts` | 18 | Config loading, saving, defaults, environment variable handling |
|
|
269
|
+
|
|
270
|
+
### Adding Tests
|
|
271
|
+
|
|
272
|
+
Test files live alongside source files with a `.test.ts` suffix:
|
|
273
|
+
- `src/utils.ts` → `src/utils.test.ts`
|
|
274
|
+
- `src/config.ts` → `src/config.test.ts`
|
|
275
|
+
|
|
276
|
+
Tests are excluded from the build output (`dist/`) but are committed to git.
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for AI CLI tool adapters
|
|
3
|
+
*/
|
|
4
|
+
export interface SendOptions {
|
|
5
|
+
/** Working directory for the tool */
|
|
6
|
+
cwd?: string;
|
|
7
|
+
/** Whether to continue the previous session (default: true) */
|
|
8
|
+
continueSession?: boolean;
|
|
9
|
+
/** Timeout in milliseconds */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Keep stdin open after command (for interactive sessions) */
|
|
12
|
+
keepStdinOpen?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ToolAdapter {
|
|
15
|
+
/** Unique name identifier for the tool */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
/** Display name for the tool */
|
|
18
|
+
readonly displayName: string;
|
|
19
|
+
/** Check if the tool is installed and available */
|
|
20
|
+
isAvailable(): Promise<boolean>;
|
|
21
|
+
/** Send a prompt to the tool and get a response */
|
|
22
|
+
send(prompt: string, options?: SendOptions): Promise<string>;
|
|
23
|
+
/** Reset conversation context */
|
|
24
|
+
resetContext(): void;
|
|
25
|
+
/** Get the command that would be executed (for debugging) */
|
|
26
|
+
getCommand(prompt: string, options?: SendOptions): string[];
|
|
27
|
+
/** Check if there's an active session with this tool */
|
|
28
|
+
hasSession(): boolean;
|
|
29
|
+
/** Set whether there's an active session (for persistence) */
|
|
30
|
+
setHasSession(value: boolean): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registry of available tool adapters
|
|
34
|
+
*/
|
|
35
|
+
export declare class AdapterRegistry {
|
|
36
|
+
private adapters;
|
|
37
|
+
register(adapter: ToolAdapter): void;
|
|
38
|
+
get(name: string): ToolAdapter | undefined;
|
|
39
|
+
getAll(): ToolAdapter[];
|
|
40
|
+
getNames(): string[];
|
|
41
|
+
getAvailable(): Promise<ToolAdapter[]>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,gCAAgC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,mDAAmD;IACnD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhC,mDAAmD;IACnD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7D,iCAAiC;IACjC,YAAY,IAAI,IAAI,CAAC;IAErB,6DAA6D;IAC7D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,EAAE,CAAC;IAE5D,wDAAwD;IACxD,UAAU,IAAI,OAAO,CAAC;IAEtB,8DAA8D;IAC9D,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAuC;IAEvD,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAIpC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI1C,MAAM,IAAI,WAAW,EAAE;IAIvB,QAAQ,IAAI,MAAM,EAAE;IAId,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;CAS7C"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of available tool adapters
|
|
3
|
+
*/
|
|
4
|
+
export class AdapterRegistry {
|
|
5
|
+
adapters = new Map();
|
|
6
|
+
register(adapter) {
|
|
7
|
+
this.adapters.set(adapter.name, adapter);
|
|
8
|
+
}
|
|
9
|
+
get(name) {
|
|
10
|
+
return this.adapters.get(name);
|
|
11
|
+
}
|
|
12
|
+
getAll() {
|
|
13
|
+
return Array.from(this.adapters.values());
|
|
14
|
+
}
|
|
15
|
+
getNames() {
|
|
16
|
+
return Array.from(this.adapters.keys());
|
|
17
|
+
}
|
|
18
|
+
async getAvailable() {
|
|
19
|
+
const available = [];
|
|
20
|
+
for (const adapter of this.adapters.values()) {
|
|
21
|
+
if (await adapter.isAvailable()) {
|
|
22
|
+
available.push(adapter);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return available;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":"AAwCA;;GAEG;AACH,MAAM,OAAO,eAAe;IAClB,QAAQ,GAA6B,IAAI,GAAG,EAAE,CAAC;IAEvD,QAAQ,CAAC,OAAoB;QAC3B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,MAAM;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,QAAQ;QACN,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,SAAS,GAAkB,EAAE,CAAC;QACpC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,MAAM,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;gBAChC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ToolAdapter, SendOptions } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for Claude Code CLI
|
|
4
|
+
*
|
|
5
|
+
* Claude Code supports:
|
|
6
|
+
* - Non-interactive mode via -p/--print flag
|
|
7
|
+
* - Output formats: text, json, stream-json
|
|
8
|
+
* - Session continuation via -c/--continue or -r/--resume
|
|
9
|
+
*/
|
|
10
|
+
export declare class ClaudeAdapter implements ToolAdapter {
|
|
11
|
+
readonly name = "claude";
|
|
12
|
+
readonly displayName = "Claude Code";
|
|
13
|
+
private hasActiveSession;
|
|
14
|
+
isAvailable(): Promise<boolean>;
|
|
15
|
+
getCommand(prompt: string, options?: SendOptions): string[];
|
|
16
|
+
send(prompt: string, options?: SendOptions): Promise<string>;
|
|
17
|
+
resetContext(): void;
|
|
18
|
+
/** Check if there's an active session */
|
|
19
|
+
hasSession(): boolean;
|
|
20
|
+
/** Mark that a session exists (for loading from persisted state) */
|
|
21
|
+
setHasSession(value: boolean): void;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGrD;;;;;;;GAOG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,WAAW,iBAAiB;IAErC,OAAO,CAAC,gBAAgB,CAAS;IAE3B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAIrC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,EAAE;IA4BrD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAwBlE,YAAY,IAAI,IAAI;IAIpB,yCAAyC;IACzC,UAAU,IAAI,OAAO;IAIrB,oEAAoE;IACpE,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;CAGpC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { runCommandPty, commandExists, stripAnsi } from '../utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for Claude Code CLI
|
|
4
|
+
*
|
|
5
|
+
* Claude Code supports:
|
|
6
|
+
* - Non-interactive mode via -p/--print flag
|
|
7
|
+
* - Output formats: text, json, stream-json
|
|
8
|
+
* - Session continuation via -c/--continue or -r/--resume
|
|
9
|
+
*/
|
|
10
|
+
export class ClaudeAdapter {
|
|
11
|
+
name = 'claude';
|
|
12
|
+
displayName = 'Claude Code';
|
|
13
|
+
hasActiveSession = false;
|
|
14
|
+
async isAvailable() {
|
|
15
|
+
return commandExists('claude');
|
|
16
|
+
}
|
|
17
|
+
getCommand(prompt, options) {
|
|
18
|
+
// For slash commands, run without -p to access Claude's internal commands
|
|
19
|
+
const isSlashCommand = prompt.startsWith('/');
|
|
20
|
+
const args = [];
|
|
21
|
+
if (!isSlashCommand) {
|
|
22
|
+
args.push('-p'); // Print mode for regular prompts
|
|
23
|
+
}
|
|
24
|
+
// Continue previous session if we've already made a call
|
|
25
|
+
const shouldContinue = options?.continueSession !== false && this.hasActiveSession;
|
|
26
|
+
if (shouldContinue) {
|
|
27
|
+
args.push('--continue');
|
|
28
|
+
}
|
|
29
|
+
if (options?.cwd) {
|
|
30
|
+
args.push('--add-dir', options.cwd);
|
|
31
|
+
}
|
|
32
|
+
// Add the prompt as the last argument (only for non-slash commands in print mode)
|
|
33
|
+
if (!isSlashCommand) {
|
|
34
|
+
args.push(prompt);
|
|
35
|
+
}
|
|
36
|
+
return ['claude', ...args];
|
|
37
|
+
}
|
|
38
|
+
async send(prompt, options) {
|
|
39
|
+
const isSlashCommand = prompt.startsWith('/');
|
|
40
|
+
const args = this.getCommand(prompt, options).slice(1); // Remove 'claude' from start
|
|
41
|
+
console.log(''); // Add newline before output
|
|
42
|
+
const result = await runCommandPty('claude', args, {
|
|
43
|
+
cwd: options?.cwd || process.cwd(),
|
|
44
|
+
keepStdinOpen: options?.keepStdinOpen,
|
|
45
|
+
// For slash commands, we'll write the command after Claude starts
|
|
46
|
+
initialInput: isSlashCommand ? prompt + '\n' : undefined,
|
|
47
|
+
});
|
|
48
|
+
if (result.exitCode !== 0 && !isSlashCommand) {
|
|
49
|
+
throw new Error(`Claude Code exited with code ${result.exitCode}`);
|
|
50
|
+
}
|
|
51
|
+
// Mark that we now have an active session
|
|
52
|
+
this.hasActiveSession = true;
|
|
53
|
+
// Return the captured output (strip ANSI for storage, but it was displayed with colors)
|
|
54
|
+
return stripAnsi(result.output).trim();
|
|
55
|
+
}
|
|
56
|
+
resetContext() {
|
|
57
|
+
this.hasActiveSession = false;
|
|
58
|
+
}
|
|
59
|
+
/** Check if there's an active session */
|
|
60
|
+
hasSession() {
|
|
61
|
+
return this.hasActiveSession;
|
|
62
|
+
}
|
|
63
|
+
/** Mark that a session exists (for loading from persisted state) */
|
|
64
|
+
setHasSession(value) {
|
|
65
|
+
this.hasActiveSession = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,OAAO,aAAa;IACf,IAAI,GAAG,QAAQ,CAAC;IAChB,WAAW,GAAG,aAAa,CAAC;IAE7B,gBAAgB,GAAG,KAAK,CAAC;IAEjC,KAAK,CAAC,WAAW;QACf,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED,UAAU,CAAC,MAAc,EAAE,OAAqB;QAC9C,0EAA0E;QAC1E,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAE9C,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,iCAAiC;QACpD,CAAC;QAED,yDAAyD;QACzD,MAAM,cAAc,GAAG,OAAO,EAAE,eAAe,KAAK,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC;QACnF,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,OAAO,EAAE,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;QAED,kFAAkF;QAClF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;QAED,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,OAAqB;QAC9C,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B;QAErF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,4BAA4B;QAE7C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE;YACjD,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;YAClC,aAAa,EAAE,OAAO,EAAE,aAAa;YACrC,kEAAkE;YAClE,YAAY,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS;SACzD,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,gCAAgC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,0CAA0C;QAC1C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAE7B,wFAAwF;QACxF,OAAO,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,YAAY;QACV,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;IAChC,CAAC;IAED,yCAAyC;IACzC,UAAU;QACR,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED,oEAAoE;IACpE,aAAa,CAAC,KAAc;QAC1B,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ToolAdapter, SendOptions } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for Gemini CLI
|
|
4
|
+
*
|
|
5
|
+
* Gemini CLI supports:
|
|
6
|
+
* - Non-interactive mode via positional query argument
|
|
7
|
+
* - Output formats: text, json, stream-json (via -o/--output-format)
|
|
8
|
+
* - Session resume via -r/--resume
|
|
9
|
+
* - YOLO mode via -y/--yolo for auto-approval
|
|
10
|
+
*/
|
|
11
|
+
export declare class GeminiAdapter implements ToolAdapter {
|
|
12
|
+
readonly name = "gemini";
|
|
13
|
+
readonly displayName = "Gemini CLI";
|
|
14
|
+
private hasActiveSession;
|
|
15
|
+
isAvailable(): Promise<boolean>;
|
|
16
|
+
getCommand(prompt: string, options?: SendOptions): string[];
|
|
17
|
+
send(prompt: string, options?: SendOptions): Promise<string>;
|
|
18
|
+
resetContext(): void;
|
|
19
|
+
/** Check if there's an active session */
|
|
20
|
+
hasSession(): boolean;
|
|
21
|
+
/** Mark that a session exists (for loading from persisted state) */
|
|
22
|
+
setHasSession(value: boolean): void;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGrD;;;;;;;;GAQG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,WAAW,gBAAgB;IAEpC,OAAO,CAAC,gBAAgB,CAAS;IAE3B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAIrC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,EAAE;IAmBrD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBlE,YAAY,IAAI,IAAI;IAIpB,yCAAyC;IACzC,UAAU,IAAI,OAAO;IAIrB,oEAAoE;IACpE,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;CAGpC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { runCommandPty, commandExists, stripAnsi } from '../utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for Gemini CLI
|
|
4
|
+
*
|
|
5
|
+
* Gemini CLI supports:
|
|
6
|
+
* - Non-interactive mode via positional query argument
|
|
7
|
+
* - Output formats: text, json, stream-json (via -o/--output-format)
|
|
8
|
+
* - Session resume via -r/--resume
|
|
9
|
+
* - YOLO mode via -y/--yolo for auto-approval
|
|
10
|
+
*/
|
|
11
|
+
export class GeminiAdapter {
|
|
12
|
+
name = 'gemini';
|
|
13
|
+
displayName = 'Gemini CLI';
|
|
14
|
+
hasActiveSession = false;
|
|
15
|
+
async isAvailable() {
|
|
16
|
+
return commandExists('gemini');
|
|
17
|
+
}
|
|
18
|
+
getCommand(prompt, options) {
|
|
19
|
+
const args = [];
|
|
20
|
+
// Resume previous session if we've already made a call
|
|
21
|
+
const shouldContinue = options?.continueSession !== false && this.hasActiveSession;
|
|
22
|
+
if (shouldContinue) {
|
|
23
|
+
args.push('--resume', 'latest');
|
|
24
|
+
}
|
|
25
|
+
if (options?.cwd) {
|
|
26
|
+
args.push('--include-directories', options.cwd);
|
|
27
|
+
}
|
|
28
|
+
// Add the prompt as the last argument (positional)
|
|
29
|
+
args.push(prompt);
|
|
30
|
+
return ['gemini', ...args];
|
|
31
|
+
}
|
|
32
|
+
async send(prompt, options) {
|
|
33
|
+
const args = this.getCommand(prompt, options).slice(1); // Remove 'gemini' from start
|
|
34
|
+
console.log(''); // Add newline before output
|
|
35
|
+
const result = await runCommandPty('gemini', args, {
|
|
36
|
+
cwd: options?.cwd || process.cwd(),
|
|
37
|
+
keepStdinOpen: options?.keepStdinOpen,
|
|
38
|
+
});
|
|
39
|
+
if (result.exitCode !== 0) {
|
|
40
|
+
throw new Error(`Gemini CLI exited with code ${result.exitCode}`);
|
|
41
|
+
}
|
|
42
|
+
// Mark that we now have an active session
|
|
43
|
+
this.hasActiveSession = true;
|
|
44
|
+
// Return the captured output (strip ANSI for storage, but it was displayed with colors)
|
|
45
|
+
return stripAnsi(result.output).trim();
|
|
46
|
+
}
|
|
47
|
+
resetContext() {
|
|
48
|
+
this.hasActiveSession = false;
|
|
49
|
+
}
|
|
50
|
+
/** Check if there's an active session */
|
|
51
|
+
hasSession() {
|
|
52
|
+
return this.hasActiveSession;
|
|
53
|
+
}
|
|
54
|
+
/** Mark that a session exists (for loading from persisted state) */
|
|
55
|
+
setHasSession(value) {
|
|
56
|
+
this.hasActiveSession = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAa;IACf,IAAI,GAAG,QAAQ,CAAC;IAChB,WAAW,GAAG,YAAY,CAAC;IAE5B,gBAAgB,GAAG,KAAK,CAAC;IAEjC,KAAK,CAAC,WAAW;QACf,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED,UAAU,CAAC,MAAc,EAAE,OAAqB;QAC9C,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,uDAAuD;QACvD,MAAM,cAAc,GAAG,OAAO,EAAE,eAAe,KAAK,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC;QACnF,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,OAAO,EAAE,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;QAED,mDAAmD;QACnD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAElB,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,OAAqB;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B;QAErF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,4BAA4B;QAE7C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE;YACjD,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;YAClC,aAAa,EAAE,OAAO,EAAE,aAAa;SACtC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,0CAA0C;QAC1C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAE7B,wFAAwF;QACxF,OAAO,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,YAAY;QACV,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;IAChC,CAAC;IAED,yCAAyC;IACzC,UAAU;QACR,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED,oEAAoE;IACpE,aAAa,CAAC,KAAc;QAC1B,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,eAAe,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface ToolConfig {
|
|
2
|
+
command?: string;
|
|
3
|
+
defaultFlags?: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface Config {
|
|
6
|
+
defaultTool: string;
|
|
7
|
+
tools: {
|
|
8
|
+
[name: string]: ToolConfig;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the config directory path
|
|
13
|
+
*/
|
|
14
|
+
export declare function getConfigDir(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Get the config file path
|
|
17
|
+
*/
|
|
18
|
+
export declare function getConfigPath(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Ensure the config directory exists
|
|
21
|
+
*/
|
|
22
|
+
export declare function ensureConfigDir(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Load configuration from disk, or return defaults
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadConfig(): Config;
|
|
27
|
+
/**
|
|
28
|
+
* Save configuration to disk
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveConfig(config: Config): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get the default tool (checks env var first, then config)
|
|
33
|
+
*/
|
|
34
|
+
export declare function getDefaultTool(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Set the default tool and save to config
|
|
37
|
+
*/
|
|
38
|
+
export declare function setDefaultTool(tool: string): {
|
|
39
|
+
success: boolean;
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QACL,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;KAC5B,CAAC;CACH;AAgBD;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAsBnC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAI/C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAUvC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAmBlF"}
|