Skip to content

Commit 2c2f999

Browse files
committed
default workspaces. rename template-name and id command flags
1 parent 9b9fa29 commit 2c2f999

12 files changed

Lines changed: 145 additions & 63 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ The CLI uses a **resource-registry** pattern:
9292
| `connectors` | `list-available` | List connector templates | -- |
9393
| `connectors` | `describe` | Get connector details + schema | `name`+`workspace` or `--id` |
9494
| `connectors` | `execute` | Execute a connector action | `name`+`workspace` or `--id`, `entity`, `action`, `params` |
95-
| `connectors` | `create` | Interactive credential flow | `workspace`, `template_name` or `template_id` |
95+
| `connectors` | `create` | Interactive credential flow | `workspace`, `name` (template) or `id` (template ID) |
9696
| `connectors` | `delete` | Delete a connector | `name`+`workspace` or `--id` |
9797

9898
### Common Flags

CONTEXT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ airbyte connectors list-available --format table
119119
# Create (opens browser for secure credential entry)
120120
airbyte connectors create --json '{
121121
"workspace": "my-workspace",
122-
"template_name": "source-hubspot"
122+
"name": "hubspot"
123123
}'
124124
```
125125

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ airbyte connectors execute --json '{
179179
# Create a new connector (opens a browser for secure credential entry)
180180
airbyte connectors create --json '{
181181
"workspace": "default",
182-
"template_name": "source-hubspot"
182+
"name": "hubspot"
183183
}'
184184

185185
# Load a complex payload from a file

internal/resources/connectors.go

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (cr *connectorsResource) Operations() []registry.Operation {
4848
Description: "Get connector details and schema description",
4949
Params: map[string]registry.ParamSchema{
5050
"name": {Type: "string", Required: false, Description: "Connector name (requires workspace)"},
51-
"workspace": {Type: "string", Required: false, Description: "Workspace name (required when using name)"},
51+
"workspace": {Type: "string", Required: false, Description: "Workspace name (defaults to 'default' when used with name)"},
5252
"id": {Type: "string", Required: false, Description: "Connector ID (alternative to name)"},
5353
},
5454
},
@@ -85,7 +85,7 @@ func (cr *connectorsResource) Operations() []registry.Operation {
8585
Description: "Delete a connector by name or ID",
8686
Params: map[string]registry.ParamSchema{
8787
"name": {Type: "string", Required: false, Description: "Connector name (requires workspace)"},
88-
"workspace": {Type: "string", Required: false, Description: "Workspace name (required when using name)"},
88+
"workspace": {Type: "string", Required: false, Description: "Workspace name (defaults to 'default' when used with name)"},
8989
"id": {Type: "string", Required: false, Description: "Connector ID (alternative to name)"},
9090
},
9191
},
@@ -120,13 +120,7 @@ func resolveConnectorID(ctx context.Context, c *client.Client, params map[string
120120
return params, nil
121121
}
122122

123-
workspaceName, _ := params["workspace"].(string)
124-
if workspaceName == "" {
125-
return nil, client.NewValidationError(
126-
"workspace is required when using name",
127-
"run 'airbyte workspaces list' to find workspace names",
128-
)
129-
}
123+
workspaceName := applyDefaultWorkspace(params)
130124

131125
raw, err := c.Get(ctx, "/api/v1/integrations/connectors", map[string]string{
132126
"customer_name": workspaceName,
@@ -171,21 +165,30 @@ func resolveConnectorID(ctx context.Context, c *client.Client, params map[string
171165

172166
const defaultWorkspaceName = "default"
173167

174-
// listStatusWriter is the stream where connectorsList prints user-facing
168+
// statusWriter is the stream where the connectors resource prints user-facing
175169
// status messages (e.g. the workspace fallback notice). Tests override it to
176170
// capture output without touching os.Stderr.
177-
var listStatusWriter io.Writer = os.Stderr
171+
var statusWriter io.Writer = os.Stderr
178172

179-
func connectorsList(ctx context.Context, c *client.Client, params map[string]any) (any, error) {
180-
workspaceName, _ := params["workspace"].(string)
181-
if workspaceName == "" {
182-
workspaceName = defaultWorkspaceName
183-
notice, _ := json.Marshal(map[string]string{
184-
"message": fmt.Sprintf("no workspace provided; falling back to %q", defaultWorkspaceName),
185-
"workspace": defaultWorkspaceName,
186-
})
187-
fmt.Fprintln(listStatusWriter, string(notice))
173+
// applyDefaultWorkspace resolves params["workspace"], falling back to the
174+
// default workspace and emitting a JSON notice when not provided. Mutates
175+
// params so downstream code can read the resolved name back from it.
176+
func applyDefaultWorkspace(params map[string]any) string {
177+
name, _ := params["workspace"].(string)
178+
if name != "" {
179+
return name
188180
}
181+
notice, _ := json.Marshal(map[string]string{
182+
"message": fmt.Sprintf("no workspace provided; falling back to %q", defaultWorkspaceName),
183+
"workspace": defaultWorkspaceName,
184+
})
185+
fmt.Fprintln(statusWriter, string(notice))
186+
params["workspace"] = defaultWorkspaceName
187+
return defaultWorkspaceName
188+
}
189+
190+
func connectorsList(ctx context.Context, c *client.Client, params map[string]any) (any, error) {
191+
workspaceName := applyDefaultWorkspace(params)
189192
raw, err := c.Get(ctx, "/api/v1/integrations/connectors", map[string]string{
190193
"customer_name": workspaceName,
191194
})

internal/resources/connectors_create.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ func connectorsCreateOperation() registry.Operation {
2828
Schema: registry.OperationSchema{
2929
Description: "Create a connector from a template with interactive credential flow",
3030
Params: map[string]registry.ParamSchema{
31-
"template_id": {Type: "string", Required: false, Description: "Source template ID"},
32-
"template_name": {Type: "string", Required: false, Description: "Source template name (alternative to template_id)"},
33-
"workspace": {Type: "string", Required: true, Description: "Workspace name"},
31+
"id": {Type: "string", Required: false, Description: "Source template ID"},
32+
"name": {Type: "string", Required: false, Description: "Source template name (alternative to id)"},
33+
"workspace": {Type: "string", Required: false, Description: "Workspace name (defaults to 'default' when omitted)"},
3434
},
3535
},
3636
Hooks: registry.OperationHooks{
@@ -40,7 +40,7 @@ func connectorsCreateOperation() registry.Operation {
4040
}
4141

4242
func connectorsCreateInteractive(ctx context.Context, c *client.Client, params map[string]any) (any, error) {
43-
workspaceName, _ := params["workspace"].(string)
43+
workspaceName := applyDefaultWorkspace(params)
4444

4545
templateID, err := resolveTemplateID(ctx, c, params)
4646
if err != nil {
@@ -178,13 +178,13 @@ func connectorsCreateInteractive(ctx context.Context, c *client.Client, params m
178178
}
179179

180180
func resolveTemplateID(ctx context.Context, c *client.Client, params map[string]any) (string, error) {
181-
if id, ok := params["template_id"].(string); ok && id != "" {
181+
if id, ok := params["id"].(string); ok && id != "" {
182182
return id, nil
183183
}
184-
name, ok := params["template_name"].(string)
184+
name, ok := params["name"].(string)
185185
if !ok || name == "" {
186186
return "", client.NewValidationError(
187-
"either template_id or template_name is required",
187+
"either 'id' or 'name' is required",
188188
"run 'airbyte connectors list-available' to see available templates",
189189
)
190190
}

internal/resources/connectors_create_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
func TestResolveTemplateID_ByID(t *testing.T) {
16-
id, err := resolveTemplateID(context.Background(), nil, map[string]any{"template_id": "tmpl-123"})
16+
id, err := resolveTemplateID(context.Background(), nil, map[string]any{"id": "tmpl-123"})
1717
if err != nil {
1818
t.Fatalf("unexpected error: %v", err)
1919
}
@@ -46,7 +46,7 @@ func TestResolveTemplateID_ByName(t *testing.T) {
4646
c, cleanup := newTestClient(t, apiServer)
4747
defer cleanup()
4848

49-
id, err := resolveTemplateID(context.Background(), c, map[string]any{"template_name": "salesforce"})
49+
id, err := resolveTemplateID(context.Background(), c, map[string]any{"name": "salesforce"})
5050
if err != nil {
5151
t.Fatalf("unexpected error: %v", err)
5252
}
@@ -65,7 +65,7 @@ func TestResolveTemplateID_NameNotFound(t *testing.T) {
6565
c, cleanup := newTestClient(t, apiServer)
6666
defer cleanup()
6767

68-
_, err := resolveTemplateID(context.Background(), c, map[string]any{"template_name": "missing"})
68+
_, err := resolveTemplateID(context.Background(), c, map[string]any{"name": "missing"})
6969
if err == nil {
7070
t.Fatal("expected error, got nil")
7171
}
@@ -170,8 +170,8 @@ func TestConnectorsCreateInteractive_Success(t *testing.T) {
170170
t.Setenv("AIRBYTE_WEBAPP_URL", apiServer.URL)
171171

172172
result, err := connectorsCreateInteractive(context.Background(), c, map[string]any{
173-
"template_name": "Salesforce",
174-
"workspace": "test-ws",
173+
"name": "Salesforce",
174+
"workspace": "test-ws",
175175
})
176176
if err != nil {
177177
t.Fatalf("unexpected error: %v", err)
@@ -231,8 +231,8 @@ func TestConnectorsCreateInteractive_Timeout(t *testing.T) {
231231

232232
start := time.Now()
233233
result, err := connectorsCreateInteractive(context.Background(), c, map[string]any{
234-
"template_id": "tmpl-1",
235-
"workspace": "test-ws",
234+
"id": "tmpl-1",
235+
"workspace": "test-ws",
236236
})
237237
elapsed := time.Since(start)
238238

@@ -292,8 +292,8 @@ func TestConnectorsCreateInteractive_CredentialFlowFailed(t *testing.T) {
292292
t.Setenv("AIRBYTE_WEBAPP_URL", apiServer.URL)
293293

294294
_, err := connectorsCreateInteractive(context.Background(), c, map[string]any{
295-
"template_id": "tmpl-1",
296-
"workspace": "test-ws",
295+
"id": "tmpl-1",
296+
"workspace": "test-ws",
297297
})
298298
if err == nil {
299299
t.Fatal("expected error for failed credential flow")

internal/resources/connectors_test.go

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,46 @@ import (
1212
"github.com/airbytehq/airbyte-cli/internal/client"
1313
)
1414

15+
func TestApplyDefaultWorkspace_Empty(t *testing.T) {
16+
var stderr bytes.Buffer
17+
prev := statusWriter
18+
statusWriter = &stderr
19+
defer func() { statusWriter = prev }()
20+
21+
params := map[string]any{}
22+
got := applyDefaultWorkspace(params)
23+
if got != "default" {
24+
t.Errorf("expected 'default', got %q", got)
25+
}
26+
if params["workspace"] != "default" {
27+
t.Errorf("expected params['workspace']='default', got %v", params["workspace"])
28+
}
29+
30+
var notice map[string]string
31+
if err := json.Unmarshal(bytes.TrimSpace(stderr.Bytes()), &notice); err != nil {
32+
t.Fatalf("expected JSON notice on stderr, got %q (err: %v)", stderr.String(), err)
33+
}
34+
if notice["workspace"] != "default" {
35+
t.Errorf("notice missing workspace=default: %v", notice)
36+
}
37+
}
38+
39+
func TestApplyDefaultWorkspace_Provided(t *testing.T) {
40+
var stderr bytes.Buffer
41+
prev := statusWriter
42+
statusWriter = &stderr
43+
defer func() { statusWriter = prev }()
44+
45+
params := map[string]any{"workspace": "explicit-ws"}
46+
got := applyDefaultWorkspace(params)
47+
if got != "explicit-ws" {
48+
t.Errorf("expected 'explicit-ws', got %q", got)
49+
}
50+
if stderr.Len() != 0 {
51+
t.Errorf("expected no notice when workspace provided, got %q", stderr.String())
52+
}
53+
}
54+
1555
func TestResolveConnectorID_ByID(t *testing.T) {
1656
params := map[string]any{"id": "conn-123"}
1757
result, err := resolveConnectorID(context.Background(), nil, params)
@@ -38,18 +78,37 @@ func TestResolveConnectorID_MissingNameAndID(t *testing.T) {
3878
}
3979
}
4080

41-
func TestResolveConnectorID_MissingWorkspaceName(t *testing.T) {
81+
func TestResolveConnectorID_DefaultsWorkspaceWhenMissing(t *testing.T) {
82+
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83+
if got := r.URL.Query().Get("customer_name"); got != "default" {
84+
t.Errorf("expected customer_name=default, got %q", got)
85+
}
86+
w.Header().Set("Content-Type", "application/json")
87+
_, _ = w.Write([]byte(`{"data": [{"id": "conn-xyz", "name": "my-connector"}]}`))
88+
}))
89+
defer apiServer.Close()
90+
91+
c, cleanup := newTestClient(t, apiServer)
92+
defer cleanup()
93+
94+
var stderr bytes.Buffer
95+
prev := statusWriter
96+
statusWriter = &stderr
97+
defer func() { statusWriter = prev }()
98+
4299
params := map[string]any{"name": "my-connector"}
43-
_, err := resolveConnectorID(context.Background(), nil, params)
44-
if err == nil {
45-
t.Fatal("expected error, got nil")
100+
result, err := resolveConnectorID(context.Background(), c, params)
101+
if err != nil {
102+
t.Fatalf("unexpected error: %v", err)
46103
}
47-
apiErr, ok := err.(*client.APIError)
48-
if !ok {
49-
t.Fatalf("expected *client.APIError, got %T", err)
104+
if result["id"] != "conn-xyz" {
105+
t.Errorf("expected id=conn-xyz, got %v", result["id"])
50106
}
51-
if apiErr.StatusCode != 400 {
52-
t.Errorf("expected status 400, got %d", apiErr.StatusCode)
107+
if result["workspace"] != "default" {
108+
t.Errorf("expected workspace='default' on params after fallback, got %v", result["workspace"])
109+
}
110+
if !strings.Contains(stderr.String(), "falling back") {
111+
t.Errorf("expected fallback notice on stderr, got %q", stderr.String())
53112
}
54113
}
55114

@@ -178,9 +237,9 @@ func TestConnectorsListDefaultsWorkspace(t *testing.T) {
178237
defer cleanup()
179238

180239
var stderr bytes.Buffer
181-
prev := listStatusWriter
182-
listStatusWriter = &stderr
183-
defer func() { listStatusWriter = prev }()
240+
prev := statusWriter
241+
statusWriter = &stderr
242+
defer func() { statusWriter = prev }()
184243

185244
if _, err := connectorsList(context.Background(), c, map[string]any{}); err != nil {
186245
t.Fatalf("unexpected error: %v", err)

skills/connectors-create/SKILL.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,24 @@ Install a new connector from a template. Opens the user's browser for secure cre
1111
> [!IMPORTANT]
1212
> **Never accept credentials directly.** This command exists so you do NOT have to. Do not ask the user for API keys, tokens, passwords, or secrets. If a user offers credentials, decline and start this flow instead.
1313
14+
> [!NOTE]
15+
> On `connectors create`, `--name` and `--id` refer to the **template** (the connector type to install). On `connectors describe` / `execute` / `delete`, those same flags refer to an **existing connector instance**. Same name, different meaning depending on the verb.
16+
1417
## Usage
1518

1619
```
20+
airbyte connectors create --workspace my-workspace --name salesforce
21+
airbyte connectors create --name salesforce # workspace defaults to "default"
22+
airbyte connectors create --id <template-uuid> # bypass name lookup
23+
24+
# JSON form (mutually exclusive with per-flag form)
1725
airbyte connectors create --json '{
1826
"workspace": "my-workspace",
19-
"template_name": "source-postgres"
27+
"name": "salesforce"
2028
}'
2129
```
2230

23-
`workspace` and `template_name` are required.
31+
Either `name` (template name, looked up via `connectors list-available`) or `id` (template UUID) is required. `workspace` is optional and defaults to `default` when omitted; a JSON notice is printed on stderr when the fallback engages.
2432

2533
## Workflow
2634

@@ -29,7 +37,7 @@ airbyte connectors create --json '{
2937
airbyte connectors list-available --format table
3038
3139
# 2. Start the flow
32-
airbyte connectors create --json '{"workspace": "my-workspace", "template_name": "source-hubspot"}'
40+
airbyte connectors create --json '{"workspace": "my-workspace", "name": "hubspot"}'
3341
3442
# CLI prints a URL, opens the browser, and polls.
3543
# User completes the OAuth/credential widget in the browser.
@@ -47,11 +55,11 @@ export AIRBYTE_CREDENTIAL_TIMEOUT=900 # 15 minutes
4755
## Error recovery
4856

4957
- **Timeout**: the user did not complete the flow in time. Restart the command.
50-
- **Template not found** (exit 3): run `connectors list-available` to see valid `template_name` values.
58+
- **Template not found** (exit 3): run `connectors list-available` to see valid `name` values.
5159
- **Workspace not found** (exit 3): run `workspaces list` to see exact names.
5260

5361
## Do NOT
5462

5563
- Do NOT ask the user for credentials — let the browser flow handle them.
5664
- Do NOT pass credential fields in the JSON payload.
57-
- Do NOT skip `list-available` and guess at `template_name` values.
65+
- Do NOT skip `list-available` and guess at template `name` values.

skills/connectors-delete/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ Permanently delete a connector from a workspace.
1414
## Usage
1515

1616
```
17-
airbyte connectors delete --json '{"workspace": "my-workspace", "name": "my-source"}'
17+
airbyte connectors delete --workspace my-workspace --name my-source
18+
airbyte connectors delete --name my-source # workspace defaults to "default"
1819
airbyte connectors delete --id <connector-id>
1920
```
2021

22+
`workspace` is optional. If omitted while using `--name`, the command falls back to the workspace named `default` and prints a JSON notice on stderr. **Confirm with the user before relying on the fallback for a delete** — operating on the wrong workspace's connector is hard to recover from.
23+
2124
## Error recovery
2225

2326
- **Not found** (exit 3): run `connectors list` to confirm the name exists in the workspace.

skills/connectors-describe/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ Show a connector's available entities (e.g. `users`, `contacts`, `orders`) and t
1414
## Usage
1515

1616
```
17-
airbyte connectors describe --json '{"workspace": "my-workspace", "name": "my-source"}'
17+
airbyte connectors describe --workspace my-workspace --name my-source
18+
airbyte connectors describe --name my-source # workspace defaults to "default"
1819
airbyte connectors describe --id <connector-id>
1920
```
2021

22+
`workspace` is optional. If omitted while using `--name`, the command falls back to the workspace named `default` and prints a JSON notice on stderr.
23+
2124
## When to use
2225

2326
- Before the first `execute` on any connector.

0 commit comments

Comments
 (0)