@chrysb/alphaclaw 0.3.5-beta.1 → 0.4.1-beta.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 (68) hide show
  1. package/bin/alphaclaw.js +1 -31
  2. package/lib/public/assets/icons/google_icon.svg +8 -0
  3. package/lib/public/css/explorer.css +53 -0
  4. package/lib/public/css/shell.css +21 -19
  5. package/lib/public/css/theme.css +17 -0
  6. package/lib/public/js/app.js +205 -109
  7. package/lib/public/js/components/credentials-modal.js +36 -8
  8. package/lib/public/js/components/file-tree.js +212 -22
  9. package/lib/public/js/components/file-viewer/editor-surface.js +5 -0
  10. package/lib/public/js/components/file-viewer/index.js +47 -6
  11. package/lib/public/js/components/file-viewer/markdown-split-view.js +2 -0
  12. package/lib/public/js/components/file-viewer/status-banners.js +11 -6
  13. package/lib/public/js/components/file-viewer/toolbar.js +56 -1
  14. package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +6 -0
  15. package/lib/public/js/components/file-viewer/use-file-diff.js +11 -0
  16. package/lib/public/js/components/file-viewer/use-file-loader.js +12 -2
  17. package/lib/public/js/components/file-viewer/use-file-viewer.js +142 -15
  18. package/lib/public/js/components/google/account-row.js +131 -0
  19. package/lib/public/js/components/google/add-account-modal.js +93 -0
  20. package/lib/public/js/components/google/gmail-setup-wizard.js +450 -0
  21. package/lib/public/js/components/google/gmail-watch-toggle.js +81 -0
  22. package/lib/public/js/components/google/index.js +553 -0
  23. package/lib/public/js/components/google/use-gmail-watch.js +140 -0
  24. package/lib/public/js/components/google/use-google-accounts.js +41 -0
  25. package/lib/public/js/components/icons.js +26 -0
  26. package/lib/public/js/components/scope-picker.js +1 -1
  27. package/lib/public/js/components/sidebar-git-panel.js +48 -20
  28. package/lib/public/js/components/sidebar.js +93 -75
  29. package/lib/public/js/components/toast.js +11 -7
  30. package/lib/public/js/components/usage-tab/constants.js +31 -0
  31. package/lib/public/js/components/usage-tab/formatters.js +24 -0
  32. package/lib/public/js/components/usage-tab/index.js +72 -0
  33. package/lib/public/js/components/usage-tab/overview-section.js +147 -0
  34. package/lib/public/js/components/usage-tab/sessions-section.js +175 -0
  35. package/lib/public/js/components/usage-tab/use-usage-tab.js +241 -0
  36. package/lib/public/js/components/webhooks.js +182 -129
  37. package/lib/public/js/lib/api.js +178 -9
  38. package/lib/public/js/lib/browse-file-policies.js +29 -11
  39. package/lib/public/js/lib/format.js +71 -0
  40. package/lib/public/js/lib/syntax-highlighters/index.js +6 -5
  41. package/lib/public/shared/browse-file-policies.json +13 -0
  42. package/lib/server/constants.js +47 -7
  43. package/lib/server/gmail-push.js +109 -0
  44. package/lib/server/gmail-serve.js +254 -0
  45. package/lib/server/gmail-watch.js +725 -0
  46. package/lib/server/google-state.js +317 -0
  47. package/lib/server/helpers.js +17 -11
  48. package/lib/server/internal-files-migration.js +31 -3
  49. package/lib/server/onboarding/github.js +21 -2
  50. package/lib/server/onboarding/index.js +1 -3
  51. package/lib/server/onboarding/openclaw.js +3 -0
  52. package/lib/server/onboarding/workspace.js +40 -0
  53. package/lib/server/routes/browse/index.js +90 -2
  54. package/lib/server/routes/gmail.js +128 -0
  55. package/lib/server/routes/google.js +433 -213
  56. package/lib/server/routes/system.js +107 -0
  57. package/lib/server/routes/usage.js +29 -2
  58. package/lib/server/routes/webhooks.js +52 -17
  59. package/lib/server/usage-db.js +283 -15
  60. package/lib/server/watchdog.js +66 -0
  61. package/lib/server/webhook-middleware.js +99 -1
  62. package/lib/server/webhooks.js +214 -65
  63. package/lib/server.js +27 -0
  64. package/lib/setup/gitignore +6 -0
  65. package/lib/setup/hourly-git-sync.sh +29 -2
  66. package/package.json +1 -1
  67. package/lib/public/js/components/google.js +0 -228
  68. package/lib/public/js/components/usage-tab.js +0 -531
