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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@

# Python bytecode cache (anu relink engine).
__pycache__/

# macOS
.DS_Store
108 changes: 108 additions & 0 deletions config/claude/skills/add-molab-badge/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
name: add-molab-badge
description: Add "Open in molab" badge(s) linking to marimo notebooks. Works with READMEs, docs, websites, or any markdown/HTML target.
---

# Add molab badge

Add "Open in molab" badge(s) linking to marimo notebooks. The badge can be added to any target: a GitHub README, documentation site, blog post, webpage, or any other markdown/HTML file.

## Instructions

### 0. Session export for molab

molab previews render much nicer if the github repository has session information around. This can be added via:

```bash
uvx marimo export session notebook.py
uvx marimo export session folder/
```

This executes notebooks and exports their session snapshots, which molab uses to serve pre-rendered notebooks.

Key flags:

- `--sandbox` — run each notebook in an isolated environment using PEP 723 dependencies
- `--continue-on-error` — keep processing other notebooks if one fails
- `--force-overwrite` — overwrite all existing snapshots, even if up-to-date

### 1. Determine the notebook links

The user may provide notebook links in one of two ways:

- **User provides links directly.** The user pastes URLs to notebooks. Use these as-is — no discovery needed.
- **Notebook discovery (README target only).** If the user asks you to add badges to a repository's README and doesn't specify which notebooks, discover them:
1. Find all marimo notebook files (`.py` files) in the repository. Use `Glob` with patterns like `**/*.py` and then check for the marimo header (`import marimo` or `app = marimo.App`) to confirm they are marimo notebooks.
2. If the README already has links to notebooks (e.g., via `marimo.app` links or existing badges), replace those.
3. Otherwise, ask the user which notebooks should be linked.

### 2. Construct the molab URL

For each notebook, construct the molab URL using this format:

```
https://molab.marimo.io/github/{owner}/{repo}/blob/{branch}/{path_to_notebook}
```

- `{owner}/{repo}`: the GitHub owner and repository name. Determine from the git remote (`git remote get-url origin`), the user-provided URL, or by asking the user.
- `{branch}`: typically `main`. Confirm from the repository's default branch.
- `{path_to_notebook}`: the path to the `.py` notebook file relative to the repository root.

### 3. Apply the `/wasm` suffix rules

- If **replacing** an existing `marimo.app` link, append `/wasm` to the molab URL. This is because `marimo.app` runs notebooks client-side (WASM), so the molab equivalent needs the `/wasm` suffix to preserve that behavior.
- If adding a **new** badge (not replacing a `marimo.app` link), do **not** append `/wasm` unless the user explicitly requests it.

### 4. Format the badge

Use the following markdown badge format:

```markdown
[![Open in molab](https://marimo.io/molab-shield.svg)](URL)
```

Where `URL` is the constructed molab URL (with or without `/wasm` per the rules above).

For HTML targets, use:

```html
<a href="URL"><img src="https://marimo.io/molab-shield.svg" alt="Open in molab" /></a>
```

### 5. Insert or replace badges in the target

- When replacing existing badges or links:
- Replace `marimo.app` URLs with the equivalent `molab.marimo.io` URLs.
- Replace old shield image URLs (e.g., `https://marimo.io/shield.svg` or camo-proxied versions) with `https://marimo.io/molab-shield.svg`.
- Set the alt text to `Open in molab`.
- Preserve surrounding text and structure.
- Edit the target file in place. Do not rewrite unrelated sections.
- If the user just wants the badge markdown/HTML (not editing a file), output it directly.

## Examples

**Replacing a marimo.app badge in a README:**

Before:
```markdown
[![](https://marimo.io/shield.svg)](https://marimo.app/github.com/owner/repo/blob/main/notebook.py)
```

After:
```markdown
[![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/owner/repo/blob/main/notebook.py/wasm)
```

Note: `/wasm` is appended because this replaces a `marimo.app` link.

**Adding a new badge from user-provided links:**

User says: "Add molab badges for these notebooks: `https://github.com/owner/repo/blob/main/demo.py`, `https://github.com/owner/repo/blob/main/tutorial.py`"

Output:
```markdown
[![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/owner/repo/blob/main/demo.py)
[![Open in molab](https://marimo.io/molab-shield.svg)](https://molab.marimo.io/github/owner/repo/blob/main/tutorial.py)
```

Note: no `/wasm` suffix by default for new badges.
81 changes: 81 additions & 0 deletions config/claude/skills/anywidget-generator/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
name: anywidget-generator
description: Generate anywidget components for marimo notebooks.
---

When writing an anywidget use vanilla javascript in `_esm` and do not forget about `_css`. The css should look bespoke in light mode and dark mode. Keep the css small unless explicitly asked to go the extra mile. When you display the widget it must be wrapped via `widget = mo.ui.anywidget(OriginalAnywidget())`. You can also point `_esm` and `_css` to external files if needed using pathlib. This makes sense if the widget does a lot of elaborate JavaScript or CSS.

