@openclaw/google-meet 2026.7.2-beta.7 → 2026.7.34

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 (207) hide show
  1. package/dist/{chrome-create-DMKruRFW.js → .setup/chrome-create-CFNlY-qH.mjs} +153 -132
  2. package/dist/{cli-DCcRyaMf.js → .setup/cli-DfOl5mwt.mjs} +707 -716
  3. package/dist/{config-BcaS1BDr.js → .setup/config-DeJhjNWQ.mjs} +95 -76
  4. package/dist/{create-af1MDlus.js → .setup/create-BVsTTJt1.mjs} +3 -21
  5. package/dist/{google-api-errors-CDUYkIcK.js → .setup/google-api-errors-BDYXfKRa.mjs} +1 -1
  6. package/dist/{oauth-DOfMKlGp.js → .setup/oauth-CtVbvmvR.mjs} +4 -3
  7. package/dist/doctor-contract-api.js +3 -1
  8. package/dist/index.js +3868 -1818
  9. package/node_modules/{jszip/node_modules/setimmediate/LICENSE.txt → commander/LICENSE} +10 -8
  10. package/node_modules/commander/Readme.md +1172 -0
  11. package/node_modules/commander/index.js +21 -0
  12. package/node_modules/commander/lib/argument.js +147 -0
  13. package/node_modules/commander/lib/command.js +2790 -0
  14. package/node_modules/commander/lib/error.js +36 -0
  15. package/node_modules/commander/lib/help.js +731 -0
  16. package/node_modules/commander/lib/option.js +377 -0
  17. package/node_modules/commander/lib/suggestSimilar.js +99 -0
  18. package/node_modules/commander/package-support.json +19 -0
  19. package/node_modules/commander/package.json +64 -0
  20. package/node_modules/commander/typings/index.d.ts +1113 -0
  21. package/node_modules/typebox/build/compile/validator.d.mts +4 -1
  22. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  23. package/node_modules/typebox/build/guard/guard.mjs +2 -5
  24. package/node_modules/typebox/build/guard/string.mjs +4 -16
  25. package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
  26. package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
  27. package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
  28. package/node_modules/typebox/build/typebox.d.mts +1 -1
  29. package/node_modules/typebox/build/typebox.mjs +1 -1
  30. package/node_modules/typebox/package.json +1 -1
  31. package/node_modules/typebox/readme.md +32 -29
  32. package/openclaw.plugin.json +3 -4
  33. package/package.json +7 -9
  34. package/dist/cli-metadata.js +0 -13
  35. package/dist/cli-output-mode-BwH_6tH8.js +0 -28
  36. package/node_modules/jszip/.codeclimate.yml +0 -16
  37. package/node_modules/jszip/.editorconfig +0 -8
  38. package/node_modules/jszip/.eslintrc.js +0 -43
  39. package/node_modules/jszip/.github/workflows/pr.yaml +0 -58
  40. package/node_modules/jszip/.jekyll-metadata +0 -0
  41. package/node_modules/jszip/.travis.yml +0 -17
  42. package/node_modules/jszip/CHANGES.md +0 -204
  43. package/node_modules/jszip/LICENSE.markdown +0 -651
  44. package/node_modules/jszip/README.markdown +0 -33
  45. package/node_modules/jszip/deps.js +0 -37
  46. package/node_modules/jszip/dist/jszip.js +0 -11577
  47. package/node_modules/jszip/dist/jszip.min.js +0 -13
  48. package/node_modules/jszip/graph.svg +0 -601
  49. package/node_modules/jszip/index.d.ts +0 -330
  50. package/node_modules/jszip/lib/base64.js +0 -106
  51. package/node_modules/jszip/lib/compressedObject.js +0 -74
  52. package/node_modules/jszip/lib/compressions.js +0 -14
  53. package/node_modules/jszip/lib/crc32.js +0 -77
  54. package/node_modules/jszip/lib/defaults.js +0 -11
  55. package/node_modules/jszip/lib/external.js +0 -18
  56. package/node_modules/jszip/lib/flate.js +0 -85
  57. package/node_modules/jszip/lib/generate/ZipFileWorker.js +0 -539
  58. package/node_modules/jszip/lib/generate/index.js +0 -57
  59. package/node_modules/jszip/lib/index.js +0 -55
  60. package/node_modules/jszip/lib/license_header.js +0 -11
  61. package/node_modules/jszip/lib/load.js +0 -88
  62. package/node_modules/jszip/lib/nodejs/NodejsStreamInputAdapter.js +0 -74
  63. package/node_modules/jszip/lib/nodejs/NodejsStreamOutputAdapter.js +0 -42
  64. package/node_modules/jszip/lib/nodejsUtils.js +0 -57
  65. package/node_modules/jszip/lib/object.js +0 -384
  66. package/node_modules/jszip/lib/readable-stream-browser.js +0 -10
  67. package/node_modules/jszip/lib/reader/ArrayReader.js +0 -57
  68. package/node_modules/jszip/lib/reader/DataReader.js +0 -116
  69. package/node_modules/jszip/lib/reader/NodeBufferReader.js +0 -19
  70. package/node_modules/jszip/lib/reader/StringReader.js +0 -38
  71. package/node_modules/jszip/lib/reader/Uint8ArrayReader.js +0 -22
  72. package/node_modules/jszip/lib/reader/readerFor.js +0 -28
  73. package/node_modules/jszip/lib/signature.js +0 -7
  74. package/node_modules/jszip/lib/stream/ConvertWorker.js +0 -26
  75. package/node_modules/jszip/lib/stream/Crc32Probe.js +0 -24
  76. package/node_modules/jszip/lib/stream/DataLengthProbe.js +0 -29
  77. package/node_modules/jszip/lib/stream/DataWorker.js +0 -116
  78. package/node_modules/jszip/lib/stream/GenericWorker.js +0 -263
  79. package/node_modules/jszip/lib/stream/StreamHelper.js +0 -214
  80. package/node_modules/jszip/lib/support.js +0 -38
  81. package/node_modules/jszip/lib/utf8.js +0 -275
  82. package/node_modules/jszip/lib/utils.js +0 -501
  83. package/node_modules/jszip/lib/zipEntries.js +0 -261
  84. package/node_modules/jszip/lib/zipEntry.js +0 -293
  85. package/node_modules/jszip/lib/zipObject.js +0 -133
  86. package/node_modules/jszip/node_modules/core-util-is/LICENSE +0 -19
  87. package/node_modules/jszip/node_modules/core-util-is/README.md +0 -3
  88. package/node_modules/jszip/node_modules/core-util-is/lib/util.js +0 -107
  89. package/node_modules/jszip/node_modules/core-util-is/package.json +0 -38
  90. package/node_modules/jszip/node_modules/immediate/LICENSE.txt +0 -20
  91. package/node_modules/jszip/node_modules/immediate/README.md +0 -93
  92. package/node_modules/jszip/node_modules/immediate/dist/immediate.js +0 -75
  93. package/node_modules/jszip/node_modules/immediate/dist/immediate.min.js +0 -1
  94. package/node_modules/jszip/node_modules/immediate/lib/browser.js +0 -69
  95. package/node_modules/jszip/node_modules/immediate/lib/index.js +0 -73
  96. package/node_modules/jszip/node_modules/immediate/package.json +0 -42
  97. package/node_modules/jszip/node_modules/inherits/LICENSE +0 -16
  98. package/node_modules/jszip/node_modules/inherits/README.md +0 -42
  99. package/node_modules/jszip/node_modules/inherits/inherits.js +0 -9
  100. package/node_modules/jszip/node_modules/inherits/inherits_browser.js +0 -27
  101. package/node_modules/jszip/node_modules/inherits/package.json +0 -29
  102. package/node_modules/jszip/node_modules/isarray/.npmignore +0 -1
  103. package/node_modules/jszip/node_modules/isarray/.travis.yml +0 -4
  104. package/node_modules/jszip/node_modules/isarray/Makefile +0 -6
  105. package/node_modules/jszip/node_modules/isarray/README.md +0 -60
  106. package/node_modules/jszip/node_modules/isarray/component.json +0 -19
  107. package/node_modules/jszip/node_modules/isarray/index.js +0 -5
  108. package/node_modules/jszip/node_modules/isarray/package.json +0 -45
  109. package/node_modules/jszip/node_modules/isarray/test.js +0 -20
  110. package/node_modules/jszip/node_modules/lie/README.md +0 -62
  111. package/node_modules/jszip/node_modules/lie/dist/lie.js +0 -350
  112. package/node_modules/jszip/node_modules/lie/dist/lie.min.js +0 -1
  113. package/node_modules/jszip/node_modules/lie/dist/lie.polyfill.js +0 -358
  114. package/node_modules/jszip/node_modules/lie/dist/lie.polyfill.min.js +0 -1
  115. package/node_modules/jszip/node_modules/lie/lib/browser.js +0 -273
  116. package/node_modules/jszip/node_modules/lie/lib/index.js +0 -298
  117. package/node_modules/jszip/node_modules/lie/license.md +0 -7
  118. package/node_modules/jszip/node_modules/lie/lie.d.ts +0 -244
  119. package/node_modules/jszip/node_modules/lie/package.json +0 -69
  120. package/node_modules/jszip/node_modules/lie/polyfill.js +0 -4
  121. package/node_modules/jszip/node_modules/pako/CHANGELOG.md +0 -164
  122. package/node_modules/jszip/node_modules/pako/LICENSE +0 -21
  123. package/node_modules/jszip/node_modules/pako/README.md +0 -191
  124. package/node_modules/jszip/node_modules/pako/dist/pako.js +0 -6818
  125. package/node_modules/jszip/node_modules/pako/dist/pako.min.js +0 -1
  126. package/node_modules/jszip/node_modules/pako/dist/pako_deflate.js +0 -3997
  127. package/node_modules/jszip/node_modules/pako/dist/pako_deflate.min.js +0 -1
  128. package/node_modules/jszip/node_modules/pako/dist/pako_inflate.js +0 -3300
  129. package/node_modules/jszip/node_modules/pako/dist/pako_inflate.min.js +0 -1
  130. package/node_modules/jszip/node_modules/pako/index.js +0 -14
  131. package/node_modules/jszip/node_modules/pako/lib/deflate.js +0 -400
  132. package/node_modules/jszip/node_modules/pako/lib/inflate.js +0 -423
  133. package/node_modules/jszip/node_modules/pako/lib/utils/common.js +0 -105
  134. package/node_modules/jszip/node_modules/pako/lib/utils/strings.js +0 -187
  135. package/node_modules/jszip/node_modules/pako/lib/zlib/README +0 -59
  136. package/node_modules/jszip/node_modules/pako/lib/zlib/adler32.js +0 -51
  137. package/node_modules/jszip/node_modules/pako/lib/zlib/constants.js +0 -68
  138. package/node_modules/jszip/node_modules/pako/lib/zlib/crc32.js +0 -59
  139. package/node_modules/jszip/node_modules/pako/lib/zlib/deflate.js +0 -1874
  140. package/node_modules/jszip/node_modules/pako/lib/zlib/gzheader.js +0 -58
  141. package/node_modules/jszip/node_modules/pako/lib/zlib/inffast.js +0 -345
  142. package/node_modules/jszip/node_modules/pako/lib/zlib/inflate.js +0 -1556
  143. package/node_modules/jszip/node_modules/pako/lib/zlib/inftrees.js +0 -343
  144. package/node_modules/jszip/node_modules/pako/lib/zlib/messages.js +0 -32
  145. package/node_modules/jszip/node_modules/pako/lib/zlib/trees.js +0 -1222
  146. package/node_modules/jszip/node_modules/pako/lib/zlib/zstream.js +0 -47
  147. package/node_modules/jszip/node_modules/pako/package.json +0 -44
  148. package/node_modules/jszip/node_modules/process-nextick-args/index.js +0 -45
  149. package/node_modules/jszip/node_modules/process-nextick-args/license.md +0 -19
  150. package/node_modules/jszip/node_modules/process-nextick-args/package.json +0 -25
  151. package/node_modules/jszip/node_modules/process-nextick-args/readme.md +0 -18
  152. package/node_modules/jszip/node_modules/readable-stream/.travis.yml +0 -34
  153. package/node_modules/jszip/node_modules/readable-stream/CONTRIBUTING.md +0 -38
  154. package/node_modules/jszip/node_modules/readable-stream/GOVERNANCE.md +0 -136
  155. package/node_modules/jszip/node_modules/readable-stream/LICENSE +0 -47
  156. package/node_modules/jszip/node_modules/readable-stream/README.md +0 -58
  157. package/node_modules/jszip/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md +0 -60
  158. package/node_modules/jszip/node_modules/readable-stream/duplex-browser.js +0 -1
  159. package/node_modules/jszip/node_modules/readable-stream/duplex.js +0 -1
  160. package/node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js +0 -131
  161. package/node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js +0 -47
  162. package/node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js +0 -1019
  163. package/node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js +0 -214
  164. package/node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js +0 -685
  165. package/node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js +0 -78
  166. package/node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js +0 -84
  167. package/node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream-browser.js +0 -1
  168. package/node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js +0 -1
  169. package/node_modules/jszip/node_modules/readable-stream/package.json +0 -52
  170. package/node_modules/jszip/node_modules/readable-stream/passthrough.js +0 -1
  171. package/node_modules/jszip/node_modules/readable-stream/readable-browser.js +0 -7
  172. package/node_modules/jszip/node_modules/readable-stream/readable.js +0 -19
  173. package/node_modules/jszip/node_modules/readable-stream/transform.js +0 -1
  174. package/node_modules/jszip/node_modules/readable-stream/writable-browser.js +0 -1
  175. package/node_modules/jszip/node_modules/readable-stream/writable.js +0 -8
  176. package/node_modules/jszip/node_modules/safe-buffer/LICENSE +0 -21
  177. package/node_modules/jszip/node_modules/safe-buffer/README.md +0 -584
  178. package/node_modules/jszip/node_modules/safe-buffer/index.d.ts +0 -187
  179. package/node_modules/jszip/node_modules/safe-buffer/index.js +0 -62
  180. package/node_modules/jszip/node_modules/safe-buffer/package.json +0 -37
  181. package/node_modules/jszip/node_modules/setimmediate/package.json +0 -30
  182. package/node_modules/jszip/node_modules/setimmediate/setImmediate.js +0 -186
  183. package/node_modules/jszip/node_modules/string_decoder/.travis.yml +0 -50
  184. package/node_modules/jszip/node_modules/string_decoder/LICENSE +0 -48
  185. package/node_modules/jszip/node_modules/string_decoder/README.md +0 -47
  186. package/node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js +0 -296
  187. package/node_modules/jszip/node_modules/string_decoder/package.json +0 -31
  188. package/node_modules/jszip/node_modules/util-deprecate/History.md +0 -16
  189. package/node_modules/jszip/node_modules/util-deprecate/LICENSE +0 -24
  190. package/node_modules/jszip/node_modules/util-deprecate/README.md +0 -53
  191. package/node_modules/jszip/node_modules/util-deprecate/browser.js +0 -67
  192. package/node_modules/jszip/node_modules/util-deprecate/node.js +0 -6
  193. package/node_modules/jszip/node_modules/util-deprecate/package.json +0 -27
  194. package/node_modules/jszip/package.json +0 -67
  195. package/node_modules/jszip/sponsors.md +0 -21
  196. package/node_modules/jszip/tsconfig.json +0 -101
  197. package/node_modules/jszip/vendor/FileSaver.js +0 -247
  198. package/node_modules/pretty-ms/index.d.ts +0 -157
  199. package/node_modules/pretty-ms/index.js +0 -149
  200. package/node_modules/pretty-ms/license +0 -9
  201. package/node_modules/pretty-ms/node_modules/parse-ms/index.d.ts +0 -30
  202. package/node_modules/pretty-ms/node_modules/parse-ms/index.js +0 -45
  203. package/node_modules/pretty-ms/node_modules/parse-ms/license +0 -9
  204. package/node_modules/pretty-ms/node_modules/parse-ms/package.json +0 -47
  205. package/node_modules/pretty-ms/node_modules/parse-ms/readme.md +0 -46
  206. package/node_modules/pretty-ms/package.json +0 -55
  207. package/node_modules/pretty-ms/readme.md +0 -179
