@openclaw/google-meet 2026.10.10 → 2026.11.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/assets/activity.svg +1 -1
  2. package/dist/.setup/{chrome-create-DIcW4QCh.mjs → chrome-create-BlDlV2YP.mjs} +11 -30
  3. package/dist/.setup/{cli-9linTFHb.mjs → cli-C-MrY7IZ.mjs} +275 -309
  4. package/dist/.setup/{cli-export-CaDYoSP8.mjs → cli-export-6eiywS3T.mjs} +16 -35
  5. package/dist/.setup/{cli-output-mode-BJSm88N9.mjs → cli-output-mode-DztqqVeZ.mjs} +5 -6
  6. package/dist/.setup/config-CMTw0Anh.mjs +110 -0
  7. package/dist/.setup/{create-D3fHR8uX.mjs → create-DMk4zbMq.mjs} +23 -9
  8. package/dist/.setup/{google-meet-platform-adapter-D7poqL1J.mjs → google-meet-platform-adapter-DgchqNLV.mjs} +52 -18
  9. package/dist/.setup/{node-host-B196lvNZ.mjs → node-host-BIZD9InM.mjs} +2 -2
  10. package/dist/.setup/{node-invoke-policy-BzRAe25r.mjs → node-invoke-policy-bg7p_BWB.mjs} +1 -1
  11. package/dist/.setup/oauth-DZkuGMs1.mjs +1 -0
  12. package/dist/.setup/{plugin-helpers-Buhixxsx.mjs → plugin-helpers-ChCBuyAl.mjs} +363 -69
  13. package/dist/.setup/{plugin-registration-B_nOxAym.mjs → plugin-registration-CAW7_zO_.mjs} +8 -8
  14. package/dist/.setup/{runtime-D-dwEsSW.mjs → runtime-Cyc6bv1i.mjs} +37 -38
  15. package/dist/.source-module-urls/c06239f68b6f4959b5d3db7e0cee004911d8eb43092f4135c729b507e37f6333.json +1 -1
  16. package/dist/cli-metadata.js +1 -1
  17. package/dist/doctor-contract-api.js +25 -21
  18. package/dist/index.js +14 -13
  19. package/node_modules/typebox/build/format/duration.d.mts +2 -2
  20. package/node_modules/typebox/build/format/duration.mjs +2 -2
  21. package/node_modules/typebox/build/format/idn_email.d.mts +1 -0
  22. package/node_modules/typebox/build/format/idn_email.mjs +1 -0
  23. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
  24. package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
  25. package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
  26. package/node_modules/typebox/build/format/ipv4.mjs +2 -2
  27. package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
  28. package/node_modules/typebox/build/format/ipv6.mjs +1 -1
  29. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  30. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  31. package/node_modules/typebox/build/schema/build.mjs +1 -0
  32. package/node_modules/typebox/build/schema/engine/_unique.d.mts +4 -2
  33. package/node_modules/typebox/build/schema/engine/_unique.mjs +7 -3
  34. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
  35. package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +0 -3
  36. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +15 -17
  37. package/node_modules/typebox/build/schema/engine/contains.mjs +3 -3
  38. package/node_modules/typebox/build/schema/engine/index.d.mts +1 -0
  39. package/node_modules/typebox/build/schema/engine/index.mjs +1 -0
  40. package/node_modules/typebox/build/schema/engine/items.mjs +7 -4
  41. package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -2
  42. package/node_modules/typebox/build/schema/engine/minContains.mjs +3 -3
  43. package/node_modules/typebox/build/schema/engine/oneOf.mjs +2 -2
  44. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +2 -2
  45. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -2
  46. package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +2 -2
  47. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +2 -2
  48. package/node_modules/typebox/build/system/hashing/hash.mjs +63 -34
  49. package/node_modules/typebox/build/system/locale/en_US.mjs +2 -1
  50. package/node_modules/typebox/build/system/memory/assign.d.mts +2 -8
  51. package/node_modules/typebox/build/system/memory/assign.mjs +13 -6
  52. package/node_modules/typebox/build/system/memory/clone.d.mts +4 -5
  53. package/node_modules/typebox/build/system/memory/clone.mjs +8 -96
  54. package/node_modules/typebox/build/system/memory/create.d.mts +1 -5
  55. package/node_modules/typebox/build/system/memory/create.mjs +18 -13
  56. package/node_modules/typebox/build/system/memory/deep.d.mts +2 -0
  57. package/node_modules/typebox/build/system/memory/deep.mjs +108 -0
  58. package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
  59. package/node_modules/typebox/build/system/memory/discard.mjs +9 -8
  60. package/node_modules/typebox/build/system/memory/finalize.d.mts +4 -0
  61. package/node_modules/typebox/build/system/memory/finalize.mjs +30 -0
  62. package/node_modules/typebox/build/system/memory/keys.d.mts +2 -0
  63. package/node_modules/typebox/build/system/memory/keys.mjs +8 -0
  64. package/node_modules/typebox/build/system/memory/update.d.mts +1 -4
  65. package/node_modules/typebox/build/system/memory/update.mjs +43 -16
  66. package/node_modules/typebox/build/type/engine/call/index.d.mts +1 -0
  67. package/node_modules/typebox/build/type/engine/call/index.mjs +1 -0
  68. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +2 -24
  69. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +2 -29
  70. package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +1 -0
  71. package/node_modules/typebox/build/type/engine/evaluate/index.mjs +1 -0
  72. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +5 -1
  73. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +6 -1
  74. package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +30 -0
  75. package/node_modules/typebox/build/type/engine/evaluate/property.mjs +29 -0
  76. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +1 -3
  77. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +1 -4
  78. package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
  79. package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
  80. package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
  81. package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
  82. package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +1 -3
  83. package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +1 -4
  84. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +1 -3
  85. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +1 -4
  86. package/node_modules/typebox/build/type/engine/this/index.d.mts +1 -0
  87. package/node_modules/typebox/build/type/engine/this/index.mjs +1 -0
  88. package/node_modules/typebox/build/value/clone/clone.d.mts +4 -3
  89. package/node_modules/typebox/build/value/clone/clone.mjs +5 -7
  90. package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +2 -0
  91. package/node_modules/typebox/build/value/repair/from_cyclic.mjs +6 -0
  92. package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +25 -14
  93. package/node_modules/typebox/package.json +1 -1
  94. package/node_modules/typebox/readme.md +1 -1
  95. package/package.json +5 -5
  96. package/dist/.setup/config-Cu3zsl_3.mjs +0 -239
  97. package/dist/.setup/oauth-DGv28ZgA.mjs +0 -158
  98. package/node_modules/typebox/build/system/memory/freeze.d.mts +0 -4
  99. package/node_modules/typebox/build/system/memory/freeze.mjs +0 -6
