Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4e0bdfe
Add axle extension: package.json
asafamos Apr 20, 2026
6c2d7d0
Add axle extension: src/scan.tsx
asafamos Apr 20, 2026
a9e94e4
Add axle extension: src/statement.tsx
asafamos Apr 20, 2026
b2c24f6
Add axle extension: raycast-env.d.ts
asafamos Apr 20, 2026
8cbabfe
Add axle extension: README.md
asafamos Apr 20, 2026
eaeb6e2
Add axle command-icon.png
asafamos Apr 20, 2026
9632889
Add CHANGELOG.md
asafamos Apr 20, 2026
313668e
Add package-lock.json (Raycast CI requirement)
asafamos Apr 20, 2026
5f94518
Add command-icon.png in assets/
asafamos Apr 20, 2026
ee68eb6
Remove root-level command-icon.png (moved to assets/)
asafamos Apr 20, 2026
eeadb01
icon path: assets/command-icon.png
asafamos Apr 20, 2026
7b16a63
Trigger CI rerun
asafamos Apr 20, 2026
b5cba7c
icon: bare filename (Raycast convention) — assets/ is implicit
asafamos Apr 20, 2026
c36515f
Add required devDependencies (typescript, eslint, prettier, types)
asafamos Apr 20, 2026
48840c2
Update package-lock.json for new devDependencies
asafamos Apr 20, 2026
44c4828
Title: 'Axle' (title case required by Raycast store)
asafamos Apr 20, 2026
a648ed1
Prettier: reformat package.json (multi-line categories)
asafamos Apr 20, 2026
21cbdd7
Prettier: printWidth 120 (Raycast CI config)
asafamos Apr 20, 2026
910a215
Add .prettierrc (printWidth 120 matching Raycast CI)
asafamos Apr 20, 2026
4bdb2f8
Add .eslintrc.json extending @raycast config
asafamos Apr 20, 2026
1720df1
Remove unused useNavigation import
asafamos Apr 20, 2026
7cddd6a
Add tsconfig.json (required for Raycast TypeScript check)
asafamos Apr 20, 2026
46b0744
Bump @raycast/api + @types/react to current Raycast versions
asafamos Apr 20, 2026
eae1716
Update lock with new versions
asafamos Apr 20, 2026
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
23 changes: 23 additions & 0 deletions extensions/axle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# axle for Raycast

Scan any URL for WCAG 2.1 / 2.2 AA violations without leaving Raycast.

## Commands

- **Scan URL for Accessibility** — prompt for a URL, run the axle scanner, show a filterable list of violations with severity and affected-element counts. Press Enter on any row for the offending HTML and a link to the WCAG reference.
- **Open Hebrew Accessibility Statement Generator** — one-click open of the free `תקנה 35`-aligned statement generator.

## Under the hood

Commands call the public axle API at `https://axle-iota.vercel.app/api/scan`. No account required. Free tier is rate-limited — bring your own `ANTHROPIC_API_KEY` (via the web UI or the CLI) for unlimited AI fixes.

## Install

Once listed in the Raycast Store: search "axle".

## Dev

