---
url: https://docs.sunlicense.com/cli/init.md
description: >-
  One command that signs you in or creates a guest account, creates a product,
  API key and test license, writes the keys to a gitignored .env and prints the
  code for your language. Plus mcp install, claim and open.
---

# Set up a project: `sunlicense init`

Run it in your project's folder. It needs no answers, so an AI agent can run it too:

```bash
sunlicense init            # or, for agents and scripts:
sunlicense init --json
```

It does the whole setup, and running it again finds what it made instead of creating more:

1. **Signs in.** Your saved account (or `SUNLICENSE_TOKEN`). Not signed in: in a terminal it asks
   whether to continue as a guest, sign in or sign up; without one (an agent's shell, CI) it creates a
   **guest account** with no email. Keep it later with [`sunlicense claim`](#keep-a-guest-account-sunlicense-claim).
2. **Creates a product** named after the project (from `package.json`, `pom.xml`, Gradle settings,
   `pyproject.toml`, `Cargo.toml`, `composer.json`, `*.csproj`, `go.mod`, `fxmanifest.lua`… or the folder),
   with a **Standard** policy (subscription, 2 machines, 365 days).
3. **Creates an API key** for the app and **issues one test license** (customer `test@example.com`, never emailed).
4. **Writes the keys** to `.env` (`.env.local` for Next.js, Vite, Nuxt, SvelteKit and Astro, or when the
   project has one) as `SUNLICENSE_API_KEY` and `SUNLICENSE_LICENSE_KEY`, keeping everything else in the file
   and its line endings, and adds the file to `.gitignore` unless git already ignores it.
5. **Prints the integration**: the SDK's install line and code for the project's language, or, for a
   language without an official SDK (Go, C/C++, Swift, Ruby, Dart, …), the HTTP request and how to read
   the answer. Rust gets the HTTP request too until its SDK has a release that validates.

Full keys are only ever written to the gitignored file. The output shows them masked (`K7QX-XXXX-XXXX-XXXX`,
`slk_ab12cd34…`).

Then check it the way your app will. With no flags, `sunlicense validate` reads both keys from `.env`, and
`--release` frees this machine's slot again afterwards, so both of the test license's 2 machines stay free for
your app:

```bash
sunlicense validate --release        # VALID, exit code 0
```

| Flag | Does |
|---|---|
| `--json` | Print the JSON below instead of text |
| `--name "Acme Editor"` | Product (and guest organization) name |
| `--language python` | Override detection: `java`, `js`, `python`, `dotnet`, `php`, `lua`, `rust` or `http` |
| `--env-file config/.env` | Where to write the keys, relative to the project |
| `--dir path/to/project` | Run for another folder |
| `--guest` / `--no-guest` | Always create a guest without asking / never create one (fail when not signed in) |
| `--product <code>` | Use this existing product (code or name) instead of one named after the project, e.g. when the plan has no room for another |
| `--no-test-license` | Don't issue a test license |
| `--project-credentials auto\|yes\|no` | Also keep a new guest's sign-in in the project (below) |

### Containers, Codespaces and CI

When the home folder won't survive a rebuild (a dev container, GitHub Codespaces, Gitpod, Replit, CI, or
any container), a new guest account's sign-in is also saved to `.sunlicense/credentials` in the project
(readable by you only, and added to `.gitignore`), and the CLI uses it whenever no account is signed in on
the machine. Never commit it: it can manage the organization. `--project-credentials yes` or `no` decides
it yourself.

## Guest accounts

A guest account is a normal Free organization you own, without an email yet. Until it's claimed it can't
send anything to other people or host files: no customer emails, webhooks, Discord posts, customer portal,
custom domains or product downloads, and at most 10 licenses. **An unclaimed guest account is deleted 30
days after it was last used** (any CLI or API call, a dashboard visit, or license checks), and every
command reminds you on stderr while it's a guest. More in [Guest accounts](/guide/guest-accounts).

### Keep a guest account: `sunlicense claim`

```bash
sunlicense claim             # opens the claim page and waits until you're done
sunlicense claim --no-open   # print the link, to open on another device
```

Add your email and a password on the page and confirm the email; everything in the organization stays,
and the CLI's sign-in keeps working. Until you've confirmed it, commands remind you to check your inbox.

Already have an account? You don't need the claim page: `sunlicense login` moves every guest account this
computer kept into the organization you sign in to, with keys and the project's API key unchanged
([details](/guide/guest-accounts#already-have-an-account-everything-moves-into-it)). `sunlicense claim` does
the same when you're signed in to your own account here too.

### Open the dashboard: `sunlicense open`

For a guest, `sunlicense open` opens the claim page, where you add your email and a password to keep the
account; **Not now** opens the dashboard signed in as the guest instead (both links work once, for 10 minutes).
While a claim waits for you to confirm your email, it signs you in straight away. For other accounts it opens
the dashboard as usual. `--print` prints the link instead.

From a guest account's third day, every command also prints one line with the date it will be deleted if it
isn't used, and `sunlicense claim`.

## Add the MCP server to your AI client: `sunlicense mcp install`

```bash
sunlicense mcp install                    # finds the client you're using
sunlicense mcp install --client cursor    # or name it
sunlicense mcp install --client codex --print
```

It tells the client apart from its environment, the processes that started the command, or the
project's config folders, and adds `sunlicense mcp` to that client's MCP servers without touching
anything else in the file:

| Client | `--client` | Written to |
|---|---|---|
| Claude Code | `claude-code` | `.mcp.json` |
| Cursor | `cursor` | `.cursor/mcp.json` |
| VS Code (Copilot agent mode) | `vscode` | `.vscode/mcp.json` |
| Gemini CLI | `gemini` | `.gemini/settings.json` |
| Zed | `zed` | `.zed/settings.json` |
| JetBrains (Junie) | `jetbrains` | `.junie/mcp/mcp.json` |
| Roo Code | `roo` | `.roo/mcp.json` |
| Kiro | `kiro` | `.kiro/settings/mcp.json` |
| Codex | `codex` | `~/.codex/config.toml` (Codex has no project config) |
| Windsurf | `windsurf` | `~/.codeium/windsurf/mcp_config.json` |
| Cline | `cline` | Cline's settings in VS Code |
| Antigravity, Aider, others | `antigravity`, `aider`, `other` | Nothing: prints what to paste |

`--scope user` writes the client's user config instead, for every project. A different existing
`sunlicense` entry is kept unless you add `--force`. No token is written: the server signs in as the CLI
does, so sign in first (`sunlicense init` does). A password sign-in expires after 12 hours; for assistants
use a guest account or `sunlicense login --with-token`. Installed through npm without a global install,
the config runs `npx -y @sunlicense/cli@latest mcp`.

Web apps and browser builders (claude.ai, ChatGPT, Lovable, Bolt, Replit) can't run a local command:
connect them to `https://api.sunlicense.com/mcp` instead ([remote MCP](/guide/remote-mcp)). More on the
tools: [AI assistants (MCP)](/cli/mcp).

## JSON output

`sunlicense init --json` prints one JSON document. It's a contract for AI agents: fields are added over
time, never renamed or removed, and `schemaVersion` changes only if that ever has to break.

```json
{
  "ok": true,
  "schemaVersion": 1,
  "account": { "organization": "space-trader", "organizationId": "…", "guest": true, "created": true,
    "deleteAfter": "2026-11-29T10:00:00Z", "server": "https://api.sunlicense.com",
    "dashboard": "https://app.sunlicense.com", "credentials": ["…/sunlicense/config.json"] },
  "project": { "dir": "/home/me/space-trader", "name": "space-trader", "language": "python",
    "languageName": "Python", "detectedFrom": "pyproject.toml" },
  "product": { "id": "…", "name": "space-trader", "code": "space-trader", "created": true },
  "policy": { "id": "…", "name": "Standard", "terms": "Subscription · 2 machines · 365 days", "created": true },
  "apiKey": { "id": "…", "name": "space-trader", "masked": "slk_ab12cd34…", "created": true,
    "envVar": "SUNLICENSE_API_KEY", "file": ".env" },
  "testLicense": { "id": "…", "key": "K7QX-XXXX-XXXX-XXXX", "status": "ACTIVE", "customerEmail": "test@example.com",
    "expiresAt": "…", "created": true, "envVar": "SUNLICENSE_LICENSE_KEY", "file": ".env" },
  "envFile": { "path": ".env", "gitignored": true, "addedToGitignore": true, "tracked": false },
  "integration": {
    "kind": "sdk",
    "sdk": { "id": "python", "name": "Python", "install": "pip install sunlicense", "repo": "https://github.com/sunlicense-com/sunlicense-py",
      "code": "…", "note": "…", "usedFor": "…" },
    "http": { "method": "POST", "url": "https://api.sunlicense.com/api/v2/license/validate",
      "headers": { "Authorization": "Bearer <SUNLICENSE_API_KEY>", "Content-Type": "application/json" },
      "body": { "license": "<the customer's license key>", "host": "<a stable id for this machine>" },
      "valid": "HTTP 200 …", "rejected": "HTTP 400 …", "codes": ["VALID", "NOT_FOUND", "EXPIRED", "…"], "example": "curl …" },
    "rules": ["Read the API key from the SUNLICENSE_API_KEY environment variable …", "…"]
  },
  "analytics": { "productAnalyticsId": "pa_7Hk2mQ9xZr4LbN8vT1wYcD", "enabled": true, "snippet": "…",
    "docs": "https://docs.sunlicense.com/guide/product-analytics" },
  "test": { "command": "sunlicense validate --release", "expect": "VALID (exit code 0) …" },
  "docs": { "llms": "https://docs.sunlicense.com/llms.txt", "validation": "…", "sdks": "…", "offline": "…", "mcp": "…", "init": "…", "analytics": "…" },
  "next": [
    { "command": "sunlicense validate --release", "why": "…", "for": "agent" },
    { "command": "sunlicense mcp install", "why": "…", "for": "agent" },
    { "command": "sunlicense guide", "why": "…", "for": "human" },
    { "command": "sunlicense claim", "why": "…", "for": "human" }
  ],
  "warnings": ["Guest account space-trader is deleted in 30 days if it isn't used. …"]
}
```

| Field | Meaning |
|---|---|
| `integration.kind` | `sdk` when an official SDK has code for the language (`integration.sdk` is set), `http` otherwise (`sdk` is `null`), including Rust until its SDK can validate. `http` is always there as a fallback. |
| `created` | `true` when this run made it, `false` when it found an earlier one |
| `analytics` | The product's [product analytics](/guide/product-analytics): its public product analytics id (`pa_…`, not a secret: free builds ship it), whether reports are on, and a snippet that starts reporting (the Java SDK's reporter for Java projects, else one report over HTTP). Left out when the server has no product analytics or your role can't read product settings |
| `envFile.tracked` | The file is already committed to git; a warning says how to fix that |
| `next[].for` | `agent`: run it now. `human`: tell the developer to run it |
| `warnings` | Sentences to pass on to the developer |

On failure the exit code is non-zero and the document is
`{"ok": false, "schemaVersion": 1, "error": {"code": "…", "message": "…"}, "next": […]}`, with `code` such as
`guest_unavailable` (this server can't create guest accounts: sign up instead), `guest_limit` (today's guest
accounts from this network are used up), `plan_limit` (the plan has no room for another product: the message
names your products; run again with `--product <code>` to use one of them, or upgrade), `product_not_found`
(`--product` names none of yours), `unavailable` (SunLicense couldn't be reached) or `error`.

`sunlicense mcp install --json` prints `{"ok", "detected": [{"id", "by", "detail"}], "results": [{"client", "name",
"scope", "file", "action", "snippet", "note"}], "server": {"command", "args"}, "remote", "warnings"}`, where
`action` is `added`, `unchanged`, `replaced`, `kept` or `printed`.
