diff --git a/.docs/code-example-guide.md b/.docs/code-example-guide.md index d73dbb4..5bcaa2a 100644 --- a/.docs/code-example-guide.md +++ b/.docs/code-example-guide.md @@ -4,10 +4,13 @@ This guide defines the standards for code examples across the Kernel documentati ## General Principles -1. **Complete and runnable**: Every code example should be complete enough to run as-is +1. **Context-aware completeness**: Full examples must run as-is. Focused snippets can rely + on variables introduced by surrounding text or sibling examples, but they must make those + dependencies obvious. 2. **Consistent naming**: Use standardized variable names across all examples -3. **No hardcoded credentials**: Never include API keys or credentials in examples -4. **Multi-language support**: When applicable, show both TypeScript/JavaScript and Python examples +3. **No real secrets**: Never include real API keys, passwords, tokens, or live credentials. + Use obviously fake values when an auth-flow example needs credential-shaped input. +4. **Multi-language support**: When applicable, show TypeScript/JavaScript, Python, and Go examples ## Variable Naming Conventions @@ -27,8 +30,25 @@ This guide defines the standards for code examples across the Kernel documentati - Context: `context` - Page: `page` +### Go +- SDK client: `client` +- Browser instance: `kernelBrowser` +- Additional browsers: `kernelBrowser2`, etc. +- Context: `ctx` +- Session ID: `sessionID` +- Invocation ID: `invocationID` + ## Code Example Structure +### Full examples vs focused snippets + +Use a full example when the reader needs to copy and run a standalone program. Include imports, +SDK initialization, context setup, the main operation, and error handling. + +Use a focused snippet when the page is walking through one step in a larger flow. Keep the snippet +small, but rely only on variables the page already introduced, such as `client`, `ctx`, +`kernelBrowser`, `auth`, or `browser`. + ### Minimal Example (Browser Creation) Always include: @@ -55,6 +75,29 @@ kernel = Kernel() kernel_browser = kernel.browsers.create() print(kernel_browser.session_id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + fmt.Println(kernelBrowser.SessionID) +} +``` ### Full Example (With Browser Automation) @@ -129,6 +172,16 @@ const kernel = new Kernel(); kernel = Kernel() ``` +```go +package main + +import "github.com/kernel/kernel-go-sdk" + +func main() { + _ = kernel.NewClient() +} +``` + The SDK automatically reads the API key from the `KERNEL_API_KEY` environment variable. ### ❌ Incorrect - Hardcoded credentials @@ -145,6 +198,22 @@ kernel = Kernel(api_key="your-api-key") kernel = Kernel(api_key=os.getenv("KERNEL_API_KEY")) ``` +```go +// DON'T DO THIS +package main + +import ( + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/option" +) + +func main() { + _ = kernel.NewClient( + option.WithAPIKey("your-api-key"), + ) +} +``` + ## Feature-Specific Examples ### Simple Feature Toggle @@ -171,6 +240,28 @@ kernel_browser = kernel.browsers.create( stealth=True, ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + _, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Stealth: kernel.Bool(true), + }) + if err != nil { + panic(err) + } +} +``` ### Feature with Configuration @@ -197,10 +288,36 @@ kernel_browser = kernel.browsers.create( stealth=True ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Stealth: kernel.Bool(true), + }) + if err != nil { + panic(err) + } + + _ = kernelBrowser +} +``` ## App Development Examples +Kernel app examples currently use TypeScript/JavaScript and Python. Add a Go version only after the Go SDK has documented app framework support and the snippet has been tested against that SDK. + For Kernel app examples, follow this pattern: @@ -299,17 +416,19 @@ finally: ### Indentation - TypeScript/JavaScript: 2 spaces - Python: 4 spaces +- Go: tabs from `gofmt` ### String Quotes - TypeScript/JavaScript: Single quotes `'` (except for avoiding escaping) - Python: Double quotes `"` +- Go: Double quotes `"` ### Line Length - Keep lines under 100 characters when possible - Break long parameter lists across multiple lines ### Comments -- Use comments sparingly - code should be self-explanatory +- Use comments sparingly; keep code self-explanatory - Add comments only for non-obvious logic or important context - Never add comments like "NEW CODE:" or similar meta-comments @@ -340,6 +459,10 @@ Always use `` with proper language labels: ```python Python # Python code here ``` + +```go Go +// Go code here +``` ```` @@ -350,11 +473,13 @@ Before publishing a code example, verify: - [ ] Includes all necessary imports - [ ] SDK is initialized without hardcoded API keys - [ ] Variable names follow conventions -- [ ] Code is complete and runnable +- [ ] Code is complete and runnable, or it is a focused snippet with obvious prerequisites - [ ] Includes error handling (for full examples) - [ ] Includes cleanup code (for full examples) - [ ] Uses proper indentation and formatting -- [ ] Both TypeScript and Python versions are provided (when applicable) +- [ ] TypeScript/JavaScript, Python, and Go versions are provided (when applicable) +- [ ] Go examples are formatted with `gofmt` +- [ ] Go examples are tested against the actual Go SDK version the docs claim to support - [ ] Code has been tested or follows proven patterns ## Reference @@ -363,4 +488,3 @@ See these files for examples: - `introduction/create.mdx` - Standard browser creation pattern - `apps/develop.mdx` - App development pattern - `browsers/file-io.mdx` - Complex automation example - diff --git a/README.md b/README.md index 7e558f1..5d5b0d9 100644 --- a/README.md +++ b/README.md @@ -11,31 +11,15 @@ This is the documentation for the Kernel platform. It's connected to [onkernel.c ## Code Snippets -Code samples in the docs are generated from our OpenAPI spec so the examples stay in sync with the API. There are two ways the generator is invoked: +Code samples in the docs are authored inline in MDX. Keep SDK examples aligned with the SDK repos, and follow the standards in `.docs/code-example-guide.md` when adding or editing examples. -- Custom MDX tag: use `get /api/v1/users` (or omit the verb to use the default behavior). +When you add Go examples: -How the generator works (current behavior): - -- The script is `.github/scripts/generate_code_samples.ts` and is executed with Bun. It fetches the OpenAPI spec from the URL configured at the top of that script. -- It reads `x-codeSamples` entries for each operation and extracts samples for TypeScript/JavaScript and Python. Samples are normalized and may be transformed by simple "overrides" (see the script for the override parsing and injection heuristics). -- It writes snippet files under `snippets/openapi/` as MDX files containing a `` with the generated code fences. It also updates any MDX files under `apps/` and `browsers/` that contain the inline or tag forms by replacing them with the generated `` blocks. -- The generator can produce a base snippet and additional variant snippets controlled by `.github/scripts/code_samples.config.json` (variants are keyed by `"method /path"`). -- The script also removes stale snippet files matching the `-.mdx` suffix pattern. - -How it affects the repository: - -- New or updated files are created under `snippets/openapi/*.mdx`. -- MDX pages in `apps/` and `browsers/` may be modified in-place to replace ``/mustache tags with generated `` blocks. -- A GitHub Action (`.github/workflows/generate_code_snippets.yaml`) runs the script on push (except to `main`), commits any changes, and pushes them to the `gh_action_generated_docs` branch so Mintlify can deploy the generated docs. - -Notes and gotchas: - -- The generator runs remotely against the OpenAPI URL defined in the script, so it needs network access and the spec to include `x-codeSamples` for useful output. -- The override parsing and code injection are heuristic. Complex sample sources might not be transformed exactly as intended. See the script for details on how keys/`log` overrides are applied. -- The GitHub Action installs Bun and runs the script; if you run it locally, install Bun and run `bun run .github/scripts/generate_code_samples.ts` from the repo root. - -Example: to add a snippet placeholder to a page, add `get /api/v1/users` and let the generator fill `snippets/openapi` and update the page during the next run. +- Test complete snippets, or wrapped focused snippets, against the minimum released Go SDK version + that supports the API you're documenting. +- Run `gofmt` on complete snippets and on wrapper files used to validate focused snippets before + publishing. +- Note the validation you ran in the pull request description. ## Local Development diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 6a37c63..494ddc4 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -34,6 +34,33 @@ invocation = kernel.invocations.create( ) print(invocation.id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + invocation, err := client.Invocations.New(ctx, kernel.InvocationNewParams{ + ActionName: "analyze", + AppName: "my-app", + Version: "1.0.0", + }) + if err != nil { + panic(err) + } + + fmt.Println(invocation.ID) +} +``` ### Asynchronous invocations @@ -70,6 +97,34 @@ invocation = kernel.invocations.create( ) print(invocation.id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + invocation, err := client.Invocations.New(ctx, kernel.InvocationNewParams{ + Async: kernel.Bool(true), + ActionName: "analyze", + AppName: "my-app", + Version: "1.0.0", + }) + if err != nil { + panic(err) + } + + fmt.Println(invocation.ID) +} +``` ## Via CLI diff --git a/apps/logs.mdx b/apps/logs.mdx index 3c80749..1345439 100644 --- a/apps/logs.mdx +++ b/apps/logs.mdx @@ -21,6 +21,35 @@ from kernel import Kernel kernel = Kernel() logs = kernel.invocations.follow(invocation_id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + logs := client.Invocations.FollowStreaming(ctx, "inv_123", kernel.InvocationFollowParams{}) + defer logs.Close() + + for logs.Next() { + event := logs.Current() + if event.Event == "log" { + fmt.Println(event.Message) + } + } + if err := logs.Err(); err != nil { + panic(err) + } +} +``` Log lines will be truncated to 64KiB. For large payloads write data to external storage and log a reference instead. diff --git a/apps/status.mdx b/apps/status.mdx index 90b8f0f..21a8198 100644 --- a/apps/status.mdx +++ b/apps/status.mdx @@ -30,6 +30,35 @@ kernel = Kernel() response = kernel.invocations.follow(id="id") print(response) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + stream := client.Invocations.FollowStreaming(ctx, "id", kernel.InvocationFollowParams{}) + defer stream.Close() + + for stream.Next() { + event := stream.Current() + if event.Event == "invocation_state" { + fmt.Println(event.Invocation.Status) + } + } + if err := stream.Err(); err != nil { + panic(err) + } +} +``` ### Example @@ -86,5 +115,26 @@ kernel = Kernel() invocation = kernel.invocations.retrieve("rr33xuugxj9h0bkf1rdt2bet") print(invocation.status) ``` - +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + invocation, err := client.Invocations.Get(ctx, "rr33xuugxj9h0bkf1rdt2bet") + if err != nil { + panic(err) + } + fmt.Println(invocation.Status) +} +``` + diff --git a/apps/stop.mdx b/apps/stop.mdx index dfdea0a..da53a56 100644 --- a/apps/stop.mdx +++ b/apps/stop.mdx @@ -33,6 +33,30 @@ invocation = kernel.invocations.update( output='{"error":"Invocation cancelled by user"}', ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + invocation, err := client.Invocations.Update(ctx, "invocation_id", kernel.InvocationUpdateParams{ + Status: kernel.InvocationUpdateParamsStatusFailed, + Output: kernel.String(`{"error":"Invocation cancelled by user"}`), + }) + if err != nil { + panic(err) + } + _ = invocation +} +``` ## Via CLI diff --git a/auth/configuration.mdx b/auth/configuration.mdx index 35e5c58..2a8e61e 100644 --- a/auth/configuration.mdx +++ b/auth/configuration.mdx @@ -37,6 +37,21 @@ auth = await kernel.auth.connections.create( auto_reauth=False, ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + HealthChecks: kernel.Bool(false), + AutoReauth: kernel.Bool(false), + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` Both flags can be flipped on an existing connection with `auth.connections.update`; changes take effect immediately on the running connection. @@ -63,6 +78,20 @@ auth = await kernel.auth.connections.create( login_url="https://example.com/auth/signin", ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + LoginURL: kernel.String("https://example.com/auth/signin"), + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` ## SSO/OAuth Support @@ -89,6 +118,20 @@ auth = await kernel.auth.connections.create( allowed_domains=["sso.custom-provider.com"], ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + AllowedDomains: []string{"sso.custom-provider.com"}, + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` ## Custom Proxy @@ -123,6 +166,29 @@ auth = await kernel.auth.connections.create( proxy={"id": proxy.id}, ) ``` + +```go Go +proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeIsp, +}) +if err != nil { + panic(err) +} + +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + Proxy: kernel.ManagedAuthCreateRequestProxyParam{ + ID: kernel.String(proxy.ID), + }, + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` You can also reference a proxy by `name` instead of `id`. The proxy must belong to the same org and project as the connection. @@ -144,6 +210,19 @@ await kernel.auth.connections.update( proxy={"id": new_proxy.id}, ) ``` + +```go Go +_, err := client.Auth.Connections.Update(ctx, auth.ID, kernel.AuthConnectionUpdateParams{ + ManagedAuthUpdateRequest: kernel.ManagedAuthUpdateRequestParam{ + Proxy: kernel.ManagedAuthUpdateRequestProxyParam{ + ID: kernel.String(newProxy.ID), + }, + }, +}) +if err != nil { + panic(err) +} +``` You can also override the connection's proxy for a single login by passing `proxy` on `.login()` — useful when you want to try a one-off egress without changing the connection-wide default (which would also affect subsequent health checks and reauths). @@ -161,6 +240,18 @@ login = await kernel.auth.connections.login( proxy={"id": one_off_proxy.id}, ) ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{ + Proxy: kernel.AuthConnectionLoginParamsProxy{ + ID: kernel.String(oneOffProxy.ID), + }, +}) +if err != nil { + panic(err) +} +_ = login +``` ## Record Sessions for Debugging @@ -183,6 +274,20 @@ auth = await kernel.auth.connections.create( record_session=True, ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + RecordSession: kernel.Bool(true), + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` You can also override the connection default for a single login by passing `record_session` on `.login()` — useful for one-off debugging on a specific login attempt without flipping the connection-wide flag (which would also record subsequent health checks and reauths). @@ -200,6 +305,16 @@ login = await kernel.auth.connections.login( record_session=True, ) ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{ + RecordSession: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = login +``` Managed auth recordings are subject to the same retention rules as other session replay recordings. Each managed auth session row stores its own `replay_id` for the recording captured during that session. @@ -225,6 +340,23 @@ if managed_auth.post_login_url: await page.goto(managed_auth.post_login_url) # Start automation from the dashboard/home page ``` + +```go Go +managedAuth, err := client.Auth.Connections.Get(ctx, auth.ID) +if err != nil { + panic(err) +} + +if managedAuth.PostLoginURL != "" { + _, err := client.Browsers.Playwright.Execute(ctx, browser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: fmt.Sprintf(`await page.goto(%q);`, managedAuth.PostLoginURL), + }) + if err != nil { + panic(err) + } + // Start automation from the dashboard/home page +} +``` ## Updating a Connection @@ -262,4 +394,17 @@ await kernel.auth.connections.update( save_credentials=True, ) ``` + +```go Go +_, err := client.Auth.Connections.Update(ctx, auth.ID, kernel.AuthConnectionUpdateParams{ + ManagedAuthUpdateRequest: kernel.ManagedAuthUpdateRequestParam{ + LoginURL: kernel.String("https://example.com/new-login"), + HealthCheckInterval: kernel.Int(1800), + SaveCredentials: kernel.Bool(true), + }, +}) +if err != nil { + panic(err) +} +``` diff --git a/auth/connection-lifecycle.mdx b/auth/connection-lifecycle.mdx index 48d2adc..535002e 100644 --- a/auth/connection-lifecycle.mdx +++ b/auth/connection-lifecycle.mdx @@ -47,6 +47,17 @@ await kernel.auth.connections.update( health_check_interval=1800, # 30 minutes ) ``` + +```go Go +_, err := client.Auth.Connections.Update(ctx, auth.ID, kernel.AuthConnectionUpdateParams{ + ManagedAuthUpdateRequest: kernel.ManagedAuthUpdateRequestParam{ + HealthCheckInterval: kernel.Int(1800), // 30 minutes + }, +}) +if err != nil { + panic(err) +} +``` ### Sessions that expire faster than the interval @@ -96,6 +107,22 @@ if state.status == "NEEDS_AUTH": login = await kernel.auth.connections.login(auth.id) # Handle login flow as usual ``` + +```go Go +state, err := client.Auth.Connections.Get(ctx, auth.ID) +if err != nil { + panic(err) +} + +if state.Status == kernel.ManagedAuthStatusNeedsAuth { + login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) + if err != nil { + panic(err) + } + _ = login + // Handle login flow as usual +} +``` ## When a login fails @@ -140,6 +167,16 @@ login = await kernel.auth.connections.login( record_session=True, ) ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{ + RecordSession: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = login +``` To record every auth session on the connection (logins, health checks, and reauths), set `record_session: true` connection-wide — see [Record Sessions for Debugging](/auth/configuration#record-sessions-for-debugging). diff --git a/auth/credentials.mdx b/auth/credentials.mdx index 5850aa9..e31350a 100644 --- a/auth/credentials.mdx +++ b/auth/credentials.mdx @@ -26,6 +26,14 @@ const login = await kernel.auth.connections.login(auth.id); ```python Python login = await kernel.auth.connections.login(auth.id) ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +_ = login +``` Once saved, browser profiles stay authenticated automatically. When the session expires, Kernel re-authenticates using the stored credentials. Credentials are updated after every successful login. One-time codes (TOTP, SMS, etc.) are not saved. @@ -48,6 +56,20 @@ auth = await kernel.auth.connections.create( save_credentials=False, ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + SaveCredentials: kernel.Bool(false), + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` ## Pre-store credentials @@ -76,6 +98,23 @@ credential = await kernel.credentials.create( }, ) ``` + +```go Go +credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{ + CreateCredentialRequest: kernel.CreateCredentialRequestParam{ + Name: "my-netflix-login", + Domain: "netflix.com", + Values: map[string]string{ + "email": "user@netflix.com", + "password": "secretpassword123", + }, + }, +}) +if err != nil { + panic(err) +} +_ = credential +``` Then link the credential when creating a connection: @@ -102,6 +141,28 @@ auth = await kernel.auth.connections.create( # Start login - authenticates automatically using stored credentials login = await kernel.auth.connections.login(auth.id) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "netflix.com", + ProfileName: "my-profile", + Credential: kernel.ManagedAuthCreateRequestCredentialParam{ + Name: kernel.String(credential.Name), + }, + }, +}) +if err != nil { + panic(err) +} + +// Start login - authenticates automatically using stored credentials +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +_ = login +``` ### 2FA with TOTP @@ -132,6 +193,24 @@ credential = await kernel.credentials.create( totp_secret="JBSWY3DPEHPK3PXP", # From authenticator app setup ) ``` + +```go Go +credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{ + CreateCredentialRequest: kernel.CreateCredentialRequestParam{ + Name: "my-login", + Domain: "github.com", + Values: map[string]string{ + "username": "my-username", + "password": "my-password", + }, + TotpSecret: kernel.String("JBSWY3DPEHPK3PXP"), // From authenticator app setup + }, +}) +if err != nil { + panic(err) +} +_ = credential +``` ### SSO / OAuth @@ -176,6 +255,37 @@ auth = await kernel.auth.connections.create( credential={"name": credential.name}, ) ``` + +```go Go +credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{ + CreateCredentialRequest: kernel.CreateCredentialRequestParam{ + Name: "my-google-login", + Domain: "accounts.google.com", + SSOProvider: kernel.String("google"), + Values: map[string]string{ + "email": "user@gmail.com", + "password": "password", + }, + }, +}) +if err != nil { + panic(err) +} + +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "target-site.com", + ProfileName: "my-profile", + Credential: kernel.ManagedAuthCreateRequestCredentialParam{ + Name: kernel.String(credential.Name), + }, + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` ## Partial Credentials @@ -245,6 +355,67 @@ while state.flow_status == "IN_PROGRESS": state = await kernel.auth.connections.retrieve(auth.id) # TOTP auto-submitted from credential → SUCCESS ``` + +```go Go +credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{ + CreateCredentialRequest: kernel.CreateCredentialRequestParam{ + Name: "my-login", + Domain: "example.com", + Values: map[string]string{ + "email": "user@example.com", // No password + }, + TotpSecret: kernel.String("JBSWY3DPEHPK3PXP"), + }, +}) +if err != nil { + panic(err) +} + +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "example.com", + ProfileName: "my-profile", + Credential: kernel.ManagedAuthCreateRequestCredentialParam{ + Name: kernel.String(credential.Name), + }, + }, +}) +if err != nil { + panic(err) +} + +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +_ = login + +// Poll until password is needed +state, err := client.Auth.Connections.Get(ctx, auth.ID) +if err != nil { + panic(err) +} +for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { + if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 { + // Only password field will be pending (email auto-filled from credential) + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + Fields: map[string]string{"password": "user-provided-password"}, + }, + }) + if err != nil { + panic(err) + } + } + + time.Sleep(2 * time.Second) + state, err = client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } +} +// TOTP auto-submitted from credential → SUCCESS +``` This is useful when you want to: diff --git a/auth/hosted-ui.mdx b/auth/hosted-ui.mdx index 3e6d289..d052d69 100644 --- a/auth/hosted-ui.mdx +++ b/auth/hosted-ui.mdx @@ -30,6 +30,19 @@ auth = await kernel.auth.connections.create( profile_name="linkedin-profile", # Name of the profile to associate with the connection ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "linkedin.com", + ProfileName: "linkedin-profile", // Name of the profile to associate with the connection + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` ### 2. Start a Login Session @@ -44,6 +57,14 @@ const login = await kernel.auth.connections.login(auth.id); ```python Python login = await kernel.auth.connections.login(auth.id) ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +_ = login +``` ### 3. Collect Credentials @@ -59,6 +80,11 @@ window.location.href = login.hosted_url; # Return the URL to your frontend print(f"Redirect to: {login.hosted_url}") ``` + +```go Go +// Return the URL to your frontend +fmt.Println("Redirect to:", login.HostedURL) +``` The user will: @@ -94,6 +120,25 @@ while state.flow_status == "IN_PROGRESS": if state.status == "AUTHENTICATED": print("Authentication successful!") ``` + +```go Go +state, err := client.Auth.Connections.Get(ctx, auth.ID) +if err != nil { + panic(err) +} + +for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { + time.Sleep(2 * time.Second) + state, err = client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } +} + +if state.Status == kernel.ManagedAuthStatusAuthenticated { + fmt.Println("Authentication successful!") +} +``` @@ -124,6 +169,26 @@ browser = await kernel.browsers.create( # Navigate to the site—you're already logged in await page.goto("https://linkedin.com") ``` + +```go Go +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("linkedin-profile"), + }, + Stealth: kernel.Bool(true), +}) +if err != nil { + panic(err) +} + +// Navigate to the site—you're already logged in +_, err = client.Browsers.Playwright.Execute(ctx, browser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: `await page.goto("https://linkedin.com");`, +}) +if err != nil { + panic(err) +} +``` @@ -202,6 +267,77 @@ if state.status == "AUTHENTICATED": # Navigate to the site—you're already logged in await page.goto("https://doordash.com") ``` + +```go Go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/shared" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + // Create connection + auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "doordash.com", + ProfileName: "doordash-user-123", + }, + }) + if err != nil { + panic(err) + } + + // Start authentication + login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) + if err != nil { + panic(err) + } + + // Send user to hosted page + fmt.Println("Login URL:", login.HostedURL) + + // Poll for completion + state, err := client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } + for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { + time.Sleep(2 * time.Second) + state, err = client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } + } + + if state.Status == kernel.ManagedAuthStatusAuthenticated { + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("doordash-user-123"), + }, + Stealth: kernel.Bool(true), + }) + if err != nil { + panic(err) + } + + // Navigate to the site—you're already logged in + _, err = client.Browsers.Playwright.Execute(ctx, browser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: `await page.goto("https://doordash.com");`, + }) + if err != nil { + panic(err) + } + } +} +``` ## Success / error redirects @@ -236,6 +372,40 @@ query["error_url"] = "https://example.com/auth-failed" redirect_url = urlunparse(parsed._replace(query=urlencode(query))) ``` + +```go Go +package main + +import ( + "context" + "fmt" + "net/url" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + login, err := client.Auth.Connections.Login(ctx, "auth_123", kernel.AuthConnectionLoginParams{}) + if err != nil { + panic(err) + } + + redirectURL, err := url.Parse(login.HostedURL) + if err != nil { + panic(err) + } + + query := redirectURL.Query() + query.Set("success_url", "https://example.com/connected") + query.Set("error_url", "https://example.com/auth-failed") + redirectURL.RawQuery = query.Encode() + + fmt.Println(redirectURL.String()) +} +``` diff --git a/auth/overview.mdx b/auth/overview.mdx index 07c95d3..3a6653b 100644 --- a/auth/overview.mdx +++ b/auth/overview.mdx @@ -25,6 +25,19 @@ auth = await kernel.auth.connections.create( profile_name="netflix-user-123", ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "netflix.com", + ProfileName: "netflix-user-123", + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` @@ -66,6 +79,33 @@ while state.flow_status == "IN_PROGRESS": if state.status == "AUTHENTICATED": print("Authenticated!") ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} + +// Send user to login page +fmt.Println("Login URL:", login.HostedURL) + +// Poll until complete +state, err := client.Auth.Connections.Get(ctx, auth.ID) +if err != nil { + panic(err) +} +for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { + time.Sleep(2 * time.Second) + state, err = client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } +} + +if state.Status == kernel.ManagedAuthStatusAuthenticated { + fmt.Println("Authenticated!") +} +``` @@ -92,6 +132,27 @@ browser = await kernel.browsers.create( # Navigate to the site—you're already logged in await page.goto("https://netflix.com") ``` + +```go Go +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("netflix-user-123"), + }, + Stealth: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = browser + +// Navigate to the site—you're already logged in +_, err = client.Browsers.Playwright.Execute(ctx, browser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: `await page.goto("https://netflix.com");`, +}) +if err != nil { + panic(err) +} +``` diff --git a/auth/profiles.mdx b/auth/profiles.mdx index 2e44aaf..5833b55 100644 --- a/auth/profiles.mdx +++ b/auth/profiles.mdx @@ -38,6 +38,35 @@ try: except ConflictError: pass ``` + +```go Go +package main + +import ( + "context" + "errors" + "net/http" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + _, err := client.Profiles.New(ctx, kernel.ProfileNewParams{ + Name: kernel.String("profiles-demo"), + }) + if err != nil { + var apiErr *kernel.Error + if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict { + // Profile already exists + return + } + panic(err) + } +} +``` ## 2. Start a browser session using the profile and save changes @@ -63,6 +92,19 @@ kernel_browser = await kernel.browsers.create( } ) ``` + +```go Go +kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("profiles-demo"), + SaveChanges: kernel.Bool(true), + }, +}) +if err != nil { + panic(err) +} +_ = kernelBrowser +``` ## 3. Use the browser, then close it to persist the state @@ -89,6 +131,16 @@ print("Live view:", kernel_browser.browser_live_view_url) await kernel.browsers.delete_by_id(kernel_browser.session_id) ``` + +```go Go +fmt.Println("Live view:", kernelBrowser.BrowserLiveViewURL) + +// Navigate and create login state... + +if err := client.Browsers.DeleteByID(ctx, kernelBrowser.SessionID); err != nil { + panic(err) +} +``` ## 4. Start a new session with the saved profile (read-only) @@ -110,6 +162,18 @@ kernel_browser2 = await kernel.browsers.create( ) print("Live view:", kernel_browser2.browser_live_view_url) ``` + +```go Go +kernelBrowser2, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("profiles-demo"), + }, +}) +if err != nil { + panic(err) +} +fmt.Println("Live view:", kernelBrowser2.BrowserLiveViewURL) +``` ## Override opening existing tabs in a new session @@ -130,6 +194,19 @@ browser = await kernel.browsers.create( start_url="https://example.com/dashboard", ) ``` + +```go Go +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("profiles-demo"), + }, + StartURL: kernel.String("https://example.com/dashboard"), +}) +if err != nil { + panic(err) +} +_ = browser +``` The same behavior applies to browser pools configured with both a profile and start url. @@ -156,6 +233,24 @@ kernel_browser = await kernel.browsers.create() # Later, load a profile into the browser await kernel.browsers.update(kernel_browser.session_id, profile={"name": "profiles-demo"}) ``` + +```go Go +// Create a browser without a profile +kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) +if err != nil { + panic(err) +} + +// Later, load a profile into the browser +_, err = client.Browsers.Update(ctx, kernelBrowser.SessionID, kernel.BrowserUpdateParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("profiles-demo"), + }, +}) +if err != nil { + panic(err) +} +``` @@ -226,6 +321,56 @@ browser = await kernel.browsers.create( stealth=True, ) ``` + +```go Go +// Create a single profile with auth connections for three sites +gmailAuth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "gmail.com", + ProfileName: "workflow-bot", + }, +}) +if err != nil { + panic(err) +} +_ = gmailAuth + +slackAuth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "slack.com", + ProfileName: "workflow-bot", + }, +}) +if err != nil { + panic(err) +} +_ = slackAuth + +crmAuth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "crm.example.com", + ProfileName: "workflow-bot", + }, +}) +if err != nil { + panic(err) +} +_ = crmAuth + +// Authenticate each connection (omitted for brevity) + +// Launch a single browser — logged in to Gmail, Slack, and the CRM +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("workflow-bot"), + }, + Stealth: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = browser +``` ### User-to-profile mapping @@ -286,6 +431,51 @@ browser = await kernel.browsers.create( stealth=True, ) ``` + +```go Go +// For each user on your platform, create one profile +// and attach all their accounts as auth connections +userID := "user-123" + +if _, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "gmail.com", + ProfileName: userID, + }, +}); err != nil { + panic(err) +} + +if _, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "linkedin.com", + ProfileName: userID, + }, +}); err != nil { + panic(err) +} + +if _, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "github.com", + ProfileName: userID, + }, +}); err != nil { + panic(err) +} + +// When user-123 triggers a workflow, launch a browser with their profile +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String(userID), + }, + Stealth: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = browser +``` ## Notes diff --git a/auth/programmatic.mdx b/auth/programmatic.mdx index 5161fff..a547737 100644 --- a/auth/programmatic.mdx +++ b/auth/programmatic.mdx @@ -44,6 +44,19 @@ auth = await kernel.auth.connections.create( profile_name="github-profile", # Name of the profile to associate with the connection ) ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "github.com", + ProfileName: "github-profile", // Name of the profile to associate with the connection + }, +}) +if err != nil { + panic(err) +} +_ = auth +``` ### 2. Start a Login Session @@ -56,6 +69,14 @@ const login = await kernel.auth.connections.login(auth.id); ```python Python login = await kernel.auth.connections.login(auth.id) ``` + +```go Go +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +_ = login +``` Credentials are saved automatically on successful login, enabling automatic re-authentication when the session expires. @@ -99,6 +120,69 @@ while state.flow_status == "IN_PROGRESS": if state.status == "AUTHENTICATED": print("Authentication successful!") ``` + +```go Go +state, err := client.Auth.Connections.Get(ctx, auth.ID) +if err != nil { + panic(err) +} + +for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { + // Submit when fields are ready (login or 2FA) + if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 { + fieldValues := map[string]string{} + missingFields := []string{} + + for _, field := range state.DiscoveredFields { + switch field.Name { + case "username": + fieldValues[field.Name] = "dev-user" + case "email": + fieldValues[field.Name] = "dev@example.com" + case "password": + fieldValues[field.Name] = "correct-horse-battery-staple" + case "otp", "code", "totp": + fieldValues[field.Name] = "123456" + default: + switch field.Type { + case "email": + fieldValues[field.Name] = "dev@example.com" + case "password": + fieldValues[field.Name] = "correct-horse-battery-staple" + case "code", "totp": + fieldValues[field.Name] = "123456" + default: + missingFields = append(missingFields, field.Name) + } + } + } + + if len(missingFields) > 0 { + fmt.Println("Collect values for fields:", missingFields) + break + } + + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + Fields: fieldValues, + }, + }) + if err != nil { + panic(err) + } + } + + time.Sleep(2 * time.Second) + state, err = client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } +} + +if state.Status == kernel.ManagedAuthStatusAuthenticated { + fmt.Println("Authentication successful!") +} +``` The `discovered_fields` array tells you what the login form needs: @@ -216,6 +300,117 @@ if state.status == "AUTHENTICATED": # Navigate to the site—you're already logged in await page.goto("https://github.com") ``` + +```go Go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/shared" +) + +func promptUserForCode() string { + return "123456" +} + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + // Create connection + auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "github.com", + ProfileName: "github-profile", + }, + }) + if err != nil { + panic(err) + } + + login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) + if err != nil { + panic(err) + } + _ = login + + // Single polling loop handles login + 2FA + state, err := client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } + + for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress { + if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 { + // Check what fields are needed + fieldNames := map[string]bool{} + for _, field := range state.DiscoveredFields { + fieldNames[field.Name] = true + } + + if fieldNames["username"] { + // Initial login + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + Fields: map[string]string{ + "username": "my-username", + "password": "my-password", + }, + }, + }) + if err != nil { + panic(err) + } + } else { + // 2FA or additional fields + code := promptUserForCode() + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + Fields: map[string]string{ + state.DiscoveredFields[0].Name: code, + }, + }, + }) + if err != nil { + panic(err) + } + } + } + + time.Sleep(2 * time.Second) + state, err = client.Auth.Connections.Get(ctx, auth.ID) + if err != nil { + panic(err) + } + } + + if state.Status == kernel.ManagedAuthStatusAuthenticated { + fmt.Println("Authentication successful!") + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Profile: shared.BrowserProfileParam{ + Name: kernel.String("github-profile"), + }, + Stealth: kernel.Bool(true), + }) + if err != nil { + panic(err) + } + + // Navigate to the site—you're already logged in + _, err = client.Browsers.Playwright.Execute(ctx, browser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: `await page.goto("https://github.com");`, + }) + if err != nil { + panic(err) + } + } +} +``` @@ -261,6 +456,25 @@ if state.pending_sso_buttons: sso_button_selector=state.pending_sso_buttons[0]["selector"], ) ``` + +```go Go +if len(state.PendingSSOButtons) > 0 { + // Show the user available SSO options + for _, btn := range state.PendingSSOButtons { + fmt.Printf("%s: %s\n", btn.Provider, btn.Label) + } + + // Submit the selected SSO button + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + SSOButtonSelector: kernel.String(state.PendingSSOButtons[0].Selector), + }, + }) + if err != nil { + panic(err) + } +} +``` @@ -289,6 +503,20 @@ if state.pending_sso_buttons: sso_provider=state.pending_sso_buttons[0]["provider"], # e.g., "google" ) ``` + +```go Go +if len(state.PendingSSOButtons) > 0 { + // Submit by provider name instead of selector + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + SSOProvider: kernel.String(state.PendingSSOButtons[0].Provider), // e.g., "google" + }, + }) + if err != nil { + panic(err) + } +} +``` @@ -326,6 +554,25 @@ if state.mfa_options: mfa_option_id="sms", ) ``` + +```go Go +if len(state.MfaOptions) > 0 { + // Available types: sms, email, totp, push, call, password, switch + for _, opt := range state.MfaOptions { + fmt.Printf("%s: %s\n", opt.Type, opt.Label) + } + + // Submit the selected MFA method + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + MfaOptionID: kernel.String("sms"), + }, + }) + if err != nil { + panic(err) + } +} +``` After selecting an MFA method, the flow continues. Poll for `discovered_fields` to submit the code, or handle external actions for push/security key. @@ -368,6 +615,28 @@ if state.sign_in_options: sign_in_option_id=state.sign_in_options[0]["id"], ) ``` + +```go Go +if len(state.SignInOptions) > 0 { + // Show available options to the user + for _, opt := range state.SignInOptions { + fmt.Printf("%s: %s\n", opt.ID, opt.Label) + if opt.Description != "" { + fmt.Println(" " + opt.Description) + } + } + + // Submit the selected option + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + SignInOptionID: kernel.String(state.SignInOptions[0].ID), + }, + }) + if err != nil { + panic(err) + } +} +``` @@ -413,6 +682,29 @@ if state.flow_step == "AWAITING_EXTERNAL_ACTION": # Otherwise keep polling—the flow resumes automatically when the user completes the action ``` + +```go Go +if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingExternalAction { + // Show the message to the user + fmt.Println(state.ExternalActionMessage) + // e.g., "Check your phone for a push notification" + + // Some sites offer fallback methods alongside the external action + // (e.g. "Try another way"). Submit one to switch verification methods. + if len(state.MfaOptions) > 0 { + _, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + MfaOptionID: kernel.String(state.MfaOptions[0].Type), + }, + }) + if err != nil { + panic(err) + } + } + + // Otherwise keep polling—the flow resumes automatically when the user completes the action +} +``` diff --git a/auth/react.mdx b/auth/react.mdx index bd295d5..4402a33 100644 --- a/auth/react.mdx +++ b/auth/react.mdx @@ -42,6 +42,24 @@ auth = await kernel.auth.connections.create( login = await kernel.auth.connections.login(auth.id) # login.id, login.handoff_code ``` + +```go Go +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "netflix.com", + ProfileName: "user-123", + }, +}) +if err != nil { + panic(err) +} + +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +fmt.Println(login.ID, login.HandoffCode) +``` diff --git a/browsers/bot-detection/stealth.mdx b/browsers/bot-detection/stealth.mdx index 53c110a..e21d0f2 100644 --- a/browsers/bot-detection/stealth.mdx +++ b/browsers/bot-detection/stealth.mdx @@ -37,6 +37,29 @@ kernel_browser = kernel.browsers.create( stealth=True, ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Stealth: kernel.Bool(true), + }) + if err != nil { + panic(err) + } + _ = kernelBrowser +} +``` ## Bring your own proxy or CAPTCHA solver @@ -65,6 +88,17 @@ kernel_browser = kernel.browsers.create( proxy_id=my_proxy.id, ) ``` + +```go Go +kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Stealth: kernel.Bool(true), + ProxyID: kernel.String(myProxy.ID), +}) +if err != nil { + panic(err) +} +_ = kernelBrowser +``` If you're looking for proxy-level configuration with Kernel browsers, see [Proxies](/proxies/overview). diff --git a/browsers/computer-controls.mdx b/browsers/computer-controls.mdx index fe5f916..98471df 100644 --- a/browsers/computer-controls.mdx +++ b/browsers/computer-controls.mdx @@ -58,6 +58,45 @@ kernel.browsers.computer.click_mouse( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Basic left click at (100, 200) + if err := client.Browsers.Computer.ClickMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerClickMouseParams{ + X: 100, + Y: 200, + }); err != nil { + panic(err) + } + + // Double right-click while holding Shift + if err := client.Browsers.Computer.ClickMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerClickMouseParams{ + X: 100, + Y: 200, + Button: kernel.BrowserComputerClickMouseParamsButtonRight, + ClickType: kernel.BrowserComputerClickMouseParamsClickTypeClick, + NumClicks: kernel.Int(2), + HoldKeys: []string{"Shift"}, + }); err != nil { + panic(err) + } +} +``` + ```bash CLI # Click the mouse at coordinates (100, 200) kernel browsers computer click-mouse --x 100 --y 200 @@ -139,6 +178,52 @@ kernel.browsers.computer.move_mouse( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Human-like smooth movement (default) + if err := client.Browsers.Computer.MoveMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerMoveMouseParams{ + X: 500, + Y: 300, + }); err != nil { + panic(err) + } + + // Smooth movement with custom duration + if err := client.Browsers.Computer.MoveMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerMoveMouseParams{ + X: 800, + Y: 600, + Smooth: kernel.Bool(true), + DurationMs: kernel.Int(1500), + }); err != nil { + panic(err) + } + + // Instant teleport (disable smooth) + if err := client.Browsers.Computer.MoveMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerMoveMouseParams{ + X: 100, + Y: 200, + Smooth: kernel.Bool(false), + }); err != nil { + panic(err) + } +} +``` + ```bash CLI # Smooth movement (default) kernel browsers computer move-mouse --x 500 --y 300 @@ -210,6 +295,68 @@ with open('region.png', 'wb') as f: f.write(image_data.read()) ``` +```go Go +package main + +import ( + "context" + "io" + "os" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Full screenshot + response, err := client.Browsers.Computer.CaptureScreenshot(ctx, kernelBrowser.SessionID, kernel.BrowserComputerCaptureScreenshotParams{}) + if err != nil { + panic(err) + } + imageData, err := io.ReadAll(response.Body) + closeErr := response.Body.Close() + if err != nil { + panic(err) + } + if closeErr != nil { + panic(closeErr) + } + if err := os.WriteFile("screenshot.png", imageData, 0644); err != nil { + panic(err) + } + + // Region screenshot + response, err = client.Browsers.Computer.CaptureScreenshot(ctx, kernelBrowser.SessionID, kernel.BrowserComputerCaptureScreenshotParams{ + Region: kernel.BrowserComputerCaptureScreenshotParamsRegion{ + X: 0, + Y: 0, + Width: 800, + Height: 600, + }, + }) + if err != nil { + panic(err) + } + imageData, err = io.ReadAll(response.Body) + closeErr = response.Body.Close() + if err != nil { + panic(err) + } + if closeErr != nil { + panic(closeErr) + } + if err := os.WriteFile("region.png", imageData, 0644); err != nil { + panic(err) + } +} +``` + ```bash CLI # Take a full screenshot kernel browsers computer screenshot --to screenshot.png @@ -297,6 +444,40 @@ kernel.browsers.computer.type_text( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Type literal text + if err := client.Browsers.Computer.TypeText(ctx, kernelBrowser.SessionID, kernel.BrowserComputerTypeTextParams{ + Text: "The quick brown fox jumps over the lazy dog.", + }); err != nil { + panic(err) + } + + // Fixed delay between keystrokes + if err := client.Browsers.Computer.TypeText(ctx, kernelBrowser.SessionID, kernel.BrowserComputerTypeTextParams{ + Text: "Slow typing...", + Delay: kernel.Int(100), + }); err != nil { + panic(err) + } +} +``` + ```bash CLI # Human-like smooth typing (default) kernel browsers computer type --text "The quick brown fox" @@ -363,6 +544,41 @@ kernel.browsers.computer.press_key( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Tap a key combination + if err := client.Browsers.Computer.PressKey(ctx, kernelBrowser.SessionID, kernel.BrowserComputerPressKeyParams{ + Keys: []string{"Ctrl+t"}, + }); err != nil { + panic(err) + } + + // Hold keys for 250ms while also holding Alt + if err := client.Browsers.Computer.PressKey(ctx, kernelBrowser.SessionID, kernel.BrowserComputerPressKeyParams{ + Keys: []string{"Ctrl+Shift+Tab"}, + Duration: kernel.Int(250), + HoldKeys: []string{"Alt"}, + }); err != nil { + panic(err) + } +} +``` + ```bash CLI # Press one or more keys (repeatable --key) kernel browsers computer press-key --key Ctrl+t @@ -406,6 +622,34 @@ kernel.browsers.computer.scroll( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + if err := client.Browsers.Computer.Scroll(ctx, kernelBrowser.SessionID, kernel.BrowserComputerScrollParams{ + X: 300, + Y: 400, + DeltaX: kernel.Int(0), + DeltaY: kernel.Int(120), + }); err != nil { + panic(err) + } +} +``` + ```bash CLI # Scroll at a position kernel browsers computer scroll --x 300 --y 400 --delta-y 120 @@ -504,6 +748,63 @@ kernel.browsers.computer.drag_mouse( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Human-like smooth drag (default) + if err := client.Browsers.Computer.DragMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerDragMouseParams{ + Path: [][]int64{ + {100, 200}, + {400, 350}, + {700, 200}, + }, + }); err != nil { + panic(err) + } + + // Smooth drag with custom duration + if err := client.Browsers.Computer.DragMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerDragMouseParams{ + Path: [][]int64{ + {100, 200}, + {400, 350}, + {700, 200}, + }, + Smooth: kernel.Bool(true), + DurationMs: kernel.Int(2000), + }); err != nil { + panic(err) + } + + // Linear interpolation drag (legacy behavior) + if err := client.Browsers.Computer.DragMouse(ctx, kernelBrowser.SessionID, kernel.BrowserComputerDragMouseParams{ + Path: [][]int64{ + {100, 200}, + {400, 350}, + {700, 200}, + }, + Smooth: kernel.Bool(false), + StepsPerSegment: kernel.Int(10), + StepDelayMs: kernel.Int(50), + }); err != nil { + panic(err) + } +} +``` + ```bash CLI # Smooth drag (default) kernel browsers computer drag-mouse \ diff --git a/browsers/curl.mdx b/browsers/curl.mdx index bb42a03..af810c8 100644 --- a/browsers/curl.mdx +++ b/browsers/curl.mdx @@ -49,6 +49,45 @@ browser = client.browsers.create() response: httpx.Response = client.browsers.request(browser.session_id, "GET", "https://example.com") print("status", response.status_code) ``` + +```go Go +package main + +import ( + "context" + "fmt" + "io" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + httpClient, err := client.Browsers.HTTPClient(browser.SessionID) + if err != nil { + panic(err) + } + + response, err := httpClient.Get("https://example.com") + if err != nil { + panic(err) + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + panic(err) + } + fmt.Println("body", string(body)) +} +``` ## Buffered browser curl @@ -82,6 +121,36 @@ browser = client.browsers.create() buffered = client.browsers.curl(browser.session_id, url="https://example.com", method="GET") print("body", buffered.body) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + buffered, err := client.Browsers.Curl(ctx, browser.SessionID, kernel.BrowserCurlParams{ + URL: "https://example.com", + Method: kernel.BrowserCurlParamsMethodGet, + }) + if err != nil { + panic(err) + } + fmt.Println("body", buffered.Body) +} +``` ## Concurrency limits diff --git a/browsers/extensions.mdx b/browsers/extensions.mdx index 736997c..1c9da35 100644 --- a/browsers/extensions.mdx +++ b/browsers/extensions.mdx @@ -70,6 +70,32 @@ kernel = Kernel() kernel_browser = kernel.browsers.create(extensions=[{"name": "my-extension"}]) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/shared" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Extensions: []shared.BrowserExtensionParam{ + {Name: kernel.String("my-extension")}, + }, + }) + if err != nil { + panic(err) + } + _ = kernelBrowser +} +``` + ```bash CLI kernel browsers create --extension my-extension ``` diff --git a/browsers/gpu-acceleration.mdx b/browsers/gpu-acceleration.mdx index 638d2ab..e9381e0 100644 --- a/browsers/gpu-acceleration.mdx +++ b/browsers/gpu-acceleration.mdx @@ -32,6 +32,29 @@ kernel_browser = kernel.browsers.create( gpu=True ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + GPU: kernel.Bool(true), + }) + if err != nil { + panic(err) + } + _ = kernelBrowser +} +``` You can also enable GPU acceleration in the dashboard when deploying a browser under **Advanced Configuration**. diff --git a/browsers/headless.mdx b/browsers/headless.mdx index ae8b239..b2bb1d9 100644 --- a/browsers/headless.mdx +++ b/browsers/headless.mdx @@ -27,6 +27,29 @@ kernel = Kernel() kernel_browser = kernel.browsers.create(headless=True) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Headless: kernel.Bool(true), + }) + if err != nil { + panic(err) + } + _ = kernelBrowser +} +``` diff --git a/browsers/live-view.mdx b/browsers/live-view.mdx index 788379f..c111407 100644 --- a/browsers/live-view.mdx +++ b/browsers/live-view.mdx @@ -24,6 +24,29 @@ kernel = Kernel() browser = kernel.browsers.create() print(browser.browser_live_view_url) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + fmt.Println(browser.BrowserLiveViewURL) +} +``` @@ -82,6 +105,16 @@ kernel_browser = kernel.browsers.create( ) ``` +```go Go +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + KioskMode: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = browser +``` + ## URL lifetime diff --git a/browsers/playwright-execution.mdx b/browsers/playwright-execution.mdx index 9b28602..fd05c76 100644 --- a/browsers/playwright-execution.mdx +++ b/browsers/playwright-execution.mdx @@ -60,6 +60,41 @@ response = kernel.browsers.playwright.execute( print(response.result) # "Example Domain" ``` +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + // Create a browser + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + // Execute Playwright code + response, err := client.Browsers.Playwright.Execute(ctx, kernelBrowser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://example.com'); + return await page.title(); + `, + }) + if err != nil { + panic(err) + } + + fmt.Println(response.Result) // "Example Domain" +} +``` + ```bash CLI kernel browsers playwright execute 'await page.goto("https://www.onkernel.com"); return page.title();' ``` @@ -107,6 +142,22 @@ response = kernel.browsers.playwright.execute( print(response.result) # {'title': 'Example Domain', 'url': 'https://example.com'} ``` + +```go Go +response, err := client.Browsers.Playwright.Execute(ctx, sessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://example.com'); + const title = await page.title(); + const url = page.url(); + return { title, url }; + `, +}) +if err != nil { + panic(err) +} + +fmt.Println(response.Result) // map[title:Example Domain url:https://example.com] +``` ## Timeout configuration @@ -137,6 +188,20 @@ response = kernel.browsers.playwright.execute( timeout_sec=120 ) ``` + +```go Go +response, err := client.Browsers.Playwright.Execute(ctx, sessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://example.com'); + return await page.title(); + `, + TimeoutSec: kernel.Int(120), +}) +if err != nil { + panic(err) +} +_ = response +``` ## Error handling @@ -174,6 +239,23 @@ if not response.success: print('Error:', response.error) print('Stderr:', response.stderr) ``` + +```go Go +response, err := client.Browsers.Playwright.Execute(ctx, sessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://invalid-url'); + return await page.title(); + `, +}) +if err != nil { + panic(err) +} + +if !response.Success { + fmt.Println("Error:", response.Error) + fmt.Println("Stderr:", response.Stderr) +} +``` ## Use cases diff --git a/browsers/pools/overview.mdx b/browsers/pools/overview.mdx index bec0d85..d3ffd1c 100644 --- a/browsers/pools/overview.mdx +++ b/browsers/pools/overview.mdx @@ -82,6 +82,41 @@ pool = kernel.browser_pools.create( print(pool.id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/shared" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + pool, err := client.BrowserPools.New(ctx, kernel.BrowserPoolNewParams{ + Name: kernel.String("my-pool"), + Size: 10, + Stealth: kernel.Bool(true), + Headless: kernel.Bool(false), + TimeoutSeconds: kernel.Int(600), + StartURL: kernel.String("https://example.com"), + Viewport: shared.BrowserViewportParam{ + Width: 1280, + Height: 800, + }, + }) + if err != nil { + panic(err) + } + + fmt.Println(pool.ID) +} +``` ### Pool configuration options @@ -111,6 +146,18 @@ browser = kernel.browser_pools.acquire( print(browser.session_id) print(browser.cdp_ws_url) ``` + +```go Go +browser, err := client.BrowserPools.Acquire(ctx, "my-pool", kernel.BrowserPoolAcquireParams{ + AcquireTimeoutSeconds: kernel.Int(30), +}) +if err != nil { + panic(err) +} + +fmt.Println(browser.SessionID) +fmt.Println(browser.CdpWsURL) +``` The acquired browser includes all the same properties as a regular browser session, including `cdp_ws_url` for CDP connections and `browser_live_view_url` for live viewing. @@ -138,6 +185,15 @@ kernel.browser_pools.release( reuse=True, ) ``` + +```go Go +if err := client.BrowserPools.Release(ctx, "my-pool", kernel.BrowserPoolReleaseParams{ + SessionID: browser.SessionID, + Reuse: kernel.Bool(true), +}); err != nil { + panic(err) +} +``` ## Update a pool @@ -159,6 +215,17 @@ updated_pool = kernel.browser_pools.update( stealth=True, ) ``` + +```go Go +updatedPool, err := client.BrowserPools.Update(ctx, "my-pool", kernel.BrowserPoolUpdateParams{ + Size: 20, + Stealth: kernel.Bool(true), +}) +if err != nil { + panic(err) +} +_ = updatedPool +``` @@ -179,6 +246,12 @@ await kernel.browserPools.flush("my-pool"); ```python Python kernel.browser_pools.flush("my-pool") ``` + +```go Go +if err := client.BrowserPools.Flush(ctx, "my-pool"); err != nil { + panic(err) +} +``` ## Get pool details @@ -199,6 +272,16 @@ pool = kernel.browser_pools.retrieve("my-pool") print(pool.available_count) print(pool.acquired_count) ``` + +```go Go +pool, err := client.BrowserPools.Get(ctx, "my-pool") +if err != nil { + panic(err) +} + +fmt.Println(pool.AvailableCount) +fmt.Println(pool.AcquiredCount) +``` ## List pools @@ -220,6 +303,17 @@ pools = kernel.browser_pools.list() for pool in pools: print(pool.name, pool.available_count) ``` + +```go Go +pools, err := client.BrowserPools.List(ctx) +if err != nil { + panic(err) +} + +for _, pool := range *pools { + fmt.Println(pool.Name, pool.AvailableCount) +} +``` ## Delete a pool @@ -242,6 +336,20 @@ kernel.browser_pools.delete("my-pool") # Force delete even if browsers are acquired kernel.browser_pools.delete("my-pool", force=True) ``` + +```go Go +// Delete a pool (fails if browsers are acquired) +if err := client.BrowserPools.Delete(ctx, "my-pool", kernel.BrowserPoolDeleteParams{}); err != nil { + panic(err) +} + +// Force delete even if browsers are acquired +if err := client.BrowserPools.Delete(ctx, "my-pool", kernel.BrowserPoolDeleteParams{ + Force: kernel.Bool(true), +}); err != nil { + panic(err) +} +``` ## Full example @@ -305,6 +413,49 @@ async def main(): asyncio.run(main()) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + // Acquire a browser from an existing pool + kernelBrowser, err := client.BrowserPools.Acquire(ctx, "my-pool", kernel.BrowserPoolAcquireParams{}) + if err != nil { + panic(err) + } + + defer func() { + // Release back to pool for reuse + if err := client.BrowserPools.Release(ctx, "my-pool", kernel.BrowserPoolReleaseParams{ + SessionID: kernelBrowser.SessionID, + }); err != nil { + panic(err) + } + }() + + response, err := client.Browsers.Playwright.Execute(ctx, kernelBrowser.SessionID, kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://example.com'); + return await page.title(); + `, + }) + if err != nil { + panic(err) + } + + fmt.Println(response.Result) +} +``` ## API reference diff --git a/browsers/pools/policy-json.mdx b/browsers/pools/policy-json.mdx index 1bd9c0d..3957b32 100644 --- a/browsers/pools/policy-json.mdx +++ b/browsers/pools/policy-json.mdx @@ -1,5 +1,5 @@ --- -title: "custom chrome policies" +title: "Custom Chrome Policies" description: "Customize Chrome behavior in reserved browser pools using Chrome policies" --- @@ -77,6 +77,53 @@ pool = kernel.browser_pools.create( } ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + pool, err := client.BrowserPools.New(ctx, kernel.BrowserPoolNewParams{ + Name: kernel.String("my-configured-pool"), + Size: 5, + ChromePolicy: map[string]any{ + "HomepageLocation": "https://kernel.sh", + "HomepageIsNewTabPage": false, + "ShowHomeButton": true, + "NewTabPageLocation": "https://kernel.sh/docs", + "RestoreOnStartup": 4, + "RestoreOnStartupURLs": []string{"https://kernel.sh"}, + "BookmarkBarEnabled": true, + "ManagedBookmarks": []map[string]any{ + {"toplevel_name": "Company Resources"}, + {"name": "Dashboard", "url": "https://example.com/dashboard"}, + {"name": "Documentation", "url": "https://example.com/docs"}, + { + "name": "Tools", + "children": []map[string]string{ + {"name": "Jira Board", "url": "https://example.com/jira"}, + {"name": "Slack", "url": "https://example.com/slack"}, + {"name": "GitHub PRs", "url": "https://example.com/github"}, + {"name": "Runbooks", "url": "https://example.com/runbooks"}, + }, + }, + }, + }, + }) + if err != nil { + panic(err) + } + _ = pool +} +``` ## Updating policies on an existing pool diff --git a/browsers/replays.mdx b/browsers/replays.mdx index a9964dd..751bf89 100644 --- a/browsers/replays.mdx +++ b/browsers/replays.mdx @@ -39,6 +39,42 @@ print(f"Recording started with ID: {replay.replay_id}") kernel.browsers.replays.stop(replay_id=replay.replay_id, id=kernel_browser.session_id) print("Recording stopped and processing") ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + replay, err := client.Browsers.Replays.Start(ctx, kernelBrowser.SessionID, kernel.BrowserReplayStartParams{}) + if err != nil { + panic(err) + } + fmt.Printf("Recording started with ID: %s\n", replay.ReplayID) + + // Perform some automation... + + if err := client.Browsers.Replays.Stop(ctx, replay.ReplayID, kernel.BrowserReplayStopParams{ + ID: kernelBrowser.SessionID, + }); err != nil { + panic(err) + } + fmt.Println("Recording stopped and processing") +} +``` ## Multiple recordings per session @@ -65,6 +101,30 @@ replay2 = kernel.browsers.replays.start(kernel_browser.session_id) # Perform different automation... kernel.browsers.replays.stop(replay_id=replay2.replay_id, id=kernel_browser.session_id) ``` + +```go Go +replay1, err := client.Browsers.Replays.Start(ctx, kernelBrowser.SessionID, kernel.BrowserReplayStartParams{}) +if err != nil { + panic(err) +} +// Perform some automation... +if err := client.Browsers.Replays.Stop(ctx, replay1.ReplayID, kernel.BrowserReplayStopParams{ + ID: kernelBrowser.SessionID, +}); err != nil { + panic(err) +} + +replay2, err := client.Browsers.Replays.Start(ctx, kernelBrowser.SessionID, kernel.BrowserReplayStartParams{}) +if err != nil { + panic(err) +} +// Perform different automation... +if err := client.Browsers.Replays.Stop(ctx, replay2.ReplayID, kernel.BrowserReplayStopParams{ + ID: kernelBrowser.SessionID, +}); err != nil { + panic(err) +} +``` ## Downloading all replays @@ -117,4 +177,63 @@ for replay in replays: print(f"Saved replay to {filename}") ``` + +```go Go +package main + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + sessionID := "brw_01jwv4tn5m8k3q2v7x9p0a1bc2" + + replays, err := client.Browsers.Replays.List(ctx, sessionID) + if err != nil { + panic(err) + } + + for _, replay := range *replays { + fmt.Printf("Replay ID: %s\n", replay.ReplayID) + fmt.Printf("View URL: %s\n", replay.ReplayViewURL) + + videoData, err := client.Browsers.Replays.Download(ctx, replay.ReplayID, kernel.BrowserReplayDownloadParams{ + ID: sessionID, + }) + if err != nil { + panic(err) + } + + filename := fmt.Sprintf("replay-%s-%s.mp4", replay.ReplayID, sessionID) + file, err := os.Create(filename) + if err != nil { + videoData.Body.Close() + panic(err) + } + + _, copyErr := io.Copy(file, videoData.Body) + closeErr := videoData.Body.Close() + fileErr := file.Close() + if copyErr != nil { + panic(copyErr) + } + if closeErr != nil { + panic(closeErr) + } + if fileErr != nil { + panic(fileErr) + } + + fmt.Printf("Saved replay to %s\n", filename) + } +} +``` diff --git a/browsers/termination.mdx b/browsers/termination.mdx index 79e2f22..add3c18 100644 --- a/browsers/termination.mdx +++ b/browsers/termination.mdx @@ -27,6 +27,25 @@ from kernel import Kernel kernel = Kernel() kernel.browsers.delete_by_id("htzv5orfit78e1m2biiifpbv") ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + if err := client.Browsers.DeleteByID(ctx, "htzv5orfit78e1m2biiifpbv"); err != nil { + panic(err) + } +} +``` ## Automatic deletion via timeout @@ -53,4 +72,29 @@ kernel = Kernel() browser = kernel.browsers.create(timeout_seconds=300) print(browser.session_id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + TimeoutSeconds: kernel.Int(300), + }) + if err != nil { + panic(err) + } + + fmt.Println(browser.SessionID) +} +``` diff --git a/browsers/viewport.mdx b/browsers/viewport.mdx index 37799ca..0c2d83b 100644 --- a/browsers/viewport.mdx +++ b/browsers/viewport.mdx @@ -21,6 +21,15 @@ const defaultViewport = await kernel.browsers.create(); default_viewport = kernel.browsers.create() ``` +```go Go +// Uses default viewport (1920x1080@25Hz) +defaultViewport, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) +if err != nil { + panic(err) +} +_ = defaultViewport +``` + ## Setting viewport configuration @@ -79,6 +88,47 @@ kernel_browser_auto = kernel.browsers.create( ) ``` +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/shared" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + // Explicitly specify refresh rate + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 1920, + Height: 1080, + RefreshRate: kernel.Int(25), + }, + }) + if err != nil { + panic(err) + } + _ = kernelBrowser + + // Auto-determine refresh rate from dimensions (25Hz for 1920x1080) + kernelBrowserAuto, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 1920, + Height: 1080, + }, + }) + if err != nil { + panic(err) + } + _ = kernelBrowserAuto +} +``` + @@ -200,6 +250,71 @@ wuxga = kernel.browsers.create( ) ``` +```go Go +// Full HD (1920x1080) at 25Hz - explicit refresh rate +fullHD, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 1920, + Height: 1080, + RefreshRate: kernel.Int(25), + }, +}) +if err != nil { + panic(err) +} +_ = fullHD + +// Full HD (1920x1080) - auto-determined 25Hz (Default configuration) +fullHDAuto, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 1920, + Height: 1080, + }, +}) +if err != nil { + panic(err) +} +_ = fullHDAuto + +// QHD (2560x1440) - auto-determined 10Hz +// Note: May affect live view responsiveness +qhd, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 2560, + Height: 1440, + }, +}) +if err != nil { + panic(err) +} +_ = qhd + +// XGA (1024x768) - auto-determined 60Hz +xga, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 1024, + Height: 768, + }, +}) +if err != nil { + panic(err) +} +_ = xga + +// WUXGA (1920x1200) at 25Hz - explicit refresh rate +wuxga, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Viewport: shared.BrowserViewportParam{ + Width: 1920, + Height: 1200, + RefreshRate: kernel.Int(25), + }, +}) +if err != nil { + panic(err) +} +_ = wuxga +``` + ## Dynamically changing the viewport @@ -227,6 +342,27 @@ kernel_browser = await kernel.browsers.create() # Later, change the viewport await kernel.browsers.update(kernel_browser.session_id, viewport={"width": 1024, "height": 768}) ``` + +```go Go +// Create a browser with default viewport +kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) +if err != nil { + panic(err) +} + +// Later, change the viewport +_, err = client.Browsers.Update(ctx, kernelBrowser.SessionID, kernel.BrowserUpdateParams{ + Viewport: kernel.BrowserUpdateParamsViewport{ + BrowserViewportParam: shared.BrowserViewportParam{ + Width: 1024, + Height: 768, + }, + }, +}) +if err != nil { + panic(err) +} +``` @@ -256,6 +392,21 @@ await kernel.browsers.update( viewport={"width": 1024, "height": 768, "force": True} ) ``` + +```go Go +_, err := client.Browsers.Update(ctx, kernelBrowser.SessionID, kernel.BrowserUpdateParams{ + Viewport: kernel.BrowserUpdateParamsViewport{ + BrowserViewportParam: shared.BrowserViewportParam{ + Width: 1024, + Height: 768, + }, + Force: kernel.Bool(true), + }, +}) +if err != nil { + panic(err) +} +``` ## Considerations diff --git a/info/api-keys.mdx b/info/api-keys.mdx index 20194ed..1f3b741 100644 --- a/info/api-keys.mdx +++ b/info/api-keys.mdx @@ -58,6 +58,34 @@ api_key = client.api_keys.create( print(api_key.key) # Save this value now. Kernel won't show it again. print(api_key.id, api_key.masked_key) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + apiKey, err := client.APIKeys.New(ctx, kernel.APIKeyNewParams{ + Name: "staging-ci", + DaysToExpire: kernel.Int(30), + ProjectID: kernel.String("proj_staging_9f3k"), + }) + if err != nil { + panic(err) + } + + fmt.Println(apiKey.Key) // Save this value now. Kernel won't show it again. + fmt.Println(apiKey.ID, apiKey.MaskedKey) +} +``` ## List and inspect API keys @@ -86,6 +114,40 @@ for api_key in client.api_keys.list(limit=20): api_key = client.api_keys.retrieve("key_01jwv4tn5m8k3q2v7x9p0a1bc2") print(api_key.project_id, api_key.expires_at) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + pager := client.APIKeys.ListAutoPaging(ctx, kernel.APIKeyListParams{ + Limit: kernel.Int(20), + }) + for pager.Next() { + apiKey := pager.Current() + fmt.Println(apiKey.ID, apiKey.Name, apiKey.MaskedKey) + } + if err := pager.Err(); err != nil { + panic(err) + } + + apiKey, err := client.APIKeys.Get(ctx, "key_01jwv4tn5m8k3q2v7x9p0a1bc2") + if err != nil { + panic(err) + } + + fmt.Println(apiKey.ProjectID, apiKey.ExpiresAt) +} +``` ## Rename or delete an API key @@ -114,6 +176,38 @@ client.api_keys.update( client.api_keys.delete("key_01jwv4tn5m8k3q2v7x9p0a1bc2") ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + apiKey, err := client.APIKeys.Update( + ctx, + "key_01jwv4tn5m8k3q2v7x9p0a1bc2", + kernel.APIKeyUpdateParams{ + Name: "staging-ci-rotated", + }, + ) + if err != nil { + panic(err) + } + fmt.Println(apiKey.ID, apiKey.Name) + + if err := client.APIKeys.Delete(ctx, "key_01jwv4tn5m8k3q2v7x9p0a1bc2"); err != nil { + panic(err) + } +} +``` ## Rotate a key diff --git a/info/projects.mdx b/info/projects.mdx index 9693db7..0a6764e 100644 --- a/info/projects.mdx +++ b/info/projects.mdx @@ -70,6 +70,43 @@ other = kernel.browsers.create( extra_headers={"X-Kernel-Project-Id": "proj_def456"}, ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/option" +) + +func main() { + ctx := context.Background() + + // Scope the whole client to a project. + client := kernel.NewClient( + option.WithHeader("X-Kernel-Project-Id", "proj_abc123"), + ) + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + _ = browser + + // Or override per-request. + other, err := client.Browsers.New( + ctx, + kernel.BrowserNewParams{}, + option.WithHeader("X-Kernel-Project-Id", "proj_def456"), + ) + if err != nil { + panic(err) + } + _ = other +} +``` ## Authentication and Project Scope @@ -143,6 +180,33 @@ kernel = Kernel() project = kernel.projects.create(name="staging") print(project.id) # proj_abc123 ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + project, err := client.Projects.New(ctx, kernel.ProjectNewParams{ + CreateProjectRequest: kernel.CreateProjectRequestParam{ + Name: "staging", + }, + }) + if err != nil { + panic(err) + } + + fmt.Println(project.ID) // proj_abc123 +} +``` ### List projects @@ -158,6 +222,17 @@ for await (const project of kernel.projects.list()) { for project in kernel.projects.list(): print(project.id, project.name, project.status) ``` + +```go Go +pager := client.Projects.ListAutoPaging(ctx, kernel.ProjectListParams{}) +for pager.Next() { + project := pager.Current() + fmt.Println(project.ID, project.Name, project.Status) +} +if err := pager.Err(); err != nil { + panic(err) +} +``` ### Update a project @@ -170,6 +245,22 @@ await kernel.projects.update('proj_abc123', { name: 'production' }); ```python Python kernel.projects.update("proj_abc123", name="production") ``` + +```go Go +project, err := client.Projects.Update( + ctx, + "proj_abc123", + kernel.ProjectUpdateParams{ + UpdateProjectRequest: kernel.UpdateProjectRequestParam{ + Name: kernel.String("production"), + }, + }, +) +if err != nil { + panic(err) +} +_ = project +``` ### Delete a project @@ -182,6 +273,12 @@ await kernel.projects.delete('proj_abc123'); ```python Python kernel.projects.delete("proj_abc123") ``` + +```go Go +if err := client.Projects.Delete(ctx, "proj_abc123"); err != nil { + panic(err) +} +``` diff --git a/integrations/1password.mdx b/integrations/1password.mdx index ea809e4..e93775b 100644 --- a/integrations/1password.mdx +++ b/integrations/1password.mdx @@ -69,6 +69,44 @@ auth = await kernel.auth.connections.create( login = await kernel.auth.connections.login(auth.id) ``` + +```go Go +// Option 1: Auto-lookup by domain +auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "github.com", + ProfileName: "my-github-profile", + Credential: kernel.ManagedAuthCreateRequestCredentialParam{ + Provider: kernel.String("my-1p"), + Auto: kernel.Bool(true), + }, + }, +}) +if err != nil { + panic(err) +} + +// Option 2: Explicit item path (VaultName/ItemName) +auth, err = client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: "github.com", + ProfileName: "my-github-profile", + Credential: kernel.ManagedAuthCreateRequestCredentialParam{ + Provider: kernel.String("my-1p"), + Path: kernel.String("Engineering/github-login"), + }, + }, +}) +if err != nil { + panic(err) +} + +login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{}) +if err != nil { + panic(err) +} +_ = login +``` diff --git a/introduction/control.mdx b/introduction/control.mdx index 820efc9..9c4e6a7 100644 --- a/introduction/control.mdx +++ b/introduction/control.mdx @@ -47,6 +47,57 @@ Kernel browsers expose four ways to drive a session. For agents, we recommend [c text="kernel cloud browsers", ) ``` + + ```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + screenshot, err := client.Browsers.Computer.CaptureScreenshot( + ctx, + kernelBrowser.SessionID, + kernel.BrowserComputerCaptureScreenshotParams{}, + ) + if err != nil { + panic(err) + } + defer screenshot.Body.Close() + + if err := client.Browsers.Computer.ClickMouse( + ctx, + kernelBrowser.SessionID, + kernel.BrowserComputerClickMouseParams{ + X: 420, + Y: 280, + }, + ); err != nil { + panic(err) + } + + if err := client.Browsers.Computer.TypeText( + ctx, + kernelBrowser.SessionID, + kernel.BrowserComputerTypeTextParams{ + Text: "kernel cloud browsers", + }, + ); err != nil { + panic(err) + } +} + ``` @@ -78,6 +129,24 @@ Kernel browsers expose four ways to drive a session. For agents, we recommend [c print(response.result) ``` + + ```go Go +response, err := client.Browsers.Playwright.Execute( + ctx, + kernelBrowser.SessionID, + kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://example.com'); + return await page.title(); + `, + }, +) +if err != nil { + panic(err) +} + +fmt.Println(response.Result) + ``` @@ -193,6 +262,26 @@ response = kernel.browsers.playwright.execute( print(response.result) ``` + +```go Go +response, err := client.Browsers.Playwright.Execute( + ctx, + kernelBrowser.SessionID, + kernel.BrowserPlaywrightExecuteParams{ + Code: ` + const rows = await page.$$eval('table tr', (trs) => + trs.map((tr) => Array.from(tr.querySelectorAll('td')).map((td) => td.textContent)) + ); + return rows; + `, + }, +) +if err != nil { + panic(err) +} + +fmt.Println(response.Result) +``` ## Going deeper diff --git a/introduction/create.mdx b/introduction/create.mdx index 78c01fe..beaf91c 100644 --- a/introduction/create.mdx +++ b/introduction/create.mdx @@ -11,6 +11,7 @@ Kernel browsers are sandboxed Chromium instances that boot in under 30ms. Your a Install the Kernel SDK first: - Typescript/Javascript: `npm install @onkernel/sdk` - Python: `pip install kernel` + - Go: `go get github.com/kernel/kernel-go-sdk` @@ -32,6 +33,29 @@ kernel_browser = kernel.browsers.create() print(kernel_browser.session_id) ``` +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + fmt.Println(kernelBrowser.SessionID) +} +``` + ```bash CLI kernel browsers create @@ -76,6 +100,12 @@ await kernel.browsers.deleteByID(kernelBrowser.session_id); kernel.browsers.delete_by_id(kernel_browser.session_id) ``` +```go Go +if err := client.Browsers.DeleteByID(ctx, kernelBrowser.SessionID); err != nil { + panic(err) +} +``` + ```bash CLI kernel browsers delete ``` @@ -134,6 +164,48 @@ except Exception as e: finally: kernel.browsers.delete_by_id(kernel_browser.session_id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + defer func() { + if err := client.Browsers.DeleteByID(ctx, kernelBrowser.SessionID); err != nil { + panic(err) + } + }() + + response, err := client.Browsers.Playwright.Execute( + ctx, + kernelBrowser.SessionID, + kernel.BrowserPlaywrightExecuteParams{ + Code: ` + await page.goto('https://www.onkernel.com'); + return await page.title(); + `, + }, + ) + if err != nil { + panic(err) + } + + fmt.Println(response.Result) +} +``` ## What's next diff --git a/introduction/observe.mdx b/introduction/observe.mdx index fba6a8f..4f22a6e 100644 --- a/introduction/observe.mdx +++ b/introduction/observe.mdx @@ -28,6 +28,29 @@ kernel_browser = kernel.browsers.create() print(kernel_browser.browser_live_view_url) ``` +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{}) + if err != nil { + panic(err) + } + + fmt.Println(kernelBrowser.BrowserLiveViewURL) +} +``` + ```bash CLI kernel browsers view ``` @@ -63,6 +86,23 @@ kernel.browsers.replays.stop( ) ``` +```go Go +replay, err := client.Browsers.Replays.Start(ctx, kernelBrowser.SessionID, kernel.BrowserReplayStartParams{}) +if err != nil { + panic(err) +} + +// ...run the agent... + +if err := client.Browsers.Replays.Stop( + ctx, + replay.ReplayID, + kernel.BrowserReplayStopParams{ID: kernelBrowser.SessionID}, +); err != nil { + panic(err) +} +``` + ```bash CLI # Start recording kernel browsers replays start @@ -94,6 +134,18 @@ screenshot = kernel.browsers.computer.capture_screenshot( ) ``` +```go Go +screenshot, err := client.Browsers.Computer.CaptureScreenshot( + ctx, + kernelBrowser.SessionID, + kernel.BrowserComputerCaptureScreenshotParams{}, +) +if err != nil { + panic(err) +} +defer screenshot.Body.Close() +``` + ```bash CLI kernel browsers computer screenshot --to screenshot.png ``` @@ -120,6 +172,22 @@ logs = kernel.invocations.follow(invocation_id) for event in logs: print(event) ``` + +```go Go +stream := client.Invocations.FollowStreaming( + ctx, + invocationID, + kernel.InvocationFollowParams{}, +) +defer stream.Close() + +for stream.Next() { + fmt.Println(stream.Current()) +} +if err := stream.Err(); err != nil { + panic(err) +} +``` Full reference: [Logs](/apps/logs). diff --git a/proxies/custom.mdx b/proxies/custom.mdx index 029bdfc..ae7f5ae 100644 --- a/proxies/custom.mdx +++ b/proxies/custom.mdx @@ -54,6 +54,46 @@ proxy = kernel.proxies.create( browser = kernel.browsers.create(proxy_id=proxy.id) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeCustom, + Name: kernel.String("my-private-proxy"), + Protocol: kernel.ProxyNewParamsProtocolHTTPS, + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigCreateCustomProxyConfig: &kernel.ProxyNewParamsConfigCreateCustomProxyConfig{ + Host: "proxy.example.com", + Port: 443, + Username: kernel.String("user123"), + Password: kernel.String("secure_password"), + }, + }, + }) + if err != nil { + panic(err) + } + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + ProxyID: kernel.String(proxy.ID), + }) + if err != nil { + panic(err) + } + _ = browser +} +``` ## Configuration Parameters @@ -118,6 +158,44 @@ proxy = kernel.proxies.create( ] ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeCustom, + Name: kernel.String("custom-with-bypass"), + Protocol: kernel.ProxyNewParamsProtocolHTTPS, + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigCreateCustomProxyConfig: &kernel.ProxyNewParamsConfigCreateCustomProxyConfig{ + Host: "proxy.example.com", + Port: 443, + Username: kernel.String("user123"), + Password: kernel.String("secure_password"), + }, + }, + BypassHosts: []string{ + "localhost", + "internal.service.local", + "*.trusted-domain.com", + }, + }) + if err != nil { + panic(err) + } + _ = proxy +} +``` This is useful for accessing internal services or metadata endpoints without routing through your proxy. See the [overview](/proxies/overview#bypass-hosts) for full bypass host rules. diff --git a/proxies/datacenter.mdx b/proxies/datacenter.mdx index 21a9a96..24f1aab 100644 --- a/proxies/datacenter.mdx +++ b/proxies/datacenter.mdx @@ -48,6 +48,42 @@ proxy = kernel.proxies.create( browser = kernel.browsers.create(proxy_id=proxy.id) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeDatacenter, + Name: kernel.String("my-us-datacenter"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigDatacenterProxyConfig: &kernel.ProxyNewParamsConfigDatacenterProxyConfig{ + Country: kernel.String("US"), + }, + }, + }) + if err != nil { + panic(err) + } + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + ProxyID: kernel.String(proxy.ID), + }) + if err != nil { + panic(err) + } + _ = browser +} +``` ## Configuration Parameters @@ -97,6 +133,40 @@ proxy = kernel.proxies.create( ] ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeDatacenter, + Name: kernel.String("datacenter-with-bypass"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigDatacenterProxyConfig: &kernel.ProxyNewParamsConfigDatacenterProxyConfig{ + Country: kernel.String("US"), + }, + }, + BypassHosts: []string{ + "localhost", + "internal.service.local", + "*.amazonaws.com", + }, + }) + if err != nil { + panic(err) + } + _ = proxy +} +``` Bypass hosts support exact hostnames and wildcard subdomains (`*.example.com`). See the [overview](/proxies/overview#bypass-hosts) for full details. diff --git a/proxies/isp.mdx b/proxies/isp.mdx index b667cc8..45c4fef 100644 --- a/proxies/isp.mdx +++ b/proxies/isp.mdx @@ -42,6 +42,37 @@ proxy = kernel.proxies.create( browser = kernel.browsers.create(proxy_id=proxy.id) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeIsp, + Name: kernel.String("my-isp-proxy"), + }) + if err != nil { + panic(err) + } + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + ProxyID: kernel.String(proxy.ID), + }) + if err != nil { + panic(err) + } + _ = browser +} +``` ## Configuration Parameters @@ -84,6 +115,35 @@ proxy = kernel.proxies.create( ] ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeIsp, + Name: kernel.String("isp-with-bypass"), + BypassHosts: []string{ + "localhost", + "internal.service.local", + "*.amazonaws.com", + }, + }) + if err != nil { + panic(err) + } + _ = proxy +} +``` -See the [overview](/proxies/overview#bypass-hosts) for full bypass host rules and examples. \ No newline at end of file +See the [overview](/proxies/overview#bypass-hosts) for full bypass host rules and examples. diff --git a/proxies/overview.mdx b/proxies/overview.mdx index 52a4dee..276dd1a 100644 --- a/proxies/overview.mdx +++ b/proxies/overview.mdx @@ -43,6 +43,30 @@ kernel = Kernel() proxy = kernel.proxies.create(type="datacenter") print(proxy.id) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeDatacenter, + }) + if err != nil { + panic(err) + } + fmt.Println(proxy.ID) +} +``` @@ -68,6 +92,28 @@ kernel = Kernel() proxies = kernel.proxies.list() print(proxies) ``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxies, err := client.Proxies.List(ctx) + if err != nil { + panic(err) + } + fmt.Println(proxies) +} +``` @@ -109,6 +155,42 @@ proxy = kernel.proxies.create( browser = kernel.browsers.create(proxy_id=proxy.id) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("my-us-residential"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + }, + }, + }) + if err != nil { + panic(err) + } + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + ProxyID: kernel.String(proxy.ID), + }) + if err != nil { + panic(err) + } + _ = browser +} +``` ## Bypass hosts @@ -155,6 +237,41 @@ proxy = kernel.proxies.create( ] ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeDatacenter, + Name: kernel.String("proxy-with-bypass"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigDatacenterProxyConfig: &kernel.ProxyNewParamsConfigDatacenterProxyConfig{ + Country: kernel.String("US"), + }, + }, + BypassHosts: []string{ + "localhost", + "internal.company.local", + "metadata.google.internal", + "*.amazonaws.com", + }, + }) + if err != nil { + panic(err) + } + _ = proxy +} +``` ### Bypass host rules @@ -249,6 +366,70 @@ kernel.browsers.update( proxy_id="", ) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + // Create two proxy configurations + proxyA, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeIsp, + Name: kernel.String("proxy-a"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigIspProxyConfig: &kernel.ProxyNewParamsConfigIspProxyConfig{ + Country: kernel.String("US"), + }, + }, + }) + if err != nil { + panic(err) + } + + proxyB, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("proxy-b"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("DE"), + }, + }, + }) + if err != nil { + panic(err) + } + + // Launch a browser with the first proxy + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + ProxyID: kernel.String(proxyA.ID), + }) + if err != nil { + panic(err) + } + + // Hot-swap to a different proxy + if _, err := client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{ + ProxyID: kernel.String(proxyB.ID), + }); err != nil { + panic(err) + } + + // Remove the proxy entirely (route directly to the internet) + if _, err := client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{ + ProxyID: kernel.String(""), + }); err != nil { + panic(err) + } +} +``` The update is synchronous — when the call returns, the proxy swap is fully applied and all new browser traffic routes through the updated proxy. The swap typically completes in 2–3 seconds. @@ -275,6 +456,17 @@ browser = kernel.browsers.create( proxy_id=my_proxy.id, ) ``` + +```go Go +browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + Stealth: kernel.Bool(false), + ProxyID: kernel.String(myProxy.ID), +}) +if err != nil { + panic(err) +} +_ = browser +``` ### Disable default proxy on stealth browsers @@ -294,6 +486,14 @@ kernel.browsers.update( disable_default_proxy=True, ) ``` + +```go Go +if _, err := client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{ + DisableDefaultProxy: kernel.Bool(true), +}); err != nil { + panic(err) +} +``` @@ -319,6 +519,25 @@ from kernel import Kernel kernel = Kernel() kernel.proxies.delete("id") ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + if err := client.Proxies.Delete(ctx, "id"); err != nil { + panic(err) + } +} +``` diff --git a/proxies/residential.mdx b/proxies/residential.mdx index dfde748..a04fee9 100644 --- a/proxies/residential.mdx +++ b/proxies/residential.mdx @@ -56,6 +56,42 @@ proxy = kernel.proxies.create( browser = kernel.browsers.create(proxy_id=proxy.id) ``` + +```go Go +package main + +import ( + "context" + + "github.com/kernel/kernel-go-sdk" +) + +func main() { + ctx := context.Background() + client := kernel.NewClient() + + proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("my-us-residential"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + }, + }, + }) + if err != nil { + panic(err) + } + + browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{ + ProxyID: kernel.String(proxy.ID), + }) + if err != nil { + panic(err) + } + _ = browser +} +``` ## Configuration Parameters @@ -101,6 +137,24 @@ proxy = kernel.proxies.create( ) ``` +```go Go +proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("la-residential"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + State: kernel.String("CA"), + City: kernel.String("los_angeles"), + }, + }, +}) +if err != nil { + panic(err) +} +_ = proxy +``` + @@ -135,6 +189,23 @@ proxy = kernel.proxies.create( ) ``` +```go Go +proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("ny-residential"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + State: kernel.String("NY"), + }, + }, +}) +if err != nil { + panic(err) +} +_ = proxy +``` + @@ -169,6 +240,23 @@ proxy = kernel.proxies.create( ) ``` +```go Go +proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("comcast-residential"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + Asn: kernel.String("AS7922"), + }, + }, +}) +if err != nil { + panic(err) +} +_ = proxy +``` + @@ -203,6 +291,23 @@ proxy = kernel.proxies.create( ) ``` +```go Go +proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("nyc-residential"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + Zip: kernel.String("10001"), + }, + }, +}) +if err != nil { + panic(err) +} +_ = proxy +``` + @@ -247,6 +352,27 @@ proxy = kernel.proxies.create( ] ) ``` + +```go Go +proxy, err := client.Proxies.New(ctx, kernel.ProxyNewParams{ + Type: kernel.ProxyNewParamsTypeResidential, + Name: kernel.String("residential-with-bypass"), + Config: kernel.ProxyNewParamsConfigUnion{ + OfProxyNewsConfigResidentialProxyConfig: &kernel.ProxyNewParamsConfigResidentialProxyConfig{ + Country: kernel.String("US"), + }, + }, + BypassHosts: []string{ + "localhost", + "metadata.google.internal", + "*.internal.company.com", + }, +}) +if err != nil { + panic(err) +} +_ = proxy +``` See the [overview](/proxies/overview#bypass-hosts) for full bypass host rules and examples.