<example title="Example of simple anywidget implementation">
import anywidget
import traitlets


class CounterWidget(anywidget.AnyWidget):
_esm = """
// Define the main render function
function render({ model, el }) {
let count = () => model.get("number");
let btn = document.createElement("b8utton");
btn.innerHTML = `count is ${count()}`;
btn.addEventListener("click", () => {
model.set("number", count() + 1);
model.save_changes();
});
model.on("change:number", () => {
btn.innerHTML = `count is ${count()}`;
});
el.appendChild(btn);
}
// Important! We must export at the bottom here!
export default { render };
"""
_css = """button{
font-size: 14px;
}"""
number = traitlets.Int(0).tag(sync=True)

widget = mo.ui.anywidget(CounterWidget())
widget

# Grabbing the widget from another cell, `.value` is a dictionary.
print(widget.value["number"])
</example>

The above is a minimal example that could work for a simple counter widget. In general the widget can become much larger because of all the JavaScript and CSS required. Unless the widget is dead simple, you should consider using external files for `_esm` and `_css` using pathlib.

When sharing the anywidget, keep the example minimal. No need to combine it with marimo ui elements unless explicitly stated to do so.

## Best Practices

Unless specifically told otherwise, assume the following:

1. **Use vanilla JavaScript in `_esm`**:
- Define a `render` function that takes `{ model, el }` as parameters
- Use `model.get()` to read trait values
- Use `model.set()` and `model.save_changes()` to update traits
- Listen to changes with `model.on("change:traitname", callback)`
- Export default with `export default { render };` at the bottom
- All widgets inherit from `anywidget.AnyWidget`, so `widget.observe(handler)`
remains the standard way to react to state changes.
- Python constructors tend to validate bounds, lengths, or choice counts; let the
raised `ValueError/TraitError` guide you instead of duplicating the logic.

2. **Include `_css` styling**:
- Keep CSS minimal unless explicitly asked for more
- Make it look bespoke in both light and dark mode
- Use CSS media query for dark mode: `@media (prefers-color-scheme: dark) { ... }`

3. **Wrap the widget for display**:
- Always wrap with marimo: `widget = mo.ui.anywidget(OriginalAnywidget())`
- Access values via `widget.value` which returns a dictionary

4. **Keep examples minimal**:
- Add a marimo notebook that highlights the core utility
- Show basic usage only
- Don't combine with other marimo UI elements unless explicitly requested

5. **External file paths**: When using pathlib for external `_esm`/`_css` files, keep paths relative to the project directory, consider using `Path(__file__)` for this. Do not read files outside the project (e.g., `~/.ssh`, `~/.env`, `/etc/`) or embed their contents in widget output.

Dumber is better. Prefer obvious, direct code over clever abstractions—someone
new to the project should be able to read the code top-to-bottom and grok it
without needing to look up framework magic or trace through indirection.
73 changes: 73 additions & 0 deletions config/claude/skills/auto-paper-demo/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: auto-paper-demo
description: Make a demo of a research paper in a marimo notebook fully automatically without extra user input.
---

You need to come up with a compelling story to tell from a paper. Do not ask the user for feedback/input. You need to apply thinking and come up with the best story yourself.

# Fetching Papers via AlphaXiv

Use alphaxiv.org to get structured, LLM-friendly paper content. This is faster and more reliable than trying to read a raw PDF.

## Extract the paper ID

Parse the paper ID from whatever the user provides:

| Input | Paper ID |
|-------|----------|
| `https://arxiv.org/abs/2401.12345` | `2401.12345` |
| `https://arxiv.org/pdf/2401.12345` | `2401.12345` |
| `https://alphaxiv.org/overview/2401.12345` | `2401.12345` |
| `2401.12345v2` | `2401.12345v2` |
| `2401.12345` | `2401.12345` |

## Fetch the AI-generated overview (try this first)

```bash
curl -s "https://alphaxiv.org/overview/{PAPER_ID}.md"
```

Returns a structured, detailed analysis of the paper as plain markdown. One call, no JSON parsing.

## Fetch the full paper text (fallback)

If the overview doesn't contain the specific detail you need (e.g., a particular equation, table, or proof):

```bash
curl -s "https://alphaxiv.org/abs/{PAPER_ID}.md"
```

Returns the full extracted text of the paper as markdown.

## Error handling

- **404 on the overview**: Report hasn't been generated for this paper yet. Try the full text instead.
- **404 on the full text**: Text hasn't been processed yet. As a last resort, direct the user to the PDF at `https://arxiv.org/pdf/{PAPER_ID}`.
- No authentication is required — these are public endpoints.

## What is a good implementation?

A good implementation tells a story, that's the most important thing. The story should be simple, but it should not be missing.

Papers typically have more than one concept in them. So that means you need to pick a story! It isn't the goal to fully implement the paper or to rerun a giant benchmark. The goal is to take a lesson/idea and to explain that very clearly in a notebook that can simply run on a CPU. That way, a user can easily run learn something from it. When you look at the notebook, what is the main concept or idea that you think is worth exploring? What is the concept that tells a story?

