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
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The official command-line interface for [Anythink](https://anythink.cloud) — t
- [roles](#roles)
- [api-keys](#api-keys)
- [menus](#menus)
- [integrations](#integrations)
- [pay](#pay)
- [oauth](#oauth)
- [api](#api)
Expand Down Expand Up @@ -467,7 +468,6 @@ anythink api-keys revoke 42 --yes

---


### menus

Manage dashboard sidebar menus in the active project. Menus control what entities appear in the Anythink dashboard and how they are grouped.
Expand Down Expand Up @@ -500,6 +500,87 @@ anythink menus add-item 250 badges --icon Award

---

### integrations

Manage integrations — both the catalog of available providers (Claude, OpenAI, Slack, Google, etc.) and the active connections that hold credentials for them.

```
anythink integrations list List available providers
anythink integrations get <provider> Show details and operations for one provider
anythink integrations connections list [--provider <p>] List your active connections

anythink integrations connect <provider> Create an API-key connection (Claude, OpenAI, etc.)
anythink integrations oauth status <provider> Show OAuth client setup status
anythink integrations oauth configure <provider> Set the OAuth client ID + secret
anythink integrations oauth connect <provider> Connect via the browser OAuth flow

anythink integrations test <connection-id> Test a connection
anythink integrations enable <connection-id> Enable a connection
anythink integrations disable <connection-id> Disable a connection
anythink integrations disconnect <connection-id> Delete a connection

anythink integrations execute <provider> <operation> Run an operation on a connected provider
```

**API-key providers — `integrations connect`**

| Flag | Description |
| -------------------- | ---------------------------------------------------------------------------------------- |
| `--api-key <key>` | API key for the provider. If omitted, you'll be prompted (input is hidden). |
| `--name <name>` | Friendly name for this connection (default: `<provider> connection`) |
| `--user-connection` | Make this a user-scoped connection (only the current user sees it). Default: tenant-wide. |

**OAuth providers — `integrations oauth connect`**

The CLI starts a local HTTP listener on `http://localhost:8745/callback`, opens your browser to the provider's authorisation URL, and exchanges the returned code for a connection — no copy/paste of auth codes required.

| Flag | Description |
| -------------------- | ------------------------------------------------------------------------ |
| `--name <name>` | Friendly name for this connection |
| `--user-connection` | Make this a user-scoped connection |
| `--port <n>` | Local callback port (default: `8745`) |
| `--no-open` | Don't try to open the browser — just print the URL |
| `--timeout <secs>` | How long to wait for the callback (default: `300`) |

OAuth credentials need to be set up once per provider before you can connect:

```bash
anythink integrations oauth configure slack # prompts for client_id + secret (hidden)
anythink integrations oauth connect slack --name main
```

**Running operations — `integrations execute`**

| Flag | Description |
| ------------------- | ----------------------------------------------------------------------------------- |
| `--input <k=v>` | Input parameter as `key=value`. Repeatable. |
| `--inputs <json>` | All inputs as a JSON object. |
| `--json` | Print the full JSON response (default: just the `content` field if present). |

**Examples**

```bash
# Browse what's available
anythink integrations list
anythink integrations get claude

# API-key flow (Claude, OpenAI)
anythink integrations connect claude --name "main"
anythink integrations execute claude generate-text --input "prompt=Tell me a haiku"

# OAuth flow (Slack, Google, GitHub)
anythink integrations oauth configure slack
anythink integrations oauth connect slack --name main

# Manage connections
anythink integrations connections list
anythink integrations test <connection-id>
anythink integrations disable <connection-id>
anythink integrations disconnect <connection-id> --yes
```

---

### pay

Configure and manage Anythink Pay — the built-in Stripe Connect integration for accepting payments in your project.
Expand Down
59 changes: 59 additions & 0 deletions src/Client/AnythinkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ public class AnythinkClient : HttpApiClient
public string BaseUrl { get; }
private string _org;

/// <summary>
/// Best-guess dashboard URL derived from the API URL. We assume the standard
/// cloud convention of "api.{host}" → "{host}". For non-standard setups the
/// caller should override this via --dashboard-url.
/// </summary>
public string DashboardUrl => BaseUrl.Replace("://api.", "://");

/// <summary>
/// Where the dashboard hosts the generic integrations OAuth callback. The
/// same URL is used for every provider — the dashboard differentiates by
/// state. Users add this to the OAuth app's "redirect URLs" list.
/// </summary>
public string IntegrationsCallbackUrl => $"{DashboardUrl}/org/{OrgId}/settings/integrations/callback";

public AnythinkClient(string orgId, string baseUrl, string? token = null, string? apiKey = null)
: base(token, apiKey)
{
Expand Down Expand Up @@ -106,6 +120,9 @@ public Task TriggerWorkflowAsync(int id, object? payload = null)

public Task DeleteWorkflowAsync(int id) => DeleteAsync(_org + $"/workflows/{id}");

public Task DeleteWorkflowStepAsync(int workflowId, int stepId)
=> DeleteAsync(_org + $"/workflows/{workflowId}/steps/{stepId}");

public async Task<PaginatedResult<WorkflowJob>> GetWorkflowJobsAsync(int workflowId, int page = 1, int pageSize = 10)
=> (await GetAsync<PaginatedResult<WorkflowJob>>(_org + $"/workflows/{workflowId}/jobs?page={page}&pageSize={pageSize}"))
?? new PaginatedResult<WorkflowJob>([], 0, null, false, page, pageSize);
Expand Down Expand Up @@ -275,6 +292,48 @@ public Task<ApiKeyResponse> CreateApiKeyAsync(CreateApiKeyRequest req)
public Task RevokeApiKeyAsync(int apiKeyId)
=> DeleteAsync(_org + $"/api-keys/{apiKeyId}");

// ── Integrations ──────────────────────────────────────────────────────────

public async Task<List<IntegrationDefinition>> GetIntegrationDefinitionsAsync()
=> (await GetAsync<List<IntegrationDefinition>>(_org + "/integrations/definitions")) ?? [];

public async Task<IntegrationDefinition?> GetIntegrationDefinitionAsync(string provider)
=> await GetAsync<IntegrationDefinition>(_org + $"/integrations/definitions/{provider}");

public async Task<List<IntegrationConnection>> GetIntegrationConnectionsAsync()
=> (await GetAsync<List<IntegrationConnection>>(_org + "/integrations/connections")) ?? [];

public async Task<List<IntegrationConnection>> GetIntegrationConnectionsForProviderAsync(string provider)
=> (await GetAsync<List<IntegrationConnection>>(_org + $"/integrations/definitions/{provider}/connections")) ?? [];

public Task<IntegrationConnection> CreateApiKeyConnectionAsync(CreateApiKeyConnectionRequest req)
=> PostAsync<IntegrationConnection>(_org + "/integrations/connections/api-key", req);

public Task<IntegrationConnection?> UpdateIntegrationConnectionAsync(string connectionId, UpdateConnectionRequest req)
=> PutAsync<IntegrationConnection>(_org + $"/integrations/connections/{connectionId}", req);

public Task DeleteIntegrationConnectionAsync(string connectionId)
=> DeleteAsync(_org + $"/integrations/connections/{connectionId}");

public Task<TestConnectionResult> TestIntegrationConnectionAsync(string connectionId)
=> PostAsync<TestConnectionResult>(_org + $"/integrations/connections/{connectionId}/test");

public Task<IntegrationOAuthSettings?> GetIntegrationOAuthSettingsAsync(string provider)
=> GetAsync<IntegrationOAuthSettings>(_org + $"/integrations/definitions/{provider}/oauth");

public Task SetIntegrationOAuthSettingsAsync(string provider, SetOAuthSettingsRequest req)
=> PutVoidAsync(_org + $"/integrations/definitions/{provider}/oauth", req);

public async Task<OAuthUrlResponse> GetIntegrationOAuthUrlAsync(string provider, string redirectUri)
=> (await GetAsync<OAuthUrlResponse>(_org + $"/integrations/definitions/{provider}/oauth-url?redirectUri={Uri.EscapeDataString(redirectUri)}"))
?? throw new AnythinkException("OAuth URL endpoint returned no response.", 0);

public Task<IntegrationConnection> CreateOAuthConnectionAsync(CreateConnectionRequest req)
=> PostAsync<IntegrationConnection>(_org + "/integrations/connections", req);

public Task<JsonObject> ExecuteIntegrationAsync(string provider, ExecuteIntegrationRequest req)
=> PostAsync<JsonObject>(_org + $"/integrations/definitions/{provider}/execute", req);

public Task<RoleResponse?> UpdateRoleWithPermissionsAsync(int roleId, UpdateRolePermissionsRequest req)
=> PutAsync<RoleResponse>(_org + $"/roles/{roleId}", req);

Expand Down
Loading