Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .github/workflows/publish-homebrew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ jobs:
publish-homebrew-formula:
runs-on: ubuntu-22.04
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PLAN: ${{ inputs.plan }}
GITHUB_USER: "axo bot"
GITHUB_EMAIL: "admin+bot@axo.dev"
if: ${{ !fromJson(inputs.plan).announcement_is_prerelease || fromJson(inputs.plan).publish_prereleases }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.HOTDATA_AUTOMATION_APP_ID }}
private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }}
owner: hotdata-dev
repositories: homebrew-tap

- uses: actions/checkout@v6
with:
persist-credentials: true
repository: "hotdata-dev/homebrew-tap"
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
token: ${{ steps.app-token.outputs.token }}

- name: Fetch homebrew formulae
uses: actions/download-artifact@v7
Expand All @@ -32,8 +38,8 @@ jobs:

- name: Patch and commit formula files
run: |
git config --global user.name "${GITHUB_USER}"
git config --global user.email "${GITHUB_EMAIL}"
git config --global user.name "hotdata-automation[bot]"
git config --global user.email "hotdata-automation[bot]@users.noreply.github.com"

for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do
filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output)
Expand All @@ -55,6 +61,8 @@ jobs:
git push

- name: Remove .rb from GitHub Release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG=$(echo "$PLAN" | jq -r '.announcement_tag')
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ API key priority (lowest to highest): config file → `HOTDATA_API_KEY` env var
| Command | Subcommands | Description |
| :-- | :-- | :-- |
| `auth` | `status`, `logout` | Authenticate (run without subcommand to log in) |
| `workspaces` | `list`, `set`, `get`, `create`, `update` | Manage workspaces |
| `connections` | `list`, `get`, `create`, `refresh`, `update`, `delete`, `new` | Manage connections |
| `workspaces` | `list`, `set` | Manage workspaces |
| `connections` | `list`, `create`, `refresh`, `new` | Manage connections |
| `tables` | `list` | List tables and columns |
| `datasets` | `list`, `create` | Manage uploaded datasets |
| `query` | | Execute a SQL query |
Expand Down Expand Up @@ -85,13 +85,14 @@ hotdata workspaces set [<workspace_id>]
## Connections

```sh
hotdata connections list [--workspace-id <id>] [--format table|json|yaml]
hotdata connections get <connection_id> [--workspace-id <id>] [--format yaml|json|table]
hotdata connections refresh <connection_id> [--workspace-id <id>]
hotdata connections new [--workspace-id <id>]
hotdata connections list [-w <id>] [-o table|json|yaml]
hotdata connections <connection_id> [-w <id>] [-o table|json|yaml]
hotdata connections refresh <connection_id> [-w <id>]
hotdata connections new [-w <id>]
```

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