@@ -1,8 +1,7 @@
1
- import { a as resolveArtifactQueryFromParams, c as resolveSpaceFromParams, d as endGoogleMeetActiveConference, f as fetchGoogleMeetSpace, h as listGoogleMeetCalendarEvents, l as buildGoogleMeetPreflightReport, m as buildGoogleMeetCalendarDayWindow, n as fetchResolvedGoogleMeetArtifacts, o as resolveGoogleMeetTokenFromParams, p as fetchLatestGoogleMeetConferenceRecord, r as fetchResolvedGoogleMeetAttendance, s as resolveMeetingFromParams, t as buildGoogleMeetExportRequest, u as createGoogleMeetSpace } from "./plugin-helpers-Buhixxsx.mjs";
2
- import { i as resolveGoogleMeetGatewayOperationTimeoutMs } from "./config-Cu3zsl_3.mjs";
3
- import { C as writeStdoutJson, S as writeSetupStatus, _ as resolveGoogleMeetOAuthCallbackTimeoutMs, a as renderAttendanceCsv, b as writeLeaveResult, c as callGoogleMeetGateway, d as parseGoogleMeetTransport, f as parseOptionalNumber, g as resolveCliJoinRequest, h as promptInput, i as renderAttendance, l as parseGoogleMeetBrowserTransport, m as parsePositiveNumber, n as exportGoogleMeetBundle, o as writeCalendarEventsSummary, p as parsePositiveIntegerOption, r as renderArtifacts, s as writeLatestConferenceRecordSummary, u as parseGoogleMeetMode, v as writeCliOutput, w as writeStdoutLine, x as writeRecoverCurrentTabResult, y as writeDoctorStatus } from "./cli-export-CaDYoSP8.mjs";
4
- import { buildGoogleMeetAuthUrl, exchangeGoogleMeetAuthCode, resolveGoogleMeetAccessToken, waitForGoogleMeetAuthCode } from "./oauth-DGv28ZgA.mjs";
5
- import { i as hasGoogleMeetOAuth, r as hasCreateSpaceConfigInput, t as createSpaceFromParams } from "./create-D3fHR8uX.mjs";
1
+ import { i as hasGoogleMeetOAuth, r as hasCreateSpaceConfigInput, t as createSpaceFromParams } from "./create-DMk4zbMq.mjs";
2
+ import { i as resolveGoogleMeetGatewayOperationTimeoutMs } from "./config-CMTw0Anh.mjs";
3
+ import { _ as writeStdoutLine, a as parseOptionalNumber, c as promptInput, d as writeCliOutput, exportGoogleMeetBundle, f as writeDoctorStatus, g as writeStdoutJson, h as writeSetupStatus, i as parseGoogleMeetTransport, l as resolveCliJoinRequest, m as writeRecoverCurrentTabResult, n as parseGoogleMeetBrowserTransport, o as parsePositiveIntegerOption, p as writeLeaveResult, r as parseGoogleMeetMode, renderArtifacts, renderAttendance, renderAttendanceCsv, s as parsePositiveNumber, t as callGoogleMeetGateway, u as resolveGoogleMeetOAuthCallbackTimeoutMs, writeCalendarEventsSummary, writeLatestConferenceRecordSummary } from "./cli-export-6eiywS3T.mjs";
4
+ import { buildGoogleMeetCalendarDayWindow, buildGoogleMeetExportRequest, buildGoogleMeetPreflightReport, c as fetchGoogleMeetSpace, endGoogleMeetActiveConference, fetchLatestGoogleMeetConferenceRecord, fetchResolvedGoogleMeetArtifacts, fetchResolvedGoogleMeetAttendance, listGoogleMeetCalendarEvents, n as exchangeGoogleMeetAuthCode, o as waitForGoogleMeetAuthCode, resolveArtifactQueryFromParams, resolveGoogleMeetTokenFromParams, resolveMeetingFromParams, resolveSpaceFromParams, s as createGoogleMeetSpace, t as buildGoogleMeetAuthUrl } from "./plugin-helpers-ChCBuyAl.mjs";
6
5
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
7
6
  import { generateHexPkceVerifierChallenge } from "openclaw/plugin-sdk/provider-auth";
8
7
  import { generateOAuthState } from "openclaw/plugin-sdk/provider-auth-runtime";
@@ -40,292 +39,6 @@ async function callGoogleMeetRuntime(context, method, payload, local, timeoutMs)
40
39
  });
41
40
  return delegated.ok ? delegated.payload : local(await context.ensureRuntime());
42
41
  }
