@chrysb/alphaclaw 0.4.1-beta.0 → 0.4.1-beta.2

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 (35) hide show
  1. package/lib/public/css/explorer.css +156 -4
  2. package/lib/public/js/components/file-tree.js +533 -48
  3. package/lib/public/js/components/file-viewer/index.js +2 -8
  4. package/lib/public/js/components/google/index.js +83 -48
  5. package/lib/public/js/components/icons.js +26 -0
  6. package/lib/public/js/components/onboarding/use-welcome-codex.js +129 -0
  7. package/lib/public/js/components/onboarding/use-welcome-pairing.js +74 -0
  8. package/lib/public/js/components/onboarding/use-welcome-storage.js +60 -0
  9. package/lib/public/js/components/sidebar.js +1 -1
  10. package/lib/public/js/components/telegram-workspace/onboarding.js +1 -1
  11. package/lib/public/js/components/webhooks.js +2 -2
  12. package/lib/public/js/components/welcome.js +57 -210
  13. package/lib/public/js/lib/api.js +27 -0
  14. package/lib/public/js/lib/browse-file-policies.js +3 -1
  15. package/lib/public/shared/browse-file-policies.json +2 -1
  16. package/lib/server/constants.js +0 -1
  17. package/lib/server/gmail-serve.js +1 -1
  18. package/lib/server/gmail-watch.js +8 -28
  19. package/lib/server/gog-skill.js +169 -0
  20. package/lib/server/onboarding/openclaw.js +9 -1
  21. package/lib/server/routes/browse/index.js +123 -6
  22. package/lib/server/routes/browse/path-utils.js +3 -1
  23. package/lib/server/routes/google.js +4 -0
  24. package/lib/server/routes/webhooks.js +3 -5
  25. package/lib/server/webhooks.js +1 -1
  26. package/lib/server.js +2 -0
  27. package/lib/setup/skills/gog-cli/calendar.md +63 -0
  28. package/lib/setup/skills/gog-cli/contacts.md +30 -0
  29. package/lib/setup/skills/gog-cli/docs.md +46 -0
  30. package/lib/setup/skills/gog-cli/drive.md +48 -0
  31. package/lib/setup/skills/gog-cli/gmail.md +64 -0
  32. package/lib/setup/skills/gog-cli/meet.md +6 -0
  33. package/lib/setup/skills/gog-cli/sheets.md +43 -0
  34. package/lib/setup/skills/gog-cli/tasks.md +32 -0
  35. package/package.json +2 -2
