@ktmcp-cli/airbyte 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/AGENT.md +48 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/bin/airbyte.js +2 -0
- package/package.json +27 -0
- package/src/api.js +181 -0
- package/src/config.js +19 -0
- package/src/index.js +421 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Airbyte CLI - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
This CLI provides programmatic access to the Airbyte Data Integration API.
|
|
4
|
+
|
|
5
|
+
## Quick Start for AI Agents
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
airbyte config set --api-key YOUR_API_KEY
|
|
9
|
+
airbyte config set --workspace-id YOUR_WORKSPACE_ID
|
|
10
|
+
airbyte connections list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Commands
|
|
14
|
+
|
|
15
|
+
### config
|
|
16
|
+
- `airbyte config set --api-key <key>` - Set API key
|
|
17
|
+
- `airbyte config set --base-url <url>` - Set API base URL
|
|
18
|
+
- `airbyte config set --workspace-id <id>` - Set default workspace ID
|
|
19
|
+
- `airbyte config get <key>` - Get a config value
|
|
20
|
+
- `airbyte config list` - List all config values
|
|
21
|
+
|
|
22
|
+
### connections
|
|
23
|
+
- `airbyte connections list` - List all connections
|
|
24
|
+
- `airbyte connections list --workspace-id <id>` - List connections in workspace
|
|
25
|
+
- `airbyte connections get <connection-id>` - Get connection details
|
|
26
|
+
- `airbyte connections create --source-id <id> --destination-id <id>` - Create connection
|
|
27
|
+
|
|
28
|
+
### sources
|
|
29
|
+
- `airbyte sources list` - List all sources
|
|
30
|
+
- `airbyte sources get <source-id>` - Get source details
|
|
31
|
+
- `airbyte sources create --workspace-id <id> --name <name> --definition-id <id>` - Create source
|
|
32
|
+
|
|
33
|
+
### destinations
|
|
34
|
+
- `airbyte destinations list` - List all destinations
|
|
35
|
+
- `airbyte destinations get <destination-id>` - Get destination details
|
|
36
|
+
- `airbyte destinations create --workspace-id <id> --name <name> --definition-id <id>` - Create destination
|
|
37
|
+
|
|
38
|
+
### workspaces
|
|
39
|
+
- `airbyte workspaces list` - List all workspaces
|
|
40
|
+
|
|
41
|
+
## Output Format
|
|
42
|
+
|
|
43
|
+
All commands output formatted tables by default. Use `--json` flag for machine-readable JSON output.
|
|
44
|
+
|
|
45
|
+
## Authentication
|
|
46
|
+
|
|
47
|
+
This CLI supports Airbyte API key authentication. Configure your API key with `airbyte config set --api-key`.
|
|
48
|
+
Default API base URL is http://localhost:8006/api for self-hosted Airbyte instances.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KTMCP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
> "Six months ago, everyone was talking about MCPs. And I was like, screw MCPs. Every MCP would be better as a CLI."
|
|
2
|
+
>
|
|
3
|
+
> — [Peter Steinberger](https://twitter.com/steipete), Founder of OpenClaw
|
|
4
|
+
> [Watch on YouTube (~2:39:00)](https://www.youtube.com/@lexfridman) | [Lex Fridman Podcast #491](https://lexfridman.com/peter-steinberger/)
|
|
5
|
+
|
|
6
|
+
# Airbyte CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for Airbyte Data Integration API.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @ktmcp-cli/airbyte
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
airbyte config set --api-key YOUR_API_KEY
|
|
20
|
+
airbyte config set --base-url http://localhost:8006/api
|
|
21
|
+
airbyte config set --workspace-id YOUR_WORKSPACE_ID
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Connections
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# List all connections
|
|
30
|
+
airbyte connections list
|
|
31
|
+
airbyte connections list --workspace-id <id>
|
|
32
|
+
|
|
33
|
+
# Get connection details
|
|
34
|
+
airbyte connections get <connection-id>
|
|
35
|
+
|
|
36
|
+
# Create a new connection
|
|
37
|
+
airbyte connections create \
|
|
38
|
+
--source-id <source-id> \
|
|
39
|
+
--destination-id <destination-id> \
|
|
40
|
+
--name "My Pipeline" \
|
|
41
|
+
--schedule manual
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Sources
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# List all sources
|
|
48
|
+
airbyte sources list
|
|
49
|
+
airbyte sources list --workspace-id <id>
|
|
50
|
+
|
|
51
|
+
# Get source details
|
|
52
|
+
airbyte sources get <source-id>
|
|
53
|
+
|
|
54
|
+
# Create a new source
|
|
55
|
+
airbyte sources create \
|
|
56
|
+
--workspace-id <id> \
|
|
57
|
+
--name "My PostgreSQL Source" \
|
|
58
|
+
--definition-id decd338e-5647-4c0b-adf4-da0e75f5a750 \
|
|
59
|
+
--config '{"host":"localhost","port":5432,"database":"mydb","username":"user","password":"pass"}'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Destinations
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# List all destinations
|
|
66
|
+
airbyte destinations list
|
|
67
|
+
|
|
68
|
+
# Get destination details
|
|
69
|
+
airbyte destinations get <destination-id>
|
|
70
|
+
|
|
71
|
+
# Create a new destination
|
|
72
|
+
airbyte destinations create \
|
|
73
|
+
--workspace-id <id> \
|
|
74
|
+
--name "My BigQuery Destination" \
|
|
75
|
+
--definition-id 22f6c74f-5699-40ff-833c-4a879ea40133 \
|
|
76
|
+
--config '{"project_id":"my-project","dataset_id":"my_dataset"}'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Workspaces
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# List all workspaces
|
|
83
|
+
airbyte workspaces list
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Configuration
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
airbyte config set --api-key YOUR_KEY
|
|
90
|
+
airbyte config get apiKey
|
|
91
|
+
airbyte config list
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## JSON Output
|
|
95
|
+
|
|
96
|
+
All commands support `--json` flag for machine-readable output:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
airbyte connections list --json
|
|
100
|
+
airbyte sources get <id> --json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/bin/airbyte.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/airbyte",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for Airbyte Data Integration API - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"airbyte": "bin/airbyte.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["airbyte", "cli", "api", "ktmcp", "data-integration", "etl", "elt"],
|
|
11
|
+
"author": "KTMCP",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"axios": "^1.6.7",
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"ora": "^8.0.1",
|
|
18
|
+
"conf": "^12.0.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": { "node": ">=18.0.0" },
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/ktmcp-cli/airbyte.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/airbyte-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/airbyte/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BASE_URL = 'http://localhost:8006/api';
|
|
5
|
+
|
|
6
|
+
function getClient() {
|
|
7
|
+
const apiKey = getConfig('apiKey');
|
|
8
|
+
const token = getConfig('token');
|
|
9
|
+
const baseURL = getConfig('baseUrl') || DEFAULT_BASE_URL;
|
|
10
|
+
|
|
11
|
+
const headers = {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'Accept': 'application/json'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (apiKey) {
|
|
17
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
18
|
+
} else if (token) {
|
|
19
|
+
headers['Authorization'] = `Basic ${token}`;
|
|
20
|
+
} else {
|
|
21
|
+
throw new Error('API credentials not configured. Run: airbyte config set --api-key YOUR_API_KEY');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return axios.create({ baseURL, headers });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleApiError(error) {
|
|
28
|
+
if (error.response) {
|
|
29
|
+
const status = error.response.status;
|
|
30
|
+
const data = error.response.data;
|
|
31
|
+
if (status === 401) throw new Error('Authentication failed. Check your Airbyte API key.');
|
|
32
|
+
if (status === 403) throw new Error('Access forbidden. Check your API permissions.');
|
|
33
|
+
if (status === 404) throw new Error('Resource not found.');
|
|
34
|
+
if (status === 422) throw new Error(`Validation error: ${JSON.stringify(data?.detail || data)}`);
|
|
35
|
+
if (status === 429) throw new Error('Rate limit exceeded. Please wait before retrying.');
|
|
36
|
+
const message = data?.message || data?.detail || JSON.stringify(data);
|
|
37
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
38
|
+
} else if (error.request) {
|
|
39
|
+
const baseURL = getConfig('baseUrl') || DEFAULT_BASE_URL;
|
|
40
|
+
throw new Error(`No response from Airbyte at ${baseURL}. Is your Airbyte instance running?`);
|
|
41
|
+
} else {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================
|
|
47
|
+
// CONNECTIONS
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
export async function listConnections(workspaceId) {
|
|
51
|
+
try {
|
|
52
|
+
const client = getClient();
|
|
53
|
+
const body = workspaceId ? { workspaceId } : {};
|
|
54
|
+
const response = await client.post('/v1/connections/list', body);
|
|
55
|
+
return response.data.connections || [];
|
|
56
|
+
} catch (error) {
|
|
57
|
+
handleApiError(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function getConnection(connectionId) {
|
|
62
|
+
try {
|
|
63
|
+
const client = getClient();
|
|
64
|
+
const response = await client.post('/v1/connections/get', { connectionId });
|
|
65
|
+
return response.data;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
handleApiError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function createConnection({ sourceId, destinationId, name, syncCatalog, scheduleType = 'manual' }) {
|
|
72
|
+
try {
|
|
73
|
+
const client = getClient();
|
|
74
|
+
const body = {
|
|
75
|
+
sourceId,
|
|
76
|
+
destinationId,
|
|
77
|
+
name: name || `Connection from ${sourceId} to ${destinationId}`,
|
|
78
|
+
status: 'active',
|
|
79
|
+
scheduleType,
|
|
80
|
+
...(syncCatalog && { syncCatalog })
|
|
81
|
+
};
|
|
82
|
+
const response = await client.post('/v1/connections/create', body);
|
|
83
|
+
return response.data;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
handleApiError(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================================
|
|
90
|
+
// SOURCES
|
|
91
|
+
// ============================================================
|
|
92
|
+
|
|
93
|
+
export async function listSources(workspaceId) {
|
|
94
|
+
try {
|
|
95
|
+
const client = getClient();
|
|
96
|
+
const body = workspaceId ? { workspaceId } : {};
|
|
97
|
+
const response = await client.post('/v1/sources/list', body);
|
|
98
|
+
return response.data.sources || [];
|
|
99
|
+
} catch (error) {
|
|
100
|
+
handleApiError(error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function getSource(sourceId) {
|
|
105
|
+
try {
|
|
106
|
+
const client = getClient();
|
|
107
|
+
const response = await client.post('/v1/sources/get', { sourceId });
|
|
108
|
+
return response.data;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
handleApiError(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function createSource({ workspaceId, name, sourceDefinitionId, connectionConfiguration }) {
|
|
115
|
+
try {
|
|
116
|
+
const client = getClient();
|
|
117
|
+
const response = await client.post('/v1/sources/create', {
|
|
118
|
+
workspaceId,
|
|
119
|
+
name,
|
|
120
|
+
sourceDefinitionId,
|
|
121
|
+
connectionConfiguration: connectionConfiguration || {}
|
|
122
|
+
});
|
|
123
|
+
return response.data;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
handleApiError(error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================
|
|
130
|
+
// DESTINATIONS
|
|
131
|
+
// ============================================================
|
|
132
|
+
|
|
133
|
+
export async function listDestinations(workspaceId) {
|
|
134
|
+
try {
|
|
135
|
+
const client = getClient();
|
|
136
|
+
const body = workspaceId ? { workspaceId } : {};
|
|
137
|
+
const response = await client.post('/v1/destinations/list', body);
|
|
138
|
+
return response.data.destinations || [];
|
|
139
|
+
} catch (error) {
|
|
140
|
+
handleApiError(error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function getDestination(destinationId) {
|
|
145
|
+
try {
|
|
146
|
+
const client = getClient();
|
|
147
|
+
const response = await client.post('/v1/destinations/get', { destinationId });
|
|
148
|
+
return response.data;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
handleApiError(error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function createDestination({ workspaceId, name, destinationDefinitionId, connectionConfiguration }) {
|
|
155
|
+
try {
|
|
156
|
+
const client = getClient();
|
|
157
|
+
const response = await client.post('/v1/destinations/create', {
|
|
158
|
+
workspaceId,
|
|
159
|
+
name,
|
|
160
|
+
destinationDefinitionId,
|
|
161
|
+
connectionConfiguration: connectionConfiguration || {}
|
|
162
|
+
});
|
|
163
|
+
return response.data;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
handleApiError(error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================================================
|
|
170
|
+
// WORKSPACES
|
|
171
|
+
// ============================================================
|
|
172
|
+
|
|
173
|
+
export async function listWorkspaces() {
|
|
174
|
+
try {
|
|
175
|
+
const client = getClient();
|
|
176
|
+
const response = await client.post('/v1/workspaces/list', {});
|
|
177
|
+
return response.data.workspaces || [];
|
|
178
|
+
} catch (error) {
|
|
179
|
+
handleApiError(error);
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/airbyte' });
|
|
4
|
+
|
|
5
|
+
export function getConfig(key) {
|
|
6
|
+
return config.get(key);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setConfig(key, value) {
|
|
10
|
+
config.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isConfigured() {
|
|
14
|
+
return !!config.get('apiKey') || !!config.get('token');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAllConfig() {
|
|
18
|
+
return config.store;
|
|
19
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, isConfigured, getAllConfig } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
listConnections, getConnection, createConnection,
|
|
7
|
+
listSources, getSource, createSource,
|
|
8
|
+
listDestinations, getDestination, createDestination,
|
|
9
|
+
listWorkspaces
|
|
10
|
+
} from './api.js';
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Helpers
|
|
16
|
+
// ============================================================
|
|
17
|
+
|
|
18
|
+
function printSuccess(message) {
|
|
19
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printError(message) {
|
|
23
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function printTable(data, columns) {
|
|
27
|
+
if (!data || data.length === 0) {
|
|
28
|
+
console.log(chalk.yellow('No results found.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const widths = {};
|
|
32
|
+
columns.forEach(col => {
|
|
33
|
+
widths[col.key] = col.label.length;
|
|
34
|
+
data.forEach(row => {
|
|
35
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
36
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
37
|
+
});
|
|
38
|
+
widths[col.key] = Math.min(widths[col.key], 45);
|
|
39
|
+
});
|
|
40
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
41
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
42
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
43
|
+
data.forEach(row => {
|
|
44
|
+
const line = columns.map(col => {
|
|
45
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
46
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
47
|
+
}).join(' ');
|
|
48
|
+
console.log(line);
|
|
49
|
+
});
|
|
50
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printJson(data) {
|
|
54
|
+
console.log(JSON.stringify(data, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function withSpinner(message, fn) {
|
|
58
|
+
const spinner = ora(message).start();
|
|
59
|
+
try {
|
|
60
|
+
const result = await fn();
|
|
61
|
+
spinner.stop();
|
|
62
|
+
return result;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
spinner.stop();
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function requireAuth() {
|
|
70
|
+
if (!isConfigured()) {
|
|
71
|
+
printError('Airbyte credentials not configured.');
|
|
72
|
+
console.log('\nRun the following to configure:');
|
|
73
|
+
console.log(chalk.cyan(' airbyte config set --api-key YOUR_API_KEY'));
|
|
74
|
+
console.log(chalk.cyan(' airbyte config set --base-url http://localhost:8006/api'));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================================
|
|
80
|
+
// Program metadata
|
|
81
|
+
// ============================================================
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.name('airbyte')
|
|
85
|
+
.description(chalk.bold('Airbyte CLI') + ' - Data integration pipelines from your terminal')
|
|
86
|
+
.version('1.0.0');
|
|
87
|
+
|
|
88
|
+
// ============================================================
|
|
89
|
+
// CONFIG
|
|
90
|
+
// ============================================================
|
|
91
|
+
|
|
92
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
93
|
+
|
|
94
|
+
configCmd
|
|
95
|
+
.command('set')
|
|
96
|
+
.description('Set configuration values')
|
|
97
|
+
.option('--api-key <key>', 'Airbyte API key')
|
|
98
|
+
.option('--base-url <url>', 'Airbyte API base URL (default: http://localhost:8006/api)')
|
|
99
|
+
.option('--workspace-id <id>', 'Default workspace ID')
|
|
100
|
+
.action((options) => {
|
|
101
|
+
if (options.apiKey) { setConfig('apiKey', options.apiKey); printSuccess('API key set'); }
|
|
102
|
+
if (options.baseUrl) { setConfig('baseUrl', options.baseUrl); printSuccess(`Base URL set to: ${options.baseUrl}`); }
|
|
103
|
+
if (options.workspaceId) { setConfig('workspaceId', options.workspaceId); printSuccess(`Default workspace ID set: ${options.workspaceId}`); }
|
|
104
|
+
if (!options.apiKey && !options.baseUrl && !options.workspaceId) {
|
|
105
|
+
printError('No options provided. Use --api-key, --base-url, or --workspace-id');
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
configCmd
|
|
110
|
+
.command('get <key>')
|
|
111
|
+
.description('Get a configuration value')
|
|
112
|
+
.action((key) => {
|
|
113
|
+
const value = getConfig(key);
|
|
114
|
+
if (value === undefined) {
|
|
115
|
+
printError(`Key "${key}" not found`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log(value);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
configCmd
|
|
122
|
+
.command('list')
|
|
123
|
+
.description('List all configuration values')
|
|
124
|
+
.action(() => {
|
|
125
|
+
const all = getAllConfig();
|
|
126
|
+
console.log(chalk.bold('\nAirbyte CLI Configuration\n'));
|
|
127
|
+
console.log('API Key: ', all.apiKey ? chalk.green('*'.repeat(16) + all.apiKey.slice(-4)) : chalk.red('not set'));
|
|
128
|
+
console.log('Base URL: ', all.baseUrl ? chalk.green(all.baseUrl) : chalk.yellow('using default: http://localhost:8006/api'));
|
|
129
|
+
console.log('Workspace ID: ', all.workspaceId ? chalk.green(all.workspaceId) : chalk.yellow('not set (use --workspace-id with commands)'));
|
|
130
|
+
console.log('');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ============================================================
|
|
134
|
+
// CONNECTIONS
|
|
135
|
+
// ============================================================
|
|
136
|
+
|
|
137
|
+
const connectionsCmd = program.command('connections').description('Manage data sync connections');
|
|
138
|
+
|
|
139
|
+
connectionsCmd
|
|
140
|
+
.command('list')
|
|
141
|
+
.description('List all connections')
|
|
142
|
+
.option('--workspace-id <id>', 'Workspace ID (uses default if configured)')
|
|
143
|
+
.option('--json', 'Output as JSON')
|
|
144
|
+
.action(async (options) => {
|
|
145
|
+
requireAuth();
|
|
146
|
+
const workspaceId = options.workspaceId || getConfig('workspaceId');
|
|
147
|
+
try {
|
|
148
|
+
const connections = await withSpinner('Fetching connections...', () => listConnections(workspaceId));
|
|
149
|
+
if (options.json) { printJson(connections); return; }
|
|
150
|
+
printTable(connections, [
|
|
151
|
+
{ key: 'connectionId', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
152
|
+
{ key: 'name', label: 'Name' },
|
|
153
|
+
{ key: 'status', label: 'Status' },
|
|
154
|
+
{ key: 'scheduleType', label: 'Schedule' },
|
|
155
|
+
{ key: 'sourceId', label: 'Source ID', format: (v) => v?.substring(0, 12) + '...' },
|
|
156
|
+
{ key: 'destinationId', label: 'Dest ID', format: (v) => v?.substring(0, 12) + '...' }
|
|
157
|
+
]);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
printError(error.message);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
connectionsCmd
|
|
165
|
+
.command('get <connection-id>')
|
|
166
|
+
.description('Get details of a specific connection')
|
|
167
|
+
.option('--json', 'Output as JSON')
|
|
168
|
+
.action(async (connectionId, options) => {
|
|
169
|
+
requireAuth();
|
|
170
|
+
try {
|
|
171
|
+
const conn = await withSpinner('Fetching connection...', () => getConnection(connectionId));
|
|
172
|
+
if (options.json) { printJson(conn); return; }
|
|
173
|
+
console.log(chalk.bold('\nConnection Details\n'));
|
|
174
|
+
console.log('ID: ', chalk.cyan(conn.connectionId));
|
|
175
|
+
console.log('Name: ', chalk.bold(conn.name));
|
|
176
|
+
console.log('Status: ', conn.status === 'active' ? chalk.green(conn.status) : chalk.yellow(conn.status));
|
|
177
|
+
console.log('Schedule: ', conn.scheduleType || 'manual');
|
|
178
|
+
console.log('Source ID: ', conn.sourceId);
|
|
179
|
+
console.log('Dest ID: ', conn.destinationId);
|
|
180
|
+
console.log('');
|
|
181
|
+
} catch (error) {
|
|
182
|
+
printError(error.message);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
connectionsCmd
|
|
188
|
+
.command('create')
|
|
189
|
+
.description('Create a new connection between source and destination')
|
|
190
|
+
.requiredOption('--source-id <id>', 'Source connector ID')
|
|
191
|
+
.requiredOption('--destination-id <id>', 'Destination connector ID')
|
|
192
|
+
.option('--name <name>', 'Connection name')
|
|
193
|
+
.option('--schedule <type>', 'Schedule type (manual|cron|basic)', 'manual')
|
|
194
|
+
.option('--json', 'Output as JSON')
|
|
195
|
+
.action(async (options) => {
|
|
196
|
+
requireAuth();
|
|
197
|
+
try {
|
|
198
|
+
const conn = await withSpinner('Creating connection...', () =>
|
|
199
|
+
createConnection({
|
|
200
|
+
sourceId: options.sourceId,
|
|
201
|
+
destinationId: options.destinationId,
|
|
202
|
+
name: options.name,
|
|
203
|
+
scheduleType: options.schedule
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
if (options.json) { printJson(conn); return; }
|
|
207
|
+
printSuccess(`Connection created: ${chalk.bold(conn.name || conn.connectionId)}`);
|
|
208
|
+
console.log('ID: ', conn.connectionId);
|
|
209
|
+
console.log('Status: ', conn.status);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
printError(error.message);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ============================================================
|
|
217
|
+
// SOURCES
|
|
218
|
+
// ============================================================
|
|
219
|
+
|
|
220
|
+
const sourcesCmd = program.command('sources').description('Manage data sources');
|
|
221
|
+
|
|
222
|
+
sourcesCmd
|
|
223
|
+
.command('list')
|
|
224
|
+
.description('List all sources')
|
|
225
|
+
.option('--workspace-id <id>', 'Workspace ID')
|
|
226
|
+
.option('--json', 'Output as JSON')
|
|
227
|
+
.action(async (options) => {
|
|
228
|
+
requireAuth();
|
|
229
|
+
const workspaceId = options.workspaceId || getConfig('workspaceId');
|
|
230
|
+
try {
|
|
231
|
+
const sources = await withSpinner('Fetching sources...', () => listSources(workspaceId));
|
|
232
|
+
if (options.json) { printJson(sources); return; }
|
|
233
|
+
printTable(sources, [
|
|
234
|
+
{ key: 'sourceId', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
235
|
+
{ key: 'name', label: 'Name' },
|
|
236
|
+
{ key: 'sourceName', label: 'Type' },
|
|
237
|
+
{ key: 'workspaceId', label: 'Workspace', format: (v) => v?.substring(0, 12) + '...' }
|
|
238
|
+
]);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
printError(error.message);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
sourcesCmd
|
|
246
|
+
.command('get <source-id>')
|
|
247
|
+
.description('Get details of a specific source')
|
|
248
|
+
.option('--json', 'Output as JSON')
|
|
249
|
+
.action(async (sourceId, options) => {
|
|
250
|
+
requireAuth();
|
|
251
|
+
try {
|
|
252
|
+
const source = await withSpinner('Fetching source...', () => getSource(sourceId));
|
|
253
|
+
if (options.json) { printJson(source); return; }
|
|
254
|
+
console.log(chalk.bold('\nSource Details\n'));
|
|
255
|
+
console.log('ID: ', chalk.cyan(source.sourceId));
|
|
256
|
+
console.log('Name: ', chalk.bold(source.name));
|
|
257
|
+
console.log('Type: ', source.sourceName);
|
|
258
|
+
console.log('Workspace: ', source.workspaceId);
|
|
259
|
+
console.log('');
|
|
260
|
+
} catch (error) {
|
|
261
|
+
printError(error.message);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
sourcesCmd
|
|
267
|
+
.command('create')
|
|
268
|
+
.description('Create a new data source')
|
|
269
|
+
.requiredOption('--workspace-id <id>', 'Workspace ID')
|
|
270
|
+
.requiredOption('--name <name>', 'Source name')
|
|
271
|
+
.requiredOption('--definition-id <id>', 'Source definition ID')
|
|
272
|
+
.option('--config <json>', 'Connection configuration as JSON')
|
|
273
|
+
.option('--json', 'Output as JSON')
|
|
274
|
+
.action(async (options) => {
|
|
275
|
+
requireAuth();
|
|
276
|
+
let connectionConfiguration = {};
|
|
277
|
+
if (options.config) {
|
|
278
|
+
try { connectionConfiguration = JSON.parse(options.config); } catch {
|
|
279
|
+
printError('Invalid JSON for --config'); process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const source = await withSpinner('Creating source...', () =>
|
|
284
|
+
createSource({
|
|
285
|
+
workspaceId: options.workspaceId,
|
|
286
|
+
name: options.name,
|
|
287
|
+
sourceDefinitionId: options.definitionId,
|
|
288
|
+
connectionConfiguration
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
if (options.json) { printJson(source); return; }
|
|
292
|
+
printSuccess(`Source created: ${chalk.bold(source.name)}`);
|
|
293
|
+
console.log('ID: ', source.sourceId);
|
|
294
|
+
console.log('Type: ', source.sourceName);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
printError(error.message);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// ============================================================
|
|
302
|
+
// DESTINATIONS
|
|
303
|
+
// ============================================================
|
|
304
|
+
|
|
305
|
+
const destinationsCmd = program.command('destinations').description('Manage data destinations');
|
|
306
|
+
|
|
307
|
+
destinationsCmd
|
|
308
|
+
.command('list')
|
|
309
|
+
.description('List all destinations')
|
|
310
|
+
.option('--workspace-id <id>', 'Workspace ID')
|
|
311
|
+
.option('--json', 'Output as JSON')
|
|
312
|
+
.action(async (options) => {
|
|
313
|
+
requireAuth();
|
|
314
|
+
const workspaceId = options.workspaceId || getConfig('workspaceId');
|
|
315
|
+
try {
|
|
316
|
+
const destinations = await withSpinner('Fetching destinations...', () => listDestinations(workspaceId));
|
|
317
|
+
if (options.json) { printJson(destinations); return; }
|
|
318
|
+
printTable(destinations, [
|
|
319
|
+
{ key: 'destinationId', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
320
|
+
{ key: 'name', label: 'Name' },
|
|
321
|
+
{ key: 'destinationName', label: 'Type' },
|
|
322
|
+
{ key: 'workspaceId', label: 'Workspace', format: (v) => v?.substring(0, 12) + '...' }
|
|
323
|
+
]);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
printError(error.message);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
destinationsCmd
|
|
331
|
+
.command('get <destination-id>')
|
|
332
|
+
.description('Get details of a specific destination')
|
|
333
|
+
.option('--json', 'Output as JSON')
|
|
334
|
+
.action(async (destinationId, options) => {
|
|
335
|
+
requireAuth();
|
|
336
|
+
try {
|
|
337
|
+
const dest = await withSpinner('Fetching destination...', () => getDestination(destinationId));
|
|
338
|
+
if (options.json) { printJson(dest); return; }
|
|
339
|
+
console.log(chalk.bold('\nDestination Details\n'));
|
|
340
|
+
console.log('ID: ', chalk.cyan(dest.destinationId));
|
|
341
|
+
console.log('Name: ', chalk.bold(dest.name));
|
|
342
|
+
console.log('Type: ', dest.destinationName);
|
|
343
|
+
console.log('Workspace: ', dest.workspaceId);
|
|
344
|
+
console.log('');
|
|
345
|
+
} catch (error) {
|
|
346
|
+
printError(error.message);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
destinationsCmd
|
|
352
|
+
.command('create')
|
|
353
|
+
.description('Create a new data destination')
|
|
354
|
+
.requiredOption('--workspace-id <id>', 'Workspace ID')
|
|
355
|
+
.requiredOption('--name <name>', 'Destination name')
|
|
356
|
+
.requiredOption('--definition-id <id>', 'Destination definition ID')
|
|
357
|
+
.option('--config <json>', 'Connection configuration as JSON')
|
|
358
|
+
.option('--json', 'Output as JSON')
|
|
359
|
+
.action(async (options) => {
|
|
360
|
+
requireAuth();
|
|
361
|
+
let connectionConfiguration = {};
|
|
362
|
+
if (options.config) {
|
|
363
|
+
try { connectionConfiguration = JSON.parse(options.config); } catch {
|
|
364
|
+
printError('Invalid JSON for --config'); process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const dest = await withSpinner('Creating destination...', () =>
|
|
369
|
+
createDestination({
|
|
370
|
+
workspaceId: options.workspaceId,
|
|
371
|
+
name: options.name,
|
|
372
|
+
destinationDefinitionId: options.definitionId,
|
|
373
|
+
connectionConfiguration
|
|
374
|
+
})
|
|
375
|
+
);
|
|
376
|
+
if (options.json) { printJson(dest); return; }
|
|
377
|
+
printSuccess(`Destination created: ${chalk.bold(dest.name)}`);
|
|
378
|
+
console.log('ID: ', dest.destinationId);
|
|
379
|
+
console.log('Type: ', dest.destinationName);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
printError(error.message);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ============================================================
|
|
387
|
+
// WORKSPACES
|
|
388
|
+
// ============================================================
|
|
389
|
+
|
|
390
|
+
const workspacesCmd = program.command('workspaces').description('List Airbyte workspaces');
|
|
391
|
+
|
|
392
|
+
workspacesCmd
|
|
393
|
+
.command('list')
|
|
394
|
+
.description('List all workspaces')
|
|
395
|
+
.option('--json', 'Output as JSON')
|
|
396
|
+
.action(async (options) => {
|
|
397
|
+
requireAuth();
|
|
398
|
+
try {
|
|
399
|
+
const workspaces = await withSpinner('Fetching workspaces...', () => listWorkspaces());
|
|
400
|
+
if (options.json) { printJson(workspaces); return; }
|
|
401
|
+
printTable(workspaces, [
|
|
402
|
+
{ key: 'workspaceId', label: 'ID', format: (v) => v?.substring(0, 16) + '...' },
|
|
403
|
+
{ key: 'name', label: 'Name' },
|
|
404
|
+
{ key: 'email', label: 'Email' },
|
|
405
|
+
{ key: 'slug', label: 'Slug' }
|
|
406
|
+
]);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
printError(error.message);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// ============================================================
|
|
414
|
+
// Parse
|
|
415
|
+
// ============================================================
|
|
416
|
+
|
|
417
|
+
program.parse(process.argv);
|
|
418
|
+
|
|
419
|
+
if (process.argv.length <= 2) {
|
|
420
|
+
program.help();
|
|
421
|
+
}
|