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.
Files changed (2) hide show
  1. package/package.json +3 -2
  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.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 the "Launching workspace" screen that's the final sign-in confirmation
150
- // URL will be like: taxflowinc.slack.com/ssb/redirect or app.slack.com/client/...
149
+ // Wait for sign-in to completedetect 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, 2000));
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: 15000 });
165
- await new Promise((r) => setTimeout(r, 1000));
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
- // Find the workspace dropdown (it's a <select> element)
176
- const selectEl = await page.waitForSelector("select", { timeout: 10000 }).catch(() => null);
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
- await new Promise((r) => setTimeout(r, 1000));
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, 500));
250
+ await new Promise((r) => setTimeout(r, 1000));
214
251
  try {
215
- const nextBtn = await page.$('button.c-button--primary');
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: 15000 });
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
- // Look for the signing secret section and click show
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?.trim() === "Show") {
315
+ const text = await page.evaluate((el) => el.textContent?.trim(), btn);
316
+ if (text === "Show" || text === "show") {
277
317
  await btn.click();
278
- await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
279
- break;
318
+ await new Promise((r) => setTimeout(r, 800));
280
319
  }
281
320
  }
282
- // Extract the signing secret value
321
+
283
322
  signingSecret = await page.evaluate(() => {
284
- const inputs = document.querySelectorAll('input[type="text"]');
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
- // Try spans/text near "Signing Secret"
290
- const labels = document.querySelectorAll('span, label, h4, h5');
291
- for (const label of labels) {
292
- if (label.textContent?.includes("Signing Secret")) {
293
- const parent = label.closest('div')?.parentElement;
294
- if (parent) {
295
- const text = parent.innerText;
296
- const match = text.match(/[0-9a-f]{30,}/i);
297
- if (match) return match[0];
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("astronaut-socket");
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 page
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.waitForFunction(
449
- () => window.location.href.includes("/oauth") || window.location.href.includes("apps/"),
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, should redirect back to OAuth page with tokens
455
- if (!page.url().includes("/oauth")) {
456
- await page.goto(installUrl, { waitUntil: "networkidle2", timeout: 15000 });
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
- return null;
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