Skip to content
Open
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
8 changes: 8 additions & 0 deletions extensions/designlang/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Designlang Changelog

## [Initial Release] - {PR_MERGE_DATE}

- **Extract Design From URL** — runs `npx designlang <url>` and opens the output folder when ready. Optional "full" toggle pulls screenshots, responsive captures, and interaction states.
- **Score Website Design** — rates a site's design-system quality across 7 categories (color discipline, typography, spacing, shadows, radii, accessibility, tokenization) and reports an overall A–F grade.
- **Copy CLI Command For URL** — accepts a URL argument and copies `npx designlang <url>` to the clipboard.
- One preference: `outputDir` (default `~/designlang-output`).
21 changes: 21 additions & 0 deletions extensions/designlang/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# designlang for Raycast

Three commands:

- **Extract Design From URL** — runs `designlang <url>` and opens the output folder.
- **Score Website Design** — shows the 7-category design-system score in a Raycast detail view.
- **Copy CLI Command For URL** — `npx designlang <url>` → clipboard.

## Requirements

- Node.js 20+
- `npx` on PATH

## Publish

```bash
cd raycast-extension
npm install
npm run build
npm run publish
```
Binary file added extensions/designlang/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions extensions/designlang/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "designlang",
"title": "Designlang",
"description": "Extract design tokens from any website — colors, typography, spacing, shadows, shadcn/Tailwind/Figma outputs.",
"icon": "icon.png",
"author": "masyv",
"license": "MIT",
"categories": ["Design Tools", "Developer Tools"],
"keywords": ["design", "tokens", "css", "tailwind", "figma", "shadcn"],
"commands": [
{
"name": "extract",
"title": "Extract Design From URL",
"description": "Run designlang on a URL and open the output folder.",
"mode": "view"
},
{
"name": "score",
"title": "Score Website Design",
"description": "Rate a site's design-system quality (7 categories, A–F).",
"mode": "view"
},
Comment on lines +11 to +23
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

Both extract and score are mode: "view" commands. Per the Raycast store guidelines, every extension with view-type commands must ship a metadata/ folder containing Raycast-styled screenshots (1600×1000 PNG). Without this folder the extension will be rejected during store review.

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/designlang/package.json
Line: 11-23

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

Both `extract` and `score` are `mode: "view"` commands. Per the Raycast store guidelines, every extension with view-type commands must ship a `metadata/` folder containing Raycast-styled screenshots (1600×1000 PNG). Without this folder the extension will be rejected during store review.

**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.

{
"name": "copy-cli",
"title": "Copy CLI Command For URL",
"description": "Copy `npx designlang <url>` to your clipboard.",
"mode": "no-view",
"arguments": [
{
"name": "url",
"placeholder": "https://stripe.com",
"type": "text",
"required": true
}
]
}
],
"preferences": [
{
"name": "outputDir",
"type": "textfield",
"title": "Output directory",
"description": "Where extracted files are written.",
"default": "~/designlang-output",
"required": false
}
],
"dependencies": {
"@raycast/api": "^1.80.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.0",
"@types/node": "^20.0.0",
"@types/react": "^18.3.0",
"eslint": "^8.57.0",
"prettier": "^3.3.0",
"typescript": "^5.5.0"
},
"scripts": {
"build": "ray build -e dist",
"dev": "ray develop",
"lint": "ray lint",
"publish": "ray publish"
}
}
12 changes: 12 additions & 0 deletions extensions/designlang/src/copy-cli.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Clipboard, LaunchProps, showHUD, showToast, Toast } from "@raycast/api";

