init pi folder
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: brave-search
|
||||
description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required.
|
||||
---
|
||||
|
||||
# Brave Search
|
||||
|
||||
Web search and content extraction using the official Brave Search API. No browser required.
|
||||
|
||||
## Setup
|
||||
|
||||
Requires a Brave Search API account with a free subscription. A credit card is required to create the free subscription (you won't be charged).
|
||||
|
||||
1. Create an account at https://api-dashboard.search.brave.com/register
|
||||
2. Create a "Free AI" subscription
|
||||
3. Create an API key for the subscription
|
||||
4. Add to your shell profile (`~/.profile` or `~/.zprofile` for zsh):
|
||||
```bash
|
||||
export BRAVE_API_KEY="your-api-key-here"
|
||||
```
|
||||
5. Install shared dependencies (run once):
|
||||
```bash
|
||||
cd ~/.pi/agent/extensions
|
||||
npm install
|
||||
```
|
||||
|
||||
## Search
|
||||
|
||||
```bash
|
||||
{baseDir}/search.js "query" # Basic search (5 results)
|
||||
{baseDir}/search.js "query" -n 10 # More results (max 20)
|
||||
{baseDir}/search.js "query" --content # Include page content as markdown
|
||||
{baseDir}/search.js "query" --freshness pw # Results from last week
|
||||
{baseDir}/search.js "query" --freshness 2024-01-01to2024-06-30 # Date range
|
||||
{baseDir}/search.js "query" --country DE # Results from Germany
|
||||
{baseDir}/search.js "query" -n 3 --content # Combined options
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `-n <num>` - Number of results (default: 5, max: 20)
|
||||
- `--content` - Fetch and include page content as markdown
|
||||
- `--country <code>` - Two-letter country code (default: US)
|
||||
- `--freshness <period>` - Filter by time:
|
||||
- `pd` - Past day (24 hours)
|
||||
- `pw` - Past week
|
||||
- `pm` - Past month
|
||||
- `py` - Past year
|
||||
- `YYYY-MM-DDtoYYYY-MM-DD` - Custom date range
|
||||
|
||||
## Extract Page Content
|
||||
|
||||
```bash
|
||||
{baseDir}/content.js https://example.com/article
|
||||
```
|
||||
|
||||
Fetches a URL and extracts readable content as markdown.
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
--- Result 1 ---
|
||||
Title: Page Title
|
||||
Link: https://example.com/page
|
||||
Age: 2 days ago
|
||||
Snippet: Description from search results
|
||||
Content: (if --content flag used)
|
||||
Markdown content extracted from the page...
|
||||
|
||||
--- Result 2 ---
|
||||
...
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
- Searching for documentation or API references
|
||||
- Looking up facts or current information
|
||||
- Fetching content from specific URLs
|
||||
- Any task requiring web search without interactive browsing
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const sharedNodeModules = path.resolve(baseDir, "../../extensions/node_modules");
|
||||
const resolveFromShared = (pkg) => require.resolve(pkg, { paths: [sharedNodeModules] });
|
||||
|
||||
const { Readability } = require(resolveFromShared("@mozilla/readability"));
|
||||
const { JSDOM } = require(resolveFromShared("jsdom"));
|
||||
const TurndownService = require(resolveFromShared("turndown"));
|
||||
const { gfm } = require(resolveFromShared("turndown-plugin-gfm"));
|
||||
|
||||
const url = process.argv[2];
|
||||
|
||||
if (!url) {
|
||||
console.log("Usage: content.js <url>");
|
||||
console.log("\nExtracts readable content from a webpage as markdown.");
|
||||
console.log("\nExamples:");
|
||||
console.log(" content.js https://example.com/article");
|
||||
console.log(" content.js https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function htmlToMarkdown(html) {
|
||||
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
||||
turndown.use(gfm);
|
||||
turndown.addRule("removeEmptyLinks", {
|
||||
filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
|
||||
replacement: () => "",
|
||||
});
|
||||
return turndown
|
||||
.turndown(html)
|
||||
.replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
|
||||
.replace(/ +/g, " ")
|
||||
.replace(/\s+,/g, ",")
|
||||
.replace(/\s+\./g, ".")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html, { url });
|
||||
const reader = new Readability(dom.window.document);
|
||||
const article = reader.parse();
|
||||
|
||||
if (article && article.content) {
|
||||
if (article.title) {
|
||||
console.log(`# ${article.title}\n`);
|
||||
}
|
||||
console.log(htmlToMarkdown(article.content));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Fallback: try to extract main content
|
||||
const fallbackDoc = new JSDOM(html, { url });
|
||||
const body = fallbackDoc.window.document;
|
||||
body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
|
||||
|
||||
const title = body.querySelector("title")?.textContent?.trim();
|
||||
const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
|
||||
|
||||
if (title) {
|
||||
console.log(`# ${title}\n`);
|
||||
}
|
||||
|
||||
const text = main?.innerHTML || "";
|
||||
if (text.trim().length > 100) {
|
||||
console.log(htmlToMarkdown(text));
|
||||
} else {
|
||||
console.error("Could not extract readable content from this page.");
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Executable
+208
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const sharedNodeModules = path.resolve(baseDir, "../../extensions/node_modules");
|
||||
const resolveFromShared = (pkg) => require.resolve(pkg, { paths: [sharedNodeModules] });
|
||||
|
||||
const { Readability } = require(resolveFromShared("@mozilla/readability"));
|
||||
const { JSDOM } = require(resolveFromShared("jsdom"));
|
||||
const TurndownService = require(resolveFromShared("turndown"));
|
||||
const { gfm } = require(resolveFromShared("turndown-plugin-gfm"));
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const contentIndex = args.indexOf("--content");
|
||||
const fetchContent = contentIndex !== -1;
|
||||
if (fetchContent) args.splice(contentIndex, 1);
|
||||
|
||||
let numResults = 5;
|
||||
const nIndex = args.indexOf("-n");
|
||||
if (nIndex !== -1 && args[nIndex + 1]) {
|
||||
numResults = parseInt(args[nIndex + 1], 10);
|
||||
args.splice(nIndex, 2);
|
||||
}
|
||||
|
||||
// Parse country option
|
||||
let country = "US";
|
||||
const countryIndex = args.indexOf("--country");
|
||||
if (countryIndex !== -1 && args[countryIndex + 1]) {
|
||||
country = args[countryIndex + 1].toUpperCase();
|
||||
args.splice(countryIndex, 2);
|
||||
}
|
||||
|
||||
// Parse freshness option
|
||||
let freshness = null;
|
||||
const freshnessIndex = args.indexOf("--freshness");
|
||||
if (freshnessIndex !== -1 && args[freshnessIndex + 1]) {
|
||||
freshness = args[freshnessIndex + 1];
|
||||
args.splice(freshnessIndex, 2);
|
||||
}
|
||||
|
||||
const query = args.join(" ");
|
||||
|
||||
if (!query) {
|
||||
console.log("Usage: search.js <query> [-n <num>] [--content] [--country <code>] [--freshness <period>]");
|
||||
console.log("\nOptions:");
|
||||
console.log(" -n <num> Number of results (default: 5, max: 20)");
|
||||
console.log(" --content Fetch readable content as markdown");
|
||||
console.log(" --country <code> Country code for results (default: US)");
|
||||
console.log(" --freshness <period> Filter by time: pd (day), pw (week), pm (month), py (year)");
|
||||
console.log("\nEnvironment:");
|
||||
console.log(" BRAVE_API_KEY Required. Your Brave Search API key.");
|
||||
console.log("\nExamples:");
|
||||
console.log(' search.js "javascript async await"');
|
||||
console.log(' search.js "rust programming" -n 10');
|
||||
console.log(' search.js "climate change" --content');
|
||||
console.log(' search.js "news today" --freshness pd');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const apiKey = process.env.BRAVE_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error("Error: BRAVE_API_KEY environment variable is required.");
|
||||
console.error("Get your API key at: https://api-dashboard.search.brave.com/app/keys");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function fetchBraveResults(query, numResults, country, freshness) {
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
count: Math.min(numResults, 20).toString(),
|
||||
country: country,
|
||||
});
|
||||
|
||||
if (freshness) {
|
||||
params.append("freshness", freshness);
|
||||
}
|
||||
|
||||
const url = `https://api.search.brave.com/res/v1/web/search?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Accept-Encoding": "gzip",
|
||||
"X-Subscription-Token": apiKey,
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}\n${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const results = [];
|
||||
|
||||
// Extract web results
|
||||
if (data.web && data.web.results) {
|
||||
for (const result of data.web.results) {
|
||||
if (results.length >= numResults) break;
|
||||
|
||||
results.push({
|
||||
title: result.title || "",
|
||||
link: result.url || "",
|
||||
snippet: result.description || "",
|
||||
age: result.age || result.page_age || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function htmlToMarkdown(html) {
|
||||
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
||||
turndown.use(gfm);
|
||||
turndown.addRule("removeEmptyLinks", {
|
||||
filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
|
||||
replacement: () => "",
|
||||
});
|
||||
return turndown
|
||||
.turndown(html)
|
||||
.replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
|
||||
.replace(/ +/g, " ")
|
||||
.replace(/\s+,/g, ",")
|
||||
.replace(/\s+\./g, ".")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
async function fetchPageContent(url) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
},
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return `(HTTP ${response.status})`;
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html, { url });
|
||||
const reader = new Readability(dom.window.document);
|
||||
const article = reader.parse();
|
||||
|
||||
if (article && article.content) {
|
||||
return htmlToMarkdown(article.content).substring(0, 5000);
|
||||
}
|
||||
|
||||
// Fallback: try to get main content
|
||||
const fallbackDoc = new JSDOM(html, { url });
|
||||
const body = fallbackDoc.window.document;
|
||||
body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
|
||||
const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
|
||||
const text = main?.textContent || "";
|
||||
|
||||
if (text.trim().length > 100) {
|
||||
return text.trim().substring(0, 5000);
|
||||
}
|
||||
|
||||
return "(Could not extract content)";
|
||||
} catch (e) {
|
||||
return `(Error: ${e.message})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
try {
|
||||
const results = await fetchBraveResults(query, numResults, country, freshness);
|
||||
|
||||
if (results.length === 0) {
|
||||
console.error("No results found.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (fetchContent) {
|
||||
for (const result of results) {
|
||||
result.content = await fetchPageContent(result.link);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const r = results[i];
|
||||
console.log(`--- Result ${i + 1} ---`);
|
||||
console.log(`Title: ${r.title}`);
|
||||
console.log(`Link: ${r.link}`);
|
||||
if (r.age) {
|
||||
console.log(`Age: ${r.age}`);
|
||||
}
|
||||
console.log(`Snippet: ${r.snippet}`);
|
||||
if (r.content) {
|
||||
console.log(`Content:\n${r.content}`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
---
|
||||
name: browser
|
||||
description: Use cmux browser automation for browser access. Validate real user-facing behavior in browser surfaces, including navigation, DOM interaction, inspection, storage, tabs, dialogs, frames, downloads, and browser logs.
|
||||
---
|
||||
|
||||
# Browser automation with cmux
|
||||
|
||||
Use this skill for browser-driven validation through `cmux browser`.
|
||||
|
||||
## Goals
|
||||
|
||||
- Verify real user-facing behavior.
|
||||
- Access and inspect web pages.
|
||||
|
||||
## When to use
|
||||
|
||||
Use this skill when the task involves any of these:
|
||||
|
||||
- Opening or navigating a page
|
||||
- Testing a UI flow end to end
|
||||
- Filling forms or clicking controls
|
||||
- Verifying visible text, URL, title, values, attributes, or counts
|
||||
- Capturing snapshots or screenshots
|
||||
- Checking console logs or browser errors
|
||||
- Working with cookies, storage, or saved browser state
|
||||
- Handling tabs, dialogs, iframes, or downloads
|
||||
|
||||
Do not use this skill for unit tests, static analysis, or API-only checks unless browser behavior is part of the task.
|
||||
|
||||
## Operating rules
|
||||
|
||||
1. Start by identifying or opening the browser surface you will use.
|
||||
2. Wait for a stable state before interacting.
|
||||
3. Prefer stable selectors and structured getters over visual guesswork.
|
||||
4. After each mutating action, verify the result with URL, text, visibility, or value checks.
|
||||
5. Treat `console` and `errors` output as evidence, not optional noise.
|
||||
6. On failure, collect artifacts before concluding root cause.
|
||||
7. Use `eval` only when the browser commands cannot express the check directly.
|
||||
8. Keep credentials and secrets out of logs, commands, screenshots, and saved state when possible.
|
||||
|
||||
## Verified command surface
|
||||
|
||||
The commands below are verified against the official cmux browser automation docs.
|
||||
|
||||
### Targeting a browser surface
|
||||
|
||||
```bash
|
||||
cmux browser open https://example.com
|
||||
cmux browser open-split https://example.com
|
||||
cmux browser identify
|
||||
cmux browser identify --surface surface:2
|
||||
cmux browser surface:2 url
|
||||
cmux browser --surface surface:2 url
|
||||
```
|
||||
|
||||
### Navigation and focus
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 navigate https://example.com/docs --snapshot-after
|
||||
cmux browser surface:2 back
|
||||
cmux browser surface:2 forward
|
||||
cmux browser surface:2 reload --snapshot-after
|
||||
cmux browser surface:2 focus-webview
|
||||
cmux browser surface:2 is-webview-focused
|
||||
```
|
||||
|
||||
### Waiting
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 wait --load-state complete --timeout-ms 15000
|
||||
cmux browser surface:2 wait --selector "#checkout" --timeout-ms 10000
|
||||
cmux browser surface:2 wait --text "Order confirmed"
|
||||
cmux browser surface:2 wait --url-contains "/dashboard"
|
||||
cmux browser surface:2 wait --function "window.__appReady === true"
|
||||
```
|
||||
|
||||
Prefer this order of readiness checks:
|
||||
|
||||
1. `--selector` when a specific element gates the next action
|
||||
2. `--text` for user-visible confirmation
|
||||
3. `--url-contains` for navigation assertions
|
||||
4. `--function` only when the app exposes a reliable readiness flag
|
||||
5. `--load-state complete` for initial page load or simple pages
|
||||
|
||||
Avoid fixed sleeps unless there is no reliable signal.
|
||||
|
||||
### DOM interaction
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 click "button[type='submit']" --snapshot-after
|
||||
cmux browser surface:2 dblclick ".item-row"
|
||||
cmux browser surface:2 hover "#menu"
|
||||
cmux browser surface:2 focus "#email"
|
||||
cmux browser surface:2 check "#terms"
|
||||
cmux browser surface:2 uncheck "#newsletter"
|
||||
cmux browser surface:2 scroll-into-view "#pricing"
|
||||
cmux browser surface:2 type "#search" "cmux"
|
||||
cmux browser surface:2 fill "#email" --text "ops@example.com"
|
||||
cmux browser surface:2 press Enter
|
||||
cmux browser surface:2 keydown Shift
|
||||
cmux browser surface:2 keyup Shift
|
||||
cmux browser surface:2 select "#region" "us-east"
|
||||
cmux browser surface:2 scroll --dy 800 --snapshot-after
|
||||
cmux browser surface:2 scroll --selector "#log-view" --dx 0 --dy 400
|
||||
```
|
||||
|
||||
Prefer `fill` when setting a known final value.
|
||||
|
||||
Prefer `type` when testing keystroke-driven behavior such as debouncing, masking, shortcuts, or suggestions.
|
||||
|
||||
### Inspection and assertions
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 snapshot --interactive --compact
|
||||
cmux browser surface:2 snapshot --selector "main" --max-depth 5
|
||||
cmux browser surface:2 screenshot --out /tmp/cmux-page.png
|
||||
|
||||
cmux browser surface:2 get title
|
||||
cmux browser surface:2 get url
|
||||
cmux browser surface:2 get text "h1"
|
||||
cmux browser surface:2 get html "main"
|
||||
cmux browser surface:2 get value "#email"
|
||||
cmux browser surface:2 get attr "a.primary" --attr href
|
||||
cmux browser surface:2 get count ".row"
|
||||
cmux browser surface:2 get box "#checkout"
|
||||
cmux browser surface:2 get styles "#total" --property color
|
||||
|
||||
cmux browser surface:2 is visible "#checkout"
|
||||
cmux browser surface:2 is enabled "button[type='submit']"
|
||||
cmux browser surface:2 is checked "#terms"
|
||||
|
||||
cmux browser surface:2 find role button --name "Continue"
|
||||
cmux browser surface:2 find text "Order confirmed"
|
||||
cmux browser surface:2 find label "Email"
|
||||
cmux browser surface:2 find placeholder "Search"
|
||||
cmux browser surface:2 find alt "Product image"
|
||||
cmux browser surface:2 find title "Open settings"
|
||||
cmux browser surface:2 find testid "save-btn"
|
||||
cmux browser surface:2 find first ".row"
|
||||
cmux browser surface:2 find last ".row"
|
||||
cmux browser surface:2 find nth 2 ".row"
|
||||
|
||||
cmux browser surface:2 highlight "#checkout"
|
||||
```
|
||||
|
||||
Preferred selector order:
|
||||
|
||||
1. Stable test IDs
|
||||
2. Accessible role and name
|
||||
3. Labels and placeholders
|
||||
4. Semantic CSS selectors
|
||||
5. Text selectors
|
||||
|
||||
Avoid brittle selectors like deeply nested `nth-child` chains unless no better option exists.
|
||||
|
||||
### JavaScript and injection
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 eval "document.title"
|
||||
cmux browser surface:2 eval --script "window.location.href"
|
||||
cmux browser surface:2 addinitscript "window.__cmuxReady = true;"
|
||||
cmux browser surface:2 addscript "document.querySelector('#name')?.focus()"
|
||||
cmux browser surface:2 addstyle "#debug-banner { display: none !important; }"
|
||||
```
|
||||
|
||||
Use `eval` sparingly. Do not bypass the UI path if the task is to validate user behavior.
|
||||
|
||||
### State and session data
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 cookies get
|
||||
cmux browser surface:2 cookies get --name session_id
|
||||
cmux browser surface:2 cookies set session_id abc123 --domain example.com --path /
|
||||
cmux browser surface:2 cookies clear --name session_id
|
||||
cmux browser surface:2 cookies clear --all
|
||||
|
||||
cmux browser surface:2 storage local set theme dark
|
||||
cmux browser surface:2 storage local get theme
|
||||
cmux browser surface:2 storage local clear
|
||||
cmux browser surface:2 storage session set flow onboarding
|
||||
cmux browser surface:2 storage session get flow
|
||||
|
||||
cmux browser surface:2 state save /tmp/cmux-browser-state.json
|
||||
cmux browser surface:2 state load /tmp/cmux-browser-state.json
|
||||
```
|
||||
|
||||
Use saved browser state for authenticated sessions, long setup flows, and repeatable bug repros.
|
||||
|
||||
Do not use saved state when validating fresh-session behavior like login, onboarding, logout, or first-run UX.
|
||||
|
||||
### Tabs, logs, dialogs, frames, downloads
|
||||
|
||||
```bash
|
||||
cmux browser surface:2 tab list
|
||||
cmux browser surface:2 tab new https://example.com/pricing
|
||||
cmux browser surface:2 tab switch 1
|
||||
cmux browser surface:2 tab switch surface:7
|
||||
cmux browser surface:2 tab close
|
||||
cmux browser surface:2 tab close surface:7
|
||||
|
||||
cmux browser surface:2 console list
|
||||
cmux browser surface:2 console clear
|
||||
cmux browser surface:2 errors list
|
||||
cmux browser surface:2 errors clear
|
||||
|
||||
cmux browser surface:2 dialog accept
|
||||
cmux browser surface:2 dialog accept "Confirmed by automation"
|
||||
cmux browser surface:2 dialog dismiss
|
||||
|
||||
cmux browser surface:2 frame "iframe[name='checkout']"
|
||||
cmux browser surface:2 frame main
|
||||
|
||||
cmux browser surface:2 download --path /tmp/report.csv --timeout-ms 30000
|
||||
```
|
||||
|
||||
## Execution playbooks
|
||||
|
||||
### Default flow
|
||||
|
||||
Use this decision sequence unless the task clearly needs a different one.
|
||||
|
||||
1. Acquire a surface.
|
||||
|
||||
```bash
|
||||
cmux browser open <URL>
|
||||
cmux browser identify
|
||||
```
|
||||
|
||||
2. Establish readiness.
|
||||
|
||||
```bash
|
||||
cmux browser surface:<ID> wait --load-state complete --timeout-ms 15000
|
||||
cmux browser surface:<ID> snapshot --interactive --compact
|
||||
```
|
||||
|
||||
3. Choose the next action type.
|
||||
|
||||
- Navigation task: use `navigate`, `back`, `forward`, or `reload`, then wait again.
|
||||
- Form task: use the form playbook below.
|
||||
- Inspection task: use `get`, `is`, `find`, `snapshot`, or `screenshot`.
|
||||
- State setup task: use `cookies`, `storage`, or `state` before continuing.
|
||||
- Download, dialog, tab, or frame task: switch to the relevant specialized playbook.
|
||||
|
||||
4. After every mutating action, verify with at least one explicit assertion.
|
||||
|
||||
Preferred assertion order:
|
||||
|
||||
1. `wait --url-contains`
|
||||
2. `wait --text`
|
||||
3. `is visible`
|
||||
4. `get value`
|
||||
5. `get count`
|
||||
6. `errors list` when debugging or validating stability
|
||||
|
||||
7. If the assertion fails, run the failure protocol before making claims.
|
||||
|
||||
### Form playbook
|
||||
|
||||
Use this for login, signup, checkout, search, and settings forms.
|
||||
|
||||
1. Wait for the first required field.
|
||||
2. Populate fields with `fill` unless the task is specifically about typing behavior.
|
||||
3. Submit with `click`, `press Enter`, or the control the user would actually use.
|
||||
4. Verify success using URL, text, and visibility checks.
|
||||
|
||||
```bash
|
||||
cmux browser surface:<ID> wait --selector "#email" --timeout-ms 10000
|
||||
cmux browser surface:<ID> fill "#email" --text "$TEST_EMAIL"
|
||||
cmux browser surface:<ID> fill "#password" --text "$TEST_PASSWORD"
|
||||
cmux browser surface:<ID> click "button[type='submit']" --snapshot-after
|
||||
cmux browser surface:<ID> wait --url-contains "/dashboard" --timeout-ms 10000
|
||||
cmux browser surface:<ID> is visible "#dashboard"
|
||||
```
|
||||
|
||||
If submission should fail, verify the expected error text or blocked state instead of forcing success assertions.
|
||||
|
||||
### Navigation playbook
|
||||
|
||||
Use this when the task is about routing, links, history, or page transitions.
|
||||
|
||||
1. Trigger navigation.
|
||||
2. Wait for URL or page content to settle.
|
||||
3. Verify the destination.
|
||||
|
||||
```bash
|
||||
cmux browser surface:<ID> navigate https://example.com/docs --snapshot-after
|
||||
cmux browser surface:<ID> wait --url-contains "/docs" --timeout-ms 10000
|
||||
cmux browser surface:<ID> get title
|
||||
```
|
||||
|
||||
### Inspection playbook
|
||||
|
||||
Use this when the task is observational rather than interactive.
|
||||
|
||||
1. Prefer `find` to discover the right selector.
|
||||
2. Use `get` or `is` for structured assertions.
|
||||
3. Use `snapshot` or `screenshot` only when human review is useful.
|
||||
|
||||
### State playbook
|
||||
|
||||
Use this when the task depends on auth, persisted preferences, or reproducible setup.
|
||||
|
||||
1. Decide whether the task should start fresh or with persisted state.
|
||||
2. If fresh-session behavior matters, do not load saved state.
|
||||
3. If setup reuse is justified, use `cookies`, `storage`, or `state load`.
|
||||
4. After state changes, reload or navigate as needed and verify the expected state is visible.
|
||||
|
||||
### Tabs, dialogs, frames, and downloads playbook
|
||||
|
||||
- Tabs: `tab list`, `tab new`, `tab switch`, then verify with `get url` or visible text.
|
||||
- Dialogs: trigger the dialog, then immediately `dialog accept` or `dialog dismiss`.
|
||||
- Frames: enter with `frame <selector>`, complete the work, then return with `frame main`.
|
||||
- Downloads: trigger the download, run `download --path ...`, then verify the file with shell tools if needed.
|
||||
|
||||
## Failure protocol
|
||||
|
||||
Run this whenever a flow fails or results are ambiguous:
|
||||
|
||||
```bash
|
||||
cmux browser surface:<ID> console list
|
||||
cmux browser surface:<ID> errors list
|
||||
cmux browser surface:<ID> screenshot --out /tmp/cmux-failure.png
|
||||
cmux browser surface:<ID> snapshot --interactive --compact
|
||||
cmux browser surface:<ID> get url
|
||||
cmux browser surface:<ID> get title
|
||||
```
|
||||
|
||||
Report all of these if available:
|
||||
|
||||
- Failed action
|
||||
- Expected behavior
|
||||
- Actual behavior
|
||||
- Current URL
|
||||
- Current title
|
||||
- Relevant visible text
|
||||
- Console findings
|
||||
- Browser error findings
|
||||
- Artifact paths
|
||||
|
||||
## Flaky triage
|
||||
|
||||
If an interaction fails, use this exact order:
|
||||
|
||||
1. Check existence: `cmux browser surface:<ID> get count "<selector>"`
|
||||
2. Check visibility: `cmux browser surface:<ID> is visible "<selector>"`
|
||||
3. Check enabled state: `cmux browser surface:<ID> is enabled "<selector>"`
|
||||
4. Scroll into view: `cmux browser surface:<ID> scroll-into-view "<selector>"`
|
||||
5. Retry the action once
|
||||
6. If it still fails, stop retrying and run the failure protocol
|
||||
|
||||
## Reporting format
|
||||
|
||||
Use this concise result format:
|
||||
|
||||
```md
|
||||
## Browser Test Result
|
||||
|
||||
Status: PASS | FAIL | BLOCKED
|
||||
Tested URL: <url>
|
||||
Scenario: <what was tested>
|
||||
Result: <one-sentence outcome>
|
||||
|
||||
Evidence:
|
||||
|
||||
- <assertion or command result>
|
||||
- <assertion or command result>
|
||||
|
||||
Artifacts:
|
||||
|
||||
- Screenshot: <path or none>
|
||||
- Snapshot: <brief summary or none>
|
||||
- Console/errors: <brief summary>
|
||||
|
||||
Notes:
|
||||
<caveats, blockers, or likely cause if supported by evidence>
|
||||
```
|
||||
|
||||
## Common mistakes
|
||||
|
||||
- Interacting before the page or element is ready
|
||||
- Assuming navigation succeeded without verifying URL or text
|
||||
- Using brittle selectors when stable ones exist
|
||||
- Treating `eval` as a substitute for user behavior
|
||||
- Ignoring `console` or `errors` output during failures
|
||||
- Reusing saved state in tests that should start fresh
|
||||
- Reporting success without at least one explicit assertion
|
||||
|
||||
## Gotcha: implicit Enter form submission
|
||||
|
||||
Some forms rely on the browser’s default behavior where pressing Enter in an input submits the form. In `cmux`, `fill`, `focus`, and `press Enter` may all appear to succeed without actually triggering submission.
|
||||
Rules:
|
||||
|
||||
- Never assume Enter submitted the form just because `press Enter` returned `OK`.
|
||||
- Always verify submission through URL change, visible success state, or expected content.
|
||||
- If Enter does not submit and there are no console/browser errors, suspect an automation limitation.
|
||||
- Retry once with `focus`, `focus-webview`, and `type "...\\n"`.
|
||||
- If still unsuccessful, validate the feature through the expected destination state and report that implicit Enter submission could not be reliably proven in `cmux`.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: chromy
|
||||
description: This skill provides access to a RAG-like context enhancer that uses Chromadb locally.
|
||||
---
|
||||
|
||||
# Chromy
|
||||
|
||||
Whenever the user asks to "use chromy", you should invoke `chromy`, which is a cli tool to perform RAG search.
|
||||
The tool should be available in the `$PATH` as `chromy`.
|
||||
|
||||
You have access to these commands:
|
||||
|
||||
- `$ chromy list-collections` -> Lists the existing collections.
|
||||
- `$ chromy query <collection> <query>` -> Performs a query. Be sure to quote the `<query>` if this is composed by multiple words.
|
||||
|
||||
Then use the response from Chromy to enhance the context and give the user a refined response.
|
||||
|
||||
## A note on file sources
|
||||
|
||||
The Chromy response returns the metadatas for the chunks it finds. Among these metadatas, there is `file_name`, which refers to the original file that was chunked and imported. **DO NOT ATTEMPT** to find or fetch these files. They most likely do not exist in the filesystem. You **SHOULD ALWAYS** however cite correctly from which files (**ONLY** from Chromy's metadatas) the information is coming.
|
||||
|
||||
## Example use case
|
||||
|
||||
**START**
|
||||
|
||||
User query:
|
||||
|
||||
> Search in Chromy information about lovecraft's Dunwich horror.
|
||||
|
||||
Step 1: Get the available collections with `chromy list-collections`. The output is:
|
||||
|
||||
```
|
||||
lovecraft
|
||||
documents
|
||||
```
|
||||
|
||||
Most likely our information is in the `lovecraft` collection. We will use that for the query.
|
||||
|
||||
Step 2: Query using `chromy query lovecraft <query>`. The query _is up to you_, create one keeping into account that this is a raw query on a vector DB. Be concise, extract keywords, avoid noise.
|
||||
|
||||
Step 3: Get the results, enhance the context, and respond to the user.
|
||||
|
||||
**END**
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: codebase-analysis
|
||||
description: Analyze a codebase to understand architecture, dependencies, runnable tasks and runtime information.
|
||||
---
|
||||
|
||||
# Codebase Analysis Skill
|
||||
|
||||
Use this skill when asked to inspect, explain, refactor, extend, debug, or assess an unfamiliar codebase.
|
||||
|
||||
## Goals
|
||||
|
||||
- Build a concise mental model of the codebase.
|
||||
- Identify entry points, core modules, data flow, and external dependencies.
|
||||
- Identify all existing runnable scripts (ex: run the dev server, run in production, etc.)
|
||||
- Surface risks, conventions, and likely impact areas.
|
||||
- Produce actionable findings before proposing code changes.
|
||||
|
||||
## Process
|
||||
|
||||
1. Inspect the codebase structure.
|
||||
2. Read top-level docs: `README`, `AGENTS`, architecture notes, contributing docs, and setup files.
|
||||
3. Identify languages, frameworks, package managers, build tools, test and run commands.
|
||||
4. Locate application entry points and configuration files.
|
||||
5. Trace the relevant feature path from input to output.
|
||||
6. Check tests, fixtures, CI workflows, and lint/type settings.
|
||||
7. Summarize findings with file references and uncertainty.
|
||||
|
||||
## Heuristics
|
||||
|
||||
- Prefer reading existing conventions over inventing new patterns.
|
||||
- Do not assume framework behavior when config or code contradicts defaults.
|
||||
- Treat generated, vendored, minified, and lock files as supporting evidence only.
|
||||
- Search for similar implementations before designing a new one.
|
||||
- Verify public APIs, side effects, and persistence boundaries before editing.
|
||||
|
||||
## Output Format
|
||||
|
||||
Provide:
|
||||
|
||||
- `Overview`: what the codebase does.
|
||||
- `Architecture`: main components and how they interact.
|
||||
- `Relevant files`: files inspected and why they matter.
|
||||
- `Execution path`: request/event/job flow, if applicable.
|
||||
- `Dependencies`: important internal and external dependencies.
|
||||
- `Risks`: fragile areas, missing tests, unclear behavior.
|
||||
- `Next steps`: recommended investigation or implementation plan.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Do not make broad claims without file evidence.
|
||||
- Mark guesses explicitly as assumptions.
|
||||
- Do not edit code until the impact area is understood.
|
||||
- Keep the summary concise and focused on the user’s task.
|
||||
@@ -0,0 +1 @@
|
||||
3.12
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: context7
|
||||
description: Get code snippet examples, get inline help about any library or language, find documentation using Context7 API.
|
||||
---
|
||||
|
||||
## How to use
|
||||
|
||||
Simply invoke the `main.py` script using `uv` and passing as command line arguments the `<library>` and the `<query>`. Make sure to use quotations if the query is composed by more than one word. The script will return useful code snippets about what has been requested.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
$ uv run main.py django "how to run django migrations?"
|
||||
|
||||
[OUTPUT]
|
||||
```
|
||||
|
||||
This skill uses Python and `uv` to run: before using the skill, the virtual environment must be ready and the libraries must be installed.
|
||||
|
||||
```sh
|
||||
uv venv --allow-existing
|
||||
uv sync
|
||||
```
|
||||
|
||||
## When to use
|
||||
|
||||
This skill must be used whenever there is the need to get additional context for the specific library or language that is in use in the project. If the project is using, for example, React, it might be useful to use it with `react` and `"how to use the the useEffect hook?"`, or if using Pydantic with `pydantic` and `"how to define an email field?"`.
|
||||
@@ -0,0 +1,62 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
CONTEXT7_API_KEY = os.environ.get("CONTEXT7_API_KEY")
|
||||
|
||||
headers = {"Authorization": f"Bearer {CONTEXT7_API_KEY}"}
|
||||
|
||||
|
||||
def search_library(library: str, query: str) -> str:
|
||||
# Step 1: Search for the library
|
||||
search_response = requests.get(
|
||||
"https://context7.com/api/v2/libs/search",
|
||||
headers=headers,
|
||||
params={"libraryName": library, "query": query},
|
||||
)
|
||||
search_response.raise_for_status()
|
||||
data = search_response.json()
|
||||
best_match = data["results"][0]
|
||||
print(f"Found: {best_match['title']} ({best_match['id']})")
|
||||
return best_match["id"]
|
||||
|
||||
|
||||
def search_snippet(library: str, query: str) -> None:
|
||||
# Step 2: Get documentation context
|
||||
context_response = requests.get(
|
||||
"https://context7.com/api/v2/context",
|
||||
headers=headers,
|
||||
params={
|
||||
"libraryId": library,
|
||||
"query": query,
|
||||
"type": "json",
|
||||
},
|
||||
)
|
||||
context_response.raise_for_status()
|
||||
docs = context_response.json()
|
||||
|
||||
for snippet in docs["codeSnippets"]:
|
||||
print(f"Title: {snippet['codeTitle']}")
|
||||
for code in snippet["codeList"]:
|
||||
print(f"Code: {code['code']}")
|
||||
|
||||
for info in docs["infoSnippets"]:
|
||||
print(f"Content: {info['content']}")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("library", help="Library name to search for")
|
||||
parser.add_argument("query", help="Documentation query to run")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
library_id = search_library(args.library, args.query)
|
||||
search_snippet(library_id, args.query)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "context7"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"requests>=2.33.1",
|
||||
]
|
||||
Generated
+129
@@ -0,0 +1,129 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.4.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "context7"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "requests", specifier = ">=2.33.1" }]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: dev-server
|
||||
description: Instructions on how to run correctly development / preview servers or background processes in new panes in cmux.
|
||||
---
|
||||
|
||||
## When to use
|
||||
|
||||
This skill is active when the user asks directly or the agent wants to run a local dev/preview server or any other long running command that ideally needs to run in the background.
|
||||
The idea is to provide a dedicated cmux pane / split where to execute this task, and skip entirely the background execution in the coding agent.
|
||||
|
||||
## How to use
|
||||
|
||||
1. Identify what is the task to run. Examples are `npm run dev`, `uv run manage.py runserver`, `python -m http.server` and so on.
|
||||
2. Create a new split in cmux. By default we want to split right: `cmux new-split right`.
|
||||
3. After the split is created, it will return some information like so: `OK surface:23 workspace:5`.
|
||||
4. We want to rename the split to give it a meaningful name, for example "Django dev server" or "npm dev server": `cmux rename-tab --surface surface:23 "npm dev server"`
|
||||
5. Now we need to send the actual command to the split: `cmux send --surface surface:23 "npm run dev"`.
|
||||
6. Lastly, we need to send the <enter> key to trigger the command execution: `cmux send-key --surface surface:23 enter`.
|
||||
|
||||
The external process now is up and running. It will be up to the user to terminate or close the split.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Python Reference
|
||||
|
||||
Use this file only when deeper guidance is needed. `SKILL.md` is the default operating contract.
|
||||
|
||||
## When To Consult This File
|
||||
|
||||
- You are choosing between multiple Python design options.
|
||||
- You need stronger guidance for typing, validation, async, or testing.
|
||||
- You are creating new modules or new project structure rather than making a local edit.
|
||||
- You are reviewing code and want a more complete checklist.
|
||||
|
||||
## Stronger Typing Guidance
|
||||
|
||||
- Prefer `collections.abc` and `typing` abstractions such as `Sequence`, `Mapping`, `Callable`, and `Iterator`.
|
||||
- Use `Protocol` for behavioral contracts when inheritance is not required.
|
||||
- Use `TypedDict` for structured dictionary payloads when a dataclass or Pydantic model is not appropriate.
|
||||
- Use `Final` for constants and `ClassVar` for class-level attributes when helpful.
|
||||
- Keep `Any` rare, local, and justified.
|
||||
- If the project supports it, use `@override` on overriding methods.
|
||||
|
||||
## Architecture Heuristics
|
||||
|
||||
- Domain layer: pure business rules, no framework or persistence code.
|
||||
- Repository or data layer: database and external service access.
|
||||
- Service or use-case layer: orchestration across domain rules and collaborators.
|
||||
- Interface layer: HTTP, CLI, events, serialization, request and response shaping.
|
||||
- Infrastructure layer: configuration, logging setup, DB sessions, clients.
|
||||
|
||||
Use this split when it fits the repository. Do not impose it mechanically on smaller scripts or simpler codebases.
|
||||
|
||||
## Data Modeling
|
||||
|
||||
- Use Pydantic for request payloads, config, and data from untrusted sources.
|
||||
- Use dataclasses for internal value objects and simple structured state.
|
||||
- Use `frozen=True` for values that should not change after creation.
|
||||
- Use `slots=True` where the codebase already prefers it or where many instances are created.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Prefer project-specific exception types over generic `ValueError` or `RuntimeError` when a domain concept exists.
|
||||
- Wrap lower-level exceptions with domain context when crossing layers.
|
||||
- Keep exception handling near boundaries unless recovery is local and explicit.
|
||||
|
||||
## Logging
|
||||
|
||||
- Prefer structured logging when the project supports it.
|
||||
- Log important operational events at service or interface boundaries.
|
||||
- Do not log secrets, credentials, or sensitive user data.
|
||||
- Avoid `print()` for operational behavior unless the program is explicitly a simple script or CLI output path.
|
||||
|
||||
## Async Guidance
|
||||
|
||||
- Use async for I/O-bound workflows, not CPU-bound work.
|
||||
- Keep blocking calls out of coroutines; use thread or process offloading where appropriate.
|
||||
- Prefer `asyncio.TaskGroup` over ad hoc task orchestration when available.
|
||||
- Be explicit about sync-to-async boundaries.
|
||||
|
||||
## Security Guidance
|
||||
|
||||
- Never hardcode secrets or credentials.
|
||||
- Validate all external input before it enters core logic.
|
||||
- Avoid dangerous calls unless explicitly justified and safely constrained:
|
||||
- `eval`, `exec`
|
||||
- `pickle.loads`
|
||||
- `yaml.load`
|
||||
- `subprocess` with `shell=True`
|
||||
- `os.system`
|
||||
- Do not interpolate raw user input into SQL, shell commands, or file-system-sensitive operations.
|
||||
|
||||
## Testing Guidance
|
||||
|
||||
- Update tests whenever behavior changes.
|
||||
- Prefer unit tests for pure logic and integration tests for boundaries.
|
||||
- Use parametrization to improve coverage without duplicating setup.
|
||||
- Test behavior and observable outcomes rather than implementation details.
|
||||
- Mock external boundaries selectively; avoid mocking the core logic you actually want to verify.
|
||||
|
||||
## Review Heuristics
|
||||
|
||||
When reviewing Python changes, prioritize:
|
||||
|
||||
- correctness and regressions
|
||||
- incomplete or misleading typing
|
||||
- invalid assumptions at trust boundaries
|
||||
- misplaced logic between layers
|
||||
- unnecessary abstraction or indirection
|
||||
- missing tests for changed behavior
|
||||
- risky calls, secret handling, or unsafe subprocess use
|
||||
|
||||
## Common Verification Flow
|
||||
|
||||
If the repository uses `uv`, the usual flow is:
|
||||
|
||||
1. `uv run ruff format <paths>`
|
||||
2. `uv run ruff check --fix <paths>`
|
||||
3. `uv run ruff check <paths>`
|
||||
4. `uv run mypy <paths-or-package>`
|
||||
5. `uv run pytest <relevant tests>`
|
||||
|
||||
If the repository uses different commands or tools, follow the repository.
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: python-dev
|
||||
description: |
|
||||
Apply this skill whenever writing, editing, generating, or reviewing Python code. Favor small, typed, idiomatic changes that fit the existing codebase and finish with lint, type checks, and relevant tests passing. Use `REFERENCE.md` only when deeper guidance is needed.
|
||||
version: 3.1.0
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Python Development
|
||||
|
||||
Use this skill for any task that creates or changes Python code.
|
||||
|
||||
## Operating Mode
|
||||
|
||||
- Match the repository's existing style and architecture before introducing new patterns.
|
||||
- Prefer the smallest correct change over broad refactors.
|
||||
- Keep code easy to read: flat control flow, clear names, limited indirection.
|
||||
- Do not force framework choices or house style upgrades into unrelated work.
|
||||
- Do not add compatibility layers, feature flags, or speculative abstractions unless required.
|
||||
- If an API or library detail is uncertain, check current docs before coding.
|
||||
|
||||
## Defaults
|
||||
|
||||
- Add `from __future__ import annotations` to new Python modules.
|
||||
- Fully annotate public functions and important variables.
|
||||
- Prefer precise types over broad ones.
|
||||
- Use `X | Y` and `X | None` syntax.
|
||||
- Avoid `Any`; if unavoidable, keep it narrow and explain why.
|
||||
- Use `TYPE_CHECKING` for type-only imports when it improves runtime imports or avoids cycles.
|
||||
- Prefer simple functions and dataclasses unless the codebase clearly wants a heavier OO design.
|
||||
- Use classes for durable domain concepts or stateful collaborators, not by default.
|
||||
- Prefer composition over inheritance.
|
||||
- Keep responsibilities separated: domain logic, data access, and interface code should stay distinct.
|
||||
- Do not place business logic directly in route handlers, CLI commands, or persistence code.
|
||||
- Validate untrusted input at the boundary.
|
||||
- Prefer Pydantic for external input, config, or data crossing trust boundaries.
|
||||
- Prefer `@dataclass(slots=True)` for internal structured data.
|
||||
- Use `frozen=True` when immutability is a natural fit.
|
||||
- Prefer `pathlib` over `os.path`.
|
||||
- Prefer f-strings.
|
||||
- Prefer standard idioms like comprehensions, `enumerate`, `zip`, and context managers where appropriate.
|
||||
- Use guard clauses to reduce nesting.
|
||||
- Avoid mutable default arguments, bare `except:`, and `type(x) == T` checks.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Raise specific exceptions with useful messages.
|
||||
- Keep `try` blocks narrow.
|
||||
- Preserve context with `raise ... from exc` when re-raising.
|
||||
- Do not swallow exceptions silently.
|
||||
- Log at system boundaries or orchestration layers, not deep inside pure domain logic.
|
||||
|
||||
## Async
|
||||
|
||||
- Use `async` only for real I/O-bound workflows.
|
||||
- Do not call blocking I/O directly from coroutines.
|
||||
- Keep sync and async boundaries explicit.
|
||||
|
||||
## Security
|
||||
|
||||
- Never hardcode secrets, tokens, or credentials.
|
||||
- Validate external input before using it.
|
||||
- Avoid dangerous calls unless explicitly justified: `eval`, `exec`, `pickle.loads`, `yaml.load`, `shell=True`, `os.system`.
|
||||
- Do not build shell commands from raw user input.
|
||||
- Avoid logging secrets or sensitive user data.
|
||||
|
||||
## Testing
|
||||
|
||||
- Add or update tests for behavior you change.
|
||||
- Prefer unit tests for pure logic and integration tests for persistence or external boundaries.
|
||||
- Use parametrization to cover multiple cases succinctly.
|
||||
- Mock at external boundaries, not inside the core logic under test, unless the repo uses a different testing style.
|
||||
|
||||
## Tooling
|
||||
|
||||
- Use `uv` for Python dependency and tool execution when the project uses it.
|
||||
- Run formatters, linters, and type checks after Python changes.
|
||||
- Default verification flow: format, lint, type-check, then run relevant tests.
|
||||
- If the repo uses different commands, follow the repo.
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
Before finishing Python work, make sure:
|
||||
|
||||
- the change matches existing project patterns
|
||||
- code is fully typed to the repo's standard
|
||||
- untrusted input is validated at the boundary
|
||||
- logic is kept in the right layer
|
||||
- `ruff` is clean
|
||||
- `mypy` is clean
|
||||
- relevant tests pass
|
||||
- no secrets or risky calls were introduced without justification
|
||||
|
||||
## Reference
|
||||
|
||||
See `REFERENCE.md` for optional deeper guidance on typing, architecture, async patterns, testing, security, and review heuristics.
|
||||
Reference in New Issue
Block a user