43
- // extensions/google-meet/src/cli-artifact-commands.ts
44
- async function resolveCliArtifactQuery(context, options) {
45
- const meeting = options.meeting?.trim() || context.config.defaults.meeting;
46
- const conferenceRecord = options.conferenceRecord?.trim();
47
- if (!meeting && !conferenceRecord && !(options.today || options.event?.trim())) throw new Error("Meeting input or conference record is required. Pass --meeting, --today, --event, --conference-record, or configure defaults.meeting.");
48
- const { lateAfterMinutes: late, earlyBeforeMinutes: early, ...queryOptions } = options;
49
- const raw = {
50
- ...resolveCliParams(queryOptions),
51
- meeting,
52
- conferenceRecord,
53
- pageSize: parsePositiveIntegerOption(options.pageSize, "page-size"),
54
- includeTranscriptEntries: options.transcriptEntries,
55
- includeAllConferenceRecords: options.allConferenceRecords,
56
- includeDocumentBodies: options.includeDocBodies,
57
- mergeDuplicateParticipants: options.mergeDuplicates
58
- };
59
- const lateAfterMinutes = parseOptionalNumber(late);
60
- const earlyBeforeMinutes = parseOptionalNumber(early);
61
- return {
62
- ...await resolveArtifactQueryFromParams(context.config, raw),
63
- lateAfterMinutes,
64
- earlyBeforeMinutes
65
- };
66
- }
67
- function resolveTokenSource(refreshed) {
68
- return refreshed ? "refresh-token" : "cached-access-token";
69
- }
70
- async function writeArtifactOutput(options, result, refreshed, render, csv) {
71
- const tokenSource = resolveTokenSource(refreshed);
72
- let text;
73
- if (options.json) text = JSON.stringify({
74
- ...result,
75
- tokenSource
76
- }, null, 2);
77
- else if (options.format === "markdown") text = render(result, "markdown");
78
- else if (options.format === "csv" && csv) text = csv(result);
79
- else if (!options.format || options.format === "summary") text = `${render(result, "summary")}token source: ${tokenSource}\n`;
80
- else throw new Error(csv ? "Unsupported format. Expected summary, markdown, or csv." : "Unsupported format. Expected summary or markdown.");
81
- await writeCliOutput(options, text);
82
- }
83
- function registerGoogleMeetArtifactCommands(context) {
84
- const { root } = context;
85
- addGoogleMeetArtifactOptions(root.command("artifacts").description("List Meet conference records and available participant/artifact metadata")).option("--no-transcript-entries", "Skip structured transcript entry lookup").option("--include-doc-bodies", "Export linked transcript and smart-note Google Docs text").option("--format <format>", "Output format: summary or markdown", "summary").option("--output <path>", "Write output to a file instead of stdout").option("--json", "Print JSON output", false).action(async (options) => {
86
- const resolved = await resolveCliArtifactQuery(context, options);
87
- await writeArtifactOutput(options, await fetchResolvedGoogleMeetArtifacts(resolved), resolved.token.refreshed, renderArtifacts);
88
- });
89
- addGoogleMeetArtifactOptions(root.command("attendance").description("List Meet participants and participant sessions")).option("--no-merge-duplicates", "Keep duplicate participant resources as separate rows").option("--late-after-minutes <n>", "Mark participants late after this many minutes", "5").option("--early-before-minutes <n>", "Mark early leavers before this many minutes", "5").option("--format <format>", "Output format: summary, markdown, or csv", "summary").option("--output <path>", "Write output to a file instead of stdout").option("--json", "Print JSON output", false).action(async (options) => {
90
- const resolved = await resolveCliArtifactQuery(context, options);
91
- await writeArtifactOutput(options, await fetchResolvedGoogleMeetAttendance(resolved), resolved.token.refreshed, renderAttendance, renderAttendanceCsv);
92
- });
93
- addGoogleMeetArtifactOptions(root.command("export").description("Write Meet artifacts, attendance, transcript, and raw JSON into a folder")).option("--no-transcript-entries", "Skip structured transcript entry lookup").option("--include-doc-bodies", "Export linked transcript and smart-note Google Docs text").option("--no-merge-duplicates", "Keep duplicate participant resources as separate rows").option("--late-after-minutes <n>", "Mark participants late after this many minutes", "5").option("--early-before-minutes <n>", "Mark early leavers before this many minutes", "5").option("--output <dir>", "Output directory").option("--zip", "Also write a portable .zip archive").option("--dry-run", "Fetch export data and print the manifest without writing files", false).option("--json", "Print JSON output", false).action(async (options) => {
94
- const resolved = await resolveCliArtifactQuery(context, options);
95
- const artifacts = await fetchResolvedGoogleMeetArtifacts(resolved);
96
- const attendance = await fetchResolvedGoogleMeetAttendance(resolved);
97
- const payload = await exportGoogleMeetBundle({
98
- outputDir: options.output,
99
- artifacts,
100
- attendance,
101
- zip: Boolean(options.zip),
102
- dryRun: options.dryRun,
103
- request: buildGoogleMeetExportRequest(resolved, options.calendar),
104
- tokenSource: resolveTokenSource(resolved.token.refreshed),
105
- calendarEvent: resolved.calendarEvent
106
- });
107
- if (options.json || "dryRun" in payload) {
108
- writeStdoutJson(payload);
109
- return;
110
- }
111
- writeStdoutLine("export: %s", payload.outputDir);
112
- for (const file of payload.files) writeStdoutLine("- %s", file);
113
- if (payload.zipFile) writeStdoutLine("zip: %s", payload.zipFile);
114
- });
115
- }
116
- // extensions/google-meet/src/cli-doctor.ts
117
- function sanitizeOAuthErrorMessage(error) {
118
- return (error instanceof Error ? error.message : String(error)).replace(/(access_token["'=:\s]+)[^"',\s&]+/gi, "$1[redacted]").replace(/(refresh_token["'=:\s]+)[^"',\s&]+/gi, "$1[redacted]").replace(/(client_secret["'=:\s]+)[^"',\s&]+/gi, "$1[redacted]");
119
- }
120
- async function buildOAuthDoctorReport(config, options) {
121
- const clientId = options.clientId?.trim() || config.oauth.clientId;
122
- const clientSecret = options.clientSecret?.trim() || config.oauth.clientSecret;
123
- const refreshToken = options.refreshToken?.trim() || config.oauth.refreshToken;
124
- const accessToken = options.accessToken?.trim() || config.oauth.accessToken;
125
- const expiresAt = parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt;
126
- const checks = [];
127
- const hasRefreshConfig = Boolean(clientId && refreshToken);
128
- if (!hasRefreshConfig && !Boolean(accessToken)) {
129
- checks.push({
130
- id: "oauth-config",
131
- ok: false,
132
- message: "Missing Google Meet OAuth credentials. Configure oauth.clientId and oauth.refreshToken, or pass --client-id and --refresh-token."
133
- });
134
- return {
135
- ok: false,
136
- configured: false,
137
- checks
138
- };
139
- }
140
- checks.push({
141
- id: "oauth-config",
142
- ok: true,
143
- message: hasRefreshConfig ? "Google Meet OAuth refresh credentials are configured" : "Google Meet cached access token is configured"
144
- });
145
- let token;
146
- try {
147
- token = await resolveGoogleMeetAccessToken({
148
- clientId,
149
- clientSecret,
150
- refreshToken,
151
- accessToken,
152
- expiresAt
153
- });
154
- checks.push({
155
- id: "oauth-token",
156
- ok: true,
157
- message: token.refreshed ? "Refresh token minted an access token" : "Cached access token is still valid"
158
- });
159
- } catch (error) {
160
- checks.push({
161
- id: "oauth-token",
162
- ok: false,
163
- message: sanitizeOAuthErrorMessage(error)
164
- });
165
- return {
166
- ok: false,
167
- configured: true,
168
- checks
169
- };
170
- }
171
- const report = {
172
- ok: true,
173
- configured: true,
174
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
175
- expiresAt: token.expiresAt,
176
- checks
177
- };
178
- const meeting = options.meeting?.trim();
179
- if (meeting) try {
180
- const space = await fetchGoogleMeetSpace({
181
- accessToken: token.accessToken,
182
- meeting
183
- });
184
- checks.push({
185
- id: "meet-spaces-get",
186
- ok: true,
187
- message: `Resolved ${space.name}`
188
- });
189
- report.meetingUri = space.meetingUri;
190
- } catch (error) {
191
- checks.push({
192
- id: "meet-spaces-get",
193
- ok: false,
194
- message: sanitizeOAuthErrorMessage(error)
195
- });
196
- }
197
- if (options.createSpace) try {
198
- const created = await createGoogleMeetSpace({ accessToken: token.accessToken });
199
- checks.push({
200
- id: "meet-spaces-create",
201
- ok: true,
202
- message: `Created ${created.space.name}`
203
- });
204
- report.createdSpace = created.space.name;
205
- report.meetingUri = created.meetingUri;
206
- } catch (error) {
207
- checks.push({
208
- id: "meet-spaces-create",
209
- ok: false,
210
- message: sanitizeOAuthErrorMessage(error)
211
- });
212
- }
213
- report.ok = checks.every((check) => check.ok);
214
- return report;
215
- }
216
- function writeOAuthDoctorReport(report) {
217
- writeStdoutLine("Google Meet OAuth: %s", report.ok ? "OK" : "needs attention");
218
- writeStdoutLine("configured: %s", report.configured ? "yes" : "no");
219
- if (report.tokenSource) writeStdoutLine("token source: %s", report.tokenSource);
220
- if (report.meetingUri) writeStdoutLine("meeting uri: %s", report.meetingUri);
221
- for (const check of report.checks) writeStdoutLine("[%s] %s: %s", check.ok ? "ok" : "fail", check.id, check.message);
222
- }
223
- function registerGoogleMeetDoctorCommand(context) {
224
- const { root } = context;
225
- addGoogleMeetOAuthOptions(root.command("doctor").description("Show human-readable Meet session/browser/realtime health").argument("[session-id]", "Meet session ID").option("--oauth", "Verify Google Meet OAuth token refresh without printing secrets", false).option("--meeting <value>", "Also verify spaces.get for a Meet URL, code, or spaces/{id}").option("--create-space", "Also verify spaces.create by creating a throwaway Meet space", false)).option("--json", "Print JSON output", false).action(async (sessionId, options) => {
226
- if (options.oauth) {
227
- const report = await buildOAuthDoctorReport(context.config, options);
228
- if (options.json) {
229
- writeStdoutJson(report);
230
- return;
231
- }
232
- writeOAuthDoctorReport(report);
233
- return;
234
- }
235
- const status = await callGoogleMeetRuntime(context, "googlemeet.status", { sessionId }, (rt) => rt.status(sessionId));
236
- if (options.json) {
237
- writeStdoutJson(status);
238
- return;
239
- }
240
- writeDoctorStatus(status);
241
- });
242
- }
243
- // extensions/google-meet/src/cli-runtime-commands.ts
244
- function registerGoogleMeetProbeCommands(context) {
245
- const { root, operationTimeoutMs } = context;
246
- root.command("join").argument("[url]", "Explicit https://meet.google.com/... URL").option("--transport <transport>", "Transport: chrome, chrome-node, or twilio").option("--mode <mode>", "Mode: agent, bidi, or transcribe").option("--message <text>", "Realtime speech to trigger after join").option("--dial-in-number <phone>", "Meet dial-in number for Twilio transport").option("--pin <pin>", "Meet phone PIN; # is appended if omitted").option("--dtmf-sequence <sequence>", "Explicit Twilio DTMF sequence").action(async (url, options) => {
247
- const payload = resolveCliJoinRequest(resolveCliMeetingInput(context.config, url), options);
248
- const result = await callGoogleMeetRuntime(context, "googlemeet.join", payload, (rt) => rt.join(payload), operationTimeoutMs);
249
- writeStdoutJson(result.session ?? result);
250
- });
251
- root.command("test-speech").argument("[url]", "Explicit https://meet.google.com/... URL").option("--transport <transport>", "Transport: chrome, chrome-node, or twilio").option("--mode <mode>", "Mode: agent, bidi, or transcribe").option("--message <text>", "Realtime speech to trigger", "Say exactly: Google Meet speech test complete.").action(async (url, options) => {
252
- const payload = {
253
- url: resolveCliMeetingInput(context.config, url),
254
- transport: parseGoogleMeetTransport(options.transport),
255
- mode: parseGoogleMeetMode(options.mode),
256
- message: options.message
257
- };
258
- writeStdoutJson(await callGoogleMeetRuntime(context, "googlemeet.testSpeech", payload, (rt) => rt.testSpeech(payload), operationTimeoutMs));
259
- });
260
- root.command("test-listen").argument("[url]", "Explicit https://meet.google.com/... URL").option("--transport <transport>", "Transport: chrome or chrome-node").option("--timeout-ms <ms>", "How long to wait for fresh captions/transcript movement").action(async (url, options) => {
261
- const payload = {
262
- url: resolveCliMeetingInput(context.config, url),
263
- transport: parseGoogleMeetBrowserTransport(options.transport),
264
- timeoutMs: parsePositiveNumber(options.timeoutMs, "timeout-ms")
265
- };
266
- writeStdoutJson(await callGoogleMeetRuntime(context, "googlemeet.testListen", payload, (rt) => rt.testListen(payload), operationTimeoutMs));
267
- });
268
- }
269
- function registerGoogleMeetSessionCommands(context) {
270
- const { root } = context;
271
- root.command("status").argument("[session-id]", "Meet session ID").option("--json", "Print JSON output", false).action(async (sessionId) => {
272
- writeStdoutJson(await callGoogleMeetRuntime(context, "googlemeet.status", { sessionId }, (rt) => rt.status(sessionId)));
273
- });
274
- root.command("transcript").description("Print the bounded caption transcript for a Meet session").argument("<session-id>", "Meet session ID").option("--since <index>", "Resume from the previous response's nextIndex").option("--json", "Print JSON output", false).action(async (sessionId, options) => {
275
- const sinceIndex = parseStrictNonNegativeInteger(options.since);
276
- if (options.since !== void 0 && sinceIndex === void 0) throw new Error("--since must be a non-negative safe integer");
277
- const result = await callGoogleMeetRuntime(context, "googlemeet.transcript", {
278
- sessionId,
279
- ...sinceIndex === void 0 ? {} : { sinceIndex }
280
- }, (rt) => rt.transcript(sessionId, sinceIndex === void 0 ? {} : { sinceIndex }));
281
- if (!result.found) throw new Error("session not found");
282
- if (options.json) {
283
- writeStdoutJson(result);
284
- return;
285
- }
286
- if (result.evicted) writeStdoutLine("# transcript evicted from runtime memory");
287
- else if (result.droppedLines) writeStdoutLine("# %d earlier lines dropped by the transcript cap", result.droppedLines);
288
- for (const line of result.lines ?? []) writeStdoutLine("%s%s%s", line.at ? `[${line.at}] ` : "", line.speaker ? `${line.speaker}: ` : "", line.text);
289
- writeStdoutLine("# nextIndex: %d", result.nextIndex ?? 0);
290
- });
291
- }
292
- function registerGoogleMeetLifecycleCommands(context) {
293
- const { root } = context;
294
- root.command("recover-tab").description("Focus and inspect an existing Google Meet tab").argument("[url]", "Optional Meet URL to match").option("--transport <transport>", "Transport to inspect: chrome or chrome-node").option("--json", "Print JSON output", false).action(async (url, options) => {
295
- const result = await (await context.ensureRuntime()).recoverCurrentTab({
296
- url,
297
- transport: parseGoogleMeetBrowserTransport(options.transport)
298
- });
299
- if (options.json) {
300
- writeStdoutJson(result);
301
- return;
302
- }
303
- writeRecoverCurrentTabResult(result);
304
- });
305
- root.command("setup").description("Show Google Meet transport setup status").option("--transport <transport>", "Transport to check: chrome, chrome-node, or twilio").option("--mode <mode>", "Mode to check: agent, bidi, or transcribe").option("--json", "Print JSON output", false).action(async (options) => {
306
- const status = await (await context.ensureRuntime()).setupStatus({
307
- transport: parseGoogleMeetTransport(options.transport),
308
- mode: parseGoogleMeetMode(options.mode)
309
- });
310
- if (options.json) writeStdoutJson(status);
311
- else writeSetupStatus(status);
312
- if (!status.ok) process.exitCode = 1;
313
- });
314
- root.command("leave").argument("<session-id>", "Meet session ID").action(async (sessionId) => {
315
- const result = await callGoogleMeetRuntime(context, "googlemeet.leave", { sessionId }, (rt) => rt.leave(sessionId));
316
- if (!result.found) throw new Error("session not found");
317
- writeLeaveResult(sessionId, result);
318
- });
319
- root.command("speak").argument("<session-id>", "Meet session ID").argument("[message]", "Realtime instructions to speak now").action(async (sessionId, message) => {
320
- const result = await callGoogleMeetRuntime(context, "googlemeet.speak", {
321
- sessionId,
322
- message
323
- }, (rt) => rt.speak(sessionId, message));
324
- if (!result.found) throw new Error("session not found");
325
- if (!result.spoken) throw new Error(result.session?.chrome?.health?.speechBlockedMessage ?? "session has no active realtime audio bridge");
326
- writeStdoutLine("speaking on %s", sessionId);
327
- });
328
- }
329
42
  // extensions/google-meet/src/cli-space-commands.ts