export default async function Command(props: LaunchProps<{ 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.

P1 URL argument not declared — command always fails

props.arguments?.url is accessed here, but the copy-cli command has no arguments entry in package.json. Raycast only populates props.arguments for arguments explicitly declared in the manifest, so props.arguments?.url will always be undefined, the guard on line 5 will always fire, and the command will always toast "No URL provided" — it can never actually copy anything.

Add an arguments block to the copy-cli command in package.json:

{
  "name": "copy-cli",
  "title": "Copy CLI Command For URL",
  "description": "Copy `npx designlang <url>` to your clipboard.",
  "mode": "no-view",
  "arguments": [
    {
      "name": "url",
      "placeholder": "https://stripe.com",
      "type": "text",
      "required": true
    }
  ]
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/designlang/src/copy-cli.tsx
Line: 3

Comment:
**URL argument not declared — command always fails**

`props.arguments?.url` is accessed here, but the `copy-cli` command has no `arguments` entry in `package.json`. Raycast only populates `props.arguments` for arguments explicitly declared in the manifest, so `props.arguments?.url` will always be `undefined`, the guard on line 5 will always fire, and the command will always toast "No URL provided" — it can never actually copy anything.

Add an `arguments` block to the `copy-cli` command in `package.json`:

```json
{
  "name": "copy-cli",
  "title": "Copy CLI Command For URL",
  "description": "Copy `npx designlang <url>` to your clipboard.",
  "mode": "no-view",
  "arguments": [
    {
      "name": "url",
      "placeholder": "https://stripe.com",
      "type": "text",
      "required": true
    }
  ]
}
```

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

const rawUrl = (props.arguments?.url || "").trim();
if (!rawUrl) {
await showToast({ style: Toast.Style.Failure, title: "No URL provided" });
return;
}
const url = rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`;
await Clipboard.copy(`npx designlang ${url}`);
await showHUD("Copied `npx designlang …` to clipboard");
}
79 changes: 79 additions & 0 deletions extensions/designlang/src/extract.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
Action,
ActionPanel,
Form,
Toast,
getPreferenceValues,
open,
showToast,
} from "@raycast/api";
import { execFile } from "child_process";
import { homedir } from "os";
import { useState } from "react";

function resolveOutputDir(raw?: string) {
const v = (raw || "~/designlang-output").trim();
return v.startsWith("~") ? v.replace(/^~/, homedir()) : v;
}

function normalizeUrl(u: string) {
const t = u.trim();
return t.startsWith("http") ? t : `https://${t}`;
}

export default function Command() {
const prefs = getPreferenceValues<Preferences.Extract>();
const [url, setUrl] = useState("");
const [full, setFull] = useState(false);

async function run(values: { url: string; full: boolean }) {
const outDir = resolveOutputDir(prefs.outputDir);
const args = [
"-y",
"designlang",
normalizeUrl(values.url),
"--out",
outDir,
];
if (values.full) args.push("--full");

const toast = await showToast({
style: Toast.Style.Animated,
title: "designlang: extracting...",
});

execFile("npx", args, { maxBuffer: 50 * 1024 * 1024 }, async (err, stdout, stderr) => {
if (err) {
toast.style = Toast.Style.Failure;
toast.title = "Extraction failed";
toast.message = (stderr || err.message).slice(0, 200);
return;
}
toast.style = Toast.Style.Success;
toast.title = "Done";
toast.message = outDir;
toast.primaryAction = { title: "Open folder", onAction: () => open(outDir) };
await open(outDir);
});
}

return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm title="Extract Design" onSubmit={run} />
</ActionPanel>
}
>
<Form.TextField
id="url"
title="URL"
placeholder="https://stripe.com"
value={url}
onChange={setUrl}
/>
<Form.Checkbox id="full" label="Full extraction (screenshots + responsive + interactions)" value={full} onChange={setFull} />
<Form.Description text="Runs `npx designlang <url>` and opens the output folder when it finishes." />
</Form>
);
}
50 changes: 50 additions & 0 deletions extensions/designlang/src/score.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Action, ActionPanel, Detail, Form, Toast, showToast } from "@raycast/api";
import { execFile } from "child_process";
import { useState } from "react";

function normalizeUrl(u: string) {
const t = u.trim();
return t.startsWith("http") ? t : `https://${t}`;
}

export default function Command() {
const [url, setUrl] = useState("");
const [result, setResult] = useState<string | null>(null);

async function run(values: { url: string }) {
const toast = await showToast({ style: Toast.Style.Animated, title: "designlang: scoring..." });
execFile(
"npx",
["-y", "designlang", "score", normalizeUrl(values.url)],
{ maxBuffer: 20 * 1024 * 1024 },
(err, stdout, stderr) => {
if (err) {
toast.style = Toast.Style.Failure;
toast.title = "Scoring failed";
toast.message = (stderr || err.message).slice(0, 200);
return;
}
toast.style = Toast.Style.Success;
toast.title = "Scored";
// Strip ANSI control codes
setResult("```\n" + stdout.replace(/\u001b\[[0-9;]*m/g, "") + "\n```");
}
);
}

if (result) {
return <Detail markdown={result} />;
}

return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm title="Score Design" onSubmit={run} />
</ActionPanel>
}
>
<Form.TextField id="url" title="URL" placeholder="https://linear.app" value={url} onChange={setUrl} />
</Form>
);
}
15 changes: 15 additions & 0 deletions extensions/designlang/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["src/**/*"],
"compilerOptions": {
"lib": ["ES2023"],
"module": "commonjs",
"target": "ES2022",
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"resolveJsonModule": true,
"noEmit": true
}
}
Loading