@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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/index.js';
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
+ }