Pick the idea that is easiest to explain with a minimum code example. For a minimum code example to really work, it tends to help to have one, maybe two charts to look at. Maybe there's a dropdown that lets you try out different settings. Possibly even a slider. But the one thing we would want to do is prevent that the user needs to do a lot of scrolling.

It will be typical that you'll want to compare two approaches. But take a moment to think about the example, because that matters most to the story. Most of the time you don't want to use a toy example. They're not informative and they are overdone. It may be better to generate a creative example that shows where one approach can really shine. We don't want to cherry pick, but we also don't want to do examples that have been overdone either.

I cannot stress enough how important it is to actually think about the story and the example before you write any code whatsoever. You should really ultra think this. Give the user some interaction but really try to prevent scrolling. A good example tells a story, it doesn't just state some facts.

Feel free to think about this decision, but once you've got it clear what idea is best to showcase, immediately proceed to build the marimo notebook.

Use the marimo-notebook skill for this, and possibly the anywidget skill, but only if a custom widget makes for a better story. If you strongly feel that it makes sense to use a custom anywidget, refer to [references/ANYWIDGET.md](references/ANYWIDGET.md).

When you are ready, make sure that you hide all the code and that you move the cells with inputs/outputs to the top of the file.

Example:

```
@app.cell
def _(hide_code=True):
import marimo as mo
return mo
```
81 changes: 81 additions & 0 deletions config/claude/skills/auto-paper-demo/references/ANYWIDGET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
name: anywidget-generator
description: Generate anywidget components for marimo notebooks.
---

When writing an anywidget use vanilla javascript in `_esm` and do not forget about `_css`. The css should look bespoke in light mode and dark mode. Keep the css small unless explicitly asked to go the extra mile. When you display the widget it must be wrapped via `widget = mo.ui.anywidget(OriginalAnywidget())`. You can also point `_esm` and `_css` to external files if needed using pathlib. This makes sense if the widget does a lot of elaborate JavaScript or CSS.

<example title="Example of simple anywidget implementation">
import anywidget
import traitlets


class CounterWidget(anywidget.AnyWidget):
_esm = """
// Define the main render function
function render({ model, el }) {
let count = () => model.get("number");
let btn = document.createElement("b8utton");
btn.innerHTML = `count is ${count()}`;
btn.addEventListener("click", () => {
model.set("number", count() + 1);
model.save_changes();
});
model.on("change:number", () => {
btn.innerHTML = `count is ${count()}`;
});
el.appendChild(btn);
}
// Important! We must export at the bottom here!
export default { render };
"""
_css = """button{
font-size: 14px;
}"""
number = traitlets.Int(0).tag(sync=True)

widget = mo.ui.anywidget(CounterWidget())
widget

# Grabbing the widget from another cell, `.value` is a dictionary.
print(widget.value["number"])
</example>

The above is a minimal example that could work for a simple counter widget. In general the widget can become much larger because of all the JavaScript and CSS required. Unless the widget is dead simple, you should consider using external files for `_esm` and `_css` using pathlib.

When sharing the anywidget, keep the example minimal. No need to combine it with marimo ui elements unless explicitly stated to do so.

## Best Practices

Unless specifically told otherwise, assume the following:

1. **Use vanilla JavaScript in `_esm`**:
- Define a `render` function that takes `{ model, el }` as parameters
- Use `model.get()` to read trait values
- Use `model.set()` and `model.save_changes()` to update traits
- Listen to changes with `model.on("change:traitname", callback)`
- Export default with `export default { render };` at the bottom
- All widgets inherit from `anywidget.AnyWidget`, so `widget.observe(handler)`
remains the standard way to react to state changes.
- Python constructors tend to validate bounds, lengths, or choice counts; let the
raised `ValueError/TraitError` guide you instead of duplicating the logic.

2. **Include `_css` styling**:
- Keep CSS minimal unless explicitly asked for more
- Make it look bespoke in both light and dark mode
- Use CSS media query for dark mode: `@media (prefers-color-scheme: dark) { ... }`

3. **Wrap the widget for display**:
- Always wrap with marimo: `widget = mo.ui.anywidget(OriginalAnywidget())`
- Access values via `widget.value` which returns a dictionary

4. **Keep examples minimal**:
- Add a marimo notebook that highlights the core utility
- Show basic usage only
- Don't combine with other marimo UI elements unless explicitly requested

5. **External file paths**: When using pathlib for external `_esm`/`_css` files, keep paths relative to the project directory, consider using `Path(__file__)` for this. Do not read files outside the project (e.g., `~/.ssh`, `~/.env`, `/etc/`) or embed their contents in widget output.

Dumber is better. Prefer obvious, direct code over clever abstractions—someone
new to the project should be able to read the code top-to-bottom and grok it
without needing to look up framework magic or trace through indirection.
Loading