@digitalpresence/cliclaw 0.1.0

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 (338) hide show
  1. package/dist/__tests__/calendar.integration.test.d.ts +2 -0
  2. package/dist/__tests__/calendar.integration.test.d.ts.map +1 -0
  3. package/dist/__tests__/calendar.integration.test.js +98 -0
  4. package/dist/__tests__/calendar.integration.test.js.map +1 -0
  5. package/dist/__tests__/forms.integration.test.d.ts +2 -0
  6. package/dist/__tests__/forms.integration.test.d.ts.map +1 -0
  7. package/dist/__tests__/forms.integration.test.js +243 -0
  8. package/dist/__tests__/forms.integration.test.js.map +1 -0
  9. package/dist/__tests__/gdrive.integration.test.d.ts +2 -0
  10. package/dist/__tests__/gdrive.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/gdrive.integration.test.js +186 -0
  12. package/dist/__tests__/gdrive.integration.test.js.map +1 -0
  13. package/dist/__tests__/gmail.integration.test.d.ts +2 -0
  14. package/dist/__tests__/gmail.integration.test.d.ts.map +1 -0
  15. package/dist/__tests__/gmail.integration.test.js +197 -0
  16. package/dist/__tests__/gmail.integration.test.js.map +1 -0
  17. package/dist/__tests__/gslides.integration.test.d.ts +2 -0
  18. package/dist/__tests__/gslides.integration.test.d.ts.map +1 -0
  19. package/dist/__tests__/gslides.integration.test.js +124 -0
  20. package/dist/__tests__/gslides.integration.test.js.map +1 -0
  21. package/dist/__tests__/sheets.integration.test.d.ts +2 -0
  22. package/dist/__tests__/sheets.integration.test.d.ts.map +1 -0
  23. package/dist/__tests__/sheets.integration.test.js +150 -0
  24. package/dist/__tests__/sheets.integration.test.js.map +1 -0
  25. package/dist/agent/crud.d.ts +6 -0
  26. package/dist/agent/crud.d.ts.map +1 -0
  27. package/dist/agent/crud.js +41 -0
  28. package/dist/agent/crud.js.map +1 -0
  29. package/dist/agent/memory.d.ts +14 -0
  30. package/dist/agent/memory.d.ts.map +1 -0
  31. package/dist/agent/memory.js +66 -0
  32. package/dist/agent/memory.js.map +1 -0
  33. package/dist/agent/permissions.d.ts +4 -0
  34. package/dist/agent/permissions.d.ts.map +1 -0
  35. package/dist/agent/permissions.js +32 -0
  36. package/dist/agent/permissions.js.map +1 -0
  37. package/dist/calendar/accounts.d.ts +3 -0
  38. package/dist/calendar/accounts.d.ts.map +1 -0
  39. package/dist/calendar/accounts.js +21 -0
  40. package/dist/calendar/accounts.js.map +1 -0
  41. package/dist/calendar/auth.d.ts +3 -0
  42. package/dist/calendar/auth.d.ts.map +1 -0
  43. package/dist/calendar/auth.js +41 -0
  44. package/dist/calendar/auth.js.map +1 -0
  45. package/dist/calendar/calendars.d.ts +3 -0
  46. package/dist/calendar/calendars.d.ts.map +1 -0
  47. package/dist/calendar/calendars.js +28 -0
  48. package/dist/calendar/calendars.js.map +1 -0
  49. package/dist/calendar/create.d.ts +3 -0
  50. package/dist/calendar/create.d.ts.map +1 -0
  51. package/dist/calendar/create.js +31 -0
  52. package/dist/calendar/create.js.map +1 -0
  53. package/dist/calendar/delete.d.ts +3 -0
  54. package/dist/calendar/delete.d.ts.map +1 -0
  55. package/dist/calendar/delete.js +21 -0
  56. package/dist/calendar/delete.js.map +1 -0
  57. package/dist/calendar/events.d.ts +3 -0
  58. package/dist/calendar/events.d.ts.map +1 -0
  59. package/dist/calendar/events.js +37 -0
  60. package/dist/calendar/events.js.map +1 -0
  61. package/dist/calendar/get.d.ts +3 -0
  62. package/dist/calendar/get.d.ts.map +1 -0
  63. package/dist/calendar/get.js +21 -0
  64. package/dist/calendar/get.js.map +1 -0
  65. package/dist/calendar/update.d.ts +3 -0
  66. package/dist/calendar/update.d.ts.map +1 -0
  67. package/dist/calendar/update.js +31 -0
  68. package/dist/calendar/update.js.map +1 -0
  69. package/dist/cli.d.ts +3 -0
  70. package/dist/cli.d.ts.map +1 -0
  71. package/dist/cli.js +37 -0
  72. package/dist/cli.js.map +1 -0
  73. package/dist/commands/agent.d.ts +6 -0
  74. package/dist/commands/agent.d.ts.map +1 -0
  75. package/dist/commands/agent.js +107 -0
  76. package/dist/commands/agent.js.map +1 -0
  77. package/dist/commands/calendar.d.ts +9 -0
  78. package/dist/commands/calendar.d.ts.map +1 -0
  79. package/dist/commands/calendar.js +104 -0
  80. package/dist/commands/calendar.js.map +1 -0
  81. package/dist/commands/cron.d.ts +6 -0
  82. package/dist/commands/cron.d.ts.map +1 -0
  83. package/dist/commands/cron.js +103 -0
  84. package/dist/commands/cron.js.map +1 -0
  85. package/dist/commands/forms.d.ts +9 -0
  86. package/dist/commands/forms.d.ts.map +1 -0
  87. package/dist/commands/forms.js +139 -0
  88. package/dist/commands/forms.js.map +1 -0
  89. package/dist/commands/gdrive.d.ts +9 -0
  90. package/dist/commands/gdrive.d.ts.map +1 -0
  91. package/dist/commands/gdrive.js +198 -0
  92. package/dist/commands/gdrive.js.map +1 -0
  93. package/dist/commands/gmail.d.ts +9 -0
  94. package/dist/commands/gmail.d.ts.map +1 -0
  95. package/dist/commands/gmail.js +231 -0
  96. package/dist/commands/gmail.js.map +1 -0
  97. package/dist/commands/gslides.d.ts +9 -0
  98. package/dist/commands/gslides.d.ts.map +1 -0
  99. package/dist/commands/gslides.js +167 -0
  100. package/dist/commands/gslides.js.map +1 -0
  101. package/dist/commands/sheets.d.ts +9 -0
  102. package/dist/commands/sheets.d.ts.map +1 -0
  103. package/dist/commands/sheets.js +174 -0
  104. package/dist/commands/sheets.js.map +1 -0
  105. package/dist/cron/daemon.d.ts +3 -0
  106. package/dist/cron/daemon.d.ts.map +1 -0
  107. package/dist/cron/daemon.js +127 -0
  108. package/dist/cron/daemon.js.map +1 -0
  109. package/dist/cron/handlers.d.ts +6 -0
  110. package/dist/cron/handlers.d.ts.map +1 -0
  111. package/dist/cron/handlers.js +58 -0
  112. package/dist/cron/handlers.js.map +1 -0
  113. package/dist/cron/logger.d.ts +4 -0
  114. package/dist/cron/logger.d.ts.map +1 -0
  115. package/dist/cron/logger.js +11 -0
  116. package/dist/cron/logger.js.map +1 -0
  117. package/dist/cron/progress.d.ts +25 -0
  118. package/dist/cron/progress.d.ts.map +1 -0
  119. package/dist/cron/progress.js +71 -0
  120. package/dist/cron/progress.js.map +1 -0
  121. package/dist/cron/ralph-wiggum.d.ts +18 -0
  122. package/dist/cron/ralph-wiggum.d.ts.map +1 -0
  123. package/dist/cron/ralph-wiggum.js +143 -0
  124. package/dist/cron/ralph-wiggum.js.map +1 -0
  125. package/dist/forms/accounts.d.ts +3 -0
  126. package/dist/forms/accounts.d.ts.map +1 -0
  127. package/dist/forms/accounts.js +20 -0
  128. package/dist/forms/accounts.js.map +1 -0
  129. package/dist/forms/auth.d.ts +3 -0
  130. package/dist/forms/auth.d.ts.map +1 -0
  131. package/dist/forms/auth.js +41 -0
  132. package/dist/forms/auth.js.map +1 -0
  133. package/dist/forms/create.d.ts +3 -0
  134. package/dist/forms/create.d.ts.map +1 -0
  135. package/dist/forms/create.js +28 -0
  136. package/dist/forms/create.js.map +1 -0
  137. package/dist/forms/get.d.ts +3 -0
  138. package/dist/forms/get.d.ts.map +1 -0
  139. package/dist/forms/get.js +21 -0
  140. package/dist/forms/get.js.map +1 -0
  141. package/dist/forms/list.d.ts +3 -0
  142. package/dist/forms/list.d.ts.map +1 -0
  143. package/dist/forms/list.js +33 -0
  144. package/dist/forms/list.js.map +1 -0
  145. package/dist/forms/questions.d.ts +6 -0
  146. package/dist/forms/questions.d.ts.map +1 -0
  147. package/dist/forms/questions.js +179 -0
  148. package/dist/forms/questions.js.map +1 -0
  149. package/dist/forms/responses.d.ts +4 -0
  150. package/dist/forms/responses.d.ts.map +1 -0
  151. package/dist/forms/responses.js +40 -0
  152. package/dist/forms/responses.js.map +1 -0
  153. package/dist/forms/update.d.ts +3 -0
  154. package/dist/forms/update.d.ts.map +1 -0
  155. package/dist/forms/update.js +44 -0
  156. package/dist/forms/update.js.map +1 -0
  157. package/dist/gdrive/about.d.ts +3 -0
  158. package/dist/gdrive/about.d.ts.map +1 -0
  159. package/dist/gdrive/about.js +31 -0
  160. package/dist/gdrive/about.js.map +1 -0
  161. package/dist/gdrive/accounts.d.ts +3 -0
  162. package/dist/gdrive/accounts.d.ts.map +1 -0
  163. package/dist/gdrive/accounts.js +20 -0
  164. package/dist/gdrive/accounts.js.map +1 -0
  165. package/dist/gdrive/auth.d.ts +3 -0
  166. package/dist/gdrive/auth.d.ts.map +1 -0
  167. package/dist/gdrive/auth.js +51 -0
  168. package/dist/gdrive/auth.js.map +1 -0
  169. package/dist/gdrive/files.d.ts +12 -0
  170. package/dist/gdrive/files.d.ts.map +1 -0
  171. package/dist/gdrive/files.js +174 -0
  172. package/dist/gdrive/files.js.map +1 -0
  173. package/dist/gdrive/folders.d.ts +4 -0
  174. package/dist/gdrive/folders.d.ts.map +1 -0
  175. package/dist/gdrive/folders.js +46 -0
  176. package/dist/gdrive/folders.js.map +1 -0
  177. package/dist/gdrive/search.d.ts +3 -0
  178. package/dist/gdrive/search.d.ts.map +1 -0
  179. package/dist/gdrive/search.js +23 -0
  180. package/dist/gdrive/search.js.map +1 -0
  181. package/dist/gdrive/sharing.d.ts +5 -0
  182. package/dist/gdrive/sharing.d.ts.map +1 -0
  183. package/dist/gdrive/sharing.js +54 -0
  184. package/dist/gdrive/sharing.js.map +1 -0
  185. package/dist/gmail/accounts.d.ts +3 -0
  186. package/dist/gmail/accounts.d.ts.map +1 -0
  187. package/dist/gmail/accounts.js +18 -0
  188. package/dist/gmail/accounts.js.map +1 -0
  189. package/dist/gmail/auth.d.ts +3 -0
  190. package/dist/gmail/auth.d.ts.map +1 -0
  191. package/dist/gmail/auth.js +50 -0
  192. package/dist/gmail/auth.js.map +1 -0
  193. package/dist/gmail/drafts.d.ts +7 -0
  194. package/dist/gmail/drafts.d.ts.map +1 -0
  195. package/dist/gmail/drafts.js +103 -0
  196. package/dist/gmail/drafts.js.map +1 -0
  197. package/dist/gmail/get.d.ts +4 -0
  198. package/dist/gmail/get.d.ts.map +1 -0
  199. package/dist/gmail/get.js +140 -0
  200. package/dist/gmail/get.js.map +1 -0
  201. package/dist/gmail/inbox.d.ts +4 -0
  202. package/dist/gmail/inbox.d.ts.map +1 -0
  203. package/dist/gmail/inbox.js +45 -0
  204. package/dist/gmail/inbox.js.map +1 -0
  205. package/dist/gmail/labels.d.ts +5 -0
  206. package/dist/gmail/labels.d.ts.map +1 -0
  207. package/dist/gmail/labels.js +45 -0
  208. package/dist/gmail/labels.js.map +1 -0
  209. package/dist/gmail/modify.d.ts +5 -0
  210. package/dist/gmail/modify.d.ts.map +1 -0
  211. package/dist/gmail/modify.js +68 -0
  212. package/dist/gmail/modify.js.map +1 -0
  213. package/dist/gmail/send.d.ts +5 -0
  214. package/dist/gmail/send.d.ts.map +1 -0
  215. package/dist/gmail/send.js +310 -0
  216. package/dist/gmail/send.js.map +1 -0
  217. package/dist/gmail/threads.d.ts +4 -0
  218. package/dist/gmail/threads.d.ts.map +1 -0
  219. package/dist/gmail/threads.js +47 -0
  220. package/dist/gmail/threads.js.map +1 -0
  221. package/dist/gslides/accounts.d.ts +3 -0
  222. package/dist/gslides/accounts.d.ts.map +1 -0
  223. package/dist/gslides/accounts.js +20 -0
  224. package/dist/gslides/accounts.js.map +1 -0
  225. package/dist/gslides/auth.d.ts +3 -0
  226. package/dist/gslides/auth.d.ts.map +1 -0
  227. package/dist/gslides/auth.js +50 -0
  228. package/dist/gslides/auth.js.map +1 -0
  229. package/dist/gslides/presentations.d.ts +29 -0
  230. package/dist/gslides/presentations.d.ts.map +1 -0
  231. package/dist/gslides/presentations.js +320 -0
  232. package/dist/gslides/presentations.js.map +1 -0
  233. package/dist/lib/config.d.ts +6 -0
  234. package/dist/lib/config.d.ts.map +1 -0
  235. package/dist/lib/config.js +12 -0
  236. package/dist/lib/config.js.map +1 -0
  237. package/dist/lib/media-utils.d.ts +5 -0
  238. package/dist/lib/media-utils.d.ts.map +1 -0
  239. package/dist/lib/media-utils.js +79 -0
  240. package/dist/lib/media-utils.js.map +1 -0
  241. package/dist/lib/output.d.ts +4 -0
  242. package/dist/lib/output.d.ts.map +1 -0
  243. package/dist/lib/output.js +12 -0
  244. package/dist/lib/output.js.map +1 -0
  245. package/dist/sheets/accounts.d.ts +3 -0
  246. package/dist/sheets/accounts.d.ts.map +1 -0
  247. package/dist/sheets/accounts.js +20 -0
  248. package/dist/sheets/accounts.js.map +1 -0
  249. package/dist/sheets/auth.d.ts +3 -0
  250. package/dist/sheets/auth.d.ts.map +1 -0
  251. package/dist/sheets/auth.js +56 -0
  252. package/dist/sheets/auth.js.map +1 -0
  253. package/dist/sheets/cells.d.ts +6 -0
  254. package/dist/sheets/cells.d.ts.map +1 -0
  255. package/dist/sheets/cells.js +89 -0
  256. package/dist/sheets/cells.js.map +1 -0
  257. package/dist/sheets/format.d.ts +8 -0
  258. package/dist/sheets/format.d.ts.map +1 -0
  259. package/dist/sheets/format.js +97 -0
  260. package/dist/sheets/format.js.map +1 -0
  261. package/dist/sheets/sheets-tab.d.ts +6 -0
  262. package/dist/sheets/sheets-tab.d.ts.map +1 -0
  263. package/dist/sheets/sheets-tab.js +88 -0
  264. package/dist/sheets/sheets-tab.js.map +1 -0
  265. package/dist/sheets/spreadsheets.d.ts +6 -0
  266. package/dist/sheets/spreadsheets.d.ts.map +1 -0
  267. package/dist/sheets/spreadsheets.js +88 -0
  268. package/dist/sheets/spreadsheets.js.map +1 -0
  269. package/package.json +33 -0
  270. package/src/__tests__/calendar.integration.test.ts +152 -0
  271. package/src/__tests__/forms.integration.test.ts +403 -0
  272. package/src/__tests__/gdrive.integration.test.ts +253 -0
  273. package/src/__tests__/gmail.integration.test.ts +294 -0
  274. package/src/__tests__/gslides.integration.test.ts +195 -0
  275. package/src/__tests__/sheets.integration.test.ts +234 -0
  276. package/src/agent/crud.ts +54 -0
  277. package/src/agent/memory.ts +95 -0
  278. package/src/agent/permissions.ts +54 -0
  279. package/src/calendar/accounts.ts +25 -0
  280. package/src/calendar/auth.ts +45 -0
  281. package/src/calendar/calendars.ts +32 -0
  282. package/src/calendar/create.ts +44 -0
  283. package/src/calendar/delete.ts +27 -0
  284. package/src/calendar/events.ts +45 -0
  285. package/src/calendar/get.ts +27 -0
  286. package/src/calendar/update.ts +44 -0
  287. package/src/cli.ts +41 -0
  288. package/src/commands/agent.ts +128 -0
  289. package/src/commands/calendar.ts +127 -0
  290. package/src/commands/cron.ts +126 -0
  291. package/src/commands/forms.ts +158 -0
  292. package/src/commands/gdrive.ts +225 -0
  293. package/src/commands/gmail.ts +290 -0
  294. package/src/commands/gslides.ts +188 -0
  295. package/src/commands/sheets.ts +193 -0
  296. package/src/cron/daemon.ts +143 -0
  297. package/src/cron/handlers.ts +78 -0
  298. package/src/cron/logger.ts +20 -0
  299. package/src/cron/progress.ts +90 -0
  300. package/src/cron/ralph-wiggum.ts +172 -0
  301. package/src/forms/accounts.ts +24 -0
  302. package/src/forms/auth.ts +45 -0
  303. package/src/forms/create.ts +34 -0
  304. package/src/forms/get.ts +26 -0
  305. package/src/forms/list.ts +38 -0
  306. package/src/forms/questions.ts +209 -0
  307. package/src/forms/responses.ts +50 -0
  308. package/src/forms/update.ts +57 -0
  309. package/src/gdrive/about.ts +36 -0
  310. package/src/gdrive/accounts.ts +24 -0
  311. package/src/gdrive/auth.ts +55 -0
  312. package/src/gdrive/files.ts +237 -0
  313. package/src/gdrive/folders.ts +58 -0
  314. package/src/gdrive/search.ts +30 -0
  315. package/src/gdrive/sharing.ts +72 -0
  316. package/src/gmail/accounts.ts +22 -0
  317. package/src/gmail/auth.ts +53 -0
  318. package/src/gmail/drafts.ts +166 -0
  319. package/src/gmail/get.ts +195 -0
  320. package/src/gmail/inbox.ts +78 -0
  321. package/src/gmail/labels.ts +69 -0
  322. package/src/gmail/modify.ts +89 -0
  323. package/src/gmail/send.ts +424 -0
  324. package/src/gmail/threads.ts +68 -0
  325. package/src/gslides/accounts.ts +24 -0
  326. package/src/gslides/auth.ts +54 -0
  327. package/src/gslides/presentations.ts +384 -0
  328. package/src/lib/config.ts +14 -0
  329. package/src/lib/media-utils.ts +82 -0
  330. package/src/lib/output.ts +13 -0
  331. package/src/sheets/accounts.ts +24 -0
  332. package/src/sheets/auth.ts +60 -0
  333. package/src/sheets/cells.ts +112 -0
  334. package/src/sheets/format.ts +114 -0
  335. package/src/sheets/sheets-tab.ts +109 -0
  336. package/src/sheets/spreadsheets.ts +106 -0
  337. package/tsconfig.json +8 -0
  338. package/vitest.config.ts +7 -0
