Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ agent-slack
│ ├── whoami
│ ├── test
│ ├── import-desktop
│ ├── import-brave
│ ├── import-chrome
│ ├── import-firefox
│ └── parse-curl
Expand Down Expand Up @@ -109,18 +110,22 @@ Notes:
On macOS and Windows, authentication happens automatically:

- Default: reads Slack Desktop local data (no need to quit Slack)
- Fallbacks: if that fails, tries Chrome/Firefox extraction (macOS)
- Fallbacks: if that fails, tries Chrome/Brave/Firefox extraction (macOS)

You can also run manual imports:

```bash
agent-slack auth whoami
agent-slack auth import-desktop
agent-slack auth import-brave
agent-slack auth import-chrome
agent-slack auth import-firefox
agent-slack auth test
```

> [!NOTE]
> `import-brave` / `import-chrome` read tokens from a logged-in Slack tab via AppleScript. Both browsers ship with **Allow JavaScript from Apple Events** disabled by default — enable it in **View → Developer** before running these commands. macOS will prompt for your password the first time.

Alternatively, set env vars:

```bash
Expand Down
9 changes: 8 additions & 1 deletion skills/agent-slack/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Claude Code's permission checker has security heuristics that force manual appro

## Quick start (auth)

Authentication is automatic on macOS and Windows (Slack Desktop first, then Chrome/Firefox fallbacks on macOS).
Authentication is automatic on macOS and Windows (Slack Desktop first, then Chrome/Brave/Firefox fallbacks on macOS).

If credentials aren’t available, run one of:

Expand All @@ -57,6 +57,13 @@ agent-slack auth import-desktop
agent-slack auth test
```

- Brave fallback (requires View → Developer → Allow JavaScript from Apple Events):

```bash
agent-slack auth import-brave
agent-slack auth test
```

- Chrome fallback:

```bash
Expand Down
1 change: 1 addition & 0 deletions skills/agent-slack/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Run `agent-slack --help` (or `agent-slack <command> --help`) for the full option
- `agent-slack auth whoami` — show configured workspaces + token sources (secrets redacted)
- `agent-slack auth test [--workspace <url-or-unique-substring>]` — verify credentials (`auth.test`)
- `agent-slack auth import-desktop` — import browser-style creds from Slack Desktop (macOS/Windows)
- `agent-slack auth import-brave` — import creds from Brave (macOS; requires View → Developer → Allow JavaScript from Apple Events)
- `agent-slack auth import-chrome` — import creds from Chrome (macOS)
- `agent-slack auth import-firefox` — import creds from Firefox profile storage (macOS/Linux)
- `agent-slack auth parse-curl` — read a copied Slack cURL command from stdin and save creds
Expand Down
48 changes: 42 additions & 6 deletions src/auth/brave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,47 @@ export type BraveExtracted = {

const IS_MACOS = platform() === "darwin";

// Brave (like Chrome) ships with `Allow JavaScript from Apple Events` OFF.
// Without it, `execute javascript` from osascript fails — and that's how this
// module reads `localStorage.localConfig_v2` from a Slack tab. We detect that
// specific failure and surface an actionable message instead of swallowing it.
export class BraveAppleScriptDisabledError extends Error {
constructor() {
super(
"Brave is blocking JavaScript from Apple Events.\n" +
"Enable it: Brave menu → View → Developer → Allow JavaScript from Apple Events\n" +
"(macOS will prompt for your password the first time.)\n" +
"Then re-run: agent-slack auth import-brave",
);
this.name = "BraveAppleScriptDisabledError";
}
}

// --- AppleScript helpers (for extracting teams from Brave tabs) ---

const APPLESCRIPT_JS_DISABLED_MARKER = "Executing JavaScript through AppleScript is turned off";

function osascript(script: string): string {
return execFileSync("osascript", ["-e", script], {
encoding: "utf8",
timeout: 7000,
stdio: ["ignore", "pipe", "pipe"],
}).trim();
try {
return execFileSync("osascript", ["-e", script], {
encoding: "utf8",
timeout: 7000,
stdio: ["ignore", "pipe", "pipe"],
}).trim();
} catch (err: unknown) {
const stderr =
err && typeof err === "object" && "stderr" in err
? String((err as { stderr: unknown }).stderr ?? "")
: "";
const message = err instanceof Error ? err.message : String(err);
if (
stderr.includes(APPLESCRIPT_JS_DISABLED_MARKER) ||
message.includes(APPLESCRIPT_JS_DISABLED_MARKER)
) {
throw new BraveAppleScriptDisabledError();
}
throw err;
}
}

const TEAM_JSON_PATHS = [
Expand Down Expand Up @@ -179,7 +212,10 @@ export async function extractFromBrave(): Promise<BraveExtracted | null> {
}

return { cookie_d, teams };
} catch {
} catch (err) {
if (err instanceof BraveAppleScriptDisabledError) {
throw err;
}
return null;
}
}
7 changes: 5 additions & 2 deletions src/cli/auth-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,16 @@ export function registerAuthCommand(input: { program: Command; ctx: CliContext }

auth
.command("import-brave")
.description("Import xoxc/xoxd from a logged-in Slack tab in Brave Browser (macOS)")
.description(
"Import xoxc/xoxd from a logged-in Slack tab in Brave Browser (macOS). Requires View → Developer → Allow JavaScript from Apple Events to be enabled in Brave.",
)
.action(async () => {
try {
const extracted = await input.ctx.importBrave();
if (!extracted) {
throw new Error(
"Could not extract tokens from Brave. Open Slack in Brave and ensure you're logged in.",
"Could not extract tokens from Brave. Open Slack in Brave and ensure you're logged in. " +
"If Brave is open with a Slack tab, also confirm View → Developer → Allow JavaScript from Apple Events is enabled.",
);
}

Expand Down
14 changes: 10 additions & 4 deletions src/cli/context-client-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extractFromBrave } from "../auth/brave.ts";
import { BraveAppleScriptDisabledError, extractFromBrave } from "../auth/brave.ts";
import { extractFromChrome } from "../auth/chrome.ts";
import { extractFromSlackDesktop } from "../auth/desktop.ts";
import { extractFromFirefox } from "../auth/firefox.ts";
Expand Down Expand Up @@ -144,9 +144,15 @@ export async function getClientForWorkspace(workspaceUrl?: string): Promise<{
if (chromeResult && chromeResult.teams.length > 0) {
browserSources.push(chromeResult);
}
const braveResult = await extractFromBrave();
if (braveResult && braveResult.teams.length > 0) {
browserSources.push(braveResult);
try {
const braveResult = await extractFromBrave();
if (braveResult && braveResult.teams.length > 0) {
browserSources.push(braveResult);
}
} catch (err) {
if (!(err instanceof BraveAppleScriptDisabledError)) {
throw err;
}
}
const firefoxResult = await extractFromFirefox();
if (firefoxResult && firefoxResult.teams.length > 0) {
Expand Down
Loading