@@ -1,17 +1,15 @@
1
- import { n as hasCreateSpaceConfigInput, r as resolveCreateSpaceConfig } from "./create-af1MDlus.js";
2
- import { a as buildGoogleMeetCalendarDayWindow, i as resolveGoogleMeetGatewayOperationTimeoutMs, o as findGoogleMeetCalendarEvent, s as listGoogleMeetCalendarEvents } from "./config-BcaS1BDr.js";
3
- import { _ as endGoogleMeetActiveConference, g as createGoogleMeetSpace, h as fetchGoogleMeetAttendance, m as fetchGoogleMeetArtifacts, p as buildGoogleMeetPreflightReport, v as fetchGoogleMeetSpace, y as fetchLatestGoogleMeetConferenceRecord } from "./chrome-create-DMKruRFW.js";
4
- import { buildGoogleMeetAuthUrl, createGoogleMeetOAuthState, createGoogleMeetPkce, exchangeGoogleMeetAuthCode, resolveGoogleMeetAccessToken, waitForGoogleMeetAuthCode } from "./oauth-DOfMKlGp.js";
5
- import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
6
- import { clampTimerTimeoutMs, parseStrictNonNegativeInteger, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
1
+ import { a as buildGoogleMeetCalendarDayWindow, i as resolveGoogleMeetGatewayOperationTimeoutMs, o as findGoogleMeetCalendarEvent, s as listGoogleMeetCalendarEvents } from "./config-DeJhjNWQ.mjs";
2
+ import { _ as fetchLatestGoogleMeetConferenceRecord, d as buildGoogleMeetPreflightReport, f as createGoogleMeetSpace, g as fetchGoogleMeetSpace, h as fetchGoogleMeetAttendance, m as fetchGoogleMeetArtifacts, p as endGoogleMeetActiveConference } from "./chrome-create-CFNlY-qH.mjs";
3
+ import { buildGoogleMeetAuthUrl, createGoogleMeetOAuthState, createGoogleMeetPkce, exchangeGoogleMeetAuthCode, resolveGoogleMeetAccessToken, waitForGoogleMeetAuthCode } from "./oauth-CtVbvmvR.mjs";
4
+ import { hasCreateSpaceConfigInput, resolveCreateSpaceConfig } from "./create-BVsTTJt1.mjs";
7
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
+ import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
7
+ import { clampTimerTimeoutMs, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
8
8
  import path from "node:path";
9
9
  import { mkdir, writeFile } from "node:fs/promises";
10
- import JSZip from "jszip";
11
10
  import { createInterface } from "node:readline/promises";
12
11
  import { format } from "node:util";
13
- import prettyMilliseconds from "pretty-ms";
14
- //#region extensions/google-meet/src/cli-shared.ts
12
+ //#region extensions/google-meet/src/cli.ts
15
13
  const testing = {
16
14
  parsePositiveNumber,
17
15
  resolveGoogleMeetGatewayOperationTimeoutMs,
@@ -105,7 +103,11 @@ async function callGoogleMeetGateway(params) {
105
103
  }
106
104
  function formatDuration(value) {
107
105
  if (value === void 0) return "n/a";
108
- return prettyMilliseconds(Math.max(0, Math.round(value / 1e3) * 1e3), { unitCount: 2 });
106
+ const totalSeconds = Math.round(value / 1e3);
107
+ const hours = Math.floor(totalSeconds / 3600);
108
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
109
+ const seconds = totalSeconds % 60;
110
+ return hours > 0 ? `${hours}h ${minutes.toString().padStart(2, "0")}m` : `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
109
111
  }
110
112
  function writeDoctorStatus(status) {
111
113
  if (!status.found) {
@@ -143,10 +145,10 @@ function writeDoctorStatus(status) {
143
145
  writeStdoutLine("captioning: %s", formatBoolean(health?.captioning));
144
146
  writeStdoutLine("transcript lines: %s", health?.transcriptLines ?? 0);
145
147
  writeStdoutLine("last caption: %s", formatOptional(health?.lastCaptionAt));
146
- writeStdoutLine("manual action: %s", formatBoolean(Boolean(health?.manualAction)));
147
- if (health?.manualAction) {
148
- writeStdoutLine("manual reason: %s", formatOptional(health.manualAction.reason));
149
- writeStdoutLine("manual message: %s", formatOptional(health.manualAction.message));
148
+ writeStdoutLine("manual action: %s", formatBoolean(health?.manualActionRequired));
149
+ if (health?.manualActionRequired) {
150
+ writeStdoutLine("manual reason: %s", formatOptional(health.manualActionReason));
151
+ writeStdoutLine("manual message: %s", formatOptional(health.manualActionMessage));
150
152
  }
151
153
  writeStdoutLine("speech ready: %s", formatBoolean(health?.speechReady));
152
154
  if (health?.speechReady === false) {
@@ -175,6 +177,112 @@ function writeDoctorStatus(status) {
175
177
  }
176
178
  }
177
179
  }
180
+ function sanitizeOAuthErrorMessage(error) {
181
+ 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]");
182
+ }
183
+ async function buildOAuthDoctorReport(config, options) {
184
+ const clientId = options.clientId?.trim() || config.oauth.clientId;
185
+ const clientSecret = options.clientSecret?.trim() || config.oauth.clientSecret;
186
+ const refreshToken = options.refreshToken?.trim() || config.oauth.refreshToken;
187
+ const accessToken = options.accessToken?.trim() || config.oauth.accessToken;
188
+ const expiresAt = parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt;
189
+ const checks = [];
190
+ const hasRefreshConfig = Boolean(clientId && refreshToken);
191
+ if (!hasRefreshConfig && !Boolean(accessToken)) {
192
+ checks.push({
193
+ id: "oauth-config",
194
+ ok: false,
195
+ message: "Missing Google Meet OAuth credentials. Configure oauth.clientId and oauth.refreshToken, or pass --client-id and --refresh-token."
196
+ });
197
+ return {
198
+ ok: false,
199
+ configured: false,
200
+ checks
201
+ };
202
+ }
203
+ checks.push({
204
+ id: "oauth-config",
205
+ ok: true,
206
+ message: hasRefreshConfig ? "Google Meet OAuth refresh credentials are configured" : "Google Meet cached access token is configured"
207
+ });
208
+ let token;
209
+ try {
210
+ token = await resolveGoogleMeetAccessToken({
211
+ clientId,
212
+ clientSecret,
213
+ refreshToken,
214
+ accessToken,
215
+ expiresAt
216
+ });
217
+ checks.push({
218
+ id: "oauth-token",
219
+ ok: true,
220
+ message: token.refreshed ? "Refresh token minted an access token" : "Cached access token is still valid"
221
+ });
222
+ } catch (error) {
223
+ checks.push({
224
+ id: "oauth-token",
225
+ ok: false,
226
+ message: sanitizeOAuthErrorMessage(error)
227
+ });
228
+ return {
229
+ ok: false,
230
+ configured: true,
231
+ checks
232
+ };
233
+ }
234
+ const report = {
235
+ ok: true,
236
+ configured: true,
237
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
238
+ expiresAt: token.expiresAt,
239
+ checks
240
+ };
241
+ const meeting = options.meeting?.trim();
242
+ if (meeting) try {
243
+ const space = await fetchGoogleMeetSpace({
244
+ accessToken: token.accessToken,
245
+ meeting
246
+ });
247
+ checks.push({
248
+ id: "meet-spaces-get",
249
+ ok: true,
250
+ message: `Resolved ${space.name}`
251
+ });
252
+ report.meetingUri = space.meetingUri;
253
+ } catch (error) {
254
+ checks.push({
255
+ id: "meet-spaces-get",
256
+ ok: false,
257
+ message: sanitizeOAuthErrorMessage(error)
258
+ });
259
+ }
260
+ if (options.createSpace) try {
261
+ const created = await createGoogleMeetSpace({ accessToken: token.accessToken });
262
+ checks.push({
263
+ id: "meet-spaces-create",
264
+ ok: true,
265
+ message: `Created ${created.space.name}`
266
+ });
267
+ report.createdSpace = created.space.name;
268
+ report.meetingUri = created.meetingUri;
269
+ } catch (error) {
270
+ checks.push({
271
+ id: "meet-spaces-create",
272
+ ok: false,
273
+ message: sanitizeOAuthErrorMessage(error)
274
+ });
275
+ }
276
+ report.ok = checks.every((check) => check.ok);
277
+ return report;
278
+ }
279
+ function writeOAuthDoctorReport(report) {
280
+ writeStdoutLine("Google Meet OAuth: %s", report.ok ? "OK" : "needs attention");
281
+ writeStdoutLine("configured: %s", report.configured ? "yes" : "no");
282
+ if (report.tokenSource) writeStdoutLine("token source: %s", report.tokenSource);
283
+ if (report.meetingUri) writeStdoutLine("meeting uri: %s", report.meetingUri);
284
+ for (const check of report.checks) writeStdoutLine("[%s] %s: %s", check.ok ? "ok" : "fail", check.id, check.message);
285
+ }
178
286
  function writeRecoverCurrentTabResult(result) {
179
287
  writeStdoutLine("Google Meet current tab: %s", result.found ? "found" : "not found");
180
288
  writeStdoutLine("transport: %s", result.transport);
@@ -208,15 +316,88 @@ function writeRecoverCurrentTabResult(result) {
208
316
  }
209
317
  });
210
318
  }
211
- function writeLeaveResult(sessionId, result) {
212
- if (result.browserLeft === false) {
213
- writeStdoutLine("left %s, but the browser participant may still be in the call; check session notes", sessionId);
214
- return;
215
- }
216
- writeStdoutLine("left %s", sessionId);
319
+ function resolveMeetingInput(config, value) {
320
+ const meeting = value?.trim() || config.defaults.meeting;
321
+ if (!meeting) throw new Error("Meeting input is required. Pass a URL/meeting code or configure defaults.meeting.");
322
+ return meeting;
323
+ }
324
+ function resolveOAuthTokenOptions(config, options) {
325
+ return {
326
+ clientId: options.clientId?.trim() || config.oauth.clientId,
327
+ clientSecret: options.clientSecret?.trim() || config.oauth.clientSecret,
328
+ refreshToken: options.refreshToken?.trim() || config.oauth.refreshToken,
329
+ accessToken: options.accessToken?.trim() || config.oauth.accessToken,
330
+ expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt
331
+ };
332
+ }
333
+ function resolveTokenOptions(config, options) {
334
+ return {
335
+ meeting: resolveMeetingInput(config, options.meeting),
336
+ ...resolveOAuthTokenOptions(config, options)
337
+ };
338
+ }
339
+ function hasCalendarLookupOptions(options) {
340
+ return Boolean(options.today || options.event?.trim());
341
+ }
342
+ async function resolveCalendarMeetingInput(params) {
343
+ if (!hasCalendarLookupOptions(params.options)) return {};
344
+ const window = params.options.today ? buildGoogleMeetCalendarDayWindow() : {};
345
+ const calendarEvent = await findGoogleMeetCalendarEvent({
346
+ accessToken: params.accessToken,
347
+ calendarId: params.options.calendar,
348
+ eventQuery: params.options.event,
349
+ ...window
350
+ });
351
+ return {
352
+ meeting: calendarEvent.meetingUri,
353
+ calendarEvent
354
+ };
355
+ }
356
+ async function resolveMeetingForToken(params) {
357
+ const calendarMeeting = await resolveCalendarMeetingInput({
358
+ accessToken: params.accessToken,
359
+ options: params.options
360
+ });
361
+ const meeting = calendarMeeting.meeting ?? params.configuredMeeting ?? params.config.defaults.meeting;
362
+ if (!meeting) throw new Error("Meeting input is required. Pass --meeting, --today, --event, or configure defaults.meeting.");
363
+ return calendarMeeting.calendarEvent ? {
364
+ meeting,
365
+ calendarEvent: calendarMeeting.calendarEvent
366
+ } : { meeting };
367
+ }
368
+ function resolveCreateTokenOptions(config, options) {
369
+ return {
370
+ clientId: options.clientId?.trim() || config.oauth.clientId,
371
+ clientSecret: options.clientSecret?.trim() || config.oauth.clientSecret,
372
+ refreshToken: options.refreshToken?.trim() || config.oauth.refreshToken,
373
+ accessToken: options.accessToken?.trim() || config.oauth.accessToken,
374
+ expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt
375
+ };
376
+ }
377
+ function resolveArtifactTokenOptions(config, options) {
378
+ const meeting = options.meeting?.trim() || config.defaults.meeting;
379
+ const conferenceRecord = options.conferenceRecord?.trim();
380
+ if (!meeting && !conferenceRecord && !hasCalendarLookupOptions(options)) throw new Error("Meeting input or conference record is required. Pass --meeting, --today, --event, --conference-record, or configure defaults.meeting.");
381
+ return {
382
+ meeting,
383
+ conferenceRecord,
384
+ clientId: options.clientId?.trim() || config.oauth.clientId,
385
+ clientSecret: options.clientSecret?.trim() || config.oauth.clientSecret,
386
+ refreshToken: options.refreshToken?.trim() || config.oauth.refreshToken,
387
+ accessToken: options.accessToken?.trim() || config.oauth.accessToken,
388
+ expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt,
389
+ pageSize: parsePositiveIntegerOption(options.pageSize, "page-size"),
390
+ includeTranscriptEntries: options.transcriptEntries !== false,
391
+ allConferenceRecords: Boolean(options.allConferenceRecords),
392
+ includeDocumentBodies: Boolean(options.includeDocBodies),
393
+ mergeDuplicateParticipants: options.mergeDuplicates !== false,
394
+ lateAfterMinutes: parseOptionalNumber(options.lateAfterMinutes),
395
+ earlyBeforeMinutes: parseOptionalNumber(options.earlyBeforeMinutes)
396
+ };
397
+ }
398
+ function hasCreateOAuth(config, options) {
399
+ return Boolean(options.accessToken?.trim() || options.refreshToken?.trim() || config.oauth.accessToken || config.oauth.refreshToken);
217
400
  }
218
- //#endregion
219
- //#region extensions/google-meet/src/cli-export.ts
220
401
  function writeArtifactsSummary(result) {
221
402
  if (result.input) writeStdoutLine("input: %s", result.input);
222
403
  if (result.space) writeStdoutLine("space: %s", result.space.name);
@@ -392,12 +573,9 @@ function renderAttendanceMarkdown(result) {
392
573
  }
393
574
  return `${lines.join("\n")}\n`;
394
575
  }
395
- function neutralizeSpreadsheetFormulaCell(text) {
396
- return /^[ \t\r\n]*[=+\-@\uFF0B\uFF0D\uFF1D\uFF20]/u.test(text) || /^[\t\r\n]/.test(text) ? `'${text}` : text;
397
- }
398
576
  function csvCell(value) {
399
- const safeText = neutralizeSpreadsheetFormulaCell(value === void 0 || value === null ? "" : typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value));
400
- return /[",\r\n]/.test(safeText) ? `"${safeText.replaceAll("\"", "\"\"")}"` : safeText;
577
+ const text = value === void 0 || value === null ? "" : typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value);
578
+ return /[",\n]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
401
579
  }
402
580
  function renderAttendanceCsv(result) {
403
581
  const rows = [[
@@ -543,6 +721,81 @@ function googleMeetExportFileNames() {
543
721
  function defaultExportDirectory() {
544
722
  return `google-meet-export-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
545
723
  }
724
+ const CRC32_TABLE = new Uint32Array(Array.from({ length: 256 }, (_, index) => {
725
+ let value = index;
726
+ for (let bit = 0; bit < 8; bit += 1) value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
727
+ return value >>> 0;
728
+ }));
729
+ function crc32(buffer) {
730
+ let value = 4294967295;
731
+ for (const byte of buffer) value = CRC32_TABLE[(value ^ byte) & 255] ^ value >>> 8;
732
+ return (value ^ 4294967295) >>> 0;
733
+ }
734
+ function dosDateTime(date = /* @__PURE__ */ new Date()) {
735
+ return {
736
+ time: date.getHours() << 11 | date.getMinutes() << 5 | Math.floor(date.getSeconds() / 2),
737
+ date: date.getFullYear() - 1980 << 9 | date.getMonth() + 1 << 5 | date.getDate()
738
+ };
739
+ }
740
+ function buildZipArchive(files) {
741
+ const localParts = [];
742
+ const centralParts = [];
743
+ let offset = 0;
744
+ const stamp = dosDateTime();
745
+ for (const file of files) {
746
+ const name = Buffer.from(file.name, "utf8");
747
+ const content = Buffer.from(file.content, "utf8");
748
+ const checksum = crc32(content);
749
+ const local = Buffer.alloc(30);
750
+ local.writeUInt32LE(67324752, 0);
751
+ local.writeUInt16LE(20, 4);
752
+ local.writeUInt16LE(0, 6);
753
+ local.writeUInt16LE(0, 8);
754
+ local.writeUInt16LE(stamp.time, 10);
755
+ local.writeUInt16LE(stamp.date, 12);
756
+ local.writeUInt32LE(checksum, 14);
757
+ local.writeUInt32LE(content.length, 18);
758
+ local.writeUInt32LE(content.length, 22);
759
+ local.writeUInt16LE(name.length, 26);
760
+ local.writeUInt16LE(0, 28);
761
+ localParts.push(local, name, content);
762
+ const central = Buffer.alloc(46);
763
+ central.writeUInt32LE(33639248, 0);
764
+ central.writeUInt16LE(20, 4);
765
+ central.writeUInt16LE(20, 6);
766
+ central.writeUInt16LE(0, 8);
767
+ central.writeUInt16LE(0, 10);
768
+ central.writeUInt16LE(stamp.time, 12);
769
+ central.writeUInt16LE(stamp.date, 14);
770
+ central.writeUInt32LE(checksum, 16);
771
+ central.writeUInt32LE(content.length, 20);
772
+ central.writeUInt32LE(content.length, 24);
773
+ central.writeUInt16LE(name.length, 28);
774
+ central.writeUInt16LE(0, 30);
775
+ central.writeUInt16LE(0, 32);
776
+ central.writeUInt16LE(0, 34);
777
+ central.writeUInt16LE(0, 36);
778
+ central.writeUInt32LE(0, 38);
779
+ central.writeUInt32LE(offset, 42);
780
+ centralParts.push(central, name);
781
+ offset += local.length + name.length + content.length;
782
+ }
783
+ const centralDirectory = Buffer.concat(centralParts);
784
+ const end = Buffer.alloc(22);
785
+ end.writeUInt32LE(101010256, 0);
786
+ end.writeUInt16LE(0, 4);
787
+ end.writeUInt16LE(0, 6);
788
+ end.writeUInt16LE(files.length, 8);
789
+ end.writeUInt16LE(files.length, 10);
790
+ end.writeUInt32LE(centralDirectory.length, 12);
791
+ end.writeUInt32LE(offset, 16);
792
+ end.writeUInt16LE(0, 20);
793
+ return Buffer.concat([
794
+ ...localParts,
795
+ centralDirectory,
796
+ end
797
+ ]);
798
+ }
546
799
  async function writeMeetExportBundle(params) {
547
800
  const outputDir = params.outputDir?.trim() || defaultExportDirectory();
548
801
  await mkdir(outputDir, { recursive: true });
@@ -588,319 +841,163 @@ async function writeMeetExportBundle(params) {
588
841
  files: files.map((file) => path.join(outputDir, file.name))
589
842
  };
590
843
  if (zipFile) {
591
- const zip = new JSZip();
592
- for (const file of files) zip.file(file.name, file.content);
593
- await writeFile(zipFile, await zip.generateAsync({ type: "nodebuffer" }));
844
+ await writeFile(zipFile, buildZipArchive(files));
594
845
  result.zipFile = zipFile;
595
846
  }
596
847
  return result;
597
848
  }
598
- //#endregion
599
- //#region extensions/google-meet/src/cli-artifact-commands.ts
600
- function registerGoogleMeetArtifactCommands(context) {
601
- const params = context;
602
- const { root, resolveArtifactTokenOptions, resolveMeetingForToken } = context;
603
- root.command("artifacts").description("List Meet conference records and available participant/artifact metadata").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--conference-record <name>", "Conference record name or id").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--page-size <n>", "Max resources per Meet API page").option("--all-conference-records", "Fetch every conference record for --meeting").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) => {
604
- const resolved = resolveArtifactTokenOptions(params.config, options);
605
- const token = await resolveGoogleMeetAccessToken(resolved);
606
- const meeting = resolved.conferenceRecord ? resolved.meeting : (await resolveMeetingForToken({
607
- config: params.config,
608
- options,
609
- accessToken: token.accessToken,
610
- configuredMeeting: resolved.meeting
611
- })).meeting;
612
- const result = await fetchGoogleMeetArtifacts({
613
- accessToken: token.accessToken,
614
- meeting,
615
- conferenceRecord: resolved.conferenceRecord,
616
- pageSize: resolved.pageSize,
617
- includeTranscriptEntries: resolved.includeTranscriptEntries,
618
- allConferenceRecords: resolved.allConferenceRecords,
619
- includeDocumentBodies: resolved.includeDocumentBodies
849
+ function registerGoogleMeetCli(params) {
850
+ const callGateway = params.callGatewayFromCli ?? callGatewayFromCli;
851
+ const operationTimeoutMs = resolveGoogleMeetGatewayOperationTimeoutMs(params.config);
852
+ const root = params.program.command("googlemeet").description("Google Meet participant utilities").addHelpText("after", () => `\nDocs: https://docs.openclaw.ai/plugins/google-meet\n`);
853
+ root.command("auth").description("Google Meet OAuth helpers").command("login").description("Run a PKCE OAuth flow and print refresh-token JSON to store in plugin config").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--manual", "Use copy/paste callback flow instead of localhost callback").option("--json", "Print the token payload as JSON", false).option("--timeout-sec <n>", "Local callback timeout in seconds", "300").action(async (options) => {
854
+ const clientId = options.clientId?.trim() || params.config.oauth.clientId;
855
+ const clientSecret = options.clientSecret?.trim() || params.config.oauth.clientSecret;
856
+ if (!clientId) throw new Error("Missing Google Meet OAuth client id. Configure oauth.clientId or pass --client-id.");
857
+ const { verifier, challenge } = createGoogleMeetPkce();
858
+ const state = createGoogleMeetOAuthState();
859
+ const authUrl = buildGoogleMeetAuthUrl({
860
+ clientId,
861
+ challenge,
862
+ state
620
863
  });
621
- if (options.json) {
622
- await writeCliOutput(options, JSON.stringify({
623
- ...result,
624
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
625
- }, null, 2));
626
- return;
627
- }
628
- if (options.format === "markdown") {
629
- await writeCliOutput(options, renderArtifactsMarkdown(result));
630
- return;
631
- }
632
- if (options.format && options.format !== "summary") throw new Error("Unsupported format. Expected summary or markdown.");
633
- writeArtifactsSummary(result);
634
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
635
- });
636
- root.command("attendance").description("List Meet participants and participant sessions").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--conference-record <name>", "Conference record name or id").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--page-size <n>", "Max resources per Meet API page").option("--all-conference-records", "Fetch every conference record for --meeting").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) => {
637
- const resolved = resolveArtifactTokenOptions(params.config, options);
638
- const token = await resolveGoogleMeetAccessToken(resolved);
639
- const meeting = resolved.conferenceRecord ? resolved.meeting : (await resolveMeetingForToken({
640
- config: params.config,
641
- options,
642
- accessToken: token.accessToken,
643
- configuredMeeting: resolved.meeting
644
- })).meeting;
645
- const result = await fetchGoogleMeetAttendance({
646
- accessToken: token.accessToken,
647
- meeting,
648
- conferenceRecord: resolved.conferenceRecord,
649
- pageSize: resolved.pageSize,
650
- allConferenceRecords: resolved.allConferenceRecords,
651
- mergeDuplicateParticipants: resolved.mergeDuplicateParticipants,
652
- lateAfterMinutes: resolved.lateAfterMinutes,
653
- earlyBeforeMinutes: resolved.earlyBeforeMinutes
864
+ const code = await waitForGoogleMeetAuthCode({
865
+ state,
866
+ manual: Boolean(options.manual),
867
+ timeoutMs: resolveGoogleMeetOAuthCallbackTimeoutMs(options.timeoutSec),
868
+ authUrl,
869
+ promptInput,
870
+ writeLine: (message) => writeStdoutLine("%s", message)
654
871
  });
655
- if (options.json) {
656
- await writeCliOutput(options, JSON.stringify({
657
- ...result,
658
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
659
- }, null, 2));
660
- return;
661
- }
662
- if (options.format === "markdown") {
663
- await writeCliOutput(options, renderAttendanceMarkdown(result));
664
- return;
665
- }
666
- if (options.format === "csv") {
667
- await writeCliOutput(options, renderAttendanceCsv(result));
668
- return;
669
- }
670
- if (options.format && options.format !== "summary") throw new Error("Unsupported format. Expected summary, markdown, or csv.");
671
- writeAttendanceSummary(result);
672
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
673
- });
674
- root.command("export").description("Write Meet artifacts, attendance, transcript, and raw JSON into a folder").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--conference-record <name>", "Conference record name or id").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--page-size <n>", "Max resources per Meet API page").option("--all-conference-records", "Fetch every conference record for --meeting").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) => {
675
- const resolved = resolveArtifactTokenOptions(params.config, options);
676
- const token = await resolveGoogleMeetAccessToken(resolved);
677
- const meetingResult = resolved.conferenceRecord ? { meeting: resolved.meeting } : await resolveMeetingForToken({
678
- config: params.config,
679
- options,
680
- accessToken: token.accessToken,
681
- configuredMeeting: resolved.meeting
682
- });
683
- const artifacts = await fetchGoogleMeetArtifacts({
684
- accessToken: token.accessToken,
685
- meeting: meetingResult.meeting,
686
- conferenceRecord: resolved.conferenceRecord,
687
- pageSize: resolved.pageSize,
688
- includeTranscriptEntries: resolved.includeTranscriptEntries,
689
- allConferenceRecords: resolved.allConferenceRecords,
690
- includeDocumentBodies: resolved.includeDocumentBodies
691
- });
692
- const attendance = await fetchGoogleMeetAttendance({
693
- accessToken: token.accessToken,
694
- meeting: meetingResult.meeting,
695
- conferenceRecord: resolved.conferenceRecord,
696
- pageSize: resolved.pageSize,
697
- allConferenceRecords: resolved.allConferenceRecords,
698
- mergeDuplicateParticipants: resolved.mergeDuplicateParticipants,
699
- lateAfterMinutes: resolved.lateAfterMinutes,
700
- earlyBeforeMinutes: resolved.earlyBeforeMinutes
701
- });
702
- const resolvedMeeting = meetingResult.meeting ?? resolved.meeting;
703
- const request = {
704
- ...resolvedMeeting ? { meeting: resolvedMeeting } : {},
705
- ...resolved.conferenceRecord ? { conferenceRecord: resolved.conferenceRecord } : {},
706
- ...meetingResult.calendarEvent?.event.id ? { calendarEventId: meetingResult.calendarEvent.event.id } : {},
707
- ...meetingResult.calendarEvent?.event.summary ? { calendarEventSummary: meetingResult.calendarEvent.event.summary } : {},
708
- ...options.calendar ? { calendarId: options.calendar } : {},
709
- ...resolved.pageSize !== void 0 ? { pageSize: resolved.pageSize } : {},
710
- includeTranscriptEntries: resolved.includeTranscriptEntries,
711
- includeDocumentBodies: resolved.includeDocumentBodies,
712
- allConferenceRecords: resolved.allConferenceRecords,
713
- mergeDuplicateParticipants: resolved.mergeDuplicateParticipants,
714
- ...resolved.lateAfterMinutes !== void 0 ? { lateAfterMinutes: resolved.lateAfterMinutes } : {},
715
- ...resolved.earlyBeforeMinutes !== void 0 ? { earlyBeforeMinutes: resolved.earlyBeforeMinutes } : {}
716
- };
717
- if (options.dryRun) {
718
- writeStdoutJson({
719
- dryRun: true,
720
- manifest: buildGoogleMeetExportManifest({
721
- artifacts,
722
- attendance,
723
- files: googleMeetExportFileNames(),
724
- request,
725
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
726
- ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {}
727
- }),
728
- ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {},
729
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
730
- });
731
- return;
732
- }
733
- const bundle = await writeMeetExportBundle({
734
- outputDir: options.output,
735
- artifacts,
736
- attendance,
737
- zip: Boolean(options.zip),
738
- request,
739
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
740
- ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {}
741
- });
742
- const payload = {
743
- ...bundle,
744
- ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {},
745
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
746
- };
747
- if (options.json) {
748
- writeStdoutJson(payload);
749
- return;
750
- }
751
- writeStdoutLine("export: %s", bundle.outputDir);
752
- for (const file of bundle.files) writeStdoutLine("- %s", file);
753
- if (bundle.zipFile) writeStdoutLine("zip: %s", bundle.zipFile);
754
- });
755
- }
756
- //#endregion
757
- //#region extensions/google-meet/src/cli-doctor.ts
758
- function sanitizeOAuthErrorMessage(error) {
759
- 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]");
760
- }
761
- async function buildOAuthDoctorReport(config, options) {
762
- const clientId = options.clientId?.trim() || config.oauth.clientId;
763
- const clientSecret = options.clientSecret?.trim() || config.oauth.clientSecret;
764
- const refreshToken = options.refreshToken?.trim() || config.oauth.refreshToken;
765
- const accessToken = options.accessToken?.trim() || config.oauth.accessToken;
766
- const expiresAt = parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt;
767
- const checks = [];
768
- const hasRefreshConfig = Boolean(clientId && refreshToken);
769
- if (!hasRefreshConfig && !Boolean(accessToken)) {
770
- checks.push({
771
- id: "oauth-config",
772
- ok: false,
773
- message: "Missing Google Meet OAuth credentials. Configure oauth.clientId and oauth.refreshToken, or pass --client-id and --refresh-token."
774
- });
775
- return {
776
- ok: false,
777
- configured: false,
778
- checks
779
- };
780
- }
781
- checks.push({
782
- id: "oauth-config",
783
- ok: true,
784
- message: hasRefreshConfig ? "Google Meet OAuth refresh credentials are configured" : "Google Meet cached access token is configured"
785
- });
786
- let token;
787
- try {
788
- token = await resolveGoogleMeetAccessToken({
872
+ const tokens = await exchangeGoogleMeetAuthCode({
789
873
  clientId,
790
874
  clientSecret,
791
- refreshToken,
792
- accessToken,
793
- expiresAt
794
- });
795
- checks.push({
796
- id: "oauth-token",
797
- ok: true,
798
- message: token.refreshed ? "Refresh token minted an access token" : "Cached access token is still valid"
799
- });
800
- } catch (error) {
801
- checks.push({
802
- id: "oauth-token",
803
- ok: false,
804
- message: sanitizeOAuthErrorMessage(error)
875
+ code,
876
+ verifier
805
877
  });
806
- return {
807
- ok: false,
808
- configured: true,
809
- checks
878
+ if (!tokens.refreshToken) throw new Error("Google OAuth did not return a refresh token. Re-run the flow with consent and offline access.");
879
+ const payload = {
880
+ oauth: {
881
+ clientId,
882
+ ...clientSecret ? { clientSecret } : {},
883
+ refreshToken: tokens.refreshToken,
884
+ accessToken: tokens.accessToken,
885
+ expiresAt: tokens.expiresAt
886
+ },
887
+ scope: tokens.scope,
888
+ tokenType: tokens.tokenType
810
889
  };
811
- }
812
- const report = {
813
- ok: true,
814
- configured: true,
815
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
816
- expiresAt: token.expiresAt,
817
- checks
818
- };
819
- const meeting = options.meeting?.trim();
820
- if (meeting) try {
821
- const space = await fetchGoogleMeetSpace({
822
- accessToken: token.accessToken,
823
- meeting
824
- });
825
- checks.push({
826
- id: "meet-spaces-get",
827
- ok: true,
828
- message: `Resolved ${space.name}`
829
- });
830
- report.meetingUri = space.meetingUri;
831
- } catch (error) {
832
- checks.push({
833
- id: "meet-spaces-get",
834
- ok: false,
835
- message: sanitizeOAuthErrorMessage(error)
836
- });
837
- }
838
- if (options.createSpace) try {
839
- const created = await createGoogleMeetSpace({ accessToken: token.accessToken });
840
- checks.push({
841
- id: "meet-spaces-create",
842
- ok: true,
843
- message: `Created ${created.space.name}`
844
- });
845
- report.createdSpace = created.space.name;
846
- report.meetingUri = created.meetingUri;
847
- } catch (error) {
848
- checks.push({
849
- id: "meet-spaces-create",
850
- ok: false,
851
- message: sanitizeOAuthErrorMessage(error)
852
- });
853
- }
854
- report.ok = checks.every((check) => check.ok);
855
- return report;
856
- }
857
- function writeOAuthDoctorReport(report) {
858
- writeStdoutLine("Google Meet OAuth: %s", report.ok ? "OK" : "needs attention");
859
- writeStdoutLine("configured: %s", report.configured ? "yes" : "no");
860
- if (report.tokenSource) writeStdoutLine("token source: %s", report.tokenSource);
861
- if (report.meetingUri) writeStdoutLine("meeting uri: %s", report.meetingUri);
862
- for (const check of report.checks) writeStdoutLine("[%s] %s: %s", check.ok ? "ok" : "fail", check.id, check.message);
863
- }
864
- function registerGoogleMeetDoctorCommand(context) {
865
- const params = context;
866
- const { root, callGateway } = context;
867
- 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("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (sessionId, options) => {
868
- if (options.oauth) {
869
- const report = await buildOAuthDoctorReport(params.config, options);
870
- if (options.json) {
871
- writeStdoutJson(report);
890
+ if (!options.json) writeStdoutLine("Paste this into plugins.entries.google-meet.config:");
891
+ writeStdoutJson(payload);
892
+ });
893
+ root.command("create").description("Create a new Google Meet space and print its meeting URL").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--access-type <type>", "Google Meet SpaceConfig accessType for API create: OPEN, TRUSTED, or RESTRICTED").option("--entry-point-access <type>", "Google Meet SpaceConfig entryPointAccess for API create: ALL or CREATOR_APP_ONLY").option("--no-join", "Only create the meeting URL; do not join it").option("--transport <transport>", "Join transport: chrome, chrome-node, or twilio").option("--mode <mode>", "Join 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").option("--json", "Print JSON output", false).action(async (options) => {
894
+ if (options.join !== false) {
895
+ const delegated = await callGoogleMeetGateway({
896
+ callGateway,
897
+ method: "googlemeet.create",
898
+ payload: { ...options },
899
+ timeoutMs: operationTimeoutMs
900
+ });
901
+ if (delegated.ok) {
902
+ const payload = delegated.payload;
903
+ if (options.json) {
904
+ writeStdoutJson(payload);
905
+ return;
906
+ }
907
+ writeStdoutLine("meeting uri: %s", payload.meetingUri);
908
+ if (payload.space?.name) writeStdoutLine("space: %s", payload.space.name);
909
+ if (payload.space?.meetingCode) writeStdoutLine("meeting code: %s", payload.space.meetingCode);
910
+ if (payload.source) writeStdoutLine("source: %s", payload.source);
911
+ if (payload.browser?.nodeId) writeStdoutLine("node: %s", payload.browser.nodeId);
912
+ if (payload.tokenSource) writeStdoutLine("token source: %s", payload.tokenSource);
913
+ if (payload.joined && payload.join?.session?.id) writeStdoutLine("joined: %s", payload.join.session.id);
914
+ else writeStdoutLine("joined: no (run `openclaw googlemeet join %s`)", payload.meetingUri);
872
915
  return;
873
916
  }
874
- writeOAuthDoctorReport(report);
875
- return;
876
917
  }
877
- const delegated = await callGoogleMeetGateway({
878
- callGateway,
879
- method: "googlemeet.status",
880
- payload: { sessionId }
881
- });
882
- if (delegated.ok) {
883
- const status = delegated.payload;
918
+ if (!hasCreateOAuth(params.config, options)) {
919
+ if (hasCreateSpaceConfigInput(options)) throw new Error("Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove --access-type/--entry-point-access.");
920
+ const rt = await params.ensureRuntime();
921
+ const result = await rt.createViaBrowser();
922
+ const join = options.join !== false ? await rt.join({
923
+ url: result.meetingUri,
924
+ transport: options.transport,
925
+ mode: options.mode,
926
+ message: options.message,
927
+ dialInNumber: options.dialInNumber,
928
+ pin: options.pin,
929
+ dtmfSequence: options.dtmfSequence
930
+ }) : void 0;
931
+ const payload = {
932
+ source: result.source,
933
+ meetingUri: result.meetingUri,
934
+ joined: Boolean(join),
935
+ ...join ? { join } : {},
936
+ browser: {
937
+ nodeId: result.nodeId,
938
+ targetId: result.targetId,
939
+ browserUrl: result.browserUrl,
940
+ browserTitle: result.browserTitle
941
+ }
942
+ };
884
943
  if (options.json) {
885
- writeStdoutJson(status);
944
+ writeStdoutJson(payload);
886
945
  return;
887
946
  }
888
- writeDoctorStatus(status);
947
+ writeStdoutLine("meeting uri: %s", result.meetingUri);
948
+ writeStdoutLine("source: browser");
949
+ writeStdoutLine("node: %s", result.nodeId);
950
+ if (join) writeStdoutLine("joined: %s", join.session.id);
951
+ else writeStdoutLine("joined: no (run `openclaw googlemeet join %s`)", result.meetingUri);
889
952
  return;
890
953
  }
891
- const status = await (await params.ensureRuntime()).status(sessionId);
954
+ const token = await resolveGoogleMeetAccessToken(resolveCreateTokenOptions(params.config, options));
955
+ const result = await createGoogleMeetSpace({
956
+ accessToken: token.accessToken,
957
+ config: resolveCreateSpaceConfig(options)
958
+ });
959
+ const join = options.join !== false ? await (await params.ensureRuntime()).join({
960
+ url: result.meetingUri,
961
+ transport: options.transport,
962
+ mode: options.mode,
963
+ message: options.message,
964
+ dialInNumber: options.dialInNumber,
965
+ pin: options.pin,
966
+ dtmfSequence: options.dtmfSequence
967
+ }) : void 0;
892
968
  if (options.json) {
893
- writeStdoutJson(status);
969
+ writeStdoutJson({
970
+ ...result,
971
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
972
+ joined: Boolean(join),
973
+ ...join ? { join } : {}
974
+ });
894
975
  return;
895
976
  }
896
- writeDoctorStatus(status);
977
+ writeStdoutLine("meeting uri: %s", result.meetingUri);
978
+ writeStdoutLine("space: %s", result.space.name);
979
+ if (result.space.meetingCode) writeStdoutLine("meeting code: %s", result.space.meetingCode);
980
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
981
+ if (join) writeStdoutLine("joined: %s", join.session.id);
982
+ else writeStdoutLine("joined: no (run `openclaw googlemeet join %s`)", result.meetingUri);
983
+ });
984
+ 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("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (meeting, options) => {
985
+ const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
986
+ const result = await endGoogleMeetActiveConference({
987
+ accessToken: token.accessToken,
988
+ meeting: resolveMeetingInput(params.config, meeting ?? options.meeting)
989
+ });
990
+ if (options.json) {
991
+ writeStdoutJson({
992
+ ...result,
993
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
994
+ });
995
+ return;
996
+ }
997
+ writeStdoutLine("space: %s", result.space);
998
+ writeStdoutLine("ended: yes");
999
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
897
1000
  });
898
- }
899
- //#endregion
900
- //#region extensions/google-meet/src/cli-runtime-commands.ts
901
- function registerGoogleMeetProbeCommands(context) {
902
- const params = context;
903
- const { root, callGateway, operationTimeoutMs, resolveMeetingInput } = context;
904
1001
  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) => {
905
1002
  const payload = {
906
1003
  url: resolveMeetingInput(params.config, url),
@@ -961,460 +1058,354 @@ function registerGoogleMeetProbeCommands(context) {
961
1058
  }
962
1059
  writeStdoutJson(await (await params.ensureRuntime()).testListen(payload));
963
1060
  });
964
- }
965
- function registerGoogleMeetSessionCommands(context) {
966
- const params = context;
967
- const { root, callGateway } = context;
968
- root.command("status").argument("[session-id]", "Meet session ID").option("--json", "Print JSON output", false).action(async (sessionId) => {
969
- const delegated = await callGoogleMeetGateway({
970
- callGateway,
971
- method: "googlemeet.status",
972
- payload: { sessionId }
1061
+ root.command("resolve-space").description("Resolve a Meet URL, meeting code, or spaces/{id} to its canonical space").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1062
+ const resolved = resolveTokenOptions(params.config, options);
1063
+ const token = await resolveGoogleMeetAccessToken(resolved);
1064
+ const space = await fetchGoogleMeetSpace({
1065
+ accessToken: token.accessToken,
1066
+ meeting: resolved.meeting
973
1067
  });
974
- if (delegated.ok) {
975
- writeStdoutJson(delegated.payload);
1068
+ if (options.json) {
1069
+ writeStdoutJson(space);
976
1070
  return;
977
1071
  }
978
- writeStdoutJson(await (await params.ensureRuntime()).status(sessionId));
1072
+ writeStdoutLine("input: %s", resolved.meeting);
1073
+ writeStdoutLine("space: %s", space.name);
1074
+ if (space.meetingCode) writeStdoutLine("meeting code: %s", space.meetingCode);
1075
+ if (space.meetingUri) writeStdoutLine("meeting uri: %s", space.meetingUri);
1076
+ writeStdoutLine("active conference: %s", space.activeConference ? "yes" : "no");
1077
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
979
1078
  });
980
- 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) => {
981
- const sinceIndex = parseStrictNonNegativeInteger(options.since);
982
- if (options.since !== void 0 && sinceIndex === void 0) throw new Error("--since must be a non-negative safe integer");
983
- const delegated = await callGoogleMeetGateway({
984
- callGateway,
985
- method: "googlemeet.transcript",
986
- payload: {
987
- sessionId,
988
- ...sinceIndex === void 0 ? {} : { sinceIndex }
989
- }
1079
+ root.command("preflight").description("Validate OAuth + meeting resolution prerequisites for Meet media work").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1080
+ const resolved = resolveTokenOptions(params.config, options);
1081
+ const token = await resolveGoogleMeetAccessToken(resolved);
1082
+ const space = await fetchGoogleMeetSpace({
1083
+ accessToken: token.accessToken,
1084
+ meeting: resolved.meeting
1085
+ });
1086
+ const report = buildGoogleMeetPreflightReport({
1087
+ input: resolved.meeting,
1088
+ space,
1089
+ previewAcknowledged: params.config.preview.enrollmentAcknowledged,
1090
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
990
1091
  });
991
- const result = delegated.ok ? delegated.payload : await (await params.ensureRuntime()).transcript(sessionId, sinceIndex === void 0 ? {} : { sinceIndex });
992
- if (!result.found) throw new Error("session not found");
993
1092
  if (options.json) {
994
- writeStdoutJson(result);
1093
+ writeStdoutJson(report);
995
1094
  return;
996
1095
  }
997
- if (result.evicted) writeStdoutLine("# transcript evicted from runtime memory");
998
- else if (result.droppedLines) writeStdoutLine("# %d earlier lines dropped by the transcript cap", result.droppedLines);
999
- for (const line of result.lines ?? []) writeStdoutLine("%s%s%s", line.at ? `[${line.at}] ` : "", line.speaker ? `${line.speaker}: ` : "", line.text);
1000
- writeStdoutLine("# nextIndex: %d", result.nextIndex ?? 0);
1096
+ writeStdoutLine("input: %s", report.input);
1097
+ writeStdoutLine("resolved space: %s", report.resolvedSpaceName);
1098
+ if (report.meetingCode) writeStdoutLine("meeting code: %s", report.meetingCode);
1099
+ if (report.meetingUri) writeStdoutLine("meeting uri: %s", report.meetingUri);
1100
+ writeStdoutLine("active conference: %s", report.hasActiveConference ? "yes" : "no");
1101
+ writeStdoutLine("preview acknowledged: %s", report.previewAcknowledged ? "yes" : "no");
1102
+ writeStdoutLine("token source: %s", report.tokenSource);
1103
+ if (report.blockers.length === 0) {
1104
+ writeStdoutLine("blockers: none");
1105
+ return;
1106
+ }
1107
+ writeStdoutLine("blockers:");
1108
+ for (const blocker of report.blockers) writeStdoutLine("- %s", blocker);
1001
1109
  });
1002
- }
1003
- function registerGoogleMeetLifecycleCommands(context) {
1004
- const params = context;
1005
- const { root, callGateway } = context;
1006
- 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) => {
1007
- const result = await (await params.ensureRuntime()).recoverCurrentTab({
1008
- url,
1009
- transport: options.transport
1110
+ root.command("latest").description("Find the latest Meet conference record for a meeting").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1111
+ const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
1112
+ const resolved = await resolveMeetingForToken({
1113
+ config: params.config,
1114
+ options,
1115
+ accessToken: token.accessToken,
1116
+ configuredMeeting: options.meeting?.trim()
1117
+ });
1118
+ const result = await fetchLatestGoogleMeetConferenceRecord({
1119
+ accessToken: token.accessToken,
1120
+ meeting: resolved.meeting
1121
+ });
1122
+ if (options.json) {
1123
+ writeStdoutJson({
1124
+ ...result,
1125
+ ...resolved.calendarEvent ? { calendarEvent: resolved.calendarEvent } : {},
1126
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1127
+ });
1128
+ return;
1129
+ }
1130
+ if (resolved.calendarEvent) {
1131
+ writeStdoutLine("calendar event: %s", resolved.calendarEvent.event.summary ?? "untitled");
1132
+ writeStdoutLine("calendar meet: %s", resolved.calendarEvent.meetingUri);
1133
+ }
1134
+ writeLatestConferenceRecordSummary(result);
1135
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1136
+ });
1137
+ 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("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1138
+ const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
1139
+ const window = options.today ? buildGoogleMeetCalendarDayWindow() : {};
1140
+ const result = await listGoogleMeetCalendarEvents({
1141
+ accessToken: token.accessToken,
1142
+ calendarId: options.calendar,
1143
+ eventQuery: options.event,
1144
+ ...window
1145
+ });
1146
+ const payload = {
1147
+ ...result,
1148
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1149
+ };
1150
+ if (options.json) {
1151
+ writeStdoutJson(payload);
1152
+ return;
1153
+ }
1154
+ writeCalendarEventsSummary(result);
1155
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1156
+ });
1157
+ root.command("artifacts").description("List Meet conference records and available participant/artifact metadata").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--conference-record <name>", "Conference record name or id").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--page-size <n>", "Max resources per Meet API page").option("--all-conference-records", "Fetch every conference record for --meeting").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) => {
1158
+ const resolved = resolveArtifactTokenOptions(params.config, options);
1159
+ const token = await resolveGoogleMeetAccessToken(resolved);
1160
+ const meeting = resolved.conferenceRecord ? resolved.meeting : (await resolveMeetingForToken({
1161
+ config: params.config,
1162
+ options,
1163
+ accessToken: token.accessToken,
1164
+ configuredMeeting: resolved.meeting
1165
+ })).meeting;
1166
+ const result = await fetchGoogleMeetArtifacts({
1167
+ accessToken: token.accessToken,
1168
+ meeting,
1169
+ conferenceRecord: resolved.conferenceRecord,
1170
+ pageSize: resolved.pageSize,
1171
+ includeTranscriptEntries: resolved.includeTranscriptEntries,
1172
+ allConferenceRecords: resolved.allConferenceRecords,
1173
+ includeDocumentBodies: resolved.includeDocumentBodies
1010
1174
  });
1011
1175
  if (options.json) {
1012
- writeStdoutJson(result);
1176
+ await writeCliOutput(options, JSON.stringify({
1177
+ ...result,
1178
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1179
+ }, null, 2));
1013
1180
  return;
1014
1181
  }
1015
- writeRecoverCurrentTabResult(result);
1182
+ if (options.format === "markdown") {
1183
+ await writeCliOutput(options, renderArtifactsMarkdown(result));
1184
+ return;
1185
+ }
1186
+ if (options.format && options.format !== "summary") throw new Error("Unsupported format. Expected summary or markdown.");
1187
+ writeArtifactsSummary(result);
1188
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1016
1189
  });
1017
- 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) => {
1018
- const status = await (await params.ensureRuntime()).setupStatus({
1019
- transport: options.transport,
1020
- mode: options.mode
1190
+ root.command("attendance").description("List Meet participants and participant sessions").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--conference-record <name>", "Conference record name or id").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--page-size <n>", "Max resources per Meet API page").option("--all-conference-records", "Fetch every conference record for --meeting").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) => {
1191
+ const resolved = resolveArtifactTokenOptions(params.config, options);
1192
+ const token = await resolveGoogleMeetAccessToken(resolved);
1193
+ const meeting = resolved.conferenceRecord ? resolved.meeting : (await resolveMeetingForToken({
1194
+ config: params.config,
1195
+ options,
1196
+ accessToken: token.accessToken,
1197
+ configuredMeeting: resolved.meeting
1198
+ })).meeting;
1199
+ const result = await fetchGoogleMeetAttendance({
1200
+ accessToken: token.accessToken,
1201
+ meeting,
1202
+ conferenceRecord: resolved.conferenceRecord,
1203
+ pageSize: resolved.pageSize,
1204
+ allConferenceRecords: resolved.allConferenceRecords,
1205
+ mergeDuplicateParticipants: resolved.mergeDuplicateParticipants,
1206
+ lateAfterMinutes: resolved.lateAfterMinutes,
1207
+ earlyBeforeMinutes: resolved.earlyBeforeMinutes
1021
1208
  });
1022
1209
  if (options.json) {
1023
- writeStdoutJson(status);
1210
+ await writeCliOutput(options, JSON.stringify({
1211
+ ...result,
1212
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1213
+ }, null, 2));
1024
1214
  return;
1025
1215
  }
1026
- writeSetupStatus(status);
1027
- });
1028
- root.command("leave").argument("<session-id>", "Meet session ID").action(async (sessionId) => {
1029
- const delegated = await callGoogleMeetGateway({
1030
- callGateway,
1031
- method: "googlemeet.leave",
1032
- payload: { sessionId }
1033
- });
1034
- if (delegated.ok) {
1035
- const result = delegated.payload;
1036
- if (!result.found) throw new Error("session not found");
1037
- writeLeaveResult(sessionId, result);
1216
+ if (options.format === "markdown") {
1217
+ await writeCliOutput(options, renderAttendanceMarkdown(result));
1038
1218
  return;
1039
1219
  }
1040
- const result = await (await params.ensureRuntime()).leave(sessionId);
1041
- if (!result.found) throw new Error("session not found");
1042
- writeLeaveResult(sessionId, result);
1043
- });
1044
- root.command("speak").argument("<session-id>", "Meet session ID").argument("[message]", "Realtime instructions to speak now").action(async (sessionId, message) => {
1045
- const delegated = await callGoogleMeetGateway({
1046
- callGateway,
1047
- method: "googlemeet.speak",
1048
- payload: {
1049
- sessionId,
1050
- message
1051
- }
1052
- });
1053
- if (delegated.ok) {
1054
- const result = delegated.payload;
1055
- if (!result.found) throw new Error("session not found");
1056
- if (!result.spoken) throw new Error(result.session?.chrome?.health?.speechBlockedMessage ?? "session has no active realtime audio bridge");
1057
- writeStdoutLine("speaking on %s", sessionId);
1220
+ if (options.format === "csv") {
1221
+ await writeCliOutput(options, renderAttendanceCsv(result));
1058
1222
  return;
1059
1223
  }
1060
- const result = await (await params.ensureRuntime()).speak(sessionId, message);
1061
- if (!result.found) throw new Error("session not found");
1062
- if (!result.spoken) throw new Error(result.session?.chrome?.health?.speechBlockedMessage ?? "session has no active realtime audio bridge");
1063
- writeStdoutLine("speaking on %s", sessionId);
1224
+ if (options.format && options.format !== "summary") throw new Error("Unsupported format. Expected summary, markdown, or csv.");
1225
+ writeAttendanceSummary(result);
1226
+ writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1064
1227
  });
1065
- }
1066
- //#endregion
1067
- //#region extensions/google-meet/src/cli-space-commands.ts
1068
- function registerGoogleMeetCreateCommands(context) {
1069
- const params = context;
1070
- const { root, callGateway, operationTimeoutMs, hasCreateOAuth, resolveMeetingInput, resolveOAuthTokenOptions } = context;
1071
- root.command("create").description("Create a new Google Meet space and print its meeting URL").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--access-type <type>", "Google Meet SpaceConfig accessType for API create: OPEN, TRUSTED, or RESTRICTED").option("--entry-point-access <type>", "Google Meet SpaceConfig entryPointAccess for API create: ALL or CREATOR_APP_ONLY").option("--no-join", "Only create the meeting URL; do not join it").option("--transport <transport>", "Join transport: chrome, chrome-node, or twilio").option("--mode <mode>", "Join 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").option("--json", "Print JSON output", false).action(async (options) => {
1072
- if (options.join !== false) {
1073
- const delegated = await callGoogleMeetGateway({
1074
- callGateway,
1075
- method: "googlemeet.create",
1076
- payload: { ...options },
1077
- timeoutMs: operationTimeoutMs
1228
+ root.command("export").description("Write Meet artifacts, attendance, transcript, and raw JSON into a folder").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--conference-record <name>", "Conference record name or id").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--page-size <n>", "Max resources per Meet API page").option("--all-conference-records", "Fetch every conference record for --meeting").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) => {
1229
+ const resolved = resolveArtifactTokenOptions(params.config, options);
1230
+ const token = await resolveGoogleMeetAccessToken(resolved);
1231
+ const meetingResult = resolved.conferenceRecord ? { meeting: resolved.meeting } : await resolveMeetingForToken({
1232
+ config: params.config,
1233
+ options,
1234
+ accessToken: token.accessToken,
1235
+ configuredMeeting: resolved.meeting
1236
+ });
1237
+ const artifacts = await fetchGoogleMeetArtifacts({
1238
+ accessToken: token.accessToken,
1239
+ meeting: meetingResult.meeting,
1240
+ conferenceRecord: resolved.conferenceRecord,
1241
+ pageSize: resolved.pageSize,
1242
+ includeTranscriptEntries: resolved.includeTranscriptEntries,
1243
+ allConferenceRecords: resolved.allConferenceRecords,
1244
+ includeDocumentBodies: resolved.includeDocumentBodies
1245
+ });
1246
+ const attendance = await fetchGoogleMeetAttendance({
1247
+ accessToken: token.accessToken,
1248
+ meeting: meetingResult.meeting,
1249
+ conferenceRecord: resolved.conferenceRecord,
1250
+ pageSize: resolved.pageSize,
1251
+ allConferenceRecords: resolved.allConferenceRecords,
1252
+ mergeDuplicateParticipants: resolved.mergeDuplicateParticipants,
1253
+ lateAfterMinutes: resolved.lateAfterMinutes,
1254
+ earlyBeforeMinutes: resolved.earlyBeforeMinutes
1255
+ });
1256
+ const resolvedMeeting = meetingResult.meeting ?? resolved.meeting;
1257
+ const request = {
1258
+ ...resolvedMeeting ? { meeting: resolvedMeeting } : {},
1259
+ ...resolved.conferenceRecord ? { conferenceRecord: resolved.conferenceRecord } : {},
1260
+ ...meetingResult.calendarEvent?.event.id ? { calendarEventId: meetingResult.calendarEvent.event.id } : {},
1261
+ ...meetingResult.calendarEvent?.event.summary ? { calendarEventSummary: meetingResult.calendarEvent.event.summary } : {},
1262
+ ...options.calendar ? { calendarId: options.calendar } : {},
1263
+ ...resolved.pageSize !== void 0 ? { pageSize: resolved.pageSize } : {},
1264
+ includeTranscriptEntries: resolved.includeTranscriptEntries,
1265
+ includeDocumentBodies: resolved.includeDocumentBodies,
1266
+ allConferenceRecords: resolved.allConferenceRecords,
1267
+ mergeDuplicateParticipants: resolved.mergeDuplicateParticipants,
1268
+ ...resolved.lateAfterMinutes !== void 0 ? { lateAfterMinutes: resolved.lateAfterMinutes } : {},
1269
+ ...resolved.earlyBeforeMinutes !== void 0 ? { earlyBeforeMinutes: resolved.earlyBeforeMinutes } : {}
1270
+ };
1271
+ if (options.dryRun) {
1272
+ writeStdoutJson({
1273
+ dryRun: true,
1274
+ manifest: buildGoogleMeetExportManifest({
1275
+ artifacts,
1276
+ attendance,
1277
+ files: googleMeetExportFileNames(),
1278
+ request,
1279
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
1280
+ ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {}
1281
+ }),
1282
+ ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {},
1283
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1078
1284
  });
1079
- if (delegated.ok) {
1080
- const payload = delegated.payload;
1081
- if (options.json) {
1082
- writeStdoutJson(payload);
1083
- return;
1084
- }
1085
- writeStdoutLine("meeting uri: %s", payload.meetingUri);
1086
- if (payload.space?.name) writeStdoutLine("space: %s", payload.space.name);
1087
- if (payload.space?.meetingCode) writeStdoutLine("meeting code: %s", payload.space.meetingCode);
1088
- if (payload.source) writeStdoutLine("source: %s", payload.source);
1089
- if (payload.browser?.nodeId) writeStdoutLine("node: %s", payload.browser.nodeId);
1090
- if (payload.tokenSource) writeStdoutLine("token source: %s", payload.tokenSource);
1091
- if (payload.joined && payload.join?.session?.id) writeStdoutLine("joined: %s", payload.join.session.id);
1092
- else writeStdoutLine("joined: no (run `openclaw googlemeet join %s`)", payload.meetingUri);
1093
- return;
1094
- }
1095
- }
1096
- if (!hasCreateOAuth(params.config, options)) {
1097
- if (hasCreateSpaceConfigInput(options)) throw new Error("Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove --access-type/--entry-point-access.");
1098
- const rt = await params.ensureRuntime();
1099
- const result = await rt.createViaBrowser();
1100
- const join = options.join !== false ? await rt.join({
1101
- url: result.meetingUri,
1102
- transport: options.transport,
1103
- mode: options.mode,
1104
- message: options.message,
1105
- dialInNumber: options.dialInNumber,
1106
- pin: options.pin,
1107
- dtmfSequence: options.dtmfSequence
1108
- }) : void 0;
1109
- const payload = {
1110
- source: result.source,
1111
- meetingUri: result.meetingUri,
1112
- joined: Boolean(join),
1113
- ...join ? { join } : {},
1114
- browser: {
1115
- nodeId: result.nodeId,
1116
- targetId: result.targetId,
1117
- browserUrl: result.browserUrl,
1118
- browserTitle: result.browserTitle
1119
- }
1120
- };
1121
- if (options.json) {
1122
- writeStdoutJson(payload);
1123
- return;
1124
- }
1125
- writeStdoutLine("meeting uri: %s", result.meetingUri);
1126
- writeStdoutLine("source: browser");
1127
- writeStdoutLine("node: %s", result.nodeId);
1128
- if (join) writeStdoutLine("joined: %s", join.session.id);
1129
- else writeStdoutLine("joined: no (run `openclaw googlemeet join %s`)", result.meetingUri);
1130
1285
  return;
1131
1286
  }
1132
- const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
1133
- const result = await createGoogleMeetSpace({
1134
- accessToken: token.accessToken,
1135
- config: resolveCreateSpaceConfig(options)
1287
+ const bundle = await writeMeetExportBundle({
1288
+ outputDir: options.output,
1289
+ artifacts,
1290
+ attendance,
1291
+ zip: Boolean(options.zip),
1292
+ request,
1293
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
1294
+ ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {}
1136
1295
  });
1137
- const join = options.join !== false ? await (await params.ensureRuntime()).join({
1138
- url: result.meetingUri,
1139
- transport: options.transport,
1140
- mode: options.mode,
1141
- message: options.message,
1142
- dialInNumber: options.dialInNumber,
1143
- pin: options.pin,
1144
- dtmfSequence: options.dtmfSequence
1145
- }) : void 0;
1296
+ const payload = {
1297
+ ...bundle,
1298
+ ...meetingResult.calendarEvent ? { calendarEvent: meetingResult.calendarEvent } : {},
1299
+ tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1300
+ };
1146
1301
  if (options.json) {
1147
- writeStdoutJson({
1148
- ...result,
1149
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
1150
- joined: Boolean(join),
1151
- ...join ? { join } : {}
1152
- });
1302
+ writeStdoutJson(payload);
1153
1303
  return;
1154
1304
  }
1155
- writeStdoutLine("meeting uri: %s", result.meetingUri);
1156
- writeStdoutLine("space: %s", result.space.name);
1157
- if (result.space.meetingCode) writeStdoutLine("meeting code: %s", result.space.meetingCode);
1158
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1159
- if (join) writeStdoutLine("joined: %s", join.session.id);
1160
- else writeStdoutLine("joined: no (run `openclaw googlemeet join %s`)", result.meetingUri);
1305
+ writeStdoutLine("export: %s", bundle.outputDir);
1306
+ for (const file of bundle.files) writeStdoutLine("- %s", file);
1307
+ if (bundle.zipFile) writeStdoutLine("zip: %s", bundle.zipFile);
1161
1308
  });
1162
- 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("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (meeting, options) => {
1163
- const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
1164
- const result = await endGoogleMeetActiveConference({
1165
- accessToken: token.accessToken,
1166
- meeting: resolveMeetingInput(params.config, meeting ?? options.meeting)
1309
+ root.command("status").argument("[session-id]", "Meet session ID").option("--json", "Print JSON output", false).action(async (sessionId) => {
1310
+ const delegated = await callGoogleMeetGateway({
1311
+ callGateway,
1312
+ method: "googlemeet.status",
1313
+ payload: { sessionId }
1167
1314
  });
1168
- if (options.json) {
1169
- writeStdoutJson({
1170
- ...result,
1171
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1172
- });
1315
+ if (delegated.ok) {
1316
+ writeStdoutJson(delegated.payload);
1173
1317
  return;
1174
1318
  }
1175
- writeStdoutLine("space: %s", result.space);
1176
- writeStdoutLine("ended: yes");
1177
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1319
+ writeStdoutJson(await (await params.ensureRuntime()).status(sessionId));
1178
1320
  });
1179
- }
1180
- function registerGoogleMeetApiCommands(context) {
1181
- const params = context;
1182
- const { root, resolveMeetingForToken, resolveOAuthTokenOptions, resolveTokenOptions } = context;
1183
- root.command("resolve-space").description("Resolve a Meet URL, meeting code, or spaces/{id} to its canonical space").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1184
- const resolved = resolveTokenOptions(params.config, options);
1185
- const token = await resolveGoogleMeetAccessToken(resolved);
1186
- const space = await fetchGoogleMeetSpace({
1187
- accessToken: token.accessToken,
1188
- meeting: resolved.meeting
1189
- });
1190
- if (options.json) {
1191
- writeStdoutJson(space);
1321
+ 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("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (sessionId, options) => {
1322
+ if (options.oauth) {
1323
+ const report = await buildOAuthDoctorReport(params.config, options);
1324
+ if (options.json) {
1325
+ writeStdoutJson(report);
1326
+ return;
1327
+ }
1328
+ writeOAuthDoctorReport(report);
1192
1329
  return;
1193
1330
  }
1194
- writeStdoutLine("input: %s", resolved.meeting);
1195
- writeStdoutLine("space: %s", space.name);
1196
- if (space.meetingCode) writeStdoutLine("meeting code: %s", space.meetingCode);
1197
- if (space.meetingUri) writeStdoutLine("meeting uri: %s", space.meetingUri);
1198
- writeStdoutLine("active conference: %s", space.activeConference ? "yes" : "no");
1199
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1200
- });
1201
- root.command("preflight").description("Validate OAuth + meeting resolution prerequisites for Meet media work").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1202
- const resolved = resolveTokenOptions(params.config, options);
1203
- const token = await resolveGoogleMeetAccessToken(resolved);
1204
- const space = await fetchGoogleMeetSpace({
1205
- accessToken: token.accessToken,
1206
- meeting: resolved.meeting
1207
- });
1208
- const report = buildGoogleMeetPreflightReport({
1209
- input: resolved.meeting,
1210
- space,
1211
- previewAcknowledged: params.config.preview.enrollmentAcknowledged,
1212
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1331
+ const delegated = await callGoogleMeetGateway({
1332
+ callGateway,
1333
+ method: "googlemeet.status",
1334
+ payload: { sessionId }
1213
1335
  });
1214
- if (options.json) {
1215
- writeStdoutJson(report);
1336
+ if (delegated.ok) {
1337
+ const status = delegated.payload;
1338
+ if (options.json) {
1339
+ writeStdoutJson(status);
1340
+ return;
1341
+ }
1342
+ writeDoctorStatus(status);
1216
1343
  return;
1217
1344
  }
1218
- writeStdoutLine("input: %s", report.input);
1219
- writeStdoutLine("resolved space: %s", report.resolvedSpaceName);
1220
- if (report.meetingCode) writeStdoutLine("meeting code: %s", report.meetingCode);
1221
- if (report.meetingUri) writeStdoutLine("meeting uri: %s", report.meetingUri);
1222
- writeStdoutLine("active conference: %s", report.hasActiveConference ? "yes" : "no");
1223
- writeStdoutLine("preview acknowledged: %s", report.previewAcknowledged ? "yes" : "no");
1224
- writeStdoutLine("token source: %s", report.tokenSource);
1225
- if (report.blockers.length === 0) {
1226
- writeStdoutLine("blockers: none");
1345
+ const status = await (await params.ensureRuntime()).status(sessionId);
1346
+ if (options.json) {
1347
+ writeStdoutJson(status);
1227
1348
  return;
1228
1349
  }
1229
- writeStdoutLine("blockers:");
1230
- for (const blocker of report.blockers) writeStdoutLine("- %s", blocker);
1350
+ writeDoctorStatus(status);
1231
1351
  });
1232
- root.command("latest").description("Find the latest Meet conference record for a meeting").option("--meeting <value>", "Meet URL, meeting code, or spaces/{id}").option("--today", "Find a Meet link on today's calendar").option("--event <query>", "Find a matching calendar event with a Meet link").option("--calendar <id>", "Calendar id for --today or --event", "primary").option("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1233
- const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
1234
- const resolved = await resolveMeetingForToken({
1235
- config: params.config,
1236
- options,
1237
- accessToken: token.accessToken,
1238
- configuredMeeting: options.meeting?.trim()
1239
- });
1240
- const result = await fetchLatestGoogleMeetConferenceRecord({
1241
- accessToken: token.accessToken,
1242
- meeting: resolved.meeting
1352
+ 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) => {
1353
+ const result = await (await params.ensureRuntime()).recoverCurrentTab({
1354
+ url,
1355
+ transport: options.transport
1243
1356
  });
1244
1357
  if (options.json) {
1245
- writeStdoutJson({
1246
- ...result,
1247
- ...resolved.calendarEvent ? { calendarEvent: resolved.calendarEvent } : {},
1248
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1249
- });
1358
+ writeStdoutJson(result);
1250
1359
  return;
1251
1360
  }
1252
- if (resolved.calendarEvent) {
1253
- writeStdoutLine("calendar event: %s", resolved.calendarEvent.event.summary ?? "untitled");
1254
- writeStdoutLine("calendar meet: %s", resolved.calendarEvent.meetingUri);
1255
- }
1256
- writeLatestConferenceRecordSummary(result);
1257
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1361
+ writeRecoverCurrentTabResult(result);
1258
1362
  });
1259
- 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("--access-token <token>", "Access token override").option("--refresh-token <token>", "Refresh token override").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds").option("--json", "Print JSON output", false).action(async (options) => {
1260
- const token = await resolveGoogleMeetAccessToken(resolveOAuthTokenOptions(params.config, options));
1261
- const window = options.today ? buildGoogleMeetCalendarDayWindow() : {};
1262
- const result = await listGoogleMeetCalendarEvents({
1263
- accessToken: token.accessToken,
1264
- calendarId: options.calendar,
1265
- eventQuery: options.event,
1266
- ...window
1363
+ 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) => {
1364
+ const status = await (await params.ensureRuntime()).setupStatus({
1365
+ transport: options.transport,
1366
+ mode: options.mode
1267
1367
  });
1268
- const payload = {
1269
- ...result,
1270
- tokenSource: token.refreshed ? "refresh-token" : "cached-access-token"
1271
- };
1272
1368
  if (options.json) {
1273
- writeStdoutJson(payload);
1369
+ writeStdoutJson(status);
1274
1370
  return;
1275
1371
  }
1276
- writeCalendarEventsSummary(result);
1277
- writeStdoutLine("token source: %s", token.refreshed ? "refresh-token" : "cached-access-token");
1278
- });
1279
- }
1280
- //#endregion
1281
- //#region extensions/google-meet/src/cli.ts
1282
- function resolveMeetingInput(config, value) {
1283
- const meeting = value?.trim() || config.defaults.meeting;
1284
- if (!meeting) throw new Error("Meeting input is required. Pass a URL/meeting code or configure defaults.meeting.");
1285
- return meeting;
1286
- }
1287
- function resolveOAuthTokenOptions(config, options) {
1288
- return {
1289
- clientId: options.clientId?.trim() || config.oauth.clientId,
1290
- clientSecret: options.clientSecret?.trim() || config.oauth.clientSecret,
1291
- refreshToken: options.refreshToken?.trim() || config.oauth.refreshToken,
1292
- accessToken: options.accessToken?.trim() || config.oauth.accessToken,
1293
- expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt
1294
- };
1295
- }
1296
- function resolveTokenOptions(config, options) {
1297
- return {
1298
- meeting: resolveMeetingInput(config, options.meeting),
1299
- ...resolveOAuthTokenOptions(config, options)
1300
- };
1301
- }
1302
- function hasCalendarLookupOptions(options) {
1303
- return Boolean(options.today || options.event?.trim());
1304
- }
1305
- async function resolveCalendarMeetingInput(params) {
1306
- if (!hasCalendarLookupOptions(params.options)) return {};
1307
- const window = params.options.today ? buildGoogleMeetCalendarDayWindow() : {};
1308
- const calendarEvent = await findGoogleMeetCalendarEvent({
1309
- accessToken: params.accessToken,
1310
- calendarId: params.options.calendar,
1311
- eventQuery: params.options.event,
1312
- ...window
1313
- });
1314
- return {
1315
- meeting: calendarEvent.meetingUri,
1316
- calendarEvent
1317
- };
1318
- }
1319
- async function resolveMeetingForToken(params) {
1320
- const calendarMeeting = await resolveCalendarMeetingInput({
1321
- accessToken: params.accessToken,
1322
- options: params.options
1372
+ writeSetupStatus(status);
1323
1373
  });
1324
- const meeting = calendarMeeting.meeting ?? params.configuredMeeting ?? params.config.defaults.meeting;
1325
- if (!meeting) throw new Error("Meeting input is required. Pass --meeting, --today, --event, or configure defaults.meeting.");
1326
- return calendarMeeting.calendarEvent ? {
1327
- meeting,
1328
- calendarEvent: calendarMeeting.calendarEvent
1329
- } : { meeting };
1330
- }
1331
- function resolveArtifactTokenOptions(config, options) {
1332
- const meeting = options.meeting?.trim() || config.defaults.meeting;
1333
- const conferenceRecord = options.conferenceRecord?.trim();
1334
- if (!meeting && !conferenceRecord && !hasCalendarLookupOptions(options)) throw new Error("Meeting input or conference record is required. Pass --meeting, --today, --event, --conference-record, or configure defaults.meeting.");
1335
- return {
1336
- meeting,
1337
- conferenceRecord,
1338
- clientId: options.clientId?.trim() || config.oauth.clientId,
1339
- clientSecret: options.clientSecret?.trim() || config.oauth.clientSecret,
1340
- refreshToken: options.refreshToken?.trim() || config.oauth.refreshToken,
1341
- accessToken: options.accessToken?.trim() || config.oauth.accessToken,
1342
- expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt,
1343
- pageSize: parsePositiveIntegerOption(options.pageSize, "page-size"),
1344
- includeTranscriptEntries: options.transcriptEntries !== false,
1345
- allConferenceRecords: Boolean(options.allConferenceRecords),
1346
- includeDocumentBodies: Boolean(options.includeDocBodies),
1347
- mergeDuplicateParticipants: options.mergeDuplicates !== false,
1348
- lateAfterMinutes: parseOptionalNumber(options.lateAfterMinutes),
1349
- earlyBeforeMinutes: parseOptionalNumber(options.earlyBeforeMinutes)
1350
- };
1351
- }
1352
- function hasCreateOAuth(config, options) {
1353
- return Boolean(options.accessToken?.trim() || options.refreshToken?.trim() || config.oauth.accessToken || config.oauth.refreshToken);
1354
- }
1355
- function registerGoogleMeetCli(params) {
1356
- const callGateway = params.callGatewayFromCli ?? callGatewayFromCli;
1357
- const operationTimeoutMs = resolveGoogleMeetGatewayOperationTimeoutMs(params.config);
1358
- const root = params.program.command("googlemeet").description("Google Meet participant utilities").addHelpText("after", () => `\nDocs: https://docs.openclaw.ai/plugins/google-meet\n`);
1359
- root.command("auth").description("Google Meet OAuth helpers").command("login").description("Run a PKCE OAuth flow and print refresh-token JSON to store in plugin config").option("--client-id <id>", "OAuth client id override").option("--client-secret <secret>", "OAuth client secret override").option("--manual", "Use copy/paste callback flow instead of localhost callback").option("--json", "Print the token payload as JSON", false).option("--timeout-sec <n>", "Local callback timeout in seconds", "300").action(async (options) => {
1360
- const clientId = options.clientId?.trim() || params.config.oauth.clientId;
1361
- const clientSecret = options.clientSecret?.trim() || params.config.oauth.clientSecret;
1362
- if (!clientId) throw new Error("Missing Google Meet OAuth client id. Configure oauth.clientId or pass --client-id.");
1363
- const { verifier, challenge } = createGoogleMeetPkce();
1364
- const state = createGoogleMeetOAuthState();
1365
- const authUrl = buildGoogleMeetAuthUrl({
1366
- clientId,
1367
- challenge,
1368
- state
1374
+ root.command("leave").argument("<session-id>", "Meet session ID").action(async (sessionId) => {
1375
+ const delegated = await callGoogleMeetGateway({
1376
+ callGateway,
1377
+ method: "googlemeet.leave",
1378
+ payload: { sessionId }
1369
1379
  });
1370
- const tokens = await exchangeGoogleMeetAuthCode({
1371
- clientId,
1372
- clientSecret,
1373
- code: await waitForGoogleMeetAuthCode({
1374
- state,
1375
- manual: Boolean(options.manual),
1376
- timeoutMs: resolveGoogleMeetOAuthCallbackTimeoutMs(options.timeoutSec),
1377
- authUrl,
1378
- promptInput,
1379
- writeLine: (message) => writeStdoutLine("%s", message)
1380
- }),
1381
- verifier
1380
+ if (delegated.ok) {
1381
+ if (!delegated.payload.found) throw new Error("session not found");
1382
+ writeStdoutLine("left %s", sessionId);
1383
+ return;
1384
+ }
1385
+ if (!(await (await params.ensureRuntime()).leave(sessionId)).found) throw new Error("session not found");
1386
+ writeStdoutLine("left %s", sessionId);
1387
+ });
1388
+ root.command("speak").argument("<session-id>", "Meet session ID").argument("[message]", "Realtime instructions to speak now").action(async (sessionId, message) => {
1389
+ const delegated = await callGoogleMeetGateway({
1390
+ callGateway,
1391
+ method: "googlemeet.speak",
1392
+ payload: {
1393
+ sessionId,
1394
+ message
1395
+ }
1382
1396
  });
1383
- if (!tokens.refreshToken) throw new Error("Google OAuth did not return a refresh token. Re-run the flow with consent and offline access.");
1384
- const payload = {
1385
- oauth: {
1386
- clientId,
1387
- ...clientSecret ? { clientSecret } : {},
1388
- refreshToken: tokens.refreshToken,
1389
- accessToken: tokens.accessToken,
1390
- expiresAt: tokens.expiresAt
1391
- },
1392
- scope: tokens.scope,
1393
- tokenType: tokens.tokenType
1394
- };
1395
- if (!options.json) writeStdoutLine("Paste this into plugins.entries.google-meet.config:");
1396
- writeStdoutJson(payload);
1397
+ if (delegated.ok) {
1398
+ const result = delegated.payload;
1399
+ if (!result.found) throw new Error("session not found");
1400
+ if (!result.spoken) throw new Error(result.session?.chrome?.health?.speechBlockedMessage ?? "session has no active realtime audio bridge");
1401
+ writeStdoutLine("speaking on %s", sessionId);
1402
+ return;
1403
+ }
1404
+ const result = await (await params.ensureRuntime()).speak(sessionId, message);
1405
+ if (!result.found) throw new Error("session not found");
1406
+ if (!result.spoken) throw new Error(result.session?.chrome?.health?.speechBlockedMessage ?? "session has no active realtime audio bridge");
1407
+ writeStdoutLine("speaking on %s", sessionId);
1397
1408
  });
1398
- const context = {
1399
- root,
1400
- config: params.config,
1401
- ensureRuntime: params.ensureRuntime,
1402
- callGateway,
1403
- operationTimeoutMs,
1404
- resolveMeetingInput,
1405
- resolveOAuthTokenOptions,
1406
- resolveTokenOptions,
1407
- resolveMeetingForToken,
1408
- resolveArtifactTokenOptions,
1409
- hasCreateOAuth
1410
- };
1411
- registerGoogleMeetCreateCommands(context);
1412
- registerGoogleMeetProbeCommands(context);
1413
- registerGoogleMeetApiCommands(context);
1414
- registerGoogleMeetArtifactCommands(context);
1415
- registerGoogleMeetSessionCommands(context);
1416
- registerGoogleMeetDoctorCommand(context);
1417
- registerGoogleMeetLifecycleCommands(context);
1418
1409
  }
1419
1410
  //#endregion
1420
1411
  export { buildGoogleMeetExportManifest, googleMeetExportFileNames, registerGoogleMeetCli, testing, writeMeetExportBundle };