@@ -0,0 +1,169 @@
1
+ const path = require("path");
2
+ const { readGoogleState } = require("./google-state");
3
+
4
+ const kSkillPartsDir = path.join(__dirname, "..", "setup", "skills", "gog-cli");
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ const uniqueServiceLabels = (scopes) =>
11
+ Array.from(
12
+ new Set(
13
+ (scopes || [])
14
+ .map((scope) => String(scope || "").split(":")[0])
15
+ .filter(Boolean),
16
+ ),
17
+ );
18
+
19
+ const collectConnectedServices = (accounts) => {
20
+ const serviceSet = new Set();
21
+ for (const account of accounts) {
22
+ if (!account.authenticated) continue;
23
+ for (const label of uniqueServiceLabels(account.services)) {
24
+ serviceSet.add(label);
25
+ }
26
+ }
27
+ return serviceSet;
28
+ };
29
+
30
+ const kServiceDisplayNames = {
31
+ gmail: "Gmail",
32
+ calendar: "Calendar",
33
+ drive: "Drive",
34
+ sheets: "Sheets",
35
+ docs: "Docs",
36
+ tasks: "Tasks",
37
+ contacts: "Contacts",
38
+ meet: "Meet",
39
+ };
40
+
41
+ // Stable ordering for service sections
42
+ const kServiceOrder = [
43
+ "gmail",
44
+ "calendar",
45
+ "drive",
46
+ "sheets",
47
+ "docs",
48
+ "tasks",
49
+ "contacts",
50
+ "meet",
51
+ ];
52
+
53
+ const readServiceSection = (fs, service) => {
54
+ try {
55
+ return fs.readFileSync(path.join(kSkillPartsDir, `${service}.md`), "utf8");
56
+ } catch {
57
+ return null;
58
+ }
59
+ };
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Skill content builder
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const buildGogSkillContent = ({ fs, accounts }) => {
66
+ const authenticatedAccounts = accounts.filter((a) => a.authenticated);
67
+ if (!authenticatedAccounts.length) return null;
68
+
69
+ const connectedServices = collectConnectedServices(authenticatedAccounts);
70
+ if (!connectedServices.size) return null;
71
+
72
+ const serviceNames = kServiceOrder
73
+ .filter((svc) => connectedServices.has(svc))
74
+ .map((svc) => kServiceDisplayNames[svc] || svc);
75
+
76
+ const lines = [];
77
+
78
+ // Frontmatter
79
+ lines.push("---");
80
+ lines.push("name: gog-cli");
81
+ lines.push(
82
+ `description: Google Workspace CLI (gog) — command reference for ${serviceNames.join(", ")}.`,
83
+ );
84
+ lines.push("---");
85
+ lines.push("");
86
+
87
+ // Header
88
+ lines.push("# gog — Google Workspace CLI");
89
+ lines.push("");
90
+ lines.push(
91
+ "Fast, script-friendly CLI for Google Workspace. All commands output structured JSON with `--json` or stable TSV with `--plain`.",
92
+ );
93
+ lines.push("");
94
+
95
+ // Global flags
96
+ lines.push("## Global Flags");
97
+ lines.push("");
98
+ lines.push("```");
99
+ lines.push("--account <email> Account to use (or set GOG_ACCOUNT)");
100
+ lines.push("--client <name> OAuth client (default: \"default\")");
101
+ lines.push("--json Structured JSON output");
102
+ lines.push("--plain Stable TSV output (no colors)");
103
+ lines.push("--force Skip confirmations");
104
+ lines.push("--verbose Verbose logging");
105
+ lines.push("```");
106
+ lines.push("");
107
+
108
+ // Account table
109
+ lines.push("## Connected Accounts");
110
+ lines.push("");
111
+ lines.push("| Email | Client | Services |");
112
+ lines.push("| ----- | ------ | -------- |");
113
+ for (const account of authenticatedAccounts) {
114
+ const email = String(account.email || "").trim() || "(unknown)";
115
+ const client = String(account.client || "default").trim();
116
+ const services = uniqueServiceLabels(account.services).join(", ");
117
+ lines.push(`| ${email} | ${client} | ${services} |`);
118
+ }
119
+ lines.push("");
120
+ lines.push(
121
+ "Always pass `--account <email>` (and `--client <name>` if not \"default\") so gog targets the correct account.",
122
+ );
123
+ lines.push("");
124
+
125
+ // Per-service sections (read from markdown files)
126
+ for (const svc of kServiceOrder) {
127
+ if (!connectedServices.has(svc)) continue;
128
+ const section = readServiceSection(fs, svc);
129
+ if (section) {
130
+ lines.push(section.trimEnd());
131
+ lines.push("");
132
+ }
133
+ }
134
+
135
+ return lines.join("\n");
136
+ };
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Installer (reads state, writes SKILL.md)
140
+ // ---------------------------------------------------------------------------
141
+
142
+ const installGogCliSkill = ({ fs, openclawDir }) => {
143
+ try {
144
+ const statePath = path.join(openclawDir, "gogcli", "state.json");
145
+ const state = readGoogleState({ fs, statePath });
146
+ const accounts = Array.isArray(state.accounts) ? state.accounts : [];
147
+ const content = buildGogSkillContent({ fs, accounts });
148
+
149
+ const skillDir = path.join(openclawDir, "skills", "gog-cli");
150
+
151
+ if (!content) {
152
+ // No authenticated accounts — remove stale skill if present
153
+ const skillPath = path.join(skillDir, "SKILL.md");
154
+ if (fs.existsSync(skillPath)) {
155
+ fs.unlinkSync(skillPath);
156
+ console.log("[gog-skill] Removed stale gog-cli skill (no connected accounts)");
157
+ }
158
+ return;
159
+ }
160
+
161
+ fs.mkdirSync(skillDir, { recursive: true });
162
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
163
+ console.log("[gog-skill] gog-cli skill installed");
164
+ } catch (e) {
165
+ console.error("[gog-skill] Install error:", e.message);
166
+ }
167
+ };
168
+
169
+ module.exports = { buildGogSkillContent, installGogCliSkill };
@@ -177,7 +177,15 @@ const writeSanitizedOpenclawConfig = ({ fs, openclawDir, varMap }) => {
177
177
  const replacements = buildSecretReplacements(varMap, process.env);
178
178
  for (const [secret, envRef] of replacements) {
179
179
  if (secret) {
180
- content = content.split(secret).join(envRef);
180
+ // Only replace exact JSON string values so path substrings are never mutated.
181
+ const secretJson = JSON.stringify(secret);
182
+ content = content.replace(
183
+ new RegExp(
184
+ secretJson.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"),
185
+ "g",
186
+ ),
187
+ JSON.stringify(envRef),
188
+ );
181
189
  }
182
190
  }
183
191
  fs.writeFileSync(configPath, content);
@@ -576,6 +576,121 @@ const registerBrowseRoutes = ({ app, fs, kRootDir }) => {
576
576
  }
577
577
  });
