Skip to content

Commit 166af68

Browse files
authored
feat(connections): Add connection information command (#28)
1 parent 340c48f commit 166af68

6 files changed

Lines changed: 105 additions & 136 deletions

File tree

.github/workflows/publish-homebrew.yml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@ jobs:
1111
publish-homebrew-formula:
1212
runs-on: ubuntu-22.04
1313
env:
14-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1514
PLAN: ${{ inputs.plan }}
16-
GITHUB_USER: "axo bot"
17-
GITHUB_EMAIL: "admin+bot@axo.dev"
1815
if: ${{ !fromJson(inputs.plan).announcement_is_prerelease || fromJson(inputs.plan).publish_prereleases }}
1916
steps:
17+
- name: Generate GitHub App token
18+
id: app-token
19+
uses: actions/create-github-app-token@v1
20+
with:
21+
app-id: ${{ secrets.HOTDATA_AUTOMATION_APP_ID }}
22+
private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }}
23+
owner: hotdata-dev
24+
repositories: homebrew-tap
25+
2026
- uses: actions/checkout@v6
2127
with:
2228
persist-credentials: true
2329
repository: "hotdata-dev/homebrew-tap"
24-
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
30+
token: ${{ steps.app-token.outputs.token }}
2531

2632
- name: Fetch homebrew formulae
2733
uses: actions/download-artifact@v7
@@ -32,8 +38,8 @@ jobs:
3238

3339
- name: Patch and commit formula files
3440
run: |
35-
git config --global user.name "${GITHUB_USER}"
36-
git config --global user.email "${GITHUB_EMAIL}"
41+
git config --global user.name "hotdata-automation[bot]"
42+
git config --global user.email "hotdata-automation[bot]@users.noreply.github.com"
3743
3844
for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do
3945
filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output)
@@ -55,6 +61,8 @@ jobs:
5561
git push
5662
5763
- name: Remove .rb from GitHub Release assets
64+
env:
65+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5866
run: |
5967
TAG=$(echo "$PLAN" | jq -r '.announcement_tag')
6068
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ API key priority (lowest to highest): config file → `HOTDATA_API_KEY` env var
5151
| Command | Subcommands | Description |
5252
| :-- | :-- | :-- |
5353
| `auth` | `status`, `logout` | Authenticate (run without subcommand to log in) |
54-
| `workspaces` | `list`, `set`, `get`, `create`, `update` | Manage workspaces |
55-
| `connections` | `list`, `get`, `create`, `refresh`, `update`, `delete`, `new` | Manage connections |
54+
| `workspaces` | `list`, `set` | Manage workspaces |
55+
| `connections` | `list`, `create`, `refresh`, `new` | Manage connections |
5656
| `tables` | `list` | List tables and columns |
5757
| `datasets` | `list`, `create` | Manage uploaded datasets |
5858
| `query` | | Execute a SQL query |
@@ -85,13 +85,14 @@ hotdata workspaces set [<workspace_id>]
8585
## Connections
8686

8787
```sh
88-
hotdata connections list [--workspace-id <id>] [--format table|json|yaml]
89-
hotdata connections get <connection_id> [--workspace-id <id>] [--format yaml|json|table]
90-
hotdata connections refresh <connection_id> [--workspace-id <id>]
91-
hotdata connections new [--workspace-id <id>]
88+
hotdata connections list [-w <id>] [-o table|json|yaml]
89+
hotdata connections <connection_id> [-w <id>] [-o table|json|yaml]
90+
hotdata connections refresh <connection_id> [-w <id>]
91+
hotdata connections new [-w <id>]
9292
```
9393

9494
- `list` returns `id`, `name`, `source_type` for each connection.
95+
- Pass a connection ID to view details (id, name, source type, table counts).
9596
- `refresh` triggers a schema refresh for a connection.
9697
- `new` launches an interactive connection creation wizard.
9798

