astro-claw 1.0.1 → 1.0.3
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 +1 -1
- package/slack-manifest.json +2 -2
- package/slack-manifest.yml +1 -1
- package/slack-setup.js +117 -84
package/package.json
CHANGED
package/slack-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"display_information": {
|
|
3
|
-
"name": "
|
|
3
|
+
"name": "Astro Claw",
|
|
4
4
|
"description": "AI crew member powered by Claude Code",
|
|
5
5
|
"background_color": "#1a1a2e"
|
|
6
6
|
},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"messages_tab_read_only_enabled": false
|
|
12
12
|
},
|
|
13
13
|
"bot_user": {
|
|
14
|
-
"display_name": "
|
|
14
|
+
"display_name": "Astro Claw",
|
|
15
15
|
"always_online": true
|
|
16
16
|
}
|
|
17
17
|
},
|
package/slack-manifest.yml
CHANGED
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,49 +211,76 @@ export default async function selfDrivingSlackSetup() {
|
|
|
207
211
|
});
|
|
208
212
|
console.log(` ${CHECK} Workspace: ${bold(chosen || "selected")}`);
|
|
209
213
|
}
|
|
210
|
-
}
|
|
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"; }
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const nextBtn = await page.$('button.c-button--primary');
|
|
216
|
-
if (nextBtn) {
|
|
217
|
-
await nextBtn.click();
|
|
218
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
219
|
-
}
|
|
220
|
-
} catch {}
|
|
221
|
+
const cards = document.querySelectorAll('[role="option"], [role="radio"], [data-qa*="workspace"]');
|
|
222
|
+
if (cards.length === 1) { cards[0].click(); return "card"; }
|
|
221
223
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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`);
|
|
229
246
|
}
|
|
230
|
-
}
|
|
247
|
+
}
|
|
231
248
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
// Helper: check if we've landed on the app page (creation complete)
|
|
250
|
+
const isOnAppPage = () => /api\.slack\.com\/apps\/[A-Z0-9]+(?:\/|$)/.test(page.url());
|
|
251
|
+
|
|
252
|
+
// Click through the wizard steps — Slack may have 2 or 3 steps
|
|
253
|
+
// After each click, check if the app was already created
|
|
254
|
+
console.log(` → Walking through wizard...`);
|
|
255
|
+
for (let step = 0; step < 3; step++) {
|
|
256
|
+
if (isOnAppPage()) break;
|
|
257
|
+
|
|
258
|
+
// Look for the primary action button (Next or Create)
|
|
259
|
+
const btn = await page.$('button.c-button--primary')
|
|
260
|
+
|| await page.$('button[data-qa="next"]')
|
|
261
|
+
|| await page.$('button[type="submit"]');
|
|
262
|
+
|
|
263
|
+
if (btn) {
|
|
264
|
+
const btnText = await page.evaluate((el) => el.textContent?.trim(), btn);
|
|
265
|
+
if (btnText === "Create" || btnText === "Create App") {
|
|
266
|
+
console.log(` → Creating app...`);
|
|
267
|
+
} else {
|
|
268
|
+
console.log(` → ${btnText || "Next"}...`);
|
|
243
269
|
}
|
|
270
|
+
await btn.click();
|
|
271
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
272
|
+
} else {
|
|
273
|
+
break;
|
|
244
274
|
}
|
|
245
|
-
}
|
|
275
|
+
}
|
|
246
276
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
277
|
+
// Wait for redirect to app page
|
|
278
|
+
if (!isOnAppPage()) {
|
|
279
|
+
await page.waitForFunction(
|
|
280
|
+
() => /api\.slack\.com\/apps\/[A-Z0-9]+/.test(window.location.href),
|
|
281
|
+
{ timeout: 30000 }
|
|
282
|
+
).catch(() => {});
|
|
283
|
+
}
|
|
253
284
|
|
|
254
285
|
const appUrl = page.url();
|
|
255
286
|
const appIdMatch = appUrl.match(/apps\/([A-Z0-9]+)/);
|
|
@@ -264,45 +295,49 @@ export default async function selfDrivingSlackSetup() {
|
|
|
264
295
|
// ── Step 3: Extract Signing Secret ──
|
|
265
296
|
console.log(` → Extracting signing secret...`);
|
|
266
297
|
const basicInfoUrl = appId ? `https://api.slack.com/apps/${appId}` : appUrl;
|
|
267
|
-
await page.goto(basicInfoUrl, { waitUntil: "networkidle2", timeout:
|
|
298
|
+
await page.goto(basicInfoUrl, { waitUntil: "networkidle2", timeout: 30000 });
|
|
299
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
268
300
|
|
|
269
|
-
// Click "Show" on the signing secret
|
|
270
301
|
let signingSecret = null;
|
|
271
302
|
try {
|
|
272
|
-
//
|
|
303
|
+
// Click all "Show" buttons to reveal hidden values
|
|
273
304
|
const showButtons = await page.$$('button');
|
|
274
305
|
for (const btn of showButtons) {
|
|
275
|
-
const text = await page.evaluate((el) => el.textContent, btn);
|
|
276
|
-
if (text
|
|
306
|
+
const text = await page.evaluate((el) => el.textContent?.trim(), btn);
|
|
307
|
+
if (text === "Show" || text === "show") {
|
|
277
308
|
await btn.click();
|
|
278
|
-
await
|
|
279
|
-
break;
|
|
309
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
280
310
|
}
|
|
281
311
|
}
|
|
282
|
-
|
|
312
|
+
|
|
283
313
|
signingSecret = await page.evaluate(() => {
|
|
284
|
-
|
|
314
|
+
// Check all input fields for hex strings
|
|
315
|
+
const inputs = document.querySelectorAll('input[type="text"], input[type="password"], input[readonly]');
|
|
285
316
|
for (const input of inputs) {
|
|
286
317
|
const val = input.value;
|
|
287
318
|
if (/^[0-9a-f]{20,}$/i.test(val)) return val;
|
|
288
319
|
}
|
|
289
|
-
//
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
320
|
+
// Search page text near "Signing Secret" label
|
|
321
|
+
const allText = document.body.innerText;
|
|
322
|
+
const sigIdx = allText.indexOf("Signing Secret");
|
|
323
|
+
if (sigIdx !== -1) {
|
|
324
|
+
const after = allText.substring(sigIdx, sigIdx + 200);
|
|
325
|
+
const match = after.match(/[0-9a-f]{25,}/i);
|
|
326
|
+
if (match) return match[0];
|
|
327
|
+
}
|
|
328
|
+
// Broader search for hex strings in the page
|
|
329
|
+
const els = document.querySelectorAll('span, code, pre, div, p');
|
|
330
|
+
for (const el of els) {
|
|
331
|
+
const text = el.textContent?.trim();
|
|
332
|
+
if (text && /^[0-9a-f]{25,}$/i.test(text)) return text;
|
|
300
333
|
}
|
|
301
334
|
return null;
|
|
302
335
|
});
|
|
303
336
|
|
|
304
337
|
if (signingSecret) {
|
|
305
338
|
console.log(` ${CHECK} Signing secret captured`);
|
|
339
|
+
} else {
|
|
340
|
+
console.log(` ${WARN} Could not auto-extract signing secret`);
|
|
306
341
|
}
|
|
307
342
|
} catch {
|
|
308
343
|
console.log(` ${WARN} Could not auto-extract signing secret`);
|
|
@@ -342,7 +377,7 @@ export default async function selfDrivingSlackSetup() {
|
|
|
342
377
|
|
|
343
378
|
if (nameInput) {
|
|
344
379
|
await nameInput.click({ clickCount: 3 });
|
|
345
|
-
await nameInput.type("
|
|
380
|
+
await nameInput.type("astro-claw-socket");
|
|
346
381
|
await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
|
|
347
382
|
}
|
|
348
383
|
|
|
@@ -437,32 +472,30 @@ export default async function selfDrivingSlackSetup() {
|
|
|
437
472
|
const installBtns = await page.$$('a, button');
|
|
438
473
|
for (const btn of installBtns) {
|
|
439
474
|
const text = await page.evaluate((el) => el.textContent?.trim(), btn);
|
|
440
|
-
if (text?.includes("Install to Workspace") || text?.includes("Reinstall")) {
|
|
475
|
+
if (text?.includes("Install to Workspace") || text?.includes("Reinstall") || text?.includes("Install App")) {
|
|
441
476
|
await btn.click();
|
|
442
477
|
break;
|
|
443
478
|
}
|
|
444
479
|
}
|
|
445
480
|
|
|
446
|
-
// Wait for OAuth consent
|
|
481
|
+
// Wait for OAuth consent — user clicks "Allow"
|
|
447
482
|
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);
|
|
483
|
+
await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 120000 }).catch(() => {});
|
|
484
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
453
485
|
|
|
454
|
-
// After Allow,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
486
|
+
// After Allow, navigate back to OAuth page to get tokens
|
|
487
|
+
await page.goto(installUrl, { waitUntil: "networkidle2", timeout: 30000 });
|
|
488
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
458
489
|
|
|
459
490
|
// Extract bot token
|
|
460
491
|
botToken = await page.evaluate(() => {
|
|
461
|
-
const inputs = document.querySelectorAll('input[type="text"], input[readonly]');
|
|
492
|
+
const inputs = document.querySelectorAll('input[type="text"], input[type="password"], input[readonly]');
|
|
462
493
|
for (const input of inputs) {
|
|
463
494
|
if (input.value?.startsWith("xoxb-")) return input.value;
|
|
464
495
|
}
|
|
465
|
-
|
|
496
|
+
// Check page text
|
|
497
|
+
const match = document.body.innerText.match(/xoxb-[A-Za-z0-9-]+/);
|
|
498
|
+
return match ? match[0] : null;
|
|
466
499
|
});
|
|
467
500
|
}
|
|
468
501
|
|