578
578
 
579
+ app.post("/api/browse/create-file", (req, res) => {
580
+ const targetPath = String(req.body?.path || "").trim();
581
+ if (!targetPath) {
582
+ return res.status(400).json({ ok: false, error: "path is required" });
583
+ }
584
+ const resolvedPath = resolveSafePath(
585
+ targetPath,
586
+ kRootResolved,
587
+ kRootWithSep,
588
+ kRootDisplayName,
589
+ );
590
+ if (!resolvedPath.ok) {
591
+ return res.status(400).json({ ok: false, error: resolvedPath.error });
592
+ }
593
+ const normalizedPolicyPath = normalizePolicyPath(resolvedPath.relativePath);
594
+ if (matchesPolicyPath(kLockedBrowsePaths, normalizedPolicyPath)) {
595
+ return res.status(403).json({
596
+ ok: false,
597
+ error: "Cannot create files in a locked path.",
598
+ });
599
+ }
600
+ try {
601
+ if (fs.existsSync(resolvedPath.absolutePath)) {
602
+ return res
603
+ .status(409)
604
+ .json({ ok: false, error: "A file or folder already exists at this path" });
605
+ }
606
+ const parentDir = path.dirname(resolvedPath.absolutePath);
607
+ fs.mkdirSync(parentDir, { recursive: true });
608
+ fs.writeFileSync(resolvedPath.absolutePath, "", "utf8");
609
+ return res.json({ ok: true, path: resolvedPath.relativePath });
610
+ } catch (error) {
611
+ return res
612
+ .status(500)
613
+ .json({ ok: false, error: error.message || "Could not create file" });
614
+ }
615
+ });
616
+
617
+ app.post("/api/browse/create-folder", (req, res) => {
618
+ const targetPath = String(req.body?.path || "").trim();
619
+ if (!targetPath) {
620
+ return res.status(400).json({ ok: false, error: "path is required" });
621
+ }
622
+ const resolvedPath = resolveSafePath(
623
+ targetPath,
624
+ kRootResolved,
625
+ kRootWithSep,
626
+ kRootDisplayName,
627
+ );
628
+ if (!resolvedPath.ok) {
629
+ return res.status(400).json({ ok: false, error: resolvedPath.error });
630
+ }
631
+ const normalizedPolicyPath = normalizePolicyPath(resolvedPath.relativePath);
632
+ if (matchesPolicyPath(kLockedBrowsePaths, normalizedPolicyPath)) {
633
+ return res.status(403).json({
634
+ ok: false,
635
+ error: "Cannot create folders in a locked path.",
636
+ });
637
+ }
638
+ try {
639
+ if (fs.existsSync(resolvedPath.absolutePath)) {
640
+ return res
641
+ .status(409)
642
+ .json({ ok: false, error: "A file or folder already exists at this path" });
643
+ }
644
+ fs.mkdirSync(resolvedPath.absolutePath, { recursive: true });
645
+ return res.json({ ok: true, path: resolvedPath.relativePath });
646
+ } catch (error) {
647
+ return res
648
+ .status(500)
649
+ .json({ ok: false, error: error.message || "Could not create folder" });
650
+ }
651
+ });
652
+
653
+ app.post("/api/browse/move", (req, res) => {
654
+ const fromPath = String(req.body?.from || "").trim();
655
+ const toPath = String(req.body?.to || "").trim();
656
+ if (!fromPath || !toPath) {
657
+ return res.status(400).json({ ok: false, error: "from and to are required" });
658
+ }
659
+ const resolvedFrom = resolveSafePath(fromPath, kRootResolved, kRootWithSep, kRootDisplayName);
660
+ if (!resolvedFrom.ok) {
661
+ return res.status(400).json({ ok: false, error: resolvedFrom.error });
662
+ }
663
+ const resolvedTo = resolveSafePath(toPath, kRootResolved, kRootWithSep, kRootDisplayName);
664
+ if (!resolvedTo.ok) {
665
+ return res.status(400).json({ ok: false, error: resolvedTo.error });
666
+ }
667
+ const normalizedFromPolicy = normalizePolicyPath(resolvedFrom.relativePath);
668
+ const normalizedToPolicy = normalizePolicyPath(resolvedTo.relativePath);
669
+ if (
670
+ matchesPolicyPath(kLockedBrowsePaths, normalizedFromPolicy) ||
671
+ matchesPolicyPath(kProtectedBrowsePaths, normalizedFromPolicy)
672
+ ) {
673
+ return res.status(403).json({ ok: false, error: "Source path is protected and cannot be moved." });
674
+ }
675
+ if (matchesPolicyPath(kLockedBrowsePaths, normalizedToPolicy)) {
676
+ return res.status(403).json({ ok: false, error: "Cannot move into a locked path." });
677
+ }
678
+ try {
679
+ if (!fs.existsSync(resolvedFrom.absolutePath)) {
680
+ return res.status(404).json({ ok: false, error: "Source path does not exist" });
681
+ }
682
+ if (fs.existsSync(resolvedTo.absolutePath)) {
683
+ return res.status(409).json({ ok: false, error: "A file or folder already exists at the destination" });
684
+ }
685
+ const parentDir = path.dirname(resolvedTo.absolutePath);
686
+ fs.mkdirSync(parentDir, { recursive: true });
687
+ fs.renameSync(resolvedFrom.absolutePath, resolvedTo.absolutePath);
688
+ return res.json({ ok: true, from: resolvedFrom.relativePath, to: resolvedTo.relativePath });
689
+ } catch (error) {
690
+ return res.status(500).json({ ok: false, error: error.message || "Could not move path" });
691
+ }
692
+ });
693
+
579
694
  app.delete("/api/browse/delete", (req, res) => {
580
695
  const targetPath = String(req.body?.path || "").trim();
581
696
  const resolvedPath = resolveSafePath(
@@ -594,26 +709,28 @@ const registerBrowseRoutes = ({ app, fs, kRootDir }) => {
594
709
  ) {
595
710
  return res.status(403).json({
596
711
  ok: false,
597
- error: "This file cannot be deleted from the explorer.",
712
+ error: "This path cannot be deleted from the explorer.",
598
713
  });
599
714
  }
600
715
  try {
601
716
  if (!fs.existsSync(resolvedPath.absolutePath)) {
602
- return res.status(404).json({ ok: false, error: "File does not exist" });
717
+ return res.status(404).json({ ok: false, error: "Path does not exist" });
603
718
  }
604
719
  const stats = fs.statSync(resolvedPath.absolutePath);
605
- if (!stats.isFile()) {
606
- return res.status(400).json({ ok: false, error: "Path is not a file" });
720
+ const isDirectory = stats.isDirectory();
721
+ if (!stats.isFile() && !isDirectory) {
722
+ return res.status(400).json({ ok: false, error: "Path is not a file or folder" });
607
723
  }
608
- fs.rmSync(resolvedPath.absolutePath, { force: true });
724
+ fs.rmSync(resolvedPath.absolutePath, { recursive: isDirectory, force: true });
609
725
  return res.json({
610
726
  ok: true,
611
727
  path: resolvedPath.relativePath,
728
+ type: isDirectory ? "folder" : "file",
612
729
  });
613
730
  } catch (error) {
614
731
  return res.status(500).json({
615
732
  ok: false,
616
- error: error.message || "Could not delete file",
733
+ error: error.message || "Could not delete path",
617
734
  });
618
735
  }
619
736
  });
@@ -36,7 +36,9 @@ const matchesPolicyPath = (policyPathSet, normalizedPath) => {
36
36
  for (const policyPath of policyPathSet) {
37
37
  if (
38
38
  safeNormalizedPath === policyPath ||
39
- safeNormalizedPath.endsWith(`/${policyPath}`)
39
+ safeNormalizedPath.endsWith(`/${policyPath}`) ||
40
+ safeNormalizedPath.startsWith(`${policyPath}/`) ||
41
+ safeNormalizedPath.includes(`/${policyPath}/`)
40
42
  ) {
41
43
  return true;
42
44
  }
@@ -11,6 +11,7 @@ const {
11
11
  removeGoogleAccount,
12
12
  } = require("../google-state");
13
13
  const { syncBootstrapPromptFiles } = require("../onboarding/workspace");
14
+ const { installGogCliSkill } = require("../gog-skill");
14
15
 
15
16
  const quoteShellArg = (value) => `"${String(value || "").replace(/(["\\$`])/g, "\\$1")}"`;
