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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,22 @@ Use exactly one of `--description` or `--clear-description`. Miftah changes only

In the browser, run `miftah dashboard --config ~/.config/miftah/gsc.json` and choose **Edit a non-secret account label**. In catalog mode, select the configuration again after a successful write before making another Console change.

### Remove an account later

When an account should no longer be part of this Miftah configuration, remove it through the same guarded lifecycle instead of hand-editing JSON:

```bash
miftah profile remove --config ~/.config/miftah/gsc.json --profile google-personal --replacement-profile google-work
```

The selected profile must already exist and at least one other profile must remain. `--replacement-profile` is required whenever the removed account is the durable default or is named by a routing rule, routing-plugin binding, or configured profile lock. Miftah moves only those durable configuration references to the chosen existing account, validates the complete candidate, keeps a recovery backup, and finalizes the configured fail-closed audit record.

It changes Miftah configuration only: it does not resolve or delete an underlying secret, read or delete a provider token cache, or change an active MCP client session. Restart or reconnect the client after a successful removal.

Miftah refuses to remove a profile with a configured native OAuth binding. There is intentionally no hand-edit workaround: removing the configuration binding and OS-vault credential needs one atomic lifecycle, which this generic account-removal flow does not yet own.

In the browser, run `miftah dashboard --config ~/.config/miftah/gsc.json` and choose **Remove an account safely**. Select the account, a replacement, and the explicit confirmation. In catalog mode, select the configuration again after the write before making another Console change.

## Everyday commands

These are shell commands. Profile switching and identity tools such as `miftah_use_profile` are MCP management tools used from the connected client.
Expand All @@ -527,6 +543,7 @@ These are shell commands. Profile switching and identity tools such as `miftah_u
| Start one profile and verify initialization | `miftah test-profile --config service.json --profile work` |
| Review configured account profiles without starting an upstream | `miftah profile list --config service.json` |
| Set or clear a non-secret account label | `miftah profile set-description --config service.json --profile personal --description "Personal account"` |
| Remove one account with explicit durable-reference replacement | `miftah profile remove --config service.json --profile personal --replacement-profile work` |
| Run the reviewed safe check for one provider-backed account | `miftah profile test --config service.json --profile work` |
| Discover one profile's upstream tools | `miftah list-tools --config service.json --profile work` |
| Read redacted audit events | `miftah logs --config service.json` |
Expand Down
3 changes: 2 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Usage: miftah [command] [options]
```

The root command list includes `serve`, `dashboard`, `console`, `validate`, `doctor`, `schema`, `setup`, `init`, `migrate-config`, `profile list|set-default|set-description|test`, `connection add|list|status|test`, `auth connect|reauth|disconnect`, `list-tools`, `test-profile`, `logs`, `audit-export`, `audit-verify`, and `version`. With no command, Miftah runs `serve`.
The root command list includes `serve`, `dashboard`, `console`, `validate`, `doctor`, `schema`, `setup`, `init`, `migrate-config`, `profile list|set-default|set-description|remove|test`, `connection add|list|status|test`, `auth connect|reauth|disconnect`, `list-tools`, `test-profile`, `logs`, `audit-export`, `audit-verify`, and `version`. With no command, Miftah runs `serve`.

