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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ SSO discovery signs in through a `127.0.0.1` browser callback, which a browser o

The setup wizard walks you through the steps below. Re-run it any time to update the configuration.

1. **Server** — with no `--server-url`/`<tenant>`, 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 `<tenant>` slug expands to `https://<tenant>.kcap.ai`; a full URL is used as-is. If you sign in with your organization's single sign-on and discovery finds no Capacitor tenant, `kcap setup` asks how to continue: create one for you (name + workspace URL, provisioned and waited for until it's live), point at a workspace you already belong to (enter its slug or URL — the same as `kcap setup <tenant>`), or cancel. That middle choice matters because SSO discovery only lists workspaces that use org SSO: a workspace whose members sign in with the GitHub App shows up here as "no tenant", so pick **I already have a workspace**, or re-run with `--github`.
1. **Server** — with no `--server-url`/`<tenant>`, 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 `<tenant>` slug expands to `https://<tenant>.kcap.ai`; a full URL is used as-is. If you sign in with your organization's single sign-on and discovery finds no Capacitor tenant, `kcap setup` asks how to continue: create one for you (name, workspace URL and plan, with the 30-day Team trial offered first; provisioned and waited for until it's live), point at a workspace you already belong to (enter its slug or URL — the same as `kcap setup <tenant>`), or cancel. That middle choice matters because SSO discovery only lists workspaces that use org SSO: a workspace whose members sign in with the GitHub App shows up here as "no tenant", so pick **I already have a workspace**, or re-run with `--github`.
2. **Login** — authenticates via your tenant's configured sign-in method; discovery completes the sign-in inline