330
43
  function writeGoogleMeetCreateOutput(payload, json) {
331
44
  if (json) {
@@ -373,7 +86,7 @@ function registerGoogleMeetCreateCommands(context) {
373
86
  }
374
87
  };
375
88
  } else {
376
- const { token, source: _source, ...result } = await createSpaceFromParams(context.config, resolveCliParams(options));
89
+ const { token, source: _source, ...result } = await createSpaceFromParams(context.config, resolveCliParams(options), context.oauthRuntime);
377
90
  created = {
378
91
  ...result,
379
92
  tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
@@ -389,7 +102,7 @@ function registerGoogleMeetCreateCommands(context) {
389
102
  }, options.json);
390
103
  });
391
104
  addGoogleMeetOAuthOptions(root.command("end-active-conference").description("End the active conference for a Google Meet space").argument("[meeting]", "Meet URL, meeting code, or spaces/{id}")).option("--json", "Print JSON output", false).action(async (meeting, options) => {
392
- const token = await resolveGoogleMeetTokenFromParams(context.config, resolveCliParams(options));
105
+ const token = await resolveGoogleMeetTokenFromParams(context.config, resolveCliParams(options), context.oauthRuntime);
393
106
  const result = await endGoogleMeetActiveConference({
394
107
  accessToken: token.accessToken,
395
108
  meeting: resolveCliMeetingInput(context.config, meeting ?? options.meeting)
@@ -410,10 +123,7 @@ function registerGoogleMeetApiCommands(context) {
410
123
  const { root } = context;
411
124
  addGoogleMeetOAuthOptions(addGoogleMeetMeetingOption(root.command("resolve-space").description("Resolve a Meet URL, meeting code, or spaces/{id} to the matching space"))).option("--json", "Print JSON output", false).action(async (options) => {
412
125
  const meeting = resolveCliMeetingInput(context.config, options.meeting);
413
- const { space, token } = await resolveSpaceFromParams(context.config, {
414
- ...resolveCliParams(options),
415
- meeting
416
- });
126
+ const { space, token } = await resolveSpaceFromParams(context.config, resolveCliParams(options), context.oauthRuntime);
417
127
  if (options.json) {
418
128
  writeStdoutJson(space);
419
129
  return;
@@ -427,10 +137,7 @@ function registerGoogleMeetApiCommands(context) {
427
137
  });
428
138
  addGoogleMeetOAuthOptions(addGoogleMeetMeetingOption(root.command("preflight").description("Validate OAuth + meeting resolution prerequisites for Meet media work"))).option("--json", "Print JSON output", false).action(async (options) => {
429
139
  const meeting = resolveCliMeetingInput(context.config, options.meeting);
430
- const { space, token } = await resolveSpaceFromParams(context.config, {
431
- ...resolveCliParams(options),
432
- meeting
433
- });
140
+ const { space, token } = await resolveSpaceFromParams(context.config, resolveCliParams(options), context.oauthRuntime);
434
141
  const report = buildGoogleMeetPreflightReport({
435
142
  input: meeting,
436
143
  space,
@@ -457,7 +164,7 @@ function registerGoogleMeetApiCommands(context) {
457
164
  });
458
165
  addGoogleMeetOAuthOptions(addGoogleMeetCalendarOptions(addGoogleMeetMeetingOption(root.command("latest").description("Find the latest Meet conference record for a meeting")))).option("--json", "Print JSON output", false).action(async (options) => {
459
166
  const raw = resolveCliParams(options);
460
- const token = await resolveGoogleMeetTokenFromParams(context.config, raw);
167
+ const token = await resolveGoogleMeetTokenFromParams(context.config, raw, context.oauthRuntime);
461
168
  if (!options.today && !options.event?.trim()) {
462
169
  const meeting = options.meeting?.trim() || context.config.defaults.meeting;
463
170
  if (!meeting) throw new Error("Meeting input is required. Pass --meeting, --today, --event, or configure defaults.meeting.");
@@ -488,7 +195,7 @@ function registerGoogleMeetApiCommands(context) {
488
195
  writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
489
196
  });
490
197
  addGoogleMeetOAuthOptions(root.command("calendar-events").description("Preview Calendar events with Google Meet links").option("--today", "Find Meet links on today's calendar").option("--event <query>", "Find matching calendar events with Meet links").option("--calendar <id>", "Calendar id for lookup", "primary")).option("--json", "Print JSON output", false).action(async (options) => {
491
- const token = await resolveGoogleMeetTokenFromParams(context.config, resolveCliParams(options));
198
+ const token = await resolveGoogleMeetTokenFromParams(context.config, resolveCliParams(options), context.oauthRuntime);
492
199
  const window = options.today ? buildGoogleMeetCalendarDayWindow() : {};
493
200
  const result = await listGoogleMeetCalendarEvents({
494
201
  accessToken: token.accessToken,
@@ -509,6 +216,123 @@ function registerGoogleMeetApiCommands(context) {
509
216
  });
510
217
  }
511
218
  // extensions/google-meet/src/cli.ts
219
+ function sanitizeOAuthErrorMessage(error) {
220
+ return (error instanceof Error ? error.message : String(error)).replace(/(access_token["'=:\s]+)[^"',\s&]+/gi, "$1[redacted]").replace(/(refresh_token["'=:\s]+)[^"',\s&]+/gi, "$1[redacted]").replace(/(client_secret["'=:\s]+)[^"',\s&]+/gi, "$1[redacted]");
221
+ }
222
+ async function buildOAuthDoctorReport(config, options, runtime) {
223
+ const clientId = options.clientId?.trim() || config.oauth.clientId;
224
+ const clientSecret = options.clientSecret?.trim() || config.oauth.clientSecret;
225
+ const refreshToken = options.refreshToken?.trim() || config.oauth.refreshToken;
226
+ const accessToken = options.accessToken?.trim() || config.oauth.accessToken;
227
+ const expiresAt = parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt;
228
+ const checks = [];
229
+ const recordFailure = (id, error) => {
230
+ checks.push({
231
+ id,
232
+ ok: false,
233
+ message: sanitizeOAuthErrorMessage(error)
234
+ });
235
+ };
236
+ const hasRefreshConfig = Boolean(clientId && refreshToken);
237
+ if (!hasRefreshConfig && !Boolean(accessToken)) {
238
+ checks.push({
239
+ id: "oauth-config",
240
+ ok: false,
241
+ message: "Missing Google Meet OAuth credentials. Configure oauth.clientId and oauth.refreshToken, or pass --client-id and --refresh-token."
242
+ });
243
+ return {
244
+ ok: false,
245
+ configured: false,
246
+ checks
247
+ };
248
+ }
249
+ checks.push({
250
+ id: "oauth-config",
251
+ ok: true,
252
+ message: hasRefreshConfig ? "Google Meet OAuth refresh credentials are configured" : "Google Meet cached access token is configured"
253
+ });
254
+ let token;
255
+ try {
256
+ token = await resolveGoogleMeetTokenFromParams(config, {
257
+ clientId,
258
+ clientSecret,
259
+ refreshToken,
260
+ accessToken,
261
+ expiresAt
262
+ }, runtime);
263
+ checks.push({
264
+ id: "oauth-token",
265
+ ok: true,
266
+ message: token.refreshed ? "Refresh token minted an access token" : "Cached access token is still valid"
267
+ });
268
+ } catch (error) {
269
+ recordFailure("oauth-token", error);
270
+ return {
271
+ ok: false,
272
+ configured: true,
273
+ checks
274
+ };
275
+ }
276
+ const report = {
277
+ ok: true,
278
+ configured: true,
279
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
280
+ expiresAt: token.expiresAt,
281
+ checks
282
+ };
283
+ const meeting = options.meeting?.trim();
284
+ if (meeting) try {
285
+ const space = await fetchGoogleMeetSpace({
286
+ accessToken: token.accessToken,
287
+ meeting
288
+ });
289
+ checks.push({
290
+ id: "meet-spaces-get",
291
+ ok: true,
292
+ message: `Resolved ${space.name}`
293
+ });
294
+ report.meetingUri = space.meetingUri;
295
+ } catch (error) {
296
+ recordFailure("meet-spaces-get", error);
297
+ }
298
+ if (options.createSpace) try {
299
+ const created = await createGoogleMeetSpace({ accessToken: token.accessToken });
300
+ checks.push({
301
+ id: "meet-spaces-create",
302
+ ok: true,
303
+ message: `Created ${created.space.name}`
304
+ });
305
+ report.createdSpace = created.space.name;
306
+ report.meetingUri = created.meetingUri;
307
+ } catch (error) {
308
+ recordFailure("meet-spaces-create", error);
309
+ }
310
+ report.ok = checks.every((check) => check.ok);
311
+ return report;
312
+ }
313
+ function writeOAuthDoctorReport(report) {
314
+ writeStdoutLine("Google Meet OAuth: %s", report.ok ? "OK" : "needs attention");
315
+ writeStdoutLine("configured: %s", report.configured ? "yes" : "no");
316
+ if (report.tokenSource) writeStdoutLine("token source: %s", report.tokenSource);
317
+ if (report.meetingUri) writeStdoutLine("meeting uri: %s", report.meetingUri);
318
+ for (const check of report.checks) writeStdoutLine("[%s] %s: %s", check.ok ? "ok" : "fail", check.id, check.message);
319
+ }
320
+ function resolveTokenSource(refreshed) {
321
+ return refreshed ? "refresh-token" : "cached-access-token";
322
+ }
323
+ async function writeArtifactOutput(options, result, refreshed, render, csv) {
324
+ const tokenSource = resolveTokenSource(refreshed);
325
+ let text;
326
+ if (options.json) text = JSON.stringify({
327
+ ...result,
328
+ tokenSource
329
+ }, null, 2);
330
+ else if (options.format === "markdown") text = render(result, "markdown");
331
+ else if (options.format === "csv" && csv) text = csv(result);
332
+ else if (!options.format || options.format === "summary") text = `${render(result, "summary")}token source: ${tokenSource}\n`;
333
+ else throw new Error(csv ? "Unsupported format. Expected summary, markdown, or csv." : "Unsupported format. Expected summary or markdown.");
334
+ await writeCliOutput(options, text);
335
+ }
512
336
  function registerGoogleMeetCli(params) {
513
337
  const operationTimeoutMs = resolveGoogleMeetGatewayOperationTimeoutMs(params.config);
514
338
  const root = params.program.command("googlemeet").description("Google Meet participant utilities").addHelpText("after", () => `\nDocs: https://docs.openclaw.ai/plugins/google-meet\n`);
@@ -556,14 +380,156 @@ function registerGoogleMeetCli(params) {
556
380
  root,
557
381
  config: params.config,
558
382
  ensureRuntime: params.ensureRuntime,
559
- operationTimeoutMs
383
+ operationTimeoutMs,
384
+ oauthRuntime: params.oauthRuntime
560
385
  };
561
386
  registerGoogleMeetCreateCommands(context);
562
- registerGoogleMeetProbeCommands(context);
387
+ root.command("join").argument("[url]", "Explicit https://meet.google.com/... URL").option("--transport <transport>", "Transport: chrome, chrome-node, or twilio").option("--mode <mode>", "Mode: agent, bidi, or transcribe").option("--message <text>", "Realtime speech to trigger after join").option("--dial-in-number <phone>", "Meet dial-in number for Twilio transport").option("--pin <pin>", "Meet phone PIN; # is appended if omitted").option("--dtmf-sequence <sequence>", "Explicit Twilio DTMF sequence").action(async (url, options) => {
388
+ const payload = resolveCliJoinRequest(resolveCliMeetingInput(context.config, url), options);
389
+ const result = await callGoogleMeetRuntime(context, "googlemeet.join", payload, (rt) => rt.join(payload), operationTimeoutMs);
390
+ writeStdoutJson(result.session ?? result);
391
+ });
392
+ root.command("test-speech").argument("[url]", "Explicit https://meet.google.com/... URL").option("--transport <transport>", "Transport: chrome, chrome-node, or twilio").option("--mode <mode>", "Mode: agent, bidi, or transcribe").option("--message <text>", "Realtime speech to trigger", "Say exactly: Google Meet speech test complete.").action(async (url, options) => {
393
+ const payload = {
394
+ url: resolveCliMeetingInput(context.config, url),
395
+ transport: parseGoogleMeetTransport(options.transport),
396
+ mode: parseGoogleMeetMode(options.mode),
397
+ message: options.message
398
+ };
399
+ writeStdoutJson(await callGoogleMeetRuntime(context, "googlemeet.testSpeech", payload, (rt) => rt.testSpeech(payload), operationTimeoutMs));
400
+ });
401
+ root.command("test-listen").argument("[url]", "Explicit https://meet.google.com/... URL").option("--transport <transport>", "Transport: chrome or chrome-node").option("--timeout-ms <ms>", "How long to wait for fresh captions/transcript movement").action(async (url, options) => {
402
+ const payload = {
403
+ url: resolveCliMeetingInput(context.config, url),
404
+ transport: parseGoogleMeetBrowserTransport(options.transport),
405
+ timeoutMs: parsePositiveNumber(options.timeoutMs, "timeout-ms")
406
+ };
407
+ writeStdoutJson(await callGoogleMeetRuntime(context, "googlemeet.testListen", payload, (rt) => rt.testListen(payload), operationTimeoutMs));
408
+ });
563
409
  registerGoogleMeetApiCommands(context);
564
- registerGoogleMeetArtifactCommands(context);
565
- registerGoogleMeetSessionCommands(context);
566
- registerGoogleMeetDoctorCommand(context);
567
- registerGoogleMeetLifecycleCommands(context);
410
+ async function resolveCliArtifactQuery(options) {
411
+ const meeting = options.meeting?.trim() || context.config.defaults.meeting;
412
+ const conferenceRecord = options.conferenceRecord?.trim();
413
+ if (!meeting && !conferenceRecord && !(options.today || options.event?.trim())) throw new Error("Meeting input or conference record is required. Pass --meeting, --today, --event, --conference-record, or configure defaults.meeting.");
414
+ const { lateAfterMinutes: late, earlyBeforeMinutes: early, ...queryOptions } = options;
415
+ const raw = {
416
+ ...resolveCliParams(queryOptions),
417
+ meeting,
418
+ conferenceRecord,
419
+ pageSize: parsePositiveIntegerOption(options.pageSize, "page-size"),
420
+ includeTranscriptEntries: options.transcriptEntries,
421
+ includeAllConferenceRecords: options.allConferenceRecords,
422
+ includeDocumentBodies: options.includeDocBodies,
423
+ mergeDuplicateParticipants: options.mergeDuplicates
424
+ };
425
+ const lateAfterMinutes = parseOptionalNumber(late);
426
+ const earlyBeforeMinutes = parseOptionalNumber(early);
427
+ return {
428
+ ...await resolveArtifactQueryFromParams(context.config, raw, context.oauthRuntime),
429
+ lateAfterMinutes,
430
+ earlyBeforeMinutes
431
+ };
432
+ }
433
+ addGoogleMeetArtifactOptions(root.command("artifacts").description("List Meet conference records and available participant/artifact metadata")).option("--no-transcript-entries", "Skip structured transcript entry lookup").option("--include-doc-bodies", "Export linked transcript and smart-note Google Docs text").option("--format <format>", "Output format: summary or markdown", "summary").option("--output <path>", "Write output to a file instead of stdout").option("--json", "Print JSON output", false).action(async (options) => {
434
+ const resolved = await resolveCliArtifactQuery(options);
435
+ await writeArtifactOutput(options, await fetchResolvedGoogleMeetArtifacts(resolved), resolved.token.refreshed, renderArtifacts);
436
+ });
437
+ addGoogleMeetArtifactOptions(root.command("attendance").description("List Meet participants and participant sessions")).option("--no-merge-duplicates", "Keep duplicate participant resources as separate rows").option("--late-after-minutes <n>", "Mark participants late after this many minutes", "5").option("--early-before-minutes <n>", "Mark early leavers before this many minutes", "5").option("--format <format>", "Output format: summary, markdown, or csv", "summary").option("--output <path>", "Write output to a file instead of stdout").option("--json", "Print JSON output", false).action(async (options) => {
438
+ const resolved = await resolveCliArtifactQuery(options);
439
+ await writeArtifactOutput(options, await fetchResolvedGoogleMeetAttendance(resolved), resolved.token.refreshed, renderAttendance, renderAttendanceCsv);
440
+ });
441
+ addGoogleMeetArtifactOptions(root.command("export").description("Write Meet artifacts, attendance, transcript, and raw JSON into a folder")).option("--no-transcript-entries", "Skip structured transcript entry lookup").option("--include-doc-bodies", "Export linked transcript and smart-note Google Docs text").option("--no-merge-duplicates", "Keep duplicate participant resources as separate rows").option("--late-after-minutes <n>", "Mark participants late after this many minutes", "5").option("--early-before-minutes <n>", "Mark early leavers before this many minutes", "5").option("--output <dir>", "Output directory").option("--zip", "Also write a portable .zip archive").option("--dry-run", "Fetch export data and print the manifest without writing files", false).option("--json", "Print JSON output", false).action(async (options) => {
442
+ const resolved = await resolveCliArtifactQuery(options);
443
+ const artifacts = await fetchResolvedGoogleMeetArtifacts(resolved);
444
+ const attendance = await fetchResolvedGoogleMeetAttendance(resolved);
445
+ const payload = await exportGoogleMeetBundle({
446
+ outputDir: options.output,
447
+ artifacts,
448
+ attendance,
449
+ zip: Boolean(options.zip),
450
+ dryRun: options.dryRun,
451
+ request: buildGoogleMeetExportRequest(resolved, options.calendar),
452
+ tokenSource: resolveTokenSource(resolved.token.refreshed),
453
+ calendarEvent: resolved.calendarEvent
454
+ });
455
+ if (options.json || "dryRun" in payload) {
456
+ writeStdoutJson(payload);
457
+ return;
458
+ }
459
+ writeStdoutLine("export: %s", payload.outputDir);
460
+ for (const file of payload.files) writeStdoutLine("- %s", file);
461
+ if (payload.zipFile) writeStdoutLine("zip: %s", payload.zipFile);
462
+ });
463
+ root.command("status").argument("[session-id]", "Meet session ID").option("--json", "Print JSON output", false).action(async (sessionId) => {
464
+ writeStdoutJson(await callGoogleMeetRuntime(context, "googlemeet.status", { sessionId }, (rt) => rt.status(sessionId)));
465
+ });
466
+ root.command("transcript").description("Print the bounded caption transcript for a Meet session").argument("<session-id>", "Meet session ID").option("--since <index>", "Resume from the previous response's nextIndex").option("--json", "Print JSON output", false).action(async (sessionId, options) => {
467
+ const sinceIndex = parseStrictNonNegativeInteger(options.since);
468
+ if (options.since !== void 0 && sinceIndex === void 0) throw new Error("--since must be a non-negative safe integer");
469
+ const result = await callGoogleMeetRuntime(context, "googlemeet.transcript", {
470
+ sessionId,
471
+ ...sinceIndex === void 0 ? {} : { sinceIndex }
472
+ }, (rt) => rt.transcript(sessionId, sinceIndex === void 0 ? {} : { sinceIndex }));
473
+ if (!result.found) throw new Error("session not found");
474
+ if (options.json) {
475
+ writeStdoutJson(result);
476
+ return;
477
+ }
478
+ if (result.evicted) writeStdoutLine("# transcript evicted from runtime memory");
479
+ else if (result.droppedLines) writeStdoutLine("# %d earlier lines dropped by the transcript cap", result.droppedLines);
480
+ for (const line of result.lines ?? []) writeStdoutLine("%s%s%s", line.at ? `[${line.at}] ` : "", line.speaker ? `${line.speaker}: ` : "", line.text);
481
+ writeStdoutLine("# nextIndex: %d", result.nextIndex ?? 0);
482
+ });
483
+ addGoogleMeetOAuthOptions(root.command("doctor").description("Show human-readable Meet session/browser/realtime health").argument("[session-id]", "Meet session ID").option("--oauth", "Verify Google Meet OAuth token refresh without printing secrets", false).option("--meeting <value>", "Also verify spaces.get for a Meet URL, code, or spaces/{id}").option("--create-space", "Also verify spaces.create by creating a throwaway Meet space", false)).option("--json", "Print JSON output", false).action(async (sessionId, options) => {
484
+ if (options.oauth) {
485
+ const report = await buildOAuthDoctorReport(context.config, options, context.oauthRuntime);
486
+ if (options.json) {
487
+ writeStdoutJson(report);
488
+ return;
489
+ }
490
+ writeOAuthDoctorReport(report);
491
+ return;
492
+ }
493
+ const status = await callGoogleMeetRuntime(context, "googlemeet.status", { sessionId }, (rt) => rt.status(sessionId));
494
+ if (options.json) {
495
+ writeStdoutJson(status);
496
+ return;
497
+ }
498
+ writeDoctorStatus(status);
499
+ });
500
+ root.command("recover-tab").description("Focus and inspect an existing Google Meet tab").argument("[url]", "Optional Meet URL to match").option("--transport <transport>", "Transport to inspect: chrome or chrome-node").option("--json", "Print JSON output", false).action(async (url, options) => {
501
+ const result = await (await context.ensureRuntime()).recoverCurrentTab({
502
+ url,
503
+ transport: parseGoogleMeetBrowserTransport(options.transport)
504
+ });
505
+ if (options.json) {
506
+ writeStdoutJson(result);
507
+ return;
508
+ }
509
+ writeRecoverCurrentTabResult(result);
510
+ });
511
+ root.command("setup").description("Show Google Meet transport setup status").option("--transport <transport>", "Transport to check: chrome, chrome-node, or twilio").option("--mode <mode>", "Mode to check: agent, bidi, or transcribe").option("--json", "Print JSON output", false).action(async (options) => {
512
+ const status = await (await context.ensureRuntime()).setupStatus({
513
+ transport: parseGoogleMeetTransport(options.transport),
514
+ mode: parseGoogleMeetMode(options.mode)
515
+ });
516
+ if (options.json) writeStdoutJson(status);
517
+ else writeSetupStatus(status);
518
+ if (!status.ok) process.exitCode = 1;
519
+ });
520
+ root.command("leave").argument("<session-id>", "Meet session ID").action(async (sessionId) => {
521
+ const result = await callGoogleMeetRuntime(context, "googlemeet.leave", { sessionId }, (rt) => rt.leave(sessionId));
522
+ if (!result.found) throw new Error("session not found");
523
+ writeLeaveResult(sessionId, result);
524
+ });
525
+ root.command("speak").argument("<session-id>", "Meet session ID").argument("[message]", "Realtime instructions to speak now").action(async (sessionId, message) => {
526
+ const result = await callGoogleMeetRuntime(context, "googlemeet.speak", {
527
+ sessionId,
528
+ message
529
+ }, (rt) => rt.speak(sessionId, message));
530
+ if (!result.found) throw new Error("session not found");
531
+ if (!result.spoken) throw new Error(result.session?.chrome?.health?.speechBlockedMessage ?? "session has no active realtime audio bridge");
532
+ writeStdoutLine("speaking on %s", sessionId);
533
+ });
568
534
  }
569
535
  export { registerGoogleMeetCli };