astro-claw 1.0.0 → 1.0.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/package.json +3 -2
- package/slack-setup.js +92 -50
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-claw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Claude Code over Slack — your AI crew member. One command: npx astro-claw",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"astro-claw": "./start.js"
|
|
8
|
+
"astro-claw": "./start.js",
|
|
9
|
+
"aclaw": "./start.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"start": "node start.js",
|
package/slack-setup.js
CHANGED
|
@@ -146,23 +146,23 @@ export default async function selfDrivingSlackSetup() {
|
|
|
146
146
|
console.log(` → ${bold("Sign in to your Slack workspace")} in the browser`);
|
|
147
147
|
console.log(` ${dim("Take your time — the wizard continues automatically after you log in")}`);
|
|
148
148
|
|
|
149
|
-
// Wait for
|
|
150
|
-
// URL will be like: taxflowinc.slack.com/ssb/redirect or app.slack.com/client/...
|
|
149
|
+
// Wait for sign-in to complete — detect by URL change away from sign-in pages
|
|
151
150
|
await page.waitForFunction(
|
|
152
151
|
() => {
|
|
153
152
|
const url = window.location.href;
|
|
154
153
|
return url.includes("/ssb/redirect") ||
|
|
155
154
|
url.includes("/client/") ||
|
|
156
|
-
url.includes("app.slack.com")
|
|
155
|
+
url.includes("app.slack.com") ||
|
|
156
|
+
url.includes("api.slack.com/apps");
|
|
157
157
|
},
|
|
158
158
|
{ timeout: 600000 } // 10 minutes
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
console.log(` ${CHECK} Signed in`);
|
|
162
|
-
await new Promise((r) => setTimeout(r,
|
|
162
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
163
163
|
// Auth cookies are set — now redirect to api.slack.com/apps
|
|
164
|
-
await page.goto("https://api.slack.com/apps", { waitUntil: "networkidle2", timeout:
|
|
165
|
-
await new Promise((r) => setTimeout(r,
|
|
164
|
+
await page.goto("https://api.slack.com/apps", { waitUntil: "networkidle2", timeout: 30000 });
|
|
165
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
166
166
|
} else {
|
|
167
167
|
console.log(` ${CHECK} Already signed in`);
|
|
168
168
|
}
|
|
@@ -172,32 +172,36 @@ export default async function selfDrivingSlackSetup() {
|
|
|
172
172
|
await new Promise((r) => setTimeout(r, 2000));
|
|
173
173
|
|
|
174
174
|
// ── Step 3: Select workspace ──
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
console.log(` → Selecting workspace...`);
|
|
176
|
+
|
|
177
|
+
// Try native <select> first, then fall back to custom UI patterns
|
|
178
|
+
const selectEl = await page.waitForSelector("select", { timeout: 8000 }).catch(() => null);
|
|
177
179
|
|
|
178
180
|
if (selectEl) {
|
|
179
|
-
// Get all options in the dropdown
|
|
180
181
|
const options = await page.evaluate(() => {
|
|
181
182
|
const sel = document.querySelector("select");
|
|
182
183
|
if (!sel) return [];
|
|
183
184
|
return Array.from(sel.options)
|
|
184
|
-
.filter((o) => o.value && o.value !== "" && !o.disabled)
|
|
185
|
+
.filter((o) => o.value && o.value !== "" && o.value !== "0" && !o.disabled)
|
|
185
186
|
.map((o) => ({ value: o.value, text: o.textContent.trim() }));
|
|
186
187
|
});
|
|
187
188
|
|
|
188
189
|
if (options.length === 1) {
|
|
189
|
-
// Single workspace — auto-select it
|
|
190
190
|
console.log(` → Auto-selecting workspace: ${bold(options[0].text)}`);
|
|
191
191
|
await page.select("select", options[0].value);
|
|
192
|
-
|
|
192
|
+
// Dispatch change event to trigger any listeners
|
|
193
|
+
await page.evaluate(() => {
|
|
194
|
+
const sel = document.querySelector("select");
|
|
195
|
+
sel.dispatchEvent(new Event("change", { bubbles: true }));
|
|
196
|
+
});
|
|
197
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
193
198
|
} else if (options.length > 1) {
|
|
194
|
-
// Multiple workspaces — let user pick
|
|
195
199
|
console.log(` → ${bold("Select your workspace")} in the browser dropdown`);
|
|
196
200
|
console.log(` ${dim("Waiting for you to pick a workspace...")}`);
|
|
197
201
|
await page.waitForFunction(
|
|
198
202
|
() => {
|
|
199
203
|
const sel = document.querySelector("select");
|
|
200
|
-
return sel && sel.value && sel.value !== "";
|
|
204
|
+
return sel && sel.value && sel.value !== "" && sel.value !== "0";
|
|
201
205
|
},
|
|
202
206
|
{ timeout: 300000 }
|
|
203
207
|
);
|
|
@@ -207,12 +211,48 @@ export default async function selfDrivingSlackSetup() {
|
|
|
207
211
|
});
|
|
208
212
|
console.log(` ${CHECK} Workspace: ${bold(chosen || "selected")}`);
|
|
209
213
|
}
|
|
214
|
+
} else {
|
|
215
|
+
// Slack may use custom UI — look for clickable workspace items
|
|
216
|
+
const clicked = await page.evaluate(() => {
|
|
217
|
+
// Look for radio buttons, clickable cards, or list items with workspace names
|
|
218
|
+
const radios = document.querySelectorAll('input[type="radio"]');
|
|
219
|
+
if (radios.length === 1) { radios[0].click(); return "radio"; }
|
|
220
|
+
|
|
221
|
+
const cards = document.querySelectorAll('[role="option"], [role="radio"], [data-qa*="workspace"]');
|
|
222
|
+
if (cards.length === 1) { cards[0].click(); return "card"; }
|
|
223
|
+
|
|
224
|
+
// Look for any clickable list with single item
|
|
225
|
+
const items = document.querySelectorAll('.c-menu_item, [class*="workspace"] a, [class*="team"] a');
|
|
226
|
+
if (items.length === 1) { items[0].click(); return "item"; }
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (clicked) {
|
|
232
|
+
console.log(` ${CHECK} Auto-selected single workspace`);
|
|
233
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
234
|
+
} else {
|
|
235
|
+
console.log(` → ${bold("Select your workspace")} in the browser`);
|
|
236
|
+
console.log(` ${dim("Waiting for you to pick a workspace...")}`);
|
|
237
|
+
// Wait for the page to move past workspace selection
|
|
238
|
+
await page.waitForFunction(
|
|
239
|
+
() => {
|
|
240
|
+
const url = window.location.href;
|
|
241
|
+
return url.includes("manifest") || document.querySelector('textarea, pre, code');
|
|
242
|
+
},
|
|
243
|
+
{ timeout: 300000 }
|
|
244
|
+
);
|
|
245
|
+
console.log(` ${CHECK} Workspace selected`);
|
|
246
|
+
}
|
|
210
247
|
}
|
|
211
248
|
|
|
212
249
|
// Click "Next" (Step 1 → Step 2 of the wizard)
|
|
213
|
-
await new Promise((r) => setTimeout(r,
|
|
250
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
214
251
|
try {
|
|
215
|
-
|
|
252
|
+
// Try multiple selectors for the primary/next button
|
|
253
|
+
const nextBtn = await page.$('button.c-button--primary')
|
|
254
|
+
|| await page.$('button[data-qa="next"]')
|
|
255
|
+
|| await page.$('button[type="submit"]');
|
|
216
256
|
if (nextBtn) {
|
|
217
257
|
await nextBtn.click();
|
|
218
258
|
await new Promise((r) => setTimeout(r, 2000));
|
|
@@ -264,45 +304,49 @@ export default async function selfDrivingSlackSetup() {
|
|
|
264
304
|
// ── Step 3: Extract Signing Secret ──
|
|
265
305
|
console.log(` → Extracting signing secret...`);
|
|
266
306
|
const basicInfoUrl = appId ? `https://api.slack.com/apps/${appId}` : appUrl;
|
|
267
|
-
await page.goto(basicInfoUrl, { waitUntil: "networkidle2", timeout:
|
|
307
|
+
await page.goto(basicInfoUrl, { waitUntil: "networkidle2", timeout: 30000 });
|
|
308
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
268
309
|
|
|
269
|
-
// Click "Show" on the signing secret
|
|
270
310
|
let signingSecret = null;
|
|
271
311
|
try {
|
|
272
|
-
//
|
|
312
|
+
// Click all "Show" buttons to reveal hidden values
|
|
273
313
|
const showButtons = await page.$$('button');
|
|
274
314
|
for (const btn of showButtons) {
|
|
275
|
-
const text = await page.evaluate((el) => el.textContent, btn);
|
|
276
|
-
if (text
|
|
315
|
+
const text = await page.evaluate((el) => el.textContent?.trim(), btn);
|
|
316
|
+
if (text === "Show" || text === "show") {
|
|
277
317
|
await btn.click();
|
|
278
|
-
await
|
|
279
|
-
break;
|
|
318
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
280
319
|
}
|
|
281
320
|
}
|
|
282
|
-
|
|
321
|
+
|
|
283
322
|
signingSecret = await page.evaluate(() => {
|
|
284
|
-
|
|
323
|
+
// Check all input fields for hex strings
|
|
324
|
+
const inputs = document.querySelectorAll('input[type="text"], input[type="password"], input[readonly]');
|
|
285
325
|
for (const input of inputs) {
|
|
286
326
|
const val = input.value;
|
|
287
327
|
if (/^[0-9a-f]{20,}$/i.test(val)) return val;
|
|
288
328
|
}
|
|
289
|
-
//
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
329
|
+
// Search page text near "Signing Secret" label
|
|
330
|
+
const allText = document.body.innerText;
|
|
331
|
+
const sigIdx = allText.indexOf("Signing Secret");
|
|
332
|
+
if (sigIdx !== -1) {
|
|
333
|
+
const after = allText.substring(sigIdx, sigIdx + 200);
|
|
334
|
+
const match = after.match(/[0-9a-f]{25,}/i);
|
|
335
|
+
if (match) return match[0];
|
|
336
|
+
}
|
|
337
|
+
// Broader search for hex strings in the page
|
|
338
|
+
const els = document.querySelectorAll('span, code, pre, div, p');
|
|
339
|
+
for (const el of els) {
|
|
340
|
+
const text = el.textContent?.trim();
|
|
341
|
+
if (text && /^[0-9a-f]{25,}$/i.test(text)) return text;
|
|
300
342
|
}
|
|
301
343
|
return null;
|
|
302
344
|
});
|
|
303
345
|
|
|
304
346
|
if (signingSecret) {
|
|
305
347
|
console.log(` ${CHECK} Signing secret captured`);
|
|
348
|
+
} else {
|
|
349
|
+
console.log(` ${WARN} Could not auto-extract signing secret`);
|
|
306
350
|
}
|
|
307
351
|
} catch {
|
|
308
352
|
console.log(` ${WARN} Could not auto-extract signing secret`);
|
|
@@ -342,7 +386,7 @@ export default async function selfDrivingSlackSetup() {
|
|
|
342
386
|
|
|
343
387
|
if (nameInput) {
|
|
344
388
|
await nameInput.click({ clickCount: 3 });
|
|
345
|
-
await nameInput.type("
|
|
389
|
+
await nameInput.type("astro-claw-socket");
|
|
346
390
|
await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
|
|
347
391
|
}
|
|
348
392
|
|
|
@@ -437,32 +481,30 @@ export default async function selfDrivingSlackSetup() {
|
|
|
437
481
|
const installBtns = await page.$$('a, button');
|
|
438
482
|
for (const btn of installBtns) {
|
|
439
483
|
const text = await page.evaluate((el) => el.textContent?.trim(), btn);
|
|
440
|
-
if (text?.includes("Install to Workspace") || text?.includes("Reinstall")) {
|
|
484
|
+
if (text?.includes("Install to Workspace") || text?.includes("Reinstall") || text?.includes("Install App")) {
|
|
441
485
|
await btn.click();
|
|
442
486
|
break;
|
|
443
487
|
}
|
|
444
488
|
}
|
|
445
489
|
|
|
446
|
-
// Wait for OAuth consent
|
|
490
|
+
// Wait for OAuth consent — user clicks "Allow"
|
|
447
491
|
console.log(` ${WARN} ${bold("Click 'Allow' in the browser to install the app")}`);
|
|
448
|
-
await page.
|
|
449
|
-
|
|
450
|
-
{ timeout: 120000 }
|
|
451
|
-
);
|
|
452
|
-
await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 2000);
|
|
492
|
+
await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 120000 }).catch(() => {});
|
|
493
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
453
494
|
|
|
454
|
-
// After Allow,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
495
|
+
// After Allow, navigate back to OAuth page to get tokens
|
|
496
|
+
await page.goto(installUrl, { waitUntil: "networkidle2", timeout: 30000 });
|
|
497
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
458
498
|
|
|
459
499
|
// Extract bot token
|
|
460
500
|
botToken = await page.evaluate(() => {
|
|
461
|
-
const inputs = document.querySelectorAll('input[type="text"], input[readonly]');
|
|
501
|
+
const inputs = document.querySelectorAll('input[type="text"], input[type="password"], input[readonly]');
|
|
462
502
|
for (const input of inputs) {
|
|
463
503
|
if (input.value?.startsWith("xoxb-")) return input.value;
|
|
464
504
|
}
|
|
465
|
-
|
|
505
|
+
// Check page text
|
|
506
|
+
const match = document.body.innerText.match(/xoxb-[A-Za-z0-9-]+/);
|
|
507
|
+
return match ? match[0] : null;
|
|
466
508
|
});
|
|
467
509
|
}
|
|
468
510
|
|