skills/hotdata-cli/SKILL.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ Returns workspaces with `public_id`, `name`, `active`, `favorite`, `provision_st
3939

4040
### List Connections
4141
```
42-
hotdata connections list [--workspace-id <workspace_id>] [--format table|json|yaml]
42+
hotdata connections list [-w <workspace_id>] [-o table|json|yaml]
43+
hotdata connections <connection_id> [-w <workspace_id>] [-o table|json|yaml]
4344
```
44-
Returns `id`, `name`, `source_type` for each connection in the workspace.
45+
- `list` returns `id`, `name`, `source_type` for each connection.
46+
- Pass a connection ID to view details (id, name, source type, table counts).
4547

4648
### Create a Connection
4749

src/command.rs

Lines changed: 8 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,19 @@ pub enum Commands {
5454

5555
/// Manage workspace connections
5656
Connections {
57+
/// Connection ID to show details
58+
id: Option<String>,
59+
5760
/// Workspace ID (defaults to first workspace from login)
5861
#[arg(long, short = 'w', global = true)]
5962
workspace_id: Option<String>,
6063

64+
/// Output format (used with connection ID)
65+
#[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "yaml"])]
66+
output: String,
67+
6168
#[command(subcommand)]
62-
command: ConnectionsCommands,
69+
command: Option<ConnectionsCommands>,
6370
},
6471

6572
/// Manage tables in a workspace
@@ -364,55 +371,6 @@ pub enum WorkspaceCommands {
364371
/// Workspace ID to set as default (omit for interactive selection)
365372
workspace_id: Option<String>,
366373
},
367-
368-
/// Get details for a workspace
369-
Get {
370-
/// Workspace ID (defaults to first workspace from login)
371-
#[arg(long, short = 'w')]
372-
workspace_id: Option<String>,
373-
374-
/// Output format
375-
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
376-
output: String,
377-
},
378-
379-
/// Create a new workspace
380-
Create {
381-
/// Workspace name
382-
#[arg(long)]
383-
name: String,
384-
385-
/// Workspace description
386-
#[arg(long, default_value = "")]
387-
description: String,
388-
389-
/// Organization ID for the workspace
390-
#[arg(long)]
391-
organization_id: String,
392-
393-
/// Output format
394-
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
395-
output: String,
396-
},
397-
398-
/// Update an existing workspace
399-
Update {
400-
/// Workspace ID (defaults to first workspace from login)
401-
#[arg(long, short = 'w')]
402-
workspace_id: Option<String>,
403-
404-
/// New workspace name
405-
#[arg(long)]
406-
name: Option<String>,
407-
408-
/// New workspace description
409-
#[arg(long)]
410-
description: Option<String>,
411-
412-
/// Output format
413-
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
414-
output: String,
415-
},
416374
}
417375

418376
#[derive(Subcommand)]
@@ -440,16 +398,6 @@ pub enum ConnectionsCommands {
440398
output: String,
441399
},
442400

443-
/// Get details for a specific connection
444-
Get {
445-
/// Connection ID
446-
connection_id: String,
447-
448-
/// Output format
449-
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
450-
output: String,
451-
},
452-
453401
/// Create a new connection, or list/inspect available connection types
454402
Create {
455403
#[command(subcommand)]
@@ -472,39 +420,11 @@ pub enum ConnectionsCommands {
472420
output: String,
473421
},
474422

475-
/// Update a connection in a workspace
476-
Update {
477-
/// Connection ID
478-
connection_id: String,
479-
480-
/// New connection name
481-
#[arg(long)]
482-
name: Option<String>,
483-
484-
/// New connection type
485-
#[arg(long = "type")]
486-
conn_type: Option<String>,
487-
488-
/// New connection config as JSON string
489-
#[arg(long)]
490-
config: Option<String>,
491-
492-
/// Output format
493-
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
494-
output: String,
495-
},
496-
497423
/// Refresh a connection's schema
498424
Refresh {
499425
/// Connection ID
500426
connection_id: String,
501427
},
502-
503-
/// Delete a connection from a workspace
504-
Delete {
505-
/// Connection ID
506-
connection_id: String,
507-
},
508428
}
509429

510430
#[derive(Subcommand)]

src/connections.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,41 @@ struct Connection {
7070
source_type: String,
7171
}
7272

73+
#[derive(Deserialize, Serialize)]
74+
struct ConnectionDetail {
75+
id: String,
76+
name: String,
77+
source_type: String,
78+
#[serde(default)]
79+
table_count: u64,
80+
#[serde(default)]
81+
synced_table_count: u64,
82+
}
83+
7384
#[derive(Deserialize)]
7485
struct ListResponse {
7586
connections: Vec<Connection>,
7687
}
7788

89+
pub fn get(workspace_id: &str, connection_id: &str, format: &str) {
90+
let api = ApiClient::new(Some(workspace_id));
91+
let detail: ConnectionDetail = api.get(&format!("/connections/{connection_id}"));
92+
93+
match format {
94+
"json" => println!("{}", serde_json::to_string_pretty(&detail).unwrap()),
95+
"yaml" => print!("{}", serde_yaml::to_string(&detail).unwrap()),
96+
"table" => {
97+
use crossterm::style::Stylize;
98+
let label = |l: &str| format!("{:<16}", l).dark_grey().to_string();
99+
println!("{}{}", label("id:"), detail.id.dark_cyan());
100+
println!("{}{}", label("name:"), detail.name.white());
101+
println!("{}{}", label("source_type:"), detail.source_type.green());
102+
println!("{}{}", label("tables:"), format!("{} synced / {} total", detail.synced_table_count.to_string().cyan(), detail.table_count.to_string().cyan()));
103+
}
104+
_ => unreachable!(),
105+
}
106+
}
107+
78108
pub fn create(
79109
workspace_id: &str,
80110
name: &str,

src/main.rs

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -131,47 +131,55 @@ fn main() {
131131
Commands::Workspaces { command } => match command {
132132
WorkspaceCommands::List { output } => workspace::list(&output),
133133
WorkspaceCommands::Set { workspace_id } => workspace::set(workspace_id.as_deref()),
134-
_ => eprintln!("not yet implemented"),
135134
},
136-
Commands::Connections { workspace_id, command } => {
135+
Commands::Connections { id, workspace_id, output, command } => {
137136
let workspace_id = resolve_workspace(workspace_id);
138-
match command {
139-
ConnectionsCommands::New => connections_new::run(&workspace_id),
140-
ConnectionsCommands::List { output } => {
141-
connections::list(&workspace_id, &output)
142-
}
143-
ConnectionsCommands::Create { command, name, source_type, config, output } => {
144-
match command {
145-
Some(ConnectionsCreateCommands::List { name, output }) => {
146-
match name.as_deref() {
147-
Some(name) => connections::types_get(&workspace_id, name, &output),
148-
None => connections::types_list(&workspace_id, &output),
137+
if let Some(id) = id {
138+
connections::get(&workspace_id, &id, &output)
139+
} else {
140+
match command {
141+
Some(ConnectionsCommands::New) => connections_new::run(&workspace_id),
142+
Some(ConnectionsCommands::List { output }) => {
143+
connections::list(&workspace_id, &output)
144+
}
145+
Some(ConnectionsCommands::Create { command, name, source_type, config, output }) => {
146+
match command {
147+
Some(ConnectionsCreateCommands::List { name, output }) => {
148+
match name.as_deref() {
149+
Some(name) => connections::types_get(&workspace_id, name, &output),
150+
None => connections::types_list(&workspace_id, &output),
151+
}
149152
}
150-
}
151-
None => {
152-
let missing: Vec<&str> = [
153-
name.is_none().then_some("--name"),
154-
source_type.is_none().then_some("--type"),
155-
config.is_none().then_some("--config"),
156-
].into_iter().flatten().collect();
157-
if !missing.is_empty() {
158-
eprintln!("error: missing required arguments: {}", missing.join(", "));
159-
std::process::exit(1);
153+
None => {
154+
let missing: Vec<&str> = [
155+
name.is_none().then_some("--name"),
156+
source_type.is_none().then_some("--type"),
157+
config.is_none().then_some("--config"),
158+
].into_iter().flatten().collect();
159+
if !missing.is_empty() {
160+
eprintln!("error: missing required arguments: {}", missing.join(", "));
161+
std::process::exit(1);
162+
}
163+
connections::create(
164+
&workspace_id,
165+
&name.unwrap(),
166+
&source_type.unwrap(),
167+
&config.unwrap(),
168+
&output,
169+
)
160170
}
161-
connections::create(
162-
&workspace_id,
163-
&name.unwrap(),
164-
&source_type.unwrap(),
165-
&config.unwrap(),
166-
&output,
167-
)
168171
}
169172
}
173+
Some(ConnectionsCommands::Refresh { connection_id }) => {
174+
connections::refresh(&workspace_id, &connection_id)
175+
}
176+
None => {
177+
use clap::CommandFactory;
178+
let mut cmd = Cli::command();
179+
cmd.build();
180+
cmd.find_subcommand_mut("connections").unwrap().print_help().unwrap();
181+
}
170182
}
171-
ConnectionsCommands::Refresh { connection_id } => {
172-
connections::refresh(&workspace_id, &connection_id)
173-
}
174-
_ => eprintln!("not yet implemented"),
175183
}
176184
},
177185
Commands::Tables { command } => match command {

0 commit comments

Comments
 (0)