@@ -0,0 +1,254 @@
1
+ const { spawn } = require("child_process");
2
+
3
+ const kDefaultStopTimeoutMs = 5000;
4
+ const kDefaultBindHost = "127.0.0.1";
5
+ const kDefaultServerHost = "127.0.0.1";
6
+ const kPersonalClientName = "personal";
7
+
8
+ const resolveClientName = (account = {}) => {
9
+ const rawClient = String(account?.client || "").trim();
10
+ if (rawClient) return rawClient;
11
+ if (account?.personal) return kPersonalClientName;
12
+ return "default";
13
+ };
14
+
15
+ const isPidRunning = (pid) => {
16
+ const normalizedPid = Number.parseInt(String(pid || ""), 10);
17
+ if (!Number.isFinite(normalizedPid) || normalizedPid <= 0) return false;
18
+ try {
19
+ process.kill(normalizedPid, 0);
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ };
25
+
26
+ const createStatus = (entry = {}) => ({
27
+ running: Boolean(entry.child && !entry.child.killed),
28
+ pid: entry.child?.pid || null,
29
+ port: entry.port || null,
30
+ accountId: entry.accountId || "",
31
+ email: entry.email || "",
32
+ client: entry.client || "default",
33
+ startedAt: entry.startedAt || null,
34
+ });
35
+
36
+ const createGmailServeManager = ({
37
+ constants,
38
+ onServeExit = () => {},
39
+ }) => {
40
+ const entriesByAccountId = new Map();
41
+
42
+ const getEntry = (accountId = "") =>
43
+ entriesByAccountId.get(String(accountId || "").trim()) || null;
44
+
45
+ const removeEntry = (accountId = "") => {
46
+ entriesByAccountId.delete(String(accountId || "").trim());
47
+ };
48
+
49
+ const getServeStatus = (accountId = "") => {
50
+ const entry = getEntry(accountId);
51
+ if (!entry) return createStatus({ accountId });
52
+ return createStatus(entry);
53
+ };
54
+
55
+ const listServeStatuses = () =>
56
+ Array.from(entriesByAccountId.values()).map((entry) => createStatus(entry));
57
+
58
+ const buildArgs = ({
59
+ account,
60
+ port,
61
+ webhookToken,
62
+ }) => {
63
+ const client = resolveClientName(account);
64
+ const args = [];
65
+ if (client !== "default") {
66
+ args.push("--client", client);
67
+ }
68
+ args.push(
69
+ "gmail",
70
+ "watch",
71
+ "serve",
72
+ "--account",
73
+ String(account?.email || ""),
74
+ "--bind",
75
+ kDefaultBindHost,
76
+ "--port",
77
+ String(port),
78
+ "--path",
79
+ "/",
80
+ "--hook-url",
81
+ `http://${kDefaultServerHost}:${constants.PORT}/hooks/gmail`,
82
+ "--hook-token",
83
+ String(webhookToken || ""),
84
+ "--include-body",
85
+ "--max-bytes",
86
+ String(constants.kGmailMaxBodyBytes || 20000),
87
+ );
88
+ return args;
89
+ };
90
+
91
+ const startServe = async ({
92
+ account,
93
+ port,
94
+ webhookToken,
95
+ }) => {
96
+ const accountId = String(account?.id || "").trim();
97
+ if (!accountId) throw new Error("Account id is required");
98
+ const email = String(account?.email || "").trim();
99
+ if (!email) throw new Error("Account email is required");
100
+ if (!isPidRunning(getEntry(accountId)?.child?.pid)) {
101
+ removeEntry(accountId);
102
+ }
103
+ const existingEntry = getEntry(accountId);
104
+ if (existingEntry?.child?.pid && isPidRunning(existingEntry.child.pid)) {
105
+ return createStatus(existingEntry);
106
+ }
107
+ const normalizedPort = Number.parseInt(String(port || ""), 10);
108
+ if (!Number.isFinite(normalizedPort) || normalizedPort <= 0) {
109
+ throw new Error("A valid serve port is required");
110
+ }
111
+ const token = String(webhookToken || "").trim();
112
+ if (!token) {
113
+ throw new Error("OPENCLAW_HOOKS_TOKEN is required to start Gmail watch serve");
114
+ }
115
+
116
+ const args = buildArgs({ account, port: normalizedPort, webhookToken: token });
117
+ const env = {
118
+ ...process.env,
119
+ XDG_CONFIG_HOME: constants.OPENCLAW_DIR,
120
+ GOG_KEYRING_PASSWORD: constants.GOG_KEYRING_PASSWORD,
121
+ };
122
+ const child = spawn("gog", args, {
123
+ env,
124
+ stdio: ["ignore", "pipe", "pipe"],
125
+ });
126
+
127
+ child.stdout.on("data", (chunk) => {
128
+ const line = String(chunk || "").trim();
129
+ if (line) {
130
+ console.log(`[alphaclaw] gmail watch serve (${email}): ${line}`);
131
+ }
132
+ });
133
+ child.stderr.on("data", (chunk) => {
134
+ const line = String(chunk || "").trim();
135
+ if (line) {
136
+ console.log(`[alphaclaw] gmail watch serve stderr (${email}): ${line}`);
137
+ }
138
+ });
139
+
140
+ const nextEntry = {
141
+ accountId,
142
+ email,
143
+ client: resolveClientName(account),
144
+ port: normalizedPort,
145
+ startedAt: new Date().toISOString(),
146
+ child,
147
+ };
148
+ entriesByAccountId.set(accountId, nextEntry);
149
+
150
+ child.on("exit", (code, signal) => {
151
+ const currentEntry = getEntry(accountId);
152
+ if (currentEntry?.child === child) {
153
+ removeEntry(accountId);
154
+ }
155
+ onServeExit({
156
+ accountId,
157
+ email,
158
+ client: nextEntry.client,
159
+ port: normalizedPort,
160
+ code,
161
+ signal,
162
+ });
163
+ });
164
+
165
+ return createStatus(nextEntry);
166
+ };
167
+
168
+ const stopServe = async ({
169
+ accountId,
170
+ timeoutMs = kDefaultStopTimeoutMs,
171
+ }) => {
172
+ const normalizedAccountId = String(accountId || "").trim();
173
+ const entry = getEntry(normalizedAccountId);
174
+ if (!entry?.child) {
175
+ return { stopped: true, accountId: normalizedAccountId };
176
+ }
177
+ const child = entry.child;
178
+ if (!isPidRunning(child.pid)) {
179
+ removeEntry(normalizedAccountId);
180
+ return { stopped: true, accountId: normalizedAccountId };
181
+ }
182
+ return await new Promise((resolve) => {
183
+ let settled = false;
184
+ const finalize = (result) => {
185
+ if (settled) return;
186
+ settled = true;
187
+ removeEntry(normalizedAccountId);
188
+ resolve(result);
189
+ };
190
+ const timeoutHandle = setTimeout(() => {
191
+ try {
192
+ child.kill("SIGKILL");
193
+ } catch {}
194
+ finalize({
195
+ stopped: false,
196
+ forced: true,
197
+ accountId: normalizedAccountId,
198
+ });
199
+ }, Math.max(100, Number(timeoutMs) || kDefaultStopTimeoutMs));
200
+ child.once("exit", () => {
201
+ clearTimeout(timeoutHandle);
202
+ finalize({
203
+ stopped: true,
204
+ forced: false,
205
+ accountId: normalizedAccountId,
206
+ });
207
+ });
208
+ try {
209
+ child.kill("SIGTERM");
210
+ } catch {
211
+ clearTimeout(timeoutHandle);
212
+ finalize({
213
+ stopped: true,
214
+ forced: false,
215
+ accountId: normalizedAccountId,
216
+ });
217
+ }
218
+ });
219
+ };
220
+
221
+ const restartServe = async ({
222
+ account,
223
+ port,
224
+ webhookToken,
225
+ }) => {
226
+ await stopServe({ accountId: account?.id || "" });
227
+ return await startServe({ account, port, webhookToken });
228
+ };
229
+
230
+ const stopAll = async () => {
231
+ const accountIds = Array.from(entriesByAccountId.keys());
232
+ const results = [];
233
+ for (const accountId of accountIds) {
234
+ // eslint-disable-next-line no-await-in-loop
235
+ const result = await stopServe({ accountId });
236
+ results.push(result);
237
+ }
238
+ return results;
239
+ };
240
+
241
+ return {
242
+ getServeStatus,
243
+ listServeStatuses,
244
+ startServe,
245
+ stopServe,
246
+ restartServe,
247
+ stopAll,
248
+ isPidRunning,
249
+ };
250
+ };
251
+
252
+ module.exports = {
253
+ createGmailServeManager,
254
+ };