```
npm install
npm run dev
```
Binary file added extensions/axle/command-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions extensions/axle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "axle",
"title": "axle — Accessibility Scanner",
"description": "Scan any URL for WCAG 2.1 / 2.2 AA accessibility violations without leaving Raycast. Results appear as a list; pick any violation to see the offending element and suggested fix.",
"icon": "command-icon.png",
"author": "asafamos",
"categories": ["Developer Tools", "Web"],
"license": "MIT",
"commands": [
{
"name": "scan",
"title": "Scan URL for Accessibility",
"subtitle": "axle",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing metadata/ folder with screenshots

The scan command has "mode": "view", so the store requires Raycast-styled screenshots in a metadata/ directory. Without them the extension will be rejected from the store. See the Raycast docs for the expected format and dimensions.

Rule Used: What: Extensions with view-type commands must incl... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/axle/package.json
Line: 14

Comment:
**Missing `metadata/` folder with screenshots**

The `scan` command has `"mode": "view"`, so the store requires Raycast-styled screenshots in a `metadata/` directory. Without them the extension will be rejected from the store. See the [Raycast docs](https://developers.raycast.com/basics/prepare-an-extension-for-store#screenshots) for the expected format and dimensions.

**Rule Used:** What: Extensions with view-type commands must incl... ([source](https://app.greptile.com/review/custom-context?memory=87059ac1-c601-487f-9f1c-bce8a3cb6209))

How can I resolve this? If you propose a fix, please make it concise.

"description": "Run an axe-core scan against any public URL and show violations.",
"mode": "view",
"arguments": [
{
"name": "url",
"type": "text",
"placeholder": "https://example.com",
"required": true
}
]
},
{
"name": "statement",
"title": "Open Hebrew Accessibility Statement Generator",
"subtitle": "axle",
"description": "Opens the free Hebrew statement generator — aligned with Israeli תקנה 35.",
"mode": "no-view"
}
],
"dependencies": {
"@raycast/api": "^1.78.0"
}
}
Comment on lines +1 to +55
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing CHANGELOG.md

Every PR to this repo must include a CHANGELOG.md file. For an initial release it should follow the standard pattern:

## [Initial Release] - {PR_MERGE_DATE}

- Initial release of axle — Accessibility Scanner

The {PR_MERGE_DATE} placeholder is filled in automatically when the PR merges.

Rule Used: What: Ensure that CHANGELOG.md is created or updat... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/axle/package.json
Line: 1-37

Comment:
**Missing `CHANGELOG.md`**

Every PR to this repo must include a `CHANGELOG.md` file. For an initial release it should follow the standard pattern:

```markdown
## [Initial Release] - {PR_MERGE_DATE}

- Initial release of axle — Accessibility Scanner
```

The `{PR_MERGE_DATE}` placeholder is filled in automatically when the PR merges.

**Rule Used:** What: Ensure that CHANGELOG.md is created or updat... ([source](https://app.greptile.com/review/custom-context?memory=97cd51bc-963b-43f5-acc3-9ba85fe7bb2d))

How can I resolve this? If you propose a fix, please make it concise.

31 changes: 31 additions & 0 deletions extensions/axle/raycast-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// <reference types="@raycast/api">

/* 🚧 🚧 🚧
* This file is auto-generated from the extension's manifest.
* Do not modify manually. Instead, update the `package.json` file.
* 🚧 🚧 🚧 */

/* eslint-disable @typescript-eslint/ban-types */

type ExtensionPreferences = {}

/** Preferences accessible in all the extension's commands */
declare type Preferences = ExtensionPreferences

declare namespace Preferences {
/** Preferences accessible in the `scan` command */
export type Scan = ExtensionPreferences & {}
/** Preferences accessible in the `statement` command */
export type Statement = ExtensionPreferences & {}
}

declare namespace Arguments {
/** Arguments passed to the `scan` command */
export type Scan = {
/** https://example.com */
"url": string
}
/** Arguments passed to the `statement` command */
export type Statement = {}
}

170 changes: 170 additions & 0 deletions extensions/axle/src/scan.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {
ActionPanel,
Action,
List,
Detail,
Icon,
showToast,
Toast,
useNavigation,
} from "@raycast/api";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused useNavigation import

useNavigation is imported but never called in this file. Action.Push handles navigation internally and doesn't require it directly.

Suggested change
import {
ActionPanel,
Action,
List,
Detail,
Icon,
showToast,
Toast,
useNavigation,
} from "@raycast/api";
import {
ActionPanel,
Action,
List,
Detail,
Icon,
showToast,
Toast,
} from "@raycast/api";
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/axle/src/scan.tsx
Line: 1-10

Comment:
**Unused `useNavigation` import**

`useNavigation` is imported but never called in this file. `Action.Push` handles navigation internally and doesn't require it directly.

```suggestion
import {
  ActionPanel,
  Action,
  List,
  Detail,
  Icon,
  showToast,
  Toast,
} from "@raycast/api";
```

How can I resolve this? If you propose a fix, please make it concise.

import { useEffect, useState } from "react";

type AxeViolation = {
id: string;
impact: "critical" | "serious" | "moderate" | "minor" | null;
help: string;
description: string;
helpUrl: string;
nodes: Array<{
html: string;
target: string[];
failureSummary: string;
}>;
};

type ScanResult = {
url: string;
title: string;
violations: AxeViolation[];
summary: {
critical: number;
serious: number;
moderate: number;
minor: number;
};
};

const AXLE_API = "https://axle-iota.vercel.app";

const IMPACT_ICON: Record<string, { source: Icon; tintColor?: string }> = {
critical: { source: Icon.ExclamationMark, tintColor: "#dc2626" },
serious: { source: Icon.Warning, tintColor: "#ea580c" },
moderate: { source: Icon.Info, tintColor: "#d97706" },
minor: { source: Icon.Circle, tintColor: "#2563eb" },
};

export default function Scan(props: { arguments: { url: string } }) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Use auto-generated argument types instead of manual typing

The raycast-env.d.ts file already exports Arguments.Scan which covers the url argument. Prefer LaunchProps with the generated type to stay in sync with the manifest automatically.

Suggested change
export default function Scan(props: { arguments: { url: string } }) {
export default function Scan(props: LaunchProps<{ arguments: Arguments.Scan }>) {

You'll also need to add LaunchProps to the @raycast/api import.

Rule Used: What: Don't manually define Preferences for `get... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/axle/src/scan.tsx
Line: 47

Comment:
**Use auto-generated argument types instead of manual typing**

The `raycast-env.d.ts` file already exports `Arguments.Scan` which covers the `url` argument. Prefer `LaunchProps` with the generated type to stay in sync with the manifest automatically.

```suggestion
export default function Scan(props: LaunchProps<{ arguments: Arguments.Scan }>) {
```

You'll also need to add `LaunchProps` to the `@raycast/api` import.

**Rule Used:** What: Don't manually define `Preferences` for `get... ([source](https://app.greptile.com/review/custom-context?memory=d93fc9fb-a45d-4479-a6a4-b1b4af98ebc8))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

const { url } = props.arguments;
const [result, setResult] = useState<ScanResult | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const normalized = /^https?:\/\//i.test(url) ? url : `https://${url}`;
showToast({ style: Toast.Style.Animated, title: "Scanning", message: normalized });
fetch(`${AXLE_API}/api/scan`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: normalized }),
})
.then(async (r) => {
const data = (await r.json()) as ScanResult & { error?: string };
if (!r.ok || data.error) throw new Error(data.error || `HTTP ${r.status}`);
setResult(data);
showToast({
style: Toast.Style.Success,
title: `${data.violations.length} violations`,
message: normalized,
});
})
.catch((err) => {
const msg = err instanceof Error ? err.message : "Scan failed";
setError(msg);
showToast({ style: Toast.Style.Failure, title: "Scan failed", message: msg });
})
.finally(() => setLoading(false));
}, [url]);

if (loading) {
return <List isLoading={true} searchBarPlaceholder="Scanning…" />;
}

if (error) {
return (
<Detail markdown={`# Scan failed\n\n${error}`} />
);
}

if (!result) return null;

return (
<List
navigationTitle={`${result.title || result.url} — ${result.violations.length} rules`}
searchBarPlaceholder="Filter violations"
>
<List.Section title="Summary">
<List.Item
title={`${result.summary.critical} critical · ${result.summary.serious} serious · ${result.summary.moderate} moderate · ${result.summary.minor} minor`}
icon={Icon.BarChart}
/>
</List.Section>
<List.Section title="Violations">
{result.violations.map((v) => (
<List.Item
key={v.id}
title={v.help}
subtitle={v.id}
accessories={[
{ text: `${v.nodes.length} element${v.nodes.length === 1 ? "" : "s"}` },
{ tag: v.impact ?? "minor" },
]}
icon={IMPACT_ICON[v.impact ?? "minor"]}
actions={
<ActionPanel>
<Action.Push title="View Details" target={<ViolationDetail violation={v} />} />
<Action.OpenInBrowser title="Open WCAG Reference" url={v.helpUrl} />
<Action.CopyToClipboard
title="Copy Rule ID"
content={v.id}
/>
</ActionPanel>
}
/>
))}
</List.Section>
</List>
);
}

function ViolationDetail({ violation }: { violation: AxeViolation }) {
const first = violation.nodes[0];
const md = [
`# ${violation.help}`,
"",
`**Rule:** \`${violation.id}\` · **Impact:** ${violation.impact ?? "minor"}`,
"",
violation.description,
"",
`---`,
"",
`## First affected element`,
"",
`\`${first?.target.join(" ")}\``,
"",
"```html",
first?.html ?? "",
"```",
"",
first?.failureSummary ? `> ${first.failureSummary}` : "",
"",
`---`,
"",
`${violation.nodes.length} element(s) affected in total. [Open full report →](${AXLE_API}/)`,
].join("\n");

return (
<Detail
markdown={md}
actions={
<ActionPanel>
<Action.OpenInBrowser title="Open WCAG Reference" url={violation.helpUrl} />
<Action.CopyToClipboard
title="Copy Element HTML"
content={first?.html ?? ""}
/>
</ActionPanel>
}
/>
);
}
6 changes: 6 additions & 0 deletions extensions/axle/src/statement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { open, showHUD } from "@raycast/api";

export default async function Statement() {
await open("https://axle-iota.vercel.app/statement");
await showHUD("Opened axle Hebrew statement generator");
}
Loading