Documented command names, options, JSON success forms, and exit categories are compatibility contracts. An incompatible CLI removal, rename, required-option change, or semantic output change requires the pre-1.0 deprecation/removal process in the [public compatibility policy](library-api.md#compatibility-policy).

Expand All @@ -30,6 +30,7 @@ Documented command names, options, JSON success forms, and exit categories are c
| `miftah profile list --config <file>` | `--config` | `--config <file>` | Lists the durable default and fixed non-secret metadata for every configured account: name, optional description/tags/policy, and profile-level upstream override names. It only loads and validates configuration; it never resolves secret references, reads credentials, headers, OAuth vault entries, or provider token caches, starts an upstream, or changes configuration. |
| `miftah profile set-default --config <file> --profile <name>` | `--config`, `--profile` | `--config <file>`, `--profile <name>` | Makes one existing profile the durable default for future Miftah sessions. It performs a guarded replacement with a recovery backup and finalizes a configured fail-closed audit record. Its result omits configuration bytes, profile data, provider paths, and secret references; it never starts an upstream, opens OAuth, or changes profile data, provider caches, or an active client session. |
| `miftah profile set-description --config <file> --profile <name>` | `--config`, `--profile`, exactly one of `--description` or `--clear-description` | `--config <file>`, `--profile <name>`, `--description <text>`, `--clear-description` | Sets or explicitly clears one existing profile's non-secret description. It validates the candidate and performs a guarded replacement with a recovery backup and configured fail-closed audit record. The public result omits configuration bytes and the submitted description. It never starts an upstream, opens OAuth, resolves credentials, changes routing/default selection, or changes provider caches or an active client session. |
| `miftah profile remove --config <file> --profile <name>` | `--config`, `--profile`; `--replacement-profile` when the profile has durable references | `--config <file>`, `--profile <name>`, `--replacement-profile <name>` | Removes one configured profile only when another remains. It requires an explicit different existing replacement before it changes a durable default, routing rule, routing-plugin binding, or configured profile lock; it performs one guarded replacement with recovery backup and fail-closed audit. It never resolves or deletes an underlying secret, provider cache, token cache, or active client session. A configured native OAuth binding causes `PROFILE_REMOVAL_OAUTH_CONNECTION`: configuration and OS-vault cleanup are not split by this generic command. |
| `miftah profile test --config <file> --profile <name>` | `--config`, `--profile` | `--config <file>`, `--profile <name>`, `--upstream <name>` | Runs only the selected provider adapter's declared read-only readiness check and writes its redacted report. It never accepts an arbitrary tool name, exposes provider output, changes configuration, reads provider token caches, or opens a browser. `--upstream` is required only when the configuration has multiple upstreams. It exits `0` only for `ready`; other bounded readiness results exit `1`. |
| `miftah connection add --config <file>` | `--config`, `--profile`, `--issuer`, `--client-registration` | `--connection <ref>`, `--upstream <name>`, repeated `--scope <scope>`, `--write` | Plans a v3 OAuth binding by default. `--write` applies the reviewed candidate with a unique recovery backup and configured audit event. It never resolves credentials or starts an upstream. |
| `miftah connection list --config <file>` | `--config` | `--client <claude-desktop\|claude-code\|cursor\|vscode\|all>` | Lists redacted connection state. Optional snippets are copyable JSON only; Miftah never edits client settings. |
Expand Down
3 changes: 2 additions & 1 deletion docs/console-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Browser sessions have a 15-minute idle limit and a one-hour absolute limit. Rest

Every request must use the exact listener `Host`. Browser mutations, including bootstrap, must also use the exact listener `Origin`. Normal same-origin navigation and authenticated `GET` and `HEAD` requests may omit `Origin`, because browsers do not consistently attach it to read requests; hostile or duplicate Origin values are still rejected, cross-site cookies remain blocked by `SameSite=Strict`, and every mutation still requires exact Origin plus CSRF. Except for the bootstrap exchange and static application assets, every API endpoint requires the session cookie. JSON request bodies are capped at 64 KiB and must use `Content-Type: application/json`, except the client-entry onboarding endpoint has a bounded envelope limit of up to 136 KiB so it can carry one separately byte-limited 64 KiB pasted document after JSON escaping. An explicitly configured request-size cap remains authoritative. Headers are capped at 16 KiB. The process admits at most 240 trusted requests per minute and only eight bootstrap attempts per minute; excess requests receive `429` plus `Retry-After`.

`POST /api/v1/sessions`, `POST /api/v1/connections/:ref/connect`, `POST /api/v1/connections/:ref/test`, `POST /api/v1/connections/:ref/reauth`, and `DELETE /api/v1/connections/:ref/credential` must send `Content-Type: application/json` with the JSON body `{}`. `POST /api/v1/connections` instead accepts a strict JSON object with required `profile`, `issuer`, `clientRegistration`, and `scopes` fields plus optional `connectionRef` and `upstream` fields; unknown fields are rejected. `POST /api/v1/profile-readiness` accepts only a selected `profile` plus an optional `upstream`; the Console always selects an exact configured upstream when there is more than one. It can invoke only a provider-declared safe read-only check after audit and policy preflight, binds that operation to the browser request so a disconnected client cancels it, and never accepts a tool name or arguments. `POST /api/v1/profiles/default` accepts only one existing `profile` name. It changes only the durable `defaultProfile` through the guarded replacement and audit transaction; it never starts an upstream, opens OAuth, accepts a credential value, or changes provider-owned token caches. Native-OAuth first run accepts only non-secret configuration name, profile, description, exact resource/issuer, client-registration mode, and scopes. Reviewed-preset first run accepts only safe catalog metadata; for Google Search Console that can include strict named account objects containing a profile name, optional description, and client-secrets path, plus an explicit default when there is more than one account. The `local-stdio` preset additionally accepts one literal executable, a bounded literal argument array, optional native absolute working directory, optional credential-environment name, and a required `acceptLocalCommand: true` acknowledgement. It never uses a shell or starts that generic local executable during onboarding; shell/wrapper, URL, control, environment-reference, and credential-shaped values are rejected. On Windows, it accepts only a direct absolute `.exe` or `.com` binary, not a bare command or `.cmd`/`.bat` shim, so the direct argv path cannot fall back to a command processor. Client-entry first run accepts a bounded in-memory JSON document plus an explicit entry name; it accepts a selected local stdio entry that fits the finite static launch grammar or one credential-free HTTPS remote entry. A remote entry must be a `url` under `mcpServers` or `servers`, explicitly marked `type: "http"` or `"streamable-http"`; it must be HTTPS without userinfo, query, fragment, or opaque credential-shaped path segments. The Console never persists, returns, or audits the pasted document, never copies `env` or headers, and does not discover OAuth or call the remote endpoint during import. On Windows, a local import accepts only a direct absolute `.exe` or `.com` executable; bare runners such as `npx` or `node`, and `.cmd`/`.bat` shims, are rejected rather than being dispatched through a command processor. Token, password, cookie, raw secret contents, callback, environment wrappers, inline code, opaque values or assignments, unsupported remote transports, opaque credential-shaped URL path segments, and arbitrary extra fields are rejected.
`POST /api/v1/sessions`, `POST /api/v1/connections/:ref/connect`, `POST /api/v1/connections/:ref/test`, `POST /api/v1/connections/:ref/reauth`, and `DELETE /api/v1/connections/:ref/credential` must send `Content-Type: application/json` with the JSON body `{}`. `POST /api/v1/connections` instead accepts a strict JSON object with required `profile`, `issuer`, `clientRegistration`, and `scopes` fields plus optional `connectionRef` and `upstream` fields; unknown fields are rejected. `POST /api/v1/profile-readiness` accepts only a selected `profile` plus an optional `upstream`; the Console always selects an exact configured upstream when there is more than one. It can invoke only a provider-declared safe read-only check after audit and policy preflight, binds that operation to the browser request so a disconnected client cancels it, and never accepts a tool name or arguments. `POST /api/v1/profiles/default` accepts only one existing `profile` name. It changes only the durable `defaultProfile` through the guarded replacement and audit transaction; it never starts an upstream, opens OAuth, accepts a credential value, or changes provider-owned token caches. `POST /api/v1/profiles/remove` accepts only a selected `profile` and optional `replacementProfile`. It refuses the last profile and requires a different existing replacement whenever it must redirect a durable default, routing rule, routing-plugin binding, or configured profile lock. It does not resolve or delete secrets, provider caches, token caches, or OS-vault data. A profile with a configured native OAuth binding is rejected because Console will not split configuration removal from the required atomic OS-vault lifecycle. Native-OAuth first run accepts only non-secret configuration name, profile, description, exact resource/issuer, client-registration mode, and scopes. Reviewed-preset first run accepts only safe catalog metadata; for Google Search Console that can include strict named account objects containing a profile name, optional description, and client-secrets path, plus an explicit default when there is more than one account. The `local-stdio` preset additionally accepts one literal executable, a bounded literal argument array, optional native absolute working directory, optional credential-environment name, and a required `acceptLocalCommand: true` acknowledgement. It never uses a shell or starts that generic local executable during onboarding; shell/wrapper, URL, control, environment-reference, and credential-shaped values are rejected. On Windows, it accepts only a direct absolute `.exe` or `.com` binary, not a bare command or `.cmd`/`.bat` shim, so the direct argv path cannot fall back to a command processor. Client-entry first run accepts a bounded in-memory JSON document plus an explicit entry name; it accepts a selected local stdio entry that fits the finite static launch grammar or one credential-free HTTPS remote entry. A remote entry must be a `url` under `mcpServers` or `servers`, explicitly marked `type: "http"` or `"streamable-http"`; it must be HTTPS without userinfo, query, fragment, or opaque credential-shaped path segments. The Console never persists, returns, or audits the pasted document, never copies `env` or headers, and does not discover OAuth or call the remote endpoint during import. On Windows, a local import accepts only a direct absolute `.exe` or `.com` executable; bare runners such as `npx` or `node`, and `.cmd`/`.bat` shims, are rejected rather than being dispatched through a command processor. Token, password, cookie, raw secret contents, callback, environment wrappers, inline code, opaque values or assignments, unsupported remote transports, opaque credential-shaped URL path segments, and arbitrary extra fields are rejected.

`POST /api/v1/profiles/environment-account` accepts only a profile slug, optional description, an environment-variable name, and optional `makeDefault: true`. It is available only for the currently selected trusted configuration when every existing profile has one matching direct credential environment binding for a single local `stdio` upstream. It rejects remote HTTP, provider-adapter, OAuth, named-upstream, raw credential, duplicate source-variable, and arbitrary profile-override cases. The operation records intent and completion in the fail-closed Console journal, writes via the guarded configuration transaction, never starts the upstream, and returns no credential environment reference or configuration bytes.

Expand All @@ -49,6 +49,7 @@ Every request must use the exact listener `Host`. Browser mutations, including b
| `POST /api/v1/configurations/:id/select` | Select one opaque catalog entry for this Console process. Requires CSRF; it never changes MCP client files or live MCP sessions. |
| `POST /api/v1/profile-readiness` | Run one declared safe read-only check for the selected profile/upstream. Requires CSRF, never auto-runs, and returns only bounded status rather than provider output. |
| `POST /api/v1/profiles/default` | Make one existing profile the durable default for future Miftah sessions. Requires CSRF, accepts only `{ "profile": "…" }`, and performs no OAuth, upstream, credential, or provider-cache operation. |
| `POST /api/v1/profiles/remove` | Remove one selected profile through the guarded audited configuration transaction. Requires CSRF and accepts only `{ "profile": "…", "replacementProfile": "…"? }`; a replacement is required for durable references. It never deletes secrets, provider caches, token caches, or OS-vault entries, and it rejects configured native OAuth bindings rather than splitting that lifecycle. |
| `POST /api/v1/profiles/environment-account` | Add one simple local environment-backed account. Requires CSRF and a selected trusted configuration. It accepts only a profile, optional description, environment-variable name, and optional durable default; never a credential value. |
| `GET /api/v1/profiles` | Return profile names, descriptions, tags, policy names, and named-upstream keys only. |
| `GET /api/v1/connections` | Return configured non-secret OAuth connection bindings without opening the vault. |
Expand Down
5 changes: 5 additions & 0 deletions src/cli/exit-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export const ERROR_EXIT_CODES = {
PROFILE_NOT_FOUND: CLI_EXIT_CODES.config,
PROFILE_ALREADY_EXISTS: CLI_EXIT_CODES.config,
PROFILE_DESCRIPTION_INPUT_INVALID: CLI_EXIT_CODES.config,
PROFILE_REMOVAL_INPUT_INVALID: CLI_EXIT_CODES.config,
PROFILE_LAST_PROFILE: CLI_EXIT_CODES.config,
PROFILE_REPLACEMENT_REQUIRED: CLI_EXIT_CODES.config,
PROFILE_REPLACEMENT_INVALID: CLI_EXIT_CODES.config,
PROFILE_REMOVAL_OAUTH_CONNECTION: CLI_EXIT_CODES.config,
PROFILE_SWITCH_DISABLED: CLI_EXIT_CODES.config,
PROFILE_SWITCH_CONFIRMATION_REQUIRED: CLI_EXIT_CODES.policy,
PROFILE_SELECTION_STALE: CLI_EXIT_CODES.policy,
Expand Down
10 changes: 10 additions & 0 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { runProfileReadinessCommand } from "./profile-readiness-command.js";
import { runProfileListCommand } from "./profile-list-command.js";
import { runDefaultProfileChange } from "../setup/profile-default-onboarding.js";
import { runProfileDescriptionChange } from "../setup/profile-description-onboarding.js";
import { runProfileRemoval } from "../setup/profile-removal-onboarding.js";
import { runAuditExportCommand } from "./audit-export.js";
import { formatAuditVerifyReport, runAuditVerifyCommand } from "./audit-verify.js";
import { runMigrateConfigCommand } from "./migrate-config.js";
Expand Down Expand Up @@ -237,6 +238,15 @@ async function main(argv = process.argv.slice(2)): Promise<void> {
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
return;
}
if (command === "profile remove") {
const report = await runProfileRemoval({
configPath: args.config,
profile: requireOption(command, "profile", args.profile),
...(args.replacementProfile === undefined ? {} : { replacementProfile: args.replacementProfile })
});
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
return;
}
if (command === "profile test") {
const result = await runProfileReadinessCommand({
configPath: args.config,
Expand Down
Loading