mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
feat(google-meet): format setup status by default
This commit is contained in:
@@ -75,6 +75,7 @@ The setup output is meant to be agent-readable. It reports Chrome profile,
|
||||
audio bridge, node pinning, delayed realtime intro, and, when Twilio delegation
|
||||
is configured, whether the `voice-call` plugin and Twilio credentials are ready.
|
||||
Treat any `ok: false` check as a blocker before asking an agent to join.
|
||||
Use `openclaw googlemeet setup --json` for scripts or machine-readable output.
|
||||
|
||||
Join a meeting:
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { PassThrough, Writable } from "node:stream";
|
||||
import { Command } from "commander";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { RealtimeVoiceProviderPlugin } from "openclaw/plugin-sdk/realtime-voice";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.ts";
|
||||
import plugin from "./index.js";
|
||||
import { registerGoogleMeetCli } from "./src/cli.js";
|
||||
import { resolveGoogleMeetConfig, resolveGoogleMeetConfigWithEnv } from "./src/config.js";
|
||||
import {
|
||||
buildGoogleMeetPreflightReport,
|
||||
@@ -19,6 +21,7 @@ import {
|
||||
import { startNodeRealtimeAudioBridge } from "./src/realtime-node.js";
|
||||
import { startCommandRealtimeAudioBridge } from "./src/realtime.js";
|
||||
import { normalizeMeetUrl } from "./src/runtime.js";
|
||||
import type { GoogleMeetRuntime } from "./src/runtime.js";
|
||||
import { buildMeetDtmfSequence, normalizeDialInNumber } from "./src/transports/twilio.js";
|
||||
|
||||
const voiceCallMocks = vi.hoisted(() => ({
|
||||
@@ -57,6 +60,18 @@ const noopLogger = {
|
||||
debug: vi.fn(),
|
||||
};
|
||||
|
||||
function captureStdout() {
|
||||
let output = "";
|
||||
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((chunk: unknown) => {
|
||||
output += String(chunk);
|
||||
return true;
|
||||
}) as typeof process.stdout.write);
|
||||
return {
|
||||
output: () => output,
|
||||
restore: () => writeSpy.mockRestore(),
|
||||
};
|
||||
}
|
||||
|
||||
type TestBridgeProcess = {
|
||||
stdin?: { write(chunk: unknown): unknown } | null;
|
||||
stdout?: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null;
|
||||
@@ -627,6 +642,65 @@ describe("google-meet plugin", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("CLI setup prints human-readable checks by default", async () => {
|
||||
const program = new Command();
|
||||
const stdout = captureStdout();
|
||||
registerGoogleMeetCli({
|
||||
program,
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
ensureRuntime: async () =>
|
||||
({
|
||||
setupStatus: () => ({
|
||||
ok: true,
|
||||
checks: [
|
||||
{
|
||||
id: "audio-bridge",
|
||||
ok: true,
|
||||
message: "Chrome command-pair realtime audio bridge configured",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}) as GoogleMeetRuntime,
|
||||
});
|
||||
|
||||
try {
|
||||
await program.parseAsync(["googlemeet", "setup"], { from: "user" });
|
||||
expect(stdout.output()).toContain("Google Meet setup: OK");
|
||||
expect(stdout.output()).toContain(
|
||||
"[ok] audio-bridge: Chrome command-pair realtime audio bridge configured",
|
||||
);
|
||||
expect(stdout.output()).not.toContain('"checks"');
|
||||
} finally {
|
||||
stdout.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("CLI setup preserves JSON output with --json", async () => {
|
||||
const program = new Command();
|
||||
const stdout = captureStdout();
|
||||
registerGoogleMeetCli({
|
||||
program,
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
ensureRuntime: async () =>
|
||||
({
|
||||
setupStatus: () => ({
|
||||
ok: false,
|
||||
checks: [{ id: "twilio-voice-call-plugin", ok: false, message: "missing" }],
|
||||
}),
|
||||
}) as GoogleMeetRuntime,
|
||||
});
|
||||
|
||||
try {
|
||||
await program.parseAsync(["googlemeet", "setup", "--json"], { from: "user" });
|
||||
expect(JSON.parse(stdout.output())).toMatchObject({
|
||||
ok: false,
|
||||
checks: [{ id: "twilio-voice-call-plugin", ok: false }],
|
||||
});
|
||||
} finally {
|
||||
stdout.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("launches Chrome after the BlackHole check", async () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", { value: "darwin" });
|
||||
|
||||
@@ -40,6 +40,10 @@ type ResolveSpaceOptions = {
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
type SetupOptions = {
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
function writeStdoutJson(value: unknown): void {
|
||||
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
@@ -71,6 +75,13 @@ function parseOptionalNumber(value: string | undefined): number | undefined {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function writeSetupStatus(status: ReturnType<GoogleMeetRuntime["setupStatus"]>): void {
|
||||
writeStdoutLine("Google Meet setup: %s", status.ok ? "OK" : "needs attention");
|
||||
for (const check of status.checks) {
|
||||
writeStdoutLine("[%s] %s: %s", check.ok ? "ok" : "fail", check.id, check.message);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveMeetingInput(config: GoogleMeetConfig, value?: string): string {
|
||||
const meeting = value?.trim() || config.defaults.meeting;
|
||||
if (!meeting) {
|
||||
@@ -319,9 +330,15 @@ export function registerGoogleMeetCli(params: {
|
||||
root
|
||||
.command("setup")
|
||||
.description("Show Google Meet transport setup status")
|
||||
.action(async () => {
|
||||
.option("--json", "Print JSON output", false)
|
||||
.action(async (options: SetupOptions) => {
|
||||
const rt = await params.ensureRuntime();
|
||||
writeStdoutJson(rt.setupStatus());
|
||||
const status = rt.setupStatus();
|
||||
if (options.json) {
|
||||
writeStdoutJson(status);
|
||||
return;
|
||||
}
|
||||
writeSetupStatus(status);
|
||||
});
|
||||
|
||||
root
|
||||
|
||||
Reference in New Issue
Block a user