Expand Down
6 changes: 4 additions & 2 deletions skills/hotdata-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ Returns workspaces with `public_id`, `name`, `active`, `favorite`, `provision_st

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

### Create a Connection

Expand Down
96 changes: 8 additions & 88 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,19 @@ pub enum Commands {

/// Manage workspace connections
Connections {
/// Connection ID to show details
id: Option<String>,

/// Workspace ID (defaults to first workspace from login)
#[arg(long, short = 'w', global = true)]
workspace_id: Option<String>,

/// Output format (used with connection ID)
#[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "yaml"])]
output: String,

#[command(subcommand)]
command: ConnectionsCommands,
command: Option<ConnectionsCommands>,
},

/// Manage tables in a workspace
Expand Down Expand Up @@ -351,55 +358,6 @@ pub enum WorkspaceCommands {
/// Workspace ID to set as default (omit for interactive selection)
workspace_id: Option<String>,
},

/// Get details for a workspace
Get {
/// Workspace ID (defaults to first workspace from login)
#[arg(long, short = 'w')]
workspace_id: Option<String>,

/// Output format
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
output: String,
},

/// Create a new workspace
Create {
/// Workspace name
#[arg(long)]
name: String,

/// Workspace description
#[arg(long, default_value = "")]
description: String,

/// Organization ID for the workspace
#[arg(long)]
organization_id: String,

/// Output format
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
output: String,
},

/// Update an existing workspace
Update {
/// Workspace ID (defaults to first workspace from login)
#[arg(long, short = 'w')]
workspace_id: Option<String>,

/// New workspace name
#[arg(long)]
name: Option<String>,

/// New workspace description
#[arg(long)]
description: Option<String>,

/// Output format
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
output: String,
},
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -427,16 +385,6 @@ pub enum ConnectionsCommands {
output: String,
},

/// Get details for a specific connection
Get {
/// Connection ID
connection_id: String,

/// Output format
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
output: String,
},

/// Create a new connection, or list/inspect available connection types
Create {
#[command(subcommand)]
Expand All @@ -459,39 +407,11 @@ pub enum ConnectionsCommands {
output: String,
},

/// Update a connection in a workspace
Update {
/// Connection ID
connection_id: String,

/// New connection name
#[arg(long)]
name: Option<String>,

/// New connection type
#[arg(long = "type")]
conn_type: Option<String>,

/// New connection config as JSON string
#[arg(long)]
config: Option<String>,

/// Output format
#[arg(long = "output", short = 'o', default_value = "yaml", value_parser = ["table", "json", "yaml"])]
output: String,
},

/// Refresh a connection's schema
Refresh {
/// Connection ID
connection_id: String,
},

/// Delete a connection from a workspace
Delete {
/// Connection ID
connection_id: String,
},
}

#[derive(Subcommand)]
Expand Down
30 changes: 30 additions & 0 deletions src/connections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,41 @@ struct Connection {
source_type: String,
}

#[derive(Deserialize, Serialize)]
struct ConnectionDetail {
id: String,
name: String,
source_type: String,
#[serde(default)]
table_count: u64,
#[serde(default)]
synced_table_count: u64,
}

#[derive(Deserialize)]
struct ListResponse {
connections: Vec<Connection>,
}

pub fn get(workspace_id: &str, connection_id: &str, format: &str) {
let api = ApiClient::new(Some(workspace_id));
let detail: ConnectionDetail = api.get(&format!("/connections/{connection_id}"));

match format {
"json" => println!("{}", serde_json::to_string_pretty(&detail).unwrap()),
"yaml" => print!("{}", serde_yaml::to_string(&detail).unwrap()),
"table" => {
use crossterm::style::Stylize;
let label = |l: &str| format!("{:<16}", l).dark_grey().to_string();
println!("{}{}", label("id:"), detail.id.dark_cyan());
println!("{}{}", label("name:"), detail.name.white());
println!("{}{}", label("source_type:"), detail.source_type.green());
println!("{}{}", label("tables:"), format!("{} synced / {} total", detail.synced_table_count.to_string().cyan(), detail.table_count.to_string().cyan()));
}
_ => unreachable!(),
}
}

pub fn create(
workspace_id: &str,
name: &str,
Expand Down
76 changes: 42 additions & 34 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,47 +116,55 @@ fn main() {
Commands::Workspaces { command } => match command {
WorkspaceCommands::List { output } => workspace::list(&output),
WorkspaceCommands::Set { workspace_id } => workspace::set(workspace_id.as_deref()),
_ => eprintln!("not yet implemented"),
},
Commands::Connections { workspace_id, command } => {
Commands::Connections { id, workspace_id, output, command } => {
let workspace_id = resolve_workspace(workspace_id);
match command {
ConnectionsCommands::New => connections_new::run(&workspace_id),
ConnectionsCommands::List { output } => {
connections::list(&workspace_id, &output)
}
ConnectionsCommands::Create { command, name, source_type, config, output } => {
match command {
Some(ConnectionsCreateCommands::List { name, output }) => {
match name.as_deref() {
Some(name) => connections::types_get(&workspace_id, name, &output),
None => connections::types_list(&workspace_id, &output),
if let Some(id) = id {
connections::get(&workspace_id, &id, &output)
} else {
match command {
Some(ConnectionsCommands::New) => connections_new::run(&workspace_id),
Some(ConnectionsCommands::List { output }) => {
connections::list(&workspace_id, &output)
}
Some(ConnectionsCommands::Create { command, name, source_type, config, output }) => {
match command {
Some(ConnectionsCreateCommands::List { name, output }) => {
match name.as_deref() {
Some(name) => connections::types_get(&workspace_id, name, &output),
None => connections::types_list(&workspace_id, &output),
}
}
}
None => {
let missing: Vec<&str> = [
name.is_none().then_some("--name"),
source_type.is_none().then_some("--type"),
config.is_none().then_some("--config"),
].into_iter().flatten().collect();
if !missing.is_empty() {
eprintln!("error: missing required arguments: {}", missing.join(", "));
std::process::exit(1);
None => {
let missing: Vec<&str> = [
name.is_none().then_some("--name"),
source_type.is_none().then_some("--type"),
config.is_none().then_some("--config"),
].into_iter().flatten().collect();
if !missing.is_empty() {
eprintln!("error: missing required arguments: {}", missing.join(", "));
std::process::exit(1);
}
connections::create(
&workspace_id,
&name.unwrap(),
&source_type.unwrap(),
&config.unwrap(),
&output,
)
}
connections::create(
&workspace_id,
&name.unwrap(),
&source_type.unwrap(),
&config.unwrap(),
&output,
)
}
}
Some(ConnectionsCommands::Refresh { connection_id }) => {
connections::refresh(&workspace_id, &connection_id)
}
None => {
use clap::CommandFactory;
let mut cmd = Cli::command();
cmd.build();
cmd.find_subcommand_mut("connections").unwrap().print_help().unwrap();
}
}
ConnectionsCommands::Refresh { connection_id } => {
connections::refresh(&workspace_id, &connection_id)
}
_ => eprintln!("not yet implemented"),
}
},
Commands::Tables { command } => match command {
Expand Down
Loading