merge M4 into implement/2026-05-18-stef-openclaw-skills
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import type { ClientName } from "./types.js";
|
||||||
|
|
||||||
|
export interface DispatchConfig {
|
||||||
|
defaultClient?: ClientName;
|
||||||
|
client?: ClientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLIENT_NAMES: ClientName[] = ["codex", "claude", "opencode"];
|
||||||
|
|
||||||
|
export function resolveClient(
|
||||||
|
prompt: string,
|
||||||
|
config?: DispatchConfig
|
||||||
|
): ClientName | null {
|
||||||
|
// Explicit --client flag takes highest precedence
|
||||||
|
if (config?.client && CLIENT_NAMES.includes(config.client)) {
|
||||||
|
return config.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = prompt.toLowerCase();
|
||||||
|
|
||||||
|
// Check for "open code" before "opencode" to handle the spaced variant
|
||||||
|
if (lower.includes("open code")) {
|
||||||
|
return "opencode";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.includes("claude")) {
|
||||||
|
return "claude";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.includes("codex")) {
|
||||||
|
return "codex";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.includes("opencode")) {
|
||||||
|
return "opencode";
|
||||||
|
}
|
||||||
|
|
||||||
|
return config?.defaultClient ?? null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { describe, it } from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { resolveClient } from "../src/dispatch.js";
|
||||||
|
import type { ClientName } from "../src/types.js";
|
||||||
|
|
||||||
|
describe("resolveClient", () => {
|
||||||
|
it('returns "codex" when prompt contains "use codex"', () => {
|
||||||
|
const result = resolveClient("use codex to refactor this");
|
||||||
|
assert.strictEqual(result, "codex");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns "claude" when prompt contains "tell claude to..."', () => {
|
||||||
|
const result = resolveClient("tell claude to review my code");
|
||||||
|
assert.strictEqual(result, "claude");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns "opencode" when prompt contains "run with opencode"', () => {
|
||||||
|
const result = resolveClient("run with opencode");
|
||||||
|
assert.strictEqual(result, "opencode");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns "opencode" when prompt contains "open code"', () => {
|
||||||
|
const result = resolveClient("open code this file");
|
||||||
|
assert.strictEqual(result, "opencode");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when no keyword matches and no default is configured", () => {
|
||||||
|
const result = resolveClient("hello world");
|
||||||
|
assert.strictEqual(result, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns defaultClient when no keyword matches", () => {
|
||||||
|
const result = resolveClient("hello world", { defaultClient: "claude" });
|
||||||
|
assert.strictEqual(result, "claude");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers --client flag over keyword parsing", () => {
|
||||||
|
const result = resolveClient("use codex for this", { client: "claude" });
|
||||||
|
assert.strictEqual(result, "claude");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers --client flag over defaultClient", () => {
|
||||||
|
const result = resolveClient("hello world", {
|
||||||
|
client: "opencode",
|
||||||
|
defaultClient: "codex",
|
||||||
|
});
|
||||||
|
assert.strictEqual(result, "opencode");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles uppercase CODEX", () => {
|
||||||
|
const result = resolveClient("Use CODEX please");
|
||||||
|
assert.strictEqual(result, "codex");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles mixed-case Claude", () => {
|
||||||
|
const result = resolveClient("Tell Claude to fix this");
|
||||||
|
assert.strictEqual(result, "claude");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns first match when multiple clients are mentioned", () => {
|
||||||
|
const result = resolveClient("ask claude or codex to help");
|
||||||
|
assert.strictEqual(result, "claude");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for empty prompt with no default", () => {
|
||||||
|
const result = resolveClient("");
|
||||||
|
assert.strictEqual(result, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns defaultClient for empty prompt", () => {
|
||||||
|
const result = resolveClient("", { defaultClient: "codex" });
|
||||||
|
assert.strictEqual(result, "codex");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates --client flag value", () => {
|
||||||
|
// Invalid client flag should fall back to keyword/default behavior
|
||||||
|
const result = resolveClient("use codex", {
|
||||||
|
client: "invalid" as ClientName,
|
||||||
|
});
|
||||||
|
// If client flag is invalid, we should fall back to keyword matching
|
||||||
|
assert.strictEqual(result, "codex");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user