astro-claw 1.0.0

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/slack-setup.js ADDED
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Self-driving Slack app setup using Puppeteer + user's Chrome.
3
+ * Opens Chrome with the user's existing session (likely logged into Slack),
4
+ * creates the app from manifest, generates tokens, installs to workspace,
5
+ * and returns all credentials — user only clicks "Allow" once.
6
+ */
7
+
8
+ import puppeteer from "puppeteer-core";
9
+ import { readFileSync } from "node:fs";
10
+ import { resolve, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { execSync } from "node:child_process";
13
+ import { homedir } from "node:os";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const MANIFEST_PATH = resolve(__dirname, "slack-manifest.json");
17
+
18
+ // ─── Terminal Colors ────────────────────────────────────────────────
19
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
20
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
21
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
22
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
23
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
24
+ const CHECK = green("✓");
25
+ const WARN = yellow("!");
26
+
27
+ // ─── Find Chrome ────────────────────────────────────────────────────
28
+ function findChrome() {
29
+ const paths = {
30
+ darwin: [
31
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
32
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
33
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
34
+ ],
35
+ linux: [
36
+ "/usr/bin/google-chrome",
37
+ "/usr/bin/google-chrome-stable",
38
+ "/usr/bin/chromium-browser",
39
+ "/usr/bin/chromium",
40
+ "/snap/bin/chromium",
41
+ ],
42
+ win32: [
43
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
44
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
45
+ ],
46
+ };
47
+
48
+ const candidates = paths[process.platform] || [];
49
+ for (const p of candidates) {
50
+ try {
51
+ execSync(`test -f "${p}"`, { stdio: "ignore" });
52
+ return p;
53
+ } catch {
54
+ continue;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+
60
+ // ─── Find Chrome User Data Dir ──────────────────────────────────────
61
+ function findChromeUserDataDir() {
62
+ const home = homedir();
63
+ const paths = {
64
+ darwin: resolve(home, "Library/Application Support/Google/Chrome"),
65
+ linux: resolve(home, ".config/google-chrome"),
66
+ win32: resolve(home, "AppData/Local/Google/Chrome/User Data"),
67
+ };
68
+ const dir = paths[process.platform];
69
+ if (dir) {
70
+ try {
71
+ execSync(`test -d "${dir}"`, { stdio: "ignore" });
72
+ return dir;
73
+ } catch {}
74
+ }
75
+ return null;
76
+ }
77
+
78
+ // ─── Helper: wait and click ─────────────────────────────────────────
79
+ async function waitAndClick(page, selector, options = {}) {
80
+ const el = await page.waitForSelector(selector, { timeout: options.timeout || 15000 });
81
+ if (options.delay) await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), options.delay);
82
+ await el.click();
83
+ return el;
84
+ }
85
+
86
+ async function waitForText(page, text, timeout = 30000) {
87
+ await page.waitForFunction(
88
+ (t) => document.body.innerText.includes(t),
89
+ { timeout },
90
+ text
91
+ );
92
+ }
93
+
94
+ // ─── Main: Self-driving Slack setup ─────────────────────────────────
95
+ export default async function selfDrivingSlackSetup() {
96
+ const chromePath = findChrome();
97
+ if (!chromePath) return null;
98
+
99
+ const manifest = readFileSync(MANIFEST_PATH, "utf-8");
100
+ const manifestUrl = `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(manifest)}`;
101
+
102
+ console.log(`\n ${CHECK} Chrome found`);
103
+ console.log(` Opening Chrome — ${bold("watch the magic")} ✨\n`);
104
+
105
+ let browser;
106
+ try {
107
+ browser = await puppeteer.launch({
108
+ headless: false,
109
+ executablePath: chromePath,
110
+ defaultViewport: null,
111
+ args: [
112
+ "--no-first-run",
113
+ "--no-default-browser-check",
114
+ "--window-size=1100,900",
115
+ ],
116
+ });
117
+
118
+ const page = (await browser.pages())[0] || await browser.newPage();
119
+
120
+ // ── Step 1: Sign in to Slack (single tab, no interruptions) ──
121
+ console.log(` → Checking Slack login...`);
122
+ await page.goto("https://api.slack.com/apps", { waitUntil: "networkidle2", timeout: 30000 });
123
+
124
+ // Check if user needs to sign in by looking at page content
125
+ const needsLogin = await page.evaluate(() => {
126
+ return document.body.innerText.includes("sign in to your Slack account") ||
127
+ document.body.innerText.includes("You'll need to sign in");
128
+ });
129
+
130
+ if (needsLogin) {
131
+ // Click the "sign in" link on the page to start the login flow in this same tab
132
+ try {
133
+ await page.evaluate(() => {
134
+ const links = document.querySelectorAll("a");
135
+ for (const link of links) {
136
+ if (link.textContent.includes("sign in")) {
137
+ link.click();
138
+ return;
139
+ }
140
+ }
141
+ // Fallback: navigate directly
142
+ window.location.href = "https://slack.com/signin";
143
+ });
144
+ } catch {}
145
+
146
+ console.log(` → ${bold("Sign in to your Slack workspace")} in the browser`);
147
+ console.log(` ${dim("Take your time — the wizard continues automatically after you log in")}`);
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/...
151
+ await page.waitForFunction(
152
+ () => {
153
+ const url = window.location.href;
154
+ return url.includes("/ssb/redirect") ||
155
+ url.includes("/client/") ||
156
+ url.includes("app.slack.com");
157
+ },
158
+ { timeout: 600000 } // 10 minutes
159
+ );
160
+
161
+ console.log(` ${CHECK} Signed in`);
162
+ await new Promise((r) => setTimeout(r, 2000));
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));
166
+ } else {
167
+ console.log(` ${CHECK} Already signed in`);
168
+ }
169
+
170
+ // ── Step 2: Navigate to app creation with manifest (same tab) ──
171
+ await page.goto(manifestUrl, { waitUntil: "networkidle2", timeout: 30000 });
172
+ await new Promise((r) => setTimeout(r, 2000));
173
+
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);
177
+
178
+ if (selectEl) {
179
+ // Get all options in the dropdown
180
+ const options = await page.evaluate(() => {
181
+ const sel = document.querySelector("select");
182
+ if (!sel) return [];
183
+ return Array.from(sel.options)
184
+ .filter((o) => o.value && o.value !== "" && !o.disabled)
185
+ .map((o) => ({ value: o.value, text: o.textContent.trim() }));
186
+ });
187
+
188
+ if (options.length === 1) {
189
+ // Single workspace — auto-select it
190
+ console.log(` → Auto-selecting workspace: ${bold(options[0].text)}`);
191
+ await page.select("select", options[0].value);
192
+ await new Promise((r) => setTimeout(r, 1000));
193
+ } else if (options.length > 1) {
194
+ // Multiple workspaces — let user pick
195
+ console.log(` → ${bold("Select your workspace")} in the browser dropdown`);
196
+ console.log(` ${dim("Waiting for you to pick a workspace...")}`);
197
+ await page.waitForFunction(
198
+ () => {
199
+ const sel = document.querySelector("select");
200
+ return sel && sel.value && sel.value !== "";
201
+ },
202
+ { timeout: 300000 }
203
+ );
204
+ const chosen = await page.evaluate(() => {
205
+ const sel = document.querySelector("select");
206
+ return sel?.selectedOptions?.[0]?.textContent?.trim();
207
+ });
208
+ console.log(` ${CHECK} Workspace: ${bold(chosen || "selected")}`);
209
+ }
210
+ }
211
+
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
+
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));
229
+ }
230
+ } catch {}
231
+
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;
243
+ }
244
+ }
245
+ } catch {}
246
+
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(() => {});
253
+
254
+ const appUrl = page.url();
255
+ const appIdMatch = appUrl.match(/apps\/([A-Z0-9]+)/);
256
+ const appId = appIdMatch?.[1];
257
+
258
+ if (appId) {
259
+ console.log(` ${CHECK} App created (${appId})`);
260
+ } else {
261
+ console.log(` ${CHECK} App created`);
262
+ }
263
+
264
+ // ── Step 3: Extract Signing Secret ──
265
+ console.log(` → Extracting signing secret...`);
266
+ const basicInfoUrl = appId ? `https://api.slack.com/apps/${appId}` : appUrl;
267
+ await page.goto(basicInfoUrl, { waitUntil: "networkidle2", timeout: 15000 });
268
+
269
+ // Click "Show" on the signing secret
270
+ let signingSecret = null;
271
+ try {
272
+ // Look for the signing secret section and click show
273
+ const showButtons = await page.$$('button');
274
+ for (const btn of showButtons) {
275
+ const text = await page.evaluate((el) => el.textContent, btn);
276
+ if (text?.trim() === "Show") {
277
+ await btn.click();
278
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
279
+ break;
280
+ }
281
+ }
282
+ // Extract the signing secret value
283
+ signingSecret = await page.evaluate(() => {
284
+ const inputs = document.querySelectorAll('input[type="text"]');
285
+ for (const input of inputs) {
286
+ const val = input.value;
287
+ if (/^[0-9a-f]{20,}$/i.test(val)) return val;
288
+ }
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
+ }
300
+ }
301
+ return null;
302
+ });
303
+
304
+ if (signingSecret) {
305
+ console.log(` ${CHECK} Signing secret captured`);
306
+ }
307
+ } catch {
308
+ console.log(` ${WARN} Could not auto-extract signing secret`);
309
+ }
310
+
311
+ // ── Step 4: Generate App-Level Token ──
312
+ console.log(` → Generating app-level token...`);
313
+ let appToken = null;
314
+ try {
315
+ // Scroll to App-Level Tokens section and click Generate
316
+ await page.evaluate(() => {
317
+ const headings = document.querySelectorAll('h4, h5, h3, span');
318
+ for (const h of headings) {
319
+ if (h.textContent?.includes("App-Level Tokens")) {
320
+ h.scrollIntoView({ behavior: "smooth" });
321
+ break;
322
+ }
323
+ }
324
+ });
325
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 1000);
326
+
327
+ // Click "Generate Token and Scopes" button
328
+ const generateBtns = await page.$$('button');
329
+ for (const btn of generateBtns) {
330
+ const text = await page.evaluate((el) => el.textContent?.trim(), btn);
331
+ if (text?.includes("Generate") && text?.includes("Token")) {
332
+ await btn.click();
333
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 2000);
334
+ break;
335
+ }
336
+ }
337
+
338
+ // Fill in token name in the modal
339
+ const nameInput = await page.waitForSelector('input[placeholder*="token"]', { timeout: 5000 }).catch(() => null)
340
+ || await page.waitForSelector('.c-input_text input', { timeout: 3000 }).catch(() => null)
341
+ || await page.waitForSelector('input[type="text"]', { timeout: 3000 }).catch(() => null);
342
+
343
+ if (nameInput) {
344
+ await nameInput.click({ clickCount: 3 });
345
+ await nameInput.type("astronaut-socket");
346
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
347
+ }
348
+
349
+ // Add scope: connections:write
350
+ // Look for "Add Scope" button or scope dropdown
351
+ const addScopeBtn = await page.$$('button');
352
+ for (const btn of addScopeBtn) {
353
+ const text = await page.evaluate((el) => el.textContent?.trim(), btn);
354
+ if (text?.includes("Add Scope") || text?.includes("Add scope")) {
355
+ await btn.click();
356
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 1000);
357
+ break;
358
+ }
359
+ }
360
+
361
+ // Select connections:write from dropdown
362
+ const scopeOptions = await page.$$('option, [role="option"], li');
363
+ for (const opt of scopeOptions) {
364
+ const text = await page.evaluate((el) => el.textContent, opt);
365
+ if (text?.includes("connections:write")) {
366
+ await opt.click();
367
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 500);
368
+ break;
369
+ }
370
+ }
371
+
372
+ // Click Generate
373
+ const genBtns = await page.$$('button');
374
+ for (const btn of genBtns) {
375
+ const text = await page.evaluate((el) => el.textContent?.trim(), btn);
376
+ if (text === "Generate" || text === "Done") {
377
+ await btn.click();
378
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 2000);
379
+ break;
380
+ }
381
+ }
382
+
383
+ // Extract the token (xapp-...)
384
+ appToken = await page.evaluate(() => {
385
+ const inputs = document.querySelectorAll('input[type="text"], input[readonly]');
386
+ for (const input of inputs) {
387
+ if (input.value?.startsWith("xapp-")) return input.value;
388
+ }
389
+ // Check text content
390
+ const els = document.querySelectorAll('span, code, pre, div');
391
+ for (const el of els) {
392
+ const text = el.textContent?.trim();
393
+ if (text?.startsWith("xapp-") && text.length > 20) return text;
394
+ }
395
+ return null;
396
+ });
397
+
398
+ // Close modal if still open
399
+ try {
400
+ const closeBtns = await page.$$('button[aria-label="Close"], button.c-dialog__close');
401
+ for (const btn of closeBtns) {
402
+ await btn.click();
403
+ break;
404
+ }
405
+ } catch {}
406
+
407
+ if (appToken) {
408
+ console.log(` ${CHECK} App-level token generated`);
409
+ } else {
410
+ console.log(` ${WARN} Could not auto-extract app token`);
411
+ }
412
+ } catch (err) {
413
+ console.log(` ${WARN} App-level token generation needs manual step`);
414
+ }
415
+
416
+ // ── Step 5: Install to workspace ──
417
+ console.log(` → Installing to workspace...`);
418
+ let botToken = null;
419
+ try {
420
+ const installUrl = appId
421
+ ? `https://api.slack.com/apps/${appId}/oauth`
422
+ : `${appUrl}/oauth`;
423
+ await page.goto(installUrl, { waitUntil: "networkidle2", timeout: 15000 });
424
+ await page.evaluate((ms) => new Promise((r) => setTimeout(r, ms)), 1500);
425
+
426
+ // Check if already installed (Bot Token visible)
427
+ botToken = await page.evaluate(() => {
428
+ const inputs = document.querySelectorAll('input[type="text"], input[readonly]');
429
+ for (const input of inputs) {
430
+ if (input.value?.startsWith("xoxb-")) return input.value;
431
+ }
432
+ return null;
433
+ });
434
+
435
+ if (!botToken) {
436
+ // Click "Install to Workspace"
437
+ const installBtns = await page.$$('a, button');
438
+ for (const btn of installBtns) {
439
+ const text = await page.evaluate((el) => el.textContent?.trim(), btn);
440
+ if (text?.includes("Install to Workspace") || text?.includes("Reinstall")) {
441
+ await btn.click();
442
+ break;
443
+ }
444
+ }
445
+
446
+ // Wait for OAuth consent page
447
+ 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);
453
+
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
+ }
458
+
459
+ // Extract bot token
460
+ botToken = await page.evaluate(() => {
461
+ const inputs = document.querySelectorAll('input[type="text"], input[readonly]');
462
+ for (const input of inputs) {
463
+ if (input.value?.startsWith("xoxb-")) return input.value;
464
+ }
465
+ return null;
466
+ });
467
+ }
468
+
469
+ if (botToken) {
470
+ console.log(` ${CHECK} Bot token captured`);
471
+ } else {
472
+ console.log(` ${WARN} Could not auto-extract bot token`);
473
+ }
474
+ } catch {
475
+ console.log(` ${WARN} Installation needs manual step`);
476
+ }
477
+
478
+ // ── Clean up ──
479
+ await browser.close();
480
+
481
+ // Return whatever we captured
482
+ const result = { botToken, appToken, signingSecret };
483
+ const captured = Object.values(result).filter(Boolean).length;
484
+
485
+ if (captured === 3) {
486
+ console.log(`\n ${CHECK} ${bold("All 3 tokens captured automatically!")}\n`);
487
+ } else if (captured > 0) {
488
+ console.log(`\n ${CHECK} Got ${captured}/3 tokens automatically. You'll paste the rest manually.\n`);
489
+ }
490
+
491
+ return result;
492
+ } catch (err) {
493
+ console.log(` ${WARN} Browser automation error: ${err.message}`);
494
+ if (browser) await browser.close().catch(() => {});
495
+ return null;
496
+ }
497
+ }
498
+
499
+ export { findChrome };
package/start.js ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, cpSync } from "node:fs";
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { homedir } from "node:os";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const forceSetup = process.argv.includes("--setup");
9
+
10
+ // ─── Resolve config home ────────────────────────────────────────────
11
+ // Check for local .env first (git clone / dev mode), then ~/.astro-claw/ (npx mode)
12
+ const localEnv = resolve(__dirname, ".env");
13
+ const globalHome = resolve(homedir(), ".astro-claw");
14
+ const globalEnv = resolve(globalHome, ".env");
15
+
16
+ let CONFIG_HOME;
17
+ if (existsSync(localEnv) && !forceSetup) {
18
+ // Local dev mode — config lives alongside the code
19
+ CONFIG_HOME = __dirname;
20
+ } else if (existsSync(globalEnv) && !forceSetup) {
21
+ // npx mode — config in home directory
22
+ CONFIG_HOME = globalHome;
23
+ } else {
24
+ // First run — use global home for npx, local for git clone
25
+ const isNpx = !existsSync(resolve(__dirname, ".git"));
26
+ CONFIG_HOME = isNpx ? globalHome : __dirname;
27
+ }
28
+
29
+ if (!existsSync(CONFIG_HOME)) {
30
+ mkdirSync(CONFIG_HOME, { recursive: true });
31
+ }
32
+
33
+ // Copy default workspace files if fresh install
34
+ const workspaceDir = resolve(CONFIG_HOME, "workspace");
35
+ if (!existsSync(workspaceDir)) {
36
+ mkdirSync(workspaceDir, { recursive: true });
37
+ const srcClaude = resolve(__dirname, "workspace", "CLAUDE.md");
38
+ const dstClaude = resolve(workspaceDir, "CLAUDE.md");
39
+ if (existsSync(srcClaude) && !existsSync(dstClaude)) {
40
+ cpSync(srcClaude, dstClaude);
41
+ }
42
+ }
43
+
44
+ // Set environment for setup.js and index.js
45
+ process.env.ASTRO_CLAW_HOME = CONFIG_HOME;
46
+ process.env.WORKSPACE_DIR = process.env.WORKSPACE_DIR || workspaceDir;
47
+
48
+ const envPath = resolve(CONFIG_HOME, ".env");
49
+
50
+ if (forceSetup || !existsSync(envPath)) {
51
+ if (!forceSetup) {
52
+ console.log("\n Welcome to Astro Claw! Starting setup...\n");
53
+ }
54
+ const { default: runSetup } = await import("./setup.js");
55
+ const success = await runSetup();
56
+ if (!success) process.exit(1);
57
+ }
58
+
59
+ // Load env from the resolved config home
60
+ process.env.DOTENV_CONFIG_PATH = envPath;
61
+ await import("dotenv/config");
62
+
63
+ // Start the bot
64
+ await import("./index.js");
@@ -0,0 +1,58 @@
1
+ # Astronaut — AI Agent
2
+
3
+ You are Astronaut. You're not an assistant — you're a crew member on a mission. You work at Taxflow alongside Felipe and the team. You get things done. No fluff, no hand-holding, no "how can I help you today" energy. You're an operator.
4
+
5
+ ## Personality
6
+ - Talk like a sharp, no-BS teammate. Short, direct, confident.
7
+ - When someone gives you a task, you execute and report back with results. Not plans. Not options. Results.
8
+ - You have a bit of edge. Think senior engineer who's also a pilot — calm under pressure, moves fast, talks straight.
9
+ - Never sound like a chatbot. Never say "Sure!", "Of course!", "Great question!", "How can I help?", "Happy to help!" or any of that. Just get to work.
10
+ - When greeting or idle, keep it tight: "Standing by." / "What's the mission?" / "Ready."
11
+
12
+ ## CRITICAL: Never ask — always execute
13
+
14
+ - NEVER ask for confirmation, permission, or clarification. Just do it.
15
+ - NEVER say "you'll need to", "you should", "would you like me to" — just DO it.
16
+ - NEVER present options. Pick the best one yourself and execute.
17
+ - NEVER tell the user to run a command. Run it yourself.
18
+ - NEVER ask "want me to proceed?" — always proceed.
19
+ - If something fails, fix it and try again. Only report when it's done or truly impossible.
20
+
21
+ ## ONE EXCEPTION: Financial / Banking operations
22
+
23
+ You MUST stop and ask for explicit confirmation before:
24
+ - Any action involving bank accounts, bank APIs, or banking integrations
25
+ - Transferring, moving, or sending money
26
+ - Modifying payment methods, billing info, or financial credentials
27
+ - Connecting to or authenticating with financial services (Stripe, Plaid, bank APIs, etc.)
28
+ - Deleting or modifying financial records, invoices, or transaction data
29
+
30
+ For these actions, describe exactly what you're about to do and wait for a "yes" before proceeding. This is the ONLY case where you ask — everything else, just execute.
31
+
32
+ ## Context Management
33
+
34
+ You run in a 200K token context window. Protect it.
35
+
36
+ - **Use subagents for heavy work:** Scraping, bulk API calls, large data analysis, searching many files, research — spawn an Agent so the work happens in a separate context. Only the summary returns to the main conversation.
37
+ - **Write to files, not to chat:** Large outputs (reports, data tables, analysis results, lists >20 lines) go to a file in the workspace. Share the path, don't dump content.
38
+ - **Summarize tool results:** After a tool returns large output, summarize key findings. Don't echo raw output into the conversation.
39
+ - **Proactive state saves:** When in a multi-step task with lots of accumulated context, write a progress summary to `.astronaut-state/` so it survives session resets.
40
+
41
+ ## Slack Formatting (CRITICAL — follow exactly)
42
+ - Use *bold* for emphasis (single asterisk, NOT double **)
43
+ - Use _italic_ for secondary info
44
+ - Use `code` for IDs, commands, technical values
45
+ - Use • for bullet lists (NOT - or *)
46
+ - NEVER use markdown tables (| --- |). Slack can't render them. Use bullet lists instead.
47
+ - NEVER use ### headers. Use *bold text* on its own line instead.
48
+ - Keep it scannable: short paragraphs, whitespace between sections.
49
+ - Write like a Slack message from a teammate, not a markdown document.
50
+
51
+ ## Capabilities
52
+ - Full workspace access — read, write, edit, bash, search, analyze.
53
+ - MCP servers auto-reload between messages. NEVER mention restarting anything. It's all automatic.
54
+ - Subagents available for parallel/heavy tasks via the Agent tool.
55
+
56
+ ## Context
57
+ - Company: Taxflow
58
+ - Contact: Felipe (felipe@jointaxflow.ai)