**Finishing in the browser.** Once you are signed in, if that server offers browser setup, kcap creates a setup link, opens your browser on it, and waits while you work through the screens there:
Expand Down Expand Up @@ -259,7 +259,7 @@ The setup wizard walks you through the steps below. Re-run it any time to update
5. **Daemon** — configure the daemon name for remote agent execution. If a daemon on another machine is already connected to your account under that name, setup asks for another (suggesting one suffixed with this machine's name); under `--no-prompt` the name is `--daemon-name`, else `KCAP_DAEMON_NAME`, else your user name, and a name held elsewhere exits 1, so pass a different `--daemon-name` (the daemon verb is `kcap daemon`; `kcap agent` is a separate group that runs coding agents — see [Local agents](#local-agents-kcap-agent)). **After a browser answer this step does not prompt:** it takes `--daemon-name` if given, else `KCAP_DAEMON_NAME`, else the profile's daemon name (your user name on a first run); if another machine holds it, setup tries the machine-suffixed name and then numbered variants of it, and ends with an error naming `--daemon-name` if all are taken. The provider API key question (asked when `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` is set) is skipped too and the stored setting kept — change it with `kcap config set use_provider_api_key true|false`. When the browser's Done screen ran the daemon as a service, setup closes by saying so rather than suggesting `kcap daemon start -d`
6. **Import past sessions** — scans every detected agent's history on this machine and prints how many repositories and sessions it found and how many sessions matched no repository, then asks (default yes) `Import past sessions from this machine?` — equivalent to `kcap import --all` under your profile's default visibility (shared only where the repository owner is your workspace's org). The most recent sessions are prioritized: about five import while you watch, and the rest continue in a detached background process that logs to `~/.config/kcap/import-<run>.log`. Setup then offers to open one of your detected coding agents with the prompt `Follow my kcap import` so you can watch the import and its evals from there (or prints the prompt to paste), and finishes when you close it. Your authentication requirements must be satisfied — which includes no-auth servers (auth provider `None`, no token needed); otherwise the step is skipped with the usual `kcap import` hint. Opt out with `--skip-import`; an import chosen on the browser's Import screen runs regardless. **Answered in the browser, this step reports rather than prompts** — the screen there chooses repositories and a history window this prompt cannot express, so re-asking would offer to redo part of what already ran. A browser flow that finished without an import answer skips this step too and names `kcap import --all` for later; handing back to the terminal with **t** keeps the prompt.

- **New tenant:** when signing in via Kurrent's hosted auth and you have no tenant yet, `setup` prompts to create one (organization name + `<slug>.kcap.ai` workspace URL) and waits up to ten minutes for it to answer; if it still does not, setup stops and names the `kcap setup --server-url <url>` to run once it is up. A listed workspace whose address answers 404 — removed, though its sign-in organization remains — is left out, unless it is still being created. Pass `--org "<name>" --slug <slug>` to answer both up front, so the fork raises no prompt. Add `--no-prompt` for a script or a session with redirected input: these two flags only settle the creation questions, and the steps after it still ask. The two are both-or-neither: the slug becomes a permanent public hostname, so kcap will not derive one for you. A slug that is taken or malformed ends the run naming it, rather than asking for another. Only an account with no workspace yet can create one, so if yours already has a different one, setup signs in, stops, and tells you — it will not go on to configure that workspace as if you had asked for it. Without the flags, a non-interactive run exits with guidance.
- **New tenant:** when signing in via Kurrent's hosted auth and you have no tenant yet, `setup` prompts to create one (organization name + `<slug>.kcap.ai` workspace URL) and waits up to ten minutes for it to answer; if it still does not, setup stops and names the `kcap setup --server-url <url>` to run once it is up. A listed workspace whose address answers 404 — removed, though its sign-in organization remains — is left out, unless it is still being created. Pass `--org "<name>" --slug <slug>` to answer both up front, so the fork raises no prompt, and `--tier team` or `--tier free` for the plan (Free when omitted). An account that cannot have the Team trial (a personal email address, or a domain that has already had one) gets a Free workspace instead, and setup says why. Add `--no-prompt` for a script or a session with redirected input: these two flags only settle the creation questions, and the steps after it still ask. The two are both-or-neither: the slug becomes a permanent public hostname, so kcap will not derive one for you. A slug that is taken or malformed ends the run naming it, rather than asking for another. Only an account with no workspace yet can create one, so if yours already has a different one, setup signs in, stops, and tells you — it will not go on to configure that workspace as if you had asked for it. Without the flags, a non-interactive run exits with guidance.

The closing summary lists, under **Changed on this machine**, every agent config file and skills directory the run installed kcap into, plus the git config that carries the commit hook (`~/.gitconfig`, or `~/.config/git/config` when only that exists).

Expand Down Expand Up @@ -288,7 +288,7 @@ kcap setup --server-url https://my-tenant.kcap.ai --default-visibility org_publi
`--no-prompt` requires a workspace (`<tenant>` or `--server-url`), unless you are creating the workspace as part of the run — `--org "<name>" --slug <slug>` supply the two answers the creation prompts would have collected, so those replace it:

```bash
kcap setup --org "Acme" --slug acme --default-visibility org_public --no-prompt
kcap setup --org "Acme" --slug acme --tier team --default-visibility org_public --no-prompt
```

In `--no-prompt` mode, the wizard installs hooks for every detected agent by default. Opt out per agent with `--skip-claude-hooks`, `--skip-codex-hooks`, `--skip-cursor-hooks`, `--skip-copilot-hooks`, `--skip-gemini-hooks`, `--skip-kiro-hooks`, `--skip-pi-hooks`, `--skip-opencode-hooks`, and/or `--skip-antigravity-hooks`. When Codex hooks are installed, the wizard also enables Codex sandbox network access for your server(s) by default; pass `--skip-codex-network-access` to leave `~/.codex/config.toml` untouched.
Expand Down
2 changes: 1 addition & 1 deletion src/Capacitor.App/Services/Onboarding/WizardAuthBridges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ async Task<ProvisionOffer> OfferCreateCoreAsync(WorkOSTokenSource tokens, Cancel
telemetry.Funnel.WorkspaceRequested();

var outcome = await client.ProvisionAsync(
baseUrl, await tokens.GetAsync(ct), orgName, slug, telemetry.Join.Current, ct);
baseUrl, await tokens.GetAsync(ct), orgName, slug, WorkspaceTier.Free, telemetry.Join.Current, ct);

switch (outcome.StatusCode) {
case 200 when outcome.Body?.WorkosOrgId is { Length: > 0 } orgId:
Expand Down
2 changes: 1 addition & 1 deletion src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ internal static string ChooseDiscoveryProvider(string[] args)
/// </summary>
internal static string WorkspaceCreationNeedsATerminalMessage(string signupUrl) =>
"Creating a workspace asks for a name and a slug, and this session is non-interactive.\n"
+ " • Answer up front: kcap setup --org \"<name>\" --slug <slug> --no-prompt\n"
+ " • Answer up front: kcap setup --org \"<name>\" --slug <slug> --tier team --no-prompt\n"
+ $" • Or create one at {signupUrl}/signup, then run: kcap setup <slug> --no-prompt\n"
+ " • Or point at a workspace you already belong to: kcap setup --server-url <url> --no-prompt";

Expand Down
4 changes: 2 additions & 2 deletions src/Capacitor.Cli.Core/Auth/TenantProvisioningClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public sealed class TenantProvisioningClient(HttpClient http) {
/// only the two that provision a workspace have a telemetry facade to take one from.
/// </param>
public async Task<ProvisionOutcome> ProvisionAsync(
string baseUrl, string token, string orgName, string slug, string? joinId, CancellationToken ct) {
string baseUrl, string token, string orgName, string slug, WorkspaceTier tier, string? joinId, CancellationToken ct) {
var payload = JsonSerializer.Serialize(
new ProvisionRequest { OrgName = orgName, Slug = slug, Tier = "free", JoinId = joinId },
new ProvisionRequest { OrgName = orgName, Slug = slug, Tier = tier.Wire(), JoinId = joinId },
CapacitorJsonContext.Default.ProvisionRequest);

try {
Expand Down
8 changes: 8 additions & 0 deletions src/Capacitor.Cli.Core/Auth/WorkspaceTier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Capacitor.Cli.Core.Auth;

// The two self-serve plans kcap-web provisions. Team starts a 30-day trial; neither is billed.
public enum WorkspaceTier { Team, Free }
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

public static class WorkspaceTierExtensions {
public static string Wire(this WorkspaceTier tier) => tier == WorkspaceTier.Team ? "team" : "free";
}
19 changes: 13 additions & 6 deletions src/Capacitor.Cli.Core/Resources/help-setup.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ Options:
create, instead of being prompted for it. Requires
--org. A slug that is taken or malformed ends the run
naming it, rather than asking for another.
--tier <team|free> Plan for a workspace created with --org and --slug:
team starts a 30-day Team trial, free is the Free
plan (the default). An account that cannot have the
trial (a personal email, or a domain that already
has one) gets a Free workspace instead, and setup
says why.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
--no-prompt Non-interactive mode (requires <tenant> or
--server-url, or --org + --slug to create a
workspace as part of the run)
Expand Down Expand Up @@ -183,12 +189,13 @@ screen ran the daemon as a service, setup says so instead of suggesting
the import prompt and names `kcap import --all` for later.

When you sign in with Kurrent's hosted auth and discovery finds no tenant, setup
asks how to continue: create one for you (organization name + workspace slug) and
wait for it to go live, point at a workspace you already belong to (enter its slug
or URL — same as `kcap setup <tenant>`), or cancel. Pass --org and --slug to answer
the creation questions up front and skip that fork entirely, which is what a script
or a session with redirected input needs, alongside --no-prompt for the steps after
it. The two flags go together: the slug becomes a permanent public hostname, so kcap
asks how to continue: create one for you (organization name, workspace slug and
plan, with the Team trial offered first) and wait for it to go live, point at a
workspace you already belong to (enter its slug or URL — same as
`kcap setup <tenant>`), or cancel. Pass --org and --slug, plus --tier for the
plan, to answer the creation questions up front and skip that fork entirely,
which is what a script or a session with redirected input needs, alongside
--no-prompt for the steps after it. The two flags go together: the slug becomes a permanent public hostname, so kcap
will not derive one from the name. They also need --no-prompt on a session that
cannot be prompted, since only the creation questions are answered by flags. Only
an account with no workspace yet can create one: if yours already has a different
Expand Down
6 changes: 3 additions & 3 deletions src/Capacitor.Cli/Commands/RequestedWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
namespace Capacitor.Cli.Commands;

/// <summary>
/// The two answers the create-a-workspace prompts collect, supplied up front by <c>--org</c> and
/// <c>--slug</c> instead. Deliberately not on <c>ITenantProvisioner</c>: only the CLI has flags to
/// The answers the create-a-workspace prompts collect, supplied up front by <c>--org</c>,
/// <c>--slug</c> and <c>--tier</c> instead. Deliberately not on <c>ITenantProvisioner</c>: only the CLI has flags to
/// fill it from, and the other host driving that interface prompts through its own UI.
/// </summary>
public sealed record RequestedWorkspace(string OrgName, string Slug) {
public sealed record RequestedWorkspace(string OrgName, string Slug, WorkspaceTier Tier = WorkspaceTier.Free) {
/// <summary>Expanded the same way `kcap setup &lt;tenant&gt;` expands a bare label.</summary>
public string Origin => ServerInput.ResolveTenantArg(Slug);
}
18 changes: 14 additions & 4 deletions src/Capacitor.Cli/Commands/SetupCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2302,10 +2302,20 @@ internal static (RequestedWorkspace? Workspace, string? Error) ParseRequestedWor
string[] args, bool haveServerUrl) {
const string Usage = "kcap setup --org \"<name>\" --slug <slug> --no-prompt";

var (orgGiven, orgName) = ValuedFlag(args, "--org");
var (slugGiven, slug) = ValuedFlag(args, "--slug");
var (orgGiven, orgName) = ValuedFlag(args, "--org");
var (slugGiven, slug) = ValuedFlag(args, "--slug");
var (tierGiven, tierText) = ValuedFlag(args, "--tier");
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

if (!orgGiven && !slugGiven) return (null, null);
if (!orgGiven && !slugGiven)
return tierGiven ? (null, $"--tier picks the plan for a workspace to create, so it needs --org and --slug: {Usage}") : (null, null);

var tier = !tierGiven ? WorkspaceTier.Free : tierText?.Trim().ToLowerInvariant() switch {
"team" => WorkspaceTier.Team,
"free" => WorkspaceTier.Free,
_ => (WorkspaceTier?)null
};

if (tier is null) return (null, "--tier takes team (a 30-day Team trial) or free.");

// Present-but-empty is rejected rather than read as absent: a script whose $ORG expanded to
// nothing still asked for a workspace.
Expand All @@ -2332,7 +2342,7 @@ internal static (RequestedWorkspace? Workspace, string? Error) ParseRequestedWor

if (!check.Ok) return (null, SpectreTenantProvisioner.SlugRejection(canonical, check.Reason, "pass a different --slug"));

return (new RequestedWorkspace(orgName!.Trim(), canonical), null);
return (new RequestedWorkspace(orgName!.Trim(), canonical, tier.Value), null);
}

static readonly string[] DiscoverFlags =
Expand Down
Loading
Loading