@@ -0,0 +1,384 @@
1
+ import { google } from "googleapis";
2
+ import type { OAuthClientManager } from "@digitalpresence/cliclaw-auth";
3
+ import { outputJson, outputError, outputAuthRequired } from "../lib/output.js";
4
+
5
+ function getSlides(clientManager: OAuthClientManager, tokenKey: string) {
6
+ const client = clientManager.getClient(tokenKey);
7
+ if (!client.credentials?.access_token && !client.credentials?.refresh_token) {
8
+ outputAuthRequired("gslides");
9
+ }
10
+ return google.slides({ version: "v1", auth: client });
11
+ }
12
+
13
+ function getDrive(clientManager: OAuthClientManager, tokenKey: string) {
14
+ const client = clientManager.getClient(tokenKey);
15
+ return google.drive({ version: "v3", auth: client });
16
+ }
17
+
18
+ export async function handleList(
19
+ clientManager: OAuthClientManager,
20
+ account: string,
21
+ maxResults: number,
22
+ ): Promise<void> {
23
+ const tokenKey = `gslides:${account}`;
24
+ try {
25
+ const drive = getDrive(clientManager, tokenKey);
26
+ const res = await drive.files.list({
27
+ q: "mimeType='application/vnd.google-apps.presentation' and trashed = false",
28
+ pageSize: maxResults,
29
+ fields: "files(id, name, modifiedTime, webViewLink)",
30
+ orderBy: "modifiedTime desc",
31
+ });
32
+ outputJson(res.data.files ?? []);
33
+ } catch (err) {
34
+ outputError("list_failed", err instanceof Error ? err.message : String(err));
35
+ }
36
+ }
37
+
38
+ export async function handleGet(
39
+ clientManager: OAuthClientManager,
40
+ account: string,
41
+ presentationId: string,
42
+ ): Promise<void> {
43
+ const tokenKey = `gslides:${account}`;
44
+ try {
45
+ const slides = getSlides(clientManager, tokenKey);
46
+ const res = await slides.presentations.get({ presentationId });
47
+ const data = res.data;
48
+ outputJson({
49
+ presentationId: data.presentationId,
50
+ title: data.title,
51
+ locale: data.locale,
52
+ slideCount: data.slides?.length ?? 0,
53
+ slides: data.slides?.map((s, i) => ({
54
+ index: i,
55
+ objectId: s.objectId,
56
+ elementCount: s.pageElements?.length ?? 0,
57
+ })),
58
+ });
59
+ } catch (err) {
60
+ outputError("get_failed", err instanceof Error ? err.message : String(err));
61
+ }
62
+ }
63
+
64
+ export async function handleCreate(
65
+ clientManager: OAuthClientManager,
66
+ account: string,
67
+ title: string,
68
+ ): Promise<void> {
69
+ const tokenKey = `gslides:${account}`;
70
+ try {
71
+ const slides = getSlides(clientManager, tokenKey);
72
+ const res = await slides.presentations.create({
73
+ requestBody: { title },
74
+ });
75
+ outputJson({
76
+ presentationId: res.data.presentationId,
77
+ title: res.data.title,
78
+ slideCount: res.data.slides?.length ?? 0,
79
+ });
80
+ } catch (err) {
81
+ outputError("create_failed", err instanceof Error ? err.message : String(err));
82
+ }
83
+ }
84
+
85
+ export async function handleDelete(
86
+ clientManager: OAuthClientManager,
87
+ account: string,
88
+ presentationId: string,
89
+ ): Promise<void> {
90
+ const tokenKey = `gslides:${account}`;
91
+ try {
92
+ const drive = getDrive(clientManager, tokenKey);
93
+ await drive.files.delete({ fileId: presentationId });
94
+ outputJson({ success: true, presentationId });
95
+ } catch (err) {
96
+ outputError("delete_failed", err instanceof Error ? err.message : String(err));
97
+ }
98
+ }
99
+
100
+ export async function handleGetSlide(
101
+ clientManager: OAuthClientManager,
102
+ account: string,
103
+ presentationId: string,
104
+ slideIndex: number,
105
+ ): Promise<void> {
106
+ const tokenKey = `gslides:${account}`;
107
+ try {
108
+ const slides = getSlides(clientManager, tokenKey);
109
+ const res = await slides.presentations.get({ presentationId });
110
+ const allSlides = res.data.slides ?? [];
111
+ if (slideIndex < 0 || slideIndex >= allSlides.length) {
112
+ outputError("invalid_index", `Slide index ${slideIndex} out of range (0-${allSlides.length - 1})`);
113
+ }
114
+ const slide = allSlides[slideIndex];
115
+ outputJson({
116
+ objectId: slide.objectId,
117
+ index: slideIndex,
118
+ elements: slide.pageElements?.map((el) => ({
119
+ objectId: el.objectId,
120
+ type: el.shape ? "shape" : el.image ? "image" : el.table ? "table" : el.video ? "video" : "other",
121
+ title: el.title,
122
+ description: el.description,
123
+ size: el.size,
124
+ transform: el.transform,
125
+ text: el.shape?.text?.textElements?.map((te) => te.textRun?.content).filter(Boolean).join("") ?? null,
126
+ })),
127
+ });
128
+ } catch (err) {
129
+ if ((err as Error).message?.includes("out of range")) throw err;
130
+ outputError("get_slide_failed", err instanceof Error ? err.message : String(err));
131
+ }
132
+ }
133
+
134
+ export async function handleAddSlide(
135
+ clientManager: OAuthClientManager,
136
+ account: string,
137
+ presentationId: string,
138
+ layout?: string,
139
+ insertionIndex?: number,
140
+ ): Promise<void> {
141
+ const tokenKey = `gslides:${account}`;
142
+ try {
143
+ const slides = getSlides(clientManager, tokenKey);
144
+ const objectId = `slide_${Date.now()}`;
145
+ const request: any = {
146
+ objectId,
147
+ };
148
+ if (layout) {
149
+ request.slideLayoutReference = { predefinedLayout: layout };
150
+ }
151
+ if (insertionIndex !== undefined) {
152
+ request.insertionIndex = insertionIndex;
153
+ }
154
+ await slides.presentations.batchUpdate({
155
+ presentationId,
156
+ requestBody: {
157
+ requests: [{ createSlide: request }],
158
+ },
159
+ });
160
+ outputJson({ success: true, objectId });
161
+ } catch (err) {
162
+ outputError("add_slide_failed", err instanceof Error ? err.message : String(err));
163
+ }
164
+ }
165
+
166
+ export async function handleDeleteSlide(
167
+ clientManager: OAuthClientManager,
168
+ account: string,
169
+ presentationId: string,
170
+ slideObjectId: string,
171
+ ): Promise<void> {
172
+ const tokenKey = `gslides:${account}`;
173
+ try {
174
+ const slides = getSlides(clientManager, tokenKey);
175
+ await slides.presentations.batchUpdate({
176
+ presentationId,
177
+ requestBody: {
178
+ requests: [{ deleteObject: { objectId: slideObjectId } }],
179
+ },
180
+ });
181
+ outputJson({ success: true, objectId: slideObjectId });
182
+ } catch (err) {
183
+ outputError("delete_slide_failed", err instanceof Error ? err.message : String(err));
184
+ }
185
+ }
186
+
187
+ export async function handleAddText(
188
+ clientManager: OAuthClientManager,
189
+ account: string,
190
+ presentationId: string,
191
+ slideObjectId: string,
192
+ text: string,
193
+ opts: { x?: number; y?: number; width?: number; height?: number; fontSize?: number },
194
+ ): Promise<void> {
195
+ const tokenKey = `gslides:${account}`;
196
+ try {
197
+ const slides = getSlides(clientManager, tokenKey);
198
+ const boxId = `textbox_${Date.now()}`;
199
+ const x = opts.x ?? 100;
200
+ const y = opts.y ?? 100;
201
+ const width = opts.width ?? 400;
202
+ const height = opts.height ?? 50;
203
+
204
+ const requests: any[] = [
205
+ {
206
+ createShape: {
207
+ objectId: boxId,
208
+ shapeType: "TEXT_BOX",
209
+ elementProperties: {
210
+ pageObjectId: slideObjectId,
211
+ size: {
212
+ width: { magnitude: width, unit: "PT" },
213
+ height: { magnitude: height, unit: "PT" },
214
+ },
215
+ transform: {
216
+ scaleX: 1,
217
+ scaleY: 1,
218
+ translateX: x,
219
+ translateY: y,
220
+ unit: "PT",
221
+ },
222
+ },
223
+ },
224
+ },
225
+ {
226
+ insertText: {
227
+ objectId: boxId,
228
+ text,
229
+ insertionIndex: 0,
230
+ },
231
+ },
232
+ ];
233
+
234
+ if (opts.fontSize) {
235
+ requests.push({
236
+ updateTextStyle: {
237
+ objectId: boxId,
238
+ style: {
239
+ fontSize: { magnitude: opts.fontSize, unit: "PT" },
240
+ },
241
+ textRange: { type: "ALL" },
242
+ fields: "fontSize",
243
+ },
244
+ });
245
+ }
246
+
247
+ await slides.presentations.batchUpdate({
248
+ presentationId,
249
+ requestBody: { requests },
250
+ });
251
+ outputJson({ success: true, objectId: boxId });
252
+ } catch (err) {
253
+ outputError("add_text_failed", err instanceof Error ? err.message : String(err));
254
+ }
255
+ }
256
+
257
+ export async function handleAddImage(
258
+ clientManager: OAuthClientManager,
259
+ account: string,
260
+ presentationId: string,
261
+ slideObjectId: string,
262
+ imageUrl: string,
263
+ opts: { x?: number; y?: number; width?: number; height?: number },
264
+ ): Promise<void> {
265
+ const tokenKey = `gslides:${account}`;
266
+ try {
267
+ const slides = getSlides(clientManager, tokenKey);
268
+ const imgId = `image_${Date.now()}`;
269
+ const x = opts.x ?? 100;
270
+ const y = opts.y ?? 100;
271
+ const width = opts.width ?? 300;
272
+ const height = opts.height ?? 200;
273
+
274
+ await slides.presentations.batchUpdate({
275
+ presentationId,
276
+ requestBody: {
277
+ requests: [
278
+ {
279
+ createImage: {
280
+ objectId: imgId,
281
+ url: imageUrl,
282
+ elementProperties: {
283
+ pageObjectId: slideObjectId,
284
+ size: {
285
+ width: { magnitude: width, unit: "PT" },
286
+ height: { magnitude: height, unit: "PT" },
287
+ },
288
+ transform: {
289
+ scaleX: 1,
290
+ scaleY: 1,
291
+ translateX: x,
292
+ translateY: y,
293
+ unit: "PT",
294
+ },
295
+ },
296
+ },
297
+ },
298
+ ],
299
+ },
300
+ });
301
+ outputJson({ success: true, objectId: imgId });
302
+ } catch (err) {
303
+ outputError("add_image_failed", err instanceof Error ? err.message : String(err));
304
+ }
305
+ }
306
+
307
+ export async function handleAddShape(
308
+ clientManager: OAuthClientManager,
309
+ account: string,
310
+ presentationId: string,
311
+ slideObjectId: string,
312
+ shapeType: string,
313
+ opts: { x?: number; y?: number; width?: number; height?: number },
314
+ ): Promise<void> {
315
+ const tokenKey = `gslides:${account}`;
316
+ try {
317
+ const slides = getSlides(clientManager, tokenKey);
318
+ const shapeId = `shape_${Date.now()}`;
319
+ const x = opts.x ?? 100;
320
+ const y = opts.y ?? 100;
321
+ const width = opts.width ?? 200;
322
+ const height = opts.height ?? 200;
323
+
324
+ await slides.presentations.batchUpdate({
325
+ presentationId,
326
+ requestBody: {
327
+ requests: [
328
+ {
329
+ createShape: {
330
+ objectId: shapeId,
331
+ shapeType: shapeType,
332
+ elementProperties: {
333
+ pageObjectId: slideObjectId,
334
+ size: {
335
+ width: { magnitude: width, unit: "PT" },
336
+ height: { magnitude: height, unit: "PT" },
337
+ },
338
+ transform: {
339
+ scaleX: 1,
340
+ scaleY: 1,
341
+ translateX: x,
342
+ translateY: y,
343
+ unit: "PT",
344
+ },
345
+ },
346
+ },
347
+ },
348
+ ],
349
+ },
350
+ });
351
+ outputJson({ success: true, objectId: shapeId });
352
+ } catch (err) {
353
+ outputError("add_shape_failed", err instanceof Error ? err.message : String(err));
354
+ }
355
+ }
356
+
357
+ export async function handleDuplicateSlide(
358
+ clientManager: OAuthClientManager,
359
+ account: string,
360
+ presentationId: string,
361
+ slideObjectId: string,
362
+ ): Promise<void> {
363
+ const tokenKey = `gslides:${account}`;
364
+ try {
365
+ const slides = getSlides(clientManager, tokenKey);
366
+ const newId = `slide_dup_${Date.now()}`;
367
+ await slides.presentations.batchUpdate({
368
+ presentationId,
369
+ requestBody: {
370
+ requests: [
371
+ {
372
+ duplicateObject: {
373
+ objectId: slideObjectId,
374
+ objectIds: { [slideObjectId]: newId },
375
+ },
376
+ },
377
+ ],
378
+ },
379
+ });
380
+ outputJson({ success: true, originalId: slideObjectId, newId });
381
+ } catch (err) {
382
+ outputError("duplicate_slide_failed", err instanceof Error ? err.message : String(err));
383
+ }
384
+ }
@@ -0,0 +1,14 @@
1
+ import { loadConfig as loadConfigBase, getConfigDir, getTokensPath } from "@digitalpresence/cliclaw-auth";
2
+ import type { CliclawConfig } from "@digitalpresence/cliclaw-auth";
3
+
4
+ export type { CliclawConfig };
5
+ export { getConfigDir, getTokensPath };
6
+
7
+ export function loadConfig(): CliclawConfig {
8
+ try {
9
+ return loadConfigBase();
10
+ } catch (err) {
11
+ console.error(err instanceof Error ? err.message : String(err));
12
+ process.exit(1);
13
+ }
14
+ }
@@ -0,0 +1,82 @@
1
+ import { existsSync, mkdirSync } from "fs";
2
+
3
+ export function ensureDir(dir: string): void {
4
+ if (!existsSync(dir)) {
5
+ mkdirSync(dir, { recursive: true });
6
+ }
7
+ }
8
+
9
+ export function mimeToExt(mime: string): string {
10
+ const map: Record<string, string> = {
11
+ "image/png": ".png",
12
+ "image/jpeg": ".jpg",
13
+ "image/webp": ".webp",
14
+ "image/gif": ".gif",
15
+ "image/svg+xml": ".svg",
16
+ "image/bmp": ".bmp",
17
+ "image/tiff": ".tiff",
18
+ "application/pdf": ".pdf",
19
+ "video/mp4": ".mp4",
20
+ "video/webm": ".webm",
21
+ "video/quicktime": ".mov",
22
+ "audio/mpeg": ".mp3",
23
+ "audio/wav": ".wav",
24
+ "audio/ogg": ".ogg",
25
+ "application/msword": ".doc",
26
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
27
+ "application/vnd.ms-excel": ".xls",
28
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
29
+ "application/vnd.ms-powerpoint": ".ppt",
30
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
31
+ "application/zip": ".zip",
32
+ "application/gzip": ".gz",
33
+ "text/plain": ".txt",
34
+ "text/csv": ".csv",
35
+ "text/html": ".html",
36
+ "application/json": ".json",
37
+ "application/xml": ".xml",
38
+ "application/octet-stream": ".bin",
39
+ };
40
+ return map[mime] ?? ".bin";
41
+ }
42
+
43
+ export function extToMime(ext: string): string {
44
+ const map: Record<string, string> = {
45
+ ".png": "image/png",
46
+ ".jpg": "image/jpeg",
47
+ ".jpeg": "image/jpeg",
48
+ ".webp": "image/webp",
49
+ ".gif": "image/gif",
50
+ ".svg": "image/svg+xml",
51
+ ".bmp": "image/bmp",
52
+ ".tiff": "image/tiff",
53
+ ".pdf": "application/pdf",
54
+ ".mp4": "video/mp4",
55
+ ".webm": "video/webm",
56
+ ".mov": "video/quicktime",
57
+ ".mp3": "audio/mpeg",
58
+ ".wav": "audio/wav",
59
+ ".ogg": "audio/ogg",
60
+ ".doc": "application/msword",
61
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
62
+ ".xls": "application/vnd.ms-excel",
63
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
64
+ ".ppt": "application/vnd.ms-powerpoint",
65
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
66
+ ".zip": "application/zip",
67
+ ".gz": "application/gzip",
68
+ ".txt": "text/plain",
69
+ ".csv": "text/csv",
70
+ ".html": "text/html",
71
+ ".json": "application/json",
72
+ ".xml": "application/xml",
73
+ };
74
+ return map[ext.toLowerCase()] ?? "application/octet-stream";
75
+ }
76
+
77
+ export function sanitizeFilename(name: string): string {
78
+ return name
79
+ .replace(/[<>:"/\\|?*\x00-\x1f]/g, "_")
80
+ .replace(/_{2,}/g, "_")
81
+ .slice(0, 200);
82
+ }
@@ -0,0 +1,13 @@
1
+ export function outputJson(data: unknown): void {
2
+ console.log(JSON.stringify(data, null, 2));
3
+ }
4
+
5
+ export function outputError(error: string, message?: string): never {
6
+ console.log(JSON.stringify({ error, message: message ?? error }));
7
+ process.exit(1);
8
+ }
9
+
10
+ export function outputAuthRequired(integration: string = "gmail"): never {
11
+ console.log(JSON.stringify({ error: "auth_required", action: `Run: cliclaw ${integration} auth` }));
12
+ process.exit(1);
13
+ }
@@ -0,0 +1,24 @@
1
+ import { google } from "googleapis";
2
+ import type { OAuthClientManager } from "@digitalpresence/cliclaw-auth";
3
+ import { outputJson } from "../lib/output.js";
4
+
5
+ export async function handleAccounts(clientManager: OAuthClientManager): Promise<void> {
6
+ const allAccounts = clientManager.listAccounts();
7
+ const gsheetsAccounts = allAccounts.filter((a) => a.startsWith("gsheets:"));
8
+
9
+ const accounts = await Promise.all(
10
+ gsheetsAccounts.map(async (tokenKey) => {
11
+ const account = tokenKey.replace("gsheets:", "");
12
+ try {
13
+ const client = clientManager.getClient(tokenKey);
14
+ const drive = google.drive({ version: "v3", auth: client });
15
+ const about = await drive.about.get({ fields: "user" });
16
+ return { account, email: about.data.user?.emailAddress ?? null };
17
+ } catch {
18
+ return { account, email: null };
19
+ }
20
+ }),
21
+ );
22
+
23
+ outputJson({ accounts });
24
+ }
@@ -0,0 +1,60 @@
1
+ import { google } from "googleapis";
2
+ import type { OAuthClientManager } from "@digitalpresence/cliclaw-auth";
3
+ import { waitForOAuthCallback, getGSheetsAuthUrl } from "@digitalpresence/cliclaw-auth";
4
+ import { outputJson, outputError } from "../lib/output.js";
5
+
6
+ export async function handleAuth(clientManager: OAuthClientManager, port: number, account: string): Promise<void> {
7
+ const tokenKey = `gsheets:${account}`;
8
+
9
+ try {
10
+ const client = clientManager.getClient(tokenKey);
11
+ const creds = client.credentials;
12
+ if (creds && (creds.refresh_token || creds.access_token)) {
13
+ const sheets = google.sheets({ version: "v4", auth: client });
14
+ // Validate by getting a simple response
15
+ await sheets.spreadsheets.create({
16
+ requestBody: { properties: { title: "__auth_check__" } },
17
+ }).then(() => {}).catch(() => {});
18
+ // Actually just check drive about for email
19
+ const drive = google.drive({ version: "v3", auth: client });
20
+ const about = await drive.about.get({ fields: "user" });
21
+ outputJson({
22
+ status: "already_authenticated",
23
+ account,
24
+ email: about.data.user?.emailAddress ?? "unknown",
25
+ message: "Existing session is still valid. No re-authentication needed.",
26
+ });
27
+ return;
28
+ }
29
+ } catch {
30
+ // Tokens invalid — proceed with re-auth
31
+ }
32
+
33
+ try {
34
+ const open = (await import("open")).default;
35
+ const rawClient = clientManager.getRawClient();
36
+
37
+ const tokens = await waitForOAuthCallback(rawClient, port, (url) => {
38
+ console.error(`Opening browser for Google Sheets authentication...`);
39
+ console.error(url);
40
+ open(url).catch(() => {
41
+ console.error("Could not open browser. Please visit the URL above manually.");
42
+ });
43
+ }, getGSheetsAuthUrl);
44
+
45
+ const client = clientManager.setCredentials(tokenKey, tokens);
46
+
47
+ const drive = google.drive({ version: "v3", auth: client });
48
+ let email = "unknown";
49
+ try {
50
+ const about = await drive.about.get({ fields: "user" });
51
+ email = about.data.user?.emailAddress ?? "unknown";
52
+ } catch {
53
+ // Non-fatal
54
+ }
55
+
56
+ outputJson({ status: "authenticated", account, email });
57
+ } catch (err) {
58
+ outputError("auth_failed", err instanceof Error ? err.message : String(err));
59
+ }
60
+ }
@@ -0,0 +1,112 @@
1
+ import { google } from "googleapis";
2
+ import type { OAuthClientManager } from "@digitalpresence/cliclaw-auth";
3
+ import { outputJson, outputError, outputAuthRequired } from "../lib/output.js";
4
+
5
+ function getSheets(clientManager: OAuthClientManager, tokenKey: string) {
6
+ const client = clientManager.getClient(tokenKey);
7
+ if (!client.credentials?.access_token && !client.credentials?.refresh_token) {
8
+ outputAuthRequired("sheets");
9
+ }
10
+ return google.sheets({ version: "v4", auth: client });
11
+ }
12
+
13
+ export async function handleRead(
14
+ clientManager: OAuthClientManager,
15
+ account: string,
16
+ spreadsheetId: string,
17
+ range: string,
18
+ ): Promise<void> {
19
+ const tokenKey = `gsheets:${account}`;
20
+ try {
21
+ const sheets = getSheets(clientManager, tokenKey);
22
+ const res = await sheets.spreadsheets.values.get({
23
+ spreadsheetId,
24
+ range,
25
+ });
26
+ outputJson({
27
+ range: res.data.range,
28
+ values: res.data.values ?? [],
29
+ });
30
+ } catch (err) {
31
+ outputError("read_failed", err instanceof Error ? err.message : String(err));
32
+ }
33
+ }
34
+
35
+ export async function handleWrite(
36
+ clientManager: OAuthClientManager,
37
+ account: string,
38
+ spreadsheetId: string,
39
+ range: string,
40
+ values: string[][],
41
+ ): Promise<void> {
42
+ const tokenKey = `gsheets:${account}`;
43
+ try {
44
+ const sheets = getSheets(clientManager, tokenKey);
45
+ const res = await sheets.spreadsheets.values.update({
46
+ spreadsheetId,
47
+ range,
48
+ valueInputOption: "USER_ENTERED",
49
+ requestBody: { values },
50
+ });
51
+ outputJson({
52
+ success: true,
53
+ updatedRange: res.data.updatedRange,
54
+ updatedRows: res.data.updatedRows,
55
+ updatedColumns: res.data.updatedColumns,
56
+ updatedCells: res.data.updatedCells,
57
+ });
58
+ } catch (err) {
59
+ outputError("write_failed", err instanceof Error ? err.message : String(err));
60
+ }
61
+ }
62
+
63
+ export async function handleAppend(
64
+ clientManager: OAuthClientManager,
65
+ account: string,
66
+ spreadsheetId: string,
67
+ range: string,
68
+ values: string[][],
69
+ ): Promise<void> {
70
+ const tokenKey = `gsheets:${account}`;
71
+ try {
72
+ const sheets = getSheets(clientManager, tokenKey);
73
+ const res = await sheets.spreadsheets.values.append({
74
+ spreadsheetId,
75
+ range,
76
+ valueInputOption: "USER_ENTERED",
77
+ requestBody: { values },
78
+ });
79
+ outputJson({
80
+ success: true,
81
+ updatedRange: res.data.updates?.updatedRange,
82
+ updatedRows: res.data.updates?.updatedRows,
83
+ updatedColumns: res.data.updates?.updatedColumns,
84
+ updatedCells: res.data.updates?.updatedCells,
85
+ });
86
+ } catch (err) {
87
+ outputError("append_failed", err instanceof Error ? err.message : String(err));
88
+ }
89
+ }
90
+
91
+ export async function handleClear(
92
+ clientManager: OAuthClientManager,
93
+ account: string,
94
+ spreadsheetId: string,
95
+ range: string,
96
+ ): Promise<void> {
97
+ const tokenKey = `gsheets:${account}`;
98
+ try {
99
+ const sheets = getSheets(clientManager, tokenKey);
100
+ const res = await sheets.spreadsheets.values.clear({
101
+ spreadsheetId,
102
+ range,
103
+ requestBody: {},
104
+ });
105
+ outputJson({
106
+ success: true,
107
+ clearedRange: res.data.clearedRange,
108
+ });
109
+ } catch (err) {
110
+ outputError("clear_failed", err instanceof Error ? err.message : String(err));
111
+ }
112
+ }