astro-claw 1.0.3 → 1.0.5

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 +1 -1
  2. package/slack-setup.js +70 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-claw",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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",
package/slack-setup.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import puppeteer from "puppeteer-core";
9
- import { readFileSync } from "node:fs";
9
+ import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
10
10
  import { resolve, dirname } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { execSync } from "node:child_process";
@@ -14,6 +14,30 @@ import { homedir } from "node:os";
14
14
 
15
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
16
  const MANIFEST_PATH = resolve(__dirname, "slack-manifest.json");
17
+ const ICON_PATH = resolve(__dirname, "astronaut-icon.png");
18
+
19
+ // Create a tiny base64 icon for the browser banner
20
+ function getIconDataUrl() {
21
+ try {
22
+ const tmpIcon = resolve(__dirname, ".banner-icon-tmp.png");
23
+ // Use sips (macOS) or convert to resize, fall back to full image
24
+ try {
25
+ execSync(`sips -z 48 48 "${ICON_PATH}" --out "${tmpIcon}" 2>/dev/null`, { stdio: "ignore" });
26
+ } catch {
27
+ try {
28
+ execSync(`convert "${ICON_PATH}" -resize 48x48 "${tmpIcon}" 2>/dev/null`, { stdio: "ignore" });
29
+ } catch {
30
+ // Use original (will be larger but still works)
31
+ return `data:image/png;base64,${readFileSync(ICON_PATH).toString("base64")}`;
32
+ }
33
+ }
34
+ const data = `data:image/png;base64,${readFileSync(tmpIcon).toString("base64")}`;
35
+ try { unlinkSync(tmpIcon); } catch {}
36
+ return data;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
17
41
 
18
42
  // ─── Terminal Colors ────────────────────────────────────────────────
19
43
  const bold = (s) => `\x1b[1m${s}\x1b[0m`;
@@ -117,6 +141,49 @@ export default async function selfDrivingSlackSetup() {
117
141
 
118
142
  const page = (await browser.pages())[0] || await browser.newPage();
119
143
 
144
+ // ── Visual cue: inject banner on every page load ──
145
+ const iconDataUrl = getIconDataUrl();
146
+ const iconEl = iconDataUrl
147
+ ? `<img src="${iconDataUrl}" style="width: 22px; height: 22px; border-radius: 5px;" />`
148
+ : `<span style="font-size: 16px;">🤖</span>`;
149
+ const BANNER_CSS = `
150
+ position: fixed; top: 0; left: 0; right: 0; z-index: 2147483647;
151
+ height: 38px; display: flex; align-items: center; justify-content: center; gap: 10px;
152
+ background: linear-gradient(135deg, #6C5CE7, #A855F7);
153
+ color: #fff; font: 600 13px/1 -apple-system, BlinkMacSystemFont, sans-serif;
154
+ box-shadow: 0 2px 8px rgba(108, 92, 231, 0.4);
155
+ letter-spacing: 0.3px;
156
+ `;
157
+ const BANNER_HTML = `
158
+ <div id="astro-claw-banner" style="${BANNER_CSS}">
159
+ ${iconEl}
160
+ <span>Astro Claw is driving this browser</span>
161
+ <span style="opacity: 0.6; font-weight: 400; font-size: 11px; margin-left: 4px;">— do not close</span>
162
+ </div>
163
+ `;
164
+ const BODY_PADDING = `
165
+ if (!document.body.dataset.astroClaw) {
166
+ document.body.style.paddingTop = (parseFloat(getComputedStyle(document.body).paddingTop) + 36) + 'px';
167
+ document.body.dataset.astroClaw = '1';
168
+ }
169
+ `;
170
+ const injectBanner = async (p) => {
171
+ try {
172
+ await p.evaluate((html, bodyJs) => {
173
+ if (!document.getElementById('astro-claw-banner')) {
174
+ document.body.insertAdjacentHTML('beforeend', html);
175
+ eval(bodyJs);
176
+ }
177
+ }, BANNER_HTML, BODY_PADDING);
178
+ } catch {}
179
+ };
180
+
181
+ // Inject banner after every navigation and periodically
182
+ page.on('load', () => injectBanner(page));
183
+ page.on('domcontentloaded', () => injectBanner(page));
184
+ page.on('framenavigated', () => setTimeout(() => injectBanner(page), 500));
185
+ const bannerInterval = setInterval(() => injectBanner(page), 2000);
186
+
120
187
  // ── Step 1: Sign in to Slack (single tab, no interruptions) ──
121
188
  console.log(` → Checking Slack login...`);
122
189
  await page.goto("https://api.slack.com/apps", { waitUntil: "networkidle2", timeout: 30000 });
@@ -509,6 +576,7 @@ export default async function selfDrivingSlackSetup() {
509
576
  }
510
577
 
511
578
  // ── Clean up ──
579
+ clearInterval(bannerInterval);
512
580
  await browser.close();
513
581
 
514
582
  // Return whatever we captured
@@ -524,6 +592,7 @@ export default async function selfDrivingSlackSetup() {
524
592
  return result;
525
593
  } catch (err) {
526
594
  console.log(` ${WARN} Browser automation error: ${err.message}`);
595
+ clearInterval(bannerInterval);
527
596
  if (browser) await browser.close().catch(() => {});
528
597
  return null;
529
598
  }