Skip to content
Open
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,62 @@ the Owner password printed by `devspace init`. It is also stored in:

Keep that password private.

## Configuration Management

Use `devspace config` to inspect the effective settings after DevSpace merges
persisted configuration, environment variables, and defaults:

```bash
devspace config
```

Update persistent settings without running setup again:

```bash
# Change the local listening port
devspace config port 7676

# Change the local bind host
devspace config host 127.0.0.1

# Set the public domain; DevSpace automatically uses /mcp
devspace config domain devspace.example.com

# Recommended: set a new Owner password through a hidden prompt
devspace config key

# Non-interactive automation can supply the password explicitly
devspace config key "your-new-owner-password"
```

`config domain` accepts a hostname or domain only. Do not include a protocol,
port, path, or `/mcp`; DevSpace stores `https://devspace.example.com` and derives
`https://devspace.example.com/mcp` automatically.

Configuration changes are saved locally. `devspace config key` opens a hidden
prompt in an interactive terminal; use an explicit password only for
non-interactive automation. Restart `devspace serve` for host, port, domain, or
Owner password changes to take effect.

## CLI Help and Version

```bash
# Show the installed version
devspace --version
devspace -v

# Show top-level help
devspace --help
devspace -h

# Show configuration command help
devspace --help config
devspace -h config
```

Running `devspace` without arguments prints top-level help. Use `devspace serve`
to start the MCP server.

## Connect Your MCP Client

The default local endpoint is:
Expand Down
28 changes: 26 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,34 @@ DEVSPACE_CONFIG_DIR=/path/to/config npx @waishnav/devspace serve
npx @waishnav/devspace init
npx @waishnav/devspace serve
npx @waishnav/devspace doctor
npx @waishnav/devspace config get
npx @waishnav/devspace config set publicBaseUrl https://devspace.example.com

# Show effective settings as JSON. Owner passwords are always masked.
npx @waishnav/devspace config

# Persist local server settings.
npx @waishnav/devspace config host 127.0.0.1
npx @waishnav/devspace config port 7676
npx @waishnav/devspace config domain devspace.example.com

# Set the Owner password and revoke persisted OAuth clients and tokens.
npx @waishnav/devspace config key "your-new-owner-password"
```

`config` prints effective settings as JSON. `config host`, `config port`, and
`config domain` persist changes in `~/.devspace/config.json`. Restart DevSpace
after changing them. `config domain` accepts a hostname such as
`devspace.example.com`, stores `https://devspace.example.com`, and DevSpace
automatically uses `/mcp` as the MCP endpoint.

`config key <key>` stores the supplied Owner password in `auth.json` and
clears persisted OAuth clients and tokens. The value must be at least 16
characters and is never printed by DevSpace. Restart DevSpace before using the
new password. It cannot update a password supplied through
`DEVSPACE_OAUTH_OWNER_TOKEN`; unset that environment variable first.

For backward compatibility, `config get` prints the persisted JSON and
`config set publicBaseUrl <url|null>` remains available.

## Core Environment Variables

| Variable | Purpose |
Expand Down
2 changes: 1 addition & 1 deletion docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ DEVSPACE_PUBLIC_BASE_URL="https://new-tunnel.example.com" npx @waishnav/devspace
For a stable public URL, persist it:

```bash
npx @waishnav/devspace config set publicBaseUrl https://devspace.example.com
npx @waishnav/devspace config domain devspace.example.com
npx @waishnav/devspace serve
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build:app": "vite build",
"dev": "node scripts/dev-server.mjs",
"start": "node dist/cli.js serve",
"test": "tsx src/config.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
"test": "tsx src/config.test.ts && tsx src/config-operations.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"keywords": [],
Expand Down
80 changes: 74 additions & 6 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,84 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as {
version: string;
};

for (const flag of ["-v", "--version"]) {
const output = execFileSync("node", ["--import", "tsx", "src/cli.ts", flag], {
encoding: "utf8",
env: { ...process.env, DEVSPACE_CONFIG_DIR: "/tmp/devspace-cli-version-test" },
}).trim();

const output = runCli([flag], { DEVSPACE_CONFIG_DIR: "/tmp/devspace-cli-version-test" }).trim();
assert.equal(output, packageJson.version);
}

const topLevelHelp = runCli(["--help"]);
assert.equal(runCli([]), topLevelHelp);
assert.match(topLevelHelp, /^usage: devspace \[--version\] \[--help\] <command> \[<args>]$/m);
assert.match(topLevelHelp, /start and connect a local MCP server/);
assert.match(topLevelHelp, /manage persistent DevSpace settings/);
assert.match(topLevelHelp, /Use `devspace config` to show current settings/);
assert.match(topLevelHelp, /Use `devspace --help config` for configuration commands/);

const configHelp = runCli(["--help", "config"]);
assert.match(configHelp, /^usage: devspace config \[<command> \[<args>\]\]$/m);
assert.match(configHelp, /\(no command\)\s+Print effective settings as JSON/);
assert.match(configHelp, /domain <domain>\s+Set the public domain; MCP uses \/mcp automatically/);
assert.match(configHelp, /key \[key\]\s+Set the Owner password and revoke saved OAuth sessions/);
assert.match(configHelp, /Omit <key> to enter it in a hidden prompt/);
assert.equal(runCli(["-h", "config"]), configHelp);
assert.equal(runCli(["help", "config"]), configHelp);

const root = mkdtempSync(join(tmpdir(), "devspace-cli-config-test-"));
try {
const env = {
DEVSPACE_CONFIG_DIR: join(root, "config"),
DEVSPACE_STATE_DIR: join(root, "state"),
};

assert.match(runCli(["config", "host", "127.0.0.1"], env), /Updated local bind host/);
assert.match(runCli(["config", "port", "8787"], env), /Updated local bind port/);
assert.match(runCli(["config", "domain", "devspace.example.com"], env), /public domain/);

const shown = JSON.parse(runCli(["config"], env)) as {
host: string;
port: number;
publicBaseUrl: string;
publicUrl: string;
accessKey: string;
};
assert.equal(shown.host, "127.0.0.1");
assert.equal(shown.port, 8787);
assert.equal(shown.publicBaseUrl, "https://devspace.example.com");
assert.equal(shown.publicUrl, "https://devspace.example.com/mcp");
assert.equal(shown.accessKey, "(not configured)");

const newOwnerPassword = "cli-owner-password-for-test";
const keyOutput = runCli(["config", "key", newOwnerPassword], env);
assert.match(keyOutput, /Owner password updated/);
assert.ok(!keyOutput.includes(newOwnerPassword));
const updated = JSON.parse(runCli(["config"], env)) as { accessKey: string };
assert.equal(updated.accessKey, "********");
} finally {
rmSync(root, { recursive: true, force: true });
}

function runCli(args: string[], overrides: NodeJS.ProcessEnv = {}): string {
const env = { ...process.env };
for (const key of [
"HOST",
"PORT",
"DEVSPACE_ALLOWED_ROOTS",
"DEVSPACE_ALLOWED_HOSTS",
"DEVSPACE_OAUTH_OWNER_TOKEN",
"DEVSPACE_PUBLIC_BASE_URL",
]) {
delete env[key];
}

return execFileSync("node", ["--import", "tsx", "src/cli.ts", ...args], {
encoding: "utf8",
env: { ...env, ...overrides },
});
}
Loading