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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,13 @@ jobs:
run: npm ci

- name: Test core contracts
if: ${{ runner.os != 'Windows' }}
run: npm run test:core

- name: Test core contracts (Windows, serialized files)
if: ${{ runner.os == 'Windows' }}
run: npm run test:core -- --no-file-parallelism

- name: Test OAuth and Console compatibility
run: npm run test:oauth-console

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format

## [Unreleased]

## [0.5.5] - 2026-07-31

### Changed

- [#202](https://github.com/mohanagy/miftah/issues/202) Made the Console task-first for returning users: validated MCP connections now lead the page and show their named account profiles, durable default, and whether live in-session switching through `miftah_use_profile` is available. The setup wizard remains directly reachable, while the authentication ownership matrix and trust-boundary reference are collapsed behind **How authentication works**. The Console still exposes only non-secret metadata, never inspects client settings or running MCP processes, and preserves the existing validation, redaction, audit, file-containment, and no-shell boundaries. External evaluator acceptance remains open and is not claimed by this change.
- [#319](https://github.com/mohanagy/miftah/issues/319) Prepared the compatible v0.5.5 patch release for the task-first Console correction. Miftah remains experimental and pre-1.0. Technical delivery and owner dogfooding do not satisfy the external acceptance counts; external validation remains incomplete under #25, #88, and #202.

## [0.5.4] - 2026-07-30

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Install Miftah, then choose the terminal wizard or the browser Console. Both use
### 1. Install the current release

```bash
npm install -g @lubab/miftah@0.5.4
npm install -g @lubab/miftah@0.5.5
miftah version
```

Expand Down
2 changes: 1 addition & 1 deletion docs/presets-and-clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This is the compatibility source of truth for generated `miftah init` configurations and client snippets.

- Catalog version: `3`
- Miftah package version: `0.5.4`
- Miftah package version: `0.5.5`
- Last tested / validation boundary: the catalog builds strict Miftah configuration that `validateConfig` accepts. The docs contract test checks generated configuration only; it does **not** construct a runtime, start, authenticate to, or smoke-test external providers.

Miftah itself requires Node.js `>=20`. That does not establish an upstream server's Node requirement.
Expand Down
4 changes: 2 additions & 2 deletions docs/whats-new-in-0.5.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# What is in Miftah 0.5

Install `@lubab/miftah@0.5.4` when you want Miftah to guide setup instead of assembling a multi-account configuration by hand:
Install `@lubab/miftah@0.5.5` when you want Miftah to guide setup instead of assembling a multi-account configuration by hand:

```bash
npm install -g @lubab/miftah@0.5.4
npm install -g @lubab/miftah@0.5.5
miftah version
```

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lubab/miftah",
"version": "0.5.4",
"version": "0.5.5",
"description": "Wrap any MCP. Use the right account without reconnecting.",
"keywords": [
"mcp",
Expand Down
137 changes: 97 additions & 40 deletions src/console/console-assets.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/console/console-config-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,9 @@ export async function discoverConsoleConfigCatalog(
name: summary.name,
version: summary.version,
profileCount: summary.profiles.length,
profileNames: summary.profiles.map(({ name }) => name),
defaultProfile: summary.defaultProfile,
profileSwitchingFromMcp: summary.profileSwitchingFromMcp === true,
authentication: summary.authentication ?? {
mode: "miftah-native-oauth",
credentialOwner: "miftah",
Expand Down
7 changes: 7 additions & 0 deletions src/console/console-config-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export interface ConsoleDiscoveredConfiguration {
readonly name: string;
readonly version: string;
readonly profileCount: number;
/** Non-secret profile names shown before a configuration is selected. */
readonly profileNames?: readonly string[];
readonly defaultProfile: string;
/** Whether the MCP client may switch the active session with `miftah_use_profile`. */
readonly profileSwitchingFromMcp?: boolean;
readonly authentication: ConsoleAuthenticationMetadata;
readonly source: "standard-config-directory";
}
Expand Down Expand Up @@ -85,6 +89,8 @@ export interface ConsoleInitializedConfigMetadata {
readonly profiles: readonly ProfileInventoryEntry[];
readonly upstreams: readonly { readonly name: string; readonly transport: string }[];
readonly oauthConnectionCount: number;
/** Whether the MCP client may switch the active session with `miftah_use_profile`. */
readonly profileSwitchingFromMcp?: boolean;
/** Present for live Console services; optional for embedding compatibility. */
readonly authentication?: ConsoleAuthenticationMetadata;
/** Present only for a no-config dashboard invocation. */
Expand Down Expand Up @@ -195,6 +201,7 @@ export function consoleInitializedConfigMetadata(config: MiftahConfig): ConsoleI
profiles: inventory.profiles,
upstreams: upstreams.map(({ name, transport }) => ({ name, transport })),
oauthConnectionCount: config.version === "3" ? Object.keys(config.oauth?.connections ?? {}).length : 0,
profileSwitchingFromMcp: config.security?.allowProfileSwitchingFromMcp === true,
authentication: consoleAuthenticationMetadata(config),
restartRequiredForExistingClients: true
};
Expand Down
9 changes: 9 additions & 0 deletions tests/config-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,21 @@ vi.mock("../src/cli/windows-config-acl.js", async (importOriginal) => {
...args: Parameters<typeof actual.createWindowsPrivateMigrationDirectory>
): Promise<boolean> => {
if (migrationRace.timeoutDiagnosticActive) migrationRace.timeoutDiagnosticPhase = "transaction-create";
if (process.platform === "win32") {
const [directory] = args;
const { mkdir } = await import("node:fs/promises");
await mkdir(directory);
return true;
}
return actual.createWindowsPrivateMigrationDirectory(...args);
},
copyWindowsConfigSecurityDescriptors: async (
...args: Parameters<typeof actual.copyWindowsConfigSecurityDescriptors>
): Promise<boolean> => {
if (migrationRace.timeoutDiagnosticActive) migrationRace.timeoutDiagnosticPhase = "security-descriptor";
// The dedicated Windows migration ACL suite exercises the real trusted
// helper end to end. This suite owns filesystem transaction semantics.
if (process.platform === "win32") return true;
return actual.copyWindowsConfigSecurityDescriptors(...args);
}
};
Expand Down
57 changes: 55 additions & 2 deletions tests/console-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,11 @@ async function clearProfileReadinessStateWhenConfigurationIsUnselected(javascrip
focus(): void {}

select(): void {}

setAttribute(name: string, value: string): void {
void name;
void value;
}
}

class FakeForm extends FakeElement {
Expand Down Expand Up @@ -1629,6 +1634,38 @@ function observePresetFieldConstraintState(javascript: string): {
}

describe("local Console control server", () => {
it("puts connections and named accounts before collapsed authentication reference", async () => {
const server = await startConsoleServer(await writeConfig(), {
bootstrapCredential: "test-only-bootstrap-credential"
});

try {
const page = await fetch(server.url);
expect(page.status).toBe(200);
const html = await page.text();
const connectionCatalog = html.indexOf('id="configuration-catalog-view"');
const setupWizard = html.indexOf('id="setup-wizard-view"');
const authenticationReference = html.indexOf('id="authentication-guide"');

expect(connectionCatalog).toBeGreaterThan(-1);
expect(setupWizard).toBeGreaterThan(connectionCatalog);
expect(authenticationReference).toBeGreaterThan(setupWizard);
expect(html).toContain("<summary>How authentication works</summary>");
expect(html).toContain("One connection, named accounts");
expect(html).toContain("Default for new connections");
expect(html).toContain("Live account switch");

const script = await fetch(new URL("/app.js", server.url));
expect(script.status).toBe(200);
const javascript = await script.text();
expect(javascript).toContain("configuration.profileNames");
expect(javascript).toContain("configuration.profileSwitchingFromMcp");
expect(javascript).toContain("miftah_use_profile");
} finally {
await server.close();
}
});

it("serves a navigation-safe local dashboard shell without exposing bootstrap credentials", async () => {
const server = await startConsoleServer(await writeConfig(), {
bootstrapCredential: "test-only-bootstrap-credential"
Expand Down Expand Up @@ -3123,7 +3160,8 @@ describe("local Console control server", () => {
name: "gsc",
defaultProfile: "work",
upstream: { transport: "stdio", command: "uvx", args: ["mcp-search-console@0.3.2"] },
profiles: { work: {} }
profiles: { work: {}, personal: {} },
security: { allowProfileSwitchingFromMcp: true }
})}\n`);

const server = await startConsoleServer(join(directory, "miftah.json"), {
Expand All @@ -3144,10 +3182,25 @@ describe("local Console control server", () => {
});
expect(initial.status).toBe(200);
const initialBody = await initial.json() as {
data: { initialized: boolean; catalog?: { configurations: Array<{ id: string; name: string }> } };
data: {
initialized: boolean;
catalog?: {
configurations: Array<{
id: string;
name: string;
profileNames: string[];
profileSwitchingFromMcp: boolean;
}>;
};
};
};
expect(initialBody.data.initialized).toBe(false);
expect(initialBody.data.catalog?.configurations).toHaveLength(1);
expect(initialBody.data.catalog?.configurations[0]).toMatchObject({
name: "gsc",
profileNames: ["personal", "work"],
profileSwitchingFromMcp: true
});
expect(JSON.stringify(initialBody)).not.toContain(directory);
const id = initialBody.data.catalog?.configurations[0]?.id;
if (id === undefined) throw new Error("Expected a discovered configuration id.");
Expand Down
22 changes: 12 additions & 10 deletions tests/release-version.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

const releaseVersion = "0.5.4";
const releaseVersion = "0.5.5";

function readRepositoryFile(path: string): string {
return readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
Expand All @@ -21,15 +21,15 @@ function releaseNotes(changelog: string, version: string): string {
return changelog.slice(match.index, end < 0 ? undefined : end);
}

describe("v0.5.4 release artifacts", () => {
describe("v0.5.5 release artifacts", () => {
it.each([
{
name: "a non-zero-padded date",
changelog: "## [0.5.4] - 2026-7-30\n\n### Changed\n"
changelog: "## [0.5.5] - 2026-7-31\n\n### Changed\n"
},
{
name: "a heading that does not start its line",
changelog: "Release candidate: ## [0.5.4] - 2026-07-30\n\n### Changed\n"
changelog: "Release candidate: ## [0.5.5] - 2026-07-31\n\n### Changed\n"
}
])("rejects $name", ({ changelog }) => {
expect(() => releaseNotes(changelog, releaseVersion)).toThrow(
Expand Down Expand Up @@ -71,7 +71,7 @@ describe("v0.5.4 release artifacts", () => {
}
});

it("documents returning-user setup while retaining the experimental package status", () => {
it("documents the task-first Console while retaining the experimental package status", () => {
const changelog = readRepositoryFile("CHANGELOG.md");
const notes = releaseNotes(changelog, releaseVersion);

Expand All @@ -80,13 +80,15 @@ describe("v0.5.4 release artifacts", () => {
const changedStart = notes.indexOf("### Changed");
const changedEnd = notes.indexOf("\n### ", changedStart + "### Changed".length);
const changedNotes = notes.slice(changedStart, changedEnd < 0 ? undefined : changedEnd);
for (const issue of [204, 314]) {
for (const issue of [202, 319]) {
expect(changedNotes).toContain(`[#${issue}](https://github.com/mohanagy/miftah/issues/${issue})`);
}
expect(notes).toMatch(/one setup path at a time/iu);
expect(notes).toMatch(/Back and Cancel provide no-write recovery/iu);
expect(notes).toMatch(/without creating or changing a configuration/iu);
expect(notes).toMatch(/external validation remains incomplete/iu);
expect(notes).toMatch(/task-first/iu);
expect(notes).toMatch(
/named account profiles, durable default, and whether live in-session switching through `miftah_use_profile` is available/iu
);
expect(notes).toContain("collapsed behind **How authentication works**");
expect(notes).toContain("external validation remains incomplete under #25, #88, and #202");

const readme = readRepositoryFile("README.md");
const featureGuide = readRepositoryFile("docs/whats-new-in-0.5.md");
Expand Down
11 changes: 11 additions & 0 deletions tests/tooling-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,15 @@ describe("repository tooling contracts", () => {

expect(windowsVerifier.slice(0, functionIndex)).toMatch(jsdocAdjacencyPattern);
});

it("serializes complete core test files on Windows so ACL helpers cannot exhaust one another", () => {
const workflow = readRepositoryFile(".github/workflows/ci.yml");

expect(workflow).toMatch(
/- name: Test core contracts \(Windows, serialized files\)\s+if: \$\{\{ runner\.os == 'Windows' \}\}\s+run: npm run test:core -- --no-file-parallelism[ \t]*(?:\r?\n|$)/u
);
expect(workflow).toMatch(
/- name: Test core contracts\s+if: \$\{\{ runner\.os != 'Windows' \}\}\s+run: npm run test:core[ \t]*(?:\r?\n|$)/u
);
});
});