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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-claw",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "display_information": {
3
- "name": "Astronaut",
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": "Astronaut",
14
+ "display_name": "Astro Claw",
15
15
  "always_online": true
16
16
  }
17
17
  },
@@ -1,5 +1,5 @@
1
1
  display_information:
2
- name: Astronaut
2
+ name: Astro Claw
3
3
  description: AI crew member powered by Claude Code
4
4
  background_color: "#1a1a2e"
5
5
  features:
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,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
- // Click "Next" (Step 1 → Step 2 of the wizard)
213
- await new Promise((r) => setTimeout(r, 500));
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
- // Step 2→3: manifest review click "Next" again
223
- console.log(` → Reviewing manifest...`);
224
- try {
225
- const nextBtn2 = await page.$('button.c-button--primary');
226
- if (nextBtn2) {
227
- await nextBtn2.click();
228
- await new Promise((r) => setTimeout(r, 2000));
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
- } catch {}
247
+ }
231
248
 
232
- // Step 3: Click "Create"
233
- console.log(` → Creating app...`);
234
- try {
235
- // Look for the Create button (usually the primary button on step 3)
236
- const buttons = await page.$$('button');
237
- for (const btn of buttons) {
238
- const text = await page.evaluate((el) => el.textContent?.trim(), btn);
239
- if (text === "Create" || text === "Create App") {
240
- await btn.click();
241
- await new Promise((r) => setTimeout(r, 3000));
242
- break;
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
- } catch {}
275
+ }
246
276
 
247
- // After creation, we should be on the app's Basic Information page
248
- // URL pattern: https://api.slack.com/apps/APPID
249
- await page.waitForFunction(
250
- () => /api\.slack\.com\/apps\/[A-Z0-9]+/.test(window.location.href),
251
- { timeout: 15000 }
252
- ).catch(() => {});
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: 15000 });
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
- // Look for the signing secret section and click show
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?.trim() === "Show") {
306
+ const text = await page.evaluate((el) => el.textContent?.trim(), btn);
307
+ if (text === "Show" || text === "show") {
277
308
  await btn.click();
278
- await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
279
- break;
309
+ await new Promise((r) => setTimeout(r, 800));
280
310
  }
281
311
  }
282
- // Extract the signing secret value
312
+
283
313
  signingSecret = await page.evaluate(() => {
284
- const inputs = document.querySelectorAll('input[type="text"]');
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
- // 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
- }
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("astronaut-socket");
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 page
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.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);
483
+ await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 120000 }).catch(() => {});
484
+ await new Promise((r) => setTimeout(r, 3000));
453
485
 
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
- }
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
- return null;
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