From c0cdbe1f9e8c07f76ca5f066cd306ff194af522a Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 25 Jun 2026 13:05:24 +0200 Subject: [PATCH 1/6] Design: default CLI login to org SSO, drop --workos vendor flag Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-25-login-default-workos-design.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-25-login-default-workos-design.md diff --git a/docs/superpowers/specs/2026-06-25-login-default-workos-design.md b/docs/superpowers/specs/2026-06-25-login-default-workos-design.md new file mode 100644 index 0000000000..c72ca122c6 --- /dev/null +++ b/docs/superpowers/specs/2026-06-25-login-default-workos-design.md @@ -0,0 +1,117 @@ +# Default CLI login to org SSO; drop the `--workos` vendor flag + +## Problem + +`kcap login` / `kcap setup` expose the auth vendor to users through the +`--workos` flag (`help-login.txt:27`, usage `[--github | --workos]`). Vendor +naming (WorkOS) should not leak into the user-facing CLI surface. The +interactive provider picker is already unbranded ("Continue" / "Continue with +GitHub"), so the flag is the only leak — plus a couple of descriptive "WorkOS" +mentions in help text. + +The product model we want: **org SSO is the default sign-in; GitHub is the +explicit opt-in via `--github`.** + +## Goals + +1. Remove the `--workos` flag. +2. No-flag `kcap login` / `kcap setup` (interactive) signs in via the org SSO + path (internally `AuthProvider.WorkOS`) directly — no provider prompt. +3. `--github` selects the GitHub App path. +4. Scrub gratuitous user-facing "WorkOS" naming from help text + README. + +## Non-goals + +- Auth mechanics (PKCE/loopback/OidcClient/device flow/org-switch) — untouched. +- `--device`, `--discover` — unchanged. +- The internal `AuthProvider.WorkOS = "workos"` constant — stays; it's the + server's `/auth/config` protocol value, not user-facing. + +## Design + +### Decision point: `OAuthLoginFlow.ChooseDiscoveryProvider` + +`src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs`. New behavior: + +| `args` | `isInteractive` | Result | +|--------|-----------------|--------| +| contains `--github` | any | `AuthProvider.GitHubApp` | +| no flag | `true` | `AuthProvider.WorkOS` *(was: `null` → prompt)* | +| no flag | `false` (headless) | `AuthProvider.GitHubApp` *(unchanged — WorkOS needs a browser loopback)* | + +- `--workos` is no longer recognized. A stale `--workos` is harmless: with no + `--github`, an interactive caller lands on the WorkOS default anyway. +- Return type tightens from `string?` to `string` (never null now). +- Headless rationale stays: WorkOS uses a 127.0.0.1 browser loopback that can't + complete without a browser, so headless no-flag falls back to GitHub device + flow. (A headless WorkOS path never worked, so nothing is lost.) + +### Remove the interactive picker + +`src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs` is **deleted** (its +Spectre "Continue / Continue with GitHub" `SelectionPrompt` is no longer +reachable — `ChooseDiscoveryProvider` always returns a concrete provider). Its +two callers call the resolver directly: + +- `src/Capacitor.Cli/Program.cs:666` +- `src/Capacitor.Cli/Commands/SetupCommand.cs:410` + +both become: + +```csharp +var provider = OAuthLoginFlow.ChooseDiscoveryProvider(args, isInteractive: !HeadlessEnvironment.IsHeadless()); +``` + +(`HeadlessEnvironment` is in `Capacitor.Cli.Core.Auth`.) Remove the now-unused +`using Spectre.Console;` that the deleted file carried. + +### Help text — `src/Capacitor.Cli.Core/Resources/help-login.txt` + +- Usage line: `kcap login [--discover] [--github] [--device]`. +- Discovery blurb: drop the "Continue / Continue with GitHub" wording; state + that login uses your organization's single sign-on by default, and `--github` + signs in with GitHub instead. +- Remove the `--workos` option line; reword `--github` to "Sign in with GitHub + instead of the default." +- Reword the "the auth method — GitHub App or WorkOS — is auto-discovered" + sentence to drop "WorkOS" (e.g. "the sign-in method is auto-discovered from + the server"). + +### README — `README.md` + +Update both the quick-start (`## Getting started`) and the per-command login / +setup sections (`## CLI commands`) to match: default = org SSO, `--github` for +GitHub, no `--workos`, no provider picker. Remove user-facing "WorkOS" +references where they're gratuitous (keep wording about SSO / "your +organization"). + +## Tests + +`test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs` — +`ChooseDiscoveryProvider_honors_flags_and_default`: + +```csharp +await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider(["--github"], isInteractive: true)).IsEqualTo(AuthProvider.GitHubApp); +await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: true)).IsEqualTo(AuthProvider.WorkOS); // default = org SSO, no prompt +await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: false)).IsEqualTo(AuthProvider.GitHubApp); // headless → GitHub device flow +``` + +Drop the old `--workos`-flag assertion and the `[]`+interactive→`null` +assertion. No test covers the deleted prompt. + +## Verification + +- `dotnet run` unit + integration suites pass. +- `dotnet publish -c Release` stays IL2026/IL3050-clean. +- Manual: `kcap login` (interactive) goes straight to SSO; `kcap login --github` + uses GitHub; `--help` shows no `--workos`. + +## Files touched + +- `src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs` — `ChooseDiscoveryProvider`. +- `src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs` — delete. +- `src/Capacitor.Cli/Program.cs` — call resolver directly. +- `src/Capacitor.Cli/Commands/SetupCommand.cs` — call resolver directly. +- `src/Capacitor.Cli.Core/Resources/help-login.txt` — flags + wording. +- `README.md` — getting-started + login/setup sections. +- `test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs` — default assertion. From ea778d44b25ef4f30d981b26a2fd57c4bba14655 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 25 Jun 2026 13:07:28 +0200 Subject: [PATCH 2/6] Plan: default CLI login to org SSO, drop --workos flag Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-06-25-login-default-workos.md | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-25-login-default-workos.md diff --git a/docs/superpowers/plans/2026-06-25-login-default-workos.md b/docs/superpowers/plans/2026-06-25-login-default-workos.md new file mode 100644 index 0000000000..fc50b78c45 --- /dev/null +++ b/docs/superpowers/plans/2026-06-25-login-default-workos.md @@ -0,0 +1,250 @@ +# Default CLI login to org SSO — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make plain `kcap login`/`kcap setup` default to the org SSO path and drop the vendor-revealing `--workos` flag; `--github` is the opt-in for the GitHub App path. + +**Architecture:** `OAuthLoginFlow.ChooseDiscoveryProvider` becomes the single decision point and always returns a concrete provider (no prompt). The interactive `DiscoveryProviderPrompt` picker is deleted and its two callers call the resolver directly. Help text + README drop `--workos` and gratuitous "WorkOS" naming. + +**Tech Stack:** .NET 10 NativeAOT, TUnit, Spectre.Console (CLI), no new dependencies. + +## Global Constraints + +- AOT: after changes, `dotnet publish src/Capacitor.Cli/Capacitor.Cli.csproj -c Release 2>&1 | grep -E 'IL[23][01][0-9]{2}'` MUST be empty. +- README sync (CLAUDE.md): any user-facing CLI surface change updates `README.md` in the same change — both quick-start (`## Getting started`) and the per-command login/setup section. +- TUnit: filter with `--treenode-filter`; run tests via `dotnet run --project `. +- The internal `AuthProvider.WorkOS = "workos"` constant stays (server `/auth/config` protocol value); only the user-facing `--workos` flag + gratuitous "WorkOS" copy are removed. + +**Spec:** `docs/superpowers/specs/2026-06-25-login-default-workos-design.md` + +--- + +### Task 1: `ChooseDiscoveryProvider` defaults to WorkOS (no prompt signal) + +**Files:** +- Modify: `src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs:59-67` +- Test: `test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs` (`ChooseDiscoveryProvider_honors_flags_and_default`) + +**Interfaces:** +- Produces: `internal static string ChooseDiscoveryProvider(string[] args, bool isInteractive)` — non-nullable; `--github` → `GitHubApp`; no flag + interactive → `WorkOS`; no flag + headless → `GitHubApp`. + +- [ ] **Step 1: Update the test to the new contract** + +In `test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs`, replace the body of `ChooseDiscoveryProvider_honors_flags_and_default` with: +```csharp + [Test] + public async Task ChooseDiscoveryProvider_honors_flags_and_default() { + await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider(["--github"], isInteractive: true)).IsEqualTo(AuthProvider.GitHubApp); + await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: true)).IsEqualTo(AuthProvider.WorkOS); // default = org SSO, no prompt + await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: false)).IsEqualTo(AuthProvider.GitHubApp); // headless → GitHub device flow + } +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `dotnet run --project test/Capacitor.Cli.Tests.Unit/Capacitor.Cli.Tests.Unit.csproj --treenode-filter "/*/*/OAuthFlowTests/ChooseDiscoveryProvider_honors_flags_and_default"` +Expected: FAIL — `[]`+interactive currently returns `null`, not `AuthProvider.WorkOS`. + +- [ ] **Step 3: Update `ChooseDiscoveryProvider`** + +In `src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs`, replace the method (currently lines 54-67) with: +```csharp + /// + /// Picks the discovery provider before any auth runs: --github selects the GitHub App + /// path; otherwise login defaults to the org SSO path (WorkOS). Headless callers can't run the + /// WorkOS 127.0.0.1 browser loopback, so a no-flag headless caller falls back to GitHub (whose + /// device flow works without a local browser). + /// + internal static string ChooseDiscoveryProvider(string[] args, bool isInteractive) { + if (args.Contains("--github")) return AuthProvider.GitHubApp; + + return isInteractive ? AuthProvider.WorkOS : AuthProvider.GitHubApp; + } +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `dotnet run --project test/Capacitor.Cli.Tests.Unit/Capacitor.Cli.Tests.Unit.csproj --treenode-filter "/*/*/OAuthFlowTests/ChooseDiscoveryProvider_honors_flags_and_default"` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs +git commit -m "Default login to org SSO; --github opts into GitHub (drop prompt signal)" +``` + +--- + +### Task 2: Delete the interactive picker; rewire callers + +**Files:** +- Delete: `src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs` +- Modify: `src/Capacitor.Cli/Program.cs:666` +- Modify: `src/Capacitor.Cli/Commands/SetupCommand.cs:410` + +**Interfaces:** +- Consumes: `OAuthLoginFlow.ChooseDiscoveryProvider(string[], bool)` (Task 1), `HeadlessEnvironment.IsHeadless()` — both in `Capacitor.Cli.Core.Auth` (already imported in both files). + +- [ ] **Step 1: Delete the picker** + +```bash +git rm src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs +``` + +- [ ] **Step 2: Rewire the login caller** + +In `src/Capacitor.Cli/Program.cs`, replace: +```csharp + var provider = DiscoveryProviderPrompt.Resolve(args); +``` +with: +```csharp + var provider = OAuthLoginFlow.ChooseDiscoveryProvider(args, isInteractive: !HeadlessEnvironment.IsHeadless()); +``` + +- [ ] **Step 3: Rewire the setup caller** + +In `src/Capacitor.Cli/Commands/SetupCommand.cs`, replace: +```csharp + var provider = DiscoveryProviderPrompt.Resolve(args); +``` +with: +```csharp + var provider = OAuthLoginFlow.ChooseDiscoveryProvider(args, isInteractive: !HeadlessEnvironment.IsHeadless()); +``` + +- [ ] **Step 4: Build to verify wiring + no stale references** + +Run: `dotnet build src/Capacitor.Cli/Capacitor.Cli.csproj` +Expected: Build succeeded. +Run: `grep -rn "DiscoveryProviderPrompt" src/ test/` +Expected: no hits. + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "Remove interactive provider picker; callers resolve provider directly" +``` + +--- + +### Task 3: Help text + README — drop `--workos`, scrub vendor naming + +**Files:** +- Modify: `src/Capacitor.Cli.Core/Resources/help-login.txt` +- Modify: `README.md` + +- [ ] **Step 1: Rewrite `help-login.txt`** + +Replace the entire contents of `src/Capacitor.Cli.Core/Resources/help-login.txt` with: +``` +kcap login — Authenticate via OAuth + +Usage: kcap login [--discover] [--github] [--device] + +Opens the system browser for OAuth authentication. + +With no configured server (or with --discover), kcap runs tenant discovery: +it signs you in with your organization's single sign-on, then lets you pick +from the tenants you belong to. No --server-url and no existing profile are +required. Pass --github to sign in with GitHub instead. With a server already +configured, kcap logs into it directly (the sign-in method is auto-discovered +from the server's /auth/config). + +For GitHub, the default flow uses a localhost callback with PKCE when the +server advertises a code-exchange endpoint — the browser opens straight to +the authorize page and the CLI receives the code on a loopback listener. +The CLI falls back to GitHub Device Flow when the server doesn't advertise +that endpoint, in headless environments (SSH, no DISPLAY), or when the +loopback port can't be bound. + +Options: + --discover Force tenant discovery even when a server is configured. + Discovery signs you in, lists the tenants you belong to, and + saves the chosen one as the active profile. + + --github Sign in with GitHub instead of your organization's SSO. + + --device Force GitHub Device Flow even when a browser is available. + Useful for SSH sessions, containers without a browser, or any + environment where the loopback callback can't open. +``` + +- [ ] **Step 2: Update README quick-start (`## Getting started`)** + +In `README.md`, replace line 62: +``` +1. **Server** — with no `--server-url`/``, kcap **discovers** your tenant: pick how to sign in (**Continue** for email/SSO, or **Continue with GitHub**), authenticate once, then choose from the tenants you belong to. A bare `` slug expands to `https://.kcap.ai`; a full URL is used as-is. +``` +with: +``` +1. **Server** — with no `--server-url`/``, kcap **discovers** your tenant: it signs you in with your organization's single sign-on (pass `--github` to use GitHub instead), then lets you choose from the tenants you belong to. A bare `` slug expands to `https://.kcap.ai`; a full URL is used as-is. +``` +and replace line 63: +``` +2. **Login** — authenticates via your tenant's provider (WorkOS or GitHub App); discovery completes the sign-in inline +``` +with: +``` +2. **Login** — authenticates via your tenant's configured sign-in method; discovery completes the sign-in inline +``` + +- [ ] **Step 3: Update README per-command setup/login section** + +In `README.md`, replace line 143: +``` +With no server argument, setup (and `kcap login`) runs **tenant discovery**: you choose how to sign in — **Continue** (WorkOS email/SSO) or **Continue with GitHub** — then pick from the tenants you belong to. `--github`/`--workos` skip the provider prompt; `--discover` forces discovery even when a server is configured. +``` +with: +``` +With no server argument, setup (and `kcap login`) runs **tenant discovery**: it signs you in with your organization's single sign-on, then lets you pick from the tenants you belong to. Pass `--github` to sign in with GitHub instead; `--discover` forces discovery even when a server is configured. +``` + +- [ ] **Step 4: Verify no user-facing `--workos` or stray vendor leak remains** + +Run: `grep -rn -- "--workos" src/Capacitor.Cli.Core/Resources/ README.md` +Expected: no hits. +Run: `grep -rni "Continue with GitHub\|WorkOS email/SSO" src/Capacitor.Cli.Core/Resources/ README.md` +Expected: no hits. + +- [ ] **Step 5: Commit** + +```bash +git add src/Capacitor.Cli.Core/Resources/help-login.txt README.md +git commit -m "Docs: default login to org SSO, drop --workos, scrub vendor naming" +``` + +--- + +### Task 4: Verification sweep + +**Files:** none (verification only). + +- [ ] **Step 1: Full unit + integration suites** + +Run: `dotnet run --project test/Capacitor.Cli.Tests.Unit/Capacitor.Cli.Tests.Unit.csproj` +Run: `dotnet run --project test/Capacitor.Cli.Tests.Integration/Capacitor.Cli.Tests.Integration.csproj` +Expected: all pass (`failed: 0`). + +- [ ] **Step 2: AOT publish gate** + +Run: `dotnet publish src/Capacitor.Cli/Capacitor.Cli.csproj -c Release 2>&1 | grep -E 'IL[23][01][0-9]{2}'` +Expected: empty output. + +- [ ] **Step 3: `--help` shows no `--workos`** + +Run: `dotnet run --project src/Capacitor.Cli/Capacitor.Cli.csproj -- login --help 2>&1 | grep -- "--workos" || echo "clean: no --workos in help"` +Expected: `clean: no --workos in help`. + +- [ ] **Step 4: No stray code references** + +Run: `grep -rn --include=*.cs "DiscoveryProviderPrompt\|\"--workos\"" src/ test/` +Expected: no hits. + +## Notes for the implementer + +- `Program.cs` and `SetupCommand.cs` already `using Capacitor.Cli.Core.Auth;`, so `OAuthLoginFlow` and `HeadlessEnvironment` resolve without new imports. Deleting `DiscoveryProviderPrompt.cs` removes the only use of its `using Spectre.Console;` — nothing else to clean up there. +- A stale `--workos` passed by a user is now an unrecognized no-op: with no `--github`, an interactive caller still lands on the WorkOS default, so old muscle-memory keeps working. +- Do not touch the `AuthProvider.WorkOS`/`GitHubApp` constants or any auth mechanics. From 3d3d4ffbacaf43cf749e518795f01df1f5d1b9ee Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 25 Jun 2026 13:08:49 +0200 Subject: [PATCH 3/6] Default login to org SSO; --github opts into GitHub (drop prompt signal) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs | 15 ++++++--------- test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs | 5 ++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs b/src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs index 0f070e47ab..284d6820d6 100644 --- a/src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs +++ b/src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs @@ -53,18 +53,15 @@ internal static GitHubFlow ChooseGitHubFlow(bool forceDevice, bool isHeadless, b => forceDevice || isHeadless || !hasExchangeUrl ? GitHubFlow.Device : GitHubFlow.Browser; /// - /// Picks the discovery provider before any auth runs: explicit --github/--workos - /// flags win; otherwise a non-interactive caller defaults to WorkOS, and an interactive caller - /// gets null (meaning "prompt the user"). + /// Picks the discovery provider before any auth runs: --github selects the GitHub App + /// path; otherwise login defaults to the org SSO path (WorkOS). Headless callers can't run the + /// WorkOS 127.0.0.1 browser loopback, so a no-flag headless caller falls back to GitHub (whose + /// device flow works without a local browser). /// - internal static string? ChooseDiscoveryProvider(string[] args, bool isInteractive) { + internal static string ChooseDiscoveryProvider(string[] args, bool isInteractive) { if (args.Contains("--github")) return AuthProvider.GitHubApp; - if (args.Contains("--workos")) return AuthProvider.WorkOS; - // No flag: prompt when interactive. Headless can't run the WorkOS browser + 127.0.0.1 - // loopback (it would hang until timeout), so fall back to GitHub, whose device flow works - // without a local browser. A headless WorkOS user must pass --workos explicitly. - return isInteractive ? null : AuthProvider.GitHubApp; + return isInteractive ? AuthProvider.WorkOS : AuthProvider.GitHubApp; } /// diff --git a/test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs b/test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs index d453fe18a3..c185fa3177 100644 --- a/test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs +++ b/test/Capacitor.Cli.Tests.Unit/OAuthFlowTests.cs @@ -151,9 +151,8 @@ public async Task GitHubBrowser_returns_null_on_non_success_browser_result() { [Test] public async Task ChooseDiscoveryProvider_honors_flags_and_default() { await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider(["--github"], isInteractive: true)).IsEqualTo(AuthProvider.GitHubApp); - await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider(["--workos"], isInteractive: true)).IsEqualTo(AuthProvider.WorkOS); - await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: false)).IsEqualTo(AuthProvider.GitHubApp); // headless → GitHub device flow, not the WorkOS browser loopback - await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: true)).IsNull(); + await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: true)).IsEqualTo(AuthProvider.WorkOS); // default = org SSO, no prompt + await Assert.That(OAuthLoginFlow.ChooseDiscoveryProvider([], isInteractive: false)).IsEqualTo(AuthProvider.GitHubApp); // headless → GitHub device flow } [Test] From c34cbd55ce44edc14a56c6727b5a7f600a644435 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 25 Jun 2026 13:09:45 +0200 Subject: [PATCH 4/6] Remove interactive provider picker; callers resolve provider directly Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Commands/DiscoveryProviderPrompt.cs | 22 ------------------- src/Capacitor.Cli/Commands/SetupCommand.cs | 2 +- src/Capacitor.Cli/Program.cs | 2 +- 3 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs diff --git a/src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs b/src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs deleted file mode 100644 index f6e822fc6e..0000000000 --- a/src/Capacitor.Cli/Commands/DiscoveryProviderPrompt.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Capacitor.Cli.Core.Auth; -using Spectre.Console; - -namespace Capacitor.Cli.Commands; - -/// -/// Resolves which provider to use for tenant discovery: explicit --github/--workos flags or a -/// non-interactive default win (); otherwise -/// prompts. The WorkOS option is shown as the unbranded "Continue" (primary), GitHub as secondary. -/// -public static class DiscoveryProviderPrompt { - public static string Resolve(string[] args) { - var chosen = OAuthLoginFlow.ChooseDiscoveryProvider(args, isInteractive: !HeadlessEnvironment.IsHeadless()); - if (chosen is not null) return chosen; - - return AnsiConsole.Prompt( - new SelectionPrompt() - .Title("How would you like to sign in?") - .AddChoices(AuthProvider.WorkOS, AuthProvider.GitHubApp) - .UseConverter(p => p == AuthProvider.GitHubApp ? "Continue with GitHub" : "Continue")); - } -} diff --git a/src/Capacitor.Cli/Commands/SetupCommand.cs b/src/Capacitor.Cli/Commands/SetupCommand.cs index 69a639671a..9a2b1a5782 100644 --- a/src/Capacitor.Cli/Commands/SetupCommand.cs +++ b/src/Capacitor.Cli/Commands/SetupCommand.cs @@ -407,7 +407,7 @@ await Console.Error.WriteLineAsync( return null; } - var provider = DiscoveryProviderPrompt.Resolve(args); + var provider = OAuthLoginFlow.ChooseDiscoveryProvider(args, isInteractive: !HeadlessEnvironment.IsHeadless()); if (provider == AuthProvider.WorkOS) { var exit = await WorkOSDiscovery.RunWithLiveAuthAsync( diff --git a/src/Capacitor.Cli/Program.cs b/src/Capacitor.Cli/Program.cs index 7a052a0cdf..1799d81a99 100644 --- a/src/Capacitor.Cli/Program.cs +++ b/src/Capacitor.Cli/Program.cs @@ -663,7 +663,7 @@ async Task HandleDiscoverLoginAsync(bool forceDevice) { return 1; } - var provider = DiscoveryProviderPrompt.Resolve(args); + var provider = OAuthLoginFlow.ChooseDiscoveryProvider(args, isInteractive: !HeadlessEnvironment.IsHeadless()); if (provider == AuthProvider.WorkOS) { return await WorkOSDiscovery.RunWithLiveAuthAsync( From def454a6dcf67d56d38a351301274582bb1c9c85 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 25 Jun 2026 13:10:25 +0200 Subject: [PATCH 5/6] Docs: default login to org SSO, drop --workos, scrub vendor naming Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 6 +++--- .../Resources/help-login.txt | 19 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 29659a0d06..eeecb81217 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ kcap setup --server-url # explicit server (self-hosted, or a full URL) The setup wizard walks you through: -1. **Server** — with no `--server-url`/``, kcap **discovers** your tenant: pick how to sign in (**Continue** for email/SSO, or **Continue with GitHub**), authenticate once, then choose from the tenants you belong to. A bare `` slug expands to `https://.kcap.ai`; a full URL is used as-is. -2. **Login** — authenticates via your tenant's provider (WorkOS or GitHub App); discovery completes the sign-in inline +1. **Server** — with no `--server-url`/``, kcap **discovers** your tenant: it signs you in with your organization's single sign-on (pass `--github` to use GitHub instead), then lets you choose from the tenants you belong to. A bare `` slug expands to `https://.kcap.ai`; a full URL is used as-is. +2. **Login** — authenticates via your tenant's configured sign-in method; discovery completes the sign-in inline 3. **Default visibility** — choose how your sessions are visible to others 4. **Coding-agent hooks** — detects Claude Code and Codex CLI on `PATH`, Cursor by user-dir presence (`~/.cursor/`), GitHub Copilot CLI by `~/.copilot/` or `copilot` on `PATH`, Google Gemini CLI by `~/.gemini/` or `gemini` on `PATH`, AWS Kiro CLI by `~/.kiro/` or `kiro`/`kiro-cli` on `PATH`, Pi by `~/.pi/` or `pi` on `PATH`, and SST OpenCode by `~/.config/opencode/` (or `~/.local/share/opencode/`) or `opencode` on `PATH`, then offers to install hooks/skills (or, for Pi/OpenCode, the live-ingest plugin) for each (user-wide) 5. **Daemon** — configure the daemon name for remote agent execution @@ -140,7 +140,7 @@ kcap setup # shorthand: https://.kcap. kcap setup --server-url --no-prompt # CI / scripted ``` -With no server argument, setup (and `kcap login`) runs **tenant discovery**: you choose how to sign in — **Continue** (WorkOS email/SSO) or **Continue with GitHub** — then pick from the tenants you belong to. `--github`/`--workos` skip the provider prompt; `--discover` forces discovery even when a server is configured. +With no server argument, setup (and `kcap login`) runs **tenant discovery**: it signs you in with your organization's single sign-on, then lets you pick from the tenants you belong to. Pass `--github` to sign in with GitHub instead; `--discover` forces discovery even when a server is configured. The setup wizard detects every supported coding agent and offers to install hooks for each, then configures the daemon. Claude Code and Codex CLI are detected via `PATH`; Cursor is detected by user-dir presence (`~/.cursor/`), so IDE users without the `cursor` shell command are covered; GitHub Copilot CLI is detected via `~/.copilot/` or `copilot` on `PATH`; Google Gemini CLI via `~/.gemini/` or `gemini` on `PATH`; AWS Kiro CLI via `~/.kiro/` or `kiro`/`kiro-cli` on `PATH`; Pi via `~/.pi/agent/` or `pi` on `PATH` (and, because Pi has no shell hooks, the wizard installs a Pi extension rather than hook config). Re-run any time to update the configuration. diff --git a/src/Capacitor.Cli.Core/Resources/help-login.txt b/src/Capacitor.Cli.Core/Resources/help-login.txt index ba08af03ea..1e2df0afb3 100644 --- a/src/Capacitor.Cli.Core/Resources/help-login.txt +++ b/src/Capacitor.Cli.Core/Resources/help-login.txt @@ -1,15 +1,15 @@ kcap login — Authenticate via OAuth -Usage: kcap login [--discover] [--github | --workos] [--device] +Usage: kcap login [--discover] [--github] [--device] Opens the system browser for OAuth authentication. With no configured server (or with --discover), kcap runs tenant discovery: -you choose how to sign in — "Continue" (email / SSO) or "Continue with -GitHub" — authenticate once, then pick from the tenants you belong to. No ---server-url and no existing profile are required. With a server already -configured, kcap logs into it directly (the auth method — GitHub App or -WorkOS — is auto-discovered from the server's /auth/config). +it signs you in with your organization's single sign-on, then lets you pick +from the tenants you belong to. No --server-url and no existing profile are +required. Pass --github to sign in with GitHub instead. With a server already +configured, kcap logs into it directly (the sign-in method is auto-discovered +from the server's /auth/config). For GitHub, the default flow uses a localhost callback with PKCE when the server advertises a code-exchange endpoint — the browser opens straight to @@ -20,11 +20,10 @@ loopback port can't be bound. Options: --discover Force tenant discovery even when a server is configured. - Discovery picks a provider, authenticates, lists the tenants - you belong to, and saves the chosen one as the active profile. + Discovery signs you in, lists the tenants you belong to, and + saves the chosen one as the active profile. - --github Skip the provider prompt and use GitHub for discovery. - --workos Skip the provider prompt and use WorkOS for discovery. + --github Sign in with GitHub instead of your organization's SSO. --device Force GitHub Device Flow even when a browser is available. Useful for SSH sessions, containers without a browser, or any From 8371c62fb39906239ed48c6d065bb6d4c0c8588c Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 25 Jun 2026 13:47:39 +0200 Subject: [PATCH 6/6] =?UTF-8?q?Docs:=20note=20headless=E2=86=92GitHub=20di?= =?UTF-8?q?scovery=20fallback;=20document=20--github=20in=20setup=20help?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Qodo review on #177: - help-login.txt + README: discovery falls back to GitHub Device Flow in SSH/headless environments (SSO needs a local browser) — docs previously implied SSO unconditionally - help-setup.txt: document --github (now the only way to pick GitHub in setup after the interactive picker was removed) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 +- src/Capacitor.Cli.Core/Resources/help-login.txt | 8 +++++--- src/Capacitor.Cli.Core/Resources/help-setup.txt | 3 +++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eeecb81217..ec13a54bcf 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ kcap setup # shorthand: https://.kcap. kcap setup --server-url --no-prompt # CI / scripted ``` -With no server argument, setup (and `kcap login`) runs **tenant discovery**: it signs you in with your organization's single sign-on, then lets you pick from the tenants you belong to. Pass `--github` to sign in with GitHub instead; `--discover` forces discovery even when a server is configured. +With no server argument, setup (and `kcap login`) runs **tenant discovery**: it signs you in with your organization's single sign-on, then lets you pick from the tenants you belong to. Pass `--github` to sign in with GitHub instead; `--discover` forces discovery even when a server is configured. In SSH / headless environments (no browser), discovery falls back to GitHub Device Flow, since SSO needs a local browser. The setup wizard detects every supported coding agent and offers to install hooks for each, then configures the daemon. Claude Code and Codex CLI are detected via `PATH`; Cursor is detected by user-dir presence (`~/.cursor/`), so IDE users without the `cursor` shell command are covered; GitHub Copilot CLI is detected via `~/.copilot/` or `copilot` on `PATH`; Google Gemini CLI via `~/.gemini/` or `gemini` on `PATH`; AWS Kiro CLI via `~/.kiro/` or `kiro`/`kiro-cli` on `PATH`; Pi via `~/.pi/agent/` or `pi` on `PATH` (and, because Pi has no shell hooks, the wizard installs a Pi extension rather than hook config). Re-run any time to update the configuration. diff --git a/src/Capacitor.Cli.Core/Resources/help-login.txt b/src/Capacitor.Cli.Core/Resources/help-login.txt index 1e2df0afb3..789d282141 100644 --- a/src/Capacitor.Cli.Core/Resources/help-login.txt +++ b/src/Capacitor.Cli.Core/Resources/help-login.txt @@ -7,9 +7,11 @@ Opens the system browser for OAuth authentication. With no configured server (or with --discover), kcap runs tenant discovery: it signs you in with your organization's single sign-on, then lets you pick from the tenants you belong to. No --server-url and no existing profile are -required. Pass --github to sign in with GitHub instead. With a server already -configured, kcap logs into it directly (the sign-in method is auto-discovered -from the server's /auth/config). +required. Pass --github to sign in with GitHub instead. In SSH / headless +environments (no browser), discovery uses GitHub Device Flow instead, since +SSO needs a local browser. With a server already configured, kcap logs into +it directly (the sign-in method is auto-discovered from the server's +/auth/config). For GitHub, the default flow uses a localhost callback with PKCE when the server advertises a code-exchange endpoint — the browser opens straight to diff --git a/src/Capacitor.Cli.Core/Resources/help-setup.txt b/src/Capacitor.Cli.Core/Resources/help-setup.txt index 248fb5a5df..080a564144 100644 --- a/src/Capacitor.Cli.Core/Resources/help-setup.txt +++ b/src/Capacitor.Cli.Core/Resources/help-setup.txt @@ -7,6 +7,9 @@ Options: --daemon-name Daemon name (skip prompt) --default-visibility Default visibility: private, org_public, public --no-prompt Non-interactive mode (requires --server-url) + --github Sign in with GitHub instead of org SSO during + the login step (default is org SSO when a + browser is available) --device Force GitHub Device Flow during the login step (use in SSH / headless envs) --skip-claude-hooks Don't install the Claude Code plugin even if