@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.
- package/lib/public/css/explorer.css +156 -4
- package/lib/public/js/components/file-tree.js +533 -48
- package/lib/public/js/components/file-viewer/index.js +2 -8
- package/lib/public/js/components/google/index.js +83 -48
- package/lib/public/js/components/icons.js +26 -0
- package/lib/public/js/components/onboarding/use-welcome-codex.js +129 -0
- package/lib/public/js/components/onboarding/use-welcome-pairing.js +74 -0
- package/lib/public/js/components/onboarding/use-welcome-storage.js +60 -0
- package/lib/public/js/components/sidebar.js +1 -1
- package/lib/public/js/components/telegram-workspace/onboarding.js +1 -1
- package/lib/public/js/components/webhooks.js +2 -2
- package/lib/public/js/components/welcome.js +57 -210
- package/lib/public/js/lib/api.js +27 -0
- package/lib/public/js/lib/browse-file-policies.js +3 -1
- package/lib/public/shared/browse-file-policies.json +2 -1
- package/lib/server/constants.js +0 -1
- package/lib/server/gmail-serve.js +1 -1
- package/lib/server/gmail-watch.js +8 -28
- package/lib/server/gog-skill.js +169 -0
- package/lib/server/onboarding/openclaw.js +9 -1
- package/lib/server/routes/browse/index.js +123 -6
- package/lib/server/routes/browse/path-utils.js +3 -1
- package/lib/server/routes/google.js +4 -0
- package/lib/server/routes/webhooks.js +3 -5
- package/lib/server/webhooks.js +1 -1
- package/lib/server.js +2 -0
- package/lib/setup/skills/gog-cli/calendar.md +63 -0
- package/lib/setup/skills/gog-cli/contacts.md +30 -0
- package/lib/setup/skills/gog-cli/docs.md +46 -0
- package/lib/setup/skills/gog-cli/drive.md +48 -0
- package/lib/setup/skills/gog-cli/gmail.md +64 -0
- package/lib/setup/skills/gog-cli/meet.md +6 -0
- package/lib/setup/skills/gog-cli/sheets.md +43 -0
- package/lib/setup/skills/gog-cli/tasks.md +32 -0
- 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
|
-
|
|
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
|
|
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: "
|
|
717
|
+
return res.status(404).json({ ok: false, error: "Path does not exist" });
|
|
603
718
|
}
|
|
604
719
|
const stats = fs.statSync(resolvedPath.absolutePath);
|
|
605
|
-
|
|
606
|
-
|
|
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
|
|
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=<
|
|
58
|
+
: `${fullUrl}?token=<WEBHOOK_TOKEN>`;
|
|
61
59
|
const authHeaderValue = token
|
|
62
60
|
? `Authorization: Bearer ${token}`
|
|
63
|
-
: "Authorization: Bearer <
|
|
61
|
+
: "Authorization: Bearer <WEBHOOK_TOKEN>";
|
|
64
62
|
return { fullUrl, queryStringUrl, authHeaderValue, hasRuntimeToken: !!token };
|
|
65
63
|
};
|
|
66
64
|
|
package/lib/server/webhooks.js
CHANGED
|
@@ -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 = "${
|
|
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}}//' # 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.
|