16
17
 
@@ -62,6 +63,9 @@ const registerGoogleRoutes = ({
62
63
  baseUrl: getBaseUrl(req),
63
64
  });
64
65
  } catch {}
66
+ try {
67
+ installGogCliSkill({ fs, openclawDir: constants.OPENCLAW_DIR });
68
+ } catch {}
65
69
  };
66
70
 
67
71
  const listAuthenticatedAccounts = async (state) => {
@@ -52,15 +52,13 @@ const normalizeStatusFilter = (rawStatus) => {
52
52
 
53
53
  const buildWebhookUrls = ({ baseUrl, name }) => {
54
54
  const fullUrl = `${baseUrl}/hooks/${name}`;
55
- const token = String(
56
- process.env.OPENCLAW_HOOKS_TOKEN || process.env.WEBHOOK_TOKEN || "",
57
- ).trim();
55
+ const token = String(process.env.WEBHOOK_TOKEN || "").trim();
58
56
  const queryStringUrl = token
59
57
  ? `${fullUrl}?token=${encodeURIComponent(token)}`
60
- : `${fullUrl}?token=<OPENCLAW_HOOKS_TOKEN>`;
58
+ : `${fullUrl}?token=<WEBHOOK_TOKEN>`;
61
59
  const authHeaderValue = token
62
60
  ? `Authorization: Bearer ${token}`
63
- : "Authorization: Bearer <OPENCLAW_HOOKS_TOKEN>";
61
+ : "Authorization: Bearer <WEBHOOK_TOKEN>";
64
62
  return { fullUrl, queryStringUrl, authHeaderValue, hasRuntimeToken: !!token };
65
63
  };
66
64
 
@@ -53,7 +53,7 @@ const ensureHooksRoot = (cfg) => {
53
53
  if (typeof cfg.hooks.path !== "string" || !cfg.hooks.path.trim())
54
54
  cfg.hooks.path = "/hooks";
55
55
  if (typeof cfg.hooks.token !== "string" || !cfg.hooks.token.trim()) {
56
- cfg.hooks.token = "${OPENCLAW_HOOKS_TOKEN}";
56
+ cfg.hooks.token = "${WEBHOOK_TOKEN}";
57
57
  }
58
58
  if (
59
59
  typeof cfg.hooks.defaultSessionKey !== "string" ||
package/lib/server.js CHANGED
@@ -79,6 +79,7 @@ const {
79
79
  const {
80
80
  migrateManagedInternalFiles,
81
81
  } = require("./server/internal-files-migration");
82
+ const { installGogCliSkill } = require("./server/gog-skill");
82
83
  const { createTelegramApi } = require("./server/telegram-api");
83
84
  const { createDiscordApi } = require("./server/discord-api");
84
85
  const { createWatchdogNotifier } = require("./server/watchdog-notify");
@@ -282,6 +283,7 @@ const doSyncPromptFiles = () => {
282
283
  openclawDir: constants.OPENCLAW_DIR,
283
284
  baseUrl: setupUiUrl,
284
285
  });
286
+ installGogCliSkill({ fs, openclawDir: constants.OPENCLAW_DIR });
285
287
  };
286
288
  doSyncPromptFiles();
287
289
  registerTelegramRoutes({
@@ -0,0 +1,63 @@
1
+ ## Calendar
2
+
3
+ ```bash
4
+ # List calendars
5
+ gog calendar calendars
6
+
7
+ # Today's events
8
+ gog calendar events <calendarId> --today
9
+ gog calendar events <calendarId> --tomorrow
10
+ gog calendar events <calendarId> --week
11
+ gog calendar events <calendarId> --days 3
12
+
13
+ # Events from all calendars
14
+ gog calendar events --all --today
15
+
16
+ # Date range
17
+ gog calendar events <calendarId> --from 2025-01-15T00:00:00Z --to 2025-01-22T00:00:00Z
18
+
19
+ # Search events
20
+ gog calendar search "meeting" --today
21
+ gog calendar search "standup" --days 30
22
+
23
+ # Get single event
24
+ gog calendar event <calendarId> <eventId>
25
+
26
+ # Create event
27
+ gog calendar create <calendarId> --summary "Meeting" --from 2025-01-15T10:00:00Z --to 2025-01-15T11:00:00Z
28
+ gog calendar create <calendarId> --summary "Team Sync" --from 2025-01-15T14:00:00Z --to 2025-01-15T15:00:00Z --attendees "alice@example.com,bob@example.com" --location "Zoom"
29
+
30
+ # Recurrence + reminders
31
+ gog calendar create <calendarId> --summary "Weekly" --from 2025-01-15T09:00:00Z --to 2025-01-15T09:30:00Z --rrule "RRULE:FREQ=WEEKLY" --reminder "popup:15m"
32
+
33
+ # Update event
34
+ gog calendar update <calendarId> <eventId> --summary "Updated" --from 2025-01-15T11:00:00Z --to 2025-01-15T12:00:00Z
35
+
36
+ # Add attendees without replacing existing
37
+ gog calendar update <calendarId> <eventId> --add-attendee "alice@example.com"
38
+
39
+ # Send notifications
40
+ gog calendar create <calendarId> --summary "Sync" --from ... --to ... --send-updates all
41
+ gog calendar update <calendarId> <eventId> --send-updates externalOnly
42
+
43
+ # Delete event
44
+ gog calendar delete <calendarId> <eventId>
45
+
46
+ # RSVP to invitation
47
+ gog calendar respond <calendarId> <eventId> --status accepted
48
+ gog calendar respond <calendarId> <eventId> --status declined
49
+ gog calendar respond <calendarId> <eventId> --status tentative
50
+
51
+ # Free/busy check
52
+ gog calendar freebusy --calendars "primary,work@example.com" --from 2025-01-15T00:00:00Z --to 2025-01-16T00:00:00Z
53
+
54
+ # Conflict detection
55
+ gog calendar conflicts --calendars "primary" --today
56
+
57
+ # Special event types
58
+ gog calendar create primary --event-type focus-time --from ... --to ...
59
+ gog calendar create primary --event-type out-of-office --from ... --to ... --all-day
60
+ ```
61
+
62
+ JSON output includes `startDayOfWeek`, `endDayOfWeek`, `timezone`, and `startLocal`/`endLocal` fields.
63
+ Use `primary` as calendarId for the user's default calendar.
@@ -0,0 +1,30 @@
1
+ ## Contacts
2
+
3
+ ```bash
4
+ # List personal contacts
5
+ gog contacts list --max 50
6
+
7
+ # Search contacts
8
+ gog contacts search "Ada" --max 50
9
+
10
+ # Get contact by resource name or email
11
+ gog contacts get people/<resourceName>
12
+ gog contacts get user@example.com
13
+
14
+ # Create contact
15
+ gog contacts create --given "John" --family "Doe" --email "john@example.com" --phone "+1234567890"
16
+
17
+ # Update contact
18
+ gog contacts update people/<resourceName> --given "Jane" --email "jane@example.com" --notes "Updated"
19
+
20
+ # Delete contact
21
+ gog contacts delete people/<resourceName>
22
+
23
+ # Other contacts (people you've interacted with)
24
+ gog contacts other list --max 50
25
+ gog contacts other search "John" --max 50
26
+
27
+ # Workspace directory (Google Workspace only)
28
+ gog contacts directory list --max 50
29
+ gog contacts directory search "Jane" --max 50
30
+ ```
@@ -0,0 +1,46 @@
1
+ ## Docs
2
+
3
+ ```bash
4
+ # Document info
5
+ gog docs info <docId>
6
+
7
+ # Read document text
8
+ gog docs cat <docId>
9
+ gog docs cat <docId> --max-bytes 10000
10
+ gog docs cat <docId> --tab "Notes"
11
+ gog docs cat <docId> --all-tabs
12
+
13
+ # List tabs
14
+ gog docs list-tabs <docId>
15
+
16
+ # Create document
17
+ gog docs create "My Doc"
18
+ gog docs create "My Doc" --file ./doc.md
19
+
20
+ # Copy document
21
+ gog docs copy <docId> "My Doc Copy"
22
+
23
+ # Export
24
+ gog docs export <docId> --format pdf --out ./doc.pdf
25
+ gog docs export <docId> --format docx --out ./doc.docx
26
+ gog docs export <docId> --format txt --out ./doc.txt
27
+
28
+ # Update document content (markdown)
29
+ gog docs update <docId> --format markdown --content-file ./doc.md
30
+ gog docs write <docId> --replace --markdown --file ./doc.md
31
+
32
+ # Find and replace
33
+ gog docs find-replace <docId> "old text" "new text"
34
+
35
+ # Sed-style editing (sedmat) with markdown formatting
36
+ gog docs sed <docId> 's/hello/**hello**/' # bold
37
+ gog docs sed <docId> 's/hello/*hello*/' # italic
38
+ gog docs sed <docId> 's/hello/`hello`/' # monospace
39
+ gog docs sed <docId> 's/hello/__hello__/' # underline
40
+ gog docs sed <docId> 's/Google/[Google](https://google.com)/' # link
41
+ gog docs sed <docId> 's/{{LOGO}}/![](https://example.com/logo.png)/' # image
42
+
43
+ # Tables via sedmat
44
+ gog docs sed <docId> 's/{{TABLE}}/|3x4|/' # create 3-row, 4-col table
45
+ gog docs sed <docId> 's/|1|[A1]/**Name**/' # set cell A1
46
+ ```
@@ -0,0 +1,48 @@
1
+ ## Drive
2
+
3
+ ```bash
4
+ # List files (default: all accessible files including shared drives)
5
+ gog drive ls --max 20
6
+ gog drive ls --parent <folderId> --max 20
7
+ gog drive ls --no-all-drives
8
+
9
+ # Search files
10
+ gog drive search "invoice" --max 20
11
+ gog drive search "mimeType = 'application/pdf'" --raw-query
12
+
13
+ # Get file metadata
14
+ gog drive get <fileId>
15
+ gog drive url <fileId>
16
+
17
+ # Download file
18
+ gog drive download <fileId> --out ./downloaded.bin
19
+
20
+ # Export Google Workspace files
21
+ gog drive download <fileId> --format pdf --out ./exported.pdf
22
+ gog drive download <fileId> --format docx --out ./doc.docx
23
+
24
+ # Upload file
25
+ gog drive upload ./path/to/file --parent <folderId>
26
+ gog drive upload ./file.docx --convert
27
+ gog drive upload ./file --replace <fileId>
28
+
29
+ # Copy file
30
+ gog drive copy <fileId> "Copy Name"
31
+
32
+ # Organize
33
+ gog drive mkdir "New Folder"
34
+ gog drive mkdir "New Folder" --parent <parentFolderId>
35
+ gog drive rename <fileId> "New Name"
36
+ gog drive move <fileId> --parent <destinationFolderId>
37
+ gog drive delete <fileId>
38
+ gog drive delete <fileId> --permanent
39
+
40
+ # Permissions
41
+ gog drive permissions <fileId>
42
+ gog drive share <fileId> --to user --email user@example.com --role reader
43
+ gog drive share <fileId> --to user --email user@example.com --role writer
44
+ gog drive unshare <fileId> --permission-id <permissionId>
45
+
46
+ # Shared drives
47
+ gog drive drives --max 100
48
+ ```
@@ -0,0 +1,64 @@
1
+ ## Gmail
2
+
3
+ ```bash
4
+ # Search threads (returns thread IDs + snippets)
5
+ gog gmail search 'newer_than:7d' --max 10
6
+ gog gmail search 'from:alice@example.com subject:invoice' --max 20
7
+
8
+ # Search individual messages (add --include-body to fetch bodies)
9
+ gog gmail messages search 'newer_than:7d' --max 10 --include-body
10
+
11
+ # Read a thread and optionally download attachments
12
+ gog gmail thread get <threadId>
13
+ gog gmail thread get <threadId> --download --out-dir ./attachments
14
+
15
+ # Read a single message
16
+ gog gmail get <messageId>
17
+
18
+ # Modify labels on a thread
19
+ gog gmail thread modify <threadId> --add STARRED --remove INBOX
20
+
21
+ # Send email (plain text)
22
+ gog gmail send --to recipient@example.com --subject "Subject" --body "Body text"
23
+
24
+ # Send email (HTML)
25
+ gog gmail send --to recipient@example.com --subject "Subject" --body "Plain fallback" --body-html "<p>Hello</p>"
26
+
27
+ # Reply to a message (with quoted original)
28
+ gog gmail send --reply-to-message-id <messageId> --quote --to recipient@example.com --subject "Re: Subject" --body "Reply text"
29
+
30
+ # Send with body from file or stdin
31
+ gog gmail send --to recipient@example.com --subject "Subject" --body-file ./message.txt
32
+ gog gmail send --to recipient@example.com --subject "Subject" --body-file -
33
+
34
+ # Labels
35
+ gog gmail labels list
36
+ gog gmail labels get INBOX --json
37
+ gog gmail labels create "My Label"
38
+ gog gmail labels delete <labelIdOrName>
39
+
40
+ # Drafts
41
+ gog gmail drafts list
42
+ gog gmail drafts create --to recipient@example.com --subject "Draft" --body "Body"
43
+ gog gmail drafts update <draftId> --subject "Updated" --body "New body"
44
+ gog gmail drafts send <draftId>
45
+
46
+ # Batch operations
47
+ gog gmail batch modify <messageId1> <messageId2> --add STARRED --remove INBOX
48
+ gog gmail batch delete <messageId1> <messageId2>
49
+
50
+ # Filters
51
+ gog gmail filters list
52
+ gog gmail filters create --from 'noreply@example.com' --add-label 'Notifications'
53
+ gog gmail filters delete <filterId>
54
+
55
+ # Vacation / auto-reply
56
+ gog gmail vacation get
57
+ gog gmail vacation enable --subject "Out of office" --message "I'm away"
58
+ gog gmail vacation disable
59
+
60
+ # History (for incremental sync)
61
+ gog gmail history --since <historyId>
62
+ ```
63
+
64
+ Output: use `--json` for structured output, `--plain` for TSV.
@@ -0,0 +1,6 @@
1
+ ## Meet
2
+
3
+ ```bash
4
+ # List meeting spaces
5
+ gog meet spaces list
6
+ ```