feat(google-meet): format setup status by default

This commit is contained in:
Peter Steinberger
2026-04-24 20:52:34 +01:00
parent 02112803b5
commit 0e23107ffb
3 changed files with 94 additions and 2 deletions

View File

@@ -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:

View File

@@